mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 22:53:14 -04:00
Change text color of selected text (#7691)
Selected text now gets the color of `visuals.selection.stroke.color`. This means you can have inverted colors for selected text, like in the new test: <img width="154" height="46" alt="image" src="https://github.com/user-attachments/assets/2666361d-d7e2-4d50-8e4d-2fcc128f1a81" /> It also means the color of selected text in labels matches that of the text color of selected buttons.
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -52,6 +54,31 @@ 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:
|
||||
@@ -59,7 +86,7 @@ pub fn paint_text_selection(
|
||||
|
||||
// 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(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use egui_kittest::Harness;
|
||||
use egui::{Color32, accesskit::Role};
|
||||
use egui_kittest::{Harness, kittest::Queryable as _};
|
||||
|
||||
#[test]
|
||||
fn test_kerning() {
|
||||
@@ -45,7 +46,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",
|
||||
@@ -55,3 +56,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
crates/egui_demo_lib/tests/snapshots/text_selection.png
Normal file
3
crates/egui_demo_lib/tests/snapshots/text_selection.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:14f253fedc94985ff1431f1016d901d747e1f9948531cc6350f6615649f29056
|
||||
size 4862
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
@@ -532,6 +533,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;
|
||||
}
|
||||
@@ -749,7 +751,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();
|
||||
@@ -844,8 +846,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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user