diff --git a/Cargo.toml b/Cargo.toml index b12683c..882ceef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,13 @@ crate-type = ["cdylib"] 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']} +wasm-bindgen = "0.2.105" +# web-sys = "0.3.82" +# 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.82", features = ['CanvasRenderingContext2d', 'Document', 'Element', 'HtmlCanvasElement', 'Window', 'ImageData']} # [dependencies.web-sys] # features = diff --git a/pages/main.xml b/pages/main.xml index 1ec8acf..c0cbd80 100644 --- a/pages/main.xml +++ b/pages/main.xml @@ -1,8 +1,4 @@ - - - - + + + + - + diff --git a/src/app/mod.rs b/src/app/mod.rs index f7f33ba..fbcb861 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -5,7 +5,7 @@ pub use cursors::{Cursor, set_cursor}; use wasm_bindgen::prelude::wasm_bindgen; use crate::{ - log, + AppErr, log, parser::{self, TEST_XML}, render::Renderer, views::View, @@ -35,7 +35,7 @@ impl AppState { } impl App { - pub fn new(renderer: Renderer) -> Self { + pub fn new(renderer: Renderer) -> Result { let (width, height) = (renderer.actual_width, renderer.actual_height); let root_view = parser::parse(TEST_XML); @@ -48,12 +48,12 @@ impl App { state: AppState::new(), }; - this.resize(width, height); + this.resize(width, height)?; - this + Ok(this) } - pub fn draw(&mut self) { + pub fn draw(&mut self) -> Result<(), AppErr> { // self.renderer.img.randomize(&mut self.renderer.rand); if let Some(view) = &mut self.root_view { let (width, height) = ( @@ -64,32 +64,39 @@ impl App { view.draw(&mut self.renderer, 0., 0., width, height); self.renderer.update(); } + + Ok(()) } } // App events impl App { - pub fn resize(&mut self, width: usize, height: usize) { + pub fn resize(&mut self, width: usize, height: usize) -> Result<(), AppErr> { self.renderer.resize(width, height); if let Some(view) = &mut self.root_view { - view.resize(0., 0., width as f32, height as f32); + view.resize(&mut self.renderer, 0., 0., width as f32, height as f32); } self.draw(); + Ok(()) } - pub fn mouse_move(&mut self, x: f32, y: f32) { + pub fn mouse_move(&mut self, x: f32, y: f32) -> Result<(), AppErr> { self.state.mouse_x = x; self.state.mouse_y = y; + Ok(()) + // if let Some(current_activity) = self.current_activity { // self.activities[current_activity].mouse_move(&mut self.renderer, &self.state); // } } - pub fn l_click(&mut self, x: f32, y: f32) { + pub fn l_click(&mut self, x: f32, y: f32) -> Result<(), AppErr> { self.state.mouse_x = x; self.state.mouse_y = y; + Ok(()) + // if let Some(current_activity) = self.current_activity { // self.activities[current_activity].l_click(&mut self.renderer, &self.state); // } diff --git a/src/lib.rs b/src/lib.rs index 3c351fc..ada5fa7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ mod parser; mod render; mod views; +use std::error::Error; + use wasm_bindgen::{Clamped, prelude::*}; use web_sys::ImageData; @@ -17,6 +19,14 @@ use crate::{ render::Renderer, }; +#[derive(Debug)] +pub enum AppErr { + ParseErr(String), + RenderErr(String), + ArrangeErr(String), + RasterErr(String), +} + #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = console)] @@ -35,28 +45,54 @@ pub fn init(canvas: &web_sys::HtmlCanvasElement, width: usize, height: usize) -> let mut renderer = Renderer::new(canvas, width, height); renderer.resize(width, height); - App::new(renderer) + match App::new(renderer) { + Ok(app) => app, + Err(e) => { + log!("Error initializing: {:?}", e); + panic!(); + } + } } #[wasm_bindgen] pub fn resize(app: &mut App, width: usize, height: usize) { - app.resize(width, height); + match app.resize(width, height) { + Ok(_) => {} + Err(e) => { + log!("Error resizing: {:?}", e); + } + } } #[wasm_bindgen] pub fn draw(app: &mut App) { // app.renderer.img.randomize(&mut app.renderer.rand); - app.draw(); + match app.draw() { + Ok(_) => {} + Err(e) => { + log!("Error drawing: {:?}", e); + } + } } #[wasm_bindgen] pub fn click(app: &mut App, x: f32, y: f32) { // ctx.img.randomize(&mut ctx.rand); // draw::draw(ctx); - app.l_click(x, y); + match app.l_click(x, y) { + Ok(_) => {} + Err(e) => { + log!("Error on click: {:?}", e); + } + } } #[wasm_bindgen] pub fn mouse_move(app: &mut App, x: f32, y: f32) { - app.mouse_move(x, y); + match app.mouse_move(x, y) { + Ok(_) => {} + Err(e) => { + log!("Error on mouse move: {:?}", e); + } + } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 401e9a6..3ef1170 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::{ log, - views::{Bounds, BoxView, ColorRectView, ConstraintLayout, View}, + views::{Bounds, BoxView, ColorRectView, ConstraintLayout, TextView, View}, }; mod parser; @@ -22,6 +22,7 @@ impl Tag { "ColoredRectView" => ColorRectView::from_tag(&self.attributes, &self.children), "ConstraintLayout" => ConstraintLayout::from_tag(&self.attributes, &self.children), "BoxView" => BoxView::from_tag(&self.attributes, &self.children), + "TextView" => TextView::from_tag(&self.attributes, &self.children), _ => panic!("Unknown tag: {}", self.name), } diff --git a/src/render/buffer.rs b/src/render/buffer.rs index dff9cd5..6571827 100644 --- a/src/render/buffer.rs +++ b/src/render/buffer.rs @@ -1,9 +1,9 @@ use crate::render::rand; pub struct ImgBuffer { - pub data: Vec, - width: usize, - height: usize, + pub(crate) data: Vec, + pub(crate) width: usize, + pub(crate) height: usize, } impl ImgBuffer { @@ -24,25 +24,6 @@ impl ImgBuffer { } } - pub fn overlay_bitmap(&mut self, other: &Bitmap, xoffset: usize, yoffset: usize) { - let length = self.data.len(); - for y in 0..other.height { - for x in 0..other.width { - // Calculate the offset for the current pixel in the buffer - // This uses bitshift instead of multiplication x4 for performance - let offset = ((y + yoffset) * self.width + (x + xoffset)) << 2; - if offset >= length { - return; - } - let color = other.data[y * other.width + x]; - self.data[offset] = color; - self.data[offset + 1] = color; - self.data[offset + 2] = color; - self.data[offset + 3] = 255; - } - } - } - pub fn resize(&mut self, width: usize, height: usize) { self.data = vec![0; width * height * 4]; self.width = width; diff --git a/src/render/buffer_overlay.rs b/src/render/buffer_overlay.rs new file mode 100644 index 0000000..379d42e --- /dev/null +++ b/src/render/buffer_overlay.rs @@ -0,0 +1,162 @@ +use crate::render::buffer::{Bitmap, ImgBuffer}; + +impl ImgBuffer { + pub fn overlay_bitmap_fg_bg( + &mut self, + other: &Bitmap, + xoffset: usize, + yoffset: usize, + foreground_color: (u8, u8, u8, u8), + background_color: (u8, u8, u8, u8), + ) { + let length = self.data.len(); + for y in 0..other.height { + for x in 0..other.width { + let offset = ((y + yoffset) * self.width + (x + xoffset)) << 2; + if offset >= length { + return; + } + let weight = other.data[y * other.width + x]; + + let (mut pixel_r, mut pixel_g, mut pixel_b, mut pixel_a) = ( + self.data[offset], + self.data[offset + 1], + self.data[offset + 2], + self.data[offset + 3], + ); + + (pixel_r, pixel_g, pixel_b, pixel_a) = Self::blend_bitmap_pixel_fg_bg( + (pixel_r, pixel_b, pixel_g, pixel_a), + weight, + foreground_color, + background_color, + ); + + self.data[offset] = pixel_r; + self.data[offset + 1] = pixel_g; + self.data[offset + 2] = pixel_b; + self.data[offset + 3] = pixel_a; + + // = color; + // self.data[offset + 1] = color; + // self.data[offset + 2] = color; + // self.data[offset + 3] = 255; + } + } + } + + pub fn overlay_bitmap_fg( + &mut self, + other: &Bitmap, + xoffset: usize, + yoffset: usize, + foreground_color: (u8, u8, u8), + ) { + let length = self.data.len(); + for y in 0..other.height { + for x in 0..other.width { + let offset = ((y + yoffset) * self.width + (x + xoffset)) << 2; + if offset >= length { + return; + } + let weight = other.data[y * other.width + x]; + + let (mut pixel_r, mut pixel_g, mut pixel_b, mut pixel_a) = ( + self.data[offset], + self.data[offset + 1], + self.data[offset + 2], + self.data[offset + 3], + ); + + (pixel_r, pixel_g, pixel_b, pixel_a) = Self::blend_bitmap_pixel_fg( + (pixel_r, pixel_b, pixel_g, pixel_a), + weight, + foreground_color, + ); + + self.data[offset] = pixel_r; + self.data[offset + 1] = pixel_g; + self.data[offset + 2] = pixel_b; + self.data[offset + 3] = pixel_a; + + // = color; + // self.data[offset + 1] = color; + // self.data[offset + 2] = color; + // self.data[offset + 3] = 255; + } + } + } + + pub fn blend_bitmap_pixel_fg_bg( + original: (u8, u8, u8, u8), + bitmap_weight: u8, + fg_color: (u8, u8, u8, u8), + bg_color: (u8, u8, u8, u8), + ) -> (u8, u8, u8, u8) { + // Step 1: Interpolate between bg_color and fg_color based on bitmap_weight + + let w = bitmap_weight as u32; + let inv_w = 255u32 - w; + + // Blend RGB channels using (x * 257) >> 16 approximation for /255 + let br = (((bg_color.0 as u32 * inv_w + fg_color.0 as u32 * w) * 257) >> 16) as u8; + let bg = (((bg_color.1 as u32 * inv_w + fg_color.1 as u32 * w) * 257) >> 16) as u8; + let bb = (((bg_color.2 as u32 * inv_w + fg_color.2 as u32 * w) * 257) >> 16) as u8; + let ba = (((bg_color.3 as u32 * inv_w + fg_color.3 as u32 * w) * 257) >> 16) as u8; + + // Step 2: Alpha blend the interpolated color over the original pixel + + if ba == 0 { + // Fully transparent, return original + return original; + } + + if ba == 255 { + // Fully opaque, return blended color + return (br, bg, bb, 255); + } + + let a = ba as u32; + let inv_a = 255u32 - a; + + let r = (((original.0 as u32 * inv_a + br as u32 * a) * 257) >> 16) as u8; + let g = (((original.1 as u32 * inv_a + bg as u32 * a) * 257) >> 16) as u8; + let b = (((original.2 as u32 * inv_a + bb as u32 * a) * 257) >> 16) as u8; + + // Alpha channel: blend using "over" operation + let final_a = (((original.3 as u32 * inv_a) * 257) >> 16) + a; + let final_a = final_a.min(255) as u8; + + (r, g, b, final_a) + } + + pub fn blend_bitmap_pixel_fg( + original: (u8, u8, u8, u8), + bitmap_weight: u8, + fg_color: (u8, u8, u8), + ) -> (u8, u8, u8, u8) { + if bitmap_weight == 0 { + // Fully transparent, return original + return original; + } + + if bitmap_weight == 255 { + // Fully opaque, return foreground color + return (fg_color.0, fg_color.1, fg_color.2, 255); + } + + // Alpha blend: original + (fg - original) * weight / 255 + let w = bitmap_weight as u32; + let inv_w = 255u32 - w; + + let r = (((original.0 as u32 * inv_w + fg_color.0 as u32 * w) * 257) >> 16) as u8; + let g = (((original.1 as u32 * inv_w + fg_color.1 as u32 * w) * 257) >> 16) as u8; + let b = (((original.2 as u32 * inv_w + fg_color.2 as u32 * w) * 257) >> 16) as u8; + + // Alpha channel: blend to full opacity based on weight + let final_a = (((original.3 as u32 * inv_w) * 257) >> 16) + w; + let final_a = final_a.min(255) as u8; + + (r, g, b, final_a) + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs index 429f48b..9e6eb0d 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,4 +1,5 @@ pub mod buffer; +pub mod buffer_overlay; pub mod rand; mod renderer; @@ -13,6 +14,7 @@ pub fn calc_resolution(width: usize, height: usize) -> (usize, usize, f32, f32) let new_width = (distortion_x * RESOLUTION as f32).round() as usize; let new_height = (distortion_y * RESOLUTION as f32).round() as usize; + (new_width, new_height, distortion_x, distortion_y) } diff --git a/src/views/box_view.rs b/src/views/box_view.rs index 729fcc5..574575c 100644 --- a/src/views/box_view.rs +++ b/src/views/box_view.rs @@ -67,8 +67,8 @@ impl View for BoxView { self.view.draw(renderer, x, y, w, h); } - fn resize(&mut self, x: f32, y: f32, w: f32, h: f32) { - self.view.resize(x, y, w, h); + fn resize(&mut self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32) { + self.view.resize(renderer, x, y, w, h); } fn from_tag( diff --git a/src/views/color_rect_view.rs b/src/views/color_rect_view.rs index 2e93958..55cc069 100644 --- a/src/views/color_rect_view.rs +++ b/src/views/color_rect_view.rs @@ -21,7 +21,7 @@ impl View for ColorRectView { // log!("Draw"); } - fn resize(&mut self, _x: f32, _y: f32, _w: f32, _h: f32) {} + fn resize(&mut self, _renderer: &mut Renderer, _x: f32, _y: f32, _w: f32, _h: f32) {} fn from_tag( attributes: &std::collections::HashMap, diff --git a/src/views/constraint_layout.rs b/src/views/constraint_layout.rs index 9a4dc47..eee6d31 100644 --- a/src/views/constraint_layout.rs +++ b/src/views/constraint_layout.rs @@ -171,7 +171,7 @@ impl View for ConstraintLayout { } } - fn resize(&mut self, x: f32, y: f32, w: f32, h: f32) { + fn resize(&mut self, _renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32) { self.recalculate_positions(x, y, w, h); } diff --git a/src/views/mod.rs b/src/views/mod.rs index a7df1ac..79a418a 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -1,8 +1,4 @@ -use std::{ - any::{Any, TypeId}, - collections::HashMap, - fmt::Debug, -}; +use std::{any::Any, collections::HashMap, fmt::Debug}; use crate::{parser::Tag, render::Renderer}; @@ -15,10 +11,11 @@ mod vertical_layout; pub use box_view::BoxView; pub use color_rect_view::ColorRectView; pub use constraint_layout::ConstraintLayout; +pub use text_view::TextView; pub trait View: Debug { 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 resize(&mut self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32); fn from_tag(attributes: &HashMap, children: &Vec) -> Box where diff --git a/src/views/text_view.rs b/src/views/text_view.rs index 40e0cb9..0e5913e 100644 --- a/src/views/text_view.rs +++ b/src/views/text_view.rs @@ -4,20 +4,21 @@ use std::fmt::Debug; use crate::{ fonts::{FONTS, FontHandle}, + log, parser::Tag, - render::{Renderer, buffer::Bitmap}, + render::{RESOLUTION, Renderer, buffer::Bitmap}, views::{Bounds, View}, }; pub struct TextView { - layout: Layout, - text: String, - scale: f32, - font: FontHandle, + pub layout: Layout, + pub text: String, + pub scale: f32, + pub font: FontHandle, } impl TextView { - pub fn new(text: String) -> Self { + pub fn new(text: String, scale: f32) -> Self { let mut layout = Layout::new(CoordinateSystem::PositiveYDown); layout.reset(&LayoutSettings { @@ -27,7 +28,7 @@ impl TextView { let mut s = Self { layout, text, - scale: 20., + scale, font: FontHandle::AtiksonHyperlegibleRegular, }; @@ -68,13 +69,24 @@ impl View for TextView { let scaled = new_bitmap.scale(renderer.ratio_x, renderer.ratio_y); - renderer.img.overlay_bitmap(&scaled, x as usize, y as usize); + renderer + .img + .overlay_bitmap_fg(&scaled, x as usize, y as usize, (255, 255, 255)); // new_bitmap } - fn resize(&mut self, x: f32, y: f32, w: f32, h: f32) { - // todo!() + fn resize(&mut self, renderer: &mut Renderer, _x: f32, _y: f32, _w: f32, _h: f32) { + self.layout.clear(); + + let font = (self.font as usize).clone(); + let e = (renderer.canvas_height as f32 / renderer.actual_height as f32) + * (renderer.canvas_width as f32 / renderer.actual_width as f32); + + let scale = self.scale * e; + + self.layout + .append(&FONTS, &TextStyle::new(&self.text, scale, font)); } fn from_tag( @@ -85,8 +97,20 @@ impl View for TextView { Self: Sized, { assert!(children.is_empty(), "TextView does not support children"); - todo!() - // Self::new(attributes) + + let text = attributes.get("text").unwrap(); + + let mut scale = 30.; + + if let Some(font_size_str) = attributes.get("font_size") { + if let Some(font_size) = font_size_str.parse::().ok() { + scale = font_size; + } + } + + let this = Self::new(text.clone(), scale); + + Box::new(this) } fn as_any(self: Box) -> Box { diff --git a/src/views/vertical_layout.rs b/src/views/vertical_layout.rs index fae9f79..f3dbddf 100644 --- a/src/views/vertical_layout.rs +++ b/src/views/vertical_layout.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use crate::{ parser::Tag, + render::Renderer, views::{Bounds, View, box_view::BoxView}, }; @@ -47,7 +48,7 @@ impl View for VerticalLayout { } } } - fn resize(&mut self, x: f32, y: f32, w: f32, h: f32) {} + fn resize(&mut self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32) {} fn from_tag(attributes: &HashMap, children: &Vec) -> Box where