drawnui-react 0.1.0-preview.1 → 0.1.0-preview.2
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 +85 -29
- package/SKIPPED.md +9 -6
- package/dist/controls/SkiaLabel.d.ts +2 -0
- package/dist/controls/SkiaLabel.js +9 -0
- package/dist/core/Canvas.d.ts +26 -1
- package/dist/core/Canvas.js +100 -3
- package/dist/core/Gestures.d.ts +28 -1
- package/dist/core/Gestures.js +27 -1
- package/dist/core/SkiaControl.d.ts +17 -1
- package/dist/core/SkiaControl.js +30 -0
- package/dist/core/Types.d.ts +7 -2
- package/dist/react/SkiaShell.js +5 -2
- package/dist/react/index.d.ts +13 -3
- package/dist/react/index.js +16 -1
- package/dist/vite/index.d.ts +74 -0
- package/dist/vite/index.js +204 -0
- package/package.json +31 -4
- package/PARITY.md +0 -287
package/PARITY.md
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
# Parity notes: DrawnUi.React vs DrawnUi (.NET)
|
|
2
|
-
|
|
3
|
-
Behavioural differences between the two implementations that are NOT plain omissions (those live in
|
|
4
|
-
[SKIPPED.md](SKIPPED.md)). Each entry says what differs, why, and whether the .NET side should adopt it.
|
|
5
|
-
Updated whenever the port deliberately diverges or finds something worth back-porting.
|
|
6
|
-
|
|
7
|
-
## Scrolling
|
|
8
|
-
|
|
9
|
-
### Viewport offset snapped to device pixels while moving
|
|
10
|
-
- **React**: `SkiaScroll.ArrangeContent` rounds `offset × scale` every frame (drag, fling, bounce, wheel).
|
|
11
|
-
- **.NET**: `SkiaScroll.PositionViewport` rounds `offsetPixels` only when `!IsUserPanning && !IsScrolling`
|
|
12
|
-
(and once after init). While moving, content sits at fractional pixels; cached cells look stable only because
|
|
13
|
-
`CachedObject.Draw` blits the bitmap with nearest sampling, which snaps as a side effect. Uncached text shimmers
|
|
14
|
-
while scrolling (each glyph re-rasterized at a new sub-pixel phase per frame).
|
|
15
|
-
- **Opinion — adopt in .NET: yes.** Two lines in `PositionViewport` (round unconditionally). Benefits: uncached
|
|
16
|
-
or `Operations`-cached content becomes as stable as `Image`-cached cells; the round happens where the code already
|
|
17
|
-
rounds at rest, so anchor/offset math downstream is unchanged. Cost: on low-DPR screens the slow fling tail steps
|
|
18
|
-
by whole device pixels — exactly what `ScrollFlingAnimator`'s pixel-aware finish (`FinishStepPixels`, gated by
|
|
19
|
-
`PixelAwareFlingFinishBelowScale`) already smooths; on high-DPR the quantum is sub-visual. Verify with the fling
|
|
20
|
-
harness (paint cadence + applied px offsets) before shipping; watch `OffsetVisibleAnchorY` paths, which pass
|
|
21
|
-
fractional points and must not fight the round.
|
|
22
|
-
|
|
23
|
-
### Mouse-wheel notches accumulate onto the running target
|
|
24
|
-
- **React**: `ApplyWheelScroll` starts from the running animator's `Parameters.Destination` when one is active, so N
|
|
25
|
-
notches inside one frame travel N × `WheelLineSize`.
|
|
26
|
-
- **.NET**: `ApplyWheelScroll` starts from `ViewportOffsetY`, which has barely moved when notches arrive faster than
|
|
27
|
-
frames; each new `ScrollTo` stops the previous one — a fast spin collapses to roughly one step.
|
|
28
|
-
- **Opinion — adopt in .NET: yes**, same shape: `var from = _animatorFlingY.IsRunning ? _animatorFlingY.Parameters.Destination : ViewportOffsetY`.
|
|
29
|
-
|
|
30
|
-
## Caching
|
|
31
|
-
|
|
32
|
-
### Text properties invalidate like bindable properties
|
|
33
|
-
- **Both**: `SkiaLabel.Text/FontSize/TextColor/FontFamily` are accessors that call `Update()` (C#: BindableProperty
|
|
34
|
-
changed callbacks). Plain public fields on other controls do NOT invalidate when assigned directly — React props go
|
|
35
|
-
through `applyProps` which calls `Update()`, but engine-level code must call `Update()` itself after mutating a field.
|
|
36
|
-
Converting the remaining hot properties to accessors is pending.
|
|
37
|
-
|
|
38
|
-
## Text
|
|
39
|
-
|
|
40
|
-
### Font weights are registered per alias
|
|
41
|
-
- **Both**: `ConfigureFonts(f => f.AddFont(source, alias, weight))`; `FontWeight`/`FontAttributes=Bold` resolve to the nearest
|
|
42
|
-
registered weight of the alias (400 = default). React adds: italic without an italic face = synthetic skew (-0.25), the C#
|
|
43
|
-
side has no synthetic italic. Defaults adopted from C#: `FontSize` 12, `TextColor` GreenYellow (unstyled text stays visible).
|
|
44
|
-
- **`FontFamilyFallback` chain (React extension)**: C# takes ONE fallback alias; React accepts a comma-separated chain
|
|
45
|
-
(`"FontSymbols,FontSymbols2"`) tried in order per codepoint, and spaces are never moved to a fallback run (keeps word gaps
|
|
46
|
-
at the main font's width). Opinion: worth back-porting to .NET — a single fallback cannot cover arrows (Math) and
|
|
47
|
-
♥/★ (Symbols 2) at once, which is exactly the split `AddSymbols()` ships.
|
|
48
|
-
|
|
49
|
-
### Markdown parser
|
|
50
|
-
- C# `SkiaRichLabel` parses with CommonMark.NET; React ships a small hand-written parser (headings, lists, fenced
|
|
51
|
-
code, inline emphasis/code/links, escapes). Same span output rules (`SpanWithAttributes`), same style properties.
|
|
52
|
-
Code blocks: C# paints `ParagraphColor` across the full line width, React paints the span background only.
|
|
53
|
-
Not worth a dependency for the demo; swap in a CommonMark library if edge cases matter.
|
|
54
|
-
|
|
55
|
-
### Span decorations use estimated metrics
|
|
56
|
-
- C# reads `UnderlinePosition` / `StrikeoutPosition` / `XHeight` from the SKFont metrics and falls back to
|
|
57
|
-
1 px / half x-height when a face lacks them. CanvasKit exposes none of the three, so React always uses the
|
|
58
|
-
C# fallbacks: underline at `baseline + 1 scaled px`, strikeout at `baseline - 0.26 * fontSize` (x-height ≈ 0.52 em).
|
|
59
|
-
Visually identical for OpenSans; faces with unusual x-height may sit the strike a pixel off.
|
|
60
|
-
- Spaces: a span fragment starting/ending with a space contributes a break opportunity but the space itself is not
|
|
61
|
-
painted with the span's `BackgroundColor` (C# paints it). Cosmetic; not worth changing on either side.
|
|
62
|
-
|
|
63
|
-
## Layout
|
|
64
|
-
|
|
65
|
-
### Grid attached properties are plain child props
|
|
66
|
-
- C#: `draw:SkiaLayout.Column="1"` attached bindable properties. React: `Column={1}` / `Row` / `ColumnSpan` / `RowSpan`
|
|
67
|
-
props on any control (fields on `SkiaControl`, read by the grid only). Same defaults (0 / 0 / 1 / 1), no behaviour change.
|
|
68
|
-
|
|
69
|
-
### `MaximumWidthRequest` / `MaximumHeightRequest`
|
|
70
|
-
- Same as C# (1.10.5.18+): caps the measured size AND the arranged Fill box; alignment uses the parent's full box. Used by
|
|
71
|
-
the demo for responsive pages (`SkiaStack MaximumWidthRequest={720} HorizontalOptions="Center"`) — fluid below the cap,
|
|
72
|
-
fixed above it, no media queries.
|
|
73
|
-
|
|
74
|
-
## Lists
|
|
75
|
-
|
|
76
|
-
### ItemsSource changes are diffed, not observed
|
|
77
|
-
- **C#**: `ObservableCollection` events say exactly what changed.
|
|
78
|
-
- **React**: state is immutable arrays; the layout compares old/new (first/middle/last element identity) to recognise
|
|
79
|
-
append and prepend and keeps its structure; anything else rebuilds. Same user-visible result for the paging and
|
|
80
|
-
chat-history cases; an in-place removal costs a rebuild here. Opinion: nothing to back-port, .NET has the events.
|
|
81
|
-
|
|
82
|
-
### MeasureVisible measures visible cells on demand, not only in the background
|
|
83
|
-
- **C#**: initial measured batch + background batches; a cell entering the viewport before its batch arrives uses
|
|
84
|
-
the estimate until measured.
|
|
85
|
-
- **React**: a cell entering the viewport is measured right there (it is being bound anyway) and laid out with its
|
|
86
|
-
real height, the estimate is used only for the anchor offset of the first visible item; the idle pass then
|
|
87
|
-
extends the exact prefix. Result: no visible resize of on-screen cells, only the far-away offsets refine.
|
|
88
|
-
- Opinion: back-portable and cheap on .NET too (measure at bind time in `DrawStack` when the height is unknown).
|
|
89
|
-
|
|
90
|
-
### Recycled cells contract
|
|
91
|
-
- Same in both: the templated `SkiaLayout` (ItemsSource + ItemTemplate) is the `SkiaScroll`'s ONLY content; anything
|
|
92
|
-
above the list goes above the scroll or into the scroll `Header` (not ported yet). Nesting the templated layout inside a
|
|
93
|
-
static stack makes it a BindableLayout, not a CollectionView, and `ScrollToIndex` requires Content to be the layout.
|
|
94
|
-
- **React-only rule**: `ItemTemplate` must be a stable function reference (module-level or `useCallback`). A new arrow on
|
|
95
|
-
every render is a new template → the pool is rebuilt each render (C# XAML sets `DataTemplate` once, so it never hits this).
|
|
96
|
-
|
|
97
|
-
## Colors
|
|
98
|
-
|
|
99
|
-
### Hex alpha position
|
|
100
|
-
- **Both**: 8-digit hex is `#AARRGGBB` (MAUI `Color.FromArgb`), 4-digit is `#ARGB`. All color strings go through
|
|
101
|
-
`Super.ParseColor`; CanvasKit's own `parseColorString` (CSS `#RRGGBBAA`) is used only for `rgb()/rgba()` strings.
|
|
102
|
-
Web developers used to CSS must be told — `"#22FFFFFF"` is 13% white here, not opaque cyan.
|
|
103
|
-
|
|
104
|
-
## Transforms
|
|
105
|
-
|
|
106
|
-
### Transform / Opacity changes stale ancestor caches, not the control's own
|
|
107
|
-
- **C#**: transform properties call `RedrawCanvas`; whether a cached parent re-records depends on the invalidation path.
|
|
108
|
-
- **React**: `RepaintComposition()` marks every ancestor cache dirty (their pictures contain this control's composited
|
|
109
|
-
output) and keeps the control's own cache (content unchanged); the reconciler routes transform/opacity prop changes
|
|
110
|
-
there instead of `Update()`, so animating a child never remeasures. Found the hard way: the animated logo lived
|
|
111
|
-
inside an `Operations`-cached `SkiaShape` and did not move until the parent was re-recorded.
|
|
112
|
-
- Opinion: matches what DrawnUi does at the top cached container; nothing to back-port beyond making sure a child's
|
|
113
|
-
transform change invalidates the parent's cache on every path.
|
|
114
|
-
|
|
115
|
-
### Cancellation and skew
|
|
116
|
-
- C# `*ToAsync` take a `CancellationTokenSource`; React takes an `AbortSignal` and rejects with `AbortError`.
|
|
117
|
-
- C# ignores negative `SkewX/SkewY` (`> 0` check); React applies both signs. Opinion: C# check looks accidental.
|
|
118
|
-
|
|
119
|
-
## Effects
|
|
120
|
-
|
|
121
|
-
### ClipEffects is honoured
|
|
122
|
-
- C# `WillClipEffects` exists but the render path always expands the clip by the effects margin. React: with
|
|
123
|
-
`IsClippedToBounds`, `ClipEffects=true` (default) clips to the exact box, `false` expands by the aggregated
|
|
124
|
-
effects margin. Opinion: wire `WillClipEffects` into `DrawWithClipAndTransforms` on .NET, it is a one-line gate.
|
|
125
|
-
|
|
126
|
-
### Color matrix units
|
|
127
|
-
- SkiaSharp `CreateColorMatrix` translations are 0..255, CanvasKit `MakeMatrix` 0..1; React divides the C# constants
|
|
128
|
-
so `Darken=5` looks the same on both. Gamma has no table filter in CanvasKit — linear approximation.
|
|
129
|
-
|
|
130
|
-
## Accessibility
|
|
131
|
-
|
|
132
|
-
### Overlay does not capture pointer events
|
|
133
|
-
- **C# (Blazor)**: the ARIA overlay elements sit above the canvas and receive clicks, so a control with
|
|
134
|
-
accessibility metadata stops getting `Pointer` (hover) gestures — documented limitation.
|
|
135
|
-
- **React**: overlay elements have `pointer-events: none`; real pointers always reach the canvas, keyboard and
|
|
136
|
-
screen-reader activation arrive as DOM `click`/`keydown` on the focused element and are routed as a `Tapped`.
|
|
137
|
-
ATs that simulate a physical click at coordinates hit the canvas directly and work as well.
|
|
138
|
-
- Opinion: back-port to Blazor — one CSS rule on `.xaml-a11y-element` (`pointer-events: none`) plus keeping
|
|
139
|
-
`tabindex`/`@onclick`/`@onkeydown` as they are; removes the hover limitation with no other change.
|
|
140
|
-
|
|
141
|
-
### Default roles per class
|
|
142
|
-
- **C#**: opt-in per control (`AccessibilityRole` null by default), `SkiaLabel` only syncs `AccessibilityLabel`.
|
|
143
|
-
- **React**: same opt-in, plus `SkiaLabel.DefaultAccessibilityRole` / `SkiaButton.DefaultAccessibilityRole` statics
|
|
144
|
-
(unset by default) so an app can make every label/button accessible in two lines; `AccessibilityLabel` falls
|
|
145
|
-
back to the control text, `AccessibilityCanInteract` to "has a Tapped handler".
|
|
146
|
-
- Opinion: worth back-porting as static defaults on `SkiaLabel`/`SkiaButton` — keeps the opt-in contract and gives
|
|
147
|
-
"readable labels" without touching every control.
|
|
148
|
-
|
|
149
|
-
### Nodes pruned instead of unregistered
|
|
150
|
-
- **C#**: controls unregister on detach/dispose/visibility change.
|
|
151
|
-
- **React**: the snapshot rebuild drops nodes without a `Superview`, invisible, or farther than one canvas size
|
|
152
|
-
outside it; rects are re-read from `DrawingRect`, so they follow scrolling. Behavioural difference: a removed
|
|
153
|
-
node can linger up to `MinUpdateIntervalMs` in the DOM. Pooled recycled cells get `Parent = undefined` on release.
|
|
154
|
-
|
|
155
|
-
### Focus scrolls the drawn content into view
|
|
156
|
-
- **React-only**: when keyboard focus lands on an overlay node that is outside its `SkiaScroll` viewport,
|
|
157
|
-
`SkiaScroll.EnsureVisible(control)` animates every scroll ancestor so the control is visible (browser
|
|
158
|
-
behaviour for DOM pages). Opinion: back-port — Blazor users tabbing through a drawn list get the same
|
|
159
|
-
experience as a native page; needs a `ScrollToView`-like helper plus the overlay `focus` callback.
|
|
160
|
-
|
|
161
|
-
## Rendering
|
|
162
|
-
|
|
163
|
-
### Redraw synchronously inside the resize callback
|
|
164
|
-
- **React**: the `ResizeObserver` callback recreates the surface and draws immediately (RO runs after layout,
|
|
165
|
-
before paint), so a live window drag never presents a blank frame; the GL context/GrContext live for the Canvas
|
|
166
|
-
lifetime, only the surface is recreated.
|
|
167
|
-
- **.NET**: platform views handle resize natively (SkiaSharp views recreate surfaces on size change and request a
|
|
168
|
-
paint); Windows `DrawnSwapChainPanel` already owns surface recreation.
|
|
169
|
-
- **Opinion**: no action; noted so the web behaviour is understood as intentional.
|
|
170
|
-
|
|
171
|
-
### SVG rendering
|
|
172
|
-
- **React**: no SVG module in CanvasKit's npm build → browser decodes, raster per displayed size, `TintColor` via
|
|
173
|
-
`SrcIn`. Effects that operate on the SVG picture (`FillGradient`, FontAwesome duotone) are not reproducible this way.
|
|
174
|
-
- **.NET**: `Svg.Skia` picture, vector at any scale.
|
|
175
|
-
- **Opinion**: web-only constraint; nothing to back-port.
|
|
176
|
-
|
|
177
|
-
## SkiaCarousel
|
|
178
|
-
|
|
179
|
-
### Wrong-direction check follows the carousel axis
|
|
180
|
-
- **React**: the first pan compares the movement along the carousel axis with the movement across it (`IsVertical` aware), using the total movement since Down.
|
|
181
|
-
- **.NET**: `movex < RenderingScale * 2 || movey > movex` on the per-event delta, regardless of `IsVertical` — a vertical carousel rejects its own vertical swipes.
|
|
182
|
-
- **Opinion**: back-port; pick the axis from `IsVertical`.
|
|
183
|
-
|
|
184
|
-
### LinearSpeedMs ratio in points
|
|
185
|
-
- **React**: `ratio = |end - start| / CellSize.Units`, so `LinearSpeedMs` is the time of exactly one slide.
|
|
186
|
-
- **.NET**: divides the unit displacement by `CellSize.Pixels.Width`, so one slide takes `LinearSpeedMs / RenderingScale` (350 ms becomes 175 ms on a 2x screen), which contradicts the doc comment.
|
|
187
|
-
- **Opinion**: back-port; use `CellSize.Units`.
|
|
188
|
-
|
|
189
|
-
### Programmatic SelectedIndex interrupts a running snap
|
|
190
|
-
- **React**: setting `SelectedIndex` (or `ScrollTo`) while a snap animates calls `InterruptSnapping` first, like `GoNext`/`GoPrev`, so the new target is honoured.
|
|
191
|
-
- **.NET**: only `GoNext`/`GoPrev` interrupt; a plain `SelectedIndex` set during `_isSnapping` is ignored by `OnSelectedIndexChanged` and the carousel ends on the old target while the property says otherwise.
|
|
192
|
-
- **Opinion**: back-port; call `InterruptSnapping()` from the `SelectedIndex` property changed handler.
|
|
193
|
-
|
|
194
|
-
## Animated frames
|
|
195
|
-
|
|
196
|
-
### Animator initialized on the first layout only
|
|
197
|
-
- **React**: `AnimatedFramesRenderer.OnLayoutChanged` runs on every frame (Arrange is per frame here), so the animator is created / auto-started only on the first layout; later `SetAnimation` calls initialize explicitly.
|
|
198
|
-
- **.NET**: `OnLayoutChanged` fires only on a real layout change, so `InitializeAnimator` + `Start` on every call is harmless.
|
|
199
|
-
- **Opinion**: no action for .NET; note for anyone porting the control to a per-frame-arrange engine.
|
|
200
|
-
|
|
201
|
-
### Overlay animators stale the ancestors' caches while running
|
|
202
|
-
- **React**: `RenderingAnimator.TickFrame` marks every ancestor cache dirty on each tick, so a ripple on a button inside a `UseCache=Image` card is drawn (the card re-records for the ~500 ms of the effect).
|
|
203
|
-
- **.NET**: the effect invalidates the parent through the regular `Update`/`Repaint` path.
|
|
204
|
-
- **Opinion**: same outcome; noted because the React cache model had to add it explicitly.
|
|
205
|
-
|
|
206
|
-
## SkiaBackdrop
|
|
207
|
-
|
|
208
|
-
### Ancestor caches staled after every backdrop paint
|
|
209
|
-
- **React**: a backdrop recorded into a cached parent (the demo card is a SkiaShape with the default Operations cache) kept a snapshot taken before the baboon image had loaded: the image's invalidation climbs its own branch and never reaches the sibling shape. After each paint the backdrop marks its ancestors' caches stale (microtask, no frame requested), so the next frame for any reason re-records it.
|
|
210
|
-
- **.NET**: the same tree in `MainPageBackdrop` works because the sandbox content loads before the first record or the page redraws for other reasons; a late-loading sibling would leave the same stale snapshot.
|
|
211
|
-
- **Opinion**: consider the same "stale ancestors after paint" in `SkiaBackdrop.Paint`; it costs nothing while the canvas is idle.
|
|
212
|
-
|
|
213
|
-
## Visual effects
|
|
214
|
-
|
|
215
|
-
### Post renderers wait for the shader to compile
|
|
216
|
-
- **React**: `SkiaControl.EffectPostRenderers` is filtered by `NeedApply` at render time; `SkiaShaderEffect.NeedApply` fetches the source (async, once) and compiles (sync) when needed, so a control with an effect whose `.sksl` is still loading is blitted plainly and takes the shader on the next frame.
|
|
217
|
-
- **.NET**: `DrawRenderObject` skips the cache blit whenever `EffectPostRenderers` is non-empty and the effect logs "failed to create shader" until compiled, so the control is invisible while the shader is missing.
|
|
218
|
-
- **Opinion**: filtering by `NeedApply` there too avoids the blank control on a slow resource / compile error.
|
|
219
|
-
|
|
220
|
-
### Texture texel origin
|
|
221
|
-
- **React**: `CachedTexture.Origin` records where texel (0,0) of the texture sits in canvas space; a cache image gets no local matrix (shaders sample `fragCoord - iOffset` in texel space, as in C#), a whole-surface snapshot gets a translation so texel (0,0) is the destination's top-left (a bounded `makeImageSnapshot` of a GPU surface is not origin-safe in CanvasKit).
|
|
222
|
-
- **.NET**: `CreateSnapshot` maps the destination through `TotalMatrix` and snapshots that sub-rect.
|
|
223
|
-
- **Opinion**: same outcome.
|
|
224
|
-
|
|
225
|
-
### Effects' `Update()` re-records the parent
|
|
226
|
-
- **React**: `SkiaEffect.Update` invalidates the parent's cache and stales the ancestors (`RepaintComposition`), the C# `Parent.Update()` semantics; the parent's own re-record is what lets `SkiaShaderCarousel` realize new slides while the transition progresses.
|
|
227
|
-
|
|
228
|
-
## SkiaShaderCarousel
|
|
229
|
-
|
|
230
|
-
### Cached as Image by default
|
|
231
|
-
- **React**: the constructor sets `UseCache="Image"` so the overlapping slides (all arranged at offset 0) are recorded into a cache that is never blitted (the post renderer replaces the blit) instead of being painted on screen under the effect.
|
|
232
|
-
- **.NET**: the user sets the cache type; with `UseCache=None` `DrawRenderObject` is never used and the transition effect never runs.
|
|
233
|
-
- **Opinion**: forcing an Image cache in the C# constructor would make the control work out of the box.
|
|
234
|
-
|
|
235
|
-
### `OnChildrenInitialized` before the first position
|
|
236
|
-
- **React**: `InitializeChildren` raises `OnChildrenInitialized` before `ApplyIndex(true)`; the shader carousel resets its from/to state there, and the first `OnScrollProgressChanged` (from the instant `ApplyPosition`) then sets them up. The other order left the first transition pair at -1 until the first swipe.
|
|
237
|
-
|
|
238
|
-
## SkiaEditor
|
|
239
|
-
|
|
240
|
-
### Hidden DOM textarea for IME / soft keyboards
|
|
241
|
-
- **React**: `TextInputProxy` mirrors the focused editor into a hidden textarea and replays its input events through the stub methods (diff of the value). DrawnUi.Blazor has no DOM input at all (physical keyboard only); this is a deliberate addition so mobile browsers can type.
|
|
242
|
-
- **.NET**: `SkiaEditor.Blazor.cs` subscribes to `KeyboardManager` only.
|
|
243
|
-
- **Opinion**: the same proxy would give the Blazor / Wasm heads mobile input; the diff approach avoids per-inputType handling and keeps IME composition intact.
|
|
244
|
-
|
|
245
|
-
## SkiaImageManager
|
|
246
|
-
|
|
247
|
-
### Queue in the browser
|
|
248
|
-
- **React**: `LoadImageManagedAsync` orders by priority and caps concurrent fetches at 5 like the C# semaphore; `SkiaImage.Source` goes through it (`LoadPriority`). Browsers already limit connections per host, so the cap mostly keeps decode work paced.
|
|
249
|
-
|
|
250
|
-
## SkiaScroll
|
|
251
|
-
|
|
252
|
-
### Scroll inside a cached parent stales it
|
|
253
|
-
- **React**: every offset change calls `RepaintComposition` (ancestor caches staled, own cache kept) instead of a plain `Repaint`; before, a `SkiaScroll` inside an Operations-cached card moved its arranged rects but never repainted (the card's picture was replayed).
|
|
254
|
-
- **.NET**: `Update()` invalidates up the tree.
|
|
255
|
-
|
|
256
|
-
### Wheel goes to the innermost scroll first
|
|
257
|
-
- **React**: a nested scroll under the pointer takes the wheel; when it sits at its edge in that direction it declines and the outer scroll moves. C# has no wheel routing rule for nested scrolls.
|
|
258
|
-
|
|
259
|
-
### Refresh indicator position
|
|
260
|
-
- **React**: `RefreshIndicator.SetDragRatio` slides the view in linearly with the overscroll and parks it at `RefreshShowDistance` (centered in the gap when the gap is taller than the view); the C# curve (`getPosition(k)`) depends on the sign convention of `InternalViewportOffset` and produced off-screen positions with this port's positive top overscroll.
|
|
261
|
-
|
|
262
|
-
### Snap uses the last paint's geometry
|
|
263
|
-
- **React**: `Snap` computes the target offset from the child's position relative to the content start as arranged at the last paint, so a fling that stopped a tick after that paint still lands the child exactly; `ScrollTo` applies its exact destination when the deceleration curve finishes (`LandScrollTo`).
|
|
264
|
-
|
|
265
|
-
## Layouts
|
|
266
|
-
|
|
267
|
-
### Templated Row / Wrap / Grid are not virtualized
|
|
268
|
-
- **React**: every item is realized through the `ViewsAdapter` (the C# non-list layouts also measure and draw all cells); only the templated single-column Column is the virtualized list.
|
|
269
|
-
|
|
270
|
-
### `OnChildrenInitialized` order in SkiaCarousel
|
|
271
|
-
- see SkiaShaderCarousel above.
|
|
272
|
-
|
|
273
|
-
## Caching
|
|
274
|
-
|
|
275
|
-
### ImageComposite dirty tracking
|
|
276
|
-
- **React**: `RepaintComposition` is the only dirty source (transform / own cache invalidation of a child); a remeasure anywhere below marks the composite for a full record. C# tracks `DirtyChildrenTracker` from `InvalidateByChild` too; both erase the union of old + new transformed bounds and pull intersecting siblings in.
|
|
277
|
-
- **React-only gotcha**: React props that are new objects on every render (`Margin={new Thickness(...)}`) remeasure the child each render and force full records; memoize them.
|
|
278
|
-
|
|
279
|
-
### Image caches on whole pixels
|
|
280
|
-
- **React**: `Image` / `ImageComposite` / `ImageDoubleBuffered` caches record the expanded rect snapped outward to integer device pixels; the blit is 1:1 and a shader effect sampling `fragCoord - iOffset` hits texel centers (a fractional `DrawingRect.Left` made `blit.sksl` bilinear-blur the image by a sub-pixel amount). Picture caches keep the exact rect.
|
|
281
|
-
- **.NET**: `CachedObject.Bounds` / recording areas are already integer pixels.
|
|
282
|
-
|
|
283
|
-
## Accessibility
|
|
284
|
-
|
|
285
|
-
### Selectable text is opt-in
|
|
286
|
-
- **React**: `AccessibilityTextSelectable` (default false) puts a label's lines into the overlay as real text with pointer events; the text then owns the pointer (selection), so it is never enabled implicitly — custom controls would lose taps and pans under their labels. ARIA roles / labels stay unaffected.
|
|
287
|
-
- **.NET**: no selectable labels (only `SkiaEditor` selects).
|