Align Left, Right, and Center works
This commit is contained in:
parent
9a4d067f9d
commit
00ff5b72f3
5 changed files with 106 additions and 31 deletions
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ShapeLine>,
|
||||
layout_opt: Option<Vec<LayoutLine>>,
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
72
src/shape.rs
72
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<LayoutLine> {
|
||||
pub fn layout(
|
||||
&self,
|
||||
font_size: i32,
|
||||
line_width: i32,
|
||||
wrap: Wrap,
|
||||
align: Align,
|
||||
) -> Vec<LayoutLine> {
|
||||
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 <CR>)
|
||||
let mut push_line = true;
|
||||
|
||||
#[derive(Default)]
|
||||
struct VisualLine {
|
||||
ranges: Vec<VlRange>,
|
||||
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<VisualLine> = 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<VlRange> = Vec::with_capacity(1);
|
||||
// let mut current_visual_line: Vec<VlRange> = 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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue