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,77 @@
|
|
|
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 { TextSpan } from "./TextSpan";
|
|
6
|
+
/**
|
|
7
|
+
* Mirrors DrawnUi SkiaRichLabel: a SkiaLabel whose Text is markdown, rendered as TextSpans.
|
|
8
|
+
* Lightweight parser like the C# one (display-oriented, not full CommonMark): headings, paragraphs, bullet and
|
|
9
|
+
* numbered lists, fenced code blocks, inline code, bold, italic, strikethrough, links (tap -> LinkTapped).
|
|
10
|
+
*/
|
|
11
|
+
export declare class SkiaRichLabel extends SkiaLabel {
|
|
12
|
+
static ColorLink: Color;
|
|
13
|
+
static ColorCodeBackground: Color;
|
|
14
|
+
static ColorCodeBlock: Color;
|
|
15
|
+
static ColorCode: Color;
|
|
16
|
+
static ColorStrikeout: Color;
|
|
17
|
+
static MaskPrefixBullet: string;
|
|
18
|
+
static MaskPrefixNumbered: string;
|
|
19
|
+
private markdownEnabled;
|
|
20
|
+
private linkColor;
|
|
21
|
+
private codeTextColor;
|
|
22
|
+
private headingTextColor;
|
|
23
|
+
private codeBlockBackgroundColor;
|
|
24
|
+
private codeBackgroundColor;
|
|
25
|
+
private strikeoutColor;
|
|
26
|
+
private prefixBullet;
|
|
27
|
+
private prefixNumbered;
|
|
28
|
+
private underlineLink;
|
|
29
|
+
private underlineWidth;
|
|
30
|
+
/** Raised when a `[text](url)` span is tapped (C# LinkTapped event). */
|
|
31
|
+
LinkTapped?: (sender: SkiaRichLabel, url: string) => void;
|
|
32
|
+
private Style;
|
|
33
|
+
/** false = one literal span, no parsing (unicode/emoji without formatting). */
|
|
34
|
+
get MarkdownEnabled(): boolean;
|
|
35
|
+
set MarkdownEnabled(v: boolean);
|
|
36
|
+
get LinkColor(): Color;
|
|
37
|
+
set LinkColor(v: Color);
|
|
38
|
+
get CodeTextColor(): Color;
|
|
39
|
+
set CodeTextColor(v: Color);
|
|
40
|
+
get HeadingTextColor(): Color;
|
|
41
|
+
set HeadingTextColor(v: Color);
|
|
42
|
+
get CodeBlockBackgroundColor(): Color;
|
|
43
|
+
set CodeBlockBackgroundColor(v: Color);
|
|
44
|
+
get CodeBackgroundColor(): Color;
|
|
45
|
+
set CodeBackgroundColor(v: Color);
|
|
46
|
+
get StrikeoutColor(): Color;
|
|
47
|
+
set StrikeoutColor(v: Color);
|
|
48
|
+
get PrefixBullet(): string;
|
|
49
|
+
set PrefixBullet(v: string);
|
|
50
|
+
/** `{0}` is replaced by the item number. */
|
|
51
|
+
get PrefixNumbered(): string;
|
|
52
|
+
set PrefixNumbered(v: string);
|
|
53
|
+
get UnderlineLink(): boolean;
|
|
54
|
+
set UnderlineLink(v: boolean);
|
|
55
|
+
get UnderlineWidth(): number;
|
|
56
|
+
set UnderlineWidth(v: number);
|
|
57
|
+
/** The markdown source; spans are rebuilt on every change (C# SetTextInternal). */
|
|
58
|
+
get Text(): string;
|
|
59
|
+
set Text(v: string);
|
|
60
|
+
get FontSize(): number;
|
|
61
|
+
set FontSize(v: number);
|
|
62
|
+
/** C# OnLinkTapped: raises LinkTapped; override to open the url yourself. */
|
|
63
|
+
OnLinkTapped(url: string, _text: string): void;
|
|
64
|
+
protected OnSpanTapped(span: TextSpan, args: SkiaGesturesParameters, apply: GestureEventProcessingInfo): SkiaControl | null;
|
|
65
|
+
private hadParagraph;
|
|
66
|
+
private RebuildSpans;
|
|
67
|
+
private static Plain;
|
|
68
|
+
/** C# AddTextSpan + SpanWithAttributes: one span per fragment carrying the current inline state. */
|
|
69
|
+
private AddTextSpan;
|
|
70
|
+
/** C# RenderInline(LineBreak): a "\n" appended to the last span (or a span of its own). */
|
|
71
|
+
private LineBreak;
|
|
72
|
+
private RenderDocument;
|
|
73
|
+
/** Blocks are separated by one line break once something was emitted (C# hadParagraph). */
|
|
74
|
+
private BeginBlock;
|
|
75
|
+
/** Inline markdown: `code`, **bold**, __bold__, *italic*, _italic_, ~~strike~~, [text](url), backslash escapes. */
|
|
76
|
+
private RenderInlines;
|
|
77
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { Colors } from "../core/Types";
|
|
2
|
+
import { SkiaLabel } from "./SkiaLabel";
|
|
3
|
+
import { TextSpan } from "./TextSpan";
|
|
4
|
+
/**
|
|
5
|
+
* Mirrors DrawnUi SkiaRichLabel: a SkiaLabel whose Text is markdown, rendered as TextSpans.
|
|
6
|
+
* Lightweight parser like the C# one (display-oriented, not full CommonMark): headings, paragraphs, bullet and
|
|
7
|
+
* numbered lists, fenced code blocks, inline code, bold, italic, strikethrough, links (tap -> LinkTapped).
|
|
8
|
+
*/
|
|
9
|
+
export class SkiaRichLabel extends SkiaLabel {
|
|
10
|
+
// DrawnUi static defaults
|
|
11
|
+
static ColorLink = "#6495ED";
|
|
12
|
+
static ColorCodeBackground = "#696969";
|
|
13
|
+
static ColorCodeBlock = "#222222";
|
|
14
|
+
static ColorCode = Colors.White;
|
|
15
|
+
static ColorStrikeout = Colors.Red;
|
|
16
|
+
static MaskPrefixBullet = "• ";
|
|
17
|
+
static MaskPrefixNumbered = "{0}. ";
|
|
18
|
+
markdownEnabled = true;
|
|
19
|
+
linkColor = SkiaRichLabel.ColorLink;
|
|
20
|
+
codeTextColor = SkiaRichLabel.ColorCode;
|
|
21
|
+
headingTextColor = SkiaRichLabel.ColorCode;
|
|
22
|
+
codeBlockBackgroundColor = SkiaRichLabel.ColorCodeBlock;
|
|
23
|
+
codeBackgroundColor = SkiaRichLabel.ColorCodeBackground;
|
|
24
|
+
strikeoutColor = SkiaRichLabel.ColorStrikeout;
|
|
25
|
+
prefixBullet = SkiaRichLabel.MaskPrefixBullet;
|
|
26
|
+
prefixNumbered = SkiaRichLabel.MaskPrefixNumbered;
|
|
27
|
+
underlineLink = true;
|
|
28
|
+
underlineWidth = -1;
|
|
29
|
+
/** Raised when a `[text](url)` span is tapped (C# LinkTapped event). */
|
|
30
|
+
LinkTapped;
|
|
31
|
+
Style(key, v) { if (this[key] !== v) {
|
|
32
|
+
this[key] = v;
|
|
33
|
+
this.RebuildSpans();
|
|
34
|
+
} }
|
|
35
|
+
/** false = one literal span, no parsing (unicode/emoji without formatting). */
|
|
36
|
+
get MarkdownEnabled() { return this.markdownEnabled; }
|
|
37
|
+
set MarkdownEnabled(v) { this.Style("markdownEnabled", v); }
|
|
38
|
+
get LinkColor() { return this.linkColor; }
|
|
39
|
+
set LinkColor(v) { this.Style("linkColor", v); }
|
|
40
|
+
get CodeTextColor() { return this.codeTextColor; }
|
|
41
|
+
set CodeTextColor(v) { this.Style("codeTextColor", v); }
|
|
42
|
+
get HeadingTextColor() { return this.headingTextColor; }
|
|
43
|
+
set HeadingTextColor(v) { this.Style("headingTextColor", v); }
|
|
44
|
+
get CodeBlockBackgroundColor() { return this.codeBlockBackgroundColor; }
|
|
45
|
+
set CodeBlockBackgroundColor(v) { this.Style("codeBlockBackgroundColor", v); }
|
|
46
|
+
get CodeBackgroundColor() { return this.codeBackgroundColor; }
|
|
47
|
+
set CodeBackgroundColor(v) { this.Style("codeBackgroundColor", v); }
|
|
48
|
+
get StrikeoutColor() { return this.strikeoutColor; }
|
|
49
|
+
set StrikeoutColor(v) { this.Style("strikeoutColor", v); }
|
|
50
|
+
get PrefixBullet() { return this.prefixBullet; }
|
|
51
|
+
set PrefixBullet(v) { this.Style("prefixBullet", v); }
|
|
52
|
+
/** `{0}` is replaced by the item number. */
|
|
53
|
+
get PrefixNumbered() { return this.prefixNumbered; }
|
|
54
|
+
set PrefixNumbered(v) { this.Style("prefixNumbered", v); }
|
|
55
|
+
get UnderlineLink() { return this.underlineLink; }
|
|
56
|
+
set UnderlineLink(v) { this.Style("underlineLink", v); }
|
|
57
|
+
get UnderlineWidth() { return this.underlineWidth; }
|
|
58
|
+
set UnderlineWidth(v) { this.Style("underlineWidth", v); }
|
|
59
|
+
/** The markdown source; spans are rebuilt on every change (C# SetTextInternal). */
|
|
60
|
+
get Text() { return super.Text; }
|
|
61
|
+
set Text(v) { super.Text = v; this.RebuildSpans(); }
|
|
62
|
+
// headings scale from the label's FontSize, so rebuild when it changes
|
|
63
|
+
get FontSize() { return super.FontSize; }
|
|
64
|
+
set FontSize(v) { super.FontSize = v; this.RebuildSpans(); }
|
|
65
|
+
/** C# OnLinkTapped: raises LinkTapped; override to open the url yourself. */
|
|
66
|
+
OnLinkTapped(url, _text) { this.LinkTapped?.(this, url); }
|
|
67
|
+
OnSpanTapped(span, args, apply) {
|
|
68
|
+
if (span.Tag) {
|
|
69
|
+
this.OnLinkTapped(span.Tag, span.Text);
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
return super.OnSpanTapped(span, args, apply);
|
|
73
|
+
}
|
|
74
|
+
// ---- markdown -> spans ----
|
|
75
|
+
hadParagraph = false;
|
|
76
|
+
RebuildSpans() {
|
|
77
|
+
for (const s of this.Spans)
|
|
78
|
+
s.Parent = undefined;
|
|
79
|
+
this.Spans.length = 0;
|
|
80
|
+
this.hadParagraph = false;
|
|
81
|
+
const text = super.Text;
|
|
82
|
+
if (text) {
|
|
83
|
+
if (!this.markdownEnabled)
|
|
84
|
+
this.AddTextSpan(text, SkiaRichLabel.Plain());
|
|
85
|
+
else
|
|
86
|
+
this.RenderDocument(text);
|
|
87
|
+
}
|
|
88
|
+
this.Update();
|
|
89
|
+
}
|
|
90
|
+
static Plain() { return { bold: false, italic: false, strike: false, heading: 0, code: false }; }
|
|
91
|
+
/** C# AddTextSpan + SpanWithAttributes: one span per fragment carrying the current inline state. */
|
|
92
|
+
AddTextSpan(text, state, modify) {
|
|
93
|
+
if (!text)
|
|
94
|
+
return undefined;
|
|
95
|
+
const span = new TextSpan();
|
|
96
|
+
span.Parent = this;
|
|
97
|
+
span.Text = text;
|
|
98
|
+
span.IsBold = state.bold;
|
|
99
|
+
span.IsItalic = state.italic;
|
|
100
|
+
span.Strikeout = state.strike;
|
|
101
|
+
if (state.strike)
|
|
102
|
+
span.StrikeoutColor = this.strikeoutColor;
|
|
103
|
+
if (state.heading === 1) {
|
|
104
|
+
span.IsBold = true;
|
|
105
|
+
span.FontSize = this.FontSize + 9;
|
|
106
|
+
span.TextColor = this.headingTextColor;
|
|
107
|
+
}
|
|
108
|
+
else if (state.heading === 2) {
|
|
109
|
+
span.IsBold = true;
|
|
110
|
+
span.FontSize = this.FontSize + 4;
|
|
111
|
+
span.TextColor = this.headingTextColor;
|
|
112
|
+
}
|
|
113
|
+
else if (state.heading === 3) {
|
|
114
|
+
span.IsBold = true;
|
|
115
|
+
span.FontSize = this.FontSize + 2;
|
|
116
|
+
span.TextColor = this.headingTextColor;
|
|
117
|
+
}
|
|
118
|
+
modify?.(span);
|
|
119
|
+
this.Spans.push(span);
|
|
120
|
+
return span;
|
|
121
|
+
}
|
|
122
|
+
/** C# RenderInline(LineBreak): a "\n" appended to the last span (or a span of its own). */
|
|
123
|
+
LineBreak() {
|
|
124
|
+
const last = this.Spans[this.Spans.length - 1];
|
|
125
|
+
if (last)
|
|
126
|
+
last.Text += "\n";
|
|
127
|
+
else
|
|
128
|
+
this.AddTextSpan("\n", SkiaRichLabel.Plain());
|
|
129
|
+
}
|
|
130
|
+
RenderDocument(text) {
|
|
131
|
+
const lines = text.replace(/\r\n?/g, "\n").split("\n");
|
|
132
|
+
let i = 0;
|
|
133
|
+
const paragraph = [];
|
|
134
|
+
const flushParagraph = (prefix) => {
|
|
135
|
+
if (paragraph.length === 0)
|
|
136
|
+
return;
|
|
137
|
+
this.BeginBlock();
|
|
138
|
+
if (prefix)
|
|
139
|
+
this.AddTextSpan(prefix, SkiaRichLabel.Plain());
|
|
140
|
+
this.RenderInlines(paragraph.join("\n"), SkiaRichLabel.Plain());
|
|
141
|
+
paragraph.length = 0;
|
|
142
|
+
};
|
|
143
|
+
while (i < lines.length) {
|
|
144
|
+
const line = lines[i];
|
|
145
|
+
const fence = /^\s*```/.exec(line);
|
|
146
|
+
const heading = /^(#{1,6})\s+(.*?)\s*#*\s*$/.exec(line);
|
|
147
|
+
const bullet = /^\s*[-*+]\s+(.*)$/.exec(line);
|
|
148
|
+
const numbered = /^\s*(\d+)[.)]\s+(.*)$/.exec(line);
|
|
149
|
+
if (fence) {
|
|
150
|
+
flushParagraph();
|
|
151
|
+
const code = [];
|
|
152
|
+
i++;
|
|
153
|
+
while (i < lines.length && !/^\s*```/.test(lines[i]))
|
|
154
|
+
code.push(lines[i++]);
|
|
155
|
+
i++; // closing fence
|
|
156
|
+
this.BeginBlock();
|
|
157
|
+
code.forEach((l, n) => {
|
|
158
|
+
if (n > 0)
|
|
159
|
+
this.LineBreak();
|
|
160
|
+
this.AddTextSpan(l, { ...SkiaRichLabel.Plain(), code: true }, (s) => { s.TextColor = this.codeTextColor; s.BackgroundColor = this.codeBlockBackgroundColor; });
|
|
161
|
+
});
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (heading) {
|
|
165
|
+
flushParagraph();
|
|
166
|
+
this.BeginBlock();
|
|
167
|
+
const level = Math.min(3, heading[1].length);
|
|
168
|
+
this.RenderInlines(heading[2], { ...SkiaRichLabel.Plain(), heading: level });
|
|
169
|
+
i++;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (bullet || numbered) {
|
|
173
|
+
flushParagraph();
|
|
174
|
+
let itemNumber = 1;
|
|
175
|
+
while (i < lines.length) {
|
|
176
|
+
const b = /^\s*[-*+]\s+(.*)$/.exec(lines[i]);
|
|
177
|
+
const n = /^\s*(\d+)[.)]\s+(.*)$/.exec(lines[i]);
|
|
178
|
+
if (!b && !n)
|
|
179
|
+
break;
|
|
180
|
+
this.BeginBlock();
|
|
181
|
+
this.AddTextSpan(b ? this.prefixBullet : this.prefixNumbered.replace("{0}", String(n ? itemNumber++ : 0)), SkiaRichLabel.Plain());
|
|
182
|
+
this.RenderInlines(b ? b[1] : n[2], SkiaRichLabel.Plain());
|
|
183
|
+
i++;
|
|
184
|
+
// continuation lines of the same item (indented, not blank, not a new item)
|
|
185
|
+
while (i < lines.length && /^\s+\S/.test(lines[i]) && !/^\s*([-*+]|\d+[.)])\s+/.test(lines[i])) {
|
|
186
|
+
this.LineBreak();
|
|
187
|
+
this.RenderInlines(lines[i].trim(), SkiaRichLabel.Plain());
|
|
188
|
+
i++;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (line.trim() === "") {
|
|
194
|
+
flushParagraph();
|
|
195
|
+
i++;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
paragraph.push(line);
|
|
199
|
+
i++;
|
|
200
|
+
}
|
|
201
|
+
flushParagraph();
|
|
202
|
+
}
|
|
203
|
+
/** Blocks are separated by one line break once something was emitted (C# hadParagraph). */
|
|
204
|
+
BeginBlock() {
|
|
205
|
+
if (this.hadParagraph)
|
|
206
|
+
this.LineBreak();
|
|
207
|
+
this.hadParagraph = true;
|
|
208
|
+
}
|
|
209
|
+
/** Inline markdown: `code`, **bold**, __bold__, *italic*, _italic_, ~~strike~~, [text](url), backslash escapes. */
|
|
210
|
+
RenderInlines(text, state) {
|
|
211
|
+
let literal = "";
|
|
212
|
+
const flush = () => { if (literal) {
|
|
213
|
+
this.AddTextSpan(literal, state);
|
|
214
|
+
literal = "";
|
|
215
|
+
} };
|
|
216
|
+
let i = 0;
|
|
217
|
+
const n = text.length;
|
|
218
|
+
const closes = (marker, from) => {
|
|
219
|
+
let k = text.indexOf(marker, from);
|
|
220
|
+
while (k > 0 && text[k - 1] === "\\")
|
|
221
|
+
k = text.indexOf(marker, k + 1);
|
|
222
|
+
return k;
|
|
223
|
+
};
|
|
224
|
+
while (i < n) {
|
|
225
|
+
const ch = text[i];
|
|
226
|
+
if (ch === "\\" && i + 1 < n) {
|
|
227
|
+
literal += text[i + 1];
|
|
228
|
+
i += 2;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (ch === "`") {
|
|
232
|
+
const end = text.indexOf("`", i + 1);
|
|
233
|
+
if (end > i) {
|
|
234
|
+
flush();
|
|
235
|
+
this.AddTextSpan(text.slice(i + 1, end), { ...state, code: true }, (s) => { s.TextColor = this.codeTextColor; s.BackgroundColor = this.codeBackgroundColor; });
|
|
236
|
+
i = end + 1;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (ch === "[") {
|
|
241
|
+
const m = /^\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/.exec(text.slice(i));
|
|
242
|
+
if (m) {
|
|
243
|
+
flush();
|
|
244
|
+
this.AddTextSpan(m[1] || m[2], state, (s) => { s.Tag = m[2]; s.TextColor = this.linkColor; s.Underline = this.underlineLink; s.UnderlineWidth = this.underlineWidth; s.ForceCaptureInput = true; });
|
|
245
|
+
i += m[0].length;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const two = text.slice(i, i + 2);
|
|
250
|
+
if (two === "**" || two === "__" || two === "~~") {
|
|
251
|
+
const end = closes(two, i + 2);
|
|
252
|
+
if (end > i + 2) {
|
|
253
|
+
flush();
|
|
254
|
+
const inner = text.slice(i + 2, end);
|
|
255
|
+
this.RenderInlines(inner, two === "~~" ? { ...state, strike: true } : { ...state, bold: true });
|
|
256
|
+
i = end + 2;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if ((ch === "*" || ch === "_") && i + 1 < n && !/\s/.test(text[i + 1])) {
|
|
261
|
+
const end = closes(ch, i + 1);
|
|
262
|
+
if (end > i + 1 && !/\s/.test(text[end - 1])) {
|
|
263
|
+
flush();
|
|
264
|
+
this.RenderInlines(text.slice(i + 1, end), { ...state, italic: true });
|
|
265
|
+
i = end + 1;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
literal += ch;
|
|
270
|
+
i++;
|
|
271
|
+
}
|
|
272
|
+
flush();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { type DrawingContext, SkiaControl } from "../core/SkiaControl";
|
|
2
|
+
import { type Color, type RelativePositionType, SKRect, ScaledSize, type ScrollOrientation, type SnapToChildrenType } from "../core/Types";
|
|
3
|
+
import { type IScrollBar, type ScrollBarVisibility } from "./SkiaScrollBar";
|
|
4
|
+
import type { IRefreshIndicator } from "./RefreshIndicator";
|
|
5
|
+
import { type GestureEventProcessingInfo, SKPoint, type SkiaGesturesParameters } from "../core/Gestures";
|
|
6
|
+
/** DrawnUi ScaledPoint: same point in points and pixels. */
|
|
7
|
+
export interface ScaledPoint {
|
|
8
|
+
Units: SKPoint;
|
|
9
|
+
Pixels: SKPoint;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Mirrors DrawnUi SkiaScroll (plain content, no header/footer/refresh/virtualization yet).
|
|
13
|
+
* Offsets are in POINTS and <= 0 while inside bounds (content moves up/left as you scroll).
|
|
14
|
+
* Physics ported 1:1: deceleration fling (rate = 1 - FrictionScrolled/100) cut at the content edge,
|
|
15
|
+
* rubber-band overscroll while dragging, spring bounce back on release, wheel = WheelLineSize per notch.
|
|
16
|
+
*/
|
|
17
|
+
export declare class SkiaScroll extends SkiaControl {
|
|
18
|
+
static WheelLineSize: number;
|
|
19
|
+
static ThesholdSwipeOnUp: number;
|
|
20
|
+
static ScrollVelocityThreshold: number;
|
|
21
|
+
Orientation: ScrollOrientation;
|
|
22
|
+
Bounces: boolean;
|
|
23
|
+
RubberDamping: number;
|
|
24
|
+
RubberEffect: number;
|
|
25
|
+
/** 0.1..1, lower = longer fling. */
|
|
26
|
+
FrictionScrolled: number;
|
|
27
|
+
ChangeVelocityScrolled: number;
|
|
28
|
+
ChangeDistancePanned: number;
|
|
29
|
+
MaxVelocity: number;
|
|
30
|
+
MaxBounceVelocity: number;
|
|
31
|
+
AutoScrollingSpeedMs: number;
|
|
32
|
+
ScrollingSpeedMs: number;
|
|
33
|
+
IgnoreWrongDirection: boolean;
|
|
34
|
+
RespondsToGestures: boolean;
|
|
35
|
+
private header?;
|
|
36
|
+
private footer?;
|
|
37
|
+
/** A view before the content along the scroll axis; `Tag="Header"` on a JSX child sets it. */
|
|
38
|
+
get Header(): SkiaControl | undefined;
|
|
39
|
+
set Header(v: SkiaControl | undefined);
|
|
40
|
+
/** A view after the content along the scroll axis; `Tag="Footer"` on a JSX child sets it. */
|
|
41
|
+
get Footer(): SkiaControl | undefined;
|
|
42
|
+
set Footer(v: SkiaControl | undefined);
|
|
43
|
+
/** The header stays at the viewport start while the content scrolls under it (drawn over the content unless HeaderBehind). */
|
|
44
|
+
HeaderSticky: boolean;
|
|
45
|
+
/** The header is drawn behind the content (the content covers it as it scrolls, parallax covers). */
|
|
46
|
+
HeaderBehind: boolean;
|
|
47
|
+
/** 1 = the header scrolls with the content, 0.5 = at half speed (parallax), 0 = stays. */
|
|
48
|
+
HeaderParallaxRatio: number;
|
|
49
|
+
/** Apply the parallax while overscrolling at the start too (false: the header follows the content there). */
|
|
50
|
+
ParallaxOverscrollEnabled: boolean;
|
|
51
|
+
/** Extra points between a HeaderBehind / HeaderSticky header and the content (C# ContentOffset). */
|
|
52
|
+
ContentOffset: number;
|
|
53
|
+
private headerSize;
|
|
54
|
+
private footerSize;
|
|
55
|
+
private scrollBar?;
|
|
56
|
+
private scrollBarHorizontal?;
|
|
57
|
+
private scrollBarsVisibility;
|
|
58
|
+
/** Creates default SkiaScrollBar overlays for the given axes (matched against Orientation). */
|
|
59
|
+
get ScrollBarsVisibility(): ScrollBarVisibility;
|
|
60
|
+
set ScrollBarsVisibility(v: ScrollBarVisibility);
|
|
61
|
+
/** Custom vertical bar (IScrollBar control); `Tag="ScrollBar"` on a JSX child sets it. */
|
|
62
|
+
get ScrollBar(): (IScrollBar & SkiaControl) | undefined;
|
|
63
|
+
set ScrollBar(v: (IScrollBar & SkiaControl) | undefined);
|
|
64
|
+
get ScrollBarHorizontal(): (IScrollBar & SkiaControl) | undefined;
|
|
65
|
+
set ScrollBarHorizontal(v: (IScrollBar & SkiaControl) | undefined);
|
|
66
|
+
private scrollBarThumbColor?;
|
|
67
|
+
private scrollBarTrackColor?;
|
|
68
|
+
/** Colors applied to the default SkiaScrollBar (C# ScrollBarThumbColor / ScrollBarTrackColor). */
|
|
69
|
+
get ScrollBarThumbColor(): Color | undefined;
|
|
70
|
+
set ScrollBarThumbColor(v: Color | undefined);
|
|
71
|
+
get ScrollBarTrackColor(): Color | undefined;
|
|
72
|
+
set ScrollBarTrackColor(v: Color | undefined);
|
|
73
|
+
private scrollBarLast?;
|
|
74
|
+
private scrollBarHLast?;
|
|
75
|
+
private ApplyScrollBarColors;
|
|
76
|
+
/** C# ApplyScrollBarsVisibility: a default bar per wanted axis, none otherwise. */
|
|
77
|
+
protected ApplyScrollBarsVisibility(): void;
|
|
78
|
+
/** C# UpdateScrollBarIndicator: pushes progress / thumb ratio / overscroll / scrolling state when any changed. */
|
|
79
|
+
protected UpdateScrollBarIndicator(): void;
|
|
80
|
+
RefreshEnabled: boolean;
|
|
81
|
+
/** Called when the pull passed RefreshDistanceLimit (C# RefreshCommand); set IsRefreshing = false when done. */
|
|
82
|
+
RefreshCommand?: (sender: SkiaScroll) => void;
|
|
83
|
+
/** Pull distance (points) that triggers the refresh. */
|
|
84
|
+
RefreshDistanceLimit: number;
|
|
85
|
+
/** Distance (points) where the indicator stops moving and stays while refreshing. */
|
|
86
|
+
RefreshShowDistance: number;
|
|
87
|
+
private refreshIndicator?;
|
|
88
|
+
/** The pull-to-refresh view (IRefreshIndicator control); `Tag="RefreshIndicator"` on a JSX child sets it. */
|
|
89
|
+
get RefreshIndicator(): (IRefreshIndicator & SkiaControl) | undefined;
|
|
90
|
+
set RefreshIndicator(v: (IRefreshIndicator & SkiaControl) | undefined);
|
|
91
|
+
private isRefreshing;
|
|
92
|
+
private wasRefreshing;
|
|
93
|
+
private scrollLocked;
|
|
94
|
+
get IsRefreshing(): boolean;
|
|
95
|
+
set IsRefreshing(v: boolean);
|
|
96
|
+
private get UsingRefreshDistanceLimit();
|
|
97
|
+
/** C# SetIsRefreshing: true locks the scroll at RefreshShowDistance and runs RefreshCommand; false hides the indicator. */
|
|
98
|
+
SetIsRefreshing(state: boolean, initial: boolean): void;
|
|
99
|
+
protected HideRefreshIndicator(): void;
|
|
100
|
+
/** C# ShowRefreshIndicatorForced: parks the offset at RefreshShowDistance and shows the indicator fully. */
|
|
101
|
+
protected ShowRefreshIndicatorForced(): void;
|
|
102
|
+
/** C# CheckNeedRefresh + ApplyScrollPositionToRefreshViewUnsafe: called from every scroll change. */
|
|
103
|
+
protected CheckNeedRefresh(): void;
|
|
104
|
+
/** Snap to the child at the tracked position after scrolling stops: Center = centered in the viewport, Side = aligned to the viewport start (or end when TrackIndexPosition is End). */
|
|
105
|
+
SnapToChildren: SnapToChildrenType;
|
|
106
|
+
/** The viewport position whose child index is reported as CurrentIndex (None = off). */
|
|
107
|
+
TrackIndexPosition: RelativePositionType;
|
|
108
|
+
/** Points added to the tracked position (C# TrackIndexPositionOffset). */
|
|
109
|
+
TrackIndexPositionOffset: number;
|
|
110
|
+
/** Index of the content child at the tracked position, -1 when none. */
|
|
111
|
+
CurrentIndex: number;
|
|
112
|
+
CurrentIndexChanged?: (sender: SkiaScroll, index: number) => void;
|
|
113
|
+
private isSnapping;
|
|
114
|
+
private snapped;
|
|
115
|
+
/** The content child covering the tracked viewport point (C# CurrentIndexHit): index, its rect and the point, canvas px. */
|
|
116
|
+
protected GetIndexHit(position: RelativePositionType): {
|
|
117
|
+
Index: number;
|
|
118
|
+
Area: SKRect;
|
|
119
|
+
Point: SKPoint;
|
|
120
|
+
} | undefined;
|
|
121
|
+
private TrackIndex;
|
|
122
|
+
/** C# CheckNeedToSnap: after the fling stopped, not panning, no bounce, no ordered scroll. */
|
|
123
|
+
protected CheckNeedToSnap(): boolean;
|
|
124
|
+
/** C# Snap: scroll so the tracked child is centered / aligned to the side. */
|
|
125
|
+
Snap(maxTimeSecs: number): void;
|
|
126
|
+
private get HeaderExtentPts();
|
|
127
|
+
private get FooterExtentPts();
|
|
128
|
+
/** Offset changed (drag, fling, bounce, ScrollTo). */
|
|
129
|
+
Scrolled?: (sender: SkiaScroll, e: ScaledPoint) => void;
|
|
130
|
+
/** Called when scrolling within LoadMoreOffset points of the end (bottom / right), or when the content underfills the viewport. */
|
|
131
|
+
LoadMoreCommand?: (sender: SkiaScroll) => void;
|
|
132
|
+
/** Called when scrolling within LoadMoreTopOffset points of the start (top / left). */
|
|
133
|
+
LoadMoreTopCommand?: (sender: SkiaScroll) => void;
|
|
134
|
+
LoadMoreOffset: number;
|
|
135
|
+
LoadMoreTopOffset: number;
|
|
136
|
+
private loadMoreBottomAt;
|
|
137
|
+
private loadMoreBottomTime;
|
|
138
|
+
/** Top fires only after the user has actually left the top edge and comes back (a fresh list at offset 0 is not a "reached the top"). */
|
|
139
|
+
private loadMoreTopArmed;
|
|
140
|
+
/** C# CheckNeedToLoadMore: fires once per content extent near an edge; re-arms when the content grows or the user moves away (>offset+100pt, >2 s). */
|
|
141
|
+
private CheckLoadMore;
|
|
142
|
+
IsUserPanning: boolean;
|
|
143
|
+
IsUserFocused: boolean;
|
|
144
|
+
IsScrolling: boolean;
|
|
145
|
+
ContentSize: ScaledSize;
|
|
146
|
+
/** Points: Left/Top = most negative allowed offset, Right/Bottom = 0. */
|
|
147
|
+
ContentOffsetBounds: SKRect;
|
|
148
|
+
OverscrollDistance: SKPoint;
|
|
149
|
+
get OverScrolled(): boolean;
|
|
150
|
+
private content?;
|
|
151
|
+
get Content(): SkiaControl | undefined;
|
|
152
|
+
set Content(value: SkiaControl | undefined);
|
|
153
|
+
private offsetX;
|
|
154
|
+
private offsetY;
|
|
155
|
+
get ViewportOffsetX(): number;
|
|
156
|
+
set ViewportOffsetX(value: number);
|
|
157
|
+
get ViewportOffsetY(): number;
|
|
158
|
+
set ViewportOffsetY(value: number);
|
|
159
|
+
private readonly animatorFlingX;
|
|
160
|
+
private readonly animatorFlingY;
|
|
161
|
+
private readonly bounceX;
|
|
162
|
+
private readonly bounceY;
|
|
163
|
+
protected OnDisposing(): void;
|
|
164
|
+
protected DisposeChildren(): void;
|
|
165
|
+
private readonly velocity;
|
|
166
|
+
private panningCurrentOffsetPts;
|
|
167
|
+
private panningLastDelta;
|
|
168
|
+
private hadDown;
|
|
169
|
+
private childWasPanning;
|
|
170
|
+
private velocityX;
|
|
171
|
+
private velocityY;
|
|
172
|
+
private static readonly MinVelocity;
|
|
173
|
+
/** Set when a fling was cut to stop at the edge: the edge offset it lands on (DrawnUi _axis / _changeSpeed). */
|
|
174
|
+
private flingEdgeX;
|
|
175
|
+
private flingEdgeY;
|
|
176
|
+
constructor();
|
|
177
|
+
/** DrawnUi BounceIfNeeded: after an edge-cut fling finishes, bounce with the velocity it still had. */
|
|
178
|
+
private BounceIfNeeded;
|
|
179
|
+
AddSubView(control: SkiaControl): void;
|
|
180
|
+
InsertSubView(_index: number, control: SkiaControl): void;
|
|
181
|
+
RemoveSubView(control: SkiaControl): void;
|
|
182
|
+
/** Draw order: behind header, content, flow / sticky header, footer (the base walks it top-most first for gestures). */
|
|
183
|
+
protected GetGestureListeners(): readonly SkiaControl[];
|
|
184
|
+
/** Content gets an infinite constraint along the scroll axis; the scroll itself takes the box it is given. */
|
|
185
|
+
protected MeasureAbsolute(widthConstraint: number, heightConstraint: number, scale: number): ScaledSize;
|
|
186
|
+
protected OnLayoutChanged(): void;
|
|
187
|
+
/** Places Content at the current offset; called on layout and every frame while the offset moves. */
|
|
188
|
+
private ArrangeContent;
|
|
189
|
+
/** Header: sticky = pinned at the viewport start, else scrolled at HeaderParallaxRatio of the offset; footer after the content. */
|
|
190
|
+
private ArrangeHeaderFooter;
|
|
191
|
+
protected Paint(ctx: DrawingContext): void;
|
|
192
|
+
private OnScrolled;
|
|
193
|
+
ClampOffset(x: number, y: number, bounds: SKRect, strict?: boolean): {
|
|
194
|
+
X: number;
|
|
195
|
+
Y: number;
|
|
196
|
+
};
|
|
197
|
+
private CalculateOverscrollDistance;
|
|
198
|
+
/** Scroll to an offset in points; maxSpeedSecs > 0 animates along the deceleration curve. */
|
|
199
|
+
/**
|
|
200
|
+
* Scrolls every SkiaScroll ancestor of `control` so its DrawingRect lies inside the viewport (React extension,
|
|
201
|
+
* used by the accessibility overlay when keyboard focus lands on an off-screen node). Offsets are points.
|
|
202
|
+
*/
|
|
203
|
+
static EnsureVisible(control: SkiaControl, maxTimeSecs?: number, paddingPts?: number): void;
|
|
204
|
+
/** Destination of the running ScrollTo per axis: the deceleration curve stops within its threshold, the exact value is applied when it finishes. */
|
|
205
|
+
private scrollToTargetX;
|
|
206
|
+
private scrollToTargetY;
|
|
207
|
+
private LandScrollTo;
|
|
208
|
+
ScrollTo(x: number, y: number, maxSpeedSecs: number, clamp?: boolean): void;
|
|
209
|
+
ScrollToTop(maxTimeSecs: number): void;
|
|
210
|
+
ScrollToBottom(maxTimeSecs: number): void;
|
|
211
|
+
StopScrolling(): void;
|
|
212
|
+
/**
|
|
213
|
+
* Scrolls so that item `index` is at the viewport start (or end). Like DrawnUi, Content must BE the
|
|
214
|
+
* templated layout (the recycled list is the scroll's only child; a header goes above the scroll or in Header).
|
|
215
|
+
*/
|
|
216
|
+
ScrollToIndex(index: number, animate: boolean, option?: RelativePositionType): void;
|
|
217
|
+
private StopAnimators;
|
|
218
|
+
/** DrawnUi DecelerationRatio: FrictionScrolled / 100, floored at 0.1 friction. */
|
|
219
|
+
private get DecelerationRatio();
|
|
220
|
+
private ResetPan;
|
|
221
|
+
ProcessGestures(args: SkiaGesturesParameters, apply: GestureEventProcessingInfo): SkiaControl | null;
|
|
222
|
+
private Bounce;
|
|
223
|
+
/** DrawnUi StartToFlingFrom + PrepareToFlingAfterInitialized: cut the curve so it stops exactly at the edge. */
|
|
224
|
+
private StartToFlingFrom;
|
|
225
|
+
/**
|
|
226
|
+
* One wheel notch = WheelLineSize points, hard-clamped, animated over AutoScrollingSpeedMs.
|
|
227
|
+
* Notches arriving while the previous one is still animating add onto its target, so a fast wheel
|
|
228
|
+
* spin travels N steps instead of restarting from the barely-moved current offset.
|
|
229
|
+
*/
|
|
230
|
+
private ApplyWheelScroll;
|
|
231
|
+
}
|