drawnui-react 0.1.0-preview.14 → 0.1.0-preview.16

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.
Files changed (41) hide show
  1. package/SKIPPED.md +5 -4
  2. package/dist/controls/SkiaBackdrop.d.ts +2 -0
  3. package/dist/controls/SkiaBackdrop.js +2 -0
  4. package/dist/controls/SkiaButton.d.ts +2 -0
  5. package/dist/controls/SkiaButton.js +2 -0
  6. package/dist/controls/SkiaCarousel.d.ts +2 -0
  7. package/dist/controls/SkiaCarousel.js +2 -0
  8. package/dist/controls/SkiaDrawer.d.ts +2 -0
  9. package/dist/controls/SkiaDrawer.js +2 -0
  10. package/dist/controls/SkiaEditor.js +10 -1
  11. package/dist/controls/SkiaLabel.d.ts +2 -0
  12. package/dist/controls/SkiaLabel.js +13 -2
  13. package/dist/controls/SkiaLayout.d.ts +15 -0
  14. package/dist/controls/SkiaLayout.js +75 -6
  15. package/dist/controls/SkiaProgress.js +2 -2
  16. package/dist/controls/SkiaScroll.d.ts +4 -0
  17. package/dist/controls/SkiaScroll.js +9 -0
  18. package/dist/controls/SkiaSlider.d.ts +2 -0
  19. package/dist/controls/SkiaSlider.js +5 -3
  20. package/dist/controls/SkiaToggle.d.ts +2 -0
  21. package/dist/controls/SkiaToggle.js +3 -1
  22. package/dist/controls/SnappingLayout.d.ts +2 -0
  23. package/dist/controls/SnappingLayout.js +3 -0
  24. package/dist/core/Accessibility.d.ts +8 -0
  25. package/dist/core/Accessibility.js +56 -5
  26. package/dist/core/Canvas.d.ts +23 -0
  27. package/dist/core/Canvas.js +65 -3
  28. package/dist/core/Hover.d.ts +37 -0
  29. package/dist/core/Hover.js +118 -0
  30. package/dist/core/KeyboardManager.d.ts +13 -0
  31. package/dist/core/KeyboardManager.js +18 -0
  32. package/dist/core/SkiaControl.d.ts +87 -1
  33. package/dist/core/SkiaControl.js +229 -18
  34. package/dist/core/SkiaEffect.d.ts +3 -0
  35. package/dist/core/SkiaEffect.js +8 -3
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.js +1 -0
  38. package/dist/react/index.d.ts +1 -1
  39. package/dist/react/index.js +2 -2
  40. package/dist/react/reconciler.js +29 -10
  41. package/package.json +4 -1
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`, `Pointer` (hover), 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`). |
13
+ | Gestures: `LongPressing`, `Pointer` (routed as a gesture; hover itself is ported, see `ReceivesHover`), 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
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`; `UseCache=ImageDoubleBuffered` re-recorded per frame. GIF on CanvasKit AnimatedImage: every frame decoded once (`GifAnimation`: `SeekFrame`, `GetFrameNumber`, `DurationMs`, `TotalFrames`), `Aspect` (AspectFitFill), alignment, `Success`/`Error`. 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
- | `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`; `Subscribe` / `Unsubscribe` instead of C# events. Not ported: `InputKey` enum (codes are strings). |
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`. |
@@ -81,7 +82,7 @@ DrawnUi name and semantics; nothing here is a redesign, only an omission.
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. |
@@ -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;
@@ -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;
@@ -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;
@@ -73,7 +73,12 @@ export class SkiaEditor extends SkiaShape {
73
73
  stubSelectionStop = -1;
74
74
  selectionMovingEdge = -1;
75
75
  onKeyDown = (key, e) => this.OnKeyDown(key, e);
76
- onKeyChar = (ch, e) => { e.preventDefault(); this.StubTypeText(ch); };
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 = {};
@@ -596,6 +601,10 @@ export class SkiaEditor extends SkiaShape {
596
601
  OnKeyDown(key, e) {
597
602
  const shift = KeyboardManager.IsShiftPressed, ctrl = KeyboardManager.IsControlPressed, alt = KeyboardManager.IsAltPressed;
598
603
  const handled = () => e.preventDefault();
604
+ // keys of another element (a page field or button, an overlay node) and of the input method are not the editor's:
605
+ // a composition keeps its arrows, Escape and Backspace (C# 845b26e9, GitHub #231)
606
+ if (KeyboardManager.IsInputMethodKey(e) || (KeyboardManager.IsOwnedByElement(e) && !TextInputProxy.IsProxyTarget(e.target)))
607
+ return;
599
608
  // with the hidden textarea focused these keys become input / clipboard events there and are applied from those
600
609
  if (TextInputProxy.IsActive(this) && TextInputProxy.IsProxyTarget(e.target) && (key === "Backspace" || key === "Delete" || key === "Enter" || key === "NumpadEnter" || (ctrl && (key === "KeyC" || key === "KeyX" || key === "KeyV"))))
601
610
  return;
@@ -53,6 +53,8 @@ export declare class SkiaLabel extends SkiaControl {
53
53
  protected DefaultAccessibilityLabel(): string | undefined;
54
54
  constructor();
55
55
  private Set;
56
+ /** A property that changes only the look (a color): no layout change, see UpdateDraw. */
57
+ private SetLook;
56
58
  get Text(): string;
57
59
  set Text(v: string);
58
60
  get FontSize(): number;
@@ -43,12 +43,17 @@ export class SkiaLabel extends SkiaControl {
43
43
  this[key] = v;
44
44
  this.Update();
45
45
  } }
46
+ /** A property that changes only the look (a color): no layout change, see UpdateDraw. */
47
+ SetLook(key, v) { if (this[key] !== v) {
48
+ this[key] = v;
49
+ this.UpdateDraw();
50
+ } }
46
51
  get Text() { return this.text; }
47
52
  set Text(v) { this.Set("text", v); }
48
53
  get FontSize() { return this.fontSize; }
49
54
  set FontSize(v) { this.Set("fontSize", v); }
50
55
  get TextColor() { return this.textColor; }
51
- set TextColor(v) { this.Set("textColor", v); }
56
+ set TextColor(v) { this.SetLook("textColor", v); }
52
57
  get FontFamily() { return this.fontFamily; }
53
58
  set FontFamily(v) { this.Set("fontFamily", v); }
54
59
  /**
@@ -85,8 +90,14 @@ export class SkiaLabel extends SkiaControl {
85
90
  InsertSubView(index, control) {
86
91
  if (!(control instanceof TextSpan))
87
92
  throw new Error("DrawnUi: SkiaLabel children must be <TextSpan>");
93
+ const at = control.Parent === this ? this.Spans.indexOf(control) : -1; // already a span: a move, never a 2nd copy
94
+ if (at >= 0) {
95
+ this.Spans.splice(at, 1);
96
+ if (at < index)
97
+ index--;
98
+ }
88
99
  control.Parent = this;
89
- this.Spans.splice(index, 0, control);
100
+ this.Spans.splice(Math.min(Math.max(0, index), this.Spans.length), 0, control);
90
101
  this.Update();
91
102
  }
92
103
  RemoveSubView(control) {
@@ -112,7 +112,22 @@ export declare class SkiaLayout extends SkiaControl {
112
112
  get Views(): readonly SkiaControl[];
113
113
  /** Settable children list like DrawnUi Children (ignored while templated). */
114
114
  get Children(): readonly SkiaControl[];
115
+ /**
116
+ * C# Children reset (ChildrenCollectionSync, 66a1eb02): the app's children the new list no longer holds are removed,
117
+ * the ones it gained are added, then all of them take the list's order inside the slots app children occupy, so the
118
+ * parts a control made itself keep their place.
119
+ */
115
120
  set Children(value: readonly SkiaControl[]);
121
+ /**
122
+ * C# Insert / Move of a Children collection (the reconciler's appendChild / insertBefore): `child` goes before
123
+ * `before` among the app's children, or last without it. A new child is attached at the end of the views and an
124
+ * append stops there; otherwise the app's children take their new order inside the slots they occupy.
125
+ */
126
+ InsertChild(child: SkiaControl, before?: SkiaControl): void;
127
+ /** C# OrderViews: the given children, in their order, inside the slots they occupy in the views; nothing else moves. */
128
+ private OrderChildren;
129
+ /** C# ClearChildren: removes every subview, the control's own parts included. */
130
+ ClearChildren(): void;
116
131
  private orderedViews?;
117
132
  /** Static children sorted by ZIndex (stable), computed once per change (C# GetOrderedSubviews). */
118
133
  protected GetOrderedSubviews(): readonly SkiaControl[];
@@ -359,11 +359,67 @@ export class SkiaLayout extends SkiaControl {
359
359
  get Views() { return this.IsTemplated ? this.ChildrenFactory.GetViewsInUse() : this.views; }
360
360
  /** Settable children list like DrawnUi Children (ignored while templated). */
361
361
  get Children() { return this.views; }
362
+ /**
363
+ * C# Children reset (ChildrenCollectionSync, 66a1eb02): the app's children the new list no longer holds are removed,
364
+ * the ones it gained are added, then all of them take the list's order inside the slots app children occupy, so the
365
+ * parts a control made itself keep their place.
366
+ */
362
367
  set Children(value) {
368
+ const keep = new Set(value);
369
+ for (const v of [...this.views])
370
+ if (v.IsChildrenItem && !keep.has(v))
371
+ this.RemoveSubView(v);
372
+ const inViews = new Set(this.views);
373
+ for (const v of value) {
374
+ v.IsChildrenItem = true;
375
+ if (!inViews.has(v))
376
+ this.AddSubView(v);
377
+ }
378
+ this.OrderChildren(value);
379
+ }
380
+ /**
381
+ * C# Insert / Move of a Children collection (the reconciler's appendChild / insertBefore): `child` goes before
382
+ * `before` among the app's children, or last without it. A new child is attached at the end of the views and an
383
+ * append stops there; otherwise the app's children take their new order inside the slots they occupy.
384
+ */
385
+ InsertChild(child, before) {
386
+ child.IsChildrenItem = true;
387
+ const attached = child.Parent === this && this.views.includes(child);
388
+ if (!attached)
389
+ this.AddSubView(child);
390
+ if (!attached && !before)
391
+ return;
392
+ const order = this.views.filter((v) => v.IsChildrenItem && v !== child);
393
+ const at = before ? order.indexOf(before) : -1;
394
+ order.splice(at < 0 ? order.length : at, 0, child);
395
+ this.OrderChildren(order);
396
+ }
397
+ /** C# OrderViews: the given children, in their order, inside the slots they occupy in the views; nothing else moves. */
398
+ OrderChildren(ordered) {
399
+ const inViews = new Set(this.views);
400
+ const present = ordered.filter((v) => inViews.delete(v));
401
+ if (present.length < 2)
402
+ return;
403
+ const isChild = new Set(present);
404
+ let next = 0, changed = false;
405
+ for (let i = 0; i < this.views.length && next < present.length; i++) {
406
+ if (!isChild.has(this.views[i]))
407
+ continue;
408
+ if (this.views[i] !== present[next]) {
409
+ this.views[i] = present[next];
410
+ changed = true;
411
+ }
412
+ next++;
413
+ }
414
+ if (changed) {
415
+ this.orderedViews = undefined;
416
+ this.InvalidateMeasure();
417
+ }
418
+ }
419
+ /** C# ClearChildren: removes every subview, the control's own parts included. */
420
+ ClearChildren() {
363
421
  for (const v of [...this.views])
364
422
  this.RemoveSubView(v);
365
- for (const v of value)
366
- this.AddSubView(v);
367
423
  }
368
424
  orderedViews;
369
425
  /** Static children sorted by ZIndex (stable), computed once per change (C# GetOrderedSubviews). */
@@ -390,10 +446,18 @@ export class SkiaLayout extends SkiaControl {
390
446
  }
391
447
  AddSubView(control) { this.InsertSubView(this.views.length, control); }
392
448
  InsertSubView(index, control) {
449
+ // already a child: a move (React reorders keyed children by inserting an existing one elsewhere), never a 2nd copy
450
+ const at = control.Parent === this ? this.views.indexOf(control) : -1;
451
+ if (at >= 0) {
452
+ this.views.splice(at, 1);
453
+ if (at < index)
454
+ index--;
455
+ }
393
456
  control.Parent = this;
394
- this.views.splice(index, 0, control);
457
+ this.views.splice(Math.min(Math.max(0, index), this.views.length), 0, control);
395
458
  this.orderedViews = undefined;
396
459
  this.InvalidateMeasure();
460
+ this.Superview?.Hover?.RequestCheck(); // a popup / page under a still mouse
397
461
  }
398
462
  RemoveSubView(control) {
399
463
  const i = this.views.indexOf(control);
@@ -403,6 +467,7 @@ export class SkiaLayout extends SkiaControl {
403
467
  this.orderedViews = undefined;
404
468
  control.Parent = undefined;
405
469
  this.InvalidateMeasure();
470
+ this.Superview?.Hover?.RequestCheck();
406
471
  }
407
472
  // ---- measure ----
408
473
  MeasureAbsolute(widthConstraint, heightConstraint, scale) {
@@ -758,9 +823,13 @@ export class SkiaLayout extends SkiaControl {
758
823
  return;
759
824
  }
760
825
  const composing = this.IsRenderingWithComposition;
761
- for (const v of this.GetOrderedSubviews())
762
- if (!composing || this.DirtyChildrenInternal.has(v))
763
- v.Render(ctx);
826
+ // inside a composite that redraws only some areas, a child entirely outside them is not even traversed
827
+ const culling = !composing && SkiaControl.CompositionCulling > 0;
828
+ for (const v of this.GetOrderedSubviews()) {
829
+ if (composing ? !this.DirtyChildrenInternal.has(v) : culling && v.IsVisible && v.OutsideCompositionClip(ctx.Context.Canvas))
830
+ continue;
831
+ v.Render(ctx);
832
+ }
764
833
  }
765
834
  GetCompositeChildren() { return this.IsTemplatedList || this.IsRecycledLayout ? [] : this.GetOrderedSubviews(); }
766
835
  drawnSlots = new Set();
@@ -33,9 +33,9 @@ export class SkiaProgress extends SkiaControl {
33
33
  set Max(v) { this.max = v; this.Update(); }
34
34
  /** Unset = per-style palette. */
35
35
  get TrackColor() { return this.trackColor ?? SkiaProgress.Palette(this.UsingControlStyle).track; }
36
- set TrackColor(v) { this.trackColor = v; this.Update(); }
36
+ set TrackColor(v) { this.trackColor = v; this.UpdateDraw(); }
37
37
  get ProgressColor() { return this.progressColor ?? SkiaProgress.Palette(this.UsingControlStyle).progress; }
38
- set ProgressColor(v) { this.progressColor = v; this.Update(); }
38
+ set ProgressColor(v) { this.progressColor = v; this.UpdateDraw(); }
39
39
  /** C# ResolvedTrackColor / ResolvedProgressColor / track heights per style. */
40
40
  static Palette(s) {
41
41
  switch (s) {
@@ -156,6 +156,10 @@ export declare class SkiaScroll extends SkiaControl {
156
156
  set ViewportOffsetX(value: number);
157
157
  get ViewportOffsetY(): number;
158
158
  set ViewportOffsetY(value: number);
159
+ /** An offset set without a glide or a pan (a jump, a touchpad step): the controls under a still mouse changed. */
160
+ private OffsetJumped;
161
+ /** Hover waits while the content glides, flings, bounces or follows a pan (C# IsScrolling). */
162
+ MovesContent(): boolean;
159
163
  private readonly animatorFlingX;
160
164
  private readonly animatorFlingY;
161
165
  private readonly bounceX;
@@ -399,12 +399,19 @@ export class SkiaScroll extends SkiaControl {
399
399
  set ViewportOffsetX(value) { if (this.offsetX !== value) {
400
400
  this.offsetX = value;
401
401
  this.OnScrolled();
402
+ this.OffsetJumped();
402
403
  } }
403
404
  get ViewportOffsetY() { return this.offsetY; }
404
405
  set ViewportOffsetY(value) { if (this.offsetY !== value) {
405
406
  this.offsetY = value;
406
407
  this.OnScrolled();
408
+ this.OffsetJumped();
407
409
  } }
410
+ /** An offset set without a glide or a pan (a jump, a touchpad step): the controls under a still mouse changed. */
411
+ OffsetJumped() { if (!this.MovesContent())
412
+ this.Superview?.Hover?.RequestCheck(); }
413
+ /** Hover waits while the content glides, flings, bounces or follows a pan (C# IsScrolling). */
414
+ MovesContent() { return this.IsScrolling || this.IsUserPanning || !!this.draggedScrollBar; }
408
415
  animatorFlingX = new ScrollFlingAnimator(this);
409
416
  animatorFlingY = new ScrollFlingAnimator(this);
410
417
  bounceX = new SpringWithVelocityAnimator(this);
@@ -506,6 +513,7 @@ export class SkiaScroll extends SkiaControl {
506
513
  }
507
514
  InsertSubView(_index, control) { this.AddSubView(control); }
508
515
  RemoveSubView(control) {
516
+ this.Superview?.Hover?.RequestCheck();
509
517
  if (this.content === control)
510
518
  this.Content = undefined;
511
519
  else if (this.header === control)
@@ -626,6 +634,7 @@ export class SkiaScroll extends SkiaControl {
626
634
  }
627
635
  }
628
636
  Paint(ctx) {
637
+ this.Superview?.Hover?.RegisterMover(this);
629
638
  const c = this.content;
630
639
  if (!c)
631
640
  return;
@@ -8,6 +8,8 @@ import { type Color, ScaledSize, Thickness } from "../core/Types";
8
8
  * Track, selected trail and thumbs are painted directly; the C# thumb-position math (SliderHeight = thumb box) is kept.
9
9
  */
10
10
  export declare class SkiaSlider extends SkiaControl {
11
+ /** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault). */
12
+ protected ReceivesHoverByDefault(): boolean;
11
13
  static DefaultAccessibilityRole?: string;
12
14
  ControlStyle: PrebuiltControlStyle;
13
15
  Min: number;
@@ -9,6 +9,8 @@ import { SkiaShape } from "./SkiaShape";
9
9
  * Track, selected trail and thumbs are painted directly; the C# thumb-position math (SliderHeight = thumb box) is kept.
10
10
  */
11
11
  export class SkiaSlider extends SkiaControl {
12
+ /** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault). */
13
+ ReceivesHoverByDefault() { return true; }
12
14
  static DefaultAccessibilityRole = "slider";
13
15
  ControlStyle = "Unset";
14
16
  Min = 0;
@@ -60,11 +62,11 @@ export class SkiaSlider extends SkiaControl {
60
62
  get SliderHeight() { return this.sliderHeight ?? this.Look().thumb + (this.EnableRange && this.UsingControlStyle !== "Unset" ? 8 : 0); }
61
63
  set SliderHeight(v) { this.sliderHeight = v; this.Update(); }
62
64
  get ThumbColor() { return this.thumbColor ?? this.Look().thumbColor; }
63
- set ThumbColor(v) { this.thumbColor = v; this.Update(); }
65
+ set ThumbColor(v) { this.thumbColor = v; this.UpdateDraw(); }
64
66
  get TrackColor() { return this.trackColor ?? this.Look().track; }
65
- set TrackColor(v) { this.trackColor = v; this.Update(); }
67
+ set TrackColor(v) { this.trackColor = v; this.UpdateDraw(); }
66
68
  get TrackSelectedColor() { return this.trackSelectedColor ?? this.Look().selected; }
67
- set TrackSelectedColor(v) { this.trackSelectedColor = v; this.Update(); }
69
+ set TrackSelectedColor(v) { this.trackSelectedColor = v; this.UpdateDraw(); }
68
70
  /** C# style builders: thumb diameter, track height, palette, thumb shadow. */
69
71
  Look() {
70
72
  switch (this.UsingControlStyle) {
@@ -10,6 +10,8 @@ import { SkiaLayout } from "./SkiaLayout";
10
10
  * take the style defaults (C# SetStyleDefault).
11
11
  */
12
12
  export declare abstract class SkiaToggle extends SkiaLayout {
13
+ /** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault): switch, checkbox, radio button. */
14
+ protected ReceivesHoverByDefault(): boolean;
13
15
  private controlStyle;
14
16
  get ControlStyle(): PrebuiltControlStyle;
15
17
  set ControlStyle(v: PrebuiltControlStyle);
@@ -8,6 +8,8 @@ import { SkiaLayout } from "./SkiaLayout";
8
8
  * take the style defaults (C# SetStyleDefault).
9
9
  */
10
10
  export class SkiaToggle extends SkiaLayout {
11
+ /** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault): switch, checkbox, radio button. */
12
+ ReceivesHoverByDefault() { return true; }
11
13
  controlStyle = "Unset";
12
14
  get ControlStyle() { return this.controlStyle; }
13
15
  set ControlStyle(v) {
@@ -83,7 +85,7 @@ export class SkiaToggle extends SkiaLayout {
83
85
  RebuildDefaultContent() {
84
86
  if (!this.contentCreated)
85
87
  return;
86
- this.Children = [];
88
+ this.ClearChildren();
87
89
  for (const p of this.stylePinned)
88
90
  this[p] = -1;
89
91
  this.stylePinned = [];
@@ -50,6 +50,8 @@ export declare abstract class SnappingLayout extends SkiaLayout {
50
50
  protected StopSnapAnimators(): void;
51
51
  /** C# SnappingLayout.Render: the transition state follows the position every frame. */
52
52
  Render(ctx: DrawingContext): void;
53
+ /** Hover waits while the content slides (C# InTransition) or follows a pan. */
54
+ MovesContent(): boolean;
53
55
  ClampOffset(x: number, y: number, rubber: boolean): SKPoint;
54
56
  /** C# GetContentOffsetBounds: the extent covered by the snap points. */
55
57
  protected BoundsFromSnapPoints(): SKRect;
@@ -80,9 +80,12 @@ export class SnappingLayout extends SkiaLayout {
80
80
  }
81
81
  /** C# SnappingLayout.Render: the transition state follows the position every frame. */
82
82
  Render(ctx) {
83
+ this.Superview?.Hover?.RegisterMover(this);
83
84
  super.Render(ctx);
84
85
  this.InTransition = !this.CheckTransitionEnded();
85
86
  }
87
+ /** Hover waits while the content slides (C# InTransition) or follows a pan. */
88
+ MovesContent() { return this.InTransition || this.IsUserPanning; }
86
89
  // ---- clamping ----
87
90
  ClampOffset(x, y, rubber) {
88
91
  const b = this.ContentOffsetBounds;
@@ -52,6 +52,12 @@ export declare const Aria: {
52
52
  * its current item, and the arrow keys move between its items.
53
53
  */
54
54
  readonly IsCompositeRole: (role?: string) => boolean;
55
+ /**
56
+ * Roles a user operates (C# Aria.IsInteractiveRole, as DrawnUi.Rust is_control_role): a node with one of them that
57
+ * cannot take input is read as disabled (aria-disabled). C# also lists listbox and scrollbar; here a listbox is an
58
+ * arrow-key group and groups are never disabled.
59
+ */
60
+ readonly IsInteractiveRole: (role?: string) => boolean;
55
61
  };
56
62
  /** One laid-out text line exposed for native selection (AccessibilityTextSelectable): CSS px relative to the node. */
57
63
  export interface AccessibilityTextLine {
@@ -72,6 +78,8 @@ export interface AccessibilityNode {
72
78
  Role: string;
73
79
  Rect: SKRect;
74
80
  CanInteract: boolean;
81
+ /** A role a user operates (Aria.IsInteractiveRole) that cannot take input: aria-disabled, no tab stop. */
82
+ Disabled: boolean;
75
83
  IsPressed?: boolean;
76
84
  Live?: string;
77
85
  Source: SkiaControl;