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.
- package/LICENSE +21 -0
- package/PARITY.md +287 -0
- package/README.md +92 -0
- package/SKIPPED.md +107 -0
- package/dist/controls/AnimatedFramesRenderer.d.ts +58 -0
- package/dist/controls/AnimatedFramesRenderer.js +133 -0
- package/dist/controls/GridStructure.d.ts +79 -0
- package/dist/controls/GridStructure.js +403 -0
- package/dist/controls/RefreshIndicator.d.ts +35 -0
- package/dist/controls/RefreshIndicator.js +82 -0
- package/dist/controls/SkiaBackdrop.d.ts +25 -0
- package/dist/controls/SkiaBackdrop.js +80 -0
- package/dist/controls/SkiaButton.d.ts +58 -0
- package/dist/controls/SkiaButton.js +148 -0
- package/dist/controls/SkiaCarousel.d.ts +133 -0
- package/dist/controls/SkiaCarousel.js +603 -0
- package/dist/controls/SkiaCheckbox.d.ts +21 -0
- package/dist/controls/SkiaCheckbox.js +118 -0
- package/dist/controls/SkiaDecoratedGrid.d.ts +15 -0
- package/dist/controls/SkiaDecoratedGrid.js +46 -0
- package/dist/controls/SkiaDrawer.d.ts +46 -0
- package/dist/controls/SkiaDrawer.js +212 -0
- package/dist/controls/SkiaDynamicDrawnCell.d.ts +11 -0
- package/dist/controls/SkiaDynamicDrawnCell.js +17 -0
- package/dist/controls/SkiaEditor.d.ts +163 -0
- package/dist/controls/SkiaEditor.js +803 -0
- package/dist/controls/SkiaGif.d.ts +54 -0
- package/dist/controls/SkiaGif.js +206 -0
- package/dist/controls/SkiaHotspot.d.ts +13 -0
- package/dist/controls/SkiaHotspot.js +36 -0
- package/dist/controls/SkiaImage.d.ts +75 -0
- package/dist/controls/SkiaImage.js +222 -0
- package/dist/controls/SkiaImageTiles.d.ts +27 -0
- package/dist/controls/SkiaImageTiles.js +76 -0
- package/dist/controls/SkiaLabel.d.ts +160 -0
- package/dist/controls/SkiaLabel.js +580 -0
- package/dist/controls/SkiaLayout.d.ts +163 -0
- package/dist/controls/SkiaLayout.js +723 -0
- package/dist/controls/SkiaLottie.d.ts +75 -0
- package/dist/controls/SkiaLottie.js +290 -0
- package/dist/controls/SkiaProgress.d.ts +40 -0
- package/dist/controls/SkiaProgress.js +80 -0
- package/dist/controls/SkiaRadioButton.d.ts +32 -0
- package/dist/controls/SkiaRadioButton.js +157 -0
- package/dist/controls/SkiaRichLabel.d.ts +77 -0
- package/dist/controls/SkiaRichLabel.js +274 -0
- package/dist/controls/SkiaScroll.d.ts +231 -0
- package/dist/controls/SkiaScroll.js +958 -0
- package/dist/controls/SkiaScrollBar.d.ts +56 -0
- package/dist/controls/SkiaScrollBar.js +158 -0
- package/dist/controls/SkiaShaderCarousel.d.ts +67 -0
- package/dist/controls/SkiaShaderCarousel.js +246 -0
- package/dist/controls/SkiaShape.d.ts +63 -0
- package/dist/controls/SkiaShape.js +317 -0
- package/dist/controls/SkiaSlider.d.ts +74 -0
- package/dist/controls/SkiaSlider.js +243 -0
- package/dist/controls/SkiaSprite.d.ts +99 -0
- package/dist/controls/SkiaSprite.js +324 -0
- package/dist/controls/SkiaSpriteSet.d.ts +27 -0
- package/dist/controls/SkiaSpriteSet.js +75 -0
- package/dist/controls/SkiaSvg.d.ts +43 -0
- package/dist/controls/SkiaSvg.js +181 -0
- package/dist/controls/SkiaSwitch.d.ts +26 -0
- package/dist/controls/SkiaSwitch.js +147 -0
- package/dist/controls/SkiaToggle.d.ts +50 -0
- package/dist/controls/SkiaToggle.js +71 -0
- package/dist/controls/SnappingLayout.d.ts +78 -0
- package/dist/controls/SnappingLayout.js +217 -0
- package/dist/controls/TextSpan.d.ts +70 -0
- package/dist/controls/TextSpan.js +76 -0
- package/dist/core/Accessibility.d.ts +104 -0
- package/dist/core/Accessibility.js +150 -0
- package/dist/core/Animators.d.ts +82 -0
- package/dist/core/Animators.js +247 -0
- package/dist/core/Canvas.d.ts +89 -0
- package/dist/core/Canvas.js +328 -0
- package/dist/core/ControlStyle.d.ts +6 -0
- package/dist/core/ControlStyle.js +11 -0
- package/dist/core/Easing.d.ts +13 -0
- package/dist/core/Easing.js +15 -0
- package/dist/core/Gestures.d.ts +74 -0
- package/dist/core/Gestures.js +104 -0
- package/dist/core/ImageEffects.d.ts +29 -0
- package/dist/core/ImageEffects.js +55 -0
- package/dist/core/KeyboardManager.d.ts +25 -0
- package/dist/core/KeyboardManager.js +54 -0
- package/dist/core/ScrollAnimators.d.ts +90 -0
- package/dist/core/ScrollAnimators.js +244 -0
- package/dist/core/SkiaControl.d.ts +401 -0
- package/dist/core/SkiaControl.js +1239 -0
- package/dist/core/SkiaEffect.d.ts +216 -0
- package/dist/core/SkiaEffect.js +571 -0
- package/dist/core/SkiaImageManager.d.ts +43 -0
- package/dist/core/SkiaImageManager.js +122 -0
- package/dist/core/Super.d.ts +61 -0
- package/dist/core/Super.js +178 -0
- package/dist/core/TextInputProxy.d.ts +31 -0
- package/dist/core/TextInputProxy.js +144 -0
- package/dist/core/Types.d.ts +177 -0
- package/dist/core/Types.js +122 -0
- package/dist/core/ViewsAdapter.d.ts +41 -0
- package/dist/core/ViewsAdapter.js +113 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +48 -0
- package/dist/react/SkiaShell.d.ts +144 -0
- package/dist/react/SkiaShell.js +490 -0
- package/dist/react/index.d.ts +110 -0
- package/dist/react/index.js +158 -0
- package/dist/react/reconciler.d.ts +14 -0
- package/dist/react/reconciler.js +160 -0
- package/package.json +60 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ManagedSkottieAnimation } from "canvaskit-wasm";
|
|
2
|
+
import type { DrawingContext } from "../core/SkiaControl";
|
|
3
|
+
import { type Color } from "../core/Types";
|
|
4
|
+
import { AnimatedFramesRenderer } from "./AnimatedFramesRenderer";
|
|
5
|
+
/**
|
|
6
|
+
* Mirrors DrawnUi SkiaLottie on CanvasKit Skottie: `Source` (URL / app path of the Lottie JSON), `AutoPlay`, `Repeat`,
|
|
7
|
+
* `SpeedRatio`, `DefaultFrame` / `DefaultFrameWhenOn` + `IsOn` (animated toggles), `StopAtCurrentFrame`,
|
|
8
|
+
* `ColorTint` / `Colors` (replace the animation colors in the JSON like C# ApplyTint), `GoToStart` / `GoToEnd`.
|
|
9
|
+
* Requires the CanvasKit "full" build (Skottie module).
|
|
10
|
+
*/
|
|
11
|
+
export declare class SkiaLottie extends AnimatedFramesRenderer {
|
|
12
|
+
/** JSON text per source, so the same file is fetched once (C# CachedAnimations). */
|
|
13
|
+
static readonly CachedAnimations: Map<string, string>;
|
|
14
|
+
private static readonly inFlight;
|
|
15
|
+
/** Stop() / Finished leave the animation where it is instead of seeking to DefaultFrame; Start() resumes from there. */
|
|
16
|
+
StopAtCurrentFrame: boolean;
|
|
17
|
+
/** Changing IsOn while stopped seeks to the matching default frame. */
|
|
18
|
+
ApplyIsOnWhenNotPlaying: boolean;
|
|
19
|
+
/** Hook to modify the JSON before it is parsed (C# ProcessJson). */
|
|
20
|
+
ProcessJson?: (json: string) => string;
|
|
21
|
+
Success?: (sender: SkiaLottie, source: string) => void;
|
|
22
|
+
Error?: (sender: SkiaLottie, error: Error) => void;
|
|
23
|
+
private source;
|
|
24
|
+
private isOn;
|
|
25
|
+
private defaultFrameWhenOn;
|
|
26
|
+
private colorTint;
|
|
27
|
+
private colors;
|
|
28
|
+
private loadGeneration;
|
|
29
|
+
private needSeek;
|
|
30
|
+
private inPoint;
|
|
31
|
+
private outPoint;
|
|
32
|
+
/** Current Skottie animation. */
|
|
33
|
+
Animation?: ManagedSkottieAnimation;
|
|
34
|
+
constructor();
|
|
35
|
+
get Source(): string;
|
|
36
|
+
set Source(v: string);
|
|
37
|
+
/** Toggle state: when stopped shows DefaultFrameWhenOn instead of DefaultFrame. */
|
|
38
|
+
get IsOn(): boolean;
|
|
39
|
+
set IsOn(v: boolean);
|
|
40
|
+
get DefaultFrameWhenOn(): number;
|
|
41
|
+
set DefaultFrameWhenOn(v: number);
|
|
42
|
+
/** Single tint replacing every color of the animation (alpha preserved). */
|
|
43
|
+
get ColorTint(): Color;
|
|
44
|
+
set ColorTint(v: Color);
|
|
45
|
+
/** Per-distinct-color replacements in order of appearance; the last one covers the rest. */
|
|
46
|
+
get Colors(): Color[];
|
|
47
|
+
set Colors(v: Color[]);
|
|
48
|
+
/** Total frames of the loaded animation. */
|
|
49
|
+
get TotalFrames(): number;
|
|
50
|
+
Stop(): void;
|
|
51
|
+
protected OnFinished(): void;
|
|
52
|
+
SeekToDefaultFrame(): void;
|
|
53
|
+
protected OnLayoutChanged(): void;
|
|
54
|
+
protected OnAnimatorSeeking(frame: number): void;
|
|
55
|
+
protected ApplyDefaultFrame(): void;
|
|
56
|
+
protected OnAnimatorUpdated(value: number): void;
|
|
57
|
+
protected ApplySpeed(): void;
|
|
58
|
+
protected OnAnimatorInitializing(): void;
|
|
59
|
+
protected CheckCanStartAnimator(): boolean;
|
|
60
|
+
protected OnAnimatorStarting(): void;
|
|
61
|
+
GoToStart(): void;
|
|
62
|
+
GoToEnd(): void;
|
|
63
|
+
protected RenderFrame(ctx: DrawingContext): void;
|
|
64
|
+
ReloadSource(): void;
|
|
65
|
+
private static FetchJson;
|
|
66
|
+
/** Loads (does not apply) the animation for a source; C# LoadSource. */
|
|
67
|
+
LoadSource(source: string): Promise<ManagedSkottieAnimation | undefined>;
|
|
68
|
+
/** Builds a Skottie animation from JSON, with the tint / colors replacement applied (C# CreateAnimation). */
|
|
69
|
+
CreateAnimation(json: string): ManagedSkottieAnimation | undefined;
|
|
70
|
+
SetAnimation(animation: ManagedSkottieAnimation, disposePrevious: boolean): void;
|
|
71
|
+
protected OnDisposing(): void;
|
|
72
|
+
static ApplyTint(json: string, tints: Color[]): string;
|
|
73
|
+
private static IsColorArray;
|
|
74
|
+
private static ToHex;
|
|
75
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { Super } from "../core/Super";
|
|
2
|
+
import { Colors } from "../core/Types";
|
|
3
|
+
import { AnimatedFramesRenderer } from "./AnimatedFramesRenderer";
|
|
4
|
+
/**
|
|
5
|
+
* Mirrors DrawnUi SkiaLottie on CanvasKit Skottie: `Source` (URL / app path of the Lottie JSON), `AutoPlay`, `Repeat`,
|
|
6
|
+
* `SpeedRatio`, `DefaultFrame` / `DefaultFrameWhenOn` + `IsOn` (animated toggles), `StopAtCurrentFrame`,
|
|
7
|
+
* `ColorTint` / `Colors` (replace the animation colors in the JSON like C# ApplyTint), `GoToStart` / `GoToEnd`.
|
|
8
|
+
* Requires the CanvasKit "full" build (Skottie module).
|
|
9
|
+
*/
|
|
10
|
+
export class SkiaLottie extends AnimatedFramesRenderer {
|
|
11
|
+
/** JSON text per source, so the same file is fetched once (C# CachedAnimations). */
|
|
12
|
+
static CachedAnimations = new Map();
|
|
13
|
+
static inFlight = new Map();
|
|
14
|
+
/** Stop() / Finished leave the animation where it is instead of seeking to DefaultFrame; Start() resumes from there. */
|
|
15
|
+
StopAtCurrentFrame = false;
|
|
16
|
+
/** Changing IsOn while stopped seeks to the matching default frame. */
|
|
17
|
+
ApplyIsOnWhenNotPlaying = true;
|
|
18
|
+
/** Hook to modify the JSON before it is parsed (C# ProcessJson). */
|
|
19
|
+
ProcessJson;
|
|
20
|
+
Success;
|
|
21
|
+
Error;
|
|
22
|
+
source = "";
|
|
23
|
+
isOn = false;
|
|
24
|
+
defaultFrameWhenOn = 0;
|
|
25
|
+
colorTint = Colors.Transparent;
|
|
26
|
+
colors = [];
|
|
27
|
+
loadGeneration = 0;
|
|
28
|
+
needSeek = false;
|
|
29
|
+
inPoint = 0;
|
|
30
|
+
outPoint = 0;
|
|
31
|
+
/** Current Skottie animation. */
|
|
32
|
+
Animation;
|
|
33
|
+
constructor() {
|
|
34
|
+
super();
|
|
35
|
+
this.UseCache = "ImageDoubleBuffered";
|
|
36
|
+
}
|
|
37
|
+
get Source() { return this.source; }
|
|
38
|
+
set Source(v) { if (this.source !== v) {
|
|
39
|
+
this.source = v;
|
|
40
|
+
this.ReloadSource();
|
|
41
|
+
} }
|
|
42
|
+
/** Toggle state: when stopped shows DefaultFrameWhenOn instead of DefaultFrame. */
|
|
43
|
+
get IsOn() { return this.isOn; }
|
|
44
|
+
set IsOn(v) {
|
|
45
|
+
if (this.isOn === v)
|
|
46
|
+
return;
|
|
47
|
+
this.isOn = v;
|
|
48
|
+
if (!this.Animator)
|
|
49
|
+
return;
|
|
50
|
+
if (this.IsPlaying || !v)
|
|
51
|
+
this.Stop();
|
|
52
|
+
else if (this.ApplyIsOnWhenNotPlaying)
|
|
53
|
+
this.SeekToDefaultFrame();
|
|
54
|
+
}
|
|
55
|
+
get DefaultFrameWhenOn() { return this.defaultFrameWhenOn; }
|
|
56
|
+
set DefaultFrameWhenOn(v) { if (this.defaultFrameWhenOn !== v) {
|
|
57
|
+
this.defaultFrameWhenOn = v;
|
|
58
|
+
if (!this.IsPlaying && this.Animator && this.isOn)
|
|
59
|
+
this.Seek(v);
|
|
60
|
+
} }
|
|
61
|
+
/** Single tint replacing every color of the animation (alpha preserved). */
|
|
62
|
+
get ColorTint() { return this.colorTint; }
|
|
63
|
+
set ColorTint(v) { if (this.colorTint !== v) {
|
|
64
|
+
this.colorTint = v;
|
|
65
|
+
this.ReloadSource();
|
|
66
|
+
} }
|
|
67
|
+
/** Per-distinct-color replacements in order of appearance; the last one covers the rest. */
|
|
68
|
+
get Colors() { return this.colors; }
|
|
69
|
+
set Colors(v) { if (this.colors !== v) {
|
|
70
|
+
this.colors = v ?? [];
|
|
71
|
+
this.ReloadSource();
|
|
72
|
+
} }
|
|
73
|
+
/** Total frames of the loaded animation. */
|
|
74
|
+
get TotalFrames() { return this.outPoint - this.inPoint; }
|
|
75
|
+
// ---- playback ----
|
|
76
|
+
Stop() {
|
|
77
|
+
super.Stop();
|
|
78
|
+
if (!this.StopAtCurrentFrame)
|
|
79
|
+
this.SeekToDefaultFrame();
|
|
80
|
+
}
|
|
81
|
+
OnFinished() {
|
|
82
|
+
super.OnFinished();
|
|
83
|
+
if (!this.StopAtCurrentFrame)
|
|
84
|
+
this.SeekToDefaultFrame();
|
|
85
|
+
}
|
|
86
|
+
SeekToDefaultFrame() {
|
|
87
|
+
if (this.Animator)
|
|
88
|
+
this.Seek(this.isOn ? this.defaultFrameWhenOn : this.DefaultFrame);
|
|
89
|
+
else
|
|
90
|
+
this.needSeek = true;
|
|
91
|
+
}
|
|
92
|
+
OnLayoutChanged() {
|
|
93
|
+
super.OnLayoutChanged();
|
|
94
|
+
if (this.needSeek) {
|
|
95
|
+
this.needSeek = false;
|
|
96
|
+
if (!this.IsPlaying)
|
|
97
|
+
this.SeekToDefaultFrame();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
OnAnimatorSeeking(frame) {
|
|
101
|
+
if (this.Animation) {
|
|
102
|
+
if (frame < 0)
|
|
103
|
+
frame = this.outPoint;
|
|
104
|
+
this.Animation.seekFrame(frame);
|
|
105
|
+
this.InvalidateCache();
|
|
106
|
+
this.RepaintComposition();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
ApplyDefaultFrame() {
|
|
110
|
+
if (!this.IsPlaying && this.Animator && !this.isOn)
|
|
111
|
+
this.Seek(this.DefaultFrame);
|
|
112
|
+
}
|
|
113
|
+
OnAnimatorUpdated(value) {
|
|
114
|
+
if (!this.Animation)
|
|
115
|
+
return;
|
|
116
|
+
this.Animation.seekFrame(value);
|
|
117
|
+
this.InvalidateCache(); // own cache re-records, ancestors composite again; no remeasure (C# Update())
|
|
118
|
+
this.RepaintComposition();
|
|
119
|
+
}
|
|
120
|
+
ApplySpeed() {
|
|
121
|
+
if (!this.Animation || !this.Animator)
|
|
122
|
+
return;
|
|
123
|
+
const durationMs = this.Animation.duration() * 1000;
|
|
124
|
+
this.Animator.Speed = this.SpeedRatio < 1 ? durationMs * (1 + this.SpeedRatio) : durationMs / this.SpeedRatio;
|
|
125
|
+
}
|
|
126
|
+
OnAnimatorInitializing() {
|
|
127
|
+
if (!this.Animation || !this.Animator)
|
|
128
|
+
return;
|
|
129
|
+
this.ApplySpeed();
|
|
130
|
+
this.Animator.mValue = this.inPoint;
|
|
131
|
+
this.Animator.mMinValue = this.inPoint;
|
|
132
|
+
this.Animator.mMaxValue = this.outPoint;
|
|
133
|
+
}
|
|
134
|
+
CheckCanStartAnimator() { return !!this.Animation; }
|
|
135
|
+
OnAnimatorStarting() {
|
|
136
|
+
if (!this.StopAtCurrentFrame && this.Animator)
|
|
137
|
+
this.Animator.mValue = this.inPoint;
|
|
138
|
+
}
|
|
139
|
+
GoToStart() { if (this.Animation) {
|
|
140
|
+
this.Animation.seek(0);
|
|
141
|
+
this.InvalidateCache();
|
|
142
|
+
this.RepaintComposition();
|
|
143
|
+
} }
|
|
144
|
+
GoToEnd() { if (this.Animation) {
|
|
145
|
+
this.Animation.seek(1);
|
|
146
|
+
this.InvalidateCache();
|
|
147
|
+
this.RepaintComposition();
|
|
148
|
+
} }
|
|
149
|
+
RenderFrame(ctx) {
|
|
150
|
+
const a = this.Animation;
|
|
151
|
+
if (!a)
|
|
152
|
+
return;
|
|
153
|
+
const r = ctx.Destination;
|
|
154
|
+
a.render(ctx.Context.Canvas, Super.CK.LTRBRect(r.Left, r.Top, r.Right, r.Bottom));
|
|
155
|
+
}
|
|
156
|
+
// ---- loading ----
|
|
157
|
+
ReloadSource() {
|
|
158
|
+
if (!this.source)
|
|
159
|
+
return;
|
|
160
|
+
const generation = ++this.loadGeneration;
|
|
161
|
+
const source = this.source;
|
|
162
|
+
this.LoadSource(source).then((animation) => { if (generation !== this.loadGeneration)
|
|
163
|
+
return; if (animation) {
|
|
164
|
+
this.Success?.(this, source);
|
|
165
|
+
this.SetAnimation(animation, true);
|
|
166
|
+
}
|
|
167
|
+
else
|
|
168
|
+
this.Error?.(this, new Error(`Failed to load source ${source}`)); }, (e) => { if (generation === this.loadGeneration)
|
|
169
|
+
this.Error?.(this, e); });
|
|
170
|
+
}
|
|
171
|
+
static FetchJson(source) {
|
|
172
|
+
const cached = SkiaLottie.CachedAnimations.get(source);
|
|
173
|
+
if (cached)
|
|
174
|
+
return Promise.resolve(cached);
|
|
175
|
+
let p = SkiaLottie.inFlight.get(source);
|
|
176
|
+
if (!p) {
|
|
177
|
+
p = fetch(source).then(async (r) => { if (!r.ok)
|
|
178
|
+
throw new Error(`${r.status} ${source}`); const json = await r.text(); SkiaLottie.CachedAnimations.set(source, json); return json; })
|
|
179
|
+
.finally(() => SkiaLottie.inFlight.delete(source));
|
|
180
|
+
SkiaLottie.inFlight.set(source, p);
|
|
181
|
+
}
|
|
182
|
+
return p;
|
|
183
|
+
}
|
|
184
|
+
/** Loads (does not apply) the animation for a source; C# LoadSource. */
|
|
185
|
+
async LoadSource(source) {
|
|
186
|
+
if (!source)
|
|
187
|
+
return undefined;
|
|
188
|
+
return this.CreateAnimation(await SkiaLottie.FetchJson(source));
|
|
189
|
+
}
|
|
190
|
+
/** Builds a Skottie animation from JSON, with the tint / colors replacement applied (C# CreateAnimation). */
|
|
191
|
+
CreateAnimation(json) {
|
|
192
|
+
if (!json)
|
|
193
|
+
return undefined;
|
|
194
|
+
if (this.colors.length > 0)
|
|
195
|
+
json = SkiaLottie.ApplyTint(json, this.colors);
|
|
196
|
+
else if (Super.ParseColor(this.colorTint)[3] > 0)
|
|
197
|
+
json = SkiaLottie.ApplyTint(json, [this.colorTint]);
|
|
198
|
+
if (this.ProcessJson)
|
|
199
|
+
json = this.ProcessJson(json);
|
|
200
|
+
const make = Super.CK.MakeManagedAnimation;
|
|
201
|
+
if (!make) {
|
|
202
|
+
console.error("[SkiaLottie] CanvasKit build has no Skottie (use canvaskit-wasm/bin/full)");
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
return make.call(Super.CK, json);
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
console.error("[SkiaLottie] failed to parse animation", e);
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
SetAnimation(animation, disposePrevious) {
|
|
214
|
+
if (!animation || animation === this.Animation)
|
|
215
|
+
return;
|
|
216
|
+
const wasPlaying = this.IsPlaying;
|
|
217
|
+
const kill = this.Animation;
|
|
218
|
+
if (wasPlaying)
|
|
219
|
+
this.Stop();
|
|
220
|
+
this.Animation = animation;
|
|
221
|
+
this.inPoint = 0;
|
|
222
|
+
this.outPoint = Math.round(animation.duration() * animation.fps());
|
|
223
|
+
this.InitializeAnimator(); // AutoPlay applied inside
|
|
224
|
+
this.OnAnimatorSeeking(this.isOn ? this.defaultFrameWhenOn : this.DefaultFrame);
|
|
225
|
+
if (wasPlaying && !this.IsPlaying)
|
|
226
|
+
this.Start();
|
|
227
|
+
if (kill && disposePrevious)
|
|
228
|
+
kill.delete();
|
|
229
|
+
this.Update();
|
|
230
|
+
}
|
|
231
|
+
OnDisposing() {
|
|
232
|
+
this.loadGeneration++;
|
|
233
|
+
super.OnDisposing();
|
|
234
|
+
this.Animation?.delete();
|
|
235
|
+
this.Animation = undefined;
|
|
236
|
+
}
|
|
237
|
+
// ---- C# ApplyTint: every distinct color of the JSON maps to the next tint, alpha preserved ----
|
|
238
|
+
static ApplyTint(json, tints) {
|
|
239
|
+
const doc = JSON.parse(json);
|
|
240
|
+
const found = [];
|
|
241
|
+
const key = (c) => c.slice(0, 3).map((v) => v.toFixed(4)).join(",");
|
|
242
|
+
const walk = (node) => {
|
|
243
|
+
if (Array.isArray(node)) {
|
|
244
|
+
for (const item of node)
|
|
245
|
+
walk(item);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (!node || typeof node !== "object")
|
|
249
|
+
return;
|
|
250
|
+
const obj = node;
|
|
251
|
+
for (const name of Object.keys(obj)) {
|
|
252
|
+
const value = obj[name];
|
|
253
|
+
if (name === "k" && SkiaLottie.IsColorArray(value)) {
|
|
254
|
+
const arr = value;
|
|
255
|
+
found.push({ color: [arr[0], arr[1], arr[2], arr.length > 3 ? arr[3] : 1], set: (c) => { obj[name] = arr.length > 3 ? c : c.slice(0, 3); } });
|
|
256
|
+
}
|
|
257
|
+
else if ((name === "sc" || name === "fc") && typeof value === "string") {
|
|
258
|
+
const c = Super.ParseColor(value);
|
|
259
|
+
found.push({ color: [c[0], c[1], c[2], c[3]], set: (nc) => { obj[name] = SkiaLottie.ToHex(nc); } });
|
|
260
|
+
}
|
|
261
|
+
walk(value);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
walk(doc);
|
|
265
|
+
const mapping = new Map();
|
|
266
|
+
let index = -1;
|
|
267
|
+
let tint = Super.ParseColor(Colors.Black);
|
|
268
|
+
for (const f of found) {
|
|
269
|
+
const k = key(f.color);
|
|
270
|
+
if (mapping.has(k))
|
|
271
|
+
continue;
|
|
272
|
+
index++;
|
|
273
|
+
if (index < tints.length)
|
|
274
|
+
tint = Super.ParseColor(tints[index]);
|
|
275
|
+
mapping.set(k, [tint[0], tint[1], tint[2]]);
|
|
276
|
+
}
|
|
277
|
+
for (const f of found) {
|
|
278
|
+
const m = mapping.get(key(f.color));
|
|
279
|
+
f.set([m[0], m[1], m[2], f.color[3]]);
|
|
280
|
+
}
|
|
281
|
+
return JSON.stringify(doc);
|
|
282
|
+
}
|
|
283
|
+
static IsColorArray(v) {
|
|
284
|
+
return Array.isArray(v) && (v.length === 3 || v.length === 4) && v.every((x) => typeof x === "number" && x >= 0 && x <= 1);
|
|
285
|
+
}
|
|
286
|
+
static ToHex(c) {
|
|
287
|
+
const h = (v) => Math.round(v * 255).toString(16).padStart(2, "0").toUpperCase();
|
|
288
|
+
return `#${h(c[3])}${h(c[0])}${h(c[1])}${h(c[2])}`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type DrawingContext, SkiaControl } from "../core/SkiaControl";
|
|
2
|
+
import { type PrebuiltControlStyle, type ResolvedControlStyle } from "../core/ControlStyle";
|
|
3
|
+
import { type Color, ScaledSize } from "../core/Types";
|
|
4
|
+
/**
|
|
5
|
+
* Mirrors DrawnUi SkiaProgress (SkiaRangeBase): a horizontal track with a progress trail, per-style track height and
|
|
6
|
+
* palette (Material3 adds the gap + stop dot). Painted directly instead of child shapes; same geometry as the C# builders.
|
|
7
|
+
*/
|
|
8
|
+
export declare class SkiaProgress extends SkiaControl {
|
|
9
|
+
static DefaultAccessibilityRole?: string;
|
|
10
|
+
ControlStyle: PrebuiltControlStyle;
|
|
11
|
+
private value;
|
|
12
|
+
private min;
|
|
13
|
+
private max;
|
|
14
|
+
private trackColor?;
|
|
15
|
+
private progressColor?;
|
|
16
|
+
constructor();
|
|
17
|
+
get UsingControlStyle(): ResolvedControlStyle;
|
|
18
|
+
get Value(): number;
|
|
19
|
+
set Value(v: number);
|
|
20
|
+
get Min(): number;
|
|
21
|
+
set Min(v: number);
|
|
22
|
+
get Max(): number;
|
|
23
|
+
set Max(v: number);
|
|
24
|
+
/** Unset = per-style palette. */
|
|
25
|
+
get TrackColor(): Color;
|
|
26
|
+
set TrackColor(v: Color);
|
|
27
|
+
get ProgressColor(): Color;
|
|
28
|
+
set ProgressColor(v: Color);
|
|
29
|
+
/** C# ResolvedTrackColor / ResolvedProgressColor / track heights per style. */
|
|
30
|
+
static Palette(s: ResolvedControlStyle): {
|
|
31
|
+
track: Color;
|
|
32
|
+
progress: Color;
|
|
33
|
+
height: number;
|
|
34
|
+
radius: number;
|
|
35
|
+
};
|
|
36
|
+
get Ratio(): number;
|
|
37
|
+
protected MeasureAbsolute(w: number, _h: number, scale: number): ScaledSize;
|
|
38
|
+
protected Paint(ctx: DrawingContext): void;
|
|
39
|
+
protected DefaultAccessibilityLabel(): string | undefined;
|
|
40
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { SkiaControl } from "../core/SkiaControl";
|
|
2
|
+
import { Super } from "../core/Super";
|
|
3
|
+
import { ResolveControlStyle } from "../core/ControlStyle";
|
|
4
|
+
import { ScaledSize } from "../core/Types";
|
|
5
|
+
/**
|
|
6
|
+
* Mirrors DrawnUi SkiaProgress (SkiaRangeBase): a horizontal track with a progress trail, per-style track height and
|
|
7
|
+
* palette (Material3 adds the gap + stop dot). Painted directly instead of child shapes; same geometry as the C# builders.
|
|
8
|
+
*/
|
|
9
|
+
export class SkiaProgress extends SkiaControl {
|
|
10
|
+
static DefaultAccessibilityRole = "progressbar";
|
|
11
|
+
ControlStyle = "Unset";
|
|
12
|
+
value = 0;
|
|
13
|
+
min = 0;
|
|
14
|
+
max = 100;
|
|
15
|
+
trackColor;
|
|
16
|
+
progressColor;
|
|
17
|
+
constructor() {
|
|
18
|
+
super();
|
|
19
|
+
this.HorizontalOptions = "Fill";
|
|
20
|
+
this.MinimumWidthRequest = 100;
|
|
21
|
+
this.UseCache = "ImageDoubleBuffered";
|
|
22
|
+
}
|
|
23
|
+
get UsingControlStyle() { return ResolveControlStyle(this.ControlStyle); }
|
|
24
|
+
get Value() { return this.value; }
|
|
25
|
+
set Value(v) { if (this.value !== v) {
|
|
26
|
+
this.value = v;
|
|
27
|
+
this.Update();
|
|
28
|
+
this.NotifyAccessibility();
|
|
29
|
+
} }
|
|
30
|
+
get Min() { return this.min; }
|
|
31
|
+
set Min(v) { this.min = v; this.Update(); }
|
|
32
|
+
get Max() { return this.max; }
|
|
33
|
+
set Max(v) { this.max = v; this.Update(); }
|
|
34
|
+
/** Unset = per-style palette. */
|
|
35
|
+
get TrackColor() { return this.trackColor ?? SkiaProgress.Palette(this.UsingControlStyle).track; }
|
|
36
|
+
set TrackColor(v) { this.trackColor = v; this.Update(); }
|
|
37
|
+
get ProgressColor() { return this.progressColor ?? SkiaProgress.Palette(this.UsingControlStyle).progress; }
|
|
38
|
+
set ProgressColor(v) { this.progressColor = v; this.Update(); }
|
|
39
|
+
/** C# ResolvedTrackColor / ResolvedProgressColor / track heights per style. */
|
|
40
|
+
static Palette(s) {
|
|
41
|
+
switch (s) {
|
|
42
|
+
case "Cupertino": return { track: "#E5E5EA", progress: "#007AFF", height: 4, radius: 2 };
|
|
43
|
+
case "Material": return { track: "#E8EAED", progress: "#2196F3", height: 4, radius: 2 };
|
|
44
|
+
case "Material3": return { track: "#E6E0E9", progress: "#6750A4", height: 4, radius: 2 };
|
|
45
|
+
case "Windows": return { track: "#F3F2F1", progress: "#0078D4", height: 6, radius: 3 };
|
|
46
|
+
default: return { track: "#D7DBE0", progress: "#DC143C", height: 8, radius: 4 };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
get Ratio() { return this.max > this.min ? Math.max(0, Math.min(1, (this.value - this.min) / (this.max - this.min))) : 0; }
|
|
50
|
+
MeasureAbsolute(w, _h, scale) {
|
|
51
|
+
const p = SkiaProgress.Palette(this.UsingControlStyle);
|
|
52
|
+
return ScaledSize.FromPixels(isFinite(w) ? w : 200 * scale, p.height * scale, scale);
|
|
53
|
+
}
|
|
54
|
+
Paint(ctx) {
|
|
55
|
+
const CK = Super.CK;
|
|
56
|
+
const p = SkiaProgress.Palette(this.UsingControlStyle);
|
|
57
|
+
const scale = ctx.Scale;
|
|
58
|
+
const d = ctx.Destination;
|
|
59
|
+
const h = p.height * scale, r = p.radius * scale;
|
|
60
|
+
const top = d.Top + (d.Height - h) / 2;
|
|
61
|
+
const progressW = d.Width * this.Ratio;
|
|
62
|
+
const paint = new CK.Paint();
|
|
63
|
+
paint.setAntiAlias(true);
|
|
64
|
+
const m3 = this.UsingControlStyle === "Material3";
|
|
65
|
+
// background track (Material3: starts after the trail + 4pt gap)
|
|
66
|
+
paint.setColor(Super.ParseColor(this.TrackColor));
|
|
67
|
+
const bgLeft = m3 && progressW > 0 ? Math.min(d.Right, d.Left + progressW + 4 * scale) : d.Left;
|
|
68
|
+
if (d.Right - bgLeft > 0)
|
|
69
|
+
ctx.Context.Canvas.drawRRect(CK.RRectXY(CK.LTRBRect(bgLeft, top, d.Right, top + h), r, r), paint);
|
|
70
|
+
paint.setColor(Super.ParseColor(this.ProgressColor));
|
|
71
|
+
if (progressW > 0)
|
|
72
|
+
ctx.Context.Canvas.drawRRect(CK.RRectXY(CK.LTRBRect(d.Left, top, d.Left + progressW, top + h), r, r), paint);
|
|
73
|
+
if (m3) {
|
|
74
|
+
const dot = 4 * scale;
|
|
75
|
+
ctx.Context.Canvas.drawCircle(d.Right - dot / 2, top + h / 2, dot / 2, paint);
|
|
76
|
+
}
|
|
77
|
+
paint.delete();
|
|
78
|
+
}
|
|
79
|
+
DefaultAccessibilityLabel() { return `${Math.round(this.Ratio * 100)}%`; }
|
|
80
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { SkiaControl } from "../core/SkiaControl";
|
|
2
|
+
import type { GestureEventProcessingInfo, SkiaGesturesParameters } from "../core/Gestures";
|
|
3
|
+
import { type Color } from "../core/Types";
|
|
4
|
+
import { SkiaLabel } from "./SkiaLabel";
|
|
5
|
+
import { SkiaShape } from "./SkiaShape";
|
|
6
|
+
import { SkiaToggle } from "./SkiaToggle";
|
|
7
|
+
/** Mirrors DrawnUi SkiaRadioButton: circle indicator + text, exclusive inside its group; tapping an untoggled one toggles it. */
|
|
8
|
+
export declare class SkiaRadioButton extends SkiaToggle {
|
|
9
|
+
static DefaultAccessibilityRole?: string;
|
|
10
|
+
private text;
|
|
11
|
+
private groupName;
|
|
12
|
+
private groupKey?;
|
|
13
|
+
ViewOn: SkiaShape;
|
|
14
|
+
ViewText: SkiaLabel;
|
|
15
|
+
private static readonly DefaultAccent;
|
|
16
|
+
private static readonly DefaultOutline;
|
|
17
|
+
constructor(text?: string);
|
|
18
|
+
get Text(): string;
|
|
19
|
+
set Text(v: string);
|
|
20
|
+
/** Buttons sharing a GroupName are exclusive; without one, siblings under the same parent are. */
|
|
21
|
+
get GroupName(): string;
|
|
22
|
+
set GroupName(v: string);
|
|
23
|
+
protected StyleDefault(name: "ColorThumbOn" | "ColorFrameOn" | "ColorThumbOff" | "ColorFrameOff"): Color | undefined;
|
|
24
|
+
protected CreateDefaultContent(): void;
|
|
25
|
+
private offRing?;
|
|
26
|
+
private dot?;
|
|
27
|
+
ApplyProperties(): void;
|
|
28
|
+
protected DefaultAccessibilityLabel(): string | undefined;
|
|
29
|
+
private UpdateGroup;
|
|
30
|
+
protected OnToggledChanged(): void;
|
|
31
|
+
ProcessGestures(args: SkiaGesturesParameters, apply: GestureEventProcessingInfo): SkiaControl | null;
|
|
32
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Colors, Thickness } from "../core/Types";
|
|
2
|
+
import { SkiaLayout } from "./SkiaLayout";
|
|
3
|
+
import { SkiaLabel } from "./SkiaLabel";
|
|
4
|
+
import { SkiaShape } from "./SkiaShape";
|
|
5
|
+
import { SkiaToggle } from "./SkiaToggle";
|
|
6
|
+
/** DrawnUi RadioButtons manager: one toggled button per group (GroupName, else the parent). */
|
|
7
|
+
const groups = new Map();
|
|
8
|
+
/** Mirrors DrawnUi SkiaRadioButton: circle indicator + text, exclusive inside its group; tapping an untoggled one toggles it. */
|
|
9
|
+
export class SkiaRadioButton extends SkiaToggle {
|
|
10
|
+
static DefaultAccessibilityRole = "radio";
|
|
11
|
+
text = "";
|
|
12
|
+
groupName = "";
|
|
13
|
+
groupKey;
|
|
14
|
+
ViewOn;
|
|
15
|
+
ViewText;
|
|
16
|
+
static DefaultAccent = "#DC143C";
|
|
17
|
+
static DefaultOutline = "#8E959D";
|
|
18
|
+
constructor(text = "") {
|
|
19
|
+
super();
|
|
20
|
+
this.text = text;
|
|
21
|
+
this.Type = "Absolute";
|
|
22
|
+
this.UseCache = "Image";
|
|
23
|
+
}
|
|
24
|
+
get Text() { return this.text; }
|
|
25
|
+
set Text(v) { this.text = v; this.ApplyProperties(); }
|
|
26
|
+
/** Buttons sharing a GroupName are exclusive; without one, siblings under the same parent are. */
|
|
27
|
+
get GroupName() { return this.groupName; }
|
|
28
|
+
set GroupName(v) { this.groupName = v; this.UpdateGroup(); }
|
|
29
|
+
StyleDefault(name) {
|
|
30
|
+
const table = {
|
|
31
|
+
Unset: { ColorThumbOff: SkiaRadioButton.DefaultOutline, ColorThumbOn: SkiaRadioButton.DefaultAccent },
|
|
32
|
+
Cupertino: { ColorThumbOff: "#BFBFBF", ColorThumbOn: "#007AFF" },
|
|
33
|
+
Material: { ColorThumbOff: "#757575", ColorThumbOn: "#2196F3" },
|
|
34
|
+
Material3: { ColorThumbOff: "#49454F", ColorThumbOn: "#6750A4" },
|
|
35
|
+
Windows: { ColorThumbOff: "#767676", ColorThumbOn: "#0078D7" },
|
|
36
|
+
};
|
|
37
|
+
return table[this.UsingControlStyle]?.[name];
|
|
38
|
+
}
|
|
39
|
+
CreateDefaultContent() {
|
|
40
|
+
if (this.MinimumHeightRequest < 0)
|
|
41
|
+
this.MinimumHeightRequest = 24;
|
|
42
|
+
const s = this.usingStyle;
|
|
43
|
+
const size = s === "Unset" ? 18 : 20;
|
|
44
|
+
const box = new SkiaLayout();
|
|
45
|
+
box.Type = "Absolute";
|
|
46
|
+
box.HeightRequest = size;
|
|
47
|
+
box.LockRatio = 1;
|
|
48
|
+
box.VerticalOptions = "Center";
|
|
49
|
+
const off = new SkiaShape();
|
|
50
|
+
off.Type = "Circle";
|
|
51
|
+
off.HorizontalOptions = "Fill";
|
|
52
|
+
off.VerticalOptions = "Fill";
|
|
53
|
+
const on = new SkiaShape();
|
|
54
|
+
on.Tag = "On";
|
|
55
|
+
on.Type = "Circle";
|
|
56
|
+
on.HorizontalOptions = "Fill";
|
|
57
|
+
on.VerticalOptions = "Fill";
|
|
58
|
+
const dot = new SkiaShape();
|
|
59
|
+
dot.Type = "Circle";
|
|
60
|
+
dot.HorizontalOptions = "Fill";
|
|
61
|
+
dot.VerticalOptions = "Fill";
|
|
62
|
+
switch (s) {
|
|
63
|
+
case "Cupertino":
|
|
64
|
+
off.StrokeWidth = 1.5;
|
|
65
|
+
dot.Margin = new Thickness(6.5);
|
|
66
|
+
dot.BackgroundColor = Colors.White;
|
|
67
|
+
on.AddSubView(dot);
|
|
68
|
+
break;
|
|
69
|
+
case "Windows":
|
|
70
|
+
off.StrokeWidth = 1;
|
|
71
|
+
on.StrokeWidth = 5;
|
|
72
|
+
break;
|
|
73
|
+
case "Material":
|
|
74
|
+
case "Material3":
|
|
75
|
+
off.StrokeWidth = 2;
|
|
76
|
+
on.StrokeWidth = 2;
|
|
77
|
+
dot.Margin = new Thickness(5);
|
|
78
|
+
on.AddSubView(dot);
|
|
79
|
+
break;
|
|
80
|
+
default:
|
|
81
|
+
off.StrokeWidth = 2;
|
|
82
|
+
on.StrokeWidth = 2;
|
|
83
|
+
dot.Margin = new Thickness(4);
|
|
84
|
+
on.AddSubView(dot);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
box.AddSubView(off);
|
|
88
|
+
box.AddSubView(on);
|
|
89
|
+
const label = new SkiaLabel();
|
|
90
|
+
label.Tag = "Text";
|
|
91
|
+
label.Margin = new Thickness(s === "Unset" ? 26 : 28, 0, 0, 0);
|
|
92
|
+
label.FontSize = 14;
|
|
93
|
+
label.MaxLines = 2;
|
|
94
|
+
label.TextColor = Colors.Black;
|
|
95
|
+
label.VerticalOptions = "Center";
|
|
96
|
+
label.AccessibilityRole = "presentation";
|
|
97
|
+
this.ViewOn = on;
|
|
98
|
+
this.ViewText = label;
|
|
99
|
+
this.offRing = off;
|
|
100
|
+
this.dot = dot;
|
|
101
|
+
this.AddSubView(box);
|
|
102
|
+
this.AddSubView(label);
|
|
103
|
+
this.UpdateGroup();
|
|
104
|
+
}
|
|
105
|
+
offRing;
|
|
106
|
+
dot;
|
|
107
|
+
ApplyProperties() {
|
|
108
|
+
if (!this.contentCreated)
|
|
109
|
+
return;
|
|
110
|
+
const s = this.usingStyle;
|
|
111
|
+
this.offRing.StrokeColor = this.ColorThumbOff;
|
|
112
|
+
if (s === "Cupertino")
|
|
113
|
+
this.ViewOn.BackgroundColor = this.ColorThumbOn;
|
|
114
|
+
else
|
|
115
|
+
this.ViewOn.StrokeColor = this.ColorThumbOn;
|
|
116
|
+
if (this.dot && s !== "Cupertino")
|
|
117
|
+
this.dot.BackgroundColor = this.ColorThumbOn;
|
|
118
|
+
this.ViewOn.IsVisible = this.IsToggled;
|
|
119
|
+
this.ViewText.Text = this.text;
|
|
120
|
+
this.Update();
|
|
121
|
+
}
|
|
122
|
+
DefaultAccessibilityLabel() { return this.text || undefined; }
|
|
123
|
+
// ---- group (C# RadioButtons.All) ----
|
|
124
|
+
UpdateGroup() {
|
|
125
|
+
if (this.groupKey)
|
|
126
|
+
groups.get(this.groupKey)?.delete(this);
|
|
127
|
+
this.groupKey = this.groupName || this.Parent;
|
|
128
|
+
if (!this.groupKey)
|
|
129
|
+
return;
|
|
130
|
+
let set = groups.get(this.groupKey);
|
|
131
|
+
if (!set) {
|
|
132
|
+
set = new Set();
|
|
133
|
+
groups.set(this.groupKey, set);
|
|
134
|
+
}
|
|
135
|
+
set.add(this);
|
|
136
|
+
}
|
|
137
|
+
OnToggledChanged() {
|
|
138
|
+
if (this.IsToggled) {
|
|
139
|
+
if (!this.groupKey || this.groupKey !== (this.groupName || this.Parent))
|
|
140
|
+
this.UpdateGroup();
|
|
141
|
+
for (const other of groups.get(this.groupKey) ?? [])
|
|
142
|
+
if (other !== this && other.IsToggled)
|
|
143
|
+
other.IsToggled = false;
|
|
144
|
+
}
|
|
145
|
+
super.OnToggledChanged();
|
|
146
|
+
}
|
|
147
|
+
ProcessGestures(args, apply) {
|
|
148
|
+
if (args.Type === "Tapped") {
|
|
149
|
+
if (!this.IsToggled && this.RespondsToGestures) {
|
|
150
|
+
this.IsToggled = true;
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return super.ProcessGestures(args, apply);
|
|
156
|
+
}
|
|
157
|
+
}
|