fixed option save/load

This commit is contained in:
Ladd 2025-12-31 01:28:32 -06:00
parent d6c3db8e45
commit 49feb0c106
8 changed files with 153 additions and 92 deletions

View File

@ -39,6 +39,7 @@ export const TOOLBAR_HEADER_CLASSNAME = 'lhg-toolbar-header';
export const WIDE_CLASSNAME = 'lhg-wide'; export const WIDE_CLASSNAME = 'lhg-wide';
export const TALL_CLASSNAME = 'lhg-tall'; export const TALL_CLASSNAME = 'lhg-tall';
export const OVERLAY_INFO_BOX_CLASSNAME = 'lhg-overlay-info-box'; export const OVERLAY_INFO_BOX_CLASSNAME = 'lhg-overlay-info-box';
export const OPTION_GROUP_CLASSNAME = 'lhg-option-group';
// EVENT NAMES // EVENT NAMES
export const EVENT_MODE_LEAVE = 'lhg-mode-leave'; export const EVENT_MODE_LEAVE = 'lhg-mode-leave';

View File

@ -208,14 +208,16 @@ export class Objects {
// with the mass creation rate accelerating over time // with the mass creation rate accelerating over time
// Scaling this parameter because of millisecond conversion // Scaling this parameter because of millisecond conversion
const massCreationRate = this.sim.getOption('param.massCreationRate') / 1000;
if (this.creatingObject !== undefined) { if (this.creatingObject !== undefined) {
const obj = this.objects[this.creatingObject]; const obj = this.objects[this.creatingObject];
const rate = massCreationRate * obj.age; // Putting in a somewhat arbitrary scaling factor here
console.log('obj.age', obj.age, 'mass creation rate', rate, 'elapsedTime', elapsedTime); let massCreationRate = this.sim.getOption('param.massCreationRate') / 1000;
// TODO: After objects merge during creation, mass creation rate can accelerate // Mass creation rate acceleration
obj.mass += rate * elapsedTime; if (this.sim.getOption('param.massAcceleration')) {
massCreationRate *= obj.age;
}
obj.mass += massCreationRate * elapsedTime;
} }
// Calculate forces due to gravity. // Calculate forces due to gravity.

View File

@ -24,29 +24,38 @@ export class Options {
for (const groupName of Object.keys(options)) { for (const groupName of Object.keys(options)) {
for (const [name, [, , defaultValue]] of Object.entries(this.options[groupName])) { for (const [name, [, , defaultValue]] of Object.entries(this.options[groupName])) {
const path = [groupName, name].join('.'); const path = [groupName, name].join('.');
let value = this.getOption(path) let value = this.getFromLocalStorage(path);
if (value === undefined) { if (value === undefined) {
value = defaultValue; value = defaultValue;
this.setOption(path, value);
} }
this.values[path] = value;
} }
} }
} }
getOption(path) { toStored(value) {
let value = this.values[path]; return JSON.stringify(value);
if (value === undefined) { }
value = localStorage.getItem(this.getStorageKey(path));
if (value === 'false') value = false; fromStored(value) {
else if (value === 'true') value = true; return JSON.parse(value);
this.values[path] = value; }
}
getFromLocalStorage(path) {
const storageKey = this.getStorageKey(path);
const value = this.fromStored(window.localStorage.getItem(storageKey));
this.values[path] = value;
return value; return value;
} }
getOption(path) {
return this.values[path];
}
setOption(path, value) { setOption(path, value) {
this.values[path] = value; this.values[path] = value;
window.localStorage.setItem(this.getStorageKey(path), value); const storageKey = this.getStorageKey(path);
window.localStorage.setItem(storageKey, this.toStored(value));
const e = new CustomEvent(EVENT_OPTION_SET, {detail: {path, value}}); const e = new CustomEvent(EVENT_OPTION_SET, {detail: {path, value}});
this.sim.div.dispatchEvent(e); this.sim.div.dispatchEvent(e);
} }

29
sim-options.js Normal file
View File

@ -0,0 +1,29 @@
export const simOptions = {
pauseDuring: {
creation: ['Pause While Creating', 'boolean', true],
selection: ['Pause While Selecting', 'boolean', true],
},
display: {
velocity: ['Velocity Vector', 'boolean', true],
acceleration: ['Accel Vector', 'boolean', true],
traces: ['Path Trace', 'boolean', true],
dashedTraces: ['Dashed', 'boolean', false, {tall: true}],
},
collision: {
merge: ['Merge Masses<br>on Collision', 'boolean', true, {wide: true}],
},
param: {
gravity: ['Gravity', 'number', 4E4],
timeScale: ['Time Scale', 'number', 0.2],
massCreationRate: ['Mass Creation Rate', 'number', 10],
massAcceleration: ['Mass Rate Accel', 'boolean', true, {wide: true}],
},
debug: {
objectsInfo: ['Objects Info', 'boolean', false],
cursorInfo: ['Cursor Info', 'boolean', false],
frameRate: ['Frame Rate', 'boolean', false, {wide: true}],
currentMode: ['Current Mode', 'boolean', false],
panningInfo: ['Panning Info', 'boolean', false],
},
};

67
sim-tools.js Normal file
View File

@ -0,0 +1,67 @@
import { Overlay } from './overlay.js';
import { Pointer } from './pointer.js';
import { ModeSwitch } from './tool/modes.js';
import { OptionsTool } from './tool/options.js';
import { PlayPause } from './tool/play-pause.js';
import { Zoom } from './tool/zoom.js';
import { UtilityTool } from './tool/utility.js';
import { Toolbar } from './toolbar.js';
import { ToolbarGroup } from './toolbar-group.js';
export function initializeTools(sim) {
sim.toolbars = {
tools: new Toolbar(sim, 'Tools'),
modes: new Toolbar(sim, 'Modes'),
utils: new Toolbar(sim, 'Utility', { expanded: false }),
options: new Toolbar(sim, 'Options'),
params: new Toolbar(sim, 'Parameters'),
debug: new Toolbar(sim, 'Debug', { expanded: false }),
};
const { tools, modes, options, params, debug, utils } = sim.toolbars;
sim.toolbarGroups = {
left: new ToolbarGroup(sim)
.addToolbar(tools)
.addToolbar(modes)
.addToolbar(utils),
right: new ToolbarGroup(sim).topRight()
.addToolbar(options)
.addToolbar(params)
.addToolbar(debug),
};
sim.overlay = new Overlay(sim);
sim.pointer = new Pointer(sim);
// Configure toolbars
// Primary
tools.addTool(new Zoom(tools));
tools.addTool(new PlayPause(tools));
// Secondary
modes.addTool(new ModeSwitch(modes));
// Utility
utils.addTool(new UtilityTool(utils));
// Options
options.addTool(new OptionsTool(options, [
'pauseDuring',
'display',
'collision'
]));
// Parameters
params.addTool(new OptionsTool(params, [
'param'
]));
// Debug
debug.addTool(new OptionsTool(debug, [
'debug'
]));
for (const id in sim.toolbars) {
const toolbar = sim.toolbars[id];
toolbar.applyExpanded();
}
}

View File

@ -6,16 +6,9 @@ import {
} from './config.js'; } from './config.js';
import { Display } from './display.js'; import { Display } from './display.js';
import { Objects } from './objects.js'; import { Objects } from './objects.js';
import { Overlay } from './overlay.js';
import { Pointer } from './pointer.js';
import { Options } from './options.js'; import { Options } from './options.js';
import { ModeSwitch } from './tool/modes.js'; import { simOptions } from './sim-options.js';
import { OptionsTool } from './tool/options.js'; import { initializeTools } from './sim-tools.js';
import { PlayPause } from './tool/play-pause.js';
import { Zoom } from './tool/zoom.js';
import { UtilityTool } from './tool/utility.js';
import { Toolbar } from './toolbar.js';
import { ToolbarGroup } from './toolbar-group.js';
const simOptions = { const simOptions = {
pauseDuring: { pauseDuring: {
@ -91,53 +84,8 @@ export class Sim {
this.options = new Options(this, simOptions); this.options = new Options(this, simOptions);
this.display = new Display(this); this.display = new Display(this);
this.objects = new Objects(this); this.objects = new Objects(this);
this.toolbars = {
tools: new Toolbar(this, 'Tools'),
modes: new Toolbar(this, 'Modes'),
utils: new Toolbar(this, 'Utility', { expanded: false }),
options: new Toolbar(this, 'Options'),
params: new Toolbar(this, 'Parameters'),
debug: new Toolbar(this, 'Debug', { expanded: false }),
};
const { tools, modes, options, params, debug, utils } = this.toolbars;
this.toolbarGroups = {
left: new ToolbarGroup(this)
.addToolbar(tools)
.addToolbar(modes)
.addToolbar(utils),
right: new ToolbarGroup(this).topRight()
.addToolbar(options)
.addToolbar(params)
.addToolbar(debug),
};
this.overlay = new Overlay(this);
this.pointer = new Pointer(this);
// Configure toolbars initializeTools(this);
// Primary
tools.addTool(new Zoom(tools));
tools.addTool(new PlayPause(tools));
// Secondary
modes.addTool(new ModeSwitch(modes));
// Utility
utils.addTool(new UtilityTool(utils));
// Options
options.addTool(new OptionsTool(options, ['pauseDuring', 'display', 'collision']));
// Parameters
params.addTool(new OptionsTool(params, ['param']));
// Debug
debug.addTool(new OptionsTool(debug, ['debug']));
for (const id in this.toolbars) {
const toolbar = this.toolbars[id];
toolbar.applyExpanded();
}
// Initiate main loop // Initiate main loop
this.rawTime = document.timeline.currentTime; this.rawTime = document.timeline.currentTime;

View File

@ -108,8 +108,7 @@ div.lhg-tool .lhg-wide {
} }
div.lhg-tool .lhg-tall { div.lhg-tool .lhg-tall {
padding-top: 1em; height: 3.666em;
padding-bottom: 1em;
} }
div.lhg-overlay-info-box { div.lhg-overlay-info-box {
@ -119,3 +118,8 @@ div.lhg-overlay-info-box {
width: fit-content; width: fit-content;
z-index: 1; z-index: 1;
} }
div.lhg-option-group > * {
display: inline-block;
vertical-align: top;
}

View File

@ -1,16 +1,29 @@
// Options picker // Options picker
import { import {
TOOL_INFO_CLASSNAME, TOOL_INFO_CLASSNAME,
OPTION_GROUP_CLASSNAME,
WIDE_CLASSNAME, WIDE_CLASSNAME,
TALL_CLASSNAME,
} from '../config.js'; } from '../config.js';
import { Tool } from '../tool.js'; import { Tool } from '../tool.js';
export class OptionsTool extends Tool { export class OptionsTool extends Tool {
constructor(container, sections) {
super(container);
for (const sectionName of sections) {
const option = this.sim.options.getSection(sectionName);
const child = this.visitItem(option);
this.div.appendChild(child);
}
}
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 group = document.createElement('div');
group.classList.add(OPTION_GROUP_CLASSNAME);
if (item.title) { if (item.title) {
const heading = document.createElement('h3'); const heading = document.createElement('h3');
heading.innerHTML = item.title; heading.innerHTML = item.title;
@ -25,13 +38,9 @@ export class OptionsTool extends Tool {
case 'boolean': { case 'boolean': {
const button = document.createElement('button'); const button = document.createElement('button');
button.innerHTML = item.title; button.innerHTML = item.title;
if (item.wide === true) { if (item.wide === true) button.classList.add(WIDE_CLASSNAME);
button.classList.add(WIDE_CLASSNAME); if (item.tall === true) button.classList.add(TALL_CLASSNAME);
}
const value = this.sim.getOption(path); const value = this.sim.getOption(path);
if (value === undefined) {
this.sim.setOption(path, item.default);
}
button.style.opacity = value ? '100%' : '50%'; button.style.opacity = value ? '100%' : '50%';
this.sim.onOptionSet(path, value => { this.sim.onOptionSet(path, value => {
console.log('option set cb', path, value); console.log('option set cb', path, value);
@ -43,6 +52,10 @@ export class OptionsTool extends Tool {
console.log('click, option value', value); console.log('click, option value', value);
this.sim.setOption(path, !value); this.sim.setOption(path, !value);
}); });
button.addEventListener('click', () => {
const value = this.sim.getOption(path);
this.setOption(path, !value);
});
return button; return button;
} }
case 'number': { case 'number': {
@ -59,8 +72,7 @@ export class OptionsTool extends Tool {
input.classList.add(WIDE_CLASSNAME); input.classList.add(WIDE_CLASSNAME);
} }
title.innerHTML = item.title; title.innerHTML = item.title;
input.value = item.default; input.value = this.sim.getOption(path);
this.sim.setOption(path, item.default);
input.addEventListener('input', () => { input.addEventListener('input', () => {
input.value = input.value.slice(0, maxLength); input.value = input.value.slice(0, maxLength);
@ -77,18 +89,7 @@ export class OptionsTool extends Tool {
return div; return div;
} }
default: default:
console.error('Unknown option type', item);
throw new Error('Unknown option type'); throw new Error('Unknown option type');
} }
} }
constructor(container, sections) {
super(container);
for (const sectionName of sections) {
const option = this.sim.options.getSection(sectionName);
const child = this.visitItem(option);
this.div.appendChild(child);
}
}
} }