Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/egui/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1129,7 +1129,10 @@ impl Visuals {
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Selection {
/// Background color behind selected text and other selectable buttons.
pub bg_fill: Color32,

/// Color of selected text.
pub stroke: Stroke,
}

Expand Down
31 changes: 29 additions & 2 deletions crates/egui/src/text_selection/visuals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ pub fn paint_text_selection(
// 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 background_color = visuals.selection.bg_fill;
let text_color = visuals.selection.stroke.color;

let [min, max] = cursor_range.sorted_cursors();
let min = galley.layout_from_cursor(min);
let max = galley.layout_from_cursor(max);
Expand Down Expand Up @@ -53,14 +55,39 @@ pub fn paint_text_selection(
let rect = Rect::from_min_max(pos2(left, 0.0), pos2(right, row.size.y));
let mesh = &mut row.visuals.mesh;

if !row.glyphs.is_empty() {
// Change color of the selected text:
let first_glyph_index = if ri == min.row { min.column } else { 0 };
let last_glyph_index = if ri == max.row {
max.column
} else {
row.glyphs.len() - 1
};

let first_vertex_index = row
.glyphs
.get(first_glyph_index)
.map_or(row.visuals.glyph_vertex_range.start, |g| {
g.first_vertex as _
});
let last_vertex_index = row
.glyphs
.get(last_glyph_index)
.map_or(row.visuals.glyph_vertex_range.end, |g| g.first_vertex as _);

for vi in first_vertex_index..last_vertex_index {
mesh.vertices[vi].color = text_color;
}
}

// 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);
mesh.add_colored_rect(rect, background_color);
assert_eq!(
num_indices_before + 6,
mesh.indices.len(),
Expand Down
26 changes: 24 additions & 2 deletions crates/egui_demo_lib/tests/misc.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use egui_kittest::Harness;
use egui::{Color32, accesskit::Role};
use egui_kittest::{Harness, kittest::Queryable as _};

#[test]
fn test_kerning() {
Expand Down Expand Up @@ -42,7 +43,7 @@ fn test_italics() {
harness.run();
harness.fit_contents();
harness.snapshot(format!(
"image_blending/image_{theme}_x{pixels_per_point:.2}",
"italics/image_{theme}_x{pixels_per_point:.2}",
theme = match theme {
egui::Theme::Dark => "dark",
egui::Theme::Light => "light",
Expand All @@ -51,3 +52,24 @@ fn test_italics() {
}
}
}

#[test]
fn test_text_selection() {
let mut harness = Harness::builder().build_ui(|ui| {
let visuals = ui.visuals_mut();
visuals.selection.bg_fill = Color32::LIGHT_GREEN;
visuals.selection.stroke.color = Color32::DARK_BLUE;

ui.label("Some varied ☺ text :)\nAnd it has a second line!");
});
harness.run();
harness.fit_contents();

// Drag to select text:
let label = harness.get_by_role(Role::Label);
harness.drag_at(label.rect().lerp_inside([0.2, 0.25]));
harness.drop_at(label.rect().lerp_inside([0.6, 0.75]));
harness.run();

harness.snapshot("text_selection");
}
3 changes: 3 additions & 0 deletions crates/egui_demo_lib/tests/snapshots/text_selection.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion crates/emath/src/rect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,8 @@ impl Rect {
/// Linearly interpolate so that `[0, 0]` is [`Self::min`] and
/// `[1, 1]` is [`Self::max`].
#[inline]
pub fn lerp_inside(&self, t: Vec2) -> Pos2 {
pub fn lerp_inside(&self, t: impl Into<Vec2>) -> Pos2 {
let t = t.into();
Pos2 {
x: lerp(self.min.x..=self.max.x, t.x),
y: lerp(self.min.y..=self.max.y, t.y),
Expand Down
9 changes: 6 additions & 3 deletions crates/epaint/src/text/text_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ fn layout_section(
font_ascent: font_metrics.ascent,
uv_rect: glyph_alloc.uv_rect,
section_index,
first_vertex: 0, // filled in later
});

paragraph.cursor_x_px += glyph_alloc.advance_width_px;
Expand Down Expand Up @@ -531,6 +532,7 @@ fn replace_last_glyph_with_overflow_character(
font_ascent: font_metrics.ascent,
uv_rect: replacement_glyph_alloc.uv_rect,
section_index,
first_vertex: 0, // filled in later
});
return;
}
Expand Down Expand Up @@ -748,7 +750,7 @@ fn tessellate_row(
point_scale: PointScale,
job: &LayoutJob,
format_summary: &FormatSummary,
row: &Row,
row: &mut Row,
) -> RowVisuals {
if row.glyphs.is_empty() {
return Default::default();
Expand Down Expand Up @@ -843,8 +845,9 @@ fn add_row_backgrounds(point_scale: PointScale, job: &LayoutJob, row: &Row, mesh
end_run(run_start.take(), last_rect.right());
}

fn tessellate_glyphs(point_scale: PointScale, job: &LayoutJob, row: &Row, mesh: &mut Mesh) {
for glyph in &row.glyphs {
fn tessellate_glyphs(point_scale: PointScale, job: &LayoutJob, row: &mut Row, mesh: &mut Mesh) {
for glyph in &mut row.glyphs {
glyph.first_vertex = mesh.vertices.len() as u32;
let uv_rect = glyph.uv_rect;
if !uv_rect.is_nothing() {
let mut left_top = glyph.pos + uv_rect.offset;
Expand Down
3 changes: 3 additions & 0 deletions crates/epaint/src/text/text_layout_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,9 @@ pub struct Glyph {
/// enable the paragraph-concat optimization path without having to
/// adjust `section_index` when concatting.
pub(crate) section_index: u32,

/// Which is our first vertex in [`RowVisuals::mesh`].
pub first_vertex: u32,
}

impl Glyph {
Expand Down
Loading