diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index fe38ec87c8..3065a443da 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -2,7 +2,7 @@ use crate::alignment; use crate::image; use crate::renderer::{self, Renderer}; use crate::svg; -use crate::text::{self, Text}; +use crate::text::{self, Text, Wrapping}; use crate::{ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, }; @@ -142,6 +142,7 @@ impl text::Editor for () { &mut self, _new_bounds: Size, _new_font: Self::Font, + _new_wrapping: Wrapping, _new_size: Pixels, _new_line_height: text::LineHeight, _new_highlighter: &mut impl text::Highlighter, diff --git a/core/src/text.rs b/core/src/text.rs index b30feae05d..9ef34ce536 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -39,6 +39,23 @@ pub struct Text { /// The [`Shaping`] strategy of the [`Text`]. pub shaping: Shaping, + + /// The [`Wrapping`] strategy of the [`Text`]. + pub wrapping: Wrapping, +} + +/// The wrapping strategy of some text. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum Wrapping { + /// No wrapping. + None, + /// Word wrapping. + /// + /// This is the default. + #[default] + Word, + /// Glyph wrapping. + Glyph, } /// The shaping strategy of some text. diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs index fbf6069607..06ee8244b3 100644 --- a/core/src/text/editor.rs +++ b/core/src/text/editor.rs @@ -1,6 +1,6 @@ //! Edit text. use crate::text::highlighter::{self, Highlighter}; -use crate::text::LineHeight; +use crate::text::{LineHeight, Wrapping}; use crate::{Pixels, Point, Rectangle, Size}; use std::sync::Arc; @@ -45,6 +45,7 @@ pub trait Editor: Sized + Default { &mut self, new_bounds: Size, new_font: Self::Font, + new_wrapping: Wrapping, new_size: Pixels, new_line_height: LineHeight, new_highlighter: &mut impl Highlighter, diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index f1f0b34586..5bc8003c4e 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -12,7 +12,7 @@ use crate::{ use std::borrow::Cow; -pub use text::{LineHeight, Shaping}; +pub use text::{LineHeight, Shaping, Wrapping}; /// A paragraph of text. #[allow(missing_debug_implementations)] @@ -31,6 +31,7 @@ where font: Option, shaping: Shaping, class: Theme::Class<'a>, + wrapping: Wrapping, } impl<'a, Theme, Renderer> Text<'a, Theme, Renderer> @@ -51,6 +52,7 @@ where vertical_alignment: alignment::Vertical::Top, shaping: Shaping::Basic, class: Theme::default(), + wrapping: Default::default(), } } @@ -110,6 +112,12 @@ where self } + /// Sets the [`Wrapping`] strategy of the [`Text`]. + pub fn wrapping(mut self, wrapping: Wrapping) -> Self { + self.wrapping = wrapping; + self + } + /// Sets the style of the [`Text`]. #[must_use] pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self @@ -191,6 +199,7 @@ where self.horizontal_alignment, self.vertical_alignment, self.shaping, + self.wrapping, ) } @@ -225,6 +234,7 @@ pub fn layout( horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, shaping: Shaping, + wrapping: Wrapping, ) -> layout::Node where Renderer: text::Renderer, @@ -246,6 +256,7 @@ where horizontal_alignment, vertical_alignment, shaping, + wrapping, }); paragraph.min_bounds() diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index ed16018a5c..117d4c3802 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -1,15 +1,13 @@ use iced::highlighter::{self, Highlighter}; use iced::keyboard; -use iced::widget::{ - button, column, container, horizontal_space, pick_list, row, text, - text_editor, tooltip, -}; +use iced::widget::{button, column, container, horizontal_space, pick_list, row, text, text_editor, toggler, tooltip}; use iced::{Alignment, Command, Element, Font, Length, Subscription, Theme}; use std::ffi; use std::io; use std::path::{Path, PathBuf}; use std::sync::Arc; +use iced::widget::text::Wrapping; pub fn main() -> iced::Result { iced::program("Editor - Iced", Editor::update, Editor::view) @@ -27,6 +25,7 @@ struct Editor { theme: highlighter::Theme, is_loading: bool, is_dirty: bool, + wrap: bool, } #[derive(Debug, Clone)] @@ -38,6 +37,7 @@ enum Message { FileOpened(Result<(PathBuf, Arc), Error>), SaveFile, FileSaved(Result), + ToggleWrap(bool) } impl Editor { @@ -48,6 +48,7 @@ impl Editor { theme: highlighter::Theme::SolarizedDark, is_loading: true, is_dirty: false, + wrap: true, } } @@ -122,6 +123,10 @@ impl Editor { Command::none() } + Message::ToggleWrap(enabled) => { + self.wrap = enabled; + Command::none() + } } } @@ -148,13 +153,16 @@ impl Editor { self.is_dirty.then_some(Message::SaveFile) ), horizontal_space(), + toggler("Word wrap".to_string(), self.wrap, Message::ToggleWrap) + .text_size(14) + .width(Length::Shrink), pick_list( highlighter::Theme::ALL, Some(self.theme), Message::ThemeSelected ) .text_size(14) - .padding([5, 10]) + .padding([5, 10]), ] .spacing(10) .align_items(Alignment::Center); @@ -184,6 +192,7 @@ impl Editor { controls, text_editor(&self.content) .height(Length::Fill) + .wrapping(if self.wrap { Wrapping::Word } else { Wrapping::None }) .on_action(Message::ActionPerformed) .highlight::( highlighter::Settings { diff --git a/graphics/src/text.rs b/graphics/src/text.rs index 30269e69eb..2ce64d2e5e 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -14,6 +14,7 @@ use crate::core::font::{self, Font}; use crate::core::text::Shaping; use crate::core::{Color, Pixels, Point, Rectangle, Size, Transformation}; +use iced_core::text::Wrapping; use once_cell::sync::OnceCell; use std::borrow::Cow; use std::sync::{Arc, RwLock, Weak}; @@ -250,6 +251,14 @@ pub fn to_attributes(font: Font) -> cosmic_text::Attrs<'static> { .style(to_style(font.style)) } +fn to_wrap(wrapping: Wrapping) -> cosmic_text::Wrap { + match wrapping { + Wrapping::None => cosmic_text::Wrap::None, + Wrapping::Word => cosmic_text::Wrap::Word, + Wrapping::Glyph => cosmic_text::Wrap::Glyph, + } +} + fn to_family(family: font::Family) -> cosmic_text::Family<'static> { match family { font::Family::Name(name) => cosmic_text::Family::Name(name), diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index c488a51c61..c8ce28e0f4 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -11,6 +11,7 @@ use cosmic_text::Edit as _; use std::fmt; use std::sync::{self, Arc}; +use crate::text::to_wrap; /// A multi-line text editor. #[derive(Debug, PartialEq)] @@ -480,6 +481,7 @@ impl editor::Editor for Editor { &mut self, new_bounds: Size, new_font: Font, + new_wrapping: text::Wrapping, new_size: Pixels, new_line_height: LineHeight, new_highlighter: &mut impl Highlighter, @@ -531,6 +533,12 @@ impl editor::Editor for Editor { ); } + let new_wrap = to_wrap(new_wrapping); + if new_wrap != internal.editor.buffer().wrap() { + log::trace!("Updating wrapping strategy of `Editor`..."); + internal.editor.buffer_mut().set_wrap(font_system.raw(), new_wrap); + } + if new_bounds != internal.bounds { log::trace!("Updating size of `Editor`..."); @@ -705,7 +713,7 @@ fn highlight_line( line: &cosmic_text::BufferLine, from: usize, to: usize, -) -> impl Iterator + '_ { +) -> impl Iterator + '_ { let layout = line .layout_opt() .as_ref() diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index 31a323ac90..28057814d6 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -5,6 +5,7 @@ use crate::core::text::{Hit, LineHeight, Shaping, Text}; use crate::core::{Font, Pixels, Point, Size}; use crate::text; +use iced_core::text::Wrapping; use std::fmt; use std::sync::{self, Arc}; @@ -22,6 +23,7 @@ struct Internal { bounds: Size, min_bounds: Size, version: text::Version, + wrapping: Wrapping, } impl Paragraph { @@ -88,6 +90,8 @@ impl core::text::Paragraph for Paragraph { text::to_shaping(text.shaping), ); + buffer.set_wrap(font_system.raw(), text::to_wrap(text.wrapping)); + let min_bounds = text::measure(&buffer); Self(Some(Arc::new(Internal { @@ -100,6 +104,7 @@ impl core::text::Paragraph for Paragraph { bounds: text.bounds, min_bounds, version: font_system.version(), + wrapping: text.wrapping, }))) } @@ -141,6 +146,7 @@ impl core::text::Paragraph for Paragraph { horizontal_alignment: internal.horizontal_alignment, vertical_alignment: internal.vertical_alignment, shaping: internal.shaping, + wrapping: internal.wrapping, }); } } @@ -159,6 +165,7 @@ impl core::text::Paragraph for Paragraph { || paragraph.shaping != text.shaping || paragraph.horizontal_alignment != text.horizontal_alignment || paragraph.vertical_alignment != text.vertical_alignment + || paragraph.wrapping != text.wrapping { core::text::Difference::Shape } else if paragraph.bounds != text.bounds { @@ -247,6 +254,7 @@ impl fmt::Debug for Paragraph { .field("vertical_alignment", ¶graph.vertical_alignment) .field("bounds", ¶graph.bounds) .field("min_bounds", ¶graph.min_bounds) + .field("wrapping", ¶graph.wrapping) .finish() } } @@ -261,6 +269,7 @@ impl PartialEq for Internal { && self.bounds == other.bounds && self.min_bounds == other.min_bounds && self.buffer.metrics() == other.buffer.metrics() + && self.wrapping == other.wrapping } } @@ -279,6 +288,7 @@ impl Default for Internal { bounds: Size::ZERO, min_bounds: Size::ZERO, version: text::Version::default(), + wrapping: Wrapping::default(), } } } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 178522de2a..d83cc577a9 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -18,10 +18,30 @@ //! [WebGPU API]: https://gpuweb.github.io/gpuweb/ //! [`glyphon`]: https://github.com/grovesNL/glyphon #![doc( - html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" +html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![allow(missing_docs)] + +pub use wgpu; + +use buffer::Buffer; +pub use engine::Engine; +#[cfg(feature = "geometry")] +pub use geometry::Geometry; +pub use iced_graphics as graphics; +pub use iced_graphics::core; +pub use layer::Layer; +pub use primitive::Primitive; +pub use settings::Settings; + +use crate::core::{ + Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, +}; +use crate::core::text::Wrapping; +use crate::graphics::text::{Editor, Paragraph}; +use crate::graphics::Viewport; + pub mod layer; pub mod primitive; pub mod settings; @@ -45,27 +65,6 @@ mod image; #[path = "image/null.rs"] mod image; -use buffer::Buffer; - -pub use iced_graphics as graphics; -pub use iced_graphics::core; - -pub use wgpu; - -pub use engine::Engine; -pub use layer::Layer; -pub use primitive::Primitive; -pub use settings::Settings; - -#[cfg(feature = "geometry")] -pub use geometry::Geometry; - -use crate::core::{ - Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, -}; -use crate::graphics::text::{Editor, Paragraph}; -use crate::graphics::Viewport; - /// A [`wgpu`] graphics renderer for [`iced`]. /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs @@ -244,7 +243,7 @@ impl Renderer { let mut text_layer = 0; #[cfg(any(feature = "svg", feature = "image"))] - let mut image_layer = 0; + let mut image_layer = 0; let scale_factor = viewport.scale_factor() as f32; let physical_bounds = Rectangle::::from(Rectangle::with_size( @@ -256,9 +255,9 @@ impl Renderer { for layer in self.layers.iter() { let Some(physical_bounds) = physical_bounds.intersection(&(layer.bounds * scale)) - else { - continue; - }; + else { + continue; + }; let Some(scissor_rect) = physical_bounds.snap() else { continue; @@ -393,6 +392,7 @@ impl Renderer { horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, shaping: core::text::Shaping::Basic, + wrapping: Wrapping::default(), }; renderer.fill_text( diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 225c316d48..2ed22ce856 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -6,6 +6,7 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::text; use crate::core::theme::palette; +use crate::core::text::Wrapping; use crate::core::touch; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; @@ -240,6 +241,7 @@ where alignment::Horizontal::Left, alignment::Vertical::Top, self.text_shaping, + Wrapping::default(), ) }, ) @@ -348,6 +350,7 @@ where horizontal_alignment: alignment::Horizontal::Center, vertical_alignment: alignment::Vertical::Center, shaping: *shaping, + wrapping: Wrapping::default(), }, bounds.center(), style.icon_color, diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 98efe30523..cad3ca0d6f 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -534,6 +534,7 @@ where horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: self.text_shaping, + wrapping: text::Wrapping::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 edccfdaaa6..e45d7af639 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -225,6 +225,7 @@ where horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: self.text_shaping, + wrapping: text::Wrapping::default(), }; for (option, paragraph) in options.iter().zip(state.options.iter_mut()) @@ -490,6 +491,7 @@ where horizontal_alignment: alignment::Horizontal::Right, vertical_alignment: alignment::Vertical::Center, shaping, + wrapping: text::Wrapping::default(), }, Point::new( bounds.x + bounds.width - self.padding.right, @@ -519,6 +521,7 @@ where horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: self.text_shaping, + wrapping: text::Wrapping::default(), }, Point::new(bounds.x + self.padding.left, bounds.center_y()), if is_selected { diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 6b22961db4..de92ab27b1 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -244,6 +244,7 @@ where alignment::Horizontal::Left, alignment::Vertical::Top, self.text_shaping, + text::Wrapping::default(), ) }, ) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 5b0b1b79c2..e3c33a9524 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -21,6 +21,7 @@ use std::ops::DerefMut; use std::sync::Arc; pub use text::editor::{Action, Edit, Motion}; +use crate::text::Wrapping; /// A multi-line text input. #[allow(missing_debug_implementations)] @@ -43,6 +44,7 @@ pub struct TextEditor< height: Length, padding: Padding, class: Theme::Class<'a>, + wrapping: Wrapping, on_edit: Option Message + 'a>>, highlighter_settings: Highlighter::Settings, highlighter_format: fn( @@ -68,6 +70,7 @@ where height: Length::Shrink, padding: Padding::new(5.0), class: Theme::default(), + wrapping: Wrapping::default(), on_edit: None, highlighter_settings: (), highlighter_format: |_highlight, _theme| { @@ -131,6 +134,12 @@ where self } + /// Sets the [`Wrapping`] strategy of the [`TextEditor`]. + pub fn wrapping(mut self, wrapping: Wrapping) -> Self { + self.wrapping = wrapping; + self + } + /// Highlights the [`TextEditor`] with the given [`Highlighter`] and /// a strategy to turn its highlights into some text format. pub fn highlight( @@ -150,6 +159,7 @@ where height: self.height, padding: self.padding, class: self.class, + wrapping: self.wrapping, on_edit: self.on_edit, highlighter_settings: settings, highlighter_format: to_format, @@ -400,6 +410,7 @@ where internal.editor.update( limits.shrink(self.padding).max(), self.font.unwrap_or_else(|| renderer.default_font()), + self.wrapping, self.text_size.unwrap_or_else(|| renderer.default_size()), self.line_height, state.highlighter.borrow_mut().deref_mut(), diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index e9f0783876..a2bd5dd049 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -238,6 +238,7 @@ where horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, + wrapping: text::Wrapping::default(), }; state.placeholder.update(placeholder_text); @@ -262,6 +263,7 @@ where horizontal_alignment: alignment::Horizontal::Center, vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, + wrapping: text::Wrapping::default(), }; state.icon.update(icon_text); @@ -1383,6 +1385,7 @@ fn replace_paragraph( horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, + wrapping: text::Wrapping::default(), }); } diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index ca6e37b0c0..ae6df3fb2a 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -216,6 +216,7 @@ where self.text_alignment, alignment::Vertical::Top, self.text_shaping, + text::Wrapping::default(), ) } else { layout::Node::new(Size::ZERO)