mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 08:11:33 +11:00
garbage renderer
This commit is contained in:
parent
93024f6bab
commit
ec3003c81d
|
@ -4,7 +4,7 @@
|
||||||
use agb::{
|
use agb::{
|
||||||
display::{
|
display::{
|
||||||
object::{
|
object::{
|
||||||
font::{BufferedRender, LayoutCache, TextAlignment},
|
font::{ObjectTextRender, TextAlignment},
|
||||||
PaletteVram, Size,
|
PaletteVram, Size,
|
||||||
},
|
},
|
||||||
palette16::Palette16,
|
palette16::Palette16,
|
||||||
|
@ -35,7 +35,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
let palette = Palette16::new(palette);
|
let palette = Palette16::new(palette);
|
||||||
let palette = PaletteVram::new(&palette).unwrap();
|
let palette = PaletteVram::new(&palette).unwrap();
|
||||||
|
|
||||||
let mut wr = BufferedRender::new(&FONT, Size::S16x8, palette);
|
let mut wr = ObjectTextRender::new(&FONT, Size::S16x8, palette);
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
wr,
|
wr,
|
||||||
"{}",
|
"{}",
|
||||||
|
@ -52,55 +52,30 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
timer.set_enabled(true);
|
timer.set_enabled(true);
|
||||||
timer.set_divider(agb::timer::Divider::Divider256);
|
timer.set_divider(agb::timer::Divider::Divider256);
|
||||||
|
|
||||||
let mut num_letters = 0;
|
wr.set_alignment(TextAlignment::Left);
|
||||||
|
wr.set_size((WIDTH / 3, 20).into());
|
||||||
let mut alignment = TextAlignment::Left;
|
wr.set_paragraph_spacing(2);
|
||||||
|
wr.layout();
|
||||||
let mut cache = LayoutCache::new();
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
vblank.wait_for_vblank();
|
vblank.wait_for_vblank();
|
||||||
input.update();
|
input.update();
|
||||||
let oam = &mut unmanaged.iter();
|
let oam = &mut unmanaged.iter();
|
||||||
cache.commit(oam);
|
wr.commit(oam, (WIDTH / 3, 0).into());
|
||||||
|
|
||||||
let start = timer.value();
|
let start = timer.value();
|
||||||
wr.process();
|
let line_done = !wr.next_letter_group();
|
||||||
cache.update(
|
if line_done && input.is_just_pressed(Button::A) {
|
||||||
&mut wr,
|
wr.pop_line();
|
||||||
Rect::new((WIDTH / 3, 0).into(), (WIDTH / 3, 100).into()),
|
}
|
||||||
alignment,
|
wr.layout();
|
||||||
2,
|
|
||||||
num_letters,
|
|
||||||
);
|
|
||||||
let end = timer.value();
|
let end = timer.value();
|
||||||
|
|
||||||
agb::println!("Took {} cycles", 256 * (end.wrapping_sub(start) as u32));
|
|
||||||
|
|
||||||
if input.is_just_pressed(Button::LEFT) {
|
|
||||||
alignment = TextAlignment::Left;
|
|
||||||
}
|
|
||||||
if input.is_just_pressed(Button::RIGHT) {
|
|
||||||
alignment = TextAlignment::Right;
|
|
||||||
}
|
|
||||||
if input.is_just_pressed(Button::UP | Button::DOWN) {
|
|
||||||
alignment = TextAlignment::Center;
|
|
||||||
}
|
|
||||||
|
|
||||||
num_letters += 1;
|
|
||||||
|
|
||||||
if input.is_just_pressed(Button::A) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let start = timer.value();
|
|
||||||
drop(wr);
|
|
||||||
let oam = unmanaged.iter();
|
|
||||||
drop(oam);
|
|
||||||
let end = timer.value();
|
|
||||||
agb::println!(
|
agb::println!(
|
||||||
"Drop took {} cycles",
|
"Took {} cycles, line done {}",
|
||||||
256 * (end.wrapping_sub(start) as u32)
|
256 * (end.wrapping_sub(start) as u32),
|
||||||
|
line_done
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use core::fmt::Write;
|
||||||
use agb_fixnum::{Rect, Vector2D};
|
use agb_fixnum::{Rect, Vector2D};
|
||||||
use alloc::{collections::VecDeque, vec::Vec};
|
use alloc::{collections::VecDeque, vec::Vec};
|
||||||
|
|
||||||
use crate::display::Font;
|
use crate::display::{object::font::preprocess::Word, Font};
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
preprocess::{Line, Preprocessed, PreprocessedElement},
|
preprocess::{Line, Preprocessed, PreprocessedElement},
|
||||||
|
@ -15,7 +15,7 @@ use super::{OamIterator, ObjectUnmanaged, PaletteVram, Size, SpriteVram};
|
||||||
mod preprocess;
|
mod preprocess;
|
||||||
mod renderer;
|
mod renderer;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub(crate) enum WhiteSpace {
|
pub(crate) enum WhiteSpace {
|
||||||
NewLine,
|
NewLine,
|
||||||
|
@ -50,23 +50,14 @@ pub struct BufferedRender<'font> {
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct Letters {
|
struct Letters {
|
||||||
letters: Vec<LetterGroup>,
|
letters: VecDeque<LetterGroup>,
|
||||||
number_of_groups: usize,
|
number_of_groups: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Write for BufferedRender<'_> {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
|
||||||
for c in s.chars() {
|
|
||||||
self.input_character(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum TextAlignment {
|
pub enum TextAlignment {
|
||||||
|
#[default]
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
Center,
|
Center,
|
||||||
|
@ -103,7 +94,7 @@ impl TextAlignment {
|
||||||
|
|
||||||
impl<'font> BufferedRender<'font> {
|
impl<'font> BufferedRender<'font> {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(font: &'font Font, sprite_size: Size, palette: PaletteVram) -> Self {
|
fn new(font: &'font Font, sprite_size: Size, palette: PaletteVram) -> Self {
|
||||||
let config = Configuration::new(sprite_size, palette);
|
let config = Configuration::new(sprite_size, palette);
|
||||||
BufferedRender {
|
BufferedRender {
|
||||||
char_render: WordRender::new(config),
|
char_render: WordRender::new(config),
|
||||||
|
@ -115,19 +106,25 @@ impl<'font> BufferedRender<'font> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_private_use(c: char) -> bool {
|
||||||
|
('\u{E000}'..'\u{F8FF}').contains(&c)
|
||||||
|
}
|
||||||
|
|
||||||
impl BufferedRender<'_> {
|
impl BufferedRender<'_> {
|
||||||
fn input_character(&mut self, character: char) {
|
fn input_character(&mut self, character: char) {
|
||||||
|
if !is_private_use(character) {
|
||||||
self.preprocessor
|
self.preprocessor
|
||||||
.add_character(self.font, character, self.char_render.sprite_width());
|
.add_character(self.font, character, self.char_render.sprite_width());
|
||||||
|
}
|
||||||
self.buffered_chars.push_back(character);
|
self.buffered_chars.push_back(character);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process(&mut self) {
|
fn process(&mut self) {
|
||||||
let Some(c) = self.buffered_chars.pop_front() else { return };
|
let Some(c) = self.buffered_chars.pop_front() else { return };
|
||||||
match c {
|
match c {
|
||||||
' ' | '\n' => {
|
' ' | '\n' => {
|
||||||
if let Some(group) = self.char_render.finalise_letter() {
|
if let Some(group) = self.char_render.finalise_letter() {
|
||||||
self.letters.letters.push(group);
|
self.letters.letters.push_back(group);
|
||||||
self.letters.number_of_groups += 1;
|
self.letters.number_of_groups += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +132,7 @@ impl BufferedRender<'_> {
|
||||||
}
|
}
|
||||||
letter => {
|
letter => {
|
||||||
if let Some(group) = self.char_render.render_char(self.font, letter) {
|
if let Some(group) = self.char_render.render_char(self.font, letter) {
|
||||||
self.letters.letters.push(group);
|
self.letters.letters.push_back(group);
|
||||||
self.letters.number_of_groups += 1;
|
self.letters.number_of_groups += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,32 +140,173 @@ impl BufferedRender<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LayoutCache {
|
pub struct ObjectTextRender<'font> {
|
||||||
objects: Vec<ObjectUnmanaged>,
|
buffer: BufferedRender<'font>,
|
||||||
state: LayoutCacheState,
|
layout: LayoutCache,
|
||||||
settings: LayoutSettings,
|
settings: LayoutSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'font> ObjectTextRender<'font> {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(font: &'font Font, sprite_size: Size, palette: PaletteVram) -> Self {
|
||||||
|
Self {
|
||||||
|
buffer: BufferedRender::new(font, sprite_size, palette),
|
||||||
|
layout: LayoutCache::new(),
|
||||||
|
settings: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for ObjectTextRender<'_> {
|
||||||
|
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||||
|
for c in s.chars() {
|
||||||
|
self.buffer.input_character(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectTextRender<'_> {
|
||||||
|
/// Remove a line from the render and shift everything up one line.
|
||||||
|
/// A full complete line must be rendered for this to do anything, incomplete lines won't be popped. Returns whether a line could be popped.
|
||||||
|
pub fn pop_line(&mut self) -> bool {
|
||||||
|
let width = self.layout.settings.area.x;
|
||||||
|
let space = self.buffer.font.letter(' ').advance_width as i32;
|
||||||
|
let Some(line) = self.buffer.preprocessor.lines(width, space).next() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let number_of_elements = line.number_of_letter_groups();
|
||||||
|
if self.layout.state.line_depth >= 1 && self.layout.objects.len() >= number_of_elements {
|
||||||
|
for _ in 0..number_of_elements {
|
||||||
|
// self.buffer.letters.letters.pop_front();
|
||||||
|
self.layout.objects.pop_front();
|
||||||
|
}
|
||||||
|
self.buffer.preprocessor.pop(&line);
|
||||||
|
|
||||||
|
self.layout.state.head_offset.y -= self.buffer.font.line_height();
|
||||||
|
for obj in self.layout.objects.iter_mut() {
|
||||||
|
obj.offset.y -= self.buffer.font.line_height() as i16;
|
||||||
|
let object_offset = obj.offset.change_base();
|
||||||
|
obj.object
|
||||||
|
.set_position(self.layout.position + object_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.layout.state.line_depth -= 1;
|
||||||
|
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On next update, the next unit of letters will be rendered. Returns whether the next element could be added.
|
||||||
|
/// Can only be called once per layout.
|
||||||
|
pub fn next_letter_group(&mut self) -> bool {
|
||||||
|
self.layout.next_letter_group(&self.buffer)
|
||||||
|
}
|
||||||
|
/// Commits work already done to screen. You can commit to multiple places in the same frame.
|
||||||
|
pub fn commit(&mut self, oam: &mut OamIterator, position: Vector2D<i32>) {
|
||||||
|
self.layout.commit(oam, position);
|
||||||
|
}
|
||||||
|
/// Updates the internal state based on the chosen render settings. Best
|
||||||
|
/// effort is made to reuse previous layouts, but a full rerender may be
|
||||||
|
/// required if certain settings are changed.
|
||||||
|
pub fn layout(&mut self) {
|
||||||
|
self.layout.update(
|
||||||
|
&mut self.buffer,
|
||||||
|
self.settings.area,
|
||||||
|
self.settings.alignment,
|
||||||
|
self.settings.paragraph_spacing,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/// Causes a change to the area that text is rendered. This will cause a relayout.
|
||||||
|
pub fn set_size(&mut self, size: Vector2D<i32>) {
|
||||||
|
self.settings.area = size;
|
||||||
|
}
|
||||||
|
/// Causes a change to the text alignment. This will cause a relayout.
|
||||||
|
pub fn set_alignment(&mut self, alignment: TextAlignment) {
|
||||||
|
self.settings.alignment = alignment;
|
||||||
|
}
|
||||||
|
/// Sets the paragraph spacing. This will cause a relayout.
|
||||||
|
pub fn set_paragraph_spacing(&mut self, paragraph_spacing: i32) {
|
||||||
|
self.settings.paragraph_spacing = paragraph_spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LayoutObject {
|
||||||
|
object: ObjectUnmanaged,
|
||||||
|
offset: Vector2D<i16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LayoutCache {
|
||||||
|
objects: VecDeque<LayoutObject>,
|
||||||
|
state: LayoutCacheState,
|
||||||
|
settings: LayoutSettings,
|
||||||
|
desired_number_of_groups: usize,
|
||||||
|
position: Vector2D<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
impl LayoutCache {
|
impl LayoutCache {
|
||||||
fn update_cache(&mut self, number_of_groups: usize, render: &BufferedRender) {
|
fn next_letter_group(&mut self, buffer: &BufferedRender) -> bool {
|
||||||
|
let width = self.settings.area.x;
|
||||||
|
let space = buffer.font.letter(' ').advance_width as i32;
|
||||||
|
let line_height = buffer.font.line_height();
|
||||||
|
|
||||||
|
if self.state.head_offset.y + line_height > self.settings.area.y {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((_line, mut line_elements)) = buffer
|
||||||
|
.preprocessor
|
||||||
|
.lines_element(width, space)
|
||||||
|
.nth(self.state.line_depth)
|
||||||
|
{
|
||||||
|
match line_elements.nth(self.state.line_element_depth) {
|
||||||
|
Some(PreprocessedElement::Word(_)) => {
|
||||||
|
self.desired_number_of_groups += 1;
|
||||||
|
}
|
||||||
|
Some(PreprocessedElement::WhiteSpace(WhiteSpace::Space)) => {
|
||||||
|
self.desired_number_of_groups += 1;
|
||||||
|
}
|
||||||
|
Some(PreprocessedElement::WhiteSpace(WhiteSpace::NewLine)) => {
|
||||||
|
self.desired_number_of_groups += 1;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if self.state.head_offset.y + line_height * 2 > self.settings.area.y {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.desired_number_of_groups += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_cache(&mut self, render: &BufferedRender) {
|
||||||
|
if self.state.rendered_groups >= self.desired_number_of_groups {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let minimum_space_width = render.font.letter(' ').advance_width as i32;
|
let minimum_space_width = render.font.letter(' ').advance_width as i32;
|
||||||
|
|
||||||
let lines = render
|
let lines = render
|
||||||
.preprocessor
|
.preprocessor
|
||||||
.lines_element(self.settings.area.size.x, minimum_space_width);
|
.lines_element(self.settings.area.x, minimum_space_width);
|
||||||
|
|
||||||
'outer: for (line, line_elements) in lines.skip(self.state.line_depth) {
|
'outer: for (line, line_elements) in lines.skip(self.state.line_depth) {
|
||||||
let settings = self.settings.alignment.settings(
|
let settings =
|
||||||
&line,
|
self.settings
|
||||||
minimum_space_width,
|
.alignment
|
||||||
self.settings.area.size,
|
.settings(&line, minimum_space_width, self.settings.area);
|
||||||
);
|
|
||||||
|
|
||||||
if self.state.line_element_depth == 0 {
|
if self.state.line_element_depth == 0 {
|
||||||
self.state.head_position.x += settings.start_x;
|
self.state.head_offset.x += settings.start_x;
|
||||||
}
|
}
|
||||||
|
|
||||||
for element in line_elements.iter().skip(self.state.line_element_depth) {
|
for element in line_elements.skip(self.state.line_element_depth) {
|
||||||
match element {
|
match element {
|
||||||
PreprocessedElement::Word(word) => {
|
PreprocessedElement::Word(word) => {
|
||||||
for letter in (word.sprite_index()
|
for letter in (word.sprite_index()
|
||||||
|
@ -177,14 +315,24 @@ impl LayoutCache {
|
||||||
.map(|x| &render.letters.letters[x])
|
.map(|x| &render.letters.letters[x])
|
||||||
{
|
{
|
||||||
let mut object = ObjectUnmanaged::new(letter.sprite.clone());
|
let mut object = ObjectUnmanaged::new(letter.sprite.clone());
|
||||||
self.state.head_position.x += letter.left as i32;
|
self.state.head_offset.x += letter.left as i32;
|
||||||
object.set_position(self.state.head_position);
|
object
|
||||||
self.state.head_position.x += letter.width as i32;
|
.set_position(self.state.head_offset + self.position)
|
||||||
object.show();
|
.show();
|
||||||
self.objects.push(object);
|
|
||||||
|
let layout_object = LayoutObject {
|
||||||
|
object,
|
||||||
|
offset: (
|
||||||
|
self.state.head_offset.x as i16,
|
||||||
|
self.state.head_offset.y as i16,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
};
|
||||||
|
self.state.head_offset.x += letter.width as i32;
|
||||||
|
self.objects.push_back(layout_object);
|
||||||
self.state.rendered_groups += 1;
|
self.state.rendered_groups += 1;
|
||||||
self.state.word_depth += 1;
|
self.state.word_depth += 1;
|
||||||
if self.state.rendered_groups >= number_of_groups {
|
if self.state.rendered_groups >= self.desired_number_of_groups {
|
||||||
break 'outer;
|
break 'outer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,36 +341,39 @@ impl LayoutCache {
|
||||||
self.state.line_element_depth += 1;
|
self.state.line_element_depth += 1;
|
||||||
}
|
}
|
||||||
PreprocessedElement::WhiteSpace(space_type) => {
|
PreprocessedElement::WhiteSpace(space_type) => {
|
||||||
if *space_type == WhiteSpace::NewLine {
|
if space_type == WhiteSpace::NewLine {
|
||||||
self.state.head_position.y += self.settings.paragraph_spacing;
|
self.state.head_offset.y += self.settings.paragraph_spacing;
|
||||||
}
|
}
|
||||||
self.state.head_position.x += settings.space_width;
|
self.state.head_offset.x += settings.space_width;
|
||||||
self.state.rendered_groups += 1;
|
self.state.rendered_groups += 1;
|
||||||
self.state.line_element_depth += 1;
|
self.state.line_element_depth += 1;
|
||||||
if self.state.rendered_groups >= number_of_groups {
|
if self.state.rendered_groups >= self.desired_number_of_groups {
|
||||||
break 'outer;
|
break 'outer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.state.head_position.y += render.font.line_height();
|
self.state.head_offset.y += render.font.line_height();
|
||||||
self.state.head_position.x = self.settings.area.position.x;
|
self.state.head_offset.x = 0;
|
||||||
|
|
||||||
self.state.line_element_depth = 0;
|
self.state.line_element_depth = 0;
|
||||||
self.state.line_depth += 1;
|
self.state.line_depth += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
r: &mut BufferedRender<'_>,
|
r: &mut BufferedRender<'_>,
|
||||||
area: Rect<i32>,
|
area: Vector2D<i32>,
|
||||||
alignment: TextAlignment,
|
alignment: TextAlignment,
|
||||||
paragraph_spacing: i32,
|
paragraph_spacing: i32,
|
||||||
number_of_groups: usize,
|
|
||||||
) {
|
) {
|
||||||
while !r.buffered_chars.is_empty() && r.letters.number_of_groups <= number_of_groups {
|
r.process();
|
||||||
|
|
||||||
|
while !r.buffered_chars.is_empty()
|
||||||
|
&& r.letters.number_of_groups <= self.desired_number_of_groups
|
||||||
|
{
|
||||||
r.process();
|
r.process();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,32 +386,43 @@ impl LayoutCache {
|
||||||
self.reset(settings);
|
self.reset(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_cache(number_of_groups, r);
|
self.update_cache(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn commit(&self, oam: &mut OamIterator) {
|
fn commit(&mut self, oam: &mut OamIterator, position: Vector2D<i32>) {
|
||||||
|
if self.position != position {
|
||||||
|
for (object, slot) in self.objects.iter_mut().zip(oam) {
|
||||||
|
let object_offset = object.offset.change_base();
|
||||||
|
object.object.set_position(position + object_offset);
|
||||||
|
slot.set(&object.object);
|
||||||
|
}
|
||||||
|
self.position = position;
|
||||||
|
} else {
|
||||||
for (object, slot) in self.objects.iter().zip(oam) {
|
for (object, slot) in self.objects.iter().zip(oam) {
|
||||||
slot.set(object);
|
slot.set(&object.object);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
objects: Vec::new(),
|
objects: VecDeque::new(),
|
||||||
state: Default::default(),
|
state: Default::default(),
|
||||||
settings: LayoutSettings {
|
settings: LayoutSettings {
|
||||||
area: Rect::new((0, 0).into(), (0, 0).into()),
|
area: (0, 0).into(),
|
||||||
alignment: TextAlignment::Right,
|
alignment: TextAlignment::Right,
|
||||||
paragraph_spacing: -100,
|
paragraph_spacing: -100,
|
||||||
},
|
},
|
||||||
|
desired_number_of_groups: 0,
|
||||||
|
position: (0, 0).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self, settings: LayoutSettings) {
|
fn reset(&mut self, settings: LayoutSettings) {
|
||||||
self.objects.clear();
|
self.objects.clear();
|
||||||
self.state = LayoutCacheState {
|
self.state = LayoutCacheState {
|
||||||
head_position: settings.area.position,
|
head_offset: (0, 0).into(),
|
||||||
word_depth: 0,
|
word_depth: 0,
|
||||||
rendered_groups: 0,
|
rendered_groups: 0,
|
||||||
line_depth: 0,
|
line_depth: 0,
|
||||||
|
@ -270,16 +432,16 @@ impl LayoutCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq, Default)]
|
||||||
struct LayoutSettings {
|
struct LayoutSettings {
|
||||||
area: Rect<i32>,
|
area: Vector2D<i32>,
|
||||||
alignment: TextAlignment,
|
alignment: TextAlignment,
|
||||||
paragraph_spacing: i32,
|
paragraph_spacing: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct LayoutCacheState {
|
struct LayoutCacheState {
|
||||||
head_position: Vector2D<i32>,
|
head_offset: Vector2D<i32>,
|
||||||
word_depth: usize,
|
word_depth: usize,
|
||||||
rendered_groups: usize,
|
rendered_groups: usize,
|
||||||
line_depth: usize,
|
line_depth: usize,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use core::num::NonZeroU8;
|
use core::num::NonZeroU8;
|
||||||
|
|
||||||
use alloc::vec::Vec;
|
use alloc::{collections::VecDeque, vec::Vec};
|
||||||
|
|
||||||
use crate::display::Font;
|
use crate::display::Font;
|
||||||
|
|
||||||
use super::WhiteSpace;
|
use super::WhiteSpace;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
pub(crate) enum PreprocessedElement {
|
pub(crate) enum PreprocessedElement {
|
||||||
Word(Word),
|
Word(Word),
|
||||||
WhiteSpace(WhiteSpace),
|
WhiteSpace(WhiteSpace),
|
||||||
|
@ -40,12 +40,9 @@ impl Word {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub(crate) struct PreprocessedElementStored(u8);
|
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub(crate) struct Preprocessed {
|
pub(crate) struct Preprocessed {
|
||||||
widths: Vec<PreprocessedElement>,
|
widths: VecDeque<PreprocessedElement>,
|
||||||
preprocessor: Preprocessor,
|
preprocessor: Preprocessor,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,14 +60,14 @@ impl Preprocessor {
|
||||||
font: &Font,
|
font: &Font,
|
||||||
character: char,
|
character: char,
|
||||||
sprite_width: i32,
|
sprite_width: i32,
|
||||||
widths: &mut Vec<PreprocessedElement>,
|
widths: &mut VecDeque<PreprocessedElement>,
|
||||||
) {
|
) {
|
||||||
match character {
|
match character {
|
||||||
space @ (' ' | '\n') => {
|
space @ (' ' | '\n') => {
|
||||||
if self.current_word_width != 0 {
|
if self.current_word_width != 0 {
|
||||||
self.number_of_sprites += 1;
|
self.number_of_sprites += 1;
|
||||||
self.total_number_of_sprites += 1;
|
self.total_number_of_sprites += 1;
|
||||||
widths.push(PreprocessedElement::Word(Word {
|
widths.push_back(PreprocessedElement::Word(Word {
|
||||||
pixels: self.current_word_width.try_into().expect("word too wide"),
|
pixels: self.current_word_width.try_into().expect("word too wide"),
|
||||||
number_of_sprites: NonZeroU8::new(
|
number_of_sprites: NonZeroU8::new(
|
||||||
self.number_of_sprites.try_into().expect("word too wide"),
|
self.number_of_sprites.try_into().expect("word too wide"),
|
||||||
|
@ -84,7 +81,7 @@ impl Preprocessor {
|
||||||
self.number_of_sprites = 0;
|
self.number_of_sprites = 0;
|
||||||
self.width_in_sprite = 0;
|
self.width_in_sprite = 0;
|
||||||
}
|
}
|
||||||
widths.push(PreprocessedElement::WhiteSpace(WhiteSpace::from_char(
|
widths.push_back(PreprocessedElement::WhiteSpace(WhiteSpace::from_char(
|
||||||
space,
|
space,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
@ -108,7 +105,7 @@ impl Preprocessor {
|
||||||
pub(crate) struct Lines<'preprocess> {
|
pub(crate) struct Lines<'preprocess> {
|
||||||
minimum_space_width: i32,
|
minimum_space_width: i32,
|
||||||
layout_width: i32,
|
layout_width: i32,
|
||||||
data: &'preprocess [PreprocessedElement],
|
data: &'preprocess VecDeque<PreprocessedElement>,
|
||||||
current_start_idx: usize,
|
current_start_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,6 +114,7 @@ pub(crate) struct Line {
|
||||||
number_of_text_elements: usize,
|
number_of_text_elements: usize,
|
||||||
number_of_spaces: usize,
|
number_of_spaces: usize,
|
||||||
number_of_words: usize,
|
number_of_words: usize,
|
||||||
|
number_of_letter_groups: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Line {
|
impl Line {
|
||||||
|
@ -136,6 +134,11 @@ impl Line {
|
||||||
pub(crate) fn number_of_words(&self) -> usize {
|
pub(crate) fn number_of_words(&self) -> usize {
|
||||||
self.number_of_words
|
self.number_of_words
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn number_of_letter_groups(&self) -> usize {
|
||||||
|
self.number_of_letter_groups
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'pre> Iterator for Lines<'pre> {
|
impl<'pre> Iterator for Lines<'pre> {
|
||||||
|
@ -151,6 +154,7 @@ impl<'pre> Iterator for Lines<'pre> {
|
||||||
let mut additional_space_count = 0;
|
let mut additional_space_count = 0;
|
||||||
let mut number_of_spaces = 0;
|
let mut number_of_spaces = 0;
|
||||||
let mut number_of_words = 0;
|
let mut number_of_words = 0;
|
||||||
|
let mut number_of_letter_groups = 0;
|
||||||
|
|
||||||
while let Some(next) = self.data.get(self.current_start_idx + line_idx_length) {
|
while let Some(next) = self.data.get(self.current_start_idx + line_idx_length) {
|
||||||
match next {
|
match next {
|
||||||
|
@ -161,6 +165,7 @@ impl<'pre> Iterator for Lines<'pre> {
|
||||||
if width + current_line_width + additional_space_width > self.layout_width {
|
if width + current_line_width + additional_space_width > self.layout_width {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
number_of_letter_groups += word.number_of_sprites.get() as usize;
|
||||||
number_of_words += 1;
|
number_of_words += 1;
|
||||||
current_line_width += width + additional_space_width;
|
current_line_width += width + additional_space_width;
|
||||||
number_of_spaces += additional_space_count;
|
number_of_spaces += additional_space_count;
|
||||||
|
@ -186,6 +191,7 @@ impl<'pre> Iterator for Lines<'pre> {
|
||||||
number_of_text_elements: line_idx_length,
|
number_of_text_elements: line_idx_length,
|
||||||
number_of_spaces,
|
number_of_spaces,
|
||||||
number_of_words,
|
number_of_words,
|
||||||
|
number_of_letter_groups,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,6 +206,13 @@ impl Preprocessed {
|
||||||
.add_character(font, c, sprite_width, &mut self.widths);
|
.add_character(font, c, sprite_width, &mut self.widths);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn pop(&mut self, line: &Line) {
|
||||||
|
let elements = line.number_of_text_elements();
|
||||||
|
for _ in 0..elements {
|
||||||
|
self.widths.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn lines(&self, layout_width: i32, minimum_space_width: i32) -> Lines<'_> {
|
pub(crate) fn lines(&self, layout_width: i32, minimum_space_width: i32) -> Lines<'_> {
|
||||||
Lines {
|
Lines {
|
||||||
minimum_space_width,
|
minimum_space_width,
|
||||||
|
@ -213,11 +226,12 @@ impl Preprocessed {
|
||||||
&self,
|
&self,
|
||||||
layout_width: i32,
|
layout_width: i32,
|
||||||
minimum_space_width: i32,
|
minimum_space_width: i32,
|
||||||
) -> impl Iterator<Item = (Line, &[PreprocessedElement])> {
|
) -> impl Iterator<Item = (Line, impl Iterator<Item = PreprocessedElement> + '_)> {
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
self.lines(layout_width, minimum_space_width).map(move |x| {
|
self.lines(layout_width, minimum_space_width).map(move |x| {
|
||||||
let length = x.number_of_text_elements;
|
let length = x.number_of_text_elements;
|
||||||
let d = &self.widths[idx..(idx + length)];
|
|
||||||
|
let d = self.widths.range(idx..(idx + length)).copied();
|
||||||
idx += length;
|
idx += length;
|
||||||
(x, d)
|
(x, d)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue