mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-04-03 22:17:24 +02:00
fix: update relief icon hover transition and click selection logic
This commit is contained in:
parent
bf22c5eaf6
commit
4515232e93
3 changed files with 96 additions and 61 deletions
|
|
@ -2089,13 +2089,12 @@ svg.button {
|
||||||
background-color: #e7e6e4;
|
background-color: #e7e6e4;
|
||||||
border: 1px solid #a9a9a9;
|
border: 1px solid #a9a9a9;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
#reliefIconsDiv svg:hover {
|
#reliefIconsDiv svg:hover {
|
||||||
border-color: #5c5c5c;
|
border-color: #5c5c5c;
|
||||||
background-color: #eef6fb;
|
background-color: #eef6fb;
|
||||||
transition: all 0.3s ease-out 3s;
|
|
||||||
transform: scale(2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#reliefIconsDiv svg.pressed {
|
#reliefIconsDiv svg.pressed {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,15 @@ function editReliefIcon() {
|
||||||
if (!layerIsOn("toggleRelief")) toggleRelief();
|
if (!layerIsOn("toggleRelief")) toggleRelief();
|
||||||
terrain.selectAll("use").call(d3.drag().on("drag", dragReliefIcon)).classed("draggable", true);
|
terrain.selectAll("use").call(d3.drag().on("drag", dragReliefIcon)).classed("draggable", true);
|
||||||
|
|
||||||
|
// Click-to-select: delegation on the terrain group covers existing and newly added <use> elements.
|
||||||
|
terrain.on("click.reliefSelect", function () {
|
||||||
|
if (d3.event.target.tagName !== "use") return;
|
||||||
|
if (!reliefIndividual.classList.contains("pressed")) return;
|
||||||
|
elSelected = d3.select(d3.event.target);
|
||||||
|
updateReliefIconSelected();
|
||||||
|
updateReliefSizeInput();
|
||||||
|
});
|
||||||
|
|
||||||
// When called from the Tools button there is no d3 click event; fall back to the first <use>.
|
// When called from the Tools button there is no d3 click event; fall back to the first <use>.
|
||||||
// When called from a map click, prefer the actual clicked element if it is a <use>.
|
// When called from a map click, prefer the actual clicked element if it is a <use>.
|
||||||
const clickTarget = d3.event && d3.event.target;
|
const clickTarget = d3.event && d3.event.target;
|
||||||
|
|
@ -32,27 +41,28 @@ function editReliefIcon() {
|
||||||
modules.editReliefIcon = true;
|
modules.editReliefIcon = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
byId("reliefIndividual").on("click", enterIndividualMode);
|
byId("reliefIndividual").addEventListener("click", enterIndividualMode);
|
||||||
byId("reliefBulkAdd").on("click", enterBulkAddMode);
|
byId("reliefBulkAdd").addEventListener("click", enterBulkAddMode);
|
||||||
byId("reliefBulkRemove").on("click", enterBulkRemoveMode);
|
byId("reliefBulkRemove").addEventListener("click", enterBulkRemoveMode);
|
||||||
|
|
||||||
byId("reliefSize").on("input", changeIconSize);
|
byId("reliefSize").addEventListener("input", changeIconSize);
|
||||||
byId("reliefSizeNumber").on("input", changeIconSize);
|
byId("reliefSizeNumber").addEventListener("input", changeIconSize);
|
||||||
byId("reliefEditorSet").on("change", changeIconsSet);
|
byId("reliefEditorSet").addEventListener("change", changeIconsSet);
|
||||||
reliefIconsDiv.querySelectorAll("svg").forEach(el => el.on("click", changeIcon));
|
reliefIconsDiv.querySelectorAll("svg").forEach(el => el.addEventListener("click", changeIcon));
|
||||||
|
|
||||||
byId("reliefEditStyle").on("click", () => editStyle("terrain"));
|
byId("reliefEditStyle").addEventListener("click", () => editStyle("terrain"));
|
||||||
byId("reliefCopy").on("click", copyIcon);
|
byId("reliefCopy").addEventListener("click", copyIcon);
|
||||||
byId("reliefMoveFront").on("click", () => elSelected.raise());
|
byId("reliefMoveFront").addEventListener("click", () => elSelected.raise());
|
||||||
byId("reliefMoveBack").on("click", () => elSelected.lower());
|
byId("reliefMoveBack").addEventListener("click", () => elSelected.lower());
|
||||||
byId("reliefRemove").on("click", removeIcon);
|
byId("reliefRemove").addEventListener("click", removeIcon);
|
||||||
|
|
||||||
function dragReliefIcon() {
|
function dragReliefIcon() {
|
||||||
const dx = +this.getAttribute("x") - d3.event.x;
|
const dx = +this.getAttribute("x") - d3.event.x;
|
||||||
const dy = +this.getAttribute("y") - d3.event.y;
|
const dy = +this.getAttribute("y") - d3.event.y;
|
||||||
|
|
||||||
let newX;
|
// initialise from current attrs so "end" has valid values even if drag never fires
|
||||||
let newY;
|
let newX = +this.getAttribute("x");
|
||||||
|
let newY = +this.getAttribute("y");
|
||||||
|
|
||||||
d3.event.on("drag", function () {
|
d3.event.on("drag", function () {
|
||||||
newX = dx + d3.event.x;
|
newX = dx + d3.event.x;
|
||||||
|
|
@ -78,7 +88,9 @@ function editReliefIcon() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateReliefIconSelected() {
|
function updateReliefIconSelected() {
|
||||||
const type = elSelected.attr("href") || elSelected.attr("data-type");
|
if (!elSelected.node()) return;
|
||||||
|
const type = elSelected.attr("href");
|
||||||
|
if (!type) return;
|
||||||
const button = reliefIconsDiv.querySelector("svg[data-type='" + type + "']");
|
const button = reliefIconsDiv.querySelector("svg[data-type='" + type + "']");
|
||||||
if (!button) return;
|
if (!button) return;
|
||||||
|
|
||||||
|
|
@ -90,7 +102,9 @@ function editReliefIcon() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateReliefSizeInput() {
|
function updateReliefSizeInput() {
|
||||||
|
if (!elSelected.node()) return;
|
||||||
const size = +elSelected.attr("width");
|
const size = +elSelected.attr("width");
|
||||||
|
if (!size) return;
|
||||||
reliefSize.value = reliefSizeNumber.value = rn(size);
|
reliefSize.value = reliefSizeNumber.value = rn(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,10 +133,10 @@ function editReliefIcon() {
|
||||||
reliefIconsSeletionAny.style.display = "none";
|
reliefIconsSeletionAny.style.display = "none";
|
||||||
|
|
||||||
const pressedType = reliefIconsDiv.querySelector("svg.pressed");
|
const pressedType = reliefIconsDiv.querySelector("svg.pressed");
|
||||||
if (pressedType.id === "reliefIconsSeletionAny") {
|
if (!pressedType || pressedType.id === "reliefIconsSeletionAny") {
|
||||||
// in "any" is pressed, select first type
|
// nothing or "any" pressed — select first specific type
|
||||||
reliefIconsSeletionAny.classList.remove("pressed");
|
if (pressedType) reliefIconsSeletionAny.classList.remove("pressed");
|
||||||
reliefIconsDiv.querySelector("svg").classList.add("pressed");
|
reliefIconsDiv.querySelector("svg:not(#reliefIconsSeletionAny)")?.classList.add("pressed");
|
||||||
}
|
}
|
||||||
|
|
||||||
viewbox.style("cursor", "crosshair").call(d3.drag().on("start", dragToAdd)).on("touchmove mousemove", moveBrush);
|
viewbox.style("cursor", "crosshair").call(d3.drag().on("start", dragToAdd)).on("touchmove mousemove", moveBrush);
|
||||||
|
|
@ -145,16 +159,17 @@ function editReliefIcon() {
|
||||||
const spacing = +reliefSpacingNumber.value;
|
const spacing = +reliefSpacingNumber.value;
|
||||||
const size = +reliefSizeNumber.value;
|
const size = +reliefSizeNumber.value;
|
||||||
|
|
||||||
// build a quadtree
|
// quadtree for spacing checks; positions (sorted by bottom-y) for painter's z-order
|
||||||
const tree = d3.quadtree();
|
const tree = d3.quadtree();
|
||||||
const positions = [];
|
const positions = [];
|
||||||
terrain.selectAll("use").each(function () {
|
terrain.selectAll("use").each(function () {
|
||||||
const x = +this.getAttribute("x") + this.getAttribute("width") / 2;
|
const cx = +this.getAttribute("x") + this.getAttribute("width") / 2;
|
||||||
const y = +this.getAttribute("y") + this.getAttribute("height") / 2;
|
const cy = +this.getAttribute("y") + this.getAttribute("height") / 2;
|
||||||
tree.add([x, y, x]);
|
tree.add([cx, cy]);
|
||||||
const box = this.getBBox();
|
const box = this.getBBox();
|
||||||
positions.push(box.y + box.height);
|
positions.push(box.y + box.height);
|
||||||
});
|
});
|
||||||
|
positions.sort((a, b) => a - b);
|
||||||
|
|
||||||
d3.event.on("drag", function () {
|
d3.event.on("drag", function () {
|
||||||
const p = d3.mouse(this);
|
const p = d3.mouse(this);
|
||||||
|
|
@ -175,20 +190,25 @@ function editReliefIcon() {
|
||||||
const z = y + h * 2;
|
const z = y + h * 2;
|
||||||
const s = rn(h * 2, 2);
|
const s = rn(h * 2, 2);
|
||||||
|
|
||||||
let nth = 1;
|
// binary insertion: find first sorted position whose bottom-y exceeds z
|
||||||
while (positions[nth] && z > positions[nth]) {
|
let insertIdx = 0;
|
||||||
nth++;
|
while (insertIdx < positions.length && positions[insertIdx] <= z) insertIdx++;
|
||||||
}
|
positions.splice(insertIdx, 0, z);
|
||||||
|
|
||||||
|
const newIcon = {i: pack.relief.length, href: type, x, y, s};
|
||||||
|
pack.relief.push(newIcon);
|
||||||
tree.add([cx, cy]);
|
tree.add([cx, cy]);
|
||||||
positions.push(z);
|
|
||||||
terrain
|
terrain
|
||||||
.insert("use", ":nth-child(" + nth + ")")
|
.insert("use", ":nth-child(" + (insertIdx + 1) + ")")
|
||||||
|
.attr("data-id", newIcon.i)
|
||||||
.attr("href", type)
|
.attr("href", type)
|
||||||
.attr("x", x)
|
.attr("x", x)
|
||||||
.attr("y", y)
|
.attr("y", y)
|
||||||
.attr("width", s)
|
.attr("width", s)
|
||||||
.attr("height", s);
|
.attr("height", s)
|
||||||
|
.call(d3.drag().on("drag", dragReliefIcon))
|
||||||
|
.classed("draggable", true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -223,19 +243,32 @@ function editReliefIcon() {
|
||||||
d3.event.on("drag", function () {
|
d3.event.on("drag", function () {
|
||||||
const p = d3.mouse(this);
|
const p = d3.mouse(this);
|
||||||
moveCircle(p[0], p[1], r);
|
moveCircle(p[0], p[1], r);
|
||||||
findAllInQuadtree(p[0], p[1], r, tree).forEach(f => f[2].remove());
|
const found = findAllInQuadtree(p[0], p[1], r, tree);
|
||||||
|
if (!found.length) return;
|
||||||
|
const removedIds = new Set(found.map(f => +f[2].dataset.id));
|
||||||
|
found.forEach(f => f[2].remove());
|
||||||
|
pack.relief = pack.relief.filter(ic => !removedIds.has(ic.i));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeIconSize() {
|
function changeIconSize(event) {
|
||||||
const size = +reliefSizeNumber.value;
|
if (!reliefIndividual.classList.contains("pressed") || !elSelected.node()) return;
|
||||||
if (!reliefIndividual.classList.contains("pressed")) return;
|
|
||||||
|
const size = +event.target.value;
|
||||||
|
reliefSize.value = reliefSizeNumber.value = rn(size);
|
||||||
|
|
||||||
const shift = (size - +elSelected.attr("width")) / 2;
|
const shift = (size - +elSelected.attr("width")) / 2;
|
||||||
elSelected.attr("width", size).attr("height", size);
|
const x = rn(+elSelected.attr("x") - shift, 2);
|
||||||
const x = +elSelected.attr("x"),
|
const y = rn(+elSelected.attr("y") - shift, 2);
|
||||||
y = +elSelected.attr("y");
|
elSelected.attr("width", size).attr("height", size).attr("x", x).attr("y", y);
|
||||||
elSelected.attr("x", x - shift).attr("y", y - shift);
|
|
||||||
|
const id = +elSelected.node().dataset.id;
|
||||||
|
const icon = pack.relief.find(ic => ic.i === id);
|
||||||
|
if (icon) {
|
||||||
|
icon.s = size;
|
||||||
|
icon.x = x;
|
||||||
|
icon.y = y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeIconsSet() {
|
function changeIconsSet() {
|
||||||
|
|
@ -250,32 +283,43 @@ function editReliefIcon() {
|
||||||
reliefIconsDiv.querySelectorAll("svg.pressed").forEach(b => b.classList.remove("pressed"));
|
reliefIconsDiv.querySelectorAll("svg.pressed").forEach(b => b.classList.remove("pressed"));
|
||||||
this.classList.add("pressed");
|
this.classList.add("pressed");
|
||||||
|
|
||||||
if (reliefIndividual.classList.contains("pressed")) {
|
if (reliefIndividual.classList.contains("pressed") && elSelected.node()) {
|
||||||
const type = this.dataset.type;
|
const type = this.dataset.type;
|
||||||
elSelected.attr("href", type);
|
elSelected.attr("href", type);
|
||||||
|
const id = +elSelected.node().dataset.id;
|
||||||
|
const icon = pack.relief.find(ic => ic.i === id);
|
||||||
|
if (icon) icon.href = type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyIcon() {
|
function copyIcon() {
|
||||||
|
if (!elSelected.node()) return;
|
||||||
const parent = elSelected.node().parentNode;
|
const parent = elSelected.node().parentNode;
|
||||||
const copy = elSelected.node().cloneNode(true);
|
const copy = elSelected.node().cloneNode(true);
|
||||||
|
|
||||||
let x = +elSelected.attr("x") - 3,
|
let x = +elSelected.attr("x") - 3,
|
||||||
y = +elSelected.attr("y") - 3;
|
y = +elSelected.attr("y") - 3;
|
||||||
while (parent.querySelector("[x='" + x + "']", "[x='" + y + "']")) {
|
while (parent.querySelector("[x='" + x + "'][y='" + y + "']")) {
|
||||||
x -= 3;
|
x -= 3;
|
||||||
y -= 3;
|
y -= 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newId = pack.relief.length;
|
||||||
|
const href = elSelected.attr("href");
|
||||||
|
const s = +elSelected.attr("width");
|
||||||
copy.setAttribute("x", x);
|
copy.setAttribute("x", x);
|
||||||
copy.setAttribute("y", y);
|
copy.setAttribute("y", y);
|
||||||
|
copy.dataset.id = String(newId);
|
||||||
|
pack.relief.push({i: newId, href, x, y, s});
|
||||||
parent.insertBefore(copy, null);
|
parent.insertBefore(copy, null);
|
||||||
|
d3.select(copy).call(d3.drag().on("drag", dragReliefIcon)).classed("draggable", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeIcon() {
|
function removeIcon() {
|
||||||
|
if (!elSelected.node() && !reliefBulkRemove.classList.contains("pressed")) return;
|
||||||
let selection = null;
|
let selection = null;
|
||||||
const pressed = reliefTools.querySelector("button.pressed");
|
const pressed = reliefTools.querySelector("button.pressed");
|
||||||
if (pressed.id === "reliefIndividual") {
|
if (!pressed || pressed.id === "reliefIndividual") {
|
||||||
alertMessage.innerHTML = "Are you sure you want to remove the icon?";
|
alertMessage.innerHTML = "Are you sure you want to remove the icon?";
|
||||||
selection = elSelected;
|
selection = elSelected;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -292,7 +336,14 @@ function editReliefIcon() {
|
||||||
title: "Remove relief icons",
|
title: "Remove relief icons",
|
||||||
buttons: {
|
buttons: {
|
||||||
Remove: function () {
|
Remove: function () {
|
||||||
if (selection) selection.remove();
|
if (selection) {
|
||||||
|
const idsToRemove = new Set();
|
||||||
|
selection.each(function () {
|
||||||
|
idsToRemove.add(+this.dataset.id);
|
||||||
|
});
|
||||||
|
pack.relief = pack.relief.filter(ic => !idsToRemove.has(ic.i));
|
||||||
|
selection.remove();
|
||||||
|
}
|
||||||
$(this).dialog("close");
|
$(this).dialog("close");
|
||||||
$("#reliefEditor").dialog("close");
|
$("#reliefEditor").dialog("close");
|
||||||
},
|
},
|
||||||
|
|
@ -304,26 +355,11 @@ function editReliefIcon() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeReliefEditor() {
|
function closeReliefEditor() {
|
||||||
|
terrain.on("click.reliefSelect", null);
|
||||||
terrain.selectAll("use").call(d3.drag().on("drag", null)).classed("draggable", false);
|
terrain.selectAll("use").call(d3.drag().on("drag", null)).classed("draggable", false);
|
||||||
removeCircle();
|
removeCircle();
|
||||||
unselect();
|
unselect();
|
||||||
clearMainTip();
|
clearMainTip();
|
||||||
|
|
||||||
// Sync pack.relief from the current SVG DOM (captures all edits)
|
|
||||||
pack.relief = [];
|
|
||||||
terrain.selectAll("use").each(function () {
|
|
||||||
const href = this.getAttribute("href") || this.getAttribute("xlink:href") || "";
|
|
||||||
if (!href) return;
|
|
||||||
pack.relief.push({
|
|
||||||
i: pack.relief.length,
|
|
||||||
href,
|
|
||||||
x: +this.getAttribute("x"),
|
|
||||||
y: +this.getAttribute("y"),
|
|
||||||
s: +this.getAttribute("width")
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Switch from SVG edit mode back to WebGL rendering
|
|
||||||
undrawRelief();
|
undrawRelief();
|
||||||
drawRelief();
|
drawRelief();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@
|
||||||
},
|
},
|
||||||
"#terrain": {
|
"#terrain": {
|
||||||
"opacity": null,
|
"opacity": null,
|
||||||
"set": "colored",
|
"set": "simple",
|
||||||
"size": 1,
|
"size": 1,
|
||||||
"density": 0.4,
|
"density": 0.4,
|
||||||
"filter": null,
|
"filter": null,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue