1use std::ops::Range;
4
5use gpui::{
6 App, BorderStyle, Bounds, Font, Global, Hsla, PaintQuad, Pixels, TextRun, Window,
7 WrappedLineLayout, fill, outline, px,
8};
9
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
12pub enum CaretShape {
13 #[default]
14 Bar,
15 Block,
16 Underline,
17}
18
19impl Global for CaretShape {}
20
21pub(crate) fn caret_shape(cx: &App) -> CaretShape {
22 cx.try_global::<CaretShape>().copied().unwrap_or_default()
23}
24
25pub(crate) fn set_caret_shape(shape: CaretShape, cx: &mut App) {
27 cx.set_global(shape);
28 cx.refresh_windows();
29}
30
31#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
33pub enum CaretHeight {
34 #[default]
36 Line,
37 Text,
39}
40
41impl Global for CaretHeight {}
42
43pub(crate) fn caret_height(cx: &App) -> CaretHeight {
44 cx.try_global::<CaretHeight>().copied().unwrap_or_default()
45}
46
47pub(crate) fn set_caret_height(height: CaretHeight, cx: &mut App) {
49 cx.set_global(height);
50 cx.refresh_windows();
51}
52
53impl CaretShape {
54 pub fn quad(
58 self,
59 mut bar: Bounds<Pixels>,
60 line_height: Pixels,
61 height: CaretHeight,
62 width: Pixels,
63 color: Hsla,
64 hollow: bool,
65 ) -> PaintQuad {
66 if self != Self::Bar {
67 bar.size.width = width;
68 }
69 match self {
70 Self::Bar => fill(bar, color),
71 Self::Block => {
72 if height == CaretHeight::Line {
73 bar.origin.y -= (line_height - bar.size.height) / 2.0;
74 bar.size.height = line_height;
75 }
76 match hollow {
77 true => outline(bar, color, BorderStyle::Solid),
78 false => fill(bar, color),
79 }
80 }
81 Self::Underline => {
82 let thickness = px(2.0).min(bar.size.height);
83 bar.origin.y += bar.size.height - thickness;
84 bar.size.height = thickness;
85 fill(bar, color)
86 }
87 }
88 }
89
90 pub fn cuts_out(self, hollow: bool) -> bool {
93 self == Self::Block && !hollow
94 }
95}
96
97pub fn zero_width(font: Font, font_size: Pixels, window: &Window) -> Pixels {
100 let run = TextRun {
101 len: 1,
102 font,
103 color: Hsla::default(),
104 background_color: None,
105 underline: None,
106 strikethrough: None,
107 };
108 window
109 .text_system()
110 .shape_line("0".into(), font_size, &[run], None)
111 .width
112}
113
114pub fn recoloured(runs: Vec<TextRun>, range: &Range<usize>, color: Hsla) -> Vec<TextRun> {
116 let mut out = Vec::with_capacity(runs.len() + 2);
117 let mut at = 0;
118 for run in runs {
119 let end = at + run.len;
120 for (start, stop, inside) in [
121 (at, end.min(range.start), false),
122 (at.max(range.start), end.min(range.end), true),
123 (at.max(range.end), end, false),
124 ] {
125 if stop <= start {
126 continue;
127 }
128 out.push(TextRun {
129 len: stop - start,
130 color: if inside { color } else { run.color },
131 ..run.clone()
132 });
133 }
134 at = end;
135 }
136 out
137}
138
139pub fn character_advance(line: &WrappedLineLayout, text: &str, offset: usize) -> Option<Pixels> {
141 if offset >= text.len() || !text.is_char_boundary(offset) {
142 return None;
143 }
144 if line
145 .wrap_boundaries()
146 .iter()
147 .any(|boundary| line.runs()[boundary.run_ix].glyphs[boundary.glyph_ix].index == offset)
148 {
149 return None;
150 }
151 let end = super::next_boundary(text, offset);
152 Some((line.unwrapped_layout.x_for_index(end) - line.unwrapped_layout.x_for_index(offset)).abs())
153}