drawnui-react 0.1.0-preview.15 → 0.1.0-preview.17
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 +1 -1
- package/SKIPPED.md +9 -8
- package/dist/controls/AnimatedFramesRenderer.d.ts +7 -0
- package/dist/controls/AnimatedFramesRenderer.js +16 -1
- package/dist/controls/DrawnGame.d.ts +4 -1
- package/dist/controls/DrawnGame.js +7 -3
- package/dist/controls/GridStructure.d.ts +13 -1
- package/dist/controls/GridStructure.js +38 -2
- package/dist/controls/SkiaBackdrop.d.ts +2 -0
- package/dist/controls/SkiaBackdrop.js +2 -0
- package/dist/controls/SkiaButton.d.ts +2 -0
- package/dist/controls/SkiaButton.js +2 -0
- package/dist/controls/SkiaCarousel.d.ts +3 -1
- package/dist/controls/SkiaCarousel.js +5 -2
- package/dist/controls/SkiaDrawer.d.ts +2 -0
- package/dist/controls/SkiaDrawer.js +2 -0
- package/dist/controls/SkiaEditor.d.ts +5 -1
- package/dist/controls/SkiaEditor.js +29 -18
- package/dist/controls/SkiaGif.d.ts +3 -0
- package/dist/controls/SkiaGif.js +11 -1
- 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/SkiaLayout.d.ts +20 -0
- package/dist/controls/SkiaLayout.js +78 -6
- package/dist/controls/SkiaLottie.js +3 -1
- package/dist/controls/SkiaProgress.d.ts +2 -1
- package/dist/controls/SkiaProgress.js +6 -3
- package/dist/controls/SkiaScroll.d.ts +16 -0
- package/dist/controls/SkiaScroll.js +85 -8
- package/dist/controls/SkiaShape.d.ts +13 -2
- package/dist/controls/SkiaShape.js +50 -10
- package/dist/controls/SkiaSlider.d.ts +7 -1
- package/dist/controls/SkiaSlider.js +12 -4
- package/dist/controls/SkiaSprite.d.ts +1 -0
- package/dist/controls/SkiaSprite.js +4 -0
- package/dist/controls/SkiaToggle.d.ts +2 -0
- package/dist/controls/SkiaToggle.js +3 -1
- package/dist/controls/SnappingLayout.d.ts +7 -0
- package/dist/controls/SnappingLayout.js +11 -0
- package/dist/core/Accessibility.d.ts +21 -0
- package/dist/core/Accessibility.js +66 -9
- package/dist/core/Animators.d.ts +28 -1
- package/dist/core/Animators.js +76 -8
- package/dist/core/Canvas.d.ts +26 -4
- package/dist/core/Canvas.js +136 -23
- package/dist/core/Easing.d.ts +12 -1
- package/dist/core/Easing.js +25 -1
- package/dist/core/Gestures.d.ts +24 -1
- package/dist/core/Gestures.js +75 -2
- 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 +97 -1
- package/dist/core/SkiaControl.js +271 -38
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/react/SkiaShell.js +33 -14
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +30 -3
- package/dist/react/reconciler.js +29 -10
- package/package.json +18 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ await Super.UseDrawnUi()
|
|
|
27
27
|
## Install
|
|
28
28
|
|
|
29
29
|
```
|
|
30
|
-
npm i drawnui-react
|
|
30
|
+
npm i drawnui-react react react-dom
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
`drawnui-react` = React tags + every engine type, `drawnui-react/core` = the engine only. Ships ES modules + `.d.ts`;
|
package/SKIPPED.md
CHANGED
|
@@ -10,14 +10,15 @@ DrawnUi name and semantics; nothing here is a redesign, only an omission.
|
|
|
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
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: `LongPressing
|
|
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
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: `RotationX/Y/Z`, `TranslationZ`, `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`; `
|
|
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. |
|
|
19
19
|
| Disposal (`Dispose` / `OnDisposing`) | ported | The React renderer calls `Dispose()` when an element leaves the tree (`detachDeletedInstance`): caches, gradient shaders, running `*ToAsync` animations, overlay effects, the accessibility node, then the children (static and templated pool); `SkiaScroll`/`SnappingLayout` stop their animators, `SkiaSvg` drops its raster, `SkiaLottie`/`SkiaGif` free the Skottie animation / frames; `Canvas.Dispose` disposes the content. Not ported: `IsDisposing` staged disposal, `Disposing` event, `OnWillDisposeWithChildren`. |
|
|
20
|
-
| `
|
|
20
|
+
| Hover: `ReceivesHover` (opt-in; on by default in SkiaButton, SkiaSlider, the toggles, SkiaRadioButton, SkiaCarousel, SkiaDrawer), `IsHovered`, `HoverChanged` (turns `ReceivesHover` on, as C# `OnHovered`) | ported | drawnui-cross 6m: every opted-in control under the mouse is hovered; nothing changes while a scroll, carousel or drawer moves its content, one check at the last pointer position when it stops and after an add / remove, a rebind, a hidden hovered control or an instant scroll jump; pointerleave clears at once; touch never hovers (`Canvas.Hover`, a `HoverManager`). Not ported: C# `SetHover`, `CheckHovered` / `OnHover` / `IsPointerOver` (no Pointer gestures are routed), SkiaSpinner / wheel picker (no such controls). |
|
|
21
|
+
| `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). |
|
|
21
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). |
|
|
@@ -61,7 +62,7 @@ DrawnUi name and semantics; nothing here is a redesign, only an omission.
|
|
|
61
62
|
|
|
62
63
|
| Control | Status | Notes |
|
|
63
64
|
|---|---|---|
|
|
64
|
-
| `SkiaLayout` Absolute / Column / Row (+ `SkiaStack`, `SkiaRow`, `SkiaLayer`) | ported | `Spacing`, `Padding`, `Children`/`Views`, `AddSubView`/`RemoveSubView`/`InsertSubView`. |
|
|
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. |
|
|
65
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. |
|
|
66
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`. |
|
|
67
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`. |
|
|
@@ -70,18 +71,18 @@ DrawnUi name and semantics; nothing here is a redesign, only an omission.
|
|
|
70
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). |
|
|
71
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. |
|
|
72
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. |
|
|
73
|
-
| `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. |
|
|
74
75
|
| `SkiaHotspot` | ported | Fill/Fill, `Tapped`, `Down`, `Up`, `LockPanning`, `TouchDown`; consumes only Tapped like the C# one. No `AnimationTapped`/ripple/shimmer. |
|
|
75
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`/`LockPanning`, `Tapped`/`Down`/`Up`. `IsPressed` is tracked but has no visual; press feedback = `ApplyEffect="Ripple"`. No `ButtonStyle` platform looks, icons, `TextCase`, elevation, shimmer, `BtnText`/`BtnShape` templating. |
|
|
76
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. |
|
|
77
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). |
|
|
78
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. |
|
|
79
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). |
|
|
80
|
-
| `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`.
|
|
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. |
|
|
81
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. |
|
|
82
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. |
|
|
83
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. |
|
|
84
|
-
| `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`. |
|
|
85
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. |
|
|
86
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). |
|
|
87
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. |
|
|
@@ -104,7 +105,7 @@ DrawnUi name and semantics; nothing here is a redesign, only an omission.
|
|
|
104
105
|
| Custom `react-reconciler` renderer, PascalCase props mapped 1:1 to control properties | ported | |
|
|
105
106
|
| `SkiaToggle` / `SkiaSwitch` / `SkiaCheckbox` / `SkiaRadioButton` | ported | Same names: `IsToggled`, `Toggled`, `DefaultValue`, `IsAnimated`, `RespondsToGestures`, `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. |
|
|
106
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. |
|
|
107
|
-
| `SkiaProgress` / `SkiaSlider` | ported (painted, not composed) | `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. |
|
|
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. |
|
|
108
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. |
|
|
109
110
|
| `SkiaButton.FontFamilyFallback` | React extension | Pass-through to the inner label so icon-glyph buttons render with `AddSymbols()` faces. |
|
|
110
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. |
|
|
@@ -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. */
|
|
@@ -24,7 +24,10 @@ export declare class DrawnGame extends SkiaLayout {
|
|
|
24
24
|
StopLoop(): void;
|
|
25
25
|
/** Starts the game loop. */
|
|
26
26
|
StartLoop(delayMs?: number): void;
|
|
27
|
-
/**
|
|
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
|
+
*/
|
|
28
31
|
protected GameTick(frameTimeNanos: number): void;
|
|
29
32
|
get IsPaused(): boolean;
|
|
30
33
|
set IsPaused(value: boolean);
|
|
@@ -34,11 +34,15 @@ export class DrawnGame extends SkiaLayout {
|
|
|
34
34
|
/** Starts the game loop. */
|
|
35
35
|
StartLoop(delayMs = 0) {
|
|
36
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
|
|
37
38
|
this.appLoop.Start(delayMs);
|
|
38
39
|
}
|
|
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
|
+
*/
|
|
40
44
|
GameTick(frameTimeNanos) {
|
|
41
|
-
const deltaSeconds = (frameTimeNanos - this.LastFrameTimeNanos) / 1_000_000_000;
|
|
45
|
+
const deltaSeconds = this.LastFrameTimeNanos === 0 ? 0 : (frameTimeNanos - this.LastFrameTimeNanos) / 1_000_000_000;
|
|
42
46
|
this.LastFrameTimeNanos = frameTimeNanos;
|
|
43
47
|
this.GameLoop(deltaSeconds);
|
|
44
48
|
}
|
|
@@ -49,7 +53,7 @@ export class DrawnGame extends SkiaLayout {
|
|
|
49
53
|
this.OnPaused();
|
|
50
54
|
}
|
|
51
55
|
Resume() {
|
|
52
|
-
this.LastFrameTimeNanos =
|
|
56
|
+
this.LastFrameTimeNanos = 0; // the frame clock restarts: the next delta is 0
|
|
53
57
|
this.IsPaused = false;
|
|
54
58
|
this.OnResumed();
|
|
55
59
|
}
|
|
@@ -17,7 +17,7 @@ export declare class DefinitionInfo {
|
|
|
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
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" | "Margin" | "MeasuredSize" | "Measure">;
|
|
20
|
+
export type GridChild = Pick<SkiaControl, "IsVisible" | "Row" | "Column" | "RowSpan" | "ColumnSpan" | "HorizontalOptions" | "VerticalOptions" | "MinimumWidthRequest" | "MinimumHeightRequest" | "HeightRequest" | "Margin" | "MeasuredSize" | "Measure">;
|
|
21
21
|
/**
|
|
22
22
|
* Port of DrawnUi SkiaGridStructure (itself adapted from the MAUI Grid manager). Works in POINTS: constraints come in
|
|
23
23
|
* already reduced by the grid's padding, cells are returned relative to the padded box. Children are measured in
|
|
@@ -65,6 +65,18 @@ export declare class SkiaGridStructure {
|
|
|
65
65
|
private ResolveStars;
|
|
66
66
|
private ResolveStarColumns;
|
|
67
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;
|
|
68
80
|
private MeasureKnownCells;
|
|
69
81
|
private AvailableWidth;
|
|
70
82
|
private AvailableHeight;
|
|
@@ -294,7 +294,20 @@ export class SkiaGridStructure {
|
|
|
294
294
|
ResolveStarRows(heightConstraint) {
|
|
295
295
|
this.ResolveStars(this.Rows, heightConstraint - this.GridHeight(), (c) => c.IsRowSpanStar, (v) => v.MeasuredSize.Units.Height);
|
|
296
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* C# SizesAutoRow: the child decides the height of its single Auto row, so it is measured with the height that row
|
|
299
|
+
* could still grow to, not with the row's current size (drawnui-cross 6p).
|
|
300
|
+
*/
|
|
301
|
+
SizesAutoRow(cell) { return cell.RowSpan === 1 && this.Rows[cell.Row].IsAuto; }
|
|
302
|
+
static NeedFillY(child) { return child.VerticalOptions === "Fill" && child.HeightRequest < 0; }
|
|
303
|
+
/**
|
|
304
|
+
* C# HeightOnAutoRow: the height the single Auto row could still grow to, unbounded for a vertical Fill child (which
|
|
305
|
+
* would take the whole constraint). The row's size so far came from a first pass at the grid's whole width, so a
|
|
306
|
+
* label wrapping in a star column was measured against its one-line height.
|
|
307
|
+
*/
|
|
308
|
+
HeightOnAutoRow(cell, child) { return SkiaGridStructure.NeedFillY(child) ? Infinity : this.AvailableHeight(cell); }
|
|
297
309
|
MeasureKnownCells() {
|
|
310
|
+
let autoRowGrew = false;
|
|
298
311
|
for (const cell of this.cells) {
|
|
299
312
|
if (!cell.NeedsKnownMeasurePass)
|
|
300
313
|
continue;
|
|
@@ -307,12 +320,30 @@ export class SkiaGridStructure {
|
|
|
307
320
|
continue;
|
|
308
321
|
const child = this.children[cell.ViewIndex];
|
|
309
322
|
const rect = this.GetCellBoundsFor(child);
|
|
310
|
-
|
|
323
|
+
if (child.IsVisible && this.SizesAutoRow(cell)) {
|
|
324
|
+
// the row grows to the child at its real width, within the room the grid has left
|
|
325
|
+
let grown = this.MeasurePx(child, rect.Width, this.HeightOnAutoRow(cell, child)).Height;
|
|
326
|
+
const available = this.AvailableHeight(cell);
|
|
327
|
+
if (isFinite(available) && grown > available)
|
|
328
|
+
grown = available;
|
|
329
|
+
if (grown > this.Rows[cell.Row].Size) {
|
|
330
|
+
this.Rows[cell.Row].Update(grown);
|
|
331
|
+
autoRowGrew = true;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
else
|
|
335
|
+
this.MeasurePx(child, rect.Width, rect.Height);
|
|
311
336
|
if (cell.IsColumnSpanStar && cell.ColumnSpan > 1)
|
|
312
337
|
this.TrackSpan({ Key: `c${cell.Column}:${cell.ColumnSpan}`, Start: cell.Column, Length: cell.ColumnSpan, IsColumn: true, Requested: rect.Width });
|
|
313
338
|
if (cell.IsRowSpanStar && cell.RowSpan > 1)
|
|
314
339
|
this.TrackSpan({ Key: `r${cell.Row}:${cell.RowSpan}`, Start: cell.Row, Length: cell.RowSpan, IsColumn: false, Requested: rect.Height });
|
|
315
340
|
}
|
|
341
|
+
if (autoRowGrew) {
|
|
342
|
+
// star rows were resolved against the smaller Auto rows: they get what is left now, nothing when Auto took it all
|
|
343
|
+
SkiaGridStructure.ZeroOutStars(this.Rows);
|
|
344
|
+
if (this.heightConstraint - this.GridHeight() > 0)
|
|
345
|
+
this.ResolveStarRows(this.heightConstraint);
|
|
346
|
+
}
|
|
316
347
|
}
|
|
317
348
|
AvailableWidth(cell) {
|
|
318
349
|
let w = 0, absolute = true;
|
|
@@ -390,10 +421,15 @@ export class SkiaGridStructure {
|
|
|
390
421
|
RemeasureChildrenAtFinalCells() {
|
|
391
422
|
for (const cell of this.cells) {
|
|
392
423
|
const child = this.children[cell.ViewIndex];
|
|
424
|
+
if (!child.IsVisible)
|
|
425
|
+
continue;
|
|
393
426
|
const rect = this.GetCellBoundsFor(child);
|
|
394
427
|
if (rect.Width <= 0 || rect.Height <= 0)
|
|
395
428
|
continue;
|
|
396
|
-
|
|
429
|
+
// on an Auto row a child that is not Fill vertically keeps the height the row can grow to, as in
|
|
430
|
+
// MeasureKnownCells: the same request returns at once, and wrapped text is not cut to the row
|
|
431
|
+
const height = this.SizesAutoRow(cell) && !SkiaGridStructure.NeedFillY(child) ? Math.max(rect.Height, this.AvailableHeight(cell)) : rect.Height;
|
|
432
|
+
const m = this.MeasurePx(child, rect.Width, height);
|
|
397
433
|
if (cell.ColumnSpan === 1 && this.Columns[cell.Column].IsAuto && m.Width > this.Columns[cell.Column].Size)
|
|
398
434
|
this.Columns[cell.Column].Update(m.Width);
|
|
399
435
|
if (cell.RowSpan === 1 && this.Rows[cell.Row].IsAuto && m.Height > this.Rows[cell.Row].Size)
|
|
@@ -8,6 +8,8 @@ import { SkiaLayout } from "./SkiaLayout";
|
|
|
8
8
|
* on every frame it is drawn in; inside a cached parent it is re-recorded with that parent).
|
|
9
9
|
*/
|
|
10
10
|
export declare class SkiaBackdrop extends SkiaLayout {
|
|
11
|
+
/** Samples what is drawn below: a composite above redraws it whole when something inside changes (drawnui-cross 6m). */
|
|
12
|
+
protected get SamplesContent(): boolean;
|
|
11
13
|
Blur: number;
|
|
12
14
|
Brightness: number;
|
|
13
15
|
/** Snapshot the surface currently drawn into (true) or the on-screen surface (false). */
|
|
@@ -8,6 +8,8 @@ import { SkiaLayout } from "./SkiaLayout";
|
|
|
8
8
|
* on every frame it is drawn in; inside a cached parent it is re-recorded with that parent).
|
|
9
9
|
*/
|
|
10
10
|
export class SkiaBackdrop extends SkiaLayout {
|
|
11
|
+
/** Samples what is drawn below: a composite above redraws it whole when something inside changes (drawnui-cross 6m). */
|
|
12
|
+
get SamplesContent() { return true; }
|
|
11
13
|
Blur = 5;
|
|
12
14
|
Brightness = 1;
|
|
13
15
|
/** Snapshot the surface currently drawn into (true) or the on-screen surface (false). */
|
|
@@ -10,6 +10,8 @@ import { type PrebuiltControlStyle, type ResolvedControlStyle } from "../core/Co
|
|
|
10
10
|
* PanThreshold. Press feedback = ApplyEffect (ripple), no darkening.
|
|
11
11
|
*/
|
|
12
12
|
export declare class SkiaButton extends SkiaLayout {
|
|
13
|
+
/** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault). */
|
|
14
|
+
protected ReceivesHoverByDefault(): boolean;
|
|
13
15
|
static PanThreshold: number;
|
|
14
16
|
Text: string;
|
|
15
17
|
/** Look of the default content (C# SkiaButton.ControlStyle): background, corners, font, minimum size when left unset. */
|
|
@@ -10,6 +10,8 @@ import { ResolveControlStyle } from "../core/ControlStyle";
|
|
|
10
10
|
* PanThreshold. Press feedback = ApplyEffect (ripple), no darkening.
|
|
11
11
|
*/
|
|
12
12
|
export class SkiaButton extends SkiaLayout {
|
|
13
|
+
/** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault). */
|
|
14
|
+
ReceivesHoverByDefault() { return true; }
|
|
13
15
|
static PanThreshold = 5;
|
|
14
16
|
Text = "";
|
|
15
17
|
/** Look of the default content (C# SkiaButton.ControlStyle): background, corners, font, minimum size when left unset. */
|
|
@@ -15,6 +15,8 @@ export interface SnapPoint {
|
|
|
15
15
|
* `DynamicSize` sizes an auto-sized carousel from the selected slide, `SwipeSpeed` / `LinearSpeedMs` tune the snap.
|
|
16
16
|
*/
|
|
17
17
|
export declare class SkiaCarousel extends SnappingLayout {
|
|
18
|
+
/** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault). */
|
|
19
|
+
protected ReceivesHoverByDefault(): boolean;
|
|
18
20
|
IsVertical: boolean;
|
|
19
21
|
/** Side padding in points so the previous/next slides peek in. */
|
|
20
22
|
SidesOffset: number;
|
|
@@ -99,7 +101,7 @@ export declare class SkiaCarousel extends SnappingLayout {
|
|
|
99
101
|
private GetVirtualSnapPoints;
|
|
100
102
|
protected GetVirtualAnchor(current: SKPoint): SnapPoint;
|
|
101
103
|
protected FindNearestAnchorInternal(current: SKPoint, velocity: SKPoint): SKPoint;
|
|
102
|
-
/** C# SkiaCarousel.ScrollToNearestAnchor: velocity below
|
|
104
|
+
/** C# SkiaCarousel.ScrollToNearestAnchor: velocity below SnapVelocityThreshold counts as none; looped snaps around virtual anchors. */
|
|
103
105
|
ScrollToNearestAnchor(location: SKPoint, velocity: SKPoint): void;
|
|
104
106
|
/** C# SkiaCarousel.SelectNextAnchor: base choice, plus a wrap to the virtual anchor at the strip borders when looped. */
|
|
105
107
|
SelectNextAnchor(origin: SKPoint, velocity: SKPoint): SKPoint;
|
|
@@ -11,6 +11,8 @@ import { SnappingLayout } from "./SnappingLayout";
|
|
|
11
11
|
* `DynamicSize` sizes an auto-sized carousel from the selected slide, `SwipeSpeed` / `LinearSpeedMs` tune the snap.
|
|
12
12
|
*/
|
|
13
13
|
export class SkiaCarousel extends SnappingLayout {
|
|
14
|
+
/** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault). */
|
|
15
|
+
ReceivesHoverByDefault() { return true; }
|
|
14
16
|
IsVertical = false;
|
|
15
17
|
/** Side padding in points so the previous/next slides peek in. */
|
|
16
18
|
SidesOffset = 0;
|
|
@@ -230,9 +232,10 @@ export class SkiaCarousel extends SnappingLayout {
|
|
|
230
232
|
}
|
|
231
233
|
return super.FindNearestAnchorInternal(current, velocity);
|
|
232
234
|
}
|
|
233
|
-
/** C# SkiaCarousel.ScrollToNearestAnchor: velocity below
|
|
235
|
+
/** C# SkiaCarousel.ScrollToNearestAnchor: velocity below SnapVelocityThreshold counts as none; looped snaps around virtual anchors. */
|
|
234
236
|
ScrollToNearestAnchor(location, velocity) {
|
|
235
|
-
|
|
237
|
+
const t = this.SnapVelocityThreshold;
|
|
238
|
+
velocity = new SKPoint(Math.abs(velocity.X) < t ? 0 : velocity.X, Math.abs(velocity.Y) < t ? 0 : velocity.Y);
|
|
236
239
|
if (this.ApplyLoopedLogic) {
|
|
237
240
|
const origin = this.FindNearestAnchorInternal(location, velocity);
|
|
238
241
|
const target = this.SelectNextAnchor(origin, velocity);
|
|
@@ -9,6 +9,8 @@ import { SnappingLayout } from "./SnappingLayout";
|
|
|
9
9
|
* so it must be aligned to its edge by the parent (VerticalOptions="End" for FromBottom, etc.).
|
|
10
10
|
*/
|
|
11
11
|
export declare class SkiaDrawer extends SnappingLayout {
|
|
12
|
+
/** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault). */
|
|
13
|
+
protected ReceivesHoverByDefault(): boolean;
|
|
12
14
|
Direction: DrawerDirection;
|
|
13
15
|
/** Points that stay visible when closed. */
|
|
14
16
|
HeaderSize: number;
|
|
@@ -7,6 +7,8 @@ import { SnappingLayout } from "./SnappingLayout";
|
|
|
7
7
|
* so it must be aligned to its edge by the parent (VerticalOptions="End" for FromBottom, etc.).
|
|
8
8
|
*/
|
|
9
9
|
export class SkiaDrawer extends SnappingLayout {
|
|
10
|
+
/** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault). */
|
|
11
|
+
ReceivesHoverByDefault() { return true; }
|
|
10
12
|
Direction = "FromBottom";
|
|
11
13
|
/** Points that stay visible when closed. */
|
|
12
14
|
HeaderSize = 0;
|
|
@@ -58,7 +58,8 @@ export declare class SkiaEditor extends SkiaShape {
|
|
|
58
58
|
private readonly placeholder;
|
|
59
59
|
private scrollX;
|
|
60
60
|
private scrollY;
|
|
61
|
-
|
|
61
|
+
/** The caret blinks on a timer, asleep between blinks: no frame runs while it waits (DrawnUi.Rust editor_rules). */
|
|
62
|
+
private blinkTimer;
|
|
62
63
|
private caretVisible;
|
|
63
64
|
private subscribed;
|
|
64
65
|
private stubSelectionStop;
|
|
@@ -102,6 +103,9 @@ export declare class SkiaEditor extends SkiaShape {
|
|
|
102
103
|
set CursorPosition(v: number);
|
|
103
104
|
get SelectionLength(): number;
|
|
104
105
|
set SelectionLength(v: number);
|
|
106
|
+
/** Shows the caret for a full half second, then blinks every 500 ms. */
|
|
107
|
+
private StartBlink;
|
|
108
|
+
private StopBlink;
|
|
105
109
|
private OnCursorMoved;
|
|
106
110
|
get IsFocused(): boolean;
|
|
107
111
|
set IsFocused(v: boolean);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { SKPoint } from "../core/Gestures";
|
|
2
2
|
import { Super } from "../core/Super";
|
|
3
|
-
import { SkiaValueAnimator } from "../core/Animators";
|
|
4
3
|
import { ResolveControlStyle } from "../core/ControlStyle";
|
|
5
4
|
import { KeyboardManager } from "../core/KeyboardManager";
|
|
6
5
|
import { TextInputProxy } from "../core/TextInputProxy";
|
|
@@ -67,13 +66,19 @@ export class SkiaEditor extends SkiaShape {
|
|
|
67
66
|
placeholder = new SkiaLabel();
|
|
68
67
|
scrollX = 0;
|
|
69
68
|
scrollY = 0;
|
|
70
|
-
|
|
69
|
+
/** The caret blinks on a timer, asleep between blinks: no frame runs while it waits (DrawnUi.Rust editor_rules). */
|
|
70
|
+
blinkTimer = 0;
|
|
71
71
|
caretVisible = true;
|
|
72
72
|
subscribed = false;
|
|
73
73
|
stubSelectionStop = -1;
|
|
74
74
|
selectionMovingEdge = -1;
|
|
75
75
|
onKeyDown = (key, e) => this.OnKeyDown(key, e);
|
|
76
|
-
onKeyChar = (ch, e) => {
|
|
76
|
+
onKeyChar = (ch, e) => {
|
|
77
|
+
if (KeyboardManager.IsOwnedByElement(e) || KeyboardManager.IsInputMethodKey(e))
|
|
78
|
+
return; // a page control or the IME owns it
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
this.StubTypeText(ch);
|
|
81
|
+
};
|
|
77
82
|
selectionAnchor = -1;
|
|
78
83
|
dragSelecting = false;
|
|
79
84
|
styleApplied = {};
|
|
@@ -93,15 +98,6 @@ export class SkiaEditor extends SkiaShape {
|
|
|
93
98
|
this.placeholder.IsVisible = false;
|
|
94
99
|
this.AddSubView(this.placeholder);
|
|
95
100
|
this.AddSubView(this.Label);
|
|
96
|
-
this.blink.mMinValue = 0;
|
|
97
|
-
this.blink.mMaxValue = 1;
|
|
98
|
-
this.blink.Speed = 1000;
|
|
99
|
-
this.blink.Repeat = -1;
|
|
100
|
-
this.blink.OnUpdated = (v) => { const visible = v < 0.5; if (visible !== this.caretVisible) {
|
|
101
|
-
this.caretVisible = visible;
|
|
102
|
-
this.InvalidateCache();
|
|
103
|
-
this.RepaintComposition();
|
|
104
|
-
} };
|
|
105
101
|
this.ApplyControlStyleVisuals();
|
|
106
102
|
this.UpdateLabel();
|
|
107
103
|
}
|
|
@@ -192,9 +188,22 @@ export class SkiaEditor extends SkiaShape {
|
|
|
192
188
|
this.selectionLength = l;
|
|
193
189
|
this.OnCursorMoved();
|
|
194
190
|
}
|
|
191
|
+
/** Shows the caret for a full half second, then blinks every 500 ms. */
|
|
192
|
+
StartBlink() {
|
|
193
|
+
this.StopBlink();
|
|
194
|
+
this.caretVisible = true;
|
|
195
|
+
this.blinkTimer = window.setInterval(() => { this.caretVisible = !this.caretVisible; this.InvalidateCache(); this.RepaintComposition(); }, 500);
|
|
196
|
+
}
|
|
197
|
+
StopBlink() { if (this.blinkTimer) {
|
|
198
|
+
clearInterval(this.blinkTimer);
|
|
199
|
+
this.blinkTimer = 0;
|
|
200
|
+
} }
|
|
195
201
|
OnCursorMoved() {
|
|
196
202
|
this.ScrollToCaret();
|
|
197
|
-
this.
|
|
203
|
+
if (this.isFocused)
|
|
204
|
+
this.StartBlink();
|
|
205
|
+
else
|
|
206
|
+
this.caretVisible = true;
|
|
198
207
|
TextInputProxy.Sync(this);
|
|
199
208
|
this.CursorMoved?.(this);
|
|
200
209
|
this.InvalidateCache();
|
|
@@ -210,8 +219,7 @@ export class SkiaEditor extends SkiaShape {
|
|
|
210
219
|
SkiaEditor.Focused.IsFocused = false;
|
|
211
220
|
SkiaEditor.Focused = this;
|
|
212
221
|
this.SetFocusNative(true);
|
|
213
|
-
this.
|
|
214
|
-
this.blink.Start();
|
|
222
|
+
this.StartBlink();
|
|
215
223
|
}
|
|
216
224
|
else {
|
|
217
225
|
if (SkiaEditor.Focused === this)
|
|
@@ -220,7 +228,7 @@ export class SkiaEditor extends SkiaShape {
|
|
|
220
228
|
this.dragSelecting = false;
|
|
221
229
|
this.stubSelectionStop = -1;
|
|
222
230
|
this.selectionMovingEdge = -1;
|
|
223
|
-
this.
|
|
231
|
+
this.StopBlink();
|
|
224
232
|
this.SetFocusNative(false);
|
|
225
233
|
}
|
|
226
234
|
this.FocusChanged?.(this, v);
|
|
@@ -596,6 +604,10 @@ export class SkiaEditor extends SkiaShape {
|
|
|
596
604
|
OnKeyDown(key, e) {
|
|
597
605
|
const shift = KeyboardManager.IsShiftPressed, ctrl = KeyboardManager.IsControlPressed, alt = KeyboardManager.IsAltPressed;
|
|
598
606
|
const handled = () => e.preventDefault();
|
|
607
|
+
// keys of another element (a page field or button, an overlay node) and of the input method are not the editor's:
|
|
608
|
+
// a composition keeps its arrows, Escape and Backspace (C# 845b26e9, GitHub #231)
|
|
609
|
+
if (KeyboardManager.IsInputMethodKey(e) || (KeyboardManager.IsOwnedByElement(e) && !TextInputProxy.IsProxyTarget(e.target)))
|
|
610
|
+
return;
|
|
599
611
|
// with the hidden textarea focused these keys become input / clipboard events there and are applied from those
|
|
600
612
|
if (TextInputProxy.IsActive(this) && TextInputProxy.IsProxyTarget(e.target) && (key === "Backspace" || key === "Delete" || key === "Enter" || key === "NumpadEnter" || (ctrl && (key === "KeyC" || key === "KeyX" || key === "KeyV"))))
|
|
601
613
|
return;
|
|
@@ -810,8 +822,7 @@ export class SkiaEditor extends SkiaShape {
|
|
|
810
822
|
return out;
|
|
811
823
|
}
|
|
812
824
|
OnDisposing() {
|
|
813
|
-
this.
|
|
814
|
-
this.blink.Dispose();
|
|
825
|
+
this.StopBlink();
|
|
815
826
|
if (SkiaEditor.Focused === this)
|
|
816
827
|
SkiaEditor.Focused = undefined;
|
|
817
828
|
this.SetFocusNative(false);
|
|
@@ -14,6 +14,8 @@ export declare class GifAnimation {
|
|
|
14
14
|
private positionsMs;
|
|
15
15
|
/** Negative = the last frame. */
|
|
16
16
|
SeekFrame(frame: number): void;
|
|
17
|
+
/** Start (ms) of the frame after the one at `msTime`; DurationMs after the last one. */
|
|
18
|
+
NextFrameStartMs(msTime: number): number;
|
|
17
19
|
GetFrameNumber(msTime: number): number;
|
|
18
20
|
/** Decodes every frame of an encoded GIF (CanvasKit AnimatedImage). */
|
|
19
21
|
LoadFromBytes(bytes: ArrayBuffer): void;
|
|
@@ -37,6 +39,7 @@ export declare class SkiaGif extends AnimatedFramesRenderer {
|
|
|
37
39
|
set Source(v: string);
|
|
38
40
|
Start(delayMs?: number): void;
|
|
39
41
|
protected OnAnimatorUpdated(value: number): void;
|
|
42
|
+
protected NextChangeMs(value: number): number | undefined;
|
|
40
43
|
/** Seeks by time in ms (the animator range is 0..DurationMs). */
|
|
41
44
|
protected OnAnimatorSeeking(time: number): void;
|
|
42
45
|
protected OnAnimatorInitializing(): void;
|