diff --git a/core/src/text.rs b/core/src/text.rs index 1598ef67b1..10d0c36252 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -40,6 +40,9 @@ pub struct Text<'a, Font> { /// The [`Shaping`] strategy of the [`Text`]. pub shaping: Shaping, + + /// The [`Wrap`] mode of the [`Text`]. + pub wrap: Wrap, } /// The shaping strategy of some text. @@ -66,6 +69,22 @@ pub enum Shaping { Advanced, } +/// The wrap mode of some text. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum Wrap { + /// No wraping + None, + /// Wraps at a glyph level + Glyph, + /// Wraps at a word level + Word, + /// Wraps at the word level, or fallback to glyph level if a word can't fit on a line by itself + /// + /// This is the default + #[default] + WordOrGlyph, +} + /// The height of a line of text in a paragraph. #[derive(Debug, Clone, Copy, PartialEq)] pub enum LineHeight { diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 4c83053f7e..c8c73bf8bc 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -11,7 +11,7 @@ use crate::{ use std::borrow::Cow; -pub use text::{LineHeight, Shaping}; +pub use text::{LineHeight, Shaping, Wrap}; /// A paragraph of text. #[allow(missing_debug_implementations)] @@ -30,6 +30,7 @@ where vertical_alignment: alignment::Vertical, font: Option, shaping: Shaping, + wrap: Wrap, style: Theme::Style, } @@ -51,6 +52,7 @@ where horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, shaping: Shaping::Advanced, + wrap: Default::default(), style: Default::default(), } } @@ -116,6 +118,12 @@ where self.shaping = shaping; self } + + /// Sets the [`Wrap`] mode of the [`Text`]. + pub fn wrap(mut self, wrap: Wrap) -> Self { + self.wrap = wrap; + self + } } /// The internal state of a [`Text`] widget. @@ -162,6 +170,7 @@ where self.horizontal_alignment, self.vertical_alignment, self.shaping, + self.wrap, ) } @@ -246,6 +255,7 @@ pub fn layout( horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, shaping: Shaping, + wrap: Wrap, ) -> layout::Node where Renderer: text::Renderer, @@ -267,6 +277,7 @@ where horizontal_alignment, vertical_alignment, shaping, + wrap, }); paragraph.min_bounds() @@ -347,6 +358,7 @@ where font: self.font, style: self.style.clone(), shaping: self.shaping, + wrap: self.wrap, } } } diff --git a/graphics/src/text.rs b/graphics/src/text.rs index f3ad3eb916..b08b602f6c 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -10,7 +10,7 @@ pub use paragraph::Paragraph; pub use cosmic_text; use crate::core::font::{self, Font}; -use crate::core::text::Shaping; +use crate::core::text::{Shaping, Wrap}; use crate::core::{Color, Point, Rectangle, Size}; use once_cell::sync::OnceCell; @@ -171,6 +171,16 @@ pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping { } } +/// Converts some [`Wrap`] mode to a [`cosmic_text::Wrap`] strategy. +pub fn to_wrap(wrap: Wrap) -> cosmic_text::Wrap { + match wrap { + Wrap::None => cosmic_text::Wrap::None, + Wrap::Glyph => cosmic_text::Wrap::Glyph, + Wrap::Word => cosmic_text::Wrap::Word, + Wrap::WordOrGlyph => cosmic_text::Wrap::WordOrGlyph, + } +} + /// Converts some [`Color`] to a [`cosmic_text::Color`]. pub fn to_color(color: Color) -> cosmic_text::Color { let [r, g, b, a] = color.into_rgba8(); diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index 00a8a52c17..ae298877b5 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -1,7 +1,7 @@ //! Draw paragraphs. use crate::core; use crate::core::alignment; -use crate::core::text::{Hit, LineHeight, Shaping, Text}; +use crate::core::text::{Hit, LineHeight, Shaping, Text, Wrap}; use crate::core::{Font, Pixels, Point, Size}; use crate::text; @@ -17,6 +17,7 @@ struct Internal { content: String, // TODO: Reuse from `buffer` (?) font: Font, shaping: Shaping, + wrap: Wrap, horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, bounds: Size, @@ -81,6 +82,8 @@ impl core::text::Paragraph for Paragraph { Some(text.bounds.height), ); + buffer.set_wrap(font_system.raw(), text::to_wrap(text.wrap)); + buffer.set_text( font_system.raw(), text.content, @@ -97,6 +100,7 @@ impl core::text::Paragraph for Paragraph { horizontal_alignment: text.horizontal_alignment, vertical_alignment: text.vertical_alignment, shaping: text.shaping, + wrap: text.wrap, bounds: text.bounds, min_bounds, version: font_system.version(), @@ -141,6 +145,7 @@ impl core::text::Paragraph for Paragraph { horizontal_alignment: internal.horizontal_alignment, vertical_alignment: internal.vertical_alignment, shaping: internal.shaping, + wrap: internal.wrap, }); } } @@ -274,6 +279,7 @@ impl Default for Internal { content: String::new(), font: Font::default(), shaping: Shaping::default(), + wrap: Wrap::default(), horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, bounds: Size::ZERO, diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index c6f1fd26cc..cb2fb8cca4 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -63,6 +63,7 @@ pub struct Checkbox< text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, + text_wrap: text::Wrap, font: Option, icon: Icon, style: ::Style, @@ -107,6 +108,7 @@ where text_size: None, text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Advanced, + text_wrap: text::Wrap::default(), font: None, icon: Icon { font: Renderer::ICON_FONT, @@ -114,6 +116,7 @@ where size: None, line_height: text::LineHeight::default(), shaping: text::Shaping::Advanced, + wrap: text::Wrap::default(), }, style: Default::default(), } @@ -158,6 +161,12 @@ where self } + /// Sets the [`text::Wrap`] mode of the [`Checkbox`]. + pub fn text_wrap(mut self, wrap: text::Wrap) -> Self { + self.text_wrap = wrap; + self + } + /// Sets the [`Renderer::Font`] of the text of the [`Checkbox`]. /// /// [`Renderer::Font`]: crate::core::text::Renderer @@ -258,6 +267,7 @@ where alignment::Horizontal::Left, alignment::Vertical::Top, self.text_shaping, + self.text_wrap, ) }, ) @@ -345,6 +355,7 @@ where size, line_height, shaping, + wrap, } = &self.icon; let size = size.unwrap_or(Pixels(bounds.height * 0.7)); @@ -359,6 +370,7 @@ where horizontal_alignment: alignment::Horizontal::Center, vertical_alignment: alignment::Vertical::Center, shaping: *shaping, + wrap: *wrap, }, bounds.center(), custom_style.icon_color, @@ -497,4 +509,6 @@ pub struct Icon { pub line_height: text::LineHeight, /// The shaping strategy of the icon. pub shaping: text::Shaping, + /// The wrap mode of the icon. + pub wrap: text::Wrap, } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index e342468bac..9b4ff35955 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -39,6 +39,7 @@ pub struct Menu< text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, + text_wrap: text::Wrap, font: Option, style: Theme::Style, } @@ -70,6 +71,7 @@ where text_size: None, text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Advanced, + text_wrap: text::Wrap::default(), font: None, style: Default::default(), } @@ -108,6 +110,12 @@ where self } + /// Sets the [`text::Wrap`] mode of the [`Menu`]. + pub fn text_wrap(mut self, wrap: text::Wrap) -> Self { + self.text_wrap = wrap; + self + } + /// Sets the font of the [`Menu`]. pub fn font(mut self, font: impl Into) -> Self { self.font = Some(font.into()); @@ -199,6 +207,7 @@ where text_size, text_line_height, text_shaping, + text_wrap, style, } = menu; @@ -211,6 +220,7 @@ where text_size, text_line_height, text_shaping, + text_wrap, padding, style: style.clone(), })); @@ -333,6 +343,7 @@ where text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, + text_wrap: text::Wrap, font: Option, style: Theme::Style, } @@ -535,6 +546,7 @@ where horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: self.text_shaping, + wrap: self.text_wrap, }, Point::new(bounds.x + self.padding.left, bounds.center_y()), if is_selected { diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index af2c3bebac..9f119959ad 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -45,6 +45,7 @@ pub struct PickList< text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, + text_wrap: text::Wrap, font: Option, handle: Handle, style: Theme::Style, @@ -82,6 +83,7 @@ where text_size: None, text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Advanced, + text_wrap: text::Wrap::default(), font: None, handle: Handle::default(), style: Default::default(), @@ -127,6 +129,12 @@ where self } + /// Sets the [`text::Wrap`] mode of the [`PickList`]. + pub fn text_wrap(mut self, wrap: text::Wrap) -> Self { + self.text_wrap = wrap; + self + } + /// Sets the font of the [`PickList`]. pub fn font(mut self, font: impl Into) -> Self { self.font = Some(font.into()); @@ -192,6 +200,7 @@ where self.text_size, self.text_line_height, self.text_shaping, + self.text_wrap, self.font, self.placeholder.as_deref(), &self.options, @@ -252,6 +261,7 @@ where self.text_size, self.text_line_height, self.text_shaping, + self.text_wrap, font, self.placeholder.as_deref(), self.selected.as_ref(), @@ -276,6 +286,7 @@ where self.padding, self.text_size, self.text_shaping, + self.text_wrap, self.font.unwrap_or_else(|| renderer.default_font()), &self.options, &self.on_selected, @@ -377,6 +388,8 @@ pub struct Icon { pub line_height: text::LineHeight, /// The shaping strategy of the icon. pub shaping: text::Shaping, + /// The wrap mode of the icon. + pub wrap: text::Wrap, } /// Computes the layout of a [`PickList`]. @@ -389,6 +402,7 @@ pub fn layout( text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, + text_wrap: text::Wrap, font: Option, placeholder: Option<&str>, options: &[T], @@ -416,6 +430,7 @@ where horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: text_shaping, + wrap: text_wrap, }; for (option, paragraph) in options.iter().zip(state.options.iter_mut()) { @@ -579,6 +594,7 @@ pub fn overlay<'a, T, Message, Theme, Renderer>( padding: Padding, text_size: Option, text_shaping: text::Shaping, + text_wrap: text::Wrap, font: Renderer::Font, options: &'a [T], on_selected: &'a dyn Fn(T) -> Message, @@ -613,6 +629,7 @@ where .padding(padding) .font(font) .text_shaping(text_shaping) + .text_wrap(text_wrap) .style(style); if let Some(text_size) = text_size { @@ -635,6 +652,7 @@ pub fn draw<'a, T, Theme, Renderer>( text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, + text_wrap: text::Wrap, font: Renderer::Font, placeholder: Option<&str>, selected: Option<&T>, @@ -673,6 +691,7 @@ pub fn draw<'a, T, Theme, Renderer>( *size, LineHeight::default(), text::Shaping::Advanced, + text::Wrap::default(), )), Handle::Static(Icon { font, @@ -680,7 +699,8 @@ pub fn draw<'a, T, Theme, Renderer>( size, line_height, shaping, - }) => Some((*font, *code_point, *size, *line_height, *shaping)), + wrap, + }) => Some((*font, *code_point, *size, *line_height, *shaping, *wrap)), Handle::Dynamic { open, closed } => { if state().is_open { Some(( @@ -689,6 +709,7 @@ pub fn draw<'a, T, Theme, Renderer>( open.size, open.line_height, open.shaping, + open.wrap, )) } else { Some(( @@ -697,13 +718,14 @@ pub fn draw<'a, T, Theme, Renderer>( closed.size, closed.line_height, closed.shaping, + closed.wrap, )) } } Handle::None => None, }; - if let Some((font, code_point, size, line_height, shaping)) = handle { + if let Some((font, code_point, size, line_height, shaping, wrap)) = handle { let size = size.unwrap_or_else(|| renderer.default_size()); renderer.fill_text( @@ -719,6 +741,7 @@ pub fn draw<'a, T, Theme, Renderer>( horizontal_alignment: alignment::Horizontal::Right, vertical_alignment: alignment::Vertical::Center, shaping, + wrap, }, Point::new( bounds.x + bounds.width - padding.horizontal(), @@ -747,6 +770,7 @@ pub fn draw<'a, T, Theme, Renderer>( horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: text_shaping, + wrap: text_wrap, }, Point::new(bounds.x + padding.left, bounds.center_y()), if is_selected { diff --git a/widget/src/radio.rs b/widget/src/radio.rs index cbc8d2924b..862a03cd14 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -83,6 +83,7 @@ where text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, + text_wrap: text::Wrap, font: Option, style: Theme::Style, } @@ -127,6 +128,7 @@ where text_size: None, text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Advanced, + text_wrap: text::Wrap::default(), font: None, style: Default::default(), } @@ -171,6 +173,12 @@ where self } + /// Sets the [`text::Wrap`] mode of the [`Radio`] button. + pub fn text_wrap(mut self, wrap: text::Wrap) -> Self { + self.text_wrap = wrap; + self + } + /// Sets the text font of the [`Radio`] button. pub fn font(mut self, font: impl Into) -> Self { self.font = Some(font.into()); @@ -234,6 +242,7 @@ where alignment::Horizontal::Left, alignment::Vertical::Top, self.text_shaping, + self.text_wrap, ) }, ) diff --git a/widget/src/text_input/text_input.rs b/widget/src/text_input/text_input.rs index c34d658e1d..fcde1a7f6d 100644 --- a/widget/src/text_input/text_input.rs +++ b/widget/src/text_input/text_input.rs @@ -497,6 +497,7 @@ where horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, + wrap: text::Wrap::default(), }; state.placeholder.update(placeholder_text); @@ -519,6 +520,7 @@ where horizontal_alignment: alignment::Horizontal::Center, vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, + wrap: text::Wrap::default(), }; state.icon.update(icon_text); @@ -1434,6 +1436,7 @@ fn replace_paragraph( horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, + wrap: text::Wrap::default(), }); } diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 35a0e8537d..20d34bf2d5 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -61,6 +61,7 @@ pub struct Toggler< text_line_height: text::LineHeight, text_alignment: alignment::Horizontal, text_shaping: text::Shaping, + text_wrap: text::Wrap, spacing: f32, font: Option, style: Theme::Style, @@ -110,6 +111,7 @@ where text_line_height: text::LineHeight::default(), text_alignment: alignment::Horizontal::Left, text_shaping: text::Shaping::Advanced, + text_wrap: text::Wrap::default(), spacing: 0.0, font: None, style: Default::default(), @@ -155,6 +157,12 @@ where self } + /// Sets the [`text::Wrap`] mode of the [`Toggler`]. + pub fn text_wrap(mut self, wrap: text::Wrap) -> Self { + self.text_wrap = wrap; + self + } + /// Sets the spacing between the [`Toggler`] and the text. pub fn spacing(mut self, spacing: impl Into) -> Self { self.spacing = spacing.into().0; @@ -263,6 +271,7 @@ where self.text_alignment, alignment::Vertical::Top, self.text_shaping, + self.text_wrap, ) } else { layout::Node::new(crate::core::Size::ZERO)