drawnui-react 0.1.0-preview.17 → 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 +3 -3
- package/dist/controls/SkiaButton.d.ts +12 -2
- package/dist/controls/SkiaButton.js +19 -6
- package/dist/controls/SkiaRadioButton.js +3 -1
- package/dist/controls/SkiaScroll.d.ts +5 -1
- package/dist/controls/SkiaScroll.js +8 -1
- package/dist/controls/SkiaSlider.d.ts +5 -1
- package/dist/controls/SkiaSlider.js +8 -1
- package/dist/controls/SkiaToggle.d.ts +5 -1
- package/dist/controls/SkiaToggle.js +11 -2
- package/dist/controls/SnappingLayout.d.ts +5 -1
- package/dist/controls/SnappingLayout.js +8 -1
- package/dist/core/SkiaControl.d.ts +52 -4
- package/dist/core/SkiaControl.js +98 -14
- package/dist/react/reconciler.js +1 -1
- package/package.json +3 -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. |
|
|
@@ -73,7 +73,7 @@ DrawnUi name and semantics; nothing here is a redesign, only an omission.
|
|
|
73
73
|
| `SkiaLayout` Grid (static children) + `SkiaGrid` alias | ported | Port of `SkiaGridStructure` (MAUI grid manager): `ColumnDefinitions`/`RowDefinitions` as `"*, 2*, Auto, 100"` or arrays, `DefaultColumnDefinition`/`DefaultRowDefinition` (Auto, like C#), `ColumnSpacing`/`RowSpacing`, child `Column`/`Row`/`ColumnSpan`/`RowSpan` (plain props instead of attached properties), implicit tracks, unknown/known measure passes, span resolution, star compression, Fill-child minimums, last-track stretch when the grid fills, remeasure at final cells. Not ported: `Split`/`Invert` (templated grids), `UseDynamicColumns`, `SkiaDecoratedGrid`, `GetOrderedSubviews` ZIndex ordering. |
|
|
74
74
|
| `SkiaLabel` | partial | Word wrap (+ character break for long words), `MaxLines` with tail ellipsis, `LineBreakMode` (NoWrap / wraps / Tail; Head/Middle behave as Tail), `HorizontalTextAlignment` Start/Center/End (Fill* accepted, align Start), `VerticalTextAlignment`, `LineSpacing`, `LineHeight`, `FontWeight` + `FontAttributes` via faces registered per weight (`AddFont(src, alias, weight)`, nearest weight wins, synthetic bold (`setEmbolden`) when the nearest face is lighter than 600 — C# `Font.Embolden`; italic = synthetic skew when no italic face; empty `FontFamily` = the first registered family, weights included), `TextTransform`, `Padding`, `LinesCount`; defaults as C# (`FontSize` 12, `TextColor` GreenYellow). `FontFamilyFallback` ported and extended: comma-separated chain (`"FontSymbols,FontSymbols2"`), per-codepoint run segmentation, spaces always stay on the main font; `fonts.AddSymbols()` (`FontSymbols` = Noto Sans Math subset, `FontSymbols2` = Noto Sans Symbols 2 subset) and `fonts.AddEmojis()` (`FontEmoji` = Noto Color Emoji faces+hands subset) ship the same subsets as DrawnUi.Blazor. `Spans` ported as `<TextSpan>` children: `Text`, `TextColor`/`FontSize`/`FontFamily` (unset = inherit from the label, like C# HasSetColor/HasSetSize/HasSetFont), `FontWeight`, `IsBold`, `IsItalic`, `Underline`/`UnderlineWidth`, `Strikeout`/`StrikeoutWidth`/`StrikeoutColor` (Red default), `BackgroundColor`, `Tapped` + `ForceCaptureInput` (hit-tested through per-fragment `Rects`, ripple on the label, `OnSpanTapped` overridable), mixed sizes on one line share a baseline. TextSpan not ported: `ParagraphColor`, `CommandTapped`, per-span `LineSpacing`/`LineHeight`, `AutoFindFont`, `DrawingOffset`, `Shape`/glyph shaping. `CharacterSpacing` (C#: (value - 1) points after every glyph, measured, wrapped and cut at the drawn width), Chinese / Japanese line breaks between characters with kinsoku (C# FitAtBreak, `CanBreakInsideWord`). Not ported: `AutoFont`, `AutoSize`/`AutoSizeText`, `DropShadow*`, text `StrokeColor`/`StrokeWidth`/`StrokeGradient`, `GradientByLines`, `MonoForDigits`, `KeepSpacesOnLineBreaks`, `Format`, RTL/bidi, hyphenation, `SkiaRichLabel`/markdown. |
|
|
75
75
|
| `SkiaHotspot` | ported | Fill/Fill, `Tapped`, `Down`, `Up`, `LockPanning`, `TouchDown`; consumes only Tapped like the C# one. No `AnimationTapped`/ripple/shimmer. |
|
|
76
|
-
| `SkiaButton` | partial | Default look only: rounded frame radius 8 (hardcoded like the C# default content), centered label, `Text`/`TextColor`/`FontSize`/`FontFamily`/`BackgroundColor`/`IsPressed`/`IsDisabled
|
|
76
|
+
| `SkiaButton` | partial | Default look only: rounded frame radius 8 (hardcoded like the C# default content), centered label, `Text`/`TextColor`/`FontSize`/`FontFamily`/`BackgroundColor`/`IsPressed`/`RespondsToGestures` (`IsDisabled` = its obsolete opposite)/`LockPanning`, `Tapped`/`Down`/`Up`. `RespondsToGestures = false`: no press, no `Tapped`/`Down`/`Up`, a press or tap on it stops there, keyboard skips it, reported disabled (drawnui-cross 6w). `IsPressed` is tracked but has no visual; press feedback = `ApplyEffect="Ripple"`. No `ButtonStyle` platform looks, icons, `TextCase`, elevation, shimmer, `BtnText`/`BtnShape` templating. |
|
|
77
77
|
| `SkiaImage` | partial | Effects ported: `AddEffect` (BlackAndWhite/Grayscale, Pastel, Sepia, InvertColors, Tint + `ColorTint`/`EffectBlendMode`, Darken, Lighten, Contrast, Saturation, Brightness, TSL, Gamma ≈ linear fit — CanvasKit has no table color filter), `Blur` (image filter, mirror tile), `ZoomX/ZoomY`, `HorizontalOffset/VerticalOffset`; C# color matrices use 0..255 translations, CanvasKit 0..1 — converted. `HSL` (hue from `Gamma`, `Saturation`, `Brightness`, tint blend), `Custom` = your own `PaintColorFilter` (C# public fields `PaintColorFilter` / `PaintImageFilter`, the latter replaces `Blur`), `ImageBitmap` (decoded image shared, not owned), `LoadPriority` of the queued `Source` load. Not ported: `UseGradient`/`StartColor`/`EndColor`, sprites, `InflateAmount`, `EraseChangedContent`, `RescaleSource`/`RescalingQuality`. `Source` (URL), `Aspect` (all `TransformAspect` values; `Tile` is a no-op like C#, tiling is `SkiaImageTiles`, same `RescaleAspect` math), `HorizontalAlignment`/`VerticalAlignment`, `Success`/`Error`, `IsLoading`, `LoadedSource`, `DisplayRect`, `AspectScale`; overflow clipped to the box. Not ported: `LoadSourceOnFirstDraw`, `PreviewBase64`, `ImageBitmap`/`LoadedImageSource`, `RescaleSource`/`CacheRescaledSource`/`RescalingQuality`, all adjustments (`Brightness`…`Blur`, `ColorTint`, gradient, `Zoom*`, offsets), sprites, `DrawWhenEmpty`, `EraseChangedContent`, `UseAssembly`. Measure: bounded box taken as is, unbounded axis from source aspect; no `NeedAutoWidth/Height` from Start alignment. |
|
|
78
78
|
| `SkiaImageManager` | partial | `Instance.LoadImageAsync(url, signal)` (direct), `LoadImageManagedAsync(url, signal, priority)` through the priority queue (High > Normal > Low, `MaxParallelLoads` 5 in flight like the C# semaphore, one fetch per url serves every requester), `PreloadImage` / `PreloadImages(urls, priority)`, `CancelAll`, `GetFromCache`, `CanReload` callback on failure, `QueuedCount` / `RunningCount`, `ReuseBitmaps`, `Clear()`; in-memory cache of decoded images only. No `CacheLongevitySecs` eviction, no platform loaders, no `LoadImageOnlineAsync` retry policy, no `ImageSource` types (urls only). |
|
|
79
79
|
| `SkiaImageTiles` | ported | `TileWidth` / `TileHeight` (points, nothing drawn until set), `TileAspect` (AspectCover), `TileOffsetX` / `TileOffsetY` (wrap), `TileCacheType` (Image) for the inner tile `SkiaImage` sharing the decoded source, clipped to bounds. |
|
|
@@ -103,7 +103,7 @@ DrawnUi name and semantics; nothing here is a redesign, only an omission.
|
|
|
103
103
|
| Area | Status | Notes |
|
|
104
104
|
|---|---|---|
|
|
105
105
|
| Custom `react-reconciler` renderer, PascalCase props mapped 1:1 to control properties | ported | |
|
|
106
|
-
| `SkiaToggle` / `SkiaSwitch` / `SkiaCheckbox` / `SkiaRadioButton` | ported | Same names: `IsToggled`, `Toggled`, `DefaultValue`, `IsAnimated`, `RespondsToGestures
|
|
106
|
+
| `SkiaToggle` / `SkiaSwitch` / `SkiaCheckbox` / `SkiaRadioButton` | ported | Same names: `IsToggled`, `Toggled`, `DefaultValue`, `IsAnimated`, `RespondsToGestures` (false: a tap does not toggle it and stops there, keyboard skips it, reported disabled), `ColorThumbOn/Off`, `ColorFrameOn/Off` (+ `ColorCheckOn`), `ControlStyle` with the Default / Cupertino / Material / Material3 / Windows looks and geometry of the C# style builders (changing it after the first measure rebuilds the content and re-pins only the sizes the style set, C# `RebuildDefaultContent`) (`Frame`/`Thumb`, `FrameOff`/`FrameOn`/`ViewCheckOn`, `On` ring + `Text`), thumb animated with `TranslateToAsync` (`AnimationSpeed` 200 ms), radio groups by `GroupName` or parent. Not ported: `CommandToggled`/`CommandTapped`, hover (`CheckHovered`), `TransformView` hotspot (the toggle itself takes the tap), `Platform` style resolves from the browser UA. |
|
|
107
107
|
| `SkiaControl.ExecuteAfterCreated` / fluent `.Initialize(...)` | skipped | C# runs it once when the control gets its parent so code-behind can reach `.Assign`'d refs; in React the element tree is declarative and `ref` / `useEffect` cover it. |
|
|
108
108
|
| `SkiaProgress` / `SkiaSlider` | ported (painted, not composed) | Screen readers: `AccessibilityValue` (aria-valuenow / min / max, valuetext "65%" or "20 – 80", a slider horizontal), the name only from `AccessibilityLabel` (drawnui-cross 6c). `Value`/`Min`/`Max`, `TrackColor`/`ProgressColor`; slider `Min`/`Max`/`Step`/`Start`/`End`/`EnableRange`/`RangeMin`/`SliderHeight`/`ThumbColor`/`TrackColor`/`TrackSelectedColor`/`ClickOnTrailEnabled`/`StartChanged`/`EndChanged`, `StartThumbX`/`EndThumbX`, the C# position<->value math (`SliderHeight` = thumb box, `Step` rounding, `RangeMin` clamps), drag + click-on-trail + per-style looks (track heights, thumb sizes, palettes, Material3 gap + stop dot). Track/trail/thumbs are painted directly instead of child shapes (`SliderTrail`/`SliderThumb` not exposed). Not ported: `Orientation` Vertical, `Invert`, `ValueStringFormat`/`MinMaxStringFormat` + `StartDesc`/`EndDesc`, `IgnoreWrongDirection`, `AvailableWidthAdjustment`, `Trail*Offset`, `Cupertino*` sizing props, hover. |
|
|
109
109
|
| `SkiaButton.ControlStyle` | ported | Cupertino / Material / Material3 / Windows accent, corner radius, font size/weight and minimum content size from the C# style builders, applied only when the user left `BackgroundColor`/`CornerRadius`(8)/`FontSize`(15)/`MinimumWidthRequest`/`MinimumHeightRequest` unset; default look is now the C# Crimson accent. `SetDefaultMinimumContentSize` is mirrored for BOTH axes (every C# style passes width 100, heights 41/36/40/40/32) and for the default `Unset` style too, skipped on a `Fill` axis or when a `WidthRequest`/`HeightRequest` is set, so short captions clamp to 100pt as in C#; the C# value adds `Margins` because it compares against a margin-inclusive size, here Measure applies it margin-exclusive and adds the margin after. Not ported: `Background` brushes, pressed-state shadow changes. |
|
|
@@ -26,7 +26,17 @@ export declare class SkiaButton extends SkiaLayout {
|
|
|
26
26
|
StrokeColor: Color;
|
|
27
27
|
StrokeWidth: number;
|
|
28
28
|
IsPressed: boolean;
|
|
29
|
-
|
|
29
|
+
private respondsToGestures;
|
|
30
|
+
/**
|
|
31
|
+
* False switches the button off for the user: taps, presses and keyboard activation do nothing (no Tapped, Down, Up),
|
|
32
|
+
* a tap on it does not reach controls under it, and screen readers report it disabled. Code can still change it.
|
|
33
|
+
* Same property as on toggles, sliders, scrolls and drawers (drawnui-cross 6w). Default true.
|
|
34
|
+
*/
|
|
35
|
+
get RespondsToGestures(): boolean;
|
|
36
|
+
set RespondsToGestures(v: boolean);
|
|
37
|
+
/** @deprecated The opposite of RespondsToGestures, which every control uses: set `RespondsToGestures = false`. */
|
|
38
|
+
get IsDisabled(): boolean;
|
|
39
|
+
set IsDisabled(v: boolean);
|
|
30
40
|
LockPanning: boolean;
|
|
31
41
|
/** Touch feedback played on Down (DrawnUi SkiaButton.ApplyEffect). */
|
|
32
42
|
ApplyEffect: SkiaTouchAnimation;
|
|
@@ -43,7 +53,7 @@ export declare class SkiaButton extends SkiaLayout {
|
|
|
43
53
|
static DefaultAccessibilityRole?: string;
|
|
44
54
|
protected DefaultAccessibilityLabel(): string | undefined;
|
|
45
55
|
protected DefaultAccessibilityCanInteract(): boolean;
|
|
46
|
-
/**
|
|
56
|
+
/** With RespondsToGestures = false the button ignores taps, keyboard navigation skips it and it is reported disabled. */
|
|
47
57
|
protected AcceptsInput(): boolean;
|
|
48
58
|
/** The button's own BackgroundColor/CornerRadius/Stroke are the frame's; the button itself paints nothing. */
|
|
49
59
|
protected PaintBackground(): void;
|
|
@@ -26,7 +26,20 @@ export class SkiaButton extends SkiaLayout {
|
|
|
26
26
|
StrokeColor = Colors.Transparent;
|
|
27
27
|
StrokeWidth = 0;
|
|
28
28
|
IsPressed = false;
|
|
29
|
-
|
|
29
|
+
respondsToGestures = true;
|
|
30
|
+
/**
|
|
31
|
+
* False switches the button off for the user: taps, presses and keyboard activation do nothing (no Tapped, Down, Up),
|
|
32
|
+
* a tap on it does not reach controls under it, and screen readers report it disabled. Code can still change it.
|
|
33
|
+
* Same property as on toggles, sliders, scrolls and drawers (drawnui-cross 6w). Default true.
|
|
34
|
+
*/
|
|
35
|
+
get RespondsToGestures() { return this.respondsToGestures; }
|
|
36
|
+
set RespondsToGestures(v) { if (this.respondsToGestures !== v) {
|
|
37
|
+
this.respondsToGestures = v;
|
|
38
|
+
this.NotifyAccessibility();
|
|
39
|
+
} }
|
|
40
|
+
/** @deprecated The opposite of RespondsToGestures, which every control uses: set `RespondsToGestures = false`. */
|
|
41
|
+
get IsDisabled() { return !this.respondsToGestures; }
|
|
42
|
+
set IsDisabled(v) { this.RespondsToGestures = !v; }
|
|
30
43
|
LockPanning = false;
|
|
31
44
|
/** Touch feedback played on Down (DrawnUi SkiaButton.ApplyEffect). */
|
|
32
45
|
ApplyEffect = "Ripple"; // C# default
|
|
@@ -60,9 +73,9 @@ export class SkiaButton extends SkiaLayout {
|
|
|
60
73
|
/** Set to `Aria.RoleButton` to expose every button (React extension). */
|
|
61
74
|
static DefaultAccessibilityRole;
|
|
62
75
|
DefaultAccessibilityLabel() { return this.Text || undefined; }
|
|
63
|
-
DefaultAccessibilityCanInteract() { return
|
|
64
|
-
/**
|
|
65
|
-
AcceptsInput() { return
|
|
76
|
+
DefaultAccessibilityCanInteract() { return this.respondsToGestures; }
|
|
77
|
+
/** With RespondsToGestures = false the button ignores taps, keyboard navigation skips it and it is reported disabled. */
|
|
78
|
+
AcceptsInput() { return this.respondsToGestures; }
|
|
66
79
|
/** The button's own BackgroundColor/CornerRadius/Stroke are the frame's; the button itself paints nothing. */
|
|
67
80
|
PaintBackground() { }
|
|
68
81
|
get UsingControlStyle() { return ResolveControlStyle(this.ControlStyle); }
|
|
@@ -130,8 +143,8 @@ export class SkiaButton extends SkiaLayout {
|
|
|
130
143
|
this.Repaint();
|
|
131
144
|
}
|
|
132
145
|
ProcessGestures(args, apply) {
|
|
133
|
-
if (this.
|
|
134
|
-
return
|
|
146
|
+
if (!this.respondsToGestures)
|
|
147
|
+
return this.BlockedGestureResult(args); // no press, no Down / Up / Tapped; a tap on it stops here
|
|
135
148
|
const point = args.Event.Location;
|
|
136
149
|
if (args.Type === "Down") {
|
|
137
150
|
this.IsPressed = true;
|
|
@@ -144,8 +144,10 @@ export class SkiaRadioButton extends SkiaToggle {
|
|
|
144
144
|
super.OnToggledChanged();
|
|
145
145
|
}
|
|
146
146
|
ProcessGestures(args, apply) {
|
|
147
|
+
if (!this.RespondsToGestures)
|
|
148
|
+
return this.BlockedGestureResult(args); // a tap does not select it, nor reach controls under it
|
|
147
149
|
if (args.Type === "Tapped") {
|
|
148
|
-
if (!this.IsToggled
|
|
150
|
+
if (!this.IsToggled) {
|
|
149
151
|
this.IsToggled = true;
|
|
150
152
|
return this;
|
|
151
153
|
}
|
|
@@ -31,7 +31,11 @@ export declare class SkiaScroll extends SkiaControl {
|
|
|
31
31
|
AutoScrollingSpeedMs: number;
|
|
32
32
|
ScrollingSpeedMs: number;
|
|
33
33
|
IgnoreWrongDirection: boolean;
|
|
34
|
-
|
|
34
|
+
private respondsToGestures;
|
|
35
|
+
/** False: neither a drag, the wheel nor the keyboard scrolls it (code still can), and it is reported disabled; its content keeps its own taps (drawnui-cross 6w). */
|
|
36
|
+
get RespondsToGestures(): boolean;
|
|
37
|
+
set RespondsToGestures(v: boolean);
|
|
38
|
+
protected AcceptsInput(): boolean;
|
|
35
39
|
private header?;
|
|
36
40
|
private footer?;
|
|
37
41
|
/** A view before the content along the scroll axis; `Tag="Header"` on a JSX child sets it. */
|
|
@@ -28,7 +28,14 @@ export class SkiaScroll extends SkiaControl {
|
|
|
28
28
|
AutoScrollingSpeedMs = 600;
|
|
29
29
|
ScrollingSpeedMs = 400;
|
|
30
30
|
IgnoreWrongDirection = false;
|
|
31
|
-
|
|
31
|
+
respondsToGestures = true;
|
|
32
|
+
/** False: neither a drag, the wheel nor the keyboard scrolls it (code still can), and it is reported disabled; its content keeps its own taps (drawnui-cross 6w). */
|
|
33
|
+
get RespondsToGestures() { return this.respondsToGestures; }
|
|
34
|
+
set RespondsToGestures(v) { if (this.respondsToGestures !== v) {
|
|
35
|
+
this.respondsToGestures = v;
|
|
36
|
+
this.NotifyAccessibility();
|
|
37
|
+
} }
|
|
38
|
+
AcceptsInput() { return this.respondsToGestures; }
|
|
32
39
|
// ---- header / footer (C# SkiaScroll.Header / Footer) ----
|
|
33
40
|
header;
|
|
34
41
|
footer;
|
|
@@ -19,7 +19,11 @@ export declare class SkiaSlider extends SkiaControl {
|
|
|
19
19
|
/** Minimum distance between the two thumbs, in value units. */
|
|
20
20
|
RangeMin: number;
|
|
21
21
|
ClickOnTrailEnabled: boolean;
|
|
22
|
-
|
|
22
|
+
private respondsToGestures;
|
|
23
|
+
/** False: neither a drag nor the arrow keys move it (code still can), and it is reported disabled (drawnui-cross 6w). */
|
|
24
|
+
get RespondsToGestures(): boolean;
|
|
25
|
+
set RespondsToGestures(v: boolean);
|
|
26
|
+
protected AcceptsInput(): boolean;
|
|
23
27
|
IsPressed: boolean;
|
|
24
28
|
IsUserPanning: boolean;
|
|
25
29
|
/** Extra points around a thumb that still count as grabbing it (C# moreHotspotSize). */
|
|
@@ -20,7 +20,14 @@ export class SkiaSlider extends SkiaControl {
|
|
|
20
20
|
/** Minimum distance between the two thumbs, in value units. */
|
|
21
21
|
RangeMin = 0;
|
|
22
22
|
ClickOnTrailEnabled = true;
|
|
23
|
-
|
|
23
|
+
respondsToGestures = true;
|
|
24
|
+
/** False: neither a drag nor the arrow keys move it (code still can), and it is reported disabled (drawnui-cross 6w). */
|
|
25
|
+
get RespondsToGestures() { return this.respondsToGestures; }
|
|
26
|
+
set RespondsToGestures(v) { if (this.respondsToGestures !== v) {
|
|
27
|
+
this.respondsToGestures = v;
|
|
28
|
+
this.NotifyAccessibility();
|
|
29
|
+
} }
|
|
30
|
+
AcceptsInput() { return this.respondsToGestures; }
|
|
24
31
|
IsPressed = false;
|
|
25
32
|
IsUserPanning = false;
|
|
26
33
|
/** Extra points around a thumb that still count as grabbing it (C# moreHotspotSize). */
|
|
@@ -19,7 +19,11 @@ export declare abstract class SkiaToggle extends SkiaLayout {
|
|
|
19
19
|
Toggled?: (sender: SkiaToggle, value: boolean) => void;
|
|
20
20
|
DefaultValue: boolean;
|
|
21
21
|
IsAnimated: boolean;
|
|
22
|
-
|
|
22
|
+
private respondsToGestures;
|
|
23
|
+
/** False: a tap or the keyboard does not toggle it (code still can), a tap on it stops there, and it is reported disabled (drawnui-cross 6w). */
|
|
24
|
+
get RespondsToGestures(): boolean;
|
|
25
|
+
set RespondsToGestures(v: boolean);
|
|
26
|
+
protected AcceptsInput(): boolean;
|
|
23
27
|
private isToggled;
|
|
24
28
|
private colorThumbOn?;
|
|
25
29
|
private colorFrameOn?;
|
|
@@ -22,7 +22,14 @@ export class SkiaToggle extends SkiaLayout {
|
|
|
22
22
|
Toggled;
|
|
23
23
|
DefaultValue = false;
|
|
24
24
|
IsAnimated = true;
|
|
25
|
-
|
|
25
|
+
respondsToGestures = true;
|
|
26
|
+
/** False: a tap or the keyboard does not toggle it (code still can), a tap on it stops there, and it is reported disabled (drawnui-cross 6w). */
|
|
27
|
+
get RespondsToGestures() { return this.respondsToGestures; }
|
|
28
|
+
set RespondsToGestures(v) { if (this.respondsToGestures !== v) {
|
|
29
|
+
this.respondsToGestures = v;
|
|
30
|
+
this.NotifyAccessibility();
|
|
31
|
+
} }
|
|
32
|
+
AcceptsInput() { return this.respondsToGestures; }
|
|
26
33
|
isToggled = false;
|
|
27
34
|
colorThumbOn;
|
|
28
35
|
colorFrameOn;
|
|
@@ -106,7 +113,9 @@ export class SkiaToggle extends SkiaLayout {
|
|
|
106
113
|
get AccessibilityIsPressed() { return this.isToggled; }
|
|
107
114
|
set AccessibilityIsPressed(_v) { }
|
|
108
115
|
ProcessGestures(args, apply) {
|
|
109
|
-
if (
|
|
116
|
+
if (!this.respondsToGestures)
|
|
117
|
+
return this.BlockedGestureResult(args); // a tap does not toggle it, nor reach controls under it
|
|
118
|
+
if (args.Type === "Tapped") {
|
|
110
119
|
this.IsToggled = !this.IsToggled;
|
|
111
120
|
return this;
|
|
112
121
|
}
|
|
@@ -11,7 +11,11 @@ import { SkiaLayout } from "./SkiaLayout";
|
|
|
11
11
|
*/
|
|
12
12
|
export declare abstract class SnappingLayout extends SkiaLayout {
|
|
13
13
|
Animated: boolean;
|
|
14
|
-
|
|
14
|
+
private respondsToGestures;
|
|
15
|
+
/** False: neither a swipe nor the keyboard moves it (code still can), and it is reported disabled; its content keeps its own taps (drawnui-cross 6w). */
|
|
16
|
+
get RespondsToGestures(): boolean;
|
|
17
|
+
set RespondsToGestures(v: boolean);
|
|
18
|
+
protected AcceptsInput(): boolean;
|
|
15
19
|
IgnoreWrongDirection: boolean;
|
|
16
20
|
Bounces: boolean;
|
|
17
21
|
RubberDamping: number;
|
|
@@ -11,7 +11,14 @@ import { SkiaLayout } from "./SkiaLayout";
|
|
|
11
11
|
*/
|
|
12
12
|
export class SnappingLayout extends SkiaLayout {
|
|
13
13
|
Animated = true;
|
|
14
|
-
|
|
14
|
+
respondsToGestures = true;
|
|
15
|
+
/** False: neither a swipe nor the keyboard moves it (code still can), and it is reported disabled; its content keeps its own taps (drawnui-cross 6w). */
|
|
16
|
+
get RespondsToGestures() { return this.respondsToGestures; }
|
|
17
|
+
set RespondsToGestures(v) { if (this.respondsToGestures !== v) {
|
|
18
|
+
this.respondsToGestures = v;
|
|
19
|
+
this.NotifyAccessibility();
|
|
20
|
+
} }
|
|
21
|
+
AcceptsInput() { return this.respondsToGestures; }
|
|
15
22
|
IgnoreWrongDirection = false;
|
|
16
23
|
Bounces = false;
|
|
17
24
|
RubberDamping = 0.7;
|
|
@@ -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;
|
|
@@ -375,8 +398,17 @@ export declare class SkiaControl {
|
|
|
375
398
|
* for the pointer. Keyboard navigation uses it: Tapped for Tab and Enter / Space, Panning for the arrow keys.
|
|
376
399
|
*/
|
|
377
400
|
CanReceiveGesture(gesture: SkiaGesturesParameters["Type"]): boolean;
|
|
378
|
-
/**
|
|
401
|
+
/**
|
|
402
|
+
* False while the control ignores input itself: RespondsToGestures = false on a button, toggle, slider, scroll...
|
|
403
|
+
* Taps, keyboard and the accessibility state (reported disabled) all follow it (drawnui-cross 6w). Default true.
|
|
404
|
+
*/
|
|
379
405
|
protected AcceptsInput(): boolean;
|
|
406
|
+
/**
|
|
407
|
+
* What a control that takes no input returns for a gesture that reached it (C# BlockedGestureResult): it keeps a
|
|
408
|
+
* press, tap or long press, so nothing under it reacts to them, and lets moves and wheel go on to the controls around
|
|
409
|
+
* it (a scroll behind it still scrolls).
|
|
410
|
+
*/
|
|
411
|
+
protected BlockedGestureResult(args: SkiaGesturesParameters): SkiaControl | null;
|
|
380
412
|
NotifyAccessibilityFocused(focused: boolean): void;
|
|
381
413
|
/** DrawnUi SmartMax: larger of two sizes, an infinite one loses. */
|
|
382
414
|
protected static SmartMax(a: number, b: number): number;
|
|
@@ -399,8 +431,22 @@ export declare class SkiaControl {
|
|
|
399
431
|
/** DrawingRect grown by the effects margin, in pixels — the rect a cache is recorded for. */
|
|
400
432
|
protected ExpandedCacheRect(scale: number): SKRect;
|
|
401
433
|
Render(ctx: DrawingContext): void;
|
|
402
|
-
/** 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. */
|
|
403
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;
|
|
404
450
|
/** Maps a point given in the parent's space into this control's untransformed space (DrawnUi TransformPointToLocalSpace). */
|
|
405
451
|
TransformPointToLocalSpace(point: SKPoint): SKPoint;
|
|
406
452
|
/**
|
|
@@ -410,6 +456,8 @@ export declare class SkiaControl {
|
|
|
410
456
|
private AlignedCacheRect;
|
|
411
457
|
/** Cache blit or live paint, then post animators — the part a transform/opacity layer wraps. */
|
|
412
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;
|
|
413
461
|
/** Background + Paint(): the part of the control that a cache captures. */
|
|
414
462
|
protected PaintContent(ctx: DrawingContext): void;
|
|
415
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)
|
|
@@ -633,8 +663,19 @@ export class SkiaControl {
|
|
|
633
663
|
return false;
|
|
634
664
|
return true;
|
|
635
665
|
}
|
|
636
|
-
/**
|
|
666
|
+
/**
|
|
667
|
+
* False while the control ignores input itself: RespondsToGestures = false on a button, toggle, slider, scroll...
|
|
668
|
+
* Taps, keyboard and the accessibility state (reported disabled) all follow it (drawnui-cross 6w). Default true.
|
|
669
|
+
*/
|
|
637
670
|
AcceptsInput() { return true; }
|
|
671
|
+
/**
|
|
672
|
+
* What a control that takes no input returns for a gesture that reached it (C# BlockedGestureResult): it keeps a
|
|
673
|
+
* press, tap or long press, so nothing under it reacts to them, and lets moves and wheel go on to the controls around
|
|
674
|
+
* it (a scroll behind it still scrolls).
|
|
675
|
+
*/
|
|
676
|
+
BlockedGestureResult(args) {
|
|
677
|
+
return args.Type === "Down" || args.Type === "Tapped" || args.Type === "LongPressing" ? this : null;
|
|
678
|
+
}
|
|
638
679
|
NotifyAccessibilityFocused(focused) { this.Superview?.AccessibilityManager.NotifyFocused(focused ? this : undefined); }
|
|
639
680
|
/** DrawnUi SmartMax: larger of two sizes, an infinite one loses. */
|
|
640
681
|
static SmartMax(a, b) {
|
|
@@ -700,7 +741,7 @@ export class SkiaControl {
|
|
|
700
741
|
const applyOpacity = this.Opacity < 1;
|
|
701
742
|
// C# Left/Top: a cached control is blitted at an offset, no matrix and no save/restore around the subtree
|
|
702
743
|
const offsetOnly = (this.Left !== 0 || this.Top !== 0) && this.UsingCacheType !== "None" && this.TranslationX === 0 && this.TranslationY === 0
|
|
703
|
-
&& 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;
|
|
704
745
|
const needTransform = this.HasTransform && !offsetOnly;
|
|
705
746
|
let dx = 0, dy = 0;
|
|
706
747
|
if (needTransform) {
|
|
@@ -746,11 +787,12 @@ export class SkiaControl {
|
|
|
746
787
|
if (saved)
|
|
747
788
|
canvas.restore();
|
|
748
789
|
}
|
|
749
|
-
/** 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. */
|
|
750
791
|
CreateRenderTransformMatrix(destination, scale) {
|
|
751
792
|
const M = Super.CK.Matrix;
|
|
752
793
|
const moveX = (this.TranslationX + this.Left) * scale, moveY = (this.TranslationY + this.Top) * scale;
|
|
753
|
-
|
|
794
|
+
const has3d = this.Has3d;
|
|
795
|
+
if (this.Rotation === 0 && this.ScaleX === 1 && this.ScaleY === 1 && this.SkewX === 0 && this.SkewY === 0 && !has3d)
|
|
754
796
|
return M.translated(moveX, moveY);
|
|
755
797
|
const px = destination.Left + destination.Width * this.AnchorX;
|
|
756
798
|
const py = destination.Top + destination.Height * this.AnchorY;
|
|
@@ -759,7 +801,36 @@ export class SkiaControl {
|
|
|
759
801
|
const scaleSkew = [this.ScaleX, kx, 0, ky, this.ScaleY, 0, 0, 0, 1];
|
|
760
802
|
const rotation = this.Rotation !== 0 ? M.rotated((Math.PI * this.Rotation) / 180) : M.identity();
|
|
761
803
|
// multiply(a, b, c) = a·b·c, the right-most applies first
|
|
762
|
-
|
|
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());
|
|
763
834
|
}
|
|
764
835
|
/** Maps a point given in the parent's space into this control's untransformed space (DrawnUi TransformPointToLocalSpace). */
|
|
765
836
|
TransformPointToLocalSpace(point) {
|
|
@@ -798,23 +869,32 @@ export class SkiaControl {
|
|
|
798
869
|
const epoch = this.Superview?.GpuEpoch ?? 0;
|
|
799
870
|
if (this.RenderObjectPrevious && this.RenderObjectPrevious.Epoch !== epoch)
|
|
800
871
|
this.DisposePrevious(); // made on a lost GPU context
|
|
801
|
-
|
|
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)))
|
|
802
876
|
this.CreateRenderingObject({ ...ctx, Destination: this.DrawingRect }, cacheType);
|
|
803
877
|
if (this.RenderObject) {
|
|
804
878
|
// C# DrawRenderObject: with post renderers an Image cache is not blitted, the effects sample it (CachedImage)
|
|
805
879
|
// and paint the result; a picture cache has no texture, so it is replayed first and snapshotted by the effect
|
|
806
880
|
if (post.length === 0 || !this.RenderObject.Image)
|
|
807
|
-
this.
|
|
881
|
+
this.DrawCache(this.RenderObject, ctx, r.Left + dx, r.Top + dy);
|
|
808
882
|
for (const e of post)
|
|
809
883
|
e.Render(own);
|
|
810
884
|
}
|
|
811
885
|
else if (this.RenderObjectPrevious)
|
|
812
|
-
this.
|
|
886
|
+
this.DrawCache(this.RenderObjectPrevious, ctx, r.Left + dx, r.Top + dy); // double buffer: last good frame
|
|
813
887
|
else
|
|
814
888
|
this.DrawPlaceholder(own);
|
|
815
889
|
}
|
|
816
890
|
this.ExecutePostAnimators(own);
|
|
817
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
|
+
}
|
|
818
898
|
/** Background + Paint(): the part of the control that a cache captures. */
|
|
819
899
|
PaintContent(ctx) {
|
|
820
900
|
if (this.BackgroundColor || (this.FillGradient && this.FillGradientPaintsBackground()) || this.PaintsBackgroundWithoutColor())
|
|
@@ -849,12 +929,16 @@ export class SkiaControl {
|
|
|
849
929
|
}
|
|
850
930
|
if (cacheType === "Operations") {
|
|
851
931
|
const recorder = new CK.PictureRecorder();
|
|
932
|
+
const playbackDistorted = this.WillDrawDistorted(ctx);
|
|
852
933
|
const canvas = recorder.beginRecording(CK.LTRBRect(r.Left, r.Top, r.Right, r.Bottom));
|
|
853
|
-
|
|
934
|
+
const recording = { ...ctx.Context, Canvas: canvas, Recording: true, PlaybackDistorted: playbackDistorted, HasImageBlits: false };
|
|
935
|
+
this.PaintContent({ ...ctx, Context: recording });
|
|
854
936
|
const picture = recorder.finishRecordingAsPicture();
|
|
855
937
|
recorder.delete();
|
|
856
938
|
this.RenderObject = new CachedObject("Operations", r, ctx.Scale, picture);
|
|
857
939
|
this.RenderObject.Epoch = this.Superview?.GpuEpoch ?? 0;
|
|
940
|
+
this.RenderObject.PlaybackDistorted = playbackDistorted;
|
|
941
|
+
this.RenderObject.HasImageBlits = recording.HasImageBlits;
|
|
858
942
|
}
|
|
859
943
|
else if (cacheType === "ImageComposite") {
|
|
860
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)",
|
|
@@ -73,6 +73,8 @@
|
|
|
73
73
|
"check:velocity": "node dev/check.mjs dev/check-velocity.ts",
|
|
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
|
+
"check:responds": "node dev/check.mjs dev/check-responds.ts",
|
|
77
|
+
"check:3d": "node dev/check.mjs dev/check-3d.ts",
|
|
76
78
|
"site:extras": "node dev/site-extras.mjs dist/demo",
|
|
77
79
|
"build:lib": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
|
|
78
80
|
"prepublishOnly": "npm run build:lib",
|