@woosh/meep-engine 3.60.1 → 3.60.2
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/package.json +1 -1
- package/src/shade/playground/sh_exponential/main.js +2 -2
- package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_CLIPMAP_PROPOSAL_2026_10_27.md +4 -0
- package/src/shade/renderer/light/GPULightCollection.d.ts.map +1 -1
- package/src/shade/renderer/light/GPULightCollection.js +3 -5
- package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts +13 -5
- package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts.map +1 -1
- package/src/shade/renderer/light/environment/execute_filter_environment_map.js +133 -7
- package/src/shade/renderer/light/environment/graph_filter_environment_map.d.ts +9 -0
- package/src/shade/renderer/light/environment/graph_filter_environment_map.d.ts.map +1 -1
- package/src/shade/renderer/light/environment/graph_filter_environment_map.js +33 -4
- package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.d.ts +13 -0
- package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.d.ts.map +1 -0
- package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.js +169 -0
- package/src/shade/renderer/light/environment/shader_downsample_environment_map.d.ts +3 -0
- package/src/shade/renderer/light/environment/shader_downsample_environment_map.d.ts.map +1 -0
- package/src/shade/renderer/light/environment/shader_downsample_environment_map.js +105 -0
- package/src/shade/renderer/light/environment/sphere_probe_lod_to_roughness.d.ts +10 -0
- package/src/shade/renderer/light/environment/sphere_probe_lod_to_roughness.d.ts.map +1 -0
- package/src/shade/renderer/light/environment/sphere_probe_lod_to_roughness.js +23 -0
- package/src/shade/renderer/shader/chunk/light/probe/chunk_probe_visibility_weight.d.ts +8 -4
- package/src/shade/renderer/shader/chunk/light/probe/chunk_probe_visibility_weight.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/light/probe/chunk_probe_visibility_weight.js +9 -5
- package/src/view/elements/progress/segmented/GhostTrail.d.ts +6 -0
- package/src/view/elements/progress/segmented/GhostTrail.d.ts.map +1 -1
- package/src/view/elements/progress/segmented/GhostTrail.js +14 -0
- package/src/view/elements/progress/segmented/SegmentedResourceBarView.d.ts +55 -19
- package/src/view/elements/progress/segmented/SegmentedResourceBarView.d.ts.map +1 -1
- package/src/view/elements/progress/segmented/SegmentedResourceBarView.js +173 -120
- package/src/view/elements/progress/segmented/playground/main.js +18 -11
- package/src/view/elements/progress/segmented/segment_cell_extent.d.ts +14 -0
- package/src/view/elements/progress/segmented/segment_cell_extent.d.ts.map +1 -0
- package/src/view/elements/progress/segmented/segment_cell_extent.js +44 -0
- package/src/view/elements/progress/segmented/segment_mask_url.d.ts +24 -0
- package/src/view/elements/progress/segmented/segment_mask_url.d.ts.map +1 -0
- package/src/view/elements/progress/segmented/segment_mask_url.js +74 -0
- package/src/view/elements/progress/segmented/segment_mask_width.d.ts +18 -0
- package/src/view/elements/progress/segmented/segment_mask_width.d.ts.map +1 -0
- package/src/view/elements/progress/segmented/segment_mask_width.js +46 -0
- package/src/view/elements/progress/segmented/segmented-resource-bar.css +6 -10
|
@@ -8,8 +8,10 @@ import View from "../../../View.js";
|
|
|
8
8
|
import EmptyView from "../../EmptyView.js";
|
|
9
9
|
import { GhostTrail } from "./GhostTrail.js";
|
|
10
10
|
import { SEGMENT_TIERS_DEFAULT } from "./SEGMENT_TIERS_DEFAULT.js";
|
|
11
|
+
import { segment_cell_extent } from "./segment_cell_extent.js";
|
|
11
12
|
import { segment_cell_layout } from "./segment_cell_layout.js";
|
|
12
|
-
import {
|
|
13
|
+
import { segment_mask_apply, segment_mask_url } from "./segment_mask_url.js";
|
|
14
|
+
import { segment_mask_width } from "./segment_mask_width.js";
|
|
13
15
|
import { segment_tick_positions } from "./segment_tick_positions.js";
|
|
14
16
|
|
|
15
17
|
/**
|
|
@@ -19,37 +21,28 @@ import { segment_tick_positions } from "./segment_tick_positions.js";
|
|
|
19
21
|
const REBUILD_WIDTH_THRESHOLD_PX = 0.5;
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
|
-
*
|
|
24
|
+
* A measured CSS scale this close to 1 is no scale at all, only the rounding in how the browser reports widths
|
|
23
25
|
* @type {number}
|
|
24
26
|
*/
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const CELL_STATE_EMPTY = 0;
|
|
28
|
-
const CELL_STATE_PARTIAL = 1;
|
|
29
|
-
const CELL_STATE_FILLED = 2;
|
|
30
|
-
|
|
31
|
-
const CELL_STATE_CLASS = ['cell empty', 'cell partial', 'cell filled'];
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @param {number} x
|
|
35
|
-
* @returns {number}
|
|
36
|
-
*/
|
|
37
|
-
function snap_whole(x) {
|
|
38
|
-
const r = Math.round(x);
|
|
39
|
-
|
|
40
|
-
return Math.abs(x - r) <= CELL_FILL_TOLERANCE ? r : x;
|
|
41
|
-
}
|
|
27
|
+
const UNSCALED_TOLERANCE = 2e-3;
|
|
42
28
|
|
|
43
29
|
/**
|
|
44
30
|
* Resource bar in the style of a MOBA health bar: the values fill it end to end, notches mark fixed amounts, and
|
|
45
31
|
* a ghost behind the fill trails a drop so the loss stays readable for a moment.
|
|
46
32
|
*
|
|
47
33
|
* Two modes:
|
|
48
|
-
* - notches (default): a continuous fill, with a notch at every multiple of each {@link SegmentTier}'s unit
|
|
49
|
-
*
|
|
34
|
+
* - notches (default): a continuous fill, with a notch at every multiple of each {@link SegmentTier}'s unit. Each
|
|
35
|
+
* tier is one layer the width of the bar (`.notch.<class_name>`), which CSS paints — colour, height — and a mask
|
|
36
|
+
* cuts to that tier's notches;
|
|
37
|
+
* - cells (`cells` given): `count` separate cells with exact, identical gaps, for counting rounds and the like. One
|
|
38
|
+
* strip (`.cell-strip`, the empty cells' colour) holding a `.cell-ghost` and a `.cell-fill`, cut into cells by a
|
|
39
|
+
* mask; the fill and ghost end on a cell's edge, or part way across one that is filling.
|
|
50
40
|
*
|
|
51
|
-
*
|
|
52
|
-
* and the
|
|
41
|
+
* The masks are drawn in whole device pixels and the layers they cut placed so those pixels land on the screen's,
|
|
42
|
+
* at any pixel ratio and under a CSS scale: every notch of a tier is the same width and every gap the same, at
|
|
43
|
+
* 125% as at 100%. Colours, notch heights and the ghost are styled by `segmented-resource-bar.css` through
|
|
44
|
+
* `--segmented-bar-*` custom properties. Where masks cannot be drawn (no canvas, as under jsdom) the layers are
|
|
45
|
+
* placed but uncut.
|
|
53
46
|
*/
|
|
54
47
|
export class SegmentedResourceBarView extends View {
|
|
55
48
|
/**
|
|
@@ -162,6 +155,13 @@ export class SegmentedResourceBarView extends View {
|
|
|
162
155
|
this.__frame_id = 0;
|
|
163
156
|
this.__animate_ghost = this.__animate_ghost.bind(this);
|
|
164
157
|
|
|
158
|
+
/**
|
|
159
|
+
* The max moved since the fill was last drawn: the next fill resets the ghost rather than trailing a loss
|
|
160
|
+
* @type {boolean}
|
|
161
|
+
* @private
|
|
162
|
+
*/
|
|
163
|
+
this.__max_moved = false;
|
|
164
|
+
|
|
165
165
|
// what the layout was last built for, so it is rebuilt only when one of them moves
|
|
166
166
|
this.__built_max = NaN;
|
|
167
167
|
this.__built_width = NaN;
|
|
@@ -169,11 +169,25 @@ export class SegmentedResourceBarView extends View {
|
|
|
169
169
|
this.__built_origin_phase = NaN;
|
|
170
170
|
|
|
171
171
|
/**
|
|
172
|
-
*
|
|
173
|
-
* @type {
|
|
172
|
+
* Cell mode: the cells as last laid out across {@link __el_strip}, or null before the bar has been measured
|
|
173
|
+
* @type {SegmentCellLayout|null}
|
|
174
|
+
* @private
|
|
175
|
+
*/
|
|
176
|
+
this.__cell_layout = null;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Device pixels per CSS pixel the cells were laid out at
|
|
180
|
+
* @type {number}
|
|
174
181
|
* @private
|
|
175
182
|
*/
|
|
176
|
-
this.
|
|
183
|
+
this.__cell_ratio = 1;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* scratch: [x, width, ...] device-pixel columns for a mask
|
|
187
|
+
* @type {number[]}
|
|
188
|
+
* @private
|
|
189
|
+
*/
|
|
190
|
+
this.__columns = [];
|
|
177
191
|
|
|
178
192
|
/**
|
|
179
193
|
*
|
|
@@ -205,23 +219,20 @@ export class SegmentedResourceBarView extends View {
|
|
|
205
219
|
this.__value_fills = [];
|
|
206
220
|
|
|
207
221
|
/**
|
|
222
|
+
* Notch mode: one layer per tier, the whole bar wide, styled by the tier's class and masked to its notches
|
|
208
223
|
* @type {HTMLElement[]}
|
|
209
224
|
* @private
|
|
210
225
|
*/
|
|
211
|
-
this.
|
|
226
|
+
this.__tier_layers = [];
|
|
212
227
|
|
|
213
228
|
/**
|
|
214
|
-
*
|
|
229
|
+
* Cell mode: the strip the cells are cut from by a mask, and the ghost and fill drawn along it
|
|
230
|
+
* @type {HTMLElement|null}
|
|
215
231
|
* @private
|
|
216
232
|
*/
|
|
217
|
-
this.
|
|
218
|
-
this.
|
|
219
|
-
this.
|
|
220
|
-
this.__cell_states = new Uint8Array(cells === null ? 0 : cells.count);
|
|
221
|
-
|
|
222
|
-
// what each cell's fill and ghost were last drawn at, so an animation frame writes only the cells that moved
|
|
223
|
-
this.__cell_fill_drawn = new Float64Array(cells === null ? 0 : cells.count);
|
|
224
|
-
this.__cell_ghost_drawn = new Float64Array(cells === null ? 0 : cells.count);
|
|
233
|
+
this.__el_strip = null;
|
|
234
|
+
this.__el_cell_ghost = null;
|
|
235
|
+
this.__el_cell_fill = null;
|
|
225
236
|
|
|
226
237
|
if (cells === null) {
|
|
227
238
|
this.el.appendChild(this.__el_ghost);
|
|
@@ -246,29 +257,35 @@ export class SegmentedResourceBarView extends View {
|
|
|
246
257
|
*/
|
|
247
258
|
this.__el_layout = document.createElement('div');
|
|
248
259
|
this.__el_layout.className = 'notches';
|
|
260
|
+
|
|
261
|
+
for (let i = 0; i < tiers.length; i++) {
|
|
262
|
+
const el_tier = document.createElement('div');
|
|
263
|
+
el_tier.className = `notch ${tiers[i].class_name}`;
|
|
264
|
+
el_tier.style.display = 'none';
|
|
265
|
+
|
|
266
|
+
this.__el_layout.appendChild(el_tier);
|
|
267
|
+
this.__tier_layers[i] = el_tier;
|
|
268
|
+
}
|
|
249
269
|
} else {
|
|
250
270
|
this.__el_layout = document.createElement('div');
|
|
251
271
|
this.__el_layout.className = 'cells';
|
|
252
272
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
el_cell.className = CELL_STATE_CLASS[CELL_STATE_EMPTY];
|
|
256
|
-
|
|
257
|
-
const el_cell_ghost = document.createElement('div');
|
|
258
|
-
el_cell_ghost.className = 'cell-ghost';
|
|
273
|
+
const el_strip = document.createElement('div');
|
|
274
|
+
el_strip.className = 'cell-strip';
|
|
259
275
|
|
|
260
|
-
|
|
261
|
-
|
|
276
|
+
const el_cell_ghost = document.createElement('div');
|
|
277
|
+
el_cell_ghost.className = 'cell-ghost';
|
|
262
278
|
|
|
263
|
-
|
|
264
|
-
|
|
279
|
+
const el_cell_fill = document.createElement('div');
|
|
280
|
+
el_cell_fill.className = 'cell-fill';
|
|
265
281
|
|
|
266
|
-
|
|
282
|
+
el_strip.appendChild(el_cell_ghost);
|
|
283
|
+
el_strip.appendChild(el_cell_fill);
|
|
284
|
+
this.__el_layout.appendChild(el_strip);
|
|
267
285
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
286
|
+
this.__el_strip = el_strip;
|
|
287
|
+
this.__el_cell_ghost = el_cell_ghost;
|
|
288
|
+
this.__el_cell_fill = el_cell_fill;
|
|
272
289
|
}
|
|
273
290
|
|
|
274
291
|
this.el.appendChild(this.__el_layout);
|
|
@@ -375,6 +392,9 @@ export class SegmentedResourceBarView extends View {
|
|
|
375
392
|
}
|
|
376
393
|
|
|
377
394
|
handleMaxChange() {
|
|
395
|
+
// the same amount out of a different max is a different share of the bar, and nothing was lost
|
|
396
|
+
this.__max_moved = true;
|
|
397
|
+
|
|
378
398
|
this.updateSegments();
|
|
379
399
|
this.updateFill();
|
|
380
400
|
}
|
|
@@ -382,15 +402,24 @@ export class SegmentedResourceBarView extends View {
|
|
|
382
402
|
/**
|
|
383
403
|
* Place the notches or cells again, if the max, the bar's width (by {@link REBUILD_WIDTH_THRESHOLD_PX} or more),
|
|
384
404
|
* its pixel ratio or the device-pixel phase of its left edge has moved since they were last placed.
|
|
405
|
+
*
|
|
406
|
+
* Both are drawn as a mask in whole device pixels over a layer CSS paints (see {@link segment_mask_url}): a DOM
|
|
407
|
+
* box per notch or cell cannot be put on the device-pixel grid at a fractional pixel ratio, where Chrome paints
|
|
408
|
+
* a row of equal 1 px gaps as 1, 2, 1, 2... whatever positions it is given (measured at 125% and 150%).
|
|
385
409
|
*/
|
|
386
410
|
updateSegments() {
|
|
387
411
|
const el = this.__el_layout;
|
|
388
412
|
|
|
389
413
|
/**
|
|
390
|
-
* width as laid out, before any transform on an ancestor
|
|
414
|
+
* width as laid out, before any transform on an ancestor. Not `offsetWidth`, which is rounded to a whole
|
|
415
|
+
* pixel and would read as a scale of its own (176 laid out against 175.84 drawn: 0.9991)
|
|
391
416
|
* @type {number}
|
|
392
417
|
*/
|
|
393
|
-
|
|
418
|
+
let layout_width = typeof getComputedStyle === 'function' ? parseFloat(getComputedStyle(el).width) : NaN;
|
|
419
|
+
|
|
420
|
+
if (!(layout_width > 0)) {
|
|
421
|
+
layout_width = el.offsetWidth;
|
|
422
|
+
}
|
|
394
423
|
|
|
395
424
|
if (!(layout_width > 0)) {
|
|
396
425
|
// not laid out (detached, display:none); nothing to place against
|
|
@@ -401,8 +430,11 @@ export class SegmentedResourceBarView extends View {
|
|
|
401
430
|
|
|
402
431
|
const dpr = typeof devicePixelRatio === 'number' && devicePixelRatio > 0 ? devicePixelRatio : 1;
|
|
403
432
|
|
|
404
|
-
// a CSS scale on an ancestor shows each CSS pixel of the bar as `scale` page pixels
|
|
405
|
-
|
|
433
|
+
// a CSS scale on an ancestor shows each CSS pixel of the bar as `scale` page pixels. The computed width is
|
|
434
|
+
// serialised to a few decimals (175.844 for 175.84375), so an unscaled bar measures a hair off 1 — enough to
|
|
435
|
+
// put a width that should be whole in device pixels just past it — and is read as unscaled
|
|
436
|
+
const measured_scale = rect.width > 0 ? rect.width / layout_width : 1;
|
|
437
|
+
const scale = Math.abs(measured_scale - 1) < UNSCALED_TOLERANCE ? 1 : measured_scale;
|
|
406
438
|
|
|
407
439
|
// device pixels per CSS pixel of the bar
|
|
408
440
|
const ratio = dpr * scale;
|
|
@@ -426,85 +458,109 @@ export class SegmentedResourceBarView extends View {
|
|
|
426
458
|
this.__built_ratio = ratio;
|
|
427
459
|
this.__built_origin_phase = origin_phase;
|
|
428
460
|
|
|
429
|
-
// the
|
|
430
|
-
const
|
|
461
|
+
// the masked layer starts on a whole device pixel, and is a width whole in CSS and device pixels both
|
|
462
|
+
const shift_px = -origin_phase / ratio;
|
|
463
|
+
const width_device = segment_mask_width(layout_width - Math.max(0, shift_px), ratio);
|
|
431
464
|
|
|
432
465
|
if (this.__cells === null) {
|
|
433
|
-
this.__place_notches(max,
|
|
466
|
+
this.__place_notches(max, width_device, shift_px, ratio);
|
|
434
467
|
} else {
|
|
435
|
-
this.__place_cells(
|
|
468
|
+
this.__place_cells(width_device, shift_px, ratio);
|
|
436
469
|
}
|
|
437
470
|
}
|
|
438
471
|
|
|
472
|
+
/**
|
|
473
|
+
* @param {HTMLElement} el
|
|
474
|
+
* @param {number} width_device
|
|
475
|
+
* @param {number} shift_px
|
|
476
|
+
* @param {number} ratio
|
|
477
|
+
* @private
|
|
478
|
+
*/
|
|
479
|
+
__place_layer(el, width_device, shift_px, ratio) {
|
|
480
|
+
const style = el.style;
|
|
481
|
+
|
|
482
|
+
style.left = `${shift_px}px`;
|
|
483
|
+
style.width = `${width_device / ratio}px`;
|
|
484
|
+
}
|
|
485
|
+
|
|
439
486
|
/**
|
|
440
487
|
* @param {number} max
|
|
441
|
-
* @param {number}
|
|
442
|
-
* @param {number}
|
|
488
|
+
* @param {number} width_device
|
|
489
|
+
* @param {number} shift_px
|
|
443
490
|
* @param {number} ratio
|
|
444
491
|
* @private
|
|
445
492
|
*/
|
|
446
|
-
__place_notches(max,
|
|
493
|
+
__place_notches(max, width_device, shift_px, ratio) {
|
|
447
494
|
const tiers = this.__tiers;
|
|
448
|
-
const ticks = segment_tick_positions(max,
|
|
495
|
+
const ticks = segment_tick_positions(max, width_device / ratio, tiers);
|
|
449
496
|
|
|
450
497
|
const tick_count = ticks.length;
|
|
451
|
-
const
|
|
452
|
-
const
|
|
498
|
+
const columns = this.__columns;
|
|
499
|
+
const layers = this.__tier_layers;
|
|
453
500
|
|
|
454
|
-
|
|
455
|
-
|
|
501
|
+
// a notch is `width_px` CSS pixels wide, scaled by the whole part of the ratio, so it stays whole
|
|
502
|
+
const notch_scale = Math.max(1, Math.round(ratio));
|
|
456
503
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
504
|
+
for (let t = 0; t < tiers.length; t++) {
|
|
505
|
+
const notch_width = tiers[t].width_px * notch_scale;
|
|
460
506
|
|
|
461
|
-
|
|
462
|
-
container.removeChild(elements.pop());
|
|
463
|
-
}
|
|
507
|
+
let count = 0;
|
|
464
508
|
|
|
465
|
-
|
|
509
|
+
for (let i = 0; i < tick_count; i++) {
|
|
510
|
+
const tick = ticks[i];
|
|
511
|
+
|
|
512
|
+
if (tick.tier !== t) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
466
515
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const tier = tiers[tick.tier];
|
|
516
|
+
const centre = Math.round(tick.value / max * width_device);
|
|
517
|
+
const x = Math.min(Math.max(0, centre - Math.floor(notch_width / 2)), width_device - notch_width);
|
|
470
518
|
|
|
471
|
-
|
|
519
|
+
columns[count * 2] = x;
|
|
520
|
+
columns[count * 2 + 1] = notch_width;
|
|
521
|
+
count++;
|
|
522
|
+
}
|
|
472
523
|
|
|
473
|
-
const el =
|
|
524
|
+
const el = layers[t];
|
|
474
525
|
|
|
475
|
-
|
|
526
|
+
if (count === 0) {
|
|
527
|
+
el.style.display = 'none';
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
476
530
|
|
|
477
|
-
|
|
531
|
+
el.style.display = '';
|
|
478
532
|
|
|
479
|
-
|
|
480
|
-
|
|
533
|
+
this.__place_layer(el, width_device, shift_px, ratio);
|
|
534
|
+
segment_mask_apply(el, segment_mask_url(width_device, columns, count));
|
|
481
535
|
}
|
|
482
536
|
}
|
|
483
537
|
|
|
484
538
|
/**
|
|
485
|
-
* @param {number}
|
|
486
|
-
* @param {number}
|
|
539
|
+
* @param {number} width_device
|
|
540
|
+
* @param {number} shift_px
|
|
487
541
|
* @param {number} ratio
|
|
488
542
|
* @private
|
|
489
543
|
*/
|
|
490
|
-
__place_cells(
|
|
544
|
+
__place_cells(width_device, shift_px, ratio) {
|
|
491
545
|
const cells = this.__cells;
|
|
492
546
|
|
|
493
|
-
const layout = segment_cell_layout(
|
|
547
|
+
const layout = segment_cell_layout(width_device / ratio, cells.count, cells.group, ratio);
|
|
494
548
|
|
|
495
|
-
const
|
|
496
|
-
const origin_shift_device = Math.round(origin_device) - origin_device;
|
|
497
|
-
|
|
498
|
-
const cell_x = layout.cell_x_device;
|
|
499
|
-
const cell_width = layout.cell_width_device;
|
|
500
|
-
const elements = this.__cell_elements;
|
|
549
|
+
const columns = this.__columns;
|
|
501
550
|
|
|
502
551
|
for (let i = 0; i < cells.count; i++) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
style.left = `${(cell_x[i] + origin_shift_device) / ratio}px`;
|
|
506
|
-
style.width = `${cell_width[i] / ratio}px`;
|
|
552
|
+
columns[i * 2] = layout.cell_x_device[i];
|
|
553
|
+
columns[i * 2 + 1] = layout.cell_width_device[i];
|
|
507
554
|
}
|
|
555
|
+
|
|
556
|
+
this.__cell_layout = layout;
|
|
557
|
+
this.__cell_ratio = ratio;
|
|
558
|
+
|
|
559
|
+
this.__place_layer(this.__el_strip, width_device, shift_px, ratio);
|
|
560
|
+
segment_mask_apply(this.__el_strip, segment_mask_url(width_device, columns, cells.count));
|
|
561
|
+
|
|
562
|
+
// the fill is drawn against the layout, which just moved
|
|
563
|
+
this.__draw(performance.now());
|
|
508
564
|
}
|
|
509
565
|
|
|
510
566
|
updateFill() {
|
|
@@ -547,7 +603,13 @@ export class SegmentedResourceBarView extends View {
|
|
|
547
603
|
|
|
548
604
|
ghost.duration_min_ms = reduced_motion ? 0 : this.__ghost_duration_min_ms;
|
|
549
605
|
ghost.duration_max_ms = reduced_motion ? 0 : this.__ghost_duration_max_ms;
|
|
550
|
-
|
|
606
|
+
|
|
607
|
+
if (this.__max_moved) {
|
|
608
|
+
this.__max_moved = false;
|
|
609
|
+
ghost.reset(total_fraction);
|
|
610
|
+
} else {
|
|
611
|
+
ghost.set(total_fraction, now);
|
|
612
|
+
}
|
|
551
613
|
|
|
552
614
|
this.__draw(now);
|
|
553
615
|
}
|
|
@@ -579,33 +641,24 @@ export class SegmentedResourceBarView extends View {
|
|
|
579
641
|
*/
|
|
580
642
|
__draw_cells(total_fraction, ghost_fraction) {
|
|
581
643
|
const count = this.__cells.count;
|
|
644
|
+
const layout = this.__cell_layout;
|
|
582
645
|
|
|
583
|
-
const
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
const states = this.__cell_states;
|
|
646
|
+
const fill = this.__el_cell_fill.style;
|
|
647
|
+
const ghost = this.__el_cell_ghost.style;
|
|
587
648
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
649
|
+
if (layout === null) {
|
|
650
|
+
// not measured yet: a share of the strip, until the cells are laid out
|
|
651
|
+
fill.width = `${total_fraction * 100}%`;
|
|
652
|
+
ghost.width = `${ghost_fraction * 100}%`;
|
|
591
653
|
|
|
592
|
-
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
593
656
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
this.__cell_elements[i].className = CELL_STATE_CLASS[state];
|
|
597
|
-
}
|
|
657
|
+
// to the edge of the last whole cell (or part way across the next), never into a gap
|
|
658
|
+
const ratio = this.__cell_ratio;
|
|
598
659
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
this.__cell_fill_elements[i].style.width = `${fill * 100}%`;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
if (this.__cell_ghost_drawn[i] !== ghost) {
|
|
605
|
-
this.__cell_ghost_drawn[i] = ghost;
|
|
606
|
-
this.__cell_ghost_elements[i].style.width = `${ghost * 100}%`;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
660
|
+
fill.width = `${segment_cell_extent(layout, total_fraction * count) / ratio}px`;
|
|
661
|
+
ghost.width = `${segment_cell_extent(layout, ghost_fraction * count) / ratio}px`;
|
|
609
662
|
}
|
|
610
663
|
|
|
611
664
|
/**
|
|
@@ -138,9 +138,12 @@ document.getElementById('reload').addEventListener('click', () => {
|
|
|
138
138
|
});
|
|
139
139
|
|
|
140
140
|
/**
|
|
141
|
-
* For a driver: every
|
|
142
|
-
*
|
|
143
|
-
*
|
|
141
|
+
* For a driver: every masked layer (a notch tier, a cell strip) and whether it starts on a whole device pixel and is
|
|
142
|
+
* a width whole in CSS and device pixels both, which is what lets its mask's columns paint where they were drawn.
|
|
143
|
+
* Chrome keeps layout in 1/64 CSS pixel steps, so anything within one such step of whole counts as whole.
|
|
144
|
+
*
|
|
145
|
+
* Necessary, not sufficient: boxes whose edges all read as whole still painted 1, 2, 1, 2 px gaps at 125% when each
|
|
146
|
+
* cell was its own box. Judge the look from the painted pixels of a screenshot.
|
|
144
147
|
* @returns {{dpr:number, checked:number, worst:number, off_grid:string[]}}
|
|
145
148
|
*/
|
|
146
149
|
globalThis.segmented_bar_pixel_report = function () {
|
|
@@ -152,19 +155,23 @@ globalThis.segmented_bar_pixel_report = function () {
|
|
|
152
155
|
let checked = 0;
|
|
153
156
|
let worst = 0;
|
|
154
157
|
|
|
155
|
-
|
|
156
|
-
|
|
158
|
+
const miss = v => Math.abs(v - Math.round(v));
|
|
159
|
+
|
|
160
|
+
for (const el of document.querySelectorAll('.notch, .cell-strip')) {
|
|
161
|
+
if (el.style.display === 'none') {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
157
164
|
|
|
158
|
-
|
|
159
|
-
const device = v * dpr;
|
|
165
|
+
const r = el.getBoundingClientRect();
|
|
160
166
|
|
|
161
|
-
|
|
167
|
+
for (const [name, v] of [['left (device)', r.left * dpr], ['width (device)', r.width * dpr], ['width (css)', r.width]]) {
|
|
168
|
+
const m = miss(v);
|
|
162
169
|
|
|
163
170
|
checked++;
|
|
164
|
-
worst = Math.max(worst,
|
|
171
|
+
worst = Math.max(worst, m);
|
|
165
172
|
|
|
166
|
-
if (
|
|
167
|
-
off_grid.push(`${el.className} ${name} ${
|
|
173
|
+
if (m > tolerance) {
|
|
174
|
+
off_grid.push(`${el.className} ${name} ${v.toFixed(4)}`);
|
|
168
175
|
}
|
|
169
176
|
}
|
|
170
177
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a fill of `cells` cells ends on a cell layout, in device pixels from its left edge: at the right edge of the
|
|
3
|
+
* last whole cell, or part way across the next one for a fraction of a cell. Never in a gap, so a fill drawn to it
|
|
4
|
+
* paints whole cells.
|
|
5
|
+
*
|
|
6
|
+
* @param {{cell_x_device: number[], cell_width_device: number[]}} layout from {@link segment_cell_layout}
|
|
7
|
+
* @param {number} cells how many cells are filled; fractional for one filling up, clamped to the layout
|
|
8
|
+
* @returns {number}
|
|
9
|
+
*/
|
|
10
|
+
export function segment_cell_extent(layout: {
|
|
11
|
+
cell_x_device: number[];
|
|
12
|
+
cell_width_device: number[];
|
|
13
|
+
}, cells: number): number;
|
|
14
|
+
//# sourceMappingURL=segment_cell_extent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segment_cell_extent.d.ts","sourceRoot":"","sources":["../../../../../../src/view/elements/progress/segmented/segment_cell_extent.js"],"names":[],"mappings":"AAMA;;;;;;;;GAQG;AACH,4CAJW;IAAC,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,iBAAiB,EAAE,MAAM,EAAE,CAAA;CAAC,SACtD,MAAM,GACJ,MAAM,CA8BlB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cell counts within this of a whole one count as whole, so 7 of 30 rounds is 7 full cells and not 7 and a sliver
|
|
3
|
+
* @type {number}
|
|
4
|
+
*/
|
|
5
|
+
const WHOLE_TOLERANCE = 1e-9;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Where a fill of `cells` cells ends on a cell layout, in device pixels from its left edge: at the right edge of the
|
|
9
|
+
* last whole cell, or part way across the next one for a fraction of a cell. Never in a gap, so a fill drawn to it
|
|
10
|
+
* paints whole cells.
|
|
11
|
+
*
|
|
12
|
+
* @param {{cell_x_device: number[], cell_width_device: number[]}} layout from {@link segment_cell_layout}
|
|
13
|
+
* @param {number} cells how many cells are filled; fractional for one filling up, clamped to the layout
|
|
14
|
+
* @returns {number}
|
|
15
|
+
*/
|
|
16
|
+
export function segment_cell_extent(layout, cells) {
|
|
17
|
+
const xs = layout.cell_x_device;
|
|
18
|
+
const widths = layout.cell_width_device;
|
|
19
|
+
const count = xs.length;
|
|
20
|
+
|
|
21
|
+
if (count === 0 || !(cells > 0)) {
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let c = cells;
|
|
26
|
+
const whole = Math.round(c);
|
|
27
|
+
|
|
28
|
+
if (Math.abs(c - whole) <= WHOLE_TOLERANCE) {
|
|
29
|
+
c = whole;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (c >= count) {
|
|
33
|
+
return xs[count - 1] + widths[count - 1];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const k = Math.floor(c);
|
|
37
|
+
const part = c - k;
|
|
38
|
+
|
|
39
|
+
if (part > 0) {
|
|
40
|
+
return xs[k] + part * widths[k];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return xs[k - 1] + widths[k - 1];
|
|
44
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether masks can be drawn here. Keyed on `OffscreenCanvas`, which every browser that draws a canvas has and
|
|
3
|
+
* jsdom does not: jsdom's own canvas reports "not implemented" on the console for every call, so it is never asked.
|
|
4
|
+
* @returns {boolean}
|
|
5
|
+
*/
|
|
6
|
+
export function segment_mask_supported(): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* A mask image of opaque columns, one device pixel tall, for a layer `width_device` device pixels wide: stretched
|
|
9
|
+
* over the layer with `mask-size: 100% 100%`, each column covers exactly the device pixels it was drawn on. CSS keeps
|
|
10
|
+
* painting the layer (its colour, gradient, height); the mask only decides which columns of it show.
|
|
11
|
+
*
|
|
12
|
+
* @param {number} width_device
|
|
13
|
+
* @param {number[]} columns [x, width, x, width, ...] in whole device pixels from the layer's left edge
|
|
14
|
+
* @param {number} column_count how many pairs of `columns` to draw
|
|
15
|
+
* @returns {string|null} a `url(...)` for `mask-image`, or null where masks cannot be drawn
|
|
16
|
+
*/
|
|
17
|
+
export function segment_mask_url(width_device: number, columns: number[], column_count: number): string | null;
|
|
18
|
+
/**
|
|
19
|
+
* Put a mask from {@link segment_mask_url} on an element, sampled nearest so its columns stay whole, or take it off
|
|
20
|
+
* @param {HTMLElement} el
|
|
21
|
+
* @param {string|null} url
|
|
22
|
+
*/
|
|
23
|
+
export function segment_mask_apply(el: HTMLElement, url: string | null): void;
|
|
24
|
+
//# sourceMappingURL=segment_mask_url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segment_mask_url.d.ts","sourceRoot":"","sources":["../../../../../../src/view/elements/progress/segmented/segment_mask_url.js"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,0CAFa,OAAO,CAInB;AAED;;;;;;;;;GASG;AACH,+CALW,MAAM,WACN,MAAM,EAAE,gBACR,MAAM,GACJ,MAAM,GAAC,IAAI,CA4BvB;AAED;;;;GAIG;AACH,uCAHW,WAAW,OACX,MAAM,GAAC,IAAI,QAkBrB"}
|