littlejsengine 1.18.17 → 1.18.19
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 +79 -15
- package/dist/littlejs.esm.js +230 -64
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +222 -63
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +222 -63
- package/package.json +1 -1
- package/plugins/drawUtilities.js +23 -12
- package/plugins/lightSystem.js +2 -2
- package/plugins/uiSystem.js +35 -25
- package/src/engine.js +1 -1
- package/src/engineDraw.js +59 -4
- package/src/engineExport.js +8 -1
- package/src/engineInput.js +80 -13
- package/src/engineMath.js +15 -0
- package/src/engineObject.js +0 -2
- package/src/engineParticles.js +4 -4
- package/src/engineSettings.js +3 -0
package/dist/littlejs.release.js
CHANGED
|
@@ -35,7 +35,7 @@ const engineName = 'LittleJS';
|
|
|
35
35
|
* @type {string}
|
|
36
36
|
* @default
|
|
37
37
|
* @memberof Engine */
|
|
38
|
-
const engineVersion = '1.18.
|
|
38
|
+
const engineVersion = '1.18.19';
|
|
39
39
|
|
|
40
40
|
/** Frames per second to update
|
|
41
41
|
* @type {number}
|
|
@@ -1545,10 +1545,25 @@ class Color
|
|
|
1545
1545
|
* @return {Color} */
|
|
1546
1546
|
setFrom(c) { return this.set(c.r, c.g, c.b, c.a); }
|
|
1547
1547
|
|
|
1548
|
+
/** Sets the alpha of this color and returns self
|
|
1549
|
+
* @param {number} [a] - alpha
|
|
1550
|
+
* @return {Color} */
|
|
1551
|
+
setAlpha(a=1)
|
|
1552
|
+
{
|
|
1553
|
+
this.a = a;
|
|
1554
|
+
ASSERT_COLOR_VALID(this);
|
|
1555
|
+
return this;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1548
1558
|
/** Returns a new color that is a copy of this
|
|
1549
1559
|
* @return {Color} */
|
|
1550
1560
|
copy() { return new Color(this.r, this.g, this.b, this.a); }
|
|
1551
1561
|
|
|
1562
|
+
/** Returns a copy of this color with the alpha set
|
|
1563
|
+
* @param {number} [a] - alpha
|
|
1564
|
+
* @return {Color} */
|
|
1565
|
+
withAlpha(a=1) { return new Color(this.r, this.g, this.b, a); }
|
|
1566
|
+
|
|
1552
1567
|
/** Returns a copy of this color plus the color passed in
|
|
1553
1568
|
* @param {Color} c - other color
|
|
1554
1569
|
* @return {Color} */
|
|
@@ -2143,6 +2158,8 @@ let canvasFixedSize = vec2();
|
|
|
2143
2158
|
let canvasPixelated = false;
|
|
2144
2159
|
|
|
2145
2160
|
/** Disables texture filtering for crisper pixel art
|
|
2161
|
+
* - Leave true for pixel art so sprites stay sharp when scaled (uses NEAREST filtering)
|
|
2162
|
+
* - Set false for smooth/high-resolution art to enable bilinear filtering and mipmaps
|
|
2146
2163
|
* @type {boolean}
|
|
2147
2164
|
* @default
|
|
2148
2165
|
* @memberof Settings */
|
|
@@ -2496,6 +2513,7 @@ function setCanvasPixelated(pixelated)
|
|
|
2496
2513
|
}
|
|
2497
2514
|
|
|
2498
2515
|
/** Disables texture filtering for crisper pixel art
|
|
2516
|
+
* - Leave true for pixel art; set false for smooth/high-resolution art
|
|
2499
2517
|
* @param {boolean} pixelated
|
|
2500
2518
|
* @memberof Settings */
|
|
2501
2519
|
function setTilesPixelated(pixelated) { tilesPixelated = pixelated; }
|
|
@@ -2963,8 +2981,6 @@ class EngineObject
|
|
|
2963
2981
|
|
|
2964
2982
|
// notify objects of collision and check if should be resolved
|
|
2965
2983
|
const collide1 = this.collideWithObject(o);
|
|
2966
|
-
// callback may have destroyed us; stop resolving against more objects
|
|
2967
|
-
if (this.destroyed) return;
|
|
2968
2984
|
const collide2 = o.collideWithObject(this);
|
|
2969
2985
|
if (!collide1 || !collide2) continue;
|
|
2970
2986
|
|
|
@@ -4387,6 +4403,62 @@ function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
|
|
|
4387
4403
|
* @memberof Draw */
|
|
4388
4404
|
function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
4389
4405
|
|
|
4406
|
+
/** Fit the camera to a rectangle in world space by setting cameraPos and cameraScale
|
|
4407
|
+
* - worldMargin pads the content rectangle in world units, so the gap scales with the content on resize
|
|
4408
|
+
* - screenInset reserves space in screen pixels on each viewport edge (for example a HUD band) and
|
|
4409
|
+
* re-centers the content away from that edge, so the reserved band stays a fixed pixel size on resize
|
|
4410
|
+
* - worldMargin and screenInset may each be a number for all sides, a Vector2 (x=left/right, y=top/bottom),
|
|
4411
|
+
* or an object with any of {top, right, bottom, left}
|
|
4412
|
+
* @param {Vector2} center - Center of the rectangle in world space
|
|
4413
|
+
* @param {Vector2} size - Size of the rectangle in world space
|
|
4414
|
+
* @param {number|Vector2|Object} [worldMargin] - World space padding added around the content rectangle
|
|
4415
|
+
* @param {number|Vector2|Object} [screenInset] - Screen space padding in pixels reserved on each viewport edge
|
|
4416
|
+
* @return {number} - The new camera scale
|
|
4417
|
+
* @memberof Draw */
|
|
4418
|
+
function cameraFit(center, size, worldMargin, screenInset)
|
|
4419
|
+
{
|
|
4420
|
+
ASSERT(isVector2(center), 'center must be a vec2');
|
|
4421
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
4422
|
+
|
|
4423
|
+
// pad the content
|
|
4424
|
+
const margin = padSides(worldMargin);
|
|
4425
|
+
const inset = padSides(screenInset);
|
|
4426
|
+
const worldW = size.x + margin.left + margin.right;
|
|
4427
|
+
const worldH = size.y + margin.top + margin.bottom;
|
|
4428
|
+
const viewW = mainCanvasSize.x - inset.left - inset.right;
|
|
4429
|
+
const viewH = mainCanvasSize.y - inset.top - inset.bottom;
|
|
4430
|
+
|
|
4431
|
+
// bail on a degenerate rect or viewport rather than NaN the camera
|
|
4432
|
+
if (!(worldW > 0 && worldH > 0 && viewW > 0 && viewH > 0))
|
|
4433
|
+
return cameraScale;
|
|
4434
|
+
|
|
4435
|
+
// scale to fit the padded content
|
|
4436
|
+
cameraScale = min(viewW / worldW, viewH / worldH);
|
|
4437
|
+
|
|
4438
|
+
// calculate offset vectors
|
|
4439
|
+
const marginVector = vec2(margin.right - margin.left, margin.top - margin.bottom).scale(.5);
|
|
4440
|
+
const insetVector = vec2(inset.right - inset.left, inset.top - inset.bottom).scale(.5 / cameraScale);
|
|
4441
|
+
|
|
4442
|
+
// apply the offsets and return camera scale
|
|
4443
|
+
cameraPos = center.add(marginVector).add(insetVector);
|
|
4444
|
+
return cameraScale;
|
|
4445
|
+
|
|
4446
|
+
function padSides(p)
|
|
4447
|
+
{
|
|
4448
|
+
// normalize a padding option to {top, right, bottom, left}
|
|
4449
|
+
if (p === undefined || isNumber(p))
|
|
4450
|
+
p = vec2(p);
|
|
4451
|
+
if (isVector2(p))
|
|
4452
|
+
return { top: p.y, right: p.x, bottom: p.y, left: p.x };
|
|
4453
|
+
return {
|
|
4454
|
+
top: p.top || 0,
|
|
4455
|
+
right: p.right || 0,
|
|
4456
|
+
bottom: p.bottom || 0,
|
|
4457
|
+
left: p.left || 0,
|
|
4458
|
+
};
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
|
|
4390
4462
|
/** Check if a box, point, or circle is on screen with a circle test
|
|
4391
4463
|
* If size is a Vector2, uses the length as diameter
|
|
4392
4464
|
* This can be used to cull offscreen objects from render or update
|
|
@@ -4425,14 +4497,13 @@ function isOnScreen(pos, size=0)
|
|
|
4425
4497
|
y + size > -h && y - size < h;
|
|
4426
4498
|
}
|
|
4427
4499
|
|
|
4428
|
-
/** Enable
|
|
4500
|
+
/** Enable additive blending
|
|
4429
4501
|
* @param {boolean} [additive]
|
|
4430
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4431
4502
|
* @memberof Draw */
|
|
4432
|
-
function
|
|
4503
|
+
function setAdditiveBlendMode(additive=true)
|
|
4433
4504
|
{
|
|
4434
4505
|
glAdditive = additive;
|
|
4435
|
-
|
|
4506
|
+
drawContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
4436
4507
|
}
|
|
4437
4508
|
|
|
4438
4509
|
/** Combines LittleJS canvases onto the main canvas
|
|
@@ -4758,11 +4829,30 @@ let mouseWheel = 0;
|
|
|
4758
4829
|
* @memberof Input */
|
|
4759
4830
|
let mouseInWindow = true;
|
|
4760
4831
|
|
|
4761
|
-
/**
|
|
4832
|
+
/** True if a gamepad is the most recently used input device.
|
|
4833
|
+
* Equivalent to usingGamepadInput(); derived from lastInputDevice each frame.
|
|
4762
4834
|
* @type {boolean}
|
|
4763
4835
|
* @memberof Input */
|
|
4764
4836
|
let isUsingGamepad = false;
|
|
4765
4837
|
|
|
4838
|
+
/** The most recently used input device: 'mouse' | 'keyboard' | 'gamepad'.
|
|
4839
|
+
* Sticky: it holds its value while every device is idle, so a mouse-follow
|
|
4840
|
+
* control (e.g. paddle = mousePos) won't snap back the instant the stick/keys
|
|
4841
|
+
* are released. With several devices in play at once (e.g. keyboard to move +
|
|
4842
|
+
* mouse to aim) it tracks whichever was touched last each frame, so it may
|
|
4843
|
+
* alternate — that's intended; use it to pick which control drives a shared
|
|
4844
|
+
* action. Updated every frame by inputUpdate().
|
|
4845
|
+
* @type {string}
|
|
4846
|
+
* @memberof Input */
|
|
4847
|
+
let lastInputDevice = 'mouse';
|
|
4848
|
+
|
|
4849
|
+
/** Screen-pixel mouse movement per frame that counts as "using the mouse"
|
|
4850
|
+
* (so sub-pixel hand jitter doesn't steal focus from the keyboard/gamepad).
|
|
4851
|
+
* @type {number}
|
|
4852
|
+
* @default
|
|
4853
|
+
* @memberof Input */
|
|
4854
|
+
let inputMouseMoveThreshold = 6;
|
|
4855
|
+
|
|
4766
4856
|
/** Prevents input continuing to the default browser handling (true by default)
|
|
4767
4857
|
* @type {boolean}
|
|
4768
4858
|
* @memberof Input */
|
|
@@ -4783,6 +4873,18 @@ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
|
4783
4873
|
* @memberof Input */
|
|
4784
4874
|
function setInputPreventDefault(preventDefault=true) { inputPreventDefault = preventDefault; }
|
|
4785
4875
|
|
|
4876
|
+
/** Set the screen-pixel mouse movement per frame that counts as using the mouse
|
|
4877
|
+
* @param {number} threshold
|
|
4878
|
+
* @memberof Input */
|
|
4879
|
+
function setInputMouseMoveThreshold(threshold) { inputMouseMoveThreshold = threshold; }
|
|
4880
|
+
|
|
4881
|
+
/** @return {boolean} - Is the mouse the most recently used input device? @memberof Input */
|
|
4882
|
+
function usingMouseInput() { return lastInputDevice === 'mouse'; }
|
|
4883
|
+
/** @return {boolean} - Is the keyboard the most recently used input device? @memberof Input */
|
|
4884
|
+
function usingKeyboardInput() { return lastInputDevice === 'keyboard'; }
|
|
4885
|
+
/** @return {boolean} - Is a gamepad the most recently used input device? @memberof Input */
|
|
4886
|
+
function usingGamepadInput() { return lastInputDevice === 'gamepad'; }
|
|
4887
|
+
|
|
4786
4888
|
/** Clears an input key state
|
|
4787
4889
|
* @param {string|number} key
|
|
4788
4890
|
* @param {number} [device]
|
|
@@ -5083,7 +5185,6 @@ function inputInit()
|
|
|
5083
5185
|
{
|
|
5084
5186
|
if (!e.repeat)
|
|
5085
5187
|
{
|
|
5086
|
-
isUsingGamepad = false;
|
|
5087
5188
|
inputData[0][e.code] = 3;
|
|
5088
5189
|
if (inputWASDEmulateDirection)
|
|
5089
5190
|
inputData[0][remapKey(e.code)] = 3;
|
|
@@ -5142,7 +5243,6 @@ function inputInit()
|
|
|
5142
5243
|
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
5143
5244
|
audioContext.resume();
|
|
5144
5245
|
|
|
5145
|
-
isUsingGamepad = false;
|
|
5146
5246
|
inputData[0][e.button] = 3;
|
|
5147
5247
|
|
|
5148
5248
|
const mousePosScreenLast = mousePosScreen;
|
|
@@ -5233,10 +5333,7 @@ function inputInit()
|
|
|
5233
5333
|
if (wasTouching)
|
|
5234
5334
|
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
5235
5335
|
else
|
|
5236
|
-
{
|
|
5237
5336
|
inputData[0][button] = 3;
|
|
5238
|
-
isUsingGamepad = false; // a passthrough tap is mouse-style input
|
|
5239
|
-
}
|
|
5240
5337
|
}
|
|
5241
5338
|
else if (wasTouching)
|
|
5242
5339
|
inputData[0][button] = inputData[0][button] & 2 | 4;
|
|
@@ -5282,7 +5379,43 @@ function inputUpdate()
|
|
|
5282
5379
|
|
|
5283
5380
|
// update gamepads if enabled
|
|
5284
5381
|
gamepadsUpdate();
|
|
5285
|
-
|
|
5382
|
+
|
|
5383
|
+
// update most recently used input device
|
|
5384
|
+
updateLastInputDevice();
|
|
5385
|
+
|
|
5386
|
+
function updateLastInputDevice()
|
|
5387
|
+
{
|
|
5388
|
+
// mouse: any button held or moved
|
|
5389
|
+
const mouseActive = mouseIsDown(0) || mouseIsDown(1) || mouseIsDown(2) || mouseDeltaScreen.length() > inputMouseMoveThreshold;
|
|
5390
|
+
|
|
5391
|
+
// gamepad: any button held or stick moved
|
|
5392
|
+
let gamepadActive = false;
|
|
5393
|
+
for (let s = gamepadStickCount(); s-- && !gamepadActive;)
|
|
5394
|
+
gamepadActive = gamepadStick(s).lengthSquared() > .04;
|
|
5395
|
+
for (let b = 17; b-- && !gamepadActive;)
|
|
5396
|
+
gamepadActive = gamepadIsDown(b);
|
|
5397
|
+
|
|
5398
|
+
// keyboard: any non-mouse key down
|
|
5399
|
+
let keyboardActive = false;
|
|
5400
|
+
for (const k in inputData[0])
|
|
5401
|
+
if (isNaN(+k) && (inputData[0][k] & 1))
|
|
5402
|
+
{
|
|
5403
|
+
keyboardActive = true;
|
|
5404
|
+
break;
|
|
5405
|
+
}
|
|
5406
|
+
|
|
5407
|
+
// update the last input
|
|
5408
|
+
if (gamepadActive)
|
|
5409
|
+
lastInputDevice = 'gamepad';
|
|
5410
|
+
else if (mouseActive)
|
|
5411
|
+
lastInputDevice = 'mouse';
|
|
5412
|
+
else if (keyboardActive)
|
|
5413
|
+
lastInputDevice = 'keyboard';
|
|
5414
|
+
|
|
5415
|
+
// set flag if gamepad is last device
|
|
5416
|
+
isUsingGamepad = lastInputDevice === 'gamepad';
|
|
5417
|
+
}
|
|
5418
|
+
|
|
5286
5419
|
// gamepads are updated by engine every frame automatically
|
|
5287
5420
|
function gamepadsUpdate()
|
|
5288
5421
|
{
|
|
@@ -5405,7 +5538,6 @@ function inputUpdate()
|
|
|
5405
5538
|
gamepadHadInput[i] = true;
|
|
5406
5539
|
if (!gamepadHadInput[gamepadPrimary])
|
|
5407
5540
|
gamepadPrimary = i;
|
|
5408
|
-
isUsingGamepad ||= (gamepadPrimary === i);
|
|
5409
5541
|
}
|
|
5410
5542
|
|
|
5411
5543
|
if (gamepad.mapping === 'standard')
|
|
@@ -5552,12 +5684,19 @@ function touchGamepadRelayout()
|
|
|
5552
5684
|
const setZone = (z, css)=> z.style.cssText =
|
|
5553
5685
|
'position:absolute;pointer-events:auto;touch-action:none;' + css;
|
|
5554
5686
|
|
|
5555
|
-
if (paused
|
|
5687
|
+
if (paused)
|
|
5556
5688
|
{
|
|
5557
|
-
// while paused,
|
|
5558
|
-
|
|
5689
|
+
// the gamepad is hidden while paused, so its side zones must not capture
|
|
5690
|
+
// touches - otherwise they silently steal taps from menus and dialogs
|
|
5559
5691
|
for (const zone of touchGamepadSideZones) zone.style.display = 'none';
|
|
5560
|
-
|
|
5692
|
+
if (touchGamepadCenterButtonSize)
|
|
5693
|
+
{
|
|
5694
|
+
// any touch presses start
|
|
5695
|
+
setZone(touchGamepadZoneC, 'inset:0');
|
|
5696
|
+
touchGamepadZoneC.style.display = '';
|
|
5697
|
+
}
|
|
5698
|
+
else
|
|
5699
|
+
touchGamepadZoneC.style.display = 'none';
|
|
5561
5700
|
}
|
|
5562
5701
|
else
|
|
5563
5702
|
{
|
|
@@ -5833,7 +5972,6 @@ function touchGamepadPointerDown(e, zone)
|
|
|
5833
5972
|
e.preventDefault();
|
|
5834
5973
|
zone.setPointerCapture(e.pointerId);
|
|
5835
5974
|
touchGamepadTimer.set();
|
|
5836
|
-
isUsingGamepad = true;
|
|
5837
5975
|
|
|
5838
5976
|
// resume audio on first interaction
|
|
5839
5977
|
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
@@ -7849,9 +7987,6 @@ class Particle
|
|
|
7849
7987
|
this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
|
|
7850
7988
|
this.color.a = (p2 * this.colorStart.a + p1 * this.colorEnd.a) * alphaFade;
|
|
7851
7989
|
|
|
7852
|
-
// draw the particle
|
|
7853
|
-
additive && setBlendMode(true);
|
|
7854
|
-
|
|
7855
7990
|
// update the position and angle for drawing
|
|
7856
7991
|
const pos = particleDrawPos.set(this.pos.x, this.pos.y);
|
|
7857
7992
|
let angle = this.angle;
|
|
@@ -7864,6 +7999,9 @@ class Particle
|
|
|
7864
7999
|
emitter.pos.y + pos.x*s + pos.y*c);
|
|
7865
8000
|
angle += a;
|
|
7866
8001
|
}
|
|
8002
|
+
|
|
8003
|
+
// draw the particle
|
|
8004
|
+
additive && setAdditiveBlendMode();
|
|
7867
8005
|
if (trailScale)
|
|
7868
8006
|
{
|
|
7869
8007
|
// trail style particles
|
|
@@ -7881,7 +8019,7 @@ class Particle
|
|
|
7881
8019
|
}
|
|
7882
8020
|
else
|
|
7883
8021
|
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
7884
|
-
additive &&
|
|
8022
|
+
additive && setAdditiveBlendMode(false);
|
|
7885
8023
|
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
7886
8024
|
}
|
|
7887
8025
|
}
|
|
@@ -9791,7 +9929,7 @@ class LightSystemPlugin
|
|
|
9791
9929
|
|
|
9792
9930
|
// 3. walk engineObjects calling renderLight() — additive blend
|
|
9793
9931
|
// (lightmap accumulates raw additive color contributions)
|
|
9794
|
-
|
|
9932
|
+
setAdditiveBlendMode();
|
|
9795
9933
|
glContext.enable(glContext.BLEND);
|
|
9796
9934
|
glContext.blendFunc(glContext.ONE, glContext.ONE);
|
|
9797
9935
|
|
|
@@ -9825,7 +9963,7 @@ class LightSystemPlugin
|
|
|
9825
9963
|
// is, and any debug text / future draw could sample the lightmap)
|
|
9826
9964
|
if (glActiveTexture)
|
|
9827
9965
|
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
9828
|
-
|
|
9966
|
+
setAdditiveBlendMode(prevAdditive);
|
|
9829
9967
|
glSetInstancedMode(true);
|
|
9830
9968
|
}
|
|
9831
9969
|
function lightSystemContextLost()
|
|
@@ -10234,7 +10372,7 @@ class UISystemPlugin
|
|
|
10234
10372
|
let targetPos, targetSize;
|
|
10235
10373
|
if (o.parent)
|
|
10236
10374
|
{
|
|
10237
|
-
targetPos = o.parent.
|
|
10375
|
+
targetPos = o.parent.nativePos;
|
|
10238
10376
|
targetSize = o.parent.size;
|
|
10239
10377
|
}
|
|
10240
10378
|
else
|
|
@@ -10248,7 +10386,7 @@ class UISystemPlugin
|
|
|
10248
10386
|
}
|
|
10249
10387
|
|
|
10250
10388
|
const a = o.anchor;
|
|
10251
|
-
o.
|
|
10389
|
+
o.nativePos = targetPos
|
|
10252
10390
|
.add(targetSize.multiply(a).scale(.5)) // anchor point on target
|
|
10253
10391
|
.subtract(o.size.multiply(a).scale(.5)) // pivot shift on self
|
|
10254
10392
|
.add(o.localPos); // user offset
|
|
@@ -10354,13 +10492,18 @@ class UISystemPlugin
|
|
|
10354
10492
|
|
|
10355
10493
|
function updateObject(o)
|
|
10356
10494
|
{
|
|
10357
|
-
if (!o.visible) return;
|
|
10495
|
+
if (o.destroyed || !o.visible) return;
|
|
10358
10496
|
|
|
10359
10497
|
// update in reverse order to detect mouse enter/leave
|
|
10360
10498
|
updateTransforms(o);
|
|
10361
10499
|
for (let i=o.children.length; i--;)
|
|
10362
|
-
|
|
10363
|
-
|
|
10500
|
+
{
|
|
10501
|
+
// a child may destroy siblings mid-update (e.g. dialog close)
|
|
10502
|
+
const child = o.children[i];
|
|
10503
|
+
child && updateObject(child);
|
|
10504
|
+
}
|
|
10505
|
+
if (!o.destroyed)
|
|
10506
|
+
o.update();
|
|
10364
10507
|
}
|
|
10365
10508
|
}
|
|
10366
10509
|
function uiRender()
|
|
@@ -10784,10 +10927,15 @@ class UIObject
|
|
|
10784
10927
|
ASSERT(isVector2(pos), 'ui object pos must be a vec2');
|
|
10785
10928
|
ASSERT(isVector2(size), 'ui object size must be a vec2');
|
|
10786
10929
|
|
|
10787
|
-
/** @property {Vector2} -
|
|
10930
|
+
/** @property {Vector2} - Position you set: an offset from this object's
|
|
10931
|
+
* anchor point (the parent box, or the canvas for roots). This is the
|
|
10932
|
+
* input that controls placement — set this, not nativePos. */
|
|
10788
10933
|
this.localPos = pos.copy();
|
|
10789
|
-
/** @property {Vector2} -
|
|
10790
|
-
|
|
10934
|
+
/** @property {Vector2} - Resolved position in native UI space, recomputed
|
|
10935
|
+
* every frame from localPos + anchor (and nativeHeight, if set). This is a
|
|
10936
|
+
* derived output used for drawing and hit-testing; assigning to it has no
|
|
10937
|
+
* effect since it is overwritten each frame. Set localPos instead. */
|
|
10938
|
+
this.nativePos = pos.copy();
|
|
10791
10939
|
/** @property {Vector2} - Screen space size of the object */
|
|
10792
10940
|
this.size = size.copy();
|
|
10793
10941
|
/** @property {Color} - Color of the object */
|
|
@@ -10922,7 +11070,7 @@ class UIObject
|
|
|
10922
11070
|
const size = !isTouchDevice ? this.size :
|
|
10923
11071
|
this.size.add(vec2(this.extraTouchSize || 0));
|
|
10924
11072
|
const pos = uiSystem.screenToNative(mousePosScreen);
|
|
10925
|
-
return isOverlapping(this.
|
|
11073
|
+
return isOverlapping(this.nativePos, size, pos);
|
|
10926
11074
|
}
|
|
10927
11075
|
|
|
10928
11076
|
/** Update the object, called automatically by plugin once each frame */
|
|
@@ -11012,7 +11160,7 @@ class UIObject
|
|
|
11012
11160
|
this.color : this.color;
|
|
11013
11161
|
const lineWidth = this.lineWidth * (isNavigationObject ? 1.5 : 1);
|
|
11014
11162
|
|
|
11015
|
-
uiSystem.drawRect(this.
|
|
11163
|
+
uiSystem.drawRect(this.nativePos, this.size, color, lineWidth, lineColor, this.cornerRadius, this.gradientColor, this.shadowColor, this.shadowBlur, this.shadowOffset);
|
|
11016
11164
|
}
|
|
11017
11165
|
|
|
11018
11166
|
/** Get the size for text with overrides and scale
|
|
@@ -11049,8 +11197,8 @@ class UIObject
|
|
|
11049
11197
|
let text = 'type = ' + this.constructor.name;
|
|
11050
11198
|
if (this.text)
|
|
11051
11199
|
text += '\ntext = ' + this.text;
|
|
11052
|
-
if (this.
|
|
11053
|
-
text += '\
|
|
11200
|
+
if (this.nativePos.x || this.nativePos.y)
|
|
11201
|
+
text += '\nnativePos = ' + this.nativePos;
|
|
11054
11202
|
if (this.localPos.x || this.localPos.y)
|
|
11055
11203
|
text += '\nlocalPos = ' + this.localPos;
|
|
11056
11204
|
if (this.size.x || this.size.y)
|
|
@@ -11070,7 +11218,7 @@ class UIObject
|
|
|
11070
11218
|
this.isHoverObject() ? YELLOW :
|
|
11071
11219
|
this.disabled ? PURPLE :
|
|
11072
11220
|
this.interactive ? RED : BLUE;
|
|
11073
|
-
uiSystem.drawRect(this.
|
|
11221
|
+
uiSystem.drawRect(this.nativePos, this.size, CLEAR_BLACK, 4, color);
|
|
11074
11222
|
}
|
|
11075
11223
|
|
|
11076
11224
|
/** Internal function called when object is clicked
|
|
@@ -11153,7 +11301,7 @@ class UIText extends UIObject
|
|
|
11153
11301
|
|
|
11154
11302
|
// render the text
|
|
11155
11303
|
const textSize = this.getTextSize();
|
|
11156
|
-
uiSystem.drawText(this.text, this.
|
|
11304
|
+
uiSystem.drawText(this.text, this.nativePos, textSize, this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow, this.shadowColor, this.shadowBlur, this.shadowOffset);
|
|
11157
11305
|
}
|
|
11158
11306
|
}
|
|
11159
11307
|
|
|
@@ -11248,7 +11396,7 @@ class UITextInput extends UIObject
|
|
|
11248
11396
|
let text = this.text;
|
|
11249
11397
|
if (this.isKeyInputObject()) // add a cursor to end of text
|
|
11250
11398
|
text += timeReal%1 < .5 ? '█' : '░';
|
|
11251
|
-
uiSystem.drawText(text, this.
|
|
11399
|
+
uiSystem.drawText(text, this.nativePos, textSize,
|
|
11252
11400
|
this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
11253
11401
|
}
|
|
11254
11402
|
}
|
|
@@ -11291,7 +11439,7 @@ class UITile extends UIObject
|
|
|
11291
11439
|
}
|
|
11292
11440
|
render()
|
|
11293
11441
|
{
|
|
11294
|
-
uiSystem.drawTile(this.
|
|
11442
|
+
uiSystem.drawTile(this.nativePos, this.size, this.tileInfo, this.color, this.angle, this.mirror, this.shadowColor, this.shadowBlur, this.shadowOffset);
|
|
11295
11443
|
}
|
|
11296
11444
|
}
|
|
11297
11445
|
|
|
@@ -11330,7 +11478,7 @@ class UIButton extends UIObject
|
|
|
11330
11478
|
|
|
11331
11479
|
// draw the text scaled to fit
|
|
11332
11480
|
const textSize = this.getTextSize();
|
|
11333
|
-
uiSystem.drawText(this.text, this.
|
|
11481
|
+
uiSystem.drawText(this.text, this.nativePos.add(this.textOffset), textSize,
|
|
11334
11482
|
this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
11335
11483
|
}
|
|
11336
11484
|
}
|
|
@@ -11378,13 +11526,13 @@ class UICheckbox extends UIObject
|
|
|
11378
11526
|
const p = this.cornerRadius / min(this.size.x, this.size.y) * 2;
|
|
11379
11527
|
const length = lerp(1, 2**.5/2, p) / 2;
|
|
11380
11528
|
let s = this.size.scale(length);
|
|
11381
|
-
uiSystem.drawLine(this.
|
|
11382
|
-
uiSystem.drawLine(this.
|
|
11529
|
+
uiSystem.drawLine(this.nativePos.add(s.multiply(vec2(-1))), this.nativePos.add(s.multiply(vec2(1))), this.lineWidth, this.lineColor);
|
|
11530
|
+
uiSystem.drawLine(this.nativePos.add(s.multiply(vec2(-1,1))), this.nativePos.add(s.multiply(vec2(1,-1))), this.lineWidth, this.lineColor);
|
|
11383
11531
|
}
|
|
11384
11532
|
|
|
11385
11533
|
// draw the text next to the checkbox
|
|
11386
11534
|
const textSize = this.getTextSize();
|
|
11387
|
-
const pos = this.
|
|
11535
|
+
const pos = this.nativePos.add(vec2(this.size.x,0));
|
|
11388
11536
|
uiSystem.drawText(this.text, pos, textSize,
|
|
11389
11537
|
this.textColor, this.textLineWidth, this.textLineColor, 'left', this.font, this.fontStyle, false, this.textShadow);
|
|
11390
11538
|
}
|
|
@@ -11440,7 +11588,7 @@ class UISlider extends UIObject
|
|
|
11440
11588
|
const isHorizontal = this.size.x > this.size.y;
|
|
11441
11589
|
const handleSize = isHorizontal ? this.size.y : this.size.x;
|
|
11442
11590
|
const barSize = isHorizontal ? this.size.x : this.size.y;
|
|
11443
|
-
const centerPos = isHorizontal ? this.
|
|
11591
|
+
const centerPos = isHorizontal ? this.nativePos.x : this.nativePos.y;
|
|
11444
11592
|
|
|
11445
11593
|
// check if value changed
|
|
11446
11594
|
const handleWidth = barSize - handleSize;
|
|
@@ -11474,7 +11622,7 @@ class UISlider extends UIObject
|
|
|
11474
11622
|
const minWidth = min(handleWidth, this.cornerRadius * 2);
|
|
11475
11623
|
const progressWidth = lerp(minWidth, barWidth, this.value);
|
|
11476
11624
|
const p = (progressWidth - barWidth) * (isHorizontal ? .5 : -.5);
|
|
11477
|
-
const pos = this.
|
|
11625
|
+
const pos = this.nativePos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
|
|
11478
11626
|
const color = this.disabled ? this.disabledColor : this.handleColor;
|
|
11479
11627
|
const drawSize = isHorizontal ?
|
|
11480
11628
|
vec2(progressWidth, this.size.y) : vec2(this.size.x, progressWidth);
|
|
@@ -11485,7 +11633,7 @@ class UISlider extends UIObject
|
|
|
11485
11633
|
// draw the slider handle
|
|
11486
11634
|
const value = clamp(isHorizontal ? this.value : 1 - this.value);
|
|
11487
11635
|
const p = (barWidth - handleWidth) * (value - .5);
|
|
11488
|
-
const pos = this.
|
|
11636
|
+
const pos = this.nativePos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
|
|
11489
11637
|
const color = this.disabled ? this.disabledColor : this.handleColor;
|
|
11490
11638
|
const drawSize = vec2(handleWidth);
|
|
11491
11639
|
uiSystem.drawRect(pos, drawSize, color, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
|
|
@@ -11493,7 +11641,7 @@ class UISlider extends UIObject
|
|
|
11493
11641
|
|
|
11494
11642
|
// draw the text scaled to fit on the slider
|
|
11495
11643
|
const textSize = this.getTextSize();
|
|
11496
|
-
uiSystem.drawText(this.text, this.
|
|
11644
|
+
uiSystem.drawText(this.text, this.nativePos, textSize,
|
|
11497
11645
|
this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
11498
11646
|
}
|
|
11499
11647
|
navigatePressed()
|
|
@@ -11632,7 +11780,7 @@ class UIVideo extends UIObject
|
|
|
11632
11780
|
const context = uiSystem.uiContext;
|
|
11633
11781
|
const s = this.size;
|
|
11634
11782
|
context.save();
|
|
11635
|
-
context.translate(this.
|
|
11783
|
+
context.translate(this.nativePos.x, this.nativePos.y);
|
|
11636
11784
|
context.drawImage(this.video, -s.x/2, -s.y/2, s.x, s.y);
|
|
11637
11785
|
context.restore();
|
|
11638
11786
|
}
|
|
@@ -13861,8 +14009,8 @@ async function box2dInit()
|
|
|
13861
14009
|
* This function can not apply color because it draws using the 2d context
|
|
13862
14010
|
* @param {Vector2} pos - Screen space position
|
|
13863
14011
|
* @param {Vector2} size - Screen space size
|
|
13864
|
-
* @param {TileInfo} startTile -
|
|
13865
|
-
* @param {number} [borderSize] -
|
|
14012
|
+
* @param {TileInfo} startTile - Top-left tile of the 3x3 block to sample (see drawNineSlice)
|
|
14013
|
+
* @param {number} [borderSize] - Rendered thickness of the border sections
|
|
13866
14014
|
* @param {number} [extraSpace] - Extra spacing adjustment
|
|
13867
14015
|
* @param {number} [angle] - Angle to rotate by
|
|
13868
14016
|
* @memberof DrawUtilities */
|
|
@@ -13873,11 +14021,16 @@ function drawNineSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2,
|
|
|
13873
14021
|
|
|
13874
14022
|
/** Draw a scalable nine-slice UI element in world space
|
|
13875
14023
|
* This function can apply color and additive color if WebGL is enabled
|
|
14024
|
+
* The nine-slice samples a 3x3 block of tiles from the tilesheet, it does not
|
|
14025
|
+
* subdivide a single tile. Pass the top-left tile of that block as startTile;
|
|
14026
|
+
* the other 8 tiles (edges, corners, and center) are taken automatically from
|
|
14027
|
+
* the 3x3 grid of tiles extending right and down from it. borderSize only sets
|
|
14028
|
+
* the rendered thickness of the edges and corners, not how the texture is cut.
|
|
13876
14029
|
* @param {Vector2} pos - World space position
|
|
13877
14030
|
* @param {Vector2} size - World space size
|
|
13878
|
-
* @param {TileInfo} startTile -
|
|
14031
|
+
* @param {TileInfo} startTile - Top-left tile of the 3x3 block to sample the nine-slice from
|
|
13879
14032
|
* @param {Color} [color] - Color to modulate with
|
|
13880
|
-
* @param {number} [borderSize] -
|
|
14033
|
+
* @param {number} [borderSize] - Rendered thickness of the border sections
|
|
13881
14034
|
* @param {Color} [additiveColor] - Additive color
|
|
13882
14035
|
* @param {number} [extraSpace] - Extra spacing adjustment
|
|
13883
14036
|
* @param {number} [angle] - Angle to rotate by
|
|
@@ -13887,7 +14040,8 @@ function drawNineSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2,
|
|
|
13887
14040
|
* @memberof DrawUtilities */
|
|
13888
14041
|
function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor, extraSpace=.05, angle=0, useWebGL=glEnable, screenSpace, context)
|
|
13889
14042
|
{
|
|
13890
|
-
// setup nine slice tiles
|
|
14043
|
+
// setup nine slice tiles - startTile is the top-left of a 3x3 tile block,
|
|
14044
|
+
// so the center tile is one tile down and right from it
|
|
13891
14045
|
const centerTile = startTile.offset(startTile.size);
|
|
13892
14046
|
const centerSize = size.add(vec2(extraSpace-borderSize*2));
|
|
13893
14047
|
const cornerSize = vec2(borderSize);
|
|
@@ -13921,8 +14075,8 @@ function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor,
|
|
|
13921
14075
|
* This function can not apply color because it draws using the 2d context
|
|
13922
14076
|
* @param {Vector2} pos - Screen space position
|
|
13923
14077
|
* @param {Vector2} size - Screen space size
|
|
13924
|
-
* @param {TileInfo} startTile -
|
|
13925
|
-
* @param {number} [borderSize] -
|
|
14078
|
+
* @param {TileInfo} startTile - First of 3 consecutive tiles: corner, side, center (see drawThreeSlice)
|
|
14079
|
+
* @param {number} [borderSize] - Rendered thickness of the border sections
|
|
13926
14080
|
* @param {number} [extraSpace] - Extra spacing adjustment
|
|
13927
14081
|
* @param {number} [angle] - Angle to rotate by
|
|
13928
14082
|
* @memberof DrawUtilities */
|
|
@@ -13933,11 +14087,15 @@ function drawThreeSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2,
|
|
|
13933
14087
|
|
|
13934
14088
|
/** Draw a scalable three-slice UI element in world space
|
|
13935
14089
|
* This function can apply color and additive color if WebGL is enabled
|
|
14090
|
+
* The three-slice samples 3 consecutive tiles from the tilesheet, it does not
|
|
14091
|
+
* subdivide a single tile. Pass the first tile as startTile; the three tiles
|
|
14092
|
+
* are used in order as corner, side, and center, then rotated and mirrored to
|
|
14093
|
+
* build all four edges and corners. borderSize only sets the rendered thickness.
|
|
13936
14094
|
* @param {Vector2} pos - World space position
|
|
13937
14095
|
* @param {Vector2} size - World space size
|
|
13938
|
-
* @param {TileInfo} startTile -
|
|
14096
|
+
* @param {TileInfo} startTile - First of 3 consecutive tiles (corner, side, center) for the three-slice
|
|
13939
14097
|
* @param {Color} [color] - Color to modulate with
|
|
13940
|
-
* @param {number} [borderSize] -
|
|
14098
|
+
* @param {number} [borderSize] - Rendered thickness of the border sections
|
|
13941
14099
|
* @param {Color} [additiveColor] - Additive color
|
|
13942
14100
|
* @param {number} [extraSpace] - Extra spacing adjustment
|
|
13943
14101
|
* @param {number} [angle] - Angle to rotate by
|
|
@@ -13947,7 +14105,7 @@ function drawThreeSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2,
|
|
|
13947
14105
|
* @memberof DrawUtilities */
|
|
13948
14106
|
function drawThreeSlice(pos, size, startTile, color, borderSize=1, additiveColor, extraSpace=.05, angle=0, useWebGL=glEnable, screenSpace, context)
|
|
13949
14107
|
{
|
|
13950
|
-
// setup three slice tiles
|
|
14108
|
+
// setup three slice tiles - 3 tiles in a row starting at startTile
|
|
13951
14109
|
const cornerTile = startTile.frame(0);
|
|
13952
14110
|
const sideTile = startTile.frame(1);
|
|
13953
14111
|
const centerTile = startTile.frame(2);
|
|
@@ -13995,8 +14153,9 @@ function drawThreeSlice(pos, size, startTile, color, borderSize=1, additiveColor
|
|
|
13995
14153
|
* @memberof DrawUtilities */
|
|
13996
14154
|
function drawCrescent(pos, size=1, percent=0, color=WHITE, angle=0, invert=false, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
|
|
13997
14155
|
{
|
|
13998
|
-
|
|
13999
|
-
|
|
14156
|
+
// build local-space points and let drawPoly apply pos/angle so screen space works
|
|
14157
|
+
const points = getCrescentPoints(vec2(), size, percent, 0, invert);
|
|
14158
|
+
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, screenSpace, context);
|
|
14000
14159
|
}
|
|
14001
14160
|
|
|
14002
14161
|
/** Get the list of points that make up a crescent / moon-phase shape
|