zaniah 0.7.0 → 0.9.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 +14 -0
- data/docs/adr/005-instance-layout.md +4 -4
- data/docs/adr/015-mime-clipboard-types.md +25 -0
- data/docs/adr/016-semantic-vector-recording.md +33 -0
- data/docs/adr/017-analytic-shadows.md +31 -0
- data/docs/adr/018-gradient-ramps.md +30 -0
- data/docs/components.md +9 -6
- 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/layout.md +21 -0
- data/docs/menus.md +64 -0
- data/docs/native.md +49 -0
- data/docs/tui.md +2 -1
- data/docs/vector.md +60 -0
- data/docs/vector_and_list.md +13 -4
- data/examples/gallery.rb +1 -0
- data/examples/metal_effect_check.rb +30 -0
- data/examples/native_menu.rb +61 -0
- data/lib/zaniah/app.rb +8 -1
- data/lib/zaniah/clipboard.rb +40 -0
- data/lib/zaniah/element.rb +10 -1
- data/lib/zaniah/gpu/instance_packing.rb +2 -0
- data/lib/zaniah/gpu/metal.rb +27 -5
- data/lib/zaniah/gpu/open_gl.rb +29 -6
- data/lib/zaniah/gpu/software.rb +58 -6
- data/lib/zaniah/gpu/vulkan/scene.frag +26 -4
- data/lib/zaniah/gpu/vulkan/scene.frag.spv +0 -0
- data/lib/zaniah/gpu/vulkan/scene.vert +2 -0
- data/lib/zaniah/gpu/vulkan/scene.vert.spv +0 -0
- data/lib/zaniah/image.rb +2 -2
- data/lib/zaniah/input/dispatcher.rb +2 -2
- data/lib/zaniah/input/focus_tree.rb +1 -0
- data/lib/zaniah/input/keymap.rb +8 -0
- data/lib/zaniah/inspection.rb +5 -0
- data/lib/zaniah/menu.rb +104 -0
- data/lib/zaniah/platform/headless/window.rb +149 -7
- data/lib/zaniah/platform/linux/wayland_window.rb +164 -13
- data/lib/zaniah/platform/linux/window.rb +296 -27
- data/lib/zaniah/platform/mac/app.rb +59 -4
- data/lib/zaniah/platform/mac/clipboard_data.rb +37 -0
- data/lib/zaniah/platform/mac/native_menu.rb +168 -0
- data/lib/zaniah/platform/mac/window.rb +246 -27
- data/lib/zaniah/platform/tui/window.rb +35 -5
- data/lib/zaniah/platform/windows/clipboard_data.rb +113 -0
- data/lib/zaniah/platform/windows/native_menu.rb +165 -0
- data/lib/zaniah/platform/windows/window.rb +264 -32
- data/lib/zaniah/scene.rb +167 -27
- data/lib/zaniah/style/gradient.rb +6 -1
- data/lib/zaniah/svg/rasterizable.rb +54 -0
- data/lib/zaniah/svg.rb +157 -14
- data/lib/zaniah/text_system/renderer.rb +31 -1
- data/lib/zaniah/ui/component.rb +7 -1
- data/lib/zaniah/ui/controls.rb +2 -0
- data/lib/zaniah/ui/data.rb +31 -1
- data/lib/zaniah/ui/grid.rb +24 -1
- data/lib/zaniah/ui/modal.rb +11 -4
- data/lib/zaniah/ui/overlays.rb +141 -11
- data/lib/zaniah/ui/primitives.rb +41 -0
- data/lib/zaniah/vector.rb +63 -0
- data/lib/zaniah/version.rb +1 -1
- data/lib/zaniah/window_state.rb +36 -0
- data/lib/zaniah.rb +3 -0
- data/sig/app.rbs +2 -0
- data/sig/clipboard.rbs +26 -0
- data/sig/elements.rbs +4 -0
- data/sig/input.rbs +5 -1
- data/sig/menu.rbs +31 -0
- data/sig/native.rbs +5 -5
- data/sig/platform.rbs +28 -7
- data/sig/scene.rbs +9 -1
- data/sig/ui.rbs +15 -3
- data/sig/vector.rbs +116 -0
- data/sig/window_state.rbs +11 -0
- data/sig/zaniah.rbs +1 -1
- metadata +21 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cc06ae1720a8d05abbc0da78849a355f7c36565c62b3907f941c80dd066594c5
|
|
4
|
+
data.tar.gz: fd00f6d9bd08c097a5ffa35ce079a83f949c07874d6ff7c76cdc802f7ddd8129
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 769bfa73b348d773f2139eb34039a7250f6878986375bdd25c361c3922b16d61ab68a3d14c59f8ef7f3cfd68e04a43d868286b624d1996d2fc2afdb92b23f861
|
|
7
|
+
data.tar.gz: a9642fa580bd9595e15c48903e698b95dfc5a6f876ca0d71438e488899522e2855351cef18a124e005dae5636e53b500aa46e90e101575c12796fc4e9ae80992
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0 — 2026-09-24
|
|
4
|
+
|
|
5
|
+
- Add semantic vector recording for UI scenes, including text, images, and raster fallbacks for unsupported drawing.
|
|
6
|
+
- Render shadows analytically across backends; their edge appearance changes from earlier releases.
|
|
7
|
+
- Add shared multi-stop and conic gradient ramps across Software, OpenGL, Metal, and Vulkan rendering.
|
|
8
|
+
- Support SVG gradients, dashed strokes, and alpha and luminance masks.
|
|
9
|
+
|
|
10
|
+
## 0.8.0 — 2026-09-24
|
|
11
|
+
|
|
12
|
+
- Add native macOS and Windows menus, declarative context menus, and platform-aware shortcut labels.
|
|
13
|
+
- Add MIME-keyed clipboard content on desktop backends, including HTML, PNG, file URLs, and X11 incremental transfers.
|
|
14
|
+
- Add Grid and Table copy/paste hooks with TSV and HTML data.
|
|
15
|
+
- Add window state persistence, frameless windows, and draggable title regions across native backends.
|
|
16
|
+
|
|
3
17
|
## 0.7.0 — 2026-09-24
|
|
4
18
|
|
|
5
19
|
- Add a stable frame inspection API with accessibility queries and DevTools integration.
|
|
@@ -26,10 +26,10 @@ fields for source UVs. Triangle vertices reuse rect and radii fields.
|
|
|
26
26
|
| 32–37 | affine transform (`a,b,c,d,tx,ty`) | affine transform |
|
|
27
27
|
| 38–39 | reserved shadow spread / inset or border flags | unused |
|
|
28
28
|
|
|
29
|
-
Opacity is multiplied into both fill and border alpha before packing.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
Opacity is multiplied into both fill and border alpha before packing. The
|
|
30
|
+
original two-stop and expanded-quad behavior has since been extended without
|
|
31
|
+
changing this layout: see [ADR 017](017-analytic-shadows.md) for the shadow
|
|
32
|
+
fields and [ADR 018](018-gradient-ramps.md) for multistop ramps and conic fills.
|
|
33
33
|
|
|
34
34
|
Metal, OpenGL, and Vulkan consume the layout directly, Software consumes the
|
|
35
35
|
equivalent 40-float quad layout, and cached text sprite batches use the same
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# ADR 015: Use MIME names for clipboard representations
|
|
2
|
+
|
|
3
|
+
- Status: Proposed
|
|
4
|
+
- Date: 2026-09-24
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Applications need to place text, HTML, images, and private formats on the
|
|
9
|
+
clipboard at the same time. Each operating system names these formats
|
|
10
|
+
differently. Exposing those native names would make application copy/paste
|
|
11
|
+
logic platform-specific; using a closed enum would exclude private formats.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
Use MIME strings as the public representation keys. Platform adapters map
|
|
16
|
+
common MIME names to native formats and preserve other names where the system
|
|
17
|
+
allows it. Items contain eagerly supplied, immutable data. The toolkit owns
|
|
18
|
+
transport and format negotiation, not conversion of application data.
|
|
19
|
+
|
|
20
|
+
## Consequences
|
|
21
|
+
|
|
22
|
+
Applications can offer multiple formats through one API without depending on
|
|
23
|
+
an OS adapter. Adapters must maintain the native mappings and account for
|
|
24
|
+
platform-specific limits. Lazy data providers may be added later if eager
|
|
25
|
+
copies become a measured problem.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# ADR 016: Record semantic drawing alongside the GPU scene
|
|
2
|
+
|
|
3
|
+
- Status: Proposed
|
|
4
|
+
- Date: 2026-09-24
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
The GPU scene contains packed quads and atlas sprites. Paths have already been
|
|
9
|
+
rasterized and text has lost its font, glyph IDs, and source clusters by the
|
|
10
|
+
time a renderer consumes those commands. Exporters need this information to
|
|
11
|
+
produce searchable text and scalable artwork. Reconstructing it from packed
|
|
12
|
+
GPU instances is impossible; maintaining a second application-specific layout
|
|
13
|
+
for export makes screen and export drift.
|
|
14
|
+
|
|
15
|
+
Two viable boundaries were considered: make the GPU scene itself semantic, or
|
|
16
|
+
keep its fast packed representation and optionally observe the drawing calls.
|
|
17
|
+
|
|
18
|
+
## Decision
|
|
19
|
+
|
|
20
|
+
Keep the GPU scene unchanged and attach an optional vector sink to `Scene`.
|
|
21
|
+
Emit semantic commands before paths and text are flattened. When a source has
|
|
22
|
+
no supported vector form, preserve its pixels as a raster command instead.
|
|
23
|
+
The sink is absent by default, so normal frames do not allocate vector data.
|
|
24
|
+
Page assembly and document formats remain outside Zaniah.
|
|
25
|
+
|
|
26
|
+
## Consequences
|
|
27
|
+
|
|
28
|
+
Exporters can reuse the same element tree and retain font and outline data
|
|
29
|
+
without adding a dependency on a PDF library. The optional path incurs a
|
|
30
|
+
nil check when unused and stores additional frame data when enabled. Raster
|
|
31
|
+
fallbacks keep output complete but cannot scale as cleanly as true vectors.
|
|
32
|
+
This choice should be revisited if packed GPU commands become a lossless
|
|
33
|
+
semantic representation or a common display list can serve both consumers.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# ADR 017: Draw each shadow as one analytic instance
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-09-24
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
The previous shadow approximation layered eight rounded quads. It produced
|
|
9
|
+
visible bands and made a scene with 100 shadows submit 800 instances.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
Keep the 40-float instance layout from ADR 005. Primitive kind `4` in field 31
|
|
14
|
+
is a shadow; field 30 is blur sigma, field 38 is spread, and field 39 is the
|
|
15
|
+
inset flag. Bounds 0–3 are expanded by `spread + 3 * blur + 1` for outer
|
|
16
|
+
shadows. Inset shadows retain the original bounds. Corner radii occupy 12–15.
|
|
17
|
+
|
|
18
|
+
Software and all three GPU fragment shaders evaluate the signed distance to
|
|
19
|
+
the original rounded rectangle. A Gaussian edge is approximated by
|
|
20
|
+
`0.5 - 0.5 * erf((distance - spread) / (sqrt(2) * blur))`, using the same bounded
|
|
21
|
+
exponential approximation of `erf` in every backend. Zero blur uses direct
|
|
22
|
+
coverage. Inset shadows multiply interior coverage by the inverted transition.
|
|
23
|
+
The existing clipping, transforms, and alpha blending remain unchanged.
|
|
24
|
+
|
|
25
|
+
## Consequences
|
|
26
|
+
|
|
27
|
+
Shadow appearance intentionally changes; the golden images and changelog must
|
|
28
|
+
record this. `bench/shadow.rb` compares 100 shadows against the old eight-quad
|
|
29
|
+
approximation and requires the analytic Software path to be faster. GPU native
|
|
30
|
+
capture tests remain platform-dependent; `script/compile_shaders` regenerates
|
|
31
|
+
the bundled Vulkan SPIR-V from the checked-in GLSL.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# ADR 018: Use bounded ramps for multistop gradients
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-09-24
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
The shared instance format carries only two inline stop colors. Extending its
|
|
9
|
+
stride would affect every renderer and text/sprite packing.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
Two-stop linear and radial gradients retain their inline color path. Two-stop
|
|
14
|
+
conic gradients use kind `3` and the same interpolation path. Three or more
|
|
15
|
+
stops use kinds `4` (linear), `5` (radial), or `6` (conic), and one row of a
|
|
16
|
+
256-by-256 RGBA8 atlas baked from the ordered stops. Field 8 stores the row
|
|
17
|
+
index; all renderers sample that row at the calculated gradient position. A
|
|
18
|
+
`Scene` caches rows by immutable `Gradient` value in an LRU capped at 256
|
|
19
|
+
entries. Rows used in the current frame cannot be evicted; if a frame exceeds
|
|
20
|
+
256 distinct ramps, it uses reusable overflow atlases for the remainder of
|
|
21
|
+
that frame. Normal quads continue to use packed-byte fast paths; only ramp
|
|
22
|
+
quads need texture-aware batching.
|
|
23
|
+
|
|
24
|
+
## Consequences
|
|
25
|
+
|
|
26
|
+
No instance layout migration is required. Sampling precision is limited to
|
|
27
|
+
256 positions. Adjacent ramps on the shared atlas batch together, while
|
|
28
|
+
overflows can add batches. SVG gradient fills use the same ordered-stop
|
|
29
|
+
interpolation semantics and fall back to a raster snapshot in vector recording
|
|
30
|
+
because a `Vector::Path` currently stores solid fills only.
|
data/docs/components.md
CHANGED
|
@@ -31,6 +31,7 @@ Zaniah::UI::Button.variants[:variant][:brand] = ->(theme) {
|
|
|
31
31
|
| L0 | `Spacer` | `(size = nil)` | fixed or flexible | none |
|
|
32
32
|
| L0 | `Card` | `(*children)`, `child` | theme surface | group |
|
|
33
33
|
| L0 | `Badge` | `(text, variant:)` | neutral/accent/success/warning/danger | text |
|
|
34
|
+
| L0 | `Kbd` | `(keys, platform:)`; `.for(action, keymap:)` | OS shortcut notation, including multi-stroke bindings | text |
|
|
34
35
|
| L0 | `Avatar` | `(name, image:, size:)` | initials or PNG | image |
|
|
35
36
|
| L0 | `Skeleton` | `(width:, height:)` | pulsing loading placeholder | progressbar/busy |
|
|
36
37
|
| L0 | `EmptyState` | `(title, message:, icon:, action:)` | compositional | group |
|
|
@@ -50,7 +51,7 @@ Zaniah::UI::Button.variants[:variant][:brand] = ->(theme) {
|
|
|
50
51
|
| L2 | `Tooltip` | `(text, anchor:, side:, open:)` | top/bottom/left/right | tooltip |
|
|
51
52
|
| L2 | `Popover` | `(content, anchor:, side:, width:, height:, open:, modal:)` | flipped and viewport-clamped | group |
|
|
52
53
|
| L2 | `ContextMenu`, `Menu` | `(items, anchor:, open:)` | pointer + arrows/Home/End/Enter/Esc | menu/menuitem |
|
|
53
|
-
| L2 | `MenuBar` | `(menus)` |
|
|
54
|
+
| L2 | `MenuBar` | `(menus)` or `.from(app.menu_bar)` | declarative menu model or legacy pairs | menubar |
|
|
54
55
|
| L2 | `Dropdown` | `(label, items:, value:)`; `on_change` | menu-backed | button |
|
|
55
56
|
| L2 | `TextField` | `(value, placeholder:, label:, prefix:, suffix:, error:, max_length:, clearable:)` | IME, selection, counter | textbox |
|
|
56
57
|
| L2 | `TextArea` | TextField plus `rows:` | multiline/wrapped | textbox/multiline |
|
|
@@ -83,8 +84,8 @@ Zaniah::UI::Button.variants[:variant][:brand] = ->(theme) {
|
|
|
83
84
|
| L3 | `Resizable` | `(content, width:, height:, min_width:, min_height:, max_width:, max_height:)`; `on_resize` | drag or keyboard resize | group/separator |
|
|
84
85
|
| L3 | `DockPanel` | `(center:, top:, right:, bottom:, left:)` | five-region layout | group |
|
|
85
86
|
| L3 | `ListView` | `(items, height:, row_height:, selected:)`; `on_select` | virtual rows and keyboard selection | list/listitem |
|
|
86
|
-
| L4 | `Table`, `DataGrid` | `(rows, columns:, height:, selection:, row_key:)`; `on_sort`, `on_select`, `on_edit` | virtual rows, sorting, resizing, editing | table/row/cell |
|
|
87
|
-
| L4 | `Grid` | `(rows:, columns:, row_height:, column_width:, frozen_rows:, frozen_columns:)`; `scroll_to`, range `selection`, `on_select`, `on_edit`, `on_fill`, `on_resize` | two-axis virtualization, frozen panes, visible-cell resize/fill
|
|
87
|
+
| L4 | `Table`, `DataGrid` | `(rows, columns:, height:, selection:, row_key:)`; `on_sort`, `on_select`, `on_edit`, `on_copy`, `on_paste` | virtual rows, sorting, resizing, editing, typed clipboard hooks | table/row/cell |
|
|
88
|
+
| L4 | `Grid` | `(rows:, columns:, row_height:, column_width:, frozen_rows:, frozen_columns:)`; `scroll_to`, range `selection`, `on_select`, `on_edit`, `on_fill`, `on_resize`, `on_copy`, `on_paste` | two-axis virtualization, frozen panes, visible-cell resize/fill and typed clipboard hooks | table |
|
|
88
89
|
| L4 | `TreeView` | `(items, height:, selected:)`; `expand`, `collapse`, `replace`, `replace_children`, `invalidate`, lazy `children` proc | arrows/Home/End | tree/treeitem |
|
|
89
90
|
| L5 | `Sparkline` | `(values, width:, height:, color:, label:)` | line + tooltip | image |
|
|
90
91
|
| L5 | `LineChart`, `BarChart`, `StackedBarChart`, `AreaChart` | `(series, width:, height:, colors:, label:)`; `AreaChart(stacked:)` | shared axes, ticks, grid lines, color-keyed legend, tooltip | image |
|
|
@@ -106,8 +107,8 @@ input order. Pass `matcher:` to either component, or set a default with
|
|
|
106
107
|
`match(query, labels)` and returns `UI::Matcher::Match` values with an original
|
|
107
108
|
label `index`, descending `score`, and half-open UTF-8 byte `ranges` for highlighting.
|
|
108
109
|
It may also implement `refine(previous_matches, query)` for incremental queries.
|
|
109
|
-
`CommandPalette.from(app.actions)` uses registered action titles
|
|
110
|
-
unavailable actions
|
|
110
|
+
`CommandPalette.from(app.actions)` uses registered action titles, disables
|
|
111
|
+
unavailable actions, and displays shortcuts with `UI::Kbd`. See [Menus](menus.md).
|
|
111
112
|
|
|
112
113
|
`UI::RichText` accepts UTF-8 byte ranges at grapheme boundaries. Inline styles are
|
|
113
114
|
`bold`, `italic`, `size`, `color`, `font`, and `link`; paragraphs support start,
|
|
@@ -143,7 +144,9 @@ viewport.
|
|
|
143
144
|
In a table, Up/Down/Home/End/Page keys move and select rows, Shift+Up/Down extends
|
|
144
145
|
a range, and Cmd/Ctrl+A selects every row in multiple-selection mode. Sortable
|
|
145
146
|
headers and resize handles are separate Tab stops; Enter sorts and arrow/Page keys
|
|
146
|
-
resize.
|
|
147
|
+
resize. Grid and Table clipboard hooks receive half-open `Grid::Area` ranges;
|
|
148
|
+
Table areas follow the current display order without changing its stable-ID
|
|
149
|
+
selection. See [Layout](layout.md#two-axis-virtual-grid). Tree views use Up/Down to select, Right to expand or enter the first child,
|
|
147
150
|
and Left to collapse or return to the parent.
|
|
148
151
|
|
|
149
152
|
Run `bundle exec ruby tools/generate_component_gallery.rb` to rebuild the dark,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/docs/layout.md
CHANGED
|
@@ -75,6 +75,27 @@ apply a validated batch with a single frame request; `row_hidden?` and
|
|
|
75
75
|
destination areas; the application owns cell values and fill semantics. Shift
|
|
76
76
|
extends a range; Cmd/Ctrl-click toggles an additional cell range.
|
|
77
77
|
|
|
78
|
+
Copy and paste are opt-in: the grid never owns cell values. `on_copy` receives
|
|
79
|
+
the selected `Grid::Area` values and returns MIME-keyed content, which Zaniah
|
|
80
|
+
writes to the clipboard. `on_paste` receives the same half-open areas and all
|
|
81
|
+
available clipboard formats; the application decides how to apply them.
|
|
82
|
+
Without the corresponding hook, the action is unhandled. With a hook but no
|
|
83
|
+
selection, the action is disabled.
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
grid.on_copy do |areas, _cx|
|
|
87
|
+
{"text/plain" => cells_as_tsv(areas), "text/html" => cells_as_html(areas)}
|
|
88
|
+
end
|
|
89
|
+
grid.on_paste do |areas, content, _cx|
|
|
90
|
+
paste_cells(areas, content.fetch("text/plain")) if content.types.include?("text/plain")
|
|
91
|
+
end
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`UI::Table` and `UI::DataGrid` use the same hooks. Their existing `selection`
|
|
95
|
+
remains a set of stable row IDs; hook arguments are `Grid::Area` ranges in the
|
|
96
|
+
current sorted display order, covering all columns. Disjoint selected rows
|
|
97
|
+
produce separate areas. The application retains ownership of table data.
|
|
98
|
+
|
|
78
99
|
`bench/grid.rb` measures a headless 800×600 viewport over a 1,000,000×16,000
|
|
79
100
|
grid at 23 sequential scroll positions, including cell construction, layout,
|
|
80
101
|
prepaint, and paint. `BUDGET=1 ruby bench/grid.rb` asserts a 16.67 ms median
|
data/docs/menus.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Menus and shortcuts
|
|
2
|
+
|
|
3
|
+
`Zaniah::Menu` describes commands; the application owns what each command does.
|
|
4
|
+
Register actions once and attach a menu model to the app:
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
app.actions.register(:save, title: "Save") { |cx| save_document(cx) }
|
|
8
|
+
app.menu_bar = Zaniah::Menu.build do
|
|
9
|
+
app_menu
|
|
10
|
+
submenu "File" do
|
|
11
|
+
item :save
|
|
12
|
+
separator
|
|
13
|
+
submenu "Recent", items: -> { recent_items }
|
|
14
|
+
end
|
|
15
|
+
submenu "Edit" do
|
|
16
|
+
standard_edit_items
|
|
17
|
+
end
|
|
18
|
+
window_menu
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`recent_items` must return a `Zaniah::Menu` or an array of `Menu::Item` values.
|
|
23
|
+
The callback runs when that submenu opens, not when the menu bar is drawn.
|
|
24
|
+
`app_menu` and `window_menu` are macOS-only placeholders. They are omitted on
|
|
25
|
+
other platforms. A missing item title comes from `app.actions`; a missing
|
|
26
|
+
shortcut comes from the most recent keymap binding valid in an empty context.
|
|
27
|
+
Context-only bindings are not shown. Multi-stroke shortcuts appear in window
|
|
28
|
+
menus and `UI::Kbd`, but native menus show only single strokes.
|
|
29
|
+
|
|
30
|
+
For Linux, headless, and TUI, place `Zaniah::UI::MenuBar.from(app.menu_bar)` in
|
|
31
|
+
the application layout. Zaniah never inserts it automatically. Menu commands
|
|
32
|
+
use `dispatcher.perform(action, source: :menu)` and are enabled or checked by
|
|
33
|
+
`available?` and `checked?` when a menu opens. The in-window menu exposes a
|
|
34
|
+
Back row for nested submenus and restores the previously focused control so
|
|
35
|
+
standard editing commands target that control. `Inspection.snapshot(window).overlays.menu`
|
|
36
|
+
returns the app's current model. Existing `UI::MenuBar.new([[label, items]])`
|
|
37
|
+
and `UI::Menu.new([[label, callback]])` forms remain valid.
|
|
38
|
+
|
|
39
|
+
`UI::Kbd.new("cmd-shift-p", platform: :mac)` displays `⌘⇧P`; on Windows or
|
|
40
|
+
Linux it displays `Super+Shift+P`. `UI::Kbd.for(:save, keymap: keymap)` uses
|
|
41
|
+
the same binding as the menu. Its TUI text always uses readable names such as
|
|
42
|
+
`Ctrl+Shift+P`.
|
|
43
|
+
|
|
44
|
+
Windows attaches a native menu to each window. It refreshes enabled and checked
|
|
45
|
+
states when a menu opens and evaluates dynamic submenus at that time. Single-key
|
|
46
|
+
shortcuts appear after a tab in item labels; key input still runs only through
|
|
47
|
+
the Zaniah keymap, not a Win32 accelerator table. The menu model remains
|
|
48
|
+
available for inspection and in-window display on all backends; headless has
|
|
49
|
+
no system menu.
|
|
50
|
+
|
|
51
|
+
On macOS, `App#menu_bar` becomes the system main menu. The app menu uses native
|
|
52
|
+
About, Hide, Show All, and Quit items; `window_menu` uses the native Window menu.
|
|
53
|
+
Dynamic submenu callbacks run when that submenu opens. Enabled and checked
|
|
54
|
+
states are refreshed from the active window's dispatcher. Native shortcuts
|
|
55
|
+
display single-stroke keymap bindings and route key events through the keymap
|
|
56
|
+
before AppKit can run a matching menu item. A missing menu model keeps the
|
|
57
|
+
original Quit-only menu. Run `ruby examples/native_menu.rb` in a macOS graphical
|
|
58
|
+
session to inspect the native behavior.
|
|
59
|
+
|
|
60
|
+
`element.context_menu(Zaniah::Menu.build { item :save })` accepts the same
|
|
61
|
+
command model as the menu bar, including separators, nested submenus, dynamic
|
|
62
|
+
items, and current enabled/checked state. macOS and Windows use native context
|
|
63
|
+
menus for this model; headless, TUI, and `UI::ContextMenu` use the in-window
|
|
64
|
+
menu. The older `[["Save", -> { save }]]` form remains supported.
|
data/docs/native.md
CHANGED
|
@@ -42,6 +42,54 @@ file drops, fullscreen, cursors, URL opening, file dialogs, appearance changes,
|
|
|
42
42
|
and PNG capture. Platform availability differs; see [the RBS declarations](../sig/native.rbs)
|
|
43
43
|
and [platform declarations](../sig/platform.rbs) for the exact API.
|
|
44
44
|
|
|
45
|
+
## Clipboard representations
|
|
46
|
+
|
|
47
|
+
`Clipboard::Item` holds eager MIME-keyed data. `text/*` values are UTF-8 text;
|
|
48
|
+
other values are bytes. The window's `clipboard` and `clipboard=` methods remain
|
|
49
|
+
shortcuts for `text/plain`.
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
item = Zaniah::Clipboard::Item.new(
|
|
53
|
+
"text/plain" => "a\tb",
|
|
54
|
+
"text/html" => "<b>a</b>",
|
|
55
|
+
"image/png" => png_bytes
|
|
56
|
+
)
|
|
57
|
+
window.write_clipboard([item])
|
|
58
|
+
window.clipboard_types # available MIME names, without reading data
|
|
59
|
+
content = window.read_clipboard(types: ["text/html", "text/plain"])
|
|
60
|
+
content.types # formats found, in requested order
|
|
61
|
+
content.fetch("text/html")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Headless keeps representations in per-window memory. TUI also keeps every
|
|
65
|
+
representation in memory and sends only `text/plain` writes to the terminal
|
|
66
|
+
with OSC 52; it cannot read the terminal's clipboard. Native macOS, Windows,
|
|
67
|
+
X11, and Wayland exchange `text/plain`, `text/html`, and `image/png` with other
|
|
68
|
+
applications. macOS also preserves arbitrary MIME types under reversible
|
|
69
|
+
`com.noxdea.zaniah.mime.<hex>` pasteboard identifiers; Windows, X11, and
|
|
70
|
+
Wayland use their native format mechanisms. Native clipboard exchange still
|
|
71
|
+
depends on a running desktop session and should be checked on each target OS.
|
|
72
|
+
|
|
73
|
+
## Window state and decorations
|
|
74
|
+
|
|
75
|
+
`window.frame` uses logical screen coordinates; `window.state.to_h` can be saved
|
|
76
|
+
by the application and passed back to `window.restore_state`. Missing displays
|
|
77
|
+
and offscreen frames are corrected toward the primary display. `maximize`,
|
|
78
|
+
`minimize`, `restore`, `fullscreen?`, `always_on_top=`, and `on_state_change`
|
|
79
|
+
operate on the native window where supported. `decorations: :native` is the
|
|
80
|
+
default; `:hidden_titlebar` and `:none` enable a custom titlebar built from
|
|
81
|
+
`window_drag_region` and `window_control` elements. `min_size:`, `resizable:`,
|
|
82
|
+
and macOS-only `traffic_lights:` are creation options.
|
|
83
|
+
|
|
84
|
+
X11 window managers must support EWMH for state and drag requests. On X11,
|
|
85
|
+
`:hidden_titlebar` has the same no-decoration effect as `:none` through Motif
|
|
86
|
+
hints. Wayland does not expose a window position: its frame origin is `nil`
|
|
87
|
+
and setting a position raises. Wayland also cannot request always-on-top or
|
|
88
|
+
programmatically restore a minimized window; those operations raise rather
|
|
89
|
+
than claim success. Client-side decorations on Wayland depend on the compositor
|
|
90
|
+
and its decoration protocol. Verify native placement and drag behavior on each
|
|
91
|
+
target desktop before relying on it.
|
|
92
|
+
|
|
45
93
|
## Displays, file watching, and terminals
|
|
46
94
|
|
|
47
95
|
`Zaniah::Platform.displays` returns the available displays for a backend.
|
|
@@ -73,6 +121,7 @@ ruby examples/native_smoke.rb --gl --check /tmp/zaniah-gl.png
|
|
|
73
121
|
ruby examples/linux_smoke.rb
|
|
74
122
|
ruby examples/linux_smoke.rb --wayland
|
|
75
123
|
ruby examples/native_watch.rb
|
|
124
|
+
ruby examples/native_menu.rb # macOS only
|
|
76
125
|
```
|
|
77
126
|
|
|
78
127
|
Linux XIM composition is exercised in CI with Xvfb and IBus/KKC. Native input,
|
data/docs/tui.md
CHANGED
|
@@ -7,6 +7,7 @@ tests and non-window integrations.
|
|
|
7
7
|
| Component family | Terminal representation |
|
|
8
8
|
| --- | --- |
|
|
9
9
|
| Label, Badge, Avatar | text, `[badge]`, `(initials)` |
|
|
10
|
+
| Kbd | textual shortcut, such as `Ctrl+Shift+P`; multiple strokes are space-separated |
|
|
10
11
|
| Icon | Unicode check/close/search/menu/info/warning symbol; unlabeled unknown icons are omitted |
|
|
11
12
|
| Divider, Card | `─`/`│` and box-drawing characters |
|
|
12
13
|
| Skeleton, Spinner, ProgressBar, Meter | shade cells, `◌`, or a ten-cell progress track; animation jumps to its terminal state |
|
|
@@ -16,7 +17,7 @@ tests and non-window integrations.
|
|
|
16
17
|
| Text inputs | `[value]`; password values are masked and validation errors have `!` |
|
|
17
18
|
| Select, Combobox, MultiSelect | labeled brackets, filtered menu marker, or comma-separated selected values |
|
|
18
19
|
| 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
|
+
| Tooltip, Popover, Menu, MenuBar, Dropdown | status text or a box/menu with `>` selection marker; nested menus expose a Back row |
|
|
20
21
|
| Tabs, Accordion, Collapsible | selected tab in brackets and `[+]`/`[-]` disclosure markers |
|
|
21
22
|
| ScrollView, Scrollbar | clipped child content and `│`/`─` track |
|
|
22
23
|
| Breadcrumb, Pagination, bars | slash-separated path, page status, space-separated content |
|
data/docs/vector.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Vector recording
|
|
2
|
+
|
|
3
|
+
Zaniah records the drawing it already performs without taking ownership of PDF
|
|
4
|
+
pages, paper sizes, or export dialogs. Require `zaniah/vector` and render a normal
|
|
5
|
+
element tree offscreen:
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
document = Zaniah::Vector.record(width: 960, height: 540,
|
|
9
|
+
theme: Zaniah::Theme.light) { slide }
|
|
10
|
+
document.commands.each { |command| export_command(command) }
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
To inspect a displayed frame, attach a recorder before rendering. `Scene#clear`
|
|
14
|
+
resets its commands each frame; save `recorder.document` before the next frame.
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
recorder = Zaniah::Vector::Recorder.new
|
|
18
|
+
window.scene.vector_sink = recorder
|
|
19
|
+
window.tick
|
|
20
|
+
document = recorder.document
|
|
21
|
+
window.scene.vector_sink = nil
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`Document#width` and `#height` are logical pixels. A `Recorder` attached to a
|
|
25
|
+
window receives that window's content size; a recorder attached to a bare
|
|
26
|
+
`Scene` may instead be initialized with `width:` and `height:`. Coordinates use
|
|
27
|
+
a top-left origin and downward y axis; colors are sRGB. Every command carries
|
|
28
|
+
its transform, clip, opacity, layer, and sequence. `Document#commands` is sorted
|
|
29
|
+
by layer and then sequence, matching `Scene#each_command`.
|
|
30
|
+
|
|
31
|
+
| Command | Export information |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `Quad` | Bounds, fill color or gradient, corner radii, border widths/color/style |
|
|
34
|
+
| `Shadow` | Bounds, corner radii, color, blur, spread, inset |
|
|
35
|
+
| `Path` | Alhena outline, fill, stroke, stroke width, fill rule and stroke cap/join |
|
|
36
|
+
| `GlyphRun` | Alhena font, size, `[glyph_id, x, baseline_y]` triples, original text and UTF-8 byte clusters |
|
|
37
|
+
| `Image` | Decoded `Zaniah::Image`, bounds and source rectangle, plus a frozen pixel snapshot |
|
|
38
|
+
| `Underline` | Position, thickness and wave flag |
|
|
39
|
+
| `Raster` | Frozen source texture bytes, pixel dimensions/format, tint and source rectangle |
|
|
40
|
+
|
|
41
|
+
Ordinary sprites and packed sprite batches become `Raster`; bitmap-color text
|
|
42
|
+
uses that fallback for the whole line. Simple SVG fills and strokes become
|
|
43
|
+
`Path` commands. SVG clipping, gradients, dash arrays, masks, group opacity,
|
|
44
|
+
and references use a `Raster`
|
|
45
|
+
fallback, so they are not silently omitted. The `Raster` bytes are a snapshot
|
|
46
|
+
at recording time, even if the source texture changes later. The converter must
|
|
47
|
+
crop to `source`, apply `color` and `opacity`, then respect `transform` and
|
|
48
|
+
`clip`. It may rasterize a `Path` or `GlyphRun` when its output format lacks
|
|
49
|
+
an equivalent vector primitive.
|
|
50
|
+
|
|
51
|
+
The headless offscreen API works on every OS and does not require a GPU.
|
|
52
|
+
Attaching a recorder to macOS, Windows, X11, or Wayland uses the same `Scene`
|
|
53
|
+
path. The TUI backend captures its terminal cells as a single `Raster` fallback
|
|
54
|
+
at an 8-by-20 cell grid, using a bundled font approximation; terminal font
|
|
55
|
+
appearance is outside Zaniah's control. Use the offscreen API when scalable
|
|
56
|
+
text is required.
|
|
57
|
+
|
|
58
|
+
See [ADR 016](adr/016-semantic-vector-recording.md) for why this is separate
|
|
59
|
+
from the GPU command stream. [Vector and list](vector_and_list.md) documents
|
|
60
|
+
the SVG input subset.
|
data/docs/vector_and_list.md
CHANGED
|
@@ -13,15 +13,24 @@ icon = Zaniah::SVG.parse(
|
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
The renderer supports SVG paths, basic shapes, groups, transforms, view boxes,
|
|
16
|
-
inherited fill and stroke, `currentColor`, opacity, local `defs`/`use`,
|
|
17
|
-
user-space clip paths
|
|
16
|
+
inherited fill and stroke, `currentColor`, opacity, local `defs`/`use`,
|
|
17
|
+
user-space clip paths, linear/radial gradients, stroke dash arrays and offsets,
|
|
18
|
+
and local alpha/luminance masks. It is intended for static icons, not arbitrary
|
|
19
|
+
web SVG.
|
|
18
20
|
|
|
19
|
-
Scripts, external references, CSS stylesheets,
|
|
20
|
-
text, markers,
|
|
21
|
+
Scripts, external references, CSS stylesheets, filters, images,
|
|
22
|
+
text, markers, nested viewports, and object-bounding-box clips are
|
|
21
23
|
unsupported and raise `ArgumentError`. Input is bounded to 2 MiB, 10,000 XML
|
|
22
24
|
nodes, 64 levels, and 100,000 path operations; each output texture is limited to
|
|
23
25
|
one megapixel.
|
|
24
26
|
|
|
27
|
+
SVG gradients support ordered stops, local references, `objectBoundingBox` and
|
|
28
|
+
`userSpaceOnUse` coordinates, and the default pad spread. Other spread methods
|
|
29
|
+
and external references remain unsupported. Masks rasterize within the same
|
|
30
|
+
one-megapixel output limit. Gradient, dashed, and masked shapes are represented
|
|
31
|
+
as a `Vector::Raster` fallback when recording; simple solid outlines remain
|
|
32
|
+
`Vector::Path` commands.
|
|
33
|
+
|
|
25
34
|
## Variable-height lists
|
|
26
35
|
|
|
27
36
|
```ruby
|
data/examples/gallery.rb
CHANGED
|
@@ -22,6 +22,7 @@ module Gallery
|
|
|
22
22
|
.child(Zaniah::Div.new.flex_row.items_center.gap(12).children([
|
|
23
23
|
Zaniah::UI::Label.new("Label"), Zaniah::UI::Icon.new(:info, label: "Information"),
|
|
24
24
|
Zaniah::UI::Badge.new("New", variant: :accent), Zaniah::UI::Avatar.new("Ruby UI"),
|
|
25
|
+
Zaniah::UI::Kbd.new("ctrl-shift-p", platform: :windows),
|
|
25
26
|
Zaniah::UI::Spacer.new(4), Zaniah::UI::Skeleton.new]))
|
|
26
27
|
.child(Zaniah::UI::Divider.new)
|
|
27
28
|
.child(Zaniah::UI::EmptyState.new("No results", message: "Try another query").h(64))
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../lib/zaniah"
|
|
4
|
+
abort "This check needs macOS and a graphical login" unless RUBY_PLATFORM.include?("darwin")
|
|
5
|
+
|
|
6
|
+
window = Zaniah::Platform.open_window(backend: :mac, gpu: :metal, width: 96, height: 72,
|
|
7
|
+
title: "Metal effect check")
|
|
8
|
+
begin
|
|
9
|
+
scene = Zaniah::Scene.new
|
|
10
|
+
scene.quad(4, 4, 30, 24, color: Zaniah::Gradient.linear(angle: 0,
|
|
11
|
+
stops: [[0, "#f00"], [0.5, "#0f0"], [1, "#00f"]]))
|
|
12
|
+
scene.quad(38, 4, 30, 24, color: Zaniah::Gradient.conic(stops: [[0, "#f00"], [1, "#00f"]]))
|
|
13
|
+
scene.shadow(28, 40, 32, 18, color: "#000", blur: 3, spread: 1, radius: 4)
|
|
14
|
+
expected = Zaniah::GPU::Software.new(96, 72).render(scene, clear: "#fff")
|
|
15
|
+
window.device.render(scene, clear: "#fff")
|
|
16
|
+
actual = window.device.pixels
|
|
17
|
+
scale = window.scale_factor
|
|
18
|
+
pixel_width = (96 * scale).round
|
|
19
|
+
samples = [[6, 15], [18, 15], [31, 15], [60, 16], [22, 49], [28, 49], [44, 49], [75, 49]]
|
|
20
|
+
samples.each do |x, y|
|
|
21
|
+
index = (y * 96 + x) * 4
|
|
22
|
+
native_index = (((y + 0.5) * scale).floor * pixel_width + ((x + 0.5) * scale).floor) * 4
|
|
23
|
+
left, right = expected.byteslice(index, 4).bytes, actual.byteslice(native_index, 4).bytes
|
|
24
|
+
raise "effect mismatch at #{x},#{y}: #{right.inspect} != #{left.inspect}" unless
|
|
25
|
+
left.zip(right).all? { |a, b| (a - b).abs <= 8 }
|
|
26
|
+
end
|
|
27
|
+
puts "Metal analytic shadow and gradient check: #{samples.length} samples passed"
|
|
28
|
+
ensure
|
|
29
|
+
window.close
|
|
30
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Run on macOS with a graphical session. Check File, Edit, Window, Cmd+S,
|
|
4
|
+
# disabled and checked items, and the Recent submenu after opening it twice.
|
|
5
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
6
|
+
require "zaniah/ui"
|
|
7
|
+
|
|
8
|
+
raise "native_menu.rb requires macOS" unless RUBY_PLATFORM.include?("darwin")
|
|
9
|
+
|
|
10
|
+
app = Zaniah::App.new
|
|
11
|
+
count = 0
|
|
12
|
+
enabled = true
|
|
13
|
+
checked = false
|
|
14
|
+
window = nil
|
|
15
|
+
app.actions.register(:save, title: "Save", enabled: ->(_cx) { enabled }) do |_cx|
|
|
16
|
+
count += 1
|
|
17
|
+
puts "Save invoked #{count} time(s)"
|
|
18
|
+
window.request_frame
|
|
19
|
+
end
|
|
20
|
+
app.actions.register(:toggle, title: "Checked item", checked: ->(_cx) { checked }) do |_cx|
|
|
21
|
+
checked = !checked
|
|
22
|
+
window.request_frame
|
|
23
|
+
end
|
|
24
|
+
app.actions.register(:enable_save, title: "Enable Save") do |_cx|
|
|
25
|
+
enabled = !enabled
|
|
26
|
+
puts "Save enabled: #{enabled}"
|
|
27
|
+
end
|
|
28
|
+
app.menu_bar = Zaniah::Menu.build do
|
|
29
|
+
app_menu
|
|
30
|
+
submenu "File" do
|
|
31
|
+
item :save
|
|
32
|
+
item :enable_save
|
|
33
|
+
separator
|
|
34
|
+
submenu "Recent", items: -> { Zaniah::Menu.build { item :save, title: "Example.txt" }.items }
|
|
35
|
+
end
|
|
36
|
+
submenu "Edit" do
|
|
37
|
+
standard_edit_items
|
|
38
|
+
separator
|
|
39
|
+
item :toggle
|
|
40
|
+
end
|
|
41
|
+
window_menu
|
|
42
|
+
end
|
|
43
|
+
window = app.open_window(backend: :mac, width: 500, height: 180, title: "Zaniah native menu") do
|
|
44
|
+
Zaniah::Div.new.p(24).child(Zaniah::UI::Label.new("Open File/Edit/Window; press Cmd+S. Saves: #{count}"))
|
|
45
|
+
end
|
|
46
|
+
window.dispatcher.keymap.bind("cmd-s", :save)
|
|
47
|
+
if ARGV.include?("--check")
|
|
48
|
+
window.tick
|
|
49
|
+
cocoa = Zaniah::Platform::Mac::O
|
|
50
|
+
menu = cocoa.send(Zaniah::Platform::Mac::App.instance.handle, "mainMenu")
|
|
51
|
+
count = cocoa.send(menu, "numberOfItems", result: :long)
|
|
52
|
+
raise "native main menu was not installed (#{count} items)" unless count == 4
|
|
53
|
+
app.menu_bar = nil
|
|
54
|
+
window.tick
|
|
55
|
+
fallback = cocoa.send(Zaniah::Platform::Mac::App.instance.handle, "mainMenu")
|
|
56
|
+
raise "Quit-only menu was not restored" unless cocoa.send(fallback, "numberOfItems", result: :long) == 1
|
|
57
|
+
puts "Native menu installed"
|
|
58
|
+
window.close
|
|
59
|
+
else
|
|
60
|
+
app.run
|
|
61
|
+
end
|
data/lib/zaniah/app.rb
CHANGED
|
@@ -7,7 +7,7 @@ require_relative "task_executor"
|
|
|
7
7
|
|
|
8
8
|
module Zaniah
|
|
9
9
|
class App
|
|
10
|
-
attr_reader :windows, :executor, :actions
|
|
10
|
+
attr_reader :windows, :executor, :actions, :menu_bar
|
|
11
11
|
|
|
12
12
|
def initialize(clock: MONOTONIC_CLOCK)
|
|
13
13
|
@slots, @generations, @free, @windows, @globals = [], [], [], [], {theme: Theme.dark}
|
|
@@ -131,6 +131,13 @@ module Zaniah
|
|
|
131
131
|
@executor.shutdown
|
|
132
132
|
end
|
|
133
133
|
|
|
134
|
+
def menu_bar=(menu)
|
|
135
|
+
raise ArgumentError, "menu_bar must be a Zaniah::Menu or nil" unless menu.nil? || menu.is_a?(Menu)
|
|
136
|
+
@menu_bar = menu
|
|
137
|
+
@windows.each(&:request_frame)
|
|
138
|
+
menu
|
|
139
|
+
end
|
|
140
|
+
|
|
134
141
|
def hot_reload(paths, **options, &block)
|
|
135
142
|
require_relative "devtools"
|
|
136
143
|
reload = DevTools::HotReload.new(self, paths, **options, &block)
|