ratatui_ruby 0.3.1 → 0.5.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 +14 -12
- data/.builds/ruby-3.3.yml +14 -12
- data/.builds/ruby-3.4.yml +14 -12
- data/.builds/ruby-4.0.0.yml +14 -12
- data/AGENTS.md +89 -132
- data/CHANGELOG.md +223 -1
- data/README.md +23 -16
- data/REUSE.toml +20 -0
- data/doc/application_architecture.md +176 -0
- data/doc/application_testing.md +17 -10
- data/doc/contributors/design/ruby_frontend.md +11 -7
- data/doc/contributors/developing_examples.md +261 -0
- data/doc/contributors/documentation_style.md +104 -0
- data/doc/contributors/dwim_dx.md +366 -0
- data/doc/contributors/index.md +2 -0
- data/doc/custom.css +14 -0
- data/doc/event_handling.md +125 -0
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_analytics.png +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/app_custom_widget.png +0 -0
- data/doc/images/app_login_form.png +0 -0
- data/doc/images/app_map_demo.png +0 -0
- data/doc/images/app_mouse_events.png +0 -0
- data/doc/images/app_table_select.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_padding.png +0 -0
- data/doc/images/widget_block_titles.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_cell_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_list_styles.png +0 -0
- data/doc/images/widget_popup_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_rect.png +0 -0
- data/doc/images/widget_render.png +0 -0
- data/doc/images/widget_rich_text.png +0 -0
- data/doc/images/widget_scroll_text.png +0 -0
- data/doc/images/widget_scrollbar_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_flex.png +0 -0
- data/doc/images/widget_tabs_demo.png +0 -0
- data/doc/index.md +1 -0
- data/doc/interactive_design.md +116 -0
- data/doc/quickstart.md +186 -84
- data/examples/app_all_events/README.md +81 -0
- data/examples/app_all_events/app.rb +93 -0
- data/examples/app_all_events/model/event_color_cycle.rb +41 -0
- data/examples/app_all_events/model/event_entry.rb +75 -0
- data/examples/app_all_events/model/events.rb +180 -0
- data/examples/app_all_events/model/highlight.rb +57 -0
- data/examples/app_all_events/model/timestamp.rb +54 -0
- data/examples/app_all_events/test/snapshots/after_focus_lost.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_focus_regained.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_key_a.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_mouse_click.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_multiple_events.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_paste.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_resize.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_right_click.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +24 -0
- data/examples/app_all_events/test/snapshots/initial_state.txt +24 -0
- data/examples/app_all_events/view/app_view.rb +78 -0
- data/examples/app_all_events/view/controls_view.rb +50 -0
- data/examples/app_all_events/view/counts_view.rb +55 -0
- data/examples/app_all_events/view/live_view.rb +69 -0
- data/examples/app_all_events/view/log_view.rb +60 -0
- data/{lib/ratatui_ruby/output.rb → examples/app_all_events/view.rb} +1 -1
- data/examples/app_all_events/view_state.rb +42 -0
- data/examples/app_color_picker/README.md +94 -0
- data/examples/app_color_picker/app.rb +112 -0
- data/examples/app_color_picker/clipboard.rb +84 -0
- data/examples/app_color_picker/color.rb +191 -0
- data/examples/app_color_picker/copy_dialog.rb +170 -0
- data/examples/app_color_picker/harmony.rb +56 -0
- data/examples/app_color_picker/input.rb +142 -0
- data/examples/app_color_picker/palette.rb +80 -0
- data/examples/app_color_picker/scene.rb +201 -0
- data/examples/app_login_form/app.rb +108 -0
- data/examples/app_map_demo/app.rb +93 -0
- data/examples/app_table_select/app.rb +201 -0
- data/examples/verify_quickstart_dsl/app.rb +45 -0
- data/examples/verify_quickstart_layout/app.rb +69 -0
- data/examples/verify_quickstart_lifecycle/app.rb +48 -0
- data/examples/verify_readme_usage/app.rb +34 -0
- data/examples/widget_barchart_demo/app.rb +238 -0
- data/examples/widget_block_padding/app.rb +67 -0
- data/examples/widget_block_titles/app.rb +69 -0
- data/examples/widget_box_demo/app.rb +250 -0
- data/examples/widget_calendar_demo/app.rb +109 -0
- data/examples/widget_cell_demo/app.rb +104 -0
- data/examples/widget_chart_demo/app.rb +213 -0
- data/examples/widget_gauge_demo/app.rb +212 -0
- data/examples/widget_layout_split/app.rb +246 -0
- data/examples/widget_line_gauge_demo/app.rb +217 -0
- data/examples/widget_list_demo/app.rb +382 -0
- data/examples/widget_list_styles/app.rb +141 -0
- data/examples/widget_popup_demo/app.rb +104 -0
- data/examples/widget_ratatui_logo_demo/app.rb +103 -0
- data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
- data/examples/widget_rect/app.rb +205 -0
- data/examples/widget_render/app.rb +184 -0
- data/examples/widget_rich_text/app.rb +137 -0
- data/examples/widget_scroll_text/app.rb +108 -0
- data/examples/widget_scrollbar_demo/app.rb +153 -0
- data/examples/widget_sparkline_demo/app.rb +274 -0
- data/examples/widget_style_colors/app.rb +102 -0
- data/examples/widget_table_flex/app.rb +95 -0
- data/examples/widget_tabs_demo/app.rb +167 -0
- data/ext/ratatui_ruby/Cargo.lock +889 -115
- data/ext/ratatui_ruby/Cargo.toml +4 -3
- data/ext/ratatui_ruby/clippy.toml +7 -0
- data/ext/ratatui_ruby/extconf.rb +7 -0
- data/ext/ratatui_ruby/src/events.rs +293 -219
- data/ext/ratatui_ruby/src/frame.rs +115 -0
- data/ext/ratatui_ruby/src/lib.rs +105 -24
- data/ext/ratatui_ruby/src/rendering.rs +94 -10
- data/ext/ratatui_ruby/src/style.rs +357 -93
- data/ext/ratatui_ruby/src/terminal.rs +121 -31
- data/ext/ratatui_ruby/src/text.rs +178 -0
- data/ext/ratatui_ruby/src/widgets/barchart.rs +99 -24
- data/ext/ratatui_ruby/src/widgets/block.rs +32 -3
- data/ext/ratatui_ruby/src/widgets/calendar.rs +45 -44
- data/ext/ratatui_ruby/src/widgets/canvas.rs +44 -9
- data/ext/ratatui_ruby/src/widgets/chart.rs +79 -27
- data/ext/ratatui_ruby/src/widgets/clear.rs +3 -1
- data/ext/ratatui_ruby/src/widgets/gauge.rs +11 -4
- data/ext/ratatui_ruby/src/widgets/layout.rs +223 -15
- data/ext/ratatui_ruby/src/widgets/line_gauge.rs +92 -0
- data/ext/ratatui_ruby/src/widgets/list.rs +114 -11
- data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
- data/ext/ratatui_ruby/src/widgets/overlay.rs +4 -2
- data/ext/ratatui_ruby/src/widgets/paragraph.rs +35 -13
- data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
- data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +51 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +61 -7
- data/ext/ratatui_ruby/src/widgets/sparkline.rs +73 -6
- data/ext/ratatui_ruby/src/widgets/table.rs +177 -64
- data/ext/ratatui_ruby/src/widgets/tabs.rs +105 -5
- data/lib/ratatui_ruby/cell.rb +166 -0
- data/lib/ratatui_ruby/event/focus_gained.rb +49 -0
- data/lib/ratatui_ruby/event/focus_lost.rb +50 -0
- data/lib/ratatui_ruby/event/key.rb +211 -0
- data/lib/ratatui_ruby/event/mouse.rb +124 -0
- data/lib/ratatui_ruby/event/none.rb +43 -0
- data/lib/ratatui_ruby/event/paste.rb +71 -0
- data/lib/ratatui_ruby/event/resize.rb +80 -0
- data/lib/ratatui_ruby/event.rb +131 -0
- data/lib/ratatui_ruby/frame.rb +87 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +45 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +23 -0
- data/lib/ratatui_ruby/schema/bar_chart.rb +226 -17
- data/lib/ratatui_ruby/schema/block.rb +178 -11
- data/lib/ratatui_ruby/schema/calendar.rb +70 -14
- data/lib/ratatui_ruby/schema/canvas.rb +213 -46
- data/lib/ratatui_ruby/schema/center.rb +46 -8
- data/lib/ratatui_ruby/schema/chart.rb +134 -32
- data/lib/ratatui_ruby/schema/clear.rb +22 -53
- data/lib/ratatui_ruby/schema/constraint.rb +72 -12
- data/lib/ratatui_ruby/schema/cursor.rb +23 -5
- data/lib/ratatui_ruby/schema/draw.rb +53 -0
- data/lib/ratatui_ruby/schema/gauge.rb +56 -12
- data/lib/ratatui_ruby/schema/layout.rb +91 -9
- data/lib/ratatui_ruby/schema/line_gauge.rb +78 -0
- data/lib/ratatui_ruby/schema/list.rb +92 -16
- data/lib/ratatui_ruby/schema/overlay.rb +29 -3
- data/lib/ratatui_ruby/schema/paragraph.rb +82 -25
- data/lib/ratatui_ruby/schema/ratatui_logo.rb +29 -0
- data/lib/ratatui_ruby/schema/ratatui_mascot.rb +34 -0
- data/lib/ratatui_ruby/schema/rect.rb +59 -10
- data/lib/ratatui_ruby/schema/scrollbar.rb +127 -19
- data/lib/ratatui_ruby/schema/shape/label.rb +66 -0
- data/lib/ratatui_ruby/schema/sparkline.rb +120 -12
- data/lib/ratatui_ruby/schema/style.rb +39 -11
- data/lib/ratatui_ruby/schema/table.rb +109 -18
- data/lib/ratatui_ruby/schema/tabs.rb +71 -10
- data/lib/ratatui_ruby/schema/text.rb +90 -0
- data/lib/ratatui_ruby/session/autodoc.rb +417 -0
- data/lib/ratatui_ruby/session.rb +163 -0
- data/lib/ratatui_ruby/test_helper.rb +322 -13
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby.rb +184 -38
- data/sig/examples/app_all_events/app.rbs +11 -0
- 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/sig/examples/app_color_picker/app.rbs +12 -0
- data/sig/examples/app_login_form/app.rbs +11 -0
- data/sig/examples/app_map_demo/app.rbs +11 -0
- data/sig/examples/app_table_select/app.rbs +11 -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_padding/app.rbs +11 -0
- data/sig/examples/widget_block_titles/app.rbs +11 -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/sig/examples/widget_gauge_demo/app.rbs +11 -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_list_styles/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/sig/examples/widget_style_colors/app.rbs +14 -0
- data/sig/examples/widget_table_flex/app.rbs +11 -0
- data/sig/ratatui_ruby/event.rbs +69 -0
- data/sig/ratatui_ruby/frame.rbs +9 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +5 -3
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +16 -0
- data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +13 -0
- data/sig/ratatui_ruby/schema/bar_chart.rbs +20 -2
- data/sig/ratatui_ruby/schema/block.rbs +5 -4
- data/sig/ratatui_ruby/schema/calendar.rbs +6 -2
- data/sig/ratatui_ruby/schema/canvas.rbs +52 -39
- data/sig/ratatui_ruby/schema/center.rbs +3 -3
- data/sig/ratatui_ruby/schema/chart.rbs +8 -5
- data/sig/ratatui_ruby/schema/constraint.rbs +8 -5
- data/sig/ratatui_ruby/schema/cursor.rbs +1 -1
- data/sig/ratatui_ruby/schema/draw.rbs +27 -0
- data/sig/ratatui_ruby/schema/gauge.rbs +4 -2
- data/sig/ratatui_ruby/schema/layout.rbs +11 -1
- data/sig/ratatui_ruby/schema/line_gauge.rbs +16 -0
- data/sig/ratatui_ruby/schema/list.rbs +5 -1
- data/sig/ratatui_ruby/schema/paragraph.rbs +4 -1
- data/sig/ratatui_ruby/schema/ratatui_logo.rbs +8 -0
- data/sig/ratatui_ruby/{buffer.rbs → schema/ratatui_mascot.rbs} +4 -3
- data/sig/ratatui_ruby/schema/rect.rbs +2 -1
- data/sig/ratatui_ruby/schema/scrollbar.rbs +18 -2
- data/sig/ratatui_ruby/schema/sparkline.rbs +6 -2
- data/sig/ratatui_ruby/schema/table.rbs +8 -1
- data/sig/ratatui_ruby/schema/tabs.rbs +5 -1
- data/sig/ratatui_ruby/schema/text.rbs +22 -0
- data/sig/ratatui_ruby/session.rbs +94 -0
- data/tasks/autodoc/inventory.rb +61 -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 +47 -0
- data/tasks/bump/history.rb +2 -2
- data/tasks/doc.rake +600 -6
- data/tasks/example_viewer.html.erb +172 -0
- data/tasks/lint.rake +8 -4
- data/tasks/resources/build.yml.erb +13 -11
- data/tasks/resources/index.html.erb +6 -0
- data/tasks/sourcehut.rake +4 -4
- data/tasks/terminal_preview/app_screenshot.rb +33 -0
- data/tasks/terminal_preview/crash_report.rb +52 -0
- data/tasks/terminal_preview/example_app.rb +25 -0
- data/tasks/terminal_preview/launcher_script.rb +46 -0
- data/tasks/terminal_preview/preview_collection.rb +58 -0
- data/tasks/terminal_preview/preview_timing.rb +22 -0
- data/tasks/terminal_preview/safety_confirmation.rb +56 -0
- data/tasks/terminal_preview/saved_screenshot.rb +53 -0
- data/tasks/terminal_preview/system_appearance.rb +11 -0
- data/tasks/terminal_preview/terminal_window.rb +136 -0
- data/tasks/terminal_preview/window_id.rb +14 -0
- data/tasks/terminal_preview.rake +28 -0
- data/tasks/test.rake +2 -2
- 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 +206 -54
- data/LICENSES/BSD-2-Clause.txt +0 -9
- data/doc/images/examples-analytics.rb.png +0 -0
- data/doc/images/examples-box_demo.rb.png +0 -0
- data/doc/images/examples-calendar_demo.rb.png +0 -0
- data/doc/images/examples-chart_demo.rb.png +0 -0
- data/doc/images/examples-custom_widget.rb.png +0 -0
- data/doc/images/examples-dashboard.rb.png +0 -0
- data/doc/images/examples-list_styles.rb.png +0 -0
- data/doc/images/examples-login_form.rb.png +0 -0
- data/doc/images/examples-map_demo.rb.png +0 -0
- data/doc/images/examples-mouse_events.rb.png +0 -0
- data/doc/images/examples-popup_demo.rb.gif +0 -0
- data/doc/images/examples-quickstart_lifecycle.rb.png +0 -0
- data/doc/images/examples-scroll_text.rb.png +0 -0
- data/doc/images/examples-scrollbar_demo.rb.png +0 -0
- data/doc/images/examples-stock_ticker.rb.png +0 -0
- data/doc/images/examples-system_monitor.rb.png +0 -0
- data/doc/images/examples-table_select.rb.png +0 -0
- data/examples/analytics.rb +0 -88
- data/examples/box_demo.rb +0 -71
- data/examples/calendar_demo.rb +0 -55
- data/examples/chart_demo.rb +0 -84
- data/examples/custom_widget.rb +0 -43
- data/examples/dashboard.rb +0 -72
- data/examples/list_styles.rb +0 -66
- data/examples/login_form.rb +0 -115
- data/examples/map_demo.rb +0 -58
- data/examples/mouse_events.rb +0 -95
- data/examples/popup_demo.rb +0 -105
- data/examples/quickstart_dsl.rb +0 -30
- data/examples/quickstart_lifecycle.rb +0 -40
- data/examples/readme_usage.rb +0 -21
- data/examples/scroll_text.rb +0 -74
- data/examples/scrollbar_demo.rb +0 -75
- data/examples/stock_ticker.rb +0 -93
- data/examples/system_monitor.rb +0 -94
- data/examples/table_select.rb +0 -70
- data/examples/test_analytics.rb +0 -65
- data/examples/test_box_demo.rb +0 -38
- data/examples/test_calendar_demo.rb +0 -66
- data/examples/test_dashboard.rb +0 -38
- data/examples/test_list_styles.rb +0 -61
- data/examples/test_login_form.rb +0 -63
- data/examples/test_map_demo.rb +0 -100
- data/examples/test_popup_demo.rb +0 -62
- data/examples/test_scroll_text.rb +0 -130
- data/examples/test_stock_ticker.rb +0 -39
- data/examples/test_system_monitor.rb +0 -40
- data/examples/test_table_select.rb +0 -37
- data/ext/ratatui_ruby/src/buffer.rs +0 -54
- data/lib/ratatui_ruby/dsl.rb +0 -64
|
@@ -0,0 +1,142 @@
|
|
|
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 "color"
|
|
7
|
+
|
|
8
|
+
# Manages text input and color parsing with error feedback.
|
|
9
|
+
#
|
|
10
|
+
# Users type color values. They make mistakes—typos, invalid formats. The app
|
|
11
|
+
# needs to validate their input and show helpful error messages. Manually
|
|
12
|
+
# tracking input state, validation, and error messages across renders is
|
|
13
|
+
# cumbersome and error-prone.
|
|
14
|
+
#
|
|
15
|
+
# This object holds the current input string. It validates by parsing. It stores
|
|
16
|
+
# errors and clears them when appropriate. It provides methods to manipulate
|
|
17
|
+
# the input (append, delete).
|
|
18
|
+
#
|
|
19
|
+
# Use it to build text input forms where validation feedback matters.
|
|
20
|
+
#
|
|
21
|
+
# === Example
|
|
22
|
+
#
|
|
23
|
+
# input = Input.new
|
|
24
|
+
# input.append_char("#")
|
|
25
|
+
# input.append_char("f")
|
|
26
|
+
# input.append_char("f")
|
|
27
|
+
# color = input.parse # => Color or nil
|
|
28
|
+
# puts input.error # => error message if parse failed
|
|
29
|
+
class Input
|
|
30
|
+
PRINTABLE_PATTERN = /[\w#,().\s%]/
|
|
31
|
+
|
|
32
|
+
# Creates a new Input with an optional initial value.
|
|
33
|
+
#
|
|
34
|
+
# [initial_value] String initial color input (default: <tt>"#F96302"</tt>)
|
|
35
|
+
def initialize(initial_value = "#F96302")
|
|
36
|
+
@value = initial_value
|
|
37
|
+
@error = ""
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Current input string.
|
|
41
|
+
#
|
|
42
|
+
# === Example
|
|
43
|
+
#
|
|
44
|
+
# input = Input.new
|
|
45
|
+
# input.value # => "#F96302"
|
|
46
|
+
def value
|
|
47
|
+
@value
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Error message from the last failed parse, or empty string.
|
|
51
|
+
#
|
|
52
|
+
# === Example
|
|
53
|
+
#
|
|
54
|
+
# input.parse # => nil (invalid)
|
|
55
|
+
# input.error # => "Invalid color format. Try: #ff0000, rgb(255,0,0), red"
|
|
56
|
+
def error
|
|
57
|
+
@error
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Clears the current error message.
|
|
61
|
+
def clear_error
|
|
62
|
+
@error = ""
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Appends a character to the input if it matches the printable pattern.
|
|
66
|
+
#
|
|
67
|
+
# Silently ignores non-printable characters. Valid characters include
|
|
68
|
+
# letters, digits, hash, comma, parentheses, dot, space, and percent.
|
|
69
|
+
#
|
|
70
|
+
# [char] String single character
|
|
71
|
+
def append_char(char)
|
|
72
|
+
@value += char if char.length == 1 && char.match?(PRINTABLE_PATTERN)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Removes the last character from the input.
|
|
76
|
+
def delete_char
|
|
77
|
+
@value = @value[0...-1]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Replaces the entire input string.
|
|
81
|
+
#
|
|
82
|
+
# [text] String new input value
|
|
83
|
+
def set(text)
|
|
84
|
+
@value = text
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Parses the current input as a Color.
|
|
88
|
+
#
|
|
89
|
+
# Returns a Color if valid; nil otherwise. Sets the error message on failure.
|
|
90
|
+
# Clears the error message on success.
|
|
91
|
+
#
|
|
92
|
+
# === Example
|
|
93
|
+
#
|
|
94
|
+
# input = Input.new("#FF0000")
|
|
95
|
+
# color = input.parse # => Color
|
|
96
|
+
# input.error # => ""
|
|
97
|
+
def parse
|
|
98
|
+
color = Color.parse(@value)
|
|
99
|
+
if color
|
|
100
|
+
clear_error
|
|
101
|
+
color
|
|
102
|
+
else
|
|
103
|
+
@error = "Invalid color format. Try: #ff0000, rgb(255,0,0), red"
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Renders the input widget for display in a TUI frame.
|
|
109
|
+
#
|
|
110
|
+
# Shows the current input value with a cursor. Displays the error message
|
|
111
|
+
# in red if one is set.
|
|
112
|
+
#
|
|
113
|
+
# [tui] Session or TUI factory object
|
|
114
|
+
#
|
|
115
|
+
# === Example
|
|
116
|
+
#
|
|
117
|
+
# input = Input.new
|
|
118
|
+
# widget = input.render(tui)
|
|
119
|
+
# frame.render_widget(widget, area)
|
|
120
|
+
def render(tui)
|
|
121
|
+
input_lines = [
|
|
122
|
+
tui.text_line(spans: [
|
|
123
|
+
tui.text_span(content: @value),
|
|
124
|
+
tui.text_span(content: "_", style: tui.style(modifiers: [:reversed])),
|
|
125
|
+
]),
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
unless @error.empty?
|
|
129
|
+
input_lines << tui.text_line(spans: [
|
|
130
|
+
tui.text_span(content: @error, style: tui.style(fg: :red)),
|
|
131
|
+
])
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
tui.block(
|
|
135
|
+
title: "Color Input",
|
|
136
|
+
borders: [:all],
|
|
137
|
+
children: [
|
|
138
|
+
tui.paragraph(text: input_lines),
|
|
139
|
+
]
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
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 "color"
|
|
7
|
+
|
|
8
|
+
# Holds a primary color and its harmonies.
|
|
9
|
+
#
|
|
10
|
+
# Color pickers need to show related colors: shades, tints, complements. Building
|
|
11
|
+
# these relationships repeatedly is redundant. Passing them individually through
|
|
12
|
+
# rendering pipelines is awkward.
|
|
13
|
+
#
|
|
14
|
+
# This object owns a primary color and generates its harmonies on demand. It
|
|
15
|
+
# provides accessor methods and rendering helpers.
|
|
16
|
+
#
|
|
17
|
+
# Use it to organize color data for palette displays.
|
|
18
|
+
#
|
|
19
|
+
# === Example
|
|
20
|
+
#
|
|
21
|
+
# color = Color.parse("#FF0000")
|
|
22
|
+
# palette = Palette.new(color)
|
|
23
|
+
# palette.main # => Color
|
|
24
|
+
# palette.all # => [Harmony, Harmony, ...]
|
|
25
|
+
# blocks = palette.as_blocks(tui) # => [Block, Block, ...]
|
|
26
|
+
class Palette
|
|
27
|
+
def initialize(primary_color)
|
|
28
|
+
@primary = primary_color
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The primary (main) color, or nil if no color is set.
|
|
32
|
+
#
|
|
33
|
+
# === Example
|
|
34
|
+
#
|
|
35
|
+
# palette = Palette.new(color)
|
|
36
|
+
# palette.main.hex # => "#FF0000"
|
|
37
|
+
def main
|
|
38
|
+
@primary
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# All harmonies: main, shade, tint, complement, split 1, split 2, split-complement.
|
|
42
|
+
#
|
|
43
|
+
# Returns an empty array if no primary color is set.
|
|
44
|
+
#
|
|
45
|
+
# === Example
|
|
46
|
+
#
|
|
47
|
+
# palette = Palette.new(color)
|
|
48
|
+
# palette.all.size # => 7
|
|
49
|
+
def all
|
|
50
|
+
return [] if @primary.nil?
|
|
51
|
+
|
|
52
|
+
@primary.harmonies
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Renders all harmonies as TUI Block widgets.
|
|
56
|
+
#
|
|
57
|
+
# Each harmony becomes a titled block showing its color swatch. Returns an empty
|
|
58
|
+
# array if no primary color is set.
|
|
59
|
+
#
|
|
60
|
+
# [tui] Session or TUI factory object
|
|
61
|
+
#
|
|
62
|
+
# === Example
|
|
63
|
+
#
|
|
64
|
+
# palette = Palette.new(color)
|
|
65
|
+
# blocks = palette.as_blocks(tui)
|
|
66
|
+
# # blocks[0] => Block titled "Main" with color swatch
|
|
67
|
+
def as_blocks(tui)
|
|
68
|
+
return [] if @primary.nil?
|
|
69
|
+
|
|
70
|
+
all.map do |harmony|
|
|
71
|
+
tui.block(
|
|
72
|
+
title: harmony.label,
|
|
73
|
+
borders: [:all],
|
|
74
|
+
children: [
|
|
75
|
+
tui.paragraph(text: harmony.color_swatch_lines(tui)),
|
|
76
|
+
]
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,201 @@
|
|
|
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 "input"
|
|
7
|
+
require_relative "palette"
|
|
8
|
+
require_relative "clipboard"
|
|
9
|
+
require_relative "copy_dialog"
|
|
10
|
+
|
|
11
|
+
# Orchestrates layout and rendering of the color picker UI.
|
|
12
|
+
#
|
|
13
|
+
# Building a complete color picker UI involves layout calculation, widget
|
|
14
|
+
# composition, and coordinate tracking for hit testing. Keeping this logic
|
|
15
|
+
# scattered across the main app makes the app harder to read and test.
|
|
16
|
+
#
|
|
17
|
+
# This object owns the layout logic. It orchestrates all sections. It calculates
|
|
18
|
+
# and caches rects for hit testing.
|
|
19
|
+
#
|
|
20
|
+
# Use it to encapsulate complex UI composition.
|
|
21
|
+
#
|
|
22
|
+
# === Example
|
|
23
|
+
#
|
|
24
|
+
# scene = Scene.new(tui)
|
|
25
|
+
# scene.render(frame, input:, palette:, clipboard:, dialog:)
|
|
26
|
+
#
|
|
27
|
+
# # For hit testing:
|
|
28
|
+
# rect = scene.export_rect
|
|
29
|
+
# if rect.contains?(x, y)
|
|
30
|
+
# # Handle click
|
|
31
|
+
# end
|
|
32
|
+
class Scene
|
|
33
|
+
def initialize(tui)
|
|
34
|
+
@tui = tui
|
|
35
|
+
@export_area_rect = nil
|
|
36
|
+
@hotkey_style = @tui.style(modifiers: [:bold, :underlined])
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Renders the complete UI given all model objects.
|
|
40
|
+
#
|
|
41
|
+
# Calculates layout once per frame. Renders input, palette, export, controls,
|
|
42
|
+
# and dialog sections. Caches the export area rect for hit testing.
|
|
43
|
+
#
|
|
44
|
+
# [frame] Frame object from RatatuiRuby.draw block
|
|
45
|
+
# [input] Input object for text input display
|
|
46
|
+
# [palette] Palette object for color display
|
|
47
|
+
# [clipboard] Clipboard object for feedback message
|
|
48
|
+
# [dialog] CopyDialog object for confirmation dialog
|
|
49
|
+
#
|
|
50
|
+
# === Example
|
|
51
|
+
#
|
|
52
|
+
# scene.render(frame, input: @input, palette: @palette, clipboard: @clipboard, dialog: @dialog)
|
|
53
|
+
def render(frame, input:, palette:, clipboard:, dialog:)
|
|
54
|
+
input_area, rest = @tui.layout_split(
|
|
55
|
+
frame.area,
|
|
56
|
+
direction: :vertical,
|
|
57
|
+
constraints: [
|
|
58
|
+
@tui.constraint_length(3),
|
|
59
|
+
@tui.constraint_fill(1),
|
|
60
|
+
]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
color_area, control_area = @tui.layout_split(
|
|
64
|
+
rest,
|
|
65
|
+
direction: :vertical,
|
|
66
|
+
constraints: [
|
|
67
|
+
@tui.constraint_length(14),
|
|
68
|
+
@tui.constraint_fill(1),
|
|
69
|
+
]
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
harmony_area, @export_area_rect = @tui.layout_split(
|
|
73
|
+
color_area,
|
|
74
|
+
direction: :vertical,
|
|
75
|
+
constraints: [
|
|
76
|
+
@tui.constraint_length(7),
|
|
77
|
+
@tui.constraint_fill(1),
|
|
78
|
+
]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
frame.render_widget(input.render(@tui), input_area)
|
|
82
|
+
frame.render_widget(build_palette_section(palette, harmony_area), harmony_area)
|
|
83
|
+
frame.render_widget(build_export_section(palette), @export_area_rect)
|
|
84
|
+
frame.render_widget(build_controls_section(clipboard), control_area)
|
|
85
|
+
|
|
86
|
+
if dialog.active?
|
|
87
|
+
dialog_center = calculate_center_area(frame.area, 40, 8)
|
|
88
|
+
frame.render_widget(@tui.clear, frame.area)
|
|
89
|
+
frame.render_widget(dialog.render(@tui, dialog_center), dialog_center)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# The cached rectangle of the export formats section, used for hit testing.
|
|
94
|
+
#
|
|
95
|
+
# Populated during #render. Use this to detect clicks on the export section.
|
|
96
|
+
#
|
|
97
|
+
# === Example
|
|
98
|
+
#
|
|
99
|
+
# scene.render(frame, ...)
|
|
100
|
+
# if scene.export_rect.contains?(x, y)
|
|
101
|
+
# # Click on export section
|
|
102
|
+
# end
|
|
103
|
+
def export_rect
|
|
104
|
+
@export_area_rect
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private def build_palette_section(palette, _harmony_area)
|
|
108
|
+
if palette.main.nil?
|
|
109
|
+
@tui.paragraph(text: "No color selected")
|
|
110
|
+
else
|
|
111
|
+
blocks = palette.as_blocks(@tui)
|
|
112
|
+
@tui.layout(
|
|
113
|
+
direction: :horizontal,
|
|
114
|
+
constraints: Array.new(blocks.size) { @tui.constraint_fill(1) },
|
|
115
|
+
children: blocks
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private def build_export_section(palette)
|
|
121
|
+
if palette.main.nil?
|
|
122
|
+
@tui.block(
|
|
123
|
+
title: "Export Formats",
|
|
124
|
+
borders: [:all],
|
|
125
|
+
children: [
|
|
126
|
+
@tui.paragraph(
|
|
127
|
+
text: @tui.text_line(spans: [
|
|
128
|
+
@tui.text_span(content: "Enter a color to see formats"),
|
|
129
|
+
])
|
|
130
|
+
),
|
|
131
|
+
]
|
|
132
|
+
)
|
|
133
|
+
else
|
|
134
|
+
color = palette.main
|
|
135
|
+
hex = color.hex
|
|
136
|
+
rgb = color.rgb
|
|
137
|
+
hsl = color.hsl_string
|
|
138
|
+
text_color = color.contrasting_text_color
|
|
139
|
+
bg_style = @tui.style(bg: hex, fg: text_color)
|
|
140
|
+
|
|
141
|
+
@tui.block(
|
|
142
|
+
title: "Export Formats",
|
|
143
|
+
borders: [:all],
|
|
144
|
+
style: bg_style,
|
|
145
|
+
children: [
|
|
146
|
+
@tui.paragraph(
|
|
147
|
+
text: [
|
|
148
|
+
@tui.text_line(spans: [
|
|
149
|
+
@tui.text_span(content: "HEX: ", style: bg_style),
|
|
150
|
+
@tui.text_span(content: hex, style: @tui.style(bg: hex, fg: text_color, modifiers: [:underlined])),
|
|
151
|
+
]),
|
|
152
|
+
@tui.text_line(spans: [
|
|
153
|
+
@tui.text_span(content: "RGB: ", style: bg_style),
|
|
154
|
+
@tui.text_span(content: rgb, style: @tui.style(bg: hex, fg: text_color, modifiers: [:underlined])),
|
|
155
|
+
]),
|
|
156
|
+
@tui.text_line(spans: [
|
|
157
|
+
@tui.text_span(content: "HSL: ", style: bg_style),
|
|
158
|
+
@tui.text_span(content: hsl, style: @tui.style(bg: hex, fg: text_color, modifiers: [:underlined])),
|
|
159
|
+
]),
|
|
160
|
+
]
|
|
161
|
+
),
|
|
162
|
+
]
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private def build_controls_section(clipboard)
|
|
168
|
+
control_lines = [
|
|
169
|
+
@tui.text_line(spans: [
|
|
170
|
+
@tui.text_span(content: "a-z/0-9", style: @hotkey_style),
|
|
171
|
+
@tui.text_span(content: ": Type "),
|
|
172
|
+
@tui.text_span(content: "enter", style: @hotkey_style),
|
|
173
|
+
@tui.text_span(content: ": Parse "),
|
|
174
|
+
@tui.text_span(content: "bksp", style: @hotkey_style),
|
|
175
|
+
@tui.text_span(content: ": Erase "),
|
|
176
|
+
@tui.text_span(content: "esc", style: @hotkey_style),
|
|
177
|
+
@tui.text_span(content: ": Quit"),
|
|
178
|
+
]),
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
unless clipboard.message.empty?
|
|
182
|
+
control_lines << @tui.text_line(spans: [
|
|
183
|
+
@tui.text_span(content: clipboard.message, style: @tui.style(fg: :green, modifiers: [:bold])),
|
|
184
|
+
])
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
@tui.block(
|
|
188
|
+
title: "Controls",
|
|
189
|
+
borders: [:all],
|
|
190
|
+
children: [
|
|
191
|
+
@tui.paragraph(text: control_lines),
|
|
192
|
+
]
|
|
193
|
+
)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
private def calculate_center_area(parent_area, width, height)
|
|
197
|
+
x = (parent_area.width - width) / 2
|
|
198
|
+
y = (parent_area.height - height) / 2
|
|
199
|
+
@tui.rect(x:, y:, width:, height:)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
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
|
+
require "ratatui_ruby"
|
|
8
|
+
|
|
9
|
+
class AppLoginForm
|
|
10
|
+
PREFIX = "Enter Username: [ "
|
|
11
|
+
SUFFIX = " ]"
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@username = ""
|
|
15
|
+
@show_popup = false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def run
|
|
19
|
+
RatatuiRuby.run do |tui|
|
|
20
|
+
@tui = tui
|
|
21
|
+
loop do
|
|
22
|
+
render
|
|
23
|
+
break if handle_input == :quit
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private def render
|
|
29
|
+
# 1. Base Layer Construction
|
|
30
|
+
# We want a cursor relative to the paragraph.
|
|
31
|
+
# So we wrap Paragraph and Cursor in an Overlay, and put that Overlay in a Center.
|
|
32
|
+
|
|
33
|
+
# Calculate cursor position
|
|
34
|
+
# Border takes 1 cell.
|
|
35
|
+
# Cursor X = 1 (border) + PREFIX.length + username.length
|
|
36
|
+
# Cursor Y = 1 (border + line 0)
|
|
37
|
+
cursor_x = 1 + PREFIX.length + @username.length
|
|
38
|
+
cursor_y = 1
|
|
39
|
+
|
|
40
|
+
# The content of the base form
|
|
41
|
+
form_content = @tui.overlay(layers: [
|
|
42
|
+
@tui.paragraph(
|
|
43
|
+
text: "#{PREFIX}#{@username}#{SUFFIX}",
|
|
44
|
+
block: @tui.block(borders: :all, title: "Login Form"),
|
|
45
|
+
alignment: :left
|
|
46
|
+
),
|
|
47
|
+
@tui.cursor(x: cursor_x, y: cursor_y),
|
|
48
|
+
])
|
|
49
|
+
|
|
50
|
+
# Center the form on screen
|
|
51
|
+
base_layer = @tui.center(
|
|
52
|
+
child: form_content,
|
|
53
|
+
width_percent: 50,
|
|
54
|
+
height_percent: 20
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# 2. Popup Layer Construction
|
|
58
|
+
final_view = if @show_popup
|
|
59
|
+
popup_message = @tui.center(
|
|
60
|
+
child: @tui.paragraph(
|
|
61
|
+
text: "Login Successful!\nPress 'q' to quit.",
|
|
62
|
+
style: @tui.style(fg: :green, bg: :black),
|
|
63
|
+
block: @tui.block(borders: :all),
|
|
64
|
+
alignment: :center,
|
|
65
|
+
wrap: true
|
|
66
|
+
),
|
|
67
|
+
width_percent: 30,
|
|
68
|
+
height_percent: 20
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Render Base Layer (background) THEN Popup Layer
|
|
72
|
+
@tui.overlay(layers: [base_layer, popup_message])
|
|
73
|
+
else
|
|
74
|
+
base_layer
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# 3. Draw
|
|
78
|
+
@tui.draw do |frame|
|
|
79
|
+
frame.render_widget(final_view, frame.area)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private def handle_input
|
|
84
|
+
case @tui.poll_event
|
|
85
|
+
in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
|
|
86
|
+
return :quit if @show_popup
|
|
87
|
+
nil
|
|
88
|
+
in { type: :key, code: "c", modifiers: ["ctrl"] }
|
|
89
|
+
:quit
|
|
90
|
+
in { type: :key, code: "enter" }
|
|
91
|
+
@show_popup ||= true
|
|
92
|
+
nil
|
|
93
|
+
in { type: :key, code: "backspace" }
|
|
94
|
+
@username.chop! unless @show_popup
|
|
95
|
+
nil
|
|
96
|
+
in { type: :key, code: "esc" }
|
|
97
|
+
:quit unless @show_popup
|
|
98
|
+
in { type: :key, code:, modifiers: [] }
|
|
99
|
+
# Simple text input (single character, no modifiers)
|
|
100
|
+
@username += code if !@show_popup && code.length == 1
|
|
101
|
+
nil
|
|
102
|
+
else
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
AppLoginForm.new.run if __FILE__ == $0
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
require "ratatui_ruby"
|
|
8
|
+
|
|
9
|
+
# An example of the Canvas widget showing a world map and animated shapes.
|
|
10
|
+
class AppMapDemo
|
|
11
|
+
include RatatuiRuby
|
|
12
|
+
|
|
13
|
+
COLORS = [:black, :blue, :white, nil].freeze
|
|
14
|
+
MARKERS = [:braille, :half_block, :dot, :block, :bar, :quadrant, :sextant, :octant].freeze
|
|
15
|
+
|
|
16
|
+
# Returns a Canvas view for the map demo with the given circle radius.
|
|
17
|
+
#
|
|
18
|
+
# +tui+:: The RatatuiRuby::Session instance.
|
|
19
|
+
# +radius+:: The radius of the animated circle.
|
|
20
|
+
# +marker+:: The marker type.
|
|
21
|
+
# +background_color+:: The background color of the canvas.
|
|
22
|
+
# +show_labels+:: Whether to show city labels.
|
|
23
|
+
def view(tui, radius, marker = :braille, background_color = nil, show_labels: true)
|
|
24
|
+
shapes = [
|
|
25
|
+
tui.shape_map(color: :green, resolution: :high),
|
|
26
|
+
tui.shape_circle(x: 0.0, y: 0.0, radius:, color: :red),
|
|
27
|
+
tui.shape_line(x1: 0.0, y1: 0.0, x2: 50.0, y2: 25.0, color: :yellow),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
if show_labels
|
|
31
|
+
shapes += [
|
|
32
|
+
tui.shape_label(x: -0.1, y: 51.5, text: "London", style: tui.style(fg: :cyan)),
|
|
33
|
+
tui.shape_label(x: 139.7, y: 35.7, text: "Tokyo", style: tui.style(fg: :magenta)),
|
|
34
|
+
tui.shape_label(x: -74.0, y: 40.7, text: "New York", style: tui.style(fg: :yellow)),
|
|
35
|
+
tui.shape_label(x: -122.4, y: 37.8, text: "San Francisco", style: tui.style(fg: :blue)),
|
|
36
|
+
tui.shape_label(x: 151.2, y: -33.9, text: "Sydney", style: tui.style(fg: :green)),
|
|
37
|
+
]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
tui.canvas(
|
|
41
|
+
shapes:,
|
|
42
|
+
x_bounds: [-180.0, 180.0],
|
|
43
|
+
y_bounds: [-90.0, 90.0],
|
|
44
|
+
marker:,
|
|
45
|
+
block: tui.block(title: "World Map ['b' bg, 'm' marker: #{marker}, 'l' labels: #{show_labels ? 'on' : 'off'}]", borders: :all),
|
|
46
|
+
background_color:
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Runs the map demo loop.
|
|
51
|
+
def run
|
|
52
|
+
RatatuiRuby.run do |tui|
|
|
53
|
+
radius = 0.0
|
|
54
|
+
direction = 1
|
|
55
|
+
bg_index = 0
|
|
56
|
+
marker_index = 0
|
|
57
|
+
show_labels = true
|
|
58
|
+
|
|
59
|
+
loop do
|
|
60
|
+
# Animate the circle radius
|
|
61
|
+
radius += 0.5 * direction
|
|
62
|
+
if radius > 10.0 || radius < 0.0
|
|
63
|
+
direction *= -1
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Define the view
|
|
67
|
+
canvas = view(tui, radius, MARKERS[marker_index], COLORS[bg_index], show_labels:)
|
|
68
|
+
|
|
69
|
+
tui.draw do |frame|
|
|
70
|
+
frame.render_widget(canvas, frame.area)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
event = tui.poll_event
|
|
74
|
+
case event
|
|
75
|
+
in { type: :key, code: "q" } | { type: :key, code: :ctrl_c }
|
|
76
|
+
break
|
|
77
|
+
in type: :key, code: "b"
|
|
78
|
+
bg_index = (bg_index + 1) % COLORS.size
|
|
79
|
+
in type: :key, code: "m"
|
|
80
|
+
marker_index = (marker_index + 1) % MARKERS.size
|
|
81
|
+
in type: :key, code: "l"
|
|
82
|
+
show_labels = !show_labels
|
|
83
|
+
else
|
|
84
|
+
# Ignore other events
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
sleep 0.05
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
AppMapDemo.new.run if __FILE__ == $PROGRAM_NAME
|