feat: add custom icon and color settings for the Fear Tracker UI

This commit is contained in:
CPTN Cosmo 2025-12-19 00:41:49 +01:00
parent c0c9a1816c
commit 217d6c9222
2 changed files with 170 additions and 0 deletions

159
scripts/module.js Normal file
View file

@ -0,0 +1,159 @@
const MODULE_ID = 'dh-feartrackerplus';
Hooks.once('init', () => {
console.log(`${MODULE_ID} | Initializing Daggerheart Fear Tracker Plus`);
// Register Settings
game.settings.register(MODULE_ID, 'iconType', {
name: 'Icon Source',
hint: 'Choose whether to use a preset icon or a custom FontAwesome class.',
scope: 'client',
config: true,
type: String,
choices: {
'preset': 'Preset List',
'custom': 'Custom FontAwesome Class'
},
default: 'preset',
onChange: () => {
// Force re-render if possible, or let the hook handle it on next update
if (ui.apps) Object.values(ui.apps).forEach(app => app.render?.());
}
});
game.settings.register(MODULE_ID, 'presetIcon', {
name: 'Preset Icon',
hint: 'Select an icon from the list.',
scope: 'client',
config: true,
type: String,
choices: {
'fa-skull': 'Skull (Default)',
'fa-ghost': 'Ghost',
'fa-fire': 'Fire',
'fa-heart-broken': 'Broken Heart',
'fa-dizzy': 'Dizzy Face',
'fa-book-dead': 'Book of Dead',
'fa-spider': 'Spider',
'fa-cloud-meatball': 'Cloud Meatball',
'fa-biohazard': 'Biohazard',
'fa-radiation': 'Radiation'
},
default: 'fa-skull',
onChange: () => { if (ui.apps) Object.values(ui.apps).forEach(app => app.render?.()); }
});
game.settings.register(MODULE_ID, 'customIcon', {
name: 'Custom Icon Class',
hint: 'Enter the full FontAwesome class string (e.g., "fa-solid fa-dragon").',
scope: 'client',
config: true,
type: String,
default: 'fa-solid fa-dragon',
onChange: () => { if (ui.apps) Object.values(ui.apps).forEach(app => app.render?.()); }
});
game.settings.register(MODULE_ID, 'fullColor', {
name: 'Full Icon Color',
hint: 'CSS color string or gradient (e.g., "#ff0000" or "linear-gradient(to top, red, yellow)").',
scope: 'client',
config: true,
type: String,
default: '#ff0000',
onChange: () => { if (ui.apps) Object.values(ui.apps).forEach(app => app.render?.()); }
});
game.settings.register(MODULE_ID, 'emptyColor', {
name: 'Empty Icon Color',
hint: 'CSS color string for inactive icons.',
scope: 'client',
config: true,
type: String,
default: '#444444',
onChange: () => { if (ui.apps) Object.values(ui.apps).forEach(app => app.render?.()); }
});
});
/**
* Handle Fear Tracker Rendering
* We hook into the renderFearTracker or whatever the ApplicationV2 is called ideally.
* But looking at the system code, it uses `FearTracker` class.
* Since it's an ApplicationV2, `renderApplication` hook might work, or `renderFearTracker` if the system supports it.
* The system code shows: export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV2)
* The template ID is systems/daggerheart/templates/ui/fearTracker.hbs
* We can hook 'renderFearTracker'.
*/
Hooks.on('renderFearTracker', (app, html, data) => {
injectFearCustomization(html);
});
// Also try to catch it if it's strictly ApplicationV2 and name might differ slightly in some contexts, but 'renderFearTracker' should work given the class name.
// Just in case the class name in the system is minified or different at runtime, we can check the element.
function injectFearCustomization(html) {
// The HTML passed might be the raw jQuery object or HTMLElement
const container = html instanceof HTMLElement ? html : html[0];
const fearContainer = container.querySelector('#resource-fear');
if (!fearContainer) return;
// Get Settings
const iconType = game.settings.get(MODULE_ID, 'iconType');
const presetIcon = game.settings.get(MODULE_ID, 'presetIcon');
const customIcon = game.settings.get(MODULE_ID, 'customIcon');
const fullColor = game.settings.get(MODULE_ID, 'fullColor');
const emptyColor = game.settings.get(MODULE_ID, 'emptyColor');
// Determine Icon Class
let iconClass = 'fa-skull'; // fallback
if (iconType === 'preset') {
iconClass = presetIcon;
} else {
iconClass = customIcon;
}
// Find all icons
// The system uses: <i class="fas fa-skull ...">
// We target them. Note: the system might have added 'inactive' class.
const icons = fearContainer.querySelectorAll('i');
icons.forEach(icon => {
// 1. Replace Icon
// Remove existing FA classes that define the icon.
// We know 'fas' and 'fa-skull' are likely there.
// Safest is to remove 'fa-skull' and add our own.
// If the user provided a full string like "fa-solid fa-dragon", we should handle that.
// Remove the default system icon class specific to fear logic if we can identify it,
// but 'fa-skull' is hardcoded in the generic template.
icon.classList.remove('fa-skull');
// Add new classes
// split by space to handle multiple classes in custom string
const newClasses = iconClass.split(' ').filter(c => c.trim() !== '');
icon.classList.add(...newClasses, 'fear-tracker-plus-custom');
// 2. Handle Colors (Override System Hue Rotate)
// System style: style="filter: hue-rotate(calc(({{this}}/{{../max}})*75deg))"
// We must override this. resetting filter is enough.
icon.style.filter = 'none';
// Check state
const isInactive = icon.classList.contains('inactive');
const targetColor = isInactive ? emptyColor : fullColor;
// Apply Color
const isGradient = targetColor.toLowerCase().includes('gradient');
if (isGradient) {
icon.classList.add('fear-tracker-plus-gradient');
icon.style.background = targetColor;
icon.style.color = 'transparent'; // Needed for background-clip: text
} else {
icon.classList.remove('fear-tracker-plus-gradient');
icon.style.background = 'none';
icon.style.color = targetColor;
}
});
}

11
styles/module.css Normal file
View file

@ -0,0 +1,11 @@
.fear-tracker-plus-gradient {
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
display: inline-block; /* Required for gradient text */
}
/* Ensure the icon keeps its size and doesn't collapse */
#resource-fear i.fear-tracker-plus-custom {
transition: all 0.5s ease;
}