diff --git a/Cargo.toml b/Cargo.toml index 63dc4c71..ffaebf9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -201,7 +201,7 @@ repository = "https://github.com/iced-rs/iced" homepage = "https://iced.rs" categories = ["gui"] keywords = ["gui", "ui", "graphics", "interface", "widgets"] -rust-version = "1.88" +rust-version = "1.89" [workspace.dependencies] iced = { version = "0.14.0", path = "." } diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index 22b89ada..52d49c64 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -157,6 +157,18 @@ impl text::Paragraph for () { fn span_bounds(&self, _index: usize) -> Vec { vec![] } + + fn min_width(&self) -> f32 { + self.min_bounds().width + } + + fn min_height(&self) -> f32 { + self.min_bounds().height + } + + fn ellipsize(&self) -> text::Ellipsize { + text::Ellipsize::default() + } } impl text::Editor for () { diff --git a/core/src/text.rs b/core/src/text.rs index 4ac4fb2a..e62ab730 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -44,6 +44,9 @@ pub struct Text { /// The [`Wrapping`] strategy of the [`Text`]. pub wrapping: Wrapping, + + /// The [`Ellipsize`] strategy of the [`Text`]. + pub ellipsize: Ellipsize, } impl Text @@ -63,6 +66,7 @@ where align_y: self.align_y, shaping: self.shaping, wrapping: self.wrapping, + ellipsize: self.ellipsize, } } } @@ -192,6 +196,31 @@ pub enum Wrapping { WordOrGlyph, } +/// The ellipsizing strategy of some text. +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum Ellipsize { + /// No ellipsizing. + /// + /// This is the default. + #[default] + None, + /// Ellipsize at the start of the text. + Start(EllipsizeHeightLimit), + /// Ellipsize in the middle of the text. + Middle(EllipsizeHeightLimit), + /// Ellipsize at the end of the text. + End(EllipsizeHeightLimit), +} + +/// The ellipsizing strategy of some text when it exceeds a certain height. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum EllipsizeHeightLimit { + /// The number of lines after which ellipsizing should occur. + Lines(usize), + /// The height limit in pixels + Height(f32), +} + /// The height of a line of text in a paragraph. #[derive(Debug, Clone, Copy, PartialEq)] pub enum LineHeight { diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs index 828b66b6..ecc55bad 100644 --- a/core/src/text/paragraph.rs +++ b/core/src/text/paragraph.rs @@ -5,6 +5,8 @@ use crate::text::{ }; use crate::{Pixels, Point, Rectangle, Size}; +use super::Ellipsize; + /// A text paragraph. pub trait Paragraph: Sized + Default { /// The font of this [`Paragraph`]. @@ -78,6 +80,10 @@ pub trait Paragraph: Sized + Default { fn min_height(&self) -> f32 { self.min_bounds().height } + + + /// Returns the [`Ellipsize`] strategy of the [`Paragraph`]> + fn ellipsize(&self) -> Ellipsize; } /// A [`Paragraph`] of plain text. @@ -169,6 +175,7 @@ impl Plain

{ align_y: self.raw.align_y(), shaping: self.raw.shaping(), wrapping: self.raw.wrapping(), + ellipsize: self.raw.ellipsize() } } } diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index c16f5d5d..1e40bf9e 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -32,7 +32,7 @@ use crate::{ }; use std::borrow::Cow; -pub use text::{Alignment, LineHeight, Shaping, Wrapping}; +pub use text::{Alignment, Ellipsize, LineHeight, Shaping, Wrapping}; /// A bunch of text. /// @@ -158,6 +158,12 @@ where self } + // Sets the [`Ellipsize`] strategy of the [`Text`]. + pub fn ellipsize(mut self, ellipsize: Ellipsize) -> Self { + self.format.ellipsize = ellipsize; + self + } + /// Sets the style of the [`Text`]. #[must_use] pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self @@ -328,6 +334,7 @@ pub struct Format { pub align_y: alignment::Vertical, pub shaping: Shaping, pub wrapping: Wrapping, + pub ellipsize: Ellipsize, } impl Default for Format { @@ -342,6 +349,7 @@ impl Default for Format { align_y: alignment::Vertical::Top, shaping: Shaping::default(), wrapping: Wrapping::default(), + ellipsize: Ellipsize::default(), } } } @@ -373,6 +381,7 @@ where align_y: format.align_y, shaping: format.shaping, wrapping: format.wrapping, + ellipsize: format.ellipsize, }); paragraph.min_bounds() diff --git a/graphics/src/text.rs b/graphics/src/text.rs index 8a5ed946..b82a289d 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -355,6 +355,37 @@ pub fn to_wrap(wrapping: Wrapping) -> cosmic_text::Wrap { } } +/// Converts some [`Ellipsize`] strategy to a [`cosmic_text::Ellipsize`] strategy. +pub fn to_ellipsize( + ellipsize: crate::core::text::Ellipsize, +) -> cosmic_text::Ellipsize { + match ellipsize { + crate::core::text::Ellipsize::None => cosmic_text::Ellipsize::None, + crate::core::text::Ellipsize::Start(limit) => { + cosmic_text::Ellipsize::Start(to_ellipsize_height_limit(limit)) + } + crate::core::text::Ellipsize::Middle(limit) => { + cosmic_text::Ellipsize::Middle(to_ellipsize_height_limit(limit)) + } + crate::core::text::Ellipsize::End(limit) => { + cosmic_text::Ellipsize::End(to_ellipsize_height_limit(limit)) + } + } +} + +pub fn to_ellipsize_height_limit( + limit: crate::core::text::EllipsizeHeightLimit, +) -> cosmic_text::EllipsizeHeightLimit { + match limit { + crate::core::text::EllipsizeHeightLimit::Lines(lines) => { + cosmic_text::EllipsizeHeightLimit::Lines(lines) + } + crate::core::text::EllipsizeHeightLimit::Height(height) => { + cosmic_text::EllipsizeHeightLimit::Height(height) + } + } +} + /// 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 f6750922..51881b6c 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -1,4 +1,6 @@ //! Draw paragraphs. +use iced_core::text::Ellipsize; + use crate::core; use crate::core::alignment; use crate::core::text::{ @@ -24,6 +26,7 @@ struct Internal { align_y: alignment::Vertical, bounds: Size, min_bounds: Size, + ellipsize: Ellipsize, version: text::Version, } @@ -83,6 +86,10 @@ impl core::text::Paragraph for Paragraph { ); buffer.set_wrap(font_system.raw(), text::to_wrap(text.wrapping)); + buffer.set_ellipsize( + font_system.raw(), + text::to_ellipsize(text.ellipsize), + ); buffer.set_text( font_system.raw(), @@ -101,6 +108,7 @@ impl core::text::Paragraph for Paragraph { align_x: text.align_x, align_y: text.align_y, shaping: text.shaping, + ellipsize: text.ellipsize, wrapping: text.wrapping, bounds: text.bounds, min_bounds, @@ -177,6 +185,7 @@ impl core::text::Paragraph for Paragraph { align_x: text.align_x, align_y: text.align_y, shaping: text.shaping, + ellipsize: text.ellipsize, wrapping: text.wrapping, bounds: text.bounds, min_bounds, @@ -394,6 +403,10 @@ impl core::text::Paragraph for Paragraph { glyph.y - glyph.y_offset * glyph.font_size, )) } + + fn ellipsize(&self) -> Ellipsize { + self.0.ellipsize + } } impl Default for Paragraph { @@ -438,6 +451,7 @@ impl Default for Internal { }), font: Font::default(), shaping: Shaping::default(), + ellipsize: Ellipsize::default(), wrapping: Wrapping::default(), align_x: Alignment::Default, align_y: alignment::Vertical::Top, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 07f4d7c3..4eb52cc8 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -694,6 +694,7 @@ impl Renderer { align_y: alignment::Vertical::Top, shaping: core::text::Shaping::Advanced, wrapping: core::text::Wrapping::Word, + ellipsize: core::text::Ellipsize::None, }; renderer.fill_text( diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index a070348b..873a4541 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -109,6 +109,7 @@ pub struct Checkbox< text_line_height: text::LineHeight, text_shaping: text::Shaping, text_wrapping: text::Wrapping, + text_ellipsize: text::Ellipsize, font: Option, icon: Icon, class: Theme::Class<'a>, @@ -145,6 +146,7 @@ where text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::default(), text_wrapping: text::Wrapping::default(), + text_ellipsize: text::Ellipsize::default(), font: None, icon: Icon { font: Renderer::ICON_FONT, @@ -351,6 +353,7 @@ where align_y: alignment::Vertical::Top, shaping: self.text_shaping, wrapping: self.text_wrapping, + ellipsize: self.text_ellipsize, }, ) } else { @@ -477,6 +480,7 @@ where align_y: alignment::Vertical::Center, shaping: *shaping, wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), }, bounds.center(), style.icon_color, diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 364641ce..94d2b6ad 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -576,6 +576,7 @@ where align_y: alignment::Vertical::Center, shaping: self.text_shaping, wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), }, 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 7e147b4b..36858cd5 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -171,6 +171,7 @@ pub struct PickList< text_line_height: text::LineHeight, text_shaping: text::Shaping, text_wrap: text::Wrapping, + text_ellipsize: text::Ellipsize, font: Option, handle: Handle, class: ::Class<'a>, @@ -209,6 +210,7 @@ where text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Advanced, text_wrap: text::Wrapping::default(), + text_ellipsize: text::Ellipsize::default(), font: None, handle: Handle::default(), class: ::default(), @@ -392,6 +394,7 @@ where align_y: alignment::Vertical::Center, shaping: self.text_shaping, wrapping: self.text_wrap, + ellipsize: self.text_ellipsize, }; for (option, paragraph) in options.iter().zip(state.options.iter_mut()) @@ -614,6 +617,7 @@ where text::LineHeight::default(), text::Shaping::Basic, text::Wrapping::default(), + text::Ellipsize::default(), )), Handle::Static(Icon { font, @@ -622,9 +626,16 @@ where line_height, shaping, wrap, - }) => { - Some((*font, *code_point, *size, *line_height, *shaping, *wrap)) - } + ellipsize, + }) => Some(( + *font, + *code_point, + *size, + *line_height, + *shaping, + *wrap, + *ellipsize, + )), Handle::Dynamic { open, closed } => { if state.is_open { Some(( @@ -634,6 +645,7 @@ where open.line_height, open.shaping, open.wrap, + open.ellipsize, )) } else { Some(( @@ -643,14 +655,22 @@ where closed.line_height, closed.shaping, closed.wrap, + closed.ellipsize, )) } } Handle::None => None, }; - if let Some((font, code_point, size, line_height, shaping, wrap)) = - handle + if let Some(( + font, + code_point, + size, + line_height, + shaping, + wrap, + ellipsize, + )) = handle { let size = size.unwrap_or_else(|| renderer.default_size()); @@ -668,6 +688,7 @@ where align_y: alignment::Vertical::Center, shaping, wrapping: wrap, + ellipsize: ellipsize, }, Point::new( bounds.x + bounds.width - self.padding.right, @@ -698,6 +719,7 @@ where align_y: alignment::Vertical::Center, shaping: self.text_shaping, wrapping: self.text_wrap, + ellipsize: self.text_ellipsize, }, Point::new(bounds.x + self.padding.left, bounds.center_y()), if selected.is_some() { @@ -851,6 +873,8 @@ pub struct Icon { pub shaping: text::Shaping, /// The wrap mode of the icon. pub wrap: text::Wrapping, + /// The ellipsize mode of the icon. + pub ellipsize: text::Ellipsize, } /// The possible status of a [`PickList`]. diff --git a/widget/src/radio.rs b/widget/src/radio.rs index d637abe4..1aa97355 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -144,6 +144,7 @@ where text_line_height: text::LineHeight, text_shaping: text::Shaping, text_wrapping: text::Wrapping, + text_ellipsize: text::Ellipsize, font: Option, class: Theme::Class<'a>, last_status: Option, @@ -190,6 +191,7 @@ where text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Advanced, text_wrapping: text::Wrapping::default(), + text_ellipsize: text::Ellipsize::default(), font: None, class: Theme::default(), last_status: None, @@ -241,6 +243,12 @@ where self } + /// Sets the [`text::Ellipsize`] strategy of the [`Radio`] button. + pub fn text_ellipsize(mut self, ellipsize: text::Ellipsize) -> Self { + self.text_ellipsize = ellipsize; + self + } + /// Sets the text font of the [`Radio`] button. pub fn font(mut self, font: impl Into) -> Self { self.font = Some(font.into()); @@ -318,6 +326,7 @@ where align_y: alignment::Vertical::Top, shaping: self.text_shaping, wrapping: self.text_wrapping, + ellipsize: self.text_ellipsize, }, ) }, diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index d88e2139..c60104d6 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -1847,6 +1847,7 @@ where align_y: alignment::Vertical::Center, shaping: text::Shaping::Basic, wrapping: text::Wrapping::None, + ellipsize: text::Ellipsize::None, }; if self.vertical { diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index 5bada7f2..cd368e83 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -4,7 +4,8 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::text::{Paragraph, Span}; use crate::core::widget::text::{ - self, Alignment, Catalog, LineHeight, Shaping, Style, StyleFn, Wrapping, + self, Alignment, Catalog, Ellipsize, LineHeight, Shaping, Style, StyleFn, + Wrapping, }; use crate::core::widget::tree::{self, Tree}; use crate::core::{ @@ -33,6 +34,7 @@ pub struct Rich< align_x: Alignment, align_y: alignment::Vertical, wrapping: Wrapping, + ellipsize: Ellipsize, class: Theme::Class<'a>, hovered_link: Option, on_link_click: Option Message + 'a>>, @@ -58,6 +60,7 @@ where align_x: Alignment::Default, align_y: alignment::Vertical::Top, wrapping: Wrapping::default(), + ellipsize: Ellipsize::default(), class: Theme::default(), hovered_link: None, on_link_click: None, @@ -145,6 +148,12 @@ where self } + /// Sets the [`Ellipsize`] strategy of the [`Rich`] text. + pub fn ellipsize(mut self, ellipsize: Ellipsize) -> Self { + self.ellipsize = ellipsize; + self + } + /// Sets the default style of the [`Rich`] text. #[must_use] pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self @@ -247,6 +256,7 @@ where self.align_x, self.align_y, self.wrapping, + self.ellipsize, ) } @@ -476,6 +486,7 @@ fn layout( align_x: Alignment, align_y: alignment::Vertical, wrapping: Wrapping, + ellipsize: Ellipsize, ) -> layout::Node where Link: Clone, @@ -497,6 +508,7 @@ where align_y, shaping: Shaping::Advanced, wrapping, + ellipsize, }; if state.spans != spans { @@ -514,6 +526,7 @@ where align_y, shaping: Shaping::Advanced, wrapping, + ellipsize, }) { core::text::Difference::None => {} core::text::Difference::Bounds => { diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index b0e279a3..b93c96f3 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -41,7 +41,7 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::text::editor::Editor as _; use crate::core::text::highlighter::{self, Highlighter}; -use crate::core::text::{self, LineHeight, Text, Wrapping}; +use crate::core::text::{self, Ellipsize, LineHeight, Text, Wrapping}; use crate::core::theme; use crate::core::time::{Duration, Instant}; use crate::core::widget::operation; @@ -129,6 +129,7 @@ pub struct TextEditor< max_height: f32, padding: Padding, wrapping: Wrapping, + ellipsize: text::Ellipsize, class: Theme::Class<'a>, key_binding: Option Option> + 'a>>, on_edit: Option Message + 'a>>, @@ -162,6 +163,7 @@ where padding: Padding::new(5.0), wrapping: Wrapping::default(), class: ::default(), + ellipsize: Ellipsize::default(), key_binding: None, on_edit: None, highlighter_settings: (), @@ -308,6 +310,7 @@ where max_height: self.max_height, padding: self.padding, wrapping: self.wrapping, + ellipsize: self.ellipsize, class: self.class, key_binding: self.key_binding, on_edit: self.on_edit, @@ -1011,6 +1014,7 @@ where align_y: alignment::Vertical::Top, shaping: text::Shaping::Advanced, wrapping: self.wrapping, + ellipsize: self.ellipsize, }, text_bounds.position(), style.placeholder, diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index d5d3e2ce..8efa74dd 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -321,6 +321,7 @@ where align_y: alignment::Vertical::Center, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), }; let _ = state.placeholder.update(placeholder_text); @@ -346,6 +347,7 @@ where align_y: alignment::Vertical::Center, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), }; let _ = state.icon.update(icon_text); @@ -1687,6 +1689,7 @@ fn replace_paragraph( align_y: alignment::Vertical::Center, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), + ellipsize: text::Ellipsize::default(), }); } diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 5ab6271a..d398a79b 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -112,6 +112,7 @@ pub struct Toggler< text_alignment: text::Alignment, text_shaping: text::Shaping, text_wrapping: text::Wrapping, + text_ellipsize: text::Ellipsize, spacing: f32, font: Option, class: Theme::Class<'a>, @@ -150,6 +151,7 @@ where text_line_height: text::LineHeight::default(), text_alignment: text::Alignment::Default, text_wrapping: text::Wrapping::default(), + text_ellipsize: text::Ellipsize::default(), spacing: Self::DEFAULT_SIZE / 2.0, text_shaping: text::Shaping::Advanced, font: None, @@ -237,6 +239,12 @@ where self } + /// Sets the [`text::Ellipsize`] strategy of the [`Toggler`]. + pub fn text_ellipsize(mut self, ellipsize: text::Ellipsize) -> Self { + self.text_ellipsize = ellipsize; + self + } + /// Sets the spacing between the [`Toggler`] and the text. pub fn spacing(mut self, spacing: impl Into) -> Self { self.spacing = spacing.into().0; @@ -362,6 +370,7 @@ where align_y: alignment::Vertical::Top, shaping: self.text_shaping, wrapping: self.text_wrapping, + ellipsize: self.text_ellipsize, }, ) } else { diff --git a/winit/src/window.rs b/winit/src/window.rs index 9ba3b774..7548ce6f 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -434,6 +434,7 @@ where align_y: alignment::Vertical::Top, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, + ellipsize: text::Ellipsize::default(), }); self.spans.clear();