ratatui_ruby 0.1.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.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.build.yml +34 -0
  3. data/.pre-commit-config.yaml +9 -0
  4. data/.rubocop.yml +8 -0
  5. data/.ruby-version +1 -0
  6. data/AGENTS.md +119 -0
  7. data/CHANGELOG.md +15 -0
  8. data/CODE_OF_CONDUCT.md +30 -0
  9. data/CONTRIBUTING.md +40 -0
  10. data/LICENSE +15 -0
  11. data/LICENSES/AGPL-3.0-or-later.txt +661 -0
  12. data/LICENSES/BSD-2-Clause.txt +9 -0
  13. data/LICENSES/CC-BY-SA-4.0.txt +427 -0
  14. data/LICENSES/CC0-1.0.txt +121 -0
  15. data/LICENSES/MIT.txt +21 -0
  16. data/README.md +86 -0
  17. data/REUSE.toml +17 -0
  18. data/Rakefile +108 -0
  19. data/docs/application_testing.md +96 -0
  20. data/docs/contributors/design/ruby_frontend.md +100 -0
  21. data/docs/contributors/design/rust_backend.md +61 -0
  22. data/docs/contributors/design.md +11 -0
  23. data/docs/contributors/index.md +16 -0
  24. data/docs/images/examples-analytics.rb.png +0 -0
  25. data/docs/images/examples-box_demo.rb.png +0 -0
  26. data/docs/images/examples-dashboard.rb.png +0 -0
  27. data/docs/images/examples-login_form.rb.png +0 -0
  28. data/docs/images/examples-map_demo.rb.png +0 -0
  29. data/docs/images/examples-mouse_events.rb.png +0 -0
  30. data/docs/images/examples-scrollbar_demo.rb.png +0 -0
  31. data/docs/images/examples-stock_ticker.rb.png +0 -0
  32. data/docs/images/examples-system_monitor.rb.png +0 -0
  33. data/docs/index.md +18 -0
  34. data/docs/quickstart.md +126 -0
  35. data/examples/analytics.rb +87 -0
  36. data/examples/box_demo.rb +71 -0
  37. data/examples/dashboard.rb +72 -0
  38. data/examples/login_form.rb +114 -0
  39. data/examples/map_demo.rb +58 -0
  40. data/examples/mouse_events.rb +95 -0
  41. data/examples/scrollbar_demo.rb +75 -0
  42. data/examples/stock_ticker.rb +85 -0
  43. data/examples/system_monitor.rb +93 -0
  44. data/examples/test_analytics.rb +65 -0
  45. data/examples/test_box_demo.rb +38 -0
  46. data/examples/test_dashboard.rb +38 -0
  47. data/examples/test_login_form.rb +63 -0
  48. data/examples/test_map_demo.rb +100 -0
  49. data/examples/test_stock_ticker.rb +39 -0
  50. data/examples/test_system_monitor.rb +40 -0
  51. data/ext/ratatui_ruby/.cargo/config.toml +8 -0
  52. data/ext/ratatui_ruby/.gitignore +4 -0
  53. data/ext/ratatui_ruby/Cargo.lock +698 -0
  54. data/ext/ratatui_ruby/Cargo.toml +16 -0
  55. data/ext/ratatui_ruby/extconf.rb +12 -0
  56. data/ext/ratatui_ruby/src/events.rs +279 -0
  57. data/ext/ratatui_ruby/src/lib.rs +105 -0
  58. data/ext/ratatui_ruby/src/rendering.rs +31 -0
  59. data/ext/ratatui_ruby/src/style.rs +149 -0
  60. data/ext/ratatui_ruby/src/terminal.rs +131 -0
  61. data/ext/ratatui_ruby/src/widgets/barchart.rs +73 -0
  62. data/ext/ratatui_ruby/src/widgets/block.rs +12 -0
  63. data/ext/ratatui_ruby/src/widgets/canvas.rs +146 -0
  64. data/ext/ratatui_ruby/src/widgets/center.rs +81 -0
  65. data/ext/ratatui_ruby/src/widgets/cursor.rs +29 -0
  66. data/ext/ratatui_ruby/src/widgets/gauge.rs +50 -0
  67. data/ext/ratatui_ruby/src/widgets/layout.rs +82 -0
  68. data/ext/ratatui_ruby/src/widgets/linechart.rs +154 -0
  69. data/ext/ratatui_ruby/src/widgets/list.rs +62 -0
  70. data/ext/ratatui_ruby/src/widgets/mod.rs +18 -0
  71. data/ext/ratatui_ruby/src/widgets/overlay.rs +20 -0
  72. data/ext/ratatui_ruby/src/widgets/paragraph.rs +56 -0
  73. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +68 -0
  74. data/ext/ratatui_ruby/src/widgets/sparkline.rs +59 -0
  75. data/ext/ratatui_ruby/src/widgets/table.rs +117 -0
  76. data/ext/ratatui_ruby/src/widgets/tabs.rs +51 -0
  77. data/lib/ratatui_ruby/output.rb +7 -0
  78. data/lib/ratatui_ruby/schema/bar_chart.rb +28 -0
  79. data/lib/ratatui_ruby/schema/block.rb +23 -0
  80. data/lib/ratatui_ruby/schema/canvas.rb +62 -0
  81. data/lib/ratatui_ruby/schema/center.rb +19 -0
  82. data/lib/ratatui_ruby/schema/constraint.rb +33 -0
  83. data/lib/ratatui_ruby/schema/cursor.rb +17 -0
  84. data/lib/ratatui_ruby/schema/gauge.rb +24 -0
  85. data/lib/ratatui_ruby/schema/layout.rb +22 -0
  86. data/lib/ratatui_ruby/schema/line_chart.rb +41 -0
  87. data/lib/ratatui_ruby/schema/list.rb +22 -0
  88. data/lib/ratatui_ruby/schema/overlay.rb +15 -0
  89. data/lib/ratatui_ruby/schema/paragraph.rb +37 -0
  90. data/lib/ratatui_ruby/schema/scrollbar.rb +33 -0
  91. data/lib/ratatui_ruby/schema/sparkline.rb +24 -0
  92. data/lib/ratatui_ruby/schema/style.rb +31 -0
  93. data/lib/ratatui_ruby/schema/table.rb +24 -0
  94. data/lib/ratatui_ruby/schema/tabs.rb +22 -0
  95. data/lib/ratatui_ruby/test_helper.rb +75 -0
  96. data/lib/ratatui_ruby/version.rb +10 -0
  97. data/lib/ratatui_ruby.rb +87 -0
  98. data/sig/ratatui_ruby/ratatui_ruby.rbs +16 -0
  99. data/sig/ratatui_ruby/schema/bar_chart.rbs +14 -0
  100. data/sig/ratatui_ruby/schema/block.rbs +11 -0
  101. data/sig/ratatui_ruby/schema/canvas.rbs +62 -0
  102. data/sig/ratatui_ruby/schema/center.rbs +11 -0
  103. data/sig/ratatui_ruby/schema/constraint.rbs +13 -0
  104. data/sig/ratatui_ruby/schema/cursor.rbs +10 -0
  105. data/sig/ratatui_ruby/schema/gauge.rbs +13 -0
  106. data/sig/ratatui_ruby/schema/layout.rbs +11 -0
  107. data/sig/ratatui_ruby/schema/line_chart.rbs +20 -0
  108. data/sig/ratatui_ruby/schema/list.rbs +11 -0
  109. data/sig/ratatui_ruby/schema/overlay.rbs +9 -0
  110. data/sig/ratatui_ruby/schema/paragraph.rbs +11 -0
  111. data/sig/ratatui_ruby/schema/scrollbar.rbs +20 -0
  112. data/sig/ratatui_ruby/schema/sparkline.rbs +12 -0
  113. data/sig/ratatui_ruby/schema/style.rbs +13 -0
  114. data/sig/ratatui_ruby/schema/table.rbs +13 -0
  115. data/sig/ratatui_ruby/schema/tabs.rbs +11 -0
  116. data/sig/ratatui_ruby/test_helper.rbs +11 -0
  117. data/sig/ratatui_ruby/version.rbs +6 -0
  118. data/vendor/goodcop/base.yml +1047 -0
  119. metadata +196 -0
data/Rakefile ADDED
@@ -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
+ require "bundler/gem_tasks"
7
+ require "minitest/test_task"
8
+
9
+ # Ruby tests are handled by test:ruby
10
+ # Cargo tests are handled by test:rust
11
+
12
+ require "rake/extensiontask"
13
+
14
+ spec = Gem::Specification.load("ratatui_ruby.gemspec")
15
+ Rake::ExtensionTask.new("ratatui_ruby", spec) do |ext|
16
+ ext.lib_dir = "lib/ratatui_ruby"
17
+ ext.ext_dir = "ext/ratatui_ruby"
18
+ end
19
+
20
+ # The :compile task is now provided by rake-compiler
21
+
22
+ require "rubocop/rake_task"
23
+
24
+ RuboCop::RakeTask.new
25
+
26
+ require "rdoc/task"
27
+
28
+ RDoc::Task.new do |rdoc|
29
+ rdoc.rdoc_dir = "doc"
30
+ rdoc.main = "README.md"
31
+ rdoc.rdoc_files.include("**/*.md", "**/*.rdoc", "lib/**/*.rb", "exe/**/*")
32
+ end
33
+
34
+ Rake::Task[:rdoc].enhance do
35
+ FileUtils.mkdir_p "doc/docs/images"
36
+ FileUtils.cp_r FileList["docs/images/*.png"], "doc/docs/images"
37
+ end
38
+
39
+ Rake::Task[:rerdoc].enhance do
40
+ FileUtils.mkdir_p "doc/docs/images"
41
+ FileUtils.cp_r FileList["docs/images/*.png"], "doc/docs/images"
42
+ end
43
+
44
+ require "rubycritic/rake_task"
45
+
46
+ RubyCritic::RakeTask.new do |task|
47
+ task.options = "--no-browser"
48
+ task.paths = FileList.new.include("exe/**/*.rb", "lib/**/*.rb", "sig/**/*.rbs")
49
+ end
50
+
51
+ require "inch/rake"
52
+
53
+ Inch::Rake::Suggest.new("doc:suggest", "exe/**/*.rb", "lib/**/*.rb", "sig/**/*.rbs") do |suggest|
54
+ suggest.args << ""
55
+ end
56
+
57
+ namespace :cargo do
58
+ desc "Run cargo fmt"
59
+ task :fmt do
60
+ sh "cd ext/ratatui_ruby && cargo fmt --all -- --check"
61
+ end
62
+
63
+ desc "Run cargo clippy"
64
+ task :clippy do
65
+ sh "cd ext/ratatui_ruby && cargo clippy -- -D warnings"
66
+ end
67
+
68
+ desc "Run cargo tests"
69
+ task :test do
70
+ sh "cd ext/ratatui_ruby && cargo test"
71
+ end
72
+ end
73
+
74
+ namespace :reuse do
75
+ desc "Run the REUSE Tool to confirm REUSE compliance"
76
+ task :lint do
77
+ sh "pipx run reuse lint"
78
+ end
79
+ end
80
+ task(:reuse) { Rake::Task["reuse:lint"].invoke }
81
+
82
+ namespace :lint do
83
+ multitask docs: %i[rubycritic rdoc:coverage reuse:lint]
84
+ multitask code: %i[rubocop rubycritic cargo:fmt cargo:clippy cargo:test]
85
+ multitask licenses: %i[reuse:lint]
86
+ multitask all: %i[docs code licenses]
87
+ end
88
+ task(:lint) { Rake::Task["lint:all"].invoke }
89
+
90
+ # Clear the default test task created by Minitest::TestTask
91
+ Rake::Task["test"].clear
92
+
93
+ desc "Run all tests (Ruby and Rust)"
94
+ task test: %w[test:ruby test:rust]
95
+
96
+ namespace :test do
97
+ desc "Run Rust tests"
98
+ task :rust do
99
+ Rake::Task["cargo:test"].invoke
100
+ end
101
+
102
+ # Create a specific Minitest task for Ruby tests
103
+ Minitest::TestTask.create(:ruby) do |t|
104
+ t.test_globs = ["test/**/test_*.rb", "examples/**/test_*.rb"]
105
+ end
106
+ end
107
+
108
+ multitask default: %i[test lint]
@@ -0,0 +1,96 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+ # Application Testing Guide
6
+
7
+ This guide explains how to test your RatatuiRuby applications using the provided `RatatuiRuby::TestHelper`.
8
+
9
+ ## Overview
10
+
11
+ RatatuiRuby includes a `TestHelper` module designed to simplify unit testing of TUI applications. It allows you to:
12
+
13
+ - Initialize a virtual "test terminal" with specific dimensions.
14
+
15
+ - Capture the rendered output (the "buffer") to assert against expected text.
16
+
17
+ - Inspect the cursor position.
18
+
19
+ - Simulate user input (using `inject_event`).
20
+
21
+ ## Setup
22
+
23
+ First, require the test helper in your test file or `test_helper.rb`:
24
+
25
+ ```ruby
26
+ require "ratatui_ruby/test_helper"
27
+ require "minitest/autorun" # or your preferred test framework
28
+ ```
29
+
30
+ Then, include the module in your test class:
31
+
32
+ ```ruby
33
+ class MyApplicationTest < Minitest::Test
34
+ include RatatuiRuby::TestHelper
35
+ # ...
36
+ end
37
+ ```
38
+
39
+ ## Basic Usage
40
+
41
+ ### `with_test_terminal`
42
+
43
+ Wrap your test assertions in `with_test_terminal`. This sets up a temporary, in-memory backend for Ratatui to draw to, instead of the real terminal. It automatically cleans up afterwards.
44
+
45
+ ```ruby
46
+ def test_rendering
47
+ # Create a 80x24 terminal
48
+ with_test_terminal(80, 24) do
49
+ # 1. Instantiate your app/component
50
+ widget = RatatuiRuby::Paragraph.new(text: "Hello World")
51
+
52
+ # 2. Render it
53
+ RatatuiRuby.draw(widget)
54
+
55
+ # 3. Assert on the output
56
+ assert_includes buffer_content[0], "Hello World"
57
+ end
58
+ end
59
+ ```
60
+
61
+ ### `buffer_content`
62
+
63
+ Returns the current state of the terminal as an Array of Strings. Useful for verifying that specific text appears where you expect it.
64
+
65
+ ```ruby
66
+ rows = buffer_content
67
+ assert_equal "Title", rows[0].strip
68
+ assert_match /Results: \d+/, rows[2]
69
+ ```
70
+
71
+ ### `cursor_position`
72
+
73
+ Returns the current cursor coordinates as `{ x: Integer, y: Integer }`. Useful for forms or ensuring focus is correct.
74
+
75
+ ```ruby
76
+ pos = cursor_position
77
+ assert_equal 5, pos[:x]
78
+ assert_equal 2, pos[:y]
79
+ ```
80
+
81
+ ### `inject_event`
82
+
83
+ Injects a mock event into the event queue. This is the preferred way to simulate user input instead of stubbing `poll_event`.
84
+
85
+ ```ruby
86
+ # Simulate 'q' key press
87
+ inject_event("key", { code: "q" })
88
+
89
+ # Now poll_event will return the 'q' key event
90
+ event = RatatuiRuby.poll_event
91
+ assert_equal "q", event[:code]
92
+ ```
93
+
94
+ ## Example
95
+
96
+ Be sure to check out the [examples directory](../examples/) in the repository, which contains several fully tested example applications showcasing these patterns.
@@ -0,0 +1,100 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Ruby Frontend Design (`ratatui_ruby`)
7
+
8
+ This document describes the design philosophy and structure of the Ruby layer in `ratatui_ruby`.
9
+
10
+ ## Core Philosophy: Data-Driven UI
11
+
12
+ The Ruby frontend is designed as a **thin, declarative layer** over the Rust backend. It uses an **Immediate Mode** paradigm where the user constructs a tree of pure data objects every frame to represent the desired UI state.
13
+
14
+ ### 1. View Tree as Data
15
+
16
+ Unlike traditional OO GUI toolkits (like Qt or Swing) where widgets are retained objects with internal state, `ratatui_ruby` widgets are immutable value objects.
17
+
18
+ * Implemented using Ruby 3.2+ `Data` classes.
19
+ * Located in `lib/ratatui_ruby/schema/`.
20
+ * These objects act as a Schema or Interface Definition Language (IDL) between Ruby and Rust.
21
+
22
+ **Example:**
23
+ ```ruby
24
+ # This is just a piece of data, not a "live" widget
25
+ paragraph = RatatuiRuby::Paragraph.new(
26
+ text: "Hello World",
27
+ style: RatatuiRuby::Style.new(fg: :red),
28
+ block: nil
29
+ )
30
+ ```
31
+
32
+ ### 2. Immediate Mode Rendering
33
+
34
+ The application loop typically looks like this:
35
+
36
+ 1. **Poll Event**: Ruby asks Rust for the next event.
37
+ 2. **Update State**: Ruby application code updates its own domain state (e.g., `counter += 1`).
38
+ 3. **Render**: Ruby constructs a fresh View Tree based on the current domain state and passes the root node to `RatatuiRuby.draw`.
39
+
40
+ ```ruby
41
+ loop do
42
+ # 1. & 2. Handle events and update state
43
+ event = RatatuiRuby.poll_event
44
+ break if event[:type] == :key && event[:code] == "esc"
45
+
46
+ # 3. Construct View Tree
47
+ ui = RatatuiRuby::Paragraph.new(text: "Time: #{Time.now}")
48
+
49
+ # 4. Draw
50
+ RatatuiRuby.draw(ui)
51
+ end
52
+ ```
53
+
54
+ ### 3. No render logic in Ruby
55
+
56
+ The Ruby classes in `lib/ratatui_ruby/schema/` should **not** contain rendering logic. They are strictly for structural definition and validation. All rendering logic resides in the Rust extension (`ext/ratatui_ruby/`), which walks this Ruby object tree and produces Ratatui primitives.
57
+
58
+ ## Adding a New Widget
59
+
60
+ To add a new widget to the Ruby frontend:
61
+
62
+ 1. Define the class in `lib/ratatui_ruby/schema/`.
63
+ 2. Use `Data.define`.
64
+ 3. Ensure attribute names match what the Rust rendering logic expects (see `ext/ratatui_ruby/src/widgets/`).
65
+
66
+ ```ruby
67
+ # lib/ratatui_ruby/schema/my_widget.rb
68
+ module RatatuiRuby
69
+ # A widget that does something specific.
70
+ #
71
+ # [some_property] The description of the property.
72
+ # [style] The style to apply.
73
+ # [block] Optional block widget.
74
+ class MyWidget < Data.define(:some_property, :style, :block)
75
+ # Creates a new MyWidget.
76
+ #
77
+ # [some_property] The description of the property.
78
+ # [style] The style to apply.
79
+ # [block] Optional block widget.
80
+ def initialize(some_property:, style: nil, block: nil)
81
+ super
82
+ end
83
+ end
84
+ end
85
+ ```
86
+
87
+ And define the types in the corresponding `.rbs` file:
88
+
89
+ ```rbs
90
+ # sig/ratatui_ruby/schema/my_widget.rbs
91
+ module RatatuiRuby
92
+ class MyWidget < Data
93
+ attr_reader some_property: String
94
+ attr_reader style: Style?
95
+ attr_reader block: Block?
96
+
97
+ def self.new: (some_property: String, ?style: Style?, ?block: Block?) -> MyWidget
98
+ end
99
+ end
100
+ ```
@@ -0,0 +1,61 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Rust Backend Design (`ratatui_ruby` extension)
7
+
8
+ This document describes the internal architecture of the `ratatui_ruby` Rust extension.
9
+
10
+ ## Architecture Guidelines
11
+
12
+ The project follows a **Structured Design** approach, separating concerns into modules to improve cohesiveness and testability.
13
+
14
+ ### Core Principles
15
+
16
+ 1. **Single Generic Renderer**: The backend implements a single generic renderer that accepts a Ruby `Value` representing the root of the view tree.
17
+ 2. **No Custom Rust Structs for UI**: Do not define custom Rust structs that mirror Ruby UI components. Instead, extract data directly from Ruby objects using `funcall`.
18
+ 3. **Dynamic Dispatch**: Use `value.class().name()` (e.g., `"RatatuiRuby::Paragraph"`) to dynamically dispatch rendering logic to the appropriate widget module.
19
+ 4. **Immediate Mode**: The renderer traverses the Ruby object tree every frame and rebuilds the Ratatui widget tree on the fly.
20
+
21
+ ### Module Structure
22
+
23
+ The Rust extension is located in `ext/ratatui_ruby/src/` and is organized as follows:
24
+
25
+ * **`lib.rs`**: The entry point for the compiled extension. It defines the Ruby module structure using `magnus` and exports public functions (`init_terminal`, `draw`, `poll_event`). It wires together the submodules.
26
+ * **`terminal.rs`**: Encapsulates the global `TERMINAL` state (mutex-wrapped `CrosstermBackend`). It provides functions to initialize and restore the terminal to raw mode.
27
+ * **`events.rs`**: Handles keyboard input polling and mapping Crossterm events to Ruby hashes.
28
+ * **`style.rs`**: Provides pure functions for parsing styling information (Colors, Styles, Blocks) from Ruby values.
29
+ * **`rendering.rs`**: The central dispatcher for the render loop. It takes the top-level Ruby View Tree node and recursively delegates to specific widget implementations based on the Ruby class name.
30
+ * **`widgets/`**: A directory containing individual modules for each Ratatui widget (e.g., `paragraph.rs`, `list.rs`).
31
+
32
+ ### Adding a New Widget
33
+
34
+ To add a new widget:
35
+
36
+ 1. Create a new file `src/widgets/my_widget.rs`.
37
+ 2. Implement a public `render` function:
38
+ ```rust
39
+ /// Renders the widget to the given area.
40
+ ///
41
+ /// # Arguments
42
+ ///
43
+ /// * `frame` - The Ratatui frame to render to.
44
+ /// * `area` - The rectangular area within the frame to draw the widget.
45
+ /// * `node` - The Ruby object (Value) containing the widget's properties.
46
+ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error>
47
+ ```
48
+ 3. Inside `render`:
49
+ * Extract properties from the `node` (Ruby value) using `.funcall("method_name", ())?`.
50
+ * Construct the Ratatui widget.
51
+ * Render it using `frame.render_widget`.
52
+ 4. Register the module in `src/widgets/mod.rs`.
53
+ 5. Add a dispatch arm in `src/rendering.rs` matching the Ruby class name (e.g., `RatatuiRuby::MyWidget`).
54
+
55
+ ### Testing Strategy
56
+
57
+ * **Unit Tests (`cargo test`)**:
58
+ * **Logic**: Test pure logic like `parse_color` in `style.rs` without needing a terminal or Ruby VM if possible (though `magnus::Value` usually requires it).
59
+ * **Rendering**: Verify that widgets render *something* to a buffer. Ratatui's `TestBackend` or `Buffer` can be used to assert that cells are filled.
60
+ * **Integration Tests (`rake test`)**:
61
+ * Run Ruby scripts that exercise the full stack. Verify no crashes and expected return values.
@@ -0,0 +1,11 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Design Documentation
7
+
8
+ This directory contains detailed design documents for the `ratatui_ruby` project.
9
+
10
+ * [Rust Backend Design](design/rust_backend.md): Details on the internal architecture of the Rust extension (`ext/ratatui_ruby`), including module structure, rendering pipeline, and widget implementation guide.
11
+ * [Ruby Frontend Design](design/ruby_frontend.md): Explains the Data-Driven UI, Immediate Mode paradigm, and the View Tree structure.
@@ -0,0 +1,16 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+ # **ratatui_ruby** Contributors’ Documentation
6
+
7
+
8
+ ## Documentation for Contributors
9
+
10
+ - [Contributing Guidelines](../../CONTRIBUTING.md)
11
+ - [The Design of **ratatui_ruby**](./design.md)
12
+
13
+ ## Documentation for Users
14
+
15
+ - [README](../../README.md)
16
+ - [More Documentation for Users](../index.md)
data/docs/index.md ADDED
@@ -0,0 +1,18 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+ # **ratatui_ruby** Documentation
6
+
7
+
8
+ ## Documentation for Users
9
+
10
+ - [README](../README.md)
11
+ - [Quickstart](./quickstart.md)
12
+ - [Testing Your Application](./application_testing.md)
13
+
14
+
15
+ ## Documentation for Contributors
16
+
17
+ - [Contributing Guidelines](../CONTRIBUTING.md)
18
+ - [More Documentation for Contributors](./contributors/index.md)
@@ -0,0 +1,126 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+ # Quickstart
6
+
7
+ Welcome to **ratatui_ruby**! This guide will help you get up and running with your first Terminal User Interface in Ruby.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'ratatui_ruby'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ ```bash
20
+ bundle install
21
+ ```
22
+
23
+ Or install it yourself as:
24
+
25
+ ```bash
26
+ gem install ratatui_ruby
27
+ ```
28
+
29
+ ## Basic Application
30
+
31
+ Here is a "Hello World" application that demonstrates the core lifecycle of a **ratatui_ruby** app.
32
+
33
+ ```ruby
34
+ require "ratatui_ruby"
35
+
36
+ # 1. Initialize the terminal
37
+ RatatuiRuby.init_terminal
38
+
39
+ begin
40
+ # The Main Loop
41
+ loop do
42
+ # 2. Create your UI (Immediate Mode)
43
+ # We define a Paragraph widget inside a Block with a title and borders.
44
+ view = RatatuiRuby::Paragraph.new(
45
+ text: "Hello, Ratatui! Press 'q' to quit.",
46
+ block: RatatuiRuby::Block.new(
47
+ title: "My First App",
48
+ borders: [:all],
49
+ border_style: "cyan"
50
+ ),
51
+ alignment: :center
52
+ )
53
+
54
+ # 3. Draw the UI
55
+ RatatuiRuby.draw(view)
56
+
57
+ # 4. Poll for events
58
+ event = RatatuiRuby.poll_event
59
+ if event && event[:type] == :key && event[:code] == "q"
60
+ break
61
+ end
62
+ end
63
+ ensure
64
+ # 5. Restore the terminal to its original state
65
+ RatatuiRuby.restore_terminal
66
+ end
67
+ ```
68
+
69
+ ### How it works
70
+
71
+ 1. **`RatatuiRuby.init_terminal`**: Enters raw mode and switches to the alternate screen.
72
+ 2. **Immediate Mode UI**: On every iteration of the loop, you describe what the UI should look like by creating `Data` objects (like `Paragraph` and `Block`).
73
+ 3. **`RatatuiRuby.draw(view)`**: The Ruby UI tree is passed to the Rust backend, which renders it to the terminal.
74
+ 4. **`RatatuiRuby.poll_event`**: Checks for keyboard, mouse, or resize events.
75
+ 5. **`RatatuiRuby.restore_terminal`**: Crucial for leaving raw mode and returning the user to their shell properly. Always wrap your loop in a `begin...ensure` block to guarantee this runs.
76
+ 6. **`sleep 0.05`**: In a real app, you'd want to control your frame rate to avoid consuming 100% CPU.
77
+
78
+ ## Examples
79
+
80
+ To see more complex layouts and widget usage, check out the `examples/` directory in the repository.
81
+
82
+ ### [Analytics](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/analytics.rb)
83
+ Demonstrates the use of `Tabs` and `BarChart` widgets with a simple data-switching mechanism.
84
+
85
+ ![Analytics Screenshot](./images/examples-analytics.rb.png)
86
+
87
+ ### [Box Demo](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/box_demo.rb)
88
+ A simple demonstration of `Block` and `Paragraph` widgets, reacting to arrow key presses to change colors.
89
+
90
+ ![Box Demo Screenshot](./images/examples-box_demo.rb.png)
91
+
92
+ ### [Dashboard](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/dashboard.rb)
93
+ Uses `Layout`, `List`, and `Paragraph` to create a classic sidebar-and-content interface.
94
+
95
+ ![Dashboard Screenshot](./images/examples-dashboard.rb.png)
96
+
97
+ ### [Login Form](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/login_form.rb)
98
+ Shows how to use `Overlay`, `Center`, and `Cursor` to build a modal login form with text input.
99
+
100
+ ![Login Form Screenshot](./images/examples-login_form.rb.png)
101
+
102
+ ### [Map Demo](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/map_demo.rb)
103
+ Exhibits the `Canvas` widget's power, rendering a world map along with animated circles and lines.
104
+
105
+ ![Map Demo Screenshot](./images/examples-map_demo.rb.png)
106
+
107
+ ### [Mouse Events](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/mouse_events.rb)
108
+ Detailed plumbing of mouse events, including clicks, drags, and movement tracking.
109
+
110
+ ![Mouse Events Screenshot](./images/examples-mouse_events.rb.png)
111
+
112
+ ### [Scrollbar Demo](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/scrollbar_demo.rb)
113
+ A simple example of integrating the `Scrollbar` widget and handling mouse wheel events for scrolling.
114
+
115
+ ![Scrollbar Demo Screenshot](./images/examples-scrollbar_demo.rb.png)
116
+
117
+ ### [Stock Ticker](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/stock_ticker.rb)
118
+ Utilizes `Sparkline` and `LineChart` widgets to visualize real-time (simulated) data.
119
+
120
+ ![Stock Ticker Screenshot](./images/examples-stock_ticker.rb.png)
121
+
122
+ ### [System Monitor](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/system_monitor.rb)
123
+ Combines `Table` and `Gauge` widgets in a vertical layout to create a functional system overview.
124
+
125
+ ![System Monitor Screenshot](./images/examples-system_monitor.rb.png)
126
+
@@ -0,0 +1,87 @@
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 "../lib/ratatui_ruby"
7
+
8
+ # Analytics Dashboard Example
9
+ # Demonstrates Tabs and BarChart widgets.
10
+
11
+ class AnalyticsApp
12
+ def initialize
13
+ @selected_tab = 0
14
+ @tabs = ["Revenue", "Traffic", "Errors"]
15
+ end
16
+
17
+ def run
18
+ RatatuiRuby.init_terminal
19
+ begin
20
+ loop do
21
+ render
22
+ break if handle_input == :quit
23
+ sleep 0.05
24
+ end
25
+ ensure
26
+ RatatuiRuby.restore_terminal
27
+ end
28
+ end
29
+
30
+ def render
31
+ # Data for different tabs
32
+ data = case @selected_tab
33
+ when 0 # Revenue
34
+ { "Q1" => 50, "Q2" => 80 }
35
+ when 1 # Traffic
36
+ { "Mon" => 120, "Tue" => 150 }
37
+ when 2 # Errors
38
+ { "DB" => 5, "UI" => 2 }
39
+ end
40
+
41
+ style = case @selected_tab
42
+ when 0 then RatatuiRuby::Style.new(fg: "green")
43
+ when 1 then RatatuiRuby::Style.new(fg: "blue")
44
+ when 2 then RatatuiRuby::Style.new(fg: "red")
45
+ end
46
+
47
+ # Build the UI
48
+ ui = RatatuiRuby::Layout.new(
49
+ direction: :vertical,
50
+ constraints: [
51
+ RatatuiRuby::Constraint.new(type: :length, value: 3),
52
+ RatatuiRuby::Constraint.new(type: :min, value: 0),
53
+ ],
54
+ children: [
55
+ RatatuiRuby::Tabs.new(
56
+ titles: @tabs,
57
+ selected_index: @selected_tab,
58
+ block: RatatuiRuby::Block.new(title: "Views", borders: [:all])
59
+ ),
60
+ RatatuiRuby::BarChart.new(
61
+ data:,
62
+ bar_width: 10,
63
+ style:,
64
+ block: RatatuiRuby::Block.new(title: "Analytics: #{@tabs[@selected_tab]}", borders: [:all])
65
+ ),
66
+ ]
67
+ )
68
+
69
+ RatatuiRuby.draw(ui)
70
+ end
71
+
72
+ def handle_input
73
+ event = RatatuiRuby.poll_event
74
+ if event
75
+ case event[:code]
76
+ when "q"
77
+ :quit
78
+ when "right"
79
+ @selected_tab = (@selected_tab + 1) % @tabs.size
80
+ when "left"
81
+ @selected_tab = (@selected_tab - 1) % @tabs.size
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ AnalyticsApp.new.run if __FILE__ == $0