ratatui_ruby 0.4.0 → 0.6.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 +98 -176
- data/CHANGELOG.md +80 -6
- data/README.md +19 -7
- data/REUSE.toml +15 -0
- data/doc/application_architecture.md +179 -45
- data/doc/application_testing.md +80 -32
- data/doc/contributors/design/ruby_frontend.md +48 -8
- data/doc/contributors/design/rust_backend.md +1 -0
- data/doc/contributors/developing_examples.md +191 -48
- data/doc/contributors/documentation_style.md +7 -0
- data/doc/contributors/examples_audit/p1_high.md +21 -0
- data/doc/contributors/examples_audit/p2_moderate.md +81 -0
- data/doc/contributors/examples_audit.md +41 -0
- data/doc/contributors/index.md +2 -0
- data/doc/event_handling.md +21 -7
- 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_box_demo.png +0 -0
- data/doc/images/widget_calendar_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_gauge_demo.png +0 -0
- data/doc/images/widget_layout_split.png +0 -0
- data/doc/images/widget_line_gauge_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_ratatui_logo_demo.png +0 -0
- data/doc/images/widget_ratatui_mascot_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_scrollbar_demo.png +0 -0
- data/doc/images/widget_sparkline_demo.png +0 -0
- data/doc/images/widget_style_colors.png +0 -0
- data/doc/images/widget_table_demo.png +0 -0
- data/doc/images/widget_table_flex.png +0 -0
- data/doc/images/widget_tabs_demo.png +0 -0
- data/doc/images/widget_text_width.png +0 -0
- data/doc/interactive_design.md +25 -30
- data/doc/quickstart.md +150 -130
- data/doc/terminal_limitations.md +92 -0
- data/examples/app_all_events/README.md +99 -0
- data/examples/app_all_events/app.rb +96 -0
- data/examples/app_all_events/model/app_model.rb +157 -0
- data/examples/app_all_events/model/event_color_cycle.rb +41 -0
- data/examples/app_all_events/model/event_entry.rb +92 -0
- data/examples/app_all_events/model/msg.rb +37 -0
- data/examples/app_all_events/model/timestamp.rb +54 -0
- data/examples/app_all_events/update.rb +73 -0
- data/examples/app_all_events/view/app_view.rb +78 -0
- data/examples/app_all_events/view/controls_view.rb +52 -0
- data/examples/app_all_events/view/counts_view.rb +59 -0
- data/examples/app_all_events/view/live_view.rb +70 -0
- data/examples/app_all_events/view/log_view.rb +55 -0
- data/examples/app_all_events/view.rb +7 -0
- data/examples/app_color_picker/README.md +134 -0
- data/examples/app_color_picker/app.rb +74 -0
- data/examples/app_color_picker/clipboard.rb +84 -0
- data/examples/app_color_picker/color.rb +191 -0
- data/examples/app_color_picker/controls.rb +90 -0
- data/examples/app_color_picker/copy_dialog.rb +166 -0
- data/examples/app_color_picker/export_pane.rb +126 -0
- data/examples/app_color_picker/harmony.rb +56 -0
- data/examples/app_color_picker/input.rb +174 -0
- data/examples/app_color_picker/main_container.rb +178 -0
- data/examples/app_color_picker/palette.rb +109 -0
- data/examples/app_login_form/README.md +47 -0
- data/examples/{login_form → app_login_form}/app.rb +38 -42
- data/examples/app_stateful_interaction/README.md +31 -0
- data/examples/app_stateful_interaction/app.rb +272 -0
- data/examples/timeout_demo.rb +43 -0
- data/examples/verify_quickstart_dsl/README.md +48 -0
- data/examples/{quickstart_dsl → verify_quickstart_dsl}/app.rb +17 -6
- data/examples/verify_quickstart_layout/README.md +71 -0
- data/examples/verify_quickstart_layout/app.rb +71 -0
- data/examples/verify_quickstart_lifecycle/README.md +56 -0
- data/examples/verify_quickstart_lifecycle/app.rb +54 -0
- data/examples/verify_readme_usage/README.md +43 -0
- data/examples/verify_readme_usage/app.rb +40 -0
- data/examples/widget_barchart_demo/README.md +49 -0
- data/examples/widget_barchart_demo/app.rb +238 -0
- data/examples/widget_block_demo/README.md +34 -0
- data/examples/widget_block_demo/app.rb +256 -0
- data/examples/widget_box_demo/README.md +45 -0
- data/examples/{box_demo → widget_box_demo}/app.rb +99 -65
- data/examples/widget_calendar_demo/README.md +39 -0
- data/examples/widget_calendar_demo/app.rb +109 -0
- 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 +111 -0
- 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 +218 -0
- data/examples/widget_gauge_demo/README.md +41 -0
- data/examples/widget_gauge_demo/app.rb +212 -0
- data/examples/widget_layout_split/README.md +44 -0
- data/examples/widget_layout_split/app.rb +246 -0
- data/examples/widget_line_gauge_demo/README.md +41 -0
- data/examples/widget_line_gauge_demo/app.rb +217 -0
- data/examples/widget_list_demo/README.md +49 -0
- data/examples/widget_list_demo/app.rb +366 -0
- data/examples/widget_map_demo/README.md +39 -0
- data/examples/{map_demo → widget_map_demo}/app.rb +24 -21
- data/examples/widget_overlay_demo/app.rb +248 -0
- data/examples/widget_popup_demo/README.md +36 -0
- data/examples/widget_popup_demo/app.rb +104 -0
- data/examples/widget_ratatui_logo_demo/README.md +34 -0
- data/examples/widget_ratatui_logo_demo/app.rb +103 -0
- data/examples/widget_ratatui_mascot_demo/README.md +34 -0
- data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
- data/examples/widget_rect/README.md +38 -0
- data/examples/widget_rect/app.rb +205 -0
- data/examples/widget_render/README.md +37 -0
- data/examples/widget_render/app.rb +184 -0
- data/examples/widget_rich_text/README.md +35 -0
- data/examples/widget_rich_text/app.rb +166 -0
- data/examples/widget_scroll_text/README.md +37 -0
- data/examples/widget_scroll_text/app.rb +107 -0
- data/examples/widget_scrollbar_demo/README.md +37 -0
- data/examples/widget_scrollbar_demo/app.rb +153 -0
- data/examples/widget_sparkline_demo/README.md +42 -0
- data/examples/widget_sparkline_demo/app.rb +275 -0
- data/examples/widget_style_colors/README.md +34 -0
- data/examples/widget_style_colors/app.rb +19 -21
- data/examples/widget_table_demo/README.md +48 -0
- data/examples/widget_table_demo/app.rb +239 -0
- data/examples/widget_tabs_demo/README.md +41 -0
- data/examples/widget_tabs_demo/app.rb +181 -0
- data/examples/widget_text_width/README.md +35 -0
- data/examples/widget_text_width/app.rb +106 -0
- data/ext/ratatui_ruby/Cargo.lock +11 -4
- data/ext/ratatui_ruby/Cargo.toml +2 -1
- data/ext/ratatui_ruby/src/events.rs +359 -62
- data/ext/ratatui_ruby/src/frame.rs +227 -0
- data/ext/ratatui_ruby/src/lib.rs +110 -27
- data/ext/ratatui_ruby/src/rendering.rs +8 -4
- data/ext/ratatui_ruby/src/string_width.rs +101 -0
- data/ext/ratatui_ruby/src/style.rs +138 -57
- data/ext/ratatui_ruby/src/terminal.rs +42 -22
- data/ext/ratatui_ruby/src/text.rs +14 -7
- data/ext/ratatui_ruby/src/widgets/barchart.rs +74 -54
- data/ext/ratatui_ruby/src/widgets/block.rs +7 -6
- data/ext/ratatui_ruby/src/widgets/canvas.rs +21 -3
- data/ext/ratatui_ruby/src/widgets/chart.rs +20 -10
- data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
- data/ext/ratatui_ruby/src/widgets/layout.rs +9 -4
- data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
- data/ext/ratatui_ruby/src/widgets/list.rs +211 -12
- 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/overlay.rs +2 -1
- data/ext/ratatui_ruby/src/widgets/paragraph.rs +1 -1
- data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +19 -8
- data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +17 -10
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +97 -3
- data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
- data/ext/ratatui_ruby/src/widgets/sparkline.rs +14 -11
- data/ext/ratatui_ruby/src/widgets/table.rs +121 -5
- data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
- data/ext/ratatui_ruby/src/widgets/tabs.rs +11 -11
- data/lib/ratatui_ruby/cell.rb +7 -7
- 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 +112 -52
- data/lib/ratatui_ruby/event/mouse.rb +3 -3
- data/lib/ratatui_ruby/event/none.rb +43 -0
- data/lib/ratatui_ruby/event/paste.rb +1 -1
- data/lib/ratatui_ruby/event.rb +56 -4
- data/lib/ratatui_ruby/frame.rb +183 -0
- data/lib/ratatui_ruby/list_state.rb +88 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +13 -13
- data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +1 -5
- data/lib/ratatui_ruby/schema/bar_chart.rb +217 -217
- data/lib/ratatui_ruby/schema/block.rb +163 -168
- data/lib/ratatui_ruby/schema/calendar.rb +66 -67
- data/lib/ratatui_ruby/schema/canvas.rb +63 -63
- data/lib/ratatui_ruby/schema/center.rb +46 -46
- data/lib/ratatui_ruby/schema/chart.rb +135 -143
- data/lib/ratatui_ruby/schema/clear.rb +42 -42
- data/lib/ratatui_ruby/schema/constraint.rb +76 -76
- data/lib/ratatui_ruby/schema/cursor.rb +30 -25
- data/lib/ratatui_ruby/schema/gauge.rb +54 -52
- data/lib/ratatui_ruby/schema/layout.rb +87 -87
- data/lib/ratatui_ruby/schema/line_gauge.rb +62 -62
- data/lib/ratatui_ruby/schema/list.rb +103 -80
- data/lib/ratatui_ruby/schema/list_item.rb +41 -0
- data/lib/ratatui_ruby/schema/overlay.rb +31 -31
- data/lib/ratatui_ruby/schema/paragraph.rb +80 -80
- data/lib/ratatui_ruby/schema/ratatui_logo.rb +10 -6
- data/lib/ratatui_ruby/schema/ratatui_mascot.rb +10 -5
- data/lib/ratatui_ruby/schema/rect.rb +99 -56
- data/lib/ratatui_ruby/schema/scrollbar.rb +119 -119
- data/lib/ratatui_ruby/schema/shape/label.rb +1 -1
- data/lib/ratatui_ruby/schema/sparkline.rb +111 -110
- data/lib/ratatui_ruby/schema/style.rb +66 -46
- data/lib/ratatui_ruby/schema/table.rb +126 -115
- data/lib/ratatui_ruby/schema/tabs.rb +66 -67
- data/lib/ratatui_ruby/schema/text.rb +69 -1
- data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
- data/lib/ratatui_ruby/session/autodoc.rb +482 -0
- data/lib/ratatui_ruby/session.rb +55 -23
- 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 +390 -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 +66 -193
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby.rb +100 -51
- data/{examples/sparkline_demo → sig/examples/app_all_events}/app.rbs +3 -2
- data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
- data/sig/examples/app_all_events/model/events.rbs +15 -0
- data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
- data/sig/examples/app_all_events/view/app_view.rbs +8 -0
- data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
- data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
- data/sig/examples/app_all_events/view/live_view.rbs +6 -0
- data/sig/examples/app_all_events/view/log_view.rbs +6 -0
- data/sig/examples/app_all_events/view.rbs +8 -0
- data/sig/examples/app_all_events/view_state.rbs +15 -0
- data/{examples/list_demo → sig/examples/app_color_picker}/app.rbs +2 -2
- data/sig/examples/app_login_form/app.rbs +11 -0
- data/sig/examples/app_stateful_interaction/app.rbs +33 -0
- data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
- data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
- data/sig/examples/verify_readme_usage/app.rbs +11 -0
- data/sig/examples/widget_block_demo/app.rbs +32 -0
- data/sig/examples/widget_box_demo/app.rbs +11 -0
- data/sig/examples/widget_calendar_demo/app.rbs +11 -0
- data/sig/examples/widget_cell_demo/app.rbs +11 -0
- data/sig/examples/widget_chart_demo/app.rbs +11 -0
- data/{examples/gauge_demo → sig/examples/widget_gauge_demo}/app.rbs +4 -0
- data/sig/examples/widget_layout_split/app.rbs +10 -0
- data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
- data/sig/examples/widget_list_demo/app.rbs +12 -0
- data/sig/examples/widget_map_demo/app.rbs +11 -0
- data/sig/examples/widget_popup_demo/app.rbs +11 -0
- data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
- data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
- data/sig/examples/widget_rect/app.rbs +12 -0
- data/sig/examples/widget_render/app.rbs +10 -0
- data/sig/examples/widget_rich_text/app.rbs +11 -0
- data/sig/examples/widget_scroll_text/app.rbs +11 -0
- data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
- data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
- data/{examples → sig/examples}/widget_style_colors/app.rbs +1 -1
- data/sig/examples/widget_table_demo/app.rbs +11 -0
- data/sig/examples/widget_text_width/app.rbs +10 -0
- data/sig/ratatui_ruby/event.rbs +11 -1
- data/sig/ratatui_ruby/frame.rbs +11 -0
- data/sig/ratatui_ruby/list_state.rbs +13 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +5 -4
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
- data/sig/ratatui_ruby/schema/draw.rbs +4 -0
- data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
- data/sig/ratatui_ruby/schema/layout.rbs +1 -1
- 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/style.rbs +3 -3
- data/sig/ratatui_ruby/schema/table.rbs +3 -1
- data/sig/ratatui_ruby/schema/text.rbs +8 -6
- data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
- data/sig/ratatui_ruby/session.rbs +107 -0
- 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/tasks/autodoc/examples.rb +79 -0
- data/tasks/autodoc/inventory.rb +63 -0
- data/tasks/autodoc/member.rb +56 -0
- data/tasks/autodoc/name.rb +19 -0
- data/tasks/autodoc/notice.rb +26 -0
- data/tasks/autodoc/rbs.rb +38 -0
- data/tasks/autodoc/rdoc.rb +45 -0
- data/tasks/autodoc.rake +53 -0
- data/tasks/bump/changelog.rb +3 -3
- data/tasks/bump/history.rb +2 -2
- data/tasks/bump/links.rb +67 -0
- data/tasks/doc.rake +600 -6
- data/tasks/example_viewer.html.erb +172 -0
- data/tasks/lint.rake +8 -4
- data/tasks/resources/index.html.erb +6 -0
- data/tasks/sourcehut.rake +70 -30
- data/tasks/terminal_preview/app_screenshot.rb +14 -6
- data/tasks/terminal_preview/crash_report.rb +7 -9
- data/tasks/terminal_preview/launcher_script.rb +4 -6
- data/tasks/terminal_preview/preview_collection.rb +4 -6
- data/tasks/terminal_preview/safety_confirmation.rb +3 -5
- data/tasks/terminal_preview/saved_screenshot.rb +10 -11
- data/tasks/terminal_preview/terminal_window.rb +7 -9
- data/tasks/test.rake +1 -1
- data/tasks/website/index_page.rb +3 -3
- data/tasks/website/version.rb +10 -10
- data/tasks/website/version_menu.rb +10 -12
- data/tasks/website/versioned_documentation.rb +49 -17
- data/tasks/website/website.rb +6 -8
- data/tasks/website.rake +4 -4
- metadata +232 -127
- data/LICENSES/BSD-2-Clause.txt +0 -9
- data/doc/contributors/better_dx.md +0 -543
- data/doc/contributors/example_analysis.md +0 -82
- data/doc/images/all_events.png +0 -0
- data/doc/images/block_padding.png +0 -0
- data/doc/images/block_titles.png +0 -0
- data/doc/images/box_demo.png +0 -0
- data/doc/images/calendar_demo.png +0 -0
- data/doc/images/cell_demo.png +0 -0
- data/doc/images/chart_demo.png +0 -0
- data/doc/images/flex_layout.png +0 -0
- data/doc/images/gauge_demo.png +0 -0
- data/doc/images/line_gauge_demo.png +0 -0
- data/doc/images/list_demo.png +0 -0
- data/doc/images/list_styles.png +0 -0
- data/doc/images/login_form.png +0 -0
- data/doc/images/quickstart_dsl.png +0 -0
- data/doc/images/quickstart_lifecycle.png +0 -0
- data/doc/images/readme_usage.png +0 -0
- data/doc/images/rich_text.png +0 -0
- data/doc/images/scroll_text.png +0 -0
- data/doc/images/scrollbar_demo.png +0 -0
- data/doc/images/sparkline_demo.png +0 -0
- data/doc/images/table_flex.png +0 -0
- data/doc/images/table_select.png +0 -0
- data/examples/all_events/app.rb +0 -169
- data/examples/all_events/app.rbs +0 -7
- data/examples/all_events/test_app.rb +0 -139
- data/examples/analytics/app.rb +0 -258
- data/examples/analytics/app.rbs +0 -7
- data/examples/analytics/test_app.rb +0 -132
- data/examples/block_padding/app.rb +0 -63
- data/examples/block_padding/app.rbs +0 -7
- data/examples/block_padding/test_app.rb +0 -31
- data/examples/block_titles/app.rb +0 -61
- data/examples/block_titles/app.rbs +0 -7
- data/examples/block_titles/test_app.rb +0 -34
- data/examples/box_demo/app.rbs +0 -7
- data/examples/box_demo/test_app.rb +0 -88
- data/examples/calendar_demo/app.rb +0 -101
- data/examples/calendar_demo/app.rbs +0 -7
- data/examples/calendar_demo/test_app.rb +0 -108
- data/examples/cell_demo/app.rb +0 -108
- data/examples/cell_demo/app.rbs +0 -7
- data/examples/cell_demo/test_app.rb +0 -36
- data/examples/chart_demo/app.rb +0 -203
- data/examples/chart_demo/app.rbs +0 -7
- data/examples/chart_demo/test_app.rb +0 -102
- data/examples/custom_widget/app.rb +0 -51
- data/examples/custom_widget/app.rbs +0 -7
- data/examples/custom_widget/test_app.rb +0 -30
- data/examples/flex_layout/app.rb +0 -156
- data/examples/flex_layout/app.rbs +0 -7
- data/examples/flex_layout/test_app.rb +0 -65
- data/examples/gauge_demo/app.rb +0 -182
- data/examples/gauge_demo/test_app.rb +0 -120
- data/examples/hit_test/app.rb +0 -175
- data/examples/hit_test/app.rbs +0 -7
- data/examples/hit_test/test_app.rb +0 -102
- data/examples/line_gauge_demo/app.rb +0 -190
- data/examples/line_gauge_demo/app.rbs +0 -7
- data/examples/line_gauge_demo/test_app.rb +0 -129
- data/examples/list_demo/app.rb +0 -253
- data/examples/list_demo/test_app.rb +0 -237
- data/examples/list_styles/app.rb +0 -140
- data/examples/list_styles/app.rbs +0 -7
- data/examples/list_styles/test_app.rb +0 -157
- data/examples/login_form/app.rbs +0 -7
- data/examples/login_form/test_app.rb +0 -51
- data/examples/map_demo/app.rbs +0 -7
- data/examples/map_demo/test_app.rb +0 -149
- data/examples/mouse_events/app.rb +0 -97
- data/examples/mouse_events/app.rbs +0 -7
- data/examples/mouse_events/test_app.rb +0 -53
- data/examples/popup_demo/app.rb +0 -103
- data/examples/popup_demo/app.rbs +0 -7
- data/examples/popup_demo/test_app.rb +0 -54
- data/examples/quickstart_dsl/app.rbs +0 -7
- data/examples/quickstart_dsl/test_app.rb +0 -29
- data/examples/quickstart_lifecycle/app.rb +0 -39
- data/examples/quickstart_lifecycle/app.rbs +0 -7
- data/examples/quickstart_lifecycle/test_app.rb +0 -29
- data/examples/ratatui_logo_demo/app.rb +0 -79
- data/examples/ratatui_logo_demo/app.rbs +0 -7
- data/examples/ratatui_logo_demo/test_app.rb +0 -51
- data/examples/ratatui_mascot_demo/app.rb +0 -84
- data/examples/ratatui_mascot_demo/app.rbs +0 -7
- data/examples/ratatui_mascot_demo/test_app.rb +0 -47
- data/examples/readme_usage/app.rb +0 -29
- data/examples/readme_usage/app.rbs +0 -7
- data/examples/readme_usage/test_app.rb +0 -29
- data/examples/rich_text/app.rb +0 -141
- data/examples/rich_text/app.rbs +0 -7
- data/examples/rich_text/test_app.rb +0 -166
- data/examples/scroll_text/app.rb +0 -103
- data/examples/scroll_text/app.rbs +0 -7
- data/examples/scroll_text/test_app.rb +0 -110
- data/examples/scrollbar_demo/app.rb +0 -143
- data/examples/scrollbar_demo/app.rbs +0 -7
- data/examples/scrollbar_demo/test_app.rb +0 -77
- data/examples/sparkline_demo/app.rb +0 -240
- data/examples/sparkline_demo/test_app.rb +0 -107
- data/examples/table_flex/app.rb +0 -65
- data/examples/table_flex/app.rbs +0 -7
- data/examples/table_flex/test_app.rb +0 -36
- data/examples/table_select/app.rb +0 -198
- data/examples/table_select/app.rbs +0 -7
- data/examples/table_select/test_app.rb +0 -180
- data/examples/widget_style_colors/test_app.rb +0 -48
- data/tasks/bump/comparison_links.rb +0 -41
- /data/doc/images/{analytics.png → app_analytics.png} +0 -0
- /data/doc/images/{custom_widget.png → app_custom_widget.png} +0 -0
- /data/doc/images/{mouse_events.png → app_mouse_events.png} +0 -0
- /data/doc/images/{map_demo.png → widget_map_demo.png} +0 -0
- /data/doc/images/{popup_demo.png → widget_popup_demo.png} +0 -0
- /data/doc/images/{hit_test.png → widget_rect.png} +0 -0
- /data/{doc/images/ratatui_logo_demo.png → exe/.gitkeep} +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
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 "../view"
|
|
7
|
+
require_relative "counts_view"
|
|
8
|
+
require_relative "live_view"
|
|
9
|
+
require_relative "log_view"
|
|
10
|
+
require_relative "controls_view"
|
|
11
|
+
|
|
12
|
+
# Orchestrates the complete UI layout and sub-view composition.
|
|
13
|
+
#
|
|
14
|
+
# Complex applications need a structured way to divide the screen and delegate rendering.
|
|
15
|
+
# Placing all layout logic in one monolithic method makes the code difficult to maintain.
|
|
16
|
+
#
|
|
17
|
+
# This class defines the screen layout using a series of split constraints and delegates to sub-views.
|
|
18
|
+
#
|
|
19
|
+
# Use it as the root view for the All Events example application.
|
|
20
|
+
#
|
|
21
|
+
# === Examples
|
|
22
|
+
#
|
|
23
|
+
# app_view = View::App.new
|
|
24
|
+
# app_view.call(model, tui, frame, area)
|
|
25
|
+
class View::App
|
|
26
|
+
# Creates a new View::App and initializes sub-views.
|
|
27
|
+
def initialize
|
|
28
|
+
@counts_view = View::Counts.new
|
|
29
|
+
@live_view = View::Live.new
|
|
30
|
+
@log_view = View::Log.new
|
|
31
|
+
@controls_view = View::Controls.new
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Renders the entire application UI to the given area.
|
|
35
|
+
#
|
|
36
|
+
# [model] AppModel containing all application data.
|
|
37
|
+
# [tui] RatatuiRuby instance.
|
|
38
|
+
# [frame] RatatuiRuby::Frame being rendered.
|
|
39
|
+
# [area] RatatuiRuby::Rect defining the total available space.
|
|
40
|
+
#
|
|
41
|
+
# === Example
|
|
42
|
+
#
|
|
43
|
+
# app_view.call(model, tui, frame, area)
|
|
44
|
+
def call(model, tui, frame, area)
|
|
45
|
+
main_area, control_area = tui.layout_split(
|
|
46
|
+
area,
|
|
47
|
+
direction: :vertical,
|
|
48
|
+
constraints: [
|
|
49
|
+
tui.constraint_fill(1),
|
|
50
|
+
tui.constraint_length(3),
|
|
51
|
+
]
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
counts_area, _margin_area, right_area = tui.layout_split(
|
|
55
|
+
main_area,
|
|
56
|
+
direction: :horizontal,
|
|
57
|
+
constraints: [
|
|
58
|
+
tui.constraint_length(20),
|
|
59
|
+
tui.constraint_length(1),
|
|
60
|
+
tui.constraint_fill(1),
|
|
61
|
+
]
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
live_area, log_area = tui.layout_split(
|
|
65
|
+
right_area,
|
|
66
|
+
direction: :vertical,
|
|
67
|
+
constraints: [
|
|
68
|
+
tui.constraint_length(9),
|
|
69
|
+
tui.constraint_fill(1),
|
|
70
|
+
]
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
@counts_view.call(model, tui, frame, counts_area)
|
|
74
|
+
@live_view.call(model, tui, frame, live_area)
|
|
75
|
+
@log_view.call(model, tui, frame, log_area)
|
|
76
|
+
@controls_view.call(model, tui, frame, control_area)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
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 "../view"
|
|
7
|
+
|
|
8
|
+
# Renders the keyboard controls and shortcuts panel.
|
|
9
|
+
#
|
|
10
|
+
# Users need to know how to interact with the application and exit.
|
|
11
|
+
# Hardcoding control descriptions into the main layout makes the code hard to read.
|
|
12
|
+
#
|
|
13
|
+
# This component renders a formatted paragraph listing available global shortcuts.
|
|
14
|
+
#
|
|
15
|
+
# Use it to display help information in a sidebar or dedicated panel.
|
|
16
|
+
#
|
|
17
|
+
# === Examples
|
|
18
|
+
#
|
|
19
|
+
# controls = View::Controls.new
|
|
20
|
+
# controls.call(model, tui, frame, area)
|
|
21
|
+
class View::Controls
|
|
22
|
+
# Renders the controls widget to the given area.
|
|
23
|
+
#
|
|
24
|
+
# [model] AppModel (unused, included for consistent interface).
|
|
25
|
+
# [tui] RatatuiRuby instance.
|
|
26
|
+
# [frame] RatatuiRuby::Frame being rendered.
|
|
27
|
+
# [area] RatatuiRuby::Rect defining the widget's bounds.
|
|
28
|
+
#
|
|
29
|
+
# === Example
|
|
30
|
+
#
|
|
31
|
+
# controls.call(model, tui, frame, area)
|
|
32
|
+
def call(_model, tui, frame, area)
|
|
33
|
+
hotkey_style = tui.style(modifiers: [:bold, :underlined])
|
|
34
|
+
|
|
35
|
+
widget = tui.paragraph(
|
|
36
|
+
text: [
|
|
37
|
+
tui.text_line(spans: [
|
|
38
|
+
tui.text_span(content: "q", style: hotkey_style),
|
|
39
|
+
tui.text_span(content: ": Quit "),
|
|
40
|
+
tui.text_span(content: "Ctrl+C", style: hotkey_style),
|
|
41
|
+
tui.text_span(content: ": Quit"),
|
|
42
|
+
]),
|
|
43
|
+
],
|
|
44
|
+
block: tui.block(
|
|
45
|
+
title: "Controls",
|
|
46
|
+
borders: [:all],
|
|
47
|
+
border_style: tui.style(fg: :white)
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
frame.render_widget(widget, area)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
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 "../view"
|
|
7
|
+
|
|
8
|
+
# Renders the event statistics dashboard.
|
|
9
|
+
#
|
|
10
|
+
# Developers auditing input need to see real-time counts of various event types.
|
|
11
|
+
#
|
|
12
|
+
# This component displays a list of event types with their total counts.
|
|
13
|
+
#
|
|
14
|
+
# Use it to build an interactive dashboard of application activity.
|
|
15
|
+
class View::Counts
|
|
16
|
+
# Renders the event counts widget to the given area.
|
|
17
|
+
#
|
|
18
|
+
# [model] AppModel containing event data.
|
|
19
|
+
# [tui] RatatuiRuby instance.
|
|
20
|
+
# [frame] RatatuiRuby::Frame being rendered.
|
|
21
|
+
# [area] RatatuiRuby::Rect defining the widget's bounds.
|
|
22
|
+
def call(model, tui, frame, area)
|
|
23
|
+
dimmed_style = tui.style(fg: :dark_gray)
|
|
24
|
+
lit_style = tui.style(fg: :green, modifiers: [:bold])
|
|
25
|
+
border_color = model.focused ? :green : :gray
|
|
26
|
+
|
|
27
|
+
count_lines = []
|
|
28
|
+
|
|
29
|
+
AppAllEvents::EVENT_TYPES.each do |type|
|
|
30
|
+
count = model.count(type)
|
|
31
|
+
label = type.to_s.capitalize
|
|
32
|
+
style = model.lit?(type) ? lit_style : nil
|
|
33
|
+
|
|
34
|
+
count_lines << tui.text_line(spans: [
|
|
35
|
+
tui.text_span(content: "#{label}: ", style:),
|
|
36
|
+
tui.text_span(content: count.to_s, style: style || tui.style(fg: :yellow)),
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
model.sub_counts(type).each do |sub_type, sub_count|
|
|
40
|
+
sub_label = sub_type.to_s.capitalize
|
|
41
|
+
count_lines << tui.text_line(spans: [
|
|
42
|
+
tui.text_span(content: " #{sub_label}: ", style: dimmed_style),
|
|
43
|
+
tui.text_span(content: sub_count.to_s, style: dimmed_style),
|
|
44
|
+
])
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
widget = tui.paragraph(
|
|
49
|
+
text: count_lines,
|
|
50
|
+
scroll: [0, 0],
|
|
51
|
+
block: tui.block(
|
|
52
|
+
title: "Event Counts",
|
|
53
|
+
borders: [:all],
|
|
54
|
+
border_style: tui.style(fg: border_color)
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
frame.render_widget(widget, area)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
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 "../view"
|
|
7
|
+
|
|
8
|
+
# Renders a real-time summary of the most recent events.
|
|
9
|
+
#
|
|
10
|
+
# Users need to see the immediate result of their actions without digging through a log.
|
|
11
|
+
# Identifying the specific details of the last key press or mouse move at a glance is difficult.
|
|
12
|
+
#
|
|
13
|
+
# This component displays a table showing the latest event of each type with its timestamp and description.
|
|
14
|
+
#
|
|
15
|
+
# Use it to provide instant feedback for user interactions.
|
|
16
|
+
#
|
|
17
|
+
# === Examples
|
|
18
|
+
#
|
|
19
|
+
# live_view = View::Live.new
|
|
20
|
+
# live_view.call(model, tui, frame, area)
|
|
21
|
+
class View::Live
|
|
22
|
+
# Renders the live event table to the given area.
|
|
23
|
+
#
|
|
24
|
+
# [model] AppModel containing event data.
|
|
25
|
+
# [tui] RatatuiRuby instance.
|
|
26
|
+
# [frame] RatatuiRuby::Frame being rendered.
|
|
27
|
+
# [area] RatatuiRuby::Rect defining the widget's bounds.
|
|
28
|
+
#
|
|
29
|
+
# === Example
|
|
30
|
+
#
|
|
31
|
+
# live_view.call(model, tui, frame, area)
|
|
32
|
+
def call(model, tui, frame, area)
|
|
33
|
+
border_color = model.focused ? :green : :gray
|
|
34
|
+
rows = []
|
|
35
|
+
|
|
36
|
+
rows << tui.text_line(spans: [
|
|
37
|
+
tui.text_span(content: "Type".ljust(9), style: tui.style(fg: :gray, modifiers: [:bold])),
|
|
38
|
+
tui.text_span(content: "Time".ljust(10), style: tui.style(fg: :gray, modifiers: [:bold])),
|
|
39
|
+
tui.text_span(content: "Description", style: tui.style(fg: :gray, modifiers: [:bold])),
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
(AppAllEvents::EVENT_TYPES - [:none]).each do |type|
|
|
43
|
+
event_data = model.live_event(type)
|
|
44
|
+
|
|
45
|
+
class_str = type.to_s.capitalize
|
|
46
|
+
time_str = event_data ? event_data[:time].strftime("%H:%M:%S") : "—"
|
|
47
|
+
desc_str = event_data ? event_data[:description] : "—"
|
|
48
|
+
|
|
49
|
+
is_lit = model.lit?(type)
|
|
50
|
+
row_style = is_lit ? tui.style(fg: :black, bg: :green) : nil
|
|
51
|
+
|
|
52
|
+
rows << tui.text_line(spans: [
|
|
53
|
+
tui.text_span(content: class_str.ljust(9), style: row_style || tui.style(fg: :cyan)),
|
|
54
|
+
tui.text_span(content: time_str.ljust(10), style: row_style || tui.style(fg: :white)),
|
|
55
|
+
tui.text_span(content: desc_str, style: row_style),
|
|
56
|
+
])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
widget = tui.paragraph(
|
|
60
|
+
text: rows,
|
|
61
|
+
scroll: [0, 0],
|
|
62
|
+
block: tui.block(
|
|
63
|
+
title: "Live Display",
|
|
64
|
+
borders: [:all],
|
|
65
|
+
border_style: tui.style(fg: border_color)
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
frame.render_widget(widget, area)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
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 "../view"
|
|
7
|
+
|
|
8
|
+
# Renders a detailed, scrollable history of application events.
|
|
9
|
+
#
|
|
10
|
+
# Debugging complex event flows requires a chronological record of raw data.
|
|
11
|
+
# Interpreting raw event objects without formatting is difficult and slow.
|
|
12
|
+
#
|
|
13
|
+
# This component renders event history as a series of formatted, color-coded entries showing raw data.
|
|
14
|
+
#
|
|
15
|
+
# Use it to provide a detailed audit trail of all terminal interactions.
|
|
16
|
+
class View::Log
|
|
17
|
+
# Renders the event log widget to the given area.
|
|
18
|
+
#
|
|
19
|
+
# [model] AppModel containing event data.
|
|
20
|
+
# [tui] RatatuiRuby instance.
|
|
21
|
+
# [frame] RatatuiRuby::Frame being rendered.
|
|
22
|
+
# [area] RatatuiRuby::Rect defining the widget's bounds.
|
|
23
|
+
def call(model, tui, frame, area)
|
|
24
|
+
dimmed_style = tui.style(fg: :dark_gray)
|
|
25
|
+
border_color = model.focused ? :green : :gray
|
|
26
|
+
|
|
27
|
+
visible_entries_count = (area.height - 2) / 2
|
|
28
|
+
display_entries = model.visible(visible_entries_count)
|
|
29
|
+
|
|
30
|
+
log_lines = []
|
|
31
|
+
if model.empty?
|
|
32
|
+
log_lines << tui.text_line(spans: [tui.text_span(content: "No events yet...", style: dimmed_style)])
|
|
33
|
+
else
|
|
34
|
+
display_entries.each do |entry|
|
|
35
|
+
entry_style = tui.style(fg: entry.color)
|
|
36
|
+
description = entry.description
|
|
37
|
+
|
|
38
|
+
log_lines << tui.text_line(spans: [tui.text_span(content: description, style: entry_style)])
|
|
39
|
+
log_lines << tui.text_line(spans: [tui.text_span(content: "", style: entry_style)])
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
widget = tui.paragraph(
|
|
44
|
+
text: log_lines,
|
|
45
|
+
scroll: [0, 0],
|
|
46
|
+
wrap: { trim: true },
|
|
47
|
+
block: tui.block(
|
|
48
|
+
title: "Event Log",
|
|
49
|
+
borders: [:all],
|
|
50
|
+
border_style: tui.style(fg: border_color)
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
frame.render_widget(widget, area)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Color Picker Example
|
|
7
|
+
|
|
8
|
+
This example demonstrates how to build a **Feature-Rich Interactive Application** using `ratatui_ruby`.
|
|
9
|
+
|
|
10
|
+
It goes beyond simple widgets to show a complete, real-world architecture for handling:
|
|
11
|
+
- **Complex State Management** (Input validation, clipboard interaction)
|
|
12
|
+
- **Mouse Interaction & Hit Testing**
|
|
13
|
+
- **Dynamic Layouts**
|
|
14
|
+
- **Modal Dialogs**
|
|
15
|
+
|
|
16
|
+
## Architecture: The "Proto-Kit" Pattern (Component-Based)
|
|
17
|
+
|
|
18
|
+
This app uses a **Strict Component-Based Architecture** where every UI element encapsulates its own **Rendering**, **State**, and **Event Handling**.
|
|
19
|
+
|
|
20
|
+
### The Component Contract
|
|
21
|
+
|
|
22
|
+
Every component implements this duck-type interface:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# Renders the component into the given area
|
|
26
|
+
# Caches `area` for hit testing
|
|
27
|
+
def render(tui, frame, area)
|
|
28
|
+
@area = area
|
|
29
|
+
# ... render using frame.render_widget
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Processes events; returns a symbolic signal or nil
|
|
33
|
+
def handle_event(event) -> Symbol | nil
|
|
34
|
+
# Returns :consumed, :submitted, :copy_requested, etc.
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Optional: time-based updates
|
|
38
|
+
def tick
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 1. The MainContainer (Orchestrator)
|
|
43
|
+
|
|
44
|
+
The `MainContainer` class (`main_container.rb`) owns all child components and orchestrates the UI:
|
|
45
|
+
|
|
46
|
+
- **Layout Phase:** Calculates `Rect`s using `tui.layout_split`.
|
|
47
|
+
- **Delegation Phase:** Calls `child.render(tui, frame, child_area)` for each component.
|
|
48
|
+
- **Event Routing (Chain of Responsibility):** Delegates events front-to-back. The modal dialog gets priority when active.
|
|
49
|
+
- **Mediator Pattern:** Interprets symbolic signals (`:submitted`, `:copy_requested`) to coordinate cross-component effects.
|
|
50
|
+
|
|
51
|
+
### 2. Self-Contained Components
|
|
52
|
+
|
|
53
|
+
Each UI element is a self-contained component:
|
|
54
|
+
|
|
55
|
+
- **`Input`**: Text entry with validation. Returns `:submitted` when Enter is pressed.
|
|
56
|
+
- **`Palette`**: Displays color harmonies. Accepts `update_color` from the container.
|
|
57
|
+
- **`ExportPane`**: Shows HEX/RGB/HSL formats. Returns `:copy_requested` when clicked.
|
|
58
|
+
- **`Controls`**: Displays keyboard shortcuts. Has a `tick` lifecycle for clipboard feedback.
|
|
59
|
+
- **`CopyDialog`**: Modal confirmation dialog. Returns `:consumed` when handling events.
|
|
60
|
+
|
|
61
|
+
### 3. The App (Minimal Runner)
|
|
62
|
+
|
|
63
|
+
The `App` class (`app.rb`) is a thin runner:
|
|
64
|
+
- Creates the `MainContainer`.
|
|
65
|
+
- Runs the main loop: `tick` → `render` → `poll` → `handle_event`.
|
|
66
|
+
- Checks for quit events.
|
|
67
|
+
|
|
68
|
+
## Key Features Showcased
|
|
69
|
+
|
|
70
|
+
### 🖱️ Encapsulated Hit Testing
|
|
71
|
+
|
|
72
|
+
Components cache their render area (`@area`) during `render`. In `handle_event`, they check `@area&.contains?(x, y)` to detect clicks. The container never calculates coordinates—hit testing is fully encapsulated.
|
|
73
|
+
|
|
74
|
+
### 🔲 Modal Dialogs via Chain of Responsibility
|
|
75
|
+
|
|
76
|
+
When `CopyDialog` is active, the `MainContainer` offers it events first. If it returns `:consumed`, event propagation stops. This creates modal behavior without explicit flags in the app.
|
|
77
|
+
|
|
78
|
+
### 📡 Symbolic Signals (Mediator Pattern)
|
|
79
|
+
|
|
80
|
+
Components return semantic symbols instead of just `:consumed`:
|
|
81
|
+
- `Input` returns `:submitted` when the user presses Enter.
|
|
82
|
+
- `ExportPane` returns `:copy_requested` when clicked.
|
|
83
|
+
|
|
84
|
+
The `MainContainer` interprets these signals to coordinate cross-component communication:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
result = @input.handle_event(event)
|
|
88
|
+
case result
|
|
89
|
+
when :submitted
|
|
90
|
+
@palette.update_color(@input.parsed_color)
|
|
91
|
+
return :consumed
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### ⏱️ Lifecycle Hooks (`tick`)
|
|
96
|
+
|
|
97
|
+
Components can have time-based updates. `Controls#tick` delegates to `Clipboard#tick` to decrement the feedback timer.
|
|
98
|
+
|
|
99
|
+
## Problem Solving: What You Can Learn
|
|
100
|
+
|
|
101
|
+
Read this example if you are trying to solve:
|
|
102
|
+
1. **"How do I structure a larger app?"** → Use the Component Contract and a Container for orchestration.
|
|
103
|
+
2. **"How do I handle mouse clicks?"** → Cache `@area` during render; check `contains?` in `handle_event`.
|
|
104
|
+
3. **"How do I make a popup?"** → Use Chain of Responsibility: the active modal gets events first.
|
|
105
|
+
4. **"How do I coordinate between components?"** → Use symbolic signals and the Mediator pattern.
|
|
106
|
+
5. **"How do I validate input?"** → Encapsulate validation inside the `Input` component.
|
|
107
|
+
|
|
108
|
+
## Usage
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
ruby examples/app_color_picker/app.rb
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- Type a hex code (e.g., `#FF0055`) or color name (`cyan`).
|
|
115
|
+
- Press `Enter` to generate the palette.
|
|
116
|
+
- Click on the **Export Formats** box to copy the hex code.
|
|
117
|
+
|
|
118
|
+
## Comparison: Choosing an Architecture
|
|
119
|
+
|
|
120
|
+
Complex applications require structured state habits. This Color Picker and the [App All Events](../app_all_events/README.md) example demonstrate two different approaches.
|
|
121
|
+
|
|
122
|
+
### The Tool Approach (Color Picker - Proto-Kit)
|
|
123
|
+
|
|
124
|
+
Tools require interaction. Users click buttons and drag sliders. Components need to know where they exist on screen for hit testing. The Container orchestrates cross-component effects.
|
|
125
|
+
|
|
126
|
+
This example uses the **Proto-Kit (Component-Based)** pattern. Each component owns its own state, rendering, and event handling. The Container routes events and mediates communication.
|
|
127
|
+
|
|
128
|
+
Use this pattern for forms, editors, and mouse-driven tools.
|
|
129
|
+
|
|
130
|
+
### The Dashboard Approach (AppAllEvents - Proto-TEA)
|
|
131
|
+
|
|
132
|
+
Dashboards display data. They rarely require complex mouse interaction. Proto-TEA (Model-View-Update) works best there. State is immutable. Logic is pure. Updates are predictable. This simplifies testing.
|
|
133
|
+
|
|
134
|
+
Use that pattern for logs, monitors, and data viewers.
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
7
|
+
$LOAD_PATH.unshift File.expand_path(__dir__)
|
|
8
|
+
|
|
9
|
+
require "ratatui_ruby"
|
|
10
|
+
require_relative "main_container"
|
|
11
|
+
|
|
12
|
+
# A terminal-based color picker application.
|
|
13
|
+
#
|
|
14
|
+
# Terminal users often need to select colors for themes or UI components.
|
|
15
|
+
# Manually typing hex codes and guessing how they will look is slow and error-prone.
|
|
16
|
+
#
|
|
17
|
+
# This application solves the problem by providing an interactive interface. It parses hex strings,
|
|
18
|
+
# generates palettes, and displays them visually in the terminal.
|
|
19
|
+
#
|
|
20
|
+
# === Architecture
|
|
21
|
+
#
|
|
22
|
+
# This example uses the Proto-Kit (Component-Based) pattern:
|
|
23
|
+
# - **Components**: Self-contained UI elements with `render`, `handle_event`, and optional `tick`
|
|
24
|
+
# - **Container**: Owns layout, delegates to children, routes events via Chain of Responsibility
|
|
25
|
+
# - **Mediator**: Container interprets symbolic signals (`:consumed`, `:submitted`) for cross-component effects
|
|
26
|
+
#
|
|
27
|
+
# === Examples
|
|
28
|
+
#
|
|
29
|
+
# AppColorPicker.new.run
|
|
30
|
+
#
|
|
31
|
+
class AppColorPicker
|
|
32
|
+
# Creates a new <tt>AppColorPicker</tt> instance.
|
|
33
|
+
def initialize
|
|
34
|
+
@container = nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Starts the terminal session and enters the main event loop.
|
|
38
|
+
#
|
|
39
|
+
# This method initializes the terminal, creates the MainContainer, and runs
|
|
40
|
+
# the event loop until the user quits.
|
|
41
|
+
#
|
|
42
|
+
# === Example
|
|
43
|
+
#
|
|
44
|
+
# app = AppColorPicker.new
|
|
45
|
+
# app.run
|
|
46
|
+
#
|
|
47
|
+
def run
|
|
48
|
+
RatatuiRuby.run do |tui|
|
|
49
|
+
@container = MainContainer.new(tui)
|
|
50
|
+
|
|
51
|
+
loop do
|
|
52
|
+
@container.tick
|
|
53
|
+
tui.draw { |frame| @container.render(tui, frame, frame.area) }
|
|
54
|
+
|
|
55
|
+
event = tui.poll_event
|
|
56
|
+
break if quit_event?(event)
|
|
57
|
+
|
|
58
|
+
@container.handle_event(event)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private def quit_event?(event)
|
|
64
|
+
case event
|
|
65
|
+
in { type: :key, code: "q" } | { type: :key, code: "esc" } |
|
|
66
|
+
{ type: :key, code: "c", modifiers: [/ctrl/] }
|
|
67
|
+
true
|
|
68
|
+
else
|
|
69
|
+
false
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
AppColorPicker.new.run if __FILE__ == $PROGRAM_NAME
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
# Manages system clipboard interaction with transient feedback.
|
|
7
|
+
#
|
|
8
|
+
# Apps need to copy data to the clipboard. Users need feedback: "Did it work?"
|
|
9
|
+
# Manual clipboard handling and feedback timers scattered through app logic is
|
|
10
|
+
# messy.
|
|
11
|
+
#
|
|
12
|
+
# This object handles clipboard writes to all platforms (pbcopy, xclip, xsel).
|
|
13
|
+
# It manages a feedback message and countdown timer.
|
|
14
|
+
#
|
|
15
|
+
# Use it to provide copy-to-clipboard functionality with user feedback.
|
|
16
|
+
#
|
|
17
|
+
# === Example
|
|
18
|
+
#
|
|
19
|
+
# clipboard = Clipboard.new
|
|
20
|
+
# clipboard.copy("#FF0000")
|
|
21
|
+
# puts clipboard.message # => "Copied!"
|
|
22
|
+
#
|
|
23
|
+
# # In render loop:
|
|
24
|
+
# clipboard.tick # Decrement timer
|
|
25
|
+
# puts clipboard.message # => "" (after 60 frames)
|
|
26
|
+
class Clipboard
|
|
27
|
+
def initialize
|
|
28
|
+
@message = ""
|
|
29
|
+
@timer = 0
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Writes text to the system clipboard.
|
|
33
|
+
#
|
|
34
|
+
# Tries pbcopy (macOS), xclip (Linux), then xsel (Linux fallback). Sets the
|
|
35
|
+
# feedback message to <tt>"Copied!"</tt> and starts a 60-frame timer.
|
|
36
|
+
#
|
|
37
|
+
# [text] String to copy
|
|
38
|
+
#
|
|
39
|
+
# === Example
|
|
40
|
+
#
|
|
41
|
+
# clipboard = Clipboard.new
|
|
42
|
+
# clipboard.copy("#FF0000")
|
|
43
|
+
# clipboard.message # => "Copied!"
|
|
44
|
+
def copy(text)
|
|
45
|
+
if `which pbcopy 2>/dev/null`.strip.length > 0
|
|
46
|
+
IO.popen("pbcopy", "w") { |io| io.write(text) }
|
|
47
|
+
elsif `which xclip 2>/dev/null`.strip.length > 0
|
|
48
|
+
IO.popen("xclip -selection clipboard", "w") { |io| io.write(text) }
|
|
49
|
+
elsif `which xsel 2>/dev/null`.strip.length > 0
|
|
50
|
+
IO.popen("xsel --clipboard --input", "w") { |io| io.write(text) }
|
|
51
|
+
end
|
|
52
|
+
@message = "Copied!"
|
|
53
|
+
@timer = 60
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Decrements the feedback timer by one frame.
|
|
57
|
+
#
|
|
58
|
+
# Call this once per render cycle. The message disappears when the timer
|
|
59
|
+
# reaches zero.
|
|
60
|
+
#
|
|
61
|
+
# === Example
|
|
62
|
+
#
|
|
63
|
+
# clipboard.copy("text") # timer = 60
|
|
64
|
+
# clipboard.tick # timer = 59
|
|
65
|
+
# 60.times { clipboard.tick } # message becomes ""
|
|
66
|
+
def tick
|
|
67
|
+
@timer -= 1 if @timer > 0
|
|
68
|
+
@message = "" if @timer <= 0
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Current feedback message.
|
|
72
|
+
#
|
|
73
|
+
# Empty string when no active message. <tt>"Copied!"</tt> after a successful
|
|
74
|
+
# copy, fading after 60 frames.
|
|
75
|
+
#
|
|
76
|
+
# === Example
|
|
77
|
+
#
|
|
78
|
+
# clipboard.message # => ""
|
|
79
|
+
# clipboard.copy("x")
|
|
80
|
+
# clipboard.message # => "Copied!"
|
|
81
|
+
def message
|
|
82
|
+
@message
|
|
83
|
+
end
|
|
84
|
+
end
|