typ_core/panel.rs
1use std::any::Any;
2
3use crossterm::event::MouseEvent;
4use ratatui::buffer::Buffer;
5use ratatui::layout::Rect;
6use ratatui::style::Color;
7
8use crate::{KeyChord, PanelEvent};
9
10/// The colors a panel is allowed to know about.
11///
12/// Deliberately a small copy rather than a reference to a full theme: panels
13/// should not be able to reach into application state through their theme.
14#[derive(Debug, Clone, Copy)]
15pub struct ThemeColors {
16 pub fg: Color,
17 pub bg: Color,
18 pub selection_bg: Color,
19 pub selection_fg: Color,
20 pub border: Color,
21 pub border_focused: Color,
22 pub line_numbers: Color,
23 pub cursor: Color,
24 pub status_bar_bg: Color,
25 pub status_bar_fg: Color,
26}
27
28impl Default for ThemeColors {
29 fn default() -> Self {
30 Self {
31 fg: Color::White,
32 bg: Color::Black,
33 selection_bg: Color::Blue,
34 selection_fg: Color::White,
35 border: Color::DarkGray,
36 border_focused: Color::Cyan,
37 line_numbers: Color::DarkGray,
38 cursor: Color::Yellow,
39 status_bar_bg: Color::DarkGray,
40 status_bar_fg: Color::White,
41 }
42 }
43}
44
45/// Everything a panel may see at render time.
46///
47/// This is the whole surface — a panel never receives `&AppState`.
48pub struct RenderContext<'a> {
49 pub theme: &'a ThemeColors,
50 pub is_focused: bool,
51 pub panel_index: usize,
52 pub terminal_width: u16,
53 pub terminal_height: u16,
54}
55
56/// A rectangular, focusable unit of UI.
57///
58/// Implementors provide five methods; everything else has a default. Panels
59/// communicate outward by returning events, never by mutating shared state.
60pub trait Panel: Any {
61 /// Stable type name, used for registry lookup and session records.
62 fn name(&self) -> &'static str;
63
64 /// Dynamic title shown in the panel header.
65 fn title(&self) -> String;
66
67 fn render(&mut self, area: Rect, buf: &mut Buffer, ctx: &RenderContext);
68
69 fn handle_key(&mut self, chord: KeyChord) -> Vec<PanelEvent>;
70
71 fn as_any(&self) -> &dyn Any;
72 fn as_any_mut(&mut self) -> &mut dyn Any;
73
74 /// `panel_area` is supplied so the panel can translate to local coordinates.
75 fn handle_mouse(&mut self, event: MouseEvent, panel_area: Rect) -> Vec<PanelEvent> {
76 let _ = (event, panel_area);
77 Vec::new()
78 }
79
80 /// Coalesced scroll. Positive is down.
81 fn handle_scroll(&mut self, delta: i32, panel_area: Rect) -> Vec<PanelEvent> {
82 let _ = (delta, panel_area);
83 Vec::new()
84 }
85
86 /// Where the terminal cursor belongs, in screen coordinates, when this
87 /// panel holds focus. `None` hides it.
88 ///
89 /// The app draws the real terminal cursor rather than a styled cell, so it
90 /// blinks and reshapes the way every other terminal program's does. A panel
91 /// with nothing to edit — a file tree, a viewer — leaves this defaulted.
92 fn cursor_position(&self, panel_area: Rect) -> Option<(u16, u16)> {
93 let _ = panel_area;
94 None
95 }
96
97 /// Perform a named action.
98 ///
99 /// This is the only way a binding, the command palette, or the vim layer
100 /// reaches a panel's behavior.
101 ///
102 /// `None` means "I do not handle this action" and lets the app try it.
103 /// `Some(vec![])` means "handled, nothing to report" — a real outcome, as
104 /// when adding a cursor at the edge of the document does nothing. Folding
105 /// those two answers into an empty vector reads fine today and becomes a
106 /// silent bug the first time an action needs both a panel implementation
107 /// and an app fallback.
108 fn apply_action(&mut self, action: crate::Action) -> Option<Vec<PanelEvent>> {
109 let _ = action;
110 None
111 }
112
113 /// Periodic hook for background work.
114 fn tick(&mut self) -> Vec<PanelEvent> {
115 Vec::new()
116 }
117
118 /// True when the panel consumes Escape itself (e.g. an open search box).
119 fn captures_escape(&self) -> bool {
120 false
121 }
122
123 /// `Some(message)` blocks closing until confirmed.
124 fn needs_close_confirmation(&self) -> Option<String> {
125 None
126 }
127}