438 lines
20 KiB
JavaScript
438 lines
20 KiB
JavaScript
"use strict";
|
|
|
|
const SCALABLE_TOOLS = new Set(scalableToolIds());
|
|
const PHYSICS_ICON_TOOL_IDS = new Set(window.TarinaiItemToolMetadata?.PHYSICS_TOOL_IDS || []);
|
|
const TEXT_ICON_TOOL_IDS = new Set(["poke", "pinch", "water_hose", "copy", "undo", "redo", "area_delete", "clear_tool"]);
|
|
|
|
|
|
function drawPhysicsToolIcon(canvas, toolId) {
|
|
const ctx = canvas?.getContext?.("2d");
|
|
if (!ctx || !PHYSICS_ICON_TOOL_IDS.has(toolId)) return false;
|
|
const w = canvas.width || 160;
|
|
const h = canvas.height || 160;
|
|
const sx = w / 160;
|
|
const sy = h / 160;
|
|
ctx.clearRect(0, 0, w, h);
|
|
ctx.save();
|
|
ctx.scale(sx, sy);
|
|
ctx.lineCap = "round";
|
|
ctx.lineJoin = "round";
|
|
const line = (x1, y1, x2, y2, width = 10, stroke = "#4f4a42") => {
|
|
ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.lineWidth = width; ctx.strokeStyle = stroke; ctx.stroke();
|
|
};
|
|
const circle = (x, y, r, fill, stroke = "#4f4a42", width = 6) => {
|
|
ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI * 2); ctx.fillStyle = fill; ctx.fill(); ctx.lineWidth = width; ctx.strokeStyle = stroke; ctx.stroke();
|
|
};
|
|
const rounded = (x, y, width, height, radius, fill, stroke = "#4f4a42", lineWidth = 6) => {
|
|
const r = Math.max(0, Math.min(radius, width / 2, height / 2));
|
|
ctx.beginPath();
|
|
ctx.moveTo(x + r, y); ctx.lineTo(x + width - r, y); ctx.quadraticCurveTo(x + width, y, x + width, y + r);
|
|
ctx.lineTo(x + width, y + height - r); ctx.quadraticCurveTo(x + width, y + height, x + width - r, y + height);
|
|
ctx.lineTo(x + r, y + height); ctx.quadraticCurveTo(x, y + height, x, y + height - r);
|
|
ctx.lineTo(x, y + r); ctx.quadraticCurveTo(x, y, x + r, y); ctx.closePath();
|
|
ctx.fillStyle = fill; ctx.fill(); ctx.lineWidth = lineWidth; ctx.strokeStyle = stroke; ctx.stroke();
|
|
};
|
|
if (toolId === "rope") {
|
|
ctx.beginPath(); ctx.moveTo(22, 112); ctx.bezierCurveTo(48, 22, 92, 144, 138, 48); ctx.lineWidth = 13; ctx.strokeStyle = "#9a6b3f"; ctx.stroke();
|
|
circle(22, 112, 10, "#f3d3a6", "#6c4b2e", 6); circle(138, 48, 10, "#f3d3a6", "#6c4b2e", 6);
|
|
} else if (toolId === "rod") {
|
|
line(28, 126, 132, 34, 18, "#8a633d"); circle(28, 126, 12, "#d7e0e8"); circle(132, 34, 12, "#d7e0e8");
|
|
} else if (toolId === "spring") {
|
|
const pts = [[18,80],[36,80],[48,48],[64,112],[80,48],[96,112],[112,48],[124,80],[142,80]];
|
|
ctx.beginPath(); ctx.moveTo(...pts[0]); for (const p of pts.slice(1)) ctx.lineTo(...p); ctx.lineWidth = 11; ctx.strokeStyle = "#647487"; ctx.stroke();
|
|
circle(18,80,8,"#e8eef4","#465564",5); circle(142,80,8,"#e8eef4","#465564",5);
|
|
} else if (toolId === "wire" || toolId === "insulated_wire") {
|
|
ctx.beginPath(); ctx.moveTo(18,112); ctx.bezierCurveTo(52,22,104,138,142,52); ctx.lineWidth = toolId === "insulated_wire" ? 16 : 10; ctx.strokeStyle = toolId === "insulated_wire" ? "#3474b8" : "#3e3b39"; ctx.stroke();
|
|
if (toolId === "insulated_wire") { ctx.lineWidth = 5; ctx.strokeStyle = "#b9ddff"; ctx.stroke(); }
|
|
else { ctx.beginPath(); ctx.moveTo(83,47); ctx.lineTo(70,78); ctx.lineTo(87,76); ctx.lineTo(75,108); ctx.lineWidth = 8; ctx.strokeStyle = "#f0b62b"; ctx.stroke(); }
|
|
circle(18,112,8,"#e9eef1"); circle(142,52,8,"#e9eef1");
|
|
} else if (toolId === "pressure_switch") {
|
|
rounded(27,91,106,36,12,"#6f7881"); rounded(46,50,68,48,15,"#e7b842"); line(58,112,102,112,5,"#c9d2d8");
|
|
} else if (toolId === "glass_wall") {
|
|
rounded(28,35,104,90,8,"rgba(111,205,225,0.34)","#4d8795",8); line(42,105,78,50,7,"#d9f7ff"); line(83,105,116,55,5,"#d9f7ff");
|
|
} else if (toolId === "bounce_fence") {
|
|
line(28,42,28,124,13,"#5b5260");
|
|
line(132,42,132,124,13,"#5b5260");
|
|
const pts = [[36,82],[52,62],[68,102],[84,62],[100,102],[124,82]];
|
|
ctx.beginPath(); ctx.moveTo(...pts[0]); for (const p of pts.slice(1)) ctx.lineTo(...p);
|
|
ctx.lineWidth = 13; ctx.strokeStyle = "#e05286"; ctx.stroke();
|
|
} else if (toolId === "gate_fence") {
|
|
line(28,32,28,132,13,"#665c50"); line(132,32,132,132,13,"#665c50"); line(34,92,74,72,12,"#c18c55"); line(126,92,86,72,12,"#c18c55"); circle(80,75,8,"#f2d197","#6d5842",5);
|
|
} else if (toolId === "rotator") {
|
|
circle(80,80,20,"#e4aa48","#5f574d",8);
|
|
for (const a of [0, Math.PI*2/3, Math.PI*4/3]) { const x=80+Math.cos(a)*52, y=80+Math.sin(a)*52; line(80,80,x,y,15,"#6d7c8c"); circle(x,y,9,"#cfd8df","#53606b",5); }
|
|
} else if (toolId === "poison_block") {
|
|
rounded(31,31,98,98,18,"#65b94f","#285f35",8);
|
|
circle(62,68,10,"#eaffdf","#285f35",4); circle(98,68,10,"#eaffdf","#285f35",4);
|
|
line(60,105,100,105,8,"#eaffdf"); line(56,118,104,90,6,"#d5f6ca"); line(56,90,104,118,6,"#d5f6ca");
|
|
} else if (toolId === "reciprocator") {
|
|
line(24,98,136,98,13,"#536474");
|
|
rounded(56,55,48,58,10,"#e59a52","#536474",8);
|
|
line(34,64,50,64,8,"#536474");
|
|
line(110,64,126,64,8,"#536474");
|
|
} else if (toolId === "one_way_fence") {
|
|
// Slatted barrier with a single clear pass direction through its center.
|
|
line(24,119,136,119,13,"#6f6254");
|
|
for (const x of [32,54,106,128]) line(x,45,x,126,7,"#a28d73");
|
|
rounded(56,52,48,48,10,"rgba(67,164,111,0.18)","#3e8f62",5);
|
|
line(48,76,111,76,12,"#3e9d69");
|
|
ctx.beginPath(); ctx.moveTo(94,56); ctx.lineTo(116,76); ctx.lineTo(94,96);
|
|
ctx.lineWidth=12; ctx.strokeStyle="#3e9d69"; ctx.stroke();
|
|
} else if (toolId === "fence_h") {
|
|
line(24,55,24,130,12,"#75512f"); line(136,55,136,130,12,"#75512f"); line(24,72,136,72,14,"#b9854d"); line(24,108,136,108,14,"#b9854d");
|
|
} else { ctx.restore(); return false; }
|
|
ctx.restore();
|
|
return true;
|
|
}
|
|
|
|
function attachPhysicsToolIcon(btn, toolId) {
|
|
if (!btn || !PHYSICS_ICON_TOOL_IDS.has(toolId)) return false;
|
|
const canvas = createToolPreviewCanvas("tool-sprite-preview tool-physics-preview", 160);
|
|
if (!drawPhysicsToolIcon(canvas, toolId)) return false;
|
|
btn.classList.add("tool-has-canvas-icon", "tool-has-physics-icon");
|
|
btn.prepend(canvas);
|
|
return true;
|
|
}
|
|
|
|
function applyToolTextIcon(btn, def, toolId) {
|
|
if (!btn || !def?.iconText || !TEXT_ICON_TOOL_IDS.has(toolId)) return false;
|
|
btn.classList.add("tool-emoji-icon");
|
|
btn.style.setProperty("--tool-emoji", JSON.stringify(String(def.iconText)));
|
|
return true;
|
|
}
|
|
|
|
function cssToolIconPath(path) {
|
|
const raw = String(path || "").trim();
|
|
if (!raw) return "";
|
|
if (/^(?:[a-z]+:|data:|\/)/i.test(raw)) return raw;
|
|
if (raw.startsWith("../")) return raw;
|
|
if (raw.startsWith("./assets/")) return `../${raw.slice(2)}`;
|
|
if (raw.startsWith("assets/")) return `../${raw}`;
|
|
return raw;
|
|
}
|
|
|
|
function applyToolImageIcon(btn, def) {
|
|
if (!btn || !def?.icon) return false;
|
|
const iconPath = cssToolIconPath(def.icon);
|
|
if (!iconPath) return false;
|
|
btn.style.setProperty("--tool-icon", `url(${JSON.stringify(iconPath)})`);
|
|
return true;
|
|
}
|
|
|
|
const SIZE_ORDER = window.TarinaiItemToolMetadata?.TOOL_SIZE_ORDER || ["small", "medium", "large"];
|
|
|
|
const SIZE_LABELS = window.TarinaiItemToolMetadata?.TOOL_SIZE_LABELS || { small: "\u5c0f", medium: "\u4e2d", large: "\u5927" };
|
|
const TOOL_SHORTCUTS = window.TarinaiItemToolMetadata?.TOOL_SHORTCUTS || {};
|
|
|
|
const PICK_MODE_LABELS = Object.freeze({
|
|
free: "\u81ea\u7531",
|
|
grid20: "20px",
|
|
grid40: "40px",
|
|
grid60: "60px",
|
|
});
|
|
|
|
const PICK_MODE_NAMES = Object.freeze({
|
|
free: "\u81ea\u7531\u914d\u7f6e",
|
|
grid20: "20px\u683c\u5b50",
|
|
grid40: "40px\u683c\u5b50",
|
|
grid60: "60px\u683c\u5b50",
|
|
});
|
|
|
|
function normalizedToolPickMode() {
|
|
const value = String(world?.toolPickMode || "free").toLowerCase();
|
|
if (value === "grid") return "grid40";
|
|
return PICK_MODE_LABELS[value] ? value : "free";
|
|
}
|
|
|
|
function toolPickModeLabel() {
|
|
return PICK_MODE_LABELS[normalizedToolPickMode()] || "\u81ea\u7531";
|
|
}
|
|
|
|
function createToolPickModeControl() {
|
|
const btn = document.createElement("button");
|
|
btn.className = "tool tool-emoji-icon tool-pick-mode-control";
|
|
btn.type = "button";
|
|
btn.dataset.pickModeControl = "1";
|
|
btn.style.setProperty("--tool-emoji", JSON.stringify("\u25a6"));
|
|
const label = document.createElement("span");
|
|
label.className = "tool-label";
|
|
label.textContent = "\u5438\u7740";
|
|
const value = document.createElement("b");
|
|
value.className = "tool-pick-mode-value";
|
|
btn.append(label, value);
|
|
window.TarinaiTooltips?.bind?.(btn, () => {
|
|
const mode = normalizedToolPickMode();
|
|
const name = PICK_MODE_NAMES[mode] || PICK_MODE_NAMES.free;
|
|
return `\u5438\u7740\u30e2\u30fc\u30c9: ${name}\u3002\u62bc\u3059\u3068\u81ea\u7531\u914d\u7f6e\u300120px\u683c\u5b50\u300140px\u683c\u5b50\u300160px\u683c\u5b50\u306e\u9806\u306b\u5207\u308a\u66ff\u3048\u307e\u3059\u3002`;
|
|
});
|
|
return btn;
|
|
}
|
|
|
|
|
|
const PANEL_SEARCH_STATE = { query: "", matches: [], index: -1 };
|
|
|
|
function panelSearchControl() {
|
|
return document.getElementById("panelSearchControl") || document.querySelector(".panel-search-control");
|
|
}
|
|
|
|
function normalizePanelSearchText(value = "") {
|
|
return String(value || "").normalize("NFKC").replace(/\s+/g, " ").trim().toLocaleLowerCase("ja-JP");
|
|
}
|
|
|
|
function clearPanelSearchHighlights() {
|
|
const root = ui?.panel || document.querySelector(".panel");
|
|
for (const el of root?.querySelectorAll?.(".panel-search-match, .panel-search-current") || []) {
|
|
el.classList.remove("panel-search-match", "panel-search-current");
|
|
}
|
|
PANEL_SEARCH_STATE.matches = [];
|
|
PANEL_SEARCH_STATE.index = -1;
|
|
}
|
|
|
|
function panelSearchHighlightTarget(el) {
|
|
return el?.closest?.(".tool, .panel-card-toggle, .tool-category-toggle, .stat, .chart-series-toggle, .log-entry, .selected-category, .colony-setting-section, label, button, summary") || el;
|
|
}
|
|
|
|
function panelSearchElementText(el) {
|
|
if (!el) return "";
|
|
const parts = [el.textContent, el.getAttribute?.("aria-label"), el.dataset?.tip];
|
|
if (typeof el.__tarinaiTipProvider === "function") {
|
|
try { parts.push(el.__tarinaiTipProvider()); } catch (_) { /* Ignore a transient tooltip provider failure. */ }
|
|
}
|
|
for (const tipEl of el.querySelectorAll?.("[data-tip]") || []) parts.push(tipEl.dataset.tip);
|
|
return normalizePanelSearchText(parts.filter(Boolean).join(" "));
|
|
}
|
|
|
|
function panelSearchCandidates(root) {
|
|
const selector = ".tool, .panel-card-toggle, .tool-category-toggle, .stat, .chart-series-toggle, .log-entry, .selected-category, .colony-setting-section, summary, label, button, option, h2, h3, p, small, strong, span";
|
|
const seen = new Set();
|
|
const candidates = [];
|
|
for (const el of root?.querySelectorAll?.(selector) || []) {
|
|
if (!el || el.closest(".panel-search-control")) continue;
|
|
if (el.matches('.tool-category[data-tool-category="operate"] .tool-label')) continue;
|
|
const target = panelSearchHighlightTarget(el);
|
|
if (!target || seen.has(target)) continue;
|
|
const text = panelSearchElementText(target);
|
|
if (!text) continue;
|
|
seen.add(target);
|
|
candidates.push({ target, text });
|
|
}
|
|
return candidates;
|
|
}
|
|
|
|
function runPanelSearch(query, { preserveIndex = false } = {}) {
|
|
const root = ui?.panel || document.querySelector(".panel");
|
|
const normalized = normalizePanelSearchText(query);
|
|
const previousIndex = PANEL_SEARCH_STATE.index;
|
|
clearPanelSearchHighlights();
|
|
PANEL_SEARCH_STATE.query = normalized;
|
|
const control = panelSearchControl();
|
|
const status = control?.querySelector?.(".panel-search-status");
|
|
const prev = control?.querySelector?.(".panel-search-prev");
|
|
const next = control?.querySelector?.(".panel-search-next");
|
|
if (!root || !normalized) {
|
|
if (status) status.textContent = "0";
|
|
if (prev) prev.disabled = true;
|
|
if (next) next.disabled = true;
|
|
return [];
|
|
}
|
|
const matches = [];
|
|
for (const candidate of panelSearchCandidates(root)) {
|
|
if (!candidate.text.includes(normalized)) continue;
|
|
candidate.target.classList.add("panel-search-match");
|
|
matches.push(candidate.target);
|
|
}
|
|
PANEL_SEARCH_STATE.matches = matches;
|
|
PANEL_SEARCH_STATE.index = preserveIndex && matches.length ? Math.min(previousIndex, matches.length - 1) : -1;
|
|
if (status) status.textContent = String(matches.length);
|
|
if (prev) prev.disabled = matches.length === 0;
|
|
if (next) next.disabled = matches.length === 0;
|
|
return matches;
|
|
}
|
|
|
|
function revealPanelSearchTarget(target) {
|
|
if (!target) return;
|
|
for (const category of [target.closest?.(".tool-category")].filter(Boolean)) {
|
|
category.classList.remove("collapsed");
|
|
category.querySelector(".tool-category-toggle")?.setAttribute("aria-expanded", "true");
|
|
}
|
|
for (const card of [target.closest?.(".collapsible-card")].filter(Boolean)) {
|
|
card.classList.remove("collapsed");
|
|
card.querySelector(".panel-card-toggle")?.setAttribute("aria-expanded", "true");
|
|
const state = card.querySelector(".panel-card-state");
|
|
if (state) state.textContent = "\u6298\u308a\u7573\u307f";
|
|
}
|
|
for (const details of target.closest?.("details") ? [target.closest("details")] : []) details.open = true;
|
|
}
|
|
|
|
function movePanelSearchMatch(input, direction = 1) {
|
|
const query = normalizePanelSearchText(input?.value || PANEL_SEARCH_STATE.query);
|
|
const matches = runPanelSearch(query, { preserveIndex: true });
|
|
if (!matches.length) return;
|
|
for (const el of matches) el.classList.remove("panel-search-current");
|
|
const step = direction < 0 ? -1 : 1;
|
|
const base = PANEL_SEARCH_STATE.index < 0 ? (step > 0 ? -1 : 0) : PANEL_SEARCH_STATE.index;
|
|
PANEL_SEARCH_STATE.index = (base + step + matches.length) % matches.length;
|
|
const target = matches[PANEL_SEARCH_STATE.index];
|
|
revealPanelSearchTarget(target);
|
|
target.classList.add("panel-search-current");
|
|
const status = panelSearchControl()?.querySelector?.(".panel-search-status");
|
|
if (status) status.textContent = `${PANEL_SEARCH_STATE.index + 1}/${matches.length}`;
|
|
requestAnimationFrame(() => target.scrollIntoView({ behavior: "smooth", block: "center", inline: "nearest" }));
|
|
}
|
|
|
|
function bindPanelSearchControl() {
|
|
const control = panelSearchControl();
|
|
const input = control?.querySelector?.(".panel-search-input");
|
|
const prev = control?.querySelector?.(".panel-search-prev");
|
|
const next = control?.querySelector?.(".panel-search-next");
|
|
if (!control || !input || control.dataset.searchBound === "1") return;
|
|
control.dataset.searchBound = "1";
|
|
input.addEventListener("input", () => runPanelSearch(input.value));
|
|
input.addEventListener("keydown", (event) => {
|
|
if (event.key === "Enter") { event.preventDefault(); movePanelSearchMatch(input, event.shiftKey ? -1 : 1); }
|
|
if (event.key === "Escape") { input.value = ""; runPanelSearch(""); }
|
|
});
|
|
prev?.addEventListener("click", () => movePanelSearchMatch(input, -1));
|
|
next?.addEventListener("click", () => movePanelSearchMatch(input, 1));
|
|
}
|
|
|
|
function renderToolPalette() {
|
|
if (!ui?.toolPalette || typeof toolCategories !== "function") return;
|
|
bindPanelSearchControl();
|
|
ui.toolPalette.innerHTML = "";
|
|
const frag = document.createDocumentFragment();
|
|
for (const category of toolCategories()) {
|
|
const section = document.createElement("section");
|
|
section.className = `tool-category ${category.themeClass || ""}`.trim();
|
|
section.dataset.toolCategory = category.id;
|
|
const mobileCompact = Boolean(window.TarinaiInputMode.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui"));
|
|
const initiallyCollapsed = mobileCompact && category.id !== "operate";
|
|
if (initiallyCollapsed) section.classList.add("collapsed");
|
|
const toggle = document.createElement("button");
|
|
toggle.className = "tool-category-toggle";
|
|
toggle.type = "button";
|
|
toggle.setAttribute("aria-expanded", initiallyCollapsed ? "false" : "true");
|
|
toggle.textContent = category.label || category.id;
|
|
const body = document.createElement("div");
|
|
body.className = "tool-grid tool-category-body";
|
|
for (const toolId of category.toolIds || []) {
|
|
const def = toolDefinition(toolId);
|
|
if (!def || def.simulationOnly || toolId === "observe") continue;
|
|
const btn = document.createElement("button");
|
|
btn.className = "tool";
|
|
const hasPhysicsIcon = attachPhysicsToolIcon(btn, toolId);
|
|
const hasTextIcon = !hasPhysicsIcon && applyToolTextIcon(btn, def, toolId);
|
|
if (!hasPhysicsIcon && !hasTextIcon) applyToolImageIcon(btn, def);
|
|
btn.dataset.tool = toolId;
|
|
btn.dataset.toolCategory = category.id || "";
|
|
const label = document.createElement("span");
|
|
label.className = "tool-label";
|
|
label.textContent = def.label || toolId;
|
|
btn.setAttribute("aria-label", label.textContent);
|
|
btn.removeAttribute("title");
|
|
btn.appendChild(label);
|
|
const shortcut = category.id === "operate" ? TOOL_SHORTCUTS[toolId] : "";
|
|
if (shortcut) {
|
|
const badge = document.createElement("kbd");
|
|
badge.className = "tool-shortcut";
|
|
badge.textContent = shortcut;
|
|
btn.setAttribute("aria-keyshortcuts", shortcut);
|
|
btn.appendChild(badge);
|
|
}
|
|
if (!hasPhysicsIcon) attachToolSpritePreview(btn, def, toolId);
|
|
if (toolId === world?.tool) btn.classList.add("selected");
|
|
body.appendChild(btn);
|
|
if (category.id === "operate" && toolId === "clear_tool") body.appendChild(createToolPickModeControl());
|
|
}
|
|
section.append(toggle, body);
|
|
frag.appendChild(section);
|
|
}
|
|
ui.toolPalette.appendChild(frag);
|
|
}
|
|
|
|
function drawToolPreviewCanvas(canvas, itemType, { watermark = false } = {}) {
|
|
if (!canvas || !itemType) return false;
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) return false;
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
if (typeof window.drawToolItemPreview !== "function") return false;
|
|
return window.drawToolItemPreview(ctx, itemType, { width: canvas.width, height: canvas.height, compact: true, watermark }) !== false;
|
|
}
|
|
|
|
function createToolPreviewCanvas(className, size = 96) {
|
|
const canvas = document.createElement("canvas");
|
|
canvas.className = className;
|
|
canvas.width = size;
|
|
canvas.height = size;
|
|
canvas.setAttribute("aria-hidden", "true");
|
|
return canvas;
|
|
}
|
|
|
|
function attachToolSpritePreview(btn, def, toolId) {
|
|
const itemType = def?.itemType || toolId;
|
|
if (!btn || !itemType || !def?.placeable || TEXT_ICON_TOOL_IDS.has(toolId)) return;
|
|
if (toolId === "reciprocator") return;
|
|
if (toolId === "ball" || itemType === "ball") return;
|
|
if (typeof window.drawToolItemPreview !== "function") return;
|
|
|
|
const iconCanvas = createToolPreviewCanvas("tool-sprite-preview", 160);
|
|
const iconDrawn = drawToolPreviewCanvas(iconCanvas, itemType);
|
|
if (!iconDrawn) return;
|
|
|
|
btn.classList.add("tool-has-canvas-icon");
|
|
btn.prepend(iconCanvas);
|
|
}
|
|
|
|
function toolSizeFor(tool) {
|
|
if (!SCALABLE_TOOLS.has(tool)) return "";
|
|
return normalizedToolSizeFor(world, tool);
|
|
}
|
|
|
|
function cycleToolSize(tool) {
|
|
if (!SCALABLE_TOOLS.has(tool)) return false;
|
|
const result = window.TarinaiCommands.dispatch(world, { type: "tool.size.cycle", toolId: tool, order: SIZE_ORDER });
|
|
if (!result?.ok) return false;
|
|
syncToolSizeBadges();
|
|
showToast(`\u30b5\u30a4\u30ba: ${SIZE_LABELS[result.size] || result.size}`);
|
|
return true;
|
|
}
|
|
|
|
function cycleToolPickMode() {
|
|
const result = window.TarinaiCommands.dispatch(world, { type: "tool.pick.cycle" });
|
|
if (!result?.ok) return false;
|
|
syncToolSizeBadges();
|
|
showToast(`\u5438\u7740: ${PICK_MODE_LABELS[result.mode] || "\u81ea\u7531"}`);
|
|
return true;
|
|
}
|
|
|
|
function syncToolPickModeControls() {
|
|
const mode = normalizedToolPickMode();
|
|
for (const btn of ui?.toolPalette?.querySelectorAll?.("[data-pick-mode-control]") || []) {
|
|
const value = btn.querySelector(".tool-pick-mode-value");
|
|
if (value) value.textContent = PICK_MODE_LABELS[mode] || "\u81ea\u7531";
|
|
btn.dataset.pickMode = mode;
|
|
btn.setAttribute("aria-label", `\u5438\u7740\u30e2\u30fc\u30c9: ${PICK_MODE_NAMES[mode] || PICK_MODE_NAMES.free}\u3002\u62bc\u3059\u3068\u6b21\u306e\u30e2\u30fc\u30c9\u3078\u5207\u308a\u66ff\u3048\u307e\u3059\u3002`);
|
|
btn.removeAttribute("title");
|
|
}
|
|
}
|
|
|
|
function syncToolSizeBadges() {
|
|
for (const btn of uiCache.toolButtons || []) {
|
|
const tool = btn.dataset.tool || "";
|
|
const old = btn.querySelector(".tool-size-badge");
|
|
if (!SCALABLE_TOOLS.has(tool)) {
|
|
if (old) old.remove();
|
|
continue;
|
|
}
|
|
const size = toolSizeFor(tool);
|
|
let badge = old;
|
|
if (!badge) {
|
|
badge = document.createElement("b");
|
|
badge.className = "tool-size-badge";
|
|
btn.appendChild(badge);
|
|
}
|
|
badge.textContent = SIZE_LABELS[size] || size;
|
|
badge.title = "\u30b5\u30a4\u30ba\u3092\u5207\u308a\u66ff\u3048\u308b";
|
|
}
|
|
syncToolPickModeControls();
|
|
}
|