Add support for text truncation to `egui::Style` (#4556)
* Closes #4473 This PR introduce `Style::wrap_mode`, which adds support for text truncation in addition to text wrapping. This PR also update some width calculation of the ComboBox. #### Core - Add `egui::TextWrapMode` (pure enum with `Extend`, `Wrap`, `Truncate`) - Add `Style::wrap_mode: Option<tTextWrapMode>` - **DEPRECATED**: `Style::wrap`, use `Style::wrap_mode` instead. - Add `Ui::wrap_mode()` to return the wrap mode to use in the current ui. If specified in `Style`, return it. Otherwise, return `TextWrapMode::Wrap` for vertical layout and wrapping horizontal layout, and `TextWrapMode::Extend` otherwise. - **DEPRECATED**: `Ui::wrap_text()`, use `Ui::wrap_mode` instead. #### Widget - Update the width calculation of the `ComboBox` button (_not_ its popup menu). - Now, `ComboBox::width()` (defaulting to `Spacing::combo_width`) is always considered a minimum width and will extend the `Ui`, regardless of the selected text width and wrap mode. - Introduce `ComboBox::wrap_mode`, which overrides `Ui::wrap_mode` for the selected text layout. - Note: since `ComboBox` uses `ui.horizontal` internally, the default wrap mode is always `TextWrapMode::Extend`, regardless of the caller's `Ui`'s layout. - The `ComboBox` button no longer extend to `ui.available_width()` with wrapping is enabled. - **BREAKING**: `ComboBox::wrap()` no longer has a `bool` argument and is now a short-hand for `ComboBox::wrap_mode(TextWrapMode::Wrap)`. - Added `ComboBox::truncate()` as short-hand for `ComboBox::wrap_mode(TextWrapMode::Truncate)`. - Update `Label` - Add `Label::wrap_mode()` to specify the text wrap mode. - **BREAKING**: `Label::wrap()` no longer has a `bool` argument and is now a short-hand for `Label::wrap_mode(TextWrapMode::Wrap)`. - **BREAKING**: `Label::truncate()` no longer has a `bool` argument and is now a short-hand for `Label::wrap_mode(TextWrapMode::Truncate)`. - Update `Button` - Add `Button::wrap_mode()` to specify the text wrap mode. - **BREAKING**: `Button::wrap()` no longer has a `bool` argument and is now a short-hand for `Button::wrap_mode(TextWrapMode::Wrap)`. - Added `Button::truncate()` as short-hand for `Button::wrap_mode(TextWrapMode::Truncate)`. #### Low-level - **BREAKING**: `WidgetText::into_galley()` now takes an `Option<TextWrapMode>` instead of a `Option<bool>` argument. - **BREAKING**: `WidgetText::into_galley_impl(()` now takes a `TextWrapping` argument instead of `wrap: bool` and `availalbe_width: f32` arguments. --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
4b59c6d414
commit
bcd91f27a1
|
|
@ -506,8 +506,12 @@ impl CollapsingHeader {
|
||||||
let available = ui.available_rect_before_wrap();
|
let available = ui.available_rect_before_wrap();
|
||||||
let text_pos = available.min + vec2(ui.spacing().indent, 0.0);
|
let text_pos = available.min + vec2(ui.spacing().indent, 0.0);
|
||||||
let wrap_width = available.right() - text_pos.x;
|
let wrap_width = available.right() - text_pos.x;
|
||||||
let wrap = Some(false);
|
let galley = text.into_galley(
|
||||||
let galley = text.into_galley(ui, wrap, wrap_width, TextStyle::Button);
|
ui,
|
||||||
|
Some(TextWrapMode::Extend),
|
||||||
|
wrap_width,
|
||||||
|
TextStyle::Button,
|
||||||
|
);
|
||||||
let text_max_x = text_pos.x + galley.size().x;
|
let text_max_x = text_pos.x + galley.size().x;
|
||||||
|
|
||||||
let mut desired_width = text_max_x + button_padding.x - available.left();
|
let mut desired_width = text_max_x + button_padding.x - available.left();
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::{style::WidgetVisuals, *};
|
||||||
#[allow(unused_imports)] // Documentation
|
#[allow(unused_imports)] // Documentation
|
||||||
use crate::style::Spacing;
|
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)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum AboveOrBelow {
|
pub enum AboveOrBelow {
|
||||||
Above,
|
Above,
|
||||||
|
|
@ -40,7 +40,7 @@ pub struct ComboBox {
|
||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
height: Option<f32>,
|
height: Option<f32>,
|
||||||
icon: Option<IconPainter>,
|
icon: Option<IconPainter>,
|
||||||
wrap_enabled: bool,
|
wrap_mode: Option<TextWrapMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComboBox {
|
impl ComboBox {
|
||||||
|
|
@ -53,7 +53,7 @@ impl ComboBox {
|
||||||
width: None,
|
width: None,
|
||||||
height: None,
|
height: None,
|
||||||
icon: None,
|
icon: None,
|
||||||
wrap_enabled: false,
|
wrap_mode: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ impl ComboBox {
|
||||||
width: None,
|
width: None,
|
||||||
height: None,
|
height: None,
|
||||||
icon: None,
|
icon: None,
|
||||||
wrap_enabled: false,
|
wrap_mode: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ impl ComboBox {
|
||||||
width: None,
|
width: None,
|
||||||
height: None,
|
height: None,
|
||||||
icon: None,
|
icon: None,
|
||||||
wrap_enabled: false,
|
wrap_mode: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,10 +148,29 @@ impl ComboBox {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Controls whether text wrap is used for the selected text
|
/// Controls the wrap mode used for the selected text.
|
||||||
|
///
|
||||||
|
/// By default, [`Ui::wrap_mode`] will be used, which can be overridden with [`Style::wrap_mode`].
|
||||||
|
///
|
||||||
|
/// Note that any `\n` in the text will always produce a new line.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn wrap(mut self, wrap: bool) -> Self {
|
pub fn wrap_mode(mut self, wrap_mode: TextWrapMode) -> Self {
|
||||||
self.wrap_enabled = wrap;
|
self.wrap_mode = Some(wrap_mode);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set [`Self::wrap_mode`] to [`TextWrapMode::Wrap`].
|
||||||
|
#[inline]
|
||||||
|
pub fn wrap(mut self) -> Self {
|
||||||
|
self.wrap_mode = Some(TextWrapMode::Wrap);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set [`Self::wrap_mode`] to [`TextWrapMode::Truncate`].
|
||||||
|
#[inline]
|
||||||
|
pub fn truncate(mut self) -> Self {
|
||||||
|
self.wrap_mode = Some(TextWrapMode::Truncate);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,7 +197,7 @@ impl ComboBox {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
icon,
|
icon,
|
||||||
wrap_enabled,
|
wrap_mode,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let button_id = ui.make_persistent_id(id_source);
|
let button_id = ui.make_persistent_id(id_source);
|
||||||
|
|
@ -190,7 +209,7 @@ impl ComboBox {
|
||||||
selected_text,
|
selected_text,
|
||||||
menu_contents,
|
menu_contents,
|
||||||
icon,
|
icon,
|
||||||
wrap_enabled,
|
wrap_mode,
|
||||||
(width, height),
|
(width, height),
|
||||||
);
|
);
|
||||||
if let Some(label) = label {
|
if let Some(label) = label {
|
||||||
|
|
@ -253,13 +272,14 @@ impl ComboBox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn combo_box_dyn<'c, R>(
|
fn combo_box_dyn<'c, R>(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
button_id: Id,
|
button_id: Id,
|
||||||
selected_text: WidgetText,
|
selected_text: WidgetText,
|
||||||
menu_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
menu_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||||
icon: Option<IconPainter>,
|
icon: Option<IconPainter>,
|
||||||
wrap_enabled: bool,
|
wrap_mode: Option<TextWrapMode>,
|
||||||
(width, height): (Option<f32>, Option<f32>),
|
(width, height): (Option<f32>, Option<f32>),
|
||||||
) -> InnerResponse<Option<R>> {
|
) -> InnerResponse<Option<R>> {
|
||||||
let popup_id = button_id.with("popup");
|
let popup_id = button_id.with("popup");
|
||||||
|
|
@ -277,45 +297,33 @@ fn combo_box_dyn<'c, R>(
|
||||||
AboveOrBelow::Above
|
AboveOrBelow::Above
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());
|
||||||
|
|
||||||
let margin = ui.spacing().button_padding;
|
let margin = ui.spacing().button_padding;
|
||||||
let button_response = button_frame(ui, button_id, is_popup_open, Sense::click(), |ui| {
|
let button_response = button_frame(ui, button_id, is_popup_open, Sense::click(), |ui| {
|
||||||
let icon_spacing = ui.spacing().icon_spacing;
|
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 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.
|
// The combo box selected text will always have this minimum width.
|
||||||
ui.available_width() - icon_spacing - icon_size.x
|
// Note: the `ComboBox::width()` if set or `Spacing::combo_width` are considered as the
|
||||||
} else {
|
// minimum overall width, regardless of the wrap mode.
|
||||||
|
let minimum_width = width.unwrap_or_else(|| ui.spacing().combo_width) - 2.0 * margin.x;
|
||||||
|
|
||||||
|
// 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.
|
// Use all the width necessary to display the currently selected value's text.
|
||||||
f32::INFINITY
|
f32::INFINITY
|
||||||
};
|
|
||||||
|
|
||||||
let galley =
|
|
||||||
selected_text.into_galley(ui, Some(wrap_enabled), wrap_width, TextStyle::Button);
|
|
||||||
|
|
||||||
// The width necessary to contain the whole widget with the currently selected value's text.
|
|
||||||
let width = if wrap_enabled {
|
|
||||||
full_minimum_width
|
|
||||||
} else {
|
} else {
|
||||||
// Occupy at least the minimum width needed to contain the widget with the currently selected value's text.
|
// Use the available width, currently selected value's text will be wrapped if exceeds this value.
|
||||||
galley.size().x + icon_spacing + icon_size.x
|
ui.available_width() - icon_spacing - icon_size.x
|
||||||
};
|
};
|
||||||
|
|
||||||
// Case : wrap_enabled : occupy all the available width.
|
let galley = selected_text.into_galley(ui, Some(wrap_mode), wrap_width, TextStyle::Button);
|
||||||
// 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 (_, 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 button_rect = ui.min_rect().expand2(ui.spacing().button_padding);
|
||||||
let response = ui.interact(button_rect, button_id, Sense::click());
|
let response = ui.interact(button_rect, button_id, Sense::click());
|
||||||
// response.active |= is_popup_open;
|
// response.active |= is_popup_open;
|
||||||
|
|
@ -371,7 +379,7 @@ fn combo_box_dyn<'c, R>(
|
||||||
// result in labels that wrap very early.
|
// result in labels that wrap very early.
|
||||||
// Instead, we turn it off by default so that the labels
|
// Instead, we turn it off by default so that the labels
|
||||||
// expand the width of the menu.
|
// expand the width of the menu.
|
||||||
ui.style_mut().wrap = Some(false);
|
ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
|
||||||
menu_contents(ui)
|
menu_contents(ui)
|
||||||
})
|
})
|
||||||
.inner
|
.inner
|
||||||
|
|
|
||||||
|
|
@ -1028,7 +1028,12 @@ fn show_title_bar(
|
||||||
collapsing.show_default_button_with_size(ui, button_size);
|
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::TextWrapMode::Extend),
|
||||||
|
f32::INFINITY,
|
||||||
|
TextStyle::Heading,
|
||||||
|
);
|
||||||
|
|
||||||
let minimum_width = if collapsible || show_close_button {
|
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):
|
// If at least one button is shown we make room for both buttons (since title is centered):
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use crate::*;
|
||||||
///
|
///
|
||||||
/// This is a built-in plugin in egui,
|
/// This is a built-in plugin in egui,
|
||||||
/// meaning [`Context`] calls this from its `Default` implementation,
|
/// 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) {
|
pub(crate) fn register(ctx: &Context) {
|
||||||
ctx.on_end_frame("debug_text", std::sync::Arc::new(State::end_frame));
|
ctx.on_end_frame("debug_text", std::sync::Arc::new(State::end_frame));
|
||||||
}
|
}
|
||||||
|
|
@ -105,13 +105,11 @@ impl State {
|
||||||
|
|
||||||
{
|
{
|
||||||
// Paint `text` to right of `pos`:
|
// Paint `text` to right of `pos`:
|
||||||
let wrap = true;
|
|
||||||
let available_width = ctx.screen_rect().max.x - pos.x;
|
let available_width = ctx.screen_rect().max.x - pos.x;
|
||||||
let galley = text.into_galley_impl(
|
let galley = text.into_galley_impl(
|
||||||
ctx,
|
ctx,
|
||||||
&ctx.style(),
|
&ctx.style(),
|
||||||
wrap,
|
text::TextWrapping::wrap_at_width(available_width),
|
||||||
available_width,
|
|
||||||
font_id.clone().into(),
|
font_id.clone().into(),
|
||||||
Align::TOP,
|
Align::TOP,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ impl Widget for &epaint::stats::PaintStats {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn label(ui: &mut Ui, alloc_info: &epaint::stats::AllocInfo, what: &str) -> Response {
|
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(TextWrapMode::Extend))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for &mut epaint::TessellationOptions {
|
impl Widget for &mut epaint::TessellationOptions {
|
||||||
|
|
|
||||||
|
|
@ -338,6 +338,7 @@
|
||||||
//! ## Code snippets
|
//! ## Code snippets
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
|
//! # use egui::TextWrapMode;
|
||||||
//! # egui::__run_test_ui(|ui| {
|
//! # egui::__run_test_ui(|ui| {
|
||||||
//! # let mut some_bool = true;
|
//! # let mut some_bool = true;
|
||||||
//! // Miscellaneous tips and tricks
|
//! // Miscellaneous tips and tricks
|
||||||
|
|
@ -358,7 +359,7 @@
|
||||||
//! ui.scope(|ui| {
|
//! ui.scope(|ui| {
|
||||||
//! ui.visuals_mut().override_text_color = Some(egui::Color32::RED);
|
//! ui.visuals_mut().override_text_color = Some(egui::Color32::RED);
|
||||||
//! ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
//! 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");
|
//! ui.label("This text will be red, monospace, and won't wrap to a new line");
|
||||||
//! }); // the temporary settings are reverted here
|
//! }); // the temporary settings are reverted here
|
||||||
|
|
@ -451,6 +452,7 @@ pub use {
|
||||||
Key,
|
Key,
|
||||||
},
|
},
|
||||||
drag_and_drop::DragAndDrop,
|
drag_and_drop::DragAndDrop,
|
||||||
|
epaint::text::TextWrapMode,
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
id::{Id, IdMap},
|
id::{Id, IdMap},
|
||||||
input_state::{InputState, MultiTouchInfo, PointerState},
|
input_state::{InputState, MultiTouchInfo, PointerState},
|
||||||
|
|
|
||||||
|
|
@ -479,11 +479,20 @@ impl SubMenuButton {
|
||||||
let button_padding = ui.spacing().button_padding;
|
let button_padding = ui.spacing().button_padding;
|
||||||
let total_extra = button_padding + button_padding;
|
let total_extra = button_padding + button_padding;
|
||||||
let text_available_width = ui.available_width() - total_extra.x;
|
let text_available_width = ui.available_width() - total_extra.x;
|
||||||
let text_galley =
|
let text_galley = text.into_galley(
|
||||||
text.into_galley(ui, Some(true), text_available_width, text_style.clone());
|
ui,
|
||||||
|
Some(TextWrapMode::Wrap),
|
||||||
|
text_available_width,
|
||||||
|
text_style.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
let icon_available_width = text_available_width - text_galley.size().x;
|
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(TextWrapMode::Wrap),
|
||||||
|
icon_available_width,
|
||||||
|
text_style,
|
||||||
|
);
|
||||||
let text_and_icon_size = Vec2::new(
|
let text_and_icon_size = Vec2::new(
|
||||||
text_galley.size().x + icon_galley.size().x,
|
text_galley.size().x + icon_galley.size().x,
|
||||||
text_galley.size().y.max(icon_galley.size().y),
|
text_galley.size().y.max(icon_galley.size().y),
|
||||||
|
|
|
||||||
|
|
@ -182,15 +182,25 @@ pub struct Style {
|
||||||
/// The style to use for [`DragValue`] text.
|
/// The style to use for [`DragValue`] text.
|
||||||
pub drag_value_text_style: TextStyle,
|
pub drag_value_text_style: TextStyle,
|
||||||
|
|
||||||
/// If set, labels buttons wtc will use this to determine whether or not
|
/// If set, labels, buttons, etc. will use this to determine whether to wrap the text at the
|
||||||
/// to wrap the text at the right edge of the [`Ui`] they are in.
|
/// right edge of the [`Ui`] they are in. By default, this is `None`.
|
||||||
/// By default this is `None`.
|
|
||||||
///
|
///
|
||||||
/// * `None`: follow layout
|
/// **Note**: this API is deprecated, use `wrap_mode` instead.
|
||||||
/// * `Some(true)`: default on
|
///
|
||||||
/// * `Some(false)`: default off
|
/// * `None`: use `wrap_mode` instead
|
||||||
|
/// * `Some(true)`: wrap mode defaults to [`crate::TextWrapMode::Wrap`]
|
||||||
|
/// * `Some(false)`: wrap mode defaults to [`crate::TextWrapMode::Extend`]
|
||||||
|
#[deprecated = "Use wrap_mode instead"]
|
||||||
pub wrap: Option<bool>,
|
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::TextWrapMode>,
|
||||||
|
|
||||||
/// Sizes and distances between widgets
|
/// Sizes and distances between widgets
|
||||||
pub spacing: Spacing,
|
pub spacing: Spacing,
|
||||||
|
|
||||||
|
|
@ -1026,12 +1036,14 @@ pub fn default_text_styles() -> BTreeMap<TextStyle, FontId> {
|
||||||
|
|
||||||
impl Default for Style {
|
impl Default for Style {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
#[allow(deprecated)]
|
||||||
Self {
|
Self {
|
||||||
override_font_id: None,
|
override_font_id: None,
|
||||||
override_text_style: None,
|
override_text_style: None,
|
||||||
text_styles: default_text_styles(),
|
text_styles: default_text_styles(),
|
||||||
drag_value_text_style: TextStyle::Button,
|
drag_value_text_style: TextStyle::Button,
|
||||||
wrap: None,
|
wrap: None,
|
||||||
|
wrap_mode: None,
|
||||||
spacing: Spacing::default(),
|
spacing: Spacing::default(),
|
||||||
interaction: Interaction::default(),
|
interaction: Interaction::default(),
|
||||||
visuals: Visuals::default(),
|
visuals: Visuals::default(),
|
||||||
|
|
@ -1317,12 +1329,14 @@ use crate::{widgets::*, Ui};
|
||||||
|
|
||||||
impl Style {
|
impl Style {
|
||||||
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
||||||
|
#[allow(deprecated)]
|
||||||
let Self {
|
let Self {
|
||||||
override_font_id,
|
override_font_id,
|
||||||
override_text_style,
|
override_text_style,
|
||||||
text_styles,
|
text_styles,
|
||||||
drag_value_text_style,
|
drag_value_text_style,
|
||||||
wrap: _,
|
wrap: _,
|
||||||
|
wrap_mode: _,
|
||||||
spacing,
|
spacing,
|
||||||
interaction,
|
interaction,
|
||||||
visuals,
|
visuals,
|
||||||
|
|
|
||||||
|
|
@ -329,20 +329,45 @@ impl Ui {
|
||||||
self.placer.layout()
|
self.placer.layout()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should text wrap in this [`Ui`]?
|
/// Which wrap mode should the text use in this [`Ui`]?
|
||||||
///
|
///
|
||||||
/// This is determined first by [`Style::wrap`], and then by the layout of this [`Ui`].
|
/// This is determined first by [`Style::wrap_mode`], and then by the layout of this [`Ui`].
|
||||||
pub fn wrap_text(&self) -> bool {
|
pub fn wrap_mode(&self) -> TextWrapMode {
|
||||||
if let Some(wrap) = self.style.wrap {
|
#[allow(deprecated)]
|
||||||
wrap
|
if let Some(wrap_mode) = self.style.wrap_mode {
|
||||||
|
wrap_mode
|
||||||
|
}
|
||||||
|
// `wrap` handling for backward compatibility
|
||||||
|
else if let Some(wrap) = self.style.wrap {
|
||||||
|
if wrap {
|
||||||
|
TextWrapMode::Wrap
|
||||||
|
} else {
|
||||||
|
TextWrapMode::Extend
|
||||||
|
}
|
||||||
} else if let Some(grid) = self.placer.grid() {
|
} else if let Some(grid) = self.placer.grid() {
|
||||||
grid.wrap_text()
|
if grid.wrap_text() {
|
||||||
|
TextWrapMode::Wrap
|
||||||
|
} else {
|
||||||
|
TextWrapMode::Extend
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let layout = self.layout();
|
let layout = self.layout();
|
||||||
layout.is_vertical() || layout.is_horizontal() && layout.main_wrap()
|
if layout.is_vertical() || layout.is_horizontal() && layout.main_wrap() {
|
||||||
|
TextWrapMode::Wrap
|
||||||
|
} else {
|
||||||
|
TextWrapMode::Extend
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Should text wrap in this [`Ui`]?
|
||||||
|
///
|
||||||
|
/// This is determined first by [`Style::wrap_mode`], and then by the layout of this [`Ui`].
|
||||||
|
#[deprecated = "Use `wrap_mode` instead"]
|
||||||
|
pub fn wrap_text(&self) -> bool {
|
||||||
|
self.wrap_mode() == TextWrapMode::Wrap
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a painter for a sub-region of this Ui.
|
/// Create a painter for a sub-region of this Ui.
|
||||||
///
|
///
|
||||||
/// The clip-rect of the returned [`Painter`] will be the intersection
|
/// The clip-rect of the returned [`Painter`] will be the intersection
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use std::{borrow::Cow, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
text::LayoutJob, Align, Color32, FontFamily, FontSelection, Galley, Style, TextStyle, Ui,
|
text::{LayoutJob, TextWrapping},
|
||||||
Visuals,
|
Align, Color32, FontFamily, FontSelection, Galley, Style, TextStyle, TextWrapMode, Ui, Visuals,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Text and optional style choices for it.
|
/// Text and optional style choices for it.
|
||||||
|
|
@ -640,47 +640,39 @@ impl WidgetText {
|
||||||
|
|
||||||
/// Layout with wrap mode based on the containing [`Ui`].
|
/// Layout with wrap mode based on the containing [`Ui`].
|
||||||
///
|
///
|
||||||
/// wrap: override for [`Ui::wrap_text`].
|
/// `wrap_mode`: override for [`Ui::wrap_mode`]
|
||||||
pub fn into_galley(
|
pub fn into_galley(
|
||||||
self,
|
self,
|
||||||
ui: &Ui,
|
ui: &Ui,
|
||||||
wrap: Option<bool>,
|
wrap_mode: Option<TextWrapMode>,
|
||||||
available_width: f32,
|
available_width: f32,
|
||||||
fallback_font: impl Into<FontSelection>,
|
fallback_font: impl Into<FontSelection>,
|
||||||
) -> Arc<Galley> {
|
) -> Arc<Galley> {
|
||||||
let wrap = wrap.unwrap_or_else(|| ui.wrap_text());
|
|
||||||
let valign = ui.layout().vertical_align();
|
let valign = ui.layout().vertical_align();
|
||||||
let style = ui.style();
|
let style = ui.style();
|
||||||
|
|
||||||
self.into_galley_impl(
|
let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());
|
||||||
ui.ctx(),
|
let text_wrapping = TextWrapping::from_wrap_mode_and_width(wrap_mode, available_width);
|
||||||
style,
|
|
||||||
wrap,
|
self.into_galley_impl(ui.ctx(), style, text_wrapping, fallback_font.into(), valign)
|
||||||
available_width,
|
|
||||||
fallback_font.into(),
|
|
||||||
valign,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_galley_impl(
|
pub fn into_galley_impl(
|
||||||
self,
|
self,
|
||||||
ctx: &crate::Context,
|
ctx: &crate::Context,
|
||||||
style: &Style,
|
style: &Style,
|
||||||
wrap: bool,
|
text_wrapping: TextWrapping,
|
||||||
available_width: f32,
|
|
||||||
fallback_font: FontSelection,
|
fallback_font: FontSelection,
|
||||||
default_valign: Align,
|
default_valign: Align,
|
||||||
) -> Arc<Galley> {
|
) -> Arc<Galley> {
|
||||||
let wrap_width = if wrap { available_width } else { f32::INFINITY };
|
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::RichText(text) => {
|
Self::RichText(text) => {
|
||||||
let mut layout_job = text.into_layout_job(style, fallback_font, default_valign);
|
let mut layout_job = text.into_layout_job(style, fallback_font, default_valign);
|
||||||
layout_job.wrap.max_width = wrap_width;
|
layout_job.wrap = text_wrapping;
|
||||||
ctx.fonts(|f| f.layout_job(layout_job))
|
ctx.fonts(|f| f.layout_job(layout_job))
|
||||||
}
|
}
|
||||||
Self::LayoutJob(mut job) => {
|
Self::LayoutJob(mut job) => {
|
||||||
job.wrap.max_width = wrap_width;
|
job.wrap = text_wrapping;
|
||||||
ctx.fonts(|f| f.layout_job(job))
|
ctx.fonts(|f| f.layout_job(job))
|
||||||
}
|
}
|
||||||
Self::Galley(galley) => galley,
|
Self::Galley(galley) => galley,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ pub struct Button<'a> {
|
||||||
image: Option<Image<'a>>,
|
image: Option<Image<'a>>,
|
||||||
text: Option<WidgetText>,
|
text: Option<WidgetText>,
|
||||||
shortcut_text: WidgetText,
|
shortcut_text: WidgetText,
|
||||||
wrap: Option<bool>,
|
wrap_mode: Option<TextWrapMode>,
|
||||||
|
|
||||||
/// None means default for interact
|
/// None means default for interact
|
||||||
fill: Option<Color32>,
|
fill: Option<Color32>,
|
||||||
|
|
@ -58,7 +58,7 @@ impl<'a> Button<'a> {
|
||||||
text,
|
text,
|
||||||
image,
|
image,
|
||||||
shortcut_text: Default::default(),
|
shortcut_text: Default::default(),
|
||||||
wrap: None,
|
wrap_mode: None,
|
||||||
fill: None,
|
fill: None,
|
||||||
stroke: None,
|
stroke: None,
|
||||||
sense: Sense::click(),
|
sense: Sense::click(),
|
||||||
|
|
@ -70,16 +70,29 @@ impl<'a> Button<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `true`, the text will wrap to stay within the max width of the [`Ui`].
|
/// Set the wrap mode for the text.
|
||||||
///
|
///
|
||||||
/// By default [`Self::wrap`] will be true in vertical layouts
|
/// By default, [`Ui::wrap_mode`] will be used, which can be overridden with [`Style::wrap_mode`].
|
||||||
/// and horizontal layouts with wrapping,
|
|
||||||
/// and false on non-wrapping horizontal layouts.
|
|
||||||
///
|
///
|
||||||
/// Note that any `\n` in the text will always produce a new line.
|
/// Note that any `\n` in the text will always produce a new line.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn wrap(mut self, wrap: bool) -> Self {
|
pub fn wrap_mode(mut self, wrap_mode: TextWrapMode) -> Self {
|
||||||
self.wrap = Some(wrap);
|
self.wrap_mode = Some(wrap_mode);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set [`Self::wrap_mode`] to [`TextWrapMode::Wrap`].
|
||||||
|
#[inline]
|
||||||
|
pub fn wrap(mut self) -> Self {
|
||||||
|
self.wrap_mode = Some(TextWrapMode::Wrap);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set [`Self::wrap_mode`] to [`TextWrapMode::Truncate`].
|
||||||
|
#[inline]
|
||||||
|
pub fn truncate(mut self) -> Self {
|
||||||
|
self.wrap_mode = Some(TextWrapMode::Truncate);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,7 +178,7 @@ impl Widget for Button<'_> {
|
||||||
text,
|
text,
|
||||||
image,
|
image,
|
||||||
shortcut_text,
|
shortcut_text,
|
||||||
wrap,
|
wrap_mode,
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
sense,
|
sense,
|
||||||
|
|
@ -211,9 +224,15 @@ impl Widget for Button<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let galley =
|
let galley =
|
||||||
text.map(|text| text.into_galley(ui, wrap, text_wrap_width, TextStyle::Button));
|
text.map(|text| text.into_galley(ui, wrap_mode, text_wrap_width, TextStyle::Button));
|
||||||
let shortcut_galley = (!shortcut_text.is_empty())
|
let shortcut_galley = (!shortcut_text.is_empty()).then(|| {
|
||||||
.then(|| shortcut_text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button));
|
shortcut_text.into_galley(
|
||||||
|
ui,
|
||||||
|
Some(TextWrapMode::Extend),
|
||||||
|
f32::INFINITY,
|
||||||
|
TextStyle::Button,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let mut desired_size = Vec2::ZERO;
|
let mut desired_size = Vec2::ZERO;
|
||||||
if image.is_some() {
|
if image.is_some() {
|
||||||
|
|
|
||||||
|
|
@ -521,7 +521,7 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
RichText::new(format!("{}{}{}", prefix, value_text.clone(), suffix))
|
RichText::new(format!("{}{}{}", prefix, value_text.clone(), suffix))
|
||||||
.text_style(text_style),
|
.text_style(text_style),
|
||||||
)
|
)
|
||||||
.wrap(false)
|
.wrap_mode(TextWrapMode::Extend)
|
||||||
.sense(Sense::click_and_drag())
|
.sense(Sense::click_and_drag())
|
||||||
.min_size(ui.spacing().interact_size); // TODO(emilk): find some more generic solution to `min_size`
|
.min_size(ui.spacing().interact_size); // TODO(emilk): find some more generic solution to `min_size`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,11 @@ use self::text_selection::LabelSelectionState;
|
||||||
/// Usually it is more convenient to use [`Ui::label`].
|
/// Usually it is more convenient to use [`Ui::label`].
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
/// # use egui::TextWrapMode;
|
||||||
/// # egui::__run_test_ui(|ui| {
|
/// # egui::__run_test_ui(|ui| {
|
||||||
/// ui.label("Equivalent");
|
/// ui.label("Equivalent");
|
||||||
/// ui.add(egui::Label::new("Equivalent"));
|
/// ui.add(egui::Label::new("Equivalent"));
|
||||||
/// ui.add(egui::Label::new("With Options").wrap(false));
|
/// ui.add(egui::Label::new("With Options").truncate());
|
||||||
/// ui.label(egui::RichText::new("With formatting").underline());
|
/// ui.label(egui::RichText::new("With formatting").underline());
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -22,8 +23,7 @@ use self::text_selection::LabelSelectionState;
|
||||||
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||||
pub struct Label {
|
pub struct Label {
|
||||||
text: WidgetText,
|
text: WidgetText,
|
||||||
wrap: Option<bool>,
|
wrap_mode: Option<TextWrapMode>,
|
||||||
truncate: bool,
|
|
||||||
sense: Option<Sense>,
|
sense: Option<Sense>,
|
||||||
selectable: Option<bool>,
|
selectable: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
@ -32,8 +32,7 @@ impl Label {
|
||||||
pub fn new(text: impl Into<WidgetText>) -> Self {
|
pub fn new(text: impl Into<WidgetText>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
text: text.into(),
|
text: text.into(),
|
||||||
wrap: None,
|
wrap_mode: None,
|
||||||
truncate: false,
|
|
||||||
sense: None,
|
sense: None,
|
||||||
selectable: None,
|
selectable: None,
|
||||||
}
|
}
|
||||||
|
|
@ -43,37 +42,29 @@ impl Label {
|
||||||
self.text.text()
|
self.text.text()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `true`, the text will wrap to stay within the max width of the [`Ui`].
|
/// Set the wrap mode for the text.
|
||||||
///
|
///
|
||||||
/// Calling `wrap` will override [`Self::truncate`].
|
/// By default, [`Ui::wrap_mode`] will be used, which can be overridden with [`Style::wrap_mode`].
|
||||||
///
|
|
||||||
/// By default [`Self::wrap`] will be `true` in vertical layouts
|
|
||||||
/// and horizontal layouts with wrapping,
|
|
||||||
/// and `false` on non-wrapping horizontal layouts.
|
|
||||||
///
|
///
|
||||||
/// Note that any `\n` in the text will always produce a new line.
|
/// Note that any `\n` in the text will always produce a new line.
|
||||||
///
|
|
||||||
/// You can also use [`crate::Style::wrap`].
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn wrap(mut self, wrap: bool) -> Self {
|
pub fn wrap_mode(mut self, wrap_mode: TextWrapMode) -> Self {
|
||||||
self.wrap = Some(wrap);
|
self.wrap_mode = Some(wrap_mode);
|
||||||
self.truncate = false;
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `true`, the text will stop at the max width of the [`Ui`],
|
/// Set [`Self::wrap_mode`] to [`TextWrapMode::Wrap`].
|
||||||
/// and what doesn't fit will be elided, replaced with `…`.
|
|
||||||
///
|
|
||||||
/// If the text is truncated, the full text will be shown on hover as a tool-tip.
|
|
||||||
///
|
|
||||||
/// Default is `false`, which means the text will expand the parent [`Ui`],
|
|
||||||
/// or wrap if [`Self::wrap`] is set.
|
|
||||||
///
|
|
||||||
/// Calling `truncate` will override [`Self::wrap`].
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn truncate(mut self, truncate: bool) -> Self {
|
pub fn wrap(mut self) -> Self {
|
||||||
self.wrap = None;
|
self.wrap_mode = Some(TextWrapMode::Wrap);
|
||||||
self.truncate = truncate;
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set [`Self::wrap_mode`] to [`TextWrapMode::Truncate`].
|
||||||
|
#[inline]
|
||||||
|
pub fn truncate(mut self) -> Self {
|
||||||
|
self.wrap_mode = Some(TextWrapMode::Truncate);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,11 +147,10 @@ impl Label {
|
||||||
.text
|
.text
|
||||||
.into_layout_job(ui.style(), FontSelection::Default, valign);
|
.into_layout_job(ui.style(), FontSelection::Default, valign);
|
||||||
|
|
||||||
let truncate = self.truncate;
|
|
||||||
let wrap = !truncate && self.wrap.unwrap_or_else(|| ui.wrap_text());
|
|
||||||
let available_width = ui.available_width();
|
let available_width = ui.available_width();
|
||||||
|
|
||||||
if wrap
|
let wrap_mode = self.wrap_mode.unwrap_or_else(|| ui.wrap_mode());
|
||||||
|
if wrap_mode == TextWrapMode::Wrap
|
||||||
&& ui.layout().main_dir() == Direction::LeftToRight
|
&& ui.layout().main_dir() == Direction::LeftToRight
|
||||||
&& ui.layout().main_wrap()
|
&& ui.layout().main_wrap()
|
||||||
&& available_width.is_finite()
|
&& available_width.is_finite()
|
||||||
|
|
@ -192,15 +182,8 @@ impl Label {
|
||||||
}
|
}
|
||||||
(pos, galley, response)
|
(pos, galley, response)
|
||||||
} else {
|
} else {
|
||||||
if truncate {
|
layout_job.wrap =
|
||||||
layout_job.wrap.max_width = available_width;
|
text::TextWrapping::from_wrap_mode_and_width(wrap_mode, available_width);
|
||||||
layout_job.wrap.max_rows = 1;
|
|
||||||
layout_job.wrap.break_anywhere = true;
|
|
||||||
} else if wrap {
|
|
||||||
layout_job.wrap.max_width = available_width;
|
|
||||||
} else {
|
|
||||||
layout_job.wrap.max_width = f32::INFINITY;
|
|
||||||
};
|
|
||||||
|
|
||||||
if ui.is_grid() {
|
if ui.is_grid() {
|
||||||
// TODO(emilk): remove special Grid hacks like these
|
// TODO(emilk): remove special Grid hacks like these
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,12 @@ impl Widget for ProgressBar {
|
||||||
format!("{}%", (progress * 100.0) as usize).into()
|
format!("{}%", (progress * 100.0) as usize).into()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let galley = text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button);
|
let galley = text.into_galley(
|
||||||
|
ui,
|
||||||
|
Some(TextWrapMode::Extend),
|
||||||
|
f32::INFINITY,
|
||||||
|
TextStyle::Button,
|
||||||
|
);
|
||||||
let text_pos = outer_rect.left_center() - Vec2::new(0.0, galley.size().y / 2.0)
|
let text_pos = outer_rect.left_center() - Vec2::new(0.0, galley.size().y / 2.0)
|
||||||
+ vec2(ui.spacing().item_spacing.x, 0.0);
|
+ vec2(ui.spacing().item_spacing.x, 0.0);
|
||||||
let text_color = visuals
|
let text_color = visuals
|
||||||
|
|
|
||||||
|
|
@ -904,7 +904,8 @@ impl<'a> Slider<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
if !self.text.is_empty() {
|
if !self.text.is_empty() {
|
||||||
let label_response = ui.add(Label::new(self.text.clone()).wrap(false));
|
let label_response =
|
||||||
|
ui.add(Label::new(self.text.clone()).wrap_mode(TextWrapMode::Extend));
|
||||||
// The slider already has an accessibility label via widget info,
|
// The slider already has an accessibility label via widget info,
|
||||||
// but sometimes it's useful for a screen reader to know
|
// but sometimes it's useful for a screen reader to know
|
||||||
// that a piece of text is a label for another widget,
|
// that a piece of text is a label for another widget,
|
||||||
|
|
|
||||||
|
|
@ -665,9 +665,19 @@ impl<'t> TextEdit<'t> {
|
||||||
let hint_text_color = ui.visuals().weak_text_color();
|
let hint_text_color = ui.visuals().weak_text_color();
|
||||||
let hint_text_font_id = hint_text_font.unwrap_or(font_id.into());
|
let hint_text_font_id = hint_text_font.unwrap_or(font_id.into());
|
||||||
let galley = if multiline {
|
let galley = if multiline {
|
||||||
hint_text.into_galley(ui, Some(true), desired_inner_size.x, hint_text_font_id)
|
hint_text.into_galley(
|
||||||
|
ui,
|
||||||
|
Some(TextWrapMode::Wrap),
|
||||||
|
desired_inner_size.x,
|
||||||
|
hint_text_font_id,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
hint_text.into_galley(ui, Some(false), f32::INFINITY, hint_text_font_id)
|
hint_text.into_galley(
|
||||||
|
ui,
|
||||||
|
Some(TextWrapMode::Extend),
|
||||||
|
f32::INFINITY,
|
||||||
|
hint_text_font_id,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
painter.galley(rect.min, galley, hint_text_color);
|
painter.galley(rect.min, galley, hint_text_color);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,7 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
ui.collapsing("Web info (location)", |ui| {
|
ui.collapsing("Web info (location)", |ui| {
|
||||||
ui.style_mut().wrap = Some(false);
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
ui.monospace(format!("{:#?}", _frame.info().web_info.location));
|
ui.monospace(format!("{:#?}", _frame.info().web_info.location));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -335,7 +335,7 @@ fn file_menu_button(ui: &mut Ui) {
|
||||||
|
|
||||||
ui.menu_button("File", |ui| {
|
ui.menu_button("File", |ui| {
|
||||||
ui.set_min_width(220.0);
|
ui.set_min_width(220.0);
|
||||||
ui.style_mut().wrap = Some(false);
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
|
|
||||||
// On the web the browser controls the zoom
|
// On the web the browser controls the zoom
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ impl super::View for FrameDemo {
|
||||||
.rounding(ui.visuals().widgets.noninteractive.rounding)
|
.rounding(ui.visuals().widgets.noninteractive.rounding)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
self.frame.show(ui, |ui| {
|
self.frame.show(ui, |ui| {
|
||||||
ui.style_mut().wrap = Some(false);
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
ui.label(egui::RichText::new("Content").color(egui::Color32::WHITE));
|
ui.label(egui::RichText::new("Content").color(egui::Color32::WHITE));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ impl LayoutTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn demo_ui(ui: &mut Ui) {
|
fn demo_ui(ui: &mut Ui) {
|
||||||
ui.add(egui::Label::new("Wrapping text followed by example widgets:").wrap(true));
|
ui.add(egui::Label::new("Wrapping text followed by example widgets:").wrap());
|
||||||
let mut dummy = false;
|
let mut dummy = false;
|
||||||
ui.checkbox(&mut dummy, "checkbox");
|
ui.checkbox(&mut dummy, "checkbox");
|
||||||
ui.radio_value(&mut dummy, false, "radio");
|
ui.radio_value(&mut dummy, false, "radio");
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,7 @@ fn label_ui(ui: &mut egui::Ui) {
|
||||||
egui::Label::new(
|
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.",
|
"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.",
|
||||||
)
|
)
|
||||||
.truncate(true),
|
.truncate(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use egui::emath::TSTransform;
|
use egui::emath::TSTransform;
|
||||||
|
use egui::TextWrapMode;
|
||||||
|
|
||||||
#[derive(Clone, Default, PartialEq)]
|
#[derive(Clone, Default, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
|
@ -122,7 +123,7 @@ impl super::View for PanZoom {
|
||||||
.stroke(ui.ctx().style().visuals.window_stroke)
|
.stroke(ui.ctx().style().visuals.window_stroke)
|
||||||
.fill(ui.style().visuals.panel_fill)
|
.fill(ui.style().visuals.panel_fill)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.style_mut().wrap = Some(false);
|
ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
|
||||||
callback(ui, self)
|
callback(ui, self)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ impl LineDemo {
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
ui.style_mut().wrap = Some(false);
|
ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
|
||||||
ui.checkbox(animate, "Animate");
|
ui.checkbox(animate, "Animate");
|
||||||
ui.checkbox(square, "Square view")
|
ui.checkbox(square, "Square view")
|
||||||
.on_hover_text("Always keep the viewport square.");
|
.on_hover_text("Always keep the viewport square.");
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ impl super::View for Scrolling {
|
||||||
}
|
}
|
||||||
ScrollDemo::Bidirectional => {
|
ScrollDemo::Bidirectional => {
|
||||||
egui::ScrollArea::both().show(ui, |ui| {
|
egui::ScrollArea::both().show(ui, |ui| {
|
||||||
ui.style_mut().wrap = Some(false);
|
ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
|
||||||
for _ in 0..100 {
|
for _ in 0..100 {
|
||||||
ui.label(crate::LOREM_IPSUM);
|
ui.label(crate::LOREM_IPSUM);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use egui::TextStyle;
|
use egui::{TextStyle, TextWrapMode};
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
|
@ -196,7 +196,7 @@ impl TableDemo {
|
||||||
ui.label(long_text(row_index));
|
ui.label(long_text(row_index));
|
||||||
});
|
});
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
ui.style_mut().wrap = Some(false);
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
if is_thick {
|
if is_thick {
|
||||||
ui.heading("Extra thick row");
|
ui.heading("Extra thick row");
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -227,7 +227,8 @@ impl TableDemo {
|
||||||
});
|
});
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
ui.add(
|
ui.add(
|
||||||
egui::Label::new("Thousands of rows of even height").wrap(false),
|
egui::Label::new("Thousands of rows of even height")
|
||||||
|
.wrap_mode(TextWrapMode::Extend),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -253,7 +254,7 @@ impl TableDemo {
|
||||||
ui.label(long_text(row_index));
|
ui.label(long_text(row_index));
|
||||||
});
|
});
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
ui.style_mut().wrap = Some(false);
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
if thick_row(row_index) {
|
if thick_row(row_index) {
|
||||||
ui.heading("Extra thick row");
|
ui.heading("Extra thick row");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ impl WidgetGallery {
|
||||||
egui::ComboBox::from_label("Take your pick")
|
egui::ComboBox::from_label("Take your pick")
|
||||||
.selected_text(format!("{radio:?}"))
|
.selected_text(format!("{radio:?}"))
|
||||||
.show_ui(ui, |ui| {
|
.show_ui(ui, |ui| {
|
||||||
ui.style_mut().wrap = Some(false);
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
ui.set_min_width(60.0);
|
ui.set_min_width(60.0);
|
||||||
ui.selectable_value(radio, Enum::First, "First");
|
ui.selectable_value(radio, Enum::First, "First");
|
||||||
ui.selectable_value(radio, Enum::Second, "Second");
|
ui.selectable_value(radio, Enum::Second, "Second");
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::{fmt::Debug, ops::RangeInclusive, sync::Arc};
|
||||||
use egui::{
|
use egui::{
|
||||||
emath::{remap_clamp, round_to_decimals, Rot2},
|
emath::{remap_clamp, round_to_decimals, Rot2},
|
||||||
epaint::TextShape,
|
epaint::TextShape,
|
||||||
Pos2, Rangef, Rect, Response, Sense, TextStyle, Ui, Vec2, WidgetText,
|
Pos2, Rangef, Rect, Response, Sense, TextStyle, TextWrapMode, Ui, Vec2, WidgetText,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{transform::PlotTransform, GridMark};
|
use super::{transform::PlotTransform, GridMark};
|
||||||
|
|
@ -264,7 +264,12 @@ impl<'a> AxisWidget<'a> {
|
||||||
|
|
||||||
{
|
{
|
||||||
let text = self.hints.label;
|
let text = self.hints.label;
|
||||||
let galley = text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Body);
|
let galley = text.into_galley(
|
||||||
|
ui,
|
||||||
|
Some(TextWrapMode::Extend),
|
||||||
|
f32::INFINITY,
|
||||||
|
TextStyle::Body,
|
||||||
|
);
|
||||||
let text_color = visuals
|
let text_color = visuals
|
||||||
.override_text_color
|
.override_text_color
|
||||||
.unwrap_or_else(|| ui.visuals().text_color());
|
.unwrap_or_else(|| ui.visuals().text_color());
|
||||||
|
|
|
||||||
|
|
@ -850,10 +850,12 @@ impl PlotItem for Text {
|
||||||
self.color
|
self.color
|
||||||
};
|
};
|
||||||
|
|
||||||
let galley =
|
let galley = self.text.clone().into_galley(
|
||||||
self.text
|
ui,
|
||||||
.clone()
|
Some(egui::TextWrapMode::Extend),
|
||||||
.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Small);
|
f32::INFINITY,
|
||||||
|
TextStyle::Small,
|
||||||
|
);
|
||||||
|
|
||||||
let pos = transform.position_from_point(&self.position);
|
let pos = transform.position_from_point(&self.position);
|
||||||
let rect = self.anchor.anchor_size(pos, galley.size());
|
let rect = self.anchor.anchor_size(pos, galley.size());
|
||||||
|
|
|
||||||
|
|
@ -319,6 +319,24 @@ impl TextFormat {
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// How to wrap and elide text.
|
||||||
|
///
|
||||||
|
/// This enum is used in high-level APIs where providing a [`TextWrapping`] is too verbose.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
pub enum TextWrapMode {
|
||||||
|
/// The text should expand the `Ui` size when reaching its boundary.
|
||||||
|
Extend,
|
||||||
|
|
||||||
|
/// The text should wrap to the next line when reaching the `Ui` boundary.
|
||||||
|
Wrap,
|
||||||
|
|
||||||
|
/// The text should be elided using "…" when reaching the `Ui` boundary.
|
||||||
|
///
|
||||||
|
/// Note that using [`TextWrapping`] and [`LayoutJob`] offers more control over the elision.
|
||||||
|
Truncate,
|
||||||
|
}
|
||||||
|
|
||||||
/// Controls the text wrapping and elision of a [`LayoutJob`].
|
/// Controls the text wrapping and elision of a [`LayoutJob`].
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
|
@ -335,7 +353,7 @@ pub struct TextWrapping {
|
||||||
|
|
||||||
/// Maximum amount of rows the text galley should have.
|
/// Maximum amount of rows the text galley should have.
|
||||||
///
|
///
|
||||||
/// If this limit is reached, text will be truncated and
|
/// If this limit is reached, text will be truncated
|
||||||
/// and [`Self::overflow_character`] appended to the final row.
|
/// and [`Self::overflow_character`] appended to the final row.
|
||||||
/// You can detect this by checking [`Galley::elided`].
|
/// You can detect this by checking [`Galley::elided`].
|
||||||
///
|
///
|
||||||
|
|
@ -394,6 +412,15 @@ impl Default for TextWrapping {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextWrapping {
|
impl TextWrapping {
|
||||||
|
/// Create a [`TextWrapping`] from a [`TextWrapMode`] and an available width.
|
||||||
|
pub fn from_wrap_mode_and_width(mode: TextWrapMode, max_width: f32) -> Self {
|
||||||
|
match mode {
|
||||||
|
TextWrapMode::Extend => Self::no_max_width(),
|
||||||
|
TextWrapMode::Wrap => Self::wrap_at_width(max_width),
|
||||||
|
TextWrapMode::Truncate => Self::truncate_at_width(max_width),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A row can be as long as it need to be.
|
/// A row can be as long as it need to be.
|
||||||
pub fn no_max_width() -> Self {
|
pub fn no_max_width() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -402,6 +429,14 @@ impl TextWrapping {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A row can be at most `max_width` wide but can wrap in any number of lines.
|
||||||
|
pub fn wrap_at_width(max_width: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
max_width,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Elide text that doesn't fit within the given width, replaced with `…`.
|
/// Elide text that doesn't fit within the given width, replaced with `…`.
|
||||||
pub fn truncate_at_width(max_width: f32) -> Self {
|
pub fn truncate_at_width(max_width: f32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue