@preference-sl/pref-viewer 2.11.0-beta.3 → 2.11.0-beta.4

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.
@@ -0,0 +1,360 @@
1
+ import { AdvancedDynamicTexture, Button, Control, Image, Slider, StackPanel } from "@babylonjs/gui";
2
+ import { OpeningAnimation } from "./babylonjs-animation-opening.js";
3
+
4
+ /**
5
+ * OpeningAnimationMenu - Manages and renders the animation control menu for opening/closing animations in a Babylon.js scene.
6
+ *
7
+ * Responsibilities:
8
+ * - Renders a GUI menu with buttons for controlling animation playback (open, close, pause, go to opened/closed, loop).
9
+ * - Updates button states and slider position based on animation state, progress, and loop mode.
10
+ * - Handles user interactions and invokes provided callbacks for animation actions.
11
+ * - Synchronizes the slider value with animation progress, avoiding callback loops.
12
+ *
13
+ * Public Setters:
14
+ * - set animationState(state): Updates the animation state and button states.
15
+ * - set animationProgress(progress): Updates the animation progress and slider value.
16
+ * - set animationLoop(loop): Updates the loop mode and loop button states.
17
+ *
18
+ * Private Methods:
19
+ * - #createMenu(): Initializes and renders the menu UI.
20
+ * - #addButton(name, imageURL, enabled, active, visible, callback): Adds a button to the menu with specified properties.
21
+ * - #createButtons(): Creates all control buttons and sets their initial states.
22
+ * - #getButtonByName(name): Retrieves a button control by its name.
23
+ * - #setButtonState(name, enabled, active, visible): Updates the visual state of a button.
24
+ * - #getPlayerButtonsState(): Returns the state (enabled, active, visible) for playback control buttons.
25
+ * - #getLoopButtonsState(): Returns the state for loop control buttons.
26
+ * - #getButtonsState(): Combines player and loop button states.
27
+ * - #setPlayerButtonsState(): Updates all playback control buttons.
28
+ * - #setLoopButtonsState(): Updates all loop control buttons.
29
+ * - #createSlider(): Creates and configures the animation progress slider.
30
+ *
31
+ * Usage Example:
32
+ * const menu = new OpeningAnimationMenu(adt, state, progress, loop, {
33
+ * onOpen: () => { ... },
34
+ * onClose: () => { ... },
35
+ * onPause: () => { ... },
36
+ * onGoToOpened: () => { ... },
37
+ * onGoToClosed: () => { ... },
38
+ * onToggleLoop: () => { ... },
39
+ * onSetAnimationProgress: (progress) => { ... }
40
+ * });
41
+ * menu.animationState = OpeningAnimation.states.opening;
42
+ * menu.animationProgress = 0.5;
43
+ * menu.animationLoop = true;
44
+ */
45
+ export class OpeningAnimationMenu {
46
+ #animationState = OpeningAnimation.states.closed;
47
+ #animationProgress = 0;
48
+ #animationLoop = false;
49
+ #callbacks = null;
50
+
51
+ // GUI Elements
52
+ #advancedDynamicTexture = null;
53
+ #mainPanel = null;
54
+ #secondaryPanel = null;
55
+ #slider = null;
56
+
57
+ // Style properties
58
+ #buttonSize = 28;
59
+ #buttonLoopPaddingLeft = 3;
60
+ #colorActive = "#6BA53A";
61
+ #colorEnabled = "#333333";
62
+ #colorDisabled = "#777777";
63
+ #colorIcon = "#FFFFFF";
64
+ #colorBorder = "#FFFFFF";
65
+ #sliderThumbWidth = 20;
66
+ #sliderBarOffset = 10;
67
+ #icon = {
68
+ close: `<svg id="play-backwards" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M16,18.86V4.86l-11,7,11,7Z"/></svg>`,
69
+ closed: `<svg id="skip-backward" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M20,5V19L13,12M6,5V19H4V5M13,5V19L6,12"/></svg>`,
70
+ open: `<svg id="play" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M8,5.14v14l11-7-11-7Z"/></svg>`,
71
+ opened: `<svg id="skip-forward" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M4,5V19L11,12M18,5V19H20V5M11,5V19L18,12"/></svg>`,
72
+ pause: `<svg id="pause" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M14,19H18V5H14M6,19H10V5H6V19Z"/></svg>`,
73
+ repeat: `<svg id="repeat" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M17,17H7V14L3,18L7,22V19H19V13H17M7,7H17V10L21,6L17,2V5H5V11H7V7Z"/></svg>`,
74
+ repeatOff: `<svg id="repeat-off" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M2,5.27L3.28,4L20,20.72L18.73,22L15.73,19H7V22L3,18L7,14V17H13.73L7,10.27V11H5V8.27L2,5.27M17,13H19V17.18L17,15.18V13M17,5V2L21,6L17,10V7H8.82L6.82,5H17Z"/></svg>`,
75
+ };
76
+
77
+ #isSettingSliderValue = false;
78
+
79
+ /**
80
+ * Constructs the OpeningAnimationMenu and initializes the menu UI.
81
+ * @param {AdvancedDynamicTexture} advancedDynamicTexture - Babylon.js GUI texture for rendering controls.
82
+ * @param {number} animationState - Current animation state (enum).
83
+ * @param {number} animationProgress - Current animation progress (0 to 1).
84
+ * @param {boolean} animationLoop - Whether the animation is set to loop.
85
+ * @param {object} callbacks - Callback functions for menu actions (play, pause, open, close, etc.).
86
+ * @public
87
+ */
88
+ constructor(advancedDynamicTexture, animationState, animationProgress, animationLoop, callbacks) {
89
+ this.#advancedDynamicTexture = advancedDynamicTexture;
90
+ this.#animationState = animationState;
91
+ this.#animationProgress = animationProgress;
92
+ this.#animationLoop = animationLoop;
93
+ this.#callbacks = callbacks;
94
+
95
+ this.#createMenu(animationState);
96
+ }
97
+
98
+ /**
99
+ * Initializes and renders the menu UI, including buttons and slider.
100
+ * @private
101
+ */
102
+ #createMenu() {
103
+ if (!this.#advancedDynamicTexture) {
104
+ return;
105
+ }
106
+ this.#mainPanel = new StackPanel();
107
+ this.#mainPanel.isVertical = true;
108
+ this.#secondaryPanel = new StackPanel();
109
+ this.#secondaryPanel.isVertical = false;
110
+ this.#secondaryPanel.height = `${this.#buttonSize}px`;
111
+ this.#mainPanel.addControl(this.#secondaryPanel);
112
+
113
+ this.#advancedDynamicTexture.addControl(this.#mainPanel);
114
+ this.#mainPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
115
+ this.#mainPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM;
116
+
117
+ this.#createButtons();
118
+ this.#createSlider();
119
+ }
120
+
121
+ /**
122
+ * Internal helper to add a button to the menu.
123
+ * Sets button appearance and attaches the callback for user interaction.
124
+ * @private
125
+ * @param {string} name - Button identifier.
126
+ * @param {string} imageURL - SVG image data URL for the button icon.
127
+ * @param {boolean} enabled - Whether the button is enabled.
128
+ * @param {boolean} active - Whether the button is visually active.
129
+ * @param {boolean} visible - Whether the button is visible.
130
+ * @param {function} callback - Callback to invoke on button click.
131
+ */
132
+ #addButton(name, imageURL, enabled = true, active = false, visible = true, callback) {
133
+ const buttonProps = {
134
+ background: active ? this.#colorActive : enabled ? this.#colorEnabled : this.#colorDisabled,
135
+ color: this.#colorBorder,
136
+ cornerRadius: 0,
137
+ height: `${this.#buttonSize}px`,
138
+ hoverCursor: "pointer",
139
+ width: `${this.#buttonSize}px`,
140
+ isVisible: visible,
141
+ };
142
+ const button = Button.CreateImageOnlyButton(`button_animation_${name}`, imageURL);
143
+ Object.assign(button, buttonProps);
144
+ button.image.stretch = Image.STRETCH_UNIFORM;
145
+ button.onPointerUpObservable.add(() => {
146
+ if (callback && typeof callback === "function") {
147
+ callback();
148
+ }
149
+ });
150
+ this.#secondaryPanel.addControl(button);
151
+ }
152
+
153
+ /**
154
+ * Creates all control buttons and sets their initial states.
155
+ * @private
156
+ */
157
+ #createButtons() {
158
+ const buttonsState = this.#getButtonsState();
159
+ this.#addButton("closed", `data:image/svg+xml,${encodeURIComponent(this.#icon.closed)}`, buttonsState.closed.enabled, buttonsState.closed.active, buttonsState.closed.visible, this.#callbacks.onGoToClosed);
160
+ this.#addButton("close", `data:image/svg+xml,${encodeURIComponent(this.#icon.close)}`, buttonsState.close.enabled, buttonsState.close.active, buttonsState.close.visible, this.#callbacks.onClose);
161
+ this.#addButton("pause", `data:image/svg+xml,${encodeURIComponent(this.#icon.pause)}`, buttonsState.pause.enabled, buttonsState.pause.active, buttonsState.pause.visible, this.#callbacks.onPause);
162
+ this.#addButton("open", `data:image/svg+xml,${encodeURIComponent(this.#icon.open)}`, buttonsState.open.enabled, buttonsState.open.active, buttonsState.open.visible, this.#callbacks.onOpen);
163
+ this.#addButton("opened", `data:image/svg+xml,${encodeURIComponent(this.#icon.opened)}`, buttonsState.opened.enabled, buttonsState.opened.active, buttonsState.opened.visible, this.#callbacks.onGoToOpened);
164
+ this.#addButton("repeat", `data:image/svg+xml,${encodeURIComponent(this.#icon.repeat)}`, buttonsState.repeat.enabled, buttonsState.repeat.active, buttonsState.repeat.visible, this.#callbacks.onToggleLoop);
165
+ this.#addButton("repeatOff", `data:image/svg+xml,${encodeURIComponent(this.#icon.repeatOff)}`, buttonsState.repeatOff.enabled, buttonsState.repeatOff.active, buttonsState.repeatOff.visible, this.#callbacks.onToggleLoop);
166
+
167
+ // Adjust padding for loop buttons
168
+ this.#getButtonByName("repeat").paddingLeft = `${this.#buttonLoopPaddingLeft}px`;
169
+ this.#getButtonByName("repeat").width = `${this.#buttonSize + this.#buttonLoopPaddingLeft}px`;
170
+ this.#getButtonByName("repeatOff").paddingLeft = `${this.#buttonLoopPaddingLeft}px`;
171
+ this.#getButtonByName("repeatOff").width = `${this.#buttonSize + this.#buttonLoopPaddingLeft}px`;
172
+ }
173
+
174
+ /**
175
+ * Retrieves a button control by its name.
176
+ * @private
177
+ * @param {string} name - Button identifier.
178
+ * @returns {Button|null} The button control or null if not found.
179
+ */
180
+ #getButtonByName(name) {
181
+ return this.#advancedDynamicTexture.getControlByName(`button_animation_${name}`);
182
+ }
183
+
184
+ /**
185
+ * Updates the visual state of a button (enabled, active, visible).
186
+ * @private
187
+ * @param {string} name - Button identifier.
188
+ * @param {boolean} enabled
189
+ * @param {boolean} active
190
+ * @param {boolean} visible
191
+ */
192
+ #setButtonState(name, enabled, active, visible = true) {
193
+ const button = this.#getButtonByName(name);
194
+ if (!button) {
195
+ return;
196
+ }
197
+ button.background = active ? this.#colorActive : enabled ? this.#colorEnabled : this.#colorDisabled;
198
+ button.isVisible = visible;
199
+ }
200
+
201
+ /**
202
+ * Returns the state (enabled, active, visible) for playback control buttons.
203
+ * @private
204
+ * @returns {object}
205
+ */
206
+ #getPlayerButtonsState() {
207
+ const buttonsState = {
208
+ opened: {
209
+ enabled: this.#animationState !== OpeningAnimation.states.opened,
210
+ active: false,
211
+ visible: true,
212
+ },
213
+ open: {
214
+ enabled: this.#animationState !== OpeningAnimation.states.opened && this.#animationState !== OpeningAnimation.states.opening,
215
+ active: this.#animationState === OpeningAnimation.states.opening,
216
+ visible: true,
217
+ },
218
+ pause: {
219
+ enabled: this.#animationState !== OpeningAnimation.states.paused && this.#animationState !== OpeningAnimation.states.closed && this.#animationState !== OpeningAnimation.states.opened,
220
+ active: this.#animationState === OpeningAnimation.states.paused,
221
+ visible: true,
222
+ },
223
+ close: {
224
+ enabled: this.#animationState !== OpeningAnimation.states.closed && this.#animationState !== OpeningAnimation.states.closing,
225
+ active: this.#animationState === OpeningAnimation.states.closing,
226
+ visible: true,
227
+ },
228
+ closed: {
229
+ enabled: this.#animationState !== OpeningAnimation.states.closed,
230
+ active: false,
231
+ visible: true,
232
+ },
233
+ };
234
+ return buttonsState;
235
+ }
236
+
237
+ /**
238
+ * Returns the state for loop control buttons.
239
+ * @private
240
+ * @returns {object}
241
+ */
242
+ #getLoopButtonsState() {
243
+ const buttonsState = {
244
+ repeat: {
245
+ enabled: this.#animationLoop,
246
+ active: false,
247
+ visible: this.#animationLoop,
248
+ },
249
+ repeatOff: {
250
+ enabled: !this.#animationLoop,
251
+ active: false,
252
+ visible: !this.#animationLoop,
253
+ },
254
+ };
255
+ return buttonsState;
256
+ }
257
+
258
+ /**
259
+ * Combines player and loop button states.
260
+ * @private
261
+ * @returns {object}
262
+ */
263
+ #getButtonsState() {
264
+ return Object.assign(this.#getPlayerButtonsState(), this.#getLoopButtonsState());
265
+ }
266
+
267
+ /**
268
+ * Updates all playback control buttons according to current animation state.
269
+ * @private
270
+ */
271
+ #setPlayerButtonsState() {
272
+ const buttonsState = this.#getPlayerButtonsState();
273
+ this.#setButtonState("opened", buttonsState.opened.enabled, buttonsState.opened.active, buttonsState.opened.visible);
274
+ this.#setButtonState("open", buttonsState.open.enabled, buttonsState.open.active, buttonsState.open.visible);
275
+ this.#setButtonState("pause", buttonsState.pause.enabled, buttonsState.pause.active, buttonsState.pause.visible);
276
+ this.#setButtonState("close", buttonsState.close.enabled, buttonsState.close.active, buttonsState.close.visible);
277
+ this.#setButtonState("closed", buttonsState.closed.enabled, buttonsState.closed.active, buttonsState.closed.visible);
278
+ }
279
+
280
+ /**
281
+ * Updates all loop control buttons according to current loop mode.
282
+ * @private
283
+ */
284
+ #setLoopButtonsState() {
285
+ const buttonsState = this.#getLoopButtonsState();
286
+ this.#setButtonState("repeat", buttonsState.repeat.enabled, buttonsState.repeat.active, buttonsState.repeat.visible);
287
+ this.#setButtonState("repeatOff", buttonsState.repeatOff.enabled, buttonsState.repeatOff.active, buttonsState.repeatOff.visible);
288
+ }
289
+
290
+ /**
291
+ * Creates and configures the animation progress slider.
292
+ * @private
293
+ */
294
+ #createSlider() {
295
+ const sliderProps = {
296
+ minimum: 0,
297
+ maximum: 1,
298
+ value: this.#animationProgress,
299
+ height: `${this.#buttonSize}px`,
300
+ width: `${this.#buttonSize * 7 + this.#buttonLoopPaddingLeft}px`, // Width based on number of buttons visible
301
+ barOffset: `${this.#sliderBarOffset}px`,
302
+ isThumbCircle: true,
303
+ thumbWidth: `${this.#sliderThumbWidth}px`,
304
+ background: this.#colorDisabled,
305
+ color: this.#colorEnabled,
306
+ borderColor: this.#colorBorder,
307
+ thumbColor: this.#colorEnabled,
308
+ };
309
+ this.#slider = new Slider("slider_animation_progress");
310
+ Object.assign(this.#slider, sliderProps);
311
+ this.#slider.onValueChangedObservable.add((value) => {
312
+ if (this.#isSettingSliderValue) {
313
+ this.#isSettingSliderValue = false;
314
+ return;
315
+ }
316
+ if (this.#callbacks && typeof this.#callbacks.onSetAnimationProgress === "function") {
317
+ this.#callbacks.onSetAnimationProgress(value);
318
+ }
319
+ });
320
+ this.#mainPanel.addControl(this.#slider);
321
+ }
322
+
323
+ /**
324
+ * ---------------------------
325
+ * Public setters
326
+ * ---------------------------
327
+ */
328
+
329
+ /**
330
+ * Sets the animation loop mode and updates loop button states.
331
+ * @public
332
+ * @param {boolean} loop
333
+ */
334
+ set animationLoop(loop) {
335
+ this.#animationLoop = loop;
336
+ this.#setLoopButtonsState();
337
+ }
338
+
339
+ /**
340
+ * Sets the animation progress value and updates the slider position.
341
+ * When called, the slider value is updated programmatically without triggering the slider's value change callback.
342
+ * @public
343
+ * @param {number} progress - The new animation progress value (between 0 and 1).
344
+ */
345
+ set animationProgress(progress) {
346
+ this.#animationProgress = progress;
347
+ this.#isSettingSliderValue = true;
348
+ this.#slider.value = progress;
349
+ }
350
+
351
+ /**
352
+ * Sets the animation state and updates playback button states.
353
+ * @public
354
+ * @param {number} state - The new animation state (enum).
355
+ */
356
+ set animationState(state) {
357
+ this.#animationState = state;
358
+ this.#setPlayerButtonsState();
359
+ }
360
+ }