mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 08:11:33 +11:00
somewhat mad left align renderer
This commit is contained in:
parent
b75303863d
commit
ce7bcacb3c
|
@ -8,11 +8,12 @@ use agb::{
|
||||||
PaletteVram, Size,
|
PaletteVram, Size,
|
||||||
},
|
},
|
||||||
palette16::Palette16,
|
palette16::Palette16,
|
||||||
Font,
|
Font, WIDTH,
|
||||||
},
|
},
|
||||||
include_font,
|
include_font,
|
||||||
input::Button,
|
input::Button,
|
||||||
};
|
};
|
||||||
|
use agb_fixnum::Rect;
|
||||||
|
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
|
|
||||||
|
@ -31,12 +32,12 @@ 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 config = Configuration::new(Size::S32x16, palette);
|
let config = Configuration::new(Size::S16x16, palette);
|
||||||
|
|
||||||
let mut wr = BufferedWordRender::new(&FONT, config);
|
let mut wr = BufferedWordRender::new(&FONT, config);
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
wr,
|
wr,
|
||||||
"Hello there!\nI spent this weekend\nwriting this text system!\nIs it any good?\n\nOh, by the way, you can\npress A to restart!"
|
"Hello there!\nI spent this weekend writing this text system! Is it any good?\n\nOh, by the way, you can press A to restart!"
|
||||||
);
|
);
|
||||||
|
|
||||||
let vblank = agb::interrupt::VBlank::get();
|
let vblank = agb::interrupt::VBlank::get();
|
||||||
|
@ -46,7 +47,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
let mut timer: agb::timer::Timer = timer.timer2;
|
let mut timer: agb::timer::Timer = timer.timer2;
|
||||||
|
|
||||||
timer.set_enabled(true);
|
timer.set_enabled(true);
|
||||||
timer.set_divider(agb::timer::Divider::Divider64);
|
timer.set_divider(agb::timer::Divider::Divider256);
|
||||||
|
|
||||||
let mut num_letters = 0;
|
let mut num_letters = 0;
|
||||||
let mut frame = 0;
|
let mut frame = 0;
|
||||||
|
@ -54,20 +55,21 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
loop {
|
loop {
|
||||||
vblank.wait_for_vblank();
|
vblank.wait_for_vblank();
|
||||||
input.update();
|
input.update();
|
||||||
let oam_frmae = &mut unmanaged.iter();
|
let oam = &mut unmanaged.iter();
|
||||||
|
wr.commit(oam);
|
||||||
|
|
||||||
let start = timer.value();
|
let start = timer.value();
|
||||||
wr.draw_partial(oam_frmae, (0, 0).into(), num_letters);
|
wr.update(Rect::new((0, 0).into(), (WIDTH, 100).into()), num_letters);
|
||||||
|
wr.process();
|
||||||
let end = timer.value();
|
let end = timer.value();
|
||||||
|
|
||||||
agb::println!("Took {} cycles", 64 * (end.wrapping_sub(start) as u32));
|
agb::println!("Took {} cycles", 256 * (end.wrapping_sub(start) as u32));
|
||||||
|
|
||||||
frame += 1;
|
frame += 1;
|
||||||
|
|
||||||
if frame % 4 == 0 {
|
if frame % 4 == 0 {
|
||||||
num_letters += 1;
|
num_letters += 1;
|
||||||
}
|
}
|
||||||
wr.process();
|
|
||||||
|
|
||||||
if input.is_just_pressed(Button::A) {
|
if input.is_just_pressed(Button::A) {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use core::fmt::Write;
|
use core::{cell::Cell, fmt::Write, num::NonZeroUsize};
|
||||||
|
|
||||||
use agb_fixnum::Vector2D;
|
use agb_fixnum::{Rect, Vector2D};
|
||||||
use alloc::{collections::VecDeque, vec::Vec};
|
use alloc::{collections::VecDeque, vec::Vec};
|
||||||
|
|
||||||
use crate::display::{object::ObjectUnmanaged, Font};
|
use crate::display::{object::ObjectUnmanaged, Font};
|
||||||
|
@ -19,81 +19,208 @@ enum WhiteSpace {
|
||||||
NewLine,
|
NewLine,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TextElement {
|
enum TextElementReference<'text> {
|
||||||
LetterGroup(LetterGroup),
|
Word(Word<'text>),
|
||||||
WhiteSpace(WhiteSpace),
|
WhiteSpace(WhiteSpace),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
struct Word<'letters> {
|
||||||
mod tests {
|
letters: &'letters [LetterGroup],
|
||||||
use super::*;
|
width: Cell<Option<NonZeroUsize>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[test_case]
|
impl<'letters> Word<'letters> {
|
||||||
fn check_size_of_text_element_is_expected(_: &mut crate::Gba) {
|
fn new(letters: &'letters [LetterGroup]) -> Self {
|
||||||
assert_eq!(
|
Self {
|
||||||
core::mem::size_of::<TextElement>(),
|
letters,
|
||||||
core::mem::size_of::<LetterGroup>()
|
width: Cell::new(None),
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TextBlock {
|
impl Word<'_> {
|
||||||
elements: Vec<TextElement>,
|
fn width(&self) -> usize {
|
||||||
cache: CachedRender,
|
match self.width.get() {
|
||||||
|
Some(width) => width.get(),
|
||||||
|
None => {
|
||||||
|
let width = self.letters.iter().fold(0, |acc, letter| {
|
||||||
|
acc + (letter.width as i32 + letter.left as i32)
|
||||||
|
});
|
||||||
|
let width = width as usize;
|
||||||
|
|
||||||
|
self.width.set(NonZeroUsize::new(width));
|
||||||
|
width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CachedRender {
|
#[derive(Clone, Copy)]
|
||||||
|
struct WordLength(u8);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum Element {
|
||||||
|
Word(u8),
|
||||||
|
NewLine,
|
||||||
|
Space,
|
||||||
|
}
|
||||||
|
const NEW_LINE: u8 = 0xFF;
|
||||||
|
const SPACE: u8 = 0xFE;
|
||||||
|
impl WordLength {
|
||||||
|
fn parse(self) -> Element {
|
||||||
|
if self.0 == NEW_LINE {
|
||||||
|
Element::NewLine
|
||||||
|
} else if self.0 == SPACE {
|
||||||
|
Element::Space
|
||||||
|
} else {
|
||||||
|
Element::Word(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_element(e: Element) -> Self {
|
||||||
|
WordLength(match e {
|
||||||
|
Element::Word(len) => len,
|
||||||
|
Element::NewLine => NEW_LINE,
|
||||||
|
Element::Space => SPACE,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Letters(Vec<LetterGroup>);
|
||||||
|
struct Words {
|
||||||
|
letters: Letters,
|
||||||
|
word_lengths: Vec<WordLength>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WordRenderCache {
|
||||||
objects: Vec<ObjectUnmanaged>,
|
objects: Vec<ObjectUnmanaged>,
|
||||||
up_to: usize,
|
state: WordRenderCacheState,
|
||||||
head_position: Vector2D<i32>,
|
poison_condition: WordRenderPoisonCondition,
|
||||||
origin: Vector2D<i32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextBlock {
|
struct WordRenderPoisonCondition {
|
||||||
fn reset_cache(&mut self, position: Vector2D<i32>) {
|
area: Rect<i32>,
|
||||||
self.cache.objects.clear();
|
}
|
||||||
self.cache.up_to = 0;
|
|
||||||
self.cache.head_position = position;
|
|
||||||
self.cache.origin = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_cache(&mut self, up_to: usize) {
|
struct WordRenderCacheState {
|
||||||
let mut head_position = self.cache.head_position;
|
depth_in_word_iterator: usize,
|
||||||
|
depth_in_word: usize,
|
||||||
|
depth_in_elements: usize,
|
||||||
|
head_position: Vector2D<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
for element in self.elements.iter().take(up_to).skip(self.cache.up_to) {
|
impl WordRenderCache {
|
||||||
match element {
|
fn new() -> Self {
|
||||||
TextElement::LetterGroup(group) => {
|
WordRenderCache {
|
||||||
let mut object = ObjectUnmanaged::new(group.sprite.clone());
|
objects: Vec::new(),
|
||||||
object.show();
|
state: WordRenderCacheState {
|
||||||
head_position.x += group.left as i32;
|
depth_in_word_iterator: 0,
|
||||||
object.set_position(head_position);
|
depth_in_word: 0,
|
||||||
head_position.x += group.width as i32;
|
depth_in_elements: 0,
|
||||||
self.cache.objects.push(object);
|
head_position: (0, 0).into(),
|
||||||
}
|
},
|
||||||
TextElement::WhiteSpace(white) => match white {
|
poison_condition: WordRenderPoisonCondition {
|
||||||
WhiteSpace::Space => head_position.x += 10,
|
area: Rect::new((0, 0).into(), (0, 0).into()),
|
||||||
WhiteSpace::NewLine => {
|
|
||||||
head_position.x = self.cache.origin.x;
|
|
||||||
head_position.y += 15;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cache.head_position = head_position;
|
fn reset_state(&mut self, position: Rect<i32>) {
|
||||||
self.cache.up_to = up_to.min(self.elements.len());
|
self.state = WordRenderCacheState {
|
||||||
|
depth_in_elements: 0,
|
||||||
|
depth_in_word: 0,
|
||||||
|
depth_in_word_iterator: 0,
|
||||||
|
head_position: position.position,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.poison_condition = WordRenderPoisonCondition { area: position };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&mut self, oam: &mut OamIterator, position: Vector2D<i32>, up_to: usize) {
|
fn generate_cache(&mut self, words: &Words, desired_element_count: usize) {
|
||||||
if position != self.cache.origin {
|
let position = self.poison_condition.area;
|
||||||
self.reset_cache(position);
|
if self.state.depth_in_elements >= desired_element_count {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.generate_cache(up_to);
|
'outer: for elem in words.iter_words().skip(self.state.depth_in_word_iterator) {
|
||||||
|
match elem {
|
||||||
|
TextElementReference::Word(word) => {
|
||||||
|
let prospective_x = self.state.head_position.x + word.width() as i32;
|
||||||
|
|
||||||
for (obj, slot) in self.cache.objects.iter().zip(oam) {
|
if self.state.depth_in_word == 0
|
||||||
slot.set(obj);
|
&& prospective_x > position.position.x + position.size.x
|
||||||
|
{
|
||||||
|
self.state.head_position.x = position.position.x;
|
||||||
|
self.state.head_position.y += 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for letter in word.letters.iter().skip(self.state.depth_in_word) {
|
||||||
|
self.state.head_position.x += letter.left as i32;
|
||||||
|
let mut object = ObjectUnmanaged::new(letter.sprite.clone());
|
||||||
|
object.show();
|
||||||
|
object.set_position(self.state.head_position);
|
||||||
|
|
||||||
|
self.objects.push(object);
|
||||||
|
|
||||||
|
self.state.head_position.x += letter.width as i32;
|
||||||
|
|
||||||
|
self.state.depth_in_elements += 1;
|
||||||
|
self.state.depth_in_word += 1;
|
||||||
|
if self.state.depth_in_elements >= desired_element_count {
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state.depth_in_word = 0;
|
||||||
|
}
|
||||||
|
TextElementReference::WhiteSpace(space) => {
|
||||||
|
match space {
|
||||||
|
WhiteSpace::Space => self.state.head_position.x += 10,
|
||||||
|
WhiteSpace::NewLine => {
|
||||||
|
self.state.head_position.x = position.position.x;
|
||||||
|
self.state.head_position.y += 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.state.depth_in_elements += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.state.depth_in_word_iterator += 1;
|
||||||
|
if self.state.depth_in_elements >= desired_element_count {
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, words: &Words, desired_element_count: usize, position: Rect<i32>) {
|
||||||
|
if self.poison_condition.area != position {
|
||||||
|
self.reset_state(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.generate_cache(words, desired_element_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit(&self, oam: &mut OamIterator) {
|
||||||
|
for (object, slot) in self.objects.iter().zip(oam) {
|
||||||
|
slot.set(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Words {
|
||||||
|
fn iter_words(&self) -> impl Iterator<Item = TextElementReference> {
|
||||||
|
let mut letters_idx: usize = 0;
|
||||||
|
|
||||||
|
self.word_lengths.iter().map(move |x| match x.parse() {
|
||||||
|
Element::Word(length) => {
|
||||||
|
let idx = letters_idx;
|
||||||
|
let end_idx = idx + length as usize;
|
||||||
|
letters_idx = end_idx;
|
||||||
|
|
||||||
|
TextElementReference::Word(Word::new(&self.letters.0[idx..end_idx]))
|
||||||
|
}
|
||||||
|
Element::NewLine => TextElementReference::WhiteSpace(WhiteSpace::NewLine),
|
||||||
|
Element::Space => TextElementReference::WhiteSpace(WhiteSpace::Space),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,8 +265,11 @@ impl Configuration {
|
||||||
|
|
||||||
pub struct BufferedWordRender<'font> {
|
pub struct BufferedWordRender<'font> {
|
||||||
word_render: WordRender<'font>,
|
word_render: WordRender<'font>,
|
||||||
block: TextBlock,
|
block: Words,
|
||||||
|
current_word_length: usize,
|
||||||
|
number_of_elements: usize,
|
||||||
buffered_chars: VecDeque<char>,
|
buffered_chars: VecDeque<char>,
|
||||||
|
cache: WordRenderCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Write for BufferedWordRender<'_> {
|
impl Write for BufferedWordRender<'_> {
|
||||||
|
@ -157,16 +287,14 @@ impl<'font> BufferedWordRender<'font> {
|
||||||
pub fn new(font: &'font Font, config: Configuration) -> Self {
|
pub fn new(font: &'font Font, config: Configuration) -> Self {
|
||||||
BufferedWordRender {
|
BufferedWordRender {
|
||||||
word_render: WordRender::new(font, config),
|
word_render: WordRender::new(font, config),
|
||||||
block: TextBlock {
|
block: Words {
|
||||||
elements: Vec::new(),
|
letters: Letters(Vec::new()),
|
||||||
cache: CachedRender {
|
word_lengths: Vec::new(),
|
||||||
objects: Vec::new(),
|
|
||||||
up_to: 0,
|
|
||||||
head_position: (0, 0).into(),
|
|
||||||
origin: (0, 0).into(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
current_word_length: 0,
|
||||||
|
number_of_elements: 0,
|
||||||
buffered_chars: VecDeque::new(),
|
buffered_chars: VecDeque::new(),
|
||||||
|
cache: WordRenderCache::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,43 +304,51 @@ impl BufferedWordRender<'_> {
|
||||||
if let Some(char) = self.buffered_chars.pop_front() {
|
if let Some(char) = self.buffered_chars.pop_front() {
|
||||||
if char == '\n' {
|
if char == '\n' {
|
||||||
if let Some(group) = self.word_render.finalise_letter() {
|
if let Some(group) = self.word_render.finalise_letter() {
|
||||||
self.block.elements.push(TextElement::LetterGroup(group));
|
self.block.letters.0.push(group);
|
||||||
|
self.current_word_length += 1;
|
||||||
}
|
}
|
||||||
self.block
|
self.block
|
||||||
.elements
|
.word_lengths
|
||||||
.push(TextElement::WhiteSpace(WhiteSpace::NewLine));
|
.push(WordLength::from_element(Element::Word(
|
||||||
|
self.current_word_length as u8,
|
||||||
|
)));
|
||||||
|
self.block
|
||||||
|
.word_lengths
|
||||||
|
.push(WordLength::from_element(Element::NewLine));
|
||||||
|
self.number_of_elements += self.current_word_length + 1;
|
||||||
|
self.current_word_length = 0;
|
||||||
} else if char == ' ' {
|
} else if char == ' ' {
|
||||||
if let Some(group) = self.word_render.finalise_letter() {
|
if let Some(group) = self.word_render.finalise_letter() {
|
||||||
self.block.elements.push(TextElement::LetterGroup(group));
|
self.block.letters.0.push(group);
|
||||||
|
self.current_word_length += 1;
|
||||||
}
|
}
|
||||||
self.block
|
self.block
|
||||||
.elements
|
.word_lengths
|
||||||
.push(TextElement::WhiteSpace(WhiteSpace::Space));
|
.push(WordLength::from_element(Element::Word(
|
||||||
|
self.current_word_length as u8,
|
||||||
|
)));
|
||||||
|
self.block
|
||||||
|
.word_lengths
|
||||||
|
.push(WordLength::from_element(Element::Space));
|
||||||
|
self.number_of_elements += self.current_word_length + 1;
|
||||||
|
self.current_word_length = 0;
|
||||||
} else if let Some(group) = self.word_render.render_char(char) {
|
} else if let Some(group) = self.word_render.render_char(char) {
|
||||||
self.block.elements.push(TextElement::LetterGroup(group));
|
self.block.letters.0.push(group);
|
||||||
|
self.current_word_length += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_partial(
|
pub fn update(&mut self, position: Rect<i32>, number_of_elements: usize) {
|
||||||
&mut self,
|
while !self.buffered_chars.is_empty() && self.number_of_elements < number_of_elements {
|
||||||
oam: &mut OamIterator,
|
|
||||||
position: Vector2D<i32>,
|
|
||||||
num_groups: usize,
|
|
||||||
) {
|
|
||||||
while self.block.elements.len() < num_groups && !self.buffered_chars.is_empty() {
|
|
||||||
self.process();
|
self.process();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.block.draw(oam, position, num_groups);
|
self.cache.update(&self.block, number_of_elements, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, oam: &mut OamIterator, position: Vector2D<i32>) {
|
pub fn commit(&self, oam: &mut OamIterator) {
|
||||||
while !self.buffered_chars.is_empty() {
|
self.cache.commit(oam);
|
||||||
self.process();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.block.draw(oam, position, usize::MAX);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue