diff --git a/examples/editor-libcosmic/src/main.rs b/examples/editor-libcosmic/src/main.rs index fc56c95..0b47a2e 100644 --- a/examples/editor-libcosmic/src/main.rs +++ b/examples/editor-libcosmic/src/main.rs @@ -32,6 +32,7 @@ use cosmic_text::{ Metrics, SyntaxEditor, SyntaxSystem, + Wrap, }; use std::{ env, @@ -60,6 +61,12 @@ static FONT_SIZES: &'static [Metrics] = &[ Metrics::new(32, 44), // Title 1 ]; +static WRAP_MODE: &'static [Wrap] = & [ + Wrap::None, + Wrap::Glyph, + Wrap::Word, +]; + fn main() -> cosmic::iced::Result { env_logger::init(); @@ -87,6 +94,7 @@ pub enum Message { Italic(bool), Monospaced(bool), MetricsChanged(Metrics), + WrapChanged(Wrap), ThemeChanged(&'static str), } @@ -213,6 +221,10 @@ impl Application for Window { let mut editor = self.editor.lock().unwrap(); editor.buffer_mut().set_metrics(metrics); }, + Message::WrapChanged(wrap) => { + let mut editor = self.editor.lock().unwrap(); + editor.buffer_mut().set_wrap(wrap); + }, Message::ThemeChanged(theme) => { self.theme = match theme { "Dark" => Theme::Dark, @@ -252,6 +264,15 @@ impl Application for Window { ) }; + let wrap_picker = { + let editor = self.editor.lock().unwrap(); + pick_list( + WRAP_MODE, + Some(editor.buffer().wrap()), + Message::WrapChanged, + ) + }; + let content: Element<_> = column![ row![ button(theme::Button::Secondary) @@ -271,6 +292,8 @@ impl Application for Window { theme_picker, text("Font Size:"), font_size_picker, + text("Wrap:"), + wrap_picker, ] .align_items(Alignment::Center) .spacing(8) diff --git a/examples/editor-libcosmic/src/text.rs b/examples/editor-libcosmic/src/text.rs index a577edb..d3bdef7 100644 --- a/examples/editor-libcosmic/src/text.rs +++ b/examples/editor-libcosmic/src/text.rs @@ -116,7 +116,7 @@ where let layout_lines = shape.layout( self.metrics.font_size, limits.max().width as i32, - self.line.wrap_simple() + self.line.wrap() ); let mut width = 0; @@ -180,7 +180,7 @@ where let layout_lines = shape.layout( self.metrics.font_size, layout_w, - self.line.wrap_simple() + self.line.wrap() ); let mut cache = state.cache.lock().unwrap(); diff --git a/src/buffer.rs b/src/buffer.rs index cdaf0cc..9dcf053 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -11,7 +11,7 @@ use core::{ }; use unicode_segmentation::UnicodeSegmentation; -use crate::{Attrs, AttrsList, BufferLine, FontSystem, LayoutGlyph, LayoutLine, ShapeLine}; +use crate::{Attrs, AttrsList, BufferLine, FontSystem, LayoutGlyph, LayoutLine, ShapeLine, Wrap}; #[cfg(feature = "swash")] use crate::Color; @@ -157,6 +157,7 @@ pub struct Buffer<'a> { scroll: i32, /// True if a redraw is requires. Set to false after processing redraw: bool, + wrap: Wrap, } impl<'a> Buffer<'a> { @@ -173,6 +174,7 @@ impl<'a> Buffer<'a> { height: 0, scroll: 0, redraw: false, + wrap: Wrap::Word, }; buffer.set_text("", Attrs::new()); buffer @@ -188,7 +190,8 @@ impl<'a> Buffer<'a> { line.layout( self.font_system, self.metrics.font_size, - self.width + self.width, + self.wrap ); } } @@ -217,7 +220,8 @@ impl<'a> Buffer<'a> { let layout = line.layout( self.font_system, self.metrics.font_size, - self.width + self.width, + self.wrap ); total_layout += layout.len() as i32; } @@ -249,7 +253,8 @@ impl<'a> Buffer<'a> { let layout = line.layout( self.font_system, self.metrics.font_size, - self.width + self.width, + self.wrap ); if line_i == cursor.line { let layout_cursor = self.layout_cursor(&cursor); @@ -350,7 +355,7 @@ impl<'a> Buffer<'a> { /// Lay out the provided line index and return the result pub fn line_layout(&mut self, line_i: usize) -> Option<&[LayoutLine]> { let line = self.lines.get_mut(line_i)?; - Some(line.layout(self.font_system, self.metrics.font_size, self.width)) + Some(line.layout(self.font_system, self.metrics.font_size, self.width, self.wrap)) } /// Get the current [`Metrics`] @@ -367,6 +372,20 @@ impl<'a> Buffer<'a> { } } + /// Get the current [`Wrap`] + pub fn wrap(&self) -> Wrap { + self.wrap + } + + /// Set the current [`Wrap`] + pub fn set_wrap(&mut self, wrap: Wrap) { + if wrap != self.wrap { + self.wrap = wrap; + self.relayout(); + self.shape_until_scroll(); + } + } + /// Get the current buffer dimensions (width, height) pub fn size(&self) -> (i32, i32) { (self.width, self.height) diff --git a/src/buffer_line.rs b/src/buffer_line.rs index 5764a05..83ebe28 100644 --- a/src/buffer_line.rs +++ b/src/buffer_line.rs @@ -4,14 +4,14 @@ use alloc::{ vec::Vec, }; -use crate::{AttrsList, FontSystem, LayoutLine, ShapeLine}; +use crate::{AttrsList, FontSystem, LayoutLine, ShapeLine, Wrap}; /// A line (or paragraph) of text that is shaped and laid out pub struct BufferLine { //TODO: make this not pub(crate) text: String, attrs_list: AttrsList, - wrap_simple: bool, + wrap: Wrap, shape_opt: Option, layout_opt: Option>, } @@ -24,7 +24,7 @@ impl BufferLine { Self { text: text.into(), attrs_list, - wrap_simple: false, + wrap: Wrap::Word, shape_opt: None, layout_opt: None, } @@ -69,18 +69,18 @@ impl BufferLine { } } - /// Get simple wrapping setting (wrap by characters only) - pub fn wrap_simple(&self) -> bool { - self.wrap_simple + /// Get wrapping setting (wrap by characters/words or no wrapping) + pub fn wrap(&self) -> Wrap { + self.wrap } - /// Set simple wrapping setting (wrap by characters only) + /// Set wrapping setting (wrap by characters/words or no wrapping) /// - /// Will reset shape and layout if it differs from current simple wrapping setting. + /// Will reset shape and layout if it differs from current wrapping setting. /// Returns true if the line was reset - pub fn set_wrap_simple(&mut self, wrap_simple: bool) -> bool { - if wrap_simple != self.wrap_simple { - self.wrap_simple = wrap_simple; + pub fn set_wrap(&mut self, wrap: Wrap) -> bool { + if wrap != self.wrap { + self.wrap = wrap; self.reset(); true } else { @@ -116,7 +116,7 @@ impl BufferLine { self.reset(); let mut new = Self::new(text, attrs_list); - new.wrap_simple = self.wrap_simple; + new.wrap = self.wrap; new } @@ -152,14 +152,14 @@ impl BufferLine { } /// Layout line, will cache results - pub fn layout(&mut self, font_system: &FontSystem, font_size: i32, width: i32) -> &[LayoutLine] { + pub fn layout(&mut self, font_system: &FontSystem, font_size: i32, width: i32, wrap: Wrap) -> &[LayoutLine] { if self.layout_opt.is_none() { - let wrap_simple = self.wrap_simple; + self.wrap = wrap; let shape = self.shape(font_system); let layout = shape.layout( font_size, width, - wrap_simple + wrap ); self.layout_opt = Some(layout); } diff --git a/src/edit/syntect.rs b/src/edit/syntect.rs index 32cdcb3..8414506 100644 --- a/src/edit/syntect.rs +++ b/src/edit/syntect.rs @@ -33,7 +33,8 @@ use crate::{ Edit, Editor, Style, - Weight, + Weight, + Wrap, }; pub struct SyntaxSystem { @@ -225,7 +226,7 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> { // Update line attributes. This operation only resets if the line changes line.set_attrs_list(attrs_list); - line.set_wrap_simple(false); + line.set_wrap(Wrap::Word); //TODO: efficiently do syntax highlighting without having to shape whole buffer buffer.line_shape(line_i); diff --git a/src/layout.rs b/src/layout.rs index bfe98a1..2625659 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 +use core::fmt::Display; + #[cfg(not(feature = "std"))] use alloc::vec::Vec; @@ -57,3 +59,24 @@ pub struct LayoutLine { /// Glyphs in line pub glyphs: Vec, } + +/// Wrapping mode +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Wrap { + /// No wrapping + None, + /// Wraps at a glyph level + Glyph, + /// Word Wrapping + Word, +} + +impl Display for Wrap { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::None => write!(f, "No Wrap"), + Self::Word => write!(f, "Word Wrap"), + Self::Glyph => write!(f, "Character"), + } + } +} diff --git a/src/shape.rs b/src/shape.rs index 88b28bc..ac5fd86 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -8,7 +8,7 @@ use core::ops::Range; use unicode_script::{Script, UnicodeScript}; use unicode_segmentation::UnicodeSegmentation; -use crate::{AttrsList, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine}; +use crate::{AttrsList, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine, Wrap}; use crate::fallback::FontFallbackIter; fn shape_fallback( @@ -432,6 +432,9 @@ pub struct ShapeLine { pub spans: Vec, } +// Visual Line Ranges: (span_index, (first_word_index, first_glyph_index), (last_word_index, last_glyph_index)) +type VlRange = (usize, (usize, usize), (usize, usize)); + impl ShapeLine { pub fn new<'a>( font_system: &'a FontSystem, @@ -543,7 +546,7 @@ impl ShapeLine { } // A modified version of second part of unicode_bidi::bidi_info::visual run - fn reorder(&self, line_range: &[(usize, (usize, usize), (usize, usize))]) -> Vec> { + fn reorder(&self, line_range: &[VlRange]) -> Vec> { let line : Vec = line_range.iter().map(|(span_index, _, _)| self.spans[*span_index].level).collect(); // Find consecutive level runs. let mut runs = Vec::new(); @@ -608,7 +611,7 @@ impl ShapeLine { &self, font_size: i32, line_width: i32, - wrap_simple: bool, + wrap: Wrap, ) -> Vec { let mut layout_lines = Vec::with_capacity(1); @@ -622,31 +625,37 @@ impl ShapeLine { let start_x = if self.rtl { line_width as f32 } else { 0.0 }; let end_x = if self.rtl { 0.0 } else { line_width as f32 }; let mut x = start_x; - let mut y = 0.0; + let mut y; + + // This would keep the maximum number of spans that would fit on a visual line // If one span is too large, this variable will hold the range of words inside that span // that fits on a line. - let mut current_visual_line: Vec<(usize, (usize, usize), (usize, usize))> = Vec::with_capacity(1); + let mut current_visual_line: Vec = Vec::with_capacity(1); - let mut fit_x = line_width as f32; + if wrap == Wrap::None { + for (span_index, span) in self.spans.iter().enumerate() { + current_visual_line.push((span_index, (0,0), (span.words.len(), 0))); + } + } + else { + let mut fit_x = line_width as f32; + for (span_index, span) in self.spans.iter().enumerate() { - for (span_index, span) in self.spans.iter().enumerate() { + let mut word_ranges = Vec::new(); + let mut word_range_width = 0.; - let mut word_ranges: Vec<((usize, usize), (usize, usize), f32)> = Vec::new(); - let mut word_range_width = 0.; - - // Create the word ranges that fits in a visual line - if self.rtl != span.level.is_rtl() { // incongruent directions - let mut fitting_start = (span.words.len(), 0); - for (i, word) in span.words.iter().enumerate().rev() { - let word_size = font_size as f32 * word.x_advance; - if fit_x - word_size >= 0. { // fits - fit_x -= word_size; - word_range_width += word_size; - continue; - } else { - if wrap_simple { + // Create the word ranges that fits in a visual line + if self.rtl != span.level.is_rtl() { // incongruent directions + let mut fitting_start = (span.words.len(), 0); + for (i, word) in span.words.iter().enumerate().rev() { + let word_size = font_size as f32 * word.x_advance; + if fit_x - word_size >= 0. { // fits + fit_x -= word_size; + word_range_width += word_size; + continue; + } else if wrap == Wrap::Glyph { for (glyph_i, glyph) in word.glyphs.iter().enumerate().rev() { let glyph_size = font_size as f32 * glyph.x_advance; if fit_x - glyph_size >= 0. { @@ -660,7 +669,7 @@ impl ShapeLine { fitting_start = (i, glyph_i+1); } } - } else { + } else { // Wrap::Word word_ranges.push(((i+1, 0), fitting_start, word_range_width)); if word.blank { @@ -674,19 +683,17 @@ impl ShapeLine { } } } - } - word_ranges.push(((0,0),fitting_start, word_range_width)); + word_ranges.push(((0,0),fitting_start, word_range_width)); - } else { // congruent direction - let mut fitting_start = (0,0); - for (i, word) in span.words.iter().enumerate() { - let word_size = font_size as f32 * word.x_advance; - if fit_x - word_size >= 0. { // fits - fit_x -= word_size; - word_range_width += word_size; - continue; - } else { - if wrap_simple { + } else { // congruent direction + let mut fitting_start = (0,0); + for (i, word) in span.words.iter().enumerate() { + let word_size = font_size as f32 * word.x_advance; + if fit_x - word_size >= 0. { // fits + fit_x -= word_size; + word_range_width += word_size; + continue; + } else if wrap == Wrap::Glyph { for (glyph_i, glyph) in word.glyphs.iter().enumerate() { let glyph_size = font_size as f32 * glyph.x_advance; if fit_x - glyph_size >= 0. { @@ -700,7 +707,7 @@ impl ShapeLine { fitting_start = (i, glyph_i); } } - } else { + } else { // Wrap::Word word_ranges.push((fitting_start,(i,0), word_range_width)); if word.blank { @@ -714,48 +721,48 @@ impl ShapeLine { } } } + word_ranges.push((fitting_start, (span.words.len(), 0), word_range_width)); } - word_ranges.push((fitting_start, (span.words.len(), 0), word_range_width)); - } - // Create a visual line - for ((starting_word, starting_glyph), (ending_word, ending_glyph), word_range_width) in word_ranges { - // To simplify the algorithm above, we might push empty ranges but we ignore them here - if ending_word == starting_word && starting_glyph == ending_glyph { - continue; - } - + // Create a visual line + for ((starting_word, starting_glyph), (ending_word, ending_glyph), word_range_width) in word_ranges { + // To simplify the algorithm above, we might push empty ranges but we ignore them here + if ending_word == starting_word && starting_glyph == ending_glyph { + continue; + } + - let fits = !if self.rtl { - x - word_range_width < end_x - } else { - x + word_range_width > end_x - }; - - - if fits { - current_visual_line.push((span_index, (starting_word, starting_glyph), (ending_word, ending_glyph))); - if self.rtl { - x -= word_range_width; + let fits = !if self.rtl { + x - word_range_width < end_x } else { - x += word_range_width; - } - } else { - if !current_visual_line.is_empty(){ - vl_range_of_spans.push(current_visual_line); - current_visual_line = Vec::with_capacity(1); - x = start_x; - } - current_visual_line.push((span_index, (starting_word, starting_glyph), (ending_word, ending_glyph))); - if self.rtl { - x -= word_range_width; + x + word_range_width > end_x + }; + + + if fits { + current_visual_line.push((span_index, (starting_word, starting_glyph), (ending_word, ending_glyph))); + if self.rtl { + x -= word_range_width; + } else { + x += word_range_width; + } } else { - x += word_range_width; - } - if word_range_width > line_width as f32 { // single word is bigger than line_width - vl_range_of_spans.push(current_visual_line); - current_visual_line = Vec::with_capacity(1); - x = start_x; + if !current_visual_line.is_empty(){ + vl_range_of_spans.push(current_visual_line); + current_visual_line = Vec::with_capacity(1); + x = start_x; + } + current_visual_line.push((span_index, (starting_word, starting_glyph), (ending_word, ending_glyph))); + if self.rtl { + x -= word_range_width; + } else { + x += word_range_width; + } + if word_range_width > line_width as f32 { // single word is bigger than line_width + vl_range_of_spans.push(current_visual_line); + current_visual_line = Vec::with_capacity(1); + x = start_x; + } } } }