2026-06-21 14:09:35 +09:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
const SCALABLE_TOOLS = new Set(scalableToolIds());
|
2026-07-01 14:41:05 +09:00
|
|
|
const TEXT_ICON_TOOL_IDS = new Set(["poke", "pinch", "water_hose", "rope", "rod", "reciprocator", "copy", "undo", "redo", "area_delete", "clear_tool"]);
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2026-06-21 14:09:35 +09:00
|
|
|
|
2026-07-05 18:01:36 +09:00
|
|
|
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"];
|
2026-06-21 14:09:35 +09:00
|
|
|
|
2026-07-05 18:01:36 +09:00
|
|
|
const SIZE_LABELS = window.TarinaiItemToolMetadata?.TOOL_SIZE_LABELS || { small: "\u5c0f", medium: "\u4e2d", large: "\u5927" };
|
2026-06-21 14:09:35 +09:00
|
|
|
|
2026-06-21 22:29:00 +09:00
|
|
|
function renderToolPalette() {
|
|
|
|
|
if (!ui?.toolPalette || typeof toolCategories !== "function") return;
|
|
|
|
|
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;
|
2026-06-29 16:21:58 +09:00
|
|
|
const mobileCompact = Boolean(window.TarinaiInputMode.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui"));
|
2026-06-28 13:40:41 +09:00
|
|
|
const initiallyCollapsed = mobileCompact && category.id !== "operate";
|
|
|
|
|
if (initiallyCollapsed) section.classList.add("collapsed");
|
2026-06-21 22:29:00 +09:00
|
|
|
const toggle = document.createElement("button");
|
|
|
|
|
toggle.className = "tool-category-toggle";
|
|
|
|
|
toggle.type = "button";
|
2026-06-28 13:40:41 +09:00
|
|
|
toggle.setAttribute("aria-expanded", initiallyCollapsed ? "false" : "true");
|
2026-06-21 22:29:00 +09:00
|
|
|
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);
|
2026-06-25 14:51:58 +09:00
|
|
|
if (!def || def.simulationOnly || toolId === "observe") continue;
|
2026-06-21 22:29:00 +09:00
|
|
|
const btn = document.createElement("button");
|
|
|
|
|
btn.className = "tool";
|
2026-07-05 18:01:36 +09:00
|
|
|
const hasTextIcon = applyToolTextIcon(btn, def, toolId);
|
|
|
|
|
if (!hasTextIcon) applyToolImageIcon(btn, def);
|
2026-06-21 22:29:00 +09:00
|
|
|
btn.dataset.tool = toolId;
|
2026-06-25 14:51:58 +09:00
|
|
|
btn.dataset.toolCategory = category.id || "";
|
2026-06-24 18:49:17 +09:00
|
|
|
const label = document.createElement("span");
|
|
|
|
|
label.className = "tool-label";
|
|
|
|
|
label.textContent = def.label || toolId;
|
2026-06-25 22:30:35 +09:00
|
|
|
btn.setAttribute("aria-label", label.textContent);
|
|
|
|
|
btn.removeAttribute("title");
|
2026-06-24 18:49:17 +09:00
|
|
|
btn.appendChild(label);
|
2026-06-23 22:39:20 +09:00
|
|
|
attachToolSpritePreview(btn, def, toolId);
|
2026-06-21 22:29:00 +09:00
|
|
|
if (toolId === world?.tool) btn.classList.add("selected");
|
|
|
|
|
body.appendChild(btn);
|
|
|
|
|
}
|
|
|
|
|
section.append(toggle, body);
|
|
|
|
|
frag.appendChild(section);
|
|
|
|
|
}
|
|
|
|
|
ui.toolPalette.appendChild(frag);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-24 18:49:17 +09:00
|
|
|
function drawToolPreviewCanvas(canvas, itemType, { watermark = false } = {}) {
|
|
|
|
|
if (!canvas || !itemType) return false;
|
2026-06-23 22:39:20 +09:00
|
|
|
const ctx = canvas.getContext("2d");
|
2026-06-24 18:49:17 +09:00
|
|
|
if (!ctx) return false;
|
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
2026-06-29 21:02:04 +09:00
|
|
|
if (typeof window.drawToolItemPreview !== "function") return false;
|
|
|
|
|
return window.drawToolItemPreview(ctx, itemType, { width: canvas.width, height: canvas.height, compact: true, watermark }) !== false;
|
2026-06-24 18:49:17 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
2026-07-01 14:41:05 +09:00
|
|
|
if (!btn || !itemType || !def?.placeable || TEXT_ICON_TOOL_IDS.has(toolId)) return;
|
2026-06-26 19:04:32 +09:00
|
|
|
if (toolId === "reciprocator") return;
|
2026-06-24 18:49:17 +09:00
|
|
|
if (toolId === "ball" || itemType === "ball") return;
|
2026-06-29 21:02:04 +09:00
|
|
|
if (typeof window.drawToolItemPreview !== "function") return;
|
2026-06-24 18:49:17 +09:00
|
|
|
|
|
|
|
|
const iconCanvas = createToolPreviewCanvas("tool-sprite-preview", 160);
|
|
|
|
|
const iconDrawn = drawToolPreviewCanvas(iconCanvas, itemType);
|
|
|
|
|
if (!iconDrawn) return;
|
|
|
|
|
|
2026-06-23 22:39:20 +09:00
|
|
|
btn.classList.add("tool-has-canvas-icon");
|
2026-06-24 18:49:17 +09:00
|
|
|
btn.prepend(iconCanvas);
|
2026-06-23 22:39:20 +09:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 14:09:35 +09:00
|
|
|
function toolSizeFor(tool) {
|
|
|
|
|
if (!SCALABLE_TOOLS.has(tool)) return "";
|
2026-06-29 16:21:58 +09:00
|
|
|
return normalizedToolSizeFor(world, tool);
|
2026-06-21 14:09:35 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function cycleToolSize(tool) {
|
|
|
|
|
if (!SCALABLE_TOOLS.has(tool)) return false;
|
2026-06-29 16:21:58 +09:00
|
|
|
const result = window.TarinaiCommands.dispatch(world, { type: "tool.size.cycle", toolId: tool, order: SIZE_ORDER });
|
2026-06-26 12:46:32 +09:00
|
|
|
if (!result?.ok) return false;
|
2026-06-21 14:09:35 +09:00
|
|
|
syncToolSizeBadges();
|
2026-06-26 12:46:32 +09:00
|
|
|
showToast(`\u30b5\u30a4\u30ba: ${SIZE_LABELS[result.size] || result.size}`);
|
2026-06-21 14:09:35 +09:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|