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: `ui.spacing()`, `ui.spacing_mut()`, `ui.visuals()`, `ui.visuals_mut()`.
|
||||
* Add: `ctx.set_visuals()`.
|
||||
* You can now control text wrapping with `Style::wrap`.
|
||||
|
||||
### Changed 🔧
|
||||
|
||||
* 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() {`.
|
||||
* `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.
|
||||
|
|
|
@ -134,9 +134,7 @@ pub struct CollapsingHeader {
|
|||
impl CollapsingHeader {
|
||||
/// The `CollapsingHeader` starts out collapsed unless you call `default_open`.
|
||||
pub fn new(label: impl Into<String>) -> Self {
|
||||
let label = Label::new(label)
|
||||
.text_style(TextStyle::Button)
|
||||
.multiline(false);
|
||||
let label = Label::new(label).text_style(TextStyle::Button).wrap(false);
|
||||
let id_source = Id::new(label.text());
|
||||
Self {
|
||||
label,
|
||||
|
|
|
@ -64,7 +64,7 @@ pub fn combo_box(
|
|||
|
||||
let text_style = TextStyle::Button;
|
||||
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 = width.at_least(full_minimum_width);
|
||||
|
|
|
@ -38,9 +38,7 @@ impl<'open> Window<'open> {
|
|||
pub fn new(title: impl Into<String>) -> Self {
|
||||
let title = title.into();
|
||||
let area = Area::new(&title);
|
||||
let title_label = Label::new(title)
|
||||
.text_style(TextStyle::Heading)
|
||||
.multiline(false);
|
||||
let title_label = Label::new(title).text_style(TextStyle::Heading).wrap(false);
|
||||
Self {
|
||||
title_label,
|
||||
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 {
|
||||
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 {
|
||||
|
|
|
@ -17,6 +17,11 @@ pub struct Style {
|
|||
/// Default `TextStyle` for normal text (i.e. for `Label` and `TextEdit`).
|
||||
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 interaction: Interaction,
|
||||
pub visuals: Visuals,
|
||||
|
@ -263,6 +268,7 @@ impl Default for Style {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
body_text_style: TextStyle::Body,
|
||||
wrap: None,
|
||||
spacing: Spacing::default(),
|
||||
interaction: Interaction::default(),
|
||||
visuals: Visuals::default(),
|
||||
|
@ -460,6 +466,7 @@ impl Style {
|
|||
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
||||
let Self {
|
||||
body_text_style,
|
||||
wrap: _,
|
||||
spacing,
|
||||
interaction,
|
||||
visuals,
|
||||
|
|
|
@ -170,6 +170,17 @@ impl Ui {
|
|||
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.
|
||||
///
|
||||
/// The clip-rect of the returned `Painter` will be the intersection
|
||||
|
|
|
@ -87,19 +87,19 @@ impl Widget for Button {
|
|||
small,
|
||||
frame,
|
||||
} = 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;
|
||||
if small {
|
||||
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;
|
||||
if !small {
|
||||
|
@ -180,11 +180,10 @@ impl<'a> Widget for Checkbox<'a> {
|
|||
let button_padding = spacing.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 single_line {
|
||||
font.layout_single_line(text)
|
||||
} else {
|
||||
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 = total_extra + galley.size;
|
||||
|
@ -272,11 +271,10 @@ impl Widget for RadioButton {
|
|||
let button_padding = ui.spacing().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 single_line {
|
||||
font.layout_single_line(text)
|
||||
} else {
|
||||
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 = total_extra + galley.size;
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::{paint::Galley, *};
|
|||
pub struct Label {
|
||||
// TODO: not pub
|
||||
pub(crate) text: String,
|
||||
pub(crate) multiline: Option<bool>,
|
||||
pub(crate) wrap: Option<bool>,
|
||||
pub(crate) text_style: Option<TextStyle>,
|
||||
pub(crate) background_color: Color32,
|
||||
pub(crate) text_color: Option<Color32>,
|
||||
|
@ -21,7 +21,7 @@ impl Label {
|
|||
pub fn new(text: impl Into<String>) -> Self {
|
||||
Self {
|
||||
text: text.into(),
|
||||
multiline: None,
|
||||
wrap: None,
|
||||
text_style: None,
|
||||
background_color: Color32::TRANSPARENT,
|
||||
text_color: None,
|
||||
|
@ -39,16 +39,21 @@ impl Label {
|
|||
}
|
||||
|
||||
/// 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 false on non-wrapping horizontal layouts.
|
||||
///
|
||||
/// If the text has any newlines (`\n`) in it, multiline will automatically turn on.
|
||||
pub fn multiline(mut self, multiline: bool) -> Self {
|
||||
self.multiline = Some(multiline);
|
||||
/// Note that any `\n` in the text label will always produce a new line.
|
||||
pub fn wrap(mut self, wrap: bool) -> Self {
|
||||
self.wrap = Some(wrap);
|
||||
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`]).
|
||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||
self.text_style = Some(text_style);
|
||||
|
@ -122,11 +127,12 @@ impl Label {
|
|||
pub fn layout_width(&self, ui: &Ui, max_width: f32) -> Galley {
|
||||
let text_style = self.text_style_or_default(ui.style());
|
||||
let font = &ui.fonts()[text_style];
|
||||
if self.is_multiline(ui) {
|
||||
font.layout_multiline(self.text.clone(), max_width) // TODO: avoid clone
|
||||
let wrap_width = if self.should_wrap(ui) {
|
||||
max_width
|
||||
} 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 {
|
||||
|
@ -207,19 +213,17 @@ impl Label {
|
|||
self.text_style.unwrap_or(style.body_text_style)
|
||||
}
|
||||
|
||||
fn is_multiline(&self, ui: &Ui) -> bool {
|
||||
self.multiline.unwrap_or_else(|| {
|
||||
fn should_wrap(&self, ui: &Ui) -> bool {
|
||||
self.wrap.or(ui.style().wrap).unwrap_or_else(|| {
|
||||
let layout = ui.layout();
|
||||
layout.is_vertical()
|
||||
|| layout.is_horizontal() && layout.main_wrap()
|
||||
|| self.text.contains('\n')
|
||||
layout.is_vertical() || layout.is_horizontal() && layout.main_wrap()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Label {
|
||||
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_wrap()
|
||||
{
|
||||
|
|
|
@ -29,11 +29,10 @@ impl Widget for SelectableLabel {
|
|||
let button_padding = ui.spacing().button_padding;
|
||||
let total_extra = button_padding + button_padding;
|
||||
|
||||
let single_line = ui.layout().is_horizontal();
|
||||
let galley = if single_line {
|
||||
font.layout_single_line(text)
|
||||
} else {
|
||||
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 = total_extra + galley.size;
|
||||
|
|
|
@ -324,12 +324,7 @@ impl<'a> Slider<'a> {
|
|||
fn label_ui(&mut self, ui: &mut Ui) {
|
||||
if let Some(label_text) = self.text.as_deref() {
|
||||
let text_color = self.text_color.unwrap_or_else(|| ui.visuals().text_color());
|
||||
|
||||
ui.add(
|
||||
Label::new(label_text)
|
||||
.multiline(false)
|
||||
.text_color(text_color),
|
||||
);
|
||||
ui.add(Label::new(label_text).wrap(false).text_color(text_color));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -275,6 +275,9 @@ impl Font {
|
|||
/// Typeset the given text onto one row.
|
||||
/// Any `\n` will show up as the replacement character.
|
||||
/// 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 {
|
||||
let x_offsets = self.layout_single_row_fragment(&text);
|
||||
let row = Row {
|
||||
|
@ -295,6 +298,13 @@ impl Font {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
self.layout_multiline_with_indentation_and_max_width(text, 0.0, max_width_in_points)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue