ratatui_ruby 1.0.0 → 1.0.1
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/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/lib/ratatui_ruby/version.rb +1 -1
- metadata +1 -232
- data/.builds/ruby-3.2.yml +0 -54
- data/.builds/ruby-3.3.yml +0 -54
- data/.builds/ruby-3.4.yml +0 -54
- data/.builds/ruby-4.0.0.yml +0 -54
- data/.pre-commit-config.yaml +0 -16
- data/.rubocop.yml +0 -10
- data/AGENTS.md +0 -146
- data/CHANGELOG.md +0 -710
- data/README.md +0 -187
- data/README.rdoc +0 -302
- data/Rakefile +0 -11
- data/Steepfile +0 -49
- data/doc/concepts/application_architecture.md +0 -321
- data/doc/concepts/application_testing.md +0 -193
- data/doc/concepts/async.md +0 -190
- data/doc/concepts/custom_widgets.md +0 -247
- data/doc/concepts/debugging.md +0 -401
- data/doc/concepts/event_handling.md +0 -162
- data/doc/concepts/interactive_design.md +0 -146
- data/doc/contributors/auditing/parity.md +0 -239
- data/doc/contributors/design/ruby_frontend.md +0 -420
- data/doc/contributors/design/rust_backend.md +0 -422
- data/doc/contributors/design.md +0 -11
- data/doc/contributors/developing_examples.md +0 -400
- data/doc/contributors/documentation_style.md +0 -121
- data/doc/contributors/index.md +0 -21
- data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -375
- data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -206
- data/doc/contributors/todo/align/terminal.md +0 -647
- data/doc/contributors/todo/future_work.md +0 -169
- data/doc/contributors/upstream_requests/tab_rects.md +0 -173
- data/doc/contributors/upstream_requests/title_rects.md +0 -132
- data/doc/custom.css +0 -22
- data/doc/getting_started/quickstart.md +0 -291
- data/doc/getting_started/why.md +0 -93
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_cli_rich_moments.gif +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/app_debugging_showcase.gif +0 -0
- data/doc/images/app_debugging_showcase.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.png +0 -0
- data/doc/images/widget_block.png +0 -0
- data/doc/images/widget_box.png +0 -0
- data/doc/images/widget_calendar.png +0 -0
- data/doc/images/widget_canvas.png +0 -0
- data/doc/images/widget_cell.png +0 -0
- data/doc/images/widget_center.png +0 -0
- data/doc/images/widget_chart.png +0 -0
- data/doc/images/widget_gauge.png +0 -0
- data/doc/images/widget_layout_split.png +0 -0
- data/doc/images/widget_line_gauge.png +0 -0
- data/doc/images/widget_list.png +0 -0
- data/doc/images/widget_map.png +0 -0
- data/doc/images/widget_overlay.png +0 -0
- data/doc/images/widget_popup.png +0 -0
- data/doc/images/widget_ratatui_logo.png +0 -0
- data/doc/images/widget_ratatui_mascot.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.png +0 -0
- data/doc/images/widget_sparkline.png +0 -0
- data/doc/images/widget_style_colors.png +0 -0
- data/doc/images/widget_table.png +0 -0
- data/doc/images/widget_tabs.png +0 -0
- data/doc/images/widget_text_width.png +0 -0
- data/doc/index.md +0 -39
- data/doc/troubleshooting/async.md +0 -4
- data/doc/troubleshooting/terminal_limitations.md +0 -131
- data/doc/troubleshooting/tui_output.md +0 -197
- data/examples/app_all_events/README.md +0 -114
- data/examples/app_all_events/app.rb +0 -98
- data/examples/app_all_events/model/app_model.rb +0 -159
- data/examples/app_all_events/model/event_color_cycle.rb +0 -43
- data/examples/app_all_events/model/event_entry.rb +0 -94
- data/examples/app_all_events/model/msg.rb +0 -39
- data/examples/app_all_events/model/timestamp.rb +0 -56
- data/examples/app_all_events/update.rb +0 -75
- data/examples/app_all_events/view/app_view.rb +0 -80
- data/examples/app_all_events/view/controls_view.rb +0 -54
- data/examples/app_all_events/view/counts_view.rb +0 -61
- data/examples/app_all_events/view/live_view.rb +0 -72
- data/examples/app_all_events/view/log_view.rb +0 -57
- data/examples/app_all_events/view.rb +0 -9
- data/examples/app_cli_rich_moments/README.md +0 -81
- data/examples/app_cli_rich_moments/app.rb +0 -189
- data/examples/app_color_picker/README.md +0 -156
- data/examples/app_color_picker/app.rb +0 -76
- data/examples/app_color_picker/clipboard.rb +0 -86
- data/examples/app_color_picker/color.rb +0 -193
- data/examples/app_color_picker/controls.rb +0 -92
- data/examples/app_color_picker/copy_dialog.rb +0 -168
- data/examples/app_color_picker/export_pane.rb +0 -128
- data/examples/app_color_picker/harmony.rb +0 -58
- data/examples/app_color_picker/input.rb +0 -176
- data/examples/app_color_picker/main_container.rb +0 -180
- data/examples/app_color_picker/palette.rb +0 -111
- data/examples/app_debugging_showcase/README.md +0 -119
- data/examples/app_debugging_showcase/app.rb +0 -318
- data/examples/app_login_form/README.md +0 -58
- data/examples/app_login_form/app.rb +0 -109
- data/examples/app_stateful_interaction/README.md +0 -35
- data/examples/app_stateful_interaction/app.rb +0 -328
- data/examples/timeout_demo.rb +0 -45
- data/examples/verify_quickstart_dsl/README.md +0 -55
- data/examples/verify_quickstart_dsl/app.rb +0 -49
- data/examples/verify_quickstart_layout/README.md +0 -77
- data/examples/verify_quickstart_layout/app.rb +0 -73
- data/examples/verify_quickstart_lifecycle/README.md +0 -68
- data/examples/verify_quickstart_lifecycle/app.rb +0 -62
- data/examples/verify_readme_usage/README.md +0 -49
- data/examples/verify_readme_usage/app.rb +0 -42
- data/examples/verify_website_managed/README.md +0 -48
- data/examples/verify_website_managed/app.rb +0 -36
- data/examples/verify_website_menu/README.md +0 -60
- data/examples/verify_website_menu/app.rb +0 -84
- data/examples/verify_website_spinner/README.md +0 -44
- data/examples/verify_website_spinner/app.rb +0 -34
- data/examples/widget_barchart/README.md +0 -58
- data/examples/widget_barchart/app.rb +0 -240
- data/examples/widget_block/README.md +0 -44
- data/examples/widget_block/app.rb +0 -258
- data/examples/widget_box/README.md +0 -54
- data/examples/widget_box/app.rb +0 -255
- data/examples/widget_calendar/README.md +0 -48
- data/examples/widget_calendar/app.rb +0 -115
- data/examples/widget_canvas/README.md +0 -31
- data/examples/widget_canvas/app.rb +0 -130
- data/examples/widget_cell/README.md +0 -45
- data/examples/widget_cell/app.rb +0 -112
- data/examples/widget_center/README.md +0 -33
- data/examples/widget_center/app.rb +0 -118
- data/examples/widget_chart/README.md +0 -50
- data/examples/widget_chart/app.rb +0 -220
- data/examples/widget_gauge/README.md +0 -50
- data/examples/widget_gauge/app.rb +0 -229
- data/examples/widget_layout_split/README.md +0 -53
- data/examples/widget_layout_split/app.rb +0 -260
- data/examples/widget_line_gauge/README.md +0 -50
- data/examples/widget_line_gauge/app.rb +0 -219
- data/examples/widget_list/README.md +0 -58
- data/examples/widget_list/app.rb +0 -384
- data/examples/widget_map/README.md +0 -48
- data/examples/widget_map/app.rb +0 -95
- data/examples/widget_overlay/README.md +0 -45
- data/examples/widget_overlay/app.rb +0 -250
- data/examples/widget_popup/README.md +0 -45
- data/examples/widget_popup/app.rb +0 -106
- data/examples/widget_ratatui_logo/README.md +0 -43
- data/examples/widget_ratatui_logo/app.rb +0 -104
- data/examples/widget_ratatui_mascot/README.md +0 -43
- data/examples/widget_ratatui_mascot/app.rb +0 -95
- data/examples/widget_rect/README.md +0 -53
- data/examples/widget_rect/app.rb +0 -222
- data/examples/widget_render/README.md +0 -46
- data/examples/widget_render/app.rb +0 -186
- data/examples/widget_render/app.rbs +0 -41
- data/examples/widget_rich_text/README.md +0 -44
- data/examples/widget_rich_text/app.rb +0 -193
- data/examples/widget_scroll_text/README.md +0 -46
- data/examples/widget_scroll_text/app.rb +0 -109
- data/examples/widget_scrollbar/README.md +0 -46
- data/examples/widget_scrollbar/app.rb +0 -155
- data/examples/widget_sparkline/README.md +0 -51
- data/examples/widget_sparkline/app.rb +0 -277
- data/examples/widget_style_colors/README.md +0 -43
- data/examples/widget_style_colors/app.rb +0 -83
- data/examples/widget_table/README.md +0 -57
- data/examples/widget_table/app.rb +0 -279
- data/examples/widget_tabs/README.md +0 -50
- data/examples/widget_tabs/app.rb +0 -183
- data/examples/widget_text_width/README.md +0 -44
- data/examples/widget_text_width/app.rb +0 -117
- data/migrate_to_buffer.rb +0 -145
- data/mise.toml +0 -8
- data/tasks/autodoc/examples.rb +0 -87
- data/tasks/autodoc/member.rb +0 -58
- data/tasks/autodoc/name.rb +0 -21
- data/tasks/autodoc.rake +0 -21
- data/tasks/bump/cargo_lockfile.rb +0 -21
- data/tasks/bump/changelog.rb +0 -47
- data/tasks/bump/header.rb +0 -32
- data/tasks/bump/history.rb +0 -32
- data/tasks/bump/links.rb +0 -69
- data/tasks/bump/manifest.rb +0 -33
- data/tasks/bump/ruby_gem.rb +0 -49
- data/tasks/bump/sem_ver.rb +0 -40
- data/tasks/bump/unreleased_section.rb +0 -56
- data/tasks/bump.rake +0 -51
- data/tasks/doc.rake +0 -887
- data/tasks/example_viewer.html.erb +0 -172
- data/tasks/extension.rake +0 -14
- data/tasks/license/headers_md.rb +0 -223
- data/tasks/license/headers_rb.rb +0 -210
- data/tasks/license/license_utils.rb +0 -130
- data/tasks/license/snippets_md.rb +0 -315
- data/tasks/license/snippets_rdoc.rb +0 -150
- data/tasks/license.rake +0 -91
- data/tasks/lint.rake +0 -170
- data/tasks/rdoc_config.rb +0 -29
- data/tasks/resources/build.yml.erb +0 -60
- data/tasks/resources/index.html.erb +0 -141
- data/tasks/resources/rubies.yml +0 -7
- data/tasks/sourcehut.rake +0 -110
- data/tasks/steep.rake +0 -11
- data/tasks/terminal_preview/app_screenshot.rb +0 -45
- data/tasks/terminal_preview/crash_report.rb +0 -54
- data/tasks/terminal_preview/example_app.rb +0 -27
- data/tasks/terminal_preview/launcher_script.rb +0 -48
- data/tasks/terminal_preview/preview_collection.rb +0 -60
- data/tasks/terminal_preview/preview_timing.rb +0 -24
- data/tasks/terminal_preview/safety_confirmation.rb +0 -58
- data/tasks/terminal_preview/saved_screenshot.rb +0 -56
- data/tasks/terminal_preview/system_appearance.rb +0 -13
- data/tasks/terminal_preview/terminal_window.rb +0 -138
- data/tasks/terminal_preview/window_id.rb +0 -16
- data/tasks/terminal_preview.rake +0 -30
- data/tasks/test.rake +0 -33
- data/tasks/website/index_page.rb +0 -30
- data/tasks/website/version.rb +0 -127
- data/tasks/website/version_menu.rb +0 -68
- data/tasks/website/versioned_documentation.rb +0 -83
- data/tasks/website/website.rb +0 -53
- data/tasks/website.rake +0 -28
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
# Semantic message types for the Model-View-Update architecture.
|
|
9
|
-
#
|
|
10
|
-
# Raw events from the terminal are converted to semantic Msg types. This
|
|
11
|
-
# decouples the Update function from the event system, making it easier
|
|
12
|
-
# to test and reason about.
|
|
13
|
-
#
|
|
14
|
-
# === Example
|
|
15
|
-
#
|
|
16
|
-
# msg = Msg::Input.new(event: key_event)
|
|
17
|
-
# msg = Msg::Quit.new
|
|
18
|
-
module Msg
|
|
19
|
-
# A keyboard, mouse, or paste event to record.
|
|
20
|
-
Input = Data.define(:event)
|
|
21
|
-
|
|
22
|
-
# A terminal resize event.
|
|
23
|
-
#
|
|
24
|
-
# [width] Integer new terminal width
|
|
25
|
-
# [height] Integer new terminal height
|
|
26
|
-
# [previous_size] Array [width, height] before resize
|
|
27
|
-
Resize = Data.define(:width, :height, :previous_size)
|
|
28
|
-
|
|
29
|
-
# A focus change event.
|
|
30
|
-
#
|
|
31
|
-
# [gained] Boolean true if focus was gained, false if lost
|
|
32
|
-
Focus = Data.define(:gained)
|
|
33
|
-
|
|
34
|
-
# A none/timeout event (no input received).
|
|
35
|
-
NoneEvent = Data.define
|
|
36
|
-
|
|
37
|
-
# A quit signal.
|
|
38
|
-
Quit = Data.define
|
|
39
|
-
end
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
# Represents a high-resolution point in time.
|
|
9
|
-
#
|
|
10
|
-
# Comparing events and calculating durations requires consistent time measurement.
|
|
11
|
-
# Standard Time objects are often too granular or complex for simple millisecond offsets.
|
|
12
|
-
#
|
|
13
|
-
# This class provides a millisecond-precision timestamp for event measurement.
|
|
14
|
-
#
|
|
15
|
-
# Use it to track event timing, calculate elapsed time, or trigger debouncing.
|
|
16
|
-
#
|
|
17
|
-
# === Examples
|
|
18
|
-
#
|
|
19
|
-
# timestamp = Timestamp.now
|
|
20
|
-
# puts timestamp.milliseconds
|
|
21
|
-
#
|
|
22
|
-
# if timestamp.elapsed?(300)
|
|
23
|
-
# puts "More than 300ms have passed."
|
|
24
|
-
# end
|
|
25
|
-
class Timestamp < Data.define(:milliseconds)
|
|
26
|
-
# Returns a new Timestamp representing the current time.
|
|
27
|
-
#
|
|
28
|
-
# === Example
|
|
29
|
-
#
|
|
30
|
-
# Timestamp.now #=> #<struct Timestamp milliseconds=123456789>
|
|
31
|
-
def self.now
|
|
32
|
-
new(milliseconds: (Time.now.to_f * 1000).to_i)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Checks if a duration has passed since this timestamp.
|
|
36
|
-
#
|
|
37
|
-
# [duration_ms] Integer duration in milliseconds.
|
|
38
|
-
#
|
|
39
|
-
# === Example
|
|
40
|
-
#
|
|
41
|
-
# timestamp = Timestamp.now
|
|
42
|
-
# sleep(0.5)
|
|
43
|
-
# timestamp.elapsed?(300) #=> true
|
|
44
|
-
def elapsed?(duration_ms)
|
|
45
|
-
Timestamp.now.milliseconds >= milliseconds + duration_ms
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Returns the current time in milliseconds.
|
|
49
|
-
#
|
|
50
|
-
# === Example
|
|
51
|
-
#
|
|
52
|
-
# Timestamp.current #=> 123456789
|
|
53
|
-
def self.current
|
|
54
|
-
now.milliseconds
|
|
55
|
-
end
|
|
56
|
-
end
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
require_relative "model/app_model"
|
|
9
|
-
require_relative "model/msg"
|
|
10
|
-
require_relative "model/event_entry"
|
|
11
|
-
require_relative "model/timestamp"
|
|
12
|
-
require_relative "model/event_color_cycle"
|
|
13
|
-
|
|
14
|
-
# Pure update function for the Model-View-Update architecture.
|
|
15
|
-
#
|
|
16
|
-
# Given a Msg and the current AppModel, returns the next AppModel.
|
|
17
|
-
# This function is pure: it does not mutate arguments, draw to the screen,
|
|
18
|
-
# or perform IO. It simply calculates the next state.
|
|
19
|
-
#
|
|
20
|
-
# === Example
|
|
21
|
-
#
|
|
22
|
-
# model = AppModel.initial
|
|
23
|
-
# msg = Msg::Input.new(event: key_event)
|
|
24
|
-
# new_model = Update.call(msg, model)
|
|
25
|
-
module Update
|
|
26
|
-
extend self
|
|
27
|
-
|
|
28
|
-
# Processes a message and returns the next model.
|
|
29
|
-
#
|
|
30
|
-
# [msg] A Msg value object
|
|
31
|
-
# [model] The current AppModel
|
|
32
|
-
#
|
|
33
|
-
# === Example
|
|
34
|
-
#
|
|
35
|
-
# Update.call(Msg::Quit.new, model) #=> model (unchanged)
|
|
36
|
-
def call(msg, model)
|
|
37
|
-
case msg
|
|
38
|
-
in Msg::Quit
|
|
39
|
-
model
|
|
40
|
-
in Msg::NoneEvent
|
|
41
|
-
model.with(none_count: model.none_count + 1)
|
|
42
|
-
in Msg::Focus(gained:)
|
|
43
|
-
event = gained ? RatatuiRuby::Event::FocusGained.new : RatatuiRuby::Event::FocusLost.new
|
|
44
|
-
entry = create_entry(event, model)
|
|
45
|
-
add_entry(model, entry, :focus).with(focused: gained)
|
|
46
|
-
in Msg::Resize(width:, height:, previous_size: _)
|
|
47
|
-
event = RatatuiRuby::Event::Resize.new(width:, height:)
|
|
48
|
-
entry = create_entry(event, model)
|
|
49
|
-
add_entry(model, entry, :resize).with(window_size: [width, height])
|
|
50
|
-
in Msg::Input(event:)
|
|
51
|
-
entry = create_entry(event, model)
|
|
52
|
-
add_entry(model, entry, entry.live_type)
|
|
53
|
-
else
|
|
54
|
-
model
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Creates an EventEntry with the next color and current timestamp.
|
|
59
|
-
def create_entry(event, model)
|
|
60
|
-
EventEntry.create(event, model.next_color, Timestamp.now)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Adds an entry to the model, updates highlights, and advances the color cycle.
|
|
64
|
-
def add_entry(model, entry, live_type)
|
|
65
|
-
new_entries = model.entries + [entry]
|
|
66
|
-
new_lit_types = model.lit_types.merge(live_type => Timestamp.now)
|
|
67
|
-
new_color_index = (model.color_cycle_index + 1) % EventColorCycle::COLORS.length
|
|
68
|
-
|
|
69
|
-
model.with(
|
|
70
|
-
entries: new_entries,
|
|
71
|
-
lit_types: new_lit_types,
|
|
72
|
-
color_cycle_index: new_color_index
|
|
73
|
-
)
|
|
74
|
-
end
|
|
75
|
-
end
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
require_relative "../view"
|
|
9
|
-
require_relative "counts_view"
|
|
10
|
-
require_relative "live_view"
|
|
11
|
-
require_relative "log_view"
|
|
12
|
-
require_relative "controls_view"
|
|
13
|
-
|
|
14
|
-
# Orchestrates the complete UI layout and sub-view composition.
|
|
15
|
-
#
|
|
16
|
-
# Complex applications need a structured way to divide the screen and delegate rendering.
|
|
17
|
-
# Placing all layout logic in one monolithic method makes the code difficult to maintain.
|
|
18
|
-
#
|
|
19
|
-
# This class defines the screen layout using a series of split constraints and delegates to sub-views.
|
|
20
|
-
#
|
|
21
|
-
# Use it as the root view for the All Events example application.
|
|
22
|
-
#
|
|
23
|
-
# === Examples
|
|
24
|
-
#
|
|
25
|
-
# app_view = View::App.new
|
|
26
|
-
# app_view.call(model, tui, frame, area)
|
|
27
|
-
class View::App
|
|
28
|
-
# Creates a new View::App and initializes sub-views.
|
|
29
|
-
def initialize
|
|
30
|
-
@counts_view = View::Counts.new
|
|
31
|
-
@live_view = View::Live.new
|
|
32
|
-
@log_view = View::Log.new
|
|
33
|
-
@controls_view = View::Controls.new
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Renders the entire application UI to the given area.
|
|
37
|
-
#
|
|
38
|
-
# [model] AppModel containing all application data.
|
|
39
|
-
# [tui] RatatuiRuby instance.
|
|
40
|
-
# [frame] RatatuiRuby::Frame being rendered.
|
|
41
|
-
# [area] RatatuiRuby::Layout::Rect defining the total available space.
|
|
42
|
-
#
|
|
43
|
-
# === Example
|
|
44
|
-
#
|
|
45
|
-
# app_view.call(model, tui, frame, area)
|
|
46
|
-
def call(model, tui, frame, area)
|
|
47
|
-
main_area, control_area = tui.layout_split(
|
|
48
|
-
area,
|
|
49
|
-
direction: :vertical,
|
|
50
|
-
constraints: [
|
|
51
|
-
tui.constraint_fill(1),
|
|
52
|
-
tui.constraint_length(3),
|
|
53
|
-
]
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
counts_area, _margin_area, right_area = tui.layout_split(
|
|
57
|
-
main_area,
|
|
58
|
-
direction: :horizontal,
|
|
59
|
-
constraints: [
|
|
60
|
-
tui.constraint_length(20),
|
|
61
|
-
tui.constraint_length(1),
|
|
62
|
-
tui.constraint_fill(1),
|
|
63
|
-
]
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
live_area, log_area = tui.layout_split(
|
|
67
|
-
right_area,
|
|
68
|
-
direction: :vertical,
|
|
69
|
-
constraints: [
|
|
70
|
-
tui.constraint_length(9),
|
|
71
|
-
tui.constraint_fill(1),
|
|
72
|
-
]
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
@counts_view.call(model, tui, frame, counts_area)
|
|
76
|
-
@live_view.call(model, tui, frame, live_area)
|
|
77
|
-
@log_view.call(model, tui, frame, log_area)
|
|
78
|
-
@controls_view.call(model, tui, frame, control_area)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
require_relative "../view"
|
|
9
|
-
|
|
10
|
-
# Renders the keyboard controls and shortcuts panel.
|
|
11
|
-
#
|
|
12
|
-
# Users need to know how to interact with the application and exit.
|
|
13
|
-
# Hardcoding control descriptions into the main layout makes the code hard to read.
|
|
14
|
-
#
|
|
15
|
-
# This component renders a formatted paragraph listing available global shortcuts.
|
|
16
|
-
#
|
|
17
|
-
# Use it to display help information in a sidebar or dedicated panel.
|
|
18
|
-
#
|
|
19
|
-
# === Examples
|
|
20
|
-
#
|
|
21
|
-
# controls = View::Controls.new
|
|
22
|
-
# controls.call(model, tui, frame, area)
|
|
23
|
-
class View::Controls
|
|
24
|
-
# Renders the controls widget to the given area.
|
|
25
|
-
#
|
|
26
|
-
# [model] AppModel (unused, included for consistent interface).
|
|
27
|
-
# [tui] RatatuiRuby instance.
|
|
28
|
-
# [frame] RatatuiRuby::Frame being rendered.
|
|
29
|
-
# [area] RatatuiRuby::Layout::Rect defining the widget's bounds.
|
|
30
|
-
#
|
|
31
|
-
# === Example
|
|
32
|
-
#
|
|
33
|
-
# controls.call(model, tui, frame, area)
|
|
34
|
-
def call(_model, tui, frame, area)
|
|
35
|
-
hotkey_style = tui.style(modifiers: [:bold, :underlined])
|
|
36
|
-
|
|
37
|
-
widget = tui.paragraph(
|
|
38
|
-
text: [
|
|
39
|
-
tui.text_line(spans: [
|
|
40
|
-
tui.text_span(content: "q", style: hotkey_style),
|
|
41
|
-
tui.text_span(content: ": Quit "),
|
|
42
|
-
tui.text_span(content: "Ctrl+C", style: hotkey_style),
|
|
43
|
-
tui.text_span(content: ": Quit"),
|
|
44
|
-
]),
|
|
45
|
-
],
|
|
46
|
-
block: tui.block(
|
|
47
|
-
title: "Controls",
|
|
48
|
-
borders: [:all],
|
|
49
|
-
border_style: tui.style(fg: :white)
|
|
50
|
-
)
|
|
51
|
-
)
|
|
52
|
-
frame.render_widget(widget, area)
|
|
53
|
-
end
|
|
54
|
-
end
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
require_relative "../view"
|
|
9
|
-
|
|
10
|
-
# Renders the event statistics dashboard.
|
|
11
|
-
#
|
|
12
|
-
# Developers auditing input need to see real-time counts of various event types.
|
|
13
|
-
#
|
|
14
|
-
# This component displays a list of event types with their total counts.
|
|
15
|
-
#
|
|
16
|
-
# Use it to build an interactive dashboard of application activity.
|
|
17
|
-
class View::Counts
|
|
18
|
-
# Renders the event counts widget to the given area.
|
|
19
|
-
#
|
|
20
|
-
# [model] AppModel containing event data.
|
|
21
|
-
# [tui] RatatuiRuby instance.
|
|
22
|
-
# [frame] RatatuiRuby::Frame being rendered.
|
|
23
|
-
# [area] RatatuiRuby::Layout::Rect defining the widget's bounds.
|
|
24
|
-
def call(model, tui, frame, area)
|
|
25
|
-
dimmed_style = tui.style(fg: :dark_gray)
|
|
26
|
-
lit_style = tui.style(fg: :green, modifiers: [:bold])
|
|
27
|
-
border_color = model.focused ? :green : :gray
|
|
28
|
-
|
|
29
|
-
count_lines = []
|
|
30
|
-
|
|
31
|
-
AppAllEvents::EVENT_TYPES.each do |type|
|
|
32
|
-
count = model.count(type)
|
|
33
|
-
label = type.to_s.capitalize
|
|
34
|
-
style = model.lit?(type) ? lit_style : nil
|
|
35
|
-
|
|
36
|
-
count_lines << tui.text_line(spans: [
|
|
37
|
-
tui.text_span(content: "#{label}: ", style:),
|
|
38
|
-
tui.text_span(content: count.to_s, style: style || tui.style(fg: :yellow)),
|
|
39
|
-
])
|
|
40
|
-
|
|
41
|
-
model.sub_counts(type).each do |sub_type, sub_count|
|
|
42
|
-
sub_label = sub_type.to_s.capitalize
|
|
43
|
-
count_lines << tui.text_line(spans: [
|
|
44
|
-
tui.text_span(content: " #{sub_label}: ", style: dimmed_style),
|
|
45
|
-
tui.text_span(content: sub_count.to_s, style: dimmed_style),
|
|
46
|
-
])
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
widget = tui.paragraph(
|
|
51
|
-
text: count_lines,
|
|
52
|
-
scroll: [0, 0],
|
|
53
|
-
block: tui.block(
|
|
54
|
-
title: "Event Counts",
|
|
55
|
-
borders: [:all],
|
|
56
|
-
border_style: tui.style(fg: border_color)
|
|
57
|
-
)
|
|
58
|
-
)
|
|
59
|
-
frame.render_widget(widget, area)
|
|
60
|
-
end
|
|
61
|
-
end
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
require_relative "../view"
|
|
9
|
-
|
|
10
|
-
# Renders a real-time summary of the most recent events.
|
|
11
|
-
#
|
|
12
|
-
# Users need to see the immediate result of their actions without digging through a log.
|
|
13
|
-
# Identifying the specific details of the last key press or mouse move at a glance is difficult.
|
|
14
|
-
#
|
|
15
|
-
# This component displays a table showing the latest event of each type with its timestamp and description.
|
|
16
|
-
#
|
|
17
|
-
# Use it to provide instant feedback for user interactions.
|
|
18
|
-
#
|
|
19
|
-
# === Examples
|
|
20
|
-
#
|
|
21
|
-
# live_view = View::Live.new
|
|
22
|
-
# live_view.call(model, tui, frame, area)
|
|
23
|
-
class View::Live
|
|
24
|
-
# Renders the live event table to the given area.
|
|
25
|
-
#
|
|
26
|
-
# [model] AppModel containing event data.
|
|
27
|
-
# [tui] RatatuiRuby instance.
|
|
28
|
-
# [frame] RatatuiRuby::Frame being rendered.
|
|
29
|
-
# [area] RatatuiRuby::Layout::Rect defining the widget's bounds.
|
|
30
|
-
#
|
|
31
|
-
# === Example
|
|
32
|
-
#
|
|
33
|
-
# live_view.call(model, tui, frame, area)
|
|
34
|
-
def call(model, tui, frame, area)
|
|
35
|
-
border_color = model.focused ? :green : :gray
|
|
36
|
-
rows = []
|
|
37
|
-
|
|
38
|
-
rows << tui.text_line(spans: [
|
|
39
|
-
tui.text_span(content: "Type".ljust(9), style: tui.style(fg: :gray, modifiers: [:bold])),
|
|
40
|
-
tui.text_span(content: "Time".ljust(10), style: tui.style(fg: :gray, modifiers: [:bold])),
|
|
41
|
-
tui.text_span(content: "Description", style: tui.style(fg: :gray, modifiers: [:bold])),
|
|
42
|
-
])
|
|
43
|
-
|
|
44
|
-
(AppAllEvents::EVENT_TYPES - [:none]).each do |type|
|
|
45
|
-
event_data = model.live_event(type)
|
|
46
|
-
|
|
47
|
-
class_str = type.to_s.capitalize
|
|
48
|
-
time_str = event_data ? event_data[:time].strftime("%H:%M:%S") : "—"
|
|
49
|
-
desc_str = event_data ? event_data[:description] : "—"
|
|
50
|
-
|
|
51
|
-
is_lit = model.lit?(type)
|
|
52
|
-
row_style = is_lit ? tui.style(fg: :green, modifiers: [:reversed]) : nil
|
|
53
|
-
|
|
54
|
-
rows << tui.text_line(spans: [
|
|
55
|
-
tui.text_span(content: class_str.ljust(9), style: row_style || tui.style(fg: :cyan)),
|
|
56
|
-
tui.text_span(content: time_str.ljust(10), style: row_style || tui.style(fg: :white)),
|
|
57
|
-
tui.text_span(content: desc_str, style: row_style),
|
|
58
|
-
])
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
widget = tui.paragraph(
|
|
62
|
-
text: rows,
|
|
63
|
-
scroll: [0, 0],
|
|
64
|
-
block: tui.block(
|
|
65
|
-
title: "Live Display",
|
|
66
|
-
borders: [:all],
|
|
67
|
-
border_style: tui.style(fg: border_color)
|
|
68
|
-
)
|
|
69
|
-
)
|
|
70
|
-
frame.render_widget(widget, area)
|
|
71
|
-
end
|
|
72
|
-
end
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
require_relative "../view"
|
|
9
|
-
|
|
10
|
-
# Renders a detailed, scrollable history of application events.
|
|
11
|
-
#
|
|
12
|
-
# Debugging complex event flows requires a chronological record of raw data.
|
|
13
|
-
# Interpreting raw event objects without formatting is difficult and slow.
|
|
14
|
-
#
|
|
15
|
-
# This component renders event history as a series of formatted, color-coded entries showing raw data.
|
|
16
|
-
#
|
|
17
|
-
# Use it to provide a detailed audit trail of all terminal interactions.
|
|
18
|
-
class View::Log
|
|
19
|
-
# Renders the event log widget to the given area.
|
|
20
|
-
#
|
|
21
|
-
# [model] AppModel containing event data.
|
|
22
|
-
# [tui] RatatuiRuby instance.
|
|
23
|
-
# [frame] RatatuiRuby::Frame being rendered.
|
|
24
|
-
# [area] RatatuiRuby::Layout::Rect defining the widget's bounds.
|
|
25
|
-
def call(model, tui, frame, area)
|
|
26
|
-
dimmed_style = tui.style(fg: :dark_gray)
|
|
27
|
-
border_color = model.focused ? :green : :gray
|
|
28
|
-
|
|
29
|
-
visible_entries_count = (area.height - 2) / 2
|
|
30
|
-
display_entries = model.visible(visible_entries_count)
|
|
31
|
-
|
|
32
|
-
log_lines = []
|
|
33
|
-
if model.empty?
|
|
34
|
-
log_lines << tui.text_line(spans: [tui.text_span(content: "No events yet...", style: dimmed_style)])
|
|
35
|
-
else
|
|
36
|
-
display_entries.each do |entry|
|
|
37
|
-
entry_style = tui.style(fg: entry.color)
|
|
38
|
-
description = entry.description
|
|
39
|
-
|
|
40
|
-
log_lines << tui.text_line(spans: [tui.text_span(content: description, style: entry_style)])
|
|
41
|
-
log_lines << tui.text_line(spans: [tui.text_span(content: "", style: entry_style)])
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
widget = tui.paragraph(
|
|
46
|
-
text: log_lines,
|
|
47
|
-
scroll: [0, 0],
|
|
48
|
-
wrap: { trim: true },
|
|
49
|
-
block: tui.block(
|
|
50
|
-
title: "Event Log",
|
|
51
|
-
borders: [:all],
|
|
52
|
-
border_style: tui.style(fg: border_color)
|
|
53
|
-
)
|
|
54
|
-
)
|
|
55
|
-
frame.render_widget(widget, area)
|
|
56
|
-
end
|
|
57
|
-
end
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
-
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
-
-->
|
|
5
|
-
|
|
6
|
-
# CLI Rich Moments Example
|
|
7
|
-
|
|
8
|
-
[](app.rb)
|
|
9
|
-
|
|
10
|
-
Demonstrates inline viewport usage for CLI tools that need brief moments of rich interactivity without full-screen commitment.
|
|
11
|
-
|
|
12
|
-
## Context
|
|
13
|
-
|
|
14
|
-
CLI applications often need moments of richness—a spinner while connecting, a quick menu selection, or a brief editor. But committing to a full-screen TUI feels wrong when 90% of the app is traditional CLI output.
|
|
15
|
-
|
|
16
|
-
## Problem
|
|
17
|
-
|
|
18
|
-
Standard full-screen TUIs (alternate screen) erase themselves on exit. Your carefully formatted CLI output disappears. Users lose their history. The terminal scrollback becomes useless.
|
|
19
|
-
|
|
20
|
-
## Solution
|
|
21
|
-
|
|
22
|
-
This example shows how **inline viewports** solve this problem. Inline regions persist in terminal scrollback after exit. You can mix inline and fullscreen viewports across multiple `RatatuiRuby.run` calls in a single application flow.
|
|
23
|
-
|
|
24
|
-
## Flow
|
|
25
|
-
|
|
26
|
-
The application demonstrates four distinct phases:
|
|
27
|
-
|
|
28
|
-
1. **1-line inline**: Braille spinner (⠋ ⠙ ⠹ etc.) + "Connecting..." status
|
|
29
|
-
2. **5-line inline**: Radio button menu with up/down/enter navigation
|
|
30
|
-
3. **Fullscreen**: Configuration editor showing legitimate use of alternate screen
|
|
31
|
-
4. **1-line inline**: Braille spinner + "Saving..." confirmation
|
|
32
|
-
|
|
33
|
-
After exit, all inline outputs remain visible in terminal history. The fullscreen portion disappears cleanly.
|
|
34
|
-
|
|
35
|
-
## Running
|
|
36
|
-
|
|
37
|
-
<!-- SPDX-SnippetBegin -->
|
|
38
|
-
<!--
|
|
39
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
40
|
-
SPDX-License-Identifier: MIT-0
|
|
41
|
-
-->
|
|
42
|
-
```bash
|
|
43
|
-
cd examples/app_cli_rich_moments
|
|
44
|
-
ruby app.rb
|
|
45
|
-
```
|
|
46
|
-
<!-- SPDX-SnippetEnd -->
|
|
47
|
-
|
|
48
|
-
## Architecture
|
|
49
|
-
|
|
50
|
-
Each phase is a separate `RatatuiRuby.run` block with its own viewport configuration. State (like menu selection) must be passed between phases via instance variables.
|
|
51
|
-
|
|
52
|
-
**Spinner phases**:
|
|
53
|
-
- Use `viewport: :inline, height: 1`
|
|
54
|
-
- Animate Braille frames with short delays
|
|
55
|
-
- No input handling needed
|
|
56
|
-
|
|
57
|
-
**Menu phase**:
|
|
58
|
-
- Uses `viewport: :inline, height: 5`
|
|
59
|
-
- Handles up/down arrow keys for selection
|
|
60
|
-
- Returns selected choice for next phase
|
|
61
|
-
|
|
62
|
-
**Editor phase**:
|
|
63
|
-
- Uses default fullscreen viewport
|
|
64
|
-
- Demonstrates when alternate screen is appropriate
|
|
65
|
-
- Full 80×24 available
|
|
66
|
-
|
|
67
|
-
## Key Concepts
|
|
68
|
-
|
|
69
|
-
- **Viewport independence**: Each `run` block can use different viewport modes
|
|
70
|
-
- **Scrollback persistence**: Inline content remains after exit
|
|
71
|
-
- **State management**: Pass data between phases via instance variables
|
|
72
|
-
- **Appropriate contexts**: Inline for brief moments, fullscreen for sustained interaction
|
|
73
|
-
|
|
74
|
-
## Learning Outcomes
|
|
75
|
-
|
|
76
|
-
After studying this example, you'll understand:
|
|
77
|
-
|
|
78
|
-
- When to use inline vs fullscreen viewports
|
|
79
|
-
- How to mix viewport modes in a single application
|
|
80
|
-
- Why inline viewports improve CLI UX for transient interactions
|
|
81
|
-
- How to manage state across multiple `run` blocks
|