drawnui-react 0.1.0-preview.20 → 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.
package/SKIPPED.md CHANGED
@@ -15,7 +15,7 @@ DrawnUi name and semantics; nothing here is a redesign, only an omission.
15
15
  | `Opacity`, `TranslationX/Y`, `Rotation`, `ScaleX/Y` (+`Scale`), `SkewX/Y`, `RotationX/Y/Z`, `TranslationZ`, `AnchorX/Y`, `HasTransform`, `RenderTransformMatrix`, `TransformPointToLocalSpace` | ported | Same matrix order as C# `ApplyTransforms` (pivot → rotation → scale/skew → 3D → translation; 3D = C# `Create3dMatrix`, camera 576 px, TranslationZ as depth), image caches drawn smooth once the final matrix distorts them (C# `WillDrawDistorted`, a picture holding image blits records again when its playback turns distorted or straight), opacity = `saveLayer` alpha over the subtree, gestures mapped through the inverse matrix (`IsGestureForChild`, child receives the local point), accessibility rect = transformed bounds. Animations: `AnimateAsync`, `AnimateRangeAsync`, `FadeToAsync`, `ScaleToAsync`, `TranslateToAsync`, `RotateToAsync` as Promises with `AbortSignal` (one running per kind, like the C# per-property cancellation). Not ported: `Perspective1/2`, `CameraAngle`, `LinkTransforms`, `AddTranslationX/Y`, `UseTranslation*` overrides, `CustomizeLayerPaint`, `SkipRenderingOutOfBounds`. |
16
16
  | `Left` / `Top` | ported (as a translate) | Same paint-time offset in points, but folded into the render matrix (a `canvas.translate`, hit-testing follows) for every control, cached or not; C# applies them only when blitting a cache. |
17
17
  | `AnimatedFramesRenderer` / `SkiaLottie` / `SkiaGif` | ported | `AutoPlay`, `Repeat`, `SpeedRatio`, `DefaultFrame` (-1 = last), `Start(delay)`/`Stop`/`Seek`, `IsPlaying`, `Started`/`Finished`, `PlayWhenAvailable`, `GetFrameAt`. Lottie on CanvasKit Skottie (needs the `full` build, +0.9 MB raw wasm): `Source` (URL / app path, JSON cached per source, in-flight dedup), `ColorTint` / `Colors` (C# `ApplyTint` JSON rewrite), `IsOn` + `DefaultFrameWhenOn` + `ApplyIsOnWhenNotPlaying`, `StopAtCurrentFrame`, `ProcessJson`, `GoToStart`/`GoToEnd`, `SetAnimation`, `LoadSource`, `CreateAnimation`, `TotalFrames`; default cache `Operations` (C# ImageDoubleBuffered, see PARITY "Lottie and GIF default caches"). GIF on CanvasKit AnimatedImage: every frame decoded once (`GifAnimation`: `SeekFrame`, `GetFrameNumber`, `DurationMs`, `TotalFrames`), `Aspect` (AspectFitFill), alignment, `Success`/`Error`, default cache `None` (C# ImageDoubleBuffered). Not ported: `SkiaGif.Display` inner SkiaImage (frames are drawn directly), custom-control ctor `SkiaGif(display)`, `SkiaLottie` Skottie property/slot editing, sound, `ReloadSource` sync file paths, `Dispose` wiring on unmount (no engine hook from the reconciler yet). |
18
- | `SkiaBackdrop` | ported | `Blur` (5), `Brightness` (gamma), `UseContext`, `BackgroundColor` tint, `HasEffects`, `GetImage`; children drawn first and blurred with the snapshot (C# order); `UseContext` snapshots the surface the pixels end up on (on-screen, or the nearest Image cache surface through its `Origin`), also while an Operations picture is being recorded, since caches record inline; after each paint the ancestors' caches are staled for the next frame (no frame requested) so content that changes beneath without invalidating this branch (an image loading later) is picked up by the next frame. Used by the shell popups/modals (`ShellDefaults.PopupsBackgroundBlur`). Not ported: `CacheSource`, the ghost mode. |
18
+ | `SkiaBackdrop` | ported | `Blur` (5), `Brightness` (gamma), `UseContext`, `BackgroundColor` tint, `HasEffects`, `GetImage`; copies the area it covers through every ancestor transform, drawn back 1:1 (C# 6fd3bae3); inside a picture-cached ancestor it marks it `HostsBackdrop` and that ancestor draws live (C# bcce0739); inside an Image-cached ancestor it copies that cache's surface (no warning: C# reverted 9d6ae911, a valid use when what shows through is inside that cache); children drawn first and blurred with the snapshot (C# order); `UseContext` snapshots the surface the pixels end up on (on-screen, or the nearest Image cache surface through its `Origin`), also while an Operations picture is being recorded, since caches record inline; after each paint the ancestors' caches are staled for the next frame (no frame requested) so content that changes beneath without invalidating this branch (an image loading later) is picked up by the next frame. Used by the shell popups/modals (`ShellDefaults.PopupsBackgroundBlur`). Not ported: `CacheSource`, the ghost mode. |
19
19
  | Disposal (`Dispose` / `OnDisposing`) | ported | The React renderer calls `Dispose()` when an element leaves the tree (`detachDeletedInstance`): caches, gradient shaders, running `*ToAsync` animations, overlay effects, the accessibility node, then the children (static and templated pool); `SkiaScroll`/`SnappingLayout` stop their animators, `SkiaSvg` drops its raster, `SkiaLottie`/`SkiaGif` free the Skottie animation / frames; `Canvas.Dispose` disposes the content. Not ported: `IsDisposing` staged disposal, `Disposing` event, `OnWillDisposeWithChildren`. |
20
20
  | Hover: `ReceivesHover` (opt-in; on by default in SkiaButton, SkiaSlider, the toggles, SkiaRadioButton, SkiaCarousel, SkiaDrawer), `IsHovered`, `HoverChanged` (turns `ReceivesHover` on, as C# `OnHovered`) | ported | drawnui-cross 6m: every opted-in control under the mouse is hovered; nothing changes while a scroll, carousel or drawer moves its content, one check at the last pointer position when it stops and after an add / remove, a rebind, a hidden hovered control or an instant scroll jump; pointerleave clears at once; touch never hovers (`Canvas.Hover`, a `HoverManager`). Not ported: C# `SetHover`, `CheckHovered` / `OnHover` / `IsPointerOver` (no Pointer gestures are routed), SkiaSpinner / wheel picker (no such controls). |
21
21
  | `KeyboardManager` | ported | Window-level keydown / keyup capture listeners like `drawnui-keyboard.js`: `KeyDown` / `KeyUp` with the DOM `event.code` ("KeyA", "ArrowLeft"; C# maps the same codes to `InputKey`), `KeyChar` for printable keys without Ctrl / Alt / Meta, `IsShiftPressed` / `IsControlPressed` / `IsAltPressed`, `AttachToKeyboard` (idempotent, pointerdown pulls window focus inside iframes), `BlurExternalTextInput`, `IsOwnedByElement` / `IsInputMethodKey` (React additions: a handler acting on keys skips keys a focused DOM element or the input method owns, C# 845b26e9); `Subscribe` / `Unsubscribe` instead of C# events. Not ported: `InputKey` enum (codes are strings). |
@@ -38,7 +38,7 @@ DrawnUi name and semantics; nothing here is a redesign, only an omission.
38
38
  | Touch feedback: `AnimationTapped="Ripple"` + `TouchEffectColor` + `AnimationTappedSpeed` on any control, `SkiaButton.ApplyEffect="Ripple"` (on Down), `PlayRippleAnimation`, `RippleAnimator`, `ClipEffects`/`CreateClip` | ported | Same numbers as C#: 500ms CubicIn, radius 300pt, opacity 0.20 fading over 1.15x progress. |
39
39
  | `Shimmer` touch animation, `ShimmerAnimator`, `ClippedEffectsWith`, `TransformView`, `DelayCallbackMs`, `removePrevious`, `Pause`/`Resume`, `UseInterpolator`, spring/deceleration timing, `AnimateExtensions` (`FadeToAsync`, `TranslateToAsync`, ...) | skipped | |
40
40
  | `VisualEffects` + `SkiaEffect` / `IPostRendererEffect` / `ISkiaGestureProcessor` | partial | `VisualEffects` array on every control (assign a new array; effects attach / detach, dispose with the control, `DisableEffects`, `GetEffectMargin` aggregated into the effects margin); post renderers run after the direct paint (C# `DrawDirectInternal`) or instead of the Image cache blit (C# `DrawRenderObject`, the effect samples `CachedImage`); effects with `ProcessGestures` see the parent's gestures first (C# `EffectsGestureProcessors`). Not ported: `IRenderEffect` chain (`ChainEffectResult`, `Draw(ctx, drawControl)`), `IColorEffect` / `IImageEffect` on VisualEffects (`BlurEffect`, `DropShadowEffect`, `OuterGlowEffect`, `ColorPresetEffect`, `AdjustRGBEffect`, `ChainAdjust*`, `Chain*`), `IStateEffect.UpdateState`, `Tag` lookup. |
41
- | `SkiaShaderEffect` / `ShaderDoubleTexturesEffect` / `ShaderTransitionEffect` / `AnimatedShaderEffect` | ported | CanvasKit `RuntimeEffect` (SkSL): `ShaderSource` (fetched once per url), `ShaderCode`, `ShaderTemplate` (`//script-goes-here`), compiled once per source text, Shadertoy uniforms `iResolution` / `iImageResolution` / `iTime` / `iOffset` / `iMouse` (`TimeSeconds`, `MouseCurrent`, `MouseInitial`), `Uniforms` + `SetUniform` (arrays sized by the next uniform slot, undeclared names skipped like C#), `UseBackground` Always / Once (`AquiredBackground`, `ReleaseFrozenSnapshot`) / Never, `AutoCreateInputTexture`, `UseContext`, `BlendMode`, `FilterMode` / `MipmapMode` / `TileMode`, `OnCompilationError` (logged when unset), `LoadedCode`, `IsCompiled`; texture children by declaration order (`uniform shader` names parsed, missing ones sample transparent); double textures: `ControlFrom` / `ControlTo` (their Image caches), `PrimarySource` / `SecondarySource` (resized to the parent box); transition: `Progress`, `ratio`, embedded gl-transitions `DefaultTemplate`; animated: `Play` / `Stop` / `Completed`, `DurationMs`, `Center` -> `iCenter`. `iOrigin` is not written (C# neither). Not ported: `SkiaShader` engine class as a standalone (`DrawRect` / `DrawImage` helpers), `CompareTo` / equality. |
41
+ | `SkiaShaderEffect` / `ShaderDoubleTexturesEffect` / `ShaderTransitionEffect` / `AnimatedShaderEffect` | ported | `ShouldInvalidateParentCache` (default false) + `UpdateUniforms()` (C# 0c6d9ef1: uniform changes and animation ticks keep the parent's cache unless asked to invalidate it). On a cached control the shader replaces it and reads its content (a picture cache hands effects its picture drawn into an image once per cache, C# caa1c8c1 `GetPictureImage`); on an uncached one it draws over the control and reads the canvas. CanvasKit `RuntimeEffect` (SkSL): `ShaderSource` (fetched once per url), `ShaderCode`, `ShaderTemplate` (`//script-goes-here`), compiled once per source text, Shadertoy uniforms `iResolution` / `iImageResolution` / `iTime` / `iOffset` / `iMouse` (`TimeSeconds`, `MouseCurrent`, `MouseInitial`), `Uniforms` + `SetUniform` (arrays sized by the next uniform slot, undeclared names skipped like C#), `UseBackground` Always / Once (`AquiredBackground`, `ReleaseFrozenSnapshot`) / Never, `AutoCreateInputTexture`, `UseContext`, `BlendMode`, `FilterMode` / `MipmapMode` / `TileMode`, `OnCompilationError` (logged when unset), `LoadedCode`, `IsCompiled`; texture children by declaration order (`uniform shader` names parsed, missing ones sample transparent); double textures: `ControlFrom` / `ControlTo` (their Image caches), `PrimarySource` / `SecondarySource` (resized to the parent box); transition: `Progress`, `ratio`, embedded gl-transitions `DefaultTemplate`; animated: `Play` / `Stop` / `Completed`, `DurationMs`, `Center` -> `iCenter`. `iOrigin` is not written (C# neither). Not ported: `SkiaShader` engine class as a standalone (`DrawRect` / `DrawImage` helpers), `CompareTo` / equality. |
42
42
  | Multithreading / offscreen rendering | skipped | Browser main thread only. |
43
43
  | Hot reload hook | skipped | Vite HMR reloads the page. |
44
44
  | Second measure pass / fill-in-auto re-measure rules | skipped | Column/Row give children an infinite main axis; cross axis = available. |
@@ -4,8 +4,10 @@ import { SkiaLayout } from "./SkiaLayout";
4
4
  /**
5
5
  * Mirrors DrawnUi SkiaBackdrop: draws what is already painted beneath its box through `Blur` (points, default 5)
6
6
  * and `Brightness` (gamma, 1 = unchanged), tinted by `BackgroundColor`; children draw first and are blurred too.
7
- * The snapshot comes from the surface being drawn (`UseContext`) or the on-screen surface. Not cached (re-blurs
8
- * on every frame it is drawn in; inside a cached parent it is re-recorded with that parent).
7
+ * The snapshot comes from the surface being drawn (`UseContext`, the area it covers there through every ancestor
8
+ * transform) or the on-screen surface. Not cached (re-blurs on every frame it is drawn in); picture-cached ancestors
9
+ * draw live once it is inside them (HostsBackdrop); inside an Image-cached ancestor it copies that cache's surface
10
+ * (right when what shows through the glass is inside that cache, wrong when it should show content outside it).
9
11
  */
10
12
  export declare class SkiaBackdrop extends SkiaLayout {
11
13
  /** Samples what is drawn below: a composite above redraws it whole when something inside changes (drawnui-cross 6m). */
@@ -23,5 +25,7 @@ export declare class SkiaBackdrop extends SkiaLayout {
23
25
  protected PaintBackground(_ctx: DrawingContext): void;
24
26
  protected PaintsBackgroundWithoutColor(): boolean;
25
27
  protected Paint(ctx: DrawingContext): void;
28
+ /** C# StopPictureCachesAbove: the picture-cached ancestors draw live from the next frame, which this asks for. */
29
+ protected StopPictureCachesAbove(): void;
26
30
  protected OnDisposing(): void;
27
31
  }
@@ -4,8 +4,10 @@ import { SkiaLayout } from "./SkiaLayout";
4
4
  /**
5
5
  * Mirrors DrawnUi SkiaBackdrop: draws what is already painted beneath its box through `Blur` (points, default 5)
6
6
  * and `Brightness` (gamma, 1 = unchanged), tinted by `BackgroundColor`; children draw first and are blurred too.
7
- * The snapshot comes from the surface being drawn (`UseContext`) or the on-screen surface. Not cached (re-blurs
8
- * on every frame it is drawn in; inside a cached parent it is re-recorded with that parent).
7
+ * The snapshot comes from the surface being drawn (`UseContext`, the area it covers there through every ancestor
8
+ * transform) or the on-screen surface. Not cached (re-blurs on every frame it is drawn in); picture-cached ancestors
9
+ * draw live once it is inside them (HostsBackdrop); inside an Image-cached ancestor it copies that cache's surface
10
+ * (right when what shows through the glass is inside that cache, wrong when it should show content outside it).
9
11
  */
10
12
  export class SkiaBackdrop extends SkiaLayout {
11
13
  /** Samples what is drawn below: a composite above redraws it whole when something inside changes (drawnui-cross 6m). */
@@ -40,16 +42,36 @@ export class SkiaBackdrop extends SkiaLayout {
40
42
  }
41
43
  if (!this.HasEffects)
42
44
  return;
45
+ // C# StopPictureCachesAbove: a screen copy recorded into an ancestor's picture would be replayed frozen; this frame
46
+ // draws no copy, the picture-cached ancestors draw live from the next frame on (HostsBackdrop)
47
+ if (ctx.Context.Recording) {
48
+ this.StopPictureCachesAbove();
49
+ return;
50
+ }
43
51
  // C# UseContext: snapshot the surface the pixels end up on — the on-screen surface or the nearest Image cache
44
- // surface (Origin = its top-left in canvas pixels). Content beneath is already there because caches are recorded
45
- // inline while the parent paints, so this also works while an Operations picture is being recorded.
52
+ // surface. Copied where the backdrop really is on that surface: its rect through the canvas matrix (ancestor
53
+ // scale, move, rotation, 3D, the cache origin), rounded out, clamped to the surface, and drawn back 1:1 with the
54
+ // matrix undone, clipped to the backdrop's own (maybe turned) rect: the blur stays the same on screen while an
55
+ // ancestor scales (C# 6fd3bae3). The global surface (UseContext false) is copied at the layout rect.
46
56
  const surface = this.UseContext ? ctx.Context.Surface : this.Superview?.Surface;
47
57
  if (!surface)
48
58
  return;
49
59
  surface.flush();
50
- const origin = this.UseContext ? ctx.Context.Origin : undefined;
51
- const ox = origin?.X ?? 0, oy = origin?.Y ?? 0;
52
- const l = Math.floor(d.Left - ox), t = Math.floor(d.Top - oy), r = Math.ceil(d.Right - ox), b = Math.ceil(d.Bottom - oy);
60
+ let l = Math.floor(d.Left), t = Math.floor(d.Top), r = Math.ceil(d.Right), b = Math.ceil(d.Bottom);
61
+ let device = null;
62
+ if (this.UseContext) {
63
+ const m = canvas.getTotalMatrix();
64
+ const p = CK.Matrix.mapPoints(m, [d.Left, d.Top, d.Right, d.Top, d.Right, d.Bottom, d.Left, d.Bottom]);
65
+ l = Math.max(0, Math.floor(Math.min(p[0], p[2], p[4], p[6])));
66
+ t = Math.max(0, Math.floor(Math.min(p[1], p[3], p[5], p[7])));
67
+ r = Math.min(surface.width(), Math.ceil(Math.max(p[0], p[2], p[4], p[6])));
68
+ b = Math.min(surface.height(), Math.ceil(Math.max(p[1], p[3], p[5], p[7])));
69
+ if (r <= l || b <= t)
70
+ return;
71
+ device = CK.Matrix.invert(m);
72
+ if (!device)
73
+ return;
74
+ }
53
75
  // whole-surface snapshot, sub-rect on draw: a bounded snapshot of a GPU surface is not origin-safe in CanvasKit
54
76
  const image = surface.makeImageSnapshot();
55
77
  if (!image)
@@ -63,7 +85,13 @@ export class SkiaBackdrop extends SkiaLayout {
63
85
  paint.setColorFilter(gamma);
64
86
  const saved = canvas.save();
65
87
  canvas.clipRect(CK.LTRBRect(d.Left, d.Top, d.Right, d.Bottom), CK.ClipOp.Intersect, true);
66
- canvas.drawImageRectOptions(image, CK.LTRBRect(l, t, r, b), CK.LTRBRect(d.Left, d.Top, d.Right, d.Bottom), CK.FilterMode.Linear, CK.MipmapMode.None, paint);
88
+ if (device) {
89
+ canvas.concat(device);
90
+ canvas.drawImageRectOptions(image, CK.LTRBRect(l, t, r, b), CK.LTRBRect(l, t, r, b), CK.FilterMode.Nearest, CK.MipmapMode.None, paint);
91
+ }
92
+ else {
93
+ canvas.drawImageRectOptions(image, CK.LTRBRect(l, t, r, b), CK.LTRBRect(d.Left, d.Top, d.Right, d.Bottom), CK.FilterMode.Linear, CK.MipmapMode.None, paint);
94
+ }
67
95
  canvas.restoreToCount(saved);
68
96
  blur?.delete();
69
97
  gamma?.delete();
@@ -78,5 +106,15 @@ export class SkiaBackdrop extends SkiaLayout {
78
106
  p = p.Parent;
79
107
  } });
80
108
  }
109
+ /** C# StopPictureCachesAbove: the picture-cached ancestors draw live from the next frame, which this asks for. */
110
+ StopPictureCachesAbove() {
111
+ for (let p = this.Parent; p; p = p.Parent) {
112
+ if (p.UsingCacheType === "Operations") {
113
+ p.HostsBackdrop = true;
114
+ p.InvalidateCache();
115
+ }
116
+ }
117
+ queueMicrotask(() => this.Update());
118
+ }
81
119
  OnDisposing() { this.snapshot?.delete(); this.snapshot = undefined; }
82
120
  }
@@ -52,6 +52,15 @@ export declare class CachedObject {
52
52
  private static BlitPaint;
53
53
  /** Replays the cache with its top-left moved to (left, top); `smooth` = an image drawn distorted (linear + mipmaps). */
54
54
  Draw(canvas: SkCanvas, left: number, top: number, smooth?: boolean): void;
55
+ /**
56
+ * C# GetPictureImage: the picture of a picture cache (Operations, OperationsFull) drawn into an image, made on the
57
+ * first call (on `main`'s GPU context when given) and kept as long as this cache lives. Shader effects read it as
58
+ * their input, so on such a control they work on its own content, as on an Image cache.
59
+ */
60
+ GetPictureImage(main?: Surface): Image | undefined;
61
+ private pictureImage?;
62
+ /** The picture drawn into an image, once GetPictureImage made it. */
63
+ get PictureImage(): Image | undefined;
55
64
  Dispose(): void;
56
65
  }
57
66
  /**
@@ -148,6 +157,11 @@ export declare class SkiaControl {
148
157
  /** What to cache for this subtree; see SkiaCacheType. Layouts default to None, SkiaLabel/SkiaSvg to Operations. */
149
158
  UseCache: SkiaCacheType;
150
159
  /** The cache type actually applied (None while caching is disabled globally). */
160
+ /**
161
+ * C# HostsBackdrop: a SkiaBackdrop inside asked this picture-cached control to draw live (a picture would replay
162
+ * the backdrop's screen copy frozen). Stays set after the backdrop is removed: the control just keeps drawing live.
163
+ */
164
+ HostsBackdrop: boolean;
151
165
  get UsingCacheType(): SkiaCacheType;
152
166
  /** C# IsCacheComposite: the offscreen surface is kept and only the changed children are re-recorded. */
153
167
  get IsCacheComposite(): boolean;
@@ -53,9 +53,36 @@ export class CachedObject {
53
53
  canvas.restoreToCount(saved);
54
54
  }
55
55
  }
56
+ /**
57
+ * C# GetPictureImage: the picture of a picture cache (Operations, OperationsFull) drawn into an image, made on the
58
+ * first call (on `main`'s GPU context when given) and kept as long as this cache lives. Shader effects read it as
59
+ * their input, so on such a control they work on its own content, as on an Image cache.
60
+ */
61
+ GetPictureImage(main) {
62
+ if (this.pictureImage || !this.Picture)
63
+ return this.pictureImage;
64
+ const w = Math.ceil(this.Bounds.Width), h = Math.ceil(this.Bounds.Height);
65
+ if (w <= 0 || h <= 0)
66
+ return undefined;
67
+ const surface = main?.makeSurface({ ...main.imageInfo(), width: w, height: h }) ?? Super.CK.MakeSurface(w, h);
68
+ if (!surface)
69
+ return undefined;
70
+ const canvas = surface.getCanvas();
71
+ canvas.clear(Super.CK.TRANSPARENT);
72
+ canvas.translate(-this.Bounds.Left, -this.Bounds.Top);
73
+ canvas.drawPicture(this.Picture);
74
+ this.pictureImage = surface.makeImageSnapshot();
75
+ surface.delete();
76
+ return this.pictureImage;
77
+ }
78
+ pictureImage;
79
+ /** The picture drawn into an image, once GetPictureImage made it. */
80
+ get PictureImage() { return this.pictureImage; }
56
81
  Dispose() {
57
82
  this.Picture?.delete();
58
83
  this.Image?.delete();
84
+ this.pictureImage?.delete();
85
+ this.pictureImage = undefined;
59
86
  }
60
87
  }
61
88
  /**
@@ -168,13 +195,18 @@ export class SkiaControl {
168
195
  /** What to cache for this subtree; see SkiaCacheType. Layouts default to None, SkiaLabel/SkiaSvg to Operations. */
169
196
  UseCache = "None";
170
197
  /** The cache type actually applied (None while caching is disabled globally). */
198
+ /**
199
+ * C# HostsBackdrop: a SkiaBackdrop inside asked this picture-cached control to draw live (a picture would replay
200
+ * the backdrop's screen copy frozen). Stays set after the backdrop is removed: the control just keeps drawing live.
201
+ */
202
+ HostsBackdrop = false;
171
203
  get UsingCacheType() {
172
204
  if (!Super.CacheEnabled)
173
205
  return "None";
174
206
  switch (this.UseCache) {
175
207
  case "None": return "None";
176
208
  case "Operations":
177
- case "OperationsFull": return "Operations";
209
+ case "OperationsFull": return this.HostsBackdrop ? "None" : "Operations";
178
210
  case "ImageDoubleBuffered": return "ImageDoubleBuffered";
179
211
  case "ImageComposite":
180
212
  case "ImageCompositeGPU": return "ImageComposite";
@@ -358,10 +390,19 @@ export class SkiaControl {
358
390
  */
359
391
  get CachedImage() {
360
392
  const c = this.RenderObject;
361
- if (!c?.Image)
393
+ if (!c)
394
+ return undefined;
395
+ if (c.Image) {
396
+ const r = this.AlignedCacheRect(c.Scale);
397
+ return { Image: c.Image, Bounds: SKRect.Create(r.Left, r.Top, c.Bounds.Width, c.Bounds.Height) };
398
+ }
399
+ // a picture cache: its picture drawn into an image (made in RenderContent), so effects work on this control's
400
+ // content instead of a snapshot of what is behind it (C# caa1c8c1)
401
+ const image = c.PictureImage;
402
+ if (!image)
362
403
  return undefined;
363
- const r = this.AlignedCacheRect(c.Scale);
364
- return { Image: c.Image, Bounds: SKRect.Create(r.Left, r.Top, c.Bounds.Width, c.Bounds.Height) };
404
+ const r = this.ExpandedCacheRect(c.Scale);
405
+ return { Image: image, Bounds: SKRect.Create(r.Left, r.Top, image.width(), image.height()) };
365
406
  }
366
407
  /** Clip overlay effects to the control's shape (CreateClip). */
367
408
  ClipEffects = true;
@@ -883,10 +924,14 @@ export class SkiaControl {
883
924
  if (this.CacheStale(ctx.Scale) || (o?.Picture && o.HasImageBlits && o.PlaybackDistorted !== this.WillDrawDistorted(ctx)))
884
925
  this.CreateRenderingObject({ ...ctx, Destination: this.DrawingRect }, cacheType);
885
926
  if (this.RenderObject) {
886
- // C# DrawRenderObject: with post renderers an Image cache is not blitted, the effects sample it (CachedImage)
887
- // and paint the result; a picture cache has no texture, so it is replayed first and snapshotted by the effect
888
- if (post.length === 0 || !this.RenderObject.Image)
889
- this.DrawCache(this.RenderObject, ctx, r.Left + dx, r.Top + dy);
927
+ // C# DrawRenderObject: with post renderers a cache is not blitted, the effects sample it (CachedImage) and paint
928
+ // the result, so the shader replaces the control; a picture cache hands them its picture drawn into an image
929
+ // (made once per cache, C# GetPictureImage); drawn as before only when that image could not be made
930
+ const cache = this.RenderObject;
931
+ if (post.length > 0 && cache.Picture)
932
+ cache.GetPictureImage(ctx.Context.Surface);
933
+ if (post.length === 0 || !(cache.Image ?? cache.PictureImage))
934
+ this.DrawCache(cache, ctx, r.Left + dx, r.Top + dy);
890
935
  for (const e of post)
891
936
  e.Render(own);
892
937
  }
@@ -119,6 +119,16 @@ 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;
@@ -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
  }
@@ -565,7 +581,7 @@ export class AnimatedShaderEffect extends SkiaShaderEffect {
565
581
  return;
566
582
  if (!this.Animator) {
567
583
  const a = new SkiaValueAnimator(parent);
568
- a.OnUpdated = (v) => { this.Progress = v; this.Update(); };
584
+ a.OnUpdated = (v) => { this.Progress = v; this.UpdateUniforms(); };
569
585
  a.Finished = () => this.Completed?.(this);
570
586
  this.Animator = a;
571
587
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drawnui-react",
3
- "version": "0.1.0-preview.20",
3
+ "version": "0.1.0-preview.21",
4
4
  "description": "DrawnUi for React: the DrawnUi (.NET) drawn-UI engine in TypeScript on CanvasKit (Skia WASM). Same controls, same PascalCase API as DrawnUi.Net.",
5
5
  "license": "MIT",
6
6
  "author": "Nick Kovalsky (AppoMobi)",
@@ -75,6 +75,10 @@
75
75
  "check:culling": "node dev/check.mjs dev/check-culling.ts",
76
76
  "check:responds": "node dev/check.mjs dev/check-responds.ts",
77
77
  "check:3d": "node dev/check.mjs dev/check-3d.ts",
78
+ "check:gesture-owner": "node dev/check.mjs dev/check-gesture-owner.ts",
79
+ "check:shader-cache": "node dev/check.mjs dev/check-shader-cache.ts",
80
+ "check:shader-content": "node dev/check.mjs dev/check-shader-content.ts",
81
+ "check:backdrop": "node dev/check.mjs dev/check-backdrop.ts",
78
82
  "site:extras": "node dev/site-extras.mjs dist/demo",
79
83
  "build:lib": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
80
84
  "prepublishOnly": "npm run build:lib",