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
|
@@ -1,58 +1,116 @@
|
|
|
1
|
-
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
-->
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
# Application Architecture
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
Architect robust TUI applications using core lifecycle patterns and API best practices.
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
## Core Concepts
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Your app lives inside a terminal. You need to respect its rules.
|
|
14
|
+
|
|
15
|
+
### Lifecycle Management
|
|
16
|
+
|
|
17
|
+
Terminals have state. They remember cursor positions, input modes, and screen buffers.
|
|
18
|
+
|
|
19
|
+
**The Problem:** If your app crashes or exits without cleaning up, it "breaks" the user's terminal. The cursor vanishes. Input echoes constantly. The alternate screen doesn't clear.
|
|
20
|
+
|
|
21
|
+
**The Solution:** The library's lifecycle manager handles this for you. It enters "raw mode" on startup and guarantees restoration on exit.
|
|
22
|
+
|
|
23
|
+
#### Use `RatatuiRuby.run`
|
|
24
|
+
|
|
25
|
+
This method acts as a safety net. It initializes the terminal, yields control to your block, and restores the terminal afterwards—even if your code raises an exception.
|
|
12
26
|
|
|
13
27
|
```ruby
|
|
14
28
|
RatatuiRuby.run do |tui|
|
|
15
29
|
loop do
|
|
16
|
-
|
|
17
|
-
|
|
30
|
+
tui.draw do |frame|
|
|
31
|
+
frame.render_widget(tui.paragraph(text: "Hello"), frame.area)
|
|
32
|
+
end
|
|
33
|
+
break if tui.poll_event == "q"
|
|
18
34
|
end
|
|
19
35
|
end
|
|
20
36
|
# Terminal is restored here
|
|
21
37
|
```
|
|
22
38
|
|
|
23
|
-
|
|
39
|
+
#### Manual Management
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
Need granular control? You can initialize and restore the terminal yourself. Use `ensure` blocks to guarantee cleanup.
|
|
26
42
|
|
|
27
43
|
```ruby
|
|
28
44
|
RatatuiRuby.init_terminal
|
|
29
45
|
begin
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
RatatuiRuby.draw do |frame|
|
|
47
|
+
frame.render_widget(RatatuiRuby::Paragraph.new(text: "Hello"), frame.area)
|
|
48
|
+
end
|
|
32
49
|
ensure
|
|
33
50
|
RatatuiRuby.restore_terminal
|
|
51
|
+
# Terminal is restored here
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Stateful Widgets
|
|
56
|
+
|
|
57
|
+
Most widgets are stateless configuration. You create them, render them, and they are gone. However, the **runtime status** of some widgets (like Lists and Tables) must persist across frames (e.g., scroll offsets or selection).
|
|
58
|
+
|
|
59
|
+
**The Problem:** If you re-create a List configuration every frame, you lose the context of where it was scrolled or what was selected. If Ratatui auto-scrolls to a selection, you can't read that new offset back from an immutable input widget.
|
|
60
|
+
|
|
61
|
+
**The Solution:** Use "Stateful Rendering". You create a mutable State object (Output/Status) once and pass it to `render_stateful_widget`. **The Widget configuration (Input) is still mandatory**, but the State object (passed separately) captures the runtime changes.
|
|
62
|
+
|
|
63
|
+
> [!IMPORTANT]
|
|
64
|
+
> **Precedence Rule:** When using `render_stateful_widget`, the **State object is the single source of truth** for selection and offset. Widget properties (`selected_index`, `selected_row`, `offset`) are **ignored**.
|
|
65
|
+
>
|
|
66
|
+
> For example: `list(selected_index: 0)` with `state.select(5)` → Item 5 is highlighted, not Item 0.
|
|
67
|
+
|
|
68
|
+
**Use Case:** When you need to read back the scroll offset (e.g., for mouse hit testing) or persist selection without managing indexes manually.
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
# Initialize state once
|
|
72
|
+
@list_state = RatatuiRuby::ListState.new
|
|
73
|
+
|
|
74
|
+
RatatuiRuby.run do |tui|
|
|
75
|
+
loop do
|
|
76
|
+
tui.draw do |frame|
|
|
77
|
+
# Create immutable widget (selected_index is ignored in stateful mode)
|
|
78
|
+
list = tui.list(items: ["A", "B", "C"])
|
|
79
|
+
|
|
80
|
+
# Render with state — state takes precedence
|
|
81
|
+
frame.render_stateful_widget(list, frame.area, @list_state)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Read back offset calculated by Ratatui
|
|
85
|
+
puts "Current Scroll Offset: #{@list_state.offset}"
|
|
86
|
+
end
|
|
34
87
|
end
|
|
35
88
|
```
|
|
36
89
|
|
|
37
|
-
|
|
90
|
+
### API Convenience
|
|
38
91
|
|
|
39
|
-
|
|
92
|
+
Writing UI trees involves nesting many widgets.
|
|
40
93
|
|
|
41
|
-
The
|
|
42
|
-
It provides factory methods for every widget class (converting snake_case to CamelCase) and aliases for module functions.
|
|
94
|
+
**The Problem:** Explicitly namespacing `RatatuiRuby::` for every widget (e.g., `RatatuiRuby::Paragraph.new`) is tedious. It creates visual noise that hides your layout structure.
|
|
43
95
|
|
|
44
|
-
**
|
|
96
|
+
**The Solution:** The Session API (`tui`) provides shorthand factories for every widget. It yields a session object to your block.
|
|
45
97
|
|
|
46
98
|
```ruby
|
|
47
99
|
RatatuiRuby.run do |tui|
|
|
48
100
|
loop do
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
101
|
+
tui.draw do |frame|
|
|
102
|
+
# Split layout using Session helpes
|
|
103
|
+
sidebar_area, content_area = tui.layout_split(
|
|
104
|
+
frame.area,
|
|
105
|
+
direction: :horizontal,
|
|
106
|
+
constraints: [
|
|
107
|
+
tui.constraint_length(20),
|
|
108
|
+
tui.constraint_min(0)
|
|
109
|
+
]
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Render sidebar
|
|
113
|
+
frame.render_widget(
|
|
56
114
|
tui.paragraph(
|
|
57
115
|
text: tui.text_line(spans: [
|
|
58
116
|
tui.text_span(content: "Side", style: tui.style(fg: :blue)),
|
|
@@ -60,15 +118,19 @@ RatatuiRuby.run do |tui|
|
|
|
60
118
|
]),
|
|
61
119
|
block: tui.block(borders: [:all], title: "Nav")
|
|
62
120
|
),
|
|
121
|
+
sidebar_area
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Render main content
|
|
125
|
+
frame.render_widget(
|
|
63
126
|
tui.paragraph(
|
|
64
127
|
text: "Main Content",
|
|
65
128
|
style: tui.style(fg: :green),
|
|
66
129
|
block: tui.block(borders: [:all], title: "Content")
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
tui.draw(layout)
|
|
130
|
+
),
|
|
131
|
+
content_area
|
|
132
|
+
)
|
|
133
|
+
end
|
|
72
134
|
|
|
73
135
|
event = tui.poll_event
|
|
74
136
|
break if event == "q" || event == :ctrl_c
|
|
@@ -76,22 +138,25 @@ RatatuiRuby.run do |tui|
|
|
|
76
138
|
end
|
|
77
139
|
```
|
|
78
140
|
|
|
79
|
-
|
|
141
|
+
#### Raw API
|
|
80
142
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
**Comparison:** Notice how much more verbose the same UI definition is.
|
|
143
|
+
Building your own abstractions? You might prefer explicit class instantiation. The raw constants are always available.
|
|
84
144
|
|
|
85
145
|
```ruby
|
|
86
146
|
RatatuiRuby.run do
|
|
87
147
|
loop do
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
148
|
+
RatatuiRuby.draw do |frame|
|
|
149
|
+
# Manual split
|
|
150
|
+
rects = RatatuiRuby::Layout.split(
|
|
151
|
+
frame.area,
|
|
152
|
+
direction: :horizontal,
|
|
153
|
+
constraints: [
|
|
154
|
+
RatatuiRuby::Constraint.length(20),
|
|
155
|
+
RatatuiRuby::Constraint.min(0)
|
|
156
|
+
]
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
frame.render_widget(
|
|
95
160
|
RatatuiRuby::Paragraph.new(
|
|
96
161
|
text: RatatuiRuby::Text::Line.new(spans: [
|
|
97
162
|
RatatuiRuby::Text::Span.new(content: "Side", style: RatatuiRuby::Style.new(fg: :blue)),
|
|
@@ -99,18 +164,87 @@ RatatuiRuby.run do
|
|
|
99
164
|
]),
|
|
100
165
|
block: RatatuiRuby::Block.new(borders: [:all], title: "Nav")
|
|
101
166
|
),
|
|
167
|
+
rects[0]
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
frame.render_widget(
|
|
102
171
|
RatatuiRuby::Paragraph.new(
|
|
103
172
|
text: "Main Content",
|
|
104
173
|
style: RatatuiRuby::Style.new(fg: :green),
|
|
105
174
|
block: RatatuiRuby::Block.new(borders: [:all], title: "Content")
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
RatatuiRuby.draw(layout)
|
|
175
|
+
),
|
|
176
|
+
rects[1]
|
|
177
|
+
)
|
|
178
|
+
end
|
|
111
179
|
|
|
112
180
|
event = RatatuiRuby.poll_event
|
|
113
181
|
break if event == "q" || event == :ctrl_c
|
|
114
182
|
end
|
|
115
183
|
end
|
|
116
184
|
```
|
|
185
|
+
|
|
186
|
+
## Thread and Ractor Safety
|
|
187
|
+
|
|
188
|
+
Building for Ruby 4.0's parallel future? Know which objects can travel between Ractors.
|
|
189
|
+
|
|
190
|
+
### Data Objects (Shareable)
|
|
191
|
+
|
|
192
|
+
These are deeply frozen and `Ractor.shareable?`. Include them in TEA Models/Messages freely:
|
|
193
|
+
|
|
194
|
+
| Object | Source |
|
|
195
|
+
|--------|--------|
|
|
196
|
+
| `Event::*` | `poll_event` |
|
|
197
|
+
| `Cell` | `get_cell_at` |
|
|
198
|
+
| `Rect` | `Layout.split`, `Frame#area` |
|
|
199
|
+
|
|
200
|
+
### I/O Handles (Not Shareable)
|
|
201
|
+
|
|
202
|
+
These have side effects and are intentionally not shareable:
|
|
203
|
+
|
|
204
|
+
| Object | Valid Usage |
|
|
205
|
+
|--------|-------------|
|
|
206
|
+
| `Session` | Cache in `@tui` during run loop. Don't include in Models. |
|
|
207
|
+
| `Frame` | Pass to helpers during draw block. Invalid after block returns. |
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
# Good: Cache session in instance variable
|
|
211
|
+
RatatuiRuby.run do |tui|
|
|
212
|
+
@tui = tui
|
|
213
|
+
loop { render; handle_input }
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Bad: Include in immutable Model (won't work with Ractors)
|
|
217
|
+
Model = Data.define(:tui, :count) # Don't do this
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
## Reference Architectures
|
|
222
|
+
|
|
223
|
+
Simple scripts work well with valid linear code. Complex apps need structure.
|
|
224
|
+
|
|
225
|
+
We provide these reference architectures to inspire you:
|
|
226
|
+
|
|
227
|
+
### Proto-TEA (Model-View-Update)
|
|
228
|
+
|
|
229
|
+
**Source:** [examples/app_all_events](../examples/app_all_events/README.md)
|
|
230
|
+
|
|
231
|
+
This pattern implements unidirectional data flow inspired by The Elm Architecture:
|
|
232
|
+
* **Model:** A single immutable `Data.define` object holding all application state.
|
|
233
|
+
* **Msg:** Semantic value objects that decouple raw events from business logic.
|
|
234
|
+
* **Update:** A pure function that computes the next state: `Update.call(msg, model) -> Model`.
|
|
235
|
+
* **View:** Pure rendering logic that accepts the immutable Model.
|
|
236
|
+
|
|
237
|
+
Use this when you want predictable state management and easy-to-test logic.
|
|
238
|
+
|
|
239
|
+
### Proto-Kit (Component-Based)
|
|
240
|
+
|
|
241
|
+
**Source:** [examples/app_color_picker](../examples/app_color_picker/README.md)
|
|
242
|
+
|
|
243
|
+
This pattern addresses the difficulty of mouse interaction and complex UI orchestration:
|
|
244
|
+
* **Component Contract:** Every UI element implements `render(tui, frame, area)` and `handle_event(event)`.
|
|
245
|
+
* **Encapsulated Hit Testing:** Components cache their render area and check `contains?` internally.
|
|
246
|
+
* **Symbolic Signals:** `handle_event` returns semantic symbols (`:consumed`, `:submitted`) instead of just booleans.
|
|
247
|
+
* **Container (Mediator):** A parent container routes events via Chain of Responsibility and coordinates cross-component effects.
|
|
248
|
+
|
|
249
|
+
Use this when you need rich interactivity (mouse clicks, drag-and-drop) or complex dynamic layouts.
|
|
250
|
+
|
data/doc/application_testing.md
CHANGED
|
@@ -8,15 +8,11 @@ This guide explains how to test your RatatuiRuby applications using the provided
|
|
|
8
8
|
|
|
9
9
|
## Overview
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
You need to verify that your application looks and behaves correctly. Manually checking every character on a terminal screen is tedious. Dealing with race conditions and complex state management in tests creates friction.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The `TestHelper` module solves this. It provides a headless "test terminal" to capture output and a suite of robust assertions to verify state.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- Inspect the cursor position.
|
|
18
|
-
|
|
19
|
-
- Simulate user input (using `inject_event`).
|
|
15
|
+
Use it to write fast, deterministic tests for your TUI applications.
|
|
20
16
|
|
|
21
17
|
## Setup
|
|
22
18
|
|
|
@@ -36,66 +32,118 @@ class MyApplicationTest < Minitest::Test
|
|
|
36
32
|
end
|
|
37
33
|
```
|
|
38
34
|
|
|
39
|
-
##
|
|
35
|
+
## Writing a View Test
|
|
40
36
|
|
|
41
|
-
|
|
37
|
+
To test a view or widget, wrap your assertions in `with_test_terminal`. This sets up a temporary, in-memory backend for Ratatui to draw to.
|
|
42
38
|
|
|
43
|
-
|
|
39
|
+
1. **Initialize the terminal:** Call `with_test_terminal`.
|
|
40
|
+
2. **Render your code:** Instantiate your widget and draw it to a frame.
|
|
41
|
+
3. **Assert output:** Check the `buffer_content` against your expectations.
|
|
44
42
|
|
|
45
43
|
```ruby
|
|
46
44
|
def test_rendering
|
|
47
45
|
# Uses default 80x24 terminal
|
|
48
46
|
with_test_terminal do
|
|
49
|
-
# 1. Instantiate your
|
|
47
|
+
# 1. Instantiate your widget
|
|
50
48
|
widget = RatatuiRuby::Paragraph.new(text: "Hello World")
|
|
51
49
|
|
|
52
|
-
# 2. Render it
|
|
53
|
-
RatatuiRuby.draw
|
|
50
|
+
# 2. Render it using the Frame API
|
|
51
|
+
RatatuiRuby.draw do |frame|
|
|
52
|
+
frame.render_widget(widget, frame.area)
|
|
53
|
+
end
|
|
54
54
|
|
|
55
55
|
# 3. Assert on the output
|
|
56
|
-
assert_includes buffer_content
|
|
56
|
+
assert_includes buffer_content.first, "Hello World"
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
For the full API list, including `buffer_content` and `cursor_position`, see [RatatuiRuby::TestHelper::Terminal](../lib/ratatui_ruby/test_helper/terminal.rb).
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
```ruby
|
|
66
|
-
rows = buffer_content
|
|
67
|
-
assert_equal "Title", rows[0].strip
|
|
68
|
-
assert_match /Results: \d+/, rows[2]
|
|
69
|
-
```
|
|
63
|
+
## Verifying Styles
|
|
70
64
|
|
|
71
|
-
|
|
65
|
+
You often need to check colors and modifiers (bold, italic) to ensure your highlighting logic works.
|
|
72
66
|
|
|
73
|
-
|
|
67
|
+
Use `assert_fg_color`, `assert_bg_color`, and modifier helpers like `assert_bold`.
|
|
74
68
|
|
|
75
69
|
```ruby
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
# Assert specific cell style
|
|
71
|
+
assert_fg_color(:red, 0, 0)
|
|
72
|
+
assert_bold(0, 0)
|
|
73
|
+
|
|
74
|
+
# Or check a whole area
|
|
75
|
+
assert_area_style({ x: 0, y: 0, w: 10, h: 1 }, bg: :blue)
|
|
79
76
|
```
|
|
80
77
|
|
|
81
|
-
|
|
78
|
+
See [RatatuiRuby::TestHelper::StyleAssertions](../lib/ratatui_ruby/test_helper/style_assertions.rb) for the comprehensive list of style helpers.
|
|
79
|
+
|
|
80
|
+
## Simulating Input
|
|
81
|
+
|
|
82
|
+
You need to test user interactions like typing or clicking. Stubbing `poll_event` directly is brittle.
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
Use `inject_event` to push mock events into the queue. This ensures safe, deterministic handling of input.
|
|
84
85
|
|
|
85
86
|
> [!IMPORTANT]
|
|
86
|
-
>
|
|
87
|
+
> Call `inject_event` inside a `with_test_terminal` block to avoid race conditions.
|
|
87
88
|
|
|
88
89
|
```ruby
|
|
89
90
|
with_test_terminal do
|
|
90
91
|
# Simulate 'q' key press
|
|
91
92
|
inject_event("key", { code: "q" })
|
|
92
93
|
|
|
93
|
-
#
|
|
94
|
+
# The application receives the 'q' event
|
|
94
95
|
event = RatatuiRuby.poll_event
|
|
95
96
|
assert_equal "q", event.code
|
|
96
97
|
end
|
|
97
98
|
```
|
|
98
99
|
|
|
100
|
+
See [RatatuiRuby::TestHelper::EventInjection](../lib/ratatui_ruby/test_helper/event_injection.rb) for helper methods like `inject_keys` and `inject_click`.
|
|
101
|
+
|
|
102
|
+
## Snapshot Testing
|
|
103
|
+
|
|
104
|
+
Snapshots let you verify complex layouts without manually asserting every line.
|
|
105
|
+
|
|
106
|
+
Use `assert_snapshot` to compare the current screen against a stored reference file.
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
with_test_terminal do
|
|
110
|
+
MyApp.new.run
|
|
111
|
+
assert_snapshot("dashboard_view")
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Handling Non-Determinism
|
|
116
|
+
|
|
117
|
+
Snapshots must be deterministic. Random data or current timestamps will cause test failures ("flakes").
|
|
118
|
+
|
|
119
|
+
To prevent this:
|
|
120
|
+
1. **Seed Randomness:** Use a fixed seed for any RNG.
|
|
121
|
+
2. **Stub Time:** Force the application to use a static time.
|
|
122
|
+
|
|
123
|
+
For detailed strategies and code examples, see [RatatuiRuby::TestHelper::Snapshot](../lib/ratatui_ruby/test_helper/snapshot.rb).
|
|
124
|
+
|
|
125
|
+
## Isolated View Testing
|
|
126
|
+
|
|
127
|
+
Sometimes you want to test a single view component without spinning up the full `TestTerminal` engine.
|
|
128
|
+
|
|
129
|
+
Use `MockFrame` and `StubRect` to test render logic in isolation.
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
def test_logs_view
|
|
133
|
+
frame = RatatuiRuby::TestHelper::TestDoubles::MockFrame.new
|
|
134
|
+
area = RatatuiRuby::TestHelper::TestDoubles::StubRect.new(width: 40, height: 10)
|
|
135
|
+
|
|
136
|
+
# Call your view directly
|
|
137
|
+
MyView.new.render(frame, area)
|
|
138
|
+
|
|
139
|
+
# Inspect what was rendered
|
|
140
|
+
rendered = frame.rendered_widgets.first
|
|
141
|
+
assert_equal "Logs", rendered[:widget].block.title
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
See [RatatuiRuby::TestHelper::TestDoubles](../lib/ratatui_ruby/test_helper/test_doubles.rb).
|
|
146
|
+
|
|
99
147
|
## Example
|
|
100
148
|
|
|
101
|
-
|
|
149
|
+
Check out the [examples directory](../examples/) for fully tested applications showcasing these patterns.
|
|
@@ -9,15 +9,20 @@ This document describes the design philosophy and structure of the Ruby layer in
|
|
|
9
9
|
|
|
10
10
|
## Core Philosophy: Data-Driven UI
|
|
11
11
|
|
|
12
|
+
|
|
13
|
+
|
|
12
14
|
The Ruby frontend is designed as a **thin, declarative layer** over the Rust backend. It uses an **Immediate Mode** paradigm where the user constructs a tree of pure data objects every frame to represent the desired UI state.
|
|
13
15
|
|
|
14
|
-
### 1.
|
|
16
|
+
### 1. Separation of Configuration and Status
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
`ratatui_ruby` strictly separates **what** a widget is (Configuration) from **where** it is (Status).
|
|
19
|
+
|
|
20
|
+
#### Configuration (Input)
|
|
21
|
+
Widgets (e.g., `RatatuiRuby::List`) are immutable value objects defining the *desired appearance* for the current frame. They are pure inputs to the renderer.
|
|
17
22
|
|
|
18
23
|
* Implemented using Ruby 3.2+ `Data` classes.
|
|
19
24
|
* Located in `lib/ratatui_ruby/schema/`.
|
|
20
|
-
*
|
|
25
|
+
* Act as a Schema/IDL between Ruby and Rust.
|
|
21
26
|
|
|
22
27
|
**Example:**
|
|
23
28
|
```ruby
|
|
@@ -29,6 +34,39 @@ paragraph = RatatuiRuby::Paragraph.new(
|
|
|
29
34
|
)
|
|
30
35
|
```
|
|
31
36
|
|
|
37
|
+
#### Status (Output)
|
|
38
|
+
**Optional.** Only specific widgets (like `List` and `Table`) rely on runtime status. State objects (e.g., `RatatuiRuby::ListState`) track metrics calculated by the backend, such as scroll offsets.
|
|
39
|
+
|
|
40
|
+
* passed as a *secondary argument* to `render_stateful_widget`.
|
|
41
|
+
* **The Widget Configuration is still required.** You cannot render a State without its corresponding Widget.
|
|
42
|
+
* Updated in-place by the Rust backend to reflect the actual rendered state.
|
|
43
|
+
|
|
44
|
+
**Example:**
|
|
45
|
+
```ruby
|
|
46
|
+
# 1. Initialize State once (Input/Output)
|
|
47
|
+
list_state = RatatuiRuby::ListState.new
|
|
48
|
+
list_state.select(3)
|
|
49
|
+
|
|
50
|
+
RatatuiRuby.run do |tui|
|
|
51
|
+
loop do
|
|
52
|
+
tui.draw do |frame|
|
|
53
|
+
# 2. Define Configuration (Input)
|
|
54
|
+
# (Note: In a real app, you'd probably use `tui.list(...)` helper)
|
|
55
|
+
list = RatatuiRuby::List.new(items: ["A", "B", "C", "D"])
|
|
56
|
+
|
|
57
|
+
# 3. Render with both (Side Effect: updates list_state)
|
|
58
|
+
frame.render_stateful_widget(list, frame.area, list_state)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# 4. Read back Status (Output)
|
|
62
|
+
# If the backend auto-scrolled to keep index 3 visible:
|
|
63
|
+
puts "Scroll Offset: #{list_state.offset}"
|
|
64
|
+
|
|
65
|
+
break if tui.poll_event == "q"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
32
70
|
### 2. Immediate Mode Rendering
|
|
33
71
|
|
|
34
72
|
The application loop typically looks like this:
|
|
@@ -43,11 +81,13 @@ loop do
|
|
|
43
81
|
event = RatatuiRuby.poll_event
|
|
44
82
|
break if event == :esc
|
|
45
83
|
|
|
46
|
-
# 3. Construct View Tree
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
84
|
+
# 3. Construct View Tree & Draw
|
|
85
|
+
RatatuiRuby.draw do |frame|
|
|
86
|
+
frame.render_widget(
|
|
87
|
+
RatatuiRuby::Paragraph.new(text: "Time: #{Time.now}"),
|
|
88
|
+
frame.area
|
|
89
|
+
)
|
|
90
|
+
end
|
|
51
91
|
end
|
|
52
92
|
```
|
|
53
93
|
|
|
@@ -16,6 +16,7 @@ The project follows a **Structured Design** approach, separating concerns into m
|
|
|
16
16
|
1. **Single Generic Renderer**: The backend implements a single generic renderer that accepts a Ruby `Value` representing the root of the view tree.
|
|
17
17
|
2. **No Custom Rust Structs for UI**: Do not define custom Rust structs that mirror Ruby UI components. Instead, extract data directly from Ruby objects using `funcall`.
|
|
18
18
|
3. **Dynamic Dispatch**: Use `value.class().name()` (e.g., `"RatatuiRuby::Paragraph"`) to dynamically dispatch rendering logic to the appropriate widget module.
|
|
19
|
+
* *Exception:* `render_stateful_widget` bypasses generic dispatch for specific Widget/State pairs (e.g., List + ListState) to allow mutating the State object.
|
|
19
20
|
4. **Immediate Mode**: The renderer traverses the Ruby object tree every frame and rebuilds the Ratatui widget tree on the fly.
|
|
20
21
|
|
|
21
22
|
### Module Structure
|