tarinai/js/ui_bind.js
2026-06-26 12:46:32 +09:00

379 lines
16 KiB
JavaScript

"use strict";
function bindUI() {
renderToolPalette();
loadLogPushSettings();
syncLogPushControls();
syncAudioControls();
applyToolTips();
window.bindGroundUI?.(world);
renderEcologyCards();
window.TarinaiSaveSystem?.bindSaveSystem?.();
window.TarinaiFreezeSystem?.bindFreezeSystem?.();
uiCache.toolButtons = Array.from(ui.toolPalette?.querySelectorAll(".tool") || []);
function isActivePlacementTool() {
const def = typeof toolDefinition === "function" ? toolDefinition(world.tool) : null;
return Boolean(def?.placeable && typeof toolItemType === "function" && toolItemType(world.tool));
}
function clickedInsideGameOrToolUi(target) {
return Boolean((canvas && canvas.contains?.(target)) || ui.toolPalette?.contains?.(target) || ui.panelQuickTabs?.contains?.(target));
}
function panelSectionForTab(tab = "") {
if (tab === "selected") return ui.selectedCard;
if (tab === "tools") return ui.toolCard;
if (tab === "colony") return document.querySelector(".colony-card");
if (tab === "events") return document.querySelector(".log-card");
return null;
}
function setActivePanelTab(tab = "") {
for (const btn of ui.panelQuickTabs?.querySelectorAll("[data-panel-tab]") || []) {
btn.classList.toggle("active", btn.dataset.panelTab === tab);
}
}
function scrollPanelToTab(tab = "") {
const panel = ui.panel;
if (!panel) return false;
if (tab === "selected" && (!world.selected || world.selected.dead || !world.tarinai.includes(world.selected))) {
selectTool("observe");
uiCache.showSelectedEmpty = true;
uiCache.selectedEmpty = false;
renderSelected();
showToast("たりないが未選択です。観察モードにしました。");
}
const target = panelSectionForTab(tab);
if (!target) return false;
target.classList?.remove?.("hidden");
const tabsH = ui.panelQuickTabs?.offsetHeight || 0;
const top = Math.max(0, target.offsetTop - tabsH - 8);
panel.scrollTo({ top, behavior: "smooth" });
setActivePanelTab(tab);
return true;
}
ui.panelQuickTabs?.addEventListener("click", (e) => {
const btn = e.target.closest("[data-panel-tab]");
if (!btn) return;
e.preventDefault();
audio.uiClick?.();
scrollPanelToTab(btn.dataset.panelTab || "");
});
ui.panel?.addEventListener("scroll", () => {
const panel = ui.panel;
if (!panel || !ui.panelQuickTabs) return;
const tabsH = ui.panelQuickTabs.offsetHeight || 0;
const entries = [
["selected", ui.selectedCard],
["tools", ui.toolCard],
["colony", document.querySelector(".colony-card")],
["events", document.querySelector(".log-card")],
].filter(([, el]) => el && !el.classList.contains("hidden"));
let active = entries[0]?.[0] || "";
const y = panel.scrollTop + tabsH + 18;
for (const [key, el] of entries) {
if (el.offsetTop <= y) active = key;
}
setActivePanelTab(active);
}, { passive: true });
syncToolSizeBadges();
setActivePanelTab(ui.selectedCard?.classList.contains("hidden") ? "tools" : "selected");
function activeRotatableToolType() {
const type = typeof toolItemType === "function" ? toolItemType(world?.tool || "") : "";
return type && typeof isRotatableItemType === "function" && isRotatableItemType(type) ? type : "";
}
window.addEventListener("keydown", (e) => {
const tag = String(e.target?.tagName || "").toLowerCase();
if (tag === "input" || tag === "textarea" || e.target?.isContentEditable) return;
const type = activeRotatableToolType();
if (!type || !world?.rotateToolAngle) return;
const key = String(e.key || "").toLowerCase();
const step = (e.shiftKey ? 5 : 45) * Math.PI / 180;
if (key === "q" || key === "[") {
e.preventDefault();
world.rotateToolAngle(type, -step);
render();
} else if (key === "e" || key === "]") {
e.preventDefault();
world.rotateToolAngle(type, step);
render();
} else if (key === "r") {
e.preventDefault();
world.setToolAngle?.(type, typeof defaultItemAngle === "function" ? defaultItemAngle(type) : 0);
render();
}
});
for (const toggle of document.querySelectorAll(".panel-card-toggle")) {
toggle.addEventListener("click", () => {
const card = toggle.closest(".collapsible-card");
if (!card) return;
audio.uiFold?.();
const collapsed = card.classList.toggle("collapsed");
toggle.setAttribute("aria-expanded", collapsed ? "false" : "true");
const state = toggle.querySelector(".panel-card-state");
if (state) state.textContent = collapsed ? "\u5c55\u958b" : "\u6298\u308a\u7573\u307f";
});
}
ui.colonyChartTabs?.addEventListener("click", (e) => {
const btn = e.target.closest("button[data-chart]");
if (!btn) return;
uiCache.chartMode = btn.dataset.chart || "population";
for (const b of ui.colonyChartTabs.querySelectorAll("button")) b.classList.toggle("active", b === btn);
uiCache.lastChartDraw = "";
uiCache.lastChartDrawAt = 0;
renderStats();
});
ui.logPushEnabled?.addEventListener("change", () => {
uiCache.logPushEnabled = Boolean(ui.logPushEnabled.checked);
audio.notify?.();
saveLogPushSettings();
showToast(uiCache.logPushEnabled ? "\u30ed\u30b0\u306e\u30d7\u30c3\u30b7\u30e5\u901a\u77e5\u3092ON\u306b\u3057\u307e\u3057\u305f\u3002" : "\u30ed\u30b0\u306e\u30d7\u30c3\u30b7\u30e5\u901a\u77e5\u3092OFF\u306b\u3057\u307e\u3057\u305f\u3002");
});
ui.logPushControls?.addEventListener("change", (e) => {
const input = e.target.closest("[data-push-kind]");
if (!input) return;
const kind = input.dataset.pushKind || "";
audio.uiClick?.();
if (input.checked) uiCache.logPushKinds.add(kind);
else uiCache.logPushKinds.delete(kind);
saveLogPushSettings();
});
ui.soundCategoryControls?.addEventListener("change", (e) => {
const input = e.target.closest("[data-sound-category]");
if (!input) return;
const category = input.dataset.soundCategory || "";
audio.setCategory?.(category, Boolean(input.checked));
if (audio.enabled && input.checked) audio.uiClick?.();
syncAudioControls();
showToast(`${audio.soundCategories?.[category] || category} ${input.checked ? "ON" : "OFF"}`);
});
ui.archiveContent?.addEventListener("scroll", scheduleArchiveWindowRender, { passive: true });
window.addEventListener("scroll", schedulePendingArchiveRender, { passive: true });
window.addEventListener("resize", schedulePendingArchiveRender, { passive: true });
const bindArchiveHorizontal = (root) => {
if (!root) return;
root.addEventListener("wheel", (e) => {
const scroller = e.target.closest(".lineage-family-tree, .lineage-main-content");
if (!scroller) return;
const canX = scroller.scrollWidth > scroller.clientWidth + 4;
if (!canX) return;
const dominant = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : (e.shiftKey ? e.deltaY : 0);
if (!dominant) return;
e.preventDefault();
scroller.scrollLeft += dominant;
}, { passive: false });
};
bindArchiveHorizontal(ui.archiveContent);
ui.archiveContent?.addEventListener("click", (e) => {
const node = e.target.closest("[data-family-tarinai-id]");
if (!node) return;
const id = node.getAttribute("data-family-tarinai-id");
const t = world.tarinai.find(x => (x.id === id || x.familyKey === id) && !x.dead);
if (!t) { showToast("\u305d\u306e\u500b\u4f53\u306f\u3082\u3046\u9078\u629e\u3067\u304d\u307e\u305b\u3093\u3002"); return; }
window.TarinaiCommands?.dispatch?.(world, { type: "selection.focus", target: t, options: { pulse: 1.15 } });
selectTool("observe");
renderStats();
showToast(`${t.name}\u3092\u9078\u629e\u3057\u307e\u3057\u305f\u3002`);
});
if (!uiCache.archiveAutoRefreshTimer && typeof window !== "undefined") {
uiCache.archiveAutoRefreshTimer = window.setInterval(() => {
if (!ui.archiveContent || !archiveAutoUpdateEnabled()) return;
scheduleArchiveWindowRender();
}, 5000);
}
ui.selectedInfo?.addEventListener("click", (e) => {
const categoryToggle = e.target.closest(".selected-category-toggle");
if (categoryToggle) {
const category = categoryToggle.closest(".selected-category");
const id = category?.dataset?.selectedCategory || "";
if (!category || !id) return;
if (!uiCache.selectedCollapsedCategories) uiCache.selectedCollapsedCategories = new Set();
audio.uiFold?.();
category.classList.toggle("collapsed");
const collapsed = category.classList.contains("collapsed");
if (collapsed) uiCache.selectedCollapsedCategories.add(id);
else uiCache.selectedCollapsedCategories.delete(id);
categoryToggle.setAttribute("aria-expanded", collapsed ? "false" : "true");
return;
}
const focusBtn = e.target.closest("[data-focus-tarinai-id]");
if (focusBtn) {
e.preventDefault();
focusTarinaiById(focusBtn.getAttribute("data-focus-tarinai-id"));
return;
}
const btn = e.target.closest("[data-selected-action=\"favorite\"]");
if (!btn || !world.selected || world.selected.dead) return;
const favResult = window.TarinaiCommands?.dispatch?.(world, { type: "selection.favorite.toggle" });
if (!favResult?.ok) return;
uiCache.selectedSnapshot = "";
renderSelected();
render();
showToast(favResult.favorite ? "\u304a\u6c17\u306b\u5165\u308a\u306b\u3057\u307e\u3057\u305f\u3002" : "\u304a\u6c17\u306b\u5165\u308a\u3092\u89e3\u9664\u3057\u307e\u3057\u305f\u3002");
});
ui.selectedInfo?.addEventListener("change", (e) => {
const input = e.target.closest("[data-selected-name]");
if (!input) return;
renameSelectedTarinai(input.value);
});
ui.selectedInfo?.addEventListener("keydown", (e) => {
const input = e.target.closest("[data-selected-name]");
if (!input) return;
if (e.key === "Enter") { e.preventDefault(); input.blur(); }
if (e.key === "Escape") { e.preventDefault(); input.value = world.selected?.name || ""; input.blur(); }
});
ui.selectedCloseBtn?.addEventListener("click", () => {
window.TarinaiCommands?.dispatch?.(world, { type: "selection.clear" });
uiCache.showSelectedEmpty = false;
uiCache.selectedSnapshot = "";
uiCache.selectedEmpty = false;
renderSelected();
render();
});
ui.pauseBtn?.addEventListener("click", () => {
audio.uiClick?.();
const pauseResult = window.TarinaiCommands?.dispatch?.(world, { type: "simulation.paused.toggle" });
const paused = pauseResult?.ok ? pauseResult.paused : world.paused;
const archiveStale = uiCache.archiveFamilyVersion !== (world.familyVersion || 0);
if (archiveAutoUpdateEnabled() && paused && (uiCache.archivePendingWhileRunning || archiveStale)) {
uiCache.archivePendingWhileRunning = false;
scheduleArchiveWindowRender();
}
syncTopButtons();
});
ui.speedBtn?.addEventListener("click", () => {
audio.uiClick?.();
window.TarinaiCommands?.dispatch?.(world, { type: "simulation.speed.cycle", speeds: [1, 2, 4] });
syncTopButtons();
});
ui.ecologyBtn?.addEventListener("click", () => { audio.uiClick?.(); openEcologyDialog(); });
ui.soundBtn?.addEventListener("click", (e) => {
e.stopPropagation();
toggleSoundPanel();
});
ui.soundPanel?.addEventListener("click", (e) => e.stopPropagation());
ui.soundMasterToggle?.addEventListener("change", () => {
const on = audio.setEnabled ? audio.setEnabled(ui.soundMasterToggle.checked) : audio.toggle();
if (on) audio.uiClick?.();
syncAudioControls();
showToast(on ? "\u52b9\u679c\u97f3\u3092ON\u306b\u3057\u307e\u3057\u305f\u3002" : "\u52b9\u679c\u97f3\u3092OFF\u306b\u3057\u307e\u3057\u305f\u3002");
});
document.addEventListener("click", (e) => {
if (!ui.soundMenu || ui.soundMenu.contains(e.target)) return;
closeSoundPanel();
});
document.addEventListener("click", (e) => {
if (!isActivePlacementTool()) return;
if (clickedInsideGameOrToolUi(e.target)) return;
selectTool("observe"); // \u9053\u5177\u8a2d\u7f6e\u30e2\u30fc\u30c9\u3092\u89e3\u9664
});
ui.creditsBtn?.addEventListener("click", () => { audio.uiClick?.(); openCreditsDialog(); });
ui.creditsCloseBtn?.addEventListener("click", closeCreditsDialog);
ui.creditsDialog?.addEventListener("click", (e) => {
if (e.target === ui.creditsDialog) closeCreditsDialog();
});
ui.resetBtn?.addEventListener("click", () => {
audio.uiClick?.();
openFieldDialog();
});
ui.fieldCancelBtn?.addEventListener("click", closeFieldDialog);
ui.fieldDialog?.addEventListener("click", (e) => {
if (e.target === ui.fieldDialog) { closeFieldDialog(); return; }
const btn = e.target.closest("[data-field-type]");
if (!btn) return;
resetWithField(btn.dataset.fieldType || "garden");
});
ui.ecologyCloseBtn?.addEventListener("click", closeEcologyDialog);
ui.ecologyDialog?.addEventListener("click", (e) => {
if (e.target === ui.ecologyDialog) closeEcologyDialog();
});
ui.archiveUpdateToggleBtn?.addEventListener("click", () => { audio.uiClick?.(); setArchiveUpdateEnabled(!archiveAutoUpdateEnabled()); });
syncArchiveUpdateButton();
ui.archiveResetBtn?.addEventListener("click", () => { audio.uiClick?.(); openArchiveResetDialog(); });
ui.archiveResetCancelBtn?.addEventListener("click", closeArchiveResetDialog);
ui.archiveResetDialog?.addEventListener("click", (e) => {
if (e.target === ui.archiveResetDialog) closeArchiveResetDialog();
});
ui.archiveResetConfirmBtn?.addEventListener("click", () => {
world.resetFamilyTree?.();
window.resetArchiveRenderState?.();
closeArchiveResetDialog();
renderArchive();
renderSelected();
showToast("\u5bb6\u7cfb\u56f3\u3092\u30ea\u30bb\u30c3\u30c8\u3057\u307e\u3057\u305f\u3002");
});
ui.toolPalette?.addEventListener("click", (e) => {
const toggle = e.target.closest(".tool-category-toggle");
if (toggle) {
const category = toggle.closest(".tool-category");
if (!category) return;
audio.uiFold?.();
category.classList.toggle("collapsed");
toggle.setAttribute("aria-expanded", category.classList.contains("collapsed") ? "false" : "true");
return;
}
const btn = e.target.closest(".tool");
if (!btn) return;
const tool = btn.dataset.tool;
audio.uiClick?.();
if (world.tool === tool && cycleToolSize(tool)) return;
selectTool(tool);
});
const chainScroll = (el) => {
if (!el) return;
el.addEventListener("wheel", (e) => {
if (Math.abs(e.deltaY) <= Math.abs(e.deltaX)) return;
const atTop = el.scrollTop <= 0;
const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 1;
if ((e.deltaY < 0 && atTop) || (e.deltaY > 0 && atBottom)) {
const page = document.scrollingElement || document.documentElement;
page.scrollTop += e.deltaY;
}
}, { passive: true });
};
const transferEdgeScroll = (child, parent) => {
if (!child || !parent) return;
child.addEventListener("wheel", (e) => {
if (Math.abs(e.deltaY) <= Math.abs(e.deltaX)) return;
const childCanScroll = child.scrollHeight > child.clientHeight + 1;
const atTop = child.scrollTop <= 0;
const atBottom = child.scrollTop + child.clientHeight >= child.scrollHeight - 1;
const movingInside = childCanScroll && ((e.deltaY < 0 && !atTop) || (e.deltaY > 0 && !atBottom));
e.preventDefault();
e.stopPropagation();
if (movingInside) {
child.scrollTop += e.deltaY;
return;
}
parent.scrollTop += e.deltaY;
}, { passive: false });
};
const rightPanel = document.querySelector(".panel");
chainScroll(rightPanel);
chainScroll(ui.archiveContent);
transferEdgeScroll(ui.log, rightPanel);
const inputContext = window.TarinaiUIInputShared?.createInputContext?.({ canvas, world, ui, uiCache });
if (!inputContext) throw new Error("Tarinai UI input context is not available");
window.TarinaiTouchInput?.bindTouchInput?.(inputContext);
window.TarinaiMouseInput?.bindMouseInput?.(inputContext);
window.addEventListener("keydown", (e) => {
if (e.key !== "Escape") return;
closeFieldDialog();
closeEcologyDialog();
closeArchiveResetDialog();
closeCreditsDialog();
});
window.addEventListener("resize", resizeCanvas);
}