littlejsengine 1.15.2 → 1.15.8
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/dist/littlejs.d.ts +190 -63
- package/dist/littlejs.esm.js +1267 -605
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1261 -604
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1179 -552
- package/examples/electron/index.html +2 -2
- package/examples/index.html +4 -3
- package/examples/particles/index.html +22 -6
- package/examples/shorts/base.html +2 -1
- package/examples/shorts/box2dCar.js +1 -1
- package/examples/shorts/box2dPool.js +9 -10
- package/examples/shorts/empty.js +1 -1
- package/examples/shorts/input.js +30 -25
- package/examples/shorts/music.js +1 -0
- package/examples/shorts/musicPlayer.js +1 -0
- package/examples/shorts/sequencer.js +2 -0
- package/examples/shorts/sound.js +1 -0
- package/examples/shorts/tileLayer.js +2 -2
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/timers.js +1 -0
- package/examples/shorts/uiSystem.js +26 -14
- package/examples/shorts/videoPlayer.js +2 -1
- package/examples/starter/index.html +3 -2
- package/examples/uiSystem/game.js +28 -15
- package/package.json +1 -1
- package/plugins/box2d.js +1 -1
- package/plugins/desktop.ini +2 -0
- package/plugins/pluginExport.js +2 -0
- package/plugins/uiSystem.js +503 -91
- package/src/engine.js +2 -169
- package/src/engineAudio.js +0 -1
- package/src/engineBuild.js +1 -0
- package/src/engineDebug.js +84 -54
- package/src/engineDraw.js +81 -40
- package/src/engineExport.js +4 -1
- package/src/engineInput.js +526 -381
- package/src/engineObject.js +27 -23
- package/src/engineSettings.js +11 -6
- package/src/engineSplash.js +173 -0
- package/src/engineTileLayer.js +3 -3
- package/src/engineUtilities.js +17 -1
package/plugins/uiSystem.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LittleJS User Interface Plugin
|
|
3
3
|
* - call new UISystemPlugin() to setup the UI system
|
|
4
|
+
* - Gamepad and keyboard navigation support
|
|
4
5
|
* - Nested Menus
|
|
5
6
|
* - Text
|
|
6
7
|
* - Buttons
|
|
7
8
|
* - Checkboxes
|
|
8
9
|
* - Images
|
|
10
|
+
* - Scrollbars
|
|
11
|
+
* - Video
|
|
9
12
|
* @namespace UISystem
|
|
10
13
|
*/
|
|
11
14
|
|
|
@@ -18,6 +21,20 @@
|
|
|
18
21
|
* @memberof UISystem */
|
|
19
22
|
let uiSystem;
|
|
20
23
|
|
|
24
|
+
/** Enable UI system debug drawing
|
|
25
|
+
* 0=off, 1=normal, 2=show invisible
|
|
26
|
+
* @type {number}
|
|
27
|
+
* @default
|
|
28
|
+
* @memberof UISystem */
|
|
29
|
+
let uiDebug = 0;
|
|
30
|
+
|
|
31
|
+
/** Enable UI system debug drawing
|
|
32
|
+
* 0=off, 1=normal, 2=show invisible
|
|
33
|
+
* @param {number|boolean} enable
|
|
34
|
+
* @memberof UISystem */
|
|
35
|
+
function uiSetDebug(debugMode)
|
|
36
|
+
{ uiDebug = typeof debugMode === 'boolean' ? (debugMode ? 1 : 0) : debugMode; }
|
|
37
|
+
|
|
21
38
|
///////////////////////////////////////////////////////////////////////////////
|
|
22
39
|
/**
|
|
23
40
|
* UI System Global Object
|
|
@@ -36,6 +53,7 @@ class UISystemPlugin
|
|
|
36
53
|
ASSERT(!uiSystem, 'UI system already initialized');
|
|
37
54
|
uiSystem = this;
|
|
38
55
|
|
|
56
|
+
// default settings
|
|
39
57
|
/** @property {Color} - Default fill color for UI elements */
|
|
40
58
|
this.defaultColor = WHITE;
|
|
41
59
|
/** @property {Color} - Default outline color for UI elements */
|
|
@@ -55,7 +73,7 @@ class UISystemPlugin
|
|
|
55
73
|
/** @property {number} - Default rounded rect corner radius for UI elements */
|
|
56
74
|
this.defaultCornerRadius = 0;
|
|
57
75
|
/** @property {number} - Default scale to use for fitting text to object */
|
|
58
|
-
this.
|
|
76
|
+
this.defaultTextFitScale = .8;
|
|
59
77
|
/** @property {string} - Default font for UI elements */
|
|
60
78
|
this.defaultFont = fontDefault;
|
|
61
79
|
/** @property {Sound} - Default sound when interactive UI element is pressed */
|
|
@@ -64,6 +82,28 @@ class UISystemPlugin
|
|
|
64
82
|
this.defaultSoundRelease = undefined;
|
|
65
83
|
/** @property {Sound} - Default sound when interactive UI element is clicked */
|
|
66
84
|
this.defaultSoundClick = undefined;
|
|
85
|
+
/** @property {Color} - Color for shadow */
|
|
86
|
+
this.defaultShadowColor = CLEAR_BLACK;
|
|
87
|
+
/** @property {number} - Size of shadow blur */
|
|
88
|
+
this.defaultShadowBlur = 5;
|
|
89
|
+
/** @property {Vector2} - Offset of shadow blur */
|
|
90
|
+
this.defaultShadowOffset = vec2(5);
|
|
91
|
+
/** @property {number} - If set ui coords will be renormalized to this canvas height */
|
|
92
|
+
this.nativeHeight = 0;
|
|
93
|
+
|
|
94
|
+
// navigation properties
|
|
95
|
+
/** @property {UIObject} - Object currently selected by navigation (gamepad or keyboard) */
|
|
96
|
+
this.navigationObject = undefined;
|
|
97
|
+
/** @property {Timer} - Cooldown timer for navigation inputs */
|
|
98
|
+
this.navigationTimer = new Timer(undefined, true);
|
|
99
|
+
/** @property {number} - Time between navigation inputs in seconds */
|
|
100
|
+
this.navigationDelay = .2;
|
|
101
|
+
/** @property {boolean} - should the navigation be horizontal, vertical, or both? */
|
|
102
|
+
this.navigationDirection = 1;
|
|
103
|
+
/** @property {boolean} - True if user last used navigation instead of mouse */
|
|
104
|
+
this.navigationMode = false;
|
|
105
|
+
|
|
106
|
+
// system state
|
|
67
107
|
/** @property {Array<UIObject>} - List of all UI elements */
|
|
68
108
|
this.uiObjects = [];
|
|
69
109
|
/** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - Context to render UI elements to */
|
|
@@ -74,50 +114,118 @@ class UISystemPlugin
|
|
|
74
114
|
this.hoverObject = undefined;
|
|
75
115
|
/** @property {UIObject} - Hover object at start of update */
|
|
76
116
|
this.lastHoverObject = undefined;
|
|
77
|
-
/** @property {
|
|
78
|
-
this.
|
|
117
|
+
/** @property {UIObject} - Current confirm menu being shown */
|
|
118
|
+
this.confirmDialog = undefined;
|
|
79
119
|
|
|
80
120
|
engineAddPlugin(uiUpdate, uiRender);
|
|
81
121
|
|
|
122
|
+
// set object position in parent space
|
|
123
|
+
function updateTransforms(o)
|
|
124
|
+
{
|
|
125
|
+
if (!o.parent) return;
|
|
126
|
+
o.pos.x = o.localPos.x + o.parent.pos.x;
|
|
127
|
+
o.pos.y = o.localPos.y + o.parent.pos.y;
|
|
128
|
+
}
|
|
129
|
+
|
|
82
130
|
// setup recursive update and render
|
|
83
131
|
// update in reverse order to detect mouse enter/leave
|
|
84
132
|
function uiUpdate()
|
|
85
133
|
{
|
|
86
|
-
|
|
134
|
+
if (uiSystem.activeObject && !uiSystem.activeObject.visible)
|
|
135
|
+
uiSystem.activeObject = undefined;
|
|
136
|
+
|
|
137
|
+
// reset hover object at start of update
|
|
138
|
+
uiSystem.lastHoverObject = uiSystem.hoverObject;
|
|
139
|
+
uiSystem.hoverObject = undefined;
|
|
140
|
+
|
|
141
|
+
if (mouseWasPressed(0))
|
|
87
142
|
{
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
updateInvisibleObject(c);
|
|
91
|
-
o.updateInvisible();
|
|
143
|
+
uiSystem.navigationMode = false;
|
|
144
|
+
uiSystem.navigationObject = undefined;
|
|
92
145
|
}
|
|
93
|
-
|
|
146
|
+
|
|
147
|
+
// navigation with gamepad/keyboard
|
|
148
|
+
const navigableObjects = uiSystem.getNavigableObjects();
|
|
149
|
+
if (!navigableObjects.length)
|
|
150
|
+
uiSystem.navigationObject = undefined;
|
|
151
|
+
else
|
|
94
152
|
{
|
|
95
|
-
if
|
|
153
|
+
// unselect object if it is no longer navigable
|
|
154
|
+
if (!navigableObjects.includes(uiSystem.navigationObject))
|
|
155
|
+
uiSystem.navigationObject = undefined;
|
|
156
|
+
|
|
157
|
+
if (!isTouchDevice)
|
|
158
|
+
if (uiSystem.navigationMode && !uiSystem.navigationObject)
|
|
96
159
|
{
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
160
|
+
// select first auto focus object
|
|
161
|
+
uiSystem.navigationObject = navigableObjects.find(o=>o.navigationAutoSelect);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// navigate with dpad or left stick
|
|
165
|
+
if (!uiSystem.navigationTimer.active())
|
|
166
|
+
{
|
|
167
|
+
// navigate through list with gamepad or keyboard
|
|
168
|
+
const direction = sign(uiSystem.getNavigationDirection());
|
|
169
|
+
if (direction)
|
|
170
|
+
{
|
|
171
|
+
let newNavigationObject;
|
|
172
|
+
if (!uiSystem.navigationObject)
|
|
173
|
+
{
|
|
174
|
+
// use auto select object
|
|
175
|
+
newNavigationObject = navigableObjects.find(o=>o.navigationAutoSelect);
|
|
176
|
+
|
|
177
|
+
if (!newNavigationObject)
|
|
178
|
+
{
|
|
179
|
+
// try first or last object
|
|
180
|
+
const newIndex = direction > 0 ? 0 : navigableObjects.length-1;
|
|
181
|
+
newNavigationObject = navigableObjects[newIndex];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else
|
|
185
|
+
{
|
|
186
|
+
const currentIndex = navigableObjects.indexOf(uiSystem.navigationObject);
|
|
187
|
+
const newIndex = mod(currentIndex + direction, navigableObjects.length);
|
|
188
|
+
newNavigationObject = navigableObjects[newIndex];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (uiSystem.navigationObject !== newNavigationObject)
|
|
192
|
+
{
|
|
193
|
+
uiSystem.navigationMode = true;
|
|
194
|
+
uiSystem.hoverObject = undefined;
|
|
195
|
+
uiSystem.navigationObject = newNavigationObject;
|
|
196
|
+
uiSystem.navigationTimer.set(uiSystem.navigationDelay);
|
|
197
|
+
newNavigationObject.soundPress &&
|
|
198
|
+
newNavigationObject.soundPress.play();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
104
201
|
}
|
|
105
|
-
|
|
106
|
-
|
|
202
|
+
|
|
203
|
+
// activate the navigation object when pressed
|
|
204
|
+
if (uiSystem.navigationObject)
|
|
205
|
+
if (uiSystem.getNavigationWasPressed())
|
|
206
|
+
uiSystem.navigationObject.navigatePressed();
|
|
107
207
|
}
|
|
108
|
-
// reset hover object at start of update
|
|
109
|
-
uiSystem.lastHoverObject = uiSystem.hoverObject;
|
|
110
|
-
uiSystem.hoverObject = undefined;
|
|
111
208
|
|
|
112
209
|
// update in reverse order so topmost objects get priority
|
|
113
210
|
for (let i = uiSystem.uiObjects.length; i--;)
|
|
114
211
|
{
|
|
115
212
|
const o = uiSystem.uiObjects[i];
|
|
116
|
-
o.parent || updateObject(o)
|
|
213
|
+
o.parent || updateObject(o);
|
|
117
214
|
}
|
|
118
215
|
|
|
119
216
|
// remove destroyed objects
|
|
120
217
|
uiSystem.uiObjects = uiSystem.uiObjects.filter(o=>!o.destroyed);
|
|
218
|
+
|
|
219
|
+
function updateObject(o)
|
|
220
|
+
{
|
|
221
|
+
if (!o.visible) return;
|
|
222
|
+
|
|
223
|
+
// update in reverse order to detect mouse enter/leave
|
|
224
|
+
updateTransforms(o);
|
|
225
|
+
for (let i=o.children.length; i--;)
|
|
226
|
+
updateObject(o.children[i]);
|
|
227
|
+
o.update();
|
|
228
|
+
}
|
|
121
229
|
}
|
|
122
230
|
function uiRender()
|
|
123
231
|
{
|
|
@@ -134,15 +242,29 @@ class UISystemPlugin
|
|
|
134
242
|
|
|
135
243
|
function renderObject(o)
|
|
136
244
|
{
|
|
137
|
-
if (!o.visible)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
245
|
+
if (!o.visible) return;
|
|
246
|
+
|
|
247
|
+
// render object and children
|
|
248
|
+
updateTransforms(o);
|
|
141
249
|
o.render();
|
|
142
250
|
for (const c of o.children)
|
|
143
251
|
renderObject(c);
|
|
144
252
|
}
|
|
145
253
|
uiSystem.uiObjects.forEach(o=> o.parent || renderObject(o));
|
|
254
|
+
|
|
255
|
+
if (uiDebug > 0)
|
|
256
|
+
{
|
|
257
|
+
// debug render all objects
|
|
258
|
+
function renderDebug(o, visible=true)
|
|
259
|
+
{
|
|
260
|
+
visible &&= !!o.visible;
|
|
261
|
+
updateTransforms(o);
|
|
262
|
+
o.renderDebug(visible);
|
|
263
|
+
for (const c of o.children)
|
|
264
|
+
renderDebug(c, visible);
|
|
265
|
+
}
|
|
266
|
+
uiSystem.uiObjects.forEach(o=> o.parent || renderDebug(o));
|
|
267
|
+
}
|
|
146
268
|
context.restore();
|
|
147
269
|
}
|
|
148
270
|
}
|
|
@@ -154,8 +276,11 @@ class UISystemPlugin
|
|
|
154
276
|
* @param {number} [lineWidth]
|
|
155
277
|
* @param {Color} [lineColor]
|
|
156
278
|
* @param {number} [cornerRadius]
|
|
157
|
-
* @param {Color} [gradientColor]
|
|
158
|
-
|
|
279
|
+
* @param {Color} [gradientColor]
|
|
280
|
+
* @param {Color} [shadowColor]
|
|
281
|
+
* @param {number} [shadowBlur]
|
|
282
|
+
* @param {Color} [shadowOffset] */
|
|
283
|
+
drawRect(pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, cornerRadius=0, gradientColor, shadowColor=BLACK, shadowBlur=0, shadowOffset=vec2())
|
|
159
284
|
{
|
|
160
285
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
161
286
|
ASSERT(isVector2(size), 'size must be a vec2');
|
|
@@ -177,13 +302,23 @@ class UISystemPlugin
|
|
|
177
302
|
}
|
|
178
303
|
else
|
|
179
304
|
context.fillStyle = color.toString();
|
|
305
|
+
if (shadowBlur || shadowOffset.x || shadowOffset.y)
|
|
306
|
+
if (shadowColor.a > 0)
|
|
307
|
+
{
|
|
308
|
+
// setup shadow
|
|
309
|
+
context.shadowColor = shadowColor.toString();
|
|
310
|
+
context.shadowBlur = shadowBlur;
|
|
311
|
+
context.shadowOffsetX = shadowOffset.x;
|
|
312
|
+
context.shadowOffsetY = shadowOffset.y;
|
|
313
|
+
}
|
|
180
314
|
context.beginPath();
|
|
181
315
|
if (cornerRadius && context['roundRect'])
|
|
182
316
|
context['roundRect'](pos.x-size.x/2, pos.y-size.y/2, size.x, size.y, cornerRadius);
|
|
183
317
|
else
|
|
184
318
|
context.rect(pos.x-size.x/2, pos.y-size.y/2, size.x, size.y);
|
|
185
319
|
context.fill();
|
|
186
|
-
|
|
320
|
+
context.shadowColor = '#0000';
|
|
321
|
+
if (lineWidth && lineColor.a > 0)
|
|
187
322
|
{
|
|
188
323
|
context.strokeStyle = lineColor.toString();
|
|
189
324
|
context.lineWidth = lineWidth;
|
|
@@ -218,10 +353,24 @@ class UISystemPlugin
|
|
|
218
353
|
* @param {TileInfo} tileInfo
|
|
219
354
|
* @param {Color} [color=uiSystem.defaultColor]
|
|
220
355
|
* @param {number} [angle]
|
|
221
|
-
* @param {boolean} [mirror]
|
|
222
|
-
|
|
356
|
+
* @param {boolean} [mirror]
|
|
357
|
+
* @param {Color} [shadowColor]
|
|
358
|
+
* @param {number} [shadowBlur]
|
|
359
|
+
* @param {Color} [shadowOffset] */
|
|
360
|
+
drawTile(pos, size, tileInfo, color=uiSystem.defaultColor, angle=0, mirror=false, shadowColor=BLACK, shadowBlur=0, shadowOffset=vec2())
|
|
223
361
|
{
|
|
224
|
-
|
|
362
|
+
const context = uiSystem.uiContext;
|
|
363
|
+
if (shadowBlur || shadowOffset.x || shadowOffset.y)
|
|
364
|
+
if (shadowColor.a > 0)
|
|
365
|
+
{
|
|
366
|
+
// setup shadow
|
|
367
|
+
context.shadowColor = shadowColor.toString();
|
|
368
|
+
context.shadowBlur = shadowBlur;
|
|
369
|
+
context.shadowOffsetX = shadowOffset.x;
|
|
370
|
+
context.shadowOffsetY = shadowOffset.y;
|
|
371
|
+
}
|
|
372
|
+
drawTile(pos, size, tileInfo, color, angle, mirror, CLEAR_BLACK, false, true, context);
|
|
373
|
+
context.shadowColor = '#0000';
|
|
225
374
|
}
|
|
226
375
|
|
|
227
376
|
/** Draw text to the UI context
|
|
@@ -236,12 +385,27 @@ class UISystemPlugin
|
|
|
236
385
|
* @param {string} [fontStyle]
|
|
237
386
|
* @param {boolean} [applyMaxWidth=true]
|
|
238
387
|
* @param {Vector2} [textShadow]
|
|
239
|
-
|
|
240
|
-
|
|
388
|
+
* @param {Color} [shadowColor]
|
|
389
|
+
* @param {number} [shadowBlur]
|
|
390
|
+
* @param {Color} [shadowOffset] */
|
|
391
|
+
drawText(text, pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, align='center', font=uiSystem.defaultFont, fontStyle='', applyMaxWidth=true, textShadow=undefined, shadowColor=BLACK, shadowBlur=0, shadowOffset=vec2())
|
|
241
392
|
{
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
393
|
+
const context = uiSystem.uiContext;
|
|
394
|
+
if (shadowColor.a > 0)
|
|
395
|
+
{
|
|
396
|
+
if (textShadow)
|
|
397
|
+
drawTextScreen(text, pos.add(textShadow), size.y, shadowColor, lineWidth, lineColor, align, font, fontStyle, applyMaxWidth ? size.x : undefined, 0, context);
|
|
398
|
+
if (shadowBlur || shadowOffset.x || shadowOffset.y)
|
|
399
|
+
{
|
|
400
|
+
// setup shadow
|
|
401
|
+
context.shadowColor = shadowColor.toString();
|
|
402
|
+
context.shadowBlur = shadowBlur;
|
|
403
|
+
context.shadowOffsetX = shadowOffset.x;
|
|
404
|
+
context.shadowOffsetY = shadowOffset.y;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, fontStyle, applyMaxWidth ? size.x : undefined, 0, context);
|
|
408
|
+
context.shadowColor = '#0000';
|
|
245
409
|
}
|
|
246
410
|
|
|
247
411
|
/**
|
|
@@ -255,7 +419,7 @@ class UISystemPlugin
|
|
|
255
419
|
* @param {DragAndDropCallback} [onDrop] - when a file is dropped
|
|
256
420
|
* @param {DragAndDropCallback} [onDragEnter] - when a file is dragged onto the window
|
|
257
421
|
* @param {DragAndDropCallback} [onDragLeave] - when a file is dragged off the window
|
|
258
|
-
* @param {DragAndDropCallback} [onDragOver] -
|
|
422
|
+
* @param {DragAndDropCallback} [onDragOver] - continuously when dragging over */
|
|
259
423
|
setupDragAndDrop(onDrop, onDragEnter, onDragLeave, onDragOver)
|
|
260
424
|
{
|
|
261
425
|
function setCallback(callback, listenerType)
|
|
@@ -271,8 +435,7 @@ class UISystemPlugin
|
|
|
271
435
|
|
|
272
436
|
/** Convert a screen space position to native UI position
|
|
273
437
|
* @param {Vector2} pos
|
|
274
|
-
* @return {Vector2}
|
|
275
|
-
*/
|
|
438
|
+
* @return {Vector2} */
|
|
276
439
|
screenToNative(pos)
|
|
277
440
|
{
|
|
278
441
|
if (!uiSystem.nativeHeight)
|
|
@@ -295,6 +458,157 @@ class UISystemPlugin
|
|
|
295
458
|
for (const o of this.uiObjects)
|
|
296
459
|
o.parent || o.destroy();
|
|
297
460
|
this.uiObjects = this.uiObjects.filter(o=>!o.destroyed);
|
|
461
|
+
this.activeObject = undefined;
|
|
462
|
+
this.hoverObject = undefined;
|
|
463
|
+
this.lastHoverObject = undefined;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** Get all navigable UI objects sorted by navigationIndex
|
|
467
|
+
* @return {Array<UIObject>} */
|
|
468
|
+
getNavigableObjects()
|
|
469
|
+
{
|
|
470
|
+
function getNavigableRecursive(o)
|
|
471
|
+
{
|
|
472
|
+
if (!o.visible || o.disabled)
|
|
473
|
+
return; // skip children if parent is invisible or disabled
|
|
474
|
+
|
|
475
|
+
if (o.isInteractive() && o.navigationIndex !== undefined)
|
|
476
|
+
objects.push(o);
|
|
477
|
+
for (let i=o.children.length; i--;)
|
|
478
|
+
getNavigableRecursive(o.children[i]);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// get all the valid navigable objects recursively
|
|
482
|
+
let objects = [];
|
|
483
|
+
for (let i = uiSystem.uiObjects.length; i--;)
|
|
484
|
+
{
|
|
485
|
+
const o = uiSystem.uiObjects[i];
|
|
486
|
+
if (uiSystem.confirmDialog && o !== uiSystem.confirmDialog)
|
|
487
|
+
continue;
|
|
488
|
+
o.parent || getNavigableRecursive(o);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// sort by navigationIndex (lower numbers first)
|
|
492
|
+
objects.sort((a, b)=> a.navigationIndex - b.navigationIndex);
|
|
493
|
+
return objects;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/** Get navigation direction from gamepad or keyboard
|
|
497
|
+
* @return {number} */
|
|
498
|
+
getNavigationDirection()
|
|
499
|
+
{
|
|
500
|
+
const vertical = uiSystem.navigationDirection === 1;
|
|
501
|
+
const both = uiSystem.navigationDirection === 2;
|
|
502
|
+
if (isUsingGamepad)
|
|
503
|
+
{
|
|
504
|
+
const stick = gamepadStick(0, gamepadPrimary);
|
|
505
|
+
const dpad = gamepadDpad(gamepadPrimary);
|
|
506
|
+
if (both)
|
|
507
|
+
return -(stick.y || dpad.y) || (stick.x || dpad.x);
|
|
508
|
+
return vertical ? -(stick.y || dpad.y) : (stick.x || dpad.x);
|
|
509
|
+
}
|
|
510
|
+
const up = 'ArrowUp', down = 'ArrowDown', left = 'ArrowLeft', right = 'ArrowRight';
|
|
511
|
+
if (both)
|
|
512
|
+
{
|
|
513
|
+
return keyIsDown(up) || keyIsDown(left) ? -1 :
|
|
514
|
+
keyIsDown(down) || keyIsDown(right) ? 1 : 0;
|
|
515
|
+
}
|
|
516
|
+
const back = vertical ? up : left;
|
|
517
|
+
const forward = vertical ? down : right;
|
|
518
|
+
return keyIsDown(back) ? -1 : keyIsDown(forward) ? 1 : 0;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** Get other axis navigation direction from gamepad or keyboard
|
|
522
|
+
* @return {Vector2} */
|
|
523
|
+
getNavigationOtherDirection()
|
|
524
|
+
{
|
|
525
|
+
if (uiSystem.navigationDirection === 2)
|
|
526
|
+
return 0; // other direction disabled
|
|
527
|
+
|
|
528
|
+
const vertical = uiSystem.navigationDirection === 1;
|
|
529
|
+
if (isUsingGamepad)
|
|
530
|
+
{
|
|
531
|
+
const stick = gamepadStick(0, gamepadPrimary);
|
|
532
|
+
const dpad = gamepadDpad(gamepadPrimary);
|
|
533
|
+
return !vertical ? (stick.y || dpad.y) : (stick.x || dpad.x);
|
|
534
|
+
}
|
|
535
|
+
const back = !vertical ? 'ArrowUp' : 'ArrowLeft';
|
|
536
|
+
const forward = !vertical ? 'ArrowDown' : 'ArrowRight';
|
|
537
|
+
return keyIsDown(back) ? -1 : keyIsDown(forward) ? 1 : 0;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** Get if navigation button was pressed from gamepad or keyboard
|
|
541
|
+
* @return {boolean} */
|
|
542
|
+
getNavigationWasPressed()
|
|
543
|
+
{
|
|
544
|
+
return isUsingGamepad ? gamepadWasPressed(0, gamepadPrimary) :
|
|
545
|
+
keyWasPressed('Space') || keyWasPressed('Enter');
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/** Show a confirmation dialog with Yes/No buttons
|
|
549
|
+
* Centers the dialog on the screen with darkened background
|
|
550
|
+
* @param {string} [text] - The message to display
|
|
551
|
+
* @param {Function} [yesCallback] - Called when Yes is clicked
|
|
552
|
+
* @param {Function} [noCallback] - Called when No is clicked
|
|
553
|
+
* @param {Vector2} [size] - Size of the confirmation dialog
|
|
554
|
+
* @param {string} [exitKey] - Key that can exit the menu
|
|
555
|
+
* @return {UIObject} The confirmation menu object
|
|
556
|
+
*/
|
|
557
|
+
showConfirmDialog(text='Are you sure?', yesCallback, noCallback, size=vec2(500,250), exitKey='Escape')
|
|
558
|
+
{
|
|
559
|
+
ASSERT(!uiSystem.confirmDialog);
|
|
560
|
+
|
|
561
|
+
const savedNavigationDirection = uiSystem.navigationDirection;
|
|
562
|
+
|
|
563
|
+
// allow both axies for navigation
|
|
564
|
+
uiSystem.navigationDirection = 2;
|
|
565
|
+
|
|
566
|
+
// confirm menu
|
|
567
|
+
const confirmMenu = new UIObject(vec2(), size);
|
|
568
|
+
uiSystem.confirmDialog = confirmMenu;
|
|
569
|
+
confirmMenu.onRender = ()=>
|
|
570
|
+
{
|
|
571
|
+
confirmMenu.pos = uiSystem.screenToNative(mainCanvasSize.scale(.5));
|
|
572
|
+
const backgroundColor = hsl(0,0,0,.7);
|
|
573
|
+
uiSystem.drawRect(vec2(), vec2(1e9), backgroundColor);
|
|
574
|
+
}
|
|
575
|
+
confirmMenu.onUpdate = ()=>
|
|
576
|
+
{
|
|
577
|
+
if (keyWasPressed(exitKey))
|
|
578
|
+
closeMenu();
|
|
579
|
+
}
|
|
580
|
+
confirmMenu.isMouseOverlapping = ()=> true; // always hover
|
|
581
|
+
|
|
582
|
+
// title text
|
|
583
|
+
const gap = 50;
|
|
584
|
+
const textTitle = new UIText(vec2(0,-50), vec2(size.x-gap,70), text);
|
|
585
|
+
confirmMenu.addChild(textTitle);
|
|
586
|
+
|
|
587
|
+
// yes button
|
|
588
|
+
const buttonYes = new UIButton(vec2(-80,50), vec2(120,70), 'Yes');
|
|
589
|
+
buttonYes.textHeight = 40;
|
|
590
|
+
buttonYes.navigationIndex = 1;
|
|
591
|
+
buttonYes.hoverColor = hsl(0,1,.5);
|
|
592
|
+
buttonYes.onClick = ()=> { closeMenu(); yesCallback && yesCallback(); };
|
|
593
|
+
confirmMenu.addChild(buttonYes);
|
|
594
|
+
|
|
595
|
+
// no button
|
|
596
|
+
const buttonNo = new UIButton(vec2(80,50), vec2(120,70), 'No');
|
|
597
|
+
buttonNo.textHeight = 40;
|
|
598
|
+
buttonNo.navigationIndex = 2;
|
|
599
|
+
buttonNo.navigationAutoSelect = true;
|
|
600
|
+
buttonNo.onClick = ()=> { closeMenu(); noCallback && noCallback(); };
|
|
601
|
+
confirmMenu.addChild(buttonNo);
|
|
602
|
+
|
|
603
|
+
// close menu and return to normal navigation
|
|
604
|
+
function closeMenu()
|
|
605
|
+
{
|
|
606
|
+
ASSERT(uiSystem.confirmDialog === confirmMenu);
|
|
607
|
+
confirmMenu.destroy();
|
|
608
|
+
uiSystem.confirmDialog = undefined;
|
|
609
|
+
uiSystem.navigationDirection = savedNavigationDirection;
|
|
610
|
+
inputClear();
|
|
611
|
+
}
|
|
298
612
|
}
|
|
299
613
|
}
|
|
300
614
|
|
|
@@ -350,7 +664,13 @@ class UIObject
|
|
|
350
664
|
/** @property {number} - Override for text height */
|
|
351
665
|
this.textHeight = undefined;
|
|
352
666
|
/** @property {number} - Scale text to fit in the object */
|
|
353
|
-
this.
|
|
667
|
+
this.textFitScale = uiSystem.defaultTextFitScale;
|
|
668
|
+
/** @property {Vector2} - How much to offset the text shadow or undefined */
|
|
669
|
+
this.textShadow = undefined;
|
|
670
|
+
/** @property {number} - Color for text line drawing */
|
|
671
|
+
this.textLineColor = uiSystem.defaultLineColor.copy();
|
|
672
|
+
/** @property {number} - Width for text line drawing */
|
|
673
|
+
this.textLineWidth = 0;
|
|
354
674
|
/** @property {boolean} - Should this object be drawn */
|
|
355
675
|
this.visible = true;
|
|
356
676
|
/** @property {Array<UIObject>} - A list of this object's children */
|
|
@@ -371,15 +691,22 @@ class UIObject
|
|
|
371
691
|
this.dragActivate = false;
|
|
372
692
|
/** @property {boolean} - True if this can be a hover object */
|
|
373
693
|
this.canBeHover = true;
|
|
374
|
-
|
|
694
|
+
/** @property {Color} - Color for shadow, undefined if no shadow */
|
|
695
|
+
this.shadowColor = uiSystem.defaultShadowColor?.copy();
|
|
696
|
+
/** @property {number} - Size of shadow blur */
|
|
697
|
+
this.shadowBlur = uiSystem.defaultShadowBlur;
|
|
698
|
+
/** @property {Vector2} - Offset of shadow blur */
|
|
699
|
+
this.shadowOffset = uiSystem.defaultShadowOffset?.copy();
|
|
700
|
+
/** @property {number} - Optional navigation order index, lower values are selected first */
|
|
701
|
+
this.navigationIndex = undefined;
|
|
702
|
+
/** @property {boolean} - Should this be auto selected by navigation? Must also have valid navigation index. */
|
|
703
|
+
this.navigationAutoSelect = false;
|
|
375
704
|
|
|
376
|
-
|
|
377
|
-
this.textShadow = undefined;
|
|
705
|
+
uiSystem.uiObjects.push(this);
|
|
378
706
|
}
|
|
379
707
|
|
|
380
708
|
/** Add a child UIObject to this object
|
|
381
|
-
* @param {UIObject} child
|
|
382
|
-
*/
|
|
709
|
+
* @param {UIObject} child */
|
|
383
710
|
addChild(child)
|
|
384
711
|
{
|
|
385
712
|
ASSERT(!child.parent && !this.children.includes(child));
|
|
@@ -388,8 +715,7 @@ class UIObject
|
|
|
388
715
|
}
|
|
389
716
|
|
|
390
717
|
/** Remove a child UIObject from this object
|
|
391
|
-
* @param {UIObject} child
|
|
392
|
-
*/
|
|
718
|
+
* @param {UIObject} child */
|
|
393
719
|
removeChild(child)
|
|
394
720
|
{
|
|
395
721
|
ASSERT(child.parent === this && this.children.includes(child));
|
|
@@ -397,7 +723,6 @@ class UIObject
|
|
|
397
723
|
child.parent = undefined;
|
|
398
724
|
}
|
|
399
725
|
|
|
400
|
-
|
|
401
726
|
/** Destroy this object, destroy its children, detach its parent, and mark it for removal */
|
|
402
727
|
destroy()
|
|
403
728
|
{
|
|
@@ -413,9 +738,9 @@ class UIObject
|
|
|
413
738
|
child.destroy();
|
|
414
739
|
}
|
|
415
740
|
}
|
|
741
|
+
|
|
416
742
|
/** Check if the mouse is overlapping a box in screen space
|
|
417
|
-
* @return {boolean} - True if overlapping
|
|
418
|
-
*/
|
|
743
|
+
* @return {boolean} - True if overlapping */
|
|
419
744
|
isMouseOverlapping()
|
|
420
745
|
{
|
|
421
746
|
if (!mouseInWindow) return false;
|
|
@@ -432,11 +757,16 @@ class UIObject
|
|
|
432
757
|
// call the custom update callback
|
|
433
758
|
this.onUpdate();
|
|
434
759
|
|
|
760
|
+
// unset active if disabled
|
|
761
|
+
if (this.disabled && this == uiSystem.activeObject)
|
|
762
|
+
uiSystem.activeObject = undefined;
|
|
763
|
+
|
|
435
764
|
const wasHover = uiSystem.lastHoverObject === this;
|
|
436
765
|
const isActive = this.isActiveObject();
|
|
437
766
|
const mouseDown = mouseIsDown(0);
|
|
438
767
|
const mousePress = this.dragActivate ? mouseDown : mouseWasPressed(0);
|
|
439
768
|
if (this.canBeHover)
|
|
769
|
+
if (!uiSystem.navigationMode) // no mouse hover in navigation mode
|
|
440
770
|
if (mousePress || isActive || (!mouseDown && !isTouchDevice))
|
|
441
771
|
if (!uiSystem.hoverObject && this.isMouseOverlapping())
|
|
442
772
|
uiSystem.hoverObject = this;
|
|
@@ -450,8 +780,7 @@ class UIObject
|
|
|
450
780
|
{
|
|
451
781
|
if (!this.dragActivate || (!wasHover || mouseWasPressed(0)))
|
|
452
782
|
this.onPress();
|
|
453
|
-
|
|
454
|
-
this.soundPress.play();
|
|
783
|
+
this.soundPress && this.soundPress.play();
|
|
455
784
|
if (uiSystem.activeObject && !isActive)
|
|
456
785
|
uiSystem.activeObject.onRelease();
|
|
457
786
|
uiSystem.activeObject = this;
|
|
@@ -460,10 +789,10 @@ class UIObject
|
|
|
460
789
|
if (!mouseDown && this.isActiveObject() && this.interactive)
|
|
461
790
|
{
|
|
462
791
|
this.onClick();
|
|
463
|
-
|
|
464
|
-
this.soundClick.play();
|
|
792
|
+
this.soundClick && this.soundClick.play();
|
|
465
793
|
}
|
|
466
794
|
}
|
|
795
|
+
|
|
467
796
|
// clear mouse was pressed state even when disabled
|
|
468
797
|
mousePress && inputClearKey(0,0,0,1,0);
|
|
469
798
|
}
|
|
@@ -471,8 +800,7 @@ class UIObject
|
|
|
471
800
|
if (!mouseDown || (this.dragActivate && !this.isHoverObject()))
|
|
472
801
|
{
|
|
473
802
|
this.onRelease();
|
|
474
|
-
|
|
475
|
-
this.soundRelease.play();
|
|
803
|
+
this.soundRelease && this.soundRelease.play();
|
|
476
804
|
uiSystem.activeObject = undefined;
|
|
477
805
|
}
|
|
478
806
|
|
|
@@ -484,29 +812,40 @@ class UIObject
|
|
|
484
812
|
/** Render the object, called automatically by plugin once each frame */
|
|
485
813
|
render()
|
|
486
814
|
{
|
|
487
|
-
|
|
815
|
+
// call the custom render callback
|
|
816
|
+
this.onRender();
|
|
488
817
|
|
|
489
|
-
|
|
490
|
-
const color = this.disabled ? this.disabledColor : this.interactive ? this.isActiveObject() ? this.activeColor || this.color : this.isHoverObject() ? this.hoverColor : this.color : this.color;
|
|
491
|
-
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius, this.gradientColor);
|
|
492
|
-
}
|
|
818
|
+
if (!this.size.x || !this.size.y) return;
|
|
493
819
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
820
|
+
const isNavigationObject = this.isNavigationObject();
|
|
821
|
+
const lineColor = isNavigationObject ? this.color :
|
|
822
|
+
this.interactive && this.isActiveObject() && !this.disabled ?
|
|
823
|
+
this.color : this.lineColor;
|
|
824
|
+
const color = isNavigationObject ? this.hoverColor :
|
|
825
|
+
this.disabled ? this.disabledColor :
|
|
826
|
+
this.interactive ?
|
|
827
|
+
this.isHoverObject() ? this.hoverColor :
|
|
828
|
+
this.isActiveObject() ? this.activeColor || this.color :
|
|
829
|
+
this.color : this.color;
|
|
830
|
+
const lineWidth = this.lineWidth * (isNavigationObject ? 1.5 : 1);
|
|
831
|
+
|
|
832
|
+
uiSystem.drawRect(this.pos, this.size, color, lineWidth, lineColor, this.cornerRadius, this.gradientColor, this.shadowColor, this.shadowBlur, this.shadowOffset);
|
|
500
833
|
}
|
|
501
834
|
|
|
502
835
|
/** Get the size for text with overrides and scale
|
|
503
|
-
* @return {Vector2}
|
|
504
|
-
*/
|
|
836
|
+
* @return {Vector2} */
|
|
505
837
|
getTextSize()
|
|
506
838
|
{
|
|
507
839
|
return vec2(
|
|
508
|
-
this.textWidth || this.
|
|
509
|
-
this.textHeight || this.
|
|
840
|
+
this.textWidth || this.textFitScale * this.size.x,
|
|
841
|
+
this.textHeight || this.textFitScale * this.size.y);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/** Called when the navigation button is pressed on this object */
|
|
845
|
+
navigatePressed()
|
|
846
|
+
{
|
|
847
|
+
this.onClick();
|
|
848
|
+
this.soundClick && this.soundClick.play();
|
|
510
849
|
}
|
|
511
850
|
|
|
512
851
|
/** @return {boolean} - Is the mouse hovering over this element */
|
|
@@ -515,9 +854,51 @@ class UIObject
|
|
|
515
854
|
/** @return {boolean} - Is the mouse held onto this element */
|
|
516
855
|
isActiveObject() { return uiSystem.activeObject === this; }
|
|
517
856
|
|
|
518
|
-
/**
|
|
857
|
+
/** @return {boolean} - Is the gamepad or keyboard navigation object */
|
|
858
|
+
isNavigationObject() { return uiSystem.navigationObject === this; }
|
|
859
|
+
|
|
860
|
+
/** @return {boolean} - Can it be interacted with */
|
|
861
|
+
isInteractive() { return this.interactive && this.visible && !this.disabled;}
|
|
862
|
+
|
|
863
|
+
/** Returns string containing info about this object for debugging
|
|
864
|
+
* @return {string} */
|
|
865
|
+
toString()
|
|
866
|
+
{
|
|
867
|
+
if (!debug) return;
|
|
868
|
+
|
|
869
|
+
let text = 'type = ' + this.constructor.name;
|
|
870
|
+
if (this.text)
|
|
871
|
+
text += '\ntext = ' + this.text;
|
|
872
|
+
if (this.pos.x || this.pos.y)
|
|
873
|
+
text += '\npos = ' + this.pos;
|
|
874
|
+
if (this.localPos.x || this.localPos.y)
|
|
875
|
+
text += '\localPos = ' + this.localPos;
|
|
876
|
+
if (this.size.x || this.size.y)
|
|
877
|
+
text += '\nsize = ' + this.size;
|
|
878
|
+
if (this.color)
|
|
879
|
+
text += '\ncolor = ' + this.color;
|
|
880
|
+
return text;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/** Called if uiDebug is enabled
|
|
884
|
+
* @param {boolean} visible */
|
|
885
|
+
renderDebug(visible=true)
|
|
886
|
+
{
|
|
887
|
+
// apply color based on state
|
|
888
|
+
const color =
|
|
889
|
+
!visible ? GREEN :
|
|
890
|
+
this.isHoverObject() ? YELLOW :
|
|
891
|
+
this.disabled ? PURPLE :
|
|
892
|
+
this.interactive ? RED : BLUE;
|
|
893
|
+
uiSystem.drawRect(this.pos, this.size, CLEAR_BLACK, 4, color);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/** Called each frame before object updates */
|
|
519
897
|
onUpdate() {}
|
|
520
898
|
|
|
899
|
+
/** Called each frame before object renders */
|
|
900
|
+
onRender() {}
|
|
901
|
+
|
|
521
902
|
/** Called when the mouse enters the object */
|
|
522
903
|
onEnter() {}
|
|
523
904
|
|
|
@@ -565,16 +946,25 @@ class UIText extends UIObject
|
|
|
565
946
|
this.align = align;
|
|
566
947
|
this.font = font;
|
|
567
948
|
|
|
568
|
-
// make text not outlined by default
|
|
569
|
-
this.lineWidth = 0;
|
|
570
949
|
// text can not be a hover object by default
|
|
571
950
|
this.canBeHover = false;
|
|
951
|
+
|
|
952
|
+
// no background by default
|
|
953
|
+
this.color = CLEAR_BLACK;
|
|
954
|
+
this.shadowColor = CLEAR_BLACK;
|
|
955
|
+
this.gradientColor = undefined;
|
|
956
|
+
this.lineWidth = 0;
|
|
957
|
+
|
|
958
|
+
// use max fit scale by default
|
|
959
|
+
this.textFitScale = 1;
|
|
572
960
|
}
|
|
573
961
|
render()
|
|
574
962
|
{
|
|
575
|
-
|
|
963
|
+
super.render();
|
|
964
|
+
|
|
965
|
+
// render the text
|
|
576
966
|
const textSize = this.getTextSize();
|
|
577
|
-
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.
|
|
967
|
+
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow, this.shadowColor, this.shadowBlur, this.shadowOffset);
|
|
578
968
|
}
|
|
579
969
|
}
|
|
580
970
|
|
|
@@ -610,10 +1000,13 @@ class UITile extends UIObject
|
|
|
610
1000
|
this.mirror = mirror;
|
|
611
1001
|
// set properties
|
|
612
1002
|
this.color = color.copy();
|
|
1003
|
+
|
|
1004
|
+
// no shadow by default
|
|
1005
|
+
this.shadowColor = CLEAR_BLACK;
|
|
613
1006
|
}
|
|
614
1007
|
render()
|
|
615
1008
|
{
|
|
616
|
-
uiSystem.drawTile(this.pos, this.size, this.tileInfo, this.color, this.angle, this.mirror);
|
|
1009
|
+
uiSystem.drawTile(this.pos, this.size, this.tileInfo, this.color, this.angle, this.mirror, this.shadowColor, this.shadowBlur, this.shadowOffset);
|
|
617
1010
|
}
|
|
618
1011
|
}
|
|
619
1012
|
|
|
@@ -638,6 +1031,9 @@ class UIButton extends UIObject
|
|
|
638
1031
|
ASSERT(isString(text), 'ui button must be a string');
|
|
639
1032
|
ASSERT(isColor(color), 'ui button color must be a color');
|
|
640
1033
|
|
|
1034
|
+
/** @property {Vector2} - Text offset for the button */
|
|
1035
|
+
this.textOffset = vec2();
|
|
1036
|
+
|
|
641
1037
|
// set properties
|
|
642
1038
|
this.text = text;
|
|
643
1039
|
this.color = color.copy();
|
|
@@ -649,8 +1045,8 @@ class UIButton extends UIObject
|
|
|
649
1045
|
|
|
650
1046
|
// draw the text scaled to fit
|
|
651
1047
|
const textSize = this.getTextSize();
|
|
652
|
-
uiSystem.drawText(this.text, this.pos, textSize,
|
|
653
|
-
this.textColor,
|
|
1048
|
+
uiSystem.drawText(this.text, this.pos.add(this.textOffset), textSize,
|
|
1049
|
+
this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
654
1050
|
}
|
|
655
1051
|
}
|
|
656
1052
|
|
|
@@ -704,7 +1100,7 @@ class UICheckbox extends UIObject
|
|
|
704
1100
|
const textSize = this.getTextSize();
|
|
705
1101
|
const pos = this.pos.add(vec2(this.size.x,0));
|
|
706
1102
|
uiSystem.drawText(this.text, pos, textSize,
|
|
707
|
-
this.textColor,
|
|
1103
|
+
this.textColor, this.textLineWidth, this.textLineColor, 'left', this.font, this.fontStyle, false, this.textShadow);
|
|
708
1104
|
}
|
|
709
1105
|
}
|
|
710
1106
|
|
|
@@ -746,7 +1142,11 @@ class UIScrollbar extends UIObject
|
|
|
746
1142
|
update()
|
|
747
1143
|
{
|
|
748
1144
|
super.update();
|
|
749
|
-
if (this.
|
|
1145
|
+
if (!this.interactive)
|
|
1146
|
+
return;
|
|
1147
|
+
|
|
1148
|
+
const oldValue = this.value;
|
|
1149
|
+
if (this.isActiveObject())
|
|
750
1150
|
{
|
|
751
1151
|
// handle horizontal or vertical scrollbar
|
|
752
1152
|
const isHorizontal = this.size.x > this.size.y;
|
|
@@ -758,14 +1158,19 @@ class UIScrollbar extends UIObject
|
|
|
758
1158
|
const handleWidth = barSize - handleSize;
|
|
759
1159
|
const p1 = centerPos - handleWidth/2;
|
|
760
1160
|
const p2 = centerPos + handleWidth/2;
|
|
761
|
-
const oldValue = this.value;
|
|
762
|
-
|
|
763
1161
|
const p = uiSystem.screenToNative(mousePosScreen);
|
|
764
1162
|
this.value = isHorizontal ?
|
|
765
1163
|
percent(p.x, p1, p2) :
|
|
766
1164
|
percent(p.y, p2, p1);
|
|
767
|
-
this.value === oldValue || this.onChange();
|
|
768
1165
|
}
|
|
1166
|
+
else if (this.isNavigationObject())
|
|
1167
|
+
{
|
|
1168
|
+
// gamepad/keyboard navigation adjustment
|
|
1169
|
+
const direction = uiSystem.getNavigationOtherDirection();
|
|
1170
|
+
if (!uiSystem.navigationTimer.active())
|
|
1171
|
+
this.value = clamp(this.value + direction*.01);
|
|
1172
|
+
}
|
|
1173
|
+
this.value === oldValue || this.onChange();
|
|
769
1174
|
}
|
|
770
1175
|
render()
|
|
771
1176
|
{
|
|
@@ -790,7 +1195,14 @@ class UIScrollbar extends UIObject
|
|
|
790
1195
|
// draw the text scaled to fit on the scrollbar
|
|
791
1196
|
const textSize = this.getTextSize();
|
|
792
1197
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
793
|
-
this.textColor,
|
|
1198
|
+
this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
1199
|
+
}
|
|
1200
|
+
navigatePressed()
|
|
1201
|
+
{
|
|
1202
|
+
// toggle value between 0 and 1
|
|
1203
|
+
this.value = this.value ? 0 : 1;
|
|
1204
|
+
this.onRelease();
|
|
1205
|
+
super.navigatePressed();
|
|
794
1206
|
}
|
|
795
1207
|
}
|
|
796
1208
|
|
|
@@ -824,7 +1236,7 @@ class UIVideo extends UIObject
|
|
|
824
1236
|
this.color = BLACK; // default to black background
|
|
825
1237
|
this.cornerRadius = 0; // default to no corner radius
|
|
826
1238
|
|
|
827
|
-
/** @property {
|
|
1239
|
+
/** @property {number} - The video volume */
|
|
828
1240
|
this.volume = volume;
|
|
829
1241
|
|
|
830
1242
|
// create video element
|
|
@@ -857,7 +1269,7 @@ class UIVideo extends UIObject
|
|
|
857
1269
|
|
|
858
1270
|
/** Check if video is currently loading
|
|
859
1271
|
* @return {boolean} */
|
|
860
|
-
|
|
1272
|
+
isLoading()
|
|
861
1273
|
{ return this.video.readyState < this.video.HAVE_CURRENT_DATA; }
|
|
862
1274
|
|
|
863
1275
|
/** Check if video is currently paused
|
|
@@ -867,7 +1279,7 @@ class UIVideo extends UIObject
|
|
|
867
1279
|
/** Check if video is currently playing
|
|
868
1280
|
* @return {boolean} */
|
|
869
1281
|
isPlaying()
|
|
870
|
-
{ return !this.isPaused() && !this.hasEnded() && !this.
|
|
1282
|
+
{ return !this.isPaused() && !this.hasEnded() && !this.isLoading(); }
|
|
871
1283
|
|
|
872
1284
|
/** Check if video has ended playing
|
|
873
1285
|
* @return {boolean} */
|
|
@@ -916,7 +1328,7 @@ class UIVideo extends UIObject
|
|
|
916
1328
|
{
|
|
917
1329
|
super.render();
|
|
918
1330
|
|
|
919
|
-
if (this.
|
|
1331
|
+
if (this.isLoading())
|
|
920
1332
|
return;
|
|
921
1333
|
const context = uiSystem.uiContext;
|
|
922
1334
|
const s = this.size;
|