From c96048f67e8d4ee7d44a4ac53ef41aab442e9773 Mon Sep 17 00:00:00 2001 From: CPTN Cosmo Date: Fri, 19 Dec 2025 01:28:50 +0100 Subject: [PATCH] feat: Add tracker scaling and position locking settings, introduce Foundryborne theme, and improve settings panel element selection. --- scripts/module.js | 123 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 7 deletions(-) diff --git a/scripts/module.js b/scripts/module.js index d1fe30b..616b1bd 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -67,6 +67,31 @@ Hooks.once('init', () => { onChange: refreshFearTracker }); + game.settings.register(MODULE_ID, 'trackerScale', { + name: 'Tracker Scale', + hint: 'Resize the fear tracker (0.25x to 2.0x).', + scope: 'client', + config: true, + type: Number, + range: { + min: 0.25, + max: 2.0, + step: 0.05 + }, + default: 1.0, + onChange: refreshFearTracker + }); + + game.settings.register(MODULE_ID, 'trackerLocked', { + name: 'Lock Tracker Position', + hint: 'Prevents the tracker from being dragged.', + scope: 'client', + config: true, // User can toggle in settings too + type: Boolean, + default: false, + onChange: refreshFearTracker + }); + game.settings.register(MODULE_ID, 'colorTheme', { name: 'Color Theme', hint: 'Choose a color preset or Custom to set your own colors below.', @@ -74,13 +99,14 @@ Hooks.once('init', () => { config: true, type: String, choices: { + 'foundryborne': 'Foundryborne (Default)', 'custom': 'Custom', 'hope-fear': 'Hope & Fear (Orange to Purple)', 'blood-moon': 'Blood Moon (Red Gradient)', 'ethereal': 'Ethereal (Cyan to Blue)', 'toxic': 'Toxic (Green to Yellow)' }, - default: 'custom', + default: 'foundryborne', onChange: refreshFearTracker }); @@ -118,9 +144,21 @@ Hooks.on('renderSettingsConfig', (app, html, data) => { const theme = themeSelect.val(); // Icon Inputs - const presetGroup = $html.find(`select[name="${MODULE_ID}.presetIcon"]`).closest('.form-group'); - const customIconGroup = $html.find(`input[name="${MODULE_ID}.customIcon"]`).closest('.form-group'); - const customSvgGroup = $html.find(`input[name="${MODULE_ID}.customSvgPath"]`).closest('.form-group'); + // Standard data-setting-id + let presetGroup = $html.find(`.form-group[data-setting-id="${MODULE_ID}.presetIcon"]`); + let customIconGroup = $html.find(`.form-group[data-setting-id="${MODULE_ID}.customIcon"]`); + let customSvgGroup = $html.find(`.form-group[data-setting-id="${MODULE_ID}.customSvgPath"]`); + + // Fallback: Find input by name and traverse to form-group + if (!presetGroup.length) presetGroup = $html.find(`select[name="${MODULE_ID}.presetIcon"]`).closest('.form-group'); + if (!customIconGroup.length) customIconGroup = $html.find(`input[name="${MODULE_ID}.customIcon"]`).closest('.form-group'); + + // SVG Fallback: Ensure we find it separately because it might be wrapped differently + if (!customSvgGroup.length) { + const svgInput = $html.find(`input[name="${MODULE_ID}.customSvgPath"]`); + // Traverse up to find the closest form-group + customSvgGroup = svgInput.closest('.form-group'); + } // Reset presetGroup.hide(); @@ -136,8 +174,13 @@ Hooks.on('renderSettingsConfig', (app, html, data) => { } // Color Inputs - const fullColorGroup = $html.find(`input[name="${MODULE_ID}.fullColor"]`).closest('.form-group'); - const emptyColorGroup = $html.find(`input[name="${MODULE_ID}.emptyColor"]`).closest('.form-group'); + let fullColorGroup = $html.find(`.form-group[data-setting-id="${MODULE_ID}.fullColor"]`); + let emptyColorGroup = $html.find(`.form-group[data-setting-id="${MODULE_ID}.emptyColor"]`); + let scaleGroup = $html.find(`.form-group[data-setting-id="${MODULE_ID}.trackerScale"]`); + + if (!fullColorGroup.length) fullColorGroup = $html.find(`input[name="${MODULE_ID}.fullColor"]`).closest('.form-group'); + if (!emptyColorGroup.length) emptyColorGroup = $html.find(`input[name="${MODULE_ID}.emptyColor"]`).closest('.form-group'); + if (!scaleGroup.length) scaleGroup = $html.find(`input[name="${MODULE_ID}.trackerScale"]`).closest('.form-group'); if (theme === 'custom') { fullColorGroup.show(); @@ -196,6 +239,63 @@ function injectFearCustomization(html) { const container = html instanceof HTMLElement ? html : html[0]; const fearContainer = container.querySelector('#resource-fear'); + // --------------------------------------------------------- + // Window Lock Button Injection + // --------------------------------------------------------- + // Find the window element (parent of content) + // ApplicationV2 structure: window > window-content > html + const windowApp = container.closest('.window-app'); + if (windowApp) { + const header = windowApp.querySelector('.window-header'); + if (header) { + // Check if button already exists + if (!header.querySelector('.fear-tracker-lock')) { + const lockBtn = document.createElement('a'); + lockBtn.classList.add('control', 'fear-tracker-lock'); + lockBtn.setAttribute('aria-label', 'Lock Position'); + // Insert before close button (usually last) + const closeBtn = header.querySelector('.control.close'); + if (closeBtn) { + header.insertBefore(lockBtn, closeBtn); + } else { + header.appendChild(lockBtn); + } + + // Click Listener + lockBtn.addEventListener('click', async (e) => { + e.preventDefault(); + const current = game.settings.get(MODULE_ID, 'trackerLocked'); + await game.settings.set(MODULE_ID, 'trackerLocked', !current); + }); + } + + // Update State + const isLocked = game.settings.get(MODULE_ID, 'trackerLocked'); + const lockBtn = header.querySelector('.fear-tracker-lock'); + if (lockBtn) { + // Update Icon + lockBtn.innerHTML = isLocked ? '' : ''; + lockBtn.title = isLocked ? 'Unlock Position' : 'Lock Position'; + } + + // Handle Drag Disabling + // We target the window-title usually, or the drag handler + const windowTitle = header.querySelector('.window-title'); + if (windowTitle) { + if (isLocked) { + windowTitle.style.pointerEvents = 'none'; // Disables drag start + windowTitle.style.cursor = 'default'; + header.classList.add('locked'); + } else { + windowTitle.style.pointerEvents = 'all'; + windowTitle.style.cursor = 'grab'; + header.classList.remove('locked'); + } + } + } + } + + if (!fearContainer) return; // Get Settings @@ -212,6 +312,7 @@ function injectFearCustomization(html) { if (colorTheme !== 'custom') { const themes = { + 'foundryborne': { start: '#FFC107', end: '#512DA8', empty: '#2e1c4a' }, 'hope-fear': { start: '#FFC107', end: '#512DA8', empty: '#2e1c4a' }, 'blood-moon': { start: '#5c0000', end: '#ff0000', empty: '#2a0000' }, 'ethereal': { start: '#00FFFF', end: '#0000FF', empty: '#002a33' }, @@ -227,8 +328,16 @@ function injectFearCustomization(html) { } } + // Apply Scaling + const scale = game.settings.get(MODULE_ID, 'trackerScale'); + if (scale !== 1.0) { + fearContainer.style.zoom = scale; + } else { + fearContainer.style.zoom = 'normal'; + } + // Determine Icon Class - let iconClass = 'fa-skull'; + let iconClass = 'fas fa-skull'; if (iconType === 'preset') { iconClass = presetIcon; } else if (iconType === 'custom') {