@qxuken/kui 0.1.0-alpha.41 → 0.1.0-alpha.43

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/howto.md CHANGED
@@ -278,6 +278,23 @@ gap, to keep it apart.
278
278
  [ADR 0035](docs/adr/0035-a-rounded-background-is-joined-by-meeting.md) ·
279
279
  [alpha.22 `### Added`](CHANGELOG.md#010-alpha22-2026-09-28)
280
280
 
281
+ ### How do I set inline code in monospace inside a paragraph?
282
+
283
+ Give the span a face of its own: `<span family="mono">cargo test</span>`,
284
+ `Span::new("cargo test").mono()` in Rust, `{ "cargo test", family = "mono" }`
285
+ in Lua, `.flags = KUI_SPAN_FAMILY, .family = KUI_FONT_MONO` on a C
286
+ `KuiSpan` (`.font` for a registered font, which wins). Any family name the
287
+ text's `family` takes works, and `size` gives a span its own size — a
288
+ larger first word, a smaller footnote mark — with the line as tall as its
289
+ tallest span. The paragraph still shapes as one flow, and its carets, hit
290
+ tests and selection are measured in the face each glyph is drawn in, so a
291
+ byte position means the same thing on either side of the change. Add a
292
+ `bg` and a `bgRadius` for the usual wash; it is the span's own height,
293
+ not the line's.
294
+
295
+ [`text` element](props.md#elements) ·
296
+ [`text.rs`](../examples/rust/widgets/text.rs)
297
+
281
298
  ### How do I list the installed fonts, the monospaced ones first?
282
299
 
283
300
  `ctx.systemFonts()` (Rust `Core::system_fonts()`, C `kui_system_fonts`)
@@ -558,6 +575,51 @@ message.
558
575
  [ADR 0019](docs/adr/0019-a-theme-derived-from-appearance-and-accent.md) ·
559
576
  [alpha.10](CHANGELOG.md#010-alpha10-2026-09-09)
560
577
 
578
+ ### How do I show the blurred desktop through my sidebar, like a Mac app?
579
+
580
+ Ask the launcher for the effect — `kui_native::app("Notes")
581
+ .backdrop(Backdrop::Blur)`, or `backdrop: 'blur'` in Node's window options
582
+ — and paint the regions that should show it with alpha:
583
+ `bg(t.raised.with_alpha(0.5))` on the sidebar, an opaque `bg` on the page
584
+ beside it. kui puts the effect behind the whole window and knows nothing of
585
+ sidebars; what you paint decides where it shows. `Blur` is a live blur of
586
+ what is behind the window (macOS vibrancy, Windows 11's Acrylic, KDE's
587
+ compositor blur), `Tinted` the desktop's colour, steady (Mica, macOS's
588
+ window-background material), `Transparent` the desktop as it is. Then read
589
+ what the window got, every frame, from `ui.env().window.backdrop` (Node
590
+ `env().window.backdrop`): where the OS has no effect — GNOME, Windows 10 —
591
+ it reads `Tinted`, the wallpaper kui reads, blurs once and draws under the
592
+ frame, or `Opaque` where no wallpaper could be read; paint the sidebar
593
+ opaque then. Hyprland, SwayFX and picom blur translucent windows
594
+ themselves: ask for `Transparent` there. Glyphs are grayscale under a
595
+ backdrop; `KUI_BACKDROP_EMULATE=1` shows the wallpaper path on Windows and Linux
596
+ (macOS reads no wallpaper, so the window there is `Opaque`).
597
+
598
+ [`window.backdrop` row](props.md#env) ·
599
+ [`backdrop.rs`](../examples/rust/features/backdrop.rs)
600
+
601
+ In C, `KuiRunConfig.backdrop` asks (`KUI_BACKDROP_BLUR`) and
602
+ `kui_ctx_backdrop(ctx)` in the view reads what the window got.
603
+
604
+ ### How do I frost a toolbar over content that scrolls under it?
605
+
606
+ Give the toolbar a `backdropBlur` and a translucent `bg`:
607
+ `NodeSpec::row().backdrop_blur(16.0).bg(Color::WHITE.with_alpha(0.25))`
608
+ in Rust, `backdropBlur: 16` in JSX, `backdrop_blur` in Lua, Odin and C's
609
+ `KuiSpec`. What blurs is everything painted before the node — the page,
610
+ the rows scrolling under it, the window's backdrop where there is one —
611
+ inside the node's rounded box and its clip, by the radius in logical px
612
+ (CSS's `backdrop-filter: blur()`). The node's own `bg`, border and
613
+ children lie on top, so an opaque `bg` hides the blur, and a
614
+ `backdrop-blur-hidden` warning says so. Float the toolbar over the
615
+ scroller, after it in the tree, so the scroller paints first. It costs a
616
+ copy and three small passes per blurred node on a frame that has one, and
617
+ nothing on a frame that does not; a renderer of your own that cannot read
618
+ back its frame draws the node unblurred.
619
+
620
+ [`backdropBlur` row](props.md#container-props) ·
621
+ [`backdrop_blur.rs`](../examples/rust/features/backdrop_blur.rs)
622
+
561
623
  ## Interaction, focus and reading
562
624
 
563
625
  ### How do I open a popup, and when is a modal enough?
@@ -734,6 +796,27 @@ the same palette from the keyboard and types the same way.
734
796
  [ADR 0030](docs/adr/0030-the-standard-menus-the-runner-keeps.md) ·
735
797
  [examples/rust/widgets/menu_bar.rs](../examples/rust/widgets/menu_bar.rs)
736
798
 
799
+ ### How do I put a submenu in a menu — "Move to ▸", "Sort by ▸"?
800
+
801
+ Give the row `items`: `MenuItem::submenu("Move to", rows)` in Rust,
802
+ `{ label: 'Move to', items: [...] }` in Node and Lua. The row draws a
803
+ chevron and opens its rows beside it when the pointer rests on it, it is
804
+ clicked, or Enter or the Right arrow is pressed; Left or Escape closes
805
+ it, and Escape again closes the menu. It nests, in a context menu and in
806
+ a menu bar, drawn or the platform's. A row inside is chosen like any
807
+ row: one `{kind:"menu", role, item}` with its own `id`, on the node the
808
+ menu is about. A host that shows menus itself reports one with
809
+ `Core::activate_menu_path(&[1, 0])` (Node `activateMenuPath`, C
810
+ `kui_activate_menu_path` with a path of `size_t`s). In C a row's
811
+ `submenu` / `submenu_count` nest the same `KuiMenuItem`s, and
812
+ `KUI_MENU_ITEM_SUBMENU` in a row's flags says it has rows, read with
813
+ `kui_menu_item_path`. An
814
+ `accel` in the portable spelling (`"mod+shift+n"`) is drawn the
815
+ platform's way, and the menu widens to its longest row.
816
+
817
+ [ADR 0018](docs/adr/0018-a-menu-bar-the-app-declares.md) ·
818
+ [`tests/submenu.rs`](../crates/kui-core/tests/submenu.rs)
819
+
737
820
  ### How do I take files dropped from the Finder?
738
821
 
739
822
  Declare `onDrop` (Rust and Lua `on_drop`, C `KuiSpec.on_drop`) on the box
package/index.d.ts CHANGED
@@ -730,6 +730,10 @@ export interface OpenMenuItem {
730
730
  * own, or its role's default (`⌘C` on a `copy` row that declared none);
731
731
  * null where there is neither. */
732
732
  accel: string | null;
733
+ /** The rows of the submenu this row opens, read the same way; absent on
734
+ * a row that opens none. A row with them is never chosen itself: report
735
+ * a row inside with `activateMenuPath`. */
736
+ items?: OpenMenuItem[];
733
737
  }
734
738
 
735
739
  /** The menu a window has open (`Ctx.menu()`): where it opened, the node it
@@ -925,6 +929,12 @@ export type WarningCode =
925
929
  * after every width is. The ratio sizes a fit height from the width, or a
926
930
  * fit width from a fixed height. */
927
931
  | 'aspect-ignored'
932
+ /** A `backdropBlur` under an opaque `bg` of the node's own: the background
933
+ * paints over the whole blur, so nothing of it shows and the copy and the
934
+ * passes are spent for nothing. Give the `bg` some transparency
935
+ * (`#ffffff40`) — frosted glass is a translucent fill over a blur (backlog
936
+ * F129). */
937
+ | 'backdrop-blur-hidden'
928
938
  /** A text node sits more than four levels below the `line` row above it,
929
939
  * which is as far as a text's place remembers its ancestors — so `textHit` /
930
940
  * `caretRect` asked by that row's key cannot find the run, and a press
@@ -1369,6 +1379,8 @@ export interface NodeInfo {
1369
1379
  /** `0xRRGGBBAA`. */
1370
1380
  borderColor: number;
1371
1381
  opacity: number;
1382
+ /** Its `backdropBlur` radius in px, 0 for none (backlog F129). */
1383
+ backdropBlur: number;
1372
1384
  /** A scroller's offset; `null` for a node that does not scroll. */
1373
1385
  scroll: { x: number; y: number } | null;
1374
1386
  /** Every handler it declared with the payload it would post: `click`,
@@ -1647,8 +1659,19 @@ export interface WindowEnv {
1647
1659
  * draws over our content — the macOS traffic lights under custom chrome.
1648
1660
  * Keep out of it. Null means the OS draws nothing over us. */
1649
1661
  nativeControls: Rect | null;
1662
+ /** What is behind the window's transparent pixels, as the runner got it:
1663
+ * `'opaque'` (the default), `'transparent'` (the desktop as it is),
1664
+ * `'blur'` (a live blur of what is behind the window) or `'tinted'`
1665
+ * (the desktop's colour, steady). Less where the platform has less — a
1666
+ * blur asked of GNOME reads `'tinted'`, the wallpaper kui draws, or
1667
+ * `'opaque'` — so a view paints its translucent regions opaque when
1668
+ * this is `'opaque'`. */
1669
+ backdrop: Backdrop;
1650
1670
  }
1651
1671
 
1672
+ /** What is behind a window's transparent pixels (`WindowEnv.backdrop`). */
1673
+ export type Backdrop = 'opaque' | 'transparent' | 'blur' | 'tinted';
1674
+
1652
1675
  /** A box in logical px: position and size. */
1653
1676
  export interface Rect {
1654
1677
  x: number;
@@ -1689,6 +1712,8 @@ export interface EnvInput {
1689
1712
  /** `x` and `y` default to the window origin; a zero-sized rect and null
1690
1713
  * both mean "nothing is drawn over us". */
1691
1714
  nativeControls?: Partial<Rect> | null;
1715
+ /** What a runner would report behind the window's transparent pixels. */
1716
+ backdrop?: Backdrop;
1692
1717
  };
1693
1718
  /** What a driver with a device would report; see `AudioEnv`. */
1694
1719
  audio?: {
@@ -1828,6 +1853,15 @@ export interface WindowOptions {
1828
1853
  maxWidth?: number;
1829
1854
  maxHeight?: number;
1830
1855
  chrome?: 'native' | 'custom' | 'borderless';
1856
+ /** What shows through the window where a frame paints nothing or paints
1857
+ * with alpha (the launcher's `backdrop`), by effect: `'opaque'` (the
1858
+ * default), `'transparent'` (the desktop as it is), `'blur'` (macOS
1859
+ * vibrancy, Windows 11 Acrylic, KDE's compositor blur) or `'tinted'`
1860
+ * (Windows 11 Mica, macOS's window-background material, the wallpaper
1861
+ * kui draws where the OS has neither). Paint the regions that should
1862
+ * show it with alpha and the rest opaque; `env().window.backdrop` says
1863
+ * what the platform gave, which is less where it has less. */
1864
+ backdrop?: Backdrop;
1831
1865
  /** How outline glyphs are antialiased: `'auto'` (the default) is LCD
1832
1866
  * subpixel coverage where the GPU blends per channel and grayscale
1833
1867
  * otherwise. `KUI_TEXT_AA=gray|subpixel` in the environment still
@@ -1890,6 +1924,8 @@ export type DoorCell = { is: string } | { as: string } | { no: string };
1890
1924
  export interface Door {
1891
1925
  rust: string;
1892
1926
  c: DoorCell;
1927
+ /** The Odin binding's (packages/odin), pinned by its generator. */
1928
+ odin: DoorCell;
1893
1929
  node: DoorCell;
1894
1930
  lua: DoorCell;
1895
1931
  doc: string;
@@ -3278,6 +3314,12 @@ export declare class Ctx {
3278
3314
  * takes. False for a row that is not there.
3279
3315
  */
3280
3316
  activateMenuBarItem(menu: number, item: number): boolean
3317
+ /**
3318
+ * `activateMenuBarItem` for a row inside a submenu of menu
3319
+ * `menu`, by its path through the rows' `items`. False for a
3320
+ * row that is not there or that opens a submenu.
3321
+ */
3322
+ activateMenuBarPath(menu: number, path: Array<number>): boolean
3281
3323
  /**
3282
3324
  * Tells the core this host can show the platform's definition
3283
3325
  * panel. The standard Look Up row is then offered where it
@@ -3293,6 +3335,13 @@ export declare class Ctx {
3293
3335
  * stays open and nothing is posted.
3294
3336
  */
3295
3337
  activateMenuItem(index: number): boolean
3338
+ /**
3339
+ * `activateMenuItem` for a row inside a submenu, by its path
3340
+ * through the rows' `items`: `[2, 0]` is the first row of the
3341
+ * third row's submenu. False where `activateMenuItem` is, and
3342
+ * for a row that opens a submenu, which is never chosen.
3343
+ */
3344
+ activateMenuPath(path: Array<number>): boolean
3296
3345
  /** Closes whatever menu is open; true when there was one. */
3297
3346
  closeMenu(): boolean
3298
3347
  /**
@@ -3479,7 +3528,7 @@ export declare class KuiWindow {
3479
3528
  /**
3480
3529
  * Options: `{width, height, minWidth, minHeight, maxWidth, maxHeight,
3481
3530
  * chrome: "native" | "custom" | "borderless", textAa: "auto" | "gray"
3482
- * | "subpixel", frameLatency, system, icon}`. The min/max pairs bound what the user can
3531
+ * | "subpixel", frameLatency, system, icon, backdrop}`. The min/max pairs bound what the user can
3483
3532
  * resize the window to; either half may stand alone. `system` pins part of `env.system` over what the OS
3484
3533
  * says, for the life of the window — `{motion: 'reduced'}` is what a
3485
3534
  * user who asked for less motion would get, on a machine whose owner
@@ -4462,6 +4511,12 @@ export declare class KuiWindow {
4462
4511
  * takes. False for a row that is not there.
4463
4512
  */
4464
4513
  activateMenuBarItem(menu: number, item: number): boolean
4514
+ /**
4515
+ * `activateMenuBarItem` for a row inside a submenu of menu
4516
+ * `menu`, by its path through the rows' `items`. False for a
4517
+ * row that is not there or that opens a submenu.
4518
+ */
4519
+ activateMenuBarPath(menu: number, path: Array<number>): boolean
4465
4520
  /**
4466
4521
  * Tells the core this host can show the platform's definition
4467
4522
  * panel. The standard Look Up row is then offered where it
@@ -4477,6 +4532,13 @@ export declare class KuiWindow {
4477
4532
  * stays open and nothing is posted.
4478
4533
  */
4479
4534
  activateMenuItem(index: number): boolean
4535
+ /**
4536
+ * `activateMenuItem` for a row inside a submenu, by its path
4537
+ * through the rows' `items`: `[2, 0]` is the first row of the
4538
+ * third row's submenu. False where `activateMenuItem` is, and
4539
+ * for a row that opens a submenu, which is never chosen.
4540
+ */
4541
+ activateMenuPath(path: Array<number>): boolean
4480
4542
  /** Closes whatever menu is open; true when there was one. */
4481
4543
  closeMenu(): boolean
4482
4544
  /**
package/index.js CHANGED
@@ -52,8 +52,12 @@ export function withEffects(model, ...effects) {
52
52
 
53
53
  // A frame crosses the boundary one way: JS encodes the tree into one
54
54
  // Float64Array + string table and the addon lowers it zero-copy. One encoder
55
- // serves every context — its buffers are consumed synchronously.
55
+ // serves every context — its buffers are consumed synchronously. Measuring
56
+ // has one of its own: a `<devtoolsTab>`'s function child is called in the
57
+ // middle of an encode, and a `measureText` there reset the frame's buffers
58
+ // under it ("binary frame must start with the root op").
56
59
  const encoder = createEncoder(native.protocol());
60
+ const measurer = createEncoder(native.protocol());
57
61
 
58
62
  // A prop name the schema does not know has no wire id, so it never crosses:
59
63
  // the encoder is the only side that can see it, and it reports what it
@@ -206,7 +210,7 @@ KuiWindow.prototype.setView = function setView(tree, window) {
206
210
  // it draws. A style name the schema does not know is reported like a
207
211
  // view's would be.
208
212
  function measureText(content, style, maxWidth) {
209
- const { stream, strings, unknown, unknownTokens } = encoder.encodeText(content, style, this[TOKENS]);
213
+ const { stream, strings, unknown, unknownTokens } = measurer.encodeText(content, style, this[TOKENS]);
210
214
  const m = this.measureTextBinary(stream, strings, maxWidth);
211
215
  reportUnknown(this, unknown, unknownTokens);
212
216
  return m;
@@ -1040,8 +1044,8 @@ runWindowed[PACE] = pacer;
1040
1044
  * is opened as a user who asked for less motion would see it.
1041
1045
  */
1042
1046
  export function windowOptions(opts = {}) {
1043
- const { width, height, minWidth, minHeight, maxWidth, maxHeight, chrome, textAa, frameLatency, system, icon } = opts;
1044
- return { width, height, minWidth, minHeight, maxWidth, maxHeight, chrome, textAa, frameLatency, system, icon };
1047
+ const { width, height, minWidth, minHeight, maxWidth, maxHeight, chrome, textAa, frameLatency, system, icon, backdrop } = opts;
1048
+ return { width, height, minWidth, minHeight, maxWidth, maxHeight, chrome, textAa, frameLatency, system, icon, backdrop };
1045
1049
  }
1046
1050
 
1047
1051
  /**
package/jsx-runtime.d.ts CHANGED
@@ -219,6 +219,11 @@ export interface MenuItemInput {
219
219
  * A declaration kui can parse is rewritten into the platform's own
220
220
  * spelling, so `'mod+s'` reads as `⌘S` on macOS and `Ctrl+S` elsewhere. */
221
221
  accel?: string;
222
+ /** The rows of a submenu: the row draws a chevron and opens them beside
223
+ * itself — on hover, a click, Enter or the Right arrow; Left or Escape
224
+ * closes it — and is never chosen itself. A chosen row inside posts its
225
+ * own `menu` event, on the node the menu is about. */
226
+ items?: MenuItemInput[];
222
227
  }
223
228
 
224
229
  /** One menu of the application menu bar (the `<menuBar menu={…}/>`
@@ -257,6 +262,8 @@ export interface GeneratedSpecProps {
257
262
  animate?: boolean;
258
263
  /** Width over height — `16/9`, `1` for a square — CSS's `aspect-ratio`. It sizes the axis left `fit`: a fit height is the final width over the ratio (so `width: grow` and a ratio is a box that keeps its shape as the window resizes), and a fit width under a fixed height is that height times it. With both axes declared, or a fit width under a `grow` or percent height, it has nothing it can set and warns. The derived axis is neither shrunk nor fitted to the children, which overflow it; `minHeight: 'fit'` floors it at them. On an image it wins over the pixels' own aspect. */
259
264
  aspectRatio?: LengthProp;
265
+ /** Blur what was drawn beneath the node, inside its rounded box, by this radius in logical px — CSS's `backdrop-filter: blur()`, the radius its standard deviation (backlog F129). What blurs is everything painted before the node: its ancestors' backgrounds, the siblings under it, content scrolling beneath it, the window's `backdrop` where the window has one. The node's own `bg`, border and children paint over the blur, so a translucent `bg` (`#ffffff40`) makes frosted glass and an opaque one hides it. Clipped as the node is, faded by its `opacity`; 0 is none. The GPU renderer reads back only the box (and a margin of three radii around it) and blurs it at reduced resolution, so it costs a copy and three small passes per blurred node on a frame that has one and nothing on a frame that does not. A renderer that cannot read back what it drew — a host's own, or anything older — leaves the node over an unblurred backdrop; the display list carries it as a `backdrop` quad (`KUI_QUAD_BACKDROP` in C) either way. */
266
+ backdropBlur?: LengthProp;
260
267
  /** Background fill. */
261
268
  bg?: ColorProp;
262
269
  /** How far a spring overshoots its target: 0 glides in with none, 0.5 bounces visibly, and values past 0.9 are held there (a spring at 1 would never settle). It replaces a spring `easing`'s own bounce (`smooth` 0, `snappy` 0.15, `spring` 0.25, `bouncy` 0.5), and on a timed easing makes the transition a spring — so `transition` plus `bounce` is a spring of that length and bounce. */
@@ -599,6 +606,18 @@ export interface SpanProps extends Keyed {
599
606
  * A selection over rows, or over a paragraph's wrapped lines, is one
600
607
  * outline. Nested spans inherit; 0 (the default) is square. */
601
608
  bgRadius?: number;
609
+ /** The span's own face: `sans`, `serif`, `mono` or an installed family's
610
+ * name, as the text's `family` takes — inline code in `mono` inside a
611
+ * sans paragraph. Carets, hits and selection are measured in the face
612
+ * the glyphs are drawn in. Nested spans inherit. */
613
+ family?: string;
614
+ /** A registered font handle (`addFont` / `addSystemFont`), the span's
615
+ * face; wins over `family`. Nested spans inherit. */
616
+ font?: string;
617
+ /** The span's own size, logical px: its line height scales with it at
618
+ * the paragraph's ratio, and a line is as tall as its tallest span.
619
+ * Nested spans inherit. */
620
+ size?: number;
602
621
  children?: KuiNode;
603
622
  }
604
623
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qxuken/kui",
3
- "version": "0.1.0-alpha.41",
3
+ "version": "0.1.0-alpha.43",
4
4
  "description": "kui for Node: JSX views lowered into the kui IR, Elm-style messages as data",
5
5
  "license": "MIT",
6
6
  "repository": {
Binary file
Binary file
Binary file