@preference-sl/pref-viewer 2.11.0-beta.9 → 2.12.0-beta.1

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.
@@ -1,24 +1,40 @@
1
- import { AdvancedDynamicTexture, Button, Control, Image, Slider, StackPanel } from "@babylonjs/gui";
2
- import { OpeningAnimation } from "./babylonjs-animation-opening.js";
1
+ import OpeningAnimation from "./babylonjs-animation-opening.js";
2
+ import { PrefViewer3DAnimationMenuStyles } from "./styles.js";
3
3
 
4
4
  /**
5
- * OpeningAnimationMenu - Manages and renders the animation control menu for opening/closing animations in a Babylon.js scene.
5
+ * OpeningAnimationMenu - Manages and renders the interactive animation control menu for opening/closing animations in a Babylon.js scene.
6
6
  *
7
- * Responsibilities:
8
- * - Renders a GUI menu with buttons for controlling animation playback (open, close, pause, go to opened/closed, loop).
7
+ * Summary:
8
+ * Provides a floating HTML-based GUI menu with playback and loop controls for animated nodes, including buttons, a progress slider, and a selector for switching between multiple animations.
9
+ * Handles user interaction, updates UI state based on animation state, and invokes external callbacks for animation actions.
10
+ * Designed for integration with Babylon.js product configurators and interactive 3D applications.
11
+ *
12
+ * Key Features:
13
+ * - Renders a menu with buttons for controlling animation playback (open, close, pause, go to opened/closed, loop).
14
+ * - Displays a selector UI for switching between multiple opening animations, showing only the animation name without prefix.
9
15
  * - Updates button states and slider position based on animation state, progress, and loop mode.
10
16
  * - Handles user interactions and invokes provided callbacks for animation actions.
11
17
  * - Synchronizes the slider value with animation progress, avoiding callback loops.
18
+ * - Provides setters to update animation state, progress, and loop mode from external controllers.
19
+ * - Disposes and cleans up all DOM resources when no longer needed.
20
+ * - Resets other animation menus when switching between animations.
12
21
  *
13
22
  * Public Setters:
14
23
  * - set animationState(state): Updates the animation state and button states.
15
24
  * - set animationProgress(progress): Updates the animation progress and slider value.
16
25
  * - set animationLoop(loop): Updates the loop mode and loop button states.
17
26
  *
27
+ * Public Methods:
28
+ * - dispose(): Disposes the animation menu and releases all associated DOM resources.
29
+ *
30
+ * Public Getters:
31
+ * - isVisible: Returns whether the animation menu is currently visible in the DOM.
32
+ *
18
33
  * 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.
34
+ * - #createMenu(): Initializes and renders the menu UI, including the selector, buttons, and slider.
35
+ * - #addButton(name, imageSVG, enabled, active, visible, callback): Adds a button to the menu with specified properties.
21
36
  * - #createButtons(): Creates all control buttons and sets their initial states.
37
+ * - #createSelector(): Creates a selector UI element for switching between available opening animations, omitting the prefix in the display name.
22
38
  * - #getButtonByName(name): Retrieves a button control by its name.
23
39
  * - #setButtonState(name, enabled, active, visible): Updates the visual state of a button.
24
40
  * - #getPlayerButtonsState(): Returns the state (enabled, active, visible) for playback control buttons.
@@ -27,97 +43,75 @@ import { OpeningAnimation } from "./babylonjs-animation-opening.js";
27
43
  * - #setPlayerButtonsState(): Updates all playback control buttons.
28
44
  * - #setLoopButtonsState(): Updates all loop control buttons.
29
45
  * - #createSlider(): Creates and configures the animation progress slider.
46
+ * - #onSliderInput(event): Handles the slider input event for animation progress.
47
+ * - #resetOtherAnimations(): Resets all other opening animations except the current one.
30
48
  *
31
49
  * Usage Example:
32
- * const menu = new OpeningAnimationMenu(adt, state, progress, loop, {
50
+ * const menu = new OpeningAnimationMenu(name, canvas, state, progress, loop, openingAnimations, {
33
51
  * onOpen: () => { ... },
34
52
  * onClose: () => { ... },
35
53
  * onPause: () => { ... },
36
54
  * onGoToOpened: () => { ... },
37
55
  * onGoToClosed: () => { ... },
38
56
  * onToggleLoop: () => { ... },
39
- * onSetAnimationProgress: (progress) => { ... }
57
+ * onSetAnimationProgress: (progress) => { ... },
58
+ * onChangeAnimation: () => { ... }
40
59
  * });
41
60
  * menu.animationState = OpeningAnimation.states.opening;
42
61
  * menu.animationProgress = 0.5;
43
62
  * menu.animationLoop = true;
44
63
  */
45
- export class OpeningAnimationMenu {
64
+ export default class OpeningAnimationMenu {
46
65
  #animationState = OpeningAnimation.states.closed;
47
66
  #animationProgress = 0;
48
67
  #animationLoop = false;
68
+ #openingAnimations = [];
49
69
  #callbacks = null;
50
70
 
51
- // GUI Elements
52
- #advancedDynamicTexture = null;
53
- #mainPanel = null;
54
- #secondaryPanel = null;
71
+ #name = "";
72
+ #canvas = null;
73
+ #containerMain = null;
74
+ #containerButtons = null;
55
75
  #slider = null;
56
76
 
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
77
  #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>`,
78
+ 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 d="M16,18.86V4.86l-11,7,11,7Z"/></svg>`,
79
+ 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 d="M20,5V19L13,12M6,5V19H4V5M13,5V19L6,12"/></svg>`,
80
+ 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 d="M8,5.14v14l11-7-11-7Z"/></svg>`,
81
+ 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 d="M4,5V19L11,12M18,5V19H20V5M11,5V19L18,12"/></svg>`,
82
+ 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 d="M14,19H18V5H14M6,19H10V5H6V19Z"/></svg>`,
83
+ 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 d="M17,17H7V14L3,18L7,22V19H19V13H17M7,7H17V10L21,6L17,2V5H5V11H7V7Z"/></svg>`,
84
+ 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 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
85
  };
76
86
 
77
- #isSettingSliderValue = false;
87
+ #handler = {
88
+ onSliderInput: null,
89
+ };
78
90
 
79
91
  /**
80
92
  * Constructs the OpeningAnimationMenu and initializes the menu UI.
81
- * @param {AdvancedDynamicTexture} advancedDynamicTexture - Babylon.js GUI texture for rendering controls.
93
+ * @param {string} name - The name of the animation.
94
+ * @param {HTMLCanvasElement} canvas - The canvas element for rendering.
82
95
  * @param {number} animationState - Current animation state (enum).
83
96
  * @param {number} animationProgress - Current animation progress (0 to 1).
84
97
  * @param {boolean} animationLoop - Whether the animation is set to loop.
98
+ * @param {Array<OpeningAnimation>} openingAnimations - Array of OpeningAnimation instances to manage.
85
99
  * @param {object} callbacks - Callback functions for menu actions (play, pause, open, close, etc.).
86
100
  * @public
87
101
  */
88
- constructor(advancedDynamicTexture, animationState, animationProgress, animationLoop, callbacks) {
89
- this.#advancedDynamicTexture = advancedDynamicTexture;
102
+ constructor(name, canvas, animationState, animationProgress, animationLoop, openingAnimations, callbacks) {
103
+ this.#name = name;
104
+ this.#canvas = canvas;
90
105
  this.#animationState = animationState;
91
106
  this.#animationProgress = animationProgress;
92
107
  this.#animationLoop = animationLoop;
108
+ this.#openingAnimations = openingAnimations;
93
109
  this.#callbacks = callbacks;
94
110
 
111
+ this.#resetOtherAnimations();
95
112
  this.#createMenu(animationState);
96
113
  }
97
114
 
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
115
  /**
122
116
  * Internal helper to add a button to the menu.
123
117
  * Sets button appearance and attaches the callback for user interaction.
@@ -129,25 +123,61 @@ export class OpeningAnimationMenu {
129
123
  * @param {boolean} visible - Whether the button is visible.
130
124
  * @param {function} callback - Callback to invoke on button click.
131
125
  */
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(() => {
126
+ #addButton(name, imageSVG, enabled = true, active = false, visible = true, callback) {
127
+ const stateClass = active ? "active" : enabled ? "enabled" : "disabled";
128
+ const visibilityClass = visible ? "visible" : "hidden";
129
+
130
+ const button = document.createElement("button");
131
+ button.classList.add(stateClass, visibilityClass);
132
+ button.setAttribute("name", `button_animation_${name}`);
133
+ button.innerHTML = imageSVG;
134
+ button.addEventListener("click", (event) => {
146
135
  if (callback && typeof callback === "function") {
147
136
  callback();
148
137
  }
149
138
  });
150
- this.#secondaryPanel.addControl(button);
139
+
140
+ this.#containerButtons.appendChild(button);
141
+ }
142
+
143
+ /**
144
+ * Creates a selector UI element for switching between available opening animations.
145
+ * @private
146
+ * @returns {void}
147
+ */
148
+ #createSelector() {
149
+ if (!Array.isArray(this.#openingAnimations) || this.#openingAnimations.length < 2) {
150
+ return; // No selector needed if only one animation
151
+ }
152
+
153
+ const selector = document.createElement("div");
154
+ selector.classList.add("animation-menu-selector");
155
+
156
+ this.#openingAnimations.forEach((animation) => {
157
+ const button = document.createElement("button");
158
+ button.classList.add("button-selector");
159
+ // Remove prefix before "_" for display
160
+ const nameParts = animation.name.split("_");
161
+ button.textContent = nameParts.length > 1 ? nameParts.slice(1).join("_") : animation.name;
162
+
163
+ // Highlight the current animation
164
+ if (animation.name === this.#name) {
165
+ button.classList.add("active");
166
+ }
167
+
168
+ button.addEventListener("click", (event) => {
169
+ if (animation.name !== this.#name) {
170
+ if (this.#callbacks.onChangeAnimation && typeof this.#callbacks.onChangeAnimation === "function") {
171
+ this.#callbacks.onChangeAnimation();
172
+ }
173
+ animation.showControls(this.#canvas, this.#openingAnimations);
174
+ }
175
+ });
176
+
177
+ selector.appendChild(button);
178
+ });
179
+
180
+ this.#containerMain.prepend(selector);
151
181
  }
152
182
 
153
183
  /**
@@ -156,19 +186,55 @@ export class OpeningAnimationMenu {
156
186
  */
157
187
  #createButtons() {
158
188
  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`;
189
+ this.#addButton("closed", this.#icon.closed, buttonsState.closed.enabled, buttonsState.closed.active, buttonsState.closed.visible, this.#callbacks.onGoToClosed);
190
+ this.#addButton("close", this.#icon.close, buttonsState.close.enabled, buttonsState.close.active, buttonsState.close.visible, this.#callbacks.onClose);
191
+ this.#addButton("pause", this.#icon.pause, buttonsState.pause.enabled, buttonsState.pause.active, buttonsState.pause.visible, this.#callbacks.onPause);
192
+ this.#addButton("open", this.#icon.open, buttonsState.open.enabled, buttonsState.open.active, buttonsState.open.visible, this.#callbacks.onOpen);
193
+ this.#addButton("opened", this.#icon.opened, buttonsState.opened.enabled, buttonsState.opened.active, buttonsState.opened.visible, this.#callbacks.onGoToOpened);
194
+ this.#addButton("repeat", this.#icon.repeat, buttonsState.repeat.enabled, buttonsState.repeat.active, buttonsState.repeat.visible, this.#callbacks.onToggleLoop);
195
+ this.#addButton("repeatOff", this.#icon.repeatOff, buttonsState.repeatOff.enabled, buttonsState.repeatOff.active, buttonsState.repeatOff.visible, this.#callbacks.onToggleLoop);
196
+ }
197
+
198
+ /**
199
+ * Creates and configures the animation progress slider.
200
+ * @private
201
+ */
202
+ #createSlider() {
203
+ this.#slider = document.createElement("input");
204
+ this.#slider.setAttribute("type", "range");
205
+ this.#slider.setAttribute("min", "0");
206
+ this.#slider.setAttribute("max", "1");
207
+ this.#slider.setAttribute("step", "0.01");
208
+ this.#slider.setAttribute("value", this.#animationProgress.toString());
209
+ this.#slider.classList.add("animation-menu-slider");
210
+ this.#handler.onSliderInput = this.#onSliderInput.bind(this);
211
+ this.#slider.addEventListener("input", this.#handler.onSliderInput);
212
+
213
+ this.#containerMain.appendChild(this.#slider);
214
+ }
215
+
216
+ /**
217
+ * Initializes and renders the menu UI, including buttons and slider.
218
+ * @private
219
+ */
220
+ #createMenu() {
221
+ this.#containerMain = document.createElement("div");
222
+ this.#containerMain.classList.add("pref-viewer-3d");
223
+ this.#containerMain.classList.add("animation-menu");
224
+ this.#containerMain.setAttribute("data-animation-name", this.#name);
225
+ this.#canvas.parentElement.prepend(this.#containerMain);
226
+
227
+ const style = document.createElement("style");
228
+ style.textContent = PrefViewer3DAnimationMenuStyles;
229
+ this.#containerMain.appendChild(style);
230
+
231
+ this.#containerButtons = document.createElement("div");
232
+ this.#containerButtons.classList.add("animation-menu-buttons");
233
+ this.#containerMain.appendChild(this.#containerButtons);
234
+
235
+ this.#createSelector();
236
+ this.#createButtons();
237
+ this.#createSlider();
172
238
  }
173
239
 
174
240
  /**
@@ -178,24 +244,37 @@ export class OpeningAnimationMenu {
178
244
  * @returns {Button|null} The button control or null if not found.
179
245
  */
180
246
  #getButtonByName(name) {
181
- return this.#advancedDynamicTexture.getControlByName(`button_animation_${name}`);
247
+ return this.#containerButtons.querySelector(`button[name="button_animation_${name}"]`);
182
248
  }
183
249
 
184
250
  /**
185
- * Updates the visual state of a button (enabled, active, visible).
251
+ * Combines player and loop button states.
186
252
  * @private
187
- * @param {string} name - Button identifier.
188
- * @param {boolean} enabled
189
- * @param {boolean} active
190
- * @param {boolean} visible
253
+ * @returns {object}
191
254
  */
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;
255
+ #getButtonsState() {
256
+ return Object.assign(this.#getPlayerButtonsState(), this.#getLoopButtonsState());
257
+ }
258
+
259
+ /**
260
+ * Returns the state for loop control buttons.
261
+ * @private
262
+ * @returns {object}
263
+ */
264
+ #getLoopButtonsState() {
265
+ const buttonsState = {
266
+ repeat: {
267
+ enabled: this.#animationLoop,
268
+ active: false,
269
+ visible: this.#animationLoop,
270
+ },
271
+ repeatOff: {
272
+ enabled: !this.#animationLoop,
273
+ active: false,
274
+ visible: !this.#animationLoop,
275
+ },
276
+ };
277
+ return buttonsState;
199
278
  }
200
279
 
201
280
  /**
@@ -235,33 +314,47 @@ export class OpeningAnimationMenu {
235
314
  }
236
315
 
237
316
  /**
238
- * Returns the state for loop control buttons.
317
+ * Resets all other opening animations except the current one.
239
318
  * @private
240
- * @returns {object}
319
+ * @returns {void}
241
320
  */
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;
321
+ #resetOtherAnimations() {
322
+ this.#openingAnimations.forEach((animation) => {
323
+ if (animation.name !== this.#name) {
324
+ animation.hideControls();
325
+ animation.goToClosed();
326
+ }
327
+ });
256
328
  }
257
329
 
258
330
  /**
259
- * Combines player and loop button states.
331
+ * Updates the visual state of a button (enabled, active, visible).
260
332
  * @private
261
- * @returns {object}
333
+ * @param {string} name - Button identifier.
334
+ * @param {boolean} enabled
335
+ * @param {boolean} active
336
+ * @param {boolean} visible
262
337
  */
263
- #getButtonsState() {
264
- return Object.assign(this.#getPlayerButtonsState(), this.#getLoopButtonsState());
338
+ #setButtonState(name, enabled, active, visible = true) {
339
+ const button = this.#getButtonByName(name);
340
+ if (!button) {
341
+ return;
342
+ }
343
+ const classToRemove = ["active", "enabled", "disabled", "hidden", "visible"];
344
+ button.classList.remove(...classToRemove);
345
+ const stateClass = active ? "active" : enabled ? "enabled" : "disabled";
346
+ const visibilityClass = visible ? "visible" : "hidden";
347
+ button.classList.add(stateClass, visibilityClass);
348
+ }
349
+
350
+ /**
351
+ * Updates all loop control buttons according to current loop mode.
352
+ * @private
353
+ */
354
+ #setLoopButtonsState() {
355
+ const buttonsState = this.#getLoopButtonsState();
356
+ this.#setButtonState("repeat", buttonsState.repeat.enabled, buttonsState.repeat.active, buttonsState.repeat.visible);
357
+ this.#setButtonState("repeatOff", buttonsState.repeatOff.enabled, buttonsState.repeatOff.active, buttonsState.repeatOff.visible);
265
358
  }
266
359
 
267
360
  /**
@@ -278,46 +371,38 @@ export class OpeningAnimationMenu {
278
371
  }
279
372
 
280
373
  /**
281
- * Updates all loop control buttons according to current loop mode.
374
+ * Handles the slider input event for animation progress.
282
375
  * @private
376
+ * @param {Event} event - The input event from the slider.
377
+ * @returns {void}
378
+ * @description
379
+ * If the slider value is being set programmatically, the event is ignored to prevent callback loops.
283
380
  */
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);
381
+ #onSliderInput(event) {
382
+ if (this.#callbacks && typeof this.#callbacks.onSetAnimationProgress === "function") {
383
+ this.#callbacks.onSetAnimationProgress(event.target.value);
384
+ }
288
385
  }
289
386
 
290
387
  /**
291
- * Creates and configures the animation progress slider.
292
- * @private
388
+ * ---------------------------
389
+ * Public methods
390
+ * ---------------------------
293
391
  */
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);
392
+
393
+ /**
394
+ * Disposes the animation menu and releases all associated DOM resources.
395
+ * @public
396
+ * @returns {void}
397
+ */
398
+ dispose() {
399
+ this.#slider.removeEventListener("input", this.#handler.onSliderInput);
400
+ if (this.isVisible) {
401
+ this.#containerMain.remove();
402
+ }
403
+ this.#containerMain = null;
404
+ this.#containerButtons = null;
405
+ this.#slider = null;
321
406
  }
322
407
 
323
408
  /**
@@ -338,14 +423,12 @@ export class OpeningAnimationMenu {
338
423
 
339
424
  /**
340
425
  * 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
426
  * @public
343
427
  * @param {number} progress - The new animation progress value (between 0 and 1).
344
428
  */
345
429
  set animationProgress(progress) {
346
430
  this.#animationProgress = progress;
347
- this.#isSettingSliderValue = true;
348
- this.#slider.value = progress;
431
+ this.#slider.value = progress.toString();
349
432
  }
350
433
 
351
434
  /**
@@ -357,4 +440,20 @@ export class OpeningAnimationMenu {
357
440
  this.#animationState = state;
358
441
  this.#setPlayerButtonsState();
359
442
  }
443
+
444
+ /**
445
+ * ---------------------------
446
+ * Public getters
447
+ * ---------------------------
448
+ */
449
+
450
+ /**
451
+ * Returns whether the animation menu is currently visible in the DOM.
452
+ * @public
453
+ * @returns {boolean} True if the menu is visible, false otherwise.
454
+ */
455
+ get isVisible() {
456
+ const menuElement = this.#canvas.parentElement.querySelector(`div.animation-menu[data-animation-name="${this.#name}"]`);
457
+ return !!menuElement;
458
+ }
360
459
  }