ratatui_ruby 1.0.0 → 1.0.1
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/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/lib/ratatui_ruby/version.rb +1 -1
- metadata +1 -232
- data/.builds/ruby-3.2.yml +0 -54
- data/.builds/ruby-3.3.yml +0 -54
- data/.builds/ruby-3.4.yml +0 -54
- data/.builds/ruby-4.0.0.yml +0 -54
- data/.pre-commit-config.yaml +0 -16
- data/.rubocop.yml +0 -10
- data/AGENTS.md +0 -146
- data/CHANGELOG.md +0 -710
- data/README.md +0 -187
- data/README.rdoc +0 -302
- data/Rakefile +0 -11
- data/Steepfile +0 -49
- data/doc/concepts/application_architecture.md +0 -321
- data/doc/concepts/application_testing.md +0 -193
- data/doc/concepts/async.md +0 -190
- data/doc/concepts/custom_widgets.md +0 -247
- data/doc/concepts/debugging.md +0 -401
- data/doc/concepts/event_handling.md +0 -162
- data/doc/concepts/interactive_design.md +0 -146
- data/doc/contributors/auditing/parity.md +0 -239
- data/doc/contributors/design/ruby_frontend.md +0 -420
- data/doc/contributors/design/rust_backend.md +0 -422
- data/doc/contributors/design.md +0 -11
- data/doc/contributors/developing_examples.md +0 -400
- data/doc/contributors/documentation_style.md +0 -121
- data/doc/contributors/index.md +0 -21
- data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -375
- data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -206
- data/doc/contributors/todo/align/terminal.md +0 -647
- data/doc/contributors/todo/future_work.md +0 -169
- data/doc/contributors/upstream_requests/tab_rects.md +0 -173
- data/doc/contributors/upstream_requests/title_rects.md +0 -132
- data/doc/custom.css +0 -22
- data/doc/getting_started/quickstart.md +0 -291
- data/doc/getting_started/why.md +0 -93
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_cli_rich_moments.gif +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/app_debugging_showcase.gif +0 -0
- data/doc/images/app_debugging_showcase.png +0 -0
- data/doc/images/app_login_form.png +0 -0
- data/doc/images/app_stateful_interaction.png +0 -0
- data/doc/images/verify_quickstart_dsl.png +0 -0
- data/doc/images/verify_quickstart_layout.png +0 -0
- data/doc/images/verify_quickstart_lifecycle.png +0 -0
- data/doc/images/verify_readme_usage.png +0 -0
- data/doc/images/widget_barchart.png +0 -0
- data/doc/images/widget_block.png +0 -0
- data/doc/images/widget_box.png +0 -0
- data/doc/images/widget_calendar.png +0 -0
- data/doc/images/widget_canvas.png +0 -0
- data/doc/images/widget_cell.png +0 -0
- data/doc/images/widget_center.png +0 -0
- data/doc/images/widget_chart.png +0 -0
- data/doc/images/widget_gauge.png +0 -0
- data/doc/images/widget_layout_split.png +0 -0
- data/doc/images/widget_line_gauge.png +0 -0
- data/doc/images/widget_list.png +0 -0
- data/doc/images/widget_map.png +0 -0
- data/doc/images/widget_overlay.png +0 -0
- data/doc/images/widget_popup.png +0 -0
- data/doc/images/widget_ratatui_logo.png +0 -0
- data/doc/images/widget_ratatui_mascot.png +0 -0
- data/doc/images/widget_rect.png +0 -0
- data/doc/images/widget_render.png +0 -0
- data/doc/images/widget_rich_text.png +0 -0
- data/doc/images/widget_scroll_text.png +0 -0
- data/doc/images/widget_scrollbar.png +0 -0
- data/doc/images/widget_sparkline.png +0 -0
- data/doc/images/widget_style_colors.png +0 -0
- data/doc/images/widget_table.png +0 -0
- data/doc/images/widget_tabs.png +0 -0
- data/doc/images/widget_text_width.png +0 -0
- data/doc/index.md +0 -39
- data/doc/troubleshooting/async.md +0 -4
- data/doc/troubleshooting/terminal_limitations.md +0 -131
- data/doc/troubleshooting/tui_output.md +0 -197
- data/examples/app_all_events/README.md +0 -114
- data/examples/app_all_events/app.rb +0 -98
- data/examples/app_all_events/model/app_model.rb +0 -159
- data/examples/app_all_events/model/event_color_cycle.rb +0 -43
- data/examples/app_all_events/model/event_entry.rb +0 -94
- data/examples/app_all_events/model/msg.rb +0 -39
- data/examples/app_all_events/model/timestamp.rb +0 -56
- data/examples/app_all_events/update.rb +0 -75
- data/examples/app_all_events/view/app_view.rb +0 -80
- data/examples/app_all_events/view/controls_view.rb +0 -54
- data/examples/app_all_events/view/counts_view.rb +0 -61
- data/examples/app_all_events/view/live_view.rb +0 -72
- data/examples/app_all_events/view/log_view.rb +0 -57
- data/examples/app_all_events/view.rb +0 -9
- data/examples/app_cli_rich_moments/README.md +0 -81
- data/examples/app_cli_rich_moments/app.rb +0 -189
- data/examples/app_color_picker/README.md +0 -156
- data/examples/app_color_picker/app.rb +0 -76
- data/examples/app_color_picker/clipboard.rb +0 -86
- data/examples/app_color_picker/color.rb +0 -193
- data/examples/app_color_picker/controls.rb +0 -92
- data/examples/app_color_picker/copy_dialog.rb +0 -168
- data/examples/app_color_picker/export_pane.rb +0 -128
- data/examples/app_color_picker/harmony.rb +0 -58
- data/examples/app_color_picker/input.rb +0 -176
- data/examples/app_color_picker/main_container.rb +0 -180
- data/examples/app_color_picker/palette.rb +0 -111
- data/examples/app_debugging_showcase/README.md +0 -119
- data/examples/app_debugging_showcase/app.rb +0 -318
- data/examples/app_login_form/README.md +0 -58
- data/examples/app_login_form/app.rb +0 -109
- data/examples/app_stateful_interaction/README.md +0 -35
- data/examples/app_stateful_interaction/app.rb +0 -328
- data/examples/timeout_demo.rb +0 -45
- data/examples/verify_quickstart_dsl/README.md +0 -55
- data/examples/verify_quickstart_dsl/app.rb +0 -49
- data/examples/verify_quickstart_layout/README.md +0 -77
- data/examples/verify_quickstart_layout/app.rb +0 -73
- data/examples/verify_quickstart_lifecycle/README.md +0 -68
- data/examples/verify_quickstart_lifecycle/app.rb +0 -62
- data/examples/verify_readme_usage/README.md +0 -49
- data/examples/verify_readme_usage/app.rb +0 -42
- data/examples/verify_website_managed/README.md +0 -48
- data/examples/verify_website_managed/app.rb +0 -36
- data/examples/verify_website_menu/README.md +0 -60
- data/examples/verify_website_menu/app.rb +0 -84
- data/examples/verify_website_spinner/README.md +0 -44
- data/examples/verify_website_spinner/app.rb +0 -34
- data/examples/widget_barchart/README.md +0 -58
- data/examples/widget_barchart/app.rb +0 -240
- data/examples/widget_block/README.md +0 -44
- data/examples/widget_block/app.rb +0 -258
- data/examples/widget_box/README.md +0 -54
- data/examples/widget_box/app.rb +0 -255
- data/examples/widget_calendar/README.md +0 -48
- data/examples/widget_calendar/app.rb +0 -115
- data/examples/widget_canvas/README.md +0 -31
- data/examples/widget_canvas/app.rb +0 -130
- data/examples/widget_cell/README.md +0 -45
- data/examples/widget_cell/app.rb +0 -112
- data/examples/widget_center/README.md +0 -33
- data/examples/widget_center/app.rb +0 -118
- data/examples/widget_chart/README.md +0 -50
- data/examples/widget_chart/app.rb +0 -220
- data/examples/widget_gauge/README.md +0 -50
- data/examples/widget_gauge/app.rb +0 -229
- data/examples/widget_layout_split/README.md +0 -53
- data/examples/widget_layout_split/app.rb +0 -260
- data/examples/widget_line_gauge/README.md +0 -50
- data/examples/widget_line_gauge/app.rb +0 -219
- data/examples/widget_list/README.md +0 -58
- data/examples/widget_list/app.rb +0 -384
- data/examples/widget_map/README.md +0 -48
- data/examples/widget_map/app.rb +0 -95
- data/examples/widget_overlay/README.md +0 -45
- data/examples/widget_overlay/app.rb +0 -250
- data/examples/widget_popup/README.md +0 -45
- data/examples/widget_popup/app.rb +0 -106
- data/examples/widget_ratatui_logo/README.md +0 -43
- data/examples/widget_ratatui_logo/app.rb +0 -104
- data/examples/widget_ratatui_mascot/README.md +0 -43
- data/examples/widget_ratatui_mascot/app.rb +0 -95
- data/examples/widget_rect/README.md +0 -53
- data/examples/widget_rect/app.rb +0 -222
- data/examples/widget_render/README.md +0 -46
- data/examples/widget_render/app.rb +0 -186
- data/examples/widget_render/app.rbs +0 -41
- data/examples/widget_rich_text/README.md +0 -44
- data/examples/widget_rich_text/app.rb +0 -193
- data/examples/widget_scroll_text/README.md +0 -46
- data/examples/widget_scroll_text/app.rb +0 -109
- data/examples/widget_scrollbar/README.md +0 -46
- data/examples/widget_scrollbar/app.rb +0 -155
- data/examples/widget_sparkline/README.md +0 -51
- data/examples/widget_sparkline/app.rb +0 -277
- data/examples/widget_style_colors/README.md +0 -43
- data/examples/widget_style_colors/app.rb +0 -83
- data/examples/widget_table/README.md +0 -57
- data/examples/widget_table/app.rb +0 -279
- data/examples/widget_tabs/README.md +0 -50
- data/examples/widget_tabs/app.rb +0 -183
- data/examples/widget_text_width/README.md +0 -44
- data/examples/widget_text_width/app.rb +0 -117
- data/migrate_to_buffer.rb +0 -145
- data/mise.toml +0 -8
- data/tasks/autodoc/examples.rb +0 -87
- data/tasks/autodoc/member.rb +0 -58
- data/tasks/autodoc/name.rb +0 -21
- data/tasks/autodoc.rake +0 -21
- data/tasks/bump/cargo_lockfile.rb +0 -21
- data/tasks/bump/changelog.rb +0 -47
- data/tasks/bump/header.rb +0 -32
- data/tasks/bump/history.rb +0 -32
- data/tasks/bump/links.rb +0 -69
- data/tasks/bump/manifest.rb +0 -33
- data/tasks/bump/ruby_gem.rb +0 -49
- data/tasks/bump/sem_ver.rb +0 -40
- data/tasks/bump/unreleased_section.rb +0 -56
- data/tasks/bump.rake +0 -51
- data/tasks/doc.rake +0 -887
- data/tasks/example_viewer.html.erb +0 -172
- data/tasks/extension.rake +0 -14
- data/tasks/license/headers_md.rb +0 -223
- data/tasks/license/headers_rb.rb +0 -210
- data/tasks/license/license_utils.rb +0 -130
- data/tasks/license/snippets_md.rb +0 -315
- data/tasks/license/snippets_rdoc.rb +0 -150
- data/tasks/license.rake +0 -91
- data/tasks/lint.rake +0 -170
- data/tasks/rdoc_config.rb +0 -29
- data/tasks/resources/build.yml.erb +0 -60
- data/tasks/resources/index.html.erb +0 -141
- data/tasks/resources/rubies.yml +0 -7
- data/tasks/sourcehut.rake +0 -110
- data/tasks/steep.rake +0 -11
- data/tasks/terminal_preview/app_screenshot.rb +0 -45
- data/tasks/terminal_preview/crash_report.rb +0 -54
- data/tasks/terminal_preview/example_app.rb +0 -27
- data/tasks/terminal_preview/launcher_script.rb +0 -48
- data/tasks/terminal_preview/preview_collection.rb +0 -60
- data/tasks/terminal_preview/preview_timing.rb +0 -24
- data/tasks/terminal_preview/safety_confirmation.rb +0 -58
- data/tasks/terminal_preview/saved_screenshot.rb +0 -56
- data/tasks/terminal_preview/system_appearance.rb +0 -13
- data/tasks/terminal_preview/terminal_window.rb +0 -138
- data/tasks/terminal_preview/window_id.rb +0 -16
- data/tasks/terminal_preview.rake +0 -30
- data/tasks/test.rake +0 -33
- data/tasks/website/index_page.rb +0 -30
- data/tasks/website/version.rb +0 -127
- data/tasks/website/version_menu.rb +0 -68
- data/tasks/website/versioned_documentation.rb +0 -83
- data/tasks/website/website.rb +0 -53
- data/tasks/website.rake +0 -28
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
-
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
-
-->
|
|
5
|
-
|
|
6
|
-
# Future Work
|
|
7
|
-
|
|
8
|
-
Ideas for post-v1.0.0 development. These do not block the initial release.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Port Upstream Ratatui Examples
|
|
13
|
-
|
|
14
|
-
Ratatui ships [example applications](https://github.com/ratatui/ratatui/tree/main/examples) demonstrating real-world patterns. Porting these to RatatuiRuby would:
|
|
15
|
-
|
|
16
|
-
1. Validate API parity in realistic usage
|
|
17
|
-
2. Provide learning resources for Ruby developers
|
|
18
|
-
3. Surface gaps in the alignment audit
|
|
19
|
-
|
|
20
|
-
**Candidates for porting:**
|
|
21
|
-
|
|
22
|
-
- `demo2` — Kitchen sink showcasing all widgets
|
|
23
|
-
- `async` — Background task handling
|
|
24
|
-
- `user_input` — Text input patterns
|
|
25
|
-
- `popup` — Modal dialogs
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## Cross-Platform Distribution
|
|
30
|
-
|
|
31
|
-
[CosmoRuby](https://github.com/igravious/cosmoruby) aims to build Ruby with [Cosmopolitan Libc](https://justine.lol/cosmopolitan/), producing single binaries that run on Linux, macOS, Windows, and BSD without recompilation.
|
|
32
|
-
|
|
33
|
-
**Gap:** RatatuiRuby is a native extension. CosmoRuby does not yet support native extensions.
|
|
34
|
-
|
|
35
|
-
**When this becomes viable:**
|
|
36
|
-
|
|
37
|
-
- CosmoRuby adds native extension support
|
|
38
|
-
- Demand emerges for single-binary TUI app distribution
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## Terminal Graphics Protocols
|
|
43
|
-
|
|
44
|
-
Terminal graphics protocols (Sixel, Kitty graphics, iTerm2 inline images) bypass the character cell model. Supporting them requires extension points that do not exist today.
|
|
45
|
-
|
|
46
|
-
### What Third Parties Need
|
|
47
|
-
|
|
48
|
-
| Extension Point | Purpose | Status |
|
|
49
|
-
|-----------------|---------|--------|
|
|
50
|
-
| `Draw::RawCmd` | Write raw escape sequences, bypassing the cell buffer | Not available |
|
|
51
|
-
| Terminal capability queries | Detect if terminal supports Sixel, Kitty, etc. | Not available |
|
|
52
|
-
| Frame hooks | Run code before/after buffer flush | Not available |
|
|
53
|
-
|
|
54
|
-
### Why These Matter
|
|
55
|
-
|
|
56
|
-
The `ratatui-image` crate provides graphics support for Rust Ratatui apps. A `ratatui_ruby-sixels` gem could wrap it, but only if RatatuiRuby exposes:
|
|
57
|
-
|
|
58
|
-
1. **Raw output access** — Sixel data writes directly to stdout as escape sequences
|
|
59
|
-
2. **Capability detection** — Apps need to query terminal support before sending graphics
|
|
60
|
-
3. **Render coordination** — Graphics must be positioned after the cell buffer renders
|
|
61
|
-
|
|
62
|
-
### Implementation Sketch
|
|
63
|
-
|
|
64
|
-
<!-- SPDX-SnippetBegin -->
|
|
65
|
-
<!--
|
|
66
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
67
|
-
SPDX-License-Identifier: MIT-0
|
|
68
|
-
-->
|
|
69
|
-
```ruby
|
|
70
|
-
# Proposed API (not implemented)
|
|
71
|
-
|
|
72
|
-
# 1. Raw draw command
|
|
73
|
-
class Draw
|
|
74
|
-
RawCmd = Data.define(:bytes)
|
|
75
|
-
def self.raw(bytes) = RawCmd.new(bytes:)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# 2. Terminal queries
|
|
79
|
-
RatatuiRuby.terminal_supports?(:sixel) # => true/false
|
|
80
|
-
RatatuiRuby.terminal_supports?(:kitty) # => true/false
|
|
81
|
-
RatatuiRuby.terminal_size_pixels # => { width: 1920, height: 1080 }
|
|
82
|
-
|
|
83
|
-
# 3. Custom widget using raw output
|
|
84
|
-
class SixelImage
|
|
85
|
-
def render(area)
|
|
86
|
-
sixel_data = encode_image_as_sixel(@image, area)
|
|
87
|
-
[RatatuiRuby::Draw.raw(sixel_data)]
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
```
|
|
91
|
-
<!-- SPDX-SnippetEnd -->
|
|
92
|
-
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
## Custom Backends
|
|
96
|
-
|
|
97
|
-
Currently hardcoded to Crossterm. A third party might want:
|
|
98
|
-
|
|
99
|
-
- SSH backend (render to a remote terminal)
|
|
100
|
-
- Web backend (render to browser canvas)
|
|
101
|
-
- Recording backend (capture frames for replay)
|
|
102
|
-
|
|
103
|
-
**Gap:** No backend plugin architecture.
|
|
104
|
-
|
|
105
|
-
---
|
|
106
|
-
|
|
107
|
-
## Custom Event Sources
|
|
108
|
-
|
|
109
|
-
Currently hardcoded to Crossterm events. A third party might want:
|
|
110
|
-
|
|
111
|
-
- Network events (WebSocket messages as TUI events)
|
|
112
|
-
- File watcher events
|
|
113
|
-
- IPC events
|
|
114
|
-
|
|
115
|
-
**Gap:** No event source plugin architecture.
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## Event Test Doubles
|
|
120
|
-
|
|
121
|
-
The TestHelper module provides `MockFrame` and `StubRect` for testing render logic in isolation. However, testing `handle_event` currently requires `init_test_terminal` and `inject_test_event`.
|
|
122
|
-
|
|
123
|
-
For pure unit tests of Kit components, stub event objects would be useful:
|
|
124
|
-
|
|
125
|
-
<!-- SPDX-SnippetBegin -->
|
|
126
|
-
<!--
|
|
127
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
128
|
-
SPDX-License-Identifier: MIT-0
|
|
129
|
-
-->
|
|
130
|
-
```ruby
|
|
131
|
-
# Proposed API (not implemented)
|
|
132
|
-
StubKeyEvent = Data.define(:code, :modifiers) do
|
|
133
|
-
def initialize(code:, modifiers: [])
|
|
134
|
-
super
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def key? = true
|
|
138
|
-
def mouse? = false
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
StubMouseEvent = Data.define(:kind, :button, :x, :y, :modifiers) do
|
|
142
|
-
def initialize(kind:, button: "left", x:, y:, modifiers: [])
|
|
143
|
-
super
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def key? = false
|
|
147
|
-
def mouse? = true
|
|
148
|
-
def down? = kind == "down"
|
|
149
|
-
def up? = kind == "up"
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Usage
|
|
153
|
-
event = StubKeyEvent.new(code: "a", modifiers: ["ctrl"])
|
|
154
|
-
result = component.handle_event(event)
|
|
155
|
-
assert_equal :consumed, result
|
|
156
|
-
```
|
|
157
|
-
<!-- SPDX-SnippetEnd -->
|
|
158
|
-
|
|
159
|
-
This would let developers test component event handling without terminal dependencies.
|
|
160
|
-
|
|
161
|
-
## Prioritization
|
|
162
|
-
|
|
163
|
-
When prioritizing post-1.0 work, consider:
|
|
164
|
-
|
|
165
|
-
1. **Port upstream examples** — Validates parity, provides learning resources
|
|
166
|
-
2. **`Draw::RawCmd`** — Lowest effort, highest impact for graphics support
|
|
167
|
-
3. **Terminal capability queries** — Required for any graphics work
|
|
168
|
-
4. **Backend plugins** — Large undertaking, defer until clear demand
|
|
169
|
-
5. **CosmoRuby** — Blocked on upstream; monitor progress
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
-
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
-
-->
|
|
5
|
-
|
|
6
|
-
# Feature Request: Expose Tabs Title Rects
|
|
7
|
-
|
|
8
|
-
## Summary
|
|
9
|
-
|
|
10
|
-
`Tabs` computes the bounding rect for each tab title during rendering but does not expose this information. Interactive applications need these rects for mouse click hit-testing.
|
|
11
|
-
|
|
12
|
-
## The Problem
|
|
13
|
-
|
|
14
|
-
Building clickable tab interfaces requires knowing where each tab renders. When a user clicks within the tabs area, the application cannot determine which specific tab was clicked without duplicating the internal layout algorithm.
|
|
15
|
-
|
|
16
|
-
Currently, the only options are:
|
|
17
|
-
|
|
18
|
-
1. **Recompute the layout manually.** Duplicate the logic from `render_tabs`, accounting for padding, dividers, and title widths. This is fragile—any upstream change breaks the user's code.
|
|
19
|
-
2. **Use coarse hit-testing.** Check if a click is anywhere in the tabs area, then guess based on x-position. This breaks when titles have different widths or styled content.
|
|
20
|
-
|
|
21
|
-
Neither approach is satisfactory.
|
|
22
|
-
|
|
23
|
-
## Use Case
|
|
24
|
-
|
|
25
|
-
Consider a TUI with a tabbed interface:
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
┌Announce v0.7.3───────────────────────────────emate┐
|
|
29
|
-
│ Preview Email ▸ Preview Commit ▸ Announce │
|
|
30
|
-
│ │
|
|
31
|
-
└───────────────────────────────────────────────────┘
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
The application wants to detect clicks on individual tab titles (`"Preview Email"`, `"Preview Commit"`, `"Announce"`) and switch to that tab.
|
|
35
|
-
|
|
36
|
-
Without title rects, the application must manually compute where each tab renders:
|
|
37
|
-
|
|
38
|
-
```rust
|
|
39
|
-
// Manual calculation - fragile and duplicates internal logic
|
|
40
|
-
let divider_width = 3; // " ▸ " is 3 characters
|
|
41
|
-
let content_row = area.y + 1; // Skip top border
|
|
42
|
-
let mut x = area.x + 2; // Skip border + padding
|
|
43
|
-
|
|
44
|
-
let tab_rects: Vec<Rect> = titles.iter().map(|title| {
|
|
45
|
-
let tab_width = title.len() as u16;
|
|
46
|
-
let rect = Rect::new(x, content_row, tab_width, 1);
|
|
47
|
-
x += tab_width + divider_width;
|
|
48
|
-
rect
|
|
49
|
-
}).collect();
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
This duplicates private logic from `Tabs::render_tabs` and breaks when:
|
|
53
|
-
|
|
54
|
-
- Padding is configured differently (`padding_left`, `padding_right`)
|
|
55
|
-
- Divider width changes
|
|
56
|
-
- Upstream layout logic changes
|
|
57
|
-
- A block is present (affects inner area calculation)
|
|
58
|
-
|
|
59
|
-
## Current State (v0.30.0)
|
|
60
|
-
|
|
61
|
-
`Tabs` has a private `render_tabs` method that computes title areas:
|
|
62
|
-
|
|
63
|
-
```rust
|
|
64
|
-
// From src/widgets/tabs.rs - private rendering logic
|
|
65
|
-
fn render_tabs(&self, tabs_area: Rect, buf: &mut Buffer) {
|
|
66
|
-
let mut x = tabs_area.left();
|
|
67
|
-
for (i, title) in self.titles.iter().enumerate() {
|
|
68
|
-
// ...padding and title rendering...
|
|
69
|
-
|
|
70
|
-
// Title rect is computed here but not exposed
|
|
71
|
-
if Some(i) == self.selected {
|
|
72
|
-
buf.set_style(
|
|
73
|
-
Rect {
|
|
74
|
-
x,
|
|
75
|
-
y: tabs_area.top(),
|
|
76
|
-
width: pos.0.saturating_sub(x),
|
|
77
|
-
height: 1,
|
|
78
|
-
},
|
|
79
|
-
self.highlight_style,
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
// ...
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
The rect is computed for applying `highlight_style` but is not accessible to users.
|
|
88
|
-
|
|
89
|
-
## Proposed API
|
|
90
|
-
|
|
91
|
-
Following the pattern established by `Block::inner(area)`, add a pure computation method that takes an area and returns computed sub-rects without rendering:
|
|
92
|
-
|
|
93
|
-
```rust
|
|
94
|
-
impl Tabs {
|
|
95
|
-
/// Returns the bounding rect for each tab title given an area.
|
|
96
|
-
///
|
|
97
|
-
/// The rects are returned in the same order as titles were added.
|
|
98
|
-
/// Useful for hit-testing mouse clicks against specific tabs.
|
|
99
|
-
///
|
|
100
|
-
/// # Example
|
|
101
|
-
///
|
|
102
|
-
/// ```rust
|
|
103
|
-
/// let tabs = Tabs::new(["Tab 1", "Tab 2", "Tab 3"])
|
|
104
|
-
/// .divider(" | ");
|
|
105
|
-
///
|
|
106
|
-
/// let rects = tabs.title_rects(area);
|
|
107
|
-
/// for (i, rect) in rects.iter().enumerate() {
|
|
108
|
-
/// if rect.contains(mouse_position) {
|
|
109
|
-
/// selected_tab = i;
|
|
110
|
-
/// break;
|
|
111
|
-
/// }
|
|
112
|
-
/// }
|
|
113
|
-
/// ```
|
|
114
|
-
pub fn title_rects(&self, area: Rect) -> Vec<Rect> { ... }
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Alternatively, a single-lookup method:
|
|
119
|
-
|
|
120
|
-
```rust
|
|
121
|
-
/// Returns the rect for the tab at the given index.
|
|
122
|
-
pub fn title_rect(&self, area: Rect, index: usize) -> Option<Rect> { ... }
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## Workaround
|
|
126
|
-
|
|
127
|
-
Without this API, users must replicate the tab layout algorithm. Here is the current approach used in RatatuiRuby:
|
|
128
|
-
|
|
129
|
-
```rust
|
|
130
|
-
// Manually compute tab title positions
|
|
131
|
-
let divider_width = 3; // " ▸ " is 3 characters
|
|
132
|
-
let content_row = area.y + 1; // Skip top border
|
|
133
|
-
let mut x = area.x + 2; // Skip left border + padding
|
|
134
|
-
|
|
135
|
-
let tab_rects: Vec<Rect> = TABS.iter().map(|title| {
|
|
136
|
-
let tab_width = title.len() as u16;
|
|
137
|
-
let rect = Rect::new(x, content_row, tab_width, 1);
|
|
138
|
-
x += tab_width + divider_width;
|
|
139
|
-
rect
|
|
140
|
-
}).collect();
|
|
141
|
-
|
|
142
|
-
// Hit testing
|
|
143
|
-
for (i, rect) in tab_rects.iter().enumerate() {
|
|
144
|
-
if rect.contains(click_position) {
|
|
145
|
-
current_tab = i;
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
This works for simple cases but breaks when:
|
|
152
|
-
|
|
153
|
-
- `padding_left` or `padding_right` are non-default
|
|
154
|
-
- The divider is styled (Span width differs from string length)
|
|
155
|
-
- A block wraps the tabs (inner area differs)
|
|
156
|
-
- Title text is styled (Line width differs from string length)
|
|
157
|
-
|
|
158
|
-
## Impact
|
|
159
|
-
|
|
160
|
-
This feature benefits any application with clickable tabs:
|
|
161
|
-
|
|
162
|
-
- Tab-based navigation interfaces
|
|
163
|
-
- Multi-panel applications with panel selectors
|
|
164
|
-
- Mode switchers (edit/view/preview)
|
|
165
|
-
- Category selectors
|
|
166
|
-
|
|
167
|
-
The `Tabs` widget is commonly used for navigation. Mouse interaction is a natural expectation for TUI applications running in modern terminals with mouse support.
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
This issue includes creative contributions from Claude (Anthropic) via Antigravity (Google). [https://declare-ai.org/1.0.0/creative.html](https://declare-ai.org/1.0.0/creative.html)
|
|
172
|
-
|
|
173
|
-
*Discovered while implementing click handling for tab navigation in RatatuiRuby.*
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
-
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
-
-->
|
|
5
|
-
|
|
6
|
-
# Feature Request: Expose Block Title Rects
|
|
7
|
-
|
|
8
|
-
## Summary
|
|
9
|
-
|
|
10
|
-
`Block` computes the position of each title during rendering but does not expose this information. Interactive applications need title rects for mouse click hit-testing.
|
|
11
|
-
|
|
12
|
-
## The Problem
|
|
13
|
-
|
|
14
|
-
Building clickable TUI interfaces requires knowing where widgets render. When a block has multiple titles (e.g., a left-aligned title and a right-aligned toggle label), each title occupies a specific screen region. The application cannot query these regions.
|
|
15
|
-
|
|
16
|
-
Currently, the only options are:
|
|
17
|
-
|
|
18
|
-
1. **Recompute the layout manually.** Duplicate the logic from `render_left_titles`, `render_right_titles`, and `render_center_titles`. This is fragile—any upstream change breaks the user's code.
|
|
19
|
-
2. **Use coarse hit-testing.** Check if a click is anywhere in the block's top border row. This cannot distinguish between multiple titles.
|
|
20
|
-
|
|
21
|
-
Neither approach is satisfactory.
|
|
22
|
-
|
|
23
|
-
## Use Case
|
|
24
|
-
|
|
25
|
-
Consider a TUI with a tabbed interface where the block's title bar contains:
|
|
26
|
-
|
|
27
|
-
- A left-aligned title: `"Announce v0.7.3"`
|
|
28
|
-
- A right-aligned toggle: `"emate"` (clickable to switch email clients)
|
|
29
|
-
|
|
30
|
-
```
|
|
31
|
-
┌Announce v0.7.3───────────────────────────────emate┐
|
|
32
|
-
│ Preview Email ▸ Preview Commit ▸ Announce │
|
|
33
|
-
│ │
|
|
34
|
-
└───────────────────────────────────────────────────┘
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
The application wants to:
|
|
38
|
-
|
|
39
|
-
1. Detect clicks on `"emate"` and toggle the email client
|
|
40
|
-
2. Detect clicks on tab names and switch tabs
|
|
41
|
-
3. Ignore clicks on the border characters
|
|
42
|
-
|
|
43
|
-
Without title rects, the application must manually compute where `"emate"` renders based on block width, borders, alignment, and title content. This duplicates private logic from `Block::render_right_titles`.
|
|
44
|
-
|
|
45
|
-
## Current State (v0.30.0)
|
|
46
|
-
|
|
47
|
-
`Block` has private methods that compute title areas:
|
|
48
|
-
|
|
49
|
-
```rust
|
|
50
|
-
// Private - not accessible to users
|
|
51
|
-
fn titles_area(&self, area: Rect, position: Position) -> Rect { ... }
|
|
52
|
-
fn render_left_titles(&self, position: Position, area: Rect, buf: &mut Buffer) { ... }
|
|
53
|
-
fn render_center_titles(&self, position: Position, area: Rect, buf: &mut Buffer) { ... }
|
|
54
|
-
fn render_right_titles(&self, position: Position, area: Rect, buf: &mut Buffer) { ... }
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
The `titles_area` method computes the general title region. The `render_*_titles` methods compute individual title rects during rendering but do not expose them.
|
|
58
|
-
|
|
59
|
-
## Proposed API
|
|
60
|
-
|
|
61
|
-
Following the pattern established by `Block::inner(area)`, add a pure computation method that takes an area and returns computed sub-rects without rendering:
|
|
62
|
-
|
|
63
|
-
```rust
|
|
64
|
-
impl Block {
|
|
65
|
-
/// Returns the bounding rect for each title given an area.
|
|
66
|
-
///
|
|
67
|
-
/// The rects are returned in the same order as titles were added.
|
|
68
|
-
/// Useful for hit-testing mouse clicks against specific titles.
|
|
69
|
-
///
|
|
70
|
-
/// # Example
|
|
71
|
-
///
|
|
72
|
-
/// ```rust
|
|
73
|
-
/// let block = Block::bordered()
|
|
74
|
-
/// .title_top("Left Title")
|
|
75
|
-
/// .title_top(Line::from("Right").right_aligned());
|
|
76
|
-
///
|
|
77
|
-
/// let rects = block.title_rects(area);
|
|
78
|
-
/// if rects[1].contains(mouse_position) {
|
|
79
|
-
/// // Clicked on "Right"
|
|
80
|
-
/// }
|
|
81
|
-
/// ```
|
|
82
|
-
pub fn title_rects(&self, area: Rect) -> Vec<Rect> { ... }
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Alternatively, expose the individual areas by alignment:
|
|
87
|
-
|
|
88
|
-
```rust
|
|
89
|
-
/// Returns the rect for the title at the given index.
|
|
90
|
-
pub fn title_rect(&self, area: Rect, index: usize) -> Option<Rect> { ... }
|
|
91
|
-
|
|
92
|
-
/// Returns the titles area for a given position (top or bottom).
|
|
93
|
-
pub fn titles_area(&self, area: Rect, position: Position) -> Rect { ... }
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Workaround
|
|
97
|
-
|
|
98
|
-
Without this API, users must replicate the title layout algorithm. Here is the current approach used in RatatuiRuby:
|
|
99
|
-
|
|
100
|
-
```rust
|
|
101
|
-
// Manually compute right-aligned title position
|
|
102
|
-
let email_label_width = email_client.len() as u16;
|
|
103
|
-
let email_label_x = area.x + area.width - email_label_width - 2; // border + padding
|
|
104
|
-
let email_label_rect = Rect::new(email_label_x, area.y, email_label_width, 1);
|
|
105
|
-
|
|
106
|
-
if email_label_rect.contains(click_position) {
|
|
107
|
-
toggle_email_client();
|
|
108
|
-
}
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
This works for simple cases but breaks when:
|
|
112
|
-
|
|
113
|
-
- Borders are not all present (`Borders::LEFT` affects x offset)
|
|
114
|
-
- Multiple right-aligned titles exist (spacing affects position)
|
|
115
|
-
- Upstream layout logic changes
|
|
116
|
-
|
|
117
|
-
## Impact
|
|
118
|
-
|
|
119
|
-
This feature benefits any application with interactive block titles:
|
|
120
|
-
|
|
121
|
-
- Clickable tabs in title bars
|
|
122
|
-
- Toggle buttons in headers
|
|
123
|
-
- Breadcrumb navigation
|
|
124
|
-
- Window controls (minimize/maximize/close buttons in the title area)
|
|
125
|
-
|
|
126
|
-
Related discussion: [ratatui#738](https://github.com/ratatui/ratatui/issues/738) (title positioning behavior)
|
|
127
|
-
|
|
128
|
-
---
|
|
129
|
-
|
|
130
|
-
This issue includes creative contributions from Claude (Anthropic) via Antigravity (Google). [https://declare-ai.org/1.0.0/creative.html](https://declare-ai.org/1.0.0/creative.html)
|
|
131
|
-
|
|
132
|
-
*Discovered while implementing click handling for block title toggles in RatatuiRuby.*
|
data/doc/custom.css
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
3
|
-
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
img {
|
|
7
|
-
max-width: 100%;
|
|
8
|
-
height: auto;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.theme-toggle {
|
|
12
|
-
margin-left: 0 !important;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/* Terminal Previews (Native PNGs)
|
|
16
|
-
* The images already contain the window chrome and shadows.
|
|
17
|
-
* We just need to center them and ensure they scale down on mobile.
|
|
18
|
-
*/
|
|
19
|
-
img[src*="images/"] {
|
|
20
|
-
display: block;
|
|
21
|
-
margin: 2em auto;
|
|
22
|
-
}
|