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, PopupDialog, calculate_menu_height,
174 handle_context_menu_key, handle_context_menu_mouse, is_context_menu_trigger,
175 };
176
177 // Display Components
178 pub use crate::components::{
179 ParagraphExt, Progress, ProgressStyle, Toast, ToastState, ToastStyle,
180 };
181
182 // Navigation Components
183 pub use crate::components::{
184 EntryType, FileEntry, FileExplorer, FileExplorerState, FileExplorerStyle, ListPicker,
185 ListPickerState, ListPickerStyle, key_hints_footer,
186 };
187
188 // Tree Components
189 pub use crate::components::{
190 FlatNode, TreeNode, TreeStyle, TreeView, TreeViewState, get_selected_id,
191 };
192
193 // Layout Components
194 pub use crate::components::{
195 Orientation, SplitPane, SplitPaneAction, SplitPaneState, SplitPaneStyle,
196 handle_split_pane_key, handle_split_pane_mouse,
197 };
198
199 // Viewer Components
200 pub use crate::components::{
201 DiffData, DiffHunk, DiffLine, DiffLineType, DiffViewMode, DiffViewer, DiffViewerAction,
202 DiffViewerState, DiffViewerStyle, LogViewer, LogViewerState, LogViewerStyle, SearchState,
203 Step, StepDisplay, StepDisplayState, StepDisplayStyle, StepStatus, SubStep,
204 handle_diff_viewer_key, handle_diff_viewer_mouse, step_display_height,
205 };
206
207 // Dialog Components
208 pub use crate::components::{
209 CategoryClickRegion, HotkeyCategory, HotkeyClickRegion, HotkeyDialog, HotkeyDialogAction,
210 HotkeyDialogState, HotkeyDialogStyle, HotkeyEntryData, HotkeyFocus, HotkeyProvider,
211 handle_hotkey_dialog_key, handle_hotkey_dialog_mouse, render_hotkey_dialog,
212 };
213
214 // Utilities
215 pub use crate::utils::{
216 clean_for_display, format_size, pad_to_width, parse_ansi_to_spans, truncate_to_width,
217 };
218
219 // Traits
220 pub use crate::traits::{
221 ClickRegion, ClickRegionRegistry, Clickable, Container, ContainerAction, EventResult,
222 FocusId, Focusable, PopupContainer,
223 };
224
225 // State management
226 pub use crate::state::FocusManager;
227
228 // Event helpers
229 pub use crate::events::{
230 get_char, get_mouse_pos, get_scroll, has_alt, has_ctrl, has_shift, is_activate_key,
231 is_backspace, is_backtab, is_close_key, is_ctrl_a, is_ctrl_e, is_ctrl_k, is_ctrl_u,
232 is_ctrl_w, is_delete, is_end, is_enter, is_home, is_left_click, is_navigation_key,
233 is_right_click, is_space, is_tab,
234 };
235}
236
237#[cfg(test)]
238mod tests {
239 use super::prelude::*;
240
241 #[test]
242 fn test_prelude_imports() {
243 // Verify all prelude items are accessible
244 let _: CheckBoxState = CheckBoxState::new(false);
245 let _: InputState = InputState::new("");
246 let _: ButtonState = ButtonState::enabled();
247 let _: FocusManager<usize> = FocusManager::new();
248 let _: ClickRegionRegistry<()> = ClickRegionRegistry::new();
249 }
250}