Skip to main content

oo_ide/widgets/
focusable.rs

1use ratatui::layout::Rect;
2use ratatui::Frame;
3
4use crate::input::{KeyEvent, MouseEvent};
5use crate::operation::{Event, Operation};
6use crate::settings::Settings;
7use crate::theme::Theme;
8
9/// Identifier for a widget within a view's focus ring.
10/// Each view assigns its own static string IDs; they need not be globally unique.
11pub type WidgetId = &'static str;
12
13/// Shared focus navigation operations.
14///
15/// Used as `Operation::Focus(FocusOp)` — any view that uses `FocusRing`
16/// handles these in its `handle_operation`. Views that don't use focus
17/// simply ignore them.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum FocusOp {
20    /// Cycle to the next widget in the focus ring (Tab).
21    Next,
22    /// Cycle to the previous widget in the focus ring (Shift+Tab).
23    Prev,
24    /// Move focus upward in a 2D layout.
25    Up,
26    /// Move focus downward in a 2D layout.
27    Down,
28    /// Move focus leftward in a 2D layout.
29    Left,
30    /// Move focus rightward in a 2D layout.
31    Right,
32}
33
34/// The result of a widget handling an input event.
35///
36/// Preserves the existing "pure key -> ops" contract: widgets never mutate
37/// themselves in `handle_key`; they return operations.
38pub struct InputResult {
39    /// Operations to emit (same as `Vec<Operation>` from `View::handle_key`).
40    pub ops: Vec<Operation>,
41    /// If `true`, the event was consumed and should not propagate to the
42    /// view's global key handler.
43    pub consumed: bool,
44}
45
46impl InputResult {
47    /// The widget handled the event and produced these operations.
48    pub fn consumed(ops: Vec<Operation>) -> Self {
49        Self {
50            ops,
51            consumed: true,
52        }
53    }
54
55    /// The widget did not handle the event.
56    pub fn ignored() -> Self {
57        Self {
58            ops: vec![],
59            consumed: false,
60        }
61    }
62}
63
64/// A widget that can receive focus, handle input, and render itself.
65///
66/// This is NOT a replacement for ratatui's `Widget` trait. It is a
67/// higher-level composition trait used by views to manage interactive
68/// sub-components. Widgets still use ratatui primitives internally.
69pub trait FocusableWidget {
70    /// Unique id within this view's focus ring.
71    fn id(&self) -> WidgetId;
72
73    /// Handle a key event while this widget has focus.
74    /// Returns ops + consumed flag. Must not mutate `self`.
75    fn handle_key(&self, key: KeyEvent) -> InputResult;
76
77    /// Handle a mouse event. `area` is the widget's last rendered rect.
78    fn handle_mouse(&self, _mouse: MouseEvent, _area: Rect) -> InputResult {
79        InputResult::ignored()
80    }
81
82    /// Apply a single operation directed at this widget.
83    /// Returns `Some(event)` if the operation was handled.
84    fn handle_operation(&mut self, op: &Operation, settings: &Settings) -> Option<Event>;
85
86    /// Render the widget into the given area.
87    /// `focused` indicates whether this widget currently has focus
88    /// (for styling: cursor visibility, border highlighting, etc.).
89    fn render(&self, frame: &mut Frame, area: Rect, focused: bool, theme: &Theme);
90
91    /// Whether this widget wants to capture Tab key presses, preventing
92    /// the view from using Tab for focus cycling.
93    /// Default: `false`. Override for multi-line text inputs.
94    fn captures_tab(&self) -> bool {
95        false
96    }
97
98    /// Whether this widget accepts focus at all.
99    /// Default: `true`. Override for decorative-only widgets.
100    fn focusable(&self) -> bool {
101        true
102    }
103}