Skip to main content

ag_tui_text/
style.rs

1//! Scoped semantic style settings for shared terminal text rendering.
2
3use std::cell::Cell;
4
5use ratatui::style::Color;
6
7thread_local! {
8    static ACTIVE_RENDER_SETTINGS: Cell<TextRenderSettings> =
9        const { Cell::new(TextRenderSettings::DEFAULT) };
10}
11
12/// Semantic color tokens used by markdown, mermaid, and text renderers.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub struct TextPalette {
15    /// Primary accent color used for headings and prompt markers.
16    pub accent: Color,
17    /// Informational color used for secondary headings and file lookups.
18    pub info: Color,
19    /// Success color used for positive emphasis and stats headings.
20    pub success: Color,
21    /// Subtle dark surface color used behind prompt transcript blocks.
22    pub surface: Color,
23    /// Subtle blue-gray surface used behind clarification prompt blocks.
24    pub surface_clarification: Color,
25    /// Elevated surface color used for table headers.
26    pub surface_elevated: Color,
27    /// Dark surface used behind code blocks.
28    pub surface_overlay: Color,
29    /// Primary readable text color.
30    pub text: Color,
31    /// Muted text color used for secondary content and code blocks.
32    pub text_muted: Color,
33    /// Extra-muted text color used for separators and diagram structure.
34    pub text_subtle: Color,
35    /// Warning color used for inline code and caution emphasis.
36    pub warning: Color,
37    /// Softer warning tone used for clarification headings.
38    pub warning_soft: Color,
39}
40
41impl TextPalette {
42    /// Default standalone palette used when callers do not inject settings.
43    pub const DEFAULT: Self = Self {
44        accent: Color::Cyan,
45        info: Color::LightBlue,
46        success: Color::Green,
47        surface: Color::Rgb(35, 42, 55),
48        surface_clarification: Color::Rgb(28, 38, 48),
49        surface_elevated: Color::Black,
50        surface_overlay: Color::Black,
51        text: Color::White,
52        text_muted: Color::Gray,
53        text_subtle: Color::DarkGray,
54        warning: Color::Yellow,
55        warning_soft: Color::LightYellow,
56    };
57}
58
59impl Default for TextPalette {
60    fn default() -> Self {
61        Self::DEFAULT
62    }
63}
64
65/// Rendering settings injected by host applications at the text-rendering
66/// boundary.
67#[derive(Clone, Copy, Debug, Eq, PartialEq)]
68pub struct TextRenderSettings {
69    /// Stable discriminator for non-content style changes that should
70    /// invalidate rendered-line caches.
71    pub cache_version: u64,
72    /// Semantic colors used by the renderer.
73    pub palette: TextPalette,
74}
75
76impl TextRenderSettings {
77    /// Default standalone settings used when callers do not inject settings.
78    pub const DEFAULT: Self = Self {
79        cache_version: 0,
80        palette: TextPalette::DEFAULT,
81    };
82}
83
84impl Default for TextRenderSettings {
85    fn default() -> Self {
86        Self::DEFAULT
87    }
88}
89
90struct RenderSettingsScope<'settings> {
91    cell: &'settings Cell<TextRenderSettings>,
92    previous_settings: TextRenderSettings,
93}
94
95impl Drop for RenderSettingsScope<'_> {
96    fn drop(&mut self) {
97        self.cell.set(self.previous_settings);
98    }
99}
100
101pub(crate) mod palette {
102    use ratatui::style::Color;
103
104    use super::active_palette;
105
106    pub(crate) fn accent() -> Color {
107        active_palette().accent
108    }
109
110    pub(crate) fn info() -> Color {
111        active_palette().info
112    }
113
114    pub(crate) fn surface() -> Color {
115        active_palette().surface
116    }
117
118    pub(crate) fn surface_clarification() -> Color {
119        active_palette().surface_clarification
120    }
121
122    pub(crate) fn surface_elevated() -> Color {
123        active_palette().surface_elevated
124    }
125
126    pub(crate) fn surface_overlay() -> Color {
127        active_palette().surface_overlay
128    }
129
130    pub(crate) fn success() -> Color {
131        active_palette().success
132    }
133
134    pub(crate) fn text() -> Color {
135        active_palette().text
136    }
137
138    pub(crate) fn text_muted() -> Color {
139        active_palette().text_muted
140    }
141
142    pub(crate) fn text_subtle() -> Color {
143        active_palette().text_subtle
144    }
145
146    pub(crate) fn warning() -> Color {
147        active_palette().warning
148    }
149
150    pub(crate) fn warning_soft() -> Color {
151        active_palette().warning_soft
152    }
153}
154
155pub(crate) fn with_render_settings<Output>(
156    settings: TextRenderSettings,
157    render: impl FnOnce() -> Output,
158) -> Output {
159    ACTIVE_RENDER_SETTINGS.with(|cell| {
160        let previous_settings = cell.replace(settings);
161        let _scope = RenderSettingsScope {
162            cell,
163            previous_settings,
164        };
165
166        render()
167    })
168}
169
170pub(crate) fn active_theme_cache_version() -> u64 {
171    active_settings().cache_version
172}
173
174fn active_palette() -> TextPalette {
175    active_settings().palette
176}
177
178fn active_settings() -> TextRenderSettings {
179    ACTIVE_RENDER_SETTINGS.with(Cell::get)
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_default_palette_uses_current_theme_table_surface() {
188        // Arrange
189        let palette = TextPalette::DEFAULT;
190
191        // Act
192        let surface_elevated = palette.surface_elevated;
193
194        // Assert
195        assert_eq!(surface_elevated, Color::Black);
196    }
197}