gpui_component/text/style.rs
1use std::sync::Arc;
2
3use gpui::{HighlightStyle, Pixels, Rems, StyleRefinement, px, rems};
4
5use crate::highlighter::HighlightTheme;
6
7/// TextViewStyle used to customize the style for [`super::TextView`].
8///
9/// This is the component-level style. It is folded onto the style the active
10/// [`crate::Theme`] already derived, so a field left at its default keeps the
11/// themed value rather than overriding it with a neutral one.
12#[derive(Clone)]
13pub struct TextViewStyle {
14 /// Gap of each paragraphs, default is 0.75 rem.
15 pub paragraph_gap: Rems,
16 /// Base font size for headings, default is 14px.
17 ///
18 /// Left at the default together with [`Self::heading_font_size`], headings
19 /// follow the text view's own scale, proportional to the body text.
20 pub heading_base_font_size: Pixels,
21 /// Function to calculate heading font size based on heading level (1-6).
22 ///
23 /// The first parameter is the heading level (1-6), the second parameter is
24 /// the base font size.
25 pub heading_font_size: Option<Arc<dyn Fn(u8, Pixels) -> Pixels + Send + Sync + 'static>>,
26 /// Highlight theme for code blocks. Default: [`HighlightTheme::default_light()`]
27 pub highlight_theme: Arc<HighlightTheme>,
28 /// The style refinement for code blocks.
29 ///
30 /// Set `overflow.y` to `Overflow::Scroll` with a max height here to scroll
31 /// long code inside the block, e.g.
32 /// `TextViewStyle::default().code_block({ let mut s = StyleRefinement::default().max_h(px(320.)); s.overflow.y = Some(Overflow::Scroll); s })`.
33 pub code_block: StyleRefinement,
34 /// Style refinement applied to the table container (the bordered wrapper
35 /// in wrap mode, the scroll viewport in horizontal-scroll mode).
36 ///
37 /// Set `overflow_x: scroll` here for adaptive table layout: columns fit
38 /// their content when space allows, shrink (wrapping cell text) down to a
39 /// per-column floor when the frame is narrower, and below that the table
40 /// scrolls horizontally instead of squeezing further, e.g.
41 /// `TextViewStyle::default().table({ let mut s = StyleRefinement::default(); s.overflow.x = Some(Overflow::Scroll); s })`.
42 pub table: StyleRefinement,
43 /// Style refinement applied to the header row (the first row) of a table,
44 /// on top of the `table_head` background and foreground from the theme.
45 pub table_head: StyleRefinement,
46 /// Style refinement applied to each table cell.
47 ///
48 /// With the scroll layout, set `white_space: nowrap` here to keep cells
49 /// on a single line — columns then never shrink and the table scrolls as
50 /// soon as the content is wider than the frame.
51 pub table_cell: StyleRefinement,
52 /// The highlight style for inline code.
53 ///
54 /// Default is [`HighlightStyle::default()`], the `background_color` will
55 /// fallback to `cx.theme().accent`, if it is `None`.
56 pub inline_code: HighlightStyle,
57 /// Whether content-specific rendering should use dark-mode assets.
58 pub is_dark: bool,
59}
60
61impl Default for TextViewStyle {
62 fn default() -> Self {
63 Self {
64 paragraph_gap: rems(0.75),
65 heading_base_font_size: px(14.),
66 heading_font_size: None,
67 highlight_theme: HighlightTheme::default_light().clone(),
68 code_block: StyleRefinement::default(),
69 table: StyleRefinement::default(),
70 table_head: StyleRefinement::default(),
71 table_cell: StyleRefinement::default(),
72 inline_code: HighlightStyle::default(),
73 is_dark: false,
74 }
75 }
76}
77
78impl PartialEq for TextViewStyle {
79 fn eq(&self, other: &Self) -> bool {
80 self.paragraph_gap == other.paragraph_gap
81 && self.heading_base_font_size == other.heading_base_font_size
82 && match (&self.heading_font_size, &other.heading_font_size) {
83 (Some(left), Some(right)) => (1..=6).all(|level| {
84 left(level, self.heading_base_font_size)
85 == right(level, other.heading_base_font_size)
86 }),
87 (None, None) => true,
88 _ => false,
89 }
90 && self.highlight_theme == other.highlight_theme
91 && self.code_block == other.code_block
92 && self.table == other.table
93 && self.table_head == other.table_head
94 && self.table_cell == other.table_cell
95 && self.inline_code == other.inline_code
96 && self.is_dark == other.is_dark
97 }
98}
99
100impl TextViewStyle {
101 /// Set paragraph gap, default is 0.75 rem.
102 pub fn paragraph_gap(mut self, gap: Rems) -> Self {
103 self.paragraph_gap = gap;
104 self
105 }
106 /// Set the function that resolves a heading's font size from its level
107 /// (1-6) and [`Self::heading_base_font_size`].
108 pub fn heading_font_size<F>(mut self, f: F) -> Self
109 where
110 F: Fn(u8, Pixels) -> Pixels + Send + Sync + 'static,
111 {
112 self.heading_font_size = Some(Arc::new(f));
113 self
114 }
115 /// Set style for code blocks.
116 ///
117 /// Set `overflow.y` to `Overflow::Scroll` with a max height on the
118 /// refinement to scroll long code inside the block instead of growing it.
119 pub fn code_block(mut self, style: StyleRefinement) -> Self {
120 self.code_block = style;
121 self
122 }
123 /// Set style for inline code spans.
124 pub fn inline_code(mut self, style: HighlightStyle) -> Self {
125 self.inline_code = style;
126 self
127 }
128 /// Set extra style for the table container.
129 ///
130 /// Set `overflow_x: scroll` on the refinement for adaptive layout: cells
131 /// wrap as the frame narrows, and once columns reach their minimum width
132 /// the table scrolls horizontally instead of shrinking further.
133 pub fn table(mut self, style: StyleRefinement) -> Self {
134 self.table = style;
135 self
136 }
137 /// Set extra style for the table header row.
138 pub fn table_head(mut self, style: StyleRefinement) -> Self {
139 self.table_head = style;
140 self
141 }
142 /// Set extra style for each table cell.
143 ///
144 /// With the scroll table layout, `white_space: nowrap` here keeps cells
145 /// on a single line and the table scrolls whenever the content is wider
146 /// than the frame.
147 pub fn table_cell(mut self, style: StyleRefinement) -> Self {
148 self.table_cell = style;
149 self
150 }
151}