@vestra-player/sdk 1.4.0 → 1.5.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/CHANGELOG.md +27 -0
- package/README.md +30 -22
- package/dist/commands.d.ts +1 -1
- package/dist/plugin/appearance.js +1 -1
- package/dist/plugin/services.js +61 -11
- package/dist/plugin/types.d.ts +118 -27
- package/package.json +1 -1
- package/slint/palette.slint +34 -24
- package/slint/vestra/icons.slint +99 -0
- package/slint/vestra/markdown.slint +6 -8
- package/slint/vestra/theme.slint +12 -8
- package/slint/vestra/widgets.slint +15 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.1
|
|
4
|
+
|
|
5
|
+
Fix; no API or type changes.
|
|
6
|
+
|
|
7
|
+
- **`storage.json` saves no longer collide.** Every save wrote through the same temporary file (`<name>.json.tmp-<pid>`), and only the writes of one store were queued. Two stores on the same file (the same name opened twice, in one scope or two) could therefore rename each other's temporary file away mid-save, and the save failed with `ENOENT` (on `chmod` or `rename`); on Windows two renames onto the file at once also fail with `EPERM`. Seen intermittently on Windows.
|
|
8
|
+
- Writes to one file are now queued together, whichever store makes them, and land in the order they were made.
|
|
9
|
+
- Each write has a temporary file of its own, made owner-only (`0600`) through its file handle before it gets the store's name, and removed again if the write fails.
|
|
10
|
+
- On Windows, replacing the file is retried for up to 2 s while another program (a virus scanner, the search indexer) briefly holds it open.
|
|
11
|
+
|
|
12
|
+
## 1.5.0
|
|
13
|
+
|
|
14
|
+
**Own visual identity: the Aurora palettes and the V-Line icons** (player 0.4.0). Additive; `apiVersion` stays `vestra/1`. Plugins that use the additions set `"minPlayerVersion": "0.4.0"`.
|
|
15
|
+
|
|
16
|
+
- **`ThemeId` gains `"vestra-light"`**, a light palette whose video area stays dark. `plugin.appearance.set("vestra-light")` switches every view; `TestHost` accepts it like the player. A player older than 0.4.0 answers it with `vestra` (an id it does not have).
|
|
17
|
+
- **New colours for `vestra`.** The palette is now Aurora, drawn from the logo's full spectrum: magenta accent (`#f72aa6`), cyan for what is playing (`Theme.playing`, `#2bb6ff`, violet before), `Theme.accent-fill` from amber through magenta and violet to cyan, and a visible `Theme.line` (`#2b2734`; it was near-black). Token names are unchanged; code that reads `Theme` follows.
|
|
18
|
+
- **Rounder corners**: `Theme.radius-control` 4px (was 3px), `Theme.radius-panel` 8px (was 6px). Sizes are otherwise unchanged.
|
|
19
|
+
- **`@vestra/icons.slint`** (new library file, `slint/vestra/icons.slint`): `Icon { name: "play"; color: Theme.text; }` draws one icon of the player's own set (round-capped 1.5px lines), and `Icons.commands(name)` / `box-width` / `box-height` / `line` give its path data. The names are listed in **Windows, panels, settings pages, dialogs and the OSD**.
|
|
20
|
+
- **`@vestra/widgets.slint`**: the bar slider's thumb is a white core in a 2px accent ring; the panel slider's track is `Theme.line`; stroked icon buttons use round caps and joins; tooltips have rounded corners. Geometry and API are unchanged.
|
|
21
|
+
- **std-widgets in a light palette**: the player still compiles plugin files with `fluent-dark` and switches the std-widgets colour scheme (`Palette.color-scheme`) to light for `vestra-light`, so `CheckBox`, `ComboBox`, `LineEdit`, `ScrollView` and the other std-widgets controls follow. The player's palette file (`slint/palette.slint`, not part of the library) now imports `std-widgets.slint` for that.
|
|
22
|
+
|
|
23
|
+
## 1.4.1
|
|
24
|
+
|
|
25
|
+
Documentation only; no code or type changes.
|
|
26
|
+
|
|
27
|
+
- **Built-in menus keep their place** (player 0.3.0). `CommandOptions` and `CommandOptions.order` document the player's menu rule: the seven built-in top-level menus (`@open` … `@settings`) sit at fixed orders (Open 110, Playback 200, Subtitles 300, Video 400, Audio 500, Screen 710, Settings 830), whether a path names them by id or by title, so a plugin's item never moves them. Directly inside a built-in menu, `order` 0 (the default) sorts after every other order, so an item without `order` goes at the end. Players before 0.3.0 move the whole menu to the top for such an item; give it an order in the menu's range there.
|
|
28
|
+
- The Blur walkthrough explains `order` inside the Video menu accordingly.
|
|
29
|
+
|
|
3
30
|
## 1.4.0
|
|
4
31
|
|
|
5
32
|
**One palette.** `ThemeId` is now `"vestra"` only: the second palette that earlier versions listed is gone from the player. This is a minor version although a type narrows, because the only plugins it affects compare `plugin.appearance.theme` with, or `set` the removed id:
|
package/README.md
CHANGED
|
@@ -33,7 +33,13 @@ npm i -D @vestra-player/sdk # Node.js 26 or newer
|
|
|
33
33
|
`manifest.json` and `main.ts`:
|
|
34
34
|
|
|
35
35
|
```json
|
|
36
|
-
{
|
|
36
|
+
{
|
|
37
|
+
"id": "examples.hello",
|
|
38
|
+
"version": "0.1.0",
|
|
39
|
+
"apiVersion": "vestra/1",
|
|
40
|
+
"entry": "main.ts",
|
|
41
|
+
"capabilities": ["commands", "ui.osd"]
|
|
42
|
+
}
|
|
37
43
|
```
|
|
38
44
|
|
|
39
45
|
```ts
|
|
@@ -54,34 +60,36 @@ Run `npx vestra-plugin pack` to check and package it, or start the player with `
|
|
|
54
60
|
|
|
55
61
|
Complete, tested plugins in [`examples/`](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples). The [examples page](https://vestrasys.github.io/vestra-player-sdk/documents/Examples.html) shows each one with a screenshot and a step-by-step walkthrough.
|
|
56
62
|
|
|
57
|
-
| Example
|
|
58
|
-
|
|
|
59
|
-
| [hello](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/hello)
|
|
60
|
-
| [blur](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/blur)
|
|
61
|
-
| [signal-generator](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/signal-generator) | Generating video and audio frames yourself
|
|
62
|
-
| [bookmark](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/bookmark)
|
|
63
|
-
| [panel-menu](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/panel-menu)
|
|
64
|
-
| [
|
|
65
|
-
| [
|
|
63
|
+
| Example | What you learn | Walkthrough |
|
|
64
|
+
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
|
|
65
|
+
| [hello](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/hello) | The smallest plugin: a manifest, one menu command, an on-screen message. | [Step by step](https://vestrasys.github.io/vestra-player-sdk/documents/Hello.html) |
|
|
66
|
+
| [blur](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/blur) | A video filter driven by one slider in a tool window. | [Step by step](https://vestrasys.github.io/vestra-player-sdk/documents/Example__Blur.html) |
|
|
67
|
+
| [signal-generator](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/signal-generator) | Generating video and audio frames yourself: colour bars and a sine tone. | [Step by step](https://vestrasys.github.io/vestra-player-sdk/documents/Example__Signal_generator.html) |
|
|
68
|
+
| [bookmark](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/bookmark) | A tool window bound to the playback state, with Mark and Jump buttons. | [Step by step](https://vestrasys.github.io/vestra-player-sdk/documents/Example__Bookmark.html) |
|
|
69
|
+
| [panel-menu](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/panel-menu) | A sidebar panel with its own right-click menu. | [Step by step](https://vestrasys.github.io/vestra-player-sdk/documents/Example__Panel_menu.html) |
|
|
70
|
+
| [menus](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/menus) | Radio items, separator groups and a menu item whose title changes. | [Step by step](https://vestrasys.github.io/vestra-player-sdk/documents/Example__Menus.html) |
|
|
71
|
+
| [theme-widgets](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/theme-widgets) | A tool window drawn with the player's theme and widgets. | [Step by step](https://vestrasys.github.io/vestra-player-sdk/documents/Example__Theme_widgets.html) |
|
|
72
|
+
| [markdown](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/markdown) | A window that shows a Markdown document. | [Step by step](https://vestrasys.github.io/vestra-player-sdk/documents/Example__Markdown.html) |
|
|
66
73
|
|
|
67
74
|
## Command line
|
|
68
75
|
|
|
69
|
-
| Command
|
|
70
|
-
|
|
|
71
|
-
| `vestra-plugin check [dir...]`
|
|
72
|
-
| `vestra-plugin test [dir...]`
|
|
73
|
-
| `vestra-plugin build [dir...] [--version x.y.z]`
|
|
74
|
-
| `vestra-plugin pack [dir...] [--version x.y.z] [--out dir]` | Builds, then packs `dist/` as `<id>-<version>.vxp`.
|
|
76
|
+
| Command | Does |
|
|
77
|
+
| ----------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
78
|
+
| `vestra-plugin check [dir...]` | Checks the manifest, then a strict type check of the plugin and its tests. |
|
|
79
|
+
| `vestra-plugin test [dir...]` | Runs `node --test` on `tests/*.test.{ts,mjs}`. |
|
|
80
|
+
| `vestra-plugin build [dir...] [--version x.y.z]` | Writes `dist/`: types stripped, files copied, manifest versioned. |
|
|
81
|
+
| `vestra-plugin pack [dir...] [--version x.y.z] [--out dir]` | Builds, then packs `dist/` as `<id>-<version>.vxp`. |
|
|
75
82
|
|
|
76
83
|
## Compatibility
|
|
77
84
|
|
|
78
|
-
| SDK | Plugin API | Vestra Player
|
|
79
|
-
| --- |
|
|
80
|
-
| 1.0 | `vestra/1` | 0.1 or newer
|
|
81
|
-
| 1.1 | `vestra/1` | 0.2 or newer for the 1.1 additions
|
|
82
|
-
| 1.2 | `vestra/1` | 0.2 or newer
|
|
83
|
-
| 1.3 | `vestra/1` | 0.2 or newer; 0.3 for the 1.3 additions (menu ids, `Theme.danger`, empty video chains)
|
|
85
|
+
| SDK | Plugin API | Vestra Player | Node.js |
|
|
86
|
+
| --- | ---------- | ----------------------------------------------------------------------------------------- | ---------------------------- |
|
|
87
|
+
| 1.0 | `vestra/1` | 0.1 or newer | 22.18 or newer |
|
|
88
|
+
| 1.1 | `vestra/1` | 0.2 or newer for the 1.1 additions | 22.18 or newer |
|
|
89
|
+
| 1.2 | `vestra/1` | 0.2 or newer | 26 (shipped with the player) |
|
|
90
|
+
| 1.3 | `vestra/1` | 0.2 or newer; 0.3 for the 1.3 additions (menu ids, `Theme.danger`, empty video chains) | 26 (shipped with the player) |
|
|
84
91
|
| 1.4 | `vestra/1` | 0.2 or newer; 0.3 for the 1.3 additions. `ThemeId` is `"vestra"` only (see the CHANGELOG) | 26 (shipped with the player) |
|
|
92
|
+
| 1.5 | `vestra/1` | 0.2 or newer; 0.3 for the 1.3 additions; 0.4 for the 1.5 additions (`vestra-light`, `@vestra/icons.slint`) | 26 (shipped with the player) |
|
|
85
93
|
|
|
86
94
|
Minor releases only add backwards-compatible API; a breaking change gets a new `apiVersion` and a new major version. `@vestra-player/sdk/runtime` is for the player only and not covered by semver.
|
|
87
95
|
|
package/dist/commands.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ export type CommandDeclaration = {
|
|
|
54
54
|
title: string;
|
|
55
55
|
/** Complete primary menu path INCLUDING its leaf; empty = not in the main menu. */
|
|
56
56
|
path: string[];
|
|
57
|
-
/** Sibling order, ascending; a submenu takes its first command's order. */
|
|
57
|
+
/** Sibling order, ascending; a submenu takes its first command's order (a built-in menu keeps a fixed one). */
|
|
58
58
|
order: number;
|
|
59
59
|
/** Separator label: adjacent siblings of different groups get a line. */
|
|
60
60
|
group: string;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { failure } from "./runtime.js";
|
|
3
3
|
import { windowOf } from "./window.js";
|
|
4
4
|
/** The palettes this SDK knows, the default first. */
|
|
5
|
-
export const THEMES = ["vestra"];
|
|
5
|
+
export const THEMES = ["vestra", "vestra-light"];
|
|
6
6
|
/**
|
|
7
7
|
* One per plugin: the value comes with the `window.state` read before
|
|
8
8
|
* activation; with `events`, `appearance.changed` keeps it current once
|
package/dist/plugin/services.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* run in this plugin's own process.
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, readFileSync, renameSync } from "node:fs";
|
|
7
|
-
import {
|
|
7
|
+
import { mkdir, open, rename, rm, stat } from "node:fs/promises";
|
|
8
8
|
import { join, resolve } from "node:path";
|
|
9
9
|
import { failure } from "./runtime.js";
|
|
10
10
|
import { systemServices } from "./local.js";
|
|
@@ -101,6 +101,54 @@ export function tools(scope) {
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
const NAME = /^[a-z0-9-]{1,64}$/;
|
|
104
|
+
/** Writes so far in this process; numbers each write's temporary file. */
|
|
105
|
+
let written = 0;
|
|
106
|
+
/**
|
|
107
|
+
* The pending writes of each file, whichever store they come from: the same
|
|
108
|
+
* name opened twice must not write or rename into one file at once.
|
|
109
|
+
*/
|
|
110
|
+
const writes = new Map();
|
|
111
|
+
/** Write `text` to `path` atomically, owner-only (0600). */
|
|
112
|
+
async function writeAtomic(path, text) {
|
|
113
|
+
// A name of its own per write, so no other write renames it away.
|
|
114
|
+
const temporary = `${path}.tmp-${process.pid}-${++written}`;
|
|
115
|
+
await mkdir(join(path, ".."), { recursive: true });
|
|
116
|
+
try {
|
|
117
|
+
// Settings can hold private paths and history. The mode is set through
|
|
118
|
+
// the handle, before the file has its name.
|
|
119
|
+
const file = await open(temporary, "w", 0o600);
|
|
120
|
+
try {
|
|
121
|
+
await file.chmod(0o600);
|
|
122
|
+
await file.writeFile(text);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
await file.close();
|
|
126
|
+
}
|
|
127
|
+
await replace(temporary, path);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
await rm(temporary, { force: true }).catch(() => { });
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Rename over `to`. Windows refuses while another program (a virus scanner,
|
|
136
|
+
* the search indexer) has `to` open for a moment: retry for up to 2 s there.
|
|
137
|
+
*/
|
|
138
|
+
async function replace(from, to) {
|
|
139
|
+
for (let attempt = 0;; attempt++)
|
|
140
|
+
try {
|
|
141
|
+
return await rename(from, to);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
const code = error.code;
|
|
145
|
+
if (process.platform !== "win32" ||
|
|
146
|
+
!(code === "EPERM" || code === "EACCES" || code === "EBUSY") ||
|
|
147
|
+
attempt >= 20)
|
|
148
|
+
throw error;
|
|
149
|
+
await new Promise((resolve) => setTimeout(resolve, 10 + attempt * 10));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
104
152
|
class Store {
|
|
105
153
|
#scope;
|
|
106
154
|
#path;
|
|
@@ -159,17 +207,19 @@ class Store {
|
|
|
159
207
|
return this.#writing;
|
|
160
208
|
this.#dirty = false;
|
|
161
209
|
const text = `${JSON.stringify(this.#value, null, 2)}\n`;
|
|
162
|
-
// One write at a time, in order; the newest value is written
|
|
163
|
-
|
|
210
|
+
// One write per file at a time, in order; the newest value is written
|
|
211
|
+
// last. The queue is per file, not per store (see `writes`).
|
|
212
|
+
const path = this.#path;
|
|
213
|
+
const write = (writes.get(path) ?? Promise.resolve())
|
|
164
214
|
.catch(() => { })
|
|
165
|
-
.then(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
215
|
+
.then(() => writeAtomic(path, text));
|
|
216
|
+
const done = () => {
|
|
217
|
+
if (writes.get(path) === write)
|
|
218
|
+
writes.delete(path);
|
|
219
|
+
};
|
|
220
|
+
writes.set(path, write);
|
|
221
|
+
write.then(done, done);
|
|
222
|
+
this.#writing = write;
|
|
173
223
|
return this.#writing;
|
|
174
224
|
}
|
|
175
225
|
async dispose() {
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -376,6 +376,21 @@ export type MenuId = "open" | "playback" | "subtitles" | "video" | "audio" | "sc
|
|
|
376
376
|
* ranges: `open` 100–199, `domain` 200–699 (playback, subtitles, video,
|
|
377
377
|
* audio…), `screen` 700–799, `windows` 800–899, `exit` 900–999.
|
|
378
378
|
*
|
|
379
|
+
* Built-in menus keep their place. The seven built-in top-level menus
|
|
380
|
+
* ({@link MenuId}) sit at fixed orders, whatever is inside them: Open 110,
|
|
381
|
+
* Playback 200, Subtitles 300, Video 400, Audio 500, Screen 710, Settings
|
|
382
|
+
* 830 (player 0.3.0). This holds whether the path names the menu by id
|
|
383
|
+
* (`["@video", "Blur"]`) or by its title (`["视频", "Blur"]`), so a plugin's
|
|
384
|
+
* item never moves the player's menu. Directly inside a built-in menu,
|
|
385
|
+
* `order` 0 (the default) sorts after every other order: an item without
|
|
386
|
+
* `order` goes at the end, after the player's own items (which all use 100
|
|
387
|
+
* or more). Give a positive order to sit between them (the player's orders
|
|
388
|
+
* are listed in its menu table, doc/product/menus.md), or a negative one to
|
|
389
|
+
* come first. Deeper levels (a plugin's own submenu) have no such rule.
|
|
390
|
+
* Players before 0.3.0 place every submenu at its smallest order, so there
|
|
391
|
+
* an item without `order` moves the whole menu to the top; to avoid that,
|
|
392
|
+
* give it an order in the menu's range (Video: 400–499).
|
|
393
|
+
*
|
|
379
394
|
* {@link Commands.add} throws `invalid` synchronously for a bad path,
|
|
380
395
|
* order, group or binding. The same path registered twice, or a path that
|
|
381
396
|
* is both an item and a submenu, fails with `conflict` (through `ready`).
|
|
@@ -406,8 +421,10 @@ export interface CommandOptions {
|
|
|
406
421
|
title?: string;
|
|
407
422
|
/**
|
|
408
423
|
* Sibling order in the menu, ascending (integer, default 0). A submenu sits
|
|
409
|
-
* where its first command sits
|
|
410
|
-
*
|
|
424
|
+
* where its first command sits, except a built-in menu, which keeps its
|
|
425
|
+
* fixed place. Directly inside a built-in menu, 0 (the default) sorts last.
|
|
426
|
+
* Equal orders: plugin id, then declaration order. Range ±1,000,000. See
|
|
427
|
+
* {@link CommandOptions} for the menu order rules.
|
|
411
428
|
*/
|
|
412
429
|
order?: number;
|
|
413
430
|
/** Separator label: a line is drawn between adjacent items of different groups. Default `"domain"`. */
|
|
@@ -1212,8 +1229,17 @@ export interface SlotState {
|
|
|
1212
1229
|
*
|
|
1213
1230
|
* @capability ui.mount; `events` to follow changes made elsewhere.
|
|
1214
1231
|
*
|
|
1215
|
-
* @example A "Show sidebar" menu item that works with any sidebar panel
|
|
1216
|
-
*
|
|
1232
|
+
* @example A "Show sidebar" menu item that works with any sidebar panel
|
|
1233
|
+
* ```ts
|
|
1234
|
+
* const sidebar = plugin.panels.sidebar;
|
|
1235
|
+
* plugin.commands.add({
|
|
1236
|
+
* id: "sidebar",
|
|
1237
|
+
* path: ["Tools", "Show sidebar"],
|
|
1238
|
+
* checked: () => sidebar.state?.preferred ?? false,
|
|
1239
|
+
* enabled: () => sidebar.state !== null,
|
|
1240
|
+
* run: () => sidebar.toggle(),
|
|
1241
|
+
* });
|
|
1242
|
+
* ```
|
|
1217
1243
|
*
|
|
1218
1244
|
* @see {@link Panels}, {@link SlotState}
|
|
1219
1245
|
* @since 1.0.0
|
|
@@ -1313,8 +1339,17 @@ export interface PanelSlot {
|
|
|
1313
1339
|
*
|
|
1314
1340
|
* @capability ui.mount
|
|
1315
1341
|
*
|
|
1316
|
-
* @example
|
|
1317
|
-
*
|
|
1342
|
+
* @example A "Detach sidebar" menu item
|
|
1343
|
+
* ```ts
|
|
1344
|
+
* const sidebar = plugin.panels.sidebar;
|
|
1345
|
+
* plugin.commands.add({
|
|
1346
|
+
* id: "detach",
|
|
1347
|
+
* path: ["Tools", "Detach sidebar"],
|
|
1348
|
+
* checked: () => sidebar.state?.detached ?? false,
|
|
1349
|
+
* enabled: () => sidebar.state !== null,
|
|
1350
|
+
* run: () => sidebar.setDetached(!sidebar.state?.detached),
|
|
1351
|
+
* });
|
|
1352
|
+
* ```
|
|
1318
1353
|
*
|
|
1319
1354
|
* @since 1.0.0
|
|
1320
1355
|
* @category UI
|
|
@@ -1562,19 +1597,20 @@ export interface Drops {
|
|
|
1562
1597
|
provide(handler: DropHandler): Disposable;
|
|
1563
1598
|
}
|
|
1564
1599
|
/**
|
|
1565
|
-
* The player's palettes
|
|
1566
|
-
*
|
|
1567
|
-
* `Theme` instead of
|
|
1600
|
+
* The player's palettes: `vestra`, the dark default, and `vestra-light`
|
|
1601
|
+
* (player 0.4.0, SDK 1.5.0), a light palette whose video area stays dark.
|
|
1602
|
+
* Both draw on the logo's colours. Read colours from `Theme` instead of
|
|
1603
|
+
* branching on the id: a later version may add palettes.
|
|
1568
1604
|
*
|
|
1569
1605
|
* @remarks
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
1572
|
-
*
|
|
1606
|
+
* The player treats an id it does not have as `vestra`: plugins built with an
|
|
1607
|
+
* older SDK that still ask for a palette removed in SDK 1.4.0 keep working,
|
|
1608
|
+
* and a player older than 0.4.0 answers `vestra-light` with `vestra`.
|
|
1573
1609
|
*
|
|
1574
1610
|
* @since 1.0.0
|
|
1575
1611
|
* @category UI
|
|
1576
1612
|
*/
|
|
1577
|
-
export type ThemeId = "vestra";
|
|
1613
|
+
export type ThemeId = "vestra" | "vestra-light";
|
|
1578
1614
|
/**
|
|
1579
1615
|
* The player's palette, which every view uses. `plugin.appearance`.
|
|
1580
1616
|
*
|
|
@@ -1584,7 +1620,9 @@ export type ThemeId = "vestra";
|
|
|
1584
1620
|
* and again when the palette changes. Slint code that reads its colours from
|
|
1585
1621
|
* `Theme` (`import { Theme } from "@vestra/theme.slint"`) follows it with no
|
|
1586
1622
|
* plugin code; hard-coded colours do not. Only colours change: corner radii,
|
|
1587
|
-
* font sizes and control sizes are the same in every palette.
|
|
1623
|
+
* font sizes and control sizes are the same in every palette. A light palette
|
|
1624
|
+
* also switches the std-widgets controls (`std-widgets.slint`) to their light
|
|
1625
|
+
* colours, and keeps the video area dark.
|
|
1588
1626
|
*
|
|
1589
1627
|
* The player saves the choice; it stays after the plugin that set it stops.
|
|
1590
1628
|
* Tell the user about a change yourself (for example with
|
|
@@ -1593,14 +1631,14 @@ export type ThemeId = "vestra";
|
|
|
1593
1631
|
* @capability None to read `theme`; `events` to follow changes; `ui.theme`
|
|
1594
1632
|
* to change it.
|
|
1595
1633
|
*
|
|
1596
|
-
* @example A menu item that selects the palette
|
|
1634
|
+
* @example A menu item that selects the light palette
|
|
1597
1635
|
* ```ts
|
|
1598
1636
|
* plugin.commands.add({
|
|
1599
|
-
* id: "
|
|
1600
|
-
* path: ["@settings", "
|
|
1637
|
+
* id: "light-colours",
|
|
1638
|
+
* path: ["@settings", "Light colours"],
|
|
1601
1639
|
* radio: true,
|
|
1602
|
-
* checked: () => plugin.appearance.theme === "vestra",
|
|
1603
|
-
* run: () => plugin.appearance.set("vestra"),
|
|
1640
|
+
* checked: () => plugin.appearance.theme === "vestra-light",
|
|
1641
|
+
* run: () => plugin.appearance.set("vestra-light"),
|
|
1604
1642
|
* });
|
|
1605
1643
|
* ```
|
|
1606
1644
|
*
|
|
@@ -1657,7 +1695,7 @@ export interface Appearance {
|
|
|
1657
1695
|
*
|
|
1658
1696
|
* @example
|
|
1659
1697
|
* ```ts
|
|
1660
|
-
* await plugin.appearance.set("vestra");
|
|
1698
|
+
* await plugin.appearance.set("vestra-light");
|
|
1661
1699
|
* ```
|
|
1662
1700
|
*
|
|
1663
1701
|
* @since 1.0.0
|
|
@@ -1694,12 +1732,59 @@ export type SettingsCategory = "general" | "playback" | "subtitles" | "video" |
|
|
|
1694
1732
|
*
|
|
1695
1733
|
* @capability ui.settings
|
|
1696
1734
|
*
|
|
1697
|
-
* @example Two preferences on a page
|
|
1698
|
-
*
|
|
1735
|
+
* @example Two preferences on a page, kept in a store
|
|
1736
|
+
* ```ts
|
|
1737
|
+
* const store = plugin.storage.json("blur", {
|
|
1738
|
+
* defaults: { restoreOnStart: false, messages: true },
|
|
1739
|
+
* });
|
|
1740
|
+
* const page = plugin.settings.page("ui/settings.slint", {
|
|
1741
|
+
* id: "blur",
|
|
1742
|
+
* category: "video",
|
|
1743
|
+
* title: "Blur",
|
|
1744
|
+
* });
|
|
1745
|
+
* page.bind(() => ({
|
|
1746
|
+
* restoreOnStart: store.value.restoreOnStart,
|
|
1747
|
+
* messages: store.value.messages,
|
|
1748
|
+
* }));
|
|
1749
|
+
* page.on("restoreToggled", (on: boolean) =>
|
|
1750
|
+
* store.save({ ...store.value, restoreOnStart: on }),
|
|
1751
|
+
* );
|
|
1752
|
+
* page.on("messagesToggled", (on: boolean) =>
|
|
1753
|
+
* store.save({ ...store.value, messages: on }),
|
|
1754
|
+
* );
|
|
1755
|
+
* ```
|
|
1756
|
+
*
|
|
1757
|
+
* `ui/settings.slint` exports the bridge global and the layout:
|
|
1699
1758
|
*
|
|
1700
|
-
*
|
|
1759
|
+
* ```slint
|
|
1760
|
+
* import { CheckBox } from "std-widgets.slint";
|
|
1761
|
+
* import { Theme } from "@vestra/theme.slint";
|
|
1701
1762
|
*
|
|
1702
|
-
* {
|
|
1763
|
+
* export global BlurPage {
|
|
1764
|
+
* in-out property <bool> restore-on-start;
|
|
1765
|
+
* in-out property <bool> messages: true;
|
|
1766
|
+
* callback restore-toggled(bool);
|
|
1767
|
+
* callback messages-toggled(bool);
|
|
1768
|
+
* }
|
|
1769
|
+
*
|
|
1770
|
+
* export component BlurSettings inherits Rectangle {
|
|
1771
|
+
* VerticalLayout {
|
|
1772
|
+
* padding: Theme.dialog-padding;
|
|
1773
|
+
* spacing: 6px;
|
|
1774
|
+
* alignment: start;
|
|
1775
|
+
* CheckBox {
|
|
1776
|
+
* text: "Turn blur back on when the player starts";
|
|
1777
|
+
* checked <=> BlurPage.restore-on-start;
|
|
1778
|
+
* toggled => { BlurPage.restore-toggled(self.checked); }
|
|
1779
|
+
* }
|
|
1780
|
+
* CheckBox {
|
|
1781
|
+
* text: "Show on-screen messages";
|
|
1782
|
+
* checked <=> BlurPage.messages;
|
|
1783
|
+
* toggled => { BlurPage.messages-toggled(self.checked); }
|
|
1784
|
+
* }
|
|
1785
|
+
* }
|
|
1786
|
+
* }
|
|
1787
|
+
* ```
|
|
1703
1788
|
*
|
|
1704
1789
|
* @see {@link Settings.page}, [Windows, panels, settings pages, dialogs and the OSD](../../docs/concepts/ui.md)
|
|
1705
1790
|
* @since 1.0.0
|
|
@@ -1834,7 +1919,7 @@ export interface Settings {
|
|
|
1834
1919
|
* @throws `capability` (rejects) The manifest does not declare
|
|
1835
1920
|
* `ui.settings`.
|
|
1836
1921
|
*
|
|
1837
|
-
* @example An "Options…" button in a tool window
|
|
1922
|
+
* @example An "Options…" button in a tool window
|
|
1838
1923
|
* ```ts
|
|
1839
1924
|
* const controls = plugin.windows.create("ui/window.slint");
|
|
1840
1925
|
* controls.on("openOptions", () => plugin.settings.show("blur"));
|
|
@@ -3031,8 +3116,14 @@ export interface OsdPanel extends Disposable {
|
|
|
3031
3116
|
*
|
|
3032
3117
|
* @capability ui.osd
|
|
3033
3118
|
*
|
|
3034
|
-
* @example On-screen messages that the user can turn off
|
|
3035
|
-
*
|
|
3119
|
+
* @example On-screen messages that the user can turn off
|
|
3120
|
+
* ```ts
|
|
3121
|
+
* const store = plugin.storage.json("prefs", { defaults: { messages: true } });
|
|
3122
|
+
* const say = (text: string) => {
|
|
3123
|
+
* if (store.value.messages) void plugin.osd.show(text);
|
|
3124
|
+
* };
|
|
3125
|
+
* say("Blur on");
|
|
3126
|
+
* ```
|
|
3036
3127
|
*
|
|
3037
3128
|
* @see {@link OsdPanel}, [Windows, panels, settings pages, dialogs and the OSD](../../docs/concepts/ui.md)
|
|
3038
3129
|
* @since 1.0.0
|
package/package.json
CHANGED
package/slint/palette.slint
CHANGED
|
@@ -1,32 +1,42 @@
|
|
|
1
1
|
// The player's colour palette (R-H6) and per-system fonts. Not part of the
|
|
2
2
|
// plugin library: the player sets these `in` properties on every view;
|
|
3
3
|
// `Theme` in @vestra/theme.slint reads them as `out` properties. Defaults:
|
|
4
|
-
//
|
|
4
|
+
// the Aurora colours of the `vestra` palette, the macOS fonts.
|
|
5
|
+
import { Palette } from "std-widgets.slint";
|
|
6
|
+
|
|
5
7
|
export global VestraPalette {
|
|
6
|
-
in property <color> bg-bar: #
|
|
7
|
-
in property <color> bg-video: #
|
|
8
|
-
in property <color> bg-list: #
|
|
9
|
-
in property <color> bg-panel: #
|
|
10
|
-
in property <color> line: #
|
|
11
|
-
in property <color> hover-bg: #
|
|
12
|
-
in property <color> press-bg: #
|
|
13
|
-
in property <color> selected-bg: #
|
|
14
|
-
in property <color> text: #
|
|
15
|
-
in property <color> text-dim: #
|
|
16
|
-
in property <color> text-list: #
|
|
17
|
-
in property <color> text-played: #
|
|
18
|
-
in property <color> accent: #
|
|
19
|
-
in property <color> accent-press: #
|
|
20
|
-
in property <color> accent-disabled: #
|
|
21
|
-
in property <color> icon-disabled: #
|
|
22
|
-
in property <color> playing: #
|
|
23
|
-
in property <color> button-bg: #
|
|
24
|
-
in property <color> button-hover: #
|
|
25
|
-
in property <color> button-press: #
|
|
26
|
-
in property <color> button-disabled: #
|
|
8
|
+
in property <color> bg-bar: #16141c;
|
|
9
|
+
in property <color> bg-video: #0a090e;
|
|
10
|
+
in property <color> bg-list: #1a1821;
|
|
11
|
+
in property <color> bg-panel: #201d28;
|
|
12
|
+
in property <color> line: #2b2734;
|
|
13
|
+
in property <color> hover-bg: #28232f;
|
|
14
|
+
in property <color> press-bg: #110f16;
|
|
15
|
+
in property <color> selected-bg: #2f2840;
|
|
16
|
+
in property <color> text: #f5f3fa;
|
|
17
|
+
in property <color> text-dim: #8e88a0;
|
|
18
|
+
in property <color> text-list: #bdb8cb;
|
|
19
|
+
in property <color> text-played: #6e6980;
|
|
20
|
+
in property <color> accent: #f72aa6;
|
|
21
|
+
in property <color> accent-press: #c41a83;
|
|
22
|
+
in property <color> accent-disabled: #5a1d48;
|
|
23
|
+
in property <color> icon-disabled: #3b3646;
|
|
24
|
+
in property <color> playing: #2bb6ff;
|
|
25
|
+
in property <color> button-bg: #2f2a3b;
|
|
26
|
+
in property <color> button-hover: #3a3448;
|
|
27
|
+
in property <color> button-press: #25202e;
|
|
28
|
+
in property <color> button-disabled: #1b1922;
|
|
27
29
|
// Errors and destructive actions (failed status, delete).
|
|
28
|
-
in property <color> danger: #
|
|
30
|
+
in property <color> danger: #ff5a5f;
|
|
29
31
|
// The system's monospace family (platform::monospace_font).
|
|
30
32
|
in property <string> font-mono: "Menlo";
|
|
31
|
-
|
|
33
|
+
// Each palette's progress gradient, logo amber → magenta → violet → cyan.
|
|
34
|
+
// The player copies one into `accent-fill` (Slint's Rust API cannot
|
|
35
|
+
// build gradients).
|
|
36
|
+
out property <brush> fill-vestra: @linear-gradient(90deg, #fcc402 0%, #fb5a35 20%, #fa0aa8 45%, #7a0afa 72%, #10a8fc 100%);
|
|
37
|
+
out property <brush> fill-vestra-light: @linear-gradient(90deg, #f5a700 0%, #f0442f 20%, #e0089a 45%, #6a08e8 72%, #0a95e6 100%);
|
|
38
|
+
in property <brush> accent-fill: self.fill-vestra;
|
|
39
|
+
// The std-widgets (fluent) controls follow the palette: the player sets
|
|
40
|
+
// `light` for a light palette.
|
|
41
|
+
in-out property <ColorScheme> color-scheme <=> Palette.color-scheme;
|
|
32
42
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// The V-Line icon set (doc/product/ui-spec.md §2.4): round-capped, round-joined
|
|
2
|
+
// lines, 1.5px (1.25px for the small glyphs), never filled. The V of the
|
|
3
|
+
// logo recurs: « » for back and forward, ∧ ∨ for moving rows, a small V for
|
|
4
|
+
// every menu, list and scroll arrow.
|
|
5
|
+
// import { Icon } from "@vestra/icons.slint";
|
|
6
|
+
// Icon { name: "play"; color: Theme.text; }
|
|
7
|
+
// Each icon is drawn in its own box (`Icons.box-width` × `Icons.box-height`),
|
|
8
|
+
// which is also the Icon's default size; an unknown name draws nothing.
|
|
9
|
+
import { Theme } from "theme.slint";
|
|
10
|
+
|
|
11
|
+
export global Icons {
|
|
12
|
+
// Path data in the icon's box.
|
|
13
|
+
public pure function commands(name: string) -> string {
|
|
14
|
+
if name == "play" { return "M 2.2 2.0 L 10.9 7 L 2.2 12.0 Z"; }
|
|
15
|
+
if name == "pause" { return "M 1.75 1.75 H 4.5 V 12.25 H 1.75 Z M 7.5 1.75 H 10.25 V 12.25 H 7.5 Z"; }
|
|
16
|
+
if name == "stop" { return "M 3.6 1.5 H 8.4 Q 10.5 1.5 10.5 3.6 V 8.4 Q 10.5 10.5 8.4 10.5 H 3.6 Q 1.5 10.5 1.5 8.4 V 3.6 Q 1.5 1.5 3.6 1.5 Z"; }
|
|
17
|
+
if name == "back" { return "M 6.5 1.25 L 2.5 5 L 6.5 8.75 M 12 1.25 L 8 5 L 12 8.75"; }
|
|
18
|
+
if name == "forward" { return "M 2 1.25 L 6 5 L 2 8.75 M 7.5 1.25 L 11.5 5 L 7.5 8.75"; }
|
|
19
|
+
if name == "open" { return "M 1.5 3.2 Q 1.5 1.5 3.2 1.5 H 5.2 L 6.8 3.2 H 10.8 Q 12.5 3.2 12.5 4.9 V 8.8 Q 12.5 10.5 10.8 10.5 H 3.2 Q 1.5 10.5 1.5 8.8 Z"; }
|
|
20
|
+
if name == "volume" { return "M 1 3.6 H 3 L 6 1.2 V 8.8 L 3 6.4 H 1 Z M 8.6 3.4 Q 9.7 5 8.6 6.6 M 10.6 1.6 Q 12.6 5 10.6 8.4"; }
|
|
21
|
+
if name == "mute" { return "M 1 3.6 H 3 L 6 1.2 V 8.8 L 3 6.4 H 1 Z M 8.8 3.4 L 12 6.6 M 12 3.4 L 8.8 6.6"; }
|
|
22
|
+
if name == "playlist" { return "M 1.25 1.5 H 11.75 M 1.25 5 H 11.75 M 1.25 8.5 H 6.5 M 9.2 7.2 L 10.5 8.5 L 11.8 7.2"; }
|
|
23
|
+
if name == "detach" { return "M 5.5 1.5 H 3.3 Q 1.5 1.5 1.5 3.3 V 8.7 Q 1.5 10.5 3.3 10.5 H 8.7 Q 10.5 10.5 10.5 8.7 V 6.5 M 8.5 1.5 H 12.5 V 5.5 M 12.5 1.5 L 6.5 7.5"; }
|
|
24
|
+
if name == "attach" { return "M 5.5 1.5 H 3.3 Q 1.5 1.5 1.5 3.3 V 8.7 Q 1.5 10.5 3.3 10.5 H 8.7 Q 10.5 10.5 10.5 8.7 V 6.5 M 12.5 1.5 L 7 7 M 7 3.5 V 7 H 10.5"; }
|
|
25
|
+
if name == "close" { return "M 3.5 3 L 10.5 10 M 10.5 3 L 3.5 10"; }
|
|
26
|
+
if name == "failed" { return "M 5 1.3 L 9.1 8.7 H 0.9 Z M 5 4.1 V 5.8 M 5 7.3 V 7.35"; }
|
|
27
|
+
if name == "move-top" { return "M 2.5 1.75 H 11.5 M 3 9.75 L 7 5.75 L 11 9.75"; }
|
|
28
|
+
if name == "move-up" { return "M 3 8 L 7 4 L 11 8"; }
|
|
29
|
+
if name == "move-down" { return "M 3 4 L 7 8 L 11 4"; }
|
|
30
|
+
if name == "move-bottom" { return "M 3 2.25 L 7 6.25 L 11 2.25 M 2.5 10.25 H 11.5"; }
|
|
31
|
+
if name == "search" { return "M 9 5.25 A 3.75 3.75 0 1 1 1.5 5.25 A 3.75 3.75 0 1 1 9 5.25 Z M 8 8 L 11.25 10.75"; }
|
|
32
|
+
if name == "chapter" { return "M 2.5 11 V 1.2 M 2.5 1.6 H 9.6 L 7.9 4.1 L 9.6 6.6 H 2.5"; }
|
|
33
|
+
if name == "bookmark" { return "M 3.2 1.2 H 8.8 V 10.6 L 6 8.3 L 3.2 10.6 Z"; }
|
|
34
|
+
if name == "check-task" { return "M 2.75 5.75 L 4.75 7.75 L 8.25 3.25"; }
|
|
35
|
+
if name == "check-box" { return "M 1.5 4.75 L 3.6 6.75 L 7.5 2.25"; }
|
|
36
|
+
if name == "menu-check" { return "M 4 7.25 L 6.25 9.5 L 10 4.75"; }
|
|
37
|
+
if name == "submenu" { return "M 1.25 1.25 L 3.75 4 L 1.25 6.75"; }
|
|
38
|
+
if name == "scroll-up" { return "M 1.25 3.75 L 4 1.25 L 6.75 3.75"; }
|
|
39
|
+
if name == "scroll-down" { return "M 1.25 1.25 L 4 3.75 L 6.75 1.25"; }
|
|
40
|
+
if name == "caret-down" { return "M 1.25 1.25 L 4 3.75 L 6.75 1.25"; }
|
|
41
|
+
if name == "sort-asc" { return "M 1 3.25 L 3.5 0.75 L 6 3.25"; }
|
|
42
|
+
if name == "sort-desc" { return "M 1 0.75 L 3.5 3.25 L 6 0.75"; }
|
|
43
|
+
if name == "tag-remove" { return "M 1.5 1.5 L 5.5 5.5 M 5.5 1.5 L 1.5 5.5"; }
|
|
44
|
+
if name == "preview-play" { return "M 1.5 1.3 L 6.9 4.5 L 1.5 7.7 Z"; }
|
|
45
|
+
if name == "add" { return "M 5 1.5 V 8.5 M 1.5 5 H 8.5"; }
|
|
46
|
+
if name == "remove" { return "M 1.5 5 H 8.5"; }
|
|
47
|
+
if name == "sort" { return "M 3 1.5 V 8.5 M 1.2 6.7 L 3 8.5 L 4.8 6.7 M 7 8.5 V 1.5 M 5.2 3.3 L 7 1.5 L 8.8 3.3"; }
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
// Line width.
|
|
51
|
+
public pure function line(name: string) -> length {
|
|
52
|
+
if name == "failed" || name == "chapter" || name == "bookmark" || name == "submenu" || name == "scroll-up" || name == "scroll-down" || name == "caret-down" || name == "tag-remove" || name == "preview-play" { return 1.25px; }
|
|
53
|
+
if name == "sort-asc" || name == "sort-desc" { return 1.1px; }
|
|
54
|
+
return 1.5px;
|
|
55
|
+
}
|
|
56
|
+
// The box the commands are drawn in.
|
|
57
|
+
public pure function box-width(name: string) -> length {
|
|
58
|
+
if name == "play" || name == "pause" || name == "stop" || name == "chapter" || name == "bookmark" { return 12px; }
|
|
59
|
+
if name == "failed" || name == "add" || name == "remove" || name == "sort" { return 10px; }
|
|
60
|
+
if name == "scroll-up" || name == "scroll-down" || name == "caret-down" || name == "preview-play" { return 8px; }
|
|
61
|
+
if name == "volume" || name == "mute" || name == "playlist" { return 13px; }
|
|
62
|
+
if name == "sort-asc" || name == "sort-desc" || name == "tag-remove" { return 7px; }
|
|
63
|
+
if name == "check-task" { return 11px; }
|
|
64
|
+
if name == "check-box" { return 9px; }
|
|
65
|
+
if name == "submenu" { return 5px; }
|
|
66
|
+
return 14px;
|
|
67
|
+
}
|
|
68
|
+
public pure function box-height(name: string) -> length {
|
|
69
|
+
if name == "back" || name == "forward" || name == "volume" || name == "mute" || name == "playlist" || name == "failed" || name == "add" || name == "remove" || name == "sort" { return 10px; }
|
|
70
|
+
if name == "play" || name == "pause" || name == "menu-check" { return 14px; }
|
|
71
|
+
if name == "scroll-up" || name == "scroll-down" || name == "caret-down" { return 5px; }
|
|
72
|
+
if name == "check-box" || name == "preview-play" { return 9px; }
|
|
73
|
+
if name == "sort-asc" || name == "sort-desc" { return 4px; }
|
|
74
|
+
if name == "close" { return 13px; }
|
|
75
|
+
if name == "check-task" { return 11px; }
|
|
76
|
+
if name == "submenu" { return 8px; }
|
|
77
|
+
if name == "tag-remove" { return 7px; }
|
|
78
|
+
return 12px;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// One icon, stroked in `color`; scaled when the caller sets another size.
|
|
83
|
+
export component Icon {
|
|
84
|
+
in property <string> name;
|
|
85
|
+
in property <brush> color: Theme.text;
|
|
86
|
+
width: Icons.box-width(root.name);
|
|
87
|
+
height: Icons.box-height(root.name);
|
|
88
|
+
Path {
|
|
89
|
+
width: 100%;
|
|
90
|
+
height: 100%;
|
|
91
|
+
viewbox-width: Icons.box-width(root.name) / 1px;
|
|
92
|
+
viewbox-height: Icons.box-height(root.name) / 1px;
|
|
93
|
+
commands: Icons.commands(root.name);
|
|
94
|
+
stroke: root.color;
|
|
95
|
+
stroke-width: Icons.line(root.name);
|
|
96
|
+
stroke-line-cap: round;
|
|
97
|
+
stroke-line-join: round;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// open (usually `plugin.system.openUrl`).
|
|
7
7
|
import { ScrollView } from "std-widgets.slint";
|
|
8
8
|
import { Theme } from "theme.slint";
|
|
9
|
+
import { Icon } from "icons.slint";
|
|
9
10
|
|
|
10
11
|
// One table row (cells are plain text).
|
|
11
12
|
export struct MarkdownRow {
|
|
@@ -86,18 +87,15 @@ component MarkdownBlockView {
|
|
|
86
87
|
y: 3px;
|
|
87
88
|
width: 11px;
|
|
88
89
|
height: 11px;
|
|
89
|
-
border-width: 1px;
|
|
90
|
-
border-radius:
|
|
90
|
+
border-width: block.task == 2 ? 0px : 1px;
|
|
91
|
+
border-radius: 3px;
|
|
91
92
|
border-color: Theme.text-dim;
|
|
92
93
|
background: block.task == 2 ? Theme.accent : transparent;
|
|
93
|
-
if block.task == 2:
|
|
94
|
+
if block.task == 2: Icon {
|
|
94
95
|
width: 100%;
|
|
95
96
|
height: 100%;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
commands: "M 2.5 5.5 L 4.5 7.5 L 8.5 3";
|
|
99
|
-
stroke: Theme.text;
|
|
100
|
-
stroke-width: 1.5px;
|
|
97
|
+
name: "check-task";
|
|
98
|
+
color: #ffffff;
|
|
101
99
|
}
|
|
102
100
|
}
|
|
103
101
|
}
|
package/slint/vestra/theme.slint
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Vestra theme tokens (doc/product/ui-spec.md §2.1–2.3). Import from any
|
|
2
2
|
// plugin or host Slint file: import { Theme } from "@vestra/theme.slint";
|
|
3
|
-
// Colours follow the player's palette (R-H6:
|
|
4
|
-
// view through ../palette.slint, which is not
|
|
3
|
+
// Colours follow the player's palette (R-H6: `vestra`, the dark default, or
|
|
4
|
+
// `vestra-light`, set on every view through ../palette.slint, which is not
|
|
5
|
+
// part of this library). They are
|
|
5
6
|
// `out` properties: Slint code reads them but cannot assign them. So is
|
|
6
7
|
// font-mono (set per system). Corner radii, font sizes and control sizes
|
|
7
8
|
// stay constants.
|
|
@@ -9,8 +10,10 @@
|
|
|
9
10
|
import { VestraPalette } from "../palette.slint";
|
|
10
11
|
|
|
11
12
|
export global Theme {
|
|
12
|
-
// Colours:
|
|
13
|
-
//
|
|
13
|
+
// Colours: the Aurora palette drawn from the logo's full spectrum (magenta
|
|
14
|
+
// accent, cyan for what is playing, amber → magenta → violet → cyan fills;
|
|
15
|
+
// violet-tinted near-black, or near-white in vestra-light, whose video area
|
|
16
|
+
// stays dark). Sizes below are the player's control geometry
|
|
14
17
|
// (doc/product/ui-spec.md §2.3), the same whatever the palette.
|
|
15
18
|
out property <color> bg-bar: VestraPalette.bg-bar;
|
|
16
19
|
out property <color> bg-video: VestraPalette.bg-video;
|
|
@@ -35,11 +38,12 @@ export global Theme {
|
|
|
35
38
|
out property <color> button-disabled: VestraPalette.button-disabled;
|
|
36
39
|
// Errors and destructive actions: a failed status line, a Delete button.
|
|
37
40
|
out property <color> danger: VestraPalette.danger;
|
|
38
|
-
// Brand gradient for "progress" fills (seek bar, volume, sliders).
|
|
41
|
+
// Brand gradient for "progress" fills (seek bar, volume, sliders, accents).
|
|
39
42
|
out property <brush> accent-fill: VestraPalette.accent-fill;
|
|
40
|
-
// Soft corners echo the logo
|
|
41
|
-
|
|
42
|
-
out property <length> radius-
|
|
43
|
+
// Soft corners echo the logo: controls, menu rows and hover plates use
|
|
44
|
+
// radius-control; menus, panels and the OSD radius-panel.
|
|
45
|
+
out property <length> radius-control: 4px;
|
|
46
|
+
out property <length> radius-panel: 8px;
|
|
43
47
|
// §2.2 fonts. No default family: every window uses the system UI font,
|
|
44
48
|
// and Slint falls back per character for scripts a font lacks (CJK in
|
|
45
49
|
// font-mono included). font-mono is the system's own monospace family,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Compact controls built on the Vestra theme (core-ui-design.md §2.1):
|
|
2
2
|
// import { Button, Slider, ToolTipArea, UrlInputRow } from "@vestra/widgets.slint";
|
|
3
|
-
// Other controls come from std-widgets, which the
|
|
3
|
+
// Icons: @vestra/icons.slint. Other controls come from std-widgets, which the
|
|
4
|
+
// host compiles as fluent-dark and switches to light for a light palette.
|
|
4
5
|
import { TextEdit } from "std-widgets.slint";
|
|
5
6
|
import { Theme } from "theme.slint";
|
|
6
7
|
|
|
@@ -49,6 +50,7 @@ export component ToolTipArea {
|
|
|
49
50
|
background: Theme.bg-panel;
|
|
50
51
|
border-color: Theme.line;
|
|
51
52
|
border-width: 1px;
|
|
53
|
+
border-radius: Theme.radius-control;
|
|
52
54
|
HorizontalLayout {
|
|
53
55
|
padding-left: 5px; padding-right: 5px; padding-top: 2px; padding-bottom: 2px;
|
|
54
56
|
Text { text: root.text; color: Theme.text; font-size: Theme.font-body; }
|
|
@@ -59,7 +61,8 @@ export component ToolTipArea {
|
|
|
59
61
|
|
|
60
62
|
// Dialog button: text (21px, or 18px when `small`), or an icon button when
|
|
61
63
|
// `text` is empty and `icon` holds Path data drawn in an icon-width × icon-height
|
|
62
|
-
// viewbox (filled, or 1.5px lines when `stroked
|
|
64
|
+
// viewbox (filled, or 1.5px round-capped lines when `stroked`, as the
|
|
65
|
+
// @vestra/icons.slint set). The caller sizes icon buttons.
|
|
63
66
|
export component Button {
|
|
64
67
|
in property <string> text;
|
|
65
68
|
in property <string> icon;
|
|
@@ -112,6 +115,8 @@ export component Button {
|
|
|
112
115
|
fill: root.stroked ? transparent : root.tint;
|
|
113
116
|
stroke: root.stroked ? root.tint : transparent;
|
|
114
117
|
stroke-width: root.stroked ? 1.5px : 0px;
|
|
118
|
+
stroke-line-cap: round;
|
|
119
|
+
stroke-line-join: round;
|
|
115
120
|
}
|
|
116
121
|
touch := TouchArea {
|
|
117
122
|
enabled: root.enabled;
|
|
@@ -119,8 +124,9 @@ export component Button {
|
|
|
119
124
|
}
|
|
120
125
|
}
|
|
121
126
|
|
|
122
|
-
// 2px line slider. Bar style (default): 8px round thumb,
|
|
123
|
-
// Panel style (`panel: true`): 14×12 thumb,
|
|
127
|
+
// 2px line slider. Bar style (default): 8px round thumb, a white core in a
|
|
128
|
+
// 2px accent ring, gradient fill. Panel style (`panel: true`): 14×12 thumb,
|
|
129
|
+
// gradient fill. The track is `Theme.line` in both.
|
|
124
130
|
export component Slider {
|
|
125
131
|
in-out property <float> value;
|
|
126
132
|
in property <float> minimum: 0;
|
|
@@ -146,7 +152,8 @@ export component Slider {
|
|
|
146
152
|
Rectangle {
|
|
147
153
|
y: (parent.height - 2px) / 2;
|
|
148
154
|
height: 2px;
|
|
149
|
-
|
|
155
|
+
border-radius: 1px;
|
|
156
|
+
background: Theme.line;
|
|
150
157
|
}
|
|
151
158
|
Rectangle {
|
|
152
159
|
y: (parent.height - 2px) / 2;
|
|
@@ -162,10 +169,9 @@ export component Slider {
|
|
|
162
169
|
width: root.thumb-width;
|
|
163
170
|
height: root.thumb-height;
|
|
164
171
|
border-radius: root.panel ? Theme.radius-control : self.height / 2;
|
|
165
|
-
background: !root.enabled ? Theme.icon-disabled
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
: touch.has-hover ? Theme.accent : Theme.text;
|
|
172
|
+
background: !root.enabled ? Theme.icon-disabled : root.panel ? Theme.text : #ffffff;
|
|
173
|
+
border-width: root.panel || !root.enabled ? 0px : 2px;
|
|
174
|
+
border-color: touch.pressed ? Theme.accent-press : Theme.accent;
|
|
169
175
|
}
|
|
170
176
|
touch := TouchArea {
|
|
171
177
|
enabled: root.enabled;
|