Skip to main content

rusty_bubbletea/
view.rs

1//! Cleanroom Rust port of upstream Go source file: `tea.go` (View definitions)
2//! Upstream Target Tag / Version: `v2.0.8`
3//!
4//! <upstream-docs>
5//! Package tea provides a framework for building rich terminal user interfaces
6//! based on the paradigms of The Elm Architecture. It's well-suited for simple
7//! and complex terminal applications, either inline, full-window, or a mix of
8//! both. It's been battle-tested in several large projects and is
9//! production-ready.
10//!
11//! A tutorial is available at https://github.com/charmbracelet/bubbletea/tree/master/tutorials
12//!
13//! Example programs can be found at https://github.com/charmbracelet/bubbletea/tree/master/examples
14//! </upstream-docs>
15
16use crate::color::Color;
17use crate::cursor::Cursor;
18use crate::keyboard::KeyboardEnhancements;
19use crate::model::Cmd;
20use crate::mouse::MouseMsg;
21use std::sync::Arc;
22
23/// MouseMode enum for declarative views.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
25pub enum MouseMode {
26    /// Disable mouse events.
27    #[default]
28    MouseModeNone = 0,
29    /// Cell motion mouse events.
30    MouseModeCellMotion = 1,
31    /// All motion mouse events.
32    MouseModeAllMotion = 2,
33}
34
35/// ProgressBarState represents the state of the progress bar.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum ProgressBarState {
38    /// No progress bar.
39    ProgressBarNone = 0,
40    /// Default progress bar state.
41    ProgressBarDefault,
42    /// Error progress bar state.
43    ProgressBarError,
44    /// Indeterminate progress bar state.
45    ProgressBarIndeterminate,
46    /// Warning progress bar state.
47    ProgressBarWarning,
48}
49
50impl ProgressBarState {
51    /// Returns a human-readable value for the given state.
52    pub fn to_string(&self) -> &'static str {
53        match self {
54            ProgressBarState::ProgressBarNone => "None",
55            ProgressBarState::ProgressBarDefault => "Default",
56            ProgressBarState::ProgressBarError => "Error",
57            ProgressBarState::ProgressBarIndeterminate => "Indeterminate",
58            ProgressBarState::ProgressBarWarning => "Warning",
59        }
60    }
61}
62
63/// ProgressBar represents the terminal progress bar.
64///
65/// Support depends on the terminal.
66///
67/// See <https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences>
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub struct ProgressBar {
70    /// State is the current state of the progress bar. It can be one of
71    /// [ProgressBarState::ProgressBarNone], [ProgressBarState::ProgressBarDefault],
72    /// [ProgressBarState::ProgressBarError], [ProgressBarState::ProgressBarIndeterminate],
73    /// and [ProgressBarState::ProgressBarWarning].
74    pub state: ProgressBarState,
75    /// Value is the current value of the progress bar. It should be between
76    /// 0 and 100.
77    pub value: usize,
78}
79
80/// NewProgressBar returns a new progress bar with the given state and value.
81/// The value is ignored if the state is [ProgressBarState::ProgressBarNone] or
82/// [ProgressBarState::ProgressBarIndeterminate].
83pub fn new_progress_bar(state: ProgressBarState, value: usize) -> ProgressBar {
84    ProgressBar {
85        state,
86        value: value.clamp(0, 100),
87    }
88}
89
90/// OnMouseFn closure type for View mouse handlers.
91///
92/// `Arc` (rather than `Box`) so that [View] clones preserve the handler:
93/// the renderer clones the view each frame and needs the closure on
94/// `last_view` to route mouse messages back to the model.
95pub type OnMouseFn = Arc<dyn Fn(MouseMsg) -> Cmd + Send + Sync>;
96
97/// View represents a declarative terminal view in Bubble Tea v2.0.8.
98pub struct View {
99    /// Screen content formatted string.
100    pub content: String,
101    /// Optional mouse event interceptor.
102    pub on_mouse: Option<OnMouseFn>,
103    /// Optional cursor configuration.
104    pub cursor: Option<Cursor>,
105    /// Optional background color.
106    pub background_color: Option<Color>,
107    /// Optional foreground color.
108    pub foreground_color: Option<Color>,
109    /// Window title string.
110    pub window_title: String,
111    /// Alternate screen buffer toggle.
112    pub alt_screen: bool,
113    /// Focus reporting toggle.
114    pub report_focus: bool,
115    /// Disable bracketed paste mode toggle.
116    pub disable_bracketed_paste_mode: bool,
117    /// Mouse tracking mode.
118    pub mouse_mode: MouseMode,
119    /// Keyboard enhancements requested.
120    pub keyboard_enhancements: KeyboardEnhancements,
121    /// Optional terminal progress bar.
122    pub progress_bar: Option<ProgressBar>,
123}
124
125impl Clone for View {
126    fn clone(&self) -> View {
127        View {
128            content: self.content.clone(),
129            on_mouse: self.on_mouse.clone(),
130            cursor: self.cursor.clone(),
131            background_color: self.background_color,
132            foreground_color: self.foreground_color,
133            window_title: self.window_title.clone(),
134            alt_screen: self.alt_screen,
135            report_focus: self.report_focus,
136            disable_bracketed_paste_mode: self.disable_bracketed_paste_mode,
137            mouse_mode: self.mouse_mode,
138            keyboard_enhancements: self.keyboard_enhancements,
139            progress_bar: self.progress_bar,
140        }
141    }
142}
143
144impl PartialEq for View {
145    fn eq(&self, other: &View) -> bool {
146        self.content == other.content
147            && self.cursor == other.cursor
148            && self.background_color == other.background_color
149            && self.foreground_color == other.foreground_color
150            && self.window_title == other.window_title
151            && self.alt_screen == other.alt_screen
152            && self.report_focus == other.report_focus
153            && self.disable_bracketed_paste_mode == other.disable_bracketed_paste_mode
154            && self.mouse_mode == other.mouse_mode
155            && self.keyboard_enhancements == other.keyboard_enhancements
156            && self.progress_bar == other.progress_bar
157    }
158}
159
160impl Default for View {
161    fn default() -> Self {
162        Self {
163            content: String::new(),
164            on_mouse: None,
165            cursor: None,
166            background_color: None,
167            foreground_color: None,
168            window_title: String::new(),
169            alt_screen: false,
170            report_focus: false,
171            disable_bracketed_paste_mode: false,
172            mouse_mode: MouseMode::MouseModeNone,
173            keyboard_enhancements: KeyboardEnhancements::default(),
174            progress_bar: None,
175        }
176    }
177}
178
179impl View {
180    /// <upstream-comment>NewView is a helper function to create a new [View] with the given styled
181    /// string. A styled string represents text with styles and hyperlinks encoded
182    /// as ANSI escape codes.
183    ///
184    /// ```text
185    /// v := tea.NewView("Hello, World!")
186    /// ```</upstream-comment>
187    pub fn new(content: &str) -> Self {
188        let mut v = Self::default();
189        v.set_content(content);
190        v
191    }
192
193    /// Helper method to set view content.
194    pub fn set_content(&mut self, s: &str) {
195        self.content = s.to_string();
196    }
197}
198
199impl View {
200    /// Returns whether two views are equivalent for rendering purposes,
201    /// mirroring the upstream `viewEquals` check used to skip re-renders.
202    pub fn equals(&self, o: &View) -> bool {
203        self.content == o.content
204            && self.alt_screen == o.alt_screen
205            && self.report_focus == o.report_focus
206            && self.disable_bracketed_paste_mode == o.disable_bracketed_paste_mode
207            && self.window_title == o.window_title
208            && self.mouse_mode == o.mouse_mode
209            && self.background_color == o.background_color
210            && self.foreground_color == o.foreground_color
211            && self.keyboard_enhancements == o.keyboard_enhancements
212            && self.cursor == o.cursor
213            && self.progress_bar == o.progress_bar
214    }
215}