Font scale

This commit is contained in:
Michael Mikovsky
2025-11-10 11:08:23 -07:00
parent da608d0dca
commit a38b37878e
14 changed files with 283 additions and 67 deletions
+7 -2
View File
@@ -10,8 +10,13 @@ crate-type = ["cdylib"]
fontdue = "0.9.3" fontdue = "0.9.3"
lazy_static = "1.5.0" lazy_static = "1.5.0"
quick-xml = "0.38.3" quick-xml = "0.38.3"
wasm-bindgen = {version = "0.2.104"} wasm-bindgen = "0.2.105"
web-sys = {version = "0.3.81", features = ['CanvasRenderingContext2d', 'Document', 'Element', 'HtmlCanvasElement', 'Window', 'ImageData']} # 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] # [dependencies.web-sys]
# features = # features =
+5 -5
View File
@@ -1,8 +1,4 @@
<ConstraintLayout> <ConstraintLayout>
<BoxView margin="10" align_top_to_top="parent" align_left_to_left="parent">
<ColoredRectView r="123" g="12" b="34" />
</BoxView>
<BoxView <BoxView
margin_left="10" margin_left="10"
margin_right="10" margin_right="10"
@@ -16,6 +12,10 @@
<ColoredRectView r="30" g="30" b="30" /> <ColoredRectView r="30" g="30" b="30" />
</BoxView> </BoxView>
<BoxView margin="10" align_top_to_top="parent" align_left_to_left="parent">
<ColoredRectView r="123" g="12" b="34" />
</BoxView>
<BoxView <BoxView
margin="10" margin="10"
align_top_to_top="parent" align_top_to_top="parent"
@@ -23,6 +23,6 @@
align_bottom_to_bottom="parent" align_bottom_to_bottom="parent"
width="200" width="200"
> >
<ColoredRectView r="123" g="125" b="34" /> <TextView text="12345\nThis is a testing!" font_size="30." />
</BoxView> </BoxView>
</ConstraintLayout> </ConstraintLayout>
+16 -9
View File
@@ -5,7 +5,7 @@ pub use cursors::{Cursor, set_cursor};
use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::prelude::wasm_bindgen;
use crate::{ use crate::{
log, AppErr, log,
parser::{self, TEST_XML}, parser::{self, TEST_XML},
render::Renderer, render::Renderer,
views::View, views::View,
@@ -35,7 +35,7 @@ impl AppState {
} }
impl App { impl App {
pub fn new(renderer: Renderer) -> Self { pub fn new(renderer: Renderer) -> Result<Self, AppErr> {
let (width, height) = (renderer.actual_width, renderer.actual_height); let (width, height) = (renderer.actual_width, renderer.actual_height);
let root_view = parser::parse(TEST_XML); let root_view = parser::parse(TEST_XML);
@@ -48,12 +48,12 @@ impl App {
state: AppState::new(), 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); // self.renderer.img.randomize(&mut self.renderer.rand);
if let Some(view) = &mut self.root_view { if let Some(view) = &mut self.root_view {
let (width, height) = ( let (width, height) = (
@@ -64,32 +64,39 @@ impl App {
view.draw(&mut self.renderer, 0., 0., width, height); view.draw(&mut self.renderer, 0., 0., width, height);
self.renderer.update(); self.renderer.update();
} }
Ok(())
} }
} }
// App events // App events
impl App { 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); self.renderer.resize(width, height);
if let Some(view) = &mut self.root_view { 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(); 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_x = x;
self.state.mouse_y = y; self.state.mouse_y = y;
Ok(())
// if let Some(current_activity) = self.current_activity { // if let Some(current_activity) = self.current_activity {
// self.activities[current_activity].mouse_move(&mut self.renderer, &self.state); // 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_x = x;
self.state.mouse_y = y; self.state.mouse_y = y;
Ok(())
// if let Some(current_activity) = self.current_activity { // if let Some(current_activity) = self.current_activity {
// self.activities[current_activity].l_click(&mut self.renderer, &self.state); // self.activities[current_activity].l_click(&mut self.renderer, &self.state);
// } // }
+41 -5
View File
@@ -4,6 +4,8 @@ mod parser;
mod render; mod render;
mod views; mod views;
use std::error::Error;
use wasm_bindgen::{Clamped, prelude::*}; use wasm_bindgen::{Clamped, prelude::*};
use web_sys::ImageData; use web_sys::ImageData;
@@ -17,6 +19,14 @@ use crate::{
render::Renderer, render::Renderer,
}; };
#[derive(Debug)]
pub enum AppErr {
ParseErr(String),
RenderErr(String),
ArrangeErr(String),
RasterErr(String),
}
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
#[wasm_bindgen(js_namespace = console)] #[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); let mut renderer = Renderer::new(canvas, width, height);
renderer.resize(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] #[wasm_bindgen]
pub fn resize(app: &mut App, width: usize, height: usize) { 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] #[wasm_bindgen]
pub fn draw(app: &mut App) { pub fn draw(app: &mut App) {
// app.renderer.img.randomize(&mut app.renderer.rand); // app.renderer.img.randomize(&mut app.renderer.rand);
app.draw(); match app.draw() {
Ok(_) => {}
Err(e) => {
log!("Error drawing: {:?}", e);
}
}
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn click(app: &mut App, x: f32, y: f32) { pub fn click(app: &mut App, x: f32, y: f32) {
// ctx.img.randomize(&mut ctx.rand); // ctx.img.randomize(&mut ctx.rand);
// draw::draw(ctx); // draw::draw(ctx);
app.l_click(x, y); match app.l_click(x, y) {
Ok(_) => {}
Err(e) => {
log!("Error on click: {:?}", e);
}
}
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn mouse_move(app: &mut App, x: f32, y: f32) { 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);
}
}
} }
+2 -1
View File
@@ -2,7 +2,7 @@ use std::collections::HashMap;
use crate::{ use crate::{
log, log,
views::{Bounds, BoxView, ColorRectView, ConstraintLayout, View}, views::{Bounds, BoxView, ColorRectView, ConstraintLayout, TextView, View},
}; };
mod parser; mod parser;
@@ -22,6 +22,7 @@ impl Tag {
"ColoredRectView" => ColorRectView::from_tag(&self.attributes, &self.children), "ColoredRectView" => ColorRectView::from_tag(&self.attributes, &self.children),
"ConstraintLayout" => ConstraintLayout::from_tag(&self.attributes, &self.children), "ConstraintLayout" => ConstraintLayout::from_tag(&self.attributes, &self.children),
"BoxView" => BoxView::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), _ => panic!("Unknown tag: {}", self.name),
} }
+3 -22
View File
@@ -1,9 +1,9 @@
use crate::render::rand; use crate::render::rand;
pub struct ImgBuffer { pub struct ImgBuffer {
pub data: Vec<u8>, pub(crate) data: Vec<u8>,
width: usize, pub(crate) width: usize,
height: usize, pub(crate) height: usize,
} }
impl ImgBuffer { 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) { pub fn resize(&mut self, width: usize, height: usize) {
self.data = vec![0; width * height * 4]; self.data = vec![0; width * height * 4];
self.width = width; self.width = width;
+162
View File
@@ -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)
}
}
+2
View File
@@ -1,4 +1,5 @@
pub mod buffer; pub mod buffer;
pub mod buffer_overlay;
pub mod rand; pub mod rand;
mod renderer; 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_width = (distortion_x * RESOLUTION as f32).round() as usize;
let new_height = (distortion_y * 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) (new_width, new_height, distortion_x, distortion_y)
} }
+2 -2
View File
@@ -67,8 +67,8 @@ impl View for BoxView {
self.view.draw(renderer, x, y, w, h); self.view.draw(renderer, x, y, w, h);
} }
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.view.resize(x, y, w, h); self.view.resize(renderer, x, y, w, h);
} }
fn from_tag( fn from_tag(
+1 -1
View File
@@ -21,7 +21,7 @@ impl View for ColorRectView {
// log!("Draw"); // 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( fn from_tag(
attributes: &std::collections::HashMap<String, String>, attributes: &std::collections::HashMap<String, String>,
+1 -1
View File
@@ -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); self.recalculate_positions(x, y, w, h);
} }
+3 -6
View File
@@ -1,8 +1,4 @@
use std::{ use std::{any::Any, collections::HashMap, fmt::Debug};
any::{Any, TypeId},
collections::HashMap,
fmt::Debug,
};
use crate::{parser::Tag, render::Renderer}; use crate::{parser::Tag, render::Renderer};
@@ -15,10 +11,11 @@ mod vertical_layout;
pub use box_view::BoxView; pub use box_view::BoxView;
pub use color_rect_view::ColorRectView; pub use color_rect_view::ColorRectView;
pub use constraint_layout::ConstraintLayout; pub use constraint_layout::ConstraintLayout;
pub use text_view::TextView;
pub trait View: Debug { pub trait View: Debug {
fn draw(&mut self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32); 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<String, String>, children: &Vec<Tag>) -> Box<dyn View> fn from_tag(attributes: &HashMap<String, String>, children: &Vec<Tag>) -> Box<dyn View>
where where
+36 -12
View File
@@ -4,20 +4,21 @@ use std::fmt::Debug;
use crate::{ use crate::{
fonts::{FONTS, FontHandle}, fonts::{FONTS, FontHandle},
log,
parser::Tag, parser::Tag,
render::{Renderer, buffer::Bitmap}, render::{RESOLUTION, Renderer, buffer::Bitmap},
views::{Bounds, View}, views::{Bounds, View},
}; };
pub struct TextView { pub struct TextView {
layout: Layout, pub layout: Layout,
text: String, pub text: String,
scale: f32, pub scale: f32,
font: FontHandle, pub font: FontHandle,
} }
impl TextView { impl TextView {
pub fn new(text: String) -> Self { pub fn new(text: String, scale: f32) -> Self {
let mut layout = Layout::new(CoordinateSystem::PositiveYDown); let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
layout.reset(&LayoutSettings { layout.reset(&LayoutSettings {
@@ -27,7 +28,7 @@ impl TextView {
let mut s = Self { let mut s = Self {
layout, layout,
text, text,
scale: 20., scale,
font: FontHandle::AtiksonHyperlegibleRegular, font: FontHandle::AtiksonHyperlegibleRegular,
}; };
@@ -68,13 +69,24 @@ impl View for TextView {
let scaled = new_bitmap.scale(renderer.ratio_x, renderer.ratio_y); 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 // new_bitmap
} }
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) {
// todo!() 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( fn from_tag(
@@ -85,8 +97,20 @@ impl View for TextView {
Self: Sized, Self: Sized,
{ {
assert!(children.is_empty(), "TextView does not support children"); 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::<f32>().ok() {
scale = font_size;
}
}
let this = Self::new(text.clone(), scale);
Box::new(this)
} }
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> {
+2 -1
View File
@@ -2,6 +2,7 @@ use std::collections::HashMap;
use crate::{ use crate::{
parser::Tag, parser::Tag,
render::Renderer,
views::{Bounds, View, box_view::BoxView}, 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<String, String>, children: &Vec<Tag>) -> Box<dyn View> fn from_tag(attributes: &HashMap<String, String>, children: &Vec<Tag>) -> Box<dyn View>
where where