diff --git a/examples/plugin.rs b/examples/plugin.rs index 5822c9d6..ce4b6c0c 100644 --- a/examples/plugin.rs +++ b/examples/plugin.rs @@ -89,7 +89,7 @@ where Some(Token::Word(word)) } - Some(Token::Break(_, _)) => { + Some(Token::Break(_)) => { self.measured += 1; token } @@ -115,7 +115,7 @@ where Some(Token::Whitespace(to_render, s.first_n_chars(to_render))) } Token::Word(s) => Some(Token::Word(s.first_n_chars(to_render))), - Token::Break(repl, orig) => Some(Token::Break(repl.first_n_chars(to_render), orig)), + Token::Break(repl) => Some(Token::Break(repl.first_n_chars(to_render))), _ => Some(token), } } diff --git a/src/alignment/mod.rs b/src/alignment/mod.rs index db2642f4..5f1441fc 100644 --- a/src/alignment/mod.rs +++ b/src/alignment/mod.rs @@ -31,14 +31,14 @@ impl HorizontalAlignment { self, renderer: &impl TextRenderer, measurement: LineMeasurement, - ) -> (u32, SpaceConfig) { + ) -> (i32, SpaceConfig) { let space_width = str_width(renderer, " "); let space_config = SpaceConfig::new(space_width, None); let remaining_space = measurement.max_line_width - measurement.width; match self { HorizontalAlignment::Left => (0, space_config), - HorizontalAlignment::Center => ((remaining_space + 1) / 2, space_config), - HorizontalAlignment::Right => (remaining_space, space_config), + HorizontalAlignment::Center => ((remaining_space as i32 + 1) / 2, space_config), + HorizontalAlignment::Right => (remaining_space as i32, space_config), HorizontalAlignment::Justified => { let space_count = measurement.space_count; let space_info = if !measurement.last_line() && space_count != 0 { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c5f525e8..b8a23d6f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -56,8 +56,8 @@ pub enum Token<'a, C> { /// A word (a sequence of non-whitespace characters). Word(&'a str), - /// A possible wrapping point - Break(&'a str, &'a str), + /// A possible wrapping point. Contains the separator character(s). + Break(&'a str), /// Change of text style. ChangeTextStyle(ChangeTextStyle), @@ -170,11 +170,6 @@ where })), SPEC_CHAR_SHY => Some(Token::Break( "-", // translate SHY to a printable character - unsafe { - // SAFETY: we only work with character boundaries and - // offset is <= length - string.get_unchecked(0..c.len_utf8()) - }, )), // count consecutive whitespace @@ -232,7 +227,7 @@ mod test { Token::Word("sit"), Token::Whitespace(1, " "), Token::Word("am"), - Token::Break("-", "\u{ad}"), + Token::Break("-"), Token::Word("et,"), Token::Tab, Token::Word("conse😅ctetur"), @@ -280,11 +275,7 @@ mod test { fn parse_shy_issue_42() { assert_tokens( "foo\u{AD}bar", - vec![ - Token::Word("foo"), - Token::Break("-", "\u{ad}"), - Token::Word("bar"), - ], + vec![Token::Word("foo"), Token::Break("-"), Token::Word("bar")], ); } } diff --git a/src/plugin/ansi/mod.rs b/src/plugin/ansi/mod.rs index a9ba9f8a..8333911e 100644 --- a/src/plugin/ansi/mod.rs +++ b/src/plugin/ansi/mod.rs @@ -329,7 +329,7 @@ mod test { let parser = Parser::parse("foo\x1b[2Dsample"); - let character_style = MonoTextStyleBuilder::new() + let text_renderer = MonoTextStyleBuilder::new() .font(&FONT_6X9) .text_color(BinaryColor::On) .background_color(BinaryColor::Off) @@ -339,20 +339,23 @@ mod test { let cursor = LineCursor::new( size_for(&FONT_6X9, 7, 1).width, - TabSize::Spaces(4).into_pixels(&character_style), + TabSize::Spaces(4).into_pixels(&text_renderer), ); let plugin = PluginWrapper::new(Ansi::new()); - let state = LineRenderState { + let mut state = LineRenderState { parser, - character_style, - style: &style, + text_renderer, end_type: LineEndType::EndOfText, plugin: &plugin, }; - StyledLineRenderer::new(cursor, state) - .draw(&mut display) - .unwrap(); + StyledLineRenderer { + cursor, + state: &mut state, + style: &style, + } + .draw(&mut display) + .unwrap(); display.assert_pattern(&[ "..........................................", diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index ac80684f..af13bebd 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -90,7 +90,7 @@ impl<'a, M: Clone, C: Clone> Clone for PluginWrapper<'a, M, C> { Self { inner: UnsafeCell::new(self.with(|this| PluginInner { plugin: this.plugin.clone(), - state: this.state.clone(), + state: this.state, peeked_token: unsafe { addr_of!(this.peeked_token).read() }, })), } diff --git a/src/rendering/cursor.rs b/src/rendering/cursor.rs index 89701a8b..a332c820 100644 --- a/src/rendering/cursor.rs +++ b/src/rendering/cursor.rs @@ -1,7 +1,7 @@ //! Cursor to track rendering position. -use embedded_graphics::{geometry::Point, prelude::Size, primitives::Rectangle, text::LineHeight}; +use embedded_graphics::{geometry::Point, primitives::Rectangle, text::LineHeight}; -use az::{SaturatingAs, SaturatingCast}; +use az::SaturatingAs; /// Tracks position within a line. #[derive(Debug, Clone)] @@ -60,10 +60,10 @@ impl LineCursor { self.position -= abs; Ok(by) } else { - Err(-self.position.saturating_as::()) + Err(-(self.position as i32)) } } else { - let space = self.space().saturating_cast(); + let space = self.space() as i32; if by <= space { // Here we know by > 0, cast is safe self.position += by as u32; @@ -73,6 +73,18 @@ impl LineCursor { } } } + + /// Moves the cursor forward by a given amount. + pub fn move_cursor_forward(&mut self, by: u32) -> Result { + let space = self.space(); + if by <= space { + // Here we know by > 0, cast is safe + self.position += by; + Ok(by) + } else { + Err(space) + } + } } /// Internal structure that keeps track of position information while rendering a [`TextBox`]. @@ -83,10 +95,11 @@ pub struct Cursor { /// Current cursor position pub y: i32, - /// TextBox bounding rectangle - bounds: Rectangle, + top_left: Point, + bottom: i32, - line_height: i32, + line_width: u32, + line_height: u32, line_spacing: i32, tab_width: u32, } @@ -103,9 +116,14 @@ impl Cursor { ) -> Self { Self { y: bounds.top_left.y, - line_height: base_line_height.saturating_as(), + + top_left: bounds.top_left, + bottom: bounds.top_left.y + bounds.size.height.saturating_as::() + - base_line_height.saturating_as::(), + + line_width: bounds.size.width, + line_height: base_line_height, line_spacing: line_height.to_absolute(base_line_height).saturating_as(), - bounds: Rectangle::new(bounds.top_left, bounds.size + Size::new(0, 1)), tab_width, } } @@ -113,34 +131,40 @@ impl Cursor { #[must_use] pub(crate) fn line(&self) -> LineCursor { LineCursor { - start: Point::new(self.bounds.top_left.x, self.y), - width: self.bounds.size.width, + start: self.line_start(), + width: self.line_width, position: 0, tab_width: self.tab_width, } } - /// Returns the coordinates of the bottom right corner. + /// Returns the coordinates of the start of the current line. + #[inline] + pub(crate) fn line_start(&self) -> Point { + Point::new(self.top_left.x, self.y) + } + + /// Returns the vertical offset of the bottom of the last visible line. #[inline] - pub fn bottom_right(&self) -> Point { - self.bounds.bottom_right().unwrap_or(self.bounds.top_left) + pub fn bottom(&self) -> i32 { + self.bottom } - /// Returns the coordinates of the bottom right corner. + /// Returns the coordinates of the top left corner. #[inline] pub fn top_left(&self) -> Point { - self.bounds.top_left + self.top_left } /// Returns the width of the text box. #[inline] pub fn line_width(&self) -> u32 { - self.bounds.size.width + self.line_width } /// Returns the height of a line. #[inline] - pub fn line_height(&self) -> i32 { + pub fn line_height(&self) -> u32 { self.line_height } @@ -159,6 +183,6 @@ impl Cursor { #[inline] #[must_use] pub fn in_display_area(&self) -> bool { - self.bounds.top_left.y <= self.y && self.y + self.line_height <= self.bottom_right().y + self.top_left.y <= self.y && self.y <= self.bottom } } diff --git a/src/rendering/line.rs b/src/rendering/line.rs index 698b4de5..7ad0daa2 100644 --- a/src/rendering/line.rs +++ b/src/rendering/line.rs @@ -10,7 +10,6 @@ use crate::{ style::TextBoxStyle, utils::str_width, }; -use az::SaturatingAs; use embedded_graphics::{ draw_target::DrawTarget, geometry::Point, @@ -21,37 +20,37 @@ use embedded_graphics::{ renderer::{CharacterStyle, TextRenderer}, Baseline, DecorationColor, }, - Drawable, }; impl ChangeTextStyle where C: PixelColor + From, { - pub(crate) fn apply>(self, style: &mut S) { + pub(crate) fn apply>(self, text_renderer: &mut S) { match self { ChangeTextStyle::Reset => { - style.set_text_color(Some(Into::::into(BinaryColor::On).into())); - style.set_background_color(None); - style.set_underline_color(DecorationColor::None); - style.set_strikethrough_color(DecorationColor::None); + text_renderer.set_text_color(Some(Into::::into(BinaryColor::On).into())); + text_renderer.set_background_color(None); + text_renderer.set_underline_color(DecorationColor::None); + text_renderer.set_strikethrough_color(DecorationColor::None); } - ChangeTextStyle::TextColor(color) => style.set_text_color(color), - ChangeTextStyle::BackgroundColor(color) => style.set_background_color(color), - ChangeTextStyle::Underline(color) => style.set_underline_color(color), - ChangeTextStyle::Strikethrough(color) => style.set_strikethrough_color(color), + ChangeTextStyle::TextColor(color) => text_renderer.set_text_color(color), + ChangeTextStyle::BackgroundColor(color) => text_renderer.set_background_color(color), + ChangeTextStyle::Underline(color) => text_renderer.set_underline_color(color), + ChangeTextStyle::Strikethrough(color) => text_renderer.set_strikethrough_color(color), } } } /// Render a single line of styled text. -pub(crate) struct StyledLineRenderer<'a, 'b, S, M> +pub(crate) struct StyledLineRenderer<'a, 'b, 'c, S, M> where S: TextRenderer + Clone, M: Plugin<'a, ::Color>, { - cursor: LineCursor, - state: LineRenderState<'a, 'b, S, M>, + pub(crate) cursor: LineCursor, + pub(crate) state: &'c mut LineRenderState<'a, 'b, S, M>, + pub(crate) style: &'c TextBoxStyle, } #[derive(Clone)] @@ -61,30 +60,17 @@ where M: Plugin<'a, S::Color>, { pub parser: Parser<'a, S::Color>, - pub character_style: S, - pub style: &'b TextBoxStyle, + pub text_renderer: S, pub end_type: LineEndType, pub plugin: &'b PluginWrapper<'a, M, S::Color>, } -impl<'a, 'b, F, M> StyledLineRenderer<'a, 'b, F, M> -where - F: TextRenderer::Color> + CharacterStyle, - ::Color: From, - M: Plugin<'a, ::Color>, -{ - /// Creates a new line renderer. - pub fn new(cursor: LineCursor, state: LineRenderState<'a, 'b, F, M>) -> Self { - Self { cursor, state } - } -} - struct RenderElementHandler<'a, 'b, F, D, M> where F: TextRenderer, D: DrawTarget, { - style: &'b mut F, + text_renderer: &'b mut F, display: &'b mut D, pos: Point, plugin: &'b PluginWrapper<'a, M, F::Color>, @@ -98,15 +84,12 @@ where M: Plugin<'a, ::Color>, { fn post_print(&mut self, width: u32, st: &str) -> Result<(), D::Error> { - let bounds = Rectangle::new( - self.pos, - Size::new(width, self.style.line_height().saturating_as()), - ); + let bounds = Rectangle::new(self.pos, Size::new(width, self.text_renderer.line_height())); - self.pos += Point::new(width.saturating_as(), 0); + self.pos += Point::new(width as i32, 0); self.plugin - .post_render(self.display, self.style, Some(st), bounds) + .post_render(self.display, self.text_renderer, Some(st), bounds) } } @@ -121,12 +104,12 @@ where type Color = ::Color; fn measure(&self, st: &str) -> u32 { - str_width(self.style, st) + str_width(self.text_renderer, st) } fn whitespace(&mut self, st: &str, _space_count: u32, width: u32) -> Result<(), Self::Error> { if width > 0 { - self.style + self.text_renderer .draw_whitespace(width, self.pos, Baseline::Top, self.display)?; } @@ -134,9 +117,9 @@ where } fn printed_characters(&mut self, st: &str, width: Option) -> Result<(), Self::Error> { - let render_width = self - .style - .draw_string(st, self.pos, Baseline::Top, self.display)?; + let render_width = + self.text_renderer + .draw_string(st, self.pos, Baseline::Top, self.display)?; let width = width.unwrap_or((render_width - self.pos).x as u32); @@ -145,7 +128,7 @@ where fn move_cursor(&mut self, by: i32) -> Result<(), Self::Error> { // LineElementIterator ensures this new pos is valid. - self.pos = Point::new(self.pos.x + by, self.pos.y); + self.pos += Point::new(by, 0); Ok(()) } @@ -153,85 +136,69 @@ where &mut self, change: ChangeTextStyle<::Color>, ) -> Result<(), Self::Error> { - change.apply(self.style); + change.apply(self.text_renderer); Ok(()) } } -impl<'a, 'b, F, M> Drawable for StyledLineRenderer<'a, 'b, F, M> +impl<'a, 'b, 'c, F, M> StyledLineRenderer<'a, 'b, 'c, F, M> where F: TextRenderer::Color> + CharacterStyle, ::Color: From, M: Plugin<'a, ::Color> + Plugin<'a, ::Color>, { - type Color = ::Color; - type Output = LineRenderState<'a, 'b, F, M>; - #[inline] - fn draw(&self, display: &mut D) -> Result + pub(crate) fn draw(mut self, display: &mut D) -> Result<(), D::Error> where - D: DrawTarget, + D: DrawTarget::Color>, { let LineRenderState { - mut parser, - mut character_style, - style, + ref mut parser, + ref mut text_renderer, plugin, .. - } = self.state.clone(); + } = self.state; let lm = { // Ensure the clone lives for as short as possible. let mut cloned_parser = parser.clone(); let measure_plugin = plugin.clone(); measure_plugin.set_state(ProcessingState::Measure); - style.measure_line( + self.style.measure_line( &measure_plugin, - &character_style, + text_renderer, &mut cloned_parser, self.cursor.line_width(), ) }; - let (left, space_config) = style.alignment.place_line(&character_style, lm); - - let mut cursor = self.cursor.clone(); - cursor.move_cursor(left.saturating_as()).ok(); + let (left, space_config) = self.style.alignment.place_line(text_renderer, lm); - let pos = cursor.pos(); - let mut elements = - LineElementParser::new(&mut parser, plugin, cursor, space_config, &style); + self.cursor.move_cursor(left as i32).ok(); let mut render_element_handler = RenderElementHandler { - style: &mut character_style, + text_renderer, display, - pos, - plugin, - }; - let end_type = elements.process(&mut render_element_handler)?; - let end_pos = render_element_handler.pos; - - let next_state = LineRenderState { - parser, - character_style, - style, - end_type, - plugin, + pos: self.cursor.pos(), + plugin: *plugin, }; + let end_type = + LineElementParser::new(parser, plugin, self.cursor, space_config, self.style) + .process(&mut render_element_handler)?; - if next_state.end_type == LineEndType::EndOfText { - next_state.plugin.post_render( + if end_type == LineEndType::EndOfText { + let end_pos = render_element_handler.pos; + plugin.post_render( display, - &next_state.character_style, + text_renderer, None, - Rectangle::new( - end_pos, - Size::new(0, next_state.character_style.line_height()), - ), + Rectangle::new(end_pos, Size::new(0, text_renderer.line_height())), )?; } - Ok(next_state) + self.state.end_type = end_type; + + Ok(()) } } @@ -255,7 +222,6 @@ mod test { pixelcolor::{BinaryColor, Rgb888}, primitives::Rectangle, text::renderer::{CharacterStyle, TextRenderer}, - Drawable, }; fn test_rendered_text<'a, S>( @@ -276,15 +242,18 @@ mod test { let plugin = PluginWrapper::new(NoPlugin::new()); - let state = LineRenderState { + let mut state = LineRenderState { parser, - character_style, - style: &style, + text_renderer: character_style, end_type: LineEndType::EndOfText, plugin: &plugin, }; - let renderer = StyledLineRenderer::new(cursor, state); + let renderer = StyledLineRenderer { + cursor, + state: &mut state, + style: &style, + }; let mut display = MockDisplay::new(); display.set_allow_overdraw(true); diff --git a/src/rendering/line_iter.rs b/src/rendering/line_iter.rs index 2604f8e9..6846fd28 100644 --- a/src/rendering/line_iter.rs +++ b/src/rendering/line_iter.rs @@ -110,7 +110,7 @@ where width_set = true; } - Some(Token::Break(w, _original)) => return Some(width + handler.measure(w)), + Some(Token::Break(w)) => return Some(width + handler.measure(w)), Some(Token::ChangeTextStyle(_)) | Some(Token::MoveCursor { .. }) => {} _ => { @@ -127,6 +127,10 @@ where self.cursor.move_cursor(by) } + fn move_cursor_forward(&mut self, by: u32) -> Result { + self.cursor.move_cursor_forward(by) + } + fn longest_fitting_substr( &mut self, handler: &E, @@ -162,13 +166,11 @@ where let lookahead = self.plugin.clone(); let mut lookahead_parser = self.parser.clone(); - // We don't want to count the current token. - lookahead.consume_peeked_token(); - let mut exit = false; while !exit { + lookahead.consume_peeked_token(); let width = match lookahead.peek_token(&mut lookahead_parser) { - Some(Token::Word(w)) | Some(Token::Break(w, _)) => { + Some(Token::Word(w)) | Some(Token::Break(w)) => { exit = true; handler.measure(w).saturating_as() } @@ -185,7 +187,6 @@ where _ => return false, }; - lookahead.consume_peeked_token(); if cursor.move_cursor(width).is_err() { return false; } @@ -198,8 +199,8 @@ where self.style.trailing_spaces } - fn render_leading_spaces(&self) -> bool { - self.style.leading_spaces + fn skip_leading_spaces(&self) -> bool { + self.empty && !self.style.leading_spaces } fn draw_whitespace( @@ -209,23 +210,24 @@ where space_count: u32, space_width: u32, ) -> Result { - if self.empty && !self.render_leading_spaces() { + if self.skip_leading_spaces() { handler.whitespace(string, space_count, 0)?; return Ok(false); } - match self.move_cursor(space_width.saturating_as()) { + match self.move_cursor_forward(space_width) { Ok(moved) => { handler.whitespace( string, space_count, - moved.saturating_as::() * self.should_draw_whitespace(handler) as u32, + moved * self.should_draw_whitespace(handler) as u32, )?; + Ok(false) } Err(moved) => { let single = space_width / space_count; - let consumed = moved as u32 / single; + let consumed = moved / single; if consumed > 0 { let consumed_str = string .char_indices() @@ -238,7 +240,7 @@ where let consumed_width = consumed * single; - let _ = self.move_cursor(consumed_width.saturating_as()); + let _ = self.move_cursor_forward(consumed_width); handler.whitespace( consumed_str, consumed, @@ -248,27 +250,23 @@ where self.plugin .consume_partial((consumed + 1).min(space_count) as usize); - return Ok(true); + Ok(true) } } - Ok(false) } - fn draw_tab( - &mut self, - handler: &mut E, - space_width: u32, - ) -> Result<(), E::Error> { - if self.empty && !self.render_leading_spaces() { + fn draw_tab(&mut self, handler: &mut E) -> Result<(), E::Error> { + if self.skip_leading_spaces() { return Ok(()); } - match self.move_cursor(space_width.saturating_as()) { + let space_width = self.cursor.next_tab_width(); + match self.move_cursor_forward(space_width) { Ok(moved) if self.should_draw_whitespace(handler) => { - handler.whitespace("\t", 0, moved.saturating_as())? + handler.whitespace("\t", 0, moved)? } - Ok(moved) | Err(moved) => handler.move_cursor(moved)?, + Ok(moved) | Err(moved) => handler.move_cursor(moved as i32)?, } Ok(()) } @@ -296,19 +294,18 @@ where } Token::Tab => { - let space_width = self.cursor.next_tab_width(); - self.draw_tab(handler, space_width)?; + self.draw_tab(handler)?; } - Token::Break(c, _original) => { + Token::Break(c) => { if let Some(word_width) = self.next_word_width(handler) { if !self.cursor.fits_in_line(word_width) || self.empty { // this line is done, decide how to end // If the next Word token does not fit the line, display break character let width = handler.measure(c); - if self.move_cursor(width.saturating_as()).is_ok() { - if let Some(Token::Break(c, _)) = self.plugin.render_token(token) { + if self.move_cursor_forward(width).is_ok() { + if let Some(Token::Break(c)) = self.plugin.render_token(token) { handler.printed_characters(c, Some(width))?; } self.consume_token(); @@ -325,7 +322,7 @@ where Token::Word(w) => { let width = handler.measure(w); - let (word, remainder) = if self.move_cursor(width.saturating_as()).is_ok() { + let (word, remainder) = if self.move_cursor_forward(width).is_ok() { // We can move the cursor here since `process_word()` // doesn't depend on it. (w, "") @@ -446,7 +443,8 @@ where } fn should_draw_whitespace(&self, handler: &E) -> bool { - (self.empty && self.render_leading_spaces()) + self.empty // We know that when this function is called, + // an empty line means leading spaces are allowed || self.render_trailing_spaces() || self.next_word_fits(handler) } diff --git a/src/rendering/mod.rs b/src/rendering/mod.rs index debc60e0..eb0c8ce5 100644 --- a/src/rendering/mod.rs +++ b/src/rendering/mod.rs @@ -95,8 +95,7 @@ where self.plugin.on_start_render(&mut cursor, props); let mut state = LineRenderState { - style: &self.style, - character_style: self.character_style.clone(), + text_renderer: self.character_style.clone(), parser: Parser::parse(self.text), end_type: LineEndType::EndOfText, plugin: &self.plugin, @@ -107,27 +106,27 @@ where let mut anything_drawn = false; loop { state.plugin.new_line(); - let line_cursor = cursor.line(); let display_range = self .style .height_mode .calculate_displayed_row_range(&cursor); - let display_size = Size::new( - cursor.line_width(), - display_range.clone().count().saturating_as(), - ); + let display_range_start = display_range.start.saturating_as::(); + let display_range_count = display_range.count() as u32; + let display_size = Size::new(cursor.line_width(), display_range_count); - let line_start = line_cursor.pos(); + let line_start = cursor.line_start(); // FIXME: cropping isn't necessary for whole lines, but make sure not to blow up the - // binary size as well. + // binary size as well. We could also use a different way to consume invisible text. let mut display = display.clipped(&Rectangle::new( - line_start + Point::new(0, display_range.start), + line_start + Point::new(0, display_range_start), display_size, )); - if display_range.start >= display_range.end { + if display_range_count == 0 { + // Display range can be empty if we are above, or below the visible text section if anything_drawn { + // We are below, so we won't be drawing anything else let remaining_bytes = state.parser.as_str().len(); let consumed_bytes = self.text.len() - remaining_bytes; @@ -135,10 +134,7 @@ where &mut display, &self.character_style, None, - Rectangle::new( - line_start, - Size::new(0, cursor.line_height().saturating_as()), - ), + Rectangle::new(line_start, Size::new(0, cursor.line_height())), )?; state.plugin.on_rendering_finished(); return Ok(self.text.get(consumed_bytes..).unwrap()); @@ -147,7 +143,12 @@ where anything_drawn = true; } - state = StyledLineRenderer::new(line_cursor, state).draw(&mut display)?; + StyledLineRenderer { + cursor: cursor.line(), + state: &mut state, + style: &self.style, + } + .draw(&mut display)?; match state.end_type { LineEndType::EndOfText => { diff --git a/src/style/height_mode.rs b/src/style/height_mode.rs index b45ba582..d3fce6c5 100644 --- a/src/style/height_mode.rs +++ b/src/style/height_mode.rs @@ -202,7 +202,7 @@ impl HeightMode { /// If a line does not fully fit in the bounding box, some `HeightMode` options allow drawing /// partial lines. For a partial line, this function calculates, which rows of each character /// should be displayed. - pub(crate) fn calculate_displayed_row_range(self, cursor: &Cursor) -> Range { + pub(crate) fn calculate_displayed_row_range(self, cursor: &Cursor) -> Range { let overdraw = match self { HeightMode::Exact(overdraw) | HeightMode::ShrinkToText(overdraw) => overdraw, HeightMode::FitToText => VerticalOverdraw::Visible, diff --git a/src/style/mod.rs b/src/style/mod.rs index 918c1346..c2646917 100644 --- a/src/style/mod.rs +++ b/src/style/mod.rs @@ -194,7 +194,6 @@ use crate::{ }, utils::str_width, }; -use az::SaturatingAs; use embedded_graphics::{ pixelcolor::Rgb888, text::{renderer::TextRenderer, LineHeight}, @@ -407,7 +406,7 @@ impl<'a, S: TextRenderer> ElementHandler for MeasureLineElementHandler<'a, S> { } fn move_cursor(&mut self, by: i32) -> Result<(), Self::Error> { - self.cursor = (self.cursor.saturating_as::() + by) as u32; + self.cursor = (self.cursor as i32 + by) as u32; Ok(()) } diff --git a/src/style/vertical_overdraw.rs b/src/style/vertical_overdraw.rs index d0bbb746..ef0d78a9 100644 --- a/src/style/vertical_overdraw.rs +++ b/src/style/vertical_overdraw.rs @@ -1,5 +1,7 @@ //! Vertical overdraw options. +use az::SaturatingAs; + use crate::rendering::cursor::Cursor; use core::ops::Range; @@ -16,7 +18,7 @@ pub enum VerticalOverdraw { impl VerticalOverdraw { /// Calculate the range of rows of the current line that can be drawn. - pub(crate) fn calculate_displayed_row_range(self, cursor: &Cursor) -> Range { + pub(crate) fn calculate_displayed_row_range(self, cursor: &Cursor) -> Range { let line_height = cursor.line_height(); match self { VerticalOverdraw::FullRowsOnly => { @@ -28,8 +30,9 @@ impl VerticalOverdraw { } VerticalOverdraw::Hidden => { - let offset_top = (cursor.top_left().y - cursor.y).max(0); - let offset_bottom = (cursor.bottom_right().y - cursor.y).min(line_height); + let offset_top = (cursor.top_left().y - cursor.y).saturating_as::(); + let offset_bottom = + line_height - (cursor.y - cursor.bottom()).saturating_as::(); offset_top..offset_bottom } diff --git a/src/utils.rs b/src/utils.rs index f25c92ff..16ca47f2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,5 @@ //! Misc utilities -use az::SaturatingAs; use embedded_graphics::{ prelude::Point, text::{renderer::TextRenderer, Baseline}, @@ -11,8 +10,7 @@ pub fn str_width(renderer: &impl TextRenderer, s: &str) -> u32 { renderer .measure_string(s, Point::zero(), Baseline::Top) .next_position - .x - .saturating_as() + .x as u32 } #[cfg(test)]