drawnui-react 0.1.0-preview.2 → 0.1.0-preview.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/SKIPPED.md +33 -27
- package/dist/controls/AnimatedFramesRenderer.d.ts +7 -0
- package/dist/controls/AnimatedFramesRenderer.js +16 -1
- package/dist/controls/DrawnGame.d.ts +40 -0
- package/dist/controls/DrawnGame.js +64 -0
- package/dist/controls/GridStructure.d.ts +16 -2
- package/dist/controls/GridStructure.js +40 -4
- package/dist/controls/SkiaBackdrop.d.ts +8 -2
- package/dist/controls/SkiaBackdrop.js +48 -8
- package/dist/controls/SkiaButton.d.ts +19 -2
- package/dist/controls/SkiaButton.js +41 -11
- package/dist/controls/SkiaCarousel.d.ts +9 -1
- package/dist/controls/SkiaCarousel.js +16 -3
- package/dist/controls/SkiaCheckbox.d.ts +0 -1
- package/dist/controls/SkiaCheckbox.js +0 -6
- package/dist/controls/SkiaDrawer.d.ts +2 -0
- package/dist/controls/SkiaDrawer.js +2 -0
- package/dist/controls/SkiaEditor.d.ts +12 -1
- package/dist/controls/SkiaEditor.js +46 -19
- package/dist/controls/SkiaGif.d.ts +3 -0
- package/dist/controls/SkiaGif.js +12 -2
- package/dist/controls/SkiaImage.js +15 -10
- package/dist/controls/SkiaLabel.d.ts +54 -3
- package/dist/controls/SkiaLabel.js +235 -52
- package/dist/controls/SkiaLabelFps.d.ts +16 -0
- package/dist/controls/SkiaLabelFps.js +41 -0
- package/dist/controls/SkiaLayout.d.ts +64 -7
- package/dist/controls/SkiaLayout.js +316 -19
- package/dist/controls/SkiaLottie.js +4 -2
- package/dist/controls/SkiaProgress.d.ts +2 -1
- package/dist/controls/SkiaProgress.js +6 -3
- package/dist/controls/SkiaRadioButton.js +4 -3
- package/dist/controls/SkiaScroll.d.ts +35 -5
- package/dist/controls/SkiaScroll.js +233 -23
- package/dist/controls/SkiaScrollBar.d.ts +27 -0
- package/dist/controls/SkiaScrollBar.js +51 -2
- package/dist/controls/SkiaShaderCarousel.d.ts +5 -0
- package/dist/controls/SkiaShaderCarousel.js +5 -0
- package/dist/controls/SkiaShape.d.ts +13 -2
- package/dist/controls/SkiaShape.js +50 -10
- package/dist/controls/SkiaSlider.d.ts +20 -2
- package/dist/controls/SkiaSlider.js +59 -5
- package/dist/controls/SkiaSprite.d.ts +1 -0
- package/dist/controls/SkiaSprite.js +4 -0
- package/dist/controls/SkiaSvg.js +1 -1
- package/dist/controls/SkiaSwitch.d.ts +0 -1
- package/dist/controls/SkiaSwitch.js +0 -6
- package/dist/controls/SkiaToggle.d.ts +25 -5
- package/dist/controls/SkiaToggle.js +58 -5
- package/dist/controls/SnappingLayout.d.ts +12 -1
- package/dist/controls/SnappingLayout.js +19 -1
- package/dist/core/Accessibility.d.ts +80 -0
- package/dist/core/Accessibility.js +365 -8
- package/dist/core/Animators.d.ts +37 -1
- package/dist/core/Animators.js +102 -9
- package/dist/core/Canvas.d.ts +108 -7
- package/dist/core/Canvas.js +363 -35
- package/dist/core/Easing.d.ts +12 -1
- package/dist/core/Easing.js +25 -1
- package/dist/core/Gestures.d.ts +48 -2
- package/dist/core/Gestures.js +95 -3
- package/dist/core/Hover.d.ts +47 -0
- package/dist/core/Hover.js +134 -0
- package/dist/core/KeyboardManager.d.ts +13 -0
- package/dist/core/KeyboardManager.js +18 -0
- package/dist/core/ScrollAnimators.js +9 -0
- package/dist/core/SkiaControl.d.ts +216 -7
- package/dist/core/SkiaControl.js +516 -59
- package/dist/core/SkiaEffect.d.ts +17 -0
- package/dist/core/SkiaEffect.js +43 -8
- package/dist/core/SkiaImageManager.js +1 -1
- package/dist/core/Styles.d.ts +51 -0
- package/dist/core/Styles.js +92 -0
- package/dist/core/Super.d.ts +17 -1
- package/dist/core/Super.js +35 -4
- package/dist/core/ViewsAdapter.d.ts +7 -2
- package/dist/core/ViewsAdapter.js +10 -3
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/react/SkiaShell.js +33 -14
- package/dist/react/index.d.ts +15 -4
- package/dist/react/index.js +102 -15
- package/dist/react/reconciler.js +34 -12
- package/dist/vite/index.d.ts +9 -0
- package/dist/vite/index.js +36 -3
- package/package.json +33 -3
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@ through a custom `react-reconciler` renderer.
|
|
|
6
6
|
|
|
7
7
|
Check out latest playbook: [helloreact.drawnui.net](https://helloreact.drawnui.net/) 👈
|
|
8
8
|
|
|
9
|
+
Playground to use and test live: [drawfiddle.com](https://drawfiddle.com/) 👈
|
|
10
|
+
|
|
9
11
|
*Work in progress*: the goal is same API surface and semantics as DrawnUi (.NET) — same control names, same PascalCase
|
|
10
12
|
property names, same measure/arrange/paint contract — so knowledge and docs transfer 1:1.
|
|
11
13
|
|
|
@@ -25,7 +27,7 @@ await Super.UseDrawnUi()
|
|
|
25
27
|
## Install
|
|
26
28
|
|
|
27
29
|
```
|
|
28
|
-
npm i drawnui-react
|
|
30
|
+
npm i drawnui-react react react-dom
|
|
29
31
|
```
|
|
30
32
|
|
|
31
33
|
`drawnui-react` = React tags + every engine type, `drawnui-react/core` = the engine only. Ships ES modules + `.d.ts`;
|
|
@@ -65,13 +67,24 @@ accessibility all live in the engine and would work identically under Vue, Svelt
|
|
|
65
67
|
That is also why the demo pages describe DrawnUi features, not React ones: the same pages are meant to be reused as
|
|
66
68
|
the showcase for other frameworks on this engine.
|
|
67
69
|
|
|
70
|
+
## Games
|
|
71
|
+
|
|
72
|
+
`DrawnGame` (from DrawnUi.Gaming) is ported: a layout with a game loop. Subclass it, build your sprites in the
|
|
73
|
+
constructor, call `StartLoop()`, and override `GameLoop(deltaSeconds)` and `OnKeyDown` / `OnKeyUp` (DOM `event.code`
|
|
74
|
+
names). Move sprites with `Left` / `Top` on cached controls, as in C#. The demo's `#/pong` page is the .NET Pong
|
|
75
|
+
sample (`src/Shared/Samples/Pong.Shared`) ported line for line: the game classes are plain TypeScript in
|
|
76
|
+
`samples/demo/pages/pong`, mounted from `PongPage.tsx` with `AddSubView`, and a small `RescalingLayout` fits the
|
|
77
|
+
360×640 field to any screen by changing the children's rendering scale.
|
|
78
|
+
|
|
68
79
|
## Context menu (right click)
|
|
69
80
|
|
|
70
|
-
A right click on the canvas normally opens the browser's own menu ("Save image as…").
|
|
71
|
-
|
|
72
|
-
`
|
|
81
|
+
A right click on the canvas normally opens the browser's own menu ("Save image as…"). Every mouse button goes
|
|
82
|
+
through the gesture pipeline as in DrawnUi.Net (`e.Parameters.Event.Pointer.Button` says which), so a right click is
|
|
83
|
+
a `Tapped` with `Button` `"Right"` for games and custom controls; a handler that should act on the primary button only
|
|
84
|
+
checks it. To take the browser menu request, handle `ContextMenu` on any control, exactly like `Tapped`:
|
|
73
85
|
|
|
74
86
|
```tsx
|
|
87
|
+
<SkiaShape Tapped={(s, e) => { if ((e.Parameters.Event.Pointer?.Button ?? "Left") === "Left") open(); }} />
|
|
75
88
|
<SkiaShape ContextMenu={(sender, e) => { openMyMenu(e.Location); return true; }} />
|
|
76
89
|
<Canvas ContextMenu={(canvas, e) => true /* nothing hit: swallow the browser menu everywhere */} />
|
|
77
90
|
```
|
package/SKIPPED.md
CHANGED
|
@@ -9,16 +9,17 @@ DrawnUi name and semantics; nothing here is a redesign, only an omission.
|
|
|
9
9
|
|---|---|---|
|
|
10
10
|
| `UseCache` | partial | `None`, `Operations` / `OperationsFull` (SkPicture replay), `Image` (offscreen surface snapshot, GPU-backed on WebGL, nearest-sampled blit), `ImageDoubleBuffered` (`RenderObjectPrevious` kept and drawn when a new cache cannot be produced yet, `DrawPlaceholder(ctx)` hook when there is none; recording itself is synchronous — no background thread, same as DrawnUi.Blazor with `CanUseCacheDoubleBuffering = false`), `ImageComposite` / `ImageCompositeGPU` (per-child composite, see the `UseCache="ImageComposite"` row below). `GPU` resolves to `Image` and `ImageCompositeGPU` to `ImageComposite`: no separate GPU-only path. `Super.CacheEnabled`, `RenderObject`/`CachedObject`, `InvalidateCache`, `DestroyRenderingObject`, `Canvas.DisposeObject` (deleted after flush). Defaults per C#: `SkiaLabel`/`SkiaSvg` Operations, everything else None. Not ported: `CacheSharing`, `AllowCaching`, offscreen bake threads, cache validity by GRContext. |
|
|
11
11
|
| Incremental invalidation | partial | `Measure` returns the previous size when `!NeedMeasure` and constraints+scale are unchanged; `Update()`/`InvalidateMeasure()` bubble up and stale every ancestor cache; `Repaint()` keeps caches. Arrange still runs per frame for the whole tree; no dirty regions, no `DirtyChildrenTracker`. Post-animators on a control nested INSIDE a cached ancestor are drawn only when that ancestor re-records. |
|
|
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.
|
|
13
|
-
| Gestures: `LongPressing
|
|
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
|
+
| 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
|
-
| `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`; `
|
|
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. |
|
|
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`; 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
|
-
| `
|
|
21
|
-
| `
|
|
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
|
+
| `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). |
|
|
22
|
+
| `SkiaEditor` | partial | SkiaShape host with padding, content `SkiaLabel` (single-line: `HorizontalTextAlignment` Center / End get the field as the label's minimum width, so short text centers and long text still scrolls), placeholder label, caret (blinking 1 s, `CursorColor`, `CanShowCursor`) and selection overlay (`SelectionColor`) painted by the editor, single line (`MaxLines` 1, scrolls horizontally to the caret) / multiline (`MaxLines` != 1, `AutoHeight`) with own scroll offsets instead of the C# inner `SkiaScroll`; `Text` (line breaks normalized, spaces for single line), `PlaceholderText/Color/HorizontalAlignment`, `FontSize` (12) / `FontFamily` / `FontWeight` / `TextColor` / `TextGradient` / `LineHeight` / `Horizontal|VerticalTextAlignment`, `IsPassword` (bullets), `ReturnType` (Send submits multiline), `KeyboardType` / `IsSpellCheckEnabled` / `UseMarkdown` / `UseUnicode` accepted, `CursorPosition`, `SelectionLength`, `IsFocused` (one focused editor at a time), `ControlStyle` (C# `ApplyControlStyleVisuals` palettes over unset values), events `TextChanged` / `FocusChanged` / `CursorMoved` / `TextSubmitted`, `Submit` / `SelectAll` / `SetSelection` / `SelectWord` / `GetSelectedText` / `DeleteSelection` / `InsertAtCursor` / `CutSelection` / `CopySelection` / `PasteFromClipboard` (navigator.clipboard), the Blazor head stubs `StubTypeText` / `StubPressEnter` / `StubBackspace` / `StubDelete` (grapheme clusters via Intl.Segmenter) / `StubMoveCursor` / `StubSelectRange` / `StubSelectAll` / `HandleVerticalArrow`, Ctrl+A / C / X / V, Home / End, Escape blurs, tap places the caret, drag selects, long press selects a word, shift + tap extends. Keys come from `KeyboardManager` like the Blazor / Wasm heads; on top of that (not in DrawnUi.Blazor) a focused editor is mirrored by a hidden DOM textarea (`TextInputProxy`, `inputmode` from `KeyboardType`, `enterkeyhint` from `ReturnType`) so IME composition, the mobile soft keyboard, autocorrect and native paste / cut / undo reach the editor: every input event is diffed against the editor text and replayed through `StubSelectRange` + `StubTypeText` / `StubBackspace` / `StubPressEnter`; while the proxy holds DOM focus, Backspace / Delete / Enter / Ctrl+C / X / V key events are left to it. iOS may need a second tap for the keyboard (focus() runs on the next frame, outside the touch handler). Not ported: `SkiaCursor` control + `CursorGradient`, `SkiaEditorSelection` handles, `Superview.FocusedChild`, `CommandOn*`, `SkiaEditorDocument` (rich formatting), single-line Enter clearing focus (Blazor keeps it). |
|
|
22
23
|
| `SkiaSprite` / `SkiaSpriteSet` | ported | Sprite: `Source` (sheets cached per source, `CachedSpriteSheets`), `Columns` / `Rows` / `MaxFrames`, `FramesPerSecond` (24), `FrameSequence` / `AnimationName` + `CreateAnimationSequence` / `RegisteredAnimations`, `CurrentFrame`, `TotalFrames` / `DurationMs` / `FrameDurationMs` / `FrameWidth` / `FrameHeight`, `ApplyPlacementConfig` (units-per-pixel, size, anchor, offset = C# SpriteFrameImage ResolveFrameMetrics), transparent frame borders trimmed per frame (pixels read once), nearest sampling, AspectFit of the frame, `DisplayRect`, `Success` / `Error`, the AnimatedFramesRenderer playback. Set: `State`, `Define(state, source, columns, rows, fps, repeat, autoPlay, placement)`, `CurrentSprite`, `OnChangeState` hook, hit box from the drawn frame. Not ported: `Display` as a separate SkiaImage child (`SpriteFrameImage`) and the standalone `SkiaSprite(display)` ctor, `RescalingQuality` other than None, `InflateAmount`, `ClearCache` / `RemoveFromCache` (SkiaImageManager owns the images), `SkiaSpriteSet.InvalidateInternal`. Code-behind subclasses (FastRepro `WarriorSprite`) mount through a ref + `AddSubView` and are disposed by the mounting effect. |
|
|
23
24
|
| `IsClippedToBounds`, `ClipEffects` | ported | Clip to `DrawingRect` around content, children and post-animators; `ClipEffects=false` grows the clip by the aggregated effects margin so a child's shadow/glow can escape. Layouts aggregate their children's effects margins (C# `AggregatedEffectsMarginPixels`), cached per control and reset by `InvalidateMeasure`. Not ported: `Clipping` callback, `ClipWith`, `ClipFrom`, platform clips; C# currently ignores `ClipEffects` in its render path (always expands). |
|
|
24
25
|
| `Padding` on base `SkiaControl` | skipped | Only `SkiaLayout.Padding` exists. |
|
|
@@ -30,12 +31,14 @@ DrawnUi name and semantics; nothing here is a redesign, only an omission.
|
|
|
30
31
|
| `ZIndex` | ported | Static children of a layout are drawn in ZIndex order (stable for equal values), gestures go to the top-most first; the sorted list is cached until a child or a ZIndex changes (C# `GetOrderedSubviews`). Templated cells keep index order. |
|
|
31
32
|
| `FillGradient`, `SkiaShape.StrokeGradient` | ported | `SkiaGradient` as a plain object: `Type` Linear (Start/End ratios or CSS `Angle`), Circular / Conical (radius = half the smaller side), Oval (radial scaled to the box), Sweep (around the center, `Value1`/`Value2` angles), `ColorPositions`, `TileMode`, `Light` (< 1 darker, > 1 lighter — HSL lightness, C# uses `MakeDarker/MakeLighter`, exact curve unverified), `Opacity`, `BlendMode`; C# `SetupGradient` paint setup (white base); shaders cached per gradient object and rect (C# `Version` cache: a new React literal replaces the old shaders). `SkiaLabel.FillGradient` paints the glyphs (`GradientByLines` default true = per line bounds, false = the text block) and the background only when `BackgroundColor` is set, like the C# label override. `SkiaLabel` text stroke (`StrokeColor`, `StrokeWidth` points, `StrokeGradient`) and drop shadow (`DropShadowColor`, `DropShadowSize`, `DropShadowOffsetX/Y`) in the C# order shadow → stroke → fill, measurement inflated by stroke × 2 and shadow size + offset like C#. Not ported: `Background` MAUI brushes, `FillBlendMode`. |
|
|
32
33
|
| `SkiaShell` | partial (React-level) | `Routes` (route -> JSX page factory), `GoToAsync(route, animated)` / `GoBackAsync(animated)` / `PopToRootAsync` with the SkiaViewSwitcher slide (`PagesAnimationSpeed` 200 ms, pushed pages opaque over the page below, lower pages hidden when idle), `NavigationStack`, `CanGoBack`, `Route`, nav bar with Back + title, `useShell()`; `OpenPopupAsync(content, { animated, closeWhenBackgroundTapped, showOverlay, backgroundColor })` (C# PopupWrapper: dimmed backdrop, scale 0.5→1 + fade over `PopupsAnimationSpeed`, tap outside the content closes), `ClosePopupAsync`, `CloseAllPopups`; `PushModalAsync(content, { useGestures, animated, freezeBackground })` (C# ModalWrapper: full-screen `SkiaDrawer` FromBottom HeaderSize 0, drag-to-close with `useGestures`), `PopModalAsync`; `ShowToast(text | content, ms)` (C# layout: bottom banner, slide + fade 300/250 ms, `ShellDefaults.Toast*`), `CloseAllToasts`; `PopupsCount`/`ModalsCount`/`ToastsCount`; `ShellDefaults` = the C# statics (`PopupBackgroundColor`, `PopupsBackgroundBlur`, `PopupsAnimationSpeed`, `PopupsCancelAnimationsAfterMs`, `ZIndex*`, `Toast*`, tab colors); popups/modals sit on a `SkiaBackdrop` (blur + tint); `Tabs` (C# SkiaViewSwitcher tabs: bottom tab bar, per-tab page stacks, `SelectedTab`, `SelectTabAsync`, `PopTabToRootAsync`); `GoBackAsync` follows C# `GoBackDefault` (top popup, then modal, then page); React extension `UseBrowserHistory` (default true): pages in the URL hash `#/a/b`, one history entry per page / popup / modal, browser Back unwinds in the same order, deep links restore the page stack. Not ported: engine-level shell / `NavigationLayout` view switcher, frozen-screenshot backgrounds, `pixelsScaleInFrom`, `Navigating`/`Navigated`/`RouteChanged` events, `RegisterRoute` by type with arguments, `IHandleGoBack`, insets, `PushAsync(page instance)`; `AnimateTabs` + `TabsAnimationSpeed` (150) ported with the C# SelectLeftTab/SelectRightTab slide (0.75 width, fade, back-ease 0.55) — only the tab roots animate, pages pushed inside the leaving tab drop instantly. Browser history: a `popstate` at the same depth with another hash (a plain `<a href="#/route">` link, a typed hash) rebuilds the stack like a forward navigation. |
|
|
33
|
-
| Styles / `ConfigureStyles` |
|
|
34
|
-
| Animators core: `AnimatorBase` / `SkiaValueAnimator` / `RenderingAnimator` (`IOverlayEffect`), `Canvas.RegisterAnimator`/`AnimatingControls`, `PostAnimators` + `ExecutePostAnimators`, `Easing` (Linear/Cubic*) | ported | Frame-driven: a running animator keeps frames coming, idle canvas draws nothing. |
|
|
34
|
+
| Styles / `ConfigureStyles` | ported | `Super.UseDrawnUi().ConfigureStyles(styles => styles.AddStyle({ TargetType, ApplyToDerivedTypes, Setters }))`, the C# `Style` shape with the `Setter` list written as a plain object (`Setters: { FontFamily: "FontText" }`) because controls hold plain properties instead of BindableProperty. Per-class resolution cached, base-type styles applied furthest-first so the nearest wins, exact type last (C# `ResolveStylesForType`). Applied by the reconciler between `new` and the JSX props, so props override; a control built in code-behind is styled on its first `Measure` and keeps values that differ from its class defaults (the React stand-in for C# `ExplicitPropertiesSet`, which needs BindableProperty). Not ported: `BasedOn`, `Class`, `Triggers`, `Behaviors`, the per-control `Style` property, `ConditionalStyle`. |
|
|
35
|
+
| Animators core: `AnimatorBase` / `SkiaValueAnimator` / `RenderingAnimator` (`IOverlayEffect`), `Canvas.RegisterAnimator`/`AnimatingControls`, `PostAnimators` + `ExecutePostAnimators`, `Easing` (Linear/Cubic*) | ported | Frame-driven: a running animator keeps frames coming, idle canvas draws nothing. An animator started before its control is attached waits in `PendingUnattachedAnimators` and starts on the control's first arrange (C# starts it in `SuperViewChanged`). Frame time = the frame's vsync timestamp, see PARITY "Animators run on vsync time". |
|
|
36
|
+
| `ActionOnTickAnimator` | ported | Runs an action with the frame time on every frame until stopped. |
|
|
37
|
+
| `DrawnGame` (DrawnUi.Gaming) | ported | `SkiaLayout` subclass: `StartLoop(delayMs)` / `StopLoop`, override `GameLoop(deltaSeconds)`, `OnKeyDown` / `OnKeyUp` (DOM `event.code` names, subscribed through `KeyboardManager` for the game's lifetime), `Pause` / `Resume` / `IsPaused` / `OnPaused` / `OnResumed`, `LastFrameTimeNanos`. Not ported: `FrameTimeInterpolator` (C# disables it on BROWSER too), `IGame`, the `IsPaused` property-changed notification. Demo: `#/pong`, the .NET Pong.Shared sample. |
|
|
35
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. |
|
|
36
39
|
| `Shimmer` touch animation, `ShimmerAnimator`, `ClippedEffectsWith`, `TransformView`, `DelayCallbackMs`, `removePrevious`, `Pause`/`Resume`, `UseInterpolator`, spring/deceleration timing, `AnimateExtensions` (`FadeToAsync`, `TranslateToAsync`, ...) | skipped | |
|
|
37
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. |
|
|
38
|
-
| `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. |
|
|
39
42
|
| Multithreading / offscreen rendering | skipped | Browser main thread only. |
|
|
40
43
|
| Hot reload hook | skipped | Vite HMR reloads the page. |
|
|
41
44
|
| Second measure pass / fill-in-auto re-measure rules | skipped | Column/Row give children an infinite main axis; cross axis = available. |
|
|
@@ -47,42 +50,43 @@ DrawnUi name and semantics; nothing here is a redesign, only an omission.
|
|
|
47
50
|
| Area | Status | Notes |
|
|
48
51
|
|---|---|---|
|
|
49
52
|
| `RenderingMode` | partial | `Accelerated` (WebGL) with automatic fallback to software; read once at first surface creation. |
|
|
50
|
-
| `Gestures` param | ported |
|
|
53
|
+
| `Gestures` param | ported | Touch: `Lock` applies `touch-action:none`; `Enabled` behaves like MAUI's Enabled inside a native scroll view: `touch-action` is the axes the page (or a scrolling ancestor) can scroll (`pan-y` / `pan-x` / both, `none` when it cannot; inside an iframe the vertical axis always counts, because the embedding page cannot be inspected cross-origin), recomputed on attach, on window / html / body resize and after every touch, so a finger pan along a page axis scrolls the page and the canvas pointer is cancelled (a cancelled pointer never flings a `SkiaScroll`), taps and the other axis stay on the canvas, and a full-page app keeps every touch. `user-select:none` in both. Claimed touches: in `Enabled` a touch or pen Down is processed immediately (queued gestures first) and, when a control used it (same rule as the wheel: marked `Handled`, or a consumer that is not a `BlockGesturesBelow` layer), the touch is claimed and its `touchmove` events are default-prevented until it ends, so the page cannot take that drag mid-gesture (iOS Safari ignores `touch-action` for off-axis drags, w3c/pointerevents#303); unclaimed touches share the page as before. Controls that use a Down: `ConsumeGestures` handlers that set `Consumed`, `SkiaCarousel` (always), `SkiaSlider`, `SkiaEditor`, `SkiaButton` when `OnDown` accepts; `SkiaScroll` and plain `Tapped` controls do not. Mouse wheel: `Enabled` shares it with the page like Blazor's `TouchHandlingStyle.Manual` (the wheel is processed in the DOM handler and the browser default is prevented only when a control used it: a scroll that moved, a `ConsumeGestures` handler that consumed, or any other consumer that is not a `BlockGesturesBelow` layer merely blocking the controls below, so an embedded canvas lets the page scroll over anything that does not scroll, including a shell page, and a scroll at its edge hands the wheel to the page; `TouchActionEventArgs.Handled` is the AppoMobi marker for that use); `Lock` prevents every wheel and also blocks `touchmove` default. Not ported: Blazor's pointer capture policy for `Enabled` (capture only while locked, release when a pan is not consumed); React captures every press. |
|
|
51
54
|
| Mouse cursor over interactive controls (Blazor `Canvas.razor` `.xaml-a11y-interactive { cursor: pointer }`) | ported (differently) | The overlay here is `pointer-events: none`, so `Canvas.UpdateCursor` hit-tests mouse moves with no active pointer against the accessibility snapshot rects (`CanInteract` = has `Tapped`, buttons, links) and asks each hit control `WantsPointerCursor(x, y)` (default `AccessibilityCanInteract`; `SkiaLabel` also answers for its tappable `TextSpan`s), setting the canvas element's `cursor` only when the answer changes. Touch pointers do nothing. Engine hover (`IsHovered` / `OnHover`, `TouchActionResult.Pointer`) stays skipped, see Gestures. |
|
|
52
55
|
| `ContextMenu` (right click / long press / Menu key, React extension) | ported (web-only) | `SkiaControl.ContextMenu` / `Canvas.ContextMenu` handlers with `ContextMenuEventArgs` (`Location` points, `Pixels`, `Local` px in the control, `Source`, `Native`); the DOM `contextmenu` event is routed like a tap (`ProcessContextMenu`: visible non-transparent children top-most first through `TransformPointToLocalSpace`, then the control, then the canvas). `true` = handled, browser menu suppressed; nothing handled = browser menu. Not in DrawnUi.Net. |
|
|
53
|
-
| Keyboard,
|
|
54
|
-
|
|
|
56
|
+
| Keyboard navigation (drawnui-cross 6c) | ported | Through the accessibility overlay, the DrawnUi.Blazor way: Tab / Shift+Tab walk the interactive nodes in reading order, Enter / Space activate (`SkiaAccessibilityManager.Activate`), ArrowLeft / Right / Up / Down, Home / End, PageUp / PageDown go to the focused control first (`OnAccessibilityKey`, `SkiaAccessibilityManager.Key`: `SkiaSlider` steps, `SkiaEditor` keeps them while editing), then to its arrow-key group (`MoveInGroup`). A container with a composite role (`Aria.IsCompositeRole`: list, listbox, grid, toolbar, radiogroup, tablist, menu, menubar) is one Tab stop (roving tabindex, `IsTabStop`, entered on the item focused last); the arrows move by item index (Column: Up / Down, Row: Left / Right, Wrap / Grid / Split: all four, Up / Down by a row), no wrap, items not realized are scrolled in with `ScrollToIndex` and focused once drawn (`OnKeyboardFocusRequested`). `SkiaEditor` is a Tab stop: Tab in takes the caret, Tab / Shift+Tab leave it for the next node (also after a click into it). Focus ring = the overlay element's `:focus-visible` outline; focus scrolls the node into view in every enclosing scroll. Not ported: `FocusedChild` / canvas `KeyboardFocusNode`, Escape leaving the drawn nodes, a canvas-drawn focus ring. |
|
|
57
|
+
| Canvas draw events | ported | `WillFirstTimeDraw` (once, right before the first frame drawn on a ready surface, with that frame's `{ Canvas, Surface }`), `WasDrawn` (after every frame, no context, like the C# `null`), `WasRendered` (set after the first frame completes, so the first `WasDrawn` still sees `false`, C# order), `CanRender` (surface ready). JSX: `<Canvas WillFirstTimeDraw WasDrawn>`; the engine draws frame 1 in its constructor, so the component hands both callbacks to the constructor's `init`. Not ported: `WillDraw`. |
|
|
58
|
+
| Rendering stats | partial | `Canvas.FPS` (frames drawn over the last second) and `Canvas.FrameTime` (ms of the last measure + arrange + render, GPU flush excluded) are computed every frame and reachable from React through the `<Canvas>` ref. `SkiaLabelFps` ported (label on top, input-transparent, parent-independent, re-measures itself on draw; its value changes only on frames that running animators request, see PARITY). Not ported: `SkiaLabelFps.ForceRefresh` / `Format` / `MonoForDigits`, `Super.EnableRenderingStats`. |
|
|
55
59
|
| Insets / safe areas | ported | `Super.Insets` measures `env(safe-area-inset-*)` off a probe element (once, and again on resize / orientation), `Super.OnInsetsChanged` notifies; `SkiaShell` pads its nav and tab bars with them. Not ported: the C# per-page `IgnoreSafeArea` / MAUI inset plumbing. |
|
|
56
60
|
|
|
57
61
|
## Controls
|
|
58
62
|
|
|
59
63
|
| Control | Status | Notes |
|
|
60
64
|
|---|---|---|
|
|
61
|
-
| `SkiaLayout` Absolute / Column / Row (+ `SkiaStack`, `SkiaRow`, `SkiaLayer`) | ported | `Spacing`, `Padding`, `Children`/`Views`, `AddSubView`/`RemoveSubView`/`InsertSubView`. |
|
|
62
|
-
| `SkiaLayout` templated (Column only): `ItemsSource` (array), `ItemTemplate` (factory `() => SkiaControl`), `RecyclingTemplate` Enabled/Disabled, `MeasureItemsStrategy` `MeasureFirst` (uniform, O(1) for 100k items)
|
|
65
|
+
| `SkiaLayout` Absolute / Column / Row (+ `SkiaStack`, `SkiaRow`, `SkiaLayer`) | ported | `Spacing`, `Padding`, `Children`/`Views`, `AddSubView`/`RemoveSubView`/`InsertSubView`, `ClearChildren`, `IsChildrenItem`. Children changes follow C# `ChildrenCollectionSync` (insert / move / reset in collection order inside the slots app children occupy); React has no `ObservableCollection`: JSX children diffs and a new `Children` list are the collection actions. |
|
|
66
|
+
| `SkiaLayout` templated (Column only): `ItemsSource` (array), `ItemTemplate` (factory `() => SkiaControl`), `RecyclingTemplate` Enabled/Disabled, `MeasureItemsStrategy` `MeasureAll` (default, per-item size) / `MeasureFirst` (explicit uniform, O(1) for 100k items), `ChildrenFactory` (ViewsAdapter pool: `GetOrCreateViewForIndex`, `ReleaseViewAt`), `FirstVisibleIndex`/`LastVisibleIndex`, `DebugString`, `ApplyItemsSource`, `VirtualisationInflated`, `BindingContext`/`ContextIndex` on every control, `SkiaDynamicDrawnCell.SetContent` | ported | Cells realized/bound/arranged/drawn only for the visible viewport (+inflation) each frame. |
|
|
63
67
|
| `LoadMoreCommand` / `LoadMoreTopCommand` / `LoadMoreOffset` / `LoadMoreTopOffset` | ported | Callbacks instead of ICommand. Bottom fires once per content extent when within the offset of the end (or when the content underfills the viewport), re-arms when the content grows or the user moves away (>offset+100 pt, >2 s) — C# CheckNeedToLoadMore. Top arms only after the offset left the top edge, fires when it comes back. Not ported: opposite-direction cooldown (`IsOppositeLoadMoreBlocked`), `IInsideViewport.ShouldTriggerLoadMore`. |
|
|
64
|
-
| Structure-preserving `ItemsSource` updates | ported (array diff) | C# listens to `ObservableCollection` Add/Insert; React apps replace the array, so the setter detects **append** (old is a prefix: measured heights kept, tail measured lazily) and **prepend** (old is a suffix: view indices shifted without rebinding, new head measured synchronously up to 200 items, `ItemsInsertedAtStart(px)` lets the `SkiaScroll` move its offset so visible rows stay put — C# head-insert rebase). Any other change rebuilds the structure (heights re-measured, offset kept). Not ported: removals
|
|
68
|
+
| Structure-preserving `ItemsSource` updates | ported (array diff) | C# listens to `ObservableCollection` Add/Insert; React apps replace the array, so the setter detects **append** (old is a prefix: measured heights kept, tail measured lazily) and **prepend** (old is a suffix: view indices shifted without rebinding, new head measured synchronously up to 200 items, `ItemsInsertedAtStart(px)` lets the `SkiaScroll` move its offset so visible rows stay put — C# head-insert rebase). and **reorder** (same items permuted, the C# `ObservableCollection.Move` case: contexts rebound, the index-keyed measured heights permuted with their items, nothing re-measured, extent and scroll offset unchanged). Any other change rebuilds the structure (heights re-measured, offset kept). Not ported: removals in place, `Replace`. |
|
|
65
69
|
| Templated Row/Grid/Wrap, `Split`, `ItemTemplateSelector`, `ItemTemplatePoolSize`/`ReserveTemplates`, `UsePreparedViews`, `SkiaCachedStack`, measure memo | skipped | |
|
|
66
70
|
| `ItemsSourceWindow` / `WindowSourceThreshold` (built-in window over huge sources) | not needed | The C# window bounds pipeline work (structures, pools, measures) for `MeasureFirst`/`MeasureAll` over big collections. React `MeasureVisible` measures on demand and keeps only visible cells realized, so the pipeline is already O(visible); memory of the array itself belongs to the app (paginate with `LoadMore*`). Revisit only if `MeasureAll` over 100k+ items becomes a requirement. |
|
|
67
71
|
| `MeasureItemsStrategy="MeasureVisible"` + background measurement | ported | Initial pass measures enough items to fill the viewport (≥ `BackgroundMeasurementBatchSize`), content extent = exact prefix + average × remaining (C# MeasureList estimate), visible cells measured on demand and laid out with their real heights, the prefix grows in `requestIdleCallback` slices (time-budgeted, then one `InvalidateMeasure`), `LastMeasuredIndex`, `DebugString` shows `measured n/N`. Not ported: cancellation/`_pendingStructureChanges` interplay, `UsePreparedViews`, windowed sources, anchor correction when an estimate above the viewport changes (content may shift by the delta). |
|
|
68
72
|
| `SkiaLayout` Wrap (static children) + `SkiaWrap` alias | ported | Left-to-right flow, `Spacing` between items and rows, row height = tallest item. Not ported: `Split`, main-axis `Fill` children sharing a row (1.9.7.4 flex-fill), `UseDynamicColumns`, RTL. |
|
|
69
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. |
|
|
70
|
-
| `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. Not ported: `AutoFont`, `
|
|
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. |
|
|
71
75
|
| `SkiaHotspot` | ported | Fill/Fill, `Tapped`, `Down`, `Up`, `LockPanning`, `TouchDown`; consumes only Tapped like the C# one. No `AnimationTapped`/ripple/shimmer. |
|
|
72
|
-
| `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. |
|
|
73
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. |
|
|
74
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). |
|
|
75
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. |
|
|
76
80
|
| `SkiaSvg` | partial | Inline `SvgString` without `xmlns` gets it injected (browsers refuse to decode an `<img>` SVG without it, SkiaSharp does not care) — the C# check-mark markup relies on that. `Source` (URL) / `SvgString`, `Aspect` (default `AspectFitFill`, uses the shared `RescaleAspect` math, not SkiaSvg's own matrix path), alignments, `TintColor` (SrcIn), `Success`/`Error`, `IsLoading`, `DisplayRect`. Rendering: browser decodes the SVG, rasterized at the displayed pixel size and cached per size (CanvasKit npm has no SVG module). Not ported: shadows, FontAwesome duotone colors, `FillGradient`, `Zoom*`/offsets/`InflateAmount`, `IconFilePath`/embedded resources, `UseCache=Operations` default (no caching yet). |
|
|
77
|
-
| `SkiaScroll` | partial | `Orientation` Vertical/Horizontal/Both, single `Content`, `ViewportOffsetX/Y` (points, <= 0), pan with the C# 0.85 delta interpolation, fling on the DrawnUi deceleration curve (`FrictionScrolled`, `ChangeVelocityScrolled`, `MaxVelocity`, edge-cut duration), rubber-band overscroll (`Bounces`, `RubberEffect`) + spring bounce (`RubberDamping`, `MaxBounceVelocity`), mouse wheel (`WheelLineSize
|
|
81
|
+
| `SkiaScroll` | partial | `Orientation` Vertical/Horizontal/Both/Neither (Neither: pans never move the viewport, the wheel is not consumed so a parent scroll may take it; programmatic `ScrollTo` still works), single `Content`, `ViewportOffsetX/Y` (points, <= 0), pan with the C# 0.85 delta interpolation, fling on the DrawnUi deceleration curve (`FrictionScrolled`, `ChangeVelocityScrolled`, `MaxVelocity`, edge-cut duration), rubber-band overscroll (`Bounces`, `RubberEffect`) + spring bounce (`RubberDamping`, `MaxBounceVelocity`), mouse wheel (`WheelLineSize` per notch, an event moving by its share of a notch: `Canvas.WheelDeltaPerNotch` 100 CSS px after `deltaMode` lines x 40 / pages x 800, dominant axis, as the C# browser heads; `AutoScrollingSpeedMs` glide for a notch, an event under half a notch moves at once (touchpads, see PARITY); events accumulate onto the running move's exact target, and a restarted glide starts at the event so a fast swipe moves on every frame; `Wheel.IsHorizontal`: a vertical scroll leaves horizontal events, a horizontal scroll takes both, C# ba03cb20), `ScrollTo`/`ScrollToTop`/`ScrollToBottom`/`StopScrolling`, `Scrolled`, `IgnoreWrongDirection`, `RespondsToGestures`, `IsUserPanning`/`IsScrolling`, `ContentSize`, `ContentOffsetBounds`, `OverScrolled`. `Header` / `Footer` (JSX children with `Tag="Header"` / `Tag="Footer"`; the header scrolls in the flow, `HeaderSticky` pins it at the viewport start drawn over the content, `HeaderBehind` draws it under the content, `HeaderParallaxRatio` (1 = with the content, 0.5 = half speed), `ParallaxOverscrollEnabled`, `ContentOffset` between a behind / sticky header and the content; both add to the scroll extent), scroll bars (`ScrollBarsVisibility` None / Vertical / Horizontal / Both creating default `SkiaScrollBar`s, `ScrollBar` / `ScrollBarHorizontal` custom `IScrollBar` controls via `Tag="ScrollBar"` / `"ScrollBarHorizontal"`, `ScrollBarThumbColor` / `ScrollBarTrackColor`; `SkiaScrollBar`: `Dock`, `ThumbColor`, `TrackColor`, `Thickness` 4, `EdgeMargin` 2, `MinThumbSize` 32, `AutoHide` + `HideDelaySecs` 1 + `HideDurationSecs` 0.25, thumb squashed on overscroll, `IsDraggable` + `GrabPadding` 8: the scroll takes a gesture that starts on the bar from down to up, thumb drag keeps the grab point, a track press jumps the thumb under the pointer, the content gets neither the pan nor the tap; a custom `IScrollBar` opts in by implementing `IsDraggable` / `BeginDrag` / `GetDragProgress`; bars are parent-independent (C# `IsParentIndependent`): a thumb resize invalidates the bar alone and the scroll re-measures it on draw, nothing above re-measures), pull to refresh (`RefreshEnabled`, `RefreshCommand` callback, `IsRefreshing` two-way, `RefreshDistanceLimit` 150, `RefreshShowDistance` 50, `RefreshIndicator` `IRefreshIndicator` control via `Tag="RefreshIndicator"`; `RefreshIndicator` base: `Orientation`, `SetDragRatio` slides the view in with the pull and parks it at RefreshShowDistance, `IsRunning` + `OnIsRunningChanged`, `VisibleRatio`), `SnapToChildren` Center / Side after a fling or a release (`Snap`, `CheckNeedToSnap`), `TrackIndexPosition` Start / Center / End + `TrackIndexPositionOffset` reporting `CurrentIndex` / `CurrentIndexChanged`. `ScrollToIndex` Start / Center / End as an open order (C# OrderedScrollToIndex): a row not measured yet is gone to by the estimate and aimed again every frame until its place is measured and the viewport stands on it; a press, the wheel or another scroll drops it; LoadMore waits while it is open. Controls outside the clip are skipped with their subtree (C# stack Virtualisation, for every control; see PARITY). Not ported: zoom (`ViewportZoom`, `ZoomLocked`), `ReverseGestures` (inverted chat), keyboard adaptation, `ResetScrollPositionOnContentSizeChanged`, `ScrollType`, `ScrollingEnded` event, `LottieRefreshIndicator`, virtualization hooks (`Virtualisation`, `UseVirtual`, windowed content bounds); the C# refresh-indicator position curve is replaced by a linear slide-in. |
|
|
78
82
|
| `SkiaShape` (+ `SkiaFrame`) | partial | `BevelType` Bevel / Emboss + `Bevel` (`Depth`, `LightColor`, `ShadowColor`, `Opacity`; C# `PaintBevelEffect` edge paths for Rectangle, rounded Rectangle, Circle/Ellipse, Polygon, Path via ContourMeasure halves). `Shadows` (`SkiaShadow` X/Y/Blur/Opacity/Color/ShadowOnly, plain literals accepted): fill painted once per shadow with a DropShadow image filter (C# `PaintWithShadowsInternal`), hollow shapes clip their outline away so only the outside shadow remains; caches record the effects margin (`3·Blur·scale ± offset`, C# `MergeShadowMargin`) via `ComputeEffectsMargin`/`ExpandedCacheRect`. Not ported: `PlatformShadow` (MAUI `Shadow` brush), `ExpandDirtyRegion`, bevel/emboss. `Type` Rectangle/Circle/Ellipse/Arc (`Value1` start, `Value2` sweep)/Polygon+Line (`Points` ratios)/Path (`PathData`, fitted + centered), `CornerRadius` (uniform or per-corner), `StrokeWidth` (points, negative = px) drawn inside the bounds, `StrokeColor`, `StrokeCap`, `ClipBackgroundColor`, children laid out inside the stroke and clipped to the shape, `CreateClip` = shape, `FillGradient` fill, `UseCache=Operations` default. Not ported: `Squricle`, `Custom`, `SmoothPoints`, `StrokePath` (dashes), `StrokeGradient`, `StrokeBlendMode`, sub-1px stroke compensation, `MeasuredStrokeAware*` exposure, `LayoutChildren` override hook. |
|
|
79
83
|
| `SkiaButton` | partial | Now a `SkiaLayout` hosting a `SkiaShape` frame (`Tag="BtnShape"`) + `SkiaLabel` (`Tag="BtnText"`), sized from the label + Padding; `CornerRadius` (default 8), `StrokeColor`/`StrokeWidth`, `BackgroundColor`, text props, `ApplyEffect`, `Tapped`/`Down`/`Up`. Still no `ButtonStyle` looks, icons, `TextCase`, elevation, shimmer, custom templating through tags. |
|
|
80
84
|
| `SnappingLayout` | ported | `Animated`, `RespondsToGestures`, `IgnoreWrongDirection`, `Bounces`, `RubberDamping`, `RubberEffect`, `AutoVelocityMultiplyPts`, `SnapDistanceRatio`, `SnapPoints`, `CurrentPosition`, `CurrentSnap`, `ContentOffsetBounds`, `IsUserPanning`/`IsUserFocused`, `InTransition`, `Scrolled`/`TransitionChanged`/`Stopped`, `ClampOffset` (rubber), `FindNearestAnchor`, `SelectNextAnchor` (dot product), `ScrollToNearestAnchor`, `ScrollToOffset` (spring `Spring(1+RubberDamping, 200, 0.5·(1+RubberDamping))` when `Bounces`, else 0.1–0.8 s from velocity). The settled state is reported when the snap animation stops (C# relies on `ApplyPosition` ticks). Not ported: `AnimatorSpring` reuse, `SnapDistanceRatio` for non-carousel layouts, hover. |
|
|
81
|
-
| `UseCache="ImageComposite"` / `ImageCompositeGPU`, `Left` / `Top` | ported | ImageComposite keeps its offscreen surface across records; a child that calls `RepaintComposition` (transform / own repaint) is tracked as dirty by its composite ancestors (`TrackChildAsDirty`, `DirtyChildrenInternal`), the next record erases the dirty children's old + new transformed bounds plus the siblings they overlap and paints only those (`IsRenderingWithComposition`, layouts skip the rest); own content, a remeasure, a size / scale change record fully (`LastCompositeRecord` diagnostics). `Left` / `Top` on a cached control blit the cache at the offset without a matrix or save/restore (the translation is still set as `RenderTransformMatrix` so gestures / accessibility map through it); uncached controls keep the matrix path. Not ported: `ExpandDirtyRegion`, C# `CachedObject` surface reuse for plain `Image`. |
|
|
85
|
+
| `UseCache="ImageComposite"` / `ImageCompositeGPU`, `Left` / `Top` | ported | ImageComposite keeps its offscreen surface across records; a change deeper than a direct child (a color through props or a color setter = `UpdateDraw`, or `RepaintComposition`) is erased and redrawn by its area (drawnui-cross 6m: the control as drawn before and now, through every ancestor's transform; an effect or a backdrop on the way draws the child whole; a rect change, more than `MaxCompositionAreas` 16 areas or more than `MaxCompositionShare` 0.5 of the composite records whole; `LastCompositeRecord.Areas` / `Changed`; children outside the areas are not traversed); a child that calls `RepaintComposition` (transform / own repaint) is tracked as dirty by its composite ancestors (`TrackChildAsDirty`, `DirtyChildrenInternal`), the next record erases the dirty children's old + new transformed bounds plus the siblings they overlap and paints only those (`IsRenderingWithComposition`, layouts skip the rest); own content, a remeasure, a size / scale change record fully (`LastCompositeRecord` diagnostics). `Left` / `Top` on a cached control blit the cache at the offset without a matrix or save/restore (the translation is still set as `RenderTransformMatrix` so gestures / accessibility map through it); uncached controls keep the matrix path. Not ported: `ExpandDirtyRegion`, C# `CachedObject` surface reuse for plain `Image`. |
|
|
82
86
|
| `SkiaCarousel` | ported | Static `Children` or recycled `ItemsSource` + `ItemTemplate` cells (only visible + neighbour slides realized, `ReleaseExcept` for the looped non-contiguous set); `IsVertical`, `SidesOffset`, `Spacing`, `IsLooped` (virtual anchors -1/-2, wrap by `GoNext`/`GoPrev`/swipe at the borders, unbounded offset along the axis, edge slides drawn on the far side, `FixIndex` teleport after the wrap, `InterruptSnapping` strip shift), `PreloadNeighboors` (default true), `DynamicSize` (auto-sized axis follows the selected slide, re-measured on index change), `SwipeSpeed` (velocity ×SwipeSpeed/2, spring stiffness 200·SwipeSpeed, linear cap 0.25/speedK s, `SinOut`), `LinearSpeedMs` (one slide = LinearSpeedMs), `SelectedIndex` + `SelectedIndexChanged`, `LastIndex`, `MaxIndex`, `ChildrenTotal`/`ChildrenCount`, `IsAtStart`/`IsAtEnd`, `ScrollProgress`, `ScrollAmount`, `TransitionProgress`, `ItemAppearing`/`ItemDisappearing`, `Stopped`, `GoNext`/`GoPrev`/`ScrollTo(index, animate)`, C# gesture rules (velocity dead zone 100, clamp 500, snap-if-no-pan on Up, only the slides drawn on screen receive gestures). `IsRightToLeft` accepted (C# TODO). Not ported: `TransitionDirection`, `SafeIndex`, `ScrolledCommand`/`SelectedIndexChangedCommand`, `SelectedItem`, `CheckConstraints` exception, `OnTemplatesAvailable` background init, hover, pinch. |
|
|
83
|
-
| Templated `Row` / `Wrap` / `Grid`, `Split`, `SkiaDecoratedGrid` | ported | `ItemsSource` + `ItemTemplate` on any layout type: Column stays the virtualized list
|
|
87
|
+
| Templated `Row` / `Wrap` / `Grid`, `Split`, `SkiaDecoratedGrid` | ported | `ItemsSource` + `ItemTemplate` on any layout type: Column stays the virtualized list. Row / Wrap / Grid / a split Column follow `RecyclingTemplate` (drawnui-cross 6b): Enabled measures every item on pooled views (C# measures the structure on one template instance) and binds views only to the slots drawn, their views go back to the pool when the slot leaves the viewport (`VirtualisationInflated` widens it), every slot draws under a cache (C# `GetOnScreenVisibleArea`); Disabled keeps one view per item, never rebound. Changing `RecyclingTemplate` rebuilds the cells. `Split` (column count for Wrap and for a Column, which then lays out like a wrap with fixed slots; a templated Grid places item i at (i % Split, i / Split), `Invert` column-major), `SplitAlign` (true: fixed slot width), `DynamicColumns` (a short last row spreads over the width). `SkiaDecoratedGrid`: `HorizontalLine` / `VerticalLine` gradients painted in `RowSpacing` / `ColumnSpacing` under the children (C# defaults). Not ported: `SplitSpace` (C# TODO), `SplitMax`, `MeasureItemsStrategy` for non-Column templated layouts (every item is measured once per structure). |
|
|
84
88
|
| `AccessibilityTextSelectable` (React extension) | ported | Opt-in per control, off by default: a `SkiaLabel` with it renders its laid-out lines as real, transparent DOM text in the accessibility overlay (one span per line at the drawn position, the registered font aliases are also installed as CSS `FontFace`s so glyphs line up), `pointer-events` on, so the browser selects, copies and reads it like HTML; the wheel over such text is re-dispatched to the canvas. Pointer input over the text never reaches the drawn control, hence opt-in. Not in DrawnUi.Net. |
|
|
85
|
-
| `SkiaShaderCarousel` | ported | `TransitionShader` (url), `TransitionShaderCode`, `TransitionTemplate`, `InterruptedTransitionMs` (50), `TransitionEffect` (`CreateTransitionEffect` factory), `TransitionFromIndex` / `TransitionToIndex`, `FromToChanged`; slides never move (`SlideOffset` hook), looped wrap rules, one gesture = at most one slide, interrupted transition wrapped up first, `RecyclingTemplate` Disabled; the carousel is cached as Image by default (React only, so the overlapping slides are never blitted). Slides MUST use `UseCache="Image"`. |
|
|
89
|
+
| `SkiaShaderCarousel` | ported | Only the selected slide takes gestures (C# `IsSlideHitTestable`): every slide sits on the same rect, so the topmost on-screen one used to catch every tap. `TransitionShader` (url), `TransitionShaderCode`, `TransitionTemplate`, `InterruptedTransitionMs` (50), `TransitionEffect` (`CreateTransitionEffect` factory), `TransitionFromIndex` / `TransitionToIndex`, `FromToChanged`; slides never move (`SlideOffset` hook), looped wrap rules, one gesture = at most one slide, interrupted transition wrapped up first, `RecyclingTemplate` Disabled; the carousel is cached as Image by default (React only, so the overlapping slides are never blitted). Slides MUST use `UseCache="Image"`. |
|
|
86
90
|
| `SkiaDrawer` | ported | `Direction` (FromBottom/Top/Left/Right), `HeaderSize`, `AmplitudeSize`, `AutoClose`, `IsOpen` + `IsOpenChanged`, `StateTransitionComplete`, `Open()`/`Close()`; moves itself with `TranslationX/Y` like C#, so it must be edge-aligned by the parent (`VerticalOptions="End"` for FromBottom); snap points `[open, hidden]` rebuilt on size change; gestures ported (children first, then vertical/horizontal drag, lock-bounce at the open edge, velocity clamp 3000, touches outside the box ignored / `AutoClose`). Not ported: `IsOpenCommand`, hover, the C# +1 px header quirk. |
|
|
87
91
|
| Everything else (`SkiaSwitch`, `SkiaCheckbox`, `SkiaSlider`, `SkiaProgress`, `SkiaCamera`, `SkiaMauiElement`, ...) | skipped | |
|
|
88
92
|
|
|
@@ -92,19 +96,21 @@ DrawnUi name and semantics; nothing here is a redesign, only an omission.
|
|
|
92
96
|
|---|---|---|
|
|
93
97
|
| `Super.UseDrawnUi().ConfigureFonts(f => f.AddFont(src, alias)).BuildAsync()` | ported | Same shape as DrawnUi.Net/OpenTK. |
|
|
94
98
|
| `AddFont(src, alias, weight)`, font fallback, emoji | skipped | |
|
|
95
|
-
| `PreloadAssets
|
|
99
|
+
| `PreloadAssets` | skipped | `ConfigureStyles` is ported, see the Engine table. |
|
|
96
100
|
|
|
97
101
|
## React layer
|
|
98
102
|
|
|
99
103
|
| Area | Status | Notes |
|
|
100
104
|
|---|---|---|
|
|
101
105
|
| Custom `react-reconciler` renderer, PascalCase props mapped 1:1 to control properties | ported | |
|
|
102
|
-
| `SkiaToggle` / `SkiaSwitch` / `SkiaCheckbox` / `SkiaRadioButton` | ported | Same names: `IsToggled`, `Toggled`, `DefaultValue`, `IsAnimated`, `RespondsToGestures
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
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
|
+
| `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
|
+
| `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
|
+
| `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. |
|
|
105
110
|
| `SkiaButton.FontFamilyFallback` | React extension | Pass-through to the inner label so icon-glyph buttons render with `AddSymbols()` faces. |
|
|
106
|
-
| Accessibility (`ISkiaAccessibilityNode` + `SkiaAccessibilityManager` + Blazor ARIA overlay) | ported | `AccessibilityRole/Label/Hint/CanInteract/IsPressed/Live`, `Aria` constants, `IsAccessibilityElement`, `GetAccessibilityPixelRect`, `NotifyAccessibility`, `OnAccessibilityActivated` (synthetic Tapped), `OnAccessibilityFocused`, snapshot rate-limited by `MinUpdateIntervalMs`,
|
|
111
|
+
| Accessibility (`ISkiaAccessibilityNode` + `SkiaAccessibilityManager` + Blazor ARIA overlay) | ported | `AccessibilityRole/Label/Hint/CanInteract/IsPressed/Live`, `Aria` constants, `IsAccessibilityElement`, `GetAccessibilityPixelRect`, `NotifyAccessibility`, `OnAccessibilityActivated` (synthetic Tapped), `OnAccessibilityFocused`, snapshot rate-limited by `MinUpdateIntervalMs`, reading order by rows (tops within half the smaller height form one row, read left to right), `AccessibilityCanInteract` only while a tap could reach the control (`CanReceiveGesture`: the control and its ancestors visible, none `InputTransparent`, no ancestor `LockChildrenGestures` holding the gesture, `AcceptsInput`, false for a disabled `SkiaButton`), `SkiaButton` hides its inner label. Extensions: per-class `DefaultAccessibilityRole`, label/interaction defaults derived from text / `Tapped`, `pointer-events:none` overlay, detached/off-canvas nodes pruned at rebuild (no explicit unregister on removal). Not ported: `WithAccessibility*` fluent helpers, `FocusChanged` event consumers, UIA/AT-SPI (browser only), `aria-level` for headings. |
|
|
107
112
|
| Static HTML for crawlers (`drawnui-react/vite` `drawnUiStatic()`, React extension) | ported | Build-time only: after `vite build` the plugin serves the build, opens it in headless Chrome (`playwright-core`, optional peer), reads the accessibility snapshot of the root page and of every page a root button opens (or `routes`), and writes visible semantic HTML (heading → heading, button → `<a href="#/route">`, text → paragraph, other controls → list items; `img` and `presentation` dropped) after the mount element or at `<!-- drawnui-static -->`, with `DEFAULT_STATIC_STYLE` (`render.style` to override / disable). Runtime: the first `<Canvas>` mount removes every `[data-drawnui-static]` element right after its engine drew frame 1 (`removeStaticContent`, one querySelectorAll, nothing in the frame loop). Not in DrawnUi.Net (Blazor sites hand-write static SEO content, see the drawnui-blazor notes). Not done: per-route documents (pages live in the hash, one URL), `<title>` / meta per page, sitemap entries. |
|
|
113
|
+
| Versioned public files (`drawnui-react/vite` `drawnUiAssetStamps()` + `Super.ResolveAssetUrl`, React extension) | ported | Build-time plugin: hashes every file of the public folder and writes the map into index.html as `globalThis.DrawnUiAssetStamps`, stamps the page's own `href` / `src` links to public files. Every engine loader (images and sprites through `SkiaImageManager`, fonts in `BuildAsync`, `SkiaSvg`, `SkiaGif`, `SkiaLottie`, shader sources) fetches through `Super.ResolveAssetUrl`, which appends `?v=<hash>` (`&v=` after an existing query) to a listed file; caches stay keyed by the source. Dev server untouched. Replaceable by the app. No C# counterpart. |
|
|
108
114
|
| React context bridging across the `<Canvas>` boundary | skipped | Contexts from the DOM tree are not visible inside the drawn tree. |
|
|
109
115
|
| Refs to engine controls from JSX | skipped | `getPublicInstance` returns the control, `ref` not wired/typed. |
|
|
110
116
|
| Fluent code-behind API (`.Assign`, `.OnTapped`, `.ObserveProperty`) | skipped | Engine classes are plain TS classes; React is the composition layer. |
|
|
@@ -38,6 +38,13 @@ export declare class AnimatedFramesRenderer extends SkiaControl {
|
|
|
38
38
|
protected OnLayoutChanged(): void;
|
|
39
39
|
PlayIfNeeded(): void;
|
|
40
40
|
InitializeAnimator(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Animation time (ms, the animator's range) of the next change of what is shown after `value`, or undefined when it
|
|
43
|
+
* may change every frame (Lottie). A GIF or a sprite returns its next frame's start.
|
|
44
|
+
*/
|
|
45
|
+
protected NextChangeMs(_value: number): number | undefined;
|
|
46
|
+
/** The animator sleeps until the next change: no ticks and no frames in between (DrawnUi.Rust sleeping frame animators). */
|
|
47
|
+
private SleepUntilNextChange;
|
|
41
48
|
/** Configure the animator range/speed once the frames source is known. */
|
|
42
49
|
protected OnAnimatorInitializing(): void;
|
|
43
50
|
/** The animator produced a new frame value. */
|
|
@@ -69,7 +69,7 @@ export class AnimatedFramesRenderer extends SkiaControl {
|
|
|
69
69
|
InitializeAnimator() {
|
|
70
70
|
if (!this.Animator) {
|
|
71
71
|
const a = new SkiaValueAnimator(this);
|
|
72
|
-
a.OnUpdated = (v) => this.OnAnimatorUpdated(v);
|
|
72
|
+
a.OnUpdated = (v) => { this.OnAnimatorUpdated(v); this.SleepUntilNextChange(a, v); };
|
|
73
73
|
a.OnStart = () => { this.WasStarted = true; this.OnStarted(); };
|
|
74
74
|
a.OnStop = () => { if (this.WasStarted)
|
|
75
75
|
this.OnFinished(); this.WasStarted = false; };
|
|
@@ -82,6 +82,21 @@ export class AnimatedFramesRenderer extends SkiaControl {
|
|
|
82
82
|
this.Start();
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Animation time (ms, the animator's range) of the next change of what is shown after `value`, or undefined when it
|
|
87
|
+
* may change every frame (Lottie). A GIF or a sprite returns its next frame's start.
|
|
88
|
+
*/
|
|
89
|
+
NextChangeMs(_value) { return undefined; }
|
|
90
|
+
/** The animator sleeps until the next change: no ticks and no frames in between (DrawnUi.Rust sleeping frame animators). */
|
|
91
|
+
SleepUntilNextChange(a, value) {
|
|
92
|
+
const next = this.NextChangeMs(value), range = a.mMaxValue - a.mMinValue;
|
|
93
|
+
if (next === undefined || !(range > 0) || !(a.Speed > 0) || next <= value) {
|
|
94
|
+
a.SleepUntilNanos = 0;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const wallMs = (Math.min(next, a.mMaxValue) - value) * (a.Speed / range);
|
|
98
|
+
a.SleepUntilNanos = a.LastFrameTimeNanos + wallMs * 1_000_000;
|
|
99
|
+
}
|
|
85
100
|
/** Configure the animator range/speed once the frames source is known. */
|
|
86
101
|
OnAnimatorInitializing() { }
|
|
87
102
|
/** The animator produced a new frame value. */
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type InputKey } from "../core/KeyboardManager";
|
|
2
|
+
import { SkiaLayout } from "./SkiaLayout";
|
|
3
|
+
/**
|
|
4
|
+
* Mirrors DrawnUi.Gaming DrawnGame (SharedGame/DrawnGame.cs + Game.Input.cs): a layout that runs a game loop.
|
|
5
|
+
* `StartLoop()` / `StopLoop()`, override `GameLoop(deltaSeconds)` for the game and `OnKeyDown` / `OnKeyUp` for keys
|
|
6
|
+
* (DOM `event.code` names, e.g. "ArrowLeft", "Space"). The loop is an `ActionOnTickAnimator`, ticked once per drawn
|
|
7
|
+
* frame; started before the game is attached it waits for the first layout, like C#.
|
|
8
|
+
* Not ported: `FrameTimeInterpolator` (C# disables it on BROWSER too, the raw frame delta is used).
|
|
9
|
+
*/
|
|
10
|
+
export declare class DrawnGame extends SkiaLayout {
|
|
11
|
+
private appLoop?;
|
|
12
|
+
protected LastFrameTimeNanos: number;
|
|
13
|
+
private isPaused;
|
|
14
|
+
private readonly onKeyDown;
|
|
15
|
+
private readonly onKeyChar;
|
|
16
|
+
private readonly onKeyUp;
|
|
17
|
+
constructor();
|
|
18
|
+
protected OnDisposing(): void;
|
|
19
|
+
protected OnResumed(): void;
|
|
20
|
+
protected OnPaused(): void;
|
|
21
|
+
/** Override this for your game. `deltaSeconds` is the time elapsed between the previous frame and this one. */
|
|
22
|
+
GameLoop(_deltaSeconds: number): void;
|
|
23
|
+
/** Stops the game loop. */
|
|
24
|
+
StopLoop(): void;
|
|
25
|
+
/** Starts the game loop. */
|
|
26
|
+
StartLoop(delayMs?: number): void;
|
|
27
|
+
/**
|
|
28
|
+
* Internal, override `GameLoop` for your game. Frame time is in nanoseconds. The first frame after StartLoop or
|
|
29
|
+
* Resume gets a delta of 0, not the time since the clock's origin or since the stop (DrawnUi.Rust game_loop).
|
|
30
|
+
*/
|
|
31
|
+
protected GameTick(frameTimeNanos: number): void;
|
|
32
|
+
get IsPaused(): boolean;
|
|
33
|
+
set IsPaused(value: boolean);
|
|
34
|
+
Pause(): void;
|
|
35
|
+
Resume(): void;
|
|
36
|
+
/** Override this to process game keys. */
|
|
37
|
+
OnKeyDown(_key: InputKey): void;
|
|
38
|
+
/** Override this to process game keys. */
|
|
39
|
+
OnKeyUp(_key: InputKey): void;
|
|
40
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ActionOnTickAnimator } from "../core/Animators";
|
|
2
|
+
import { KeyboardManager } from "../core/KeyboardManager";
|
|
3
|
+
import { SkiaLayout } from "./SkiaLayout";
|
|
4
|
+
/**
|
|
5
|
+
* Mirrors DrawnUi.Gaming DrawnGame (SharedGame/DrawnGame.cs + Game.Input.cs): a layout that runs a game loop.
|
|
6
|
+
* `StartLoop()` / `StopLoop()`, override `GameLoop(deltaSeconds)` for the game and `OnKeyDown` / `OnKeyUp` for keys
|
|
7
|
+
* (DOM `event.code` names, e.g. "ArrowLeft", "Space"). The loop is an `ActionOnTickAnimator`, ticked once per drawn
|
|
8
|
+
* frame; started before the game is attached it waits for the first layout, like C#.
|
|
9
|
+
* Not ported: `FrameTimeInterpolator` (C# disables it on BROWSER too, the raw frame delta is used).
|
|
10
|
+
*/
|
|
11
|
+
export class DrawnGame extends SkiaLayout {
|
|
12
|
+
appLoop;
|
|
13
|
+
LastFrameTimeNanos = 0;
|
|
14
|
+
isPaused = false;
|
|
15
|
+
onKeyDown = (key) => this.OnKeyDown(key);
|
|
16
|
+
onKeyChar = () => { };
|
|
17
|
+
onKeyUp = (key) => this.OnKeyUp(key);
|
|
18
|
+
constructor() {
|
|
19
|
+
super();
|
|
20
|
+
KeyboardManager.Subscribe(this.onKeyDown, this.onKeyChar, this.onKeyUp);
|
|
21
|
+
}
|
|
22
|
+
OnDisposing() {
|
|
23
|
+
KeyboardManager.Unsubscribe(this.onKeyDown, this.onKeyChar, this.onKeyUp);
|
|
24
|
+
this.appLoop?.Dispose();
|
|
25
|
+
this.appLoop = undefined;
|
|
26
|
+
super.OnDisposing();
|
|
27
|
+
}
|
|
28
|
+
OnResumed() { }
|
|
29
|
+
OnPaused() { }
|
|
30
|
+
/** Override this for your game. `deltaSeconds` is the time elapsed between the previous frame and this one. */
|
|
31
|
+
GameLoop(_deltaSeconds) { }
|
|
32
|
+
/** Stops the game loop. */
|
|
33
|
+
StopLoop() { this.appLoop?.Stop(); }
|
|
34
|
+
/** Starts the game loop. */
|
|
35
|
+
StartLoop(delayMs = 0) {
|
|
36
|
+
this.appLoop ??= new ActionOnTickAnimator(this, (t) => this.GameTick(t));
|
|
37
|
+
this.LastFrameTimeNanos = 0; // the first frame of a (re)started loop has no previous one: its delta is 0
|
|
38
|
+
this.appLoop.Start(delayMs);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Internal, override `GameLoop` for your game. Frame time is in nanoseconds. The first frame after StartLoop or
|
|
42
|
+
* Resume gets a delta of 0, not the time since the clock's origin or since the stop (DrawnUi.Rust game_loop).
|
|
43
|
+
*/
|
|
44
|
+
GameTick(frameTimeNanos) {
|
|
45
|
+
const deltaSeconds = this.LastFrameTimeNanos === 0 ? 0 : (frameTimeNanos - this.LastFrameTimeNanos) / 1_000_000_000;
|
|
46
|
+
this.LastFrameTimeNanos = frameTimeNanos;
|
|
47
|
+
this.GameLoop(deltaSeconds);
|
|
48
|
+
}
|
|
49
|
+
get IsPaused() { return this.isPaused; }
|
|
50
|
+
set IsPaused(value) { this.isPaused = value; }
|
|
51
|
+
Pause() {
|
|
52
|
+
this.IsPaused = true;
|
|
53
|
+
this.OnPaused();
|
|
54
|
+
}
|
|
55
|
+
Resume() {
|
|
56
|
+
this.LastFrameTimeNanos = 0; // the frame clock restarts: the next delta is 0
|
|
57
|
+
this.IsPaused = false;
|
|
58
|
+
this.OnResumed();
|
|
59
|
+
}
|
|
60
|
+
/** Override this to process game keys. */
|
|
61
|
+
OnKeyDown(_key) { }
|
|
62
|
+
/** Override this to process game keys. */
|
|
63
|
+
OnKeyUp(_key) { }
|
|
64
|
+
}
|
|
@@ -16,6 +16,8 @@ export declare class DefinitionInfo {
|
|
|
16
16
|
}
|
|
17
17
|
/** Parses "*, 2*, Auto, 100" (or an array of the same values) into tracks. */
|
|
18
18
|
export declare function ParseDefinitions(defs: string | GridLength[] | undefined): DefinitionInfo[];
|
|
19
|
+
/** What the structure reads from a child: a control, or the slot of a recycled templated item (measured on a pooled view). */
|
|
20
|
+
export type GridChild = Pick<SkiaControl, "IsVisible" | "Row" | "Column" | "RowSpan" | "ColumnSpan" | "HorizontalOptions" | "VerticalOptions" | "MinimumWidthRequest" | "MinimumHeightRequest" | "HeightRequest" | "Margin" | "MeasuredSize" | "Measure">;
|
|
19
21
|
/**
|
|
20
22
|
* Port of DrawnUi SkiaGridStructure (itself adapted from the MAUI Grid manager). Works in POINTS: constraints come in
|
|
21
23
|
* already reduced by the grid's padding, cells are returned relative to the padded box. Children are measured in
|
|
@@ -35,7 +37,7 @@ export declare class SkiaGridStructure {
|
|
|
35
37
|
private readonly spans;
|
|
36
38
|
private readonly explicitWidth;
|
|
37
39
|
private readonly explicitHeight;
|
|
38
|
-
constructor(grid: SkiaLayout, widthConstraint: number, heightConstraint: number, scale: number);
|
|
40
|
+
constructor(grid: SkiaLayout, widthConstraint: number, heightConstraint: number, scale: number, children?: readonly GridChild[]);
|
|
39
41
|
private InitializeTracks;
|
|
40
42
|
private static CreateDefinitionIfMissing;
|
|
41
43
|
private static ToLengthType;
|
|
@@ -46,7 +48,7 @@ export declare class SkiaGridStructure {
|
|
|
46
48
|
LeftEdgeOfColumn(column: number): number;
|
|
47
49
|
TopEdgeOfRow(row: number): number;
|
|
48
50
|
/** Cell rectangle in points relative to the padded box (+ offsets). */
|
|
49
|
-
GetCellBoundsFor(view:
|
|
51
|
+
GetCellBoundsFor(view: GridChild, xOffset?: number, yOffset?: number): {
|
|
50
52
|
Left: number;
|
|
51
53
|
Top: number;
|
|
52
54
|
Width: number;
|
|
@@ -63,6 +65,18 @@ export declare class SkiaGridStructure {
|
|
|
63
65
|
private ResolveStars;
|
|
64
66
|
private ResolveStarColumns;
|
|
65
67
|
private ResolveStarRows;
|
|
68
|
+
/**
|
|
69
|
+
* C# SizesAutoRow: the child decides the height of its single Auto row, so it is measured with the height that row
|
|
70
|
+
* could still grow to, not with the row's current size (drawnui-cross 6p).
|
|
71
|
+
*/
|
|
72
|
+
private SizesAutoRow;
|
|
73
|
+
private static NeedFillY;
|
|
74
|
+
/**
|
|
75
|
+
* C# HeightOnAutoRow: the height the single Auto row could still grow to, unbounded for a vertical Fill child (which
|
|
76
|
+
* would take the whole constraint). The row's size so far came from a first pass at the grid's whole width, so a
|
|
77
|
+
* label wrapping in a star column was measured against its one-line height.
|
|
78
|
+
*/
|
|
79
|
+
private HeightOnAutoRow;
|
|
66
80
|
private MeasureKnownCells;
|
|
67
81
|
private AvailableWidth;
|
|
68
82
|
private AvailableHeight;
|