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}