use std::sync::Arc;
use crate::{pos2, vec2, Galley, Painter, Rect, Ui, Visuals};
use super::CursorRange;
#[derive(Clone, Debug)]
pub struct RowVertexIndices {
pub row: usize,
pub vertex_indices: [u32; 6],
}
/// Adds text selection rectangles to the galley.
pub fn paint_text_selection(
galley: &mut Arc<Galley>,
visuals: &Visuals,
cursor_range: &CursorRange,
mut new_vertex_indices: Option<&mut Vec<RowVertexIndices>>,
) {
if cursor_range.is_empty() {
return;
}
// We need to modify the galley (add text selection painting to it),
// and so we need to clone it if it is shared:
let galley: &mut Galley = Arc::make_mut(galley);
let color = visuals.selection.bg_fill;
let [min, max] = cursor_range.sorted_cursors();
let min = min.rcursor;
let max = max.rcursor;
for ri in min.row..=max.row {
let row = &mut galley.rows[ri];
let left = if ri == min.row {
row.x_offset(min.column)
} else {
row.rect.left()
};
let right = if ri == max.row {
row.x_offset(max.column)
} else {
let newline_size = if row.ends_with_newline {
row.height() / 2.0 // visualize that we select the newline
} else {
0.0
};
row.rect.right() + newline_size
};
let rect = Rect::from_min_max(pos2(left, row.min_y()), pos2(right, row.max_y()));
let mesh = &mut row.visuals.mesh;
// Time to insert the selection rectangle into the row mesh.
// It should be on top (after) of any background in the galley,
// but behind (before) any glyphs. The row visuals has this information:
let glyph_index_start = row.visuals.glyph_index_start;
// Start by appending the selection rectangle to end of the mesh, as two triangles (= 6 indices):
let num_indices_before = mesh.indices.len();
mesh.add_colored_rect(rect, color);
assert_eq!(num_indices_before + 6, mesh.indices.len());
// Copy out the new triangles:
let selection_triangles = [
mesh.indices[num_indices_before],
mesh.indices[num_indices_before + 1],
mesh.indices[num_indices_before + 2],
mesh.indices[num_indices_before + 3],
mesh.indices[num_indices_before + 4],
mesh.indices[num_indices_before + 5],
];
// Move every old triangle forwards by 6 indices to make room for the new triangle:
for i in (glyph_index_start..num_indices_before).rev() {
mesh.indices.swap(i, i + 6);
}
// Put the new triangle in place:
mesh.indices[glyph_index_start..glyph_index_start + 6]
.clone_from_slice(&selection_triangles);
row.visuals.mesh_bounds = mesh.calc_bounds();
if let Some(new_vertex_indices) = &mut new_vertex_indices {
new_vertex_indices.push(RowVertexIndices {
row: ri,
vertex_indices: selection_triangles,
});
}
}
}
/// Paint one end of the selection, e.g. the primary cursor.
///
/// This will never blink.
pub fn paint_cursor_end(painter: &Painter, visuals: &Visuals, cursor_rect: Rect) {
let stroke = visuals.text_cursor.stroke;
let top = cursor_rect.center_top();
let bottom = cursor_rect.center_bottom();
painter.line_segment([top, bottom], (stroke.width, stroke.color));
if false {
// Roof/floor:
let extrusion = 3.0;
let width = 1.0;
painter.line_segment(
[top - vec2(extrusion, 0.0), top + vec2(extrusion, 0.0)],
(width, stroke.color),
);
painter.line_segment(
[bottom - vec2(extrusion, 0.0), bottom + vec2(extrusion, 0.0)],
(width, stroke.color),
);
}
}
/// Paint one end of the selection, e.g. the primary cursor, with blinking (if enabled).
pub fn paint_text_cursor(
ui: &Ui,
painter: &Painter,
primary_cursor_rect: Rect,
time_since_last_interaction: f64,
) {
if ui.visuals().text_cursor.blink {
let on_duration = ui.visuals().text_cursor.on_duration;
let off_duration = ui.visuals().text_cursor.off_duration;
let total_duration = on_duration + off_duration;
let time_in_cycle = (time_since_last_interaction % (total_duration as f64)) as f32;
let wake_in = if time_in_cycle < on_duration {
// Cursor is visible
paint_cursor_end(painter, ui.visuals(), primary_cursor_rect);
on_duration - time_in_cycle
} else {
// Cursor is not visible
total_duration - time_in_cycle
};
ui.ctx().request_repaint_after_secs(wake_in);
} else {
paint_cursor_end(painter, ui.visuals(), primary_cursor_rect);
}
}