1use std::ops::Range;
4
5use gpui::{
6 App, BorderStyle, Bounds, Font, Global, Hsla, LineLayout, PaintQuad, Pixels, Point, TextRun,
7 Window, WrappedLineLayout, fill, outline, point, px,
8};
9
10use super::overdraw::Layers;
11
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
14pub enum CaretShape {
15 #[default]
16 Bar,
17 Block,
18 Underline,
19}
20
21impl Global for CaretShape {}
22
23pub(crate) fn caret_shape(cx: &App) -> CaretShape {
24 cx.try_global::<CaretShape>().copied().unwrap_or_default()
25}
26
27pub(crate) fn set_caret_shape(shape: CaretShape, cx: &mut App) {
29 cx.set_global(shape);
30 cx.refresh_windows();
31}
32
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
35pub enum CaretHeight {
36 #[default]
38 Line,
39 Text,
41}
42
43impl Global for CaretHeight {}
44
45pub(crate) fn caret_height(cx: &App) -> CaretHeight {
46 cx.try_global::<CaretHeight>().copied().unwrap_or_default()
47}
48
49pub(crate) fn set_caret_height(height: CaretHeight, cx: &mut App) {
51 cx.set_global(height);
52 cx.refresh_windows();
53}
54
55#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
57pub enum InactiveCaret {
58 #[default]
60 Hollow,
61 Hidden,
63}
64
65impl Global for InactiveCaret {}
66
67pub(crate) fn inactive_caret(cx: &App) -> InactiveCaret {
68 cx.try_global::<InactiveCaret>()
69 .copied()
70 .unwrap_or_default()
71}
72
73pub(crate) fn set_inactive_caret(inactive: InactiveCaret, cx: &mut App) {
75 cx.set_global(inactive);
76 cx.refresh_windows();
77}
78
79impl CaretShape {
80 pub fn shown(self, window_active: bool, inactive: InactiveCaret) -> bool {
83 window_active || self != Self::Block || inactive == InactiveCaret::Hollow
84 }
85
86 pub fn quad(
90 self,
91 mut bar: Bounds<Pixels>,
92 line_height: Pixels,
93 height: CaretHeight,
94 width: Pixels,
95 color: Hsla,
96 hollow: bool,
97 ) -> PaintQuad {
98 if self != Self::Bar {
99 bar.size.width = width;
100 }
101 match self {
102 Self::Bar => fill(bar, color),
103 Self::Block => {
104 if height == CaretHeight::Line {
105 bar.origin.y -= (line_height - bar.size.height) / 2.0;
106 bar.size.height = line_height;
107 }
108 match hollow {
109 true => outline(bar, color, BorderStyle::Solid),
110 false => fill(bar, color),
111 }
112 }
113 Self::Underline => {
114 let thickness = px(2.0).min(bar.size.height);
115 bar.origin.y += bar.size.height - thickness;
116 bar.size.height = thickness;
117 fill(bar, color)
118 }
119 }
120 }
121
122 pub fn cuts_out(self, hollow: bool) -> bool {
125 self == Self::Block && !hollow
126 }
127}
128
129pub fn zero_width(font: Font, font_size: Pixels, window: &Window) -> Pixels {
132 let run = TextRun {
133 len: 1,
134 font,
135 color: Hsla::default(),
136 background_color: None,
137 underline: None,
138 strikethrough: None,
139 };
140 window
141 .text_system()
142 .shape_line("0".into(), font_size, &[run], None)
143 .width
144}
145
146pub fn paint_covered(
153 layout: &LineLayout,
154 range: &Range<usize>,
155 at: Point<Pixels>,
156 line_height: Pixels,
157 color: Hsla,
158 window: &mut Window,
159) {
160 let mut glyphs = layout
161 .runs
162 .iter()
163 .flat_map(|run| run.glyphs.iter().map(move |glyph| (run.font_id, glyph)))
164 .filter(|(_, glyph)| range.contains(&glyph.index))
165 .peekable();
166 let Some(left) = glyphs.peek().map(|(_, glyph)| glyph.position.x) else {
167 return;
168 };
169 let baseline = (line_height - layout.ascent - layout.descent) / 2. + layout.ascent;
170 for (font_id, glyph) in glyphs {
171 let origin = point(
172 at.x + glyph.position.x - left,
173 at.y + baseline + glyph.position.y,
174 );
175 let _ = match glyph.is_emoji {
176 true => window.paint_emoji(origin, font_id, glyph.id, layout.font_size),
177 false => window.paint_glyph(origin, font_id, glyph.id, layout.font_size, color),
178 };
179 }
180}
181
182pub fn cover<S>(
186 runs: &[TextRun],
187 offset: usize,
188 page: Hsla,
189 layers: &Layers<S>,
190 caret: Hsla,
191) -> Hsla {
192 let mut at = 0;
193 let text = runs
194 .iter()
195 .find(|run| {
196 at += run.len;
197 offset < at
198 })
199 .or(runs.last())
200 .map_or(page, |run| run.color);
201 let surface = layers.surface(page, &(offset..offset + 1));
202 theme::ink_over(text, page, surface, theme::stack(page, [caret]))
203}
204
205pub fn recoloured(runs: Vec<TextRun>, range: &Range<usize>, color: Hsla) -> Vec<TextRun> {
207 let mut out = Vec::with_capacity(runs.len() + 2);
208 let mut at = 0;
209 for run in runs {
210 let end = at + run.len;
211 for (start, stop, inside) in [
212 (at, end.min(range.start), false),
213 (at.max(range.start), end.min(range.end), true),
214 (at.max(range.end), end, false),
215 ] {
216 if stop <= start {
217 continue;
218 }
219 out.push(TextRun {
220 len: stop - start,
221 color: if inside { color } else { run.color },
222 ..run.clone()
223 });
224 }
225 at = end;
226 }
227 out
228}
229
230pub fn character_advance(line: &WrappedLineLayout, text: &str, offset: usize) -> Option<Pixels> {
232 if offset >= text.len() || !text.is_char_boundary(offset) {
233 return None;
234 }
235 if line
236 .wrap_boundaries()
237 .iter()
238 .any(|boundary| line.runs()[boundary.run_ix].glyphs[boundary.glyph_ix].index == offset)
239 {
240 return None;
241 }
242 let end = super::next_boundary(text, offset);
243 Some((line.unwrapped_layout.x_for_index(end) - line.unwrapped_layout.x_for_index(offset)).abs())
244}