ratatui_ruby 0.5.0 → 0.7.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/.builds/ruby-3.2.yml +1 -1
- data/.builds/ruby-3.3.yml +1 -1
- data/.builds/ruby-3.4.yml +1 -1
- data/.builds/ruby-4.0.0.yml +1 -1
- data/AGENTS.md +10 -4
- data/CHANGELOG.md +79 -7
- data/README.md +37 -5
- data/REUSE.toml +2 -7
- data/doc/application_architecture.md +96 -22
- data/doc/application_testing.md +76 -30
- data/doc/contributors/architectural_overhaul/chat_conversations.md +4952 -0
- data/doc/contributors/architectural_overhaul/implementation_plan.md +60 -0
- data/doc/contributors/architectural_overhaul/task.md +37 -0
- data/doc/contributors/design/ruby_frontend.md +288 -56
- data/doc/contributors/design/rust_backend.md +349 -54
- data/doc/contributors/developing_examples.md +134 -49
- data/doc/contributors/index.md +7 -5
- data/doc/contributors/v1.0.0_blockers.md +1729 -0
- data/doc/event_handling.md +11 -3
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_color_picker.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_demo.png +0 -0
- data/doc/images/widget_block_demo.png +0 -0
- data/doc/images/widget_canvas_demo.png +0 -0
- data/doc/images/widget_cell_demo.png +0 -0
- data/doc/images/widget_center_demo.png +0 -0
- data/doc/images/widget_chart_demo.png +0 -0
- data/doc/images/widget_list_demo.png +0 -0
- data/doc/images/widget_overlay_demo.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_sparkline_demo.png +0 -0
- data/doc/images/widget_table_demo.png +0 -0
- data/doc/images/widget_tabs_demo.png +0 -0
- data/doc/images/widget_text_width.png +0 -0
- data/doc/index.md +11 -6
- data/doc/interactive_design.md +2 -2
- data/doc/quickstart.md +127 -165
- data/doc/terminal_limitations.md +92 -0
- data/doc/v0.7.0_migration.md +236 -0
- data/doc/why.md +93 -0
- data/examples/app_all_events/README.md +47 -27
- data/examples/app_all_events/app.rb +38 -35
- data/examples/app_all_events/model/app_model.rb +157 -0
- data/examples/app_all_events/model/event_entry.rb +17 -0
- data/examples/app_all_events/model/msg.rb +37 -0
- data/examples/app_all_events/update.rb +73 -0
- data/examples/app_all_events/view/app_view.rb +9 -9
- data/examples/app_all_events/view/controls_view.rb +9 -7
- data/examples/app_all_events/view/counts_view.rb +13 -9
- data/examples/app_all_events/view/live_view.rb +9 -8
- data/examples/app_all_events/view/log_view.rb +11 -16
- data/examples/app_color_picker/README.md +84 -42
- data/examples/app_color_picker/app.rb +24 -62
- data/examples/app_color_picker/controls.rb +90 -0
- data/examples/app_color_picker/copy_dialog.rb +45 -49
- data/examples/app_color_picker/export_pane.rb +126 -0
- data/examples/app_color_picker/input.rb +99 -67
- data/examples/app_color_picker/main_container.rb +178 -0
- data/examples/app_color_picker/palette.rb +55 -26
- data/examples/app_login_form/README.md +49 -0
- data/examples/app_login_form/app.rb +2 -3
- data/examples/app_stateful_interaction/README.md +33 -0
- data/examples/app_stateful_interaction/app.rb +272 -0
- data/examples/timeout_demo.rb +43 -0
- data/examples/verify_quickstart_dsl/README.md +49 -0
- data/examples/verify_quickstart_dsl/app.rb +2 -0
- data/examples/verify_quickstart_layout/README.md +71 -0
- data/examples/verify_quickstart_layout/app.rb +2 -0
- data/examples/verify_quickstart_lifecycle/README.md +56 -0
- data/examples/verify_quickstart_lifecycle/app.rb +10 -4
- data/examples/verify_readme_usage/README.md +43 -0
- data/examples/verify_readme_usage/app.rb +8 -2
- data/examples/widget_barchart_demo/README.md +50 -0
- data/examples/widget_barchart_demo/app.rb +5 -5
- data/examples/widget_block_demo/README.md +36 -0
- data/examples/widget_block_demo/app.rb +256 -0
- data/examples/widget_box_demo/README.md +45 -0
- data/examples/widget_calendar_demo/README.md +39 -0
- data/examples/widget_calendar_demo/app.rb +5 -1
- data/examples/widget_canvas_demo/README.md +27 -0
- data/examples/widget_canvas_demo/app.rb +123 -0
- data/examples/widget_cell_demo/README.md +36 -0
- data/examples/widget_cell_demo/app.rb +31 -24
- data/examples/widget_center_demo/README.md +29 -0
- data/examples/widget_center_demo/app.rb +116 -0
- data/examples/widget_chart_demo/README.md +41 -0
- data/examples/widget_chart_demo/app.rb +7 -2
- data/examples/widget_gauge_demo/README.md +41 -0
- data/examples/widget_layout_split/README.md +44 -0
- data/examples/widget_line_gauge_demo/README.md +41 -0
- data/examples/widget_list_demo/README.md +49 -0
- data/examples/widget_list_demo/app.rb +91 -107
- data/examples/widget_map_demo/README.md +39 -0
- data/examples/{app_map_demo → widget_map_demo}/app.rb +4 -4
- data/examples/widget_overlay_demo/README.md +36 -0
- data/examples/widget_overlay_demo/app.rb +248 -0
- data/examples/widget_popup_demo/README.md +36 -0
- data/examples/widget_ratatui_logo_demo/README.md +34 -0
- data/examples/widget_ratatui_logo_demo/app.rb +1 -1
- data/examples/widget_ratatui_mascot_demo/README.md +34 -0
- data/examples/widget_rect/README.md +38 -0
- data/examples/widget_render/README.md +37 -0
- data/examples/widget_render/app.rb +3 -3
- data/examples/widget_rich_text/README.md +35 -0
- data/examples/widget_rich_text/app.rb +62 -33
- data/examples/widget_scroll_text/README.md +37 -0
- data/examples/widget_scroll_text/app.rb +0 -1
- data/examples/widget_scrollbar_demo/README.md +37 -0
- data/examples/widget_sparkline_demo/README.md +42 -0
- data/examples/widget_sparkline_demo/app.rb +4 -3
- data/examples/widget_style_colors/README.md +34 -0
- data/examples/widget_table_demo/README.md +48 -0
- data/examples/{app_table_select → widget_table_demo}/app.rb +65 -12
- data/examples/widget_tabs_demo/README.md +41 -0
- data/examples/widget_tabs_demo/app.rb +15 -1
- data/examples/widget_text_width/README.md +35 -0
- data/examples/widget_text_width/app.rb +113 -0
- data/exe/.gitkeep +0 -0
- data/ext/ratatui_ruby/Cargo.lock +11 -4
- data/ext/ratatui_ruby/Cargo.toml +2 -1
- data/ext/ratatui_ruby/src/events.rs +238 -26
- data/ext/ratatui_ruby/src/frame.rs +116 -3
- data/ext/ratatui_ruby/src/lib.rs +37 -6
- data/ext/ratatui_ruby/src/rendering.rs +22 -21
- data/ext/ratatui_ruby/src/string_width.rs +101 -0
- data/ext/ratatui_ruby/src/terminal.rs +39 -15
- data/ext/ratatui_ruby/src/text.rs +13 -4
- data/ext/ratatui_ruby/src/widgets/barchart.rs +24 -6
- data/ext/ratatui_ruby/src/widgets/canvas.rs +5 -5
- data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
- data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
- data/ext/ratatui_ruby/src/widgets/list.rs +179 -3
- data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
- data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +93 -1
- data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
- data/ext/ratatui_ruby/src/widgets/table.rs +191 -34
- data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
- data/lib/ratatui_ruby/buffer/cell.rb +168 -0
- data/lib/ratatui_ruby/buffer.rb +15 -0
- data/lib/ratatui_ruby/cell.rb +4 -4
- data/lib/ratatui_ruby/event/key/character.rb +35 -0
- data/lib/ratatui_ruby/event/key/media.rb +44 -0
- data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
- data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
- data/lib/ratatui_ruby/event/key/system.rb +45 -0
- data/lib/ratatui_ruby/event/key.rb +111 -51
- data/lib/ratatui_ruby/event/mouse.rb +3 -3
- data/lib/ratatui_ruby/event/paste.rb +1 -1
- data/lib/ratatui_ruby/frame.rb +100 -4
- data/lib/ratatui_ruby/layout/constraint.rb +95 -0
- data/lib/ratatui_ruby/layout/layout.rb +106 -0
- data/lib/ratatui_ruby/layout/rect.rb +118 -0
- data/lib/ratatui_ruby/layout.rb +19 -0
- data/lib/ratatui_ruby/list_state.rb +88 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
- data/lib/ratatui_ruby/schema/cursor.rb +5 -0
- data/lib/ratatui_ruby/schema/gauge.rb +3 -1
- data/lib/ratatui_ruby/schema/layout.rb +1 -1
- data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
- data/lib/ratatui_ruby/schema/list.rb +25 -4
- data/lib/ratatui_ruby/schema/list_item.rb +41 -0
- data/lib/ratatui_ruby/schema/rect.rb +43 -0
- data/lib/ratatui_ruby/schema/row.rb +66 -0
- data/lib/ratatui_ruby/schema/style.rb +24 -4
- data/lib/ratatui_ruby/schema/table.rb +29 -11
- data/lib/ratatui_ruby/schema/text.rb +96 -3
- data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
- data/lib/ratatui_ruby/style/style.rb +81 -0
- data/lib/ratatui_ruby/style.rb +15 -0
- data/lib/ratatui_ruby/table_state.rb +90 -0
- data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
- data/lib/ratatui_ruby/test_helper/snapshot.rb +414 -0
- data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
- data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
- data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
- data/lib/ratatui_ruby/test_helper.rb +65 -358
- data/lib/ratatui_ruby/tui/buffer_factories.rb +20 -0
- data/lib/ratatui_ruby/tui/canvas_factories.rb +44 -0
- data/lib/ratatui_ruby/tui/core.rb +38 -0
- data/lib/ratatui_ruby/tui/layout_factories.rb +74 -0
- data/lib/ratatui_ruby/tui/state_factories.rb +33 -0
- data/lib/ratatui_ruby/tui/style_factories.rb +20 -0
- data/lib/ratatui_ruby/tui/text_factories.rb +44 -0
- data/lib/ratatui_ruby/tui/widget_factories.rb +195 -0
- data/lib/ratatui_ruby/tui.rb +75 -0
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +47 -0
- data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +25 -0
- data/lib/ratatui_ruby/widgets/bar_chart.rb +239 -0
- data/lib/ratatui_ruby/widgets/block.rb +192 -0
- data/lib/ratatui_ruby/widgets/calendar.rb +84 -0
- data/lib/ratatui_ruby/widgets/canvas.rb +231 -0
- data/lib/ratatui_ruby/widgets/cell.rb +47 -0
- data/lib/ratatui_ruby/widgets/center.rb +59 -0
- data/lib/ratatui_ruby/widgets/chart.rb +185 -0
- data/lib/ratatui_ruby/widgets/clear.rb +54 -0
- data/lib/ratatui_ruby/widgets/cursor.rb +42 -0
- data/lib/ratatui_ruby/widgets/gauge.rb +72 -0
- data/lib/ratatui_ruby/widgets/line_gauge.rb +80 -0
- data/lib/ratatui_ruby/widgets/list.rb +127 -0
- data/lib/ratatui_ruby/widgets/list_item.rb +43 -0
- data/lib/ratatui_ruby/widgets/overlay.rb +43 -0
- data/lib/ratatui_ruby/widgets/paragraph.rb +99 -0
- data/lib/ratatui_ruby/widgets/ratatui_logo.rb +31 -0
- data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +36 -0
- data/lib/ratatui_ruby/widgets/row.rb +68 -0
- data/lib/ratatui_ruby/widgets/scrollbar.rb +143 -0
- data/lib/ratatui_ruby/widgets/shape/label.rb +68 -0
- data/lib/ratatui_ruby/widgets/sparkline.rb +134 -0
- data/lib/ratatui_ruby/widgets/table.rb +141 -0
- data/lib/ratatui_ruby/widgets/tabs.rb +85 -0
- data/lib/ratatui_ruby/widgets.rb +40 -0
- data/lib/ratatui_ruby.rb +64 -57
- data/sig/examples/app_all_events/view.rbs +1 -1
- data/sig/examples/app_all_events/view_state.rbs +1 -1
- data/sig/examples/app_stateful_interaction/app.rbs +33 -0
- data/sig/examples/widget_block_demo/app.rbs +32 -0
- data/sig/examples/{app_map_demo → widget_map_demo}/app.rbs +2 -2
- data/sig/examples/{app_table_select → widget_table_demo}/app.rbs +2 -2
- data/sig/examples/{widget_table_flex → widget_text_width}/app.rbs +2 -3
- data/sig/ratatui_ruby/event.rbs +11 -1
- data/sig/ratatui_ruby/frame.rbs +2 -0
- data/sig/ratatui_ruby/list_state.rbs +13 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -2
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
- data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
- data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
- data/sig/ratatui_ruby/schema/list.rbs +4 -2
- data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
- data/sig/ratatui_ruby/schema/rect.rbs +3 -0
- data/sig/ratatui_ruby/schema/row.rbs +22 -0
- data/sig/ratatui_ruby/schema/style.rbs +3 -3
- data/sig/ratatui_ruby/schema/table.rbs +3 -1
- data/sig/ratatui_ruby/schema/text.rbs +9 -6
- data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
- data/sig/ratatui_ruby/session.rbs +41 -48
- data/sig/ratatui_ruby/table_state.rbs +15 -0
- data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
- data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
- data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
- data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
- data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
- data/sig/ratatui_ruby/test_helper.rbs +5 -4
- data/sig/ratatui_ruby/tui/buffer_factories.rbs +10 -0
- data/sig/ratatui_ruby/tui/canvas_factories.rbs +14 -0
- data/sig/ratatui_ruby/tui/core.rbs +14 -0
- data/sig/ratatui_ruby/tui/layout_factories.rbs +19 -0
- data/sig/ratatui_ruby/tui/state_factories.rbs +12 -0
- data/sig/ratatui_ruby/tui/style_factories.rbs +10 -0
- data/sig/ratatui_ruby/tui/text_factories.rbs +14 -0
- data/sig/ratatui_ruby/tui/widget_factories.rbs +39 -0
- data/sig/ratatui_ruby/tui.rbs +19 -0
- data/tasks/autodoc/examples.rb +79 -0
- data/tasks/autodoc.rake +7 -35
- data/tasks/bump/changelog.rb +3 -3
- data/tasks/bump/links.rb +67 -0
- data/tasks/sourcehut.rake +64 -21
- data/tasks/terminal_preview/app_screenshot.rb +13 -3
- data/tasks/terminal_preview/saved_screenshot.rb +4 -3
- metadata +169 -48
- data/doc/contributors/dwim_dx.md +0 -366
- data/doc/images/app_analytics.png +0 -0
- data/doc/images/app_custom_widget.png +0 -0
- data/doc/images/app_mouse_events.png +0 -0
- data/doc/images/app_table_select.png +0 -0
- data/doc/images/widget_block_padding.png +0 -0
- data/doc/images/widget_block_titles.png +0 -0
- data/doc/images/widget_list_styles.png +0 -0
- data/doc/images/widget_table_flex.png +0 -0
- data/examples/app_all_events/model/events.rb +0 -180
- data/examples/app_all_events/model/highlight.rb +0 -57
- data/examples/app_all_events/test/snapshots/after_focus_lost.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_focus_regained.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_key_a.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_mouse_click.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_multiple_events.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_paste.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_right_click.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/initial_state.txt +0 -24
- data/examples/app_all_events/view_state.rb +0 -42
- data/examples/app_color_picker/scene.rb +0 -201
- data/examples/widget_block_padding/app.rb +0 -67
- data/examples/widget_block_titles/app.rb +0 -69
- data/examples/widget_list_styles/app.rb +0 -141
- data/examples/widget_table_flex/app.rb +0 -95
- data/lib/ratatui_ruby/session/autodoc.rb +0 -417
- data/lib/ratatui_ruby/session.rb +0 -163
- data/sig/examples/widget_block_padding/app.rbs +0 -11
- data/sig/examples/widget_block_titles/app.rbs +0 -11
- data/sig/examples/widget_list_styles/app.rbs +0 -11
- data/tasks/autodoc/inventory.rb +0 -61
- data/tasks/autodoc/notice.rb +0 -26
- data/tasks/autodoc/rbs.rb +0 -38
- data/tasks/autodoc/rdoc.rb +0 -45
- data/tasks/bump/comparison_links.rb +0 -41
- /data/doc/images/{app_map_demo.png → widget_map_demo.png} +0 -0
data/doc/contributors/dwim_dx.md
DELETED
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
<!-- SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com> -->
|
|
2
|
-
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
3
|
-
|
|
4
|
-
# DWIM / DX Improvements for Application Developers
|
|
5
|
-
|
|
6
|
-
## Problem Statement
|
|
7
|
-
|
|
8
|
-
Ruby's philosophy of "Do What I Mean" (DWIM) and human-centric design should extend to ratatui_ruby's API. Currently, app developers encounter friction points that force them to remember non-obvious conventions, use overly verbose code, or pattern-match when simple predicates would suffice.
|
|
9
|
-
|
|
10
|
-
This proposal identifies DX issues across the widget API and suggests improvements that maintain backward compatibility while providing ergonomic alternatives.
|
|
11
|
-
|
|
12
|
-
## DX Issues Identified
|
|
13
|
-
|
|
14
|
-
### 1. Confusing Event Method Names
|
|
15
|
-
|
|
16
|
-
**Current problem**: `event.char` doesn't exist, but `event.code` returns things like `"enter"`, `"ctrl"`, not just characters.
|
|
17
|
-
|
|
18
|
-
**What users expect**:
|
|
19
|
-
- `event.char` should return the printable character (matching the name)
|
|
20
|
-
- `event.ctrl_c?`, `event.enter?`, etc. should work for all key combinations
|
|
21
|
-
- `event.key?`, `event.mouse?` predicates exist but only for broad categories
|
|
22
|
-
|
|
23
|
-
**Solution implemented**: Added `char` method and dynamic predicates via `method_missing`. See `lib/ratatui_ruby/event/key.rb`.
|
|
24
|
-
|
|
25
|
-
### 2. Dual Parameter APIs Without Predicates
|
|
26
|
-
|
|
27
|
-
**Current problem**: Widgets accept both forms but no convenience methods to query the state:
|
|
28
|
-
|
|
29
|
-
```ruby
|
|
30
|
-
# Both work, but which one does the widget store?
|
|
31
|
-
gauge1 = Gauge.new(ratio: 0.75)
|
|
32
|
-
gauge2 = Gauge.new(percent: 75)
|
|
33
|
-
gauge1.ratio # Works
|
|
34
|
-
gauge1.percent # Does NOT exist
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Similarly with List and Table:
|
|
38
|
-
```ruby
|
|
39
|
-
list.selected_index = 2 # Works
|
|
40
|
-
list.selected? # Does NOT exist
|
|
41
|
-
list.is_selected? # Does NOT exist
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
**Affected widgets**:
|
|
45
|
-
- `Gauge` (ratio vs percent)
|
|
46
|
-
- `LineGauge` (ratio vs percent)
|
|
47
|
-
- `List` (selected_index with no query methods)
|
|
48
|
-
- `Table` (selected_row and selected_column with no query methods)
|
|
49
|
-
|
|
50
|
-
**Suggested solutions**:
|
|
51
|
-
|
|
52
|
-
For `Gauge` and `LineGauge`:
|
|
53
|
-
```ruby
|
|
54
|
-
# Add convenience predicates
|
|
55
|
-
gauge.percent # => 75 (coerced from ratio internally)
|
|
56
|
-
gauge.percent = 50 # => Updates ratio to 0.5
|
|
57
|
-
|
|
58
|
-
# Or provide explicit accessors
|
|
59
|
-
gauge.as_percent # => 75
|
|
60
|
-
gauge.as_ratio # => 0.75
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
For `List` and `Table`:
|
|
64
|
-
```ruby
|
|
65
|
-
list.selected? # => true if selected_index is not nil
|
|
66
|
-
list.selection # => 2 (alias for selected_index)
|
|
67
|
-
list.selected_item # => "Item 3"
|
|
68
|
-
|
|
69
|
-
table.selected_row? # => true if selected_row is not nil
|
|
70
|
-
table.selected_cell? # => true if both row and column selected
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### 3. Symbol Constants for Enum Values
|
|
74
|
-
|
|
75
|
-
**Current problem**: Magic symbol values scattered across code:
|
|
76
|
-
|
|
77
|
-
```ruby
|
|
78
|
-
list = List.new(
|
|
79
|
-
highlight_spacing: :when_selected, # What are the other options?
|
|
80
|
-
direction: :top_to_bottom, # Is :bottom_to_top valid?
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
layout = Layout.new(
|
|
84
|
-
flex: :legacy # What does "legacy" mean?
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
gauge = Gauge.new(
|
|
88
|
-
use_unicode: true # Unclear what ASCII fallback looks like
|
|
89
|
-
)
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Users must consult docs or source code to discover valid options.
|
|
93
|
-
|
|
94
|
-
**Suggested solution**: Add constants to widget classes:
|
|
95
|
-
|
|
96
|
-
```ruby
|
|
97
|
-
class List < Data
|
|
98
|
-
# Highlight spacing modes
|
|
99
|
-
HIGHLIGHT_ALWAYS = :always
|
|
100
|
-
HIGHLIGHT_WHEN_SELECTED = :when_selected
|
|
101
|
-
HIGHLIGHT_NEVER = :never
|
|
102
|
-
|
|
103
|
-
# Direction modes
|
|
104
|
-
DIRECTION_TOP_TO_BOTTOM = :top_to_bottom
|
|
105
|
-
DIRECTION_BOTTOM_TO_TOP = :bottom_to_top
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
list = List.new(
|
|
109
|
-
highlight_spacing: List::HIGHLIGHT_WHEN_SELECTED,
|
|
110
|
-
direction: List::DIRECTION_TOP_TO_BOTTOM,
|
|
111
|
-
)
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Benefits:
|
|
115
|
-
- IDE autocomplete shows valid options
|
|
116
|
-
- Self-documenting code
|
|
117
|
-
- Typos caught at runtime (symbol vs constant)
|
|
118
|
-
- Easy to grep for where these modes are used
|
|
119
|
-
|
|
120
|
-
Affected widgets and their enum values:
|
|
121
|
-
- `List`: `highlight_spacing` (:always, :when_selected, :never), `direction` (:top_to_bottom, :bottom_to_top)
|
|
122
|
-
- `Table`: `highlight_spacing` (same as List), `flex` (:legacy, :default, :fill)
|
|
123
|
-
- `Layout`: `direction` (:vertical, :horizontal), `flex` (:legacy, :default, :fill)
|
|
124
|
-
- `Gauge`/`LineGauge`: `use_unicode` (boolean, but could have MODE_UNICODE, MODE_ASCII)
|
|
125
|
-
- `Paragraph`: `alignment` (:left, :center, :right)
|
|
126
|
-
- `Block`: `border_type` (:plain, :rounded, :double, :thick)
|
|
127
|
-
- `Canvas`: `marker` (:braille, :dots, :half_block, :sextant, :octant)
|
|
128
|
-
|
|
129
|
-
### 4. Inconsistent Style APIs
|
|
130
|
-
|
|
131
|
-
**Current problem**: Different widgets accept styles differently:
|
|
132
|
-
|
|
133
|
-
```ruby
|
|
134
|
-
# Table accepts both
|
|
135
|
-
table = Table.new(style: Style.new(fg: :blue))
|
|
136
|
-
table = Table.new(style: { fg: :blue }) # Hash shorthand
|
|
137
|
-
|
|
138
|
-
# But Paragraph doesn't
|
|
139
|
-
paragraph = Paragraph.new(text: "hi", style: Style.new(fg: :blue))
|
|
140
|
-
paragraph = Paragraph.new(text: "hi", style: { fg: :blue }) # Works but undocumented
|
|
141
|
-
|
|
142
|
-
# And Gauge has separate properties
|
|
143
|
-
gauge = Gauge.new(style: Style.new(fg: :blue), gauge_style: Style.new(fg: :green))
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
**Suggested solution**: Standardize style handling across all widgets:
|
|
147
|
-
|
|
148
|
-
1. All widgets should accept `Style` objects and `Hash` shorthand
|
|
149
|
-
2. Document this clearly in each widget
|
|
150
|
-
3. Add a convenience constructor:
|
|
151
|
-
|
|
152
|
-
```ruby
|
|
153
|
-
class Style
|
|
154
|
-
def self.with(fg: nil, bg: nil, modifiers: [])
|
|
155
|
-
Style.new(fg: fg, bg: bg, modifiers: modifiers)
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# Cleaner than always spelling out keyword args
|
|
160
|
-
paragraph = Paragraph.new(text: "hi", style: Style.with(fg: :blue))
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### 5. Missing State Query Predicates
|
|
164
|
-
|
|
165
|
-
**Current problem**: Widgets store state but provide no query methods:
|
|
166
|
-
|
|
167
|
-
```ruby
|
|
168
|
-
list.selected_index = 0
|
|
169
|
-
|
|
170
|
-
# To check if something is selected, must do:
|
|
171
|
-
if list.selected_index&.nonzero? # Awkward
|
|
172
|
-
if list.selected_index.nil? == false # Confusing
|
|
173
|
-
|
|
174
|
-
# Should be:
|
|
175
|
-
list.selected? # => true
|
|
176
|
-
list.empty? # => false (for items array)
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
**Suggested solution**: Add predicates to state-holding widgets:
|
|
180
|
-
|
|
181
|
-
```ruby
|
|
182
|
-
# List
|
|
183
|
-
list.selected? # => !selected_index.nil?
|
|
184
|
-
list.empty? # => items.empty?
|
|
185
|
-
list.selection # => selected_index (alias)
|
|
186
|
-
list.selected_item # => items[selected_index] (convenience)
|
|
187
|
-
|
|
188
|
-
# Table
|
|
189
|
-
table.selected_row? # => !selected_row.nil?
|
|
190
|
-
table.selected_cell? # => !selected_row.nil? && !selected_column.nil?
|
|
191
|
-
table.empty? # => rows.empty?
|
|
192
|
-
|
|
193
|
-
# Gauge
|
|
194
|
-
gauge.filled? # => ratio > 0
|
|
195
|
-
gauge.complete? # => ratio >= 1.0
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
### 6. Magic Numeric Coercions
|
|
199
|
-
|
|
200
|
-
**Current problem**: Widgets accept `Numeric` but silently coerce:
|
|
201
|
-
|
|
202
|
-
```ruby
|
|
203
|
-
# These all work, but behavior is undocumented
|
|
204
|
-
list = List.new(selected_index: "2") # Coerced to 2
|
|
205
|
-
list = List.new(selected_index: 2.7) # Coerced to 2
|
|
206
|
-
list = List.new(selected_index: 2.0) # Coerced to 2
|
|
207
|
-
|
|
208
|
-
gauge = Gauge.new(percent: 150) # Should clamp?
|
|
209
|
-
gauge = Gauge.new(ratio: 1.5) # Should clamp?
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
**Suggested solution**:
|
|
213
|
-
|
|
214
|
-
1. Document coercion rules explicitly in RDoc
|
|
215
|
-
2. Add validation and raise on invalid inputs:
|
|
216
|
-
|
|
217
|
-
```ruby
|
|
218
|
-
def initialize(percent: nil, ...)
|
|
219
|
-
if percent
|
|
220
|
-
raise ArgumentError, "percent must be 0..100, got #{percent}" unless percent.between?(0, 100)
|
|
221
|
-
ratio = Float(percent) / 100.0
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
3. Provide clear error messages:
|
|
227
|
-
```ruby
|
|
228
|
-
gauge = Gauge.new(percent: 150)
|
|
229
|
-
# => ArgumentError: percent must be between 0 and 100 (got 150)
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
## Implementation Strategy
|
|
233
|
-
|
|
234
|
-
### Phase 1: Event Improvements (DONE)
|
|
235
|
-
- [x] Add `char` method to Key event
|
|
236
|
-
- [x] Implement dynamic predicates via `method_missing`
|
|
237
|
-
- [x] Update examples to use new API
|
|
238
|
-
|
|
239
|
-
### Phase 2: State Query Predicates
|
|
240
|
-
- [ ] Add predicates to `List` (selected?, empty?, selected_item)
|
|
241
|
-
- [ ] Add predicates to `Table` (selected_row?, selected_cell?, empty?)
|
|
242
|
-
- [ ] Add predicates to `Gauge` (filled?, complete?)
|
|
243
|
-
- [ ] Tests for all new predicates
|
|
244
|
-
|
|
245
|
-
### Phase 3: Symbol Constants
|
|
246
|
-
- [ ] Add enum constants to `List`, `Table`, `Layout`
|
|
247
|
-
- [ ] Add enum constants to `Gauge`, `LineGauge`, `Paragraph`, `Block`
|
|
248
|
-
- [ ] Update all examples to use constants
|
|
249
|
-
- [ ] Document constants in RDoc
|
|
250
|
-
|
|
251
|
-
### Phase 4: Style Consistency
|
|
252
|
-
- [ ] Standardize `Hash` shorthand support across all widgets
|
|
253
|
-
- [ ] Add `Style.with(fg:, bg:, modifiers:)` convenience constructor
|
|
254
|
-
- [ ] Update `.rbs` files to reflect HashStyle support
|
|
255
|
-
- [ ] Document in style guide
|
|
256
|
-
|
|
257
|
-
### Phase 5: Numeric Coercion Validation
|
|
258
|
-
- [ ] Add validation to `Gauge`, `LineGauge`, `List`, `Table`
|
|
259
|
-
- [ ] Raise `ArgumentError` on out-of-range values
|
|
260
|
-
- [ ] Provide clear error messages
|
|
261
|
-
- [ ] Update tests
|
|
262
|
-
|
|
263
|
-
### Phase 6: Convenience Accessors
|
|
264
|
-
- [ ] Add `percent` to `Gauge` and `LineGauge`
|
|
265
|
-
- [ ] Add `selection` alias to `List` and `Table`
|
|
266
|
-
- [ ] Add `selected_item` to `List`
|
|
267
|
-
- [ ] Tests and documentation
|
|
268
|
-
|
|
269
|
-
## Example: Before and After
|
|
270
|
-
|
|
271
|
-
### Before (Confusing)
|
|
272
|
-
```ruby
|
|
273
|
-
class GameApp
|
|
274
|
-
def initialize
|
|
275
|
-
@menu = List.new(
|
|
276
|
-
items: ["Start Game", "Load Game", "Options", "Quit"],
|
|
277
|
-
selected_index: 0,
|
|
278
|
-
highlight_spacing: :when_selected, # What's valid here?
|
|
279
|
-
direction: :top_to_bottom
|
|
280
|
-
)
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
def handle_input(event)
|
|
284
|
-
case event
|
|
285
|
-
when :ctrl_c
|
|
286
|
-
exit
|
|
287
|
-
when :up
|
|
288
|
-
if @menu.selected_index && @menu.selected_index > 0
|
|
289
|
-
@menu = @menu.with(selected_index: @menu.selected_index - 1)
|
|
290
|
-
end
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
def render(tui)
|
|
295
|
-
tui.draw(@menu)
|
|
296
|
-
end
|
|
297
|
-
end
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
### After (DWIM)
|
|
301
|
-
```ruby
|
|
302
|
-
class GameApp
|
|
303
|
-
def initialize
|
|
304
|
-
@menu = List.new(
|
|
305
|
-
items: ["Start Game", "Load Game", "Options", "Quit"],
|
|
306
|
-
selected_index: 0,
|
|
307
|
-
highlight_spacing: List::HIGHLIGHT_WHEN_SELECTED, # IDE autocomplete!
|
|
308
|
-
direction: List::DIRECTION_TOP_TO_BOTTOM
|
|
309
|
-
)
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
def handle_input(event)
|
|
313
|
-
return if event.ctrl_c? # Dynamic predicate!
|
|
314
|
-
|
|
315
|
-
if event.up?
|
|
316
|
-
move_menu_up if @menu.selected? # State predicate!
|
|
317
|
-
end
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
def move_menu_up
|
|
321
|
-
index = @menu.selected_index
|
|
322
|
-
return if index == 0
|
|
323
|
-
@menu = @menu.with(selected_index: index - 1)
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
def render(tui)
|
|
327
|
-
tui.draw(@menu)
|
|
328
|
-
end
|
|
329
|
-
end
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
## Migration Path
|
|
333
|
-
|
|
334
|
-
All changes are backward compatible (additive):
|
|
335
|
-
- Existing code using symbols continues to work
|
|
336
|
-
- New constants coexist with symbols
|
|
337
|
-
- New predicates don't change existing behavior
|
|
338
|
-
- New methods are additions, not replacements
|
|
339
|
-
|
|
340
|
-
Apps can migrate at their own pace:
|
|
341
|
-
```ruby
|
|
342
|
-
# Old style still works
|
|
343
|
-
list = List.new(highlight_spacing: :when_selected)
|
|
344
|
-
|
|
345
|
-
# New style also works
|
|
346
|
-
list = List.new(highlight_spacing: List::HIGHLIGHT_WHEN_SELECTED)
|
|
347
|
-
|
|
348
|
-
# Mix and match
|
|
349
|
-
if list.selected? # New predicate
|
|
350
|
-
puts list.selected_index # Old accessor
|
|
351
|
-
end
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
## Metrics for Success
|
|
355
|
-
|
|
356
|
-
1. **Discoverability**: New developers can find valid options via IDE autocomplete
|
|
357
|
-
2. **Clarity**: Code self-documents valid states and modes
|
|
358
|
-
3. **Type safety**: Constants and predicates provide type checking
|
|
359
|
-
4. **Error feedback**: Invalid inputs raise with helpful messages
|
|
360
|
-
5. **Backward compatibility**: Zero breaking changes, all existing code works
|
|
361
|
-
|
|
362
|
-
## Related Issues
|
|
363
|
-
|
|
364
|
-
- AGENTS.md requirement: All examples must have tests verifying behavior
|
|
365
|
-
- Example improvements: Apply constants and predicates to all example code
|
|
366
|
-
- Documentation: Update style guide with DWIM principles
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
-
|
|
6
|
-
require_relative "timestamp"
|
|
7
|
-
require_relative "event_color_cycle"
|
|
8
|
-
require_relative "event_entry"
|
|
9
|
-
|
|
10
|
-
# Manages the history and state of all application events.
|
|
11
|
-
#
|
|
12
|
-
# Applications need to track, count, and display event history for debugging and feedback.
|
|
13
|
-
# Direct management of event arrays and counters across the app leads to scattered state.
|
|
14
|
-
#
|
|
15
|
-
# This class centralizes event storage. It records new events, maintains counts, and manages temporary highlights.
|
|
16
|
-
#
|
|
17
|
-
# Use it to store key presses, mouse clicks, and window resizes for display in a log or counter.
|
|
18
|
-
#
|
|
19
|
-
# === Examples
|
|
20
|
-
#
|
|
21
|
-
# events = Events.new
|
|
22
|
-
# events.record(key_event)
|
|
23
|
-
# puts events.count(:key) #=> 1
|
|
24
|
-
#
|
|
25
|
-
# if events.lit?(:key)
|
|
26
|
-
# puts "Key event just happened!"
|
|
27
|
-
# end
|
|
28
|
-
class Events
|
|
29
|
-
# Duration in milliseconds for an event to remain highlighted in the UI.
|
|
30
|
-
HIGHLIGHT_DURATION_MS = 300
|
|
31
|
-
|
|
32
|
-
# Creates a new Events manager.
|
|
33
|
-
def initialize
|
|
34
|
-
@entries = []
|
|
35
|
-
@color_cycle = EventColorCycle.new
|
|
36
|
-
@none_count = 0
|
|
37
|
-
@lit_type = nil
|
|
38
|
-
@lit_until = Timestamp.now
|
|
39
|
-
@live = {}
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Records a new event.
|
|
43
|
-
#
|
|
44
|
-
# [event] RatatuiRuby::Event object.
|
|
45
|
-
# [context] Hash of additional context (e.g., last_dimensions).
|
|
46
|
-
#
|
|
47
|
-
# === Example
|
|
48
|
-
#
|
|
49
|
-
# events.record(mouse_event)
|
|
50
|
-
def record(event, context: {})
|
|
51
|
-
if event.is_a?(RatatuiRuby::Event::None) || event == :none
|
|
52
|
-
@none_count += 1
|
|
53
|
-
return
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
color = @color_cycle.next_color
|
|
57
|
-
timestamp = Timestamp.now
|
|
58
|
-
entry = EventEntry.create(event, color, timestamp)
|
|
59
|
-
@entries << entry
|
|
60
|
-
update_lit_type(entry)
|
|
61
|
-
|
|
62
|
-
display_type = live_type_for(entry.type)
|
|
63
|
-
@live[display_type] = { time: Time.now, description: entry.description }
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
private def live_type_for(type)
|
|
67
|
-
case type
|
|
68
|
-
when :focus_gained, :focus_lost
|
|
69
|
-
:focus
|
|
70
|
-
else
|
|
71
|
-
type
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Returns the most recent live event data for a type.
|
|
76
|
-
#
|
|
77
|
-
# [type] Symbol event type to look up.
|
|
78
|
-
#
|
|
79
|
-
# === Example
|
|
80
|
-
#
|
|
81
|
-
# events.live_event(:key) #=> { time: ..., description: "..." }
|
|
82
|
-
def live_event(type)
|
|
83
|
-
@live[type]
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Returns all recorded live event data.
|
|
87
|
-
#
|
|
88
|
-
# === Example
|
|
89
|
-
#
|
|
90
|
-
# events.live_events #=> { key: { ... }, mouse: { ... } }
|
|
91
|
-
def live_events
|
|
92
|
-
@live
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Returns the most recent entries up to the given limit.
|
|
96
|
-
#
|
|
97
|
-
# [max_entries] Integer maximum number of entries to return.
|
|
98
|
-
#
|
|
99
|
-
# === Example
|
|
100
|
-
#
|
|
101
|
-
# events.visible(10) #=> [#<EventEntry ...>, ...]
|
|
102
|
-
def visible(max_entries)
|
|
103
|
-
@entries.last(max_entries)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# Checks if any events have been recorded.
|
|
107
|
-
#
|
|
108
|
-
# === Example
|
|
109
|
-
#
|
|
110
|
-
# events.empty? #=> true
|
|
111
|
-
def empty?
|
|
112
|
-
@entries.empty?
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Returns the count of events for a type.
|
|
116
|
-
#
|
|
117
|
-
# [type] Symbol event type.
|
|
118
|
-
#
|
|
119
|
-
# === Example
|
|
120
|
-
#
|
|
121
|
-
# events.count(:key) #=> 5
|
|
122
|
-
def count(type)
|
|
123
|
-
return @none_count if type == :none
|
|
124
|
-
|
|
125
|
-
@entries.count { |e| e.matches_type?(type) }
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# Returns counts grouped by subtype (kind or modifier status).
|
|
129
|
-
#
|
|
130
|
-
# [type] Symbol event type.
|
|
131
|
-
#
|
|
132
|
-
# === Example
|
|
133
|
-
#
|
|
134
|
-
# events.sub_counts(:mouse) #=> { "down" => 1, "up" => 2 }
|
|
135
|
-
def sub_counts(type)
|
|
136
|
-
return {} if type == :none
|
|
137
|
-
|
|
138
|
-
entries = @entries.select { |e| e.matches_type?(type) }
|
|
139
|
-
|
|
140
|
-
defaults = { key: %w[unmodified modified], focus: %w[gained lost], mouse: %w[down up drag moved scroll_up scroll_down] }
|
|
141
|
-
entries.each_with_object(defaults.fetch(type, []).to_h { |k| [k, 0] }) do |entry, counts|
|
|
142
|
-
group = if entry.event.respond_to?(:kind)
|
|
143
|
-
entry.event.kind.to_s
|
|
144
|
-
elsif entry.event.respond_to?(:modifiers)
|
|
145
|
-
entry.event.modifiers.empty? ? "unmodified" : "modified"
|
|
146
|
-
elsif type == :focus
|
|
147
|
-
entry.type.to_s.sub("focus_", "")
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
counts[group] += 1 if group
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# Checks if a type should be highlighted.
|
|
155
|
-
#
|
|
156
|
-
# [type] Symbol event type.
|
|
157
|
-
#
|
|
158
|
-
# === Example
|
|
159
|
-
#
|
|
160
|
-
# events.lit?(:key) #=> true
|
|
161
|
-
def lit?(type)
|
|
162
|
-
return false if Timestamp.now.milliseconds >= @lit_until.milliseconds
|
|
163
|
-
|
|
164
|
-
@lit_type == type
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Returns all event entries.
|
|
168
|
-
#
|
|
169
|
-
# === Example
|
|
170
|
-
#
|
|
171
|
-
# events.entries #=> [#<EventEntry ...>, ...]
|
|
172
|
-
def entries
|
|
173
|
-
@entries
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
private def update_lit_type(entry)
|
|
177
|
-
@lit_type = live_type_for(entry.type)
|
|
178
|
-
@lit_until = Timestamp.new(milliseconds: Timestamp.now.milliseconds + HIGHLIGHT_DURATION_MS)
|
|
179
|
-
end
|
|
180
|
-
end
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
-
|
|
6
|
-
require_relative "timestamp"
|
|
7
|
-
|
|
8
|
-
# Manages temporary visual highlights for different event types.
|
|
9
|
-
#
|
|
10
|
-
# Users need visual feedback when an event occurs, but highlights should fade.
|
|
11
|
-
# Manually tracking timers for every highlightable element is complex.
|
|
12
|
-
#
|
|
13
|
-
# This class manages the "lit" state of multiple keys with a consistent duration.
|
|
14
|
-
#
|
|
15
|
-
# Use it to trigger and check for temporary UI highlights.
|
|
16
|
-
#
|
|
17
|
-
# === Examples
|
|
18
|
-
#
|
|
19
|
-
# highlight = Highlight.new
|
|
20
|
-
# highlight.light_up(:key)
|
|
21
|
-
# highlight.lit?(:key) #=> true
|
|
22
|
-
# sleep(0.4)
|
|
23
|
-
# highlight.lit?(:key) #=> false
|
|
24
|
-
class Highlight
|
|
25
|
-
# Duration in milliseconds that a highlight remains active.
|
|
26
|
-
DURATION_MS = 300
|
|
27
|
-
|
|
28
|
-
# Creates a new Highlight manager.
|
|
29
|
-
def initialize
|
|
30
|
-
@lit_types = {}
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Triggers a highlight for the given type.
|
|
34
|
-
#
|
|
35
|
-
# [type] Symbol to highlight.
|
|
36
|
-
#
|
|
37
|
-
# === Example
|
|
38
|
-
#
|
|
39
|
-
# highlight.light_up(:mouse)
|
|
40
|
-
def light_up(type)
|
|
41
|
-
@lit_types[type] = Timestamp.now
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Checks if a highlight is currently active for the given type.
|
|
45
|
-
#
|
|
46
|
-
# [type] Symbol to check.
|
|
47
|
-
#
|
|
48
|
-
# === Example
|
|
49
|
-
#
|
|
50
|
-
# highlight.lit?(:mouse) #=> true
|
|
51
|
-
def lit?(type)
|
|
52
|
-
timestamp = @lit_types[type]
|
|
53
|
-
return false unless timestamp
|
|
54
|
-
|
|
55
|
-
!timestamp.elapsed?(DURATION_MS)
|
|
56
|
-
end
|
|
57
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
┌Event Counts──────┐ ┌Live Display─────────────────────────────────────────────┐
|
|
2
|
-
│Key: 0 │ │Type Time Description │
|
|
3
|
-
│ Unmodified: 0 │ │Key — — │
|
|
4
|
-
│ Modified: 0 │ │Mouse — — │
|
|
5
|
-
│Mouse: 0 │ │Resize — — │
|
|
6
|
-
│ Down: 0 │ │Paste — — │
|
|
7
|
-
│ Up: 0 │ │Focus XX:XX:XX #<RatatuiRuby::Event::FocusLost:0xXXXXXX│
|
|
8
|
-
│ Drag: 0 │ │ │
|
|
9
|
-
│ Moved: 0 │ └─────────────────────────────────────────────────────────┘
|
|
10
|
-
│ Scroll_up: 0 │ ┌Event Log────────────────────────────────────────────────┐
|
|
11
|
-
│ Scroll_down: 0 │ │#<RatatuiRuby::Event::FocusLost:0xXXXXXX> │
|
|
12
|
-
│Resize: 0 │ │ │
|
|
13
|
-
│Paste: 0 │ │ │
|
|
14
|
-
│Focus: 1 │ │ │
|
|
15
|
-
│ Gained: 0 │ │ │
|
|
16
|
-
│ Lost: 1 │ │ │
|
|
17
|
-
│None: 0 │ │ │
|
|
18
|
-
│ │ │ │
|
|
19
|
-
│ │ │ │
|
|
20
|
-
│ │ │ │
|
|
21
|
-
└──────────────────┘ └─────────────────────────────────────────────────────────┘
|
|
22
|
-
┌Controls──────────────────────────────────────────────────────────────────────┐
|
|
23
|
-
│q: Quit Ctrl+C: Quit │
|
|
24
|
-
└──────────────────────────────────────────────────────────────────────────────┘
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
┌Event Counts──────┐ ┌Live Display─────────────────────────────────────────────┐
|
|
2
|
-
│Key: 0 │ │Type Time Description │
|
|
3
|
-
│ Unmodified: 0 │ │Key — — │
|
|
4
|
-
│ Modified: 0 │ │Mouse — — │
|
|
5
|
-
│Mouse: 0 │ │Resize — — │
|
|
6
|
-
│ Down: 0 │ │Paste — — │
|
|
7
|
-
│ Up: 0 │ │Focus XX:XX:XX #<RatatuiRuby::Event::FocusGained:0xXXXXXX│
|
|
8
|
-
│ Drag: 0 │ │ │
|
|
9
|
-
│ Moved: 0 │ └─────────────────────────────────────────────────────────┘
|
|
10
|
-
│ Scroll_up: 0 │ ┌Event Log────────────────────────────────────────────────┐
|
|
11
|
-
│ Scroll_down: 0 │ │#<RatatuiRuby::Event::FocusLost:0xXXXXXX> │
|
|
12
|
-
│Resize: 0 │ │ │
|
|
13
|
-
│Paste: 0 │ │#<RatatuiRuby::Event::FocusGained:0xXXXXXX> │
|
|
14
|
-
│Focus: 2 │ │ │
|
|
15
|
-
│ Gained: 1 │ │ │
|
|
16
|
-
│ Lost: 1 │ │ │
|
|
17
|
-
│None: 0 │ │ │
|
|
18
|
-
│ │ │ │
|
|
19
|
-
│ │ │ │
|
|
20
|
-
│ │ │ │
|
|
21
|
-
└──────────────────┘ └─────────────────────────────────────────────────────────┘
|
|
22
|
-
┌Controls──────────────────────────────────────────────────────────────────────┐
|
|
23
|
-
│q: Quit Ctrl+C: Quit │
|
|
24
|
-
└──────────────────────────────────────────────────────────────────────────────┘
|