zaniah 0.2.0 → 0.4.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.
Files changed (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/LICENSE.txt +18 -0
  4. data/README.md +14 -4
  5. data/assets/icons/check.svg +1 -0
  6. data/assets/icons/close.svg +1 -0
  7. data/assets/icons/info.svg +1 -0
  8. data/assets/icons/menu.svg +1 -0
  9. data/assets/icons/search.svg +1 -0
  10. data/assets/icons/warning.svg +1 -0
  11. data/docs/accessibility.md +61 -0
  12. data/docs/adr/004-style-resolution.md +17 -0
  13. data/docs/adr/005-instance-layout.md +43 -0
  14. data/docs/adr/006-component-model.md +17 -0
  15. data/docs/adr/007-text-buffer.md +17 -0
  16. data/docs/adr/008-accessibility-bridge.md +19 -0
  17. data/docs/adr/009-grid-scope.md +17 -0
  18. data/docs/adr/010-text-overlay-layout.md +24 -0
  19. data/docs/animation.md +46 -0
  20. data/docs/components.md +109 -0
  21. data/docs/gallery/command-palette-dark.png +0 -0
  22. data/docs/gallery/command-palette-high_contrast.png +0 -0
  23. data/docs/gallery/command-palette-light.png +0 -0
  24. data/docs/gallery/components-dark.png +0 -0
  25. data/docs/gallery/components-high_contrast.png +0 -0
  26. data/docs/gallery/components-light.png +0 -0
  27. data/docs/gallery/context-menu-dark.png +0 -0
  28. data/docs/gallery/context-menu-high_contrast.png +0 -0
  29. data/docs/gallery/context-menu-light.png +0 -0
  30. data/docs/gallery/dialog-dark.png +0 -0
  31. data/docs/gallery/dialog-high_contrast.png +0 -0
  32. data/docs/gallery/dialog-light.png +0 -0
  33. data/docs/gallery/drawer-left-dark.png +0 -0
  34. data/docs/gallery/drawer-left-high_contrast.png +0 -0
  35. data/docs/gallery/drawer-left-light.png +0 -0
  36. data/docs/gallery/drawer-right-dark.png +0 -0
  37. data/docs/gallery/drawer-right-high_contrast.png +0 -0
  38. data/docs/gallery/drawer-right-light.png +0 -0
  39. data/docs/gallery/menu-dark.png +0 -0
  40. data/docs/gallery/menu-high_contrast.png +0 -0
  41. data/docs/gallery/menu-light.png +0 -0
  42. data/docs/gallery/modal-dark.png +0 -0
  43. data/docs/gallery/modal-high_contrast.png +0 -0
  44. data/docs/gallery/modal-light.png +0 -0
  45. data/docs/gallery/popover-bottom-dark.png +0 -0
  46. data/docs/gallery/popover-bottom-high_contrast.png +0 -0
  47. data/docs/gallery/popover-bottom-light.png +0 -0
  48. data/docs/gallery/popover-left-dark.png +0 -0
  49. data/docs/gallery/popover-left-high_contrast.png +0 -0
  50. data/docs/gallery/popover-left-light.png +0 -0
  51. data/docs/gallery/popover-right-dark.png +0 -0
  52. data/docs/gallery/popover-right-high_contrast.png +0 -0
  53. data/docs/gallery/popover-right-light.png +0 -0
  54. data/docs/gallery/popover-top-dark.png +0 -0
  55. data/docs/gallery/popover-top-high_contrast.png +0 -0
  56. data/docs/gallery/popover-top-light.png +0 -0
  57. data/docs/gallery/toast-danger-dark.png +0 -0
  58. data/docs/gallery/toast-danger-high_contrast.png +0 -0
  59. data/docs/gallery/toast-danger-light.png +0 -0
  60. data/docs/gallery/toast-info-dark.png +0 -0
  61. data/docs/gallery/toast-info-high_contrast.png +0 -0
  62. data/docs/gallery/toast-info-light.png +0 -0
  63. data/docs/gallery/toast-success-dark.png +0 -0
  64. data/docs/gallery/toast-success-high_contrast.png +0 -0
  65. data/docs/gallery/toast-success-light.png +0 -0
  66. data/docs/gallery/toast-warning-dark.png +0 -0
  67. data/docs/gallery/toast-warning-high_contrast.png +0 -0
  68. data/docs/gallery/toast-warning-light.png +0 -0
  69. data/docs/gallery/tooltip-bottom-dark.png +0 -0
  70. data/docs/gallery/tooltip-bottom-high_contrast.png +0 -0
  71. data/docs/gallery/tooltip-bottom-light.png +0 -0
  72. data/docs/gallery/tooltip-left-dark.png +0 -0
  73. data/docs/gallery/tooltip-left-high_contrast.png +0 -0
  74. data/docs/gallery/tooltip-left-light.png +0 -0
  75. data/docs/gallery/tooltip-right-dark.png +0 -0
  76. data/docs/gallery/tooltip-right-high_contrast.png +0 -0
  77. data/docs/gallery/tooltip-right-light.png +0 -0
  78. data/docs/gallery/tooltip-top-dark.png +0 -0
  79. data/docs/gallery/tooltip-top-high_contrast.png +0 -0
  80. data/docs/gallery/tooltip-top-light.png +0 -0
  81. data/docs/index.html +74 -0
  82. data/docs/layout.md +31 -0
  83. data/docs/native.md +7 -2
  84. data/docs/site.css +32 -0
  85. data/docs/text.md +54 -0
  86. data/docs/theme.md +50 -0
  87. data/docs/tui.md +36 -0
  88. data/examples/gallery.rb +155 -0
  89. data/examples/native_ime.rb +13 -6
  90. data/lib/zaniah/accessibility/linux/service.rb +361 -0
  91. data/lib/zaniah/accessibility/linux.rb +23 -0
  92. data/lib/zaniah/accessibility/mac.rb +136 -0
  93. data/lib/zaniah/accessibility/native_tree.rb +53 -0
  94. data/lib/zaniah/accessibility/node.rb +12 -0
  95. data/lib/zaniah/accessibility/tree.rb +61 -0
  96. data/lib/zaniah/accessibility/windows/provider.rb +273 -0
  97. data/lib/zaniah/accessibility/windows.rb +38 -0
  98. data/lib/zaniah/accessibility.rb +63 -0
  99. data/lib/zaniah/animation/animator.rb +95 -0
  100. data/lib/zaniah/animation/easing.rb +45 -0
  101. data/lib/zaniah/animation/spring.rb +36 -0
  102. data/lib/zaniah/animation/transition.rb +5 -0
  103. data/lib/zaniah/animation.rb +6 -0
  104. data/lib/zaniah/app.rb +22 -2
  105. data/lib/zaniah/color.rb +16 -0
  106. data/lib/zaniah/devtools/hot_reload.rb +40 -0
  107. data/lib/zaniah/devtools/inspector.rb +111 -0
  108. data/lib/zaniah/devtools/stats_overlay.rb +16 -0
  109. data/lib/zaniah/devtools.rb +13 -0
  110. data/lib/zaniah/element.rb +210 -15
  111. data/lib/zaniah/frame_context.rb +4 -1
  112. data/lib/zaniah/gpu/frame_encoder.rb +10 -1
  113. data/lib/zaniah/gpu/instance_packing.rb +33 -5
  114. data/lib/zaniah/gpu/metal.rb +26 -10
  115. data/lib/zaniah/gpu/open_gl.rb +35 -14
  116. data/lib/zaniah/gpu/software.rb +116 -14
  117. data/lib/zaniah/gpu/vulkan/scene.frag +51 -0
  118. data/lib/zaniah/gpu/vulkan/scene.frag.spv +0 -0
  119. data/lib/zaniah/gpu/vulkan/scene.vert +50 -0
  120. data/lib/zaniah/gpu/vulkan/scene.vert.spv +0 -0
  121. data/lib/zaniah/gpu/vulkan.rb +282 -36
  122. data/lib/zaniah/gpu.rb +8 -3
  123. data/lib/zaniah/input/dispatcher.rb +96 -12
  124. data/lib/zaniah/input/focus_handle.rb +13 -4
  125. data/lib/zaniah/input/focus_tree.rb +77 -0
  126. data/lib/zaniah/input/keymap.rb +77 -0
  127. data/lib/zaniah/input.rb +1 -0
  128. data/lib/zaniah/interactivity.rb +30 -0
  129. data/lib/zaniah/layout/engine.rb +178 -36
  130. data/lib/zaniah/layout/grid.rb +134 -0
  131. data/lib/zaniah/layout/style.rb +16 -1
  132. data/lib/zaniah/layout.rb +1 -0
  133. data/lib/zaniah/length.rb +3 -0
  134. data/lib/zaniah/length_units.rb +7 -0
  135. data/lib/zaniah/list.rb +27 -15
  136. data/lib/zaniah/platform/appearance.rb +11 -0
  137. data/lib/zaniah/platform/headless/window.rb +101 -9
  138. data/lib/zaniah/platform/linux/appearance_aware.rb +8 -0
  139. data/lib/zaniah/platform/linux/wayland_window.rb +3 -1
  140. data/lib/zaniah/platform/linux/window.rb +4 -2
  141. data/lib/zaniah/platform/mac/app.rb +1 -1
  142. data/lib/zaniah/platform/mac/core_text.rb +3 -0
  143. data/lib/zaniah/platform/mac/window.rb +9 -1
  144. data/lib/zaniah/platform/tui/window.rb +1 -1
  145. data/lib/zaniah/platform/windows/window.rb +12 -2
  146. data/lib/zaniah/platform.rb +1 -0
  147. data/lib/zaniah/scene.rb +137 -28
  148. data/lib/zaniah/scroll_state.rb +117 -0
  149. data/lib/zaniah/scroll_view.rb +94 -0
  150. data/lib/zaniah/style/gradient.rb +28 -0
  151. data/lib/zaniah/style/ring.rb +13 -0
  152. data/lib/zaniah/style/shadow.rb +13 -0
  153. data/lib/zaniah/style/style_set.rb +43 -0
  154. data/lib/zaniah/style/transform.rb +40 -0
  155. data/lib/zaniah/svg.rb +19 -0
  156. data/lib/zaniah/text.rb +499 -7
  157. data/lib/zaniah/text_buffer.rb +119 -0
  158. data/lib/zaniah/text_selection.rb +18 -0
  159. data/lib/zaniah/text_system/atlas_cache.rb +1 -1
  160. data/lib/zaniah/text_system/fallback.rb +24 -0
  161. data/lib/zaniah/text_system/font_db.rb +4 -1
  162. data/lib/zaniah/text_system/kinsoku.rb +28 -0
  163. data/lib/zaniah/text_system/line_breaker.rb +58 -0
  164. data/lib/zaniah/text_system/paragraph.rb +246 -0
  165. data/lib/zaniah/text_system/renderer.rb +13 -2
  166. data/lib/zaniah/text_system/typesetter.rb +4 -0
  167. data/lib/zaniah/text_system.rb +3 -0
  168. data/lib/zaniah/theme.rb +39 -0
  169. data/lib/zaniah/ui/charts.rb +165 -0
  170. data/lib/zaniah/ui/choice_fields.rb +134 -0
  171. data/lib/zaniah/ui/component.rb +73 -0
  172. data/lib/zaniah/ui/controls.rb +439 -0
  173. data/lib/zaniah/ui/data.rb +364 -0
  174. data/lib/zaniah/ui/editors.rb +67 -0
  175. data/lib/zaniah/ui/forms.rb +131 -0
  176. data/lib/zaniah/ui/layout_components.rb +205 -0
  177. data/lib/zaniah/ui/modal.rb +129 -0
  178. data/lib/zaniah/ui/overlays.rb +282 -0
  179. data/lib/zaniah/ui/pickers.rb +169 -0
  180. data/lib/zaniah/ui/primitives.rb +199 -0
  181. data/lib/zaniah/ui/scrollbar.rb +104 -0
  182. data/lib/zaniah/ui/structure.rb +224 -0
  183. data/lib/zaniah/ui/text_inputs.rb +169 -0
  184. data/lib/zaniah/ui/variants.rb +23 -0
  185. data/lib/zaniah/ui.rb +18 -0
  186. data/lib/zaniah/unicode.rb +42 -0
  187. data/lib/zaniah/uniform_list.rb +27 -6
  188. data/lib/zaniah/version.rb +1 -1
  189. data/lib/zaniah.rb +14 -0
  190. data/sig/accessibility.rbs +34 -0
  191. data/sig/animation.rbs +54 -0
  192. data/sig/app.rbs +2 -1
  193. data/sig/devtools.rbs +37 -0
  194. data/sig/elements.rbs +108 -6
  195. data/sig/gpu.rbs +9 -2
  196. data/sig/input.rbs +31 -5
  197. data/sig/layout.rbs +2 -1
  198. data/sig/platform.rbs +24 -4
  199. data/sig/scene.rbs +15 -2
  200. data/sig/style.rbs +82 -0
  201. data/sig/text.rbs +86 -0
  202. data/sig/theme.rbs +64 -0
  203. data/sig/ui.rbs +390 -0
  204. data/sig/zaniah.rbs +56 -3
  205. data/tools/generate_component_gallery.rb +48 -0
  206. metadata +151 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e74156d5d7a3d4ed439e9d46aac5e8fb25b65e974013937dcc1743a5ae1aa665
4
- data.tar.gz: ad0468ac4a87fc3181974ad2b905a515b3c414e30df4b190ba07b220c3ecfbc6
3
+ metadata.gz: f6787d1edc51a6a692859fe62834270cfa2339ade8b00e596e8861632eb59913
4
+ data.tar.gz: 7407abf9f7a64c80fc307843ffeadd9d130cfb45a12673a85f6cbedf2710a3e4
5
5
  SHA512:
6
- metadata.gz: f65083c0483107b854879b2303b4bb87d4c05debcaba6167d7335596628d5ccab4d4ccf310d99fd2c10958a3edefa7355d9d9d0229541dc1bc6cba639c8ffc87
7
- data.tar.gz: 5881db80886ae7543493bdaebc459301968075b1a41a375e93b0f91a1566829bd5c3169190ff342cfed7e4bd2dc2b573827e7331f2e920a22fd6e2c874d34242
6
+ metadata.gz: 35cdfc811f06f704d21202e60029c2869eb5b01698c4e52cde3ce2b7c0e45eaa0c6c5a60ec46e9e9f4e15fd2e1b7f88a75b71b9ec829423540259238739cf468
7
+ data.tar.gz: beaae799ef7227cf30d354819b4d896ee1957666c1617d4a43a15fd8d0112180b0dcf74dadf930c6df8376eaa0045663c83400cca2f6bf019142ab9cb2af0592
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.4.0 — 2026-09-15
6
+
7
+ - Add inline and block text overlays with wrapping-aware layout, hit testing, and row-local relayout.
8
+
9
+ ## 0.3.0 — 2026-09-14
10
+
11
+ - Improve large flex layouts, GPU scene submission, and software-rendered gradients for smoother frames.
12
+ - Add dark, light, and high-contrast themes with immediate hover, active, focus, and disabled styling.
13
+ - Add Grid, ScrollView/ScrollState, sticky and logical layout, spatial focus navigation, and platform cursors.
14
+ - Add gradients, transforms, paths, layered effects, and matching Software, OpenGL, Metal, and Vulkan scene rendering.
15
+ - Add paragraph layout, Japanese kinsoku, font fallback, selection, Unicode-safe editing, and native IME composition.
16
+ - Add the opt-in `zaniah/ui` component library with keyboard and TUI support.
17
+ - Add clock-driven animation, keyed transitions, inertial scrolling, and reduced-motion support.
18
+ - Add virtual data views, charts, validated forms, native screen-reader support on macOS, Windows, and Linux, DevTools, and a three-theme component gallery.
19
+
3
20
  ## 0.2.0 — 2026-09-11
4
21
 
5
22
  - Add read-only element, hit-region, popup, and frame inspection APIs.
data/LICENSE.txt CHANGED
@@ -19,3 +19,21 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  THE SOFTWARE.
22
+
23
+ Bundled icons in assets/icons are derived from Lucide (https://lucide.dev):
24
+
25
+ ISC License
26
+
27
+ Copyright (c) 2020, Lucide Contributors
28
+
29
+ Permission to use, copy, modify, and/or distribute this software for any
30
+ purpose with or without fee is hereby granted, provided that the above
31
+ copyright notice and this permission notice appear in all copies.
32
+
33
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
34
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
35
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
36
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
37
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
38
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
39
+ PERFORMANCE OF THIS SOFTWARE.
data/README.md CHANGED
@@ -33,11 +33,15 @@ the drawing surface beneath the editor.
33
33
 
34
34
  ## Features
35
35
 
36
- - Flex, constraint, absolute, wrapping, and baseline-aware layouts
37
- - Rounded rectangles, images, static SVG, shadows, clipping, and glyph atlases
36
+ - Flex, Grid, sticky, absolute, wrapping, and baseline-aware layouts
37
+ - Gradients, transforms, paths, SVG, shadows, clipping, and GPU glyph atlases
38
38
  - Retained element state, subscriptions, and non-blocking background tasks
39
- - Uniform and variable-height virtual lists
40
- - OpenType ligatures and horizontal positioning with configurable text providers
39
+ - Scroll views, inertial input, and uniform or variable-height virtual lists
40
+ - OpenType shaping, font fallback, Japanese wrapping, text overlays, selection, editing, and IME
41
+ - Themes, state styles, keyed animation, springs, and reduced-motion support
42
+ - Opt-in controls, overlays, tables, trees, charts, forms, and terminal fallbacks
43
+ - Spatial keyboard focus and a diffed cross-platform accessibility tree
44
+ - F12 inspector, frame statistics, hot reload, and headless golden-image tests
41
45
  - Native input, IME, clipboard, file-drop, display, and filesystem events
42
46
  - RBS declarations for the public API
43
47
 
@@ -110,6 +114,12 @@ Native backends use the operating system libraries through Ruby's Fiddle. See
110
114
  - [Native backends](docs/native.md) — windows, displays, file watching, and terminals
111
115
  - [Text system](docs/text.md) — font discovery, shaping, rasterization, and caching
112
116
  - [SVG and lists](docs/vector_and_list.md) — static vector icons and virtual lists
117
+ - [Layout and scrolling](docs/layout.md) — Grid, ScrollView, sticky positioning, and RTL foundations
118
+ - [Themes](docs/theme.md) — semantic tokens and state styles
119
+ - [Components](docs/components.md) — controls, overlays, data views, charts, and forms
120
+ - [Animation](docs/animation.md) — easing, springs, transitions, and reduced motion
121
+ - [Accessibility](docs/accessibility.md) — semantic trees, diffs, and native bridges
122
+ - [Terminal UI](docs/tui.md) — deterministic component degradation
113
123
  - [CPU process workers](docs/process_pool.md) — portable background CPU work
114
124
  - [Architecture decisions](docs/adr) — design records and rationale
115
125
  - [RBS declarations](sig) — public API signatures
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.7 18 13.7 4a2 2 0 0 0-3.4 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.7-3Z"/><path d="M12 9v4M12 17h.01"/></svg>
@@ -0,0 +1,61 @@
1
+ # Accessibility
2
+
3
+ Every window maintains a backend-neutral `Accessibility::Tree` beside its render
4
+ tree. Components expose role, label, value, bounds, states, children, and actions
5
+ through `Accessibility::Node`. The window publishes a notification only when the
6
+ semantic tree changes.
7
+
8
+ ```ruby
9
+ window.accessibility_tree.each do |node, path|
10
+ puts [path, node.role, node.label].inspect
11
+ end
12
+ ```
13
+
14
+ `window.accessibility_revision` increments after each semantic change, and
15
+ `window.accessibility_tree.changes` contains `added`, `removed`, or `updated`
16
+ records. This makes accessibility assertions deterministic on the headless
17
+ backend without mocking an operating system API.
18
+
19
+ ## Component semantics
20
+
21
+ Interactive components publish their current state and supported actions. For
22
+ example, checkboxes expose `checked`, mixed checkboxes expose `mixed`, disabled
23
+ controls omit mutating actions, modal dialogs expose `modal`, and live Toast
24
+ messages use the `status` role. `FormField` marks invalid controls and associates
25
+ the error node through its `describedby` state.
26
+
27
+ Custom components implement the same method:
28
+
29
+ ```ruby
30
+ def accessibility_node(_cx)
31
+ Zaniah::Accessibility.node(
32
+ role: :button,
33
+ label: "Refresh",
34
+ states: {busy: @loading},
35
+ actions: @loading ? [] : [:press]
36
+ )
37
+ end
38
+ ```
39
+
40
+ Decorative content should return `nil`. Container elements without an explicit
41
+ node retain semantic descendants in a generated `group` node.
42
+
43
+ ## Native bridges
44
+
45
+ - macOS publishes an `NSAccessibilityElement` hierarchy with roles, labels,
46
+ values, state, bounds, and children, then posts `AXLayoutChanged`.
47
+ - Windows answers `WM_GETOBJECT` with `IRawElementProviderSimple`, fragment,
48
+ fragment-root, and Invoke providers. UI Automation clients can navigate and
49
+ query the semantic hierarchy rather than relying on refresh events alone.
50
+ - Linux registers `Accessible`, `Component`, `Action`, and `Application`
51
+ objects on the AT-SPI D-Bus, embeds the application root in the registry,
52
+ and emits object-change events. Actions route back through the normal input
53
+ dispatcher on every platform.
54
+
55
+ The Linux provider is exercised in CI inside a private D-Bus session. Native
56
+ macOS and Windows objects retain the same `Accessibility::NativeTree` path and
57
+ bounds model used by the headless assertions.
58
+
59
+ The semantic tree remains the authoritative source on every backend. See
60
+ [ADR-008](adr/008-accessibility-bridge.md) for the boundary between components
61
+ and native adapters.
@@ -0,0 +1,17 @@
1
+ # ADR 004: Resolve interaction styles in elements before paint
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-09-12
5
+ - Decision deadline: before M1 implementation
6
+
7
+ ## Context
8
+
9
+ State-specific styles need stable layout inputs and same-frame hover feedback. Resolution could live in elements, the frame context, or a separate style object.
10
+
11
+ ## Decision
12
+
13
+ Store variants in `StyleSet`, resolve flags from interaction state during prepaint, and let each element use the resolved style during paint. Keep `FrameContext` as a service carrier rather than a style owner.
14
+
15
+ ## Consequences
16
+
17
+ Existing element builders remain compatible and paint stays deterministic. Elements must register hits when they only have state-specific styles.
@@ -0,0 +1,43 @@
1
+ # ADR 005: Extend the shared GPU instance layout once
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-09-12
5
+ - Decision deadline: before M3 implementation
6
+
7
+ ## Context
8
+
9
+ Metal, OpenGL, Vulkan, and Software consume the same 24-float instance payload. Gradients and transforms need more fields; changing the stride independently for each feature repeats risky backend work.
10
+
11
+ ## Decision
12
+
13
+ Use this 40-float layout. Quad data uses every field; sprites reuse the edge
14
+ fields for source UVs. Triangle vertices reuse rect and radii fields.
15
+
16
+ | Floats | Quad | Sprite |
17
+ | --- | --- | --- |
18
+ | 0–3 | bounds | bounds |
19
+ | 4–7 | primary color | tint |
20
+ | 8–11 | secondary gradient color | unused |
21
+ | 12–15 | corner radii | unused |
22
+ | 16–19 | border color | unused |
23
+ | 20–23 | edge border widths | source UV |
24
+ | 24–27 | gradient kind, stop positions, angle | unused |
25
+ | 28–31 | gradient center, radius, primitive kind | primitive kind |
26
+ | 32–37 | affine transform (`a,b,c,d,tx,ty`) | affine transform |
27
+ | 38–39 | reserved shadow spread / inset or border flags | unused |
28
+
29
+ Opacity is multiplied into both fill and border alpha before packing. Linear
30
+ and radial gradients use exactly two stops; a future multi-stop implementation
31
+ may use a 1D texture without another stride change. Shadows are expanded into
32
+ ordinary quad instances, so the reserved shadow fields remain available.
33
+
34
+ Metal, OpenGL, and Vulkan consume the layout directly, Software consumes the
35
+ equivalent 40-float quad layout, and cached text sprite batches use the same
36
+ stride. Vulkan's bundled SPIR-V shaders render `Scene` quads, sprites, and path
37
+ triangles offscreen; its user-supplied SPIR-V triangle probe remains available.
38
+
39
+ ## Consequences
40
+
41
+ The coordinated change avoids multiple format migrations. Shader compilation,
42
+ Software golden tests, instance packing tests, and platform-native capture tests
43
+ are release gates on their available hosts.
@@ -0,0 +1,17 @@
1
+ # ADR 006: Components participate by duck typing
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-09-12
5
+ - Decision deadline: before M6 implementation
6
+
7
+ ## Context
8
+
9
+ Components can inherit `Element` or wrap an element tree while implementing the existing render protocol. Inheritance exposes low-level mutable state that components do not need.
10
+
11
+ ## Decision
12
+
13
+ Components implement `request_layout`, `layout_node`, `prepaint`, and `paint`, delegating those calls to a built root element.
14
+
15
+ ## Consequences
16
+
17
+ The UI layer stays optional and thin. Protocol changes must be reflected in both elements and components.
@@ -0,0 +1,17 @@
1
+ # ADR 007: Start with a String-backed text buffer
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-09-12
5
+ - Decision deadline: before M5 implementation
6
+
7
+ ## Context
8
+
9
+ Text editing needs Unicode-safe changes and undo. A piece table scales better for very large documents, while a Ruby `String` is substantially simpler for the intended settings and editor examples.
10
+
11
+ ## Decision
12
+
13
+ Use a mutable UTF-8 `String` with byte offsets and grapheme-aware navigation. Revisit a piece table only after representative documents show a measurable problem.
14
+
15
+ ## Consequences
16
+
17
+ The first implementation is small and easy to verify. Large edits are linear in buffer size.
@@ -0,0 +1,19 @@
1
+ # ADR 008: Separate the accessibility tree from OS bridges
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-09-12
5
+ - Decision deadline: before M8 implementation
6
+
7
+ ## Context
8
+
9
+ NSAccessibility, UI Automation, and AT-SPI expose different native contracts, but components need one testable semantic model.
10
+
11
+ ## Decision
12
+
13
+ Build a backend-neutral accessibility tree first. Add Fiddle-based adapters for macOS, Windows, and Linux that publish tree differences without changing component APIs.
14
+
15
+ ## Consequences
16
+
17
+ Semantics and diffs are tested headlessly. Native adapters publish layout-change
18
+ query providers and notifications through NSAccessibility, Windows UI Automation,
19
+ and AT-SPI's D-Bus interfaces while retaining the same backend-neutral tree.
@@ -0,0 +1,17 @@
1
+ # ADR 009: Implement a bounded grid layout subset
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-09-12
5
+ - Decision deadline: before M2 implementation
6
+
7
+ ## Context
8
+
9
+ Full CSS Grid is too large for the toolkit's first grid implementation. Applications primarily need explicit tracks, spans, gaps, fractional space, and predictable automatic placement.
10
+
11
+ ## Decision
12
+
13
+ Support explicit rows and columns, row-first auto placement, integer and range placement, `fr`, `minmax`, `auto`, and gaps. Defer named areas and `auto-fit`/`auto-fill`.
14
+
15
+ ## Consequences
16
+
17
+ Common application layouts are covered with a tractable engine. Deferred CSS features require an explicit later extension.
@@ -0,0 +1,24 @@
1
+ # ADR 010: Model text overlays as layout gaps
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-09-15
5
+ - Decision deadline: before text overlay implementation
6
+
7
+ ## Context
8
+
9
+ Inline hints and block annotations must affect wrapping and pointer mapping without
10
+ becoming editable text. Absolutely painting them over an ordinary paragraph would
11
+ leave wrapping, carets, and selection unaware of their size.
12
+
13
+ ## Decision
14
+
15
+ Represent inline overlays as measured gaps at UTF-8 grapheme boundaries and block
16
+ overlays as measured rows around logical source lines. Cache the wrapped paragraph
17
+ for each explicit source line so changing one inline overlay only reshapes that row.
18
+ Keep overlay elements as ordinary positioned children for painting and input.
19
+
20
+ ## Consequences
21
+
22
+ Text offsets remain unchanged, overlay children stay clickable, and coordinate
23
+ conversion accounts for every gap. Layout changes after an overlay row still shift
24
+ later vertical positions, but those rows reuse their shaped and wrapped paragraphs.
data/docs/animation.md ADDED
@@ -0,0 +1,46 @@
1
+ # Animation
2
+
3
+ Zaniah uses one clock-driven `Animator` per window. Rendering stays idle when no
4
+ animation is active, and injected clocks keep headless tests deterministic.
5
+
6
+ ```ruby
7
+ window.animator.animate(:progress, from: 0, to: 1,
8
+ duration: 0.18, easing: :ease_out)
9
+ window.animator.value(:progress)
10
+ ```
11
+
12
+ Available easing values are `linear`, `ease_in`, `ease_out`, and `ease_in_out`.
13
+ `Zaniah::Easing.cubic_bezier(x1, y1, x2, y2)` and
14
+ `window.animator.spring(key, to:, stiffness:, damping:, mass:)` cover custom
15
+ curves and spring motion. Numeric values, `Color`, same-unit `Length`, and
16
+ `Transform` values can be interpolated.
17
+
18
+ ## Style transitions
19
+
20
+ ```ruby
21
+ Div.new.key(:save).bg(theme.colors.surface)
22
+ .hover(background: theme.colors.surface_hover)
23
+ .transition(:background, duration: theme.motion.duration_base)
24
+ ```
25
+
26
+ Transitions require `key`; without one the new style is applied immediately.
27
+ Keyed children of a stable `Div` or `List` automatically animate additions,
28
+ removals, and position changes. Removed children remain paintable in the
29
+ window's existing element-state store until their exit animation completes.
30
+
31
+ `ScrollView`, `List`, and `UniformList` use the same animator for inertial
32
+ scrolling. Pass `inertia: false` to a standalone `ScrollState` to disable it.
33
+ Overlay scrollbars fade after activity; modal overlays and collapsible content
34
+ animate visibility. Editable text carets, skeletons, and spinners also use the
35
+ window clock.
36
+
37
+ ## Reduced motion
38
+
39
+ The macOS, Windows, and Linux backends read the operating-system reduced-motion
40
+ setting. When `theme.motion.reduced?` is true, the animator completes every
41
+ animation immediately. Tests or applications can opt in explicitly:
42
+
43
+ ```ruby
44
+ theme = Theme.dark
45
+ app.global(:theme, theme.with(motion: theme.motion.with(reduced: true)))
46
+ ```
@@ -0,0 +1,109 @@
1
+ # Components
2
+
3
+ The optional component layer is loaded with `require "zaniah/ui"`. Components use the
4
+ same `request_layout` / `prepaint` / `paint` protocol as elements and may be placed
5
+ directly in an element tree. Builder methods return the component.
6
+
7
+ ```ruby
8
+ require "zaniah/ui"
9
+
10
+ save = Zaniah::UI::Button.new("Save", variant: :primary, size: :md)
11
+ .icon(:check)
12
+ .on_click { |_event, cx| cx.window.request_frame }
13
+ ```
14
+
15
+ Applications may extend variant tables:
16
+
17
+ ```ruby
18
+ Zaniah::UI::Button.variants[:variant][:brand] = ->(theme) {
19
+ {background: theme.colors.success, hover: theme.colors.success,
20
+ foreground: theme.colors.text_inverse, border: theme.colors.success}
21
+ }
22
+ ```
23
+
24
+ ## Component reference
25
+
26
+ | Layer | Component | Main constructor/options | Variants | Accessibility role |
27
+ | --- | --- | --- | --- | --- |
28
+ | L0 | `Label` | `(text, tone:, size:, wrap:)` | tone: default/muted/inverse; size: xs–xl | text |
29
+ | L0 | `Icon` | `(source, size:, color:, label:)` | bundled: check/close/search/menu/info/warning | image when labeled |
30
+ | L0 | `Divider` | `(axis:)` | horizontal/vertical | separator |
31
+ | L0 | `Spacer` | `(size = nil)` | fixed or flexible | none |
32
+ | L0 | `Card` | `(*children)`, `child` | theme surface | group |
33
+ | L0 | `Badge` | `(text, variant:)` | neutral/accent/success/warning/danger | text |
34
+ | L0 | `Avatar` | `(name, image:, size:)` | initials or PNG | image |
35
+ | L0 | `Skeleton` | `(width:, height:)` | pulsing loading placeholder | progressbar/busy |
36
+ | L0 | `EmptyState` | `(title, message:, icon:, action:)` | compositional | group |
37
+ | L1 | `Button` | `(label, size:, variant:)`; `disabled`, `loading`, `icon`, `on_click` | sm/md/lg × primary/secondary/ghost/danger | button |
38
+ | L1 | `IconButton` | `(icon, label:, ...)` | Button variants | button |
39
+ | L1 | `ToggleButton` | `(label, value:)`; `on_change` | Button variants | button/pressed |
40
+ | L1 | `ButtonGroup` | `(*buttons)`, `child` | horizontal | group |
41
+ | L1 | `Checkbox` | `(label, value:, disabled:)`; `on_change` | true/false/mixed | checkbox |
42
+ | L1 | `Radio` | `(label, value:, disabled:)`; `on_change` | selected/unselected | radio |
43
+ | L1 | `RadioGroup` | `(options, value:)`; `on_change` | one selected value | radiogroup |
44
+ | L1 | `Switch` | `(label, value:, disabled:)`; `on_change` | on/off | switch |
45
+ | L1 | `Slider` | `(value:, min:, max:, step:, label:)`; `on_change` | pointer + arrow/Home/End/Page keys | slider |
46
+ | L1 | `RangeSlider` | `(value: [low, high], ...)` | two thumbs | slider |
47
+ | L1 | `ProgressBar` | `(value:, min:, max:, label:)` | determinate/indeterminate | progressbar |
48
+ | L1 | `Spinner` | `(label:, size:)` | animated loading indicator | progressbar/busy |
49
+ | L1 | `Meter` | `(value:, low:, high:, optimum:)` | thresholds | meter |
50
+ | L2 | `Tooltip` | `(text, anchor:, side:, open:)` | top/bottom/left/right | tooltip |
51
+ | L2 | `Popover` | `(content, anchor:, side:, width:, height:, open:, modal:)` | flipped and viewport-clamped | group |
52
+ | L2 | `ContextMenu`, `Menu` | `(items, anchor:, open:)` | pointer + arrows/Home/End/Enter/Esc | menu/menuitem |
53
+ | L2 | `MenuBar` | `(menus)` | compositional | menubar |
54
+ | L2 | `Dropdown` | `(label, items:, value:)`; `on_change` | menu-backed | button |
55
+ | L2 | `TextField` | `(value, placeholder:, label:, prefix:, suffix:, error:, max_length:, clearable:)` | IME, selection, counter | textbox |
56
+ | L2 | `TextArea` | TextField plus `rows:` | multiline/wrapped | textbox/multiline |
57
+ | L2 | `SearchInput` | TextField options | search + clear icons | searchbox |
58
+ | L2 | `PasswordInput` | TextField options | masked display | textbox |
59
+ | L2 | `NumberInput` | TextField plus `min:`, `max:`, `step:`; `increment`, `decrement` | numeric | textbox |
60
+ | L2 | `TagInput` | `(tags, separator:, ...)`; `on_tags_change` | badge list + editor | textbox |
61
+ | L2 | `Select` | `(items, label:, value:, disabled:)`; `on_change` | single choice | combobox |
62
+ | L2 | `Combobox` | `(items, value:, label:, placeholder:, disabled:)`; `on_change` | editable, filtered choices | combobox |
63
+ | L2 | `MultiSelect` | `(items, value:, label:, disabled:)`; `on_change` | multiple selected badges | listbox |
64
+ | L2 | `DatePicker` | `(value, min:, max:, label:, disabled:)`; `on_change` | ISO date, day/week keyboard steps | combobox |
65
+ | L2 | `TimePicker` | `(value, step:, label:, disabled:)`; `on_change` | 24-hour time, minute/hour keyboard steps | combobox |
66
+ | L2 | `ColorPicker` | `(value, label:, swatches:, disabled:)`; `on_change` | hex input and swatches | combobox |
67
+ | L3 | `Tabs` | `(items, selected:)`; `on_change` | arrows/Home/End | tab/tabpanel |
68
+ | L3 | `ScrollView` | `(axis:, scrollbar:)`; `scroll_to` | vertical/horizontal/both; overlay/always/hidden | child tree |
69
+ | L3 | `Scrollbar` | `(scroll_state, axis:, mode:)` | drag, track paging, arrows/Home/End/Page keys | scrollbar |
70
+ | L3 | `Accordion` | `(items, multiple:, open:)` | single/multiple | group |
71
+ | L3 | `Collapsible` | `(label, content, open:)`; `on_change` | open/closed | button/expanded |
72
+ | L3 | `Breadcrumb` | `(items)` | labels or label/callback pairs | navigation/link |
73
+ | L3 | `Pagination` | `(page:, pages:, window:)`; `on_change` | previous/window/next | navigation |
74
+ | L3 | `Toolbar` | `(*children)`, `child` | horizontal | toolbar |
75
+ | L3 | `StatusBar` | `(*children)`, `child` | horizontal | status |
76
+ | L3 | `Sidebar` | `(*children, width:)`, `child` | vertical | navigation |
77
+ | L3 | `Modal`, `Dialog` | `(content, title:, open:, close_on_scrim:, width:)` | focus trap, scrim, Esc | dialog/modal |
78
+ | L3 | `Drawer` | Modal plus `side:` | left/right | dialog/modal |
79
+ | L3 | `Toast` | `(message, variant:, queue:)`; `dismiss` | info/success/warning/danger | live status |
80
+ | L3 | `CommandPalette` | `(commands, open:, placeholder:)` | searchable modal | dialog/list |
81
+ | L3 | `SplitPane` | `(first, second, orientation:, ratio:, min:, max:)`; `on_change` | horizontal/vertical, draggable separator | group/separator |
82
+ | L3 | `Resizable` | `(content, width:, height:, min_width:, min_height:, max_width:, max_height:)`; `on_resize` | drag or keyboard resize | group/separator |
83
+ | L3 | `DockPanel` | `(center:, top:, right:, bottom:, left:)` | five-region layout | group |
84
+ | L3 | `ListView` | `(items, height:, row_height:, selected:)`; `on_select` | virtual rows and keyboard selection | list/listitem |
85
+ | L4 | `Table`, `DataGrid` | `(rows, columns:, height:, selection:, row_key:)`; `on_sort`, `on_select`, `on_edit` | virtual rows, sorting, resizing, editing | table/row/cell |
86
+ | L4 | `TreeView` | `(items, height:, selected:)`; `expand`, `collapse`, lazy `children` proc | arrows/Home/End | tree/treeitem |
87
+ | L5 | `Sparkline` | `(values, width:, height:, color:, label:)` | line + tooltip | image |
88
+ | L5 | `LineChart`, `BarChart` | `(series, width:, height:, colors:, label:)` | axes, legend, tooltip | image |
89
+ | L5 | `Validation` | `required`, `format`, `length`, `number`, `rule` | composable rules | n/a |
90
+ | L5 | `FormField` | `(name, value:, label:, control:, validation:, hint:)` | errors + describedby | group/control/alert |
91
+ | L5 | `Form` | `field`, `on_change`, `on_submit`, `values`, `valid?` | validates before submit | form |
92
+ | L5 | `CodeEditor` | `(value, language:, line_numbers:, read_only:)`; `on_change` | multiline editor with scrolling | textbox |
93
+ | L5 | `RichText` | `(runs, selectable:)` | styled selectable runs | text |
94
+
95
+ All input components are keyboard operable. Disabled controls remain visible but are
96
+ removed from focus traversal. Overlay components close on Esc; modal overlays restore
97
+ the previous focus. See [TUI](tui.md) for terminal representations.
98
+
99
+ Table columns are hashes with `key`, and optional `label`, `width`, `sortable`,
100
+ `resizable`, `editable`, and `render`. Tree items accept hashes containing `id`,
101
+ `label`, and either an array or lazy proc in `children`.
102
+
103
+ In a table, Up/Down/Home/End/Page keys move and select rows, Shift+Up/Down extends
104
+ a range, and Cmd/Ctrl+A selects every row in multiple-selection mode. Sortable
105
+ headers and resize handles are separate Tab stops; Enter sorts and arrow/Page keys
106
+ resize. Tree views use Up/Down to select and Left/Right to collapse or expand.
107
+
108
+ Run `bundle exec ruby tools/generate_component_gallery.rb` to rebuild the dark,
109
+ light, and high-contrast component sheets plus overlay variants in all three themes.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file