@woosh/meep-engine 3.60.1 → 3.60.3
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 +198 -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 +74 -19
- package/src/view/elements/progress/segmented/SegmentedResourceBarView.d.ts.map +1 -1
- package/src/view/elements/progress/segmented/SegmentedResourceBarView.js +219 -116
- 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_frame_transform.d.ts +15 -0
- package/src/view/elements/progress/segmented/segment_frame_transform.d.ts.map +1 -0
- package/src/view/elements/progress/segmented/segment_frame_transform.js +69 -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,11 @@ 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_frame_transform } from "./segment_frame_transform.js";
|
|
14
|
+
import { segment_mask_apply, segment_mask_url } from "./segment_mask_url.js";
|
|
15
|
+
import { segment_mask_width } from "./segment_mask_width.js";
|
|
13
16
|
import { segment_tick_positions } from "./segment_tick_positions.js";
|
|
14
17
|
|
|
15
18
|
/**
|
|
@@ -19,37 +22,35 @@ import { segment_tick_positions } from "./segment_tick_positions.js";
|
|
|
19
22
|
const REBUILD_WIDTH_THRESHOLD_PX = 0.5;
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
|
-
*
|
|
25
|
+
* A measured CSS scale this close to 1 is no scale at all, only the rounding in how the browser reports widths
|
|
23
26
|
* @type {number}
|
|
24
27
|
*/
|
|
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'];
|
|
28
|
+
const UNSCALED_TOLERANCE = 2e-3;
|
|
32
29
|
|
|
33
30
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
31
|
+
* How often a linked bar checks that its notches or cells still sit on the screen's pixels, in milliseconds. A
|
|
32
|
+
* transform on an ancestor or on a containing iframe moves them without any event the bar hears.
|
|
33
|
+
* @type {number}
|
|
36
34
|
*/
|
|
37
|
-
|
|
38
|
-
const r = Math.round(x);
|
|
39
|
-
|
|
40
|
-
return Math.abs(x - r) <= CELL_FILL_TOLERANCE ? r : x;
|
|
41
|
-
}
|
|
35
|
+
const RECHECK_INTERVAL_MS = 250;
|
|
42
36
|
|
|
43
37
|
/**
|
|
44
38
|
* Resource bar in the style of a MOBA health bar: the values fill it end to end, notches mark fixed amounts, and
|
|
45
39
|
* a ghost behind the fill trails a drop so the loss stays readable for a moment.
|
|
46
40
|
*
|
|
47
41
|
* Two modes:
|
|
48
|
-
* - notches (default): a continuous fill, with a notch at every multiple of each {@link SegmentTier}'s unit
|
|
49
|
-
*
|
|
42
|
+
* - notches (default): a continuous fill, with a notch at every multiple of each {@link SegmentTier}'s unit. Each
|
|
43
|
+
* tier is one layer the width of the bar (`.notch.<class_name>`), which CSS paints — colour, height — and a mask
|
|
44
|
+
* cuts to that tier's notches;
|
|
45
|
+
* - cells (`cells` given): `count` separate cells with exact, identical gaps, for counting rounds and the like. One
|
|
46
|
+
* strip (`.cell-strip`, the empty cells' colour) holding a `.cell-ghost` and a `.cell-fill`, cut into cells by a
|
|
47
|
+
* mask; the fill and ghost end on a cell's edge, or part way across one that is filling.
|
|
50
48
|
*
|
|
51
|
-
*
|
|
52
|
-
* and the
|
|
49
|
+
* The masks are drawn in whole device pixels and the layers they cut placed so those pixels land on the screen's,
|
|
50
|
+
* at any pixel ratio and under a CSS scale: every notch of a tier is the same width and every gap the same, at
|
|
51
|
+
* 125% as at 100%. Colours, notch heights and the ghost are styled by `segmented-resource-bar.css` through
|
|
52
|
+
* `--segmented-bar-*` custom properties. Where masks cannot be drawn (no canvas, as under jsdom) the layers are
|
|
53
|
+
* placed but uncut.
|
|
53
54
|
*/
|
|
54
55
|
export class SegmentedResourceBarView extends View {
|
|
55
56
|
/**
|
|
@@ -162,6 +163,13 @@ export class SegmentedResourceBarView extends View {
|
|
|
162
163
|
this.__frame_id = 0;
|
|
163
164
|
this.__animate_ghost = this.__animate_ghost.bind(this);
|
|
164
165
|
|
|
166
|
+
/**
|
|
167
|
+
* The max moved since the fill was last drawn: the next fill resets the ghost rather than trailing a loss
|
|
168
|
+
* @type {boolean}
|
|
169
|
+
* @private
|
|
170
|
+
*/
|
|
171
|
+
this.__max_moved = false;
|
|
172
|
+
|
|
165
173
|
// what the layout was last built for, so it is rebuilt only when one of them moves
|
|
166
174
|
this.__built_max = NaN;
|
|
167
175
|
this.__built_width = NaN;
|
|
@@ -169,11 +177,41 @@ export class SegmentedResourceBarView extends View {
|
|
|
169
177
|
this.__built_origin_phase = NaN;
|
|
170
178
|
|
|
171
179
|
/**
|
|
172
|
-
*
|
|
173
|
-
* @type {
|
|
180
|
+
* Cell mode: the cells as last laid out across {@link __el_strip}, or null before the bar has been measured
|
|
181
|
+
* @type {SegmentCellLayout|null}
|
|
182
|
+
* @private
|
|
183
|
+
*/
|
|
184
|
+
this.__cell_layout = null;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Device pixels per CSS pixel the cells were laid out at
|
|
188
|
+
* @type {number}
|
|
189
|
+
* @private
|
|
190
|
+
*/
|
|
191
|
+
this.__cell_ratio = 1;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* scratch: [x, width, ...] device-pixel columns for a mask
|
|
195
|
+
* @type {number[]}
|
|
174
196
|
* @private
|
|
175
197
|
*/
|
|
176
|
-
this.
|
|
198
|
+
this.__columns = [];
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* scratch for {@link segment_frame_transform}: [scale, offset]
|
|
202
|
+
* @type {number[]}
|
|
203
|
+
* @private
|
|
204
|
+
*/
|
|
205
|
+
this.__frame = [1, 0];
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The timer that re-checks the placement while linked; 0 when none
|
|
209
|
+
* @type {number}
|
|
210
|
+
* @private
|
|
211
|
+
*/
|
|
212
|
+
this.__recheck_timer = 0;
|
|
213
|
+
|
|
214
|
+
this.__recheck = this.__recheck.bind(this);
|
|
177
215
|
|
|
178
216
|
/**
|
|
179
217
|
*
|
|
@@ -205,23 +243,20 @@ export class SegmentedResourceBarView extends View {
|
|
|
205
243
|
this.__value_fills = [];
|
|
206
244
|
|
|
207
245
|
/**
|
|
246
|
+
* Notch mode: one layer per tier, the whole bar wide, styled by the tier's class and masked to its notches
|
|
208
247
|
* @type {HTMLElement[]}
|
|
209
248
|
* @private
|
|
210
249
|
*/
|
|
211
|
-
this.
|
|
250
|
+
this.__tier_layers = [];
|
|
212
251
|
|
|
213
252
|
/**
|
|
214
|
-
*
|
|
253
|
+
* Cell mode: the strip the cells are cut from by a mask, and the ghost and fill drawn along it
|
|
254
|
+
* @type {HTMLElement|null}
|
|
215
255
|
* @private
|
|
216
256
|
*/
|
|
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);
|
|
257
|
+
this.__el_strip = null;
|
|
258
|
+
this.__el_cell_ghost = null;
|
|
259
|
+
this.__el_cell_fill = null;
|
|
225
260
|
|
|
226
261
|
if (cells === null) {
|
|
227
262
|
this.el.appendChild(this.__el_ghost);
|
|
@@ -246,29 +281,35 @@ export class SegmentedResourceBarView extends View {
|
|
|
246
281
|
*/
|
|
247
282
|
this.__el_layout = document.createElement('div');
|
|
248
283
|
this.__el_layout.className = 'notches';
|
|
284
|
+
|
|
285
|
+
for (let i = 0; i < tiers.length; i++) {
|
|
286
|
+
const el_tier = document.createElement('div');
|
|
287
|
+
el_tier.className = `notch ${tiers[i].class_name}`;
|
|
288
|
+
el_tier.style.display = 'none';
|
|
289
|
+
|
|
290
|
+
this.__el_layout.appendChild(el_tier);
|
|
291
|
+
this.__tier_layers[i] = el_tier;
|
|
292
|
+
}
|
|
249
293
|
} else {
|
|
250
294
|
this.__el_layout = document.createElement('div');
|
|
251
295
|
this.__el_layout.className = 'cells';
|
|
252
296
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
el_cell.className = CELL_STATE_CLASS[CELL_STATE_EMPTY];
|
|
297
|
+
const el_strip = document.createElement('div');
|
|
298
|
+
el_strip.className = 'cell-strip';
|
|
256
299
|
|
|
257
|
-
|
|
258
|
-
|
|
300
|
+
const el_cell_ghost = document.createElement('div');
|
|
301
|
+
el_cell_ghost.className = 'cell-ghost';
|
|
259
302
|
|
|
260
|
-
|
|
261
|
-
|
|
303
|
+
const el_cell_fill = document.createElement('div');
|
|
304
|
+
el_cell_fill.className = 'cell-fill';
|
|
262
305
|
|
|
263
|
-
|
|
264
|
-
|
|
306
|
+
el_strip.appendChild(el_cell_ghost);
|
|
307
|
+
el_strip.appendChild(el_cell_fill);
|
|
308
|
+
this.__el_layout.appendChild(el_strip);
|
|
265
309
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
this.__cell_ghost_elements[i] = el_cell_ghost;
|
|
270
|
-
this.__cell_fill_elements[i] = el_cell_fill;
|
|
271
|
-
}
|
|
310
|
+
this.__el_strip = el_strip;
|
|
311
|
+
this.__el_cell_ghost = el_cell_ghost;
|
|
312
|
+
this.__el_cell_fill = el_cell_fill;
|
|
272
313
|
}
|
|
273
314
|
|
|
274
315
|
this.el.appendChild(this.__el_layout);
|
|
@@ -347,6 +388,10 @@ export class SegmentedResourceBarView extends View {
|
|
|
347
388
|
this.__sizeObserver.start();
|
|
348
389
|
this.__watch_device_pixel_ratio();
|
|
349
390
|
this.update();
|
|
391
|
+
|
|
392
|
+
if (typeof setInterval === 'function') {
|
|
393
|
+
this.__recheck_timer = setInterval(this.__recheck, RECHECK_INTERVAL_MS);
|
|
394
|
+
}
|
|
350
395
|
}
|
|
351
396
|
|
|
352
397
|
unlink() {
|
|
@@ -356,6 +401,11 @@ export class SegmentedResourceBarView extends View {
|
|
|
356
401
|
|
|
357
402
|
this.__sizeObserver.stop();
|
|
358
403
|
|
|
404
|
+
if (this.__recheck_timer !== 0) {
|
|
405
|
+
clearInterval(this.__recheck_timer);
|
|
406
|
+
this.__recheck_timer = 0;
|
|
407
|
+
}
|
|
408
|
+
|
|
359
409
|
if (this.__dpr_query !== null) {
|
|
360
410
|
this.__dpr_query.removeEventListener('change', this.__handle_dpr_change);
|
|
361
411
|
this.__dpr_query = null;
|
|
@@ -375,6 +425,9 @@ export class SegmentedResourceBarView extends View {
|
|
|
375
425
|
}
|
|
376
426
|
|
|
377
427
|
handleMaxChange() {
|
|
428
|
+
// the same amount out of a different max is a different share of the bar, and nothing was lost
|
|
429
|
+
this.__max_moved = true;
|
|
430
|
+
|
|
378
431
|
this.updateSegments();
|
|
379
432
|
this.updateFill();
|
|
380
433
|
}
|
|
@@ -382,15 +435,24 @@ export class SegmentedResourceBarView extends View {
|
|
|
382
435
|
/**
|
|
383
436
|
* Place the notches or cells again, if the max, the bar's width (by {@link REBUILD_WIDTH_THRESHOLD_PX} or more),
|
|
384
437
|
* its pixel ratio or the device-pixel phase of its left edge has moved since they were last placed.
|
|
438
|
+
*
|
|
439
|
+
* Both are drawn as a mask in whole device pixels over a layer CSS paints (see {@link segment_mask_url}): a DOM
|
|
440
|
+
* box per notch or cell cannot be put on the device-pixel grid at a fractional pixel ratio, where Chrome paints
|
|
441
|
+
* a row of equal 1 px gaps as 1, 2, 1, 2... whatever positions it is given (measured at 125% and 150%).
|
|
385
442
|
*/
|
|
386
443
|
updateSegments() {
|
|
387
444
|
const el = this.__el_layout;
|
|
388
445
|
|
|
389
446
|
/**
|
|
390
|
-
* width as laid out, before any transform on an ancestor
|
|
447
|
+
* width as laid out, before any transform on an ancestor. Not `offsetWidth`, which is rounded to a whole
|
|
448
|
+
* pixel and would read as a scale of its own (176 laid out against 175.84 drawn: 0.9991)
|
|
391
449
|
* @type {number}
|
|
392
450
|
*/
|
|
393
|
-
|
|
451
|
+
let layout_width = typeof getComputedStyle === 'function' ? parseFloat(getComputedStyle(el).width) : NaN;
|
|
452
|
+
|
|
453
|
+
if (!(layout_width > 0)) {
|
|
454
|
+
layout_width = el.offsetWidth;
|
|
455
|
+
}
|
|
394
456
|
|
|
395
457
|
if (!(layout_width > 0)) {
|
|
396
458
|
// not laid out (detached, display:none); nothing to place against
|
|
@@ -401,13 +463,21 @@ export class SegmentedResourceBarView extends View {
|
|
|
401
463
|
|
|
402
464
|
const dpr = typeof devicePixelRatio === 'number' && devicePixelRatio > 0 ? devicePixelRatio : 1;
|
|
403
465
|
|
|
404
|
-
//
|
|
405
|
-
const
|
|
466
|
+
// how the iframes this bar sits in are drawn by the pages around them — invisible from in here
|
|
467
|
+
const frame = segment_frame_transform(el.ownerDocument.defaultView, this.__frame);
|
|
468
|
+
const frame_scale = frame[0];
|
|
469
|
+
const frame_offset = frame[1];
|
|
470
|
+
|
|
471
|
+
// a CSS scale on an ancestor shows each CSS pixel of the bar as `scale` page pixels. The computed width is
|
|
472
|
+
// serialised to a few decimals (175.844 for 175.84375), so an unscaled bar measures a hair off 1 — enough to
|
|
473
|
+
// put a width that should be whole in device pixels just past it — and is read as unscaled
|
|
474
|
+
const measured_scale = (rect.width > 0 ? rect.width / layout_width : 1) * frame_scale;
|
|
475
|
+
const scale = Math.abs(measured_scale - 1) < UNSCALED_TOLERANCE ? 1 : measured_scale;
|
|
406
476
|
|
|
407
477
|
// device pixels per CSS pixel of the bar
|
|
408
478
|
const ratio = dpr * scale;
|
|
409
479
|
|
|
410
|
-
const origin_device = rect.left * dpr;
|
|
480
|
+
const origin_device = (frame_offset + rect.left * frame_scale) * dpr;
|
|
411
481
|
const origin_phase = origin_device - Math.round(origin_device);
|
|
412
482
|
|
|
413
483
|
const max = this.__value_max.getValue();
|
|
@@ -426,85 +496,109 @@ export class SegmentedResourceBarView extends View {
|
|
|
426
496
|
this.__built_ratio = ratio;
|
|
427
497
|
this.__built_origin_phase = origin_phase;
|
|
428
498
|
|
|
429
|
-
// the
|
|
430
|
-
const
|
|
499
|
+
// the masked layer starts on a whole device pixel, and is a width whole in CSS and device pixels both
|
|
500
|
+
const shift_px = -origin_phase / ratio;
|
|
501
|
+
const width_device = segment_mask_width(layout_width - Math.max(0, shift_px), ratio);
|
|
431
502
|
|
|
432
503
|
if (this.__cells === null) {
|
|
433
|
-
this.__place_notches(max,
|
|
504
|
+
this.__place_notches(max, width_device, shift_px, ratio);
|
|
434
505
|
} else {
|
|
435
|
-
this.__place_cells(
|
|
506
|
+
this.__place_cells(width_device, shift_px, ratio);
|
|
436
507
|
}
|
|
437
508
|
}
|
|
438
509
|
|
|
510
|
+
/**
|
|
511
|
+
* @param {HTMLElement} el
|
|
512
|
+
* @param {number} width_device
|
|
513
|
+
* @param {number} shift_px
|
|
514
|
+
* @param {number} ratio
|
|
515
|
+
* @private
|
|
516
|
+
*/
|
|
517
|
+
__place_layer(el, width_device, shift_px, ratio) {
|
|
518
|
+
const style = el.style;
|
|
519
|
+
|
|
520
|
+
style.left = `${shift_px}px`;
|
|
521
|
+
style.width = `${width_device / ratio}px`;
|
|
522
|
+
}
|
|
523
|
+
|
|
439
524
|
/**
|
|
440
525
|
* @param {number} max
|
|
441
|
-
* @param {number}
|
|
442
|
-
* @param {number}
|
|
526
|
+
* @param {number} width_device
|
|
527
|
+
* @param {number} shift_px
|
|
443
528
|
* @param {number} ratio
|
|
444
529
|
* @private
|
|
445
530
|
*/
|
|
446
|
-
__place_notches(max,
|
|
531
|
+
__place_notches(max, width_device, shift_px, ratio) {
|
|
447
532
|
const tiers = this.__tiers;
|
|
448
|
-
const ticks = segment_tick_positions(max,
|
|
533
|
+
const ticks = segment_tick_positions(max, width_device / ratio, tiers);
|
|
449
534
|
|
|
450
535
|
const tick_count = ticks.length;
|
|
451
|
-
const
|
|
452
|
-
const
|
|
536
|
+
const columns = this.__columns;
|
|
537
|
+
const layers = this.__tier_layers;
|
|
453
538
|
|
|
454
|
-
|
|
455
|
-
|
|
539
|
+
// a notch is `width_px` CSS pixels wide, scaled by the whole part of the ratio, so it stays whole
|
|
540
|
+
const notch_scale = Math.max(1, Math.round(ratio));
|
|
456
541
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
542
|
+
for (let t = 0; t < tiers.length; t++) {
|
|
543
|
+
const notch_width = tiers[t].width_px * notch_scale;
|
|
460
544
|
|
|
461
|
-
|
|
462
|
-
container.removeChild(elements.pop());
|
|
463
|
-
}
|
|
545
|
+
let count = 0;
|
|
464
546
|
|
|
465
|
-
|
|
547
|
+
for (let i = 0; i < tick_count; i++) {
|
|
548
|
+
const tick = ticks[i];
|
|
466
549
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
550
|
+
if (tick.tier !== t) {
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
470
553
|
|
|
471
|
-
|
|
554
|
+
const centre = Math.round(tick.value / max * width_device);
|
|
555
|
+
const x = Math.min(Math.max(0, centre - Math.floor(notch_width / 2)), width_device - notch_width);
|
|
472
556
|
|
|
473
|
-
|
|
557
|
+
columns[count * 2] = x;
|
|
558
|
+
columns[count * 2 + 1] = notch_width;
|
|
559
|
+
count++;
|
|
560
|
+
}
|
|
474
561
|
|
|
475
|
-
el
|
|
562
|
+
const el = layers[t];
|
|
476
563
|
|
|
477
|
-
|
|
564
|
+
if (count === 0) {
|
|
565
|
+
el.style.display = 'none';
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
478
568
|
|
|
479
|
-
style.
|
|
480
|
-
|
|
569
|
+
el.style.display = '';
|
|
570
|
+
|
|
571
|
+
this.__place_layer(el, width_device, shift_px, ratio);
|
|
572
|
+
segment_mask_apply(el, segment_mask_url(width_device, columns, count));
|
|
481
573
|
}
|
|
482
574
|
}
|
|
483
575
|
|
|
484
576
|
/**
|
|
485
|
-
* @param {number}
|
|
486
|
-
* @param {number}
|
|
577
|
+
* @param {number} width_device
|
|
578
|
+
* @param {number} shift_px
|
|
487
579
|
* @param {number} ratio
|
|
488
580
|
* @private
|
|
489
581
|
*/
|
|
490
|
-
__place_cells(
|
|
582
|
+
__place_cells(width_device, shift_px, ratio) {
|
|
491
583
|
const cells = this.__cells;
|
|
492
584
|
|
|
493
|
-
const layout = segment_cell_layout(
|
|
494
|
-
|
|
495
|
-
const origin_device = left * ratio;
|
|
496
|
-
const origin_shift_device = Math.round(origin_device) - origin_device;
|
|
585
|
+
const layout = segment_cell_layout(width_device / ratio, cells.count, cells.group, ratio);
|
|
497
586
|
|
|
498
|
-
const
|
|
499
|
-
const cell_width = layout.cell_width_device;
|
|
500
|
-
const elements = this.__cell_elements;
|
|
587
|
+
const columns = this.__columns;
|
|
501
588
|
|
|
502
589
|
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`;
|
|
590
|
+
columns[i * 2] = layout.cell_x_device[i];
|
|
591
|
+
columns[i * 2 + 1] = layout.cell_width_device[i];
|
|
507
592
|
}
|
|
593
|
+
|
|
594
|
+
this.__cell_layout = layout;
|
|
595
|
+
this.__cell_ratio = ratio;
|
|
596
|
+
|
|
597
|
+
this.__place_layer(this.__el_strip, width_device, shift_px, ratio);
|
|
598
|
+
segment_mask_apply(this.__el_strip, segment_mask_url(width_device, columns, cells.count));
|
|
599
|
+
|
|
600
|
+
// the fill is drawn against the layout, which just moved
|
|
601
|
+
this.__draw(performance.now());
|
|
508
602
|
}
|
|
509
603
|
|
|
510
604
|
updateFill() {
|
|
@@ -547,7 +641,13 @@ export class SegmentedResourceBarView extends View {
|
|
|
547
641
|
|
|
548
642
|
ghost.duration_min_ms = reduced_motion ? 0 : this.__ghost_duration_min_ms;
|
|
549
643
|
ghost.duration_max_ms = reduced_motion ? 0 : this.__ghost_duration_max_ms;
|
|
550
|
-
|
|
644
|
+
|
|
645
|
+
if (this.__max_moved) {
|
|
646
|
+
this.__max_moved = false;
|
|
647
|
+
ghost.reset(total_fraction);
|
|
648
|
+
} else {
|
|
649
|
+
ghost.set(total_fraction, now);
|
|
650
|
+
}
|
|
551
651
|
|
|
552
652
|
this.__draw(now);
|
|
553
653
|
}
|
|
@@ -579,32 +679,35 @@ export class SegmentedResourceBarView extends View {
|
|
|
579
679
|
*/
|
|
580
680
|
__draw_cells(total_fraction, ghost_fraction) {
|
|
581
681
|
const count = this.__cells.count;
|
|
682
|
+
const layout = this.__cell_layout;
|
|
582
683
|
|
|
583
|
-
const
|
|
584
|
-
const
|
|
684
|
+
const fill = this.__el_cell_fill.style;
|
|
685
|
+
const ghost = this.__el_cell_ghost.style;
|
|
585
686
|
|
|
586
|
-
|
|
687
|
+
if (layout === null) {
|
|
688
|
+
// not measured yet: a share of the strip, until the cells are laid out
|
|
689
|
+
fill.width = `${total_fraction * 100}%`;
|
|
690
|
+
ghost.width = `${ghost_fraction * 100}%`;
|
|
587
691
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const ghost = clamp01(ghost_cells - i);
|
|
591
|
-
|
|
592
|
-
const state = fill >= 1 ? CELL_STATE_FILLED : (fill > 0 ? CELL_STATE_PARTIAL : CELL_STATE_EMPTY);
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
593
694
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
this.__cell_elements[i].className = CELL_STATE_CLASS[state];
|
|
597
|
-
}
|
|
695
|
+
// to the edge of the last whole cell (or part way across the next), never into a gap
|
|
696
|
+
const ratio = this.__cell_ratio;
|
|
598
697
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
}
|
|
698
|
+
fill.width = `${segment_cell_extent(layout, total_fraction * count) / ratio}px`;
|
|
699
|
+
ghost.width = `${segment_cell_extent(layout, ghost_fraction * count) / ratio}px`;
|
|
700
|
+
}
|
|
603
701
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
702
|
+
/**
|
|
703
|
+
* Place again if anything moved that no event reports: an ancestor's transform, the transform of an iframe the
|
|
704
|
+
* bar sits in, a move by a fraction of a pixel. Cheap when nothing did — a computed width and a few rects —
|
|
705
|
+
* and {@link updateSegments} rebuilds only on a change.
|
|
706
|
+
* @private
|
|
707
|
+
*/
|
|
708
|
+
__recheck() {
|
|
709
|
+
if (this.isLinked) {
|
|
710
|
+
this.updateSegments();
|
|
608
711
|
}
|
|
609
712
|
}
|
|
610
713
|
|
|
@@ -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,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a point in a window's CSS pixels lands in the top-most window this one can see: `top = offset + local * scale`,
|
|
3
|
+
* through every same-origin iframe it sits in, each frame's own transform included.
|
|
4
|
+
*
|
|
5
|
+
* Nothing inside an iframe can see how the page around it draws it: an iframe the host scales to fit (a preview, a
|
|
6
|
+
* playground) reports `devicePixelRatio` and every rect as though unscaled, so something drawn for that pixel grid
|
|
7
|
+
* is painted on another. Walking out through `frameElement` is the one way to find out. A cross-origin frame stops
|
|
8
|
+
* the walk (its `frameElement` reads null, or throws), and is taken as unscaled from there out.
|
|
9
|
+
*
|
|
10
|
+
* @param {Window|null|undefined} win the window the element is in
|
|
11
|
+
* @param {number[]} result receives [scale, offset]
|
|
12
|
+
* @returns {number[]} result
|
|
13
|
+
*/
|
|
14
|
+
export function segment_frame_transform(win: Window | null | undefined, result: number[]): number[];
|
|
15
|
+
//# sourceMappingURL=segment_frame_transform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segment_frame_transform.d.ts","sourceRoot":"","sources":["../../../../../../src/view/elements/progress/segmented/segment_frame_transform.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,6CAJW,MAAM,GAAC,IAAI,GAAC,SAAS,UACrB,MAAM,EAAE,GACN,MAAM,EAAE,CAyDpB"}
|