crates/flotilla-tui/src/widgets/mod.rs
Line | Count | Source |
1 | | pub mod action_menu; |
2 | | pub mod branch_input; |
3 | | pub mod close_confirm; |
4 | | pub mod columns; |
5 | | pub mod command_palette; |
6 | | pub mod delete_confirm; |
7 | | pub mod event_log; |
8 | | pub mod file_picker; |
9 | | pub mod help; |
10 | | pub mod issue_search; |
11 | | pub mod overview_page; |
12 | | pub mod preview_panel; |
13 | | pub mod repo_page; |
14 | | pub mod screen; |
15 | | pub mod section_table; |
16 | | pub mod split_table; |
17 | | pub mod status_bar_widget; |
18 | | pub mod tabs; |
19 | | |
20 | | use std::{any::Any, collections::HashMap}; |
21 | | |
22 | | use crossterm::event::{KeyEvent, MouseEvent}; |
23 | | use flotilla_core::config::ConfigStore; |
24 | | use flotilla_protocol::{HostName, ProvisioningTarget, RepoIdentity}; |
25 | | use ratatui::{layout::Rect, Frame}; |
26 | | |
27 | | use crate::{ |
28 | | app::{CommandQueue, InFlightCommand, TuiModel, UiState}, |
29 | | binding_table::{KeyBindingMode, StatusFragment}, |
30 | | keymap::{Action, Keymap}, |
31 | | theme::Theme, |
32 | | }; |
33 | | |
34 | | /// Human-readable label and protocol key for each provider category. |
35 | | /// |
36 | | /// Shared by widgets that render provider status tables (event log, work-item table). |
37 | | pub(crate) const PROVIDER_CATEGORIES: [(&str, &str); 9] = [ |
38 | | ("VCS", "vcs"), |
39 | | ("Checkout mgr", "checkout_manager"), |
40 | | ("Change request", "change_request"), |
41 | | ("Issue tracker", "issue_tracker"), |
42 | | ("Cloud agents", "cloud_agent"), |
43 | | ("AI utility", "ai_utility"), |
44 | | ("Workspace mgr", "workspace_manager"), |
45 | | ("Terminal pool", "terminal_pool"), |
46 | | ("Environment", "environment_provider"), |
47 | | ]; |
48 | | |
49 | | /// App-level effects that widgets can request. Processed by the event |
50 | | /// loop after widget dispatch — widgets declare intent, the app executes. |
51 | | #[derive(Debug, Clone)] |
52 | | pub enum AppAction { |
53 | | Quit, |
54 | | CancelCommand(u64), |
55 | | CycleTheme, |
56 | | SetTheme(String), |
57 | | CycleLayout, |
58 | | SetLayout(String), |
59 | | CycleHost, |
60 | | SetTarget(String), |
61 | | ToggleDebug, |
62 | | ToggleStatusBarKeys, |
63 | | ToggleProviders, |
64 | | ToggleMultiSelect, |
65 | | OpenActionMenu, |
66 | | ActionEnter, |
67 | | StatusBarKeyPress { code: crossterm::event::KeyCode, modifiers: crossterm::event::KeyModifiers }, |
68 | | ClearError(usize), |
69 | | SwitchToConfig, |
70 | | SwitchToRepo(usize), |
71 | | SaveTabOrder, |
72 | | OpenFilePicker, |
73 | | PrevTab, |
74 | | NextTab, |
75 | | MoveTabLeft, |
76 | | MoveTabRight, |
77 | | Refresh, |
78 | | ShowStatus(String), |
79 | | SetSearchQuery { repo: RepoIdentity, query: String }, |
80 | | ClearSearchQuery { repo: RepoIdentity }, |
81 | | } |
82 | | |
83 | | /// Result of handling an event in a widget. |
84 | | pub enum Outcome { |
85 | | /// Event was handled; no further dispatch needed. |
86 | | Consumed, |
87 | | /// Event was not handled; try the next widget in the stack. |
88 | | Ignored, |
89 | | /// Widget is done; pop it from the stack. |
90 | | Finished, |
91 | | /// Push a new widget on top of the current one. |
92 | | Push(Box<dyn InteractiveWidget>), |
93 | | /// Pop the current widget and push a replacement. |
94 | | Swap(Box<dyn InteractiveWidget>), |
95 | | } |
96 | | |
97 | | /// Mutable context provided to widgets during event handling. |
98 | | pub struct WidgetContext<'a> { |
99 | | pub model: &'a TuiModel, |
100 | | pub keymap: &'a Keymap, |
101 | | pub config: &'a ConfigStore, |
102 | | pub in_flight: &'a HashMap<u64, InFlightCommand>, |
103 | | pub provisioning_target: &'a ProvisioningTarget, |
104 | | pub my_host: Option<HostName>, |
105 | | pub active_repo: usize, |
106 | | pub repo_order: &'a [RepoIdentity], |
107 | | pub commands: &'a mut CommandQueue, |
108 | | pub is_config: &'a mut bool, |
109 | | pub active_repo_is_remote_only: bool, |
110 | | pub app_actions: Vec<AppAction>, |
111 | | } |
112 | | |
113 | | /// Context provided to widgets during rendering. |
114 | | /// |
115 | | /// Mutable fields (`ui`) are needed because the base layer rendering updates |
116 | | /// table state and layout areas. |
117 | | pub struct RenderContext<'a> { |
118 | | pub model: &'a TuiModel, |
119 | | pub ui: &'a mut UiState, |
120 | | pub theme: &'a Theme, |
121 | | pub keymap: &'a Keymap, |
122 | | pub in_flight: &'a HashMap<u64, InFlightCommand>, |
123 | | } |
124 | | |
125 | | /// A self-contained interactive widget that handles events and renders itself. |
126 | | pub trait InteractiveWidget { |
127 | | /// Handle a resolved keymap action. |
128 | | fn handle_action(&mut self, action: Action, ctx: &mut WidgetContext) -> Outcome; |
129 | | |
130 | | /// Handle a raw key event (for text input widgets that need every keystroke). |
131 | 2 | fn handle_raw_key(&mut self, _key: KeyEvent, _ctx: &mut WidgetContext) -> Outcome { |
132 | 2 | Outcome::Ignored |
133 | 2 | } |
134 | | |
135 | | /// Handle a mouse event. |
136 | 1 | fn handle_mouse(&mut self, _mouse: MouseEvent, _ctx: &mut WidgetContext) -> Outcome { |
137 | 1 | Outcome::Ignored |
138 | 1 | } |
139 | | |
140 | | /// Render the widget into the given area. |
141 | | fn render(&mut self, frame: &mut Frame, area: Rect, ctx: &mut RenderContext); |
142 | | |
143 | | /// The binding mode for keymap resolution. |
144 | | fn binding_mode(&self) -> KeyBindingMode; |
145 | | |
146 | | /// Whether this widget needs raw key events instead of resolved actions. |
147 | 27 | fn captures_raw_keys(&self) -> bool { |
148 | 27 | false |
149 | 27 | } |
150 | | |
151 | | /// Widget-provided status content for the status bar. |
152 | | /// |
153 | | /// The default returns an empty `StatusFragment`. Modal widgets should |
154 | | /// override this to provide mode-specific labels or input text. |
155 | 0 | fn status_fragment(&self) -> StatusFragment { |
156 | 0 | StatusFragment::default() |
157 | 0 | } |
158 | | |
159 | | /// Downcast support for reading widget state from outside the trait. |
160 | | fn as_any(&self) -> &dyn Any; |
161 | | |
162 | | /// Downcast support for updating widget state from outside the trait. |
163 | | fn as_any_mut(&mut self) -> &mut dyn Any; |
164 | | } |