mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 05:40:03 -04:00
Add option to truncate text at wrap width (#3244)
* Add option to clip text to wrap width * Spelling * Better naming, and report back wether the text was elided * Improve docstrings * Simplify * Fix max_rows with multiple paragraphs * Add note * Typos * fix doclink * Add `Label::elide` * Label: show full non-elided text on hover * Add demo of `Label::elide` * Call it `Label::truncate` * Clarify limitations of `break_anywhere` * Better docstrings
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
use std::ops::RangeInclusive;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, Row, RowVisuals};
|
||||
use crate::{Color32, Mesh, Stroke, Vertex};
|
||||
use emath::*;
|
||||
|
||||
use crate::{Color32, Mesh, Stroke, Vertex};
|
||||
|
||||
use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, Row, RowVisuals};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Represents GUI scale and convenience methods for rounding to pixels.
|
||||
@@ -54,6 +56,20 @@ struct Paragraph {
|
||||
/// In most cases you should use [`crate::Fonts::layout_job`] instead
|
||||
/// since that memoizes the input, making subsequent layouting of the same text much faster.
|
||||
pub fn layout(fonts: &mut FontsImpl, job: Arc<LayoutJob>) -> Galley {
|
||||
if job.wrap.max_rows == 0 {
|
||||
// Early-out: no text
|
||||
return Galley {
|
||||
job,
|
||||
rows: Default::default(),
|
||||
rect: Rect::from_min_max(Pos2::ZERO, Pos2::ZERO),
|
||||
mesh_bounds: Rect::NOTHING,
|
||||
num_vertices: 0,
|
||||
num_indices: 0,
|
||||
pixels_per_point: fonts.pixels_per_point(),
|
||||
elided: true,
|
||||
};
|
||||
}
|
||||
|
||||
let mut paragraphs = vec![Paragraph::default()];
|
||||
for (section_index, section) in job.sections.iter().enumerate() {
|
||||
layout_section(fonts, &job, section_index as u32, section, &mut paragraphs);
|
||||
@@ -61,7 +77,8 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc<LayoutJob>) -> Galley {
|
||||
|
||||
let point_scale = PointScale::new(fonts.pixels_per_point());
|
||||
|
||||
let mut rows = rows_from_paragraphs(fonts, paragraphs, &job);
|
||||
let mut elided = false;
|
||||
let mut rows = rows_from_paragraphs(fonts, paragraphs, &job, &mut elided);
|
||||
|
||||
let justify = job.justify && job.wrap.max_width.is_finite();
|
||||
|
||||
@@ -80,7 +97,7 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc<LayoutJob>) -> Galley {
|
||||
}
|
||||
}
|
||||
|
||||
galley_from_rows(point_scale, job, rows)
|
||||
galley_from_rows(point_scale, job, rows, elided)
|
||||
}
|
||||
|
||||
fn layout_section(
|
||||
@@ -145,12 +162,18 @@ fn rows_from_paragraphs(
|
||||
fonts: &mut FontsImpl,
|
||||
paragraphs: Vec<Paragraph>,
|
||||
job: &LayoutJob,
|
||||
elided: &mut bool,
|
||||
) -> Vec<Row> {
|
||||
let num_paragraphs = paragraphs.len();
|
||||
|
||||
let mut rows = vec![];
|
||||
|
||||
for (i, paragraph) in paragraphs.into_iter().enumerate() {
|
||||
if job.wrap.max_rows <= rows.len() {
|
||||
*elided = true;
|
||||
break;
|
||||
}
|
||||
|
||||
let is_last_paragraph = (i + 1) == num_paragraphs;
|
||||
|
||||
if paragraph.glyphs.is_empty() {
|
||||
@@ -166,7 +189,7 @@ fn rows_from_paragraphs(
|
||||
} else {
|
||||
let paragraph_max_x = paragraph.glyphs.last().unwrap().max_x();
|
||||
if paragraph_max_x <= job.wrap.max_width {
|
||||
// early-out optimization
|
||||
// Early-out optimization: the whole paragraph fits on one row.
|
||||
let paragraph_min_x = paragraph.glyphs[0].pos.x;
|
||||
rows.push(Row {
|
||||
glyphs: paragraph.glyphs,
|
||||
@@ -175,7 +198,7 @@ fn rows_from_paragraphs(
|
||||
ends_with_newline: !is_last_paragraph,
|
||||
});
|
||||
} else {
|
||||
line_break(fonts, ¶graph, job, &mut rows);
|
||||
line_break(fonts, ¶graph, job, &mut rows, elided);
|
||||
rows.last_mut().unwrap().ends_with_newline = !is_last_paragraph;
|
||||
}
|
||||
}
|
||||
@@ -189,6 +212,7 @@ fn line_break(
|
||||
paragraph: &Paragraph,
|
||||
job: &LayoutJob,
|
||||
out_rows: &mut Vec<Row>,
|
||||
elided: &mut bool,
|
||||
) {
|
||||
// Keeps track of good places to insert row break if we exceed `wrap_width`.
|
||||
let mut row_break_candidates = RowBreakCandidates::default();
|
||||
@@ -196,16 +220,18 @@ fn line_break(
|
||||
let mut first_row_indentation = paragraph.glyphs[0].pos.x;
|
||||
let mut row_start_x = 0.0;
|
||||
let mut row_start_idx = 0;
|
||||
let mut non_empty_rows = 0;
|
||||
|
||||
for i in 0..paragraph.glyphs.len() {
|
||||
let potential_row_width = paragraph.glyphs[i].max_x() - row_start_x;
|
||||
|
||||
if job.wrap.max_rows > 0 && non_empty_rows >= job.wrap.max_rows {
|
||||
if job.wrap.max_rows <= out_rows.len() {
|
||||
*elided = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if potential_row_width > job.wrap.max_width {
|
||||
if job.wrap.max_width < potential_row_width {
|
||||
// Row break:
|
||||
|
||||
if first_row_indentation > 0.0
|
||||
&& !row_break_candidates.has_good_candidate(job.wrap.break_anywhere)
|
||||
{
|
||||
@@ -240,10 +266,10 @@ fn line_break(
|
||||
ends_with_newline: false,
|
||||
});
|
||||
|
||||
// Start a new row:
|
||||
row_start_idx = last_kept_index + 1;
|
||||
row_start_x = paragraph.glyphs[row_start_idx].pos.x;
|
||||
row_break_candidates = Default::default();
|
||||
non_empty_rows += 1;
|
||||
} else {
|
||||
// Found no place to break, so we have to overrun wrap_width.
|
||||
}
|
||||
@@ -253,9 +279,12 @@ fn line_break(
|
||||
}
|
||||
|
||||
if row_start_idx < paragraph.glyphs.len() {
|
||||
if job.wrap.max_rows > 0 && non_empty_rows == job.wrap.max_rows {
|
||||
// Final row of text:
|
||||
|
||||
if job.wrap.max_rows <= out_rows.len() {
|
||||
if let Some(last_row) = out_rows.last_mut() {
|
||||
replace_last_glyph_with_overflow_character(fonts, job, last_row);
|
||||
*elided = true;
|
||||
}
|
||||
} else {
|
||||
let glyphs: Vec<Glyph> = paragraph.glyphs[row_start_idx..]
|
||||
@@ -280,6 +309,7 @@ fn line_break(
|
||||
}
|
||||
}
|
||||
|
||||
/// Trims the last glyphs in the row and replaces it with an overflow character (e.g. `…`).
|
||||
fn replace_last_glyph_with_overflow_character(
|
||||
fonts: &mut FontsImpl,
|
||||
job: &LayoutJob,
|
||||
@@ -318,6 +348,7 @@ fn replace_last_glyph_with_overflow_character(
|
||||
let (font_impl, glyph_info) = font.glyph_info_and_font_impl(last_glyph.chr);
|
||||
last_glyph.size = vec2(glyph_info.advance_width, font_height);
|
||||
last_glyph.uv_rect = glyph_info.uv_rect;
|
||||
last_glyph.ascent = glyph_info.ascent;
|
||||
|
||||
// reapply kerning
|
||||
last_glyph.pos.x += font_impl
|
||||
@@ -325,16 +356,20 @@ fn replace_last_glyph_with_overflow_character(
|
||||
.map(|(font_impl, prev_glyph_id)| font_impl.pair_kerning(prev_glyph_id, glyph_info.id))
|
||||
.unwrap_or_default();
|
||||
|
||||
// check if we're still within width budget
|
||||
row.rect.max.x = last_glyph.max_x();
|
||||
|
||||
// check if we're within width budget
|
||||
let row_end_x = last_glyph.max_x();
|
||||
let row_start_x = row.glyphs.first().unwrap().pos.x; // if `last_mut()` returned `Some`, then so will `first()`
|
||||
let row_width = row_end_x - row_start_x;
|
||||
if row_width <= job.wrap.max_width {
|
||||
break;
|
||||
return; // we are done
|
||||
}
|
||||
|
||||
row.glyphs.pop();
|
||||
}
|
||||
|
||||
// We failed to insert `overflow_character` without exceeding `wrap_width`.
|
||||
}
|
||||
|
||||
fn halign_and_justify_row(
|
||||
@@ -428,7 +463,12 @@ fn halign_and_justify_row(
|
||||
}
|
||||
|
||||
/// Calculate the Y positions and tessellate the text.
|
||||
fn galley_from_rows(point_scale: PointScale, job: Arc<LayoutJob>, mut rows: Vec<Row>) -> Galley {
|
||||
fn galley_from_rows(
|
||||
point_scale: PointScale,
|
||||
job: Arc<LayoutJob>,
|
||||
mut rows: Vec<Row>,
|
||||
elided: bool,
|
||||
) -> Galley {
|
||||
let mut first_row_min_height = job.first_row_min_height;
|
||||
let mut cursor_y = 0.0;
|
||||
let mut min_x: f32 = 0.0;
|
||||
@@ -489,6 +529,7 @@ fn galley_from_rows(point_scale: PointScale, job: Arc<LayoutJob>, mut rows: Vec<
|
||||
Galley {
|
||||
job,
|
||||
rows,
|
||||
elided,
|
||||
rect,
|
||||
mesh_bounds,
|
||||
num_vertices,
|
||||
|
||||
Reference in New Issue
Block a user