From 00ff5b72f3619133d49d83e4d9caedee44d6f86f Mon Sep 17 00:00:00 2001 From: Hojjat Date: Wed, 22 Feb 2023 18:31:49 -0700 Subject: [PATCH 1/9] Align Left, Right, and Center works --- examples/editor-libcosmic/src/text.rs | 8 ++- src/buffer.rs | 10 +++- src/buffer_line.rs | 27 +++++++++- src/layout.rs | 20 ++++++++ src/shape.rs | 72 +++++++++++++++++---------- 5 files changed, 106 insertions(+), 31 deletions(-) diff --git a/examples/editor-libcosmic/src/text.rs b/examples/editor-libcosmic/src/text.rs index 66a9c81..5986f0e 100644 --- a/examples/editor-libcosmic/src/text.rs +++ b/examples/editor-libcosmic/src/text.rs @@ -100,6 +100,7 @@ where self.metrics.font_size, limits.max().width as i32, self.line.wrap(), + self.line.align(), ); let mut width = 0; @@ -160,7 +161,12 @@ where let shape = self.line.shape_opt().as_ref().unwrap(); //TODO: can we cache this? - let layout_lines = shape.layout(self.metrics.font_size, layout_w, self.line.wrap()); + let layout_lines = shape.layout( + self.metrics.font_size, + layout_w, + self.line.wrap(), + self.line.align(), + ); let mut cache = state.cache.lock().unwrap(); diff --git a/src/buffer.rs b/src/buffer.rs index 7bd6554..c89cd33 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -10,7 +10,9 @@ use unicode_segmentation::UnicodeSegmentation; #[cfg(feature = "swash")] use crate::Color; -use crate::{Attrs, AttrsList, BufferLine, FontSystem, LayoutGlyph, LayoutLine, ShapeLine, Wrap}; +use crate::{ + Align, Attrs, AttrsList, BufferLine, FontSystem, LayoutGlyph, LayoutLine, ShapeLine, Wrap, +}; /// Current cursor location #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] @@ -301,6 +303,7 @@ pub struct Buffer<'a> { /// True if a redraw is requires. Set to false after processing redraw: bool, wrap: Wrap, + align: Align, } impl<'a> Buffer<'a> { @@ -317,6 +320,7 @@ impl<'a> Buffer<'a> { scroll: 0, redraw: false, wrap: Wrap::Word, + align: Align::Center, }; buffer.set_text("", Attrs::new()); buffer @@ -334,6 +338,7 @@ impl<'a> Buffer<'a> { self.metrics.font_size, self.width, self.wrap, + self.align, ); } } @@ -364,6 +369,7 @@ impl<'a> Buffer<'a> { self.metrics.font_size, self.width, self.wrap, + self.align, ); total_layout += layout.len() as i32; } @@ -397,6 +403,7 @@ impl<'a> Buffer<'a> { self.metrics.font_size, self.width, self.wrap, + self.align, ); if line_i == cursor.line { let layout_cursor = self.layout_cursor(&cursor); @@ -482,6 +489,7 @@ impl<'a> Buffer<'a> { self.metrics.font_size, self.width, self.wrap, + self.align, )) } diff --git a/src/buffer_line.rs b/src/buffer_line.rs index 428504c..0151b93 100644 --- a/src/buffer_line.rs +++ b/src/buffer_line.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "std"))] use alloc::{string::String, vec::Vec}; -use crate::{AttrsList, FontSystem, LayoutLine, ShapeLine, Wrap}; +use crate::{Align, AttrsList, FontSystem, LayoutLine, ShapeLine, Wrap}; /// A line (or paragraph) of text that is shaped and laid out pub struct BufferLine { @@ -9,6 +9,7 @@ pub struct BufferLine { text: String, attrs_list: AttrsList, wrap: Wrap, + align: Align, shape_opt: Option, layout_opt: Option>, } @@ -22,6 +23,7 @@ impl BufferLine { text: text.into(), attrs_list, wrap: Wrap::Word, + align: Align::Left, shape_opt: None, layout_opt: None, } @@ -94,6 +96,25 @@ impl BufferLine { } } + /// Get the Text alignment + pub fn align(&self) -> Align { + self.align + } + + /// Set the text alignment + /// + /// Will reset shape and layout if it differs from current alignment. + /// Returns true if the line was reset + pub fn set_align(&mut self, align: Align) -> bool { + if align != self.align { + self.align = align; + self.reset(); + true + } else { + false + } + } + /// Append line at end of this line /// /// The wrap setting of the appended line will be lost @@ -165,11 +186,13 @@ impl BufferLine { font_size: i32, width: i32, wrap: Wrap, + align: Align, ) -> &[LayoutLine] { if self.layout_opt.is_none() { self.wrap = wrap; + self.align = align; let shape = self.shape(font_system); - let layout = shape.layout(font_size, width, wrap); + let layout = shape.layout(font_size, width, wrap, align); self.layout_opt = Some(layout); } self.layout_opt.as_ref().expect("layout not found") diff --git a/src/layout.rs b/src/layout.rs index 2625659..9ed6212 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -80,3 +80,23 @@ impl Display for Wrap { } } } + +/// Align or justify +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Align { + Left, + Right, + Center, + Justified, +} + +impl Display for Align { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Left => write!(f, "Left"), + Self::Right => write!(f, "Right"), + Self::Center => write!(f, "Center"), + Self::Justified => write!(f, "Justified"), + } + } +} diff --git a/src/shape.rs b/src/shape.rs index 561224d..a7c49db 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -9,7 +9,7 @@ use unicode_script::{Script, UnicodeScript}; use unicode_segmentation::UnicodeSegmentation; use crate::fallback::FontFallbackIter; -use crate::{AttrsList, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine, Wrap}; +use crate::{Align, AttrsList, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine, Wrap}; fn shape_fallback( font: &Font, @@ -600,15 +600,28 @@ impl ShapeLine { runs } - pub fn layout(&self, font_size: i32, line_width: i32, wrap: Wrap) -> Vec { + pub fn layout( + &self, + font_size: i32, + line_width: i32, + wrap: Wrap, + align: Align, + ) -> Vec { let mut layout_lines = Vec::with_capacity(1); // This is used to create a visual line for empty lines (e.g. lines with only a ) let mut push_line = true; + #[derive(Default)] + struct VisualLine { + ranges: Vec, + spaces: u32, + w: f32, + } // For each visual line a list of (span index, and range of words in that span) // Note that a BiDi visual line could have multiple spans or parts of them - let mut vl_range_of_spans = Vec::with_capacity(1); + // let mut vl_range_of_spans = Vec::with_capacity(1); + let mut vl_range_of_spans: Vec = Vec::with_capacity(1); 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 }; @@ -618,11 +631,14 @@ impl ShapeLine { // 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 = Vec::with_capacity(1); + // let mut current_visual_line: Vec = Vec::with_capacity(1); + let mut current_visual_line = VisualLine::default(); 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))); + current_visual_line + .ranges + .push((span_index, (0, 0), (span.words.len(), 0))); } } else { let mut fit_x = line_width as f32; @@ -740,27 +756,29 @@ impl ShapeLine { }; if fits { - current_visual_line.push(( + current_visual_line.ranges.push(( span_index, (starting_word, starting_glyph), (ending_word, ending_glyph), )); + current_visual_line.w += word_range_width; if self.rtl { x -= word_range_width; } else { x += word_range_width; } } else { - if !current_visual_line.is_empty() { + if !current_visual_line.ranges.is_empty() { vl_range_of_spans.push(current_visual_line); - current_visual_line = Vec::with_capacity(1); + current_visual_line = VisualLine::default(); x = start_x; } - current_visual_line.push(( + current_visual_line.ranges.push(( span_index, (starting_word, starting_glyph), (ending_word, ending_glyph), )); + current_visual_line.w += word_range_width; if self.rtl { x -= word_range_width; } else { @@ -769,7 +787,7 @@ impl ShapeLine { 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); + current_visual_line = VisualLine::default(); x = start_x; } } @@ -777,23 +795,32 @@ impl ShapeLine { } } - if !current_visual_line.is_empty() { + if !current_visual_line.ranges.is_empty() { vl_range_of_spans.push(current_visual_line); } // Create the LayoutLines using the ranges inside visual lines - for visual_line in &vl_range_of_spans { - let new_order = self.reorder(visual_line); + for visual_line in vl_range_of_spans { + let new_order = self.reorder(&visual_line.ranges); let mut glyphs = Vec::with_capacity(1); x = start_x; y = 0.; + let alignment_correction = match (align, self.rtl) { + (Align::Left, true) => line_width as f32 - visual_line.w, + (Align::Left, false) => 0., + (Align::Right, true) => 0., + (Align::Right, false) => line_width as f32 - visual_line.w, + (Align::Center, _) => (line_width as f32 - visual_line.w) / 2.0, + (Align::Justified, _) => unimplemented!("I need to work on the number of spaces"), + }; if self.rtl { + x -= alignment_correction; for range in new_order.iter().rev() { for ( span_index, (starting_word, starting_glyph), (ending_word, ending_glyph), - ) in visual_line[range.clone()].iter() + ) in visual_line.ranges[range.clone()].iter() { let span = &self.spans[*span_index]; if starting_word == ending_word { @@ -803,13 +830,8 @@ impl ShapeLine { { let x_advance = font_size as f32 * glyph.x_advance; let y_advance = font_size as f32 * glyph.y_advance; - if self.rtl { - x -= x_advance; - } + x -= x_advance; glyphs.push(glyph.layout(font_size, x, y, span.level)); - if !self.rtl { - x += x_advance; - } y += y_advance; } } else { @@ -841,12 +863,13 @@ impl ShapeLine { } } } else { + x += alignment_correction; for range in new_order { for ( span_index, (starting_word, starting_glyph), (ending_word, ending_glyph), - ) in visual_line[range.clone()].iter() + ) in visual_line.ranges[range.clone()].iter() { let span = &self.spans[*span_index]; if starting_word == ending_word { @@ -856,13 +879,8 @@ impl ShapeLine { { let x_advance = font_size as f32 * glyph.x_advance; let y_advance = font_size as f32 * glyph.y_advance; - if self.rtl { - x -= x_advance; - } glyphs.push(glyph.layout(font_size, x, y, span.level)); - if !self.rtl { - x += x_advance; - } + x += x_advance; y += y_advance; } } else { From 59e89bdbaa067624de7a009352824bd6de09029d Mon Sep 17 00:00:00 2001 From: Hojjat Date: Wed, 22 Feb 2023 20:48:57 -0700 Subject: [PATCH 2/9] Justified --- src/buffer.rs | 2 +- src/shape.rs | 96 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 77 insertions(+), 21 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index c89cd33..2b5e1ef 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -320,7 +320,7 @@ impl<'a> Buffer<'a> { scroll: 0, redraw: false, wrap: Wrap::Word, - align: Align::Center, + align: Align::Justified, }; buffer.set_text("", Attrs::new()); buffer diff --git a/src/shape.rs b/src/shape.rs index a7c49db..75a7fa0 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -645,6 +645,7 @@ impl ShapeLine { for (span_index, span) in self.spans.iter().enumerate() { let mut word_ranges = Vec::new(); let mut word_range_width = 0.; + let mut number_of_blanks = 0; // Create the word ranges that fits in a visual line if self.rtl != span.level.is_rtl() { @@ -656,6 +657,9 @@ impl ShapeLine { // fits fit_x -= word_size; word_range_width += word_size; + if word.blank { + number_of_blanks += 1; + } continue; } else if wrap == Wrap::Glyph { for (glyph_i, glyph) in word.glyphs.iter().enumerate().rev() { @@ -669,7 +673,9 @@ impl ShapeLine { (i, glyph_i + 1), fitting_start, word_range_width, + number_of_blanks, )); + number_of_blanks = 0; fit_x = line_width as f32 - glyph_size; word_range_width = glyph_size; fitting_start = (i, glyph_i + 1); @@ -677,8 +683,16 @@ impl ShapeLine { } } else { // Wrap::Word - word_ranges.push(((i + 1, 0), fitting_start, word_range_width)); - + if word.blank && number_of_blanks > 0 { + number_of_blanks -= 1; + } + word_ranges.push(( + (i + 1, 0), + fitting_start, + word_range_width, + number_of_blanks, + )); + number_of_blanks = 0; if word.blank { fit_x = line_width as f32; word_range_width = 0.; @@ -690,7 +704,7 @@ impl ShapeLine { } } } - word_ranges.push(((0, 0), fitting_start, word_range_width)); + word_ranges.push(((0, 0), fitting_start, word_range_width, number_of_blanks)); } else { // congruent direction let mut fitting_start = (0, 0); @@ -700,6 +714,9 @@ impl ShapeLine { // fits fit_x -= word_size; word_range_width += word_size; + if word.blank { + number_of_blanks += 1; + } continue; } else if wrap == Wrap::Glyph { for (glyph_i, glyph) in word.glyphs.iter().enumerate() { @@ -713,7 +730,9 @@ impl ShapeLine { fitting_start, (i, glyph_i), word_range_width, + number_of_blanks, )); + number_of_blanks = 0; fit_x = line_width as f32 - glyph_size; word_range_width = glyph_size; fitting_start = (i, glyph_i); @@ -721,7 +740,16 @@ impl ShapeLine { } } else { // Wrap::Word - word_ranges.push((fitting_start, (i, 0), word_range_width)); + if word.blank && number_of_blanks > 0 { + number_of_blanks -= 1; + } + word_ranges.push(( + fitting_start, + (i, 0), + word_range_width, + number_of_blanks, + )); + number_of_blanks = 0; if word.blank { fit_x = line_width as f32; @@ -734,7 +762,12 @@ 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, + number_of_blanks, + )); } // Create a visual line @@ -742,6 +775,7 @@ impl ShapeLine { (starting_word, starting_glyph), (ending_word, ending_glyph), word_range_width, + number_of_blanks, ) in word_ranges { // To simplify the algorithm above, we might push empty ranges but we ignore them here @@ -762,6 +796,7 @@ impl ShapeLine { (ending_word, ending_glyph), )); current_visual_line.w += word_range_width; + current_visual_line.spaces += number_of_blanks; if self.rtl { x -= word_range_width; } else { @@ -779,6 +814,7 @@ impl ShapeLine { (ending_word, ending_glyph), )); current_visual_line.w += word_range_width; + current_visual_line.spaces += number_of_blanks; if self.rtl { x -= word_range_width; } else { @@ -800,7 +836,8 @@ impl ShapeLine { } // Create the LayoutLines using the ranges inside visual lines - for visual_line in vl_range_of_spans { + let number_of_visual_lines = vl_range_of_spans.len(); + for (index, visual_line) in vl_range_of_spans.iter().enumerate() { let new_order = self.reorder(&visual_line.ranges); let mut glyphs = Vec::with_capacity(1); x = start_x; @@ -811,10 +848,19 @@ impl ShapeLine { (Align::Right, true) => 0., (Align::Right, false) => line_width as f32 - visual_line.w, (Align::Center, _) => (line_width as f32 - visual_line.w) / 2.0, - (Align::Justified, _) => unimplemented!("I need to work on the number of spaces"), + (Align::Justified, _) => { + // Don't justify the last line in a paragraph. + if visual_line.spaces > 0 && index != number_of_visual_lines - 1 { + (line_width as f32 - visual_line.w) / visual_line.spaces as f32 + } else { + 0. + } + } }; if self.rtl { - x -= alignment_correction; + if align != Align::Justified { + x -= alignment_correction; + } for range in new_order.iter().rev() { for ( span_index, @@ -824,6 +870,7 @@ impl ShapeLine { { let span = &self.spans[*span_index]; if starting_word == ending_word { + let word_blank = span.words[*starting_word].blank; for glyph in span.words[*starting_word].glyphs [*starting_glyph..*ending_glyph] .iter() @@ -831,6 +878,9 @@ impl ShapeLine { let x_advance = font_size as f32 * glyph.x_advance; let y_advance = font_size as f32 * glyph.y_advance; x -= x_advance; + if word_blank && align == Align::Justified { + x -= alignment_correction; + } glyphs.push(glyph.layout(font_size, x, y, span.level)); y += y_advance; } @@ -845,16 +895,15 @@ impl ShapeLine { (0, word.glyphs.len()) }; + let word_blank = word.blank; for glyph in &word.glyphs[g1..g2] { let x_advance = font_size as f32 * glyph.x_advance; let y_advance = font_size as f32 * glyph.y_advance; - if self.rtl { - x -= x_advance; + if word_blank && align == Align::Justified { + x -= alignment_correction; } + x -= x_advance; glyphs.push(glyph.layout(font_size, x, y, span.level)); - if !self.rtl { - x += x_advance; - } y += y_advance; } } @@ -862,8 +911,12 @@ impl ShapeLine { } } } - } else { - x += alignment_correction; + } else + /* LTR */ + { + if align != Align::Justified { + x += alignment_correction; + } for range in new_order { for ( span_index, @@ -873,6 +926,7 @@ impl ShapeLine { { let span = &self.spans[*span_index]; if starting_word == ending_word { + let word_blank = span.words[*starting_word].blank; for glyph in span.words[*starting_word].glyphs [*starting_glyph..*ending_glyph] .iter() @@ -881,6 +935,9 @@ impl ShapeLine { let y_advance = font_size as f32 * glyph.y_advance; glyphs.push(glyph.layout(font_size, x, y, span.level)); x += x_advance; + if word_blank && align == Align::Justified { + x += alignment_correction; + } y += y_advance; } } else { @@ -894,16 +951,15 @@ impl ShapeLine { (0, word.glyphs.len()) }; + let word_blank = word.blank; for glyph in &word.glyphs[g1..g2] { let x_advance = font_size as f32 * glyph.x_advance; let y_advance = font_size as f32 * glyph.y_advance; - if self.rtl { - x -= x_advance; - } glyphs.push(glyph.layout(font_size, x, y, span.level)); - if !self.rtl { - x += x_advance; + if word_blank && align == Align::Justified { + x += alignment_correction; } + x += x_advance; y += y_advance; } } From 244242276245295bb0c62b8fa75e4c85dd9d7182 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Wed, 22 Feb 2023 21:59:03 -0700 Subject: [PATCH 3/9] Add justification buttons to editor-libcosmic --- examples/editor-libcosmic/Cargo.toml | 2 +- examples/editor-libcosmic/src/main.rs | 23 ++++++++++++++++++++++- examples/editor-libcosmic/src/text.rs | 3 ++- examples/editor-libcosmic/src/text_box.rs | 5 +++-- src/buffer.rs | 14 ++++++++++++++ 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/examples/editor-libcosmic/Cargo.toml b/examples/editor-libcosmic/Cargo.toml index 2c8082f..3db022c 100644 --- a/examples/editor-libcosmic/Cargo.toml +++ b/examples/editor-libcosmic/Cargo.toml @@ -15,7 +15,7 @@ log = "0.4" [dependencies.libcosmic] git = "https://github.com/pop-os/libcosmic" -rev = "5fe44511" +rev = "2dde95ee" default-features = false features = ["wgpu", "winit"] #path = "../../../libcosmic" diff --git a/examples/editor-libcosmic/src/main.rs b/examples/editor-libcosmic/src/main.rs index 250bba7..f90bd52 100644 --- a/examples/editor-libcosmic/src/main.rs +++ b/examples/editor-libcosmic/src/main.rs @@ -12,7 +12,7 @@ use cosmic::{ Element, }; use cosmic_text::{ - Attrs, AttrsList, Buffer, Edit, FontSystem, Metrics, SyntaxEditor, SyntaxSystem, Wrap, + Align, Attrs, AttrsList, Buffer, Edit, FontSystem, Metrics, SyntaxEditor, SyntaxSystem, Wrap, }; use std::{env, fs, path::PathBuf, sync::Mutex}; @@ -66,6 +66,7 @@ pub enum Message { Monospaced(bool), MetricsChanged(Metrics), WrapChanged(Wrap), + AlignmentChanged(Align), ThemeChanged(&'static str), } @@ -202,6 +203,10 @@ impl Application for Window { let mut editor = self.editor.lock().unwrap(); editor.buffer_mut().set_wrap(wrap); } + Message::AlignmentChanged(align) => { + let mut editor = self.editor.lock().unwrap(); + editor.buffer_mut().set_align(align); + } Message::ThemeChanged(theme) => { self.theme = match theme { "Dark" => Theme::Dark, @@ -282,8 +287,24 @@ impl Application for Window { theme_picker, text("Font Size:"), font_size_picker, + ] + .align_items(Alignment::Center) + .spacing(8), + row![ text("Wrap:"), wrap_picker, + button(theme::Button::Text) + .icon(theme::Svg::Default, "format-justify-left", 20) + .on_press(Message::AlignmentChanged(Align::Left)), + button(theme::Button::Text) + .icon(theme::Svg::Symbolic, "format-justify-center", 20) + .on_press(Message::AlignmentChanged(Align::Center)), + button(theme::Button::Text) + .icon(theme::Svg::Symbolic, "format-justify-right", 20) + .on_press(Message::AlignmentChanged(Align::Right)), + button(theme::Button::Text) + .icon(theme::Svg::SymbolicLink, "format-justify-fill", 20) + .on_press(Message::AlignmentChanged(Align::Justified)), ] .align_items(Alignment::Center) .spacing(8), diff --git a/examples/editor-libcosmic/src/text.rs b/examples/editor-libcosmic/src/text.rs index 5986f0e..4b487d8 100644 --- a/examples/editor-libcosmic/src/text.rs +++ b/examples/editor-libcosmic/src/text.rs @@ -8,6 +8,7 @@ use cosmic::{ widget::{self, tree, Widget}, {Color, Element, Length, Point, Rectangle, Size}, }, + iced_winit::renderer::BorderRadius, theme::Theme, }; use cosmic_text::{Attrs, AttrsList, BufferLine, Metrics, SwashCache}; @@ -140,7 +141,7 @@ where renderer.fill_quad( renderer::Quad { bounds: layout.bounds(), - border_radius: 0.0, + border_radius: BorderRadius::default(), border_width: 0.0, border_color: Color::TRANSPARENT, }, diff --git a/examples/editor-libcosmic/src/text_box.rs b/examples/editor-libcosmic/src/text_box.rs index 1faeff8..8a6b6fc 100644 --- a/examples/editor-libcosmic/src/text_box.rs +++ b/examples/editor-libcosmic/src/text_box.rs @@ -13,6 +13,7 @@ use cosmic::{ widget::{self, tree, Widget}, Padding, {Color, Element, Length, Point, Rectangle, Shell, Size}, }, + iced_winit::renderer::BorderRadius, theme::Theme, }; use cosmic_text::{Action, Edit, SwashCache}; @@ -142,7 +143,7 @@ where renderer.fill_quad( renderer::Quad { bounds: layout.bounds(), - border_radius: 0.0, + border_radius: BorderRadius::default(), border_width: 0.0, border_color: Color::TRANSPARENT, }, @@ -190,7 +191,7 @@ where + [self.padding.left as f32, self.padding.top as f32].into(), Size::new(w as f32, h as f32), ), - border_radius: 0.0, + border_radius: BorderRadius::default(), border_width: 0.0, border_color: Color::TRANSPARENT, }, diff --git a/src/buffer.rs b/src/buffer.rs index 2b5e1ef..ba189f1 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -522,6 +522,20 @@ impl<'a> Buffer<'a> { } } + /// Get the current [`Align`] + pub fn align(&self) -> Align { + self.align + } + + /// Set the current [`Wrap`] + pub fn set_align(&mut self, align: Align) { + if align != self.align { + self.align = align; + self.relayout(); + self.shape_until_scroll(); + } + } + /// Get the current buffer dimensions (width, height) pub fn size(&self) -> (i32, i32) { (self.width, self.height) From 27d52a12e973ec574c1d09643cdc2c16291a8097 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Thu, 23 Feb 2023 13:26:21 -0700 Subject: [PATCH 4/9] Fix the trailing space --- src/shape.rs | 62 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/src/shape.rs b/src/shape.rs index 75a7fa0..03370fc 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -683,15 +683,34 @@ impl ShapeLine { } } else { // Wrap::Word + let mut prev_word_width = None; if word.blank && number_of_blanks > 0 { + // current word causing a wrap is a space so we ignore it number_of_blanks -= 1; + } else if let Some(previous_word) = span.words.get(i - 1) { + // Current word causing a wrap is not whitespace, so we ignore the + // previous word if it's a whitespace + if previous_word.blank { + number_of_blanks -= 1; + prev_word_width = + Some(previous_word.x_advance * font_size as f32) + } + } + if let Some(width) = prev_word_width { + word_ranges.push(( + (i, 0), + fitting_start, + word_range_width - width, + number_of_blanks, + )); + } else { + word_ranges.push(( + (i + 1, 0), + fitting_start, + word_range_width, + number_of_blanks, + )); } - word_ranges.push(( - (i + 1, 0), - fitting_start, - word_range_width, - number_of_blanks, - )); number_of_blanks = 0; if word.blank { fit_x = line_width as f32; @@ -740,15 +759,34 @@ impl ShapeLine { } } else { // Wrap::Word + let mut prev_word_width = None; if word.blank && number_of_blanks > 0 { + // current word causing a wrap is a space so we ignore it number_of_blanks -= 1; + } else if let Some(previous_word) = span.words.get(i - 1) { + // Current word causing a wrap is not whitespace, so we ignore the + // previous word if it's a whitespace + if previous_word.blank { + number_of_blanks -= 1; + prev_word_width = + Some(previous_word.x_advance * font_size as f32) + } + } + if let Some(width) = prev_word_width { + word_ranges.push(( + fitting_start, + (i - 1, 0), + word_range_width - width, + number_of_blanks, + )); + } else { + word_ranges.push(( + fitting_start, + (i, 0), + word_range_width, + number_of_blanks, + )); } - word_ranges.push(( - fitting_start, - (i, 0), - word_range_width, - number_of_blanks, - )); number_of_blanks = 0; if word.blank { From 4e7e1cc79ec777772a261e5810b84dd7be4e53e3 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Thu, 23 Feb 2023 13:45:34 -0700 Subject: [PATCH 5/9] Alignment is based on text direction by default now --- src/buffer.rs | 10 +++++----- src/buffer_line.rs | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index ba189f1..81f3997 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -303,7 +303,7 @@ pub struct Buffer<'a> { /// True if a redraw is requires. Set to false after processing redraw: bool, wrap: Wrap, - align: Align, + align: Option, } impl<'a> Buffer<'a> { @@ -320,7 +320,7 @@ impl<'a> Buffer<'a> { scroll: 0, redraw: false, wrap: Wrap::Word, - align: Align::Justified, + align: None, }; buffer.set_text("", Attrs::new()); buffer @@ -523,14 +523,14 @@ impl<'a> Buffer<'a> { } /// Get the current [`Align`] - pub fn align(&self) -> Align { + pub fn align(&self) -> Option { self.align } /// Set the current [`Wrap`] pub fn set_align(&mut self, align: Align) { - if align != self.align { - self.align = align; + if Some(align) != self.align { + self.align = Some(align); self.relayout(); self.shape_until_scroll(); } diff --git a/src/buffer_line.rs b/src/buffer_line.rs index 0151b93..2a5ff3b 100644 --- a/src/buffer_line.rs +++ b/src/buffer_line.rs @@ -9,7 +9,7 @@ pub struct BufferLine { text: String, attrs_list: AttrsList, wrap: Wrap, - align: Align, + align: Option, shape_opt: Option, layout_opt: Option>, } @@ -23,7 +23,7 @@ impl BufferLine { text: text.into(), attrs_list, wrap: Wrap::Word, - align: Align::Left, + align: None, shape_opt: None, layout_opt: None, } @@ -97,7 +97,7 @@ impl BufferLine { } /// Get the Text alignment - pub fn align(&self) -> Align { + pub fn align(&self) -> Option { self.align } @@ -106,8 +106,8 @@ impl BufferLine { /// Will reset shape and layout if it differs from current alignment. /// Returns true if the line was reset pub fn set_align(&mut self, align: Align) -> bool { - if align != self.align { - self.align = align; + if Some(align) != self.align { + self.align = Some(align); self.reset(); true } else { @@ -186,7 +186,7 @@ impl BufferLine { font_size: i32, width: i32, wrap: Wrap, - align: Align, + align: Option, ) -> &[LayoutLine] { if self.layout_opt.is_none() { self.wrap = wrap; From d313713f44241c214cf4e14447e85f29448cc525 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Thu, 23 Feb 2023 14:23:56 -0700 Subject: [PATCH 6/9] Removed alignment from Buffer, added alignment per Bufferline to editor-libcosmic --- examples/editor-libcosmic/src/main.rs | 9 ++++++++- src/buffer.rs | 24 +----------------------- src/buffer_line.rs | 7 +++---- src/shape.rs | 19 +++++++++++++------ 4 files changed, 25 insertions(+), 34 deletions(-) diff --git a/examples/editor-libcosmic/src/main.rs b/examples/editor-libcosmic/src/main.rs index f90bd52..0bc178d 100644 --- a/examples/editor-libcosmic/src/main.rs +++ b/examples/editor-libcosmic/src/main.rs @@ -205,7 +205,7 @@ impl Application for Window { } Message::AlignmentChanged(align) => { let mut editor = self.editor.lock().unwrap(); - editor.buffer_mut().set_align(align); + update_alignment(&mut *editor, align); } Message::ThemeChanged(theme) => { self.theme = match theme { @@ -324,3 +324,10 @@ fn update_attrs<'a, T: Edit<'a>>(editor: &mut T, attrs: Attrs<'a>) { line.set_attrs_list(AttrsList::new(attrs)); }); } + +fn update_alignment<'a, T: Edit<'a>>(editor: &mut T, align: Align) { + let current_line = editor.cursor().line; + if let Some(line) = editor.buffer_mut().lines.get_mut(current_line) { + line.set_align(align); + } +} diff --git a/src/buffer.rs b/src/buffer.rs index 81f3997..7bd6554 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -10,9 +10,7 @@ use unicode_segmentation::UnicodeSegmentation; #[cfg(feature = "swash")] use crate::Color; -use crate::{ - Align, Attrs, AttrsList, BufferLine, FontSystem, LayoutGlyph, LayoutLine, ShapeLine, Wrap, -}; +use crate::{Attrs, AttrsList, BufferLine, FontSystem, LayoutGlyph, LayoutLine, ShapeLine, Wrap}; /// Current cursor location #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] @@ -303,7 +301,6 @@ pub struct Buffer<'a> { /// True if a redraw is requires. Set to false after processing redraw: bool, wrap: Wrap, - align: Option, } impl<'a> Buffer<'a> { @@ -320,7 +317,6 @@ impl<'a> Buffer<'a> { scroll: 0, redraw: false, wrap: Wrap::Word, - align: None, }; buffer.set_text("", Attrs::new()); buffer @@ -338,7 +334,6 @@ impl<'a> Buffer<'a> { self.metrics.font_size, self.width, self.wrap, - self.align, ); } } @@ -369,7 +364,6 @@ impl<'a> Buffer<'a> { self.metrics.font_size, self.width, self.wrap, - self.align, ); total_layout += layout.len() as i32; } @@ -403,7 +397,6 @@ impl<'a> Buffer<'a> { self.metrics.font_size, self.width, self.wrap, - self.align, ); if line_i == cursor.line { let layout_cursor = self.layout_cursor(&cursor); @@ -489,7 +482,6 @@ impl<'a> Buffer<'a> { self.metrics.font_size, self.width, self.wrap, - self.align, )) } @@ -522,20 +514,6 @@ impl<'a> Buffer<'a> { } } - /// Get the current [`Align`] - pub fn align(&self) -> Option { - self.align - } - - /// Set the current [`Wrap`] - pub fn set_align(&mut self, align: Align) { - if Some(align) != self.align { - self.align = Some(align); - 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 2a5ff3b..57acdac 100644 --- a/src/buffer_line.rs +++ b/src/buffer_line.rs @@ -89,7 +89,7 @@ impl BufferLine { pub fn set_wrap(&mut self, wrap: Wrap) -> bool { if wrap != self.wrap { self.wrap = wrap; - self.reset(); + self.reset_layout(); true } else { false @@ -108,7 +108,7 @@ impl BufferLine { pub fn set_align(&mut self, align: Align) -> bool { if Some(align) != self.align { self.align = Some(align); - self.reset(); + self.reset_layout(); true } else { false @@ -186,11 +186,10 @@ impl BufferLine { font_size: i32, width: i32, wrap: Wrap, - align: Option, ) -> &[LayoutLine] { if self.layout_opt.is_none() { self.wrap = wrap; - self.align = align; + let align = self.align; let shape = self.shape(font_system); let layout = shape.layout(font_size, width, wrap, align); self.layout_opt = Some(layout); diff --git a/src/shape.rs b/src/shape.rs index 03370fc..41e60f5 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -605,10 +605,18 @@ impl ShapeLine { font_size: i32, line_width: i32, wrap: Wrap, - align: Align, + align: Option, ) -> Vec { let mut layout_lines = Vec::with_capacity(1); + let align = align.unwrap_or({ + if self.rtl { + Align::Right + } else { + Align::Left + } + }); + // This is used to create a visual line for empty lines (e.g. lines with only a ) let mut push_line = true; @@ -693,7 +701,7 @@ impl ShapeLine { if previous_word.blank { number_of_blanks -= 1; prev_word_width = - Some(previous_word.x_advance * font_size as f32) + Some(previous_word.x_advance * font_size as f32); } } if let Some(width) = prev_word_width { @@ -769,7 +777,7 @@ impl ShapeLine { if previous_word.blank { number_of_blanks -= 1; prev_word_width = - Some(previous_word.x_advance * font_size as f32) + Some(previous_word.x_advance * font_size as f32); } } if let Some(width) = prev_word_width { @@ -949,9 +957,8 @@ impl ShapeLine { } } } - } else - /* LTR */ - { + } else { + /* LTR */ if align != Align::Justified { x += alignment_correction; } From 45f6474a74a25517835c57869e5ba3fc915eb84d Mon Sep 17 00:00:00 2001 From: Hojjat Date: Thu, 23 Feb 2023 18:25:41 -0700 Subject: [PATCH 7/9] Apply alignment to selection in editor-libcosmic --- examples/editor-libcosmic/src/main.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/editor-libcosmic/src/main.rs b/examples/editor-libcosmic/src/main.rs index 0bc178d..c3abaef 100644 --- a/examples/editor-libcosmic/src/main.rs +++ b/examples/editor-libcosmic/src/main.rs @@ -327,7 +327,16 @@ fn update_attrs<'a, T: Edit<'a>>(editor: &mut T, attrs: Attrs<'a>) { fn update_alignment<'a, T: Edit<'a>>(editor: &mut T, align: Align) { let current_line = editor.cursor().line; - if let Some(line) = editor.buffer_mut().lines.get_mut(current_line) { + if let Some(select) = editor.select_opt() { + let (start, end) = match select.line.cmp(¤t_line) { + std::cmp::Ordering::Greater => (current_line, select.line), + std::cmp::Ordering::Less => (select.line, current_line), + std::cmp::Ordering::Equal => (current_line, current_line), + }; + for line in editor.buffer_mut().lines[start..=end].iter_mut() { + line.set_align(align); + } + } else if let Some(line) = editor.buffer_mut().lines.get_mut(current_line) { line.set_align(align); } } From fff8389bbfe1a52b42b6840a2848dbd9dd75f4c3 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Fri, 24 Feb 2023 08:39:24 -0700 Subject: [PATCH 8/9] Allow set_align to clear the alignment by setting it to None --- examples/editor-libcosmic/src/main.rs | 4 ++-- src/buffer_line.rs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/editor-libcosmic/src/main.rs b/examples/editor-libcosmic/src/main.rs index c3abaef..4222359 100644 --- a/examples/editor-libcosmic/src/main.rs +++ b/examples/editor-libcosmic/src/main.rs @@ -334,9 +334,9 @@ fn update_alignment<'a, T: Edit<'a>>(editor: &mut T, align: Align) { std::cmp::Ordering::Equal => (current_line, current_line), }; for line in editor.buffer_mut().lines[start..=end].iter_mut() { - line.set_align(align); + line.set_align(Some(align)); } } else if let Some(line) = editor.buffer_mut().lines.get_mut(current_line) { - line.set_align(align); + line.set_align(Some(align)); } } diff --git a/src/buffer_line.rs b/src/buffer_line.rs index 57acdac..c1f5b92 100644 --- a/src/buffer_line.rs +++ b/src/buffer_line.rs @@ -104,10 +104,11 @@ impl BufferLine { /// Set the text alignment /// /// Will reset shape and layout if it differs from current alignment. + /// Setting to None will use `Align::Right` for RTL lines, and `Align::Left` for LTR lines. /// Returns true if the line was reset - pub fn set_align(&mut self, align: Align) -> bool { - if Some(align) != self.align { - self.align = Some(align); + pub fn set_align(&mut self, align: Option) -> bool { + if align != self.align { + self.align = align; self.reset_layout(); true } else { From 260748afe7e71b352ed81e9089e24ee2243639b7 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Fri, 24 Feb 2023 10:07:23 -0700 Subject: [PATCH 9/9] Make sure the lines exists before applying the alignment --- examples/editor-libcosmic/src/main.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/editor-libcosmic/src/main.rs b/examples/editor-libcosmic/src/main.rs index 4222359..974e97f 100644 --- a/examples/editor-libcosmic/src/main.rs +++ b/examples/editor-libcosmic/src/main.rs @@ -333,9 +333,11 @@ fn update_alignment<'a, T: Edit<'a>>(editor: &mut T, align: Align) { std::cmp::Ordering::Less => (select.line, current_line), std::cmp::Ordering::Equal => (current_line, current_line), }; - for line in editor.buffer_mut().lines[start..=end].iter_mut() { - line.set_align(Some(align)); - } + editor.buffer_mut().lines.get_mut(start..=end).map(|lines| { + for line in lines.iter_mut() { + line.set_align(Some(align)); + } + }); } else if let Some(line) = editor.buffer_mut().lines.get_mut(current_line) { line.set_align(Some(align)); }