1use std::sync::Arc;
2
3use emath::Pos2;
4use epaint::{
5 Stroke,
6 text::{
7 CharIndex,
8 cursor::{CCursor, LayoutCursor},
9 },
10};
11
12use crate::{
13 Galley, Painter, Rect, Ui, Visuals, pos2, text_selection::text_cursor_state::cursor_rect, vec2,
14};
15
16use super::CCursorRange;
17
18#[derive(Clone, Debug)]
19pub struct RowVertexIndices {
20 pub row: usize,
21 pub vertex_indices: [u32; 6],
22}
23
24pub fn paint_text_selection(
26 galley: &mut Arc<Galley>,
27 visuals: &Visuals,
28 cursor_range: &CCursorRange,
29 mut new_vertex_indices: Option<&mut Vec<RowVertexIndices>>,
30) {
31 if cursor_range.is_empty() {
32 return;
33 }
34
35 let galley: &mut Galley = Arc::make_mut(galley);
38
39 let background_color = visuals.selection.bg_fill;
40 let text_color = visuals.selection.stroke.color;
41
42 let [min, max] = cursor_range.sorted_cursors();
43 let min = galley.layout_from_cursor(min);
44 let max = galley.layout_from_cursor(max);
45
46 for ri in min.row..=max.row {
47 let placed_row = &mut galley.rows[ri];
48 let row = Arc::make_mut(&mut placed_row.row);
49
50 let left = if ri == min.row {
51 row.x_offset(min.column)
52 } else {
53 0.0
54 };
55 let right = if ri == max.row {
56 row.x_offset(max.column)
57 } else {
58 let newline_size = if placed_row.ends_with_newline {
59 row.height() / 2.0 } else {
61 0.0
62 };
63 row.size.x + newline_size
64 };
65
66 let rect = Rect::from_min_max(pos2(left, 0.0), pos2(right, row.size.y));
67 let mesh = &mut row.visuals.mesh;
68
69 if !row.glyphs.is_empty() {
70 let first_glyph_index = if ri == min.row { min.column.0 } else { 0 };
72 let last_glyph_index = if ri == max.row {
73 max.column.0
74 } else {
75 row.glyphs.len()
76 };
77
78 let first_vertex_index = row
79 .glyphs
80 .get(first_glyph_index)
81 .map_or(row.visuals.glyph_vertex_range.end, |g| g.first_vertex as _);
82 let last_vertex_index = row
83 .glyphs
84 .get(last_glyph_index)
85 .map_or(row.visuals.glyph_vertex_range.end, |g| g.first_vertex as _);
86
87 for vi in first_vertex_index..last_vertex_index {
88 mesh.vertices[vi].color = text_color;
89 }
90 }
91
92 let glyph_index_start = row.visuals.glyph_index_start;
96
97 let num_indices_before = mesh.indices.len();
99 mesh.add_colored_rect(rect, background_color);
100 assert_eq!(
101 num_indices_before + 6,
102 mesh.indices.len(),
103 "We expect exactly 6 new indices"
104 );
105
106 let selection_triangles = [
108 mesh.indices[num_indices_before],
109 mesh.indices[num_indices_before + 1],
110 mesh.indices[num_indices_before + 2],
111 mesh.indices[num_indices_before + 3],
112 mesh.indices[num_indices_before + 4],
113 mesh.indices[num_indices_before + 5],
114 ];
115
116 for i in (glyph_index_start..num_indices_before).rev() {
118 mesh.indices.swap(i, i + 6);
119 }
120 mesh.indices[glyph_index_start..glyph_index_start + 6]
122 .clone_from_slice(&selection_triangles);
123
124 row.visuals.mesh_bounds = mesh.calc_bounds();
125
126 if let Some(new_vertex_indices) = &mut new_vertex_indices {
127 new_vertex_indices.push(RowVertexIndices {
128 row: ri,
129 vertex_indices: selection_triangles,
130 });
131 }
132 }
133}
134
135#[expect(clippy::too_many_arguments)]
136pub(crate) fn paint_ime_preedit_text_visuals(
137 pos: Pos2,
138 ui: &Ui,
139 painter: &Painter,
140 galley: &Arc<Galley>,
141 row_height: f32,
142 preedit_range: core::ops::Range<CCursor>,
143 mut relative_active_range: Option<core::ops::Range<CCursor>>,
144 time_since_last_interaction: f64,
145) {
146 fn is_cursor_range_empty(range: &core::ops::Range<CCursor>) -> bool {
154 range.start.index == range.end.index
155 }
156
157 if is_cursor_range_empty(&preedit_range) {
158 return;
159 }
160
161 if let Some(relative_active_range) = &mut relative_active_range
162 && relative_active_range.end.index > preedit_range.end.index - preedit_range.start.index
163 {
164 relative_active_range.end.index = preedit_range.end.index - preedit_range.start.index;
165 }
166
167 let visuals = ui.visuals();
168 let active_underline_stroke = visuals.ime_composition.active_underline_stroke;
169 let inactive_underline_stroke = visuals.ime_composition.inactive_underline_stroke;
170
171 if let Some(relative_active_range) = &relative_active_range
172 && !is_cursor_range_empty(relative_active_range)
173 {
174 if relative_active_range.start.index > CharIndex::ZERO {
175 paint_underlines(
176 pos,
177 painter,
178 galley,
179 galley.layout_from_cursor(preedit_range.start),
180 galley.layout_from_cursor(preedit_range.start + relative_active_range.start.index),
181 inactive_underline_stroke,
182 );
183 }
184
185 paint_underlines(
186 pos,
187 painter,
188 galley,
189 galley.layout_from_cursor(preedit_range.start + relative_active_range.start.index),
190 galley.layout_from_cursor(preedit_range.start + relative_active_range.end.index),
191 active_underline_stroke,
192 );
193
194 if !is_cursor_range_empty(
195 &(relative_active_range.end..(preedit_range.end - preedit_range.start.index)),
196 ) {
197 paint_underlines(
198 pos,
199 painter,
200 galley,
201 galley.layout_from_cursor(preedit_range.start + relative_active_range.end.index),
202 galley.layout_from_cursor(preedit_range.end),
203 inactive_underline_stroke,
204 );
205 }
206 } else {
207 paint_underlines(
208 pos,
209 painter,
210 galley,
211 galley.layout_from_cursor(preedit_range.start),
212 galley.layout_from_cursor(preedit_range.end),
213 inactive_underline_stroke,
214 );
215 }
216
217 if let Some(relative_active_range) = relative_active_range
218 && is_cursor_range_empty(&relative_active_range)
219 {
220 let active_cursor = preedit_range.start + relative_active_range.start.index;
221 let cursor_rect = cursor_rect(galley, &active_cursor, row_height);
222
223 paint_text_cursor(
224 ui,
225 painter,
226 cursor_rect.translate(pos.to_vec2()),
227 time_since_last_interaction,
228 );
229 }
230}
231
232fn paint_underlines(
233 pos: Pos2,
234 painter: &Painter,
235 galley: &Arc<Galley>,
236 min: LayoutCursor,
237 max: LayoutCursor,
238 stroke: Stroke,
239) {
240 for ri in min.row..=max.row {
241 let placed_row = &galley.rows[ri];
242 let row = &placed_row.row;
243
244 let left = if ri == min.row {
245 row.x_offset(min.column)
246 } else {
247 0.0
248 };
249 let right = if ri == max.row {
250 row.x_offset(max.column)
251 } else {
252 row.size.x
253 };
254
255 let offset_y = placed_row.pos.y + row.size.y;
256
257 painter.line_segment(
258 [pos + vec2(left, offset_y), pos + vec2(right, offset_y)],
259 stroke,
260 );
261 }
262}
263
264pub fn paint_cursor_end(painter: &Painter, visuals: &Visuals, cursor_rect: Rect) {
268 let stroke = visuals.text_cursor.stroke;
269
270 let top = cursor_rect.center_top();
271 let bottom = cursor_rect.center_bottom();
272
273 painter.line_segment([top, bottom], stroke);
274
275 if false {
276 let extrusion = 3.0;
278 let width = 1.0;
279 painter.line_segment(
280 [top - vec2(extrusion, 0.0), top + vec2(extrusion, 0.0)],
281 (width, stroke.color),
282 );
283 painter.line_segment(
284 [bottom - vec2(extrusion, 0.0), bottom + vec2(extrusion, 0.0)],
285 (width, stroke.color),
286 );
287 }
288}
289
290pub fn paint_text_cursor(
292 ui: &Ui,
293 painter: &Painter,
294 primary_cursor_rect: Rect,
295 time_since_last_interaction: f64,
296) {
297 if ui.visuals().text_cursor.blink {
298 let on_duration = ui.visuals().text_cursor.on_duration;
299 let off_duration = ui.visuals().text_cursor.off_duration;
300 let total_duration = on_duration + off_duration;
301
302 let time_in_cycle = (time_since_last_interaction % (total_duration as f64)) as f32;
303
304 let wake_in = if time_in_cycle < on_duration {
305 paint_cursor_end(painter, ui.visuals(), primary_cursor_rect);
307 on_duration - time_in_cycle
308 } else {
309 total_duration - time_in_cycle
311 };
312
313 ui.request_repaint_after_secs(wake_in);
314 } else {
315 paint_cursor_end(painter, ui.visuals(), primary_cursor_rect);
316 }
317}