drawnui-react 0.1.0-preview.2 → 0.1.0-preview.21

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.
Files changed (87) hide show
  1. package/README.md +17 -4
  2. package/SKIPPED.md +33 -27
  3. package/dist/controls/AnimatedFramesRenderer.d.ts +7 -0
  4. package/dist/controls/AnimatedFramesRenderer.js +16 -1
  5. package/dist/controls/DrawnGame.d.ts +40 -0
  6. package/dist/controls/DrawnGame.js +64 -0
  7. package/dist/controls/GridStructure.d.ts +16 -2
  8. package/dist/controls/GridStructure.js +40 -4
  9. package/dist/controls/SkiaBackdrop.d.ts +8 -2
  10. package/dist/controls/SkiaBackdrop.js +48 -8
  11. package/dist/controls/SkiaButton.d.ts +19 -2
  12. package/dist/controls/SkiaButton.js +41 -11
  13. package/dist/controls/SkiaCarousel.d.ts +9 -1
  14. package/dist/controls/SkiaCarousel.js +16 -3
  15. package/dist/controls/SkiaCheckbox.d.ts +0 -1
  16. package/dist/controls/SkiaCheckbox.js +0 -6
  17. package/dist/controls/SkiaDrawer.d.ts +2 -0
  18. package/dist/controls/SkiaDrawer.js +2 -0
  19. package/dist/controls/SkiaEditor.d.ts +12 -1
  20. package/dist/controls/SkiaEditor.js +46 -19
  21. package/dist/controls/SkiaGif.d.ts +3 -0
  22. package/dist/controls/SkiaGif.js +12 -2
  23. package/dist/controls/SkiaImage.js +15 -10
  24. package/dist/controls/SkiaLabel.d.ts +54 -3
  25. package/dist/controls/SkiaLabel.js +235 -52
  26. package/dist/controls/SkiaLabelFps.d.ts +16 -0
  27. package/dist/controls/SkiaLabelFps.js +41 -0
  28. package/dist/controls/SkiaLayout.d.ts +64 -7
  29. package/dist/controls/SkiaLayout.js +316 -19
  30. package/dist/controls/SkiaLottie.js +4 -2
  31. package/dist/controls/SkiaProgress.d.ts +2 -1
  32. package/dist/controls/SkiaProgress.js +6 -3
  33. package/dist/controls/SkiaRadioButton.js +4 -3
  34. package/dist/controls/SkiaScroll.d.ts +35 -5
  35. package/dist/controls/SkiaScroll.js +233 -23
  36. package/dist/controls/SkiaScrollBar.d.ts +27 -0
  37. package/dist/controls/SkiaScrollBar.js +51 -2
  38. package/dist/controls/SkiaShaderCarousel.d.ts +5 -0
  39. package/dist/controls/SkiaShaderCarousel.js +5 -0
  40. package/dist/controls/SkiaShape.d.ts +13 -2
  41. package/dist/controls/SkiaShape.js +50 -10
  42. package/dist/controls/SkiaSlider.d.ts +20 -2
  43. package/dist/controls/SkiaSlider.js +59 -5
  44. package/dist/controls/SkiaSprite.d.ts +1 -0
  45. package/dist/controls/SkiaSprite.js +4 -0
  46. package/dist/controls/SkiaSvg.js +1 -1
  47. package/dist/controls/SkiaSwitch.d.ts +0 -1
  48. package/dist/controls/SkiaSwitch.js +0 -6
  49. package/dist/controls/SkiaToggle.d.ts +25 -5
  50. package/dist/controls/SkiaToggle.js +58 -5
  51. package/dist/controls/SnappingLayout.d.ts +12 -1
  52. package/dist/controls/SnappingLayout.js +19 -1
  53. package/dist/core/Accessibility.d.ts +80 -0
  54. package/dist/core/Accessibility.js +365 -8
  55. package/dist/core/Animators.d.ts +37 -1
  56. package/dist/core/Animators.js +102 -9
  57. package/dist/core/Canvas.d.ts +108 -7
  58. package/dist/core/Canvas.js +363 -35
  59. package/dist/core/Easing.d.ts +12 -1
  60. package/dist/core/Easing.js +25 -1
  61. package/dist/core/Gestures.d.ts +48 -2
  62. package/dist/core/Gestures.js +95 -3
  63. package/dist/core/Hover.d.ts +47 -0
  64. package/dist/core/Hover.js +134 -0
  65. package/dist/core/KeyboardManager.d.ts +13 -0
  66. package/dist/core/KeyboardManager.js +18 -0
  67. package/dist/core/ScrollAnimators.js +9 -0
  68. package/dist/core/SkiaControl.d.ts +216 -7
  69. package/dist/core/SkiaControl.js +516 -59
  70. package/dist/core/SkiaEffect.d.ts +17 -0
  71. package/dist/core/SkiaEffect.js +43 -8
  72. package/dist/core/SkiaImageManager.js +1 -1
  73. package/dist/core/Styles.d.ts +51 -0
  74. package/dist/core/Styles.js +92 -0
  75. package/dist/core/Super.d.ts +17 -1
  76. package/dist/core/Super.js +35 -4
  77. package/dist/core/ViewsAdapter.d.ts +7 -2
  78. package/dist/core/ViewsAdapter.js +10 -3
  79. package/dist/index.d.ts +4 -0
  80. package/dist/index.js +4 -0
  81. package/dist/react/SkiaShell.js +33 -14
  82. package/dist/react/index.d.ts +15 -4
  83. package/dist/react/index.js +102 -15
  84. package/dist/react/reconciler.js +34 -12
  85. package/dist/vite/index.d.ts +9 -0
  86. package/dist/vite/index.js +36 -3
  87. package/package.json +33 -3
@@ -119,12 +119,29 @@ export declare class SkiaShaderEffect extends SkiaEffect implements IPostRendere
119
119
  get NeedApply(): boolean;
120
120
  /** Loads (async, once) and compiles (sync) when needed; true when a compiled effect exists. */
121
121
  EnsureCompiled(): boolean;
122
+ /**
123
+ * C# ShouldInvalidateParentCache (default false): animation ticks, `SetUniform` and `UpdateUniforms()` only redraw
124
+ * this effect over the parent's cache (ancestor caches still redraw), because the effect paints over the parent's
125
+ * cached content and a uniform change needs no new cache. True: they also invalidate the parent's cache, so the parent
126
+ * re-renders its content on every change; set it when that content refreshes only through the effect's redraws (a
127
+ * custom Paint reading changing data without calling Update()). `Update()` always invalidates the cache.
128
+ */
129
+ ShouldInvalidateParentCache: boolean;
130
+ /** Call after changing a value only the shader reads (a uniform, an animation progress): redraws the effect over the parent's cache, or `Update()` when ShouldInvalidateParentCache is true. */
131
+ UpdateUniforms(): void;
122
132
  SetUniform(name: string, ...values: number[]): this;
123
133
  ReleaseFrozenSnapshot(): void;
124
134
  private DisposeImage;
125
135
  protected ApplyShaderSource(): void;
126
136
  /** Snapshot of what the parent painted (C# CreateSnapshot): the surface the pixels are on, mapped through its origin. */
127
137
  protected CreateSnapshot(ctx: DrawingContext, destination: SKRect): CachedTexture | undefined;
138
+ private frozenEpoch;
139
+ /** Canvas.GpuEpoch: GPU textures made before a WebGL context restore are made again. */
140
+ protected GpuEpoch(): number;
141
+ /** Where the parent was when the texture was frozen (Once): the texture moves with it, as its cache does. */
142
+ private frozenAt?;
143
+ private ParentOrigin;
144
+ private FollowParent;
128
145
  /** C# GetPrimaryTexture: the parent's cache, a frozen snapshot (Once) or nothing (Never). */
129
146
  protected GetPrimaryTexture(_ctx: DrawingContext, _destination: SKRect): CachedTexture | undefined;
130
147
  /** Image → shader with the effect's sampling, translated so texel (0,0) = bounds top-left; cached per image. */
@@ -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.
@@ -116,11 +116,27 @@ export class SkiaShaderEffect extends SkiaEffect {
116
116
  }
117
117
  return this.compiled !== undefined;
118
118
  }
119
+ /**
120
+ * C# ShouldInvalidateParentCache (default false): animation ticks, `SetUniform` and `UpdateUniforms()` only redraw
121
+ * this effect over the parent's cache (ancestor caches still redraw), because the effect paints over the parent's
122
+ * cached content and a uniform change needs no new cache. True: they also invalidate the parent's cache, so the parent
123
+ * re-renders its content on every change; set it when that content refreshes only through the effect's redraws (a
124
+ * custom Paint reading changing data without calling Update()). `Update()` always invalidates the cache.
125
+ */
126
+ ShouldInvalidateParentCache = false;
127
+ /** Call after changing a value only the shader reads (a uniform, an animation progress): redraws the effect over the parent's cache, or `Update()` when ShouldInvalidateParentCache is true. */
128
+ UpdateUniforms() {
129
+ if (this.ShouldInvalidateParentCache) {
130
+ this.Update();
131
+ return;
132
+ }
133
+ this.Parent?.RepaintComposition();
134
+ }
119
135
  // ---- uniforms API (C# SetUniform overloads) ----
120
136
  SetUniform(name, ...values) {
121
137
  if (name && values.length > 0) {
122
138
  this.Uniforms.set(name, values);
123
- this.Update();
139
+ this.UpdateUniforms();
124
140
  }
125
141
  return this;
126
142
  }
@@ -129,6 +145,7 @@ export class SkiaShaderEffect extends SkiaEffect {
129
145
  this.DisposeImage(this.frozen.Image);
130
146
  this.frozen = undefined;
131
147
  this.frozenOwned = false;
148
+ this.frozenAt = undefined;
132
149
  }
133
150
  DisposeImage(image) {
134
151
  const sv = this.Parent?.Superview;
@@ -158,12 +175,27 @@ export class SkiaShaderEffect extends SkiaEffect {
158
175
  return undefined;
159
176
  return { Image: image, Bounds: destination, Origin: { X: ox, Y: oy } };
160
177
  }
178
+ frozenEpoch = 0;
179
+ /** Canvas.GpuEpoch: GPU textures made before a WebGL context restore are made again. */
180
+ GpuEpoch() { return this.Parent?.Superview?.GpuEpoch ?? 0; }
181
+ /** Where the parent was when the texture was frozen (Once): the texture moves with it, as its cache does. */
182
+ frozenAt;
183
+ ParentOrigin() { const r = this.Parent?.DrawingRect; return { X: r?.Left ?? 0, Y: r?.Top ?? 0 }; }
184
+ FollowParent(texture) {
185
+ if (!texture || !this.frozenAt)
186
+ return texture;
187
+ const now = this.ParentOrigin(), dx = now.X - this.frozenAt.X, dy = now.Y - this.frozenAt.Y;
188
+ if (dx === 0 && dy === 0)
189
+ return texture;
190
+ const b = texture.Bounds;
191
+ 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 } };
192
+ }
161
193
  /** C# GetPrimaryTexture: the parent's cache, a frozen snapshot (Once) or nothing (Never). */
162
194
  GetPrimaryTexture(_ctx, _destination) {
163
195
  switch (this.UseBackground) {
164
196
  case "Never": return undefined;
165
197
  case "Once":
166
- if (!this.AquiredBackground || !this.frozen) {
198
+ if (!this.AquiredBackground || !this.frozen || this.frozenEpoch !== this.GpuEpoch()) { // a frozen texture of a lost GPU context is taken again
167
199
  this.ReleaseFrozenSnapshot();
168
200
  let snapshot = this.Parent?.CachedImage;
169
201
  let owned = false;
@@ -175,9 +207,11 @@ export class SkiaShaderEffect extends SkiaEffect {
175
207
  this.frozen = snapshot;
176
208
  this.frozenOwned = owned;
177
209
  this.AquiredBackground = true;
210
+ this.frozenAt = this.ParentOrigin();
211
+ this.frozenEpoch = this.GpuEpoch();
178
212
  }
179
213
  }
180
- return this.frozen;
214
+ return this.FollowParent(this.frozen);
181
215
  default:
182
216
  return this.Parent?.CachedImage;
183
217
  }
@@ -228,7 +262,7 @@ export class SkiaShaderEffect extends SkiaEffect {
228
262
  static async FetchText(url) {
229
263
  let p = sourceCache.get(url);
230
264
  if (!p) {
231
- p = fetch(url).then((r) => { if (!r.ok)
265
+ p = fetch(Super.ResolveAssetUrl(url)).then((r) => { if (!r.ok)
232
266
  throw new Error(`${r.status} ${url}`); return r.text(); });
233
267
  sourceCache.set(url, p);
234
268
  }
@@ -420,7 +454,8 @@ export class ShaderDoubleTexturesEffect extends SkiaShaderEffect {
420
454
  return undefined;
421
455
  const w = Math.round(d.Width), h = Math.round(d.Height);
422
456
  const cur = slot === "p" ? this.resizedPrimary : this.resizedSecondary;
423
- if (cur && cur.source === source && cur.w === w && cur.h === h)
457
+ const epoch = this.GpuEpoch();
458
+ if (cur && cur.source === source && cur.w === w && cur.h === h && cur.epoch === epoch)
424
459
  return cur.image;
425
460
  const main = ctx.Context.Surface;
426
461
  if (!main)
@@ -435,7 +470,7 @@ export class ShaderDoubleTexturesEffect extends SkiaShaderEffect {
435
470
  const image = surface.makeImageSnapshot();
436
471
  surface.delete();
437
472
  cur?.image.delete();
438
- const entry = { source, w, h, image };
473
+ const entry = { source, w, h, epoch, image };
439
474
  if (slot === "p")
440
475
  this.resizedPrimary = entry;
441
476
  else
@@ -546,7 +581,7 @@ export class AnimatedShaderEffect extends SkiaShaderEffect {
546
581
  return;
547
582
  if (!this.Animator) {
548
583
  const a = new SkiaValueAnimator(parent);
549
- a.OnUpdated = (v) => { this.Progress = v; this.Update(); };
584
+ a.OnUpdated = (v) => { this.Progress = v; this.UpdateUniforms(); };
550
585
  a.Finished = () => this.Completed?.(this);
551
586
  this.Animator = a;
552
587
  }
@@ -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
+ }
@@ -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
@@ -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.DefaultTypeface ??= Super.CK.Typeface.GetDefault() ?? undefined;
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
- const weights = Super.Fonts.get(alias || Super.DefaultFontAlias);
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
- /** View bound to items[index]: existing, recycled from the pool, or freshly created. */
30
- GetOrCreateViewForIndex(index: number): SkiaControl | undefined;
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
- /** View bound to items[index]: existing, recycled from the pool, or freshly created. */
56
- GetOrCreateViewForIndex(index) {
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
- view.Parent = this.parent;
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";