drawnui-react 0.1.0-preview.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/PARITY.md +287 -0
  3. package/README.md +92 -0
  4. package/SKIPPED.md +107 -0
  5. package/dist/controls/AnimatedFramesRenderer.d.ts +58 -0
  6. package/dist/controls/AnimatedFramesRenderer.js +133 -0
  7. package/dist/controls/GridStructure.d.ts +79 -0
  8. package/dist/controls/GridStructure.js +403 -0
  9. package/dist/controls/RefreshIndicator.d.ts +35 -0
  10. package/dist/controls/RefreshIndicator.js +82 -0
  11. package/dist/controls/SkiaBackdrop.d.ts +25 -0
  12. package/dist/controls/SkiaBackdrop.js +80 -0
  13. package/dist/controls/SkiaButton.d.ts +58 -0
  14. package/dist/controls/SkiaButton.js +148 -0
  15. package/dist/controls/SkiaCarousel.d.ts +133 -0
  16. package/dist/controls/SkiaCarousel.js +603 -0
  17. package/dist/controls/SkiaCheckbox.d.ts +21 -0
  18. package/dist/controls/SkiaCheckbox.js +118 -0
  19. package/dist/controls/SkiaDecoratedGrid.d.ts +15 -0
  20. package/dist/controls/SkiaDecoratedGrid.js +46 -0
  21. package/dist/controls/SkiaDrawer.d.ts +46 -0
  22. package/dist/controls/SkiaDrawer.js +212 -0
  23. package/dist/controls/SkiaDynamicDrawnCell.d.ts +11 -0
  24. package/dist/controls/SkiaDynamicDrawnCell.js +17 -0
  25. package/dist/controls/SkiaEditor.d.ts +163 -0
  26. package/dist/controls/SkiaEditor.js +803 -0
  27. package/dist/controls/SkiaGif.d.ts +54 -0
  28. package/dist/controls/SkiaGif.js +206 -0
  29. package/dist/controls/SkiaHotspot.d.ts +13 -0
  30. package/dist/controls/SkiaHotspot.js +36 -0
  31. package/dist/controls/SkiaImage.d.ts +75 -0
  32. package/dist/controls/SkiaImage.js +222 -0
  33. package/dist/controls/SkiaImageTiles.d.ts +27 -0
  34. package/dist/controls/SkiaImageTiles.js +76 -0
  35. package/dist/controls/SkiaLabel.d.ts +160 -0
  36. package/dist/controls/SkiaLabel.js +580 -0
  37. package/dist/controls/SkiaLayout.d.ts +163 -0
  38. package/dist/controls/SkiaLayout.js +723 -0
  39. package/dist/controls/SkiaLottie.d.ts +75 -0
  40. package/dist/controls/SkiaLottie.js +290 -0
  41. package/dist/controls/SkiaProgress.d.ts +40 -0
  42. package/dist/controls/SkiaProgress.js +80 -0
  43. package/dist/controls/SkiaRadioButton.d.ts +32 -0
  44. package/dist/controls/SkiaRadioButton.js +157 -0
  45. package/dist/controls/SkiaRichLabel.d.ts +77 -0
  46. package/dist/controls/SkiaRichLabel.js +274 -0
  47. package/dist/controls/SkiaScroll.d.ts +231 -0
  48. package/dist/controls/SkiaScroll.js +958 -0
  49. package/dist/controls/SkiaScrollBar.d.ts +56 -0
  50. package/dist/controls/SkiaScrollBar.js +158 -0
  51. package/dist/controls/SkiaShaderCarousel.d.ts +67 -0
  52. package/dist/controls/SkiaShaderCarousel.js +246 -0
  53. package/dist/controls/SkiaShape.d.ts +63 -0
  54. package/dist/controls/SkiaShape.js +317 -0
  55. package/dist/controls/SkiaSlider.d.ts +74 -0
  56. package/dist/controls/SkiaSlider.js +243 -0
  57. package/dist/controls/SkiaSprite.d.ts +99 -0
  58. package/dist/controls/SkiaSprite.js +324 -0
  59. package/dist/controls/SkiaSpriteSet.d.ts +27 -0
  60. package/dist/controls/SkiaSpriteSet.js +75 -0
  61. package/dist/controls/SkiaSvg.d.ts +43 -0
  62. package/dist/controls/SkiaSvg.js +181 -0
  63. package/dist/controls/SkiaSwitch.d.ts +26 -0
  64. package/dist/controls/SkiaSwitch.js +147 -0
  65. package/dist/controls/SkiaToggle.d.ts +50 -0
  66. package/dist/controls/SkiaToggle.js +71 -0
  67. package/dist/controls/SnappingLayout.d.ts +78 -0
  68. package/dist/controls/SnappingLayout.js +217 -0
  69. package/dist/controls/TextSpan.d.ts +70 -0
  70. package/dist/controls/TextSpan.js +76 -0
  71. package/dist/core/Accessibility.d.ts +104 -0
  72. package/dist/core/Accessibility.js +150 -0
  73. package/dist/core/Animators.d.ts +82 -0
  74. package/dist/core/Animators.js +247 -0
  75. package/dist/core/Canvas.d.ts +89 -0
  76. package/dist/core/Canvas.js +328 -0
  77. package/dist/core/ControlStyle.d.ts +6 -0
  78. package/dist/core/ControlStyle.js +11 -0
  79. package/dist/core/Easing.d.ts +13 -0
  80. package/dist/core/Easing.js +15 -0
  81. package/dist/core/Gestures.d.ts +74 -0
  82. package/dist/core/Gestures.js +104 -0
  83. package/dist/core/ImageEffects.d.ts +29 -0
  84. package/dist/core/ImageEffects.js +55 -0
  85. package/dist/core/KeyboardManager.d.ts +25 -0
  86. package/dist/core/KeyboardManager.js +54 -0
  87. package/dist/core/ScrollAnimators.d.ts +90 -0
  88. package/dist/core/ScrollAnimators.js +244 -0
  89. package/dist/core/SkiaControl.d.ts +401 -0
  90. package/dist/core/SkiaControl.js +1239 -0
  91. package/dist/core/SkiaEffect.d.ts +216 -0
  92. package/dist/core/SkiaEffect.js +571 -0
  93. package/dist/core/SkiaImageManager.d.ts +43 -0
  94. package/dist/core/SkiaImageManager.js +122 -0
  95. package/dist/core/Super.d.ts +61 -0
  96. package/dist/core/Super.js +178 -0
  97. package/dist/core/TextInputProxy.d.ts +31 -0
  98. package/dist/core/TextInputProxy.js +144 -0
  99. package/dist/core/Types.d.ts +177 -0
  100. package/dist/core/Types.js +122 -0
  101. package/dist/core/ViewsAdapter.d.ts +41 -0
  102. package/dist/core/ViewsAdapter.js +113 -0
  103. package/dist/index.d.ts +48 -0
  104. package/dist/index.js +48 -0
  105. package/dist/react/SkiaShell.d.ts +144 -0
  106. package/dist/react/SkiaShell.js +490 -0
  107. package/dist/react/index.d.ts +110 -0
  108. package/dist/react/index.js +158 -0
  109. package/dist/react/reconciler.d.ts +14 -0
  110. package/dist/react/reconciler.js +160 -0
  111. package/package.json +60 -0
@@ -0,0 +1,328 @@
1
+ import { Super } from "./Super";
2
+ import { SkiaAccessibilityManager } from "./Accessibility";
3
+ import { Colors, SKRect } from "./Types";
4
+ import { GestureEventProcessingInfo, SKPoint, SkiaGesturesParameters, TouchActionEventArgs, } from "./Gestures";
5
+ /**
6
+ * Mirrors DrawnUi Canvas (DrawnView): hosts one Content control on an HTML canvas element,
7
+ * owns RenderingScale (devicePixelRatio), the surface, the on-demand frame loop and raw input.
8
+ * Frames are drawn only after Update() (invalidation), never continuously.
9
+ * Gestures are accumulated and processed in order at the START of the next frame, like DrawnUi.
10
+ */
11
+ export class Canvas {
12
+ Element;
13
+ /** Points a pointer may travel between Down and Up and still count as a tap (AppoMobi TouchEffect default). */
14
+ static TappedCancelMoveThresholdPoints = 16;
15
+ BackgroundColor = Colors.Transparent;
16
+ /** Accelerated = WebGL surface, Default = software. Read once at first frame. */
17
+ RenderingMode = "Accelerated";
18
+ RenderingScale = 1;
19
+ /** Duration of the last Draw in ms (measure + arrange + render, excluding GPU flush). */
20
+ FrameTime = 0;
21
+ /** Frames per second over the last second of drawn frames. */
22
+ FPS = 0;
23
+ frameTimes = [];
24
+ /** Registry + rate-limited snapshot of accessible controls, rendered by the DOM overlay (DrawnUi AccessibilityManager). */
25
+ AccessibilityManager = new SkiaAccessibilityManager();
26
+ content;
27
+ get Content() { return this.content; }
28
+ set Content(value) {
29
+ if (this.content)
30
+ this.content._superview = undefined;
31
+ this.content = value;
32
+ if (value) {
33
+ value.Parent = undefined;
34
+ value._superview = this;
35
+ }
36
+ this.Update();
37
+ }
38
+ surface;
39
+ /** GL context + GrContext live for the Canvas lifetime; only the on-screen surface is recreated on resize. */
40
+ glHandle;
41
+ grContext;
42
+ frameId = 0;
43
+ disposed = false;
44
+ observer;
45
+ constructor(Element) {
46
+ this.Element = Element;
47
+ if (!Super.CK)
48
+ throw new Error("DrawnUi: call Super.UseDrawnUi()...BuildAsync() before creating a Canvas");
49
+ this.observer = new ResizeObserver(() => this.OnResized());
50
+ this.observer.observe(Element);
51
+ this.OnResized();
52
+ }
53
+ /** Request a redraw (full measure + arrange + render) on the next animation frame. */
54
+ Update() {
55
+ if (this.frameId || !this.surface || this.disposed)
56
+ return;
57
+ const surface = this.surface;
58
+ this.frameId = surface.requestAnimationFrame((c) => {
59
+ this.frameId = 0;
60
+ if (this.surface === surface && !this.disposed) {
61
+ this.Draw(c);
62
+ surface.flush();
63
+ this.DrainDisposeQueue();
64
+ }
65
+ });
66
+ }
67
+ /** Deletes the current surface; a pending frame on it is cancelled first (drawing on a deleted surface faults). */
68
+ ReleaseSurface() {
69
+ if (this.frameId) {
70
+ cancelAnimationFrame(this.frameId);
71
+ this.frameId = 0;
72
+ }
73
+ this.surface?.delete();
74
+ this.surface = undefined;
75
+ }
76
+ /** On-screen surface at the element's current pixel size, reusing the GL/GrContext when accelerated. */
77
+ CreateSurface(w, h) {
78
+ const CK = Super.CK;
79
+ if (this.RenderingMode === "Accelerated") {
80
+ if (!this.grContext) {
81
+ this.glHandle = CK.GetWebGLContext(this.Element);
82
+ this.grContext = (this.glHandle ? CK.MakeWebGLContext(this.glHandle) : null) ?? undefined;
83
+ }
84
+ if (this.grContext) {
85
+ const gl = CK.MakeOnScreenGLSurface(this.grContext, w, h, CK.ColorSpace.SRGB);
86
+ if (gl)
87
+ return gl;
88
+ }
89
+ }
90
+ return CK.MakeSWCanvasSurface(this.Element) ?? undefined;
91
+ }
92
+ /**
93
+ * Resize = the browser wipes the bitmap when width/height change, so the new frame is drawn
94
+ * SYNCHRONOUSLY here. ResizeObserver callbacks run after layout and before paint, so the
95
+ * redrawn content lands in the same paint and no blank frame is ever shown while dragging.
96
+ */
97
+ OnResized() {
98
+ const dpr = window.devicePixelRatio || 1;
99
+ const w = Math.max(1, Math.round(this.Element.clientWidth * dpr));
100
+ const h = Math.max(1, Math.round(this.Element.clientHeight * dpr));
101
+ if (this.surface && this.Element.width === w && this.Element.height === h && this.RenderingScale === dpr)
102
+ return;
103
+ this.RenderingScale = dpr;
104
+ this.Element.width = w;
105
+ this.Element.height = h;
106
+ this.ReleaseSurface();
107
+ this.surface = this.CreateSurface(w, h);
108
+ if (!this.surface)
109
+ throw new Error("DrawnUi: cannot create surface");
110
+ this.DrawNow();
111
+ }
112
+ /** Draws one frame immediately (outside the rAF loop) and presents it. */
113
+ DrawNow() {
114
+ const surface = this.surface;
115
+ if (!surface || this.disposed)
116
+ return;
117
+ this.Draw(surface.getCanvas());
118
+ surface.flush();
119
+ this.DrainDisposeQueue();
120
+ }
121
+ Draw(canvas) {
122
+ const started = performance.now();
123
+ this.ProcessPendingGestures();
124
+ const executed = this.ExecuteAnimators(Math.round(started * 1_000_000));
125
+ canvas.clear(Super.ParseColor(this.BackgroundColor));
126
+ const root = this.content;
127
+ if (root) {
128
+ const scale = this.RenderingScale;
129
+ const w = this.Element.width, h = this.Element.height;
130
+ root.Measure(w, h, scale);
131
+ root.Arrange(new SKRect(0, 0, w, h), root.WidthRequest, root.HeightRequest, scale);
132
+ root.Render({ Context: { Canvas: canvas, Surface: this.surface }, Destination: new SKRect(0, 0, w, h), Scale: scale });
133
+ }
134
+ this.AccessibilityManager.OnFrameEnd(this.RenderingScale, this.Element.width, this.Element.height, () => this.Update());
135
+ const now = performance.now();
136
+ this.FrameTime = now - started;
137
+ this.FrameIndex++;
138
+ this.frameTimes.push(now);
139
+ while (this.frameTimes.length && this.frameTimes[0] < now - 1000)
140
+ this.frameTimes.shift();
141
+ this.FPS = this.frameTimes.length;
142
+ if (executed > 0)
143
+ this.Update(); // animators running: keep frames coming
144
+ }
145
+ // ---- deferred disposal (DrawnUi DisposeObject: never delete Skia objects mid-frame) ----
146
+ disposeQueue = [];
147
+ /** Queues a cache for deletion after the current frame has been flushed. */
148
+ DisposeObject(obj) { this.disposeQueue.push(obj); }
149
+ DrainDisposeQueue() {
150
+ if (this.disposeQueue.length === 0)
151
+ return;
152
+ for (const o of this.disposeQueue)
153
+ o.Dispose();
154
+ this.disposeQueue.length = 0;
155
+ }
156
+ // ---- animators (DrawnView.AnimatingControls) ----
157
+ AnimatingControls = new Map();
158
+ RegisterAnimator(animator) {
159
+ if (this.disposed)
160
+ return false;
161
+ this.AnimatingControls.set(animator.Uid, animator);
162
+ return true;
163
+ }
164
+ UnregisterAnimator(uid) { this.AnimatingControls.delete(uid); }
165
+ /** Ticks every registered animator once; returns how many ran. */
166
+ ExecuteAnimators(frameTimeNanos) {
167
+ let executed = 0;
168
+ for (const a of [...this.AnimatingControls.values()]) {
169
+ if (!a.Parent) {
170
+ this.AnimatingControls.delete(a.Uid);
171
+ continue;
172
+ }
173
+ a.TickFrame(frameTimeNanos);
174
+ executed++;
175
+ }
176
+ return executed;
177
+ }
178
+ /** Frames drawn so far (SkiaBackdrop uses it to refresh once after a cached record). */
179
+ FrameIndex = 0;
180
+ /** On-screen surface (SkiaBackdrop snapshots it). */
181
+ get Surface() { return this.surface; }
182
+ Dispose() {
183
+ this.disposed = true;
184
+ this.Gestures = "Disabled";
185
+ this.observer.disconnect();
186
+ this.content?.Dispose();
187
+ this.Content = undefined;
188
+ this.ReleaseSurface();
189
+ this.grContext?.delete();
190
+ this.grContext = undefined;
191
+ if (this.glHandle) {
192
+ Super.CK.deleteContext(this.glHandle);
193
+ this.glHandle = undefined;
194
+ }
195
+ }
196
+ // ---- gestures: raw pointer -> TouchActionEventArgs -> recognized SkiaGesturesParameters -> queue ----
197
+ gestures = "Disabled";
198
+ get Gestures() { return this.gestures; }
199
+ set Gestures(value) {
200
+ if (this.gestures === value)
201
+ return;
202
+ if (this.gestures !== "Disabled")
203
+ this.DetachInput();
204
+ this.gestures = value;
205
+ if (value !== "Disabled")
206
+ this.AttachInput();
207
+ }
208
+ activeTouchIds = new Set();
209
+ pointerDownArgs = new Map();
210
+ previousTouchArgs = new Map();
211
+ pendingGestures = [];
212
+ onPointer = (e) => {
213
+ const type = e.type === "pointerdown" ? "Pressed" :
214
+ e.type === "pointermove" ? "Moved" :
215
+ e.type === "pointerup" ? "Released" :
216
+ e.type === "pointercancel" ? "Cancelled" : undefined;
217
+ if (!type)
218
+ return;
219
+ if (type === "Moved" && !this.activeTouchIds.has(e.pointerId))
220
+ return; // hover not ported (TouchActionResult.Pointer)
221
+ // Capture so Up outside the element still arrives; throws for synthetic events (tests) — harmless.
222
+ if (type === "Pressed") {
223
+ try {
224
+ this.Element.setPointerCapture(e.pointerId);
225
+ }
226
+ catch { /* synthetic pointer */ }
227
+ }
228
+ const rect = this.Element.getBoundingClientRect();
229
+ const args = new TouchActionEventArgs();
230
+ args.Id = e.pointerId;
231
+ args.Type = type;
232
+ args.Scale = this.RenderingScale;
233
+ args.Location = new SKPoint((e.clientX - rect.left) * this.RenderingScale, (e.clientY - rect.top) * this.RenderingScale);
234
+ this.OnTouchAction(args);
235
+ };
236
+ preventTouch = (e) => e.preventDefault();
237
+ /** Mouse wheel -> TouchActionResult.Wheel (page scroll suppressed while gestures are enabled). */
238
+ onWheel = (e) => {
239
+ e.preventDefault();
240
+ const rect = this.Element.getBoundingClientRect();
241
+ const args = new TouchActionEventArgs();
242
+ args.Id = -1;
243
+ args.Type = "Wheel";
244
+ args.Scale = this.RenderingScale;
245
+ args.Location = new SKPoint((e.clientX - rect.left) * this.RenderingScale, (e.clientY - rect.top) * this.RenderingScale);
246
+ args.StartingLocation = args.Location;
247
+ args.Wheel = { Delta: e.deltaY !== 0 ? e.deltaY : e.deltaX };
248
+ this.OnGestureEvent(args, "Wheel");
249
+ };
250
+ AttachInput() {
251
+ const el = this.Element;
252
+ el.style.touchAction = "none";
253
+ el.style.userSelect = "none";
254
+ for (const t of ["pointerdown", "pointermove", "pointerup", "pointercancel"])
255
+ el.addEventListener(t, this.onPointer);
256
+ el.addEventListener("wheel", this.onWheel, { passive: false });
257
+ if (this.gestures === "Lock")
258
+ el.addEventListener("touchmove", this.preventTouch, { passive: false });
259
+ }
260
+ DetachInput() {
261
+ const el = this.Element;
262
+ el.style.touchAction = "";
263
+ el.style.userSelect = "";
264
+ for (const t of ["pointerdown", "pointermove", "pointerup", "pointercancel"])
265
+ el.removeEventListener(t, this.onPointer);
266
+ el.removeEventListener("wheel", this.onWheel);
267
+ el.removeEventListener("touchmove", this.preventTouch);
268
+ this.activeTouchIds.clear();
269
+ this.pointerDownArgs.clear();
270
+ this.previousTouchArgs.clear();
271
+ }
272
+ /** Port of DrawnUi.Blazor Canvas.OnTouchAction: per-pointer state machine producing Down / Panning / Tapped / Up. */
273
+ OnTouchAction(args) {
274
+ if (this.gestures === "Disabled")
275
+ return;
276
+ const id = args.Id;
277
+ if (args.Type === "Pressed") {
278
+ this.activeTouchIds.add(id);
279
+ args.NumberOfTouches = this.activeTouchIds.size;
280
+ args.StartingLocation = args.Location;
281
+ args.IsInContact = true;
282
+ this.pointerDownArgs.set(id, args);
283
+ this.previousTouchArgs.set(id, args);
284
+ this.OnGestureEvent(args, "Down");
285
+ return;
286
+ }
287
+ args.NumberOfTouches = this.activeTouchIds.size;
288
+ TouchActionEventArgs.FillDistanceInfo(args, this.previousTouchArgs.get(id));
289
+ const downArgs = this.pointerDownArgs.get(id);
290
+ args.StartingLocation = downArgs ? downArgs.StartingLocation : args.Location;
291
+ if (args.Type === "Moved") {
292
+ if (args.Distance.Delta.X !== 0 || args.Distance.Delta.Y !== 0)
293
+ this.OnGestureEvent(args, "Panning");
294
+ this.previousTouchArgs.set(id, args);
295
+ return;
296
+ }
297
+ if (args.Type === "Released" || args.Type === "Cancelled") {
298
+ args.IsInContact = args.NumberOfTouches > 1;
299
+ if (!args.IsInContact && downArgs && args.Type === "Released") {
300
+ const threshold = Canvas.TappedCancelMoveThresholdPoints * Math.max(0.1, this.RenderingScale);
301
+ if (Math.abs(args.Distance.Total.X) < threshold && Math.abs(args.Distance.Total.Y) < threshold)
302
+ this.OnGestureEvent(args, "Tapped");
303
+ }
304
+ this.OnGestureEvent(args, "Up");
305
+ this.previousTouchArgs.delete(id);
306
+ this.pointerDownArgs.delete(id);
307
+ this.activeTouchIds.delete(id);
308
+ }
309
+ }
310
+ OnGestureEvent(args, result) {
311
+ this.pendingGestures.push(SkiaGesturesParameters.Create(result, args));
312
+ this.Update();
313
+ }
314
+ ProcessPendingGestures() {
315
+ if (this.pendingGestures.length === 0)
316
+ return;
317
+ const batch = this.pendingGestures.splice(0);
318
+ const root = this.content;
319
+ if (!root)
320
+ return;
321
+ for (const args of batch)
322
+ this.ProcessGestures(root, args);
323
+ }
324
+ /** Entry into the control tree, same shape as DrawnUi Canvas.ProcessGestures. */
325
+ ProcessGestures(root, args) {
326
+ return root.ProcessGestures(args, new GestureEventProcessingInfo(args.Event.Location, SKPoint.Empty, SKPoint.Empty, null));
327
+ }
328
+ }
@@ -0,0 +1,6 @@
1
+ /** DrawnUi PrebuiltControlStyle: the look a styled control builds its default content with. */
2
+ export type PrebuiltControlStyle = "Unset" | "Platform" | "Cupertino" | "Material" | "Material3" | "Windows";
3
+ /** Resolved look: `Platform` picks from the browser's OS (C# picks from the running platform). */
4
+ export type ResolvedControlStyle = Exclude<PrebuiltControlStyle, "Platform">;
5
+ /** C# UsingControlStyle: Platform -> iOS/macOS = Cupertino, Android = Material, Windows = Windows, else the DrawnUi default look. */
6
+ export declare function ResolveControlStyle(style: PrebuiltControlStyle): ResolvedControlStyle;
@@ -0,0 +1,11 @@
1
+ let platformStyle;
2
+ /** C# UsingControlStyle: Platform -> iOS/macOS = Cupertino, Android = Material, Windows = Windows, else the DrawnUi default look. */
3
+ export function ResolveControlStyle(style) {
4
+ if (style !== "Platform")
5
+ return style;
6
+ if (!platformStyle) {
7
+ const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
8
+ platformStyle = /iPhone|iPad|iPod|Macintosh/.test(ua) ? "Cupertino" : /Android/.test(ua) ? "Material" : /Windows/.test(ua) ? "Windows" : "Unset";
9
+ }
10
+ return platformStyle;
11
+ }
@@ -0,0 +1,13 @@
1
+ /** Mirrors MAUI Easing (subset): Ease(t) with t in 0..1. */
2
+ export declare class Easing {
3
+ private readonly fn;
4
+ constructor(fn: (x: number) => number);
5
+ Ease(x: number): number;
6
+ static readonly Linear: Easing;
7
+ static readonly CubicIn: Easing;
8
+ static readonly CubicOut: Easing;
9
+ static readonly CubicInOut: Easing;
10
+ static readonly SinOut: Easing;
11
+ static readonly SinIn: Easing;
12
+ static readonly Default: Easing;
13
+ }
@@ -0,0 +1,15 @@
1
+ /** Mirrors MAUI Easing (subset): Ease(t) with t in 0..1. */
2
+ export class Easing {
3
+ fn;
4
+ constructor(fn) {
5
+ this.fn = fn;
6
+ }
7
+ Ease(x) { return this.fn(x); }
8
+ static Linear = new Easing((x) => x);
9
+ static CubicIn = new Easing((x) => x * x * x);
10
+ static CubicOut = new Easing((x) => (x - 1) ** 3 + 1);
11
+ static CubicInOut = new Easing((x) => (x < 0.5 ? 4 * x ** 3 : (x - 1) * (2 * x - 2) ** 2 + 1));
12
+ static SinOut = new Easing((x) => Math.sin((x * Math.PI) / 2));
13
+ static SinIn = new Easing((x) => 1 - Math.cos((x * Math.PI) / 2));
14
+ static Default = Easing.CubicInOut;
15
+ }
@@ -0,0 +1,74 @@
1
+ import type { SkiaControl } from "./SkiaControl";
2
+ export declare class SKPoint {
3
+ X: number;
4
+ Y: number;
5
+ constructor(X?: number, Y?: number);
6
+ static readonly Empty: SKPoint;
7
+ Add(p: SKPoint): SKPoint;
8
+ Subtract(p: SKPoint): SKPoint;
9
+ }
10
+ /** Raw platform action (subset of TouchActionType). */
11
+ export type TouchActionType = "Pressed" | "Moved" | "Released" | "Cancelled" | "Wheel" | "Pointer";
12
+ /** Recognized gesture (TouchActionResult). LongPressing/Wheel/Pointer/Touch declared for parity, not produced yet. */
13
+ export type TouchActionResult = "Touch" | "Down" | "Up" | "Tapped" | "LongPressing" | "Panning" | "Wheel" | "Pointer";
14
+ export type GesturesMode = "Disabled" | "Enabled" | "Lock";
15
+ export type LockTouch = "Disabled" | "Enabled" | "PassNone" | "PassTap" | "PassTapAndLongPress";
16
+ /** TouchActionEventArgs.DistanceInfo. Velocity is pixels per second. */
17
+ export declare class DistanceInfo {
18
+ Start: SKPoint;
19
+ End: SKPoint;
20
+ Delta: SKPoint;
21
+ Total: SKPoint;
22
+ Velocity: SKPoint;
23
+ }
24
+ /** AppoMobi.Gestures TouchActionEventArgs. */
25
+ export declare class TouchActionEventArgs {
26
+ Id: number;
27
+ Type: TouchActionType;
28
+ /** Pixels. */
29
+ Location: SKPoint;
30
+ StartingLocation: SKPoint;
31
+ Distance: DistanceInfo;
32
+ NumberOfTouches: number;
33
+ IsInContact: boolean;
34
+ Scale: number;
35
+ Timestamp: number;
36
+ /** ms since the previous event of the same pointer. */
37
+ DeltaTimeMs: number;
38
+ /** Mouse wheel: Delta > 0 = wheel down (browser deltaY sign). */
39
+ Wheel: {
40
+ Delta: number;
41
+ };
42
+ /** Same as the .NET helper: derives Start/End/Delta/Total from the previous event of the same pointer. */
43
+ static FillDistanceInfo(current: TouchActionEventArgs, previous: TouchActionEventArgs | undefined): void;
44
+ }
45
+ /** DrawnUi SkiaGesturesParameters: recognized gesture + its raw event. */
46
+ export declare class SkiaGesturesParameters {
47
+ Type: TouchActionResult;
48
+ Event: TouchActionEventArgs;
49
+ ArrivedTimeNanos: number;
50
+ static Create(action: TouchActionResult, args: TouchActionEventArgs): SkiaGesturesParameters;
51
+ }
52
+ /** DrawnUi GestureEventProcessingInfo. */
53
+ export declare class GestureEventProcessingInfo {
54
+ MappedLocation: SKPoint;
55
+ ChildOffset: SKPoint;
56
+ ChildOffsetDirect: SKPoint;
57
+ AlreadyConsumed: SkiaControl | null;
58
+ constructor(MappedLocation?: SKPoint, ChildOffset?: SKPoint, ChildOffsetDirect?: SKPoint, AlreadyConsumed?: SkiaControl | null);
59
+ static readonly Empty: GestureEventProcessingInfo;
60
+ }
61
+ /** DrawnUi SkiaGesturesInfo: payload of ConsumeGestures, set Consumed=true to stop propagation. */
62
+ export declare class SkiaGesturesInfo {
63
+ Args: SkiaGesturesParameters;
64
+ Info: GestureEventProcessingInfo;
65
+ Consumed: boolean;
66
+ constructor(Args: SkiaGesturesParameters, Info: GestureEventProcessingInfo);
67
+ }
68
+ /** DrawnUi ControlTappedEventArgs. */
69
+ export declare class ControlTappedEventArgs {
70
+ Control: SkiaControl;
71
+ Parameters: SkiaGesturesParameters;
72
+ ProcessingInfo: GestureEventProcessingInfo;
73
+ constructor(Control: SkiaControl, Parameters: SkiaGesturesParameters, ProcessingInfo: GestureEventProcessingInfo);
74
+ }
@@ -0,0 +1,104 @@
1
+ // Mirrors AppoMobi.Gestures + DrawnUi.Features.Gestures types. Locations are PIXELS (like DrawnUi Event.Location).
2
+ export class SKPoint {
3
+ X;
4
+ Y;
5
+ constructor(X = 0, Y = 0) {
6
+ this.X = X;
7
+ this.Y = Y;
8
+ }
9
+ static Empty = new SKPoint();
10
+ Add(p) { return new SKPoint(this.X + p.X, this.Y + p.Y); }
11
+ Subtract(p) { return new SKPoint(this.X - p.X, this.Y - p.Y); }
12
+ }
13
+ /** TouchActionEventArgs.DistanceInfo. Velocity is pixels per second. */
14
+ export class DistanceInfo {
15
+ Start = SKPoint.Empty;
16
+ End = SKPoint.Empty;
17
+ Delta = SKPoint.Empty;
18
+ Total = SKPoint.Empty;
19
+ Velocity = SKPoint.Empty;
20
+ }
21
+ /** AppoMobi.Gestures TouchActionEventArgs. */
22
+ export class TouchActionEventArgs {
23
+ Id = 0;
24
+ Type = "Pressed";
25
+ /** Pixels. */
26
+ Location = SKPoint.Empty;
27
+ StartingLocation = SKPoint.Empty;
28
+ Distance = new DistanceInfo();
29
+ NumberOfTouches = 1;
30
+ IsInContact = false;
31
+ Scale = 1;
32
+ Timestamp = performance.now();
33
+ /** ms since the previous event of the same pointer. */
34
+ DeltaTimeMs = 0;
35
+ /** Mouse wheel: Delta > 0 = wheel down (browser deltaY sign). */
36
+ Wheel = { Delta: 0 };
37
+ /** Same as the .NET helper: derives Start/End/Delta/Total from the previous event of the same pointer. */
38
+ static FillDistanceInfo(current, previous) {
39
+ if (!previous) {
40
+ current.Distance = new DistanceInfo();
41
+ return;
42
+ }
43
+ current.StartingLocation = previous.StartingLocation;
44
+ current.IsInContact = previous.IsInContact;
45
+ current.DeltaTimeMs = current.Timestamp - previous.Timestamp;
46
+ const d = new DistanceInfo();
47
+ d.Start = previous.Location;
48
+ const released = current.Type === "Released" || current.Type === "Cancelled";
49
+ d.End = released ? previous.Location : current.Location;
50
+ d.Delta = released ? SKPoint.Empty : current.Location.Subtract(previous.Location);
51
+ d.Total = previous.Distance.Total.Add(d.Delta);
52
+ const secs = current.DeltaTimeMs / 1000;
53
+ d.Velocity = secs > 0 && !released ? new SKPoint(d.Delta.X / secs, d.Delta.Y / secs) : previous.Distance.Velocity;
54
+ current.Distance = d;
55
+ }
56
+ }
57
+ /** DrawnUi SkiaGesturesParameters: recognized gesture + its raw event. */
58
+ export class SkiaGesturesParameters {
59
+ Type = "Touch";
60
+ Event = new TouchActionEventArgs();
61
+ ArrivedTimeNanos = 0;
62
+ static Create(action, args) {
63
+ const p = new SkiaGesturesParameters();
64
+ p.Type = action;
65
+ p.Event = args;
66
+ p.ArrivedTimeNanos = Math.round(performance.now() * 1e6);
67
+ return p;
68
+ }
69
+ }
70
+ /** DrawnUi GestureEventProcessingInfo. */
71
+ export class GestureEventProcessingInfo {
72
+ MappedLocation;
73
+ ChildOffset;
74
+ ChildOffsetDirect;
75
+ AlreadyConsumed;
76
+ constructor(MappedLocation = SKPoint.Empty, ChildOffset = SKPoint.Empty, ChildOffsetDirect = SKPoint.Empty, AlreadyConsumed = null) {
77
+ this.MappedLocation = MappedLocation;
78
+ this.ChildOffset = ChildOffset;
79
+ this.ChildOffsetDirect = ChildOffsetDirect;
80
+ this.AlreadyConsumed = AlreadyConsumed;
81
+ }
82
+ static Empty = new GestureEventProcessingInfo();
83
+ }
84
+ /** DrawnUi SkiaGesturesInfo: payload of ConsumeGestures, set Consumed=true to stop propagation. */
85
+ export class SkiaGesturesInfo {
86
+ Args;
87
+ Info;
88
+ Consumed = false;
89
+ constructor(Args, Info) {
90
+ this.Args = Args;
91
+ this.Info = Info;
92
+ }
93
+ }
94
+ /** DrawnUi ControlTappedEventArgs. */
95
+ export class ControlTappedEventArgs {
96
+ Control;
97
+ Parameters;
98
+ ProcessingInfo;
99
+ constructor(Control, Parameters, ProcessingInfo) {
100
+ this.Control = Control;
101
+ this.Parameters = Parameters;
102
+ this.ProcessingInfo = ProcessingInfo;
103
+ }
104
+ }
@@ -0,0 +1,29 @@
1
+ import type { ColorFilter } from "canvaskit-wasm";
2
+ import type { Color } from "./Types";
3
+ /** DrawnUi SkiaImageEffect. */
4
+ export type SkiaImageEffect = "None" | "BlackAndWhite" | "Pastel" | "Tint" | "Darken" | "Lighten" | "Grayscale" | "Sepia" | "InvertColors" | "Contrast" | "Saturation" | "Brightness" | "Gamma" | "TSL" | "HSL" | "Custom";
5
+ /** SKBlendMode names understood by CanvasKit (`CK.BlendMode[name]`). */
6
+ export type BlendMode = "Clear" | "Src" | "Dst" | "SrcOver" | "DstOver" | "SrcIn" | "DstIn" | "SrcOut" | "DstOut" | "SrcATop" | "DstATop" | "Xor" | "Plus" | "Modulate" | "Screen" | "Overlay" | "Darken" | "Lighten" | "ColorDodge" | "ColorBurn" | "HardLight" | "SoftLight" | "Difference" | "Exclusion" | "Multiply" | "Hue" | "Saturation" | "Color" | "Luminosity";
7
+ /**
8
+ * Port of DrawnUi SkiaImageEffects (color matrices). C# matrices use 0..255 translation values (SkiaSharp);
9
+ * CanvasKit's MakeMatrix takes 0..1, so every translation is divided by 255 here to keep the same look.
10
+ */
11
+ export declare const SkiaImageEffects: {
12
+ Matrix(m: number[]): ColorFilter;
13
+ Tint(color: Color, mode: BlendMode): ColorFilter;
14
+ Darken(amount: number): ColorFilter;
15
+ Lighten(amount: number): ColorFilter;
16
+ Grayscale(): ColorFilter;
17
+ Pastel(): ColorFilter;
18
+ Sepia(): ColorFilter;
19
+ InvertColors(): ColorFilter;
20
+ Contrast(amount: number): ColorFilter;
21
+ Saturation(amount: number): ColorFilter;
22
+ Brightness(amount: number): ColorFilter;
23
+ Lightness(amount: number): ColorFilter;
24
+ /** C# uses a lookup table (SKColorFilter.CreateTable); CanvasKit has no table filter, so gamma is approximated by a linear contrast/brightness fit. */
25
+ Gamma(gamma: number): ColorFilter;
26
+ /** C# HSL: saturation + lightness matrices, then a tint with the color at (hue, 1, lightness) (MAUI Color.FromHsla). */
27
+ HSL(hue: number, saturation: number, lightness: number, mode: BlendMode): ColorFilter;
28
+ TintSL(tint: Color, saturation: number, lightness: number, mode: BlendMode): ColorFilter;
29
+ };
@@ -0,0 +1,55 @@
1
+ import { Super } from "./Super";
2
+ /**
3
+ * Port of DrawnUi SkiaImageEffects (color matrices). C# matrices use 0..255 translation values (SkiaSharp);
4
+ * CanvasKit's MakeMatrix takes 0..1, so every translation is divided by 255 here to keep the same look.
5
+ */
6
+ export const SkiaImageEffects = {
7
+ Matrix(m) { return Super.CK.ColorFilter.MakeMatrix(m); },
8
+ Tint(color, mode) {
9
+ const CK = Super.CK;
10
+ return CK.ColorFilter.MakeBlend(Super.ParseColor(color), CK.BlendMode[mode] ?? CK.BlendMode.SrcIn);
11
+ },
12
+ Darken(amount) { const a = -amount / 255; return SkiaImageEffects.Matrix([1, 0, 0, 0, a, 0, 1, 0, 0, a, 0, 0, 1, 0, a, 0, 0, 0, 1, 0]); },
13
+ Lighten(amount) { const a = amount / 255; return SkiaImageEffects.Matrix([1, 0, 0, 0, a, 0, 1, 0, 0, a, 0, 0, 1, 0, a, 0, 0, 0, 1, 0]); },
14
+ Grayscale() { return SkiaImageEffects.Matrix([0.2989, 0.587, 0.114, 0, 0, 0.2989, 0.587, 0.114, 0, 0, 0.2989, 0.587, 0.114, 0, 0, 0, 0, 0, 1, 0]); },
15
+ Pastel() { return SkiaImageEffects.Matrix([0.75, 0.25, 0.25, 0, 0, 0.25, 0.75, 0.25, 0, 0, 0.25, 0.25, 0.75, 0, 0, 0, 0, 0, 1, 0]); },
16
+ Sepia() { return SkiaImageEffects.Matrix([0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, 0.272, 0.534, 0.131, 0, 0, 0, 0, 0, 1, 0]); },
17
+ InvertColors() { return SkiaImageEffects.Matrix([-1, 0, 0, 0, 1, 0, -1, 0, 0, 1, 0, 0, -1, 0, 1, 0, 0, 0, 1, 0]); },
18
+ Contrast(amount) {
19
+ const c = amount + 1, l = (0.5 * (1 - amount)) / 255;
20
+ return SkiaImageEffects.Matrix([c, 0, 0, 0, l, 0, c, 0, 0, l, 0, 0, c, 0, l, 0, 0, 0, 1, 0]);
21
+ },
22
+ Saturation(amount) {
23
+ const rr = 0.213 * (1 - amount), rg = 0.715 * (1 - amount), rb = 0.072 * (1 - amount);
24
+ return SkiaImageEffects.Matrix([rr + amount, rg, rb, 0, 0, rr, rg + amount, rb, 0, 0, rr, rg, rb + amount, 0, 0, 0, 0, 0, 1, 0]);
25
+ },
26
+ Brightness(amount) { return SkiaImageEffects.Matrix([1, 0, 0, 0, amount, 0, 1, 0, 0, amount, 0, 0, 1, 0, amount, 0, 0, 0, 1, 0]); },
27
+ Lightness(amount) { const a = amount / 255; return SkiaImageEffects.Matrix([1, 0, 0, 0, a, 0, 1, 0, 0, a, 0, 0, 1, 0, a, 0, 0, 0, 1, 0]); },
28
+ /** C# uses a lookup table (SKColorFilter.CreateTable); CanvasKit has no table filter, so gamma is approximated by a linear contrast/brightness fit. */
29
+ Gamma(gamma) {
30
+ if (gamma < 1)
31
+ gamma += 1;
32
+ else if (gamma > 1)
33
+ gamma -= 1;
34
+ const g = Math.max(0.01, gamma);
35
+ // best linear fit of x^g on [0,1]: slope/offset from the endpoints and midpoint
36
+ const mid = Math.pow(0.5, g), slope = 2 * (mid - 0), offset = 0.5 - slope * 0.5 + (mid - 0.5) * 0;
37
+ return SkiaImageEffects.Matrix([slope, 0, 0, 0, offset, 0, slope, 0, 0, offset, 0, 0, slope, 0, offset, 0, 0, 0, 1, 0]);
38
+ },
39
+ /** C# HSL: saturation + lightness matrices, then a tint with the color at (hue, 1, lightness) (MAUI Color.FromHsla). */
40
+ HSL(hue, saturation, lightness, mode) {
41
+ const CK = Super.CK;
42
+ const h = ((hue % 1) + 1) % 1, l = Math.max(0, Math.min(1, lightness));
43
+ const q = l < 0.5 ? l * 2 : 1, p = 2 * l - q; // s = 1
44
+ const ch = (t) => { t = ((t % 1) + 1) % 1; return t < 1 / 6 ? p + (q - p) * 6 * t : t < 0.5 ? q : t < 2 / 3 ? p + (q - p) * (2 / 3 - t) * 6 : p; };
45
+ const toHex = (v) => Math.round(v * 255).toString(16).padStart(2, "0");
46
+ const tint = `#${toHex(ch(h + 1 / 3))}${toHex(ch(h))}${toHex(ch(h - 1 / 3))}`;
47
+ const combined = CK.ColorFilter.MakeCompose(SkiaImageEffects.Saturation(saturation), SkiaImageEffects.Lightness(lightness));
48
+ return CK.ColorFilter.MakeCompose(combined, SkiaImageEffects.Tint(tint, mode));
49
+ },
50
+ TintSL(tint, saturation, lightness, mode) {
51
+ const CK = Super.CK;
52
+ const a = CK.ColorFilter.MakeCompose(SkiaImageEffects.Lightness(lightness), SkiaImageEffects.Saturation(saturation));
53
+ return CK.ColorFilter.MakeCompose(SkiaImageEffects.Tint(tint, mode), a);
54
+ },
55
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Mirrors DrawnUi KeyboardManager (Blazor / Wasm heads, drawnui-keyboard.js): window-level keydown / keyup
3
+ * capture listeners feeding `KeyDown` / `KeyUp` (the DOM `event.code`, e.g. "KeyA", "Backspace", "ArrowLeft")
4
+ * and `KeyChar` (a printable character, no Ctrl / Alt / Meta), plus the modifier state. A drawn editor
5
+ * subscribes while focused. Characters typed into the hidden TextInputProxy textarea (IME, soft keyboard) arrive
6
+ * as input events there, so its key events are not forwarded as KeyChar here (a React-port addition over
7
+ * DrawnUi.Blazor, which has no DOM text input).
8
+ */
9
+ export type InputKey = string;
10
+ export declare class KeyboardManager {
11
+ private static attached;
12
+ private static readonly pressed;
13
+ private static readonly keyDown;
14
+ private static readonly keyUp;
15
+ private static readonly keyChar;
16
+ static get IsShiftPressed(): boolean;
17
+ static get IsControlPressed(): boolean;
18
+ static get IsAltPressed(): boolean;
19
+ /** C# AttachToKeyboardAsync / JS attachGlobalKeyboard: idempotent. */
20
+ static AttachToKeyboard(): void;
21
+ static Subscribe(down: (key: InputKey, e: KeyboardEvent) => void, char: (ch: string, e: KeyboardEvent) => void, up?: (key: InputKey, e: KeyboardEvent) => void): void;
22
+ static Unsubscribe(down: (key: InputKey, e: KeyboardEvent) => void, char: (ch: string, e: KeyboardEvent) => void, up?: (key: InputKey, e: KeyboardEvent) => void): void;
23
+ /** JS blurExternalTextInput: a page text input outside the canvas would keep receiving the keys. */
24
+ static BlurExternalTextInput(): void;
25
+ }