Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to truncate text at wrap width #3244

Merged
merged 15 commits into from
Aug 14, 2023
2 changes: 1 addition & 1 deletion crates/egui/src/widget_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ impl WidgetTextGalley {
self.galley.size()
}

/// Size of the laid out text.
/// The full, non-elided text of the input job.
#[inline]
pub fn text(&self) -> &str {
self.galley.text()
Expand Down
40 changes: 36 additions & 4 deletions crates/egui/src/widgets/label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ use crate::{widget_text::WidgetTextGalley, *};
/// ui.label(egui::RichText::new("With formatting").underline());
/// # });
/// ```
///
/// For full control of the text you can use [`crate::text::LayoutJob`]
/// as argument to [`Self::new`].
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
pub struct Label {
text: WidgetText,
wrap: Option<bool>,
elide: bool,
sense: Option<Sense>,
}

Expand All @@ -24,6 +28,7 @@ impl Label {
Self {
text: text.into(),
wrap: None,
elide: false,
sense: None,
}
}
Expand All @@ -34,6 +39,8 @@ impl Label {

/// If `true`, the text will wrap to stay within the max width of the [`Ui`].
///
/// Calling `wrap` will override [`Self::elide`].
///
/// By default [`Self::wrap`] will be `true` in vertical layouts
/// and horizontal layouts with wrapping,
/// and `false` on non-wrapping horizontal layouts.
Expand All @@ -44,6 +51,21 @@ impl Label {
#[inline]
pub fn wrap(mut self, wrap: bool) -> Self {
self.wrap = Some(wrap);
self.elide = false;
self
}

/// If `true`, the text will stop at the max width of the [`Ui`],
/// and what doesn't fit will be elided, replaced with `…`.
///
/// Default is `false`, which means the text will expand the parent [`Ui`],
/// or wrap if [`Self::wrap`] is set.
///
/// Calling `elide` will override [`Self::wrap`].
#[inline]
pub fn elide(mut self, elide: bool) -> Self {
self.wrap = None;
self.elide = elide;
self
}

Expand Down Expand Up @@ -98,10 +120,11 @@ impl Label {
.text
.into_text_job(ui.style(), FontSelection::Default, valign);

let should_wrap = self.wrap.unwrap_or_else(|| ui.wrap_text());
let elide = self.elide;
let wrap = !elide && self.wrap.unwrap_or_else(|| ui.wrap_text());
let available_width = ui.available_width();

if should_wrap
if wrap
&& ui.layout().main_dir() == Direction::LeftToRight
&& ui.layout().main_wrap()
&& available_width.is_finite()
Expand Down Expand Up @@ -138,7 +161,11 @@ impl Label {
}
(pos, text_galley, response)
} else {
if should_wrap {
if elide {
text_job.job.wrap.max_width = available_width;
text_job.job.wrap.max_rows = 1;
text_job.job.wrap.break_anywhere = true;
} else if wrap {
text_job.job.wrap.max_width = available_width;
} else {
text_job.job.wrap.max_width = f32::INFINITY;
Expand Down Expand Up @@ -167,9 +194,14 @@ impl Label {

impl Widget for Label {
fn ui(self, ui: &mut Ui) -> Response {
let (pos, text_galley, response) = self.layout_in_ui(ui);
let (pos, text_galley, mut response) = self.layout_in_ui(ui);
response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, text_galley.text()));

if text_galley.galley.elided {
// Show the full (non-elided) text on hover:
response = response.on_hover_text(text_galley.text());
}

if ui.is_rect_visible(response.rect) {
let response_color = ui.style().interact(&response).text_color();

Expand Down
175 changes: 109 additions & 66 deletions crates/egui_demo_lib/src/demo/misc_demo_window.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use super::*;
use crate::LOREM_IPSUM;
use egui::{epaint::text::TextWrapping, *};

/// Showcase some ui code
Expand All @@ -8,9 +7,7 @@ use egui::{epaint::text::TextWrapping, *};
pub struct MiscDemoWindow {
num_columns: usize,

break_anywhere: bool,
max_rows: usize,
overflow_character: Option<char>,
text_break: TextBreakDemo,

widgets: Widgets,
colors: ColorWidgets,
Expand All @@ -27,9 +24,7 @@ impl Default for MiscDemoWindow {
MiscDemoWindow {
num_columns: 2,

max_rows: 2,
break_anywhere: false,
overflow_character: Some('…'),
text_break: Default::default(),

widgets: Default::default(),
colors: Default::default(),
Expand Down Expand Up @@ -61,21 +56,27 @@ impl View for MiscDemoWindow {
fn ui(&mut self, ui: &mut Ui) {
ui.set_min_width(250.0);

CollapsingHeader::new("Widgets")
CollapsingHeader::new("Label")
.default_open(true)
.show(ui, |ui| {
label_ui(ui);
});

CollapsingHeader::new("Misc widgets")
.default_open(false)
.show(ui, |ui| {
self.widgets.ui(ui);
});

CollapsingHeader::new("Text layout")
.default_open(false)
.show(ui, |ui| {
text_layout_ui(
ui,
&mut self.max_rows,
&mut self.break_anywhere,
&mut self.overflow_character,
);
text_layout_demo(ui);
ui.separator();
self.text_break.ui(ui);
ui.vertical_centered(|ui| {
ui.add(crate::egui_github_link_file_line!());
});
});

CollapsingHeader::new("Colors")
Expand Down Expand Up @@ -177,6 +178,43 @@ impl View for MiscDemoWindow {

// ----------------------------------------------------------------------------

fn label_ui(ui: &mut egui::Ui) {
ui.vertical_centered(|ui| {
ui.add(crate::egui_github_link_file_line!());
});

ui.horizontal_wrapped(|ui| {
// Trick so we don't have to add spaces in the text below:
let width = ui.fonts(|f|f.glyph_width(&TextStyle::Body.resolve(ui.style()), ' '));
ui.spacing_mut().item_spacing.x = width;

ui.label(RichText::new("Text can have").color(Color32::from_rgb(110, 255, 110)));
ui.colored_label(Color32::from_rgb(128, 140, 255), "color"); // Shortcut version
ui.label("and tooltips.").on_hover_text(
"This is a multiline tooltip that demonstrates that you can easily add tooltips to any element.\nThis is the second line.\nThis is the third.",
);

ui.label("You can mix in other widgets into text, like");
let _ = ui.small_button("this button");
ui.label(".");

ui.label("The default font supports all latin and cyrillic characters (ИÅđ…), common math symbols (∫√∞²⅓…), and many emojis (💓🌟🖩…).")
.on_hover_text("There is currently no support for right-to-left languages.");
ui.label("See the 🔤 Font Book for more!");

ui.monospace("There is also a monospace font.");
});

ui.add(
egui::Label::new(
"Labels containing long text can be set to elide the text that doesn't fit on a single line using `Label::elide`. When hovered, the label will show the full text.",
)
.elide(true),
);
}

// ----------------------------------------------------------------------------

#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Widgets {
Expand All @@ -200,28 +238,6 @@ impl Widgets {
ui.add(crate::egui_github_link_file_line!());
});

ui.horizontal_wrapped(|ui| {
// Trick so we don't have to add spaces in the text below:
let width = ui.fonts(|f|f.glyph_width(&TextStyle::Body.resolve(ui.style()), ' '));
ui.spacing_mut().item_spacing.x = width;

ui.label(RichText::new("Text can have").color(Color32::from_rgb(110, 255, 110)));
ui.colored_label(Color32::from_rgb(128, 140, 255), "color"); // Shortcut version
ui.label("and tooltips.").on_hover_text(
"This is a multiline tooltip that demonstrates that you can easily add tooltips to any element.\nThis is the second line.\nThis is the third.",
);

ui.label("You can mix in other widgets into text, like");
let _ = ui.small_button("this button");
ui.label(".");

ui.label("The default font supports all latin and cyrillic characters (ИÅđ…), common math symbols (∫√∞²⅓…), and many emojis (💓🌟🖩…).")
.on_hover_text("There is currently no support for right-to-left languages.");
ui.label("See the 🔤 Font Book for more!");

ui.monospace("There is also a monospace font.");
});

let tooltip_ui = |ui: &mut Ui| {
ui.heading("The name of the tooltip");
ui.horizontal(|ui| {
Expand Down Expand Up @@ -473,12 +489,7 @@ impl Tree {

// ----------------------------------------------------------------------------

fn text_layout_ui(
ui: &mut egui::Ui,
max_rows: &mut usize,
break_anywhere: &mut bool,
overflow_character: &mut Option<char>,
) {
fn text_layout_demo(ui: &mut Ui) {
use egui::text::LayoutJob;

let mut job = LayoutJob::default();
Expand Down Expand Up @@ -632,32 +643,64 @@ fn text_layout_ui(
);

ui.label(job);
}

ui.separator();
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
struct TextBreakDemo {
break_anywhere: bool,
max_rows: usize,
overflow_character: Option<char>,
}

ui.horizontal(|ui| {
ui.add(DragValue::new(max_rows));
ui.label("Max rows");
});
ui.checkbox(break_anywhere, "Break anywhere");
ui.horizontal(|ui| {
ui.selectable_value(overflow_character, None, "None");
ui.selectable_value(overflow_character, Some('…'), "…");
ui.selectable_value(overflow_character, Some('—'), "—");
ui.selectable_value(overflow_character, Some('-'), " - ");
ui.label("Overflow character");
});
impl Default for TextBreakDemo {
fn default() -> Self {
Self {
max_rows: 1,
break_anywhere: true,
overflow_character: Some('…'),
}
}
}

let mut job = LayoutJob::single_section(LOREM_IPSUM.to_owned(), TextFormat::default());
job.wrap = TextWrapping {
max_rows: *max_rows,
break_anywhere: *break_anywhere,
overflow_character: *overflow_character,
..Default::default()
};
ui.label(job);
impl TextBreakDemo {
pub fn ui(&mut self, ui: &mut Ui) {
let Self {
break_anywhere,
max_rows,
overflow_character,
} = self;

ui.vertical_centered(|ui| {
ui.add(crate::egui_github_link_file_line!());
});
use egui::text::LayoutJob;

ui.horizontal(|ui| {
ui.add(DragValue::new(max_rows));
ui.label("Max rows");
});

ui.horizontal(|ui| {
ui.label("Break:");
ui.radio_value(break_anywhere, false, "word boundaries");
ui.radio_value(break_anywhere, true, "anywhere");
});

ui.horizontal(|ui| {
ui.selectable_value(overflow_character, None, "None");
ui.selectable_value(overflow_character, Some('…'), "…");
ui.selectable_value(overflow_character, Some('—'), "—");
ui.selectable_value(overflow_character, Some('-'), " - ");
ui.label("Overflow character");
});

let mut job =
LayoutJob::single_section(crate::LOREM_IPSUM_LONG.to_owned(), TextFormat::default());
job.wrap = TextWrapping {
max_rows: *max_rows,
break_anywhere: *break_anywhere,
overflow_character: *overflow_character,
..Default::default()
};

ui.label(job); // `Label` overrides some of the wrapping settings, e.g. wrap width
}
}
Loading
Loading