Start work on views system

This commit is contained in:
Michael Mikovsky
2025-10-23 11:25:02 -06:00
parent b8ca903bba
commit d83e81fabc
14 changed files with 556 additions and 95 deletions
+8 -4
View File
@@ -16,11 +16,15 @@ addEventListener("TrunkApplicationStarted", (event) => {
window.wasmBindings.resize(context, width, height); window.wasmBindings.resize(context, width, height);
}); });
canvas.addEventListener("click", (e) => { canvas.addEventListener("mousemove", (event) => {
// console.log(e); window.wasmBindings.mouse_move(context, event.pageX, event.pageY);
});
// [width, height] = [window.innerWidth, window.innerHeight]; canvas.addEventListener("click", (e) => {
// console.log("Window resized: (", width, ", ", height, ")");
window.wasmBindings.click(context, e.pageX, e.pageY); window.wasmBindings.click(context, e.pageX, e.pageY);
}); });
}); });
// export function cursor(name) {
// document.body.style.cursor = name;
// }
+3
View File
@@ -0,0 +1,3 @@
mod test_activity;
pub use test_activity::TestActivity;
+166
View File
@@ -0,0 +1,166 @@
use crate::{
app::{Activity, App, AppState},
log,
render::Renderer,
};
pub struct TestActivity {
elements: Vec<Element>,
}
impl TestActivity {
pub fn top_element_at_point(&mut self, x: f32, y: f32) -> Option<(&mut Element, usize)> {
for (i, element) in self.elements.iter_mut().enumerate().rev() {
if x >= element.x
&& x <= element.x + element.width
&& y >= element.y
&& y <= element.y + element.height
{
return Some((element, i));
}
}
None
}
pub fn bottom_element_at_point(
&mut self,
x: f32,
y: f32,
min_index: usize,
) -> Option<(&mut Element, usize)> {
for (i, element) in self.elements.iter_mut().enumerate().skip(min_index) {
if x >= element.x
&& x <= element.x + element.width
&& y >= element.y
&& y <= element.y + element.height
{
return Some((element, i));
}
}
None
}
fn mark_for_redraw(&mut self, current_elements: &mut Vec<usize>, element_index: usize) {
current_elements.push(element_index);
let element = &self.elements[element_index];
for (x, y) in vec![
(element.x.clone(), element.y.clone()),
(element.x.clone() + element.width, element.y.clone()),
(element.x.clone(), element.y.clone() + element.height),
(
element.x.clone() + element.width,
element.y.clone() + element.height,
),
] {
let option = self.bottom_element_at_point(x, y, element_index + 1);
if let Some((element, index)) = option {
if element_index == index || current_elements.contains(&index) {
continue;
}
self.mark_for_redraw(current_elements, index);
}
}
}
}
impl Activity for TestActivity {
fn new() -> Self
where
Self: Sized,
{
Self {
elements: Vec::new(),
}
}
fn update(&mut self, dt: f32) {
// todo!()
}
fn draw(&self, renderer: &mut Renderer, state: &AppState) {
renderer.img.randomize(&mut renderer.rand);
let (cx, cy) = (renderer.actual_width / 2, renderer.actual_height / 2);
renderer.circle(cx as i32, cy as i32, 100, (127, 127, 127));
// renderer.rect_center_xywh(cx as i32, cy as i32, 200, 200, (155, 127, 155));
// renderer.rect_xyxy(10, 10, cx as i32, cy as i32, (255, 127, 155));
for element in &self.elements {
element.draw(renderer);
}
renderer.update();
}
fn l_click(&mut self, renderer: &mut Renderer, state: &AppState) {
// log!("Mouse moved");
let option = self.top_element_at_point(state.mouse_x, state.mouse_y);
if let Some((_, index)) = option {
let mut for_redraw: Vec<usize> = Vec::new();
self.mark_for_redraw(&mut for_redraw, index);
for_redraw.sort();
for n in for_redraw.iter_mut() {
let element = &mut self.elements[*n];
element.color = (
renderer.rand.next_u32() as u8,
renderer.rand.next_u32() as u8,
renderer.rand.next_u32() as u8,
);
element.draw(renderer);
}
renderer.update();
} else {
self.elements.push(Element::new(
state.mouse_x,
state.mouse_y,
50.0,
50.0,
(
renderer.rand.next_u32() as u8,
renderer.rand.next_u32() as u8,
renderer.rand.next_u32() as u8,
),
));
self.draw(renderer, state);
renderer.update();
}
}
fn mouse_move(&mut self, renderer: &mut Renderer, state: &AppState) {}
}
pub struct Element {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
pub color: (u8, u8, u8),
}
impl Element {
pub fn new(x: f32, y: f32, width: f32, height: f32, color: (u8, u8, u8)) -> Self {
Self {
x,
y,
width,
height,
color,
}
}
pub fn draw(&self, renderer: &mut Renderer) {
renderer.rect_xywh(
self.x as i32,
self.y as i32,
self.width as i32,
self.height as i32,
self.color,
);
}
}
-57
View File
@@ -1,57 +0,0 @@
use wasm_bindgen::prelude::wasm_bindgen;
use crate::render::Renderer;
pub trait Activity {
fn new() -> Self
where
Self: Sized;
fn update(&mut self, dt: f32);
fn draw(&self, ctx: &mut Renderer);
}
#[wasm_bindgen]
pub struct App {
pub(crate) activities: Vec<Box<dyn Activity>>,
pub(crate) renderer: Renderer,
pub(crate) current_activity: Option<usize>,
}
impl App {
pub fn new(renderer: Renderer) -> Self {
App {
activities: Vec::new(),
renderer,
current_activity: None,
}
}
pub fn draw(&mut self) {
// if let Some(current_activity) = self.current_activity {
// self.activities[current_activity].draw(&mut self.renderer);
// }
self.renderer.img.randomize(&mut self.renderer.rand);
let (cx, cy) = (
self.renderer.actual_width / 2,
self.renderer.actual_height / 2,
);
self.renderer
.circle(cx as i32, cy as i32, 200, (255, 255, 255));
self.renderer.update();
}
}
// App events
impl App {
pub fn resize(&mut self, width: u32, height: u32) {
self.renderer.resize(width, height);
self.draw();
}
pub fn click(&mut self, x: f32, y: f32) {
// self.renderer.click(x, y);
}
}
+91
View File
@@ -0,0 +1,91 @@
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen(module = "/js/index.js")]
extern "C" {
fn cursor(name: &str);
}
pub fn set_cursor(c: Cursor) {
cursor(c.value());
}
#[allow(dead_code)]
pub enum Cursor {
Alias,
AllScroll,
Auto,
Cell,
ColResize,
ContextMenu,
Copy,
Crosshair,
Default,
EResize,
EWResize,
Grab,
Grabbing,
Help,
Move,
NResize,
NEResize,
NESWResize,
NSResize,
NWResize,
NWSEResize,
NoDrop,
None,
NotAllowed,
Pointer,
Progress,
RowResize,
SResize,
SEResize,
SWResize,
Text,
WResize,
Wait,
ZoomIn,
ZoomOut,
}
impl Cursor {
fn value(&self) -> &str {
match self {
Cursor::Alias => "alias",
Cursor::AllScroll => "all-scroll",
Cursor::Auto => "auto",
Cursor::Cell => "cell",
Cursor::ColResize => "col-resize",
Cursor::ContextMenu => "context-menu",
Cursor::Copy => "copy",
Cursor::Crosshair => "crosshair",
Cursor::Default => "default",
Cursor::EResize => "e-resize",
Cursor::EWResize => "ew-resize",
Cursor::Grab => "grab",
Cursor::Grabbing => "grabbing",
Cursor::Help => "help",
Cursor::Move => "move",
Cursor::NResize => "n-resize",
Cursor::NEResize => "ne-resize",
Cursor::NESWResize => "nesw-resize",
Cursor::NSResize => "ns-resize",
Cursor::NWResize => "nw-resize",
Cursor::NWSEResize => "nwse-resize",
Cursor::NoDrop => "no-drop",
Cursor::None => "none",
Cursor::NotAllowed => "not-allowed",
Cursor::Pointer => "pointer",
Cursor::Progress => "progress",
Cursor::RowResize => "row-resize",
Cursor::SResize => "s-resize",
Cursor::SEResize => "se-resize",
Cursor::SWResize => "sw-resize",
Cursor::Text => "text",
Cursor::WResize => "w-resize",
Cursor::Wait => "wait",
Cursor::ZoomIn => "zoom-in",
Cursor::ZoomOut => "zoom-out",
}
}
}
+93
View File
@@ -0,0 +1,93 @@
mod cursors;
pub use cursors::{Cursor, set_cursor};
use wasm_bindgen::prelude::wasm_bindgen;
use crate::{
log,
render::Renderer,
views::{View, default_view},
};
pub trait Activity {
fn new() -> Self
where
Self: Sized;
fn update(&mut self, dt: f32);
fn draw(&self, renderer: &mut Renderer, state: &AppState);
fn l_click(&mut self, renderer: &mut Renderer, state: &AppState);
fn mouse_move(&mut self, renderer: &mut Renderer, state: &AppState);
}
#[wasm_bindgen]
pub struct App {
// pub(crate) activities: Vec<Box<dyn Activity>>,
pub(crate) root_view: Option<Box<dyn View>>,
pub(crate) renderer: Renderer,
// pub(crate) current_activity: Option<usize>,
pub(crate) state: AppState,
}
pub struct AppState {
pub(crate) mouse_x: f32,
pub(crate) mouse_y: f32,
}
impl AppState {
pub fn new() -> Self {
AppState {
mouse_x: 0.0,
mouse_y: 0.0,
}
}
}
impl App {
pub fn new(renderer: Renderer) -> Self {
App {
root_view: Some(default_view()),
renderer,
// current_activity: Some(0),
state: AppState::new(),
}
}
pub fn draw(&mut self) {
if let Some(view) = &self.root_view {
let (width, height) = (
self.renderer.actual_width.clone() as f32,
self.renderer.actual_height.clone() as f32,
);
view.draw(&mut self.renderer, 0., 0., width, height);
self.renderer.update();
}
}
}
// App events
impl App {
pub fn resize(&mut self, width: u32, height: u32) {
self.renderer.resize(width, height);
self.draw();
}
pub fn mouse_move(&mut self, x: f32, y: f32) {
self.state.mouse_x = x;
self.state.mouse_y = y;
// 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) {
self.state.mouse_x = x;
self.state.mouse_y = y;
// if let Some(current_activity) = self.current_activity {
// self.activities[current_activity].l_click(&mut self.renderer, &self.state);
// }
}
}
-15
View File
@@ -1,15 +0,0 @@
use crate::render::Renderer;
pub fn draw(ctx: &mut Renderer) {
// // Draw the background
// ctx.background(0.0, 0.0, 0.0);
// // Draw the foreground
// ctx.fill(1.0, 1.0, 1.0);
// ctx.rect(50.0, 50.0, 100.0, 100.0);
//
let (cx, cy) = (ctx.actual_width / 2, ctx.actual_height / 2);
ctx.circle(cx as i32, cy as i32, 200, (255, 255, 255));
}
+14 -12
View File
@@ -1,5 +1,7 @@
// mod activities;
mod app; mod app;
mod render; mod render;
mod views;
use wasm_bindgen::{Clamped, prelude::*}; use wasm_bindgen::{Clamped, prelude::*};
use web_sys::ImageData; use web_sys::ImageData;
@@ -8,7 +10,10 @@ use web_sys::ImageData;
use render::buffer::ImgBuffer; use render::buffer::ImgBuffer;
use render::rand::Rnd; use render::rand::Rnd;
use crate::{app::App, render::Renderer}; use crate::{
app::{App, Cursor, set_cursor},
render::Renderer,
};
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
@@ -28,6 +33,7 @@ pub fn init(canvas: &web_sys::HtmlCanvasElement, width: u32, height: u32) -> App
canvas.set_height(cheight); canvas.set_height(cheight);
log!("WASM Successfully initialized!"); log!("WASM Successfully initialized!");
// set_cursor(Cursor::Auto);
let mut renderer = Renderer { let mut renderer = Renderer {
ctx: canvas ctx: canvas
@@ -65,17 +71,13 @@ pub fn draw(app: &mut App) {
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn click(app: &mut App, x: i32, y: i32) { 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);
app.renderer.circle(x, y, 20, (255, 255, 255)); }
let data = ImageData::new_with_u8_clamped_array_and_sh( #[wasm_bindgen]
Clamped(&app.renderer.img.data), pub fn mouse_move(app: &mut App, x: f32, y: f32) {
app.renderer.img.width(), app.mouse_move(x, y);
app.renderer.img.height(),
)
.unwrap();
app.renderer.ctx.put_image_data(&data, 0., 0.).unwrap();
} }
+19 -1
View File
@@ -4,7 +4,7 @@ mod renderer;
pub use renderer::Renderer; pub use renderer::Renderer;
pub const RESOLUTION: u32 = 800; pub const RESOLUTION: u32 = 1200;
pub fn calc_resolution(width: u32, height: u32) -> (u32, u32, f32, f32) { pub fn calc_resolution(width: u32, height: u32) -> (u32, u32, f32, f32) {
let aspect = width as f32 / height as f32; let aspect = width as f32 / height as f32;
@@ -15,3 +15,21 @@ pub fn calc_resolution(width: u32, height: u32) -> (u32, u32, f32, f32) {
let new_height = (distortion_y * RESOLUTION as f32).round() as u32; let new_height = (distortion_y * RESOLUTION as f32).round() as u32;
(new_width, new_height, distortion_x, distortion_y) (new_width, new_height, distortion_x, distortion_y)
} }
// aspect = width / height
// dist_x = aspect
// dist_y = height / width
//
// cw = RES * width / height
// ch = RES * height / width
//
// ux = x * (width / cw)
// ux = x * (width / (RES * width / height))
// ux = x * (height / RES)
//
// uy = y * (width / RES)
//
// e = (cw * ch) / (width * height)
// e = ((RES * (width / height)) * RES * height / width) / (width * height)
// e = ((RES * (1 / height)) * RES * (1 / width)
// e = RES^2 / (width * height)
+50 -6
View File
@@ -1,10 +1,7 @@
use wasm_bindgen::{Clamped, prelude::*}; use wasm_bindgen::{Clamped, prelude::*};
use web_sys::ImageData; use web_sys::ImageData;
use crate::{ use crate::render::{RESOLUTION, buffer::ImgBuffer, calc_resolution, rand::Rnd};
log,
render::{buffer::ImgBuffer, calc_resolution, rand::Rnd},
};
#[wasm_bindgen] #[wasm_bindgen]
pub struct Renderer { pub struct Renderer {
@@ -72,6 +69,10 @@ impl Renderer {
.unwrap(); .unwrap();
self.ctx.put_image_data(&data, 0., 0.).unwrap(); self.ctx.put_image_data(&data, 0., 0.).unwrap();
} }
pub fn clear(&mut self) {
self.img.data = vec![0; (self.img.width() * self.img.height() * 4) as usize];
}
} }
impl Renderer { impl Renderer {
@@ -82,6 +83,47 @@ impl Renderer {
) )
} }
/// Draw a rectangle centered at (cx, cy) with the given width and height.
pub fn rect_center_xywh(
&mut self,
cx: i32,
cy: i32,
width: i32,
height: i32,
color: (u8, u8, u8),
) {
self.rect_xyxy(
cx - width / 2,
cy - height / 2,
cx + width / 2,
cy + height / 2,
color,
);
}
/// Draw a rectangle at (x, y) with the given width and height.
pub fn rect_xywh(&mut self, x: i32, y: i32, width: i32, height: i32, color: (u8, u8, u8)) {
self.rect_xyxy(x, y, x + width, y + height, color);
}
/// Draw a rectangle at (x1, y1) with the given width and height.
pub fn rect_xyxy(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: (u8, u8, u8)) {
let (leftx, topy) = self.undistort(x1 as f32, y1 as f32);
let (rightx, bottomy) = self.undistort(x2 as f32, y2 as f32);
let leftx = leftx.max(0f32);
let topy = topy.max(0f32);
let rightx = rightx.min(self.canvas_width as f32);
let bottomy = bottomy.min(self.canvas_height as f32);
for x in leftx as i32..rightx as i32 {
for y in topy as i32..bottomy as i32 {
self.img.set_pixel(x as u32, y as u32, color);
}
}
}
/// Calculate the square distance between two points on the screen, and convert to canvas coordinates for processing
pub fn screen_dist_sq(&self, x: f32, y: f32, rx: f32, ry: f32) -> f32 { pub fn screen_dist_sq(&self, x: f32, y: f32, rx: f32, ry: f32) -> f32 {
(rx - x).powf(2.) * self.distortion_y + (ry - y).powf(2.) * self.distortion_x (rx - x).powf(2.) * self.distortion_y + (ry - y).powf(2.) * self.distortion_x
} }
@@ -99,8 +141,10 @@ impl Renderer {
let rightx = rightx.min(self.canvas_width as f32); let rightx = rightx.min(self.canvas_width as f32);
let bottomy = bottomy.min(self.canvas_height as f32); let bottomy = bottomy.min(self.canvas_height as f32);
let e = (self.canvas_height as f32 / self.actual_height as f32) // let e = (self.canvas_height as f32 / self.actual_height as f32)
* (self.canvas_width as f32 / self.actual_width as f32); // * (self.canvas_width as f32 / self.actual_width as f32);
let e = RESOLUTION.pow(2) as f32 / (self.actual_width * self.actual_height) as f32;
let r2 = (radius).powf(2.) * e; let r2 = (radius).powf(2.) * e;
+25
View File
@@ -0,0 +1,25 @@
use crate::{
log,
render::Renderer,
views::{Bounds, View},
};
pub struct ColorRectView {
color: (u8, u8, u8),
}
impl ColorRectView {
pub fn new(r: u8, g: u8, b: u8) -> Self {
Self { color: (r, g, b) }
}
}
impl View for ColorRectView {
fn draw(&self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32) {
renderer.rect_xywh(x as i32, y as i32, w as i32, h as i32, self.color);
log!("Draw");
}
fn bounds(&self, _ph: f32, _pw: f32) -> (Bounds, Bounds) {
(Bounds::MatchParent, Bounds::Pixels(2200.))
}
}
+28
View File
@@ -0,0 +1,28 @@
use crate::render::Renderer;
mod color_rect_view;
mod vertical_layout;
use color_rect_view::ColorRectView;
use vertical_layout::VerticalLayout;
pub trait View {
fn draw(&self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32);
fn bounds(&self, pw: f32, ph: f32) -> (Bounds, Bounds);
}
pub enum Bounds {
MatchParent,
Pixels(f32),
}
// pub trait ViewPosition: Drawable {
// // fn draw(&self)
// }
pub fn default_view() -> Box<dyn View> {
Box::new(VerticalLayout::new(vec![
Box::new(ColorRectView::new(12, 34, 56)),
Box::new(ColorRectView::new(20, 60, 80)),
]))
}
+59
View File
@@ -0,0 +1,59 @@
use crate::views::{Bounds, View};
pub struct VerticalLayout {
pub views: Vec<Box<dyn View>>,
}
impl VerticalLayout {
pub fn new(views: Vec<Box<dyn View>>) -> Self {
Self { views }
}
}
impl View for VerticalLayout {
fn draw(&self, renderer: &mut crate::render::Renderer, x: f32, y: f32, w: f32, h: f32) {
let mut cur_y = y;
for view in &self.views {
let (vx, vy) = view.bounds(w, h);
let vx = match vx {
super::Bounds::MatchParent => w,
super::Bounds::Pixels(x) => x,
};
let vy = match vy {
super::Bounds::MatchParent => h,
super::Bounds::Pixels(x) => x,
};
view.draw(renderer, x, cur_y, w.min(vx), vy);
cur_y += vy;
if cur_y > h {
return;
}
}
}
fn bounds(&self, pw: f32, ph: f32) -> (Bounds, Bounds) {
let (mut maxx, mut totaly): (f32, f32) = (0., 0.);
for view in &self.views {
let (vx, vy) = view.bounds(pw, ph);
let vx = match vx {
super::Bounds::MatchParent => pw,
super::Bounds::Pixels(x) => x,
};
let vy = match vy {
super::Bounds::MatchParent => ph,
super::Bounds::Pixels(x) => x,
};
maxx = maxx.max(vx);
totaly += vy;
}
(Bounds::Pixels(maxx), Bounds::Pixels(totaly))
}
}