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 support for text truncation to egui::Style #4556

Merged
merged 10 commits into from
May 28, 2024
8 changes: 6 additions & 2 deletions crates/egui/src/containers/collapsing_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,8 +506,12 @@ impl CollapsingHeader {
let available = ui.available_rect_before_wrap();
let text_pos = available.min + vec2(ui.spacing().indent, 0.0);
let wrap_width = available.right() - text_pos.x;
let wrap = Some(false);
let galley = text.into_galley(ui, wrap, wrap_width, TextStyle::Button);
let galley = text.into_galley(
ui,
Some(text::TextWrapMode::Extend),
wrap_width,
TextStyle::Button,
);
let text_max_x = text_pos.x + galley.size().x;

let mut desired_width = text_max_x + button_padding.x - available.left();
Expand Down
85 changes: 45 additions & 40 deletions crates/egui/src/containers/combo_box.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use epaint::text::TextWrapMode;
use epaint::Shape;

use crate::{style::WidgetVisuals, *};

#[allow(unused_imports)] // Documentation
use crate::style::Spacing;

/// Indicate whether or not a popup will be shown above or below the box.
/// Indicate whether a popup will be shown above or below the box.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AboveOrBelow {
Above,
Expand Down Expand Up @@ -40,7 +41,7 @@ pub struct ComboBox {
width: Option<f32>,
height: Option<f32>,
icon: Option<IconPainter>,
wrap_enabled: bool,
wrap_mode: Option<TextWrapMode>,
}

impl ComboBox {
Expand All @@ -53,7 +54,7 @@ impl ComboBox {
width: None,
height: None,
icon: None,
wrap_enabled: false,
wrap_mode: None,
}
}

Expand All @@ -67,7 +68,7 @@ impl ComboBox {
width: None,
height: None,
icon: None,
wrap_enabled: false,
wrap_mode: None,
}
}

Expand All @@ -80,7 +81,7 @@ impl ComboBox {
width: None,
height: None,
icon: None,
wrap_enabled: false,
wrap_mode: None,
}
}

Expand Down Expand Up @@ -148,10 +149,23 @@ impl ComboBox {
self
}

/// Controls whether text wrap is used for the selected text
/// Controls whether text wrap is used for the selected text.
#[deprecated = "Use `wrap_mode` instead"]
#[inline]
pub fn wrap(mut self, wrap: bool) -> Self {
self.wrap_enabled = wrap;
self.wrap_mode = if wrap { Some(TextWrapMode::Wrap) } else { None };
self
}

/// Controls the wrap mode used for the selected text.
///
/// By default, the text will extend the [`Ui`] boundary. This default behavior can be
/// overridden with [`Style::wrap_mode`].
abey79 marked this conversation as resolved.
Show resolved Hide resolved
///
/// Note that any `\n` in the text will always produce a new line.
#[inline]
pub fn wrap_mode(mut self, wrap_mode: TextWrapMode) -> Self {
self.wrap_mode = Some(wrap_mode);
self
}

Expand All @@ -178,7 +192,7 @@ impl ComboBox {
width,
height,
icon,
wrap_enabled,
wrap_mode,
} = self;

let button_id = ui.make_persistent_id(id_source);
Expand All @@ -190,7 +204,7 @@ impl ComboBox {
selected_text,
menu_contents,
icon,
wrap_enabled,
wrap_mode,
(width, height),
);
if let Some(label) = label {
Expand Down Expand Up @@ -253,13 +267,14 @@ impl ComboBox {
}
}

#[allow(clippy::too_many_arguments)]
fn combo_box_dyn<'c, R>(
ui: &mut Ui,
button_id: Id,
selected_text: WidgetText,
menu_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
icon: Option<IconPainter>,
wrap_enabled: bool,
wrap_mode: Option<TextWrapMode>,
(width, height): (Option<f32>, Option<f32>),
) -> InnerResponse<Option<R>> {
let popup_id = button_id.with("popup");
Expand All @@ -277,45 +292,33 @@ fn combo_box_dyn<'c, R>(
AboveOrBelow::Above
};

let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());

let margin = ui.spacing().button_padding;
let button_response = button_frame(ui, button_id, is_popup_open, Sense::click(), |ui| {
let icon_spacing = ui.spacing().icon_spacing;
// We don't want to change width when user selects something new
let full_minimum_width = if wrap_enabled {
// Currently selected value's text will be wrapped if needed, so occupy the available width.
ui.available_width()
} else {
// Occupy at least the minimum width assigned to ComboBox.
let width = width.unwrap_or_else(|| ui.spacing().combo_width);
width - 2.0 * margin.x
};
let icon_size = Vec2::splat(ui.spacing().icon_width);
let wrap_width = if wrap_enabled {
// Use the available width, currently selected value's text will be wrapped if exceeds this value.
ui.available_width() - icon_spacing - icon_size.x
} else {
// Use all the width necessary to display the currently selected value's text.
f32::INFINITY
};

let galley =
selected_text.into_galley(ui, Some(wrap_enabled), wrap_width, TextStyle::Button);
// The combo box selected text will always have this minimum width.
// Note: the `ComboBox::width()` if set or `Spacing::combo_width` are considered as the
// minimum overall width, regardless of the wrap mode.
let minimum_width = width.unwrap_or_else(|| ui.spacing().combo_width) - 2.0 * margin.x;

// The width necessary to contain the whole widget with the currently selected value's text.
let width = if wrap_enabled {
full_minimum_width
// width against which to lay out the selected text
let wrap_width = if wrap_mode == TextWrapMode::Extend {
// Use all the width necessary to display the currently selected value's text.
f32::INFINITY
} else {
// Occupy at least the minimum width needed to contain the widget with the currently selected value's text.
galley.size().x + icon_spacing + icon_size.x
// Use the available width, currently selected value's text will be wrapped if exceeds this value.
ui.available_width() - icon_spacing - icon_size.x
};

// Case : wrap_enabled : occupy all the available width.
// Case : !wrap_enabled : occupy at least the minimum width assigned to Slider and ComboBox,
// increase if the currently selected value needs additional horizontal space to fully display its text (up to wrap_width (f32::INFINITY)).
let width = width.at_least(full_minimum_width);
let height = galley.size().y.max(icon_size.y);
let galley = selected_text.into_galley(ui, Some(wrap_mode), wrap_width, TextStyle::Button);

let (_, rect) = ui.allocate_space(Vec2::new(width, height));
let actual_width = (galley.size().x + icon_spacing + icon_size.x).at_least(minimum_width);
let actual_height = galley.size().y.max(icon_size.y);

let (_, rect) = ui.allocate_space(Vec2::new(actual_width, actual_height));
let button_rect = ui.min_rect().expand2(ui.spacing().button_padding);
let response = ui.interact(button_rect, button_id, Sense::click());
// response.active |= is_popup_open;
Expand Down Expand Up @@ -371,7 +374,9 @@ fn combo_box_dyn<'c, R>(
// result in labels that wrap very early.
// Instead, we turn it off by default so that the labels
// expand the width of the menu.
ui.style_mut().wrap = Some(false);
if ui.style().wrap_mode == Some(TextWrapMode::Wrap) {
ui.style_mut().wrap_mode = None;
}
abey79 marked this conversation as resolved.
Show resolved Hide resolved
menu_contents(ui)
})
.inner
Expand Down
7 changes: 6 additions & 1 deletion crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1028,7 +1028,12 @@ fn show_title_bar(
collapsing.show_default_button_with_size(ui, button_size);
}

let title_galley = title.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Heading);
let title_galley = title.into_galley(
ui,
Some(crate::text::TextWrapMode::Extend),
f32::INFINITY,
TextStyle::Heading,
);

let minimum_width = if collapsible || show_close_button {
// If at least one button is shown we make room for both buttons (since title is centered):
Expand Down
6 changes: 2 additions & 4 deletions crates/egui/src/debug_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::*;
///
/// This is a built-in plugin in egui,
/// meaning [`Context`] calls this from its `Default` implementation,
/// so this i marked as `pub(crate)`.
/// so this is marked as `pub(crate)`.
pub(crate) fn register(ctx: &Context) {
ctx.on_end_frame("debug_text", std::sync::Arc::new(State::end_frame));
}
Expand Down Expand Up @@ -105,13 +105,11 @@ impl State {

{
// Paint `text` to right of `pos`:
let wrap = true;
let available_width = ctx.screen_rect().max.x - pos.x;
let galley = text.into_galley_impl(
ctx,
&ctx.style(),
wrap,
available_width,
text::TextWrapping::wrap_at_width(available_width),
font_id.clone().into(),
Align::TOP,
);
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/introspection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ impl Widget for &epaint::stats::PaintStats {
}

fn label(ui: &mut Ui, alloc_info: &epaint::stats::AllocInfo, what: &str) -> Response {
ui.add(Label::new(alloc_info.format(what)).wrap(false))
ui.add(Label::new(alloc_info.format(what)).wrap_mode(text::TextWrapMode::Extend))
}

impl Widget for &mut epaint::TessellationOptions {
Expand Down
5 changes: 3 additions & 2 deletions crates/egui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@
//! ## Code snippets
//!
//! ```
//! # use egui::text::TextWrapMode;
//! # egui::__run_test_ui(|ui| {
//! # let mut some_bool = true;
//! // Miscellaneous tips and tricks
Expand All @@ -358,7 +359,7 @@
//! ui.scope(|ui| {
//! ui.visuals_mut().override_text_color = Some(egui::Color32::RED);
//! ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
//! ui.style_mut().wrap = Some(false);
//! ui.style_mut().wrap_mode = Some(TextWrapMode::Truncate);
//!
//! ui.label("This text will be red, monospace, and won't wrap to a new line");
//! }); // the temporary settings are reverted here
Expand Down Expand Up @@ -436,7 +437,7 @@ pub mod text {
pub use crate::text_selection::{CCursorRange, CursorRange};
pub use epaint::text::{
cursor::CCursor, FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob,
LayoutSection, TextFormat, TextWrapping, TAB_SIZE,
LayoutSection, TextFormat, TextWrapMode, TextWrapping, TAB_SIZE,
};
}

Expand Down
15 changes: 12 additions & 3 deletions crates/egui/src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,11 +479,20 @@ impl SubMenuButton {
let button_padding = ui.spacing().button_padding;
let total_extra = button_padding + button_padding;
let text_available_width = ui.available_width() - total_extra.x;
let text_galley =
text.into_galley(ui, Some(true), text_available_width, text_style.clone());
let text_galley = text.into_galley(
ui,
Some(text::TextWrapMode::Wrap),
text_available_width,
text_style.clone(),
);

let icon_available_width = text_available_width - text_galley.size().x;
let icon_galley = icon.into_galley(ui, Some(true), icon_available_width, text_style);
let icon_galley = icon.into_galley(
ui,
Some(text::TextWrapMode::Wrap),
icon_available_width,
text_style,
);
let text_and_icon_size = Vec2::new(
text_galley.size().x + icon_galley.size().x,
text_galley.size().y.max(icon_galley.size().y),
Expand Down
26 changes: 20 additions & 6 deletions crates/egui/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,15 +182,25 @@ pub struct Style {
/// The style to use for [`DragValue`] text.
pub drag_value_text_style: TextStyle,

/// If set, labels buttons wtc will use this to determine whether or not
/// to wrap the text at the right edge of the [`Ui`] they are in.
/// By default this is `None`.
/// If set, labels, buttons, etc. will use this to determine whether to wrap the text at the
/// right edge of the [`Ui`] they are in. By default, this is `None`.
///
/// * `None`: follow layout
/// * `Some(true)`: default on
/// * `Some(false)`: default off
/// **Note**: this API is deprecated, use `wrap_mode` instead.
///
/// * `None`: use `wrap_mode` instead
/// * `Some(true)`: wrap mode defaults to [`crate::text::TextWrapMode::Wrap`]
/// * `Some(false)`: wrap mode defaults to [`crate::text::TextWrapMode::Extend`]
#[deprecated = "Use wrap_mode instead"]
pub wrap: Option<bool>,

/// If set, labels, buttons, etc. will use this to determine whether to wrap or truncate the
/// text at the right edge of the [`Ui`] they are in, or to extend it. By default, this is
/// `None`.
///
/// * `None`: follow layout (with may wrap)
/// * `Some(mode)`: use the specified mode as default
pub wrap_mode: Option<crate::text::TextWrapMode>,
abey79 marked this conversation as resolved.
Show resolved Hide resolved

/// Sizes and distances between widgets
pub spacing: Spacing,

Expand Down Expand Up @@ -1026,12 +1036,14 @@ pub fn default_text_styles() -> BTreeMap<TextStyle, FontId> {

impl Default for Style {
fn default() -> Self {
#[allow(deprecated)]
Self {
override_font_id: None,
override_text_style: None,
text_styles: default_text_styles(),
drag_value_text_style: TextStyle::Button,
wrap: None,
wrap_mode: None,
spacing: Spacing::default(),
interaction: Interaction::default(),
visuals: Visuals::default(),
Expand Down Expand Up @@ -1317,12 +1329,14 @@ use crate::{widgets::*, Ui};

impl Style {
pub fn ui(&mut self, ui: &mut crate::Ui) {
#[allow(deprecated)]
let Self {
override_font_id,
override_text_style,
text_styles,
drag_value_text_style,
wrap: _,
wrap_mode: _,
spacing,
interaction,
visuals,
Expand Down
Loading
Loading