drawnui-react 0.1.0-preview.18 → 0.1.0-preview.19
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 +1 -1
- package/dist/core/SkiaControl.d.ts +42 -3
- package/dist/core/SkiaControl.js +86 -13
- package/dist/react/reconciler.js +1 -1
- package/package.json +2 -1
package/SKIPPED.md
CHANGED
|
@@ -12,7 +12,7 @@ DrawnUi name and semantics; nothing here is a redesign, only an omission.
|
|
|
12
12
|
| Gestures core: `Canvas.Gestures` (Disabled/Enabled/Lock), `ProcessGestures(args, apply)`, `ConsumeGestures`, `Tapped`/`ChildTapped`, `InputTransparent`, `BlockGesturesBelow`, `LockChildrenGestures`, `HitBoxAuto`/`HitIsInside`/`IsGestureForChild`, `SkiaGesturesParameters`/`GestureEventProcessingInfo`/`SkiaGesturesInfo`/`ControlTappedEventArgs` | ported | Pointer events -> per-pointer `OnTouchAction` state machine (port of DrawnUi.Blazor) -> Down/Panning/Tapped/Up, queued and processed at frame start. Tap slop = 16pt like AppoMobi TouchEffect. The control that consumes a gesture keeps receiving it until it lets go (DrawnUi `Canvas.HadInput`, one pointer here instead of C#'s set): without it a drag handle, a slider thumb or a held button stops getting `Panning` the moment the finger leaves its box, and a control that stops consuming hands routing straight back to the tree (a button whose press turns into a scroll pan). Every mouse button is delivered (`Event.Pointer`: `Button`, `ButtonNumber`, `DeviceType`, `PressedButtons`, the AppoMobi.Gestures PointerData): a right click is Down / Up / Tapped with `Button` "Right" plus a `ContextMenu`; handlers that must be primary-only check `e.Parameters.Event.Pointer?.Button`. |
|
|
13
13
|
| Gestures: `Pointer` (routed as a gesture; hover itself is ported, see `ReceivesHover`; `LongPressing` is produced since 2026-10-10: `Canvas.LongPressTimeMs` 1500 held still, no Tapped after it), multi-touch pinch, `SoftLock`, `AddGestures` attached props, `OnGestures` delegate | skipped | The enum members exist for parity and are never produced; there is no pinch member at all. Ported instead of skipped: velocity (`Gestures.ts` fills `Distance.Velocity` per move, the scroll / snapping physics run on it) and transform-aware mapping — a child receives the point through the inverse `RenderTransformMatrix` (`TransformPointToLocalSpace`), so rotated / scaled / translated controls hit-test correctly. `ChildOffset` stays zero because that inverse matrix replaces the C# cache-offset math (`TranslateInputCoords`), not because transforms are missing. `Wheel` IS produced (`Canvas.onWheel`). |
|
|
14
14
|
| C# multi-subscriber `event`s | changed shape | One callback per event prop (`Tapped={fn}`), same names. `Command*` (ICommand) variants not ported. |
|
|
15
|
-
| `Opacity`, `TranslationX/Y`, `Rotation`, `ScaleX/Y` (+`Scale`), `SkewX/Y`, `AnchorX/Y`, `HasTransform`, `RenderTransformMatrix`, `TransformPointToLocalSpace` | ported | Same matrix order as C# `ApplyTransforms` (pivot → rotation → scale/skew → translation), 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: `
|
|
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
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. |
|
|
@@ -21,6 +21,10 @@ export interface DrawingContext {
|
|
|
21
21
|
Y: number;
|
|
22
22
|
};
|
|
23
23
|
Recording?: boolean;
|
|
24
|
+
/** The picture being recorded is drawn scaled, turned, skewed or projected: images blitted into it sample smoothly (C#). */
|
|
25
|
+
PlaybackDistorted?: boolean;
|
|
26
|
+
/** An image cache (or a picture holding one) was blitted here (C#): the picture is recorded again when its playback distortion changes. */
|
|
27
|
+
HasImageBlits?: boolean;
|
|
24
28
|
};
|
|
25
29
|
Destination: SKRect;
|
|
26
30
|
Scale: number;
|
|
@@ -38,8 +42,12 @@ export declare class CachedObject {
|
|
|
38
42
|
Bounds: SKRect, Scale: number, Picture?: SkPicture | undefined, Image?: Image | undefined);
|
|
39
43
|
/** Canvas.GpuEpoch it was made in: after a WebGL context restore it is remade (a picture may replay old GPU images). */
|
|
40
44
|
Epoch: number;
|
|
41
|
-
/**
|
|
42
|
-
|
|
45
|
+
/** A picture recorded to be drawn distorted: the images in it sample smoothly (C# CachedObject.PlaybackDistorted). */
|
|
46
|
+
PlaybackDistorted: boolean;
|
|
47
|
+
/** A picture holding image blits, whose sampling was fixed when it was recorded (C# CachedObject.HasImageBlits). */
|
|
48
|
+
HasImageBlits: boolean;
|
|
49
|
+
/** Replays the cache with its top-left moved to (left, top); `smooth` = an image drawn distorted (linear + mipmaps). */
|
|
50
|
+
Draw(canvas: SkCanvas, left: number, top: number, smooth?: boolean): void;
|
|
43
51
|
Dispose(): void;
|
|
44
52
|
}
|
|
45
53
|
/**
|
|
@@ -86,6 +94,14 @@ export declare class SkiaControl {
|
|
|
86
94
|
/** Degrees. */
|
|
87
95
|
SkewX: number;
|
|
88
96
|
SkewY: number;
|
|
97
|
+
/** Degrees, a 3D turn around the horizontal axis through the pivot, seen with perspective (C# RotationX). */
|
|
98
|
+
RotationX: number;
|
|
99
|
+
/** Degrees, a 3D turn around the vertical axis through the pivot (C# RotationY). */
|
|
100
|
+
RotationY: number;
|
|
101
|
+
/** Degrees, a 3D turn around the axis facing the viewer, after RotationX and RotationY (C# RotationZ). */
|
|
102
|
+
RotationZ: number;
|
|
103
|
+
/** Depth: toward the viewer (+, bigger) or away (−, smaller), turned or flat; draws at 576 / (576 − TranslationZ) (C# TranslationZ). */
|
|
104
|
+
TranslationZ: number;
|
|
89
105
|
/** Pivot for Rotation/Scale/Skew as a fraction of the box (MAUI default 0.5). */
|
|
90
106
|
AnchorX: number;
|
|
91
107
|
AnchorY: number;
|
|
@@ -109,6 +125,7 @@ export declare class SkiaControl {
|
|
|
109
125
|
get Scale(): number;
|
|
110
126
|
set Scale(v: number);
|
|
111
127
|
get HasTransform(): boolean;
|
|
128
|
+
private get Has3d();
|
|
112
129
|
/** Matrix applied at the last render (canvas space), undefined when none; gestures map through its inverse. */
|
|
113
130
|
RenderTransformMatrix?: number[];
|
|
114
131
|
/** Generic numeric parameters (DrawnUi Value1/Value2): SkiaShape Arc uses start angle / sweep angle in degrees. */
|
|
@@ -293,6 +310,12 @@ export declare class SkiaControl {
|
|
|
293
310
|
protected MeasureAbsolute(_widthConstraint: number, _heightConstraint: number, scale: number): ScaledSize;
|
|
294
311
|
/** Places MeasuredSize inside destination per Margin + Horizontal/VerticalOptions -> DrawingRect. */
|
|
295
312
|
Arrange(destination: SKRect, _widthRequest: number, _heightRequest: number, scale: number): void;
|
|
313
|
+
/**
|
|
314
|
+
* A cached ancestor replays its picture where a re-arrange moved it (a scroll) without rendering this control again,
|
|
315
|
+
* so the matrix made at its last Render moves with its rect: taps, hover and the accessibility rect stay on what is
|
|
316
|
+
* drawn (C# offsets the cached render tree instead). A control that renders again gets a fresh matrix there.
|
|
317
|
+
*/
|
|
318
|
+
private FollowMatrix;
|
|
296
319
|
/** C# SuperViewChanged: starts the animators queued before this control was attached. */
|
|
297
320
|
private StartPendingAnimators;
|
|
298
321
|
private static nextAccessibilityId;
|
|
@@ -408,8 +431,22 @@ export declare class SkiaControl {
|
|
|
408
431
|
/** DrawingRect grown by the effects margin, in pixels — the rect a cache is recorded for. */
|
|
409
432
|
protected ExpandedCacheRect(scale: number): SKRect;
|
|
410
433
|
Render(ctx: DrawingContext): void;
|
|
411
|
-
/** Port of DrawnUi ApplyTransforms: T(-pivot) · rotation · scale/skew · T(pivot) · translation, in canvas pixels. */
|
|
434
|
+
/** Port of DrawnUi ApplyTransforms: T(-pivot) · rotation · scale/skew · 3D · T(pivot) · translation, in canvas pixels. */
|
|
412
435
|
protected CreateRenderTransformMatrix(destination: SKRect, scale: number): number[];
|
|
436
|
+
/** C# camera distance of the 3D transforms (Sk3dView.CameraDistance): 8 inches at 72 dpi, in canvas pixels. */
|
|
437
|
+
static readonly CameraDistance = 576;
|
|
438
|
+
/**
|
|
439
|
+
* C# Create3dMatrix (Sk3dView): RotationX, then RotationY, then RotationZ (negated) around the pivot, projected from
|
|
440
|
+
* the camera; TranslationZ is depth, a flat control at depth z draws at camera / (camera − z).
|
|
441
|
+
*/
|
|
442
|
+
static Create3dMatrix(rotationX: number, rotationY: number, rotationZ: number, translationZ: number): number[];
|
|
443
|
+
/** C# SkiaSamplingOptions.IsMoveOnly: the matrix only moves (or mirrors), so a bitmap keeps its pixels 1:1. */
|
|
444
|
+
static IsMoveOnly(m: ArrayLike<number>): boolean;
|
|
445
|
+
/**
|
|
446
|
+
* C# WillDrawDistorted: what this control draws here ends up scaled, turned, skewed or projected, by its own transforms
|
|
447
|
+
* or a live ancestor's (both in the canvas matrix) or by the picture it is recorded into. Images then sample smoothly.
|
|
448
|
+
*/
|
|
449
|
+
protected WillDrawDistorted(ctx: DrawingContext): boolean;
|
|
413
450
|
/** Maps a point given in the parent's space into this control's untransformed space (DrawnUi TransformPointToLocalSpace). */
|
|
414
451
|
TransformPointToLocalSpace(point: SKPoint): SKPoint;
|
|
415
452
|
/**
|
|
@@ -419,6 +456,8 @@ export declare class SkiaControl {
|
|
|
419
456
|
private AlignedCacheRect;
|
|
420
457
|
/** Cache blit or live paint, then post animators — the part a transform/opacity layer wraps. */
|
|
421
458
|
private RenderContent;
|
|
459
|
+
/** Blits a cache: an image samples smoothly when drawn distorted; a picture being recorded learns it holds image blits. */
|
|
460
|
+
private DrawCache;
|
|
422
461
|
/** Background + Paint(): the part of the control that a cache captures. */
|
|
423
462
|
protected PaintContent(ctx: DrawingContext): void;
|
|
424
463
|
/** Subclasses whose PaintBackground has something to draw without a BackgroundColor (shape shadows). */
|
package/dist/core/SkiaControl.js
CHANGED
|
@@ -24,12 +24,19 @@ export class CachedObject {
|
|
|
24
24
|
}
|
|
25
25
|
/** Canvas.GpuEpoch it was made in: after a WebGL context restore it is remade (a picture may replay old GPU images). */
|
|
26
26
|
Epoch = 0;
|
|
27
|
-
/**
|
|
28
|
-
|
|
27
|
+
/** A picture recorded to be drawn distorted: the images in it sample smoothly (C# CachedObject.PlaybackDistorted). */
|
|
28
|
+
PlaybackDistorted = false;
|
|
29
|
+
/** A picture holding image blits, whose sampling was fixed when it was recorded (C# CachedObject.HasImageBlits). */
|
|
30
|
+
HasImageBlits = false;
|
|
31
|
+
/** Replays the cache with its top-left moved to (left, top); `smooth` = an image drawn distorted (linear + mipmaps). */
|
|
32
|
+
Draw(canvas, left, top, smooth = false) {
|
|
29
33
|
const CK = Super.CK;
|
|
30
34
|
if (this.Image) {
|
|
31
|
-
//
|
|
32
|
-
|
|
35
|
+
// nearest keeps a bitmap blitted 1:1 sharp; once scaled, turned or projected it needs smooth sampling (C# 1e4610f6)
|
|
36
|
+
if (smooth)
|
|
37
|
+
canvas.drawImageOptions(this.Image, left, top, CK.FilterMode.Linear, CK.MipmapMode.Linear, null);
|
|
38
|
+
else
|
|
39
|
+
canvas.drawImageOptions(this.Image, left, top, CK.FilterMode.Nearest, CK.MipmapMode.None, null);
|
|
33
40
|
}
|
|
34
41
|
else if (this.Picture) {
|
|
35
42
|
const saved = canvas.save();
|
|
@@ -89,6 +96,14 @@ export class SkiaControl {
|
|
|
89
96
|
/** Degrees. */
|
|
90
97
|
SkewX = 0;
|
|
91
98
|
SkewY = 0;
|
|
99
|
+
/** Degrees, a 3D turn around the horizontal axis through the pivot, seen with perspective (C# RotationX). */
|
|
100
|
+
RotationX = 0;
|
|
101
|
+
/** Degrees, a 3D turn around the vertical axis through the pivot (C# RotationY). */
|
|
102
|
+
RotationY = 0;
|
|
103
|
+
/** Degrees, a 3D turn around the axis facing the viewer, after RotationX and RotationY (C# RotationZ). */
|
|
104
|
+
RotationZ = 0;
|
|
105
|
+
/** Depth: toward the viewer (+, bigger) or away (−, smaller), turned or flat; draws at 576 / (576 − TranslationZ) (C# TranslationZ). */
|
|
106
|
+
TranslationZ = 0;
|
|
92
107
|
/** Pivot for Rotation/Scale/Skew as a fraction of the box (MAUI default 0.5). */
|
|
93
108
|
AnchorX = 0.5;
|
|
94
109
|
AnchorY = 0.5;
|
|
@@ -116,8 +131,9 @@ export class SkiaControl {
|
|
|
116
131
|
get Scale() { return this.ScaleX; }
|
|
117
132
|
set Scale(v) { this.ScaleX = v; this.ScaleY = v; }
|
|
118
133
|
get HasTransform() {
|
|
119
|
-
return this.TranslationX !== 0 || this.TranslationY !== 0 || this.Left !== 0 || this.Top !== 0 || this.Rotation !== 0 || this.ScaleX !== 1 || this.ScaleY !== 1 || this.SkewX !== 0 || this.SkewY !== 0;
|
|
134
|
+
return this.TranslationX !== 0 || this.TranslationY !== 0 || this.Left !== 0 || this.Top !== 0 || this.Rotation !== 0 || this.ScaleX !== 1 || this.ScaleY !== 1 || this.SkewX !== 0 || this.SkewY !== 0 || this.Has3d;
|
|
120
135
|
}
|
|
136
|
+
get Has3d() { return this.RotationX !== 0 || this.RotationY !== 0 || this.RotationZ !== 0 || this.TranslationZ !== 0; }
|
|
121
137
|
/** Matrix applied at the last render (canvas space), undefined when none; gestures map through its inverse. */
|
|
122
138
|
RenderTransformMatrix;
|
|
123
139
|
/** Generic numeric parameters (DrawnUi Value1/Value2): SkiaShape Arc uses start angle / sweep angle in degrees. */
|
|
@@ -470,7 +486,9 @@ export class SkiaControl {
|
|
|
470
486
|
const fullW = destination.Width - m.HorizontalThickness * scale, fullH = destination.Height - m.VerticalThickness * scale;
|
|
471
487
|
const x = availL + SkiaControl.Align(this.HorizontalOptions, fullW, w);
|
|
472
488
|
const y = availT + SkiaControl.Align(this.VerticalOptions, fullH, h);
|
|
489
|
+
const old = this.DrawingRect;
|
|
473
490
|
this.DrawingRect = SKRect.Create(x, y, w, h);
|
|
491
|
+
this.FollowMatrix(old);
|
|
474
492
|
this.OnLayoutChanged();
|
|
475
493
|
if (this.PendingUnattachedAnimators)
|
|
476
494
|
this.StartPendingAnimators();
|
|
@@ -478,6 +496,18 @@ export class SkiaControl {
|
|
|
478
496
|
if (!this.registeredWithAccessibility && this.IsAccessibilityElement)
|
|
479
497
|
this.NotifyAccessibility();
|
|
480
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* A cached ancestor replays its picture where a re-arrange moved it (a scroll) without rendering this control again,
|
|
501
|
+
* so the matrix made at its last Render moves with its rect: taps, hover and the accessibility rect stay on what is
|
|
502
|
+
* drawn (C# offsets the cached render tree instead). A control that renders again gets a fresh matrix there.
|
|
503
|
+
*/
|
|
504
|
+
FollowMatrix(old) {
|
|
505
|
+
const m = this.RenderTransformMatrix, r = this.DrawingRect;
|
|
506
|
+
if (!m || (old.Left === r.Left && old.Top === r.Top))
|
|
507
|
+
return;
|
|
508
|
+
const M = Super.CK.Matrix, dx = r.Left - old.Left, dy = r.Top - old.Top;
|
|
509
|
+
this.RenderTransformMatrix = M.multiply(M.translated(dx, dy), m, M.translated(-dx, -dy));
|
|
510
|
+
}
|
|
481
511
|
/** C# SuperViewChanged: starts the animators queued before this control was attached. */
|
|
482
512
|
StartPendingAnimators() {
|
|
483
513
|
if (!this.Superview)
|
|
@@ -711,7 +741,7 @@ export class SkiaControl {
|
|
|
711
741
|
const applyOpacity = this.Opacity < 1;
|
|
712
742
|
// C# Left/Top: a cached control is blitted at an offset, no matrix and no save/restore around the subtree
|
|
713
743
|
const offsetOnly = (this.Left !== 0 || this.Top !== 0) && this.UsingCacheType !== "None" && this.TranslationX === 0 && this.TranslationY === 0
|
|
714
|
-
&& this.Rotation === 0 && this.ScaleX === 1 && this.ScaleY === 1 && this.SkewX === 0 && this.SkewY === 0;
|
|
744
|
+
&& this.Rotation === 0 && this.ScaleX === 1 && this.ScaleY === 1 && this.SkewX === 0 && this.SkewY === 0 && !this.Has3d;
|
|
715
745
|
const needTransform = this.HasTransform && !offsetOnly;
|
|
716
746
|
let dx = 0, dy = 0;
|
|
717
747
|
if (needTransform) {
|
|
@@ -757,11 +787,12 @@ export class SkiaControl {
|
|
|
757
787
|
if (saved)
|
|
758
788
|
canvas.restore();
|
|
759
789
|
}
|
|
760
|
-
/** Port of DrawnUi ApplyTransforms: T(-pivot) · rotation · scale/skew · T(pivot) · translation, in canvas pixels. */
|
|
790
|
+
/** Port of DrawnUi ApplyTransforms: T(-pivot) · rotation · scale/skew · 3D · T(pivot) · translation, in canvas pixels. */
|
|
761
791
|
CreateRenderTransformMatrix(destination, scale) {
|
|
762
792
|
const M = Super.CK.Matrix;
|
|
763
793
|
const moveX = (this.TranslationX + this.Left) * scale, moveY = (this.TranslationY + this.Top) * scale;
|
|
764
|
-
|
|
794
|
+
const has3d = this.Has3d;
|
|
795
|
+
if (this.Rotation === 0 && this.ScaleX === 1 && this.ScaleY === 1 && this.SkewX === 0 && this.SkewY === 0 && !has3d)
|
|
765
796
|
return M.translated(moveX, moveY);
|
|
766
797
|
const px = destination.Left + destination.Width * this.AnchorX;
|
|
767
798
|
const py = destination.Top + destination.Height * this.AnchorY;
|
|
@@ -770,7 +801,36 @@ export class SkiaControl {
|
|
|
770
801
|
const scaleSkew = [this.ScaleX, kx, 0, ky, this.ScaleY, 0, 0, 0, 1];
|
|
771
802
|
const rotation = this.Rotation !== 0 ? M.rotated((Math.PI * this.Rotation) / 180) : M.identity();
|
|
772
803
|
// multiply(a, b, c) = a·b·c, the right-most applies first
|
|
773
|
-
|
|
804
|
+
if (!has3d)
|
|
805
|
+
return M.multiply(M.translated(px + moveX, py + moveY), scaleSkew, rotation, M.translated(-px, -py));
|
|
806
|
+
return M.multiply(M.translated(px + moveX, py + moveY), SkiaControl.Create3dMatrix(this.RotationX, this.RotationY, this.RotationZ, this.TranslationZ), scaleSkew, rotation, M.translated(-px, -py));
|
|
807
|
+
}
|
|
808
|
+
/** C# camera distance of the 3D transforms (Sk3dView.CameraDistance): 8 inches at 72 dpi, in canvas pixels. */
|
|
809
|
+
static CameraDistance = 576;
|
|
810
|
+
/**
|
|
811
|
+
* C# Create3dMatrix (Sk3dView): RotationX, then RotationY, then RotationZ (negated) around the pivot, projected from
|
|
812
|
+
* the camera; TranslationZ is depth, a flat control at depth z draws at camera / (camera − z).
|
|
813
|
+
*/
|
|
814
|
+
static Create3dMatrix(rotationX, rotationY, rotationZ, translationZ) {
|
|
815
|
+
const d = Math.PI / 180, camera = SkiaControl.CameraDistance;
|
|
816
|
+
const sx = Math.sin(rotationX * d), cx = Math.cos(rotationX * d), sy = Math.sin(rotationY * d), cy = Math.cos(rotationY * d);
|
|
817
|
+
const sz = Math.sin(-rotationZ * d), cz = Math.cos(-rotationZ * d);
|
|
818
|
+
return [
|
|
819
|
+
cy * cz, cy * sz, 0,
|
|
820
|
+
sx * sy * cz - cx * sz, sx * sy * sz + cx * cz, 0,
|
|
821
|
+
(cx * sy * cz + sx * sz) / camera, (cx * sy * sz - sx * cz) / camera, Math.max(camera - translationZ, 1) / camera,
|
|
822
|
+
];
|
|
823
|
+
}
|
|
824
|
+
/** C# SkiaSamplingOptions.IsMoveOnly: the matrix only moves (or mirrors), so a bitmap keeps its pixels 1:1. */
|
|
825
|
+
static IsMoveOnly(m) {
|
|
826
|
+
return m[1] === 0 && m[3] === 0 && m[6] === 0 && m[7] === 0 && m[8] === 1 && Math.abs(m[0]) === 1 && Math.abs(m[4]) === 1;
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* C# WillDrawDistorted: what this control draws here ends up scaled, turned, skewed or projected, by its own transforms
|
|
830
|
+
* or a live ancestor's (both in the canvas matrix) or by the picture it is recorded into. Images then sample smoothly.
|
|
831
|
+
*/
|
|
832
|
+
WillDrawDistorted(ctx) {
|
|
833
|
+
return !!ctx.Context.PlaybackDistorted || !SkiaControl.IsMoveOnly(ctx.Context.Canvas.getTotalMatrix());
|
|
774
834
|
}
|
|
775
835
|
/** Maps a point given in the parent's space into this control's untransformed space (DrawnUi TransformPointToLocalSpace). */
|
|
776
836
|
TransformPointToLocalSpace(point) {
|
|
@@ -809,23 +869,32 @@ export class SkiaControl {
|
|
|
809
869
|
const epoch = this.Superview?.GpuEpoch ?? 0;
|
|
810
870
|
if (this.RenderObjectPrevious && this.RenderObjectPrevious.Epoch !== epoch)
|
|
811
871
|
this.DisposePrevious(); // made on a lost GPU context
|
|
812
|
-
|
|
872
|
+
// a picture replays its image blits with the sampling chosen when it was recorded: recorded again once its
|
|
873
|
+
// playback turns distorted or straight (C# SamplingMismatch), else a cached child of a turning card stays jagged
|
|
874
|
+
const o = this.RenderObject;
|
|
875
|
+
if (this.CacheStale(ctx.Scale) || (o?.Picture && o.HasImageBlits && o.PlaybackDistorted !== this.WillDrawDistorted(ctx)))
|
|
813
876
|
this.CreateRenderingObject({ ...ctx, Destination: this.DrawingRect }, cacheType);
|
|
814
877
|
if (this.RenderObject) {
|
|
815
878
|
// C# DrawRenderObject: with post renderers an Image cache is not blitted, the effects sample it (CachedImage)
|
|
816
879
|
// and paint the result; a picture cache has no texture, so it is replayed first and snapshotted by the effect
|
|
817
880
|
if (post.length === 0 || !this.RenderObject.Image)
|
|
818
|
-
this.
|
|
881
|
+
this.DrawCache(this.RenderObject, ctx, r.Left + dx, r.Top + dy);
|
|
819
882
|
for (const e of post)
|
|
820
883
|
e.Render(own);
|
|
821
884
|
}
|
|
822
885
|
else if (this.RenderObjectPrevious)
|
|
823
|
-
this.
|
|
886
|
+
this.DrawCache(this.RenderObjectPrevious, ctx, r.Left + dx, r.Top + dy); // double buffer: last good frame
|
|
824
887
|
else
|
|
825
888
|
this.DrawPlaceholder(own);
|
|
826
889
|
}
|
|
827
890
|
this.ExecutePostAnimators(own);
|
|
828
891
|
}
|
|
892
|
+
/** Blits a cache: an image samples smoothly when drawn distorted; a picture being recorded learns it holds image blits. */
|
|
893
|
+
DrawCache(cache, ctx, left, top) {
|
|
894
|
+
if (cache.Image || cache.HasImageBlits)
|
|
895
|
+
ctx.Context.HasImageBlits = true;
|
|
896
|
+
cache.Draw(ctx.Context.Canvas, left, top, !!cache.Image && this.WillDrawDistorted(ctx));
|
|
897
|
+
}
|
|
829
898
|
/** Background + Paint(): the part of the control that a cache captures. */
|
|
830
899
|
PaintContent(ctx) {
|
|
831
900
|
if (this.BackgroundColor || (this.FillGradient && this.FillGradientPaintsBackground()) || this.PaintsBackgroundWithoutColor())
|
|
@@ -860,12 +929,16 @@ export class SkiaControl {
|
|
|
860
929
|
}
|
|
861
930
|
if (cacheType === "Operations") {
|
|
862
931
|
const recorder = new CK.PictureRecorder();
|
|
932
|
+
const playbackDistorted = this.WillDrawDistorted(ctx);
|
|
863
933
|
const canvas = recorder.beginRecording(CK.LTRBRect(r.Left, r.Top, r.Right, r.Bottom));
|
|
864
|
-
|
|
934
|
+
const recording = { ...ctx.Context, Canvas: canvas, Recording: true, PlaybackDistorted: playbackDistorted, HasImageBlits: false };
|
|
935
|
+
this.PaintContent({ ...ctx, Context: recording });
|
|
865
936
|
const picture = recorder.finishRecordingAsPicture();
|
|
866
937
|
recorder.delete();
|
|
867
938
|
this.RenderObject = new CachedObject("Operations", r, ctx.Scale, picture);
|
|
868
939
|
this.RenderObject.Epoch = this.Superview?.GpuEpoch ?? 0;
|
|
940
|
+
this.RenderObject.PlaybackDistorted = playbackDistorted;
|
|
941
|
+
this.RenderObject.HasImageBlits = recording.HasImageBlits;
|
|
869
942
|
}
|
|
870
943
|
else if (cacheType === "ImageComposite") {
|
|
871
944
|
this.CreateCompositeRenderingObject(ctx, r, w, h);
|
package/dist/react/reconciler.js
CHANGED
|
@@ -38,7 +38,7 @@ export const Registry = {
|
|
|
38
38
|
};
|
|
39
39
|
const SKIP = new Set(["children", "key", "ref"]);
|
|
40
40
|
/** Props that change how the subtree is composited, not what it contains: repaint, keep caches (DrawnUi RedrawCanvas). */
|
|
41
|
-
const REPAINT_ONLY = new Set(["TranslationX", "TranslationY", "Rotation", "ScaleX", "ScaleY", "Scale", "SkewX", "SkewY", "AnchorX", "AnchorY", "Opacity"]);
|
|
41
|
+
const REPAINT_ONLY = new Set(["TranslationX", "TranslationY", "TranslationZ", "Rotation", "RotationX", "RotationY", "RotationZ", "ScaleX", "ScaleY", "Scale", "SkewX", "SkewY", "AnchorX", "AnchorY", "Opacity"]);
|
|
42
42
|
/** Props that change only how a control looks, never its size (C# NeedDraw): an ImageComposite above redraws only its area. */
|
|
43
43
|
const isDrawOnly = (k) => k.endsWith("Color") || k === "FillGradient" || k === "StrokeGradient";
|
|
44
44
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drawnui-react",
|
|
3
|
-
"version": "0.1.0-preview.
|
|
3
|
+
"version": "0.1.0-preview.19",
|
|
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)",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"check:scroll-index": "node dev/check.mjs dev/check-scroll-index.ts",
|
|
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
|
+
"check:3d": "node dev/check.mjs dev/check-3d.ts",
|
|
77
78
|
"site:extras": "node dev/site-extras.mjs dist/demo",
|
|
78
79
|
"build:lib": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
|
|
79
80
|
"prepublishOnly": "npm run build:lib",
|