From 54ed2838f71870fad0773526661880a60ab2b4c1 Mon Sep 17 00:00:00 2001 From: Ladd Date: Sun, 4 Jan 2026 13:29:19 -0600 Subject: [PATCH] Enhancement: options showIf --- helper.js | 38 +++++++++++++++++++++++- sim-options.js | 6 ++-- tool.js | 1 + tool/options.js | 77 +++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 103 insertions(+), 19 deletions(-) diff --git a/helper.js b/helper.js index 33d0443..3517273 100644 --- a/helper.js +++ b/helper.js @@ -1,2 +1,38 @@ -export function makeUtilityButton() { +// `items` is an array of which `item` is a member +// `item` must let us read/write property `hidden` +// `parentEl` is the containing element for `itemEl` +// `itemEl` is the +export function show({items, item, parentEl, itemEl}) { + if (items.length < 2) { + parentEl.appendChild(itemEl); + return; + } + // To determine placement, + // Start with our index in the toolbar tools; + // iterate through toolbar tools before this one, + // and subtract hidden ones from the index. + + let countHidden = 0; + let index = items.indexOf(item); + for (let i = 0; i < index; i++) { + const sibling = items[i]; + if (sibling.hidden) countHidden += 1; + } + index -= countHidden; + + // Now we need to find our place. + // Add to parent using insertBefore. + let idx = 0; + let nextEl = parentEl.firstChild; + while (idx < index) { + nextEl = nextEl.nextSibling; + idx += 1; + } + parentEl.insertBefore(itemEl, nextEl); + item.hidden = false; +} + +export function hide({item, parentEl, itemEl}) { + parentEl.removeChild(itemEl); + item.hidden = true; } diff --git a/sim-options.js b/sim-options.js index 598f2be..f4a6cd5 100644 --- a/sim-options.js +++ b/sim-options.js @@ -8,9 +8,9 @@ export const simOptions = { velocity: ['Velocity Vectors', 'boolean', true], acceleration: ['Accel Vectors', 'boolean', true], traces: ['Path Traces', 'boolean', true], - dashedTraces: ['Dashed', 'boolean', false, {tall: true, hideUnless: 'display.traces'}], - velocityScale: ['Velocity
Vec Scale', 'number', 80, {hideUnless: 'display.velocity'}], - accelerationScale: ['Accel
Vec Scale', 'number', 800, {hideUnless: 'display.acceleration'}], + dashedTraces: ['Dashed', 'boolean', false, {tall: true, showIf: 'display.traces'}], + velocityScale: ['Velocity
Vec Scale', 'number', 80, {showIf: 'display.velocity'}], + accelerationScale: ['Accel
Vec Scale', 'number', 800, {showIf: 'display.acceleration'}], zoomVectors: ['Zoom Vectors', 'boolean', true] }, compensate: { diff --git a/tool.js b/tool.js index 0513e7b..91a7b2d 100644 --- a/tool.js +++ b/tool.js @@ -9,6 +9,7 @@ import { export class Tool { container = undefined; sim = undefined; + hidden = false; constructor() { const div = document.createElement('div'); diff --git a/tool/options.js b/tool/options.js index fe04a1e..fa81043 100644 --- a/tool/options.js +++ b/tool/options.js @@ -1,45 +1,92 @@ // Options picker import { - TOOL_INFO_CLASSNAME, OPTION_GROUP_CLASSNAME, - WIDE_CLASSNAME, TALL_CLASSNAME, + TOOL_INFO_CLASSNAME, + WIDE_CLASSNAME, } from '../config.js'; -import { Tool } from '../tool.js'; +import {Tool} from '../tool.js'; +import {show, hide} from '../helper.js'; export class OptionsTool extends Tool { - sections = undefined; + sectionNames = undefined; + groups = {}; - constructor(sections) { + constructor(sectionNames) { super(); - this.sections = sections; + this.sectionNames = sectionNames; } setContainer(container) { super.setContainer(container); - for (const sectionName of this.sections) { - const option = this.sim.options.getSection(sectionName); - const item = this.visitItem(option); + // Initialize + for (const sectionName of this.sectionNames) { + const group = this.sim.options.getSection(sectionName); + const item = this.visitItem(group); this.div.appendChild(item); } } + // For now, `showIf` must be the name of a boolean property, with optional negation + shouldShow(option) { + if (option.showIf === undefined) return true; + const {name, value} = this.deconstructOption(option.showIf); + return this.sim.getOption(name) === value; + } + + deconstructOption(showIf) { + let name = showIf; + let value = true; + if (name.startsWith('!')) { + value = false; + name = name.slice(1); + } + return {name, value}; + } + visitItem(item, path) { path = [path, item.name].filter(x => !!x).join('.'); switch (item.type) { case 'group': { - const group = document.createElement('div'); - group.classList.add(OPTION_GROUP_CLASSNAME); + const groupEl = document.createElement('div'); + groupEl.classList.add(OPTION_GROUP_CLASSNAME); + const group = {groupEl, items: []}; + this.groups[path] = group; if (item.title) { const heading = document.createElement('h3'); heading.innerHTML = item.title; - group.appendChild(heading); + groupEl.appendChild(heading); + groupEl.items.push({itemEl: heading}); } for (const next of item.items) { - const child = this.visitItem(next, path); - group.appendChild(child); + const optionEl = this.visitItem(next, path); + // const option = {itemEl: optionEl}; + group.items.push(next); + if (this.shouldShow(next)) { + groupEl.appendChild(optionEl); + } + if (next.showIf) { + const {name} = this.deconstructOption(next.showIf); + this.sim.onOptionSet(name, () => { + if (this.shouldShow(next)) { + show({ + items: group.items, + item: next, + parentEl: groupEl, + itemEl: optionEl, + }); + } else { + hide({ + item: next, + parentEl: groupEl, + itemEl: optionEl, + }); + } + }); + } + } - return group; + return groupEl; } case 'boolean': { const button = document.createElement('button');