@vestra-player/sdk 1.5.27 → 1.5.29

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.
@@ -386,6 +386,77 @@ export type Bindable<T> = T | (() => T);
386
386
  * `MENU_IDS` in `@vestra-player/sdk/testing`.
387
387
  */
388
388
  export type MenuId = "open" | "playback" | "subtitles" | "video" | "audio" | "screen" | "settings";
389
+ /**
390
+ * An icon of the player's V-Line set (`@vestra/icons.slint`), by name: what
391
+ * a bottom-bar button may show. The list is `BAR_BUTTON_ICONS` in
392
+ * `@vestra-player/sdk/testing`.
393
+ *
394
+ * @since 1.5.29
395
+ */
396
+ export type BarButtonIcon = "play" | "pause" | "stop" | "back" | "forward" | "open" | "volume" | "mute" | "playlist" | "detach" | "attach" | "close" | "failed" | "move-top" | "move-up" | "move-down" | "move-bottom" | "search" | "chapter" | "bookmark" | "check-task" | "check-box" | "menu-check" | "submenu" | "scroll-up" | "scroll-down" | "caret-down" | "sort-asc" | "sort-desc" | "tag-remove" | "preview-play" | "add" | "remove" | "sort" | "fullscreen" | "exit-fullscreen" | "store" | "ai";
397
+ /**
398
+ * A command's button on the bottom bar: {@link CommandOptions.button}.
399
+ *
400
+ * @remarks
401
+ * **Where it shows.** The bottom bar places the player's strip of command
402
+ * buttons, `BarButtons` from `@vestra/bar-buttons.slint`, at its right end;
403
+ * the player fills it with the buttons of every plugin, left to right by
404
+ * `order`, and its own sidebar toggle rightmost. When the window is narrow
405
+ * the strip loses cells from its outer (left) end inward: the lowest order
406
+ * goes first, the sidebar toggle last. A
407
+ * bottom bar that does not place `BarButtons` shows no buttons: the
408
+ * command's menu entry stays, and the player logs the buttons it cannot
409
+ * show and whose bar hides them. The built-in bar (`vestra.playback`) places
410
+ * it; see
411
+ * [Writing a bottom bar](../../docs/guidelines/interface.md#writing-a-bottom-bar).
412
+ *
413
+ * **What it shows.** The icon, the command's title (translated, a trailing
414
+ * "…" removed) and its first key as the tooltip ("Plugin Store (F9)"),
415
+ * `enabled`, `busy` and `reason` as the menu item has them: a disabled or
416
+ * busy command's button is drawn disabled, its tooltip adds the reason, and
417
+ * a click runs nothing. A command with `checked` shows its icon in the
418
+ * accent colour while checked. A click runs the command exactly like a
419
+ * click on its menu item.
420
+ *
421
+ * **Limits.** One button per plugin and eight in the player
422
+ * (`BAR_BUTTON_LIMITS`): a button past them is left out (its command and
423
+ * menu entry stay) and the player logs it with `limit`. The button goes
424
+ * with its command: disposing the command, or disabling, stopping or
425
+ * uninstalling the plugin, removes it.
426
+ *
427
+ * @example
428
+ * ```ts
429
+ * const store = plugin.windows.create("ui/store.slint");
430
+ * plugin.commands.add({
431
+ * id: "open",
432
+ * path: ["Plugin Store…"],
433
+ * button: { icon: "store", order: 200 },
434
+ * run: () => store.show(),
435
+ * });
436
+ * ```
437
+ *
438
+ * @player 0.5.10; older players show the menu entry without a button
439
+ * (declare `"minPlayerVersion": "0.5.10"` when the button matters).
440
+ * @since 1.5.29
441
+ */
442
+ export interface CommandButton {
443
+ /**
444
+ * A name of the player's icon set ({@link BarButtonIcon}), or an image
445
+ * made with {@link Ui.image} (a PNG or JPEG in your package or data
446
+ * directory), drawn in the 14×12 icon box, aspect kept, coloured like the
447
+ * player's line icons: only its alpha counts, so draw it as a dark shape
448
+ * on a transparent background. An unknown name fails with `invalid`.
449
+ */
450
+ icon: BarButtonIcon | ImageRef;
451
+ /**
452
+ * Place in the strip, left to right ascending: an integer 0–999. It is
453
+ * position only: a lower order sits further left, and in a narrow window
454
+ * (or past the limit of eight) it is the first to go. Equal orders sort
455
+ * by plugin id, then declaration order. The player's own buttons use AI
456
+ * 100, Plugin Store 200.
457
+ */
458
+ order: number;
459
+ }
389
460
  /**
390
461
  * The declaration passed to {@link Commands.add}.
391
462
  *
@@ -500,6 +571,16 @@ export interface CommandOptions {
500
571
  focus?: "panel";
501
572
  /** Fire again on keyboard auto-repeat while the key is held. Default false. */
502
573
  repeat?: boolean;
574
+ /**
575
+ * A button for this command on the bottom bar (SDK 1.5.29, player 0.5.10):
576
+ * an icon and a place, `{ icon: "store", order: 200 }`. Needs a `path`: the
577
+ * menu entry is where the command stays reachable when the bar shows no
578
+ * button (a bar without `BarButtons`, a narrow window, a limit). See
579
+ * {@link CommandButton} for where it shows, what it shows and the limits.
580
+ * Fails with `invalid` for an unknown icon name, an order outside 0–999 or
581
+ * a button without a path.
582
+ */
583
+ button?: CommandButton;
503
584
  /** Whether the command can run (default true). */
504
585
  enabled?: Bindable<boolean>;
505
586
  /** Check mark (or radio dot with `radio`); `null` (default) draws none. */
package/llms-full.txt CHANGED
@@ -1,4 +1,4 @@
1
- # @vestra-player/sdk 1.5.27: full reference
1
+ # @vestra-player/sdk 1.5.29: full reference
2
2
 
3
3
  > Everything needed to write a Vestra Player plugin (plugin API `vestra/1`): the guides, every public declaration of `@vestra-player/sdk` and `@vestra-player/sdk/testing` with its documentation, and the complete source of the example plugins. Generated from the SDK's sources by scripts/llms.mjs; the short index is llms.txt.
4
4
 
@@ -498,11 +498,25 @@ The user can rebind any command, the player's own included. Those **overrides**
498
498
  - `list()` returns every declared command with its default and current inputs, which of them another command shadows, and the overrides whose command does not exist right now (its plugin is stopped); those are kept and apply again when it returns.
499
499
  - To find a command by its place in the menu, compare BindingInfo.key (the English menu keys, the same in every language), not `path`, which is the translated text.
500
500
 
501
+ #### Bottom-bar buttons
502
+
503
+ A command can also be a button on the bottom bar: give it `button: { icon, order }` (CommandButton, player 0.5.10) besides its menu `path`. The player shows the buttons of all plugins in one strip at the bar's right end, left to right by `order`, its own **Toggle Sidebar** rightmost; in a narrow window the lowest order (leftmost) goes first and the toggle last; a click runs the command like its menu item, the tooltip is the item's title and first key, and a disabled or busy command's button is drawn disabled with the reason in its tooltip. One button per plugin and eight in the player (`BAR_BUTTON_LIMITS`). The strip lives in the bottom bar's own layout (`BarButtons`, [Writing a bottom bar): a bar that does not place it shows no buttons, which is why a button always has a menu entry too.
504
+
505
+ ```ts
506
+ const store = plugin.windows.create("ui/store.slint");
507
+ plugin.commands.add({
508
+ id: "open",
509
+ path: ["Plugin Store…"],
510
+ button: { icon: "store", order: 200 },
511
+ run: () => store.show(),
512
+ });
513
+ ```
514
+
501
515
  #### The player's own commands
502
516
 
503
- The player registers some commands itself, with the owner `host`: **Toggle Fullscreen**, **Leave Fullscreen**, **Quit** and the core playback actions (play/pause, stop, previous/next, frame step, relative seek, speed, volume, mute). They exist whether or not any plugin runs, and the user rebinds them like any other command.
517
+ The player registers some commands itself, with the owner `host`: **Toggle Fullscreen**, **Leave Fullscreen**, **Quit**, **Toggle Sidebar** (the bottom bar's sidebar button, player 0.5.10) and the core playback actions (play/pause, stop, previous/next, frame step, relative seek, speed, volume, mute). They exist whether or not any plugin runs, and the user rebinds them like any other command.
504
518
 
505
- A plugin button that does what a core action does should run that action with plugin.host.run (capability `commands`): it applies the same checks and shows the same on-screen text as the menu item and the key, so a bottom bar's play button and the Space key behave identically. The action ids, their menu places and default keys are in [Reference: commands; what they do to playback is in Playback.
519
+ A plugin button that does what a core action does should run that action with plugin.host.run (capability `commands`): it applies the same checks and shows the same on-screen text as the menu item and the key, so a bottom bar's play button and the Space key behave identically. The action ids, their menu places and default keys are in Reference: commands; what they do to playback is in Playback.
506
520
 
507
521
  ## Concept: Playback state, controls and sources
508
522
 
@@ -779,7 +793,7 @@ It assumes you have written a first plugin (Getting started). For every method's
779
793
  Two rules of thumb:
780
794
 
781
795
  - **Prefer the smallest surface that works.** An OSD message before a dialog, a settings page before a tool window. Every window the user has to close is a cost.
782
- - **Panels are shared space.** There is one sidebar, one bottom bar and one backdrop for all plugins. The sidebar holds several panels as tabs (the player draws the tab strip while there are two or more; showing, hiding and detaching apply to all of them). The bottom bar and the backdrop hold one: while one plugin's panel is there, another plugin's mount rejects with `conflict`, which does not fail its activation. Mount a panel only when the plugin's main purpose is to live there.
796
+ - **Panels are shared space.** There is one sidebar, one bottom bar and one backdrop for all plugins. The sidebar holds several panels as tabs (the player draws the tab strip while there are two or more; showing, hiding and detaching apply to all of them). The bottom bar and the backdrop hold one: while one plugin's panel is there, another plugin's mount rejects with `conflict`, which does not fail its activation. Mount a panel only when the plugin's main purpose is to live there. A command that needs a one-click entry point does not need a panel: give it a bottom-bar button (`button` on commands.add), which the player shows in the strip of command buttons every bottom bar places (Writing a bottom bar).
783
797
 
784
798
  #### Life cycles
785
799
 
@@ -2081,6 +2095,29 @@ The player's picture, audio and subtitle tools share one layout; a window of the
2081
2095
  - **Commands of a panel go into its right-click menu**, made of the plugin's commands (panel.showMenu); see Menus.
2082
2096
  - **The bottom bar is icons only**: no text buttons, no source path, no status text (the source name is in the window title, status is an OSD message).
2083
2097
 
2098
+ ##### Writing a bottom bar
2099
+
2100
+ The bottom bar holds one panel, and every plugin's command buttons (CommandOptions.button) and the player's sidebar toggle show inside it, in a strip the player fills. A bar that replaces the built-in one (`vestra.playback`) must place that strip, or those buttons show nowhere while it is mounted (their menu entries stay):
2101
+
2102
+ - **Place `BarButtons`** from `@vestra/bar-buttons.slint` at the right end of the button row, and give it `room`: the width left after your own controls. It shows as many 41×40 cells as fit and loses them from its outer (left) end inward: of the plugins' buttons the lowest order goes first, and the sidebar toggle goes last. The strip is as wide as the cells it shows, so lay your controls out against its `width`. Do not put it under an `if`: it must exist for the player to see it placed. `first-shown` (out) is the index of the leftmost plugin button shown, if your layout needs it.
2103
+ - **Decide what goes first when the window narrows.** The built-in bar gives the strip only the room its own buttons and the time leave, so the strip shrinks first, then its Fullscreen button hides, and the time is never cut.
2104
+ - **Do not draw a sidebar toggle of your own**: the strip has the player's (**Toggle Sidebar**, `host/sidebar`), with its state.
2105
+ - `vestra-plugin check` warns when a plugin mounts a bottom bar whose Slint file does not import `BarButtons` from `@vestra/bar-buttons.slint`, itself or through the plugin's own Slint files it imports, and the player logs the buttons a mounted bar hides.
2106
+
2107
+ ```slint
2108
+ import { BarButtons } from "@vestra/bar-buttons.slint";
2109
+
2110
+ export component MyBar inherits Rectangle {
2111
+ height: 40px;
2112
+ // Your controls on the left, 3 × 41px wide …
2113
+ strip := BarButtons {
2114
+ x: root.width - self.width - 4px;
2115
+ y: 0px;
2116
+ room: max(0px, root.width - 3 * 41px - 8px);
2117
+ }
2118
+ }
2119
+ ```
2120
+
2084
2121
  #### Text
2085
2122
 
2086
2123
  - **Every visible text is English source text** that the player translates (Translate a plugin). Write short, plain sentences; one sentence per status or error.
@@ -2102,7 +2139,7 @@ How paths merge, how `order` and `group` sort items and how a key finds its comm
2102
2139
 
2103
2140
  #### Organize by what the user does
2104
2141
 
2105
- - **Put a command where its subject is, not under your plugin's name.** A video filter goes into Video, a subtitle tool into Subtitles, an open-something command into Open. A plugin that brings a domain of its own gets a submenu named after the domain, not after the plugin (the NES plugin's Emulator).
2142
+ - **Put a command where its subject is, not under your plugin's name.** A video filter goes into Video, a subtitle tool into Subtitles, an open-something command into Open. A plugin that brings a domain of its own gets a submenu named after the domain, not after the plugin (Games for an emulator, not the emulator's name).
2106
2143
  - **Before adding an item, look for an existing one.** Every command the player and its built-in plugins add, with its menu path and default key, is listed in Built-in commands. Do not add a second entry for something the player already does, and do not scatter one feature over several menus.
2107
2144
  - **Top level is for frequent, direct commands**: opening files, closing, fullscreen and the entries that show a window or panel. Everything else goes into a submenu of its domain.
2108
2145
  - **No "File" or "View" menu.** Commands that open something go into Open ▸; commands that show a window or panel sit directly at the top level.
@@ -2122,7 +2159,6 @@ Playback ▸ group domain
2122
2159
  Subtitles ▸
2123
2160
  Video ▸
2124
2161
  Audio ▸
2125
- Emulator ▸
2126
2162
  ────────────────────────────────────────
2127
2163
  Toggle Fullscreen Enter group screen
2128
2164
  Screen ▸
@@ -2254,6 +2290,7 @@ The table lists the API whose documentation names a player version, newest last:
2254
2290
  | 0.5.2 | HostAction |
2255
2291
  | 0.5.3 | Scrub.seek |
2256
2292
  | 0.5.5 | SessionStorage |
2293
+ | 0.5.10 | CommandButton, CommandOptions.button |
2257
2294
 
2258
2295
  The Slint library carries no version tags: `Theme.danger` needs player 0.3.0; `@vestra/icons.slint` and the palette `vestra-light` need 0.4.0 (Theme).
2259
2296
 
@@ -2331,6 +2368,16 @@ A command added past a limit fails with code `limit` (its `ready` rejects; durin
2331
2368
  | `COMMAND_LIMITS.perPlugin` | 160 | Commands one plugin instance may have at the same time. |
2332
2369
  | `COMMAND_LIMITS.total` | 1024 | Commands of every plugin plus the player's own, together. |
2333
2370
 
2371
+ #### Bottom-bar buttons
2372
+
2373
+ A command's button (CommandOptions.button, player 0.5.10) past a limit is left out, not refused: the command registers and its menu entry stays, and the player logs the button with `limit`. Each plugin keeps its first declared button; of those, the player keeps the eight highest orders, so the lowest go first, as in a narrow strip (equal orders: the smaller plugin id, then the earlier declaration, goes first). The player's own sidebar toggle is extra.
2374
+
2375
+ | Limit | Value | Meaning |
2376
+ | --- | --- | --- |
2377
+ | `BAR_BUTTON_LIMITS.perPlugin` | 1 | Buttons one plugin shows. |
2378
+ | `BAR_BUTTON_LIMITS.total` | 8 | Buttons the player shows, every plugin together, besides its own sidebar toggle. |
2379
+ | `BAR_BUTTON_LIMITS.maxOrder` | 999 | The highest `order`. |
2380
+
2334
2381
  #### Property values
2335
2382
 
2336
2383
  One value written to a window, panel or settings page property (View.set, View.bind or an initial property). A value over a limit is refused whole with code `limit`; the property keeps what it showed.
@@ -2491,7 +2538,6 @@ The submenus that exist already, by key path. Write the same keys to add a comma
2491
2538
  | Settings ▸ Appearance | `vestra.preferences`, `vestra.visualizer` |
2492
2539
  | Settings ▸ Language | `vestra.preferences` |
2493
2540
  | Copy to Clipboard | `vestra.playback` |
2494
- | Emulator | `vestra.nes` |
2495
2541
 
2496
2542
  #### Built-in commands
2497
2543
 
@@ -2600,8 +2646,8 @@ Every command the player (`host`) and its built-in plugins register when the pla
2600
2646
  | Subtitles ▸ Subtitle Style ▸ Bold | `Alt+B` | `vestra.subtitles/bold` |
2601
2647
  | Subtitles ▸ Subtitle Style ▸ Size + | `Alt+PageUp` | `vestra.subtitles/size-up` |
2602
2648
  | Subtitles ▸ Subtitle Style ▸ Size − | `Alt+PageDown` | `vestra.subtitles/size-down` |
2603
- | Subtitles ▸ Subtitle Style ▸ Move Up | `Alt+ArrowUp` | `vestra.subtitles/move-up` |
2604
- | Subtitles ▸ Subtitle Style ▸ Move Down | `Alt+ArrowDown` | `vestra.subtitles/move-down` |
2649
+ | Subtitles ▸ Subtitle Style ▸ Move Up | | `vestra.subtitles/move-up` |
2650
+ | Subtitles ▸ Subtitle Style ▸ Move Down | | `vestra.subtitles/move-down` |
2605
2651
  | Subtitles ▸ Subtitle Style ▸ Reset Position | `Alt+Home` | `vestra.subtitles/move-reset` |
2606
2652
  | Subtitles ▸ Subtitle Style ▸ Shadow | | `vestra.subtitles/shadow` |
2607
2653
  | Subtitles ▸ Subtitle Style ▸ Opaque Box | | `vestra.subtitles/box` |
@@ -2741,9 +2787,6 @@ Every command the player (`host`) and its built-in plugins register when the pla
2741
2787
  | Audio ▸ Processing ▸ Crystalizer | `Shift+C` | `vestra.audio/audio.clarity` |
2742
2788
  | Audio ▸ Reset | | `vestra.audio/audio.reset` |
2743
2789
  | Audio ▸ Show Album Cover for Audio Only | | `vestra.visualizer/audio-cover` |
2744
- | Emulator ▸ Open NES ROM… | | `vestra.nes/nes.open` |
2745
- | Emulator ▸ Stop NES | | `vestra.nes/nes.stop` |
2746
- | Emulator ▸ NES Tools… | | `vestra.nes/nes.window` |
2747
2790
  | Toggle Fullscreen | `Enter`, `Alt+Enter`, `F11` | `host/fullscreen` |
2748
2791
  | Screen ▸ Picture-in-Picture | | `vestra.window/pip` |
2749
2792
  | Screen ▸ Mini Size | ``` | `vestra.window/mini` |
@@ -2789,6 +2832,7 @@ Every command the player (`host`) and its built-in plugins register when the pla
2789
2832
  | About Vestra Player… | `F1` | `vestra.info/info.about` |
2790
2833
  | Quit | `Cmd+Q` | `host/quit` |
2791
2834
  | Leave Fullscreen (not in the menu) | `Escape` | `host/exit-fullscreen` |
2835
+ | Toggle Sidebar (not in the menu) | | `host/sidebar` |
2792
2836
  | Brief Info (not in the menu) | `Shift+Tab` | `vestra.info/info.brief` |
2793
2837
  | Play (not in the menu) | `Enter`, `P` (panel focus) | `vestra.playlist/playlist.play` |
2794
2838
  | Add Files… (not in the menu) | `Ctrl+I` (panel focus) | `vestra.playlist/playlist.list-add` |
@@ -2822,6 +2866,23 @@ Every command the player (`host`) and its built-in plugins register when the pla
2822
2866
  | Close List (not in the menu) | `F6` (panel focus) | `vestra.playlist/playlist.close` |
2823
2867
  | Playlist Settings… (not in the menu) | `F5` (panel focus) | `vestra.playlist/playlist.settings` |
2824
2868
 
2869
+ #### Bottom-bar buttons
2870
+
2871
+ `button: { icon, order }` on commands.add (CommandButton, SDK 1.5.29, player 0.5.10) gives a command a button in the bottom bar's strip of command buttons (`BarButtons`, Writing a bottom bar).
2872
+
2873
+ | Field | Rule |
2874
+ | --- | --- |
2875
+ | `icon` | A name of the player's icon set (the names in Theme: Icons, `BAR_BUTTON_ICONS`), or an image from plugin.ui.image drawn in the 14×12 icon box in the icon colour (only its alpha counts). Unknown name: `invalid`. |
2876
+ | `order` | Integer 0–999, left to right ascending; equal orders by plugin id, then declaration order. The built-in plugins use 100 (Vestra Player AI) and 200 (Plugin Store). |
2877
+ | `path` | Required: a button without a menu path is `invalid`. |
2878
+
2879
+ - **Shown**: the icon; the tooltip is the command's shown title without a trailing "…" and its first active key ("Plugin Store (F9)"), plus the reason on a second line while the command is disabled or busy. Disabled or busy: drawn disabled, a click runs nothing. `checked: true`: the icon in the accent colour. Accessibility: a button named by the title, with its enabled and checked state and a default action.
2880
+ - **Click**: runs the command exactly as a click on its menu item does.
2881
+ - **Order and room**: left to right by `order`, the player's **Toggle Sidebar** rightmost; in a narrow window the strip loses cells from its outer (left) end inward: the lowest order goes first, the sidebar toggle last. `order` is position only: low is further left and goes first.
2882
+ - **Limits** (Limits): one button per plugin (its first declared), eight in the player (the highest orders; the lowest go first, ties: the smaller plugin id, then the earlier declaration). A button past them is left out and logged with `limit`; the command and its menu entry stay.
2883
+ - **Life cycle**: the button belongs to its command. Disposing the command, or disabling, stopping or uninstalling the plugin, removes it.
2884
+ - **Older players** (before 0.5.10) build the declaration with their own copy of the SDK, which does not send `button`: the command and its menu entry work, without a button. Declare `"minPlayerVersion": "0.5.10"` when the button matters.
2885
+
2825
2886
  #### Binding syntax
2826
2887
 
2827
2888
  ```text
@@ -2872,7 +2933,7 @@ The player's own playback commands, which plugin.host.run runs by id (capability
2872
2933
  | `mute` | Audio ▸ Mute | `M` |
2873
2934
  | `fullscreen` | Toggle Fullscreen | `Enter`, `Alt+Enter`, `F11` |
2874
2935
 
2875
- The player also owns **Toggle Fullscreen** (`host/fullscreen`), **Leave Fullscreen** (`host/exit-fullscreen`, `Escape`, only in fullscreen) and **Quit** (`host/quit`); their keys are in Built-in commands.
2936
+ The player also owns **Toggle Fullscreen** (`host/fullscreen`), **Leave Fullscreen** (`host/exit-fullscreen`, `Escape`, only in fullscreen) and **Quit** (`host/quit`), and **Toggle Sidebar** (`host/sidebar`, player 0.5.10: the bottom bar's sidebar button, outside the menu, no default key); their keys are in Built-in commands.
2876
2937
 
2877
2938
  #### Keys the system takes first
2878
2939
 
@@ -3047,6 +3108,8 @@ Icon { name: "close"; width: 20px; height: 20px; } // scaled
3047
3108
  | `sort` | 10 × 10 | 1.5px |
3048
3109
  | `fullscreen` | 14 × 12 | 1.5px |
3049
3110
  | `exit-fullscreen` | 14 × 12 | 1.5px |
3111
+ | `store` | 14 × 12 | 1.5px |
3112
+ | `ai` | 14 × 12 | 1.5px |
3050
3113
 
3051
3114
  #### Widgets
3052
3115
 
@@ -3128,6 +3191,19 @@ Struct **`MarkdownRow`**: One table row (cells are plain text).
3128
3191
 
3129
3192
  Struct **`MarkdownBlock`**: One displayed block. Plugins only pass these through; the fields are filled by the player.
3130
3193
 
3194
+ #### Bar buttons
3195
+
3196
+ `@vestra/bar-buttons.slint` (player 0.5.10) is the strip of command buttons a bottom bar places at its right end: the buttons plugins give their commands (CommandOptions.button) and the player's sidebar toggle. The player fills and runs it; the bar only places it and gives it `room`, the width it can spare. See Writing a bottom bar.
3197
+
3198
+ **`BarButtons`**: The strip: place it at the bar's right end and give it the width you can spare; it is as wide as the cells it shows (read `width` to lay out the rest).
3199
+
3200
+ | Member | Kind | Default | Note |
3201
+ | --- | --- | --- | --- |
3202
+ | `room` | in `length` | | The width the bar can give the strip; the cells that fit show. |
3203
+ | `first-shown` | out `int` | `BarButtonsHost.buttons.length - root.count` | The index (in the player's list, ascending order) of the leftmost plugin button shown; the ones before it are hidden for want of room. |
3204
+
3205
+ Struct **`BarButtonItem`**: One button as the player shows it.
3206
+
3131
3207
  ## Reference: Command line
3132
3208
 
3133
3209
  `vestra-plugin` comes with `@vestra-player/sdk` and works in any project that depends on it (`npx vestra-plugin …`, or an npm script). A plugin directory is a directory with `manifest.json`; the default is the current directory, and several can be given at once.
@@ -3499,6 +3575,35 @@ Playback, Subtitles, Video, Audio, Screen, Settings. The list is
3499
3575
  type MenuId = "open" | "playback" | "subtitles" | "video" | "audio" | "screen" | "settings";
3500
3576
  ```
3501
3577
 
3578
+ ### BarButtonIcon
3579
+
3580
+ An icon of the player's V-Line set (`@vestra/icons.slint`), by name: what
3581
+ a bottom-bar button may show. The list is `BAR_BUTTON_ICONS` in
3582
+ `@vestra-player/sdk/testing`.
3583
+
3584
+ ```ts
3585
+ type BarButtonIcon = "play" | "pause" | "stop" | "back" | "forward" | "open" | "volume" | "mute" | "playlist" | "detach" | "attach" | "close" | "failed" | "move-top" | "move-up" | "move-down" | "move-bottom" | "search" | "chapter" | "bookmark" | "check-task" | "check-box" | "menu-check" | "submenu" | "scroll-up" | "scroll-down" | "caret-down" | "sort-asc" | "sort-desc" | "tag-remove" | "preview-play" | "add" | "remove" | "sort" | "fullscreen" | "exit-fullscreen" | "store" | "ai";
3586
+ ```
3587
+
3588
+ ### CommandButton
3589
+
3590
+ A command's button on the bottom bar: CommandOptions.button.
3591
+
3592
+ - @player 0.5.10; older players show the menu entry without a button (declare `"minPlayerVersion": "0.5.10"` when the button matters).
3593
+
3594
+ ```ts
3595
+ interface CommandButton {
3596
+ /**
3597
+ * A name of the player's icon set (BarButtonIcon), or an image made with Ui.image (a PNG or JPEG in your package or data directory), drawn in the 14×12 icon box, aspect kept, coloured like the player's line icons: only its alpha counts, so draw it as a dark shape on a transparent background. An unknown name fails with `invalid`.
3598
+ */
3599
+ icon: BarButtonIcon | ImageRef;
3600
+ /**
3601
+ * Place in the strip, left to right ascending: an integer 0–999. It is position only: a lower order sits further left, and in a narrow window (or past the limit of eight) it is the first to go. Equal orders sort by plugin id, then declaration order. The player's own buttons use AI 100, Plugin Store 200.
3602
+ */
3603
+ order: number;
3604
+ }
3605
+ ```
3606
+
3502
3607
  ### CommandOptions
3503
3608
 
3504
3609
  The declaration passed to Commands.add.
@@ -3597,6 +3702,10 @@ interface CommandOptions {
3597
3702
  focus?: "panel";
3598
3703
  /** Fire again on keyboard auto-repeat while the key is held. Default false. */
3599
3704
  repeat?: boolean;
3705
+ /**
3706
+ * A button for this command on the bottom bar (SDK 1.5.29, player 0.5.10): an icon and a place, `{ icon: "store", order: 200 }`. Needs a `path`: the menu entry is where the command stays reachable when the bar shows no button (a bar without `BarButtons`, a narrow window, a limit). See CommandButton for where it shows, what it shows and the limits. Fails with `invalid` for an unknown icon name, an order outside 0–999 or a button without a path.
3707
+ */
3708
+ button?: CommandButton;
3600
3709
  /** Whether the command can run (default true). */
3601
3710
  enabled?: Bindable<boolean>;
3602
3711
  /** Check mark (or radio dot with `radio`); `null` (default) draws none. */
@@ -7016,6 +7125,34 @@ interface FakeCommand {
7016
7125
  }
7017
7126
  ```
7018
7127
 
7128
+ ### FakeBarButton
7129
+
7130
+ A command's bottom-bar button (`CommandOptions.button`) as the player shows
7131
+ it: an entry of TestHost.barButtons.
7132
+
7133
+ ```ts
7134
+ interface FakeBarButton {
7135
+ /** The command's local id. */
7136
+ readonly command: string;
7137
+ /** The icon name, or the image's path for a `plugin.ui.image` icon. */
7138
+ readonly icon: string;
7139
+ /** `true` when the icon is the plugin's image. */
7140
+ readonly image: boolean;
7141
+ /** The declared `order`. */
7142
+ readonly order: number;
7143
+ /** Clickable: the command is enabled and not busy. */
7144
+ readonly enabled: boolean;
7145
+ /** Drawn in the accent colour: the command is checked. */
7146
+ readonly checked: boolean;
7147
+ /** The tooltip without the key: the command's shown title (trailing "…" removed), then, while it cannot run, its reason on a second line. */
7148
+ readonly tooltip: string;
7149
+ /**
7150
+ * Click the button, then wait until the host is idle: runs the command as its menu item would. Rejects (with a HostError) while the button is disabled, and once the command is gone.
7151
+ */
7152
+ click(): Promise<void>;
7153
+ }
7154
+ ```
7155
+
7019
7156
  ### FakeProducer
7020
7157
 
7021
7158
  A generated stream (`plugin.player.openStream`) as the test host sees it:
@@ -7815,6 +7952,10 @@ class TestHost implements Transport {
7815
7952
  step(direction: "next" | "previous"): Promise<unknown>;
7816
7953
  /** The plugin's command with this local id. Throws when it is not registered (never added, or disposed). */
7817
7954
  command(id: string): FakeCommand;
7955
+ /**
7956
+ * The plugin's bottom-bar buttons (`CommandOptions.button`) as the player shows them: at most one, the first declared (`BAR_BUTTON_LIMITS`; a later one stays a menu item only). Empty once the command is disposed. Whether the bar places them is the bar's business: the list is what the player hands to `BarButtons`.
7957
+ */
7958
+ get barButtons(): readonly FakeBarButton[];
7818
7959
  /**
7819
7960
  * The plugin's tool window with this id (by default the file name without extension: `ui/picture.slint` → `picture`). Throws when it was never opened with `show()`.
7820
7961
  */
@@ -7996,6 +8137,39 @@ agrees with the player's.
7996
8137
  const MENU_IDS: readonly ["open", "playback", "subtitles", "video", "audio", "screen", "settings"];
7997
8138
  ```
7998
8139
 
8140
+ ### BAR_BUTTON_ICONS
8141
+
8142
+ The icons a bottom-bar button may name (`CommandOptions.button.icon`):
8143
+ every icon of the player's V-Line set, `@vestra/icons.slint` (draw one
8144
+ in your own Slint with `Icon { name: "store"; }`). The player's tests check
8145
+ that this list is its icon set.
8146
+
8147
+ ```ts
8148
+ const BAR_BUTTON_ICONS: readonly ["play", "pause", "stop", "back", "forward", "open", "volume", "mute", "playlist", "detach", "attach", "close", "failed", "move-top", "move-up", "move-down", "move-bottom", "search", "chapter", "bookmark", "check-task", "check-box", "menu-check", "submenu", "scroll-up", "scroll-down", "caret-down", "sort-asc", "sort-desc", "tag-remove", "preview-play", "add", "remove", "sort", "fullscreen", "exit-fullscreen", "store", "ai"];
8149
+ ```
8150
+
8151
+ ### BAR_BUTTON_LIMITS
8152
+
8153
+ The player's limits on bottom-bar buttons (`CommandOptions.button`), the
8154
+ same values as the player's (its tests check that they agree). A button
8155
+ past `perPlugin` or `total` is left out: its command still registers and
8156
+ its menu entry stays, and the player logs the button it left out
8157
+ (`limit`). Which ones stay: each plugin's first declared button, then the
8158
+ `total` highest by order, plugin id and declaration order: as in a narrow
8159
+ strip, the lowest order goes first (equal orders: the smaller plugin id,
8160
+ then the earlier declaration, goes first).
8161
+
8162
+ ```ts
8163
+ const BAR_BUTTON_LIMITS: {
8164
+ /** Buttons one plugin shows (1). */
8165
+ readonly perPlugin: 1;
8166
+ /** Buttons the player shows, every plugin together, besides its own sidebar toggle (8). */
8167
+ readonly total: 8;
8168
+ /** The highest `order` (999; the lowest is 0). */
8169
+ readonly maxOrder: 999;
8170
+ };
8171
+ ```
8172
+
7999
8173
  ### BuiltinSubmenu
8000
8174
 
8001
8175
  A submenu that one of the player's built-in plugins declares: an entry of
@@ -8688,6 +8862,13 @@ type CommandDeclaration = {
8688
8862
  focus: "panel" | null;
8689
8863
  /** Fire again on keyboard auto-repeat while held. */
8690
8864
  repeat: boolean;
8865
+ /**
8866
+ * A bottom-bar button (player 0.5.10): an icon name, or an image in the `plugin.ui.image` wire form. Sent only when set: older players refuse unknown declaration fields.
8867
+ */
8868
+ button?: {
8869
+ icon: string | Json;
8870
+ order: number;
8871
+ };
8691
8872
  };
8692
8873
  ```
8693
8874
 
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vestra Player SDK
2
2
 
3
- > @vestra-player/sdk 1.5.27: write plugins for Vestra Player in TypeScript (plugin API `vestra/1`). A plugin is a folder with `manifest.json` and an entry module whose default export `activate(plugin)` the player calls; it runs in its own Node.js process. Everything a plugin creates (commands, windows, panels, filters, listeners…) is a handle that is released automatically when the plugin stops. L<n> are line numbers in llms-full.txt, next to this file: every page, declaration and example.
3
+ > @vestra-player/sdk 1.5.29: write plugins for Vestra Player in TypeScript (plugin API `vestra/1`). A plugin is a folder with `manifest.json` and an entry module whose default export `activate(plugin)` the player calls; it runs in its own Node.js process. Everything a plugin creates (commands, windows, panels, filters, listeners…) is a handle that is released automatically when the plugin stops. L<n> are line numbers in llms-full.txt, next to this file: every page, declaration and example.
4
4
 
5
5
  **Rules**
6
6
 
@@ -33,7 +33,7 @@ my-plugin/
33
33
  }
34
34
  ```
35
35
 
36
- Fields: `id`, `version`, `apiVersion`, `entry`, `capabilities`, `name`, `author`, `punchline`, `description`, `tags`, `icon`, `readme`, `changelog`, `sourceUrl`, `license`, `minPlayerVersion`; unknown fields and capabilities are rejected. Rules and limits: L2195–2261.
36
+ Fields: `id`, `version`, `apiVersion`, `entry`, `capabilities`, `name`, `author`, `punchline`, `description`, `tags`, `icon`, `readme`, `changelog`, `sourceUrl`, `license`, `minPlayerVersion`; unknown fields and capabilities are rejected. Rules and limits: L2231–2298.
37
37
 
38
38
  **main.ts**
39
39
 
@@ -76,9 +76,9 @@ export default async function activate(plugin: Plugin) {
76
76
  - `ui.settings`: Settings.page/show
77
77
  - `ui.theme`: Appearance.set/setLanguage
78
78
  - `secrets`: Secrets.get/set/delete
79
- - No capability: `tools`, `storage`, `system`, `app`, `ui` (images and Markdown), `i18n`, reading window.state, appearance.theme and packages.list. Details: L2262–2320.
79
+ - No capability: `tools`, `storage`, `system`, `app`, `ui` (images and Markdown), `i18n`, reading window.state, appearance.theme and packages.list. Details: L2299–2357.
80
80
 
81
- **Test**: `tests/*.test.ts` with `node:test`, run by `vestra-plugin test`. `startPlugin(activate, { directory: new URL("..", import.meta.url) })` from `@vestra-player/sdk/testing` returns `{ host, stop }`; `host` is a TestHost (an in-memory player): `host.setSource(…)`, `host.command(id).run()`, `host.press(key)`, `await host.settle()`, then assert on `host.osd`, `host.window(id).props`… Guide: L1658–1846; TestHost: L7061.
81
+ **Test**: `tests/*.test.ts` with `node:test`, run by `vestra-plugin test`. `startPlugin(activate, { directory: new URL("..", import.meta.url) })` from `@vestra-player/sdk/testing` returns `{ host, stop }`; `host` is a TestHost (an in-memory player): `host.setSource(…)`, `host.command(id).run()`, `host.press(key)`, `await host.settle()`, then assert on `host.osd`, `host.window(id).props`… Guide: L1672–1860; TestHost: L7198.
82
82
 
83
83
  **Command line**
84
84
 
@@ -106,39 +106,39 @@ export default async function activate(plugin: Plugin) {
106
106
  - `limit`: A player-wide limit was reached, a property value is over the limits of PropValue
107
107
  - `reserved`: The id is `vestra` or starts with `vestra.`
108
108
  - `versionMismatch`: The package needs a newer player or another `apiVersion`
109
- - Others: PluginError, llms-full.txt L3231.
109
+ - Others: PluginError, llms-full.txt L3307.
110
110
 
111
111
  ## Reference
112
112
 
113
113
  - [llms-full.txt](https://vestrasys.github.io/vestra-player-sdk/llms-full.txt), also node_modules/@vestra-player/sdk/llms-full.txt; the pages are on the [site](https://vestrasys.github.io/vestra-player-sdk/).
114
114
  - Tutorials: Getting started L9–237
115
- - Concepts: How a plugin runs L238–408; Commands, menus and key bindings L409–506; Playback state, controls and sources L507–652; Video and audio processing L653–731; Windows, panels, settings pages, dialogs and the OSD L732–1020
116
- - How-to: Keep a slider responsive while dragging L1021–1195; Remember a window's state between runs L1196–1325; Translate a plugin L1326–1509; Migrate a plugin to English source text L1510–1657; Test a plugin L1658–1846; Publish a plugin L1847–1935; Migrating off package rollback (1.5.16, removed in 1.5.17) L1936–2011
117
- - Guidelines: Interface guidelines L2012–2096; Menu guidelines L2097–2194
118
- - Reference: Manifest L2195–2261; Capabilities L2262–2320; Limits L2321–2425; Commands, menus and bindings L2426–2894; Platforms L2895–2923; Theme L2924–3130; Command line L3131–3188
119
- - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L3189–6896
120
- - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html): L6897–8302
115
+ - Concepts: How a plugin runs L238–408; Commands, menus and key bindings L409–520; Playback state, controls and sources L521–666; Video and audio processing L667–745; Windows, panels, settings pages, dialogs and the OSD L746–1034
116
+ - How-to: Keep a slider responsive while dragging L1035–1209; Remember a window's state between runs L1210–1339; Translate a plugin L1340–1523; Migrate a plugin to English source text L1524–1671; Test a plugin L1672–1860; Publish a plugin L1861–1949; Migrating off package rollback (1.5.16, removed in 1.5.17) L1950–2025
117
+ - Guidelines: Interface guidelines L2026–2133; Menu guidelines L2134–2230
118
+ - Reference: Manifest L2231–2298; Capabilities L2299–2357; Limits L2358–2472; Commands, menus and bindings L2473–2955; Platforms L2956–2984; Theme L2985–3206; Command line L3207–3264
119
+ - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L3265–7005
120
+ - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html): L7006–8476
121
121
 
122
122
  ## API
123
123
 
124
124
  Where each module of `plugin` (`plugin.<module>`) is declared:
125
125
 
126
- - Plugin L3423, Scope L3292, PluginError L3231, Disposable L3204
127
- - Commands L3635; Windows L3865; Panels L4508; Dialogs L4794; Player L5261; video: VideoFilter L5568, VideoView L5631, RenderOptions L5673; audio: AudioFilter L5592; Subtitles L5771; media: MediaCapture L5932; Resolvers L6037; Packages L6279; Osd L5504; Host L5435; Clipboard L6411; Power L6431; Tools L6464; Storage L6507; System L6594; Secrets L6612; window: MainWindow L4631; App L4712; Bindings L6381; Ui L3740; Timeline L4588; Drops L4164; Appearance L4193; I18n L4335; Settings L4441.
126
+ - Plugin L3499, Scope L3368, PluginError L3307, Disposable L3280
127
+ - Commands L3744; Windows L3974; Panels L4617; Dialogs L4903; Player L5370; video: VideoFilter L5677, VideoView L5740, RenderOptions L5782; audio: AudioFilter L5701; Subtitles L5880; media: MediaCapture L6041; Resolvers L6146; Packages L6388; Osd L5613; Host L5544; Clipboard L6520; Power L6540; Tools L6573; Storage L6616; System L6703; Secrets L6721; window: MainWindow L4740; App L4821; Bindings L6490; Ui L3849; Timeline L4697; Drops L4273; Appearance L4302; I18n L4444; Settings L4550.
128
128
 
129
129
  ## Examples
130
130
 
131
131
  Tested plugins in [examples/](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples):
132
132
 
133
- - hello: The README Quick start: one menu command and an on-screen message. L8726–8815
134
- - blur: A tool window with one slider that blurs the playing video. L8816–9077
135
- - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L9078–9410
136
- - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L9411–9595
137
- - panel-menu: A sidebar panel with its own right-click menu. L9596–9801
138
- - menus: Radio menu items, separator groups and a menu item whose title changes. L9802–9961
139
- - theme-widgets: A tool window drawn with the player's theme and widgets. L9962–10125
140
- - markdown: A window that shows a Markdown document. L10126–10320
141
- - i18n: A plugin in the player's language: English source text, a Chinese translation. L10321–10488
133
+ - hello: The README Quick start: one menu command and an on-screen message. L8907–8996
134
+ - blur: A tool window with one slider that blurs the playing video. L8997–9258
135
+ - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L9259–9591
136
+ - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L9592–9776
137
+ - panel-menu: A sidebar panel with its own right-click menu. L9777–9982
138
+ - menus: Radio menu items, separator groups and a menu item whose title changes. L9983–10142
139
+ - theme-widgets: A tool window drawn with the player's theme and widgets. L10143–10306
140
+ - markdown: A window that shows a Markdown document. L10307–10501
141
+ - i18n: A plugin in the player's language: English source text, a Chinese translation. L10502–10669
142
142
 
143
143
  ## Optional
144
144
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vestra-player/sdk",
3
- "version": "1.5.27",
3
+ "version": "1.5.29",
4
4
  "description": "Plugin API types, test host and plugin CLI for Vestra Player (apiVersion vestra/1)",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {