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,191 @@
|
|
|
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 "chroma"
|
|
7
|
+
require "wcag_color_contrast"
|
|
8
|
+
require_relative "harmony"
|
|
9
|
+
|
|
10
|
+
# Represents a single color with format conversion and harmony generation.
|
|
11
|
+
#
|
|
12
|
+
# Colors are central to visual design. Users need to work with colors in multiple
|
|
13
|
+
# formats: hex, RGB, HSL. They also need to generate color schemes: shades, tints,
|
|
14
|
+
# and complementary colors. Managing these conversions and relationships manually
|
|
15
|
+
# is tedious and error-prone.
|
|
16
|
+
#
|
|
17
|
+
# This object wraps a Chroma color. It exposes format conversions. It generates
|
|
18
|
+
# color harmonies. It calculates contrast ratios to choose readable text colors.
|
|
19
|
+
#
|
|
20
|
+
# Use it to parse user input, transform colors, and build color palettes.
|
|
21
|
+
#
|
|
22
|
+
# === Example
|
|
23
|
+
#
|
|
24
|
+
# color = Color.parse("#FF0000")
|
|
25
|
+
# puts color.hex # => "#FF0000"
|
|
26
|
+
# puts color.rgb # => "rgb(255, 0, 0)"
|
|
27
|
+
# puts color.hsl_string # => "hsl(0, 100%, 50%)"
|
|
28
|
+
#
|
|
29
|
+
# # Generate harmonies
|
|
30
|
+
# harmonies = color.harmonies # => [main, shade, tint, complement, ...]
|
|
31
|
+
#
|
|
32
|
+
# # Transform colors
|
|
33
|
+
# lighter = color.tint(5)
|
|
34
|
+
# darker = color.shade(3)
|
|
35
|
+
# rotated = color.spin(180)
|
|
36
|
+
class Color
|
|
37
|
+
def initialize(chroma_color)
|
|
38
|
+
@chroma = chroma_color
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Parses a color string and returns a Color, or nil if the string is invalid.
|
|
42
|
+
#
|
|
43
|
+
# Accepts hex, RGB, HSL, and named colors. Trims whitespace and handles
|
|
44
|
+
# empty strings gracefully.
|
|
45
|
+
#
|
|
46
|
+
# [input_str] String in any format Chroma supports (e.g., <tt>"#FF0000"</tt>, <tt>"red"</tt>, <tt>"rgb(255,0,0)"</tt>)
|
|
47
|
+
#
|
|
48
|
+
# === Example
|
|
49
|
+
#
|
|
50
|
+
# Color.parse("#FF0000") # => Color
|
|
51
|
+
# Color.parse("red") # => Color
|
|
52
|
+
# Color.parse("invalid") # => nil
|
|
53
|
+
# Color.parse("") # => nil
|
|
54
|
+
def self.parse(input_str)
|
|
55
|
+
input_str = input_str.to_s.strip
|
|
56
|
+
return nil if input_str.empty?
|
|
57
|
+
|
|
58
|
+
new(Chroma.paint(input_str.dup))
|
|
59
|
+
rescue
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Hex color code (uppercase).
|
|
64
|
+
#
|
|
65
|
+
# === Example
|
|
66
|
+
#
|
|
67
|
+
# color = Color.parse("red")
|
|
68
|
+
# color.hex # => "#FF0000"
|
|
69
|
+
def hex
|
|
70
|
+
@chroma.to_hex.upcase
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# RGB color code.
|
|
74
|
+
#
|
|
75
|
+
# === Example
|
|
76
|
+
#
|
|
77
|
+
# color = Color.parse("red")
|
|
78
|
+
# color.rgb # => "rgb(255, 0, 0)"
|
|
79
|
+
def rgb
|
|
80
|
+
@chroma.to_rgb
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# HSL color string with percentage formatting.
|
|
84
|
+
#
|
|
85
|
+
# === Example
|
|
86
|
+
#
|
|
87
|
+
# color = Color.parse("red")
|
|
88
|
+
# color.hsl_string # => "hsl(0, 100%, 50%)"
|
|
89
|
+
def hsl_string
|
|
90
|
+
hsl_obj = @chroma.hsl
|
|
91
|
+
h = hsl_obj.h
|
|
92
|
+
s = hsl_obj.s
|
|
93
|
+
l = hsl_obj.l
|
|
94
|
+
format("hsl(%.0f, %.1f%%, %.1f%%)", h, s * 100, l * 100)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Darkens the color. Returns a new Color.
|
|
98
|
+
#
|
|
99
|
+
# [amount] Integer amount to darken (default: 3)
|
|
100
|
+
#
|
|
101
|
+
# === Example
|
|
102
|
+
#
|
|
103
|
+
# color = Color.parse("red")
|
|
104
|
+
# color.shade(5).hex # => darker red
|
|
105
|
+
def shade(amount = 3)
|
|
106
|
+
Color.new(@chroma.darken(amount))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Lightens the color. Returns a new Color.
|
|
110
|
+
#
|
|
111
|
+
# [amount] Integer amount to lighten (default: 3)
|
|
112
|
+
#
|
|
113
|
+
# === Example
|
|
114
|
+
#
|
|
115
|
+
# color = Color.parse("red")
|
|
116
|
+
# color.tint(5).hex # => lighter red
|
|
117
|
+
def tint(amount = 3)
|
|
118
|
+
Color.new(@chroma.lighten(amount))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Rotates the hue. Returns a new Color.
|
|
122
|
+
#
|
|
123
|
+
# [degrees] Integer degrees to rotate (0-360)
|
|
124
|
+
#
|
|
125
|
+
# === Example
|
|
126
|
+
#
|
|
127
|
+
# color = Color.parse("red")
|
|
128
|
+
# color.spin(180).hex # => cyan
|
|
129
|
+
def spin(degrees)
|
|
130
|
+
Color.new(@chroma.spin(degrees))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Determines optimal text color (:white or :black) for maximum contrast.
|
|
134
|
+
#
|
|
135
|
+
# Uses WCAG contrast ratio calculation. Returns <tt>:white</tt> if white has
|
|
136
|
+
# higher contrast; <tt>:black</tt> otherwise.
|
|
137
|
+
#
|
|
138
|
+
# === Example
|
|
139
|
+
#
|
|
140
|
+
# Color.parse("yellow").contrasting_text_color # => :black
|
|
141
|
+
# Color.parse("navy").contrasting_text_color # => :white
|
|
142
|
+
def contrasting_text_color
|
|
143
|
+
white_contrast = WCAGColorContrast.ratio(hex.sub(/^#/, ""), "ffffff")
|
|
144
|
+
black_contrast = WCAGColorContrast.ratio(hex.sub(/^#/, ""), "000000")
|
|
145
|
+
(white_contrast > black_contrast) ? :white : :black
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Background color for rendering this color as a swatch.
|
|
149
|
+
#
|
|
150
|
+
# Returns <tt>"#000000"</tt> if text should be white; <tt>"#ffffff"</tt> if black.
|
|
151
|
+
# Used to frame color swatches with contrasting borders.
|
|
152
|
+
#
|
|
153
|
+
# === Example
|
|
154
|
+
#
|
|
155
|
+
# Color.parse("yellow").frame_color # => "#000000"
|
|
156
|
+
def frame_color
|
|
157
|
+
(contrasting_text_color == :white) ? "#000000" : "#ffffff"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Seven-color harmony: main, shade, tint, complement, split 1, split 2, split-complement.
|
|
161
|
+
#
|
|
162
|
+
# Generates a complete color scheme for UI design. Each harmony is a Harmony
|
|
163
|
+
# value object with label, hex, and styling information.
|
|
164
|
+
#
|
|
165
|
+
# === Example
|
|
166
|
+
#
|
|
167
|
+
# color = Color.parse("red")
|
|
168
|
+
# harmonies = color.harmonies
|
|
169
|
+
# harmonies.first.label # => "Main"
|
|
170
|
+
# harmonies.size # => 7
|
|
171
|
+
def harmonies
|
|
172
|
+
[
|
|
173
|
+
harmony_with_label("Main"),
|
|
174
|
+
shade.harmony_with_label("Shade"),
|
|
175
|
+
tint.harmony_with_label("Tint"),
|
|
176
|
+
spin(180).harmony_with_label("Comp"),
|
|
177
|
+
spin(150).harmony_with_label("Split 1"),
|
|
178
|
+
spin(210).harmony_with_label("Split 2"),
|
|
179
|
+
spin(30).harmony_with_label("S.Comp"),
|
|
180
|
+
]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def harmony_with_label(label)
|
|
184
|
+
Harmony.new(
|
|
185
|
+
label:,
|
|
186
|
+
hex:,
|
|
187
|
+
text_color: contrasting_text_color,
|
|
188
|
+
frame_color:,
|
|
189
|
+
)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
+
# A display-only component showing keyboard shortcuts and clipboard feedback.
|
|
7
|
+
#
|
|
8
|
+
# Users need to know what keys are available. They also need feedback when
|
|
9
|
+
# they copy a color. This component renders the controls section.
|
|
10
|
+
#
|
|
11
|
+
# === Component Contract
|
|
12
|
+
#
|
|
13
|
+
# - `render(tui, frame, area, clipboard:)`: Draws the controls; stores `area`
|
|
14
|
+
# - `handle_event(event) -> nil`: Display-only, always returns nil
|
|
15
|
+
# - `tick`: Delegates to clipboard for time-based feedback updates
|
|
16
|
+
#
|
|
17
|
+
# === Example
|
|
18
|
+
#
|
|
19
|
+
# controls = Controls.new
|
|
20
|
+
# controls.render(tui, frame, area, clipboard: clipboard)
|
|
21
|
+
# controls.tick(clipboard)
|
|
22
|
+
class Controls
|
|
23
|
+
def initialize
|
|
24
|
+
@area = nil
|
|
25
|
+
@hotkey_style = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The cached render area.
|
|
29
|
+
attr_reader :area
|
|
30
|
+
|
|
31
|
+
# Renders the controls section into the given area.
|
|
32
|
+
#
|
|
33
|
+
# Shows keyboard shortcuts and clipboard feedback message if one is active.
|
|
34
|
+
#
|
|
35
|
+
# [tui] Session or TUI factory object
|
|
36
|
+
# [frame] Frame object from RatatuiRuby.draw block
|
|
37
|
+
# [area] Rect area to draw into
|
|
38
|
+
# [clipboard] Clipboard object for feedback message
|
|
39
|
+
#
|
|
40
|
+
# === Example
|
|
41
|
+
#
|
|
42
|
+
# controls.render(tui, frame, control_area, clipboard: clipboard)
|
|
43
|
+
def render(tui, frame, area, clipboard:)
|
|
44
|
+
@area = area
|
|
45
|
+
@hotkey_style ||= tui.style(modifiers: [:bold, :underlined])
|
|
46
|
+
widget = build_widget(tui, clipboard)
|
|
47
|
+
frame.render_widget(widget, area)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Display-only component; always returns nil.
|
|
51
|
+
def handle_event(_event)
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Delegates tick to the clipboard for time-based updates.
|
|
56
|
+
#
|
|
57
|
+
# [clipboard] Clipboard object to tick
|
|
58
|
+
def tick(clipboard)
|
|
59
|
+
clipboard.tick
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private def build_widget(tui, clipboard)
|
|
63
|
+
control_lines = [
|
|
64
|
+
tui.text_line(spans: [
|
|
65
|
+
tui.text_span(content: "a-z/0-9", style: @hotkey_style),
|
|
66
|
+
tui.text_span(content: ": Type "),
|
|
67
|
+
tui.text_span(content: "enter", style: @hotkey_style),
|
|
68
|
+
tui.text_span(content: ": Parse "),
|
|
69
|
+
tui.text_span(content: "bksp", style: @hotkey_style),
|
|
70
|
+
tui.text_span(content: ": Erase "),
|
|
71
|
+
tui.text_span(content: "esc", style: @hotkey_style),
|
|
72
|
+
tui.text_span(content: ": Quit"),
|
|
73
|
+
]),
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
unless clipboard.message.empty?
|
|
77
|
+
control_lines << tui.text_line(spans: [
|
|
78
|
+
tui.text_span(content: clipboard.message, style: tui.style(fg: :green, modifiers: [:bold])),
|
|
79
|
+
])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
tui.block(
|
|
83
|
+
title: "Controls",
|
|
84
|
+
borders: [:all],
|
|
85
|
+
children: [
|
|
86
|
+
tui.paragraph(text: control_lines),
|
|
87
|
+
]
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
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 "clipboard"
|
|
7
|
+
|
|
8
|
+
# A self-contained modal dialog component for copying text to the clipboard.
|
|
9
|
+
#
|
|
10
|
+
# Users click on content they want to copy. The app needs to confirm: "Are you
|
|
11
|
+
# sure?" This component owns dialog state, renders itself, and handles keyboard
|
|
12
|
+
# input.
|
|
13
|
+
#
|
|
14
|
+
# === Component Contract
|
|
15
|
+
#
|
|
16
|
+
# - `render(tui, frame, area)`: Draws the dialog; stores `area`
|
|
17
|
+
# - `handle_event(event) -> Symbol | nil`: Returns `:consumed` when handled
|
|
18
|
+
# - `open(text)`: Opens the dialog with the text to copy
|
|
19
|
+
# - `close`: Closes the dialog
|
|
20
|
+
# - `active?`: True if the dialog is visible
|
|
21
|
+
#
|
|
22
|
+
# === Example
|
|
23
|
+
#
|
|
24
|
+
# dialog = CopyDialog.new(clipboard)
|
|
25
|
+
# dialog.open("#FF0000")
|
|
26
|
+
#
|
|
27
|
+
# result = dialog.handle_event(event)
|
|
28
|
+
# # result == :consumed when dialog handled the event
|
|
29
|
+
#
|
|
30
|
+
# dialog.render(tui, frame, center_area)
|
|
31
|
+
class CopyDialog
|
|
32
|
+
def initialize(clipboard)
|
|
33
|
+
@clipboard = clipboard
|
|
34
|
+
@text = ""
|
|
35
|
+
@selected = :yes
|
|
36
|
+
@active = false
|
|
37
|
+
@area = nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The cached render area.
|
|
41
|
+
attr_reader :area
|
|
42
|
+
|
|
43
|
+
# Opens the dialog with text to copy.
|
|
44
|
+
#
|
|
45
|
+
# Initializes selection to <tt>:yes</tt> and sets active to true.
|
|
46
|
+
#
|
|
47
|
+
# [text] String text to show and copy
|
|
48
|
+
#
|
|
49
|
+
# === Example
|
|
50
|
+
#
|
|
51
|
+
# dialog.open("#FF0000")
|
|
52
|
+
# dialog.active? # => true
|
|
53
|
+
def open(text)
|
|
54
|
+
@text = text
|
|
55
|
+
@selected = :yes
|
|
56
|
+
@active = true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Closes the dialog and deactivates it.
|
|
60
|
+
def close
|
|
61
|
+
@active = false
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# True if the dialog is currently open and visible.
|
|
65
|
+
def active?
|
|
66
|
+
@active
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Renders the dialog into the given area.
|
|
70
|
+
#
|
|
71
|
+
# Shows the text to copy, Yes/No buttons with current selection highlighted,
|
|
72
|
+
# and keyboard instructions.
|
|
73
|
+
#
|
|
74
|
+
# [tui] Session or TUI factory object
|
|
75
|
+
# [frame] Frame object from RatatuiRuby.draw block
|
|
76
|
+
# [area] Rect area to draw into
|
|
77
|
+
#
|
|
78
|
+
# === Example
|
|
79
|
+
#
|
|
80
|
+
# dialog.render(tui, frame, center_area)
|
|
81
|
+
def render(tui, frame, area)
|
|
82
|
+
@area = area
|
|
83
|
+
widget = build_widget(tui)
|
|
84
|
+
frame.render_widget(widget, area)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Processes a keyboard event and updates selection or closes the dialog.
|
|
88
|
+
#
|
|
89
|
+
# Returns:
|
|
90
|
+
# - `:consumed` when the event was handled
|
|
91
|
+
# - `nil` when the event was ignored or dialog is inactive
|
|
92
|
+
#
|
|
93
|
+
# [event] Event from RatatuiRuby.poll_event
|
|
94
|
+
#
|
|
95
|
+
# === Example
|
|
96
|
+
#
|
|
97
|
+
# result = dialog.handle_event(event)
|
|
98
|
+
def handle_event(event)
|
|
99
|
+
return nil unless @active
|
|
100
|
+
|
|
101
|
+
case event
|
|
102
|
+
in { type: :key, code: "left" } | { type: :key, code: "h" }
|
|
103
|
+
@selected = :yes
|
|
104
|
+
:consumed
|
|
105
|
+
in { type: :key, code: "right" } | { type: :key, code: "l" }
|
|
106
|
+
@selected = :no
|
|
107
|
+
:consumed
|
|
108
|
+
in { type: :key, code: "enter" }
|
|
109
|
+
if @selected == :yes
|
|
110
|
+
@clipboard.copy(@text)
|
|
111
|
+
end
|
|
112
|
+
@active = false
|
|
113
|
+
:consumed
|
|
114
|
+
in { type: :key, code: "y" }
|
|
115
|
+
@clipboard.copy(@text)
|
|
116
|
+
@active = false
|
|
117
|
+
:consumed
|
|
118
|
+
in { type: :key, code: "n" }
|
|
119
|
+
@active = false
|
|
120
|
+
:consumed
|
|
121
|
+
else
|
|
122
|
+
nil
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private def build_widget(tui)
|
|
127
|
+
yes_style = if @selected == :yes
|
|
128
|
+
tui.style(bg: :cyan, fg: :black, modifiers: [:bold])
|
|
129
|
+
else
|
|
130
|
+
tui.style(fg: :gray)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
no_style = if @selected == :no
|
|
134
|
+
tui.style(bg: :cyan, fg: :black, modifiers: [:bold])
|
|
135
|
+
else
|
|
136
|
+
tui.style(fg: :gray)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
tui.block(
|
|
140
|
+
title: "Copy to Clipboard",
|
|
141
|
+
borders: [:all],
|
|
142
|
+
border_type: :rounded,
|
|
143
|
+
style: tui.style(bg: :black, fg: :white),
|
|
144
|
+
children: [
|
|
145
|
+
tui.paragraph(
|
|
146
|
+
text: [
|
|
147
|
+
tui.text_line(spans: [
|
|
148
|
+
tui.text_span(content: "Copy #{@text}?", style: tui.style(fg: :white)),
|
|
149
|
+
]),
|
|
150
|
+
tui.text_line(spans: []),
|
|
151
|
+
tui.text_line(spans: [
|
|
152
|
+
tui.text_span(content: "[", style: tui.style(fg: :white)),
|
|
153
|
+
tui.text_span(content: "Yes", style: yes_style),
|
|
154
|
+
tui.text_span(content: "] [", style: tui.style(fg: :white)),
|
|
155
|
+
tui.text_span(content: "No", style: no_style),
|
|
156
|
+
tui.text_span(content: "]", style: tui.style(fg: :white)),
|
|
157
|
+
]),
|
|
158
|
+
tui.text_line(spans: [
|
|
159
|
+
tui.text_span(content: "Use ←/→ or h/l to select, Enter to confirm", style: tui.style(fg: :gray, modifiers: [:italic])),
|
|
160
|
+
]),
|
|
161
|
+
]
|
|
162
|
+
),
|
|
163
|
+
]
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
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
|
+
# A self-contained component displaying export formats for a color.
|
|
7
|
+
#
|
|
8
|
+
# Users need to copy color values in different formats (HEX, RGB, HSL).
|
|
9
|
+
# This component renders the export section and detects clicks on itself.
|
|
10
|
+
#
|
|
11
|
+
# === Component Contract
|
|
12
|
+
#
|
|
13
|
+
# - `render(tui, frame, area, palette:)`: Draws the export formats; stores `area` for hit testing
|
|
14
|
+
# - `handle_event(event) -> Symbol | nil`: Returns `:copy_requested` when clicked
|
|
15
|
+
#
|
|
16
|
+
# === Example
|
|
17
|
+
#
|
|
18
|
+
# export_pane = ExportPane.new
|
|
19
|
+
# export_pane.render(tui, frame, area, palette: palette)
|
|
20
|
+
#
|
|
21
|
+
# result = export_pane.handle_event(event)
|
|
22
|
+
# if result == :copy_requested && palette.main
|
|
23
|
+
# dialog.open(palette.main.hex)
|
|
24
|
+
# end
|
|
25
|
+
class ExportPane
|
|
26
|
+
def initialize
|
|
27
|
+
@area = nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The cached render area, for hit testing.
|
|
31
|
+
attr_reader :area
|
|
32
|
+
|
|
33
|
+
# Renders the export formats section into the given area.
|
|
34
|
+
#
|
|
35
|
+
# Shows HEX, RGB, and HSL values for the current color. If no color is set,
|
|
36
|
+
# displays a placeholder message.
|
|
37
|
+
#
|
|
38
|
+
# [tui] Session or TUI factory object
|
|
39
|
+
# [frame] Frame object from RatatuiRuby.draw block
|
|
40
|
+
# [area] Rect area to draw into
|
|
41
|
+
# [palette] Palette object containing the color to display
|
|
42
|
+
#
|
|
43
|
+
# === Example
|
|
44
|
+
#
|
|
45
|
+
# export_pane.render(tui, frame, export_area, palette: palette)
|
|
46
|
+
def render(tui, frame, area, palette:)
|
|
47
|
+
@area = area
|
|
48
|
+
widget = build_widget(tui, palette)
|
|
49
|
+
frame.render_widget(widget, area)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Processes a mouse event and returns a signal if clicked.
|
|
53
|
+
#
|
|
54
|
+
# Returns:
|
|
55
|
+
# - `:copy_requested` when the pane is clicked (caller should open copy dialog)
|
|
56
|
+
# - `nil` when the event was ignored or outside the area
|
|
57
|
+
#
|
|
58
|
+
# [event] Event from RatatuiRuby.poll_event
|
|
59
|
+
#
|
|
60
|
+
# === Example
|
|
61
|
+
#
|
|
62
|
+
# result = export_pane.handle_event(event)
|
|
63
|
+
# if result == :copy_requested
|
|
64
|
+
# dialog.open(palette.main.hex)
|
|
65
|
+
# end
|
|
66
|
+
def handle_event(event)
|
|
67
|
+
case event
|
|
68
|
+
in { type: :mouse, kind: "down", button: "left", x:, y: }
|
|
69
|
+
if @area&.contains?(x, y)
|
|
70
|
+
:copy_requested
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private def build_widget(tui, palette)
|
|
78
|
+
if palette.main.nil?
|
|
79
|
+
tui.block(
|
|
80
|
+
title: "Export Formats",
|
|
81
|
+
borders: [:all],
|
|
82
|
+
children: [
|
|
83
|
+
tui.paragraph(
|
|
84
|
+
text: tui.text_line(spans: [
|
|
85
|
+
tui.text_span(content: "Enter a color to see formats"),
|
|
86
|
+
])
|
|
87
|
+
),
|
|
88
|
+
]
|
|
89
|
+
)
|
|
90
|
+
else
|
|
91
|
+
build_color_widget(tui, palette.main)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private def build_color_widget(tui, color)
|
|
96
|
+
hex = color.hex
|
|
97
|
+
rgb = color.rgb
|
|
98
|
+
hsl = color.hsl_string
|
|
99
|
+
text_color = color.contrasting_text_color
|
|
100
|
+
bg_style = tui.style(bg: hex, fg: text_color)
|
|
101
|
+
|
|
102
|
+
tui.block(
|
|
103
|
+
title: "Export Formats",
|
|
104
|
+
borders: [:all],
|
|
105
|
+
style: bg_style,
|
|
106
|
+
children: [
|
|
107
|
+
tui.paragraph(
|
|
108
|
+
text: [
|
|
109
|
+
tui.text_line(spans: [
|
|
110
|
+
tui.text_span(content: "HEX: ", style: bg_style),
|
|
111
|
+
tui.text_span(content: hex, style: tui.style(bg: hex, fg: text_color, modifiers: [:underlined])),
|
|
112
|
+
]),
|
|
113
|
+
tui.text_line(spans: [
|
|
114
|
+
tui.text_span(content: "RGB: ", style: bg_style),
|
|
115
|
+
tui.text_span(content: rgb, style: tui.style(bg: hex, fg: text_color, modifiers: [:underlined])),
|
|
116
|
+
]),
|
|
117
|
+
tui.text_line(spans: [
|
|
118
|
+
tui.text_span(content: "HSL: ", style: bg_style),
|
|
119
|
+
tui.text_span(content: hsl, style: tui.style(bg: hex, fg: text_color, modifiers: [:underlined])),
|
|
120
|
+
]),
|
|
121
|
+
]
|
|
122
|
+
),
|
|
123
|
+
]
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
# A single color variant with label and styling information.
|
|
7
|
+
#
|
|
8
|
+
# Color palettes need to show individual colors with labels (Main, Shade, Tint,
|
|
9
|
+
# Complement). Bundling a color's hex code, text color, and frame color together
|
|
10
|
+
# is natural—they're always used as a set.
|
|
11
|
+
#
|
|
12
|
+
# This value object pairs a color with its metadata and rendering styles.
|
|
13
|
+
#
|
|
14
|
+
# Use it to represent colors in a palette or harmony.
|
|
15
|
+
#
|
|
16
|
+
# === Attributes
|
|
17
|
+
#
|
|
18
|
+
# [label] String label for this color variant
|
|
19
|
+
# [hex] String hex color code
|
|
20
|
+
# [text_color] Symbol (:white or :black) for readable text
|
|
21
|
+
# [frame_color] String background color for the swatch frame
|
|
22
|
+
#
|
|
23
|
+
# === Example
|
|
24
|
+
#
|
|
25
|
+
# harmony = Harmony.new(
|
|
26
|
+
# label: "Main",
|
|
27
|
+
# hex: "#FF0000",
|
|
28
|
+
# text_color: :white,
|
|
29
|
+
# frame_color: "#000000"
|
|
30
|
+
# )
|
|
31
|
+
Harmony = Data.define(:label, :hex, :text_color, :frame_color) do
|
|
32
|
+
# Renders a 4-line color swatch for display in a TUI Block.
|
|
33
|
+
#
|
|
34
|
+
# Produces a visual representation: a 7-character-wide box with the color
|
|
35
|
+
# centered and the hex code below.
|
|
36
|
+
#
|
|
37
|
+
# [tui] Session or TUI factory object
|
|
38
|
+
#
|
|
39
|
+
# === Example
|
|
40
|
+
#
|
|
41
|
+
# harmony = Harmony.new(...)
|
|
42
|
+
# lines = harmony.color_swatch_lines(tui)
|
|
43
|
+
# # => [TextLine, TextLine, TextLine, TextLine]
|
|
44
|
+
def color_swatch_lines(tui)
|
|
45
|
+
[
|
|
46
|
+
tui.text_line(spans: Array.new(7) { tui.text_span(content: " ", style: tui.style(bg: frame_color)) }),
|
|
47
|
+
tui.text_line(spans: [
|
|
48
|
+
*Array.new(3) { tui.text_span(content: " ", style: tui.style(bg: frame_color)) },
|
|
49
|
+
tui.text_span(content: " ", style: tui.style(bg: hex, fg: text_color)),
|
|
50
|
+
*Array.new(3) { tui.text_span(content: " ", style: tui.style(bg: frame_color)) },
|
|
51
|
+
]),
|
|
52
|
+
tui.text_line(spans: Array.new(7) { tui.text_span(content: " ", style: tui.style(bg: frame_color)) }),
|
|
53
|
+
tui.text_line(spans: [tui.text_span(content: hex, style: tui.style(fg: :white))]),
|
|
54
|
+
]
|
|
55
|
+
end
|
|
56
|
+
end
|