tweaks 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/tweaks.js ADDED
@@ -0,0 +1,184 @@
1
+ import { typeRegistry, syncedTweaks, sortedKeys, saveState, syncState, nextOrder } from './state.js';
2
+ import { GUI } from './gui.js';
3
+ export { GUI };
4
+
5
+ /**
6
+ * @import {TweakRegister, StateProp} from './state.js'
7
+ */
8
+
9
+ /** @type {Map<string, TweakGroup>} */
10
+ const groups = new Map();
11
+
12
+ /**
13
+ * @typedef {Object} TweakGroupMethods - Built-in type methods, added dynamically by registerType
14
+ * @property {(key: string, defaultValue?: number, min?: number, max?: number, step?: number) => number} Float
15
+ * @property {(key: string, defaultValue?: number, min?: number, max?: number) => number} Int
16
+ * @property {(key: string, defaultValue?: string) => string} Str
17
+ * @property {(key: string, defaultValue?: boolean) => boolean} Bool
18
+ * @property {(key: string, defaultValue?: string) => string} Color
19
+ */
20
+
21
+ /**
22
+ * @typedef {TweakGroupBase & TweakGroupMethods & {[key: string]: any}} TweakGroup
23
+ */
24
+
25
+ /**
26
+ * @param {TweakGroup} tweakGroup
27
+ * @param {string} key
28
+ * @param {TweakRegister} params
29
+ */
30
+ function register(tweakGroup, key, params) {
31
+ const { type, defaultValue, n1, n2, n3, n4, n5, n6, n7 } = params;
32
+ const nameSpace = tweakGroup.nameSpace;
33
+ // Add getters / setters so group.key reads/writes nameSpace[key].value
34
+ if (!(key in tweakGroup)) {
35
+ Object.defineProperty(tweakGroup, key, {
36
+ get() { return this.nameSpace[key].value; },
37
+ set(v) {
38
+ this.nameSpace[key].value = v;
39
+ this.dirty = true;
40
+ },
41
+ });
42
+ }
43
+ let prop = nameSpace[key];
44
+ if (!prop) {
45
+ nameSpace[key] = {
46
+ type,
47
+ labelKey: `value::"${key}"`,
48
+ value: defaultValue,
49
+ default: defaultValue,
50
+ saved: defaultValue,
51
+ used: true,
52
+ order: nextOrder(),
53
+ n1,
54
+ n2,
55
+ n3,
56
+ n4,
57
+ n5,
58
+ n6,
59
+ n7,
60
+ }
61
+ tweakGroup.keys.push(key);
62
+ tweakGroup.dirty = true;
63
+ saveState();
64
+ return defaultValue;
65
+ }
66
+ prop.used = true;
67
+ const newOrder = nextOrder();
68
+ if (prop.order !== newOrder) {
69
+ prop.order = newOrder;
70
+ tweakGroup.dirty = true;
71
+ }
72
+ if (prop.type !== type) {
73
+ prop.type = type;
74
+ prop.value = defaultValue;
75
+ prop.saved = defaultValue;
76
+ prop.default = defaultValue;
77
+ saveState();
78
+ return defaultValue;
79
+ }
80
+ if (defaultValue !== prop.default) {
81
+ prop.value = defaultValue;
82
+ prop.default = defaultValue;
83
+ prop.saved = defaultValue;
84
+ saveState();
85
+ }
86
+ if (prop.n1 !== n1) prop.n1 = n1;
87
+ if (prop.n2 !== n2) prop.n2 = n2;
88
+ if (prop.n3 !== n3) prop.n3 = n3;
89
+ if (prop.n4 !== n4) prop.n4 = n4;
90
+ if (prop.n5 !== n5) prop.n5 = n5;
91
+ if (prop.n6 !== n6) prop.n6 = n6;
92
+ if (prop.n7 !== n7) prop.n7 = n7;
93
+ return prop.value;
94
+ }
95
+
96
+ class TweakGroupBase {
97
+ /**
98
+ * @param {string} stateName
99
+ * @param {Record<string, StateProp>} nameSpace
100
+ * @param {string[]} keys
101
+ */
102
+ constructor(stateName, nameSpace, keys) {
103
+ this.stateName = stateName;
104
+ this.nameSpace = nameSpace;
105
+ this.keys = keys;
106
+ this.dirty = false;
107
+ this.isTweakGroup = true;
108
+ this.changed = false;
109
+ }
110
+ /** Register a pre-built type descriptor, e.g. group.add('x', Float(0, 0, 100))
111
+ * @param {string} key
112
+ * @param {TweakRegister} params
113
+ */
114
+ add(key, params) {
115
+ return register(/** @type {TweakGroup} */(/** @type {any} */(this)), key, params);
116
+ }
117
+ }
118
+
119
+ /** @param {string} stateName */
120
+ function createNamespace(stateName) {
121
+ if (!syncedTweaks[stateName]) syncedTweaks[stateName] = {};
122
+ if (!sortedKeys[stateName]) sortedKeys[stateName] = [];
123
+ return /** @type {TweakGroup} */(new TweakGroupBase(stateName, syncedTweaks[stateName], sortedKeys[stateName]));
124
+ }
125
+
126
+ export const TWEAKS = {
127
+ /**
128
+ * @param {string} name
129
+ * @param {Record<string, TweakRegister>} [schema]
130
+ */
131
+ group(name, schema) {
132
+ let group = groups.get(name);
133
+ if (!group) {
134
+ group = createNamespace(name);
135
+ groups.set(name, group);
136
+ }
137
+ if (schema) {
138
+ for (const key in schema) {
139
+ register(group, key, schema[key]);
140
+ }
141
+ }
142
+ return group;
143
+ },
144
+ /**
145
+ * @param {string} typeName
146
+ * @param {any} typeDefault
147
+ * @param {(prop: StateProp, labelKey: string) => boolean} [constructorFn]
148
+ * @return {(...args: any[]) => TweakRegister}
149
+ */
150
+ registerType(typeName, typeDefault, constructorFn) {
151
+ /** @type {(...args: any[]) => TweakRegister} */
152
+ const paramsFactory = (defaultValue = typeDefault, ...ns) => ({
153
+ type: typeName, defaultValue, n1: ns[0], n2: ns[1], n3: ns[2], n4: ns[3], n5: ns[4], n6: ns[5], n7: ns[6]
154
+ });
155
+ typeRegistry.set(typeName, { params: paramsFactory, constructor: constructorFn || (() => false) });
156
+ /** @type {any} */ (TweakGroupBase.prototype)[typeName] =
157
+ /** @this {TweakGroup} @param {string} key @param {...any} args */
158
+ function(key, ...args) {
159
+ // Fast getter: no extra args and key already exists
160
+ if (args.length === 0 && this.nameSpace[key]) {
161
+ const prop = this.nameSpace[key];
162
+ prop.used = true;
163
+ const newOrder = nextOrder();
164
+ if (prop.order !== newOrder) {
165
+ prop.order = newOrder;
166
+ this.dirty = true;
167
+ }
168
+ return prop.value;
169
+ }
170
+ return register(this, key, paramsFactory(...args));
171
+ };
172
+ return paramsFactory;
173
+ },
174
+ sync: syncState,
175
+ save: saveState,
176
+ }
177
+
178
+ // Register built-in tweak types
179
+
180
+ export const Float = TWEAKS.registerType('Float', 0, (prop, labelKey) => GUI.Number(prop, labelKey, prop.n1, prop.n2, prop.n3));
181
+ export const Int = TWEAKS.registerType('Int', 0, (prop, labelKey) => GUI.Number(prop, labelKey, prop.n1, prop.n2, 1));
182
+ export const Str = TWEAKS.registerType('Str', '', (prop, labelKey) => GUI.Text(prop, labelKey));
183
+ export const Bool = TWEAKS.registerType('Bool', false, (prop, labelKey) => GUI.Checkbox(prop, labelKey));
184
+ export const Color = TWEAKS.registerType('Color', 'rgb(0, 0, 0)', (prop, labelKey) => GUI.Color(prop, labelKey));