drawnui-react 0.1.0-preview.15 → 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.
- package/SKIPPED.md +5 -4
- 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 +2 -0
- package/dist/controls/SkiaCarousel.js +2 -0
- package/dist/controls/SkiaDrawer.d.ts +2 -0
- package/dist/controls/SkiaDrawer.js +2 -0
- package/dist/controls/SkiaEditor.js +10 -1
- package/dist/controls/SkiaLabel.d.ts +2 -0
- package/dist/controls/SkiaLabel.js +13 -2
- package/dist/controls/SkiaLayout.d.ts +15 -0
- package/dist/controls/SkiaLayout.js +75 -6
- package/dist/controls/SkiaProgress.js +2 -2
- package/dist/controls/SkiaScroll.d.ts +4 -0
- package/dist/controls/SkiaScroll.js +9 -0
- package/dist/controls/SkiaSlider.d.ts +2 -0
- package/dist/controls/SkiaSlider.js +5 -3
- package/dist/controls/SkiaToggle.d.ts +2 -0
- package/dist/controls/SkiaToggle.js +3 -1
- package/dist/controls/SnappingLayout.d.ts +2 -0
- package/dist/controls/SnappingLayout.js +3 -0
- package/dist/core/Accessibility.d.ts +8 -0
- package/dist/core/Accessibility.js +56 -5
- package/dist/core/Canvas.d.ts +5 -0
- package/dist/core/Canvas.js +15 -1
- package/dist/core/Hover.d.ts +37 -0
- package/dist/core/Hover.js +118 -0
- package/dist/core/KeyboardManager.d.ts +13 -0
- package/dist/core/KeyboardManager.js +18 -0
- package/dist/core/SkiaControl.d.ts +85 -1
- package/dist/core/SkiaControl.js +217 -16
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +2 -2
- package/dist/react/reconciler.js +29 -10
- package/package.json +4 -1
package/dist/core/SkiaControl.js
CHANGED
|
@@ -134,6 +134,7 @@ export class SkiaControl {
|
|
|
134
134
|
return;
|
|
135
135
|
this.bindingContext = value;
|
|
136
136
|
this.OnBindingContextChanged();
|
|
137
|
+
this.Superview?.Hover?.RequestCheck(); // a recycled cell under a still mouse now shows another item
|
|
137
138
|
}
|
|
138
139
|
/** Index of the bound item inside its ItemsSource, -1 when not templated. */
|
|
139
140
|
ContextIndex = -1;
|
|
@@ -166,15 +167,45 @@ export class SkiaControl {
|
|
|
166
167
|
compositeSurface;
|
|
167
168
|
/** Own content or structure changed: the next composite record is a full one. */
|
|
168
169
|
compositeFull = true;
|
|
170
|
+
/** Where the kept composite surface starts relative to DrawingRect (the effects margin it was laid out with). */
|
|
171
|
+
compositeInsetX = 0;
|
|
172
|
+
compositeInsetY = 0;
|
|
169
173
|
/** Canvas-pixel bounds this control covered when its parent last recorded it (composite erase region). */
|
|
170
174
|
lastCompositeBounds;
|
|
171
|
-
/**
|
|
172
|
-
|
|
175
|
+
/**
|
|
176
|
+
* What the last composite record did (diagnostics): "full" or "partial", the number of children re-recorded and which
|
|
177
|
+
* ones; Areas: the pixels erased and drawn again for changes deeper than a child (C# Areas), Changed: the controls
|
|
178
|
+
* those areas belong to (C# Changed).
|
|
179
|
+
*/
|
|
180
|
+
LastCompositeRecord = { Mode: "full", Children: 0, Dirty: [], Areas: [], Changed: [] };
|
|
181
|
+
/** C# MaxCompositionAreas: more areas than this in one composite record draw it whole instead. */
|
|
182
|
+
static MaxCompositionAreas = 16;
|
|
183
|
+
/** C# MaxCompositionShare: areas covering more than this share of a composite draw it whole instead. */
|
|
184
|
+
static MaxCompositionShare = 0.5;
|
|
185
|
+
/**
|
|
186
|
+
* Changes deeper than a direct child since the last composite record (drawnui-cross 6m): the control, the direct child
|
|
187
|
+
* it is in, where it was drawn before the change (its own drawn bounds, as drawn) and its rect then.
|
|
188
|
+
*/
|
|
189
|
+
compositeDeep;
|
|
190
|
+
/** Set while an UpdateDraw climbs: the ancestors re-measure but an ImageComposite among them keeps its partial record. */
|
|
191
|
+
static drawOrigin = null;
|
|
192
|
+
/** Nonzero while a composite redraws only some areas: layouts skip children entirely outside the clip. */
|
|
193
|
+
static CompositionCulling = 0;
|
|
173
194
|
/** The children a composite record can re-record individually; layouts return their views. */
|
|
174
195
|
GetCompositeChildren() { return []; }
|
|
175
196
|
/** C# TrackChildAsDirty (DirtyChildrenTracker): the child changed without a remeasure of this control. */
|
|
176
197
|
TrackChildAsDirty(child) { if (this.IsCacheComposite)
|
|
177
198
|
this.DirtyChildrenInternal.add(child); }
|
|
199
|
+
static rejectRect = new Float32Array(4);
|
|
200
|
+
/** During a partial composite record: true when this control draws nothing inside the canvas clip (skipped whole). */
|
|
201
|
+
OutsideCompositionClip(canvas) {
|
|
202
|
+
const b = this.DrawnBoundsNow(this.RenderingScale), q = SkiaControl.rejectRect;
|
|
203
|
+
q[0] = b.Left;
|
|
204
|
+
q[1] = b.Top;
|
|
205
|
+
q[2] = b.Right;
|
|
206
|
+
q[3] = b.Bottom;
|
|
207
|
+
return canvas.quickReject(q);
|
|
208
|
+
}
|
|
178
209
|
/** Drawn bounds in canvas pixels including transforms and effects margins (C# GetTransformedDirtyBounds). */
|
|
179
210
|
GetTransformedDirtyBounds() {
|
|
180
211
|
const r = this.ExpandedCacheRect(this.RenderingScale), m = this.RenderTransformMatrix;
|
|
@@ -193,6 +224,55 @@ export class SkiaControl {
|
|
|
193
224
|
/** Consume every gesture that lands on this control so nothing below (z-order) receives it. */
|
|
194
225
|
BlockGesturesBelow = false;
|
|
195
226
|
LockChildrenGestures = "Disabled";
|
|
227
|
+
// ---- hover (drawnui-cross 6m, C# ReceivesHover / IsHovered / HoverChanged) ----
|
|
228
|
+
receivesHover;
|
|
229
|
+
/**
|
|
230
|
+
* Whether this control takes mouse hover: the canvas sets IsHovered while the mouse is over it. Opt-in: false by
|
|
231
|
+
* default, true by default only in the controls that always had hover (SkiaButton, SkiaSlider, the toggles,
|
|
232
|
+
* SkiaRadioButton, SkiaCarousel, SkiaDrawer), never because a control has a tap handler. A HoverChanged handler turns
|
|
233
|
+
* it on (C# fluent OnHovered) unless it is set.
|
|
234
|
+
*/
|
|
235
|
+
get ReceivesHover() { return this.receivesHover ?? (this.HoverChanged ? true : this.ReceivesHoverByDefault()); }
|
|
236
|
+
set ReceivesHover(value) { this.receivesHover = value; }
|
|
237
|
+
/** The value of ReceivesHover until it is set. */
|
|
238
|
+
ReceivesHoverByDefault() { return false; }
|
|
239
|
+
isHovered = false;
|
|
240
|
+
/**
|
|
241
|
+
* True while the mouse is over this control and it takes hover. Every such control under the pointer is hovered at
|
|
242
|
+
* once (a card and the button inside it, as CSS :hover); it stays as it is while content moves under the pointer and
|
|
243
|
+
* is checked again when that stops. Touch never hovers.
|
|
244
|
+
*/
|
|
245
|
+
get IsHovered() { return this.isHovered; }
|
|
246
|
+
/** IsHovered changed: true when the mouse came over this control, false when it left (C# HoverChanged / OnHovered). */
|
|
247
|
+
HoverChanged;
|
|
248
|
+
/** Set by the canvas's HoverManager. */
|
|
249
|
+
ApplyHover(on) {
|
|
250
|
+
if (this.isHovered === on)
|
|
251
|
+
return;
|
|
252
|
+
this.isHovered = on;
|
|
253
|
+
this.HoverChanged?.(this, on);
|
|
254
|
+
}
|
|
255
|
+
/** A scroll, carousel or drawer moving its content right now: hover waits until it stops (C# IsScrolling / InTransition). */
|
|
256
|
+
MovesContent() { return false; }
|
|
257
|
+
/**
|
|
258
|
+
* Hover hit path (HoverManager): this control is under the point (in its own space), its children under it follow,
|
|
259
|
+
* top-most first, by the routing rules of ProcessGestures. True when this subtree blocks gestures below it, so the
|
|
260
|
+
* controls under it get no hover (a popup over a list).
|
|
261
|
+
*/
|
|
262
|
+
CollectHovered(point, into) {
|
|
263
|
+
into.push(this);
|
|
264
|
+
if (!this.CheckChildrenGesturesLocked("Pointer")) {
|
|
265
|
+
const listeners = this.GetGestureListeners();
|
|
266
|
+
for (let i = listeners.length - 1; i >= 0; i--) {
|
|
267
|
+
const child = listeners[i];
|
|
268
|
+
if (!child.IsVisible || child.InputTransparent || !this.IsGestureForChild(child, point))
|
|
269
|
+
continue;
|
|
270
|
+
if (child.CollectHovered(child.TransformPointToLocalSpace(point), into))
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return this.BlockGesturesBelow;
|
|
275
|
+
}
|
|
196
276
|
// ---- touch feedback (same names as DrawnUi) ----
|
|
197
277
|
TouchEffectColor = Colors.White;
|
|
198
278
|
/** Plays a ripple (from the tap point) when Tapped fires. */
|
|
@@ -277,6 +357,11 @@ export class SkiaControl {
|
|
|
277
357
|
if (!SkiaStyles.IsEmpty)
|
|
278
358
|
SkiaStyles.Apply(this, atConstruction);
|
|
279
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* C# IsChildrenItem: added by the app (JSX children or the `Children` list), not a part the control made itself. A new
|
|
362
|
+
* `Children` list drops only these, so a control's own parts keep their place.
|
|
363
|
+
*/
|
|
364
|
+
IsChildrenItem = false;
|
|
280
365
|
AddSubView(_control) { throw new Error(`DrawnUi: ${this.constructor.name} cannot host children`); }
|
|
281
366
|
InsertSubView(_index, control) { this.AddSubView(control); }
|
|
282
367
|
RemoveSubView(_control) { }
|
|
@@ -813,14 +898,46 @@ export class SkiaControl {
|
|
|
813
898
|
}
|
|
814
899
|
const canvas = surface.getCanvas();
|
|
815
900
|
const children = this.GetCompositeChildren();
|
|
816
|
-
const
|
|
901
|
+
const deep = this.compositeDeep;
|
|
902
|
+
// the same size with another effects margin moves the content inside the surface: record whole (C# 97683f19)
|
|
903
|
+
const insetX = r.Left - this.DrawingRect.Left, insetY = r.Top - this.DrawingRect.Top;
|
|
904
|
+
const sameInset = insetX === this.compositeInsetX && insetY === this.compositeInsetY;
|
|
905
|
+
this.compositeInsetX = insetX;
|
|
906
|
+
this.compositeInsetY = insetY;
|
|
907
|
+
let partial = sameGeometry && sameInset && !this.compositeFull && children.length > 0 && (this.DirtyChildrenInternal.size > 0 || !!deep?.length);
|
|
908
|
+
// changes deeper than a child: only their areas, the child they are in drawn clipped to them (drawnui-cross 6m)
|
|
909
|
+
const areas = [], changed = [], inside = new Set();
|
|
910
|
+
if (partial && deep?.length) {
|
|
911
|
+
let covered = 0;
|
|
912
|
+
for (const d of deep) {
|
|
913
|
+
if (!children.includes(d.Child) || this.DirtyChildrenInternal.has(d.Child))
|
|
914
|
+
continue;
|
|
915
|
+
const area = SkiaControl.DeepArea(d, this, ctx.Scale);
|
|
916
|
+
if (area === null) {
|
|
917
|
+
partial = false;
|
|
918
|
+
break;
|
|
919
|
+
}
|
|
920
|
+
if (!area) {
|
|
921
|
+
this.DirtyChildrenInternal.add(d.Child);
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
areas.push(area);
|
|
925
|
+
changed.push(d.Origin);
|
|
926
|
+
inside.add(d.Child);
|
|
927
|
+
covered += area.Width * area.Height;
|
|
928
|
+
}
|
|
929
|
+
if (areas.length > SkiaControl.MaxCompositionAreas || covered > r.Width * r.Height * SkiaControl.MaxCompositionShare)
|
|
930
|
+
partial = false;
|
|
931
|
+
}
|
|
932
|
+
if (deep)
|
|
933
|
+
deep.length = 0;
|
|
817
934
|
const offset = prev && sameGeometry ? { X: r.Left - prev.Bounds.Left, Y: r.Top - prev.Bounds.Top } : { X: 0, Y: 0 };
|
|
818
935
|
const saved = canvas.save();
|
|
819
936
|
canvas.translate(-r.Left, -r.Top);
|
|
820
937
|
if (partial) {
|
|
821
938
|
// dirty = reported children + siblings intersecting their old or new bounds (C# makes intersecting children dirty too)
|
|
822
939
|
const dirty = new Set();
|
|
823
|
-
const rects = [];
|
|
940
|
+
const rects = [...areas];
|
|
824
941
|
const boundsOf = (c) => {
|
|
825
942
|
const out = [c.GetTransformedDirtyBounds()];
|
|
826
943
|
if (c.lastCompositeBounds)
|
|
@@ -836,7 +953,7 @@ export class SkiaControl {
|
|
|
836
953
|
while (grew) {
|
|
837
954
|
grew = false;
|
|
838
955
|
for (const c of children) {
|
|
839
|
-
if (dirty.has(c) || !c.IsVisible)
|
|
956
|
+
if (dirty.has(c) || inside.has(c) || !c.IsVisible)
|
|
840
957
|
continue;
|
|
841
958
|
const own = boundsOf(c);
|
|
842
959
|
if (rects.some((d) => own.some((o) => o.Left < d.Right && d.Left < o.Right && o.Top < d.Bottom && d.Top < o.Bottom))) {
|
|
@@ -863,14 +980,23 @@ export class SkiaControl {
|
|
|
863
980
|
this.DirtyChildrenInternal.clear();
|
|
864
981
|
for (const c of dirty)
|
|
865
982
|
this.DirtyChildrenInternal.add(c);
|
|
866
|
-
|
|
983
|
+
for (const c of inside)
|
|
984
|
+
this.DirtyChildrenInternal.add(c);
|
|
985
|
+
SkiaControl.CompositionCulling++;
|
|
986
|
+
try {
|
|
987
|
+
this.PaintContent({ ...ctx, Context: { Canvas: canvas, Surface: surface, Origin: { X: r.Left, Y: r.Top } } });
|
|
988
|
+
}
|
|
989
|
+
finally {
|
|
990
|
+
SkiaControl.CompositionCulling--;
|
|
991
|
+
}
|
|
867
992
|
this.IsRenderingWithComposition = false;
|
|
868
|
-
|
|
993
|
+
const redrawn = children.filter((c) => this.DirtyChildrenInternal.has(c));
|
|
994
|
+
this.LastCompositeRecord = { Mode: "partial", Children: redrawn.length, Dirty: redrawn, Areas: areas, Changed: changed };
|
|
869
995
|
}
|
|
870
996
|
else {
|
|
871
997
|
canvas.clear(CK.TRANSPARENT);
|
|
872
998
|
this.PaintContent({ ...ctx, Context: { Canvas: canvas, Surface: surface, Origin: { X: r.Left, Y: r.Top } } });
|
|
873
|
-
this.LastCompositeRecord = { Mode: "full", Children: children.length, Dirty: children };
|
|
999
|
+
this.LastCompositeRecord = { Mode: "full", Children: children.length, Dirty: children, Areas: [], Changed: [] };
|
|
874
1000
|
}
|
|
875
1001
|
canvas.restoreToCount(saved);
|
|
876
1002
|
for (const c of children)
|
|
@@ -916,7 +1042,11 @@ export class SkiaControl {
|
|
|
916
1042
|
/** Marks the cache stale; re-recorded on the next frame (a composite records fully: its own content changed). */
|
|
917
1043
|
InvalidateCache() { this.cacheDirty = true; this.compositeFull = true; }
|
|
918
1044
|
/** Effects margin recomputed on the next use (C# InvalidateEffectsMargin). */
|
|
919
|
-
InvalidateEffectsMargin() {
|
|
1045
|
+
InvalidateEffectsMargin() {
|
|
1046
|
+
// a layout's margin aggregates what its children paint outside it: every ancestor recomputes too (C# 97683f19)
|
|
1047
|
+
for (let c = this; c; c = c.Parent)
|
|
1048
|
+
c.effectsMarginCache = undefined;
|
|
1049
|
+
}
|
|
920
1050
|
/** Draws PostAnimators above content; an effect returning true asks for another frame. */
|
|
921
1051
|
ExecutePostAnimators(ctx) {
|
|
922
1052
|
if (this.PostAnimators.length === 0)
|
|
@@ -1134,15 +1264,84 @@ export class SkiaControl {
|
|
|
1134
1264
|
* cache holds its composited output, so those are staled before redrawing (DrawnUi RedrawCanvas + parent invalidation).
|
|
1135
1265
|
*/
|
|
1136
1266
|
RepaintComposition() {
|
|
1137
|
-
|
|
1138
|
-
|
|
1267
|
+
this.ReportToComposites();
|
|
1268
|
+
for (let p = this.Parent; p; p = p.Parent)
|
|
1139
1269
|
p.cacheDirty = true;
|
|
1140
|
-
p.TrackChildAsDirty(child);
|
|
1141
|
-
child = p;
|
|
1142
|
-
p = p.Parent;
|
|
1143
|
-
}
|
|
1144
1270
|
this.Repaint();
|
|
1145
1271
|
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Update() for a change of how this control looks, not of its size (a color set through props, C# NeedDraw): the same
|
|
1274
|
+
* re-measure and cache invalidation, but an ImageComposite above redraws only this control's area instead of
|
|
1275
|
+
* recording whole (drawnui-cross 6m). The record still goes whole when the control's rect changed after all.
|
|
1276
|
+
*/
|
|
1277
|
+
UpdateDraw() {
|
|
1278
|
+
this.ReportToComposites();
|
|
1279
|
+
const outer = SkiaControl.drawOrigin;
|
|
1280
|
+
SkiaControl.drawOrigin = this;
|
|
1281
|
+
try {
|
|
1282
|
+
this.InvalidateMeasure();
|
|
1283
|
+
}
|
|
1284
|
+
finally {
|
|
1285
|
+
SkiaControl.drawOrigin = outer;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* A change of this control that keeps its layout, to every ImageComposite above: a direct child is drawn again whole
|
|
1290
|
+
* (C# TrackChildAsDirty), a deeper control by its area, read now as it was drawn (its last matrix and effects margin).
|
|
1291
|
+
*/
|
|
1292
|
+
ReportToComposites() {
|
|
1293
|
+
let before;
|
|
1294
|
+
let child = this;
|
|
1295
|
+
for (let p = this.Parent; p; child = p, p = p.Parent) {
|
|
1296
|
+
if (!p.IsCacheComposite)
|
|
1297
|
+
continue;
|
|
1298
|
+
if (child === this) {
|
|
1299
|
+
p.TrackChildAsDirty(child);
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
const deep = p.compositeDeep ??= [];
|
|
1303
|
+
if (deep.some((d) => d.Origin === this))
|
|
1304
|
+
continue;
|
|
1305
|
+
before ??= this.GetTransformedDirtyBounds();
|
|
1306
|
+
const r = this.DrawingRect;
|
|
1307
|
+
deep.push({ Origin: this, Child: child, Before: before, Rect: new SKRect(r.Left, r.Top, r.Right, r.Bottom) });
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
/** The matrix this control will be drawn with (Render), from its current properties; undefined when none. */
|
|
1311
|
+
CurrentMatrix(scale) {
|
|
1312
|
+
return this.HasTransform ? this.CreateRenderTransformMatrix(this.DrawingRect, scale) : undefined;
|
|
1313
|
+
}
|
|
1314
|
+
/** Where this control will be drawn: its rect grown by its effects margin, through its current matrix. */
|
|
1315
|
+
DrawnBoundsNow(scale) { return SkiaControl.MapRect(this.CurrentMatrix(scale), this.ExpandedCacheRect(scale)); }
|
|
1316
|
+
static MapRect(m, r) {
|
|
1317
|
+
if (!m)
|
|
1318
|
+
return r;
|
|
1319
|
+
const pts = Super.CK.Matrix.mapPoints(m, [r.Left, r.Top, r.Right, r.Top, r.Right, r.Bottom, r.Left, r.Bottom]);
|
|
1320
|
+
return new SKRect(Math.min(pts[0], pts[2], pts[4], pts[6]), Math.min(pts[1], pts[3], pts[5], pts[7]), Math.max(pts[0], pts[2], pts[4], pts[6]), Math.max(pts[1], pts[3], pts[5], pts[7]));
|
|
1321
|
+
}
|
|
1322
|
+
/** Its output depends on all of its content (a visual effect; a backdrop samples below): a change inside redraws it whole. */
|
|
1323
|
+
get SamplesContent() { return !this.DisableEffects && this.visualEffects.length > 0; }
|
|
1324
|
+
/**
|
|
1325
|
+
* Where a deep change shows inside the composite (drawnui-cross 6m rule b): the control as drawn before and as drawn
|
|
1326
|
+
* now, then through the transform of every ancestor up to the composite. undefined: draw the child whole (an effect
|
|
1327
|
+
* or a backdrop on the way, the control gone from the child); null: the layout changed, record whole.
|
|
1328
|
+
*/
|
|
1329
|
+
static DeepArea(d, composite, scale) {
|
|
1330
|
+
const o = d.Origin, r = o.DrawingRect;
|
|
1331
|
+
if (r.Left !== d.Rect.Left || r.Top !== d.Rect.Top || r.Right !== d.Rect.Right || r.Bottom !== d.Rect.Bottom)
|
|
1332
|
+
return null;
|
|
1333
|
+
if (!o.IsVisible)
|
|
1334
|
+
return undefined;
|
|
1335
|
+
const now = o.DrawnBoundsNow(scale);
|
|
1336
|
+
let area = new SKRect(Math.min(now.Left, d.Before.Left), Math.min(now.Top, d.Before.Top), Math.max(now.Right, d.Before.Right), Math.max(now.Bottom, d.Before.Bottom));
|
|
1337
|
+
let p = o.Parent, child = o;
|
|
1338
|
+
for (; p && p !== composite; child = p, p = p.Parent) {
|
|
1339
|
+
if (p.SamplesContent || !p.IsVisible)
|
|
1340
|
+
return undefined;
|
|
1341
|
+
area = SkiaControl.MapRect(p.CurrentMatrix(scale), area);
|
|
1342
|
+
}
|
|
1343
|
+
return p === composite && child === d.Child ? area : undefined;
|
|
1344
|
+
}
|
|
1146
1345
|
/**
|
|
1147
1346
|
* DrawnUi IsParentIndependent: this control's size never changes the layout around it, so its own invalidation
|
|
1148
1347
|
* stops here (ancestors composite again, nothing above re-measures). The parent re-measures it on draw when
|
|
@@ -1153,7 +1352,9 @@ export class SkiaControl {
|
|
|
1153
1352
|
InvalidateMeasure() {
|
|
1154
1353
|
this.NeedMeasure = true;
|
|
1155
1354
|
this.cacheDirty = true;
|
|
1156
|
-
|
|
1355
|
+
// structure may change: a composite cannot patch it; a draw-only change (UpdateDraw) reported its area instead
|
|
1356
|
+
if (SkiaControl.drawOrigin === null || SkiaControl.drawOrigin === this)
|
|
1357
|
+
this.compositeFull = true;
|
|
1157
1358
|
this.effectsMarginCache = undefined;
|
|
1158
1359
|
if (this.Parent && this.IsParentIndependent)
|
|
1159
1360
|
this.RepaintComposition();
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./core/SkiaControl";
|
|
|
9
9
|
export * from "./core/Canvas";
|
|
10
10
|
export * from "./controls/SkiaLayout";
|
|
11
11
|
export * from "./core/Accessibility";
|
|
12
|
+
export * from "./core/Hover";
|
|
12
13
|
export * from "./controls/SkiaLabel";
|
|
13
14
|
export * from "./controls/TextSpan";
|
|
14
15
|
export * from "./controls/SkiaRichLabel";
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./core/SkiaControl";
|
|
|
9
9
|
export * from "./core/Canvas";
|
|
10
10
|
export * from "./controls/SkiaLayout";
|
|
11
11
|
export * from "./core/Accessibility";
|
|
12
|
+
export * from "./core/Hover";
|
|
12
13
|
export * from "./controls/SkiaLabel";
|
|
13
14
|
export * from "./controls/TextSpan";
|
|
14
15
|
export * from "./controls/SkiaRichLabel";
|
package/dist/react/index.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ import type { GesturesMode } from "../core/Gestures";
|
|
|
42
42
|
* and never appeared in completion.
|
|
43
43
|
*/
|
|
44
44
|
type PropsOf<T> = Partial<{
|
|
45
|
-
[K in keyof T as T[K] extends Function ? (undefined extends T[K] ? K : never) : K extends "Children" | "Views" | "Parent" | "Spans" | "GridStructure" | "AccessibilityId" | "IsAccessibilityElement" | "HasTransform" | "RenderTransformMatrix" | "RenderObjectPrevious" | "LastMeasuredIndex" | "ItemsInsertedAtStart" | "UsingControlStyle" | "Track" | "Thumb" | "FrameOn" | "FrameOff" | "ViewCheckOn" | "ViewOn" | "ViewText" | "Ratio" | "StartThumbX" | "EndThumbX" | "SnapPoints" | "CurrentPosition" | "CurrentSnap" | "ContentOffsetBounds" | "InTransition" | "CanAnimate" | "MaxIndex" | "ChildrenTotal" | "IsAtStart" | "IsAtEnd" | "ScrollProgress" | "ScrollAmount" | "TransitionProgress" | "LastIndex" | "ChildrenCount" | "Horizontal" | "Animator" | "Animation" | "IsPlaying" | "PlayWhenAvailable" | "TotalFrames" | "HasEffects" | "IsDisposed" | "Label" | "IsMultiline" | "HasSelection" | "LayoutVersion" | "LinesCount" | "MeasuredLineHeight" | "Focused" | "SpriteSheet" | "FrameWidth" | "FrameHeight" | "DurationMs" | "FrameDurationMs" | "CurrentSprite" | "Rects" | "HasTapHandler" | "HasDecorations" | "LinesCount" | "Superview" | "DrawingRect" | "MeasuredSize" | "RenderingScale" | "NeedMeasure" | "_superview" | "HitBoxAuto" | "TotalDown" | "TotalTapped" | "TouchDown" | "PostAnimators" | "LoadedSource" | "IsLoading" | "DisplayRect" | "AspectScale" | "Content" | "ContentSize" | "ContentOffsetBounds" | "OverscrollDistance" | "OverScrolled" | "IsUserPanning" | "IsUserFocused" | "IsScrolling" | "IsTemplated" | "FirstVisibleIndex" | "LastVisibleIndex" | "DebugString" | "ChildrenFactory" | "ContextIndex" | "RenderObject" | "UsingCacheType" | "TransitionEffect" | "TransitionFromIndex" | "TransitionToIndex" | "EffectPostRenderers" | "CachedImage" | "ImageBitmap" | "ShouldSubmitOnEnter" | "Header" | "Footer" | "RefreshIndicator" | "ScrollBar" | "ScrollBarHorizontal" | "IsRunning" | "VisibleRatio" | "CurrentIndex" | "LastCompositeRecord" | "DirtyChildrenInternal" | "IsRenderingWithComposition" ? never : K]: T[K];
|
|
45
|
+
[K in keyof T as T[K] extends Function ? (undefined extends T[K] ? K : never) : K extends "Children" | "Views" | "Parent" | "Spans" | "GridStructure" | "AccessibilityId" | "IsAccessibilityElement" | "HasTransform" | "RenderTransformMatrix" | "RenderObjectPrevious" | "LastMeasuredIndex" | "ItemsInsertedAtStart" | "UsingControlStyle" | "Track" | "Thumb" | "FrameOn" | "FrameOff" | "ViewCheckOn" | "ViewOn" | "ViewText" | "Ratio" | "StartThumbX" | "EndThumbX" | "SnapPoints" | "CurrentPosition" | "CurrentSnap" | "ContentOffsetBounds" | "InTransition" | "CanAnimate" | "MaxIndex" | "ChildrenTotal" | "IsAtStart" | "IsAtEnd" | "ScrollProgress" | "ScrollAmount" | "TransitionProgress" | "LastIndex" | "ChildrenCount" | "Horizontal" | "Animator" | "Animation" | "IsPlaying" | "PlayWhenAvailable" | "TotalFrames" | "HasEffects" | "IsDisposed" | "Label" | "IsMultiline" | "HasSelection" | "LayoutVersion" | "LinesCount" | "MeasuredLineHeight" | "Focused" | "SpriteSheet" | "FrameWidth" | "FrameHeight" | "DurationMs" | "FrameDurationMs" | "CurrentSprite" | "Rects" | "HasTapHandler" | "HasDecorations" | "LinesCount" | "Superview" | "DrawingRect" | "MeasuredSize" | "RenderingScale" | "NeedMeasure" | "_superview" | "HitBoxAuto" | "TotalDown" | "TotalTapped" | "TouchDown" | "PostAnimators" | "LoadedSource" | "IsLoading" | "DisplayRect" | "AspectScale" | "Content" | "ContentSize" | "ContentOffsetBounds" | "OverscrollDistance" | "OverScrolled" | "IsUserPanning" | "IsUserFocused" | "IsScrolling" | "IsTemplated" | "FirstVisibleIndex" | "LastVisibleIndex" | "DebugString" | "ChildrenFactory" | "ContextIndex" | "RenderObject" | "UsingCacheType" | "TransitionEffect" | "TransitionFromIndex" | "TransitionToIndex" | "EffectPostRenderers" | "CachedImage" | "ImageBitmap" | "ShouldSubmitOnEnter" | "Header" | "Footer" | "RefreshIndicator" | "ScrollBar" | "ScrollBarHorizontal" | "IsRunning" | "VisibleRatio" | "CurrentIndex" | "LastCompositeRecord" | "DirtyChildrenInternal" | "IsRenderingWithComposition" | "IsHovered" ? never : K]: T[K];
|
|
46
46
|
}>;
|
|
47
47
|
/** `ref` receives the engine control instance (react-reconciler getPublicInstance). */
|
|
48
48
|
type LeafProps<T> = PropsOf<T> & {
|
package/dist/react/index.js
CHANGED
|
@@ -221,9 +221,9 @@ function AccessibilityOverlay({ view }) {
|
|
|
221
221
|
n.Source.NotifyAccessibilityFocused(false);
|
|
222
222
|
}, children: n.Label }, n.Id)) : n.TextLines ? (
|
|
223
223
|
// AccessibilityTextSelectable: one positioned span per drawn line, in the drawn font, so the browser selects and copies it like HTML
|
|
224
|
-
_jsx("div", { role: n.Role, title: n.Hint, "aria-live": n.Live, className: "drawnui-a11y-node drawnui-a11y-text", style: pos, children: n.TextLines.map((l, i) => (_jsxs("span", { "data-w": l.Width, style: { left: l.Left, top: l.Top, height: l.Height, fontFamily: l.FontFamily, fontWeight: l.FontWeight, fontSize: l.FontSize, lineHeight: `${l.Height}px` }, children: [l.Text, i < n.TextLines.length - 1 ? "\n" : ""] }, i))) }, n.Id)) : (
|
|
224
|
+
_jsx("div", { role: n.Role, title: n.Hint, "aria-disabled": n.Disabled || undefined, "aria-live": n.Live, className: "drawnui-a11y-node drawnui-a11y-text", style: pos, children: n.TextLines.map((l, i) => (_jsxs("span", { "data-w": l.Width, style: { left: l.Left, top: l.Top, height: l.Height, fontFamily: l.FontFamily, fontWeight: l.FontWeight, fontSize: l.FontSize, lineHeight: `${l.Height}px` }, children: [l.Text, i < n.TextLines.length - 1 ? "\n" : ""] }, i))) }, n.Id)) : (
|
|
225
225
|
// static text is exposed as real (transparent) text content; aria-label only for roles that need a name
|
|
226
|
-
_jsx("div", { role: n.Role, "aria-label": n.Role === "text" ? undefined : n.Label, title: n.Hint, "aria-live": n.Live, className: "drawnui-a11y-node", style: pos, children: n.Label }, n.Id));
|
|
226
|
+
_jsx("div", { role: n.Role, "aria-label": n.Role === "text" ? undefined : n.Label, title: n.Hint, "aria-disabled": n.Disabled || undefined, "aria-live": n.Live, className: "drawnui-a11y-node", style: pos, children: n.Label }, n.Id));
|
|
227
227
|
})] }));
|
|
228
228
|
}
|
|
229
229
|
// One import for apps: React tags + every engine type (Colors, Thickness, Super, gestures, animators...).
|
package/dist/react/reconciler.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createContext } from "react";
|
|
2
2
|
import Reconciler from "react-reconciler";
|
|
3
3
|
import { ConcurrentRoot, DefaultEventPriority } from "react-reconciler/constants";
|
|
4
|
+
import { SkiaControl } from "../core/SkiaControl";
|
|
4
5
|
import { SkiaLabel } from "../controls/SkiaLabel";
|
|
5
6
|
import { TextSpan } from "../controls/TextSpan";
|
|
6
7
|
import { SkiaRichLabel } from "../controls/SkiaRichLabel";
|
|
@@ -38,12 +39,14 @@ export const Registry = {
|
|
|
38
39
|
const SKIP = new Set(["children", "key", "ref"]);
|
|
39
40
|
/** Props that change how the subtree is composited, not what it contains: repaint, keep caches (DrawnUi RedrawCanvas). */
|
|
40
41
|
const REPAINT_ONLY = new Set(["TranslationX", "TranslationY", "Rotation", "ScaleX", "ScaleY", "Scale", "SkewX", "SkewY", "AnchorX", "AnchorY", "Opacity"]);
|
|
42
|
+
/** Props that change only how a control looks, never its size (C# NeedDraw): an ImageComposite above redraws only its area. */
|
|
43
|
+
const isDrawOnly = (k) => k.endsWith("Color") || k === "FillGradient" || k === "StrokeGradient";
|
|
41
44
|
/**
|
|
42
45
|
* Assigns changed props straight onto the control (same names as the C# properties).
|
|
43
46
|
* Handler props (functions) are swapped without invalidating: inline arrows change identity every render.
|
|
44
47
|
*/
|
|
45
48
|
function applyProps(inst, prev, next) {
|
|
46
|
-
let changed = false, repaint = false;
|
|
49
|
+
let changed = false, repaint = false, layout = false;
|
|
47
50
|
for (const k in next) {
|
|
48
51
|
if (SKIP.has(k) || (prev && prev[k] === next[k]))
|
|
49
52
|
continue;
|
|
@@ -52,23 +55,36 @@ function applyProps(inst, prev, next) {
|
|
|
52
55
|
continue;
|
|
53
56
|
if (REPAINT_ONLY.has(k))
|
|
54
57
|
repaint = true;
|
|
55
|
-
else
|
|
58
|
+
else {
|
|
56
59
|
changed = true;
|
|
60
|
+
if (!isDrawOnly(k))
|
|
61
|
+
layout = true;
|
|
62
|
+
}
|
|
57
63
|
}
|
|
58
64
|
if (prev)
|
|
59
65
|
for (const k in prev)
|
|
60
66
|
if (!SKIP.has(k) && !(k in next)) {
|
|
61
67
|
inst[k] = new inst.constructor()[k];
|
|
62
|
-
changed = true;
|
|
68
|
+
changed = layout = true;
|
|
63
69
|
}
|
|
64
|
-
if (changed && prev)
|
|
65
|
-
inst
|
|
70
|
+
if (changed && prev) {
|
|
71
|
+
if (layout || !(inst instanceof SkiaControl))
|
|
72
|
+
inst.Update();
|
|
73
|
+
else
|
|
74
|
+
inst.UpdateDraw();
|
|
75
|
+
}
|
|
66
76
|
else if (repaint && prev)
|
|
67
77
|
inst.RepaintComposition?.();
|
|
68
78
|
}
|
|
69
79
|
let currentUpdatePriority = DefaultEventPriority;
|
|
70
80
|
const noop = () => { };
|
|
71
81
|
const hostContext = {};
|
|
82
|
+
function appendChild(parent, child) {
|
|
83
|
+
if (parent instanceof SkiaLayout)
|
|
84
|
+
return parent.InsertChild(child);
|
|
85
|
+
child.IsChildrenItem = true;
|
|
86
|
+
parent.AddSubView(child);
|
|
87
|
+
}
|
|
72
88
|
// Record<string, unknown> absorbs members that react-reconciler 0.33 reads but @types 0.32 does not declare yet.
|
|
73
89
|
const hostConfig = {
|
|
74
90
|
supportsMutation: true,
|
|
@@ -94,12 +110,15 @@ const hostConfig = {
|
|
|
94
110
|
createTextInstance(text) {
|
|
95
111
|
throw new Error(`DrawnUi: raw text "${text}" is not allowed, use <SkiaLabel Text="..." />`);
|
|
96
112
|
},
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
// JSX children are the app's children (C# IsChildrenItem): a layout keeps them in JSX order inside the slots they
|
|
114
|
+
// occupy, so its own parts keep their place; an existing child passed again is a move
|
|
115
|
+
appendInitialChild: (parent, child) => appendChild(parent, child),
|
|
116
|
+
appendChild: (parent, child) => appendChild(parent, child),
|
|
99
117
|
insertBefore: (parent, child, before) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
118
|
+
if (parent instanceof SkiaLayout)
|
|
119
|
+
return parent.InsertChild(child, before);
|
|
120
|
+
child.IsChildrenItem = true;
|
|
121
|
+
parent.InsertSubView(parent instanceof SkiaLabel ? parent.Spans.indexOf(before) : 0, child);
|
|
103
122
|
},
|
|
104
123
|
removeChild: (parent, child) => parent.RemoveSubView(child),
|
|
105
124
|
appendChildToContainer: (canvas, child) => { canvas.Content = child; },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drawnui-react",
|
|
3
|
-
"version": "0.1.0-preview.
|
|
3
|
+
"version": "0.1.0-preview.16",
|
|
4
4
|
"description": "DrawnUi for React: the DrawnUi (.NET) drawn-UI engine in TypeScript on CanvasKit (Skia WASM). Same controls, same PascalCase API as DrawnUi.Net.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nick Kovalsky (AppoMobi)",
|
|
@@ -56,6 +56,9 @@
|
|
|
56
56
|
"check:recycling": "node dev/check.mjs dev/check-recycling.ts",
|
|
57
57
|
"check:keyboard": "node dev/check.mjs dev/check-keyboard.ts",
|
|
58
58
|
"check:wheel": "node dev/check.mjs dev/check-wheel.ts",
|
|
59
|
+
"check:children": "node dev/check.mjs dev/check-children.tsx",
|
|
60
|
+
"check:composite": "node dev/check.mjs dev/check-composite.tsx",
|
|
61
|
+
"check:hover": "node dev/check.mjs dev/check-hover.ts",
|
|
59
62
|
"site:extras": "node dev/site-extras.mjs dist/demo",
|
|
60
63
|
"build:lib": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
|
|
61
64
|
"prepublishOnly": "npm run build:lib",
|