drawnui-react 0.1.0-preview.1 → 0.1.0-preview.11
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 +98 -29
- package/SKIPPED.md +23 -16
- package/dist/controls/DrawnGame.d.ts +37 -0
- package/dist/controls/DrawnGame.js +60 -0
- package/dist/controls/SkiaButton.d.ts +4 -1
- package/dist/controls/SkiaButton.js +20 -7
- package/dist/controls/SkiaCarousel.d.ts +6 -0
- package/dist/controls/SkiaCarousel.js +7 -1
- package/dist/controls/SkiaCheckbox.d.ts +0 -1
- package/dist/controls/SkiaCheckbox.js +0 -6
- package/dist/controls/SkiaEditor.js +8 -0
- package/dist/controls/SkiaLabel.d.ts +2 -0
- package/dist/controls/SkiaLabel.js +9 -0
- package/dist/controls/SkiaLayout.d.ts +10 -0
- package/dist/controls/SkiaLayout.js +64 -1
- package/dist/controls/SkiaRadioButton.js +1 -2
- package/dist/controls/SkiaScroll.d.ts +9 -0
- package/dist/controls/SkiaScroll.js +120 -13
- package/dist/controls/SkiaScrollBar.d.ts +27 -0
- package/dist/controls/SkiaScrollBar.js +51 -2
- package/dist/controls/SkiaShaderCarousel.d.ts +5 -0
- package/dist/controls/SkiaShaderCarousel.js +5 -0
- package/dist/controls/SkiaSwitch.d.ts +0 -1
- package/dist/controls/SkiaSwitch.js +0 -6
- package/dist/controls/SkiaToggle.d.ts +18 -4
- package/dist/controls/SkiaToggle.js +45 -3
- package/dist/core/Animators.d.ts +9 -0
- package/dist/core/Animators.js +26 -1
- package/dist/core/Canvas.d.ts +82 -4
- package/dist/core/Canvas.js +261 -10
- package/dist/core/Gestures.d.ts +47 -1
- package/dist/core/Gestures.js +43 -1
- package/dist/core/SkiaControl.d.ts +38 -2
- package/dist/core/SkiaControl.js +76 -3
- package/dist/core/Styles.d.ts +51 -0
- package/dist/core/Styles.js +92 -0
- package/dist/core/Super.d.ts +10 -1
- package/dist/core/Super.js +20 -3
- package/dist/core/Types.d.ts +7 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/react/SkiaShell.js +5 -2
- package/dist/react/index.d.ts +22 -4
- package/dist/react/index.js +25 -4
- package/dist/react/reconciler.js +2 -0
- package/dist/vite/index.d.ts +74 -0
- package/dist/vite/index.js +204 -0
- package/package.json +35 -4
- package/PARITY.md +0 -287
package/dist/core/Gestures.js
CHANGED
|
@@ -18,9 +18,20 @@ export class DistanceInfo {
|
|
|
18
18
|
Total = SKPoint.Empty;
|
|
19
19
|
Velocity = SKPoint.Empty;
|
|
20
20
|
}
|
|
21
|
+
/** AppoMobi.Gestures PointerData: the device and button behind a Down / Up / Tapped (every button is delivered). */
|
|
22
|
+
export class PointerData {
|
|
23
|
+
Button = "Left";
|
|
24
|
+
/** 1 = Left, 2 = Right, 3 = Middle, 4+ = extended, like AppoMobi.Gestures. */
|
|
25
|
+
ButtonNumber = 1;
|
|
26
|
+
DeviceType = "Mouse";
|
|
27
|
+
/** DOM `buttons` bitmask of the buttons held (1 left, 2 right, 4 middle, 8 back, 16 forward). */
|
|
28
|
+
PressedButtons = 0;
|
|
29
|
+
}
|
|
21
30
|
/** AppoMobi.Gestures TouchActionEventArgs. */
|
|
22
31
|
export class TouchActionEventArgs {
|
|
23
32
|
Id = 0;
|
|
33
|
+
/** Device and button of this event (mouse, pen, touch); undefined for wheel. */
|
|
34
|
+
Pointer;
|
|
24
35
|
Type = "Pressed";
|
|
25
36
|
/** Pixels. */
|
|
26
37
|
Location = SKPoint.Empty;
|
|
@@ -34,6 +45,11 @@ export class TouchActionEventArgs {
|
|
|
34
45
|
DeltaTimeMs = 0;
|
|
35
46
|
/** Mouse wheel: Delta > 0 = wheel down (browser deltaY sign). */
|
|
36
47
|
Wheel = { Delta: 0 };
|
|
48
|
+
/**
|
|
49
|
+
* AppoMobi TouchActionEventArgs.Handled: set by a control that actually used the event. The canvas prevents the
|
|
50
|
+
* browser default (the page scrolling under the wheel) only for a Handled event in `Gestures="Enabled"`.
|
|
51
|
+
*/
|
|
52
|
+
Handled = false;
|
|
37
53
|
/** Same as the .NET helper: derives Start/End/Delta/Total from the previous event of the same pointer. */
|
|
38
54
|
static FillDistanceInfo(current, previous) {
|
|
39
55
|
if (!previous) {
|
|
@@ -91,7 +107,33 @@ export class SkiaGesturesInfo {
|
|
|
91
107
|
this.Info = Info;
|
|
92
108
|
}
|
|
93
109
|
}
|
|
94
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* Arguments of SkiaControl.ContextMenu / Canvas.ContextMenu: a browser `contextmenu` request on the canvas (right
|
|
112
|
+
* click, long press on touch, the Menu key). Handlers return true to take it: the browser's own menu ("Save image")
|
|
113
|
+
* is then suppressed; otherwise it shows as usual.
|
|
114
|
+
*/
|
|
115
|
+
export class ContextMenuEventArgs {
|
|
116
|
+
Location;
|
|
117
|
+
Pixels;
|
|
118
|
+
Source;
|
|
119
|
+
Native;
|
|
120
|
+
/** Deepest control under the point that had a ContextMenu handler (set while routing). */
|
|
121
|
+
Control;
|
|
122
|
+
/** Point inside Control, in pixels relative to its DrawingRect origin (set while routing). */
|
|
123
|
+
Local = SKPoint.Empty;
|
|
124
|
+
constructor(
|
|
125
|
+
/** Point on the canvas, in points (CSS px). */
|
|
126
|
+
Location,
|
|
127
|
+
/** Same point in pixels (canvas space). */
|
|
128
|
+
Pixels, Source,
|
|
129
|
+
/** The DOM event: modifiers, target, preventDefault if you need it yourself. */
|
|
130
|
+
Native) {
|
|
131
|
+
this.Location = Location;
|
|
132
|
+
this.Pixels = Pixels;
|
|
133
|
+
this.Source = Source;
|
|
134
|
+
this.Native = Native;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
95
137
|
export class ControlTappedEventArgs {
|
|
96
138
|
Control;
|
|
97
139
|
Parameters;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Canvas as SkCanvas, Image, Path, SkPicture, Surface } from "canvaskit-wasm";
|
|
2
2
|
import { type Color, type LayoutOptions, SKRect, ScaledSize, type SkiaCacheType, type SkiaGradient, type SkiaTouchAnimation, Thickness } from "./Types";
|
|
3
|
-
import { type IOverlayEffect } from "./Animators";
|
|
3
|
+
import { type AnimatorBase, type IOverlayEffect } from "./Animators";
|
|
4
4
|
import { Easing } from "./Easing";
|
|
5
5
|
import type { Canvas } from "./Canvas";
|
|
6
|
-
import { ControlTappedEventArgs, GestureEventProcessingInfo, type LockTouch, SKPoint, SkiaGesturesInfo, SkiaGesturesParameters } from "./Gestures";
|
|
6
|
+
import { ContextMenuEventArgs, ControlTappedEventArgs, GestureEventProcessingInfo, type LockTouch, SKPoint, SkiaGesturesInfo, SkiaGesturesParameters } from "./Gestures";
|
|
7
7
|
import { type CachedTexture, type IPostRendererEffect, type SkiaEffect } from "./SkiaEffect";
|
|
8
8
|
/** Mirrors DrawnUi DrawingContext: ctx.Context.Canvas / Surface, ctx.Destination (pixels), ctx.Scale. */
|
|
9
9
|
export interface DrawingContext {
|
|
@@ -164,6 +164,11 @@ export declare class SkiaControl {
|
|
|
164
164
|
AnimationTappedSpeed: number;
|
|
165
165
|
/** Overlay effects drawn above this control's content every frame (ripple etc). */
|
|
166
166
|
readonly PostAnimators: IOverlayEffect[];
|
|
167
|
+
/**
|
|
168
|
+
* C# PendingUnattachedAnimators: animators started while no Canvas was above this control yet (e.g. a game loop
|
|
169
|
+
* started in a constructor). They start on the control's first arrange, where C# starts them in SuperViewChanged.
|
|
170
|
+
*/
|
|
171
|
+
PendingUnattachedAnimators?: AnimatorBase[];
|
|
167
172
|
private visualEffects;
|
|
168
173
|
/** Post-renderer effects among VisualEffects (C# EffectPostRenderers). */
|
|
169
174
|
EffectPostRenderers: (SkiaEffect & IPostRendererEffect)[];
|
|
@@ -180,11 +185,24 @@ export declare class SkiaControl {
|
|
|
180
185
|
/** Clip overlay effects to the control's shape (CreateClip). */
|
|
181
186
|
ClipEffects: boolean;
|
|
182
187
|
Tapped?: (sender: SkiaControl, e: ControlTappedEventArgs) => void;
|
|
188
|
+
/**
|
|
189
|
+
* Context-menu request over this control (right click, long press on touch, Menu key). Return true to handle it:
|
|
190
|
+
* the browser's own canvas menu is suppressed. Routed deepest child first, then parents, then Canvas.ContextMenu.
|
|
191
|
+
* Not in DrawnUi.Net (a web concept).
|
|
192
|
+
*/
|
|
193
|
+
ContextMenu?: (sender: SkiaControl, e: ContextMenuEventArgs) => boolean | void;
|
|
183
194
|
ChildTapped?: (sender: SkiaControl, e: ControlTappedEventArgs) => void;
|
|
184
195
|
/** Raw gesture hook: set e.Consumed = true to stop propagation (not for Up). */
|
|
185
196
|
ConsumeGestures?: (sender: SkiaControl, e: SkiaGesturesInfo) => void;
|
|
186
197
|
Parent?: SkiaControl;
|
|
187
198
|
/** Containers override; a leaf control cannot host children. */
|
|
199
|
+
private styledInitially;
|
|
200
|
+
/**
|
|
201
|
+
* C# ApplyInitialStyles: takes the property defaults registered with `ConfigureStyles` for this control's class.
|
|
202
|
+
* The reconciler calls it with `atConstruction` right after `new`, before the JSX props, so props win; a control
|
|
203
|
+
* built in code-behind is styled on its first measure and keeps whatever the code already set.
|
|
204
|
+
*/
|
|
205
|
+
ApplyInitialStyles(atConstruction?: boolean): void;
|
|
188
206
|
AddSubView(_control: SkiaControl): void;
|
|
189
207
|
InsertSubView(_index: number, control: SkiaControl): void;
|
|
190
208
|
RemoveSubView(_control: SkiaControl): void;
|
|
@@ -207,6 +225,8 @@ export declare class SkiaControl {
|
|
|
207
225
|
protected MeasureAbsolute(_widthConstraint: number, _heightConstraint: number, scale: number): ScaledSize;
|
|
208
226
|
/** Places MeasuredSize inside destination per Margin + Horizontal/VerticalOptions -> DrawingRect. */
|
|
209
227
|
Arrange(destination: SKRect, _widthRequest: number, _heightRequest: number, scale: number): void;
|
|
228
|
+
/** C# SuperViewChanged: starts the animators queued before this control was attached. */
|
|
229
|
+
private StartPendingAnimators;
|
|
210
230
|
private static nextAccessibilityId;
|
|
211
231
|
/** Stable identity for the DOM overlay. */
|
|
212
232
|
readonly AccessibilityId: number;
|
|
@@ -235,6 +255,11 @@ export declare class SkiaControl {
|
|
|
235
255
|
get AccessibilityCanInteract(): boolean;
|
|
236
256
|
set AccessibilityCanInteract(v: boolean);
|
|
237
257
|
protected DefaultAccessibilityCanInteract(): boolean;
|
|
258
|
+
/**
|
|
259
|
+
* Whether the mouse at this point (pixels, relative to DrawingRect's origin) is over something tappable, for the
|
|
260
|
+
* host's pointer cursor. Default: the control as a whole (AccessibilityCanInteract); SkiaLabel adds its tappable spans.
|
|
261
|
+
*/
|
|
262
|
+
WantsPointerCursor(_x: number, _y: number): boolean;
|
|
238
263
|
/** aria-pressed for toggles; undefined = not a toggle. */
|
|
239
264
|
get AccessibilityIsPressed(): boolean | undefined;
|
|
240
265
|
set AccessibilityIsPressed(v: boolean | undefined);
|
|
@@ -364,12 +389,23 @@ export declare class SkiaControl {
|
|
|
364
389
|
* cache holds its composited output, so those are staled before redrawing (DrawnUi RedrawCanvas + parent invalidation).
|
|
365
390
|
*/
|
|
366
391
|
RepaintComposition(): void;
|
|
392
|
+
/**
|
|
393
|
+
* DrawnUi IsParentIndependent: this control's size never changes the layout around it, so its own invalidation
|
|
394
|
+
* stops here (ancestors composite again, nothing above re-measures). The parent re-measures it on draw when
|
|
395
|
+
* NeedMeasure is set. Used by SkiaScroll for its scroll bars.
|
|
396
|
+
*/
|
|
397
|
+
IsParentIndependent: boolean;
|
|
367
398
|
/** Size or content may have changed: this control and all ancestors remeasure and re-record. */
|
|
368
399
|
InvalidateMeasure(): void;
|
|
369
400
|
/** Hit rect in pixels (no transforms in this port, so it is DrawingRect). */
|
|
370
401
|
get HitBoxAuto(): SKRect;
|
|
371
402
|
HitIsInside(x: number, y: number): boolean;
|
|
372
403
|
IsGestureForChild(child: SkiaControl, point: SKPoint): boolean;
|
|
404
|
+
/**
|
|
405
|
+
* Routes a context-menu request like a tap: visible, non-transparent children under the point first (top-most
|
|
406
|
+
* first, each in its own transformed space), then this control's ContextMenu handler. True = handled.
|
|
407
|
+
*/
|
|
408
|
+
ProcessContextMenu(point: SKPoint, e: ContextMenuEventArgs): boolean;
|
|
373
409
|
/** Children that may receive gestures, top-most LAST (layouts return their Views). */
|
|
374
410
|
protected GetGestureListeners(): readonly SkiaControl[];
|
|
375
411
|
private static readonly NoListeners;
|
package/dist/core/SkiaControl.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Super } from "./Super";
|
|
2
|
+
import { SkiaStyles } from "./Styles";
|
|
2
3
|
import { Colors, SKRect, ScaledSize, Thickness } from "./Types";
|
|
3
4
|
import { RippleAnimator, SkiaValueAnimator } from "./Animators";
|
|
4
5
|
import { Easing } from "./Easing";
|
|
@@ -198,6 +199,11 @@ export class SkiaControl {
|
|
|
198
199
|
AnimationTappedSpeed = 0;
|
|
199
200
|
/** Overlay effects drawn above this control's content every frame (ripple etc). */
|
|
200
201
|
PostAnimators = [];
|
|
202
|
+
/**
|
|
203
|
+
* C# PendingUnattachedAnimators: animators started while no Canvas was above this control yet (e.g. a game loop
|
|
204
|
+
* started in a constructor). They start on the control's first arrange, where C# starts them in SuperViewChanged.
|
|
205
|
+
*/
|
|
206
|
+
PendingUnattachedAnimators;
|
|
201
207
|
// ---- visual effects (C# VisualEffects: attached SkiaEffect objects) ----
|
|
202
208
|
visualEffects = [];
|
|
203
209
|
/** Post-renderer effects among VisualEffects (C# EffectPostRenderers). */
|
|
@@ -238,12 +244,29 @@ export class SkiaControl {
|
|
|
238
244
|
ClipEffects = true;
|
|
239
245
|
// ---- gesture events (single handler each; C# events map to one callback prop) ----
|
|
240
246
|
Tapped;
|
|
247
|
+
/**
|
|
248
|
+
* Context-menu request over this control (right click, long press on touch, Menu key). Return true to handle it:
|
|
249
|
+
* the browser's own canvas menu is suppressed. Routed deepest child first, then parents, then Canvas.ContextMenu.
|
|
250
|
+
* Not in DrawnUi.Net (a web concept).
|
|
251
|
+
*/
|
|
252
|
+
ContextMenu;
|
|
241
253
|
ChildTapped;
|
|
242
254
|
/** Raw gesture hook: set e.Consumed = true to stop propagation (not for Up). */
|
|
243
255
|
ConsumeGestures;
|
|
244
256
|
// ---- tree ----
|
|
245
257
|
Parent;
|
|
246
258
|
/** Containers override; a leaf control cannot host children. */
|
|
259
|
+
styledInitially = false;
|
|
260
|
+
/**
|
|
261
|
+
* C# ApplyInitialStyles: takes the property defaults registered with `ConfigureStyles` for this control's class.
|
|
262
|
+
* The reconciler calls it with `atConstruction` right after `new`, before the JSX props, so props win; a control
|
|
263
|
+
* built in code-behind is styled on its first measure and keeps whatever the code already set.
|
|
264
|
+
*/
|
|
265
|
+
ApplyInitialStyles(atConstruction = false) {
|
|
266
|
+
this.styledInitially = true;
|
|
267
|
+
if (!SkiaStyles.IsEmpty)
|
|
268
|
+
SkiaStyles.Apply(this, atConstruction);
|
|
269
|
+
}
|
|
247
270
|
AddSubView(_control) { throw new Error(`DrawnUi: ${this.constructor.name} cannot host children`); }
|
|
248
271
|
InsertSubView(_index, control) { this.AddSubView(control); }
|
|
249
272
|
RemoveSubView(_control) { }
|
|
@@ -265,6 +288,8 @@ export class SkiaControl {
|
|
|
265
288
|
* this is what keeps a cached tree from re-measuring text every frame.
|
|
266
289
|
*/
|
|
267
290
|
Measure(widthConstraint, heightConstraint, scale) {
|
|
291
|
+
if (!this.styledInitially)
|
|
292
|
+
this.ApplyInitialStyles(false);
|
|
268
293
|
if (!this.NeedMeasure && this.RenderingScale === scale
|
|
269
294
|
&& Object.is(this.lastWidthConstraint, widthConstraint) && Object.is(this.lastHeightConstraint, heightConstraint)) {
|
|
270
295
|
return this.MeasuredSize;
|
|
@@ -343,10 +368,21 @@ export class SkiaControl {
|
|
|
343
368
|
const y = availT + SkiaControl.Align(this.VerticalOptions, fullH, h);
|
|
344
369
|
this.DrawingRect = SKRect.Create(x, y, w, h);
|
|
345
370
|
this.OnLayoutChanged();
|
|
371
|
+
if (this.PendingUnattachedAnimators)
|
|
372
|
+
this.StartPendingAnimators();
|
|
346
373
|
// DrawnUi OnLayoutReady: an accessible control registers itself on its first layout
|
|
347
374
|
if (!this.registeredWithAccessibility && this.IsAccessibilityElement)
|
|
348
375
|
this.NotifyAccessibility();
|
|
349
376
|
}
|
|
377
|
+
/** C# SuperViewChanged: starts the animators queued before this control was attached. */
|
|
378
|
+
StartPendingAnimators() {
|
|
379
|
+
if (!this.Superview)
|
|
380
|
+
return;
|
|
381
|
+
const pending = this.PendingUnattachedAnimators;
|
|
382
|
+
this.PendingUnattachedAnimators = undefined;
|
|
383
|
+
for (const animator of pending)
|
|
384
|
+
animator.Start();
|
|
385
|
+
}
|
|
350
386
|
// ---- accessibility (DrawnUi ISkiaAccessibilityNode) ----
|
|
351
387
|
static nextAccessibilityId = 1;
|
|
352
388
|
/** Stable identity for the DOM overlay. */
|
|
@@ -388,6 +424,11 @@ export class SkiaControl {
|
|
|
388
424
|
this.AccessibilityChanged();
|
|
389
425
|
} }
|
|
390
426
|
DefaultAccessibilityCanInteract() { return !!this.Tapped; }
|
|
427
|
+
/**
|
|
428
|
+
* Whether the mouse at this point (pixels, relative to DrawingRect's origin) is over something tappable, for the
|
|
429
|
+
* host's pointer cursor. Default: the control as a whole (AccessibilityCanInteract); SkiaLabel adds its tappable spans.
|
|
430
|
+
*/
|
|
431
|
+
WantsPointerCursor(_x, _y) { return this.AccessibilityCanInteract; }
|
|
391
432
|
/** aria-pressed for toggles; undefined = not a toggle. */
|
|
392
433
|
get AccessibilityIsPressed() { return this.accessibilityIsPressed; }
|
|
393
434
|
set AccessibilityIsPressed(v) { if (this.accessibilityIsPressed !== v) {
|
|
@@ -1013,6 +1054,7 @@ export class SkiaControl {
|
|
|
1013
1054
|
this.ownAnimations.clear();
|
|
1014
1055
|
for (const e of [...this.PostAnimators])
|
|
1015
1056
|
e.Stop?.();
|
|
1057
|
+
this.PendingUnattachedAnimators = undefined;
|
|
1016
1058
|
this.PostAnimators.length = 0;
|
|
1017
1059
|
this.DestroyRenderingObject();
|
|
1018
1060
|
this.compositeSurface?.delete();
|
|
@@ -1057,13 +1099,21 @@ export class SkiaControl {
|
|
|
1057
1099
|
}
|
|
1058
1100
|
this.Repaint();
|
|
1059
1101
|
}
|
|
1102
|
+
/**
|
|
1103
|
+
* DrawnUi IsParentIndependent: this control's size never changes the layout around it, so its own invalidation
|
|
1104
|
+
* stops here (ancestors composite again, nothing above re-measures). The parent re-measures it on draw when
|
|
1105
|
+
* NeedMeasure is set. Used by SkiaScroll for its scroll bars.
|
|
1106
|
+
*/
|
|
1107
|
+
IsParentIndependent = false;
|
|
1060
1108
|
/** Size or content may have changed: this control and all ancestors remeasure and re-record. */
|
|
1061
1109
|
InvalidateMeasure() {
|
|
1062
1110
|
this.NeedMeasure = true;
|
|
1063
1111
|
this.cacheDirty = true;
|
|
1064
1112
|
this.compositeFull = true; // structure may change: a composite cannot patch it
|
|
1065
1113
|
this.effectsMarginCache = undefined;
|
|
1066
|
-
if (this.Parent)
|
|
1114
|
+
if (this.Parent && this.IsParentIndependent)
|
|
1115
|
+
this.RepaintComposition();
|
|
1116
|
+
else if (this.Parent)
|
|
1067
1117
|
this.Parent.InvalidateMeasure();
|
|
1068
1118
|
else
|
|
1069
1119
|
this.Superview?.Update();
|
|
@@ -1079,6 +1129,25 @@ export class SkiaControl {
|
|
|
1079
1129
|
const local = child.TransformPointToLocalSpace(point);
|
|
1080
1130
|
return child.HitIsInside(local.X, local.Y);
|
|
1081
1131
|
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Routes a context-menu request like a tap: visible, non-transparent children under the point first (top-most
|
|
1134
|
+
* first, each in its own transformed space), then this control's ContextMenu handler. True = handled.
|
|
1135
|
+
*/
|
|
1136
|
+
ProcessContextMenu(point, e) {
|
|
1137
|
+
const listeners = this.GetGestureListeners();
|
|
1138
|
+
for (let i = listeners.length - 1; i >= 0; i--) {
|
|
1139
|
+
const listener = listeners[i];
|
|
1140
|
+
if (!listener.IsVisible || listener.InputTransparent || !this.IsGestureForChild(listener, point))
|
|
1141
|
+
continue;
|
|
1142
|
+
if (listener.ProcessContextMenu(listener.TransformPointToLocalSpace(point), e))
|
|
1143
|
+
return true;
|
|
1144
|
+
}
|
|
1145
|
+
if (!this.ContextMenu)
|
|
1146
|
+
return false;
|
|
1147
|
+
e.Control = this;
|
|
1148
|
+
e.Local = new SKPoint(point.X - this.DrawingRect.Left, point.Y - this.DrawingRect.Top);
|
|
1149
|
+
return this.ContextMenu(this, e) === true;
|
|
1150
|
+
}
|
|
1082
1151
|
/** Children that may receive gestures, top-most LAST (layouts return their Views). */
|
|
1083
1152
|
GetGestureListeners() { return SkiaControl.NoListeners; }
|
|
1084
1153
|
static NoListeners = [];
|
|
@@ -1106,15 +1175,19 @@ export class SkiaControl {
|
|
|
1106
1175
|
if (this.ConsumeGestures) {
|
|
1107
1176
|
const sent = new SkiaGesturesInfo(args, apply);
|
|
1108
1177
|
this.ConsumeGestures(this, sent);
|
|
1109
|
-
if (args.Type !== "Up" && sent.Consumed)
|
|
1178
|
+
if (args.Type !== "Up" && sent.Consumed) {
|
|
1179
|
+
args.Event.Handled = true;
|
|
1110
1180
|
return this;
|
|
1181
|
+
}
|
|
1111
1182
|
}
|
|
1112
1183
|
// C# EffectsGestureProcessors: attached effects that process gestures (ISkiaGestureProcessor) see them first
|
|
1113
1184
|
if (!this.DisableEffects)
|
|
1114
1185
|
for (const e of this.visualEffects) {
|
|
1115
1186
|
const p = e.ProcessGestures;
|
|
1116
|
-
if (typeof p === "function" && p.call(e, args, apply) && args.Type !== "Up")
|
|
1187
|
+
if (typeof p === "function" && p.call(e, args, apply) && args.Type !== "Up") {
|
|
1188
|
+
args.Event.Handled = true;
|
|
1117
1189
|
return this;
|
|
1190
|
+
}
|
|
1118
1191
|
}
|
|
1119
1192
|
if (this.CheckChildrenGesturesLocked(args.Type))
|
|
1120
1193
|
return consumedDefault;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { SkiaControl } from "./SkiaControl";
|
|
2
|
+
/**
|
|
3
|
+
* Mirrors DrawnUi.Net `Style` (SharedNet/Internals/Core/Style.cs): a set of property defaults for one control type,
|
|
4
|
+
* registered once at startup through `Super.UseDrawnUi().ConfigureStyles(...)`.
|
|
5
|
+
*
|
|
6
|
+
* The C# `Setters` list of `Setter { Property, Value }` becomes a plain object here, because React controls are
|
|
7
|
+
* classes with plain properties instead of BindableProperty: `Setters: { FontFamily: "FontText", FontSize: 14 }`.
|
|
8
|
+
*/
|
|
9
|
+
export interface Style {
|
|
10
|
+
/** The control class the style targets, e.g. `SkiaLabel`. */
|
|
11
|
+
TargetType: Function;
|
|
12
|
+
/** Also style classes deriving from TargetType (C# Style.ApplyToDerivedTypes). Default false. */
|
|
13
|
+
ApplyToDerivedTypes?: boolean;
|
|
14
|
+
/** Property defaults, by property name. Anything the app sets itself wins over these. */
|
|
15
|
+
Setters: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
/** Mirrors DrawnUi.Net `IStylesCollection` / `StylesCollection`: the startup registry, static like the C# one. */
|
|
18
|
+
export declare class StylesCollection {
|
|
19
|
+
/** Every style registered through ConfigureStyles, in registration order (C# StylesCollection.Styles). */
|
|
20
|
+
static readonly Styles: Style[];
|
|
21
|
+
AddStyle(style: Style): StylesCollection;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Applies registered styles to controls. Port of the C# `SkiaControl` styles partial
|
|
25
|
+
* (SharedNet/Draw/SkiaControl.NetShared.cs) with one difference forced by the language: C# knows which properties the
|
|
26
|
+
* app set explicitly (BindableProperty + ExplicitPropertiesSet) and skips those; here a property counts as "not set by
|
|
27
|
+
* the app" while it still equals the value the control's own constructor left, captured once per class.
|
|
28
|
+
*/
|
|
29
|
+
export declare class SkiaStyles {
|
|
30
|
+
private static readonly resolved;
|
|
31
|
+
private static readonly defaults;
|
|
32
|
+
/** True while no style is registered: lets Measure skip the whole feature at zero cost. */
|
|
33
|
+
static get IsEmpty(): boolean;
|
|
34
|
+
/** C# InvalidateStylesCache: drop the per-type resolution after the registry changed. */
|
|
35
|
+
static InvalidateStylesCache(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Setters that apply to a control class: the exact-type style plus every ApplyToDerivedTypes style of a base class,
|
|
38
|
+
* furthest base first so a nearer style overrides it, exact type last (C# sorts by inheritance distance).
|
|
39
|
+
*/
|
|
40
|
+
private static For;
|
|
41
|
+
/** The values a freshly constructed control of this class carries, for the properties a style touches. */
|
|
42
|
+
private static SnapshotDefaults;
|
|
43
|
+
/**
|
|
44
|
+
* C# ApplyInitialStyles: applies the registered defaults to one control. `atConstruction` is true when nothing has
|
|
45
|
+
* touched the control yet (the reconciler calls it between `new` and the JSX props), so every setter applies and the
|
|
46
|
+
* pre-style values are kept as this class's defaults; a control styled later only gets the properties that still
|
|
47
|
+
* hold those defaults, which is how an explicit assignment wins — the C# ExplicitPropertiesSet rule without
|
|
48
|
+
* property accessors.
|
|
49
|
+
*/
|
|
50
|
+
static Apply(control: SkiaControl, atConstruction: boolean): void;
|
|
51
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** Mirrors DrawnUi.Net `IStylesCollection` / `StylesCollection`: the startup registry, static like the C# one. */
|
|
2
|
+
export class StylesCollection {
|
|
3
|
+
/** Every style registered through ConfigureStyles, in registration order (C# StylesCollection.Styles). */
|
|
4
|
+
static Styles = [];
|
|
5
|
+
AddStyle(style) {
|
|
6
|
+
StylesCollection.Styles.push(style);
|
|
7
|
+
SkiaStyles.InvalidateStylesCache();
|
|
8
|
+
return this;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Applies registered styles to controls. Port of the C# `SkiaControl` styles partial
|
|
13
|
+
* (SharedNet/Draw/SkiaControl.NetShared.cs) with one difference forced by the language: C# knows which properties the
|
|
14
|
+
* app set explicitly (BindableProperty + ExplicitPropertiesSet) and skips those; here a property counts as "not set by
|
|
15
|
+
* the app" while it still equals the value the control's own constructor left, captured once per class.
|
|
16
|
+
*/
|
|
17
|
+
export class SkiaStyles {
|
|
18
|
+
static resolved = new Map();
|
|
19
|
+
static defaults = new Map();
|
|
20
|
+
/** True while no style is registered: lets Measure skip the whole feature at zero cost. */
|
|
21
|
+
static get IsEmpty() { return StylesCollection.Styles.length === 0; }
|
|
22
|
+
/** C# InvalidateStylesCache: drop the per-type resolution after the registry changed. */
|
|
23
|
+
static InvalidateStylesCache() { SkiaStyles.resolved.clear(); SkiaStyles.defaults.clear(); }
|
|
24
|
+
/**
|
|
25
|
+
* Setters that apply to a control class: the exact-type style plus every ApplyToDerivedTypes style of a base class,
|
|
26
|
+
* furthest base first so a nearer style overrides it, exact type last (C# sorts by inheritance distance).
|
|
27
|
+
*/
|
|
28
|
+
static For(type) {
|
|
29
|
+
let hit = SkiaStyles.resolved.get(type);
|
|
30
|
+
if (hit)
|
|
31
|
+
return hit;
|
|
32
|
+
const inherited = [];
|
|
33
|
+
let exact;
|
|
34
|
+
for (const style of StylesCollection.Styles) {
|
|
35
|
+
if (style.TargetType === type) {
|
|
36
|
+
exact = style;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (!style.ApplyToDerivedTypes)
|
|
40
|
+
continue;
|
|
41
|
+
let distance = 0;
|
|
42
|
+
for (let base = Object.getPrototypeOf(type); base; base = Object.getPrototypeOf(base)) {
|
|
43
|
+
distance++;
|
|
44
|
+
if (base === style.TargetType) {
|
|
45
|
+
inherited.push({ Distance: distance, Style: style });
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
inherited.sort((a, b) => b.Distance - a.Distance);
|
|
51
|
+
const setters = {};
|
|
52
|
+
for (const i of inherited)
|
|
53
|
+
Object.assign(setters, i.Style.Setters);
|
|
54
|
+
if (exact)
|
|
55
|
+
Object.assign(setters, exact.Setters);
|
|
56
|
+
hit = { Setters: setters, Empty: Object.keys(setters).length === 0 };
|
|
57
|
+
SkiaStyles.resolved.set(type, hit);
|
|
58
|
+
return hit;
|
|
59
|
+
}
|
|
60
|
+
/** The values a freshly constructed control of this class carries, for the properties a style touches. */
|
|
61
|
+
static SnapshotDefaults(type, sample, keys) {
|
|
62
|
+
if (SkiaStyles.defaults.has(type))
|
|
63
|
+
return;
|
|
64
|
+
const known = {};
|
|
65
|
+
for (const key of keys)
|
|
66
|
+
known[key] = sample[key];
|
|
67
|
+
SkiaStyles.defaults.set(type, known);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* C# ApplyInitialStyles: applies the registered defaults to one control. `atConstruction` is true when nothing has
|
|
71
|
+
* touched the control yet (the reconciler calls it between `new` and the JSX props), so every setter applies and the
|
|
72
|
+
* pre-style values are kept as this class's defaults; a control styled later only gets the properties that still
|
|
73
|
+
* hold those defaults, which is how an explicit assignment wins — the C# ExplicitPropertiesSet rule without
|
|
74
|
+
* property accessors.
|
|
75
|
+
*/
|
|
76
|
+
static Apply(control, atConstruction) {
|
|
77
|
+
const type = control.constructor;
|
|
78
|
+
const resolved = SkiaStyles.For(type);
|
|
79
|
+
if (resolved.Empty)
|
|
80
|
+
return;
|
|
81
|
+
const keys = Object.keys(resolved.Setters);
|
|
82
|
+
const target = control;
|
|
83
|
+
if (atConstruction)
|
|
84
|
+
SkiaStyles.SnapshotDefaults(type, control, keys);
|
|
85
|
+
const defaults = atConstruction ? undefined : SkiaStyles.defaults.get(type);
|
|
86
|
+
for (const key of keys) {
|
|
87
|
+
// no snapshot for this class (nothing of it ever went through the reconciler): apply as a plain default
|
|
88
|
+
if (!defaults || Object.is(target[key], defaults[key]))
|
|
89
|
+
target[key] = resolved.Setters[key];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
package/dist/core/Super.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Thickness } from "./Types";
|
|
2
|
+
import { StylesCollection } from "./Styles";
|
|
2
3
|
import type { CanvasKit, Font, Typeface } from "canvaskit-wasm";
|
|
3
4
|
/** Mirrors DrawnUi.Net IFontCollection: fonts.AddFont(source, alias[, weight]), plus the Blazor-head AddSymbols/AddEmojis. */
|
|
4
5
|
export declare class FontCollection {
|
|
@@ -16,10 +17,16 @@ export declare class FontCollection {
|
|
|
16
17
|
/** Registers a face under an alias; weight 100..900 lets FontWeight / FontAttributes=Bold pick the right file. */
|
|
17
18
|
AddFont(source: string, alias: string, weight?: number): FontCollection;
|
|
18
19
|
}
|
|
19
|
-
/** Mirrors DrawnUi.Net DrawnUiBuilder: Super.UseDrawnUi().ConfigureFonts(...).BuildAsync(). */
|
|
20
|
+
/** Mirrors DrawnUi.Net DrawnUiBuilder: Super.UseDrawnUi().ConfigureFonts(...).ConfigureStyles(...).BuildAsync(). */
|
|
20
21
|
export declare class DrawnUiBuilder {
|
|
21
22
|
private readonly fonts;
|
|
22
23
|
ConfigureFonts(configure: (fonts: FontCollection) => void): DrawnUiBuilder;
|
|
24
|
+
/**
|
|
25
|
+
* Mirrors DrawnUi.Net ConfigureStyles: property defaults per control type, applied to every control unless the app
|
|
26
|
+
* sets that property itself. The usual one, same as the .NET Blazor / Fiddle hosts:
|
|
27
|
+
* `styles.AddStyle({ TargetType: SkiaLabel, ApplyToDerivedTypes: true, Setters: { FontFamily: "FontText" } })`.
|
|
28
|
+
*/
|
|
29
|
+
ConfigureStyles(configure: (styles: StylesCollection) => void): DrawnUiBuilder;
|
|
23
30
|
/** Loads CanvasKit + registered fonts. Must complete before the first Canvas is created. */
|
|
24
31
|
BuildAsync(): Promise<void>;
|
|
25
32
|
}
|
|
@@ -39,6 +46,8 @@ export declare class Super {
|
|
|
39
46
|
static readonly Fonts: Map<string, Map<number, Typeface>>;
|
|
40
47
|
/** First registered font, or CanvasKit's built-in one. */
|
|
41
48
|
static DefaultTypeface?: Typeface;
|
|
49
|
+
/** CanvasKit's built-in face: what an empty FontFamily resolves to, like C# SKTypeface.CreateDefault(). */
|
|
50
|
+
static SkiaDefaultTypeface?: Typeface;
|
|
42
51
|
private static readonly fontCache;
|
|
43
52
|
static UseDrawnUi(): DrawnUiBuilder;
|
|
44
53
|
/** Master switch: false makes every control render uncached (DrawnUi Super.CacheEnabled). */
|
package/dist/core/Super.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Thickness } from "./Types";
|
|
2
|
+
import { StylesCollection } from "./Styles";
|
|
2
3
|
// the "full" build: same API plus Skottie (SkiaLottie) and the paragraph module; +0.9 MB raw over the default build
|
|
3
4
|
import CanvasKitInit from "canvaskit-wasm/bin/full/canvaskit.js";
|
|
4
5
|
import wasmUrl from "canvaskit-wasm/bin/full/canvaskit.wasm?url";
|
|
@@ -22,13 +23,22 @@ export class FontCollection {
|
|
|
22
23
|
return this;
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
|
-
/** Mirrors DrawnUi.Net DrawnUiBuilder: Super.UseDrawnUi().ConfigureFonts(...).BuildAsync(). */
|
|
26
|
+
/** Mirrors DrawnUi.Net DrawnUiBuilder: Super.UseDrawnUi().ConfigureFonts(...).ConfigureStyles(...).BuildAsync(). */
|
|
26
27
|
export class DrawnUiBuilder {
|
|
27
28
|
fonts = new FontCollection();
|
|
28
29
|
ConfigureFonts(configure) {
|
|
29
30
|
configure(this.fonts);
|
|
30
31
|
return this;
|
|
31
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Mirrors DrawnUi.Net ConfigureStyles: property defaults per control type, applied to every control unless the app
|
|
35
|
+
* sets that property itself. The usual one, same as the .NET Blazor / Fiddle hosts:
|
|
36
|
+
* `styles.AddStyle({ TargetType: SkiaLabel, ApplyToDerivedTypes: true, Setters: { FontFamily: "FontText" } })`.
|
|
37
|
+
*/
|
|
38
|
+
ConfigureStyles(configure) {
|
|
39
|
+
configure(new StylesCollection());
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
32
42
|
/** Loads CanvasKit + registered fonts. Must complete before the first Canvas is created. */
|
|
33
43
|
async BuildAsync() {
|
|
34
44
|
Super.CK = await CanvasKitInit({ locateFile: () => wasmUrl });
|
|
@@ -61,7 +71,8 @@ export class DrawnUiBuilder {
|
|
|
61
71
|
if (!Super.DefaultFontAlias)
|
|
62
72
|
Super.DefaultFontAlias = f.Alias;
|
|
63
73
|
}
|
|
64
|
-
Super.
|
|
74
|
+
Super.SkiaDefaultTypeface ??= Super.CK.Typeface.GetDefault() ?? undefined;
|
|
75
|
+
Super.DefaultTypeface ??= Super.SkiaDefaultTypeface;
|
|
65
76
|
}
|
|
66
77
|
}
|
|
67
78
|
/** Mirrors DrawnUi static Super: global engine state. */
|
|
@@ -105,6 +116,8 @@ export class Super {
|
|
|
105
116
|
static Fonts = new Map();
|
|
106
117
|
/** First registered font, or CanvasKit's built-in one. */
|
|
107
118
|
static DefaultTypeface;
|
|
119
|
+
/** CanvasKit's built-in face: what an empty FontFamily resolves to, like C# SKTypeface.CreateDefault(). */
|
|
120
|
+
static SkiaDefaultTypeface;
|
|
108
121
|
static fontCache = new Map();
|
|
109
122
|
static UseDrawnUi() { return new DrawnUiBuilder(); }
|
|
110
123
|
/** Master switch: false makes every control render uncached (DrawnUi Super.CacheEnabled). */
|
|
@@ -145,7 +158,11 @@ export class Super {
|
|
|
145
158
|
}
|
|
146
159
|
/** Nearest registered weight of the alias (empty alias = the first registered family); reports the weight actually used. */
|
|
147
160
|
static ResolveTypeface(alias, weight) {
|
|
148
|
-
|
|
161
|
+
// No family: the Skia built-in face, like C# SkiaFontManager.GetFont("") -> SKTypeface.CreateDefault(). An app that
|
|
162
|
+
// wants its own font everywhere registers it as a style default (ConfigureStyles), exactly as the .NET hosts do.
|
|
163
|
+
if (!alias)
|
|
164
|
+
return { Typeface: Super.SkiaDefaultTypeface ?? Super.DefaultTypeface ?? null, Weight: 400 };
|
|
165
|
+
const weights = Super.Fonts.get(alias);
|
|
149
166
|
const target = weight > 0 ? weight : 400;
|
|
150
167
|
if (weights && weights.size > 0) {
|
|
151
168
|
if (weights.has(target))
|
package/dist/core/Types.d.ts
CHANGED
|
@@ -134,9 +134,14 @@ export type DrawImageAlignment = "Start" | "Center" | "End";
|
|
|
134
134
|
/** MAUI/DrawnUi ScrollOrientation. */
|
|
135
135
|
export type ScrollOrientation = "Vertical" | "Horizontal" | "Both" | "Neither";
|
|
136
136
|
/**
|
|
137
|
-
* DrawnUi SkiaCacheType. Operations = recorded draw commands (SkPicture) replayed each frame
|
|
137
|
+
* DrawnUi SkiaCacheType. Operations / OperationsFull = recorded draw commands (SkPicture) replayed each frame.
|
|
138
138
|
* Image = offscreen surface snapshot blitted each frame (GPU-backed when the canvas is WebGL).
|
|
139
|
-
*
|
|
139
|
+
* ImageDoubleBuffered = an Image cache that keeps the previous one and draws it — or DrawPlaceholder when there is
|
|
140
|
+
* none — while a new one cannot be produced yet.
|
|
141
|
+
* ImageComposite / ImageCompositeGPU = an Image cache whose offscreen surface survives between records: when only
|
|
142
|
+
* children changed, their old and new bounds plus the siblings those overlap are erased and only those children are
|
|
143
|
+
* painted again.
|
|
144
|
+
* GPU resolves to Image and ImageCompositeGPU to ImageComposite; the browser has no separate GPU-only path.
|
|
140
145
|
*/
|
|
141
146
|
export type SkiaCacheType = "None" | "Operations" | "OperationsFull" | "Image" | "ImageDoubleBuffered" | "ImageComposite" | "ImageCompositeGPU" | "GPU";
|
|
142
147
|
/** DrawnUi GradientType (Conical accepted, drawn as Circular). */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./core/Types";
|
|
2
2
|
export * from "./core/Gestures";
|
|
3
|
+
export * from "./core/Styles";
|
|
3
4
|
export * from "./core/Easing";
|
|
4
5
|
export * from "./core/Animators";
|
|
5
6
|
export * from "./core/ScrollAnimators";
|
|
@@ -39,6 +40,7 @@ export * from "./controls/SkiaBackdrop";
|
|
|
39
40
|
export * from "./controls/SkiaEditor";
|
|
40
41
|
export * from "./controls/SkiaSprite";
|
|
41
42
|
export * from "./controls/SkiaSpriteSet";
|
|
43
|
+
export * from "./controls/DrawnGame";
|
|
42
44
|
export * from "./core/KeyboardManager";
|
|
43
45
|
export * from "./controls/AnimatedFramesRenderer";
|
|
44
46
|
export * from "./controls/SkiaLottie";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./core/Types";
|
|
2
2
|
export * from "./core/Gestures";
|
|
3
|
+
export * from "./core/Styles";
|
|
3
4
|
export * from "./core/Easing";
|
|
4
5
|
export * from "./core/Animators";
|
|
5
6
|
export * from "./core/ScrollAnimators";
|
|
@@ -39,6 +40,7 @@ export * from "./controls/SkiaBackdrop";
|
|
|
39
40
|
export * from "./controls/SkiaEditor";
|
|
40
41
|
export * from "./controls/SkiaSprite";
|
|
41
42
|
export * from "./controls/SkiaSpriteSet";
|
|
43
|
+
export * from "./controls/DrawnGame";
|
|
42
44
|
export * from "./core/KeyboardManager";
|
|
43
45
|
export * from "./controls/AnimatedFramesRenderer";
|
|
44
46
|
export * from "./controls/SkiaLottie";
|
package/dist/react/SkiaShell.js
CHANGED
|
@@ -440,9 +440,12 @@ export const SkiaShell = forwardRef(function SkiaShell({ Routes, children, NavBa
|
|
|
440
440
|
}
|
|
441
441
|
})();
|
|
442
442
|
}
|
|
443
|
-
else
|
|
444
|
-
// forward
|
|
443
|
+
else {
|
|
444
|
+
// forward (depth grew), or the same depth with another hash = a plain <a href="#/route"> link or a typed hash:
|
|
445
|
+
// the hash names the pages, rebuild the stack without animation (overlays cannot be restored)
|
|
445
446
|
const target = parseHash();
|
|
447
|
+
if (depth === history.current.length && target.join("/") === live.current.stack.join("/"))
|
|
448
|
+
return;
|
|
446
449
|
history.current = target.map(() => "page");
|
|
447
450
|
window.history.replaceState({ [HISTORY_KEY]: history.current.length }, "", hashFor(target));
|
|
448
451
|
setStacks((all) => { const next = all.slice(); next[live.current.selectedTab] = target; return next; });
|