Add XML Parsing

This commit is contained in:
Michael Mikovsky
2025-10-30 14:44:41 -06:00
parent 2cbf0fcab2
commit 48067c3eef
13 changed files with 274 additions and 46 deletions
+1
View File
@@ -9,6 +9,7 @@ crate-type = ["cdylib"]
[dependencies]
fontdue = "0.9.3"
lazy_static = "1.5.0"
quick-xml = "0.38.3"
wasm-bindgen = {version = "0.2.104"}
web-sys = {version = "0.3.81", features = ['CanvasRenderingContext2d', 'Document', 'Element', 'HtmlCanvasElement', 'Window', 'ImageData']}
+2
View File
@@ -0,0 +1,2 @@
<ColoredRectView r="20" g="40" b="60" />
<!-- </ColoredRectView> -->
+5 -1
View File
@@ -5,6 +5,7 @@ pub use cursors::{Cursor, set_cursor};
use wasm_bindgen::prelude::wasm_bindgen;
use crate::{
parser::{self, TEST_XML},
render::Renderer,
views::{View, default_view},
};
@@ -36,8 +37,10 @@ impl App {
pub fn new(renderer: Renderer) -> Self {
let (width, height) = (renderer.actual_width, renderer.actual_height);
let root_view = parser::parse(TEST_XML);
let mut this = App {
root_view: Some(default_view()),
root_view: Some(root_view),
renderer,
// current_activity: Some(0),
state: AppState::new(),
@@ -49,6 +52,7 @@ impl App {
}
pub fn draw(&mut self) {
// self.renderer.img.randomize(&mut self.renderer.rand);
if let Some(view) = &mut self.root_view {
let (width, height) = (
self.renderer.actual_width.clone() as f32,
+2
View File
@@ -1,5 +1,6 @@
mod app;
mod fonts;
mod parser;
mod render;
mod views;
@@ -12,6 +13,7 @@ use render::rand::Rnd;
use crate::{
app::{App, Cursor, set_cursor},
parser::TEST_XML,
render::Renderer,
};
+13
View File
@@ -0,0 +1,13 @@
use crate::{
parser::Tag,
views::{ColorRectView, ConstraintLayout, View},
};
pub fn interpret_tag(tag: Tag) -> Box<dyn View> {
match tag.name.as_str() {
"ColoredRectView" => Box::new(ColorRectView::from_tag(&tag.attributes)),
"ConstraintLayout" => Box::new(ConstraintLayout::from_tag(&tag.attributes)),
_ => panic!("Unknown tag: {}", tag.name),
}
}
+20
View File
@@ -0,0 +1,20 @@
use std::collections::HashMap;
use crate::views::View;
mod interpreter;
mod parser;
pub const TEST_XML: &'static str = include_str!("../../pages/main.xml");
#[derive(Debug)]
struct Tag {
name: String,
children: Vec<Tag>,
attributes: HashMap<String, String>,
}
pub fn parse(xml: &str) -> Box<dyn View> {
let tag = parser::parse_xml(xml);
interpreter::interpret_tag(tag)
}
+112
View File
@@ -0,0 +1,112 @@
use std::collections::HashMap;
use quick_xml::{
Reader,
events::{BytesStart, Event},
};
use crate::{log, parser::Tag};
pub fn parse_xml(xml: &str) -> Tag {
let mut reader = Reader::from_str(xml);
reader.config_mut().trim_text(true);
// let mut count = 0;
// let mut txt = Vec::new();
let mut buf = Vec::new();
let mut tag = None;
loop {
let event = reader.read_event_into(&mut buf);
match event {
Err(e) => panic!("Error at position {}: {:?}", reader.error_position(), e),
Ok(Event::Eof) => break,
Ok(Event::Start(e)) => {
tag = Some(parse_tag(e, &mut reader));
}
Ok(Event::Empty(e)) => {
tag = Some(parse_tag_empty(e));
}
Ok(Event::End(_)) => {
break;
}
_ => {}
};
buf.clear();
}
if tag.is_none() {
log!("No root tag found");
}
tag.unwrap()
}
fn parse_tag(event: BytesStart<'_>, reader: &mut Reader<&[u8]>) -> Tag {
let mut children = Vec::new();
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Err(e) => panic!("Error at position {}: {:?}", reader.error_position(), e),
// exits the loop when reaching end of file
Ok(Event::Eof) => panic!("Unexpected end of file"),
Ok(Event::Start(e)) => {
children.push(parse_tag(e, reader));
}
Ok(Event::Empty(e)) => {
children.push(parse_tag_empty(e));
}
Ok(Event::End(e)) => {
println!("End: {:?}", e);
break;
}
_ => (),
}
buf.clear();
}
let name = String::from_utf8_lossy(event.name().as_ref()).to_string();
let attributes = event
.attributes()
.map(|a| {
let a = a.unwrap();
(
String::from_utf8_lossy(a.key.as_ref()).to_string(),
String::from_utf8_lossy(a.value.as_ref()).to_string(),
)
})
.collect::<HashMap<_, _>>();
Tag {
name,
children,
attributes,
}
}
fn parse_tag_empty(event: BytesStart<'_>) -> Tag {
let name = String::from_utf8_lossy(event.name().as_ref()).to_string();
let attributes = event
.attributes()
.map(|a| {
let a = a.unwrap();
(
String::from_utf8_lossy(a.key.as_ref()).to_string(),
String::from_utf8_lossy(a.value.as_ref()).to_string(),
)
})
.collect::<HashMap<_, _>>();
Tag {
name,
children: Vec::new(),
attributes,
}
}
+9 -9
View File
@@ -1,4 +1,4 @@
// use crate::render::rand;
use crate::render::rand;
pub struct ImgBuffer {
pub data: Vec<u8>,
@@ -15,14 +15,14 @@ impl ImgBuffer {
}
}
// pub fn randomize(&mut self, rand: &mut rand::Rnd) {
// for pixel in self.data.chunks_mut(4) {
// pixel[0] = rand.next_i32() as u8;
// pixel[1] = pixel[0];
// pixel[2] = pixel[0];
// pixel[3] = 255;
// }
// }
pub fn randomize(&mut self, rand: &mut rand::Rnd) {
for pixel in self.data.chunks_mut(4) {
pixel[0] = rand.next_i32() as u8;
pixel[1] = pixel[0];
pixel[2] = pixel[0];
pixel[3] = 255;
}
}
pub fn overlay_bitmap(&mut self, other: &Bitmap, xoffset: usize, yoffset: usize) {
let length = self.data.len();
+24 -1
View File
@@ -45,7 +45,30 @@ impl View for ColorRectView {
// log!("Draw");
}
fn resize(&mut self, x: f32, y: f32, w: f32, h: f32) {}
fn resize(&mut self, _x: f32, _y: f32, _w: f32, _h: f32) {}
fn from_tag(attributes: &std::collections::HashMap<String, String>) -> Self
where
Self: Sized,
{
let r = attributes
.get("r")
.unwrap_or(&"0".to_string())
.parse()
.unwrap();
let g = attributes
.get("g")
.unwrap_or(&"0".to_string())
.parse()
.unwrap();
let b = attributes
.get("b")
.unwrap_or(&"0".to_string())
.parse()
.unwrap();
Self::new(r, g, b)
}
}
impl LayoutView for ColorRectView {
+43 -27
View File
@@ -96,46 +96,55 @@ impl ConstraintLayout {
crate::views::Bounds::Pixels(x) => x,
};
let y_align = |constraint: &Option<Constraint>| match constraint {
Some(Constraint::TopParent(margin)) => Some(self_top + margin),
Some(Constraint::BottomParent(margin)) => Some(self_bottom - margin),
Some(Constraint::Top(margin, id)) => {
let other = calculated_positions.get(&id).unwrap();
Some(other.y - margin)
}
let y_align = |constraint: &Option<Constraint>, margin_reverse: bool| {
let margin_mult = if margin_reverse { -1. } else { 1. };
match constraint {
Some(Constraint::TopParent(margin)) => Some(self_top + margin * margin_mult),
Some(Constraint::BottomParent(margin)) => {
Some(self_bottom + margin * margin_mult)
}
Some(Constraint::Top(margin, id)) => {
let other = calculated_positions.get(&id).unwrap();
Some(other.y + margin * margin_mult)
}
Some(Constraint::Bottom(margin, id)) => {
let other = calculated_positions.get(&id).unwrap();
Some(other.y + other.height + margin)
Some(Constraint::Bottom(margin, id)) => {
let other = calculated_positions.get(&id).unwrap();
Some(other.y + other.height + margin * margin_mult)
}
None => None,
_ => unreachable!(),
}
None => None,
_ => unreachable!(),
};
let y = match (y_align(c_top), y_align(c_bottom)) {
let y = match (y_align(c_top, false), y_align(c_bottom, true)) {
(Some(top), Some(bottom)) => (bottom + top - c_height) / 2.,
(Some(top), None) => top,
(None, Some(bottom)) => bottom - c_height,
(None, None) => unreachable!("Vertically unconstrained"),
};
let x_align = |constraint: &Option<Constraint>| match constraint {
Some(Constraint::LeftParent(margin)) => Some(self_left + margin),
Some(Constraint::RightParent(margin)) => Some(self_right - margin),
Some(Constraint::Left(margin, id)) => {
let other = calculated_positions.get(&id).unwrap();
Some(other.x - margin)
let x_align = |constraint: &Option<Constraint>, margin_reverse: bool| {
let margin_mult = if margin_reverse { -1. } else { 1. };
match constraint {
Some(Constraint::LeftParent(margin)) => Some(self_left + margin * margin_mult),
Some(Constraint::RightParent(margin)) => {
Some(self_right + margin * margin_mult)
}
Some(Constraint::Left(margin, id)) => {
let other = calculated_positions.get(&id).unwrap();
Some(other.x + margin * margin_mult)
}
Some(Constraint::Right(margin, id)) => {
let other = calculated_positions.get(&id).unwrap();
Some(other.x + other.width + margin * margin_mult)
}
None => None,
_ => unreachable!(),
}
Some(Constraint::Right(margin, id)) => {
let other = calculated_positions.get(&id).unwrap();
Some(other.x + other.width - margin)
}
None => None,
_ => unreachable!(),
};
let x = match (x_align(c_left), x_align(c_right)) {
let x = match (x_align(c_left, false), x_align(c_right, true)) {
(Some(left), Some(right)) => (right + left - c_width) / 2.,
(Some(left), None) => left,
(None, Some(right)) => right - c_width,
@@ -170,6 +179,13 @@ impl View for ConstraintLayout {
fn resize(&mut self, x: f32, y: f32, w: f32, h: f32) {
self.recalculate_positions(x, y, w, h);
}
fn from_tag(attributes: &std::collections::HashMap<String, String>) -> Self
where
Self: Sized,
{
todo!()
}
}
#[derive(Clone, Copy, PartialEq)]
+30 -8
View File
@@ -1,6 +1,8 @@
use std::collections::HashMap;
use crate::{
render::Renderer,
views::constraint_layout::{Constraint, ConstraintLayout, ConstraintView},
views::constraint_layout::{Constraint, ConstraintView},
};
mod color_rect_view;
@@ -8,13 +10,18 @@ mod constraint_layout;
mod text_view;
mod vertical_layout;
use color_rect_view::ColorRectView;
use text_view::TextView;
use vertical_layout::VerticalLayout;
pub use color_rect_view::ColorRectView;
pub use constraint_layout::ConstraintLayout;
pub use text_view::TextView;
pub use vertical_layout::VerticalLayout;
pub trait View {
fn draw(&mut self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32);
fn resize(&mut self, x: f32, y: f32, w: f32, h: f32);
fn from_tag(attributes: &HashMap<String, String>) -> Self
where
Self: Sized;
}
#[derive(Debug, Clone, Copy, PartialEq)]
@@ -82,16 +89,30 @@ pub fn default_view() -> Box<dyn View> {
Some(Constraint::BottomParent(temp_margin)),
);
center.set_bounds(Some(Bounds::Pixels(350.)), Some(Bounds::Pixels(100.)));
center.set_bounds(Some(Bounds::Pixels(350.)), Some(Bounds::Pixels(350.)));
let mut center_top = ColorRectView::new(127, 0, 127);
center_top.set_constraints(
None,
Some(Constraint::Bottom(0., 4)),
// None,
// Some(Constraint::Left(0., 4)),
None,
Some(Constraint::Right(0., 4)),
Some(Constraint::Top(20., 4)),
// None,
Some(Constraint::Left(10., 4)),
Some(Constraint::Top(0., 4)),
);
let mut inside_top = ColorRectView::new(127, 0, 127);
inside_top.set_constraints(
Some(Constraint::Bottom(0., 4)),
// None,
// Some(Constraint::Left(0., 4)),
None,
// None,
Some(Constraint::Right(10., 4)),
Some(Constraint::Top(0., 4)),
);
Box::new(ConstraintLayout::new(vec![
@@ -101,6 +122,7 @@ pub fn default_view() -> Box<dyn View> {
Box::new(d),
Box::new(center),
Box::new(center_top),
Box::new(inside_top),
]))
// Box::new(VerticalLayout::new(vec![
+7
View File
@@ -78,6 +78,13 @@ impl View for TextView {
fn resize(&mut self, x: f32, y: f32, w: f32, h: f32) {
// todo!()
}
fn from_tag(attributes: &std::collections::HashMap<String, String>) -> Self
where
Self: Sized,
{
todo!()
}
}
impl LayoutView for TextView {
+6
View File
@@ -42,6 +42,12 @@ impl View for VerticalLayout {
}
}
fn resize(&mut self, x: f32, y: f32, w: f32, h: f32) {}
fn from_tag(attributes: &std::collections::HashMap<String, String>) -> Self
where
Self: Sized,
{
todo!()
}
}
impl LayoutView for VerticalLayout {