@decky.fx/raven-tsx 0.0.0-stage → 0.1.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 +202 -0
- package/NOTICE +12 -0
- package/RAVEN_VERSION +1 -0
- package/README.md +444 -2
- package/package.json +75 -4
- package/src/core/errors.ts +20 -0
- package/src/core/events.ts +925 -0
- package/src/core/frame.ts +76 -0
- package/src/core/index.ts +28 -0
- package/src/core/library-check.ts +89 -0
- package/src/core/native.ts +82 -0
- package/src/core/plot.ts +179 -0
- package/src/core/props.generated.ts +172 -0
- package/src/core/props.ts +158 -0
- package/src/core/raven.ts +10 -0
- package/src/core/subscriptions.ts +78 -0
- package/src/core/tags.ts +307 -0
- package/src/core/theme.ts +207 -0
- package/src/core/web.ts +84 -0
- package/src/core/widget-instance.ts +1057 -0
- package/src/core/window-instance.ts +671 -0
- package/src/react/components.generated.ts +179 -0
- package/src/react/hooks.ts +51 -0
- package/src/react/host-config.ts +138 -0
- package/src/react/index.ts +26 -0
- package/src/react/root.ts +119 -0
- package/src/react/tags.ts +50 -0
- package/src/react/web.ts +5 -0
- package/src/solid/babel-preset-solid.d.ts +7 -0
- package/src/solid/components.generated.ts +163 -0
- package/src/solid/index.ts +29 -0
- package/src/solid/node.ts +405 -0
- package/src/solid/plugin.ts +108 -0
- package/src/solid/portal.ts +27 -0
- package/src/solid/preload.ts +14 -0
- package/src/solid/primitives.ts +43 -0
- package/src/solid/renderer.ts +37 -0
- package/src/solid/root.ts +137 -0
- package/src/solid/tag.ts +37 -0
- package/src/solid/tags.ts +101 -0
- package/src/solid/teardown.ts +50 -0
- package/src/solid/web.ts +5 -0
- package/src/vendor/raven.ts +12652 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Themes in TypeScript, in place of theme files: the same theme, typed, with nothing to write in
|
|
3
|
+
* XML. `defineTheme` checks it, `loadTheme` gives it to Raven, which keeps it under its name.
|
|
4
|
+
*
|
|
5
|
+
* const ocean = defineTheme({
|
|
6
|
+
* Name: "Ocean", Base: "Dark", Accent: "#22D3EE",
|
|
7
|
+
* Colors: { Window: "#0B1220", Panel: "#111A2C" },
|
|
8
|
+
* Styles: [{ Widget: "Button", CornerRadius: 16 }, { Class: "danger", Accent: "#FB7185" }],
|
|
9
|
+
* });
|
|
10
|
+
* await runApp(<App />, options, { setup: () => raven.Rtheme_use(loadTheme(ocean)) });
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ColorRole, MaterialName, MetricName } from "./props.generated";
|
|
14
|
+
import { RavenError } from "./errors";
|
|
15
|
+
import { escapeXml } from "./props";
|
|
16
|
+
import * as r from "./raven";
|
|
17
|
+
|
|
18
|
+
/** A color: `#RRGGBB`, or `#RRGGBBAA` with its opacity. */
|
|
19
|
+
export type ThemeColor = `#${string}`;
|
|
20
|
+
|
|
21
|
+
/** The looks a theme can start from and impose, with `Look`. */
|
|
22
|
+
export const LOOKS = [
|
|
23
|
+
"RavenDark", "RavenLight", "RavenGlassDark", "RavenGlassLight", "RavenHighContrastDark", "RavenHighContrastLight",
|
|
24
|
+
"WindowsDark", "WindowsLight", "MacOSDark", "MacOSLight", "MacOSGlassDark", "MacOSGlassLight", "GnomeDark", "GnomeLight", "NativeDark", "NativeLight",
|
|
25
|
+
] as const;
|
|
26
|
+
export type LookName = (typeof LOOKS)[number];
|
|
27
|
+
|
|
28
|
+
/** The kinds of text the text editor colors, for `Syntax`. */
|
|
29
|
+
export const SYNTAX_TOKENS = ["Comment", "String", "Number", "Keyword", "Type", "Function", "Tag", "Attribute", "Punctuation", "Directive", "Heading", "Strong", "Emphasis", "Link"] as const;
|
|
30
|
+
export type SyntaxToken = (typeof SYNTAX_TOKENS)[number];
|
|
31
|
+
|
|
32
|
+
/** What the glass of a theme does, as multipliers (1: as designed, 0: none), for `Glass`. */
|
|
33
|
+
export const GLASS_PROPERTIES = ["Frost", "Refraction", "Saturation", "Rim", "Shadow"] as const;
|
|
34
|
+
export type GlassProperty = (typeof GLASS_PROPERTIES)[number];
|
|
35
|
+
|
|
36
|
+
/** The parts of widgets a style can reach (which widget has which: docs/THEMES.md of Raven). */
|
|
37
|
+
export const THEME_PARTS = [
|
|
38
|
+
"Box", "Mark", "Radio", "Dot", "Track", "Knob", "Fill", "Thumb", "Arrow", "Popup", "Item", "Indicator", "Tab", "Header",
|
|
39
|
+
"Scrollbar", "ScrollThumb", "Tooltip", "CaptionButton", "CloseButton", "Focus", "Grid", "Node", "Pin", "Link", "Ruler", "Clip", "Key", "Marker", "Playhead",
|
|
40
|
+
] as const;
|
|
41
|
+
export type ThemePart = (typeof THEME_PARTS)[number];
|
|
42
|
+
|
|
43
|
+
/** The states a style can ask for; given several, all must hold. `Primary` and `Flat` are styles of a button. */
|
|
44
|
+
export const THEME_STATES = ["Hovered", "Pressed", "Focused", "Disabled", "Checked", "Selected", "Open", "Primary", "Flat"] as const;
|
|
45
|
+
export type ThemeState = (typeof THEME_STATES)[number];
|
|
46
|
+
|
|
47
|
+
/** The forms of a part that can take several: pins and keys, links, grids, headers of nodes and clips. */
|
|
48
|
+
export const PART_SHAPES = [
|
|
49
|
+
"Circle", "Ring", "Square", "Diamond", "Triangle", "Arrow", "Bezier", "Straight", "Step", "Angled",
|
|
50
|
+
"Dots", "Lines", "Cross", "Solid", "Gradient", "Stripe", "Tint", "Outline", "None",
|
|
51
|
+
] as const;
|
|
52
|
+
export type PartShape = (typeof PART_SHAPES)[number];
|
|
53
|
+
|
|
54
|
+
/** A color for each role it names. */
|
|
55
|
+
export type ThemeColors = { readonly [Role in ColorRole]?: ThemeColor | undefined };
|
|
56
|
+
/** A value for each metric it names. */
|
|
57
|
+
export type ThemeMetrics = { readonly [Metric in MetricName]?: number | undefined };
|
|
58
|
+
|
|
59
|
+
/** What a style gives a part of a widget, in its states. */
|
|
60
|
+
export interface PartProperties {
|
|
61
|
+
/** What it is filled with; `None`, nothing. */
|
|
62
|
+
readonly Background?: ThemeColor | "None" | undefined;
|
|
63
|
+
/** Its text, marks and glyphs. */
|
|
64
|
+
readonly Foreground?: ThemeColor | undefined;
|
|
65
|
+
readonly BorderColor?: ThemeColor | undefined;
|
|
66
|
+
readonly BorderWidth?: number | undefined;
|
|
67
|
+
readonly BorderTopColor?: ThemeColor | undefined;
|
|
68
|
+
readonly BorderBottomColor?: ThemeColor | undefined;
|
|
69
|
+
readonly BorderBottomWidth?: number | undefined;
|
|
70
|
+
readonly CornerRadius?: number | undefined;
|
|
71
|
+
/** The size of a part that has one of its own: a knob, a dot, a pill. */
|
|
72
|
+
readonly Size?: number | undefined;
|
|
73
|
+
/** From 0 to 1. */
|
|
74
|
+
readonly Opacity?: number | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* The shadow it drops: popups, and a `Border`, whose own `Shadow` comes before `ShadowSize`.
|
|
77
|
+
* A `Border` of glass without an `Effect` takes only `ShadowSize`, and one with nothing to draw
|
|
78
|
+
* (a transparent `Background`, no outline, no `Effect`) drops no shadow at all.
|
|
79
|
+
*/
|
|
80
|
+
readonly ShadowColor?: ThemeColor | undefined;
|
|
81
|
+
readonly ShadowSize?: number | undefined;
|
|
82
|
+
readonly Shape?: PartShape | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Light of its color around it, from 0 to 1: a `Label` (not one with `Markdown`, which Raven
|
|
85
|
+
* draws without it), a `Border` (not one of glass without an `Effect`, nor one with nothing to
|
|
86
|
+
* draw), and the pins, links, nodes, clips, keys, markers and playhead of node graphs and
|
|
87
|
+
* timelines. A node glows only with a `BorderWidth` above 0: a selected node has one, so give one
|
|
88
|
+
* with `Glow` to the others.
|
|
89
|
+
*/
|
|
90
|
+
readonly Glow?: number | undefined;
|
|
91
|
+
/** The color it fades to: a header of a node, a clip of the `Gradient` form. */
|
|
92
|
+
readonly Gradient?: ThemeColor | undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A rule of a theme, for the widgets of a type and/or a class, or a part of them in some states.
|
|
97
|
+
* Without `Part` and `State`, its colors and metrics are those of the widgets it names; with
|
|
98
|
+
* them, only its properties count. The most specific rule wins (a type counts 1, a class or a
|
|
99
|
+
* state 2, as in CSS), and the last one between equals.
|
|
100
|
+
*/
|
|
101
|
+
export interface ThemeStyle extends ThemeColors, ThemeMetrics, PartProperties {
|
|
102
|
+
/** The tag of the widgets: `"Button"`. */
|
|
103
|
+
readonly Widget?: string | undefined;
|
|
104
|
+
/** A class of `Class="..."` on widgets. */
|
|
105
|
+
readonly Class?: string | undefined;
|
|
106
|
+
readonly Part?: ThemePart | undefined;
|
|
107
|
+
/** A state, or several that must all hold. */
|
|
108
|
+
readonly State?: ThemeState | readonly ThemeState[] | undefined;
|
|
109
|
+
readonly Material?: MaterialName | undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** A theme: what a theme file says, as an object. Any color or metric can also be given at its top. */
|
|
113
|
+
export interface ThemeSpec extends ThemeColors, ThemeMetrics {
|
|
114
|
+
/** The name the theme is kept under, for `Rtheme_use` and the `Theme` prop. */
|
|
115
|
+
readonly Name: string;
|
|
116
|
+
/** The look it starts from, and imposes. Without `Look` (nor `Base`) it is laid over the look in use, and follows it. */
|
|
117
|
+
readonly Look?: LookName | undefined;
|
|
118
|
+
/** A theme loaded before, or a look by its name of Raven 0.2 (`Dark`, `GlassLight`...); `Look` is the new way. */
|
|
119
|
+
readonly Base?: string | undefined;
|
|
120
|
+
/** `Springy`: things bounce (Raven). `Smooth`: short transitions that settle at once (the platforms). */
|
|
121
|
+
readonly Motion?: "Springy" | "Smooth" | undefined;
|
|
122
|
+
/** The accent color, and the shades that follow from it. */
|
|
123
|
+
readonly Accent?: ThemeColor | undefined;
|
|
124
|
+
/** The material of its surfaces. (`Default` is for widgets and styles: it puts them back to the theme's) */
|
|
125
|
+
readonly Material?: Exclude<MaterialName, "Default"> | undefined;
|
|
126
|
+
/** Whether it is dark, when its base does not say it right. */
|
|
127
|
+
readonly Dark?: boolean | undefined;
|
|
128
|
+
/** A font file: an absolute path, or one from the current folder. */
|
|
129
|
+
readonly Font?: string | undefined;
|
|
130
|
+
/** What is behind the content of the windows: an image file, `Dark`, `Light`, colors to blend (`"#1E3A8A,#7C3AED"`), or `None`. */
|
|
131
|
+
readonly Wallpaper?: string | undefined;
|
|
132
|
+
readonly Colors?: ThemeColors | undefined;
|
|
133
|
+
readonly Metrics?: ThemeMetrics | undefined;
|
|
134
|
+
readonly Glass?: { readonly [Property in GlassProperty]?: number | undefined } | undefined;
|
|
135
|
+
/** The colors of code in the text editor. */
|
|
136
|
+
readonly Syntax?: { readonly [Token in SyntaxToken]?: ThemeColor | undefined } | undefined;
|
|
137
|
+
readonly Styles?: readonly ThemeStyle[] | undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The keys of a spec that are its sections, not attributes of `<Theme>`. */
|
|
141
|
+
const SECTIONS: ReadonlySet<string> = new Set(["Colors", "Metrics", "Glass", "Syntax", "Styles"]);
|
|
142
|
+
|
|
143
|
+
/** `T`, with `never` for each key `Shape` does not have, at every depth: what makes a misspelled key an error. */
|
|
144
|
+
type OnlyKnown<T, Shape> = T extends readonly (infer Item)[]
|
|
145
|
+
? readonly OnlyKnown<Item, NonNullable<Shape> extends readonly (infer Allowed)[] ? Allowed : never>[]
|
|
146
|
+
: T extends object
|
|
147
|
+
? string extends keyof T
|
|
148
|
+
? T // (an object made by code, `Object.fromEntries`: its keys are not known before it runs)
|
|
149
|
+
: { readonly [K in keyof T]: K extends keyof NonNullable<Shape> ? OnlyKnown<T[K], NonNullable<Shape>[K]> : never }
|
|
150
|
+
: T;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* A theme, checked by its type: a misspelled key does not compile, at any depth, in the object
|
|
154
|
+
* written here or in one made apart (the error is at the key: `Type 'string' is not assignable to
|
|
155
|
+
* type 'never'`). An object made at runtime, `Object.fromEntries`, has no keys to check before it
|
|
156
|
+
* runs. Returns the theme as it was given, with its own type: `ocean.Colors.Panel` is a string.
|
|
157
|
+
*/
|
|
158
|
+
export function defineTheme<const T extends ThemeSpec>(theme: T & OnlyKnown<T, ThemeSpec>): T {
|
|
159
|
+
return theme;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** The attributes of an element, from the keys of an object that have a value. */
|
|
163
|
+
function attributes(values: object, skip: ReadonlySet<string> = new Set()): string {
|
|
164
|
+
let text = "";
|
|
165
|
+
for (const [name, value] of Object.entries(values)) {
|
|
166
|
+
if (value === undefined || value === null || skip.has(name)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
// (Raven would report it and skip it: it is a mistake of the code, not of a theme file)
|
|
170
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
171
|
+
RavenError.throw(`themeXml: ${name}="${value}" is not a finite number`);
|
|
172
|
+
}
|
|
173
|
+
// (several states are written apart by spaces)
|
|
174
|
+
const written = Array.isArray(value) ? value.join(" ") : String(value);
|
|
175
|
+
text += ` ${name}="${escapeXml(written)}"`;
|
|
176
|
+
}
|
|
177
|
+
return text;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** The text of the theme file a theme stands for. */
|
|
181
|
+
export function themeXml(theme: ThemeSpec): string {
|
|
182
|
+
const lines = [`<Theme${attributes(theme, SECTIONS)}>`];
|
|
183
|
+
for (const section of ["Colors", "Metrics", "Glass", "Syntax"] as const) {
|
|
184
|
+
const values = theme[section];
|
|
185
|
+
if (values && Object.keys(values).length > 0) {
|
|
186
|
+
lines.push(`\t<${section}${attributes(values)}/>`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
for (const style of theme.Styles ?? []) {
|
|
190
|
+
lines.push(`\t<Style${attributes(style)}/>`);
|
|
191
|
+
}
|
|
192
|
+
lines.push("</Theme>");
|
|
193
|
+
return lines.join("\n");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Gives a theme to Raven, which keeps it under its name: `Rtheme_use` then makes it the theme of
|
|
198
|
+
* the application, and the `Theme` prop gives it to a part of the window. Loading it again
|
|
199
|
+
* replaces it. Returns its name; throws when Raven refuses it.
|
|
200
|
+
*/
|
|
201
|
+
export function loadTheme(theme: ThemeSpec): string {
|
|
202
|
+
const name = r.Rtheme_load_from_memory(themeXml(theme));
|
|
203
|
+
if (name === null) {
|
|
204
|
+
return RavenError.fromLibrary(`loadTheme(${JSON.stringify(theme.Name)})`);
|
|
205
|
+
}
|
|
206
|
+
return name;
|
|
207
|
+
}
|
package/src/core/web.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web pages in a widget: `raven_web.h`, which is not in the binding, as a library can be built
|
|
3
|
+
* without it (`-DRAVEN_BUILD_WEB=OFF`) and the binding needs all it declares in order to start.
|
|
4
|
+
* So nothing is opened here until it is asked for.
|
|
5
|
+
*
|
|
6
|
+
* await runApp(<WebView Url="https://example.com" Slot={{ Fill: 1 }} />, options, { setup: registerWebView });
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { dlopen } from "bun:ffi";
|
|
10
|
+
import * as r from "./raven";
|
|
11
|
+
import { RavenError } from "./errors";
|
|
12
|
+
import type { CustomProps } from "./props.generated";
|
|
13
|
+
|
|
14
|
+
const SYMBOLS = {
|
|
15
|
+
Rweb_is_available: { args: [], returns: "i32" },
|
|
16
|
+
Rweb_set_browser_path: { args: ["ptr"], returns: "void" },
|
|
17
|
+
Rweb_get_browser_path: { args: [], returns: "cstring" },
|
|
18
|
+
Rweb_set_profile_folder: { args: ["ptr"], returns: "void" },
|
|
19
|
+
Rweb_set_browser_arguments: { args: ["ptr"], returns: "void" },
|
|
20
|
+
Rweb_register_xml: { args: [], returns: "void" },
|
|
21
|
+
Rweb_shutdown: { args: [], returns: "void" },
|
|
22
|
+
Rweb_view_create: { args: [], returns: "u32" },
|
|
23
|
+
Rweb_view_is_valid: { args: ["u32"], returns: "i32" },
|
|
24
|
+
Rweb_view_load_url: { args: ["u32", "ptr"], returns: "void" },
|
|
25
|
+
Rweb_view_load_file: { args: ["u32", "ptr"], returns: "void" },
|
|
26
|
+
Rweb_view_load_html: { args: ["u32", "ptr", "ptr"], returns: "void" },
|
|
27
|
+
Rweb_view_reload: { args: ["u32"], returns: "void" },
|
|
28
|
+
Rweb_view_stop: { args: ["u32"], returns: "void" },
|
|
29
|
+
Rweb_view_go_back: { args: ["u32"], returns: "void" },
|
|
30
|
+
Rweb_view_go_forward: { args: ["u32"], returns: "void" },
|
|
31
|
+
Rweb_view_can_go_back: { args: ["u32"], returns: "i32" },
|
|
32
|
+
Rweb_view_can_go_forward: { args: ["u32"], returns: "i32" },
|
|
33
|
+
Rweb_view_get_url: { args: ["u32"], returns: "cstring" },
|
|
34
|
+
Rweb_view_get_title: { args: ["u32"], returns: "cstring" },
|
|
35
|
+
Rweb_view_is_loading: { args: ["u32"], returns: "i32" },
|
|
36
|
+
Rweb_view_is_ready: { args: ["u32"], returns: "i32" },
|
|
37
|
+
Rweb_view_get_error: { args: ["u32"], returns: "cstring" },
|
|
38
|
+
Rweb_view_get_texture: { args: ["u32"], returns: "u32" },
|
|
39
|
+
Rweb_view_set_zoom: { args: ["u32", "f32"], returns: "void" },
|
|
40
|
+
Rweb_view_get_zoom: { args: ["u32"], returns: "f32" },
|
|
41
|
+
Rweb_view_set_frames: { args: ["u32", "i32", "i32"], returns: "void" },
|
|
42
|
+
Rweb_view_run_javascript: { args: ["u32", "ptr", "ptr", "ptr"], returns: "void" },
|
|
43
|
+
Rweb_view_set_message_callback: { args: ["u32", "ptr", "ptr"], returns: "void" },
|
|
44
|
+
Rweb_view_set_event_callback: { args: ["u32", "ptr", "ptr"], returns: "void" },
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The props of every widget, and what `<WebView>` takes in XML. Not `OnEvent`: a widget has one
|
|
49
|
+
* event callback, and this one forwards the mouse and the keys to the page.
|
|
50
|
+
*/
|
|
51
|
+
export type WebViewProps = Omit<CustomProps, "OnPaint" | "OnMeasure" | "OnEvent"> & {
|
|
52
|
+
readonly Url?: string | undefined;
|
|
53
|
+
/** A file: it can also be given by `Url` as `file://...`. */
|
|
54
|
+
readonly Source?: string | undefined;
|
|
55
|
+
readonly Html?: string | undefined;
|
|
56
|
+
readonly Zoom?: number | undefined;
|
|
57
|
+
readonly Frames?: "Jpeg" | "Png" | undefined;
|
|
58
|
+
readonly Quality?: number | undefined;
|
|
59
|
+
/** Fired when the page changes: read it through a ref, with `webFunctions`. */
|
|
60
|
+
readonly OnChanged?: string | (() => void) | undefined;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
let opened: ReturnType<typeof dlopen<typeof SYMBOLS>>["symbols"] | null = null;
|
|
64
|
+
|
|
65
|
+
/** The functions of `raven_web.h`, as bun:ffi gives them: a text is a `Buffer` that ends with a zero. */
|
|
66
|
+
export function webFunctions(): NonNullable<typeof opened> {
|
|
67
|
+
if (!opened) {
|
|
68
|
+
const library = process.env["RAVEN_LIBRARY"];
|
|
69
|
+
if (r.Rget_version() === null || !library) {
|
|
70
|
+
RavenError.throw("raven-tsx: RAVEN_LIBRARY should say where the library is, and it does not");
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
opened = dlopen(library, SYMBOLS).symbols;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
RavenError.throw(`raven-tsx: this library has no web pages: build Raven without -DRAVEN_BUILD_WEB=OFF (${error instanceof Error ? error.message : String(error)})`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return opened;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Makes `<WebView>` a tag Raven knows: once, after Raven started, as the `setup` of `runApp`. */
|
|
82
|
+
export function registerWebView(): void {
|
|
83
|
+
webFunctions().Rweb_register_xml();
|
|
84
|
+
}
|