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