Skip to main content

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}