Enhancement: options showIf

This commit is contained in:
Ladd 2026-01-04 13:29:19 -06:00
parent 4eec03dd1f
commit 54ed2838f7
4 changed files with 103 additions and 19 deletions

View File

@ -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;
} }

View File

@ -8,9 +8,9 @@ export const simOptions = {
velocity: ['Velocity Vectors', 'boolean', true], velocity: ['Velocity Vectors', 'boolean', true],
acceleration: ['Accel Vectors', 'boolean', true], acceleration: ['Accel Vectors', 'boolean', true],
traces: ['Path Traces', 'boolean', true], traces: ['Path Traces', 'boolean', true],
dashedTraces: ['Dashed', 'boolean', false, {tall: true, hideUnless: 'display.traces'}], dashedTraces: ['Dashed', 'boolean', false, {tall: true, showIf: 'display.traces'}],
velocityScale: ['Velocity<br>Vec Scale', 'number', 80, {hideUnless: 'display.velocity'}], velocityScale: ['Velocity<br>Vec Scale', 'number', 80, {showIf: 'display.velocity'}],
accelerationScale: ['Accel<br>Vec Scale', 'number', 800, {hideUnless: 'display.acceleration'}], accelerationScale: ['Accel<br>Vec Scale', 'number', 800, {showIf: 'display.acceleration'}],
zoomVectors: ['Zoom Vectors', 'boolean', true] zoomVectors: ['Zoom Vectors', 'boolean', true]
}, },
compensate: { compensate: {

View File

@ -9,6 +9,7 @@ import {
export class Tool { export class Tool {
container = undefined; container = undefined;
sim = undefined; sim = undefined;
hidden = false;
constructor() { constructor() {
const div = document.createElement('div'); const div = document.createElement('div');

View File

@ -1,45 +1,92 @@
// Options picker // Options picker
import { import {
TOOL_INFO_CLASSNAME,
OPTION_GROUP_CLASSNAME, OPTION_GROUP_CLASSNAME,
WIDE_CLASSNAME,
TALL_CLASSNAME, TALL_CLASSNAME,
TOOL_INFO_CLASSNAME,
WIDE_CLASSNAME,
} from '../config.js'; } from '../config.js';
import { Tool } from '../tool.js'; import {Tool} from '../tool.js';
import {show, hide} from '../helper.js';
export class OptionsTool extends Tool { export class OptionsTool extends Tool {
sections = undefined; sectionNames = undefined;
groups = {};
constructor(sections) { constructor(sectionNames) {
super(); super();
this.sections = sections; this.sectionNames = sectionNames;
} }
setContainer(container) { setContainer(container) {
super.setContainer(container); super.setContainer(container);
for (const sectionName of this.sections) { // Initialize
const option = this.sim.options.getSection(sectionName); for (const sectionName of this.sectionNames) {
const item = this.visitItem(option); const group = this.sim.options.getSection(sectionName);
const item = this.visitItem(group);
this.div.appendChild(item); 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) { visitItem(item, path) {
path = [path, item.name].filter(x => !!x).join('.'); path = [path, item.name].filter(x => !!x).join('.');
switch (item.type) { switch (item.type) {
case 'group': { case 'group': {
const group = document.createElement('div'); const groupEl = document.createElement('div');
group.classList.add(OPTION_GROUP_CLASSNAME); groupEl.classList.add(OPTION_GROUP_CLASSNAME);
const group = {groupEl, items: []};
this.groups[path] = group;
if (item.title) { if (item.title) {
const heading = document.createElement('h3'); const heading = document.createElement('h3');
heading.innerHTML = item.title; heading.innerHTML = item.title;
group.appendChild(heading); groupEl.appendChild(heading);
groupEl.items.push({itemEl: heading});
} }
for (const next of item.items) { for (const next of item.items) {
const child = this.visitItem(next, path); const optionEl = this.visitItem(next, path);
group.appendChild(child); // 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': { case 'boolean': {
const button = document.createElement('button'); const button = document.createElement('button');