egui/text_selection/
visuals.rs

1use std::sync::Arc;
2
3use crate::{pos2, vec2, Galley, Painter, Rect, Ui, Visuals};
4
5use super::CursorRange;
6
7#[derive(Clone, Debug)]
8pub struct RowVertexIndices {
9    pub row: usize,
10    pub vertex_indices: [u32; 6],
11}
12
13/// Adds text selection rectangles to the galley.
14pub fn paint_text_selection(
15    galley: &mut Arc<Galley>,
16    visuals: &Visuals,
17    cursor_range: &CursorRange,
18    mut new_vertex_indices: Option<&mut Vec<RowVertexIndices>>,
19) {
20    if cursor_range.is_empty() {
21        return;
22    }
23
24    // We need to modify the galley (add text selection painting to it),
25    // and so we need to clone it if it is shared:
26    let galley: &mut Galley = Arc::make_mut(galley);
27
28    let color = visuals.selection.bg_fill;
29    let [min, max] = cursor_range.sorted_cursors();
30    let min = min.rcursor;
31    let max = max.rcursor;
32
33    for ri in min.row..=max.row {
34        let row = &mut galley.rows[ri];
35        let left = if ri == min.row {
36            row.x_offset(min.column)
37        } else {
38            row.rect.left()
39        };
40        let right = if ri == max.row {
41            row.x_offset(max.column)
42        } else {
43            let newline_size = if row.ends_with_newline {
44                row.height() / 2.0 // visualize that we select the newline
45            } else {
46                0.0
47            };
48            row.rect.right() + newline_size
49        };
50
51        let rect = Rect::from_min_max(pos2(left, row.min_y()), pos2(right, row.max_y()));
52        let mesh = &mut row.visuals.mesh;
53
54        // Time to insert the selection rectangle into the row mesh.
55        // It should be on top (after) of any background in the galley,
56        // but behind (before) any glyphs. The row visuals has this information:
57        let glyph_index_start = row.visuals.glyph_index_start;
58
59        // Start by appending the selection rectangle to end of the mesh, as two triangles (= 6 indices):
60        let num_indices_before = mesh.indices.len();
61        mesh.add_colored_rect(rect, color);
62        assert_eq!(num_indices_before + 6, mesh.indices.len());
63
64        // Copy out the new triangles:
65        let selection_triangles = [
66            mesh.indices[num_indices_before],
67            mesh.indices[num_indices_before + 1],
68            mesh.indices[num_indices_before + 2],
69            mesh.indices[num_indices_before + 3],
70            mesh.indices[num_indices_before + 4],
71            mesh.indices[num_indices_before + 5],
72        ];
73
74        // Move every old triangle forwards by 6 indices to make room for the new triangle:
75        for i in (glyph_index_start..num_indices_before).rev() {
76            mesh.indices.swap(i, i + 6);
77        }
78        // Put the new triangle in place:
79        mesh.indices[glyph_index_start..glyph_index_start + 6]
80            .clone_from_slice(&selection_triangles);
81
82        row.visuals.mesh_bounds = mesh.calc_bounds();
83
84        if let Some(new_vertex_indices) = &mut new_vertex_indices {
85            new_vertex_indices.push(RowVertexIndices {
86                row: ri,
87                vertex_indices: selection_triangles,
88            });
89        }
90    }
91}
92
93/// Paint one end of the selection, e.g. the primary cursor.
94///
95/// This will never blink.
96pub fn paint_cursor_end(painter: &Painter, visuals: &Visuals, cursor_rect: Rect) {
97    let stroke = visuals.text_cursor.stroke;
98
99    let top = cursor_rect.center_top();
100    let bottom = cursor_rect.center_bottom();
101
102    painter.line_segment([top, bottom], (stroke.width, stroke.color));
103
104    if false {
105        // Roof/floor:
106        let extrusion = 3.0;
107        let width = 1.0;
108        painter.line_segment(
109            [top - vec2(extrusion, 0.0), top + vec2(extrusion, 0.0)],
110            (width, stroke.color),
111        );
112        painter.line_segment(
113            [bottom - vec2(extrusion, 0.0), bottom + vec2(extrusion, 0.0)],
114            (width, stroke.color),
115        );
116    }
117}
118
119/// Paint one end of the selection, e.g. the primary cursor, with blinking (if enabled).
120pub fn paint_text_cursor(
121    ui: &Ui,
122    painter: &Painter,
123    primary_cursor_rect: Rect,
124    time_since_last_interaction: f64,
125) {
126    if ui.visuals().text_cursor.blink {
127        let on_duration = ui.visuals().text_cursor.on_duration;
128        let off_duration = ui.visuals().text_cursor.off_duration;
129        let total_duration = on_duration + off_duration;
130
131        let time_in_cycle = (time_since_last_interaction % (total_duration as f64)) as f32;
132
133        let wake_in = if time_in_cycle < on_duration {
134            // Cursor is visible
135            paint_cursor_end(painter, ui.visuals(), primary_cursor_rect);
136            on_duration - time_in_cycle
137        } else {
138            // Cursor is not visible
139            total_duration - time_in_cycle
140        };
141
142        ui.ctx().request_repaint_after_secs(wake_in);
143    } else {
144        paint_cursor_end(painter, ui.visuals(), primary_cursor_rect);
145    }
146}