drawnui-react 0.1.0-preview.2 → 0.1.0-preview.20
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/README.md +17 -4
- package/SKIPPED.md +31 -25
- package/dist/controls/AnimatedFramesRenderer.d.ts +7 -0
- package/dist/controls/AnimatedFramesRenderer.js +16 -1
- package/dist/controls/DrawnGame.d.ts +40 -0
- package/dist/controls/DrawnGame.js +64 -0
- package/dist/controls/GridStructure.d.ts +16 -2
- package/dist/controls/GridStructure.js +40 -4
- package/dist/controls/SkiaBackdrop.d.ts +2 -0
- package/dist/controls/SkiaBackdrop.js +2 -0
- package/dist/controls/SkiaButton.d.ts +19 -2
- package/dist/controls/SkiaButton.js +41 -11
- package/dist/controls/SkiaCarousel.d.ts +9 -1
- package/dist/controls/SkiaCarousel.js +16 -3
- package/dist/controls/SkiaCheckbox.d.ts +0 -1
- package/dist/controls/SkiaCheckbox.js +0 -6
- package/dist/controls/SkiaDrawer.d.ts +2 -0
- package/dist/controls/SkiaDrawer.js +2 -0
- package/dist/controls/SkiaEditor.d.ts +12 -1
- package/dist/controls/SkiaEditor.js +46 -19
- package/dist/controls/SkiaGif.d.ts +3 -0
- package/dist/controls/SkiaGif.js +12 -2
- package/dist/controls/SkiaImage.js +15 -10
- package/dist/controls/SkiaLabel.d.ts +54 -3
- package/dist/controls/SkiaLabel.js +235 -52
- package/dist/controls/SkiaLabelFps.d.ts +16 -0
- package/dist/controls/SkiaLabelFps.js +41 -0
- package/dist/controls/SkiaLayout.d.ts +64 -7
- package/dist/controls/SkiaLayout.js +316 -19
- package/dist/controls/SkiaLottie.js +4 -2
- package/dist/controls/SkiaProgress.d.ts +2 -1
- package/dist/controls/SkiaProgress.js +6 -3
- package/dist/controls/SkiaRadioButton.js +4 -3
- package/dist/controls/SkiaScroll.d.ts +35 -5
- package/dist/controls/SkiaScroll.js +233 -23
- package/dist/controls/SkiaScrollBar.d.ts +27 -0
- package/dist/controls/SkiaScrollBar.js +51 -2
- package/dist/controls/SkiaShaderCarousel.d.ts +5 -0
- package/dist/controls/SkiaShaderCarousel.js +5 -0
- package/dist/controls/SkiaShape.d.ts +13 -2
- package/dist/controls/SkiaShape.js +50 -10
- package/dist/controls/SkiaSlider.d.ts +20 -2
- package/dist/controls/SkiaSlider.js +59 -5
- package/dist/controls/SkiaSprite.d.ts +1 -0
- package/dist/controls/SkiaSprite.js +4 -0
- package/dist/controls/SkiaSvg.js +1 -1
- package/dist/controls/SkiaSwitch.d.ts +0 -1
- package/dist/controls/SkiaSwitch.js +0 -6
- package/dist/controls/SkiaToggle.d.ts +25 -5
- package/dist/controls/SkiaToggle.js +58 -5
- package/dist/controls/SnappingLayout.d.ts +12 -1
- package/dist/controls/SnappingLayout.js +19 -1
- package/dist/core/Accessibility.d.ts +80 -0
- package/dist/core/Accessibility.js +365 -8
- package/dist/core/Animators.d.ts +37 -1
- package/dist/core/Animators.js +102 -9
- package/dist/core/Canvas.d.ts +108 -7
- package/dist/core/Canvas.js +363 -35
- package/dist/core/Easing.d.ts +12 -1
- package/dist/core/Easing.js +25 -1
- package/dist/core/Gestures.d.ts +48 -2
- package/dist/core/Gestures.js +95 -3
- package/dist/core/Hover.d.ts +47 -0
- package/dist/core/Hover.js +134 -0
- package/dist/core/KeyboardManager.d.ts +13 -0
- package/dist/core/KeyboardManager.js +18 -0
- package/dist/core/ScrollAnimators.js +9 -0
- package/dist/core/SkiaControl.d.ts +202 -7
- package/dist/core/SkiaControl.js +467 -55
- package/dist/core/SkiaEffect.d.ts +7 -0
- package/dist/core/SkiaEffect.js +25 -6
- package/dist/core/SkiaImageManager.js +1 -1
- package/dist/core/Styles.d.ts +51 -0
- package/dist/core/Styles.js +92 -0
- package/dist/core/Super.d.ts +17 -1
- package/dist/core/Super.js +35 -4
- package/dist/core/ViewsAdapter.d.ts +7 -2
- package/dist/core/ViewsAdapter.js +10 -3
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/react/SkiaShell.js +33 -14
- package/dist/react/index.d.ts +15 -4
- package/dist/react/index.js +102 -15
- package/dist/react/reconciler.js +34 -12
- package/dist/vite/index.d.ts +9 -0
- package/dist/vite/index.js +36 -3
- package/package.json +29 -3
|
@@ -125,6 +125,13 @@ export declare class SkiaShaderEffect extends SkiaEffect implements IPostRendere
|
|
|
125
125
|
protected ApplyShaderSource(): void;
|
|
126
126
|
/** Snapshot of what the parent painted (C# CreateSnapshot): the surface the pixels are on, mapped through its origin. */
|
|
127
127
|
protected CreateSnapshot(ctx: DrawingContext, destination: SKRect): CachedTexture | undefined;
|
|
128
|
+
private frozenEpoch;
|
|
129
|
+
/** Canvas.GpuEpoch: GPU textures made before a WebGL context restore are made again. */
|
|
130
|
+
protected GpuEpoch(): number;
|
|
131
|
+
/** Where the parent was when the texture was frozen (Once): the texture moves with it, as its cache does. */
|
|
132
|
+
private frozenAt?;
|
|
133
|
+
private ParentOrigin;
|
|
134
|
+
private FollowParent;
|
|
128
135
|
/** C# GetPrimaryTexture: the parent's cache, a frozen snapshot (Once) or nothing (Never). */
|
|
129
136
|
protected GetPrimaryTexture(_ctx: DrawingContext, _destination: SKRect): CachedTexture | undefined;
|
|
130
137
|
/** Image → shader with the effect's sampling, translated so texel (0,0) = bounds top-left; cached per image. */
|
package/dist/core/SkiaEffect.js
CHANGED
|
@@ -2,7 +2,7 @@ import { SkiaValueAnimator } from "./Animators";
|
|
|
2
2
|
import { Easing } from "./Easing";
|
|
3
3
|
import { SkiaImageManager } from "./SkiaImageManager";
|
|
4
4
|
import { Super } from "./Super";
|
|
5
|
-
import { Thickness } from "./Types";
|
|
5
|
+
import { SKRect, Thickness } from "./Types";
|
|
6
6
|
/**
|
|
7
7
|
* Mirrors DrawnUi SkiaEffect: something attached to a control through `VisualEffects`. `Update()` re-renders the
|
|
8
8
|
* parent (C# invalidates the parent's cache and redraws), `GetEffectMargin` reports what it paints outside the box.
|
|
@@ -129,6 +129,7 @@ export class SkiaShaderEffect extends SkiaEffect {
|
|
|
129
129
|
this.DisposeImage(this.frozen.Image);
|
|
130
130
|
this.frozen = undefined;
|
|
131
131
|
this.frozenOwned = false;
|
|
132
|
+
this.frozenAt = undefined;
|
|
132
133
|
}
|
|
133
134
|
DisposeImage(image) {
|
|
134
135
|
const sv = this.Parent?.Superview;
|
|
@@ -158,12 +159,27 @@ export class SkiaShaderEffect extends SkiaEffect {
|
|
|
158
159
|
return undefined;
|
|
159
160
|
return { Image: image, Bounds: destination, Origin: { X: ox, Y: oy } };
|
|
160
161
|
}
|
|
162
|
+
frozenEpoch = 0;
|
|
163
|
+
/** Canvas.GpuEpoch: GPU textures made before a WebGL context restore are made again. */
|
|
164
|
+
GpuEpoch() { return this.Parent?.Superview?.GpuEpoch ?? 0; }
|
|
165
|
+
/** Where the parent was when the texture was frozen (Once): the texture moves with it, as its cache does. */
|
|
166
|
+
frozenAt;
|
|
167
|
+
ParentOrigin() { const r = this.Parent?.DrawingRect; return { X: r?.Left ?? 0, Y: r?.Top ?? 0 }; }
|
|
168
|
+
FollowParent(texture) {
|
|
169
|
+
if (!texture || !this.frozenAt)
|
|
170
|
+
return texture;
|
|
171
|
+
const now = this.ParentOrigin(), dx = now.X - this.frozenAt.X, dy = now.Y - this.frozenAt.Y;
|
|
172
|
+
if (dx === 0 && dy === 0)
|
|
173
|
+
return texture;
|
|
174
|
+
const b = texture.Bounds;
|
|
175
|
+
return { Image: texture.Image, Bounds: new SKRect(b.Left + dx, b.Top + dy, b.Right + dx, b.Bottom + dy), Origin: texture.Origin && { X: texture.Origin.X + dx, Y: texture.Origin.Y + dy } };
|
|
176
|
+
}
|
|
161
177
|
/** C# GetPrimaryTexture: the parent's cache, a frozen snapshot (Once) or nothing (Never). */
|
|
162
178
|
GetPrimaryTexture(_ctx, _destination) {
|
|
163
179
|
switch (this.UseBackground) {
|
|
164
180
|
case "Never": return undefined;
|
|
165
181
|
case "Once":
|
|
166
|
-
if (!this.AquiredBackground || !this.frozen) {
|
|
182
|
+
if (!this.AquiredBackground || !this.frozen || this.frozenEpoch !== this.GpuEpoch()) { // a frozen texture of a lost GPU context is taken again
|
|
167
183
|
this.ReleaseFrozenSnapshot();
|
|
168
184
|
let snapshot = this.Parent?.CachedImage;
|
|
169
185
|
let owned = false;
|
|
@@ -175,9 +191,11 @@ export class SkiaShaderEffect extends SkiaEffect {
|
|
|
175
191
|
this.frozen = snapshot;
|
|
176
192
|
this.frozenOwned = owned;
|
|
177
193
|
this.AquiredBackground = true;
|
|
194
|
+
this.frozenAt = this.ParentOrigin();
|
|
195
|
+
this.frozenEpoch = this.GpuEpoch();
|
|
178
196
|
}
|
|
179
197
|
}
|
|
180
|
-
return this.frozen;
|
|
198
|
+
return this.FollowParent(this.frozen);
|
|
181
199
|
default:
|
|
182
200
|
return this.Parent?.CachedImage;
|
|
183
201
|
}
|
|
@@ -228,7 +246,7 @@ export class SkiaShaderEffect extends SkiaEffect {
|
|
|
228
246
|
static async FetchText(url) {
|
|
229
247
|
let p = sourceCache.get(url);
|
|
230
248
|
if (!p) {
|
|
231
|
-
p = fetch(url).then((r) => { if (!r.ok)
|
|
249
|
+
p = fetch(Super.ResolveAssetUrl(url)).then((r) => { if (!r.ok)
|
|
232
250
|
throw new Error(`${r.status} ${url}`); return r.text(); });
|
|
233
251
|
sourceCache.set(url, p);
|
|
234
252
|
}
|
|
@@ -420,7 +438,8 @@ export class ShaderDoubleTexturesEffect extends SkiaShaderEffect {
|
|
|
420
438
|
return undefined;
|
|
421
439
|
const w = Math.round(d.Width), h = Math.round(d.Height);
|
|
422
440
|
const cur = slot === "p" ? this.resizedPrimary : this.resizedSecondary;
|
|
423
|
-
|
|
441
|
+
const epoch = this.GpuEpoch();
|
|
442
|
+
if (cur && cur.source === source && cur.w === w && cur.h === h && cur.epoch === epoch)
|
|
424
443
|
return cur.image;
|
|
425
444
|
const main = ctx.Context.Surface;
|
|
426
445
|
if (!main)
|
|
@@ -435,7 +454,7 @@ export class ShaderDoubleTexturesEffect extends SkiaShaderEffect {
|
|
|
435
454
|
const image = surface.makeImageSnapshot();
|
|
436
455
|
surface.delete();
|
|
437
456
|
cur?.image.delete();
|
|
438
|
-
const entry = { source, w, h, image };
|
|
457
|
+
const entry = { source, w, h, epoch, image };
|
|
439
458
|
if (slot === "p")
|
|
440
459
|
this.resizedPrimary = entry;
|
|
441
460
|
else
|
|
@@ -39,7 +39,7 @@ export class SkiaImageManager {
|
|
|
39
39
|
return inflight;
|
|
40
40
|
}
|
|
41
41
|
async Fetch(source, signal) {
|
|
42
|
-
const response = await fetch(source, signal ? { signal } : undefined);
|
|
42
|
+
const response = await fetch(Super.ResolveAssetUrl(source), signal ? { signal } : undefined);
|
|
43
43
|
if (!response.ok)
|
|
44
44
|
throw new Error(`DrawnUi: ${response.status} loading image '${source}'`);
|
|
45
45
|
const image = Super.CK.MakeImageFromEncoded(await response.arrayBuffer());
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { SkiaControl } from "./SkiaControl";
|
|
2
|
+
/**
|
|
3
|
+
* Mirrors DrawnUi.Net `Style` (SharedNet/Internals/Core/Style.cs): a set of property defaults for one control type,
|
|
4
|
+
* registered once at startup through `Super.UseDrawnUi().ConfigureStyles(...)`.
|
|
5
|
+
*
|
|
6
|
+
* The C# `Setters` list of `Setter { Property, Value }` becomes a plain object here, because React controls are
|
|
7
|
+
* classes with plain properties instead of BindableProperty: `Setters: { FontFamily: "FontText", FontSize: 14 }`.
|
|
8
|
+
*/
|
|
9
|
+
export interface Style {
|
|
10
|
+
/** The control class the style targets, e.g. `SkiaLabel`. */
|
|
11
|
+
TargetType: Function;
|
|
12
|
+
/** Also style classes deriving from TargetType (C# Style.ApplyToDerivedTypes). Default false. */
|
|
13
|
+
ApplyToDerivedTypes?: boolean;
|
|
14
|
+
/** Property defaults, by property name. Anything the app sets itself wins over these. */
|
|
15
|
+
Setters: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
/** Mirrors DrawnUi.Net `IStylesCollection` / `StylesCollection`: the startup registry, static like the C# one. */
|
|
18
|
+
export declare class StylesCollection {
|
|
19
|
+
/** Every style registered through ConfigureStyles, in registration order (C# StylesCollection.Styles). */
|
|
20
|
+
static readonly Styles: Style[];
|
|
21
|
+
AddStyle(style: Style): StylesCollection;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Applies registered styles to controls. Port of the C# `SkiaControl` styles partial
|
|
25
|
+
* (SharedNet/Draw/SkiaControl.NetShared.cs) with one difference forced by the language: C# knows which properties the
|
|
26
|
+
* app set explicitly (BindableProperty + ExplicitPropertiesSet) and skips those; here a property counts as "not set by
|
|
27
|
+
* the app" while it still equals the value the control's own constructor left, captured once per class.
|
|
28
|
+
*/
|
|
29
|
+
export declare class SkiaStyles {
|
|
30
|
+
private static readonly resolved;
|
|
31
|
+
private static readonly defaults;
|
|
32
|
+
/** True while no style is registered: lets Measure skip the whole feature at zero cost. */
|
|
33
|
+
static get IsEmpty(): boolean;
|
|
34
|
+
/** C# InvalidateStylesCache: drop the per-type resolution after the registry changed. */
|
|
35
|
+
static InvalidateStylesCache(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Setters that apply to a control class: the exact-type style plus every ApplyToDerivedTypes style of a base class,
|
|
38
|
+
* furthest base first so a nearer style overrides it, exact type last (C# sorts by inheritance distance).
|
|
39
|
+
*/
|
|
40
|
+
private static For;
|
|
41
|
+
/** The values a freshly constructed control of this class carries, for the properties a style touches. */
|
|
42
|
+
private static SnapshotDefaults;
|
|
43
|
+
/**
|
|
44
|
+
* C# ApplyInitialStyles: applies the registered defaults to one control. `atConstruction` is true when nothing has
|
|
45
|
+
* touched the control yet (the reconciler calls it between `new` and the JSX props), so every setter applies and the
|
|
46
|
+
* pre-style values are kept as this class's defaults; a control styled later only gets the properties that still
|
|
47
|
+
* hold those defaults, which is how an explicit assignment wins — the C# ExplicitPropertiesSet rule without
|
|
48
|
+
* property accessors.
|
|
49
|
+
*/
|
|
50
|
+
static Apply(control: SkiaControl, atConstruction: boolean): void;
|
|
51
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** Mirrors DrawnUi.Net `IStylesCollection` / `StylesCollection`: the startup registry, static like the C# one. */
|
|
2
|
+
export class StylesCollection {
|
|
3
|
+
/** Every style registered through ConfigureStyles, in registration order (C# StylesCollection.Styles). */
|
|
4
|
+
static Styles = [];
|
|
5
|
+
AddStyle(style) {
|
|
6
|
+
StylesCollection.Styles.push(style);
|
|
7
|
+
SkiaStyles.InvalidateStylesCache();
|
|
8
|
+
return this;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Applies registered styles to controls. Port of the C# `SkiaControl` styles partial
|
|
13
|
+
* (SharedNet/Draw/SkiaControl.NetShared.cs) with one difference forced by the language: C# knows which properties the
|
|
14
|
+
* app set explicitly (BindableProperty + ExplicitPropertiesSet) and skips those; here a property counts as "not set by
|
|
15
|
+
* the app" while it still equals the value the control's own constructor left, captured once per class.
|
|
16
|
+
*/
|
|
17
|
+
export class SkiaStyles {
|
|
18
|
+
static resolved = new Map();
|
|
19
|
+
static defaults = new Map();
|
|
20
|
+
/** True while no style is registered: lets Measure skip the whole feature at zero cost. */
|
|
21
|
+
static get IsEmpty() { return StylesCollection.Styles.length === 0; }
|
|
22
|
+
/** C# InvalidateStylesCache: drop the per-type resolution after the registry changed. */
|
|
23
|
+
static InvalidateStylesCache() { SkiaStyles.resolved.clear(); SkiaStyles.defaults.clear(); }
|
|
24
|
+
/**
|
|
25
|
+
* Setters that apply to a control class: the exact-type style plus every ApplyToDerivedTypes style of a base class,
|
|
26
|
+
* furthest base first so a nearer style overrides it, exact type last (C# sorts by inheritance distance).
|
|
27
|
+
*/
|
|
28
|
+
static For(type) {
|
|
29
|
+
let hit = SkiaStyles.resolved.get(type);
|
|
30
|
+
if (hit)
|
|
31
|
+
return hit;
|
|
32
|
+
const inherited = [];
|
|
33
|
+
let exact;
|
|
34
|
+
for (const style of StylesCollection.Styles) {
|
|
35
|
+
if (style.TargetType === type) {
|
|
36
|
+
exact = style;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (!style.ApplyToDerivedTypes)
|
|
40
|
+
continue;
|
|
41
|
+
let distance = 0;
|
|
42
|
+
for (let base = Object.getPrototypeOf(type); base; base = Object.getPrototypeOf(base)) {
|
|
43
|
+
distance++;
|
|
44
|
+
if (base === style.TargetType) {
|
|
45
|
+
inherited.push({ Distance: distance, Style: style });
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
inherited.sort((a, b) => b.Distance - a.Distance);
|
|
51
|
+
const setters = {};
|
|
52
|
+
for (const i of inherited)
|
|
53
|
+
Object.assign(setters, i.Style.Setters);
|
|
54
|
+
if (exact)
|
|
55
|
+
Object.assign(setters, exact.Setters);
|
|
56
|
+
hit = { Setters: setters, Empty: Object.keys(setters).length === 0 };
|
|
57
|
+
SkiaStyles.resolved.set(type, hit);
|
|
58
|
+
return hit;
|
|
59
|
+
}
|
|
60
|
+
/** The values a freshly constructed control of this class carries, for the properties a style touches. */
|
|
61
|
+
static SnapshotDefaults(type, sample, keys) {
|
|
62
|
+
if (SkiaStyles.defaults.has(type))
|
|
63
|
+
return;
|
|
64
|
+
const known = {};
|
|
65
|
+
for (const key of keys)
|
|
66
|
+
known[key] = sample[key];
|
|
67
|
+
SkiaStyles.defaults.set(type, known);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* C# ApplyInitialStyles: applies the registered defaults to one control. `atConstruction` is true when nothing has
|
|
71
|
+
* touched the control yet (the reconciler calls it between `new` and the JSX props), so every setter applies and the
|
|
72
|
+
* pre-style values are kept as this class's defaults; a control styled later only gets the properties that still
|
|
73
|
+
* hold those defaults, which is how an explicit assignment wins — the C# ExplicitPropertiesSet rule without
|
|
74
|
+
* property accessors.
|
|
75
|
+
*/
|
|
76
|
+
static Apply(control, atConstruction) {
|
|
77
|
+
const type = control.constructor;
|
|
78
|
+
const resolved = SkiaStyles.For(type);
|
|
79
|
+
if (resolved.Empty)
|
|
80
|
+
return;
|
|
81
|
+
const keys = Object.keys(resolved.Setters);
|
|
82
|
+
const target = control;
|
|
83
|
+
if (atConstruction)
|
|
84
|
+
SkiaStyles.SnapshotDefaults(type, control, keys);
|
|
85
|
+
const defaults = atConstruction ? undefined : SkiaStyles.defaults.get(type);
|
|
86
|
+
for (const key of keys) {
|
|
87
|
+
// no snapshot for this class (nothing of it ever went through the reconciler): apply as a plain default
|
|
88
|
+
if (!defaults || Object.is(target[key], defaults[key]))
|
|
89
|
+
target[key] = resolved.Setters[key];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
package/dist/core/Super.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Thickness } from "./Types";
|
|
2
|
+
import { StylesCollection } from "./Styles";
|
|
2
3
|
import type { CanvasKit, Font, Typeface } from "canvaskit-wasm";
|
|
3
4
|
/** Mirrors DrawnUi.Net IFontCollection: fonts.AddFont(source, alias[, weight]), plus the Blazor-head AddSymbols/AddEmojis. */
|
|
4
5
|
export declare class FontCollection {
|
|
@@ -16,10 +17,16 @@ export declare class FontCollection {
|
|
|
16
17
|
/** Registers a face under an alias; weight 100..900 lets FontWeight / FontAttributes=Bold pick the right file. */
|
|
17
18
|
AddFont(source: string, alias: string, weight?: number): FontCollection;
|
|
18
19
|
}
|
|
19
|
-
/** Mirrors DrawnUi.Net DrawnUiBuilder: Super.UseDrawnUi().ConfigureFonts(...).BuildAsync(). */
|
|
20
|
+
/** Mirrors DrawnUi.Net DrawnUiBuilder: Super.UseDrawnUi().ConfigureFonts(...).ConfigureStyles(...).BuildAsync(). */
|
|
20
21
|
export declare class DrawnUiBuilder {
|
|
21
22
|
private readonly fonts;
|
|
22
23
|
ConfigureFonts(configure: (fonts: FontCollection) => void): DrawnUiBuilder;
|
|
24
|
+
/**
|
|
25
|
+
* Mirrors DrawnUi.Net ConfigureStyles: property defaults per control type, applied to every control unless the app
|
|
26
|
+
* sets that property itself. The usual one, same as the .NET Blazor / Fiddle hosts:
|
|
27
|
+
* `styles.AddStyle({ TargetType: SkiaLabel, ApplyToDerivedTypes: true, Setters: { FontFamily: "FontText" } })`.
|
|
28
|
+
*/
|
|
29
|
+
ConfigureStyles(configure: (styles: StylesCollection) => void): DrawnUiBuilder;
|
|
23
30
|
/** Loads CanvasKit + registered fonts. Must complete before the first Canvas is created. */
|
|
24
31
|
BuildAsync(): Promise<void>;
|
|
25
32
|
}
|
|
@@ -39,10 +46,19 @@ export declare class Super {
|
|
|
39
46
|
static readonly Fonts: Map<string, Map<number, Typeface>>;
|
|
40
47
|
/** First registered font, or CanvasKit's built-in one. */
|
|
41
48
|
static DefaultTypeface?: Typeface;
|
|
49
|
+
/** CanvasKit's built-in face: what an empty FontFamily resolves to, like C# SKTypeface.CreateDefault(). */
|
|
50
|
+
static SkiaDefaultTypeface?: Typeface;
|
|
42
51
|
private static readonly fontCache;
|
|
43
52
|
static UseDrawnUi(): DrawnUiBuilder;
|
|
44
53
|
/** Master switch: false makes every control render uncached (DrawnUi Super.CacheEnabled). */
|
|
45
54
|
static CacheEnabled: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Address every engine loader fetches a source from (images, sprites, fonts, SVG, GIF, Lottie, shaders); caches
|
|
57
|
+
* stay keyed by the source itself. Web-only, no C# counterpart. Default: a file listed in
|
|
58
|
+
* `globalThis.DrawnUiAssetStamps` (written into index.html by the `drawnUiAssetStamps()` Vite plugin) gets
|
|
59
|
+
* `?v=<content hash>`, so a browser never keeps an old copy of a changed file. Replace it to map sources elsewhere.
|
|
60
|
+
*/
|
|
61
|
+
static ResolveAssetUrl: (source: string) => string;
|
|
46
62
|
private static readonly colorCache;
|
|
47
63
|
/**
|
|
48
64
|
* Color string -> CanvasKit color. Hex follows the MAUI/DrawnUi convention: #RGB, #ARGB, #RRGGBB, #AARRGGBB
|
package/dist/core/Super.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Thickness } from "./Types";
|
|
2
|
+
import { StylesCollection } from "./Styles";
|
|
2
3
|
// the "full" build: same API plus Skottie (SkiaLottie) and the paragraph module; +0.9 MB raw over the default build
|
|
3
4
|
import CanvasKitInit from "canvaskit-wasm/bin/full/canvaskit.js";
|
|
4
5
|
import wasmUrl from "canvaskit-wasm/bin/full/canvaskit.wasm?url";
|
|
@@ -22,13 +23,22 @@ export class FontCollection {
|
|
|
22
23
|
return this;
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
|
-
/** Mirrors DrawnUi.Net DrawnUiBuilder: Super.UseDrawnUi().ConfigureFonts(...).BuildAsync(). */
|
|
26
|
+
/** Mirrors DrawnUi.Net DrawnUiBuilder: Super.UseDrawnUi().ConfigureFonts(...).ConfigureStyles(...).BuildAsync(). */
|
|
26
27
|
export class DrawnUiBuilder {
|
|
27
28
|
fonts = new FontCollection();
|
|
28
29
|
ConfigureFonts(configure) {
|
|
29
30
|
configure(this.fonts);
|
|
30
31
|
return this;
|
|
31
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Mirrors DrawnUi.Net ConfigureStyles: property defaults per control type, applied to every control unless the app
|
|
35
|
+
* sets that property itself. The usual one, same as the .NET Blazor / Fiddle hosts:
|
|
36
|
+
* `styles.AddStyle({ TargetType: SkiaLabel, ApplyToDerivedTypes: true, Setters: { FontFamily: "FontText" } })`.
|
|
37
|
+
*/
|
|
38
|
+
ConfigureStyles(configure) {
|
|
39
|
+
configure(new StylesCollection());
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
32
42
|
/** Loads CanvasKit + registered fonts. Must complete before the first Canvas is created. */
|
|
33
43
|
async BuildAsync() {
|
|
34
44
|
Super.CK = await CanvasKitInit({ locateFile: () => wasmUrl });
|
|
@@ -36,7 +46,7 @@ export class DrawnUiBuilder {
|
|
|
36
46
|
for (const f of this.fonts.Fonts) {
|
|
37
47
|
let face = loaded.get(f.Source);
|
|
38
48
|
if (!face) {
|
|
39
|
-
const data = await (await fetch(f.Source)).arrayBuffer();
|
|
49
|
+
const data = await (await fetch(Super.ResolveAssetUrl(f.Source))).arrayBuffer();
|
|
40
50
|
face = Super.CK.Typeface.MakeFreeTypeFaceFromData(data) ?? undefined;
|
|
41
51
|
if (!face)
|
|
42
52
|
throw new Error(`DrawnUi: cannot load font '${f.Source}'`);
|
|
@@ -61,7 +71,8 @@ export class DrawnUiBuilder {
|
|
|
61
71
|
if (!Super.DefaultFontAlias)
|
|
62
72
|
Super.DefaultFontAlias = f.Alias;
|
|
63
73
|
}
|
|
64
|
-
Super.
|
|
74
|
+
Super.SkiaDefaultTypeface ??= Super.CK.Typeface.GetDefault() ?? undefined;
|
|
75
|
+
Super.DefaultTypeface ??= Super.SkiaDefaultTypeface;
|
|
65
76
|
}
|
|
66
77
|
}
|
|
67
78
|
/** Mirrors DrawnUi static Super: global engine state. */
|
|
@@ -105,10 +116,26 @@ export class Super {
|
|
|
105
116
|
static Fonts = new Map();
|
|
106
117
|
/** First registered font, or CanvasKit's built-in one. */
|
|
107
118
|
static DefaultTypeface;
|
|
119
|
+
/** CanvasKit's built-in face: what an empty FontFamily resolves to, like C# SKTypeface.CreateDefault(). */
|
|
120
|
+
static SkiaDefaultTypeface;
|
|
108
121
|
static fontCache = new Map();
|
|
109
122
|
static UseDrawnUi() { return new DrawnUiBuilder(); }
|
|
110
123
|
/** Master switch: false makes every control render uncached (DrawnUi Super.CacheEnabled). */
|
|
111
124
|
static CacheEnabled = true;
|
|
125
|
+
/**
|
|
126
|
+
* Address every engine loader fetches a source from (images, sprites, fonts, SVG, GIF, Lottie, shaders); caches
|
|
127
|
+
* stay keyed by the source itself. Web-only, no C# counterpart. Default: a file listed in
|
|
128
|
+
* `globalThis.DrawnUiAssetStamps` (written into index.html by the `drawnUiAssetStamps()` Vite plugin) gets
|
|
129
|
+
* `?v=<content hash>`, so a browser never keeps an old copy of a changed file. Replace it to map sources elsewhere.
|
|
130
|
+
*/
|
|
131
|
+
static ResolveAssetUrl = (source) => {
|
|
132
|
+
const stamps = globalThis.DrawnUiAssetStamps;
|
|
133
|
+
if (!stamps)
|
|
134
|
+
return source;
|
|
135
|
+
const q = source.indexOf("?");
|
|
136
|
+
const v = stamps[(q < 0 ? source : source.slice(0, q)).replace(/^\.?\//, "")];
|
|
137
|
+
return v ? `${source}${q < 0 ? "?" : "&"}v=${v}` : source;
|
|
138
|
+
};
|
|
112
139
|
static colorCache = new Map();
|
|
113
140
|
/**
|
|
114
141
|
* Color string -> CanvasKit color. Hex follows the MAUI/DrawnUi convention: #RGB, #ARGB, #RRGGBB, #AARRGGBB
|
|
@@ -145,7 +172,11 @@ export class Super {
|
|
|
145
172
|
}
|
|
146
173
|
/** Nearest registered weight of the alias (empty alias = the first registered family); reports the weight actually used. */
|
|
147
174
|
static ResolveTypeface(alias, weight) {
|
|
148
|
-
|
|
175
|
+
// No family: the Skia built-in face, like C# SkiaFontManager.GetFont("") -> SKTypeface.CreateDefault(). An app that
|
|
176
|
+
// wants its own font everywhere registers it as a style default (ConfigureStyles), exactly as the .NET hosts do.
|
|
177
|
+
if (!alias)
|
|
178
|
+
return { Typeface: Super.SkiaDefaultTypeface ?? Super.DefaultTypeface ?? null, Weight: 400 };
|
|
179
|
+
const weights = Super.Fonts.get(alias);
|
|
149
180
|
const target = weight > 0 ? weight : 400;
|
|
150
181
|
if (weights && weights.size > 0) {
|
|
151
182
|
if (weights.has(target))
|
|
@@ -16,6 +16,8 @@ export declare class ViewsAdapter {
|
|
|
16
16
|
Created: number;
|
|
17
17
|
constructor(parent: SkiaControl);
|
|
18
18
|
get PoolSize(): number;
|
|
19
|
+
/** The items views are bound to. */
|
|
20
|
+
get Items(): readonly unknown[];
|
|
19
21
|
get InUseCount(): number;
|
|
20
22
|
/** New template / items / mode: every realized view is dropped (DrawnUi ApplyItemsSource). */
|
|
21
23
|
Initialize(template: (() => SkiaControl) | undefined, items: readonly unknown[], recycling: RecyclingTemplate): void;
|
|
@@ -26,8 +28,11 @@ export declare class ViewsAdapter {
|
|
|
26
28
|
ShiftIndices(by: number, items: readonly unknown[]): void;
|
|
27
29
|
/** The realized view for an index, if any (no creation). */
|
|
28
30
|
GetViewForIndex(index: number): SkiaControl | undefined;
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
+
/**
|
|
32
|
+
* View bound to items[index]: existing, recycled from the pool, or freshly created. `quiet`: bound before it is
|
|
33
|
+
* attached, so the rebind invalidates the view alone and not the layout, which measures or draws it right away.
|
|
34
|
+
*/
|
|
35
|
+
GetOrCreateViewForIndex(index: number, quiet?: boolean): SkiaControl | undefined;
|
|
31
36
|
/** Index left the visible range: recycled views go back to the pool, non-recycled ones stay realized. */
|
|
32
37
|
ReleaseViewAt(index: number): void;
|
|
33
38
|
/** Releases every realized index outside [first, last]. */
|
|
@@ -16,6 +16,8 @@ export class ViewsAdapter {
|
|
|
16
16
|
this.parent = parent;
|
|
17
17
|
}
|
|
18
18
|
get PoolSize() { return this.pool.length; }
|
|
19
|
+
/** The items views are bound to. */
|
|
20
|
+
get Items() { return this.items; }
|
|
19
21
|
get InUseCount() { return this.inUse.size; }
|
|
20
22
|
/** New template / items / mode: every realized view is dropped (DrawnUi ApplyItemsSource). */
|
|
21
23
|
Initialize(template, items, recycling) {
|
|
@@ -52,8 +54,11 @@ export class ViewsAdapter {
|
|
|
52
54
|
}
|
|
53
55
|
/** The realized view for an index, if any (no creation). */
|
|
54
56
|
GetViewForIndex(index) { return this.inUse.get(index); }
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
+
/**
|
|
58
|
+
* View bound to items[index]: existing, recycled from the pool, or freshly created. `quiet`: bound before it is
|
|
59
|
+
* attached, so the rebind invalidates the view alone and not the layout, which measures or draws it right away.
|
|
60
|
+
*/
|
|
61
|
+
GetOrCreateViewForIndex(index, quiet = false) {
|
|
57
62
|
const existing = this.inUse.get(index);
|
|
58
63
|
if (existing)
|
|
59
64
|
return existing;
|
|
@@ -64,9 +69,11 @@ export class ViewsAdapter {
|
|
|
64
69
|
view = this.template();
|
|
65
70
|
this.Created++;
|
|
66
71
|
}
|
|
67
|
-
|
|
72
|
+
if (!quiet)
|
|
73
|
+
view.Parent = this.parent;
|
|
68
74
|
view.ContextIndex = index;
|
|
69
75
|
view.BindingContext = this.items[index];
|
|
76
|
+
view.Parent = this.parent;
|
|
70
77
|
this.inUse.set(index, view);
|
|
71
78
|
return view;
|
|
72
79
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./core/Types";
|
|
2
2
|
export * from "./core/Gestures";
|
|
3
|
+
export * from "./core/Styles";
|
|
3
4
|
export * from "./core/Easing";
|
|
4
5
|
export * from "./core/Animators";
|
|
5
6
|
export * from "./core/ScrollAnimators";
|
|
@@ -8,9 +9,11 @@ export * from "./core/SkiaControl";
|
|
|
8
9
|
export * from "./core/Canvas";
|
|
9
10
|
export * from "./controls/SkiaLayout";
|
|
10
11
|
export * from "./core/Accessibility";
|
|
12
|
+
export * from "./core/Hover";
|
|
11
13
|
export * from "./controls/SkiaLabel";
|
|
12
14
|
export * from "./controls/TextSpan";
|
|
13
15
|
export * from "./controls/SkiaRichLabel";
|
|
16
|
+
export * from "./controls/SkiaLabelFps";
|
|
14
17
|
export * from "./core/ControlStyle";
|
|
15
18
|
export * from "./core/ImageEffects";
|
|
16
19
|
export * from "./controls/SkiaToggle";
|
|
@@ -39,6 +42,7 @@ export * from "./controls/SkiaBackdrop";
|
|
|
39
42
|
export * from "./controls/SkiaEditor";
|
|
40
43
|
export * from "./controls/SkiaSprite";
|
|
41
44
|
export * from "./controls/SkiaSpriteSet";
|
|
45
|
+
export * from "./controls/DrawnGame";
|
|
42
46
|
export * from "./core/KeyboardManager";
|
|
43
47
|
export * from "./controls/AnimatedFramesRenderer";
|
|
44
48
|
export * from "./controls/SkiaLottie";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./core/Types";
|
|
2
2
|
export * from "./core/Gestures";
|
|
3
|
+
export * from "./core/Styles";
|
|
3
4
|
export * from "./core/Easing";
|
|
4
5
|
export * from "./core/Animators";
|
|
5
6
|
export * from "./core/ScrollAnimators";
|
|
@@ -8,9 +9,11 @@ export * from "./core/SkiaControl";
|
|
|
8
9
|
export * from "./core/Canvas";
|
|
9
10
|
export * from "./controls/SkiaLayout";
|
|
10
11
|
export * from "./core/Accessibility";
|
|
12
|
+
export * from "./core/Hover";
|
|
11
13
|
export * from "./controls/SkiaLabel";
|
|
12
14
|
export * from "./controls/TextSpan";
|
|
13
15
|
export * from "./controls/SkiaRichLabel";
|
|
16
|
+
export * from "./controls/SkiaLabelFps";
|
|
14
17
|
export * from "./core/ControlStyle";
|
|
15
18
|
export * from "./core/ImageEffects";
|
|
16
19
|
export * from "./controls/SkiaToggle";
|
|
@@ -39,6 +42,7 @@ export * from "./controls/SkiaBackdrop";
|
|
|
39
42
|
export * from "./controls/SkiaEditor";
|
|
40
43
|
export * from "./controls/SkiaSprite";
|
|
41
44
|
export * from "./controls/SkiaSpriteSet";
|
|
45
|
+
export * from "./controls/DrawnGame";
|
|
42
46
|
export * from "./core/KeyboardManager";
|
|
43
47
|
export * from "./controls/AnimatedFramesRenderer";
|
|
44
48
|
export * from "./controls/SkiaLottie";
|
package/dist/react/SkiaShell.js
CHANGED
|
@@ -389,6 +389,12 @@ export const SkiaShell = forwardRef(function SkiaShell({ Routes, children, NavBa
|
|
|
389
389
|
prev ? prev.TranslateToAsync(-dir * w, 0, TabsAnimationSpeed, TabsEasing) : Promise.resolve(),
|
|
390
390
|
]), TabsAnimationSpeed + 500);
|
|
391
391
|
}
|
|
392
|
+
// the tab left stays mounted (its state kept): hidden first, then back in place for its next turn
|
|
393
|
+
if (prev) {
|
|
394
|
+
prev.IsVisible = false;
|
|
395
|
+
prev.TranslationX = 0;
|
|
396
|
+
prev.TranslationY = 0;
|
|
397
|
+
}
|
|
392
398
|
setTabTransition(null);
|
|
393
399
|
}, [hasTabs, Tabs, AnimateTabs, TabsAnimationSpeed]);
|
|
394
400
|
// popstate: the browser popped OUR last entry (back) — or moved forward to a hash we can rebuild
|
|
@@ -405,7 +411,13 @@ export const SkiaShell = forwardRef(function SkiaShell({ Routes, children, NavBa
|
|
|
405
411
|
}
|
|
406
412
|
const onPop = () => {
|
|
407
413
|
const depth = window.history.state?.[HISTORY_KEY] ?? 0;
|
|
408
|
-
|
|
414
|
+
const target = parseHash();
|
|
415
|
+
// a Back lands on one of OUR entries: its hash is the stack it was pushed with (the first N pages, N = the page
|
|
416
|
+
// entries below it). A typed hash or a plain <a href="#/route"> makes a new entry with no state (depth 0):
|
|
417
|
+
// that is a navigation to the hash, not a Back to the root
|
|
418
|
+
const pagesBelow = history.current.slice(0, depth).filter((k) => k === "page").length;
|
|
419
|
+
const back = depth < history.current.length && target.join("/") === live.current.stack.slice(0, pagesBelow).join("/");
|
|
420
|
+
if (back) {
|
|
409
421
|
// back: unwind our entries down to depth
|
|
410
422
|
void (async () => {
|
|
411
423
|
while (history.current.length > depth) {
|
|
@@ -441,9 +453,8 @@ export const SkiaShell = forwardRef(function SkiaShell({ Routes, children, NavBa
|
|
|
441
453
|
})();
|
|
442
454
|
}
|
|
443
455
|
else {
|
|
444
|
-
// forward (depth grew), or
|
|
456
|
+
// forward (depth grew), or another hash = a plain <a href="#/route"> link or a typed hash:
|
|
445
457
|
// the hash names the pages, rebuild the stack without animation (overlays cannot be restored)
|
|
446
|
-
const target = parseHash();
|
|
447
458
|
if (depth === history.current.length && target.join("/") === live.current.stack.join("/"))
|
|
448
459
|
return;
|
|
449
460
|
history.current = target.map(() => "page");
|
|
@@ -466,28 +477,36 @@ export const SkiaShell = forwardRef(function SkiaShell({ Routes, children, NavBa
|
|
|
466
477
|
const transitioning = transitions > 0 || leaving !== null;
|
|
467
478
|
const rootBottom = hasTabs ? TabBarHeight + insets.Bottom : 0;
|
|
468
479
|
const navTop = NavBarHeight + insets.Top;
|
|
469
|
-
|
|
480
|
+
// every tab keeps its root and its pages mounted (state, scroll positions), only the selected one shows (C# tab
|
|
481
|
+
// views, DrawnUi.Rust "tabs keep their stacks")
|
|
482
|
+
const tabIndexes = hasTabs ? Tabs.map((_, i) => i) : [0];
|
|
470
483
|
const entering = tabTransition ? { TranslationX: tabTransition.dir * 0.75 * (typeof window !== "undefined" ? window.innerWidth : 0), Opacity: 0.001, ZIndex: 1 } : {};
|
|
471
|
-
return (_jsx(ShellContext.Provider, { value: nav, children: _jsxs(SkiaLayer, { VerticalOptions: "Fill", children: [
|
|
472
|
-
|
|
473
|
-
|
|
484
|
+
return (_jsx(ShellContext.Provider, { value: nav, children: _jsxs(SkiaLayer, { VerticalOptions: "Fill", children: [tabIndexes.map((t) => {
|
|
485
|
+
const selected = t === selectedTab;
|
|
486
|
+
return (_jsx(SkiaLayer, { VerticalOptions: "Fill", Margin: new Thickness(0, 0, 0, rootBottom), BackgroundColor: hasTabs ? PageBackgroundColor : undefined, IsVisible: selected ? visiblePages.length === 0 || transitioning : tabTransition?.from === t, ZIndex: 0, ...(selected ? entering : {}), ref: (c) => { if (c)
|
|
487
|
+
tabRoots.current.set(t, c); }, children: hasTabs ? RenderRoute(Tabs[t]?.route ?? "") : children }, hasTabs ? `tab-${t}` : "root"));
|
|
488
|
+
}), tabIndexes.flatMap((t) => (t === selectedTab ? visiblePages : stacks[t] ?? []).map((r, i) => (_jsxs(PageHost, { route: r, speed: PagesAnimationSpeed, active: t === selectedTab, visible: t === selectedTab && (r === topRoute || transitioning), background: PageBackgroundColor, bottom: rootBottom, register: (c) => { if (c)
|
|
474
489
|
pageCtrls.current.set(r, c);
|
|
475
490
|
else
|
|
476
|
-
pageCtrls.current.delete(r); }, children: [_jsx(SkiaLayer, { VerticalOptions: "Fill", Margin: new Thickness(0, navTop, 0, 0), children: RenderRoute(r) }), _jsxs(SkiaLayer, { HeightRequest: navTop, BackgroundColor: NavBarColor, Padding: new Thickness(0, insets.Top, 0, 0), children: [_jsx(SkiaButton, { Text: "\u2039 Back", BackgroundColor: "#00000000", TextColor: "#6EA8FE", FontSize: 16, VerticalOptions: "Center", Margin: new Thickness(8, 0), ApplyEffect: "Ripple", Tapped: () => void GoBackAsync(), AccessibilityRole: "button", AccessibilityLabel: "Back" }), _jsx(SkiaLabel, { Text: Titles?.[RouteName(r)] ?? RouteName(r), FontSize: 18, FontFamily: "FontTextBold", TextColor: Colors.White, HorizontalOptions: "Fill", HorizontalTextAlignment: "Center", VerticalOptions: "Center", MaxLines: 1, Margin: new Thickness(96, 0), AccessibilityRole: "heading" }), _jsx(SkiaButton, { Text: "Home", BackgroundColor: "#00000000", TextColor: "#6EA8FE", FontSize: 16, HorizontalOptions: "End", VerticalOptions: "Center", Margin: new Thickness(0, 0, 8, 0), ApplyEffect: "Ripple", Tapped: () => void PopToRootAsync(), AccessibilityRole: "button", AccessibilityLabel: "Home" }), _jsx(SkiaLayer, { HeightRequest: 1, VerticalOptions: "End", BackgroundColor: "#343A40" })] })] }, `${
|
|
491
|
+
pageCtrls.current.delete(r); }, children: [_jsx(SkiaLayer, { VerticalOptions: "Fill", Margin: new Thickness(0, navTop, 0, 0), children: RenderRoute(r) }), _jsxs(SkiaLayer, { HeightRequest: navTop, BackgroundColor: NavBarColor, Padding: new Thickness(0, insets.Top, 0, 0), children: [_jsx(SkiaButton, { Text: "\u2039 Back", BackgroundColor: "#00000000", TextColor: "#6EA8FE", FontSize: 16, VerticalOptions: "Center", Margin: new Thickness(8, 0), ApplyEffect: "Ripple", Tapped: () => void GoBackAsync(), AccessibilityRole: "button", AccessibilityLabel: "Back" }), _jsx(SkiaLabel, { Text: Titles?.[RouteName(r)] ?? RouteName(r), FontSize: 18, FontFamily: "FontTextBold", TextColor: Colors.White, HorizontalOptions: "Fill", HorizontalTextAlignment: "Center", VerticalOptions: "Center", MaxLines: 1, Margin: new Thickness(96, 0), AccessibilityRole: "heading" }), _jsx(SkiaButton, { Text: "Home", BackgroundColor: "#00000000", TextColor: "#6EA8FE", FontSize: 16, HorizontalOptions: "End", VerticalOptions: "Center", Margin: new Thickness(0, 0, 8, 0), ApplyEffect: "Ripple", Tapped: () => void PopToRootAsync(), AccessibilityRole: "button", AccessibilityLabel: "Home" }), _jsx(SkiaLayer, { HeightRequest: 1, VerticalOptions: "End", BackgroundColor: "#343A40" })] })] }, `${t}:${r}#${i}`)))), hasTabs && (_jsxs(SkiaLayer, { HeightRequest: TabBarHeight + insets.Bottom, VerticalOptions: "End", BackgroundColor: TabBarColor, ZIndex: ShellDefaults.ZIndexModals - 1, BlockGesturesBelow: true, Padding: new Thickness(0, 0, 0, insets.Bottom), children: [_jsx(SkiaLayer, { HeightRequest: 1, BackgroundColor: "#343A40" }), _jsx(SkiaGrid, { ColumnDefinitions: Tabs.map(() => "*").join(","), HorizontalOptions: "Fill", VerticalOptions: "Fill", children: Tabs.map((t, i) => (_jsxs(SkiaLayer, { Column: i, HorizontalOptions: "Fill", VerticalOptions: "Fill", AnimationTapped: "Ripple", Tapped: () => void SelectTabAsync(i), AccessibilityRole: "button", AccessibilityLabel: t.title, children: [_jsx(SkiaLabel, { Text: t.title, FontSize: 13, FontFamily: i === selectedTab ? "FontTextBold" : undefined, TextColor: i === selectedTab ? ShellDefaults.TabSelectedColor : ShellDefaults.TabColor, HorizontalOptions: "Center", VerticalOptions: "Center", AccessibilityRole: "presentation" }), i === selectedTab && _jsx(SkiaShape, { Type: "Rectangle", HeightRequest: 3, WidthRequest: 36, CornerRadius: 2, BackgroundColor: ShellDefaults.TabSelectedColor, HorizontalOptions: "Center", VerticalOptions: "End" })] }, t.route))) })] })), modals.map((m) => m.node), popups.map((p) => p.node), toasts.map((t) => t.node)] }) }));
|
|
477
492
|
});
|
|
478
493
|
/** A pushed page: slides in from the right on mount (C# SkiaViewSwitcher PushView), covers what is below. */
|
|
479
|
-
function PageHost({ route, speed, visible, background, bottom, register, children }) {
|
|
494
|
+
function PageHost({ route, speed, active, visible, background, bottom, register, children }) {
|
|
480
495
|
const ctrl = useRef(null);
|
|
496
|
+
// slides in once, when pushed; a page of another tab stays mounted and is registered only while its tab is selected
|
|
481
497
|
useEffect(() => {
|
|
482
498
|
const c = ctrl.current;
|
|
483
|
-
if (
|
|
484
|
-
return;
|
|
485
|
-
register(c);
|
|
486
|
-
if (speed > 0) {
|
|
499
|
+
if (c && speed > 0) {
|
|
487
500
|
c.TranslationX = CanvasWidthPts(c);
|
|
488
501
|
void c.TranslateToAsync(0, 0, speed);
|
|
489
502
|
}
|
|
490
|
-
return () => register(null);
|
|
491
503
|
}, [route]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
504
|
+
useEffect(() => {
|
|
505
|
+
const c = ctrl.current;
|
|
506
|
+
if (!c || !active)
|
|
507
|
+
return;
|
|
508
|
+
register(c);
|
|
509
|
+
return () => register(null);
|
|
510
|
+
}, [route, active]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
492
511
|
return (_jsx(SkiaLayer, { ref: (c) => { ctrl.current = c; }, VerticalOptions: "Fill", HorizontalOptions: "Fill", Margin: new Thickness(0, 0, 0, bottom), BackgroundColor: background, IsVisible: visible, BlockGesturesBelow: true, children: children }));
|
|
493
512
|
}
|