ratatui_interact/lib.rs
1//! # TUI Extension
2//!
3//! Reusable UI components extending ratatui with focus management and mouse support.
4//!
5//! This crate provides interactive UI components that integrate with ratatui's
6//! widget system while adding:
7//!
8//! - **Focus Management**: Tab navigation between components
9//! - **Click Regions**: Mouse click support with hit-testing
10//! - **Composition**: Container-based component hierarchies for dialogs
11//!
12//! ## Quick Start
13//!
14//! ```rust,ignore
15//! use ratatui_interact::prelude::*;
16//!
17//! // Create component state
18//! let mut checkbox_state = CheckBoxState::new(false);
19//! let mut input_state = InputState::new("Hello");
20//! let button_state = ButtonState::enabled();
21//!
22//! // Use in your render function
23//! fn render(frame: &mut Frame, area: Rect) {
24//! let checkbox = CheckBox::new("Enable", &checkbox_state);
25//! let input = Input::new(&input_state).label("Name");
26//! let button = Button::new("Submit", &button_state);
27//!
28//! // Render and get click regions
29//! let cb_region = checkbox.render_stateful(area, frame.buffer_mut());
30//! let input_region = input.render_stateful(frame, input_area);
31//! let btn_region = button.render_stateful(button_area, frame.buffer_mut());
32//! }
33//! ```
34//!
35//! ## Components
36//!
37//! ### CheckBox
38//!
39//! A toggleable checkbox with customizable symbols:
40//!
41//! ```rust
42//! use ratatui_interact::components::{CheckBox, CheckBoxState, CheckBoxStyle};
43//!
44//! let mut state = CheckBoxState::new(false);
45//! let checkbox = CheckBox::new("Dark mode", &state)
46//! .style(CheckBoxStyle::unicode()); // ☑ / ☐
47//!
48//! // Toggle on user action
49//! state.toggle();
50//! ```
51//!
52//! ### Input
53//!
54//! A text input field with cursor and editing support:
55//!
56//! ```rust
57//! use ratatui_interact::components::{Input, InputState};
58//!
59//! let mut state = InputState::new("Initial text");
60//!
61//! // Edit text
62//! state.insert_char('!');
63//! state.move_left();
64//! state.delete_char_backward();
65//! ```
66//!
67//! ### Button
68//!
69//! Buttons with multiple display variants:
70//!
71//! ```rust
72//! use ratatui_interact::components::{Button, ButtonState, ButtonVariant, ButtonStyle};
73//!
74//! let state = ButtonState::enabled();
75//!
76//! // Different styles
77//! let simple = Button::new("OK", &state);
78//! let with_icon = Button::new("Save", &state).icon("💾");
79//! let block_style = Button::new("Submit", &state).variant(ButtonVariant::Block);
80//! let toggle = Button::new("Active", &ButtonState::toggled(true))
81//! .variant(ButtonVariant::Toggle);
82//! ```
83//!
84//! ### PopupDialog
85//!
86//! A container for popup dialogs with focus management:
87//!
88//! ```rust,ignore
89//! use ratatui_interact::components::{DialogConfig, DialogState, PopupDialog};
90//! use ratatui_interact::traits::ContainerAction;
91//!
92//! let config = DialogConfig::new("Settings")
93//! .width_percent(50)
94//! .ok_cancel();
95//!
96//! let mut state = DialogState::new(MyContent::default());
97//! state.show();
98//!
99//! let mut dialog = PopupDialog::new(&config, &mut state, |frame, area, content| {
100//! // Render dialog content
101//! });
102//! dialog.render(frame);
103//!
104//! // Handle events
105//! match dialog.handle_key(key_event) {
106//! EventResult::Action(ContainerAction::Submit) => { /* save */ }
107//! EventResult::Action(ContainerAction::Close) => { /* cancel */ }
108//! _ => {}
109//! }
110//! ```
111//!
112//! ## Focus Management
113//!
114//! The `FocusManager` handles Tab navigation:
115//!
116//! ```rust
117//! use ratatui_interact::state::FocusManager;
118//!
119//! #[derive(Clone, PartialEq, Eq, Hash)]
120//! enum Element { Name, Email, Submit }
121//!
122//! let mut focus = FocusManager::new();
123//! focus.register(Element::Name);
124//! focus.register(Element::Email);
125//! focus.register(Element::Submit);
126//!
127//! // Navigate
128//! focus.next(); // Name -> Email
129//! focus.prev(); // Email -> Name
130//! focus.set(Element::Submit); // Jump to Submit
131//! ```
132//!
133//! ## Click Regions
134//!
135//! Track clickable areas with `ClickRegionRegistry`:
136//!
137//! ```rust
138//! use ratatui_interact::traits::ClickRegionRegistry;
139//! use ratatui::layout::Rect;
140//!
141//! let mut registry: ClickRegionRegistry<&str> = ClickRegionRegistry::new();
142//!
143//! // Register during render
144//! registry.clear();
145//! registry.register(Rect::new(0, 0, 10, 1), "button1");
146//! registry.register(Rect::new(15, 0, 10, 1), "button2");
147//!
148//! // Check clicks during event handling
149//! if let Some(clicked) = registry.handle_click(5, 0) {
150//! println!("Clicked: {}", clicked);
151//! }
152//! ```
153
154pub mod components;
155pub mod events;
156pub mod state;
157pub mod traits;
158pub mod utils;
159
160/// Prelude for convenient imports.
161///
162/// Import everything commonly needed:
163///
164/// ```rust
165/// use ratatui_interact::prelude::*;
166/// ```
167pub mod prelude {
168 // Interactive Components
169 pub use crate::components::{
170 Button, ButtonAction, ButtonState, ButtonStyle, ButtonVariant, CheckBox, CheckBoxAction,
171 CheckBoxState, CheckBoxStyle, ContextMenu, ContextMenuAction, ContextMenuItem,
172 ContextMenuState, ContextMenuStyle, DialogConfig, DialogFocusTarget, DialogState, Input,
173 InputAction, InputState, InputStyle, Menu, MenuBar, MenuBarAction, MenuBarClickTarget,
174 MenuBarItem, MenuBarState, MenuBarStyle, PopupDialog, calculate_menu_bar_height,
175 calculate_menu_height, handle_context_menu_key, handle_context_menu_mouse,
176 handle_menu_bar_key, handle_menu_bar_mouse, is_context_menu_trigger,
177 menu_bar_dropdown_height,
178 };
179
180 // Display Components
181 pub use crate::components::{
182 ParagraphExt, Progress, ProgressStyle, Toast, ToastState, ToastStyle,
183 };
184
185 // Utility Components
186 pub use crate::components::{MousePointer, MousePointerState, MousePointerStyle};
187
188 // Navigation Components
189 pub use crate::components::{
190 EntryType, FileEntry, FileExplorer, FileExplorerState, FileExplorerStyle, ListPicker,
191 ListPickerState, ListPickerStyle, key_hints_footer,
192 };
193
194 // Tree Components
195 pub use crate::components::{
196 FlatNode, TreeNode, TreeStyle, TreeView, TreeViewState, get_selected_id,
197 };
198
199 // Layout Components
200 pub use crate::components::{
201 Orientation, SplitPane, SplitPaneAction, SplitPaneState, SplitPaneStyle,
202 handle_split_pane_key, handle_split_pane_mouse,
203 };
204
205 // Viewer Components
206 pub use crate::components::{
207 DiffData, DiffHunk, DiffLine, DiffLineType, DiffViewMode, DiffViewer, DiffViewerAction,
208 DiffViewerState, DiffViewerStyle, LogViewer, LogViewerState, LogViewerStyle, SearchState,
209 Step, StepDisplay, StepDisplayState, StepDisplayStyle, StepStatus, SubStep,
210 handle_diff_viewer_key, handle_diff_viewer_mouse, step_display_height,
211 };
212
213 // Dialog Components
214 pub use crate::components::{
215 CategoryClickRegion, HotkeyCategory, HotkeyClickRegion, HotkeyDialog, HotkeyDialogAction,
216 HotkeyDialogState, HotkeyDialogStyle, HotkeyEntryData, HotkeyFocus, HotkeyProvider,
217 handle_hotkey_dialog_key, handle_hotkey_dialog_mouse, render_hotkey_dialog,
218 };
219
220 // Utilities
221 pub use crate::utils::{
222 clean_for_display, format_size, pad_to_width, parse_ansi_to_spans, truncate_to_width,
223 };
224
225 // Traits
226 pub use crate::traits::{
227 ClickRegion, ClickRegionRegistry, Clickable, Container, ContainerAction, EventResult,
228 FocusId, Focusable, PopupContainer,
229 };
230
231 // State management
232 pub use crate::state::FocusManager;
233
234 // Event helpers
235 pub use crate::events::{
236 get_char, get_mouse_pos, get_scroll, has_alt, has_ctrl, has_shift, is_activate_key,
237 is_backspace, is_backtab, is_close_key, is_ctrl_a, is_ctrl_e, is_ctrl_k, is_ctrl_u,
238 is_ctrl_w, is_delete, is_end, is_enter, is_home, is_left_click, is_mouse_drag,
239 is_mouse_move, is_navigation_key, is_right_click, is_space, is_tab,
240 };
241}
242
243#[cfg(test)]
244mod tests {
245 use super::prelude::*;
246
247 #[test]
248 fn test_prelude_imports() {
249 // Verify all prelude items are accessible
250 let _: CheckBoxState = CheckBoxState::new(false);
251 let _: InputState = InputState::new("");
252 let _: ButtonState = ButtonState::enabled();
253 let _: FocusManager<usize> = FocusManager::new();
254 let _: ClickRegionRegistry<()> = ClickRegionRegistry::new();
255 }
256}