zaniah 0.2.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/LICENSE.txt +18 -0
- data/README.md +14 -4
- data/assets/icons/check.svg +1 -0
- data/assets/icons/close.svg +1 -0
- data/assets/icons/info.svg +1 -0
- data/assets/icons/menu.svg +1 -0
- data/assets/icons/search.svg +1 -0
- data/assets/icons/warning.svg +1 -0
- data/docs/accessibility.md +61 -0
- data/docs/adr/004-style-resolution.md +17 -0
- data/docs/adr/005-instance-layout.md +43 -0
- data/docs/adr/006-component-model.md +17 -0
- data/docs/adr/007-text-buffer.md +17 -0
- data/docs/adr/008-accessibility-bridge.md +19 -0
- data/docs/adr/009-grid-scope.md +17 -0
- data/docs/animation.md +46 -0
- data/docs/components.md +109 -0
- data/docs/gallery/command-palette-dark.png +0 -0
- data/docs/gallery/command-palette-high_contrast.png +0 -0
- data/docs/gallery/command-palette-light.png +0 -0
- data/docs/gallery/components-dark.png +0 -0
- data/docs/gallery/components-high_contrast.png +0 -0
- data/docs/gallery/components-light.png +0 -0
- data/docs/gallery/context-menu-dark.png +0 -0
- data/docs/gallery/context-menu-high_contrast.png +0 -0
- data/docs/gallery/context-menu-light.png +0 -0
- data/docs/gallery/dialog-dark.png +0 -0
- data/docs/gallery/dialog-high_contrast.png +0 -0
- data/docs/gallery/dialog-light.png +0 -0
- data/docs/gallery/drawer-left-dark.png +0 -0
- data/docs/gallery/drawer-left-high_contrast.png +0 -0
- data/docs/gallery/drawer-left-light.png +0 -0
- data/docs/gallery/drawer-right-dark.png +0 -0
- data/docs/gallery/drawer-right-high_contrast.png +0 -0
- data/docs/gallery/drawer-right-light.png +0 -0
- data/docs/gallery/menu-dark.png +0 -0
- data/docs/gallery/menu-high_contrast.png +0 -0
- data/docs/gallery/menu-light.png +0 -0
- data/docs/gallery/modal-dark.png +0 -0
- data/docs/gallery/modal-high_contrast.png +0 -0
- data/docs/gallery/modal-light.png +0 -0
- data/docs/gallery/popover-bottom-dark.png +0 -0
- data/docs/gallery/popover-bottom-high_contrast.png +0 -0
- data/docs/gallery/popover-bottom-light.png +0 -0
- data/docs/gallery/popover-left-dark.png +0 -0
- data/docs/gallery/popover-left-high_contrast.png +0 -0
- data/docs/gallery/popover-left-light.png +0 -0
- data/docs/gallery/popover-right-dark.png +0 -0
- data/docs/gallery/popover-right-high_contrast.png +0 -0
- data/docs/gallery/popover-right-light.png +0 -0
- data/docs/gallery/popover-top-dark.png +0 -0
- data/docs/gallery/popover-top-high_contrast.png +0 -0
- data/docs/gallery/popover-top-light.png +0 -0
- data/docs/gallery/toast-danger-dark.png +0 -0
- data/docs/gallery/toast-danger-high_contrast.png +0 -0
- data/docs/gallery/toast-danger-light.png +0 -0
- data/docs/gallery/toast-info-dark.png +0 -0
- data/docs/gallery/toast-info-high_contrast.png +0 -0
- data/docs/gallery/toast-info-light.png +0 -0
- data/docs/gallery/toast-success-dark.png +0 -0
- data/docs/gallery/toast-success-high_contrast.png +0 -0
- data/docs/gallery/toast-success-light.png +0 -0
- data/docs/gallery/toast-warning-dark.png +0 -0
- data/docs/gallery/toast-warning-high_contrast.png +0 -0
- data/docs/gallery/toast-warning-light.png +0 -0
- data/docs/gallery/tooltip-bottom-dark.png +0 -0
- data/docs/gallery/tooltip-bottom-high_contrast.png +0 -0
- data/docs/gallery/tooltip-bottom-light.png +0 -0
- data/docs/gallery/tooltip-left-dark.png +0 -0
- data/docs/gallery/tooltip-left-high_contrast.png +0 -0
- data/docs/gallery/tooltip-left-light.png +0 -0
- data/docs/gallery/tooltip-right-dark.png +0 -0
- data/docs/gallery/tooltip-right-high_contrast.png +0 -0
- data/docs/gallery/tooltip-right-light.png +0 -0
- data/docs/gallery/tooltip-top-dark.png +0 -0
- data/docs/gallery/tooltip-top-high_contrast.png +0 -0
- data/docs/gallery/tooltip-top-light.png +0 -0
- data/docs/index.html +74 -0
- data/docs/layout.md +31 -0
- data/docs/native.md +7 -2
- data/docs/site.css +32 -0
- data/docs/text.md +35 -0
- data/docs/theme.md +50 -0
- data/docs/tui.md +36 -0
- data/examples/gallery.rb +155 -0
- data/examples/native_ime.rb +13 -6
- data/lib/zaniah/accessibility/linux/service.rb +361 -0
- data/lib/zaniah/accessibility/linux.rb +23 -0
- data/lib/zaniah/accessibility/mac.rb +136 -0
- data/lib/zaniah/accessibility/native_tree.rb +53 -0
- data/lib/zaniah/accessibility/node.rb +12 -0
- data/lib/zaniah/accessibility/tree.rb +61 -0
- data/lib/zaniah/accessibility/windows/provider.rb +273 -0
- data/lib/zaniah/accessibility/windows.rb +38 -0
- data/lib/zaniah/accessibility.rb +63 -0
- data/lib/zaniah/animation/animator.rb +95 -0
- data/lib/zaniah/animation/easing.rb +45 -0
- data/lib/zaniah/animation/spring.rb +36 -0
- data/lib/zaniah/animation/transition.rb +5 -0
- data/lib/zaniah/animation.rb +6 -0
- data/lib/zaniah/app.rb +22 -2
- data/lib/zaniah/color.rb +16 -0
- data/lib/zaniah/devtools/hot_reload.rb +40 -0
- data/lib/zaniah/devtools/inspector.rb +111 -0
- data/lib/zaniah/devtools/stats_overlay.rb +16 -0
- data/lib/zaniah/devtools.rb +13 -0
- data/lib/zaniah/element.rb +210 -15
- data/lib/zaniah/frame_context.rb +4 -1
- data/lib/zaniah/gpu/frame_encoder.rb +10 -1
- data/lib/zaniah/gpu/instance_packing.rb +33 -5
- data/lib/zaniah/gpu/metal.rb +26 -10
- data/lib/zaniah/gpu/open_gl.rb +35 -14
- data/lib/zaniah/gpu/software.rb +116 -14
- data/lib/zaniah/gpu/vulkan/scene.frag +51 -0
- data/lib/zaniah/gpu/vulkan/scene.frag.spv +0 -0
- data/lib/zaniah/gpu/vulkan/scene.vert +50 -0
- data/lib/zaniah/gpu/vulkan/scene.vert.spv +0 -0
- data/lib/zaniah/gpu/vulkan.rb +282 -36
- data/lib/zaniah/gpu.rb +8 -3
- data/lib/zaniah/input/dispatcher.rb +96 -12
- data/lib/zaniah/input/focus_handle.rb +13 -4
- data/lib/zaniah/input/focus_tree.rb +77 -0
- data/lib/zaniah/input/keymap.rb +77 -0
- data/lib/zaniah/input.rb +1 -0
- data/lib/zaniah/interactivity.rb +30 -0
- data/lib/zaniah/layout/engine.rb +173 -36
- data/lib/zaniah/layout/grid.rb +134 -0
- data/lib/zaniah/layout/style.rb +16 -1
- data/lib/zaniah/layout.rb +1 -0
- data/lib/zaniah/length.rb +3 -0
- data/lib/zaniah/length_units.rb +7 -0
- data/lib/zaniah/list.rb +27 -15
- data/lib/zaniah/platform/appearance.rb +11 -0
- data/lib/zaniah/platform/headless/window.rb +101 -9
- data/lib/zaniah/platform/linux/appearance_aware.rb +8 -0
- data/lib/zaniah/platform/linux/wayland_window.rb +3 -1
- data/lib/zaniah/platform/linux/window.rb +4 -2
- data/lib/zaniah/platform/mac/app.rb +1 -1
- data/lib/zaniah/platform/mac/core_text.rb +3 -0
- data/lib/zaniah/platform/mac/window.rb +9 -1
- data/lib/zaniah/platform/tui/window.rb +1 -1
- data/lib/zaniah/platform/windows/window.rb +12 -2
- data/lib/zaniah/platform.rb +1 -0
- data/lib/zaniah/scene.rb +137 -28
- data/lib/zaniah/scroll_state.rb +117 -0
- data/lib/zaniah/scroll_view.rb +94 -0
- data/lib/zaniah/style/gradient.rb +28 -0
- data/lib/zaniah/style/ring.rb +13 -0
- data/lib/zaniah/style/shadow.rb +13 -0
- data/lib/zaniah/style/style_set.rb +43 -0
- data/lib/zaniah/style/transform.rb +40 -0
- data/lib/zaniah/svg.rb +19 -0
- data/lib/zaniah/text.rb +261 -6
- data/lib/zaniah/text_buffer.rb +119 -0
- data/lib/zaniah/text_selection.rb +18 -0
- data/lib/zaniah/text_system/atlas_cache.rb +1 -1
- data/lib/zaniah/text_system/fallback.rb +24 -0
- data/lib/zaniah/text_system/font_db.rb +4 -1
- data/lib/zaniah/text_system/kinsoku.rb +28 -0
- data/lib/zaniah/text_system/line_breaker.rb +54 -0
- data/lib/zaniah/text_system/paragraph.rb +99 -0
- data/lib/zaniah/text_system/renderer.rb +13 -2
- data/lib/zaniah/text_system/typesetter.rb +4 -0
- data/lib/zaniah/text_system.rb +3 -0
- data/lib/zaniah/theme.rb +39 -0
- data/lib/zaniah/ui/charts.rb +165 -0
- data/lib/zaniah/ui/choice_fields.rb +134 -0
- data/lib/zaniah/ui/component.rb +73 -0
- data/lib/zaniah/ui/controls.rb +439 -0
- data/lib/zaniah/ui/data.rb +364 -0
- data/lib/zaniah/ui/editors.rb +67 -0
- data/lib/zaniah/ui/forms.rb +131 -0
- data/lib/zaniah/ui/layout_components.rb +205 -0
- data/lib/zaniah/ui/modal.rb +129 -0
- data/lib/zaniah/ui/overlays.rb +282 -0
- data/lib/zaniah/ui/pickers.rb +169 -0
- data/lib/zaniah/ui/primitives.rb +199 -0
- data/lib/zaniah/ui/scrollbar.rb +104 -0
- data/lib/zaniah/ui/structure.rb +224 -0
- data/lib/zaniah/ui/text_inputs.rb +169 -0
- data/lib/zaniah/ui/variants.rb +23 -0
- data/lib/zaniah/ui.rb +18 -0
- data/lib/zaniah/unicode.rb +42 -0
- data/lib/zaniah/uniform_list.rb +27 -6
- data/lib/zaniah/version.rb +1 -1
- data/lib/zaniah.rb +14 -0
- data/sig/accessibility.rbs +34 -0
- data/sig/animation.rbs +54 -0
- data/sig/app.rbs +2 -1
- data/sig/devtools.rbs +37 -0
- data/sig/elements.rbs +88 -6
- data/sig/gpu.rbs +9 -2
- data/sig/input.rbs +31 -5
- data/sig/layout.rbs +2 -1
- data/sig/platform.rbs +24 -4
- data/sig/scene.rbs +15 -2
- data/sig/style.rbs +82 -0
- data/sig/text.rbs +35 -0
- data/sig/theme.rbs +64 -0
- data/sig/ui.rbs +390 -0
- data/sig/zaniah.rbs +56 -3
- data/tools/generate_component_gallery.rb +48 -0
- metadata +147 -2
data/docs/index.html
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<meta name="description" content="Zaniah is a pure Ruby UI toolkit for native GPU, headless, and terminal applications.">
|
|
7
|
+
<title>Zaniah — Ruby UI, from one tree</title>
|
|
8
|
+
<link rel="stylesheet" href="site.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<header class="masthead">
|
|
12
|
+
<a class="brand" href="../README.md"><img src="../assets/logo.png" alt="" width="42" height="42">Zaniah</a>
|
|
13
|
+
<nav aria-label="Primary"><a href="#gallery">Gallery</a><a href="#capabilities">Capabilities</a><a href="#start">Start</a><a href="components.md">Reference</a></nav>
|
|
14
|
+
</header>
|
|
15
|
+
|
|
16
|
+
<main>
|
|
17
|
+
<section class="hero">
|
|
18
|
+
<div>
|
|
19
|
+
<h1>Ruby UI,<br>from one tree.</h1>
|
|
20
|
+
<p>Build native GPU windows, deterministic PNG tests, and terminal interfaces with the same Ruby element tree.</p>
|
|
21
|
+
<div class="actions"><a class="primary" href="#start">Build a window</a><a href="https://github.com/noxdea/zaniah">View source</a></div>
|
|
22
|
+
</div>
|
|
23
|
+
<dl class="backends" aria-label="Rendering backends">
|
|
24
|
+
<div><dt>macOS</dt><dd>Metal / OpenGL</dd></div>
|
|
25
|
+
<div><dt>Linux</dt><dd>Wayland / X11</dd></div>
|
|
26
|
+
<div><dt>Windows</dt><dd>Win32 / WGL</dd></div>
|
|
27
|
+
<div><dt>Tests</dt><dd>Pure Ruby pixels</dd></div>
|
|
28
|
+
<div><dt>Terminal</dt><dd>ANSI cells</dd></div>
|
|
29
|
+
</dl>
|
|
30
|
+
</section>
|
|
31
|
+
|
|
32
|
+
<section class="gallery" id="gallery">
|
|
33
|
+
<div class="section-heading"><h2>The component surface</h2><p>Controls, data views, charts, forms, overlays, focus, motion, and accessibility are rendered by Zaniah itself.</p></div>
|
|
34
|
+
<figure><img src="gallery/components-dark.png" alt="Dark Zaniah component gallery showing controls, inputs, navigation, tables, trees, charts, and forms" loading="lazy"><figcaption>Dark theme · generated by the headless backend</figcaption></figure>
|
|
35
|
+
<figure><img src="gallery/components-light.png" alt="Light Zaniah component gallery showing the same controls and data components" loading="lazy"><figcaption>Light theme · the same Ruby tree</figcaption></figure>
|
|
36
|
+
<figure><img src="gallery/components-high_contrast.png" alt="High-contrast Zaniah component gallery" loading="lazy"><figcaption>High contrast · keyboard focus and semantic colors remain visible</figcaption></figure>
|
|
37
|
+
<div class="overlay-grid">
|
|
38
|
+
<figure><img src="gallery/menu-dark.png" alt="Menu component" loading="lazy"><figcaption>Menu</figcaption></figure>
|
|
39
|
+
<figure><img src="gallery/dialog-light.png" alt="Dialog component" loading="lazy"><figcaption>Dialog</figcaption></figure>
|
|
40
|
+
<figure><img src="gallery/drawer-right-dark.png" alt="Right drawer component" loading="lazy"><figcaption>Drawer</figcaption></figure>
|
|
41
|
+
<figure><img src="gallery/toast-success-light.png" alt="Success toast component" loading="lazy"><figcaption>Toast</figcaption></figure>
|
|
42
|
+
</div>
|
|
43
|
+
</section>
|
|
44
|
+
|
|
45
|
+
<section class="capabilities" id="capabilities">
|
|
46
|
+
<div class="section-heading"><h2>One pipeline, five useful views</h2><p>Rendering features stay connected to input, semantics, testing, and performance.</p></div>
|
|
47
|
+
<div class="capability"><h3>Layout</h3><p>Flex, Grid, sticky positioning, virtual lists, bidirectional scrolling, and keyed movement.</p><code>display: :grid</code></div>
|
|
48
|
+
<div class="capability"><h3>Text</h3><p>OpenType shaping, fallback, Japanese line breaking, grapheme-safe editing, selection, and native IME.</p><code>TextBuffer.new</code></div>
|
|
49
|
+
<div class="capability"><h3>Interaction</h3><p>State styles, spatial focus, capture, popovers, modals, transitions, springs, and reduced motion.</p><code>.transition(:background)</code></div>
|
|
50
|
+
<div class="capability"><h3>Semantics</h3><p>A diffed accessibility tree is queryable through NSAccessibility, UI Automation, and AT-SPI.</p><code>window.accessibility_tree</code></div>
|
|
51
|
+
<div class="capability"><h3>Inspection</h3><p>Press F12 for bounds, resolved styles, frame timing, and command counts. Reload Ruby files without dropping entities.</p><code>Zaniah::DevTools.attach</code></div>
|
|
52
|
+
</section>
|
|
53
|
+
|
|
54
|
+
<section class="start" id="start">
|
|
55
|
+
<div>
|
|
56
|
+
<h2>A window is ordinary Ruby</h2>
|
|
57
|
+
<p>The UI layer is opt-in. Use core elements alone, or load the component library when the application needs it.</p>
|
|
58
|
+
<ul><li><a href="components.md">Component reference</a></li><li><a href="animation.md">Animation</a></li><li><a href="accessibility.md">Accessibility</a></li><li><a href="tui.md">Terminal behavior</a></li><li><a href="theme.md">Themes</a></li></ul>
|
|
59
|
+
</div>
|
|
60
|
+
<pre><code>require "zaniah/ui"
|
|
61
|
+
|
|
62
|
+
app = Zaniah::App.new
|
|
63
|
+
app.open_window(backend: :mac) do
|
|
64
|
+
Zaniah::UI::Button.new("Save")
|
|
65
|
+
.icon(:check)
|
|
66
|
+
.on_click { save }
|
|
67
|
+
end
|
|
68
|
+
app.run</code></pre>
|
|
69
|
+
</section>
|
|
70
|
+
</main>
|
|
71
|
+
|
|
72
|
+
<footer><span>Zaniah is MIT licensed.</span><a href="https://github.com/noxdea/zaniah">GitHub</a><a href="https://rubygems.org/gems/zaniah">RubyGems</a></footer>
|
|
73
|
+
</body>
|
|
74
|
+
</html>
|
data/docs/layout.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Layout and scrolling
|
|
2
|
+
|
|
3
|
+
Zaniah uses flex layout by default. Set `display: :grid` for a bounded Grid
|
|
4
|
+
subset with explicit tracks, row-first automatic placement, spans, gaps, `fr`,
|
|
5
|
+
`minmax`, and `auto` tracks.
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
include Zaniah::LengthUnits
|
|
9
|
+
|
|
10
|
+
Zaniah::Div.new.style(
|
|
11
|
+
display: :grid,
|
|
12
|
+
grid_template_columns: [px(120), fr(1), fr(2)],
|
|
13
|
+
column_gap: 8
|
|
14
|
+
).children(items)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Place an item with `grid_column: 2` or span inclusive tracks with
|
|
18
|
+
`grid_column: 1..3`. Named areas and `auto-fit`/`auto-fill` are not supported.
|
|
19
|
+
|
|
20
|
+
`ScrollView` accepts one child and clips it to the viewport. Its `axis` is
|
|
21
|
+
`:vertical`, `:horizontal`, or `:both`; use `scroll_to` for programmatic
|
|
22
|
+
scrolling and `scroll_state` for offsets and edge checks.
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
view = Zaniah::ScrollView.new(axis: :vertical).w(320).h(240).child(content)
|
|
26
|
+
view.scroll_to(120)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`aspect_ratio` derives an unspecified dimension. Logical edge properties such
|
|
30
|
+
as `padding_start` and `margin_end` follow `direction: :ltr` or `:rtl`.
|
|
31
|
+
`position: :sticky` is effective inside a `ScrollView`.
|
data/docs/native.md
CHANGED
|
@@ -28,6 +28,10 @@ window.run
|
|
|
28
28
|
On Linux, pass `display_server: :wayland` or `:x11` to require one display
|
|
29
29
|
server. On macOS, pass `gpu: :opengl` to use OpenGL instead of Metal.
|
|
30
30
|
|
|
31
|
+
For offscreen Vulkan rendering, use `GPU.create(backend: :vulkan, width:, height:)`.
|
|
32
|
+
It accepts the same `Scene` instance layout as Metal, OpenGL, and Software and
|
|
33
|
+
supports capture through `pixels` or `write_png`.
|
|
34
|
+
|
|
31
35
|
Native backends require Ruby's Fiddle library and the platform graphics
|
|
32
36
|
libraries. Fiddle is not a default gem on Ruby 4, so applications using native
|
|
33
37
|
windows must install it. Linux file dialogs and URL opening use `zenity` and
|
|
@@ -62,5 +66,6 @@ ruby examples/linux_smoke.rb --wayland
|
|
|
62
66
|
ruby examples/native_watch.rb
|
|
63
67
|
```
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
on
|
|
69
|
+
Linux XIM composition is exercised in CI with Xvfb and IBus/KKC. Native input,
|
|
70
|
+
IME on macOS and Windows, dialogs, and terminal integration also require manual
|
|
71
|
+
checks on the target operating system.
|
data/docs/site.css
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
@font-face{font-family:Abel;src:url("../assets/fonts/Abel-Regular.ttf") format("truetype");font-display:swap}
|
|
2
|
+
:root{--ground:#f4f7fb;--paper:#fff;--ink:#17233b;--muted:#5f6e85;--blue:#245b9b;--ruby:#c33b52;--rule:#d8e2ef;--code:#eaf0f7;color-scheme:light}
|
|
3
|
+
*{box-sizing:border-box}
|
|
4
|
+
html{scroll-behavior:smooth}
|
|
5
|
+
body{margin:0;background:var(--ground);color:var(--ink);font:16px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif}
|
|
6
|
+
a{color:inherit;text-decoration-thickness:1px;text-underline-offset:.2em}
|
|
7
|
+
a:focus-visible{outline:3px solid var(--ruby);outline-offset:4px}
|
|
8
|
+
.masthead,main,footer{width:min(1180px,calc(100% - 40px));margin-inline:auto}
|
|
9
|
+
.masthead{height:82px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--rule)}
|
|
10
|
+
.brand{display:flex;align-items:center;gap:10px;font:28px/1 Abel,sans-serif;text-decoration:none;letter-spacing:.02em}
|
|
11
|
+
.brand img{object-fit:contain}
|
|
12
|
+
nav{display:flex;gap:24px;font-size:14px}nav a{text-decoration:none}nav a:hover{text-decoration:underline}
|
|
13
|
+
.hero{min-height:570px;display:grid;grid-template-columns:minmax(0,1.45fr) minmax(250px,.55fr);align-items:center;gap:8vw;border-bottom:1px solid var(--rule)}
|
|
14
|
+
h1,h2,h3{font-family:Abel,sans-serif;font-weight:400;margin:0}
|
|
15
|
+
h1{font-size:clamp(64px,10vw,132px);line-height:.82;letter-spacing:-.035em;max-width:760px}
|
|
16
|
+
.hero p{max-width:590px;font-size:clamp(19px,2.1vw,27px);line-height:1.3;color:var(--muted);margin:38px 0 28px}
|
|
17
|
+
.actions{display:flex;gap:20px;align-items:center}.actions a{font-weight:650}.actions .primary{background:var(--blue);color:#fff;padding:11px 17px;text-decoration:none}
|
|
18
|
+
.backends{margin:0;border-top:3px solid var(--ink)}.backends div{display:flex;justify-content:space-between;gap:20px;padding:13px 0;border-bottom:1px solid var(--rule)}
|
|
19
|
+
.backends dt{font-weight:700}.backends dd{margin:0;color:var(--muted);text-align:right}
|
|
20
|
+
.gallery,.capabilities,.start{padding:92px 0;border-bottom:1px solid var(--rule)}
|
|
21
|
+
.section-heading{display:grid;grid-template-columns:1fr 1fr;gap:40px;margin-bottom:40px;align-items:end}
|
|
22
|
+
.section-heading h2,.start h2{font-size:clamp(42px,6vw,76px);line-height:.95}.section-heading p,.start p{max-width:540px;color:var(--muted);margin:0}
|
|
23
|
+
figure{margin:0 0 48px;background:var(--paper);border:1px solid var(--rule);padding:12px}figure img{display:block;width:100%;height:auto}figcaption{font-size:13px;color:var(--muted);padding:10px 2px 0}
|
|
24
|
+
.overlay-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px}.overlay-grid figure{margin:0}
|
|
25
|
+
.capability{display:grid;grid-template-columns:180px 1fr 250px;gap:32px;align-items:baseline;border-top:1px solid var(--rule);padding:22px 0}.capability:last-child{border-bottom:1px solid var(--rule)}
|
|
26
|
+
.capability h3{font-size:28px}.capability p{margin:0;color:var(--muted);max-width:620px}.capability code{background:var(--code);padding:5px 8px;color:var(--blue);justify-self:start}
|
|
27
|
+
.start{display:grid;grid-template-columns:1fr 1fr;gap:8vw}.start p{margin:24px 0}.start ul{columns:2;padding-left:20px}.start li{margin:7px 0}
|
|
28
|
+
pre{margin:0;background:var(--ink);color:#edf4ff;padding:28px;overflow:auto;border-left:5px solid var(--ruby);font-size:14px;line-height:1.65}
|
|
29
|
+
footer{display:flex;gap:24px;padding:28px 0 50px;color:var(--muted);font-size:14px}footer span{margin-right:auto}
|
|
30
|
+
@media(max-width:760px){.masthead{height:auto;padding:18px 0;align-items:flex-start}.masthead nav{display:grid;grid-template-columns:1fr 1fr;gap:6px 18px}.hero{grid-template-columns:1fr;padding:80px 0}.section-heading,.start,.overlay-grid{grid-template-columns:1fr}.capability{grid-template-columns:1fr;gap:4px}.capability code{margin-top:8px}.gallery,.capabilities,.start{padding:64px 0}}
|
|
31
|
+
@media(max-width:480px){.masthead,main,footer{width:min(100% - 24px,1180px)}.brand{font-size:24px}.brand img{width:34px;height:34px}h1{font-size:60px}.hero p{font-size:18px}.actions{align-items:flex-start;flex-direction:column}.start ul{columns:1}footer{flex-wrap:wrap}footer span{width:100%}}
|
|
32
|
+
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
|
data/docs/text.md
CHANGED
|
@@ -88,3 +88,38 @@ ruby --yjit -Ilib script/shaper_oracle assets/fonts/Abel-Regular.ttf /path/to/fo
|
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
The shaping oracle is optional and requires `hb-shape`; it is not used at runtime.
|
|
91
|
+
|
|
92
|
+
## Paragraphs, selection, and editing
|
|
93
|
+
|
|
94
|
+
Single-line layout remains the default. Enable wrapping explicitly on a `Text`
|
|
95
|
+
element or construct a paragraph for layout-only work:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
text = Zaniah::Text.new("長い文章…", wrap: :word, line_height: 1.5,
|
|
99
|
+
letter_spacing: 0.2, align: :start, kinsoku: :push).w(320)
|
|
100
|
+
|
|
101
|
+
paragraph = system.layout_paragraph("Text", width: 320, wrap: :word)
|
|
102
|
+
offset = paragraph.hit_test(Zaniah::Point.new(40, 20))
|
|
103
|
+
point = paragraph.offset_to_point(offset)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Wrapping supports `:none`, `:word`, and `:anywhere`; Japanese kinsoku modes are
|
|
107
|
+
`:push`, `:hanging`, and `:none`. Font fallback is selected per grapheme from the
|
|
108
|
+
requested font through CJK, emoji, symbol, and general system faces. Colored
|
|
109
|
+
COLR, CBDT, and sbix glyphs use the RGBA atlas.
|
|
110
|
+
|
|
111
|
+
Use `selectable` for pointer/keyboard selection and `editable` for a UTF-8 text
|
|
112
|
+
buffer with undo, redo, grapheme-safe changes, and IME composition:
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
buffer = Zaniah::TextBuffer.new("hello")
|
|
116
|
+
field = Zaniah::Text.new(buffer.to_s).editable(buffer)
|
|
117
|
+
|
|
118
|
+
buffer.insert(buffer.bytesize, " 👋")
|
|
119
|
+
buffer.undo.redo
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Offsets are UTF-8 byte offsets and edits must land on extended grapheme-cluster
|
|
123
|
+
boundaries. The string-backed buffer is intended for ordinary fields and
|
|
124
|
+
documents up to tens of thousands of characters; a piece table is deliberately
|
|
125
|
+
outside the current scope.
|
data/docs/theme.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Themes and state styles
|
|
2
|
+
|
|
3
|
+
`Zaniah::Theme.dark`, `.light`, and `.high_contrast` provide semantic color,
|
|
4
|
+
spacing, radius, shadow, typography, and motion tokens. `App` installs the dark
|
|
5
|
+
theme by default and follows native window appearance changes. Read the active
|
|
6
|
+
theme with `FrameContext#theme` or `EntityContext#theme`.
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
theme = cx.theme
|
|
10
|
+
card = Zaniah::Div.new.bg(theme.colors.surface).p(theme.spacing[4])
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
State styles resolve in this order, with later states winning:
|
|
14
|
+
`selected`, `hover`, `active`, `focus`, `focus_visible`, `disabled`.
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
Zaniah::Div.new
|
|
18
|
+
.bg(theme.colors.surface)
|
|
19
|
+
.hover { |style| style.bg(theme.colors.surface_hover) }
|
|
20
|
+
.active { |style| style.bg(theme.colors.surface_pressed) }
|
|
21
|
+
.disabled { |style| style.opacity(0.5) }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Create a custom theme by constructing `Theme` with the same token value objects.
|
|
25
|
+
Public token names are semantic; applications should not depend on the built-in
|
|
26
|
+
themes' raw color values.
|
|
27
|
+
|
|
28
|
+
## Token groups
|
|
29
|
+
|
|
30
|
+
- `colors`: background/surface states, border/focus, text, accent, status,
|
|
31
|
+
overlay, selection, and ring colors.
|
|
32
|
+
- `spacing`: a compact 4-pixel scale indexed by token number.
|
|
33
|
+
- `radii` and `shadows`: semantic `none`, `sm`, `md`, `lg`, and `full` values.
|
|
34
|
+
- `typography`: sans/mono families, six sizes, four weights, and three line heights.
|
|
35
|
+
- `motion`: fast/base/slow durations, easing names, and `reduced?`.
|
|
36
|
+
|
|
37
|
+
Records support `with`, so a local override does not mutate the shared theme:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
base = Zaniah::Theme.light
|
|
41
|
+
quiet = base.with(
|
|
42
|
+
colors: base.colors.with(accent: Zaniah::Color.parse("#245b9b")),
|
|
43
|
+
motion: base.motion.with(reduced: true)
|
|
44
|
+
)
|
|
45
|
+
app.global(:theme, quiet)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Native windows select dark or light initially and refresh the application theme
|
|
49
|
+
when system appearance changes. A system reduced-motion preference sets
|
|
50
|
+
`theme.motion.reduced?` and collapses animation durations to zero.
|
data/docs/tui.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# TUI component degradation
|
|
2
|
+
|
|
3
|
+
`Platform.open_window(backend: :tui)` renders the ordinary element tree as terminal
|
|
4
|
+
cells. `Component#tui_cells` also exposes the deterministic fallback used by snapshot
|
|
5
|
+
tests and non-window integrations.
|
|
6
|
+
|
|
7
|
+
| Component family | Terminal representation |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| Label, Badge, Avatar | text, `[badge]`, `(initials)` |
|
|
10
|
+
| Icon | Unicode check/close/search/menu/info/warning symbol; unlabeled unknown icons are omitted |
|
|
11
|
+
| Divider, Card | `─`/`│` and box-drawing characters |
|
|
12
|
+
| Skeleton, Spinner, ProgressBar, Meter | shade cells, `◌`, or a ten-cell progress track; animation jumps to its terminal state |
|
|
13
|
+
| Button, IconButton, ToggleButton | `[ label ]`; disabled uses `( label )` |
|
|
14
|
+
| Checkbox, Radio, Switch | `[x]`, `(o)`, `[on ]` |
|
|
15
|
+
| Slider, RangeSlider | ten-cell track and numeric value/range |
|
|
16
|
+
| Text inputs | `[value]`; password values are masked and validation errors have `!` |
|
|
17
|
+
| Select, Combobox, MultiSelect | labeled brackets, filtered menu marker, or comma-separated selected values |
|
|
18
|
+
| DatePicker, TimePicker, ColorPicker | labeled ISO date, 24-hour time, or hex color in brackets |
|
|
19
|
+
| Tooltip, Popover, Menu, Dropdown | status text or a box/menu with `>` selection marker |
|
|
20
|
+
| Tabs, Accordion, Collapsible | selected tab in brackets and `[+]`/`[-]` disclosure markers |
|
|
21
|
+
| ScrollView, Scrollbar | clipped child content and `│`/`─` track |
|
|
22
|
+
| Breadcrumb, Pagination, bars | slash-separated path, page status, space-separated content |
|
|
23
|
+
| Modal, Dialog, Drawer, CommandPalette | box-drawing overlay; focus stays inside until dismissed |
|
|
24
|
+
| SplitPane, Resizable, DockPanel | `│`/`─` separators, resize corner, or ordered dock regions |
|
|
25
|
+
| ListView | visible rows with `>` on the selected item |
|
|
26
|
+
| Toast | live status text |
|
|
27
|
+
| Table, DataGrid | header and visible rows separated with `|` |
|
|
28
|
+
| TreeView | indentation with `▸`/`▾` expansion markers |
|
|
29
|
+
| Sparkline, LineChart, BarChart | eight-level Unicode sparkline summary |
|
|
30
|
+
| Form, FormField | labeled values, `!` errors, and a submit button |
|
|
31
|
+
| CodeEditor, RichText | numbered plain-text lines or concatenated text runs |
|
|
32
|
+
|
|
33
|
+
Rounded corners, shadows, gradients, and alpha scrims reduce to solid cells. Focus is
|
|
34
|
+
reported by the terminal cursor/reverse-video capability; motion introduced in M7 is
|
|
35
|
+
sampled at its final state. New components must document text, focus, disabled, and
|
|
36
|
+
overlay behavior in this table before release.
|
data/examples/gallery.rb
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
4
|
+
require "zaniah/ui"
|
|
5
|
+
|
|
6
|
+
module Gallery
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def section(title, *children, height: nil)
|
|
10
|
+
card = Zaniah::UI::Card.new(Zaniah::UI::Label.new(title, size: :lg),
|
|
11
|
+
Zaniah::Div.new.flex_row.style(flex_wrap: :wrap).gap(10).children(children.flatten.compact))
|
|
12
|
+
card.h(height) if height
|
|
13
|
+
card
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def page
|
|
17
|
+
clicks = ->(*) {}
|
|
18
|
+
tabs = Zaniah::UI::Tabs.new([["Overview", Zaniah::UI::Label.new("Overview panel")], ["Details", Zaniah::UI::Label.new("Details panel")]]).w(220)
|
|
19
|
+
rows = Array.new(40) { |index| {id: index + 1, name: "Record #{index + 1}", status: index.even? ? "Ready" : "Queued"} }
|
|
20
|
+
validation = Zaniah::UI::Validation.new.required.format(/@/, message: "Enter an email address")
|
|
21
|
+
foundation = Zaniah::Div.new.w_full.gap(8)
|
|
22
|
+
.child(Zaniah::Div.new.flex_row.items_center.gap(12).children([
|
|
23
|
+
Zaniah::UI::Label.new("Label"), Zaniah::UI::Icon.new(:info, label: "Information"),
|
|
24
|
+
Zaniah::UI::Badge.new("New", variant: :accent), Zaniah::UI::Avatar.new("Ruby UI"),
|
|
25
|
+
Zaniah::UI::Spacer.new(4), Zaniah::UI::Skeleton.new]))
|
|
26
|
+
.child(Zaniah::UI::Divider.new)
|
|
27
|
+
.child(Zaniah::UI::EmptyState.new("No results", message: "Try another query").h(64))
|
|
28
|
+
button_variants = %i[primary secondary ghost danger].map do |variant|
|
|
29
|
+
Zaniah::Div.new.flex_row.gap(8).children(%i[sm md lg].map { |size| Zaniah::UI::Button.new("#{variant} #{size}", variant: variant, size: size) })
|
|
30
|
+
end
|
|
31
|
+
variants = Zaniah::Div.new.w_full.gap(8).children(button_variants)
|
|
32
|
+
.child(Zaniah::Div.new.flex_row.gap(8).children(%i[neutral accent success warning danger].map { |variant| Zaniah::UI::Badge.new(variant, variant: variant) }))
|
|
33
|
+
.child(Zaniah::Div.new.flex_row.gap(14).children(%i[default muted inverse].map { |tone| Zaniah::UI::Label.new(tone, tone: tone) }))
|
|
34
|
+
.child(Zaniah::Div.new.flex_row.items_center.gap(14).children(%i[xs sm md lg xl].map { |size| Zaniah::UI::Label.new(size, size: size) }))
|
|
35
|
+
structure = Zaniah::Div.new.w_full.gap(10)
|
|
36
|
+
.child(Zaniah::Div.new.flex_row.gap(12).children([tabs,
|
|
37
|
+
Zaniah::UI::Accordion.new([["Section one", Zaniah::UI::Label.new("Content")], ["Section two", Zaniah::UI::Label.new("More")]]).w(240),
|
|
38
|
+
Zaniah::UI::Collapsible.new("Disclosure", Zaniah::UI::Label.new("Visible"), open: true).w(210),
|
|
39
|
+
Zaniah::UI::Sidebar.new(Zaniah::UI::Label.new("Sidebar", size: :sm), width: 100).h(80)]))
|
|
40
|
+
.child(Zaniah::UI::Toolbar.new(Zaniah::UI::Button.new("New", size: :sm), Zaniah::UI::Button.new("Edit", size: :sm)))
|
|
41
|
+
.child(Zaniah::UI::StatusBar.new(Zaniah::UI::Label.new("Ready", size: :sm)))
|
|
42
|
+
data = Zaniah::Div.new.w_full.gap(12)
|
|
43
|
+
.child(Zaniah::Div.new.flex_row.gap(12).children([
|
|
44
|
+
Zaniah::UI::Table.new(rows.first(8), columns: [{key: :id, width: 54}, {key: :name, width: 136}], height: 190, row_key: ->(row) { row[:id] }).w(200),
|
|
45
|
+
Zaniah::UI::DataGrid.new(rows, columns: [{key: :id, width: 54}, {key: :name, width: 130, editable: true}, {key: :status, width: 90}], height: 190, row_key: ->(row) { row[:id] }).w(285),
|
|
46
|
+
Zaniah::UI::TreeView.new([{id: :src, label: "lib", children: [{id: :core, label: "zaniah.rb"}, {id: :ui, label: "ui", children: ["controls.rb", "data.rb"]}]}], height: 190).w(240)]))
|
|
47
|
+
.child(Zaniah::Div.new.flex_row.gap(12).children([
|
|
48
|
+
Zaniah::UI::Sparkline.new([2, 5, 3, 8, 6, 9], width: 190, height: 120, label: "Frames"),
|
|
49
|
+
Zaniah::UI::LineChart.new({Frames: [2, 5, 3, 8, 6, 9], Input: [1, 2, 2, 4, 3, 5]}, width: 300, height: 150),
|
|
50
|
+
Zaniah::UI::BarChart.new({GPU: [4, 7, 6], TUI: [3, 4, 5]}, width: 300, height: 150)]))
|
|
51
|
+
choices = Zaniah::Div.new.w_full.gap(10)
|
|
52
|
+
.child(Zaniah::Div.new.flex_row.gap(12).children([
|
|
53
|
+
Zaniah::UI::Select.new([["Ruby", :ruby], ["Zig", :zig]], value: :ruby),
|
|
54
|
+
Zaniah::UI::Combobox.new(%w[Ruby Zig Rust], value: "Ruby", label: "Language"),
|
|
55
|
+
Zaniah::UI::MultiSelect.new(%w[GPU TUI A11y], value: %w[GPU A11y], label: "Targets")]))
|
|
56
|
+
.child(Zaniah::Div.new.flex_row.gap(12).children([
|
|
57
|
+
Zaniah::UI::DatePicker.new("2026-09-13"), Zaniah::UI::TimePicker.new("14:30"),
|
|
58
|
+
Zaniah::UI::ColorPicker.new("#2563eb")]))
|
|
59
|
+
workspaces = Zaniah::Div.new.w_full.gap(12)
|
|
60
|
+
.child(Zaniah::Div.new.flex_row.gap(12).children([
|
|
61
|
+
Zaniah::UI::SplitPane.new(Zaniah::UI::Label.new("Left pane"), Zaniah::UI::Label.new("Right pane")).w(300).h(100),
|
|
62
|
+
Zaniah::UI::Resizable.new(Zaniah::UI::Label.new("Resize me"), width: 180, height: 100),
|
|
63
|
+
Zaniah::UI::ListView.new(%w[Alpha Beta Gamma Delta], height: 100, selected: 1).w(180)]))
|
|
64
|
+
.child(Zaniah::UI::DockPanel.new(
|
|
65
|
+
center: Zaniah::UI::Label.new("Editor"), top: Zaniah::UI::StatusBar.new(Zaniah::UI::Label.new("Top", size: :sm)),
|
|
66
|
+
left: Zaniah::UI::Sidebar.new(Zaniah::UI::Label.new("Files", size: :sm), width: 90),
|
|
67
|
+
bottom: Zaniah::UI::StatusBar.new(Zaniah::UI::Label.new("Ready", size: :sm))).w(420).h(120))
|
|
68
|
+
editors = Zaniah::Div.new.w_full.flex_row.gap(12).children([
|
|
69
|
+
Zaniah::UI::CodeEditor.new("def hello\n :world\nend\n", language: :ruby).w(390).h(150),
|
|
70
|
+
Zaniah::UI::RichText.new([{text: "Rich ", size: 20}, {text: "text", color: "#2563eb", size: 20}]).w(260)])
|
|
71
|
+
Zaniah::Div.new.h(2480).p(16).gap(12)
|
|
72
|
+
.child(section("Foundation", foundation, height: 180))
|
|
73
|
+
.child(section("Actions",
|
|
74
|
+
Zaniah::UI::Button.new("Primary").icon(:check).on_click(&clicks),
|
|
75
|
+
Zaniah::UI::IconButton.new(:menu, label: "Menu", variant: :secondary),
|
|
76
|
+
Zaniah::UI::ToggleButton.new("Toggle"),
|
|
77
|
+
Zaniah::UI::ButtonGroup.new(Zaniah::UI::Button.new("One", size: :sm), Zaniah::UI::Button.new("Two", size: :sm)),
|
|
78
|
+
Zaniah::UI::Checkbox.new("Mixed", value: :mixed), Zaniah::UI::Radio.new("Radio", value: true),
|
|
79
|
+
Zaniah::UI::RadioGroup.new([["A", :a], ["B", :b]], value: :a), Zaniah::UI::Switch.new("Switch", value: true)))
|
|
80
|
+
.child(section("Values",
|
|
81
|
+
Zaniah::UI::Slider.new(value: 35, label: "Slider"), Zaniah::UI::RangeSlider.new(value: [20, 80], label: "Range"),
|
|
82
|
+
Zaniah::UI::ProgressBar.new(value: 60), Zaniah::UI::Spinner.new, Zaniah::UI::Meter.new(value: 72, low: 25, high: 80)))
|
|
83
|
+
.child(section("Text input",
|
|
84
|
+
Zaniah::UI::TextField.new("Ruby", label: "Text field", clearable: true),
|
|
85
|
+
Zaniah::UI::TextArea.new("Multiple\nlines", label: "Text area", rows: 3),
|
|
86
|
+
Zaniah::UI::SearchInput.new("", placeholder: "Search"), Zaniah::UI::PasswordInput.new("secret", label: "Password"),
|
|
87
|
+
Zaniah::UI::NumberInput.new(4, min: 0, max: 10), Zaniah::UI::TagInput.new(%w[ruby ui], placeholder: "Tags")))
|
|
88
|
+
.child(section("Navigation",
|
|
89
|
+
Zaniah::UI::MenuBar.new([["File", [["Open", clicks], ["Disabled", nil]]]]),
|
|
90
|
+
Zaniah::UI::Dropdown.new("Choose", items: [["First", 1], ["Second", 2]]),
|
|
91
|
+
Zaniah::UI::Breadcrumb.new([["Home", clicks], ["Gallery", nil]]), Zaniah::UI::Pagination.new(page: 4, pages: 12)))
|
|
92
|
+
.child(section("Structure", structure, height: 235))
|
|
93
|
+
.child(section("Variants", variants, height: 285))
|
|
94
|
+
.child(section("Data", data, height: 440))
|
|
95
|
+
.child(section("Choice and pickers", choices, height: 210))
|
|
96
|
+
.child(section("Workspace layout", workspaces, height: 300))
|
|
97
|
+
.child(section("Editors", editors, height: 210))
|
|
98
|
+
.child(section("Forms", Zaniah::UI::Form.new.field(name: :email, label: "Release email", value: "ruby@example.com", validation: validation).w(320), height: 150))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def overlay(name, window)
|
|
102
|
+
anchor = Zaniah::Bounds.new(280, 80, 80, 32)
|
|
103
|
+
content = Zaniah::UI::Label.new("Overlay content")
|
|
104
|
+
items = [["First command", ->(*) {}], ["Unavailable", nil], ["Close", ->(*) {}]]
|
|
105
|
+
case name
|
|
106
|
+
when /\Atooltip-(top|bottom|left|right)\z/ then Zaniah::UI::Tooltip.new("Helpful text", anchor: anchor, side: Regexp.last_match(1).to_sym)
|
|
107
|
+
when /\Apopover-(top|bottom|left|right)\z/ then Zaniah::UI::Popover.new(content, anchor: anchor, side: Regexp.last_match(1).to_sym)
|
|
108
|
+
when /\Adrawer-(left|right)\z/ then Zaniah::UI::Drawer.new(content, title: "Drawer", side: Regexp.last_match(1).to_sym)
|
|
109
|
+
when /\Atoast-(info|success|warning|danger)\z/ then Zaniah::UI::Toast.new("Saved", variant: Regexp.last_match(1).to_sym)
|
|
110
|
+
when "tooltip" then Zaniah::UI::Tooltip.new("Helpful text", anchor: anchor)
|
|
111
|
+
when "popover" then Zaniah::UI::Popover.new(content, anchor: anchor)
|
|
112
|
+
when "menu" then Zaniah::UI::Menu.new(items, anchor: Zaniah::Point.new(anchor.x, anchor.bottom))
|
|
113
|
+
when "context-menu" then Zaniah::UI::ContextMenu.new(items, anchor: Zaniah::Point.new(anchor.x, anchor.bottom))
|
|
114
|
+
when "modal" then Zaniah::UI::Modal.new(content, title: "Modal")
|
|
115
|
+
when "dialog" then Zaniah::UI::Dialog.new(content, title: "Dialog")
|
|
116
|
+
when "drawer" then Zaniah::UI::Drawer.new(content, title: "Drawer")
|
|
117
|
+
when "toast" then Zaniah::UI::Toast.new("Saved", variant: :success)
|
|
118
|
+
when "command-palette" then Zaniah::UI::CommandPalette.new(items, open: true)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
if $PROGRAM_NAME == __FILE__
|
|
124
|
+
backend_option = ARGV.find { |argument| argument.start_with?("--backend=") }&.split("=", 2)&.last || "headless"
|
|
125
|
+
backend = if backend_option == "native"
|
|
126
|
+
RUBY_PLATFORM.include?("darwin") ? :mac : RUBY_PLATFORM.match?(/mingw|mswin/) ? :windows : :linux
|
|
127
|
+
else
|
|
128
|
+
backend_option.to_sym
|
|
129
|
+
end
|
|
130
|
+
window = Zaniah::Platform.open_window(backend: backend, width: 900, height: 700, title: "Zaniah component gallery")
|
|
131
|
+
unless backend == :tui
|
|
132
|
+
require "alhena"
|
|
133
|
+
font_path = File.expand_path("../assets/fonts/Abel-Regular.ttf", __dir__)
|
|
134
|
+
window.text_system = Zaniah::TextSystem::Renderer.new(font: Alhena::Font.open(font_path), font_db: Zaniah::TextSystem::FontDB.new(paths: []))
|
|
135
|
+
end
|
|
136
|
+
overlay_name = ARGV.find { |argument| argument.start_with?("--overlay=") }&.split("=", 2)&.last
|
|
137
|
+
scroll = Zaniah::ScrollView.new(scrollbar: :overlay).child(Gallery.page)
|
|
138
|
+
root = Zaniah::Div.new.child(scroll)
|
|
139
|
+
root.child(Gallery.overlay(overlay_name, window)) if overlay_name
|
|
140
|
+
window.draw { root }
|
|
141
|
+
|
|
142
|
+
if ARGV.include?("--check")
|
|
143
|
+
window.tick
|
|
144
|
+
output = ARGV.find { |argument| argument.end_with?(".png") }
|
|
145
|
+
window.write_png(output) if output && backend != :tui
|
|
146
|
+
raise "gallery did not render" if window.scene.commands.empty?
|
|
147
|
+
if !%i[headless tui].include?(backend) && (!window.device.respond_to?(:draw_calls) || !window.device.draw_calls&.positive?)
|
|
148
|
+
raise "gallery produced no native GPU draw calls"
|
|
149
|
+
end
|
|
150
|
+
puts "gallery: #{backend} rendered #{window.scene.commands.length} scene commands"
|
|
151
|
+
window.close
|
|
152
|
+
else
|
|
153
|
+
window.run
|
|
154
|
+
end
|
|
155
|
+
end
|
data/examples/native_ime.rb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Run inside a disposable D-Bus/X11 session with IBus
|
|
3
|
+
# Run inside a disposable D-Bus/X11 session with a Japanese IBus engine installed.
|
|
4
4
|
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
5
5
|
require "zaniah"
|
|
6
6
|
require "open3"
|
|
7
7
|
|
|
8
|
+
engine = "kkc"
|
|
9
|
+
|
|
8
10
|
window = Zaniah::Platform.open_window(backend: :linux, display_server: :x11, width: 400, height: 240, title: "IBus Japanese input check")
|
|
9
11
|
events = []
|
|
10
12
|
window.on_input { |event| events << event }
|
|
@@ -17,7 +19,7 @@ while Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline
|
|
|
17
19
|
window.tick
|
|
18
20
|
sleep 0.01
|
|
19
21
|
end
|
|
20
|
-
selector = Process.spawn("ibus", "engine",
|
|
22
|
+
selector = Process.spawn("ibus", "engine", engine)
|
|
21
23
|
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 5
|
|
22
24
|
until (result = Process.waitpid2(selector, Process::WNOHANG))
|
|
23
25
|
raise "IBus engine selection timed out" if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
|
|
@@ -25,9 +27,14 @@ until (result = Process.waitpid2(selector, Process::WNOHANG))
|
|
|
25
27
|
sleep 0.01
|
|
26
28
|
end
|
|
27
29
|
current, = Open3.capture2e("ibus", "engine")
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
raise "
|
|
30
|
+
# Engine selection may report setxkbmap's status after switching successfully.
|
|
31
|
+
# The active engine determines readiness.
|
|
32
|
+
raise "IBus engine selection failed: #{result.last.inspect}, current=#{current.inspect}" unless current.strip == engine
|
|
33
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 1
|
|
34
|
+
while Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline
|
|
35
|
+
window.tick
|
|
36
|
+
sleep 0.01
|
|
37
|
+
end
|
|
31
38
|
sender = Process.spawn("xdotool", "type", "--delay", "100", "nihon")
|
|
32
39
|
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 3
|
|
33
40
|
until Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
|
|
@@ -46,5 +53,5 @@ preedit = events.grep(Zaniah::Input::Composition).map(&:text)
|
|
|
46
53
|
committed = events.grep(Zaniah::Input::TextInput).map(&:text).join
|
|
47
54
|
raise "Japanese preedit missing: #{events.inspect}" unless preedit.any? { |text| text.match?(/[にほ日]/) }
|
|
48
55
|
raise "Japanese commit missing or duplicated: #{events.inspect}" unless ["日本", "にほん"].include?(committed)
|
|
49
|
-
puts "IBus
|
|
56
|
+
puts "IBus/#{engine}: preedit=#{preedit.inspect}, committed=#{committed.inspect}"
|
|
50
57
|
window.close
|