Widgets will now always line break at \n
characters
This commit is contained in:
parent
de204b5436
commit
91ce18d62f
12 changed files with 73 additions and 51 deletions
|
@ -19,11 +19,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
* Add `Slider::clamp_to_range(bool)`: if set, clamp the incoming and outgoing values to the slider range.
|
* Add `Slider::clamp_to_range(bool)`: if set, clamp the incoming and outgoing values to the slider range.
|
||||||
* Add: `ui.spacing()`, `ui.spacing_mut()`, `ui.visuals()`, `ui.visuals_mut()`.
|
* Add: `ui.spacing()`, `ui.spacing_mut()`, `ui.visuals()`, `ui.visuals_mut()`.
|
||||||
* Add: `ctx.set_visuals()`.
|
* Add: `ctx.set_visuals()`.
|
||||||
|
* You can now control text wrapping with `Style::wrap`.
|
||||||
|
|
||||||
### Changed 🔧
|
### Changed 🔧
|
||||||
|
|
||||||
* Text will now wrap at newlines, spaces, dashes, punctuation or in the middle of a words if necessary, in that order of priority.
|
* Text will now wrap at newlines, spaces, dashes, punctuation or in the middle of a words if necessary, in that order of priority.
|
||||||
* `mouse` has be renamed `pointer` everywhere (to make it clear it includes touches too).
|
* Widgets will now always line break at `\n` characters.
|
||||||
|
* `mouse` has been renamed `pointer` everywhere (to make it clear it includes touches too).
|
||||||
* Most parts of `Response` are now methods, so `if ui.button("…").clicked {` is now `if ui.button("…").clicked() {`.
|
* Most parts of `Response` are now methods, so `if ui.button("…").clicked {` is now `if ui.button("…").clicked() {`.
|
||||||
* `Response::active` is now gone. You can use `response.dragged()` or `response.clicked()` instead.
|
* `Response::active` is now gone. You can use `response.dragged()` or `response.clicked()` instead.
|
||||||
* Backend: pointer (mouse/touch) position and buttons are now passed to egui in the event stream.
|
* Backend: pointer (mouse/touch) position and buttons are now passed to egui in the event stream.
|
||||||
|
|
|
@ -134,9 +134,7 @@ pub struct CollapsingHeader {
|
||||||
impl CollapsingHeader {
|
impl CollapsingHeader {
|
||||||
/// The `CollapsingHeader` starts out collapsed unless you call `default_open`.
|
/// The `CollapsingHeader` starts out collapsed unless you call `default_open`.
|
||||||
pub fn new(label: impl Into<String>) -> Self {
|
pub fn new(label: impl Into<String>) -> Self {
|
||||||
let label = Label::new(label)
|
let label = Label::new(label).text_style(TextStyle::Button).wrap(false);
|
||||||
.text_style(TextStyle::Button)
|
|
||||||
.multiline(false);
|
|
||||||
let id_source = Id::new(label.text());
|
let id_source = Id::new(label.text());
|
||||||
Self {
|
Self {
|
||||||
label,
|
label,
|
||||||
|
|
|
@ -64,7 +64,7 @@ pub fn combo_box(
|
||||||
|
|
||||||
let text_style = TextStyle::Button;
|
let text_style = TextStyle::Button;
|
||||||
let font = &ui.fonts()[text_style];
|
let font = &ui.fonts()[text_style];
|
||||||
let galley = font.layout_single_line(selected.into());
|
let galley = font.layout_no_wrap(selected.into());
|
||||||
|
|
||||||
let width = galley.size.x + ui.spacing().item_spacing.x + icon_size.x;
|
let width = galley.size.x + ui.spacing().item_spacing.x + icon_size.x;
|
||||||
let width = width.at_least(full_minimum_width);
|
let width = width.at_least(full_minimum_width);
|
||||||
|
|
|
@ -38,9 +38,7 @@ impl<'open> Window<'open> {
|
||||||
pub fn new(title: impl Into<String>) -> Self {
|
pub fn new(title: impl Into<String>) -> Self {
|
||||||
let title = title.into();
|
let title = title.into();
|
||||||
let area = Area::new(&title);
|
let area = Area::new(&title);
|
||||||
let title_label = Label::new(title)
|
let title_label = Label::new(title).text_style(TextStyle::Heading).wrap(false);
|
||||||
.text_style(TextStyle::Heading)
|
|
||||||
.multiline(false);
|
|
||||||
Self {
|
Self {
|
||||||
title_label,
|
title_label,
|
||||||
open: None,
|
open: None,
|
||||||
|
|
|
@ -116,7 +116,7 @@ impl Widget for &epaint::stats::PaintStats {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn label(ui: &mut Ui, alloc_info: &epaint::stats::AllocInfo, what: &str) -> Response {
|
pub fn label(ui: &mut Ui, alloc_info: &epaint::stats::AllocInfo, what: &str) -> Response {
|
||||||
ui.add(Label::new(alloc_info.format(what)).multiline(false))
|
ui.add(Label::new(alloc_info.format(what)).wrap(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for &mut epaint::TessellationOptions {
|
impl Widget for &mut epaint::TessellationOptions {
|
||||||
|
|
|
@ -17,6 +17,11 @@ pub struct Style {
|
||||||
/// Default `TextStyle` for normal text (i.e. for `Label` and `TextEdit`).
|
/// Default `TextStyle` for normal text (i.e. for `Label` and `TextEdit`).
|
||||||
pub body_text_style: TextStyle,
|
pub body_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`.
|
||||||
|
pub wrap: Option<bool>,
|
||||||
|
|
||||||
pub spacing: Spacing,
|
pub spacing: Spacing,
|
||||||
pub interaction: Interaction,
|
pub interaction: Interaction,
|
||||||
pub visuals: Visuals,
|
pub visuals: Visuals,
|
||||||
|
@ -263,6 +268,7 @@ impl Default for Style {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
body_text_style: TextStyle::Body,
|
body_text_style: TextStyle::Body,
|
||||||
|
wrap: None,
|
||||||
spacing: Spacing::default(),
|
spacing: Spacing::default(),
|
||||||
interaction: Interaction::default(),
|
interaction: Interaction::default(),
|
||||||
visuals: Visuals::default(),
|
visuals: Visuals::default(),
|
||||||
|
@ -460,6 +466,7 @@ impl Style {
|
||||||
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
||||||
let Self {
|
let Self {
|
||||||
body_text_style,
|
body_text_style,
|
||||||
|
wrap: _,
|
||||||
spacing,
|
spacing,
|
||||||
interaction,
|
interaction,
|
||||||
visuals,
|
visuals,
|
||||||
|
|
|
@ -170,6 +170,17 @@ impl Ui {
|
||||||
self.placer.layout()
|
self.placer.layout()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Should text wrap in this `Ui`?
|
||||||
|
/// This is determined first by [`Style::wrap`], and then by the layout of this `Ui`.
|
||||||
|
pub fn wrap_text(&self) -> bool {
|
||||||
|
if let Some(wrap) = self.style.wrap {
|
||||||
|
wrap
|
||||||
|
} else {
|
||||||
|
// In vertical layouts we wrap text, but in horizontal we keep going.
|
||||||
|
self.layout().is_vertical()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 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
|
||||||
|
|
|
@ -87,19 +87,19 @@ impl Widget for Button {
|
||||||
small,
|
small,
|
||||||
frame,
|
frame,
|
||||||
} = self;
|
} = self;
|
||||||
let font = &ui.fonts()[text_style];
|
|
||||||
|
|
||||||
let single_line = ui.layout().is_horizontal();
|
|
||||||
let galley = if single_line {
|
|
||||||
font.layout_single_line(text)
|
|
||||||
} else {
|
|
||||||
font.layout_multiline(text, ui.available_width())
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut button_padding = ui.spacing().button_padding;
|
let mut button_padding = ui.spacing().button_padding;
|
||||||
if small {
|
if small {
|
||||||
button_padding.y = 0.0;
|
button_padding.y = 0.0;
|
||||||
}
|
}
|
||||||
|
let total_extra = button_padding + button_padding;
|
||||||
|
|
||||||
|
let font = &ui.fonts()[text_style];
|
||||||
|
let galley = if ui.wrap_text() {
|
||||||
|
font.layout_multiline(text, ui.available_width() - total_extra.x)
|
||||||
|
} else {
|
||||||
|
font.layout_no_wrap(text)
|
||||||
|
};
|
||||||
|
|
||||||
let mut desired_size = galley.size + 2.0 * button_padding;
|
let mut desired_size = galley.size + 2.0 * button_padding;
|
||||||
if !small {
|
if !small {
|
||||||
|
@ -180,11 +180,10 @@ impl<'a> Widget for Checkbox<'a> {
|
||||||
let button_padding = spacing.button_padding;
|
let button_padding = spacing.button_padding;
|
||||||
let total_extra = button_padding + vec2(icon_width + icon_spacing, 0.0) + button_padding;
|
let total_extra = button_padding + vec2(icon_width + icon_spacing, 0.0) + button_padding;
|
||||||
|
|
||||||
let single_line = ui.layout().is_horizontal();
|
let galley = if ui.wrap_text() {
|
||||||
let galley = if single_line {
|
|
||||||
font.layout_single_line(text)
|
|
||||||
} else {
|
|
||||||
font.layout_multiline(text, ui.available_width() - total_extra.x)
|
font.layout_multiline(text, ui.available_width() - total_extra.x)
|
||||||
|
} else {
|
||||||
|
font.layout_no_wrap(text)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut desired_size = total_extra + galley.size;
|
let mut desired_size = total_extra + galley.size;
|
||||||
|
@ -272,11 +271,10 @@ impl Widget for RadioButton {
|
||||||
let button_padding = ui.spacing().button_padding;
|
let button_padding = ui.spacing().button_padding;
|
||||||
let total_extra = button_padding + vec2(icon_width + icon_spacing, 0.0) + button_padding;
|
let total_extra = button_padding + vec2(icon_width + icon_spacing, 0.0) + button_padding;
|
||||||
|
|
||||||
let single_line = ui.layout().is_horizontal();
|
let galley = if ui.wrap_text() {
|
||||||
let galley = if single_line {
|
|
||||||
font.layout_single_line(text)
|
|
||||||
} else {
|
|
||||||
font.layout_multiline(text, ui.available_width() - total_extra.x)
|
font.layout_multiline(text, ui.available_width() - total_extra.x)
|
||||||
|
} else {
|
||||||
|
font.layout_no_wrap(text)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut desired_size = total_extra + galley.size;
|
let mut desired_size = total_extra + galley.size;
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{paint::Galley, *};
|
||||||
pub struct Label {
|
pub struct Label {
|
||||||
// TODO: not pub
|
// TODO: not pub
|
||||||
pub(crate) text: String,
|
pub(crate) text: String,
|
||||||
pub(crate) multiline: Option<bool>,
|
pub(crate) wrap: Option<bool>,
|
||||||
pub(crate) text_style: Option<TextStyle>,
|
pub(crate) text_style: Option<TextStyle>,
|
||||||
pub(crate) background_color: Color32,
|
pub(crate) background_color: Color32,
|
||||||
pub(crate) text_color: Option<Color32>,
|
pub(crate) text_color: Option<Color32>,
|
||||||
|
@ -21,7 +21,7 @@ impl Label {
|
||||||
pub fn new(text: impl Into<String>) -> Self {
|
pub fn new(text: impl Into<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
text: text.into(),
|
text: text.into(),
|
||||||
multiline: None,
|
wrap: None,
|
||||||
text_style: None,
|
text_style: None,
|
||||||
background_color: Color32::TRANSPARENT,
|
background_color: Color32::TRANSPARENT,
|
||||||
text_color: None,
|
text_color: None,
|
||||||
|
@ -39,16 +39,21 @@ impl Label {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `true`, the text will wrap at the `max_width`.
|
/// If `true`, the text will wrap at the `max_width`.
|
||||||
/// By default `multiline` will be true in vertical layouts
|
/// By default [`wrap`] will be true in vertical layouts
|
||||||
/// and horizontal layouts with wrapping,
|
/// and horizontal layouts with wrapping,
|
||||||
/// and false on non-wrapping horizontal layouts.
|
/// and false on non-wrapping horizontal layouts.
|
||||||
///
|
///
|
||||||
/// If the text has any newlines (`\n`) in it, multiline will automatically turn on.
|
/// Note that any `\n` in the text label will always produce a new line.
|
||||||
pub fn multiline(mut self, multiline: bool) -> Self {
|
pub fn wrap(mut self, wrap: bool) -> Self {
|
||||||
self.multiline = Some(multiline);
|
self.wrap = Some(wrap);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Use Label::wrap instead"]
|
||||||
|
pub fn multiline(self, multiline: bool) -> Self {
|
||||||
|
self.wrap(multiline)
|
||||||
|
}
|
||||||
|
|
||||||
/// The default is [`Style::body_text_style`] (generally [`TextStyle::Body`]).
|
/// The default is [`Style::body_text_style`] (generally [`TextStyle::Body`]).
|
||||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||||
self.text_style = Some(text_style);
|
self.text_style = Some(text_style);
|
||||||
|
@ -122,11 +127,12 @@ impl Label {
|
||||||
pub fn layout_width(&self, ui: &Ui, max_width: f32) -> Galley {
|
pub fn layout_width(&self, ui: &Ui, max_width: f32) -> Galley {
|
||||||
let text_style = self.text_style_or_default(ui.style());
|
let text_style = self.text_style_or_default(ui.style());
|
||||||
let font = &ui.fonts()[text_style];
|
let font = &ui.fonts()[text_style];
|
||||||
if self.is_multiline(ui) {
|
let wrap_width = if self.should_wrap(ui) {
|
||||||
font.layout_multiline(self.text.clone(), max_width) // TODO: avoid clone
|
max_width
|
||||||
} else {
|
} else {
|
||||||
font.layout_single_line(self.text.clone()) // TODO: avoid clone
|
f32::INFINITY
|
||||||
}
|
};
|
||||||
|
font.layout_multiline(self.text.clone(), wrap_width) // TODO: avoid clone
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn font_height(&self, fonts: &paint::text::Fonts, style: &Style) -> f32 {
|
pub fn font_height(&self, fonts: &paint::text::Fonts, style: &Style) -> f32 {
|
||||||
|
@ -207,19 +213,17 @@ impl Label {
|
||||||
self.text_style.unwrap_or(style.body_text_style)
|
self.text_style.unwrap_or(style.body_text_style)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_multiline(&self, ui: &Ui) -> bool {
|
fn should_wrap(&self, ui: &Ui) -> bool {
|
||||||
self.multiline.unwrap_or_else(|| {
|
self.wrap.or(ui.style().wrap).unwrap_or_else(|| {
|
||||||
let layout = ui.layout();
|
let layout = ui.layout();
|
||||||
layout.is_vertical()
|
layout.is_vertical() || layout.is_horizontal() && layout.main_wrap()
|
||||||
|| layout.is_horizontal() && layout.main_wrap()
|
|
||||||
|| self.text.contains('\n')
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Label {
|
impl Widget for Label {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
if self.is_multiline(ui)
|
if self.should_wrap(ui)
|
||||||
&& ui.layout().main_dir() == Direction::LeftToRight
|
&& ui.layout().main_dir() == Direction::LeftToRight
|
||||||
&& ui.layout().main_wrap()
|
&& ui.layout().main_wrap()
|
||||||
{
|
{
|
||||||
|
|
|
@ -29,11 +29,10 @@ impl Widget for SelectableLabel {
|
||||||
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 single_line = ui.layout().is_horizontal();
|
let galley = if ui.wrap_text() {
|
||||||
let galley = if single_line {
|
|
||||||
font.layout_single_line(text)
|
|
||||||
} else {
|
|
||||||
font.layout_multiline(text, ui.available_width() - total_extra.x)
|
font.layout_multiline(text, ui.available_width() - total_extra.x)
|
||||||
|
} else {
|
||||||
|
font.layout_no_wrap(text)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut desired_size = total_extra + galley.size;
|
let mut desired_size = total_extra + galley.size;
|
||||||
|
|
|
@ -324,12 +324,7 @@ impl<'a> Slider<'a> {
|
||||||
fn label_ui(&mut self, ui: &mut Ui) {
|
fn label_ui(&mut self, ui: &mut Ui) {
|
||||||
if let Some(label_text) = self.text.as_deref() {
|
if let Some(label_text) = self.text.as_deref() {
|
||||||
let text_color = self.text_color.unwrap_or_else(|| ui.visuals().text_color());
|
let text_color = self.text_color.unwrap_or_else(|| ui.visuals().text_color());
|
||||||
|
ui.add(Label::new(label_text).wrap(false).text_color(text_color));
|
||||||
ui.add(
|
|
||||||
Label::new(label_text)
|
|
||||||
.multiline(false)
|
|
||||||
.text_color(text_color),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -275,6 +275,9 @@ impl Font {
|
||||||
/// Typeset the given text onto one row.
|
/// Typeset the given text onto one row.
|
||||||
/// Any `\n` will show up as the replacement character.
|
/// Any `\n` will show up as the replacement character.
|
||||||
/// Always returns exactly one `Row` in the `Galley`.
|
/// Always returns exactly one `Row` in the `Galley`.
|
||||||
|
///
|
||||||
|
/// Most often you probably want `\n` to produce a new row,
|
||||||
|
/// and so [`Self::layout_no_wrap`] may be a better choice.
|
||||||
pub fn layout_single_line(&self, text: String) -> Galley {
|
pub fn layout_single_line(&self, text: String) -> Galley {
|
||||||
let x_offsets = self.layout_single_row_fragment(&text);
|
let x_offsets = self.layout_single_row_fragment(&text);
|
||||||
let row = Row {
|
let row = Row {
|
||||||
|
@ -295,6 +298,13 @@ impl Font {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Always returns at least one row.
|
/// Always returns at least one row.
|
||||||
|
/// Will line break at `\n`.
|
||||||
|
pub fn layout_no_wrap(&self, text: String) -> Galley {
|
||||||
|
self.layout_multiline(text, f32::INFINITY)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Always returns at least one row.
|
||||||
|
/// Will wrap text at the given width.
|
||||||
pub fn layout_multiline(&self, text: String, max_width_in_points: f32) -> Galley {
|
pub fn layout_multiline(&self, text: String, max_width_in_points: f32) -> Galley {
|
||||||
self.layout_multiline_with_indentation_and_max_width(text, 0.0, max_width_in_points)
|
self.layout_multiline_with_indentation_and_max_width(text, 0.0, max_width_in_points)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue