From 48067c3eef6025fc9ee5dcb8913f82809de86083 Mon Sep 17 00:00:00 2001
From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com>
Date: Thu, 30 Oct 2025 14:44:41 -0600
Subject: [PATCH] Add XML Parsing
---
Cargo.toml | 1 +
pages/main.xml | 2 +
src/app/mod.rs | 6 +-
src/lib.rs | 2 +
src/parser/interpreter.rs | 13 ++++
src/parser/mod.rs | 20 ++++++
src/parser/parser.rs | 112 +++++++++++++++++++++++++++++++++
src/render/buffer.rs | 18 +++---
src/views/color_rect_view.rs | 25 +++++++-
src/views/constraint_layout.rs | 70 +++++++++++++--------
src/views/mod.rs | 38 ++++++++---
src/views/text_view.rs | 7 +++
src/views/vertical_layout.rs | 6 ++
13 files changed, 274 insertions(+), 46 deletions(-)
create mode 100644 pages/main.xml
create mode 100644 src/parser/interpreter.rs
create mode 100644 src/parser/mod.rs
create mode 100644 src/parser/parser.rs
diff --git a/Cargo.toml b/Cargo.toml
index dea5b48..b12683c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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']}
diff --git a/pages/main.xml b/pages/main.xml
new file mode 100644
index 0000000..b4a58eb
--- /dev/null
+++ b/pages/main.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 3da75e1..cb21fb3 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -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,
diff --git a/src/lib.rs b/src/lib.rs
index 572308b..3c351fc 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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,
};
diff --git a/src/parser/interpreter.rs b/src/parser/interpreter.rs
new file mode 100644
index 0000000..b073eab
--- /dev/null
+++ b/src/parser/interpreter.rs
@@ -0,0 +1,13 @@
+use crate::{
+ parser::Tag,
+ views::{ColorRectView, ConstraintLayout, View},
+};
+
+pub fn interpret_tag(tag: Tag) -> Box {
+ 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),
+ }
+}
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
new file mode 100644
index 0000000..100a2cd
--- /dev/null
+++ b/src/parser/mod.rs
@@ -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,
+ attributes: HashMap,
+}
+
+pub fn parse(xml: &str) -> Box {
+ let tag = parser::parse_xml(xml);
+ interpreter::interpret_tag(tag)
+}
diff --git a/src/parser/parser.rs b/src/parser/parser.rs
new file mode 100644
index 0000000..6006a3b
--- /dev/null
+++ b/src/parser/parser.rs
@@ -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::>();
+
+ 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::>();
+
+ Tag {
+ name,
+ children: Vec::new(),
+ attributes,
+ }
+}
diff --git a/src/render/buffer.rs b/src/render/buffer.rs
index f75c8e7..dff9cd5 100644
--- a/src/render/buffer.rs
+++ b/src/render/buffer.rs
@@ -1,4 +1,4 @@
-// use crate::render::rand;
+use crate::render::rand;
pub struct ImgBuffer {
pub data: Vec,
@@ -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();
diff --git a/src/views/color_rect_view.rs b/src/views/color_rect_view.rs
index b3de4ec..54e9344 100644
--- a/src/views/color_rect_view.rs
+++ b/src/views/color_rect_view.rs
@@ -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) -> 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 {
diff --git a/src/views/constraint_layout.rs b/src/views/constraint_layout.rs
index 44d8213..903016d 100644
--- a/src/views/constraint_layout.rs
+++ b/src/views/constraint_layout.rs
@@ -96,46 +96,55 @@ impl ConstraintLayout {
crate::views::Bounds::Pixels(x) => x,
};
- let y_align = |constraint: &Option| 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, 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| 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, 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) -> Self
+ where
+ Self: Sized,
+ {
+ todo!()
+ }
}
#[derive(Clone, Copy, PartialEq)]
diff --git a/src/views/mod.rs b/src/views/mod.rs
index d1c2ab4..f04d57e 100644
--- a/src/views/mod.rs
+++ b/src/views/mod.rs
@@ -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) -> Self
+ where
+ Self: Sized;
}
#[derive(Debug, Clone, Copy, PartialEq)]
@@ -82,16 +89,30 @@ pub fn default_view() -> Box {
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 {
Box::new(d),
Box::new(center),
Box::new(center_top),
+ Box::new(inside_top),
]))
// Box::new(VerticalLayout::new(vec![
diff --git a/src/views/text_view.rs b/src/views/text_view.rs
index de439fe..d872259 100644
--- a/src/views/text_view.rs
+++ b/src/views/text_view.rs
@@ -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) -> Self
+ where
+ Self: Sized,
+ {
+ todo!()
+ }
}
impl LayoutView for TextView {
diff --git a/src/views/vertical_layout.rs b/src/views/vertical_layout.rs
index 5cb1bd4..e788404 100644
--- a/src/views/vertical_layout.rs
+++ b/src/views/vertical_layout.rs
@@ -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) -> Self
+ where
+ Self: Sized,
+ {
+ todo!()
+ }
}
impl LayoutView for VerticalLayout {