diff --git a/agb/examples/object_text_render.rs b/agb/examples/object_text_render.rs index 22bb20a9c..536bf25a9 100644 --- a/agb/examples/object_text_render.rs +++ b/agb/examples/object_text_render.rs @@ -73,7 +73,7 @@ fn main(mut gba: agb::Gba) -> ! { vblank.wait_for_vblank(); input.update(); let oam = &mut unmanaged.iter(); - wr.commit(oam); + wr.commit(oam, (0, HEIGHT - 40)); let start = timer.value(); if frame % 4 == 0 { @@ -83,7 +83,7 @@ fn main(mut gba: agb::Gba) -> ! { line_done = false; wr.pop_line(); } - wr.update((0, HEIGHT - 40)); + wr.update(); let end = timer.value(); frame += 1; diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index 85df2a554..4cf5c71db 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -23,7 +23,7 @@ pub use affine::AffineMatrixInstance; pub use managed::{OamManaged, Object}; pub use unmanaged::{AffineMode, OamIterator, OamSlot, OamUnmanaged, ObjectUnmanaged}; -pub use font::{ChangeColour, ObjectTextRender, TextAlignment}; +pub use font::{ChangeColour, LetterGroup, LetterGroupIter, ObjectTextRender, TextAlignment}; use super::DISPLAY_CONTROL; diff --git a/agb/src/display/object/font.rs b/agb/src/display/object/font.rs index f7a03b41b..b6efbeb01 100644 --- a/agb/src/display/object/font.rs +++ b/agb/src/display/object/font.rs @@ -1,7 +1,10 @@ -use core::fmt::{Display, Write}; +use core::{ + fmt::{Display, Write}, + ops::Add, +}; use agb_fixnum::{Num, Vector2D}; -use alloc::{collections::VecDeque, vec::Vec}; +use alloc::collections::VecDeque; use crate::display::Font; @@ -256,10 +259,9 @@ impl<'font> ObjectTextRender<'font> { buffer: BufferedRender::new(font, sprite_size, palette), number_of_objects: 0, layout: LayoutCache { - positions: VecDeque::new(), + relative_positions: VecDeque::new(), line_capacity: VecDeque::new(), - objects: Vec::new(), - objects_are_at_origin: (0, 0).into(), + area: (0, 0).into(), }, } @@ -276,12 +278,100 @@ impl Write for ObjectTextRender<'_> { } } +/// A borrowed letter group and its relative position. +pub struct LetterGroup<'a> { + sprite: &'a SpriteVram, + relative_position: Vector2D, +} + +impl<'a> LetterGroup<'a> { + /// The sprite in vram for this group of letters. + #[must_use] + pub fn sprite(&self) -> &'a SpriteVram { + self.sprite + } + + /// The relative position of the letter group. For example a left aligned + /// text would start at (0,0) but a right aligned would have the last group + /// of a line be at (0,0). + #[must_use] + pub fn relative_position(&self) -> Vector2D { + self.relative_position + } +} + +impl<'a> Add> for &LetterGroup<'a> { + type Output = LetterGroup<'a>; + + fn add(self, rhs: Vector2D) -> Self::Output { + LetterGroup { + sprite: self.sprite, + relative_position: self.relative_position + rhs, + } + } +} + +impl<'a> From> for ObjectUnmanaged { + fn from(value: LetterGroup<'a>) -> Self { + let mut object = ObjectUnmanaged::new(value.sprite.clone()); + object.set_position(value.relative_position); + object.show(); + + object + } +} + +/// An iterator over the currently displaying letter groups +pub struct LetterGroupIter<'a> { + sprite_iter: alloc::collections::vec_deque::Iter<'a, SpriteVram>, + position_iter: alloc::collections::vec_deque::Iter<'a, Vector2D>, + remaining_letter_groups: usize, +} + +impl<'a> Iterator for LetterGroupIter<'a> { + type Item = LetterGroup<'a>; + + fn next(&mut self) -> Option { + if self.remaining_letter_groups == 0 { + return None; + } + self.remaining_letter_groups -= 1; + + match (self.sprite_iter.next(), self.position_iter.next()) { + (Some(sprite), Some(position)) => Some(LetterGroup { + sprite, + relative_position: position.change_base(), + }), + _ => None, + } + } +} + impl ObjectTextRender<'_> { + #[must_use] + /// An iterator over the letter groups that make up the text + pub fn letter_groups(&self) -> LetterGroupIter<'_> { + LetterGroupIter { + sprite_iter: self.buffer.letters.letters.iter(), + position_iter: self.layout.relative_positions.iter(), + remaining_letter_groups: self.number_of_objects, + } + } + /// Commits work already done to screen. You can commit to multiple places in the same frame. - pub fn commit(&mut self, oam: &mut OamIterator) { - for (object, slot) in self.layout.objects.iter().zip(oam) { - slot.set(object); + pub fn commit>>(&mut self, oam: &mut OamIterator, position: V) { + fn inner(this: &mut ObjectTextRender, oam: &mut OamIterator, position: Vector2D) { + let objects = this + .letter_groups() + .map(|x| &x + position) + .map(ObjectUnmanaged::from); + + for (object, slot) in objects.zip(oam) { + slot.set(&object); + } } + + inner(self, oam, position.into()); } /// Force a relayout, must be called after writing. @@ -309,17 +399,16 @@ impl ObjectTextRender<'_> { let line_height = self.buffer.font.line_height(); if let Some(line) = self.buffer.preprocessor.lines(width, space).next() { // there is a line - if self.layout.objects.len() >= line.number_of_letter_groups() { + if self.number_of_objects >= line.number_of_letter_groups() { // we have enough rendered letter groups to count self.number_of_objects -= line.number_of_letter_groups(); for _ in 0..line.number_of_letter_groups() { self.buffer.letters.letters.pop_front(); - self.layout.positions.pop_front(); + self.layout.relative_positions.pop_front(); } self.layout.line_capacity.pop_front(); - self.layout.objects.clear(); self.buffer.preprocessor.pop(&line); - for position in self.layout.positions.iter_mut() { + for position in self.layout.relative_positions.iter_mut() { position.y -= line_height as i16; } return true; @@ -331,18 +420,12 @@ impl ObjectTextRender<'_> { /// Updates the internal state of the number of letters to write and popped /// line. Should be called in the same frame as and after /// [`next_letter_group`][ObjectTextRender::next_letter_group], [`next_line`][ObjectTextRender::next_line], and [`pop_line`][ObjectTextRender::pop_line]. - pub fn update(&mut self, position: impl Into>) { + pub fn update(&mut self) { if !self.buffer.buffered_chars.is_empty() && self.buffer.letters.letters.len() <= self.number_of_objects + 5 { self.buffer.process(); } - - self.layout.update_objects_to_display_at_position( - position.into(), - self.buffer.letters.letters.iter(), - self.number_of_objects, - ); } /// Causes the next letter group to be shown on the next update. Returns @@ -406,43 +489,12 @@ impl ObjectTextRender<'_> { } struct LayoutCache { - positions: VecDeque>, + relative_positions: VecDeque>, line_capacity: VecDeque, - objects: Vec, - objects_are_at_origin: Vector2D, area: Vector2D, } impl LayoutCache { - fn update_objects_to_display_at_position<'a>( - &mut self, - position: Vector2D, - letters: impl Iterator, - number_of_objects: usize, - ) { - let already_done = if position == self.objects_are_at_origin { - self.objects.len() - } else { - self.objects.clear(); - 0 - }; - self.objects.extend( - self.positions - .iter() - .zip(letters) - .take(number_of_objects) - .skip(already_done) - .map(|(offset, letter)| { - let position = offset.change_base() + position; - let mut object = ObjectUnmanaged::new(letter.clone()); - object.show().set_position(position); - object - }), - ); - self.objects.truncate(number_of_objects); - self.objects_are_at_origin = position; - } - fn create_positions( &mut self, font: &Font, @@ -451,10 +503,10 @@ impl LayoutCache { ) { self.area = settings.area; self.line_capacity.clear(); - self.positions.clear(); + self.relative_positions.clear(); for (line, line_positions) in Self::create_layout(font, preprocessed, settings) { self.line_capacity.push_back(line.number_of_letter_groups()); - self.positions + self.relative_positions .extend(line_positions.map(|x| Vector2D::new(x.x as i16, x.y as i16))); } }