diff --git a/js/index.js b/js/index.js index 3537bf8..cf7d03c 100644 --- a/js/index.js +++ b/js/index.js @@ -16,11 +16,15 @@ addEventListener("TrunkApplicationStarted", (event) => { window.wasmBindings.resize(context, width, height); }); - canvas.addEventListener("click", (e) => { - // console.log(e); + canvas.addEventListener("mousemove", (event) => { + window.wasmBindings.mouse_move(context, event.pageX, event.pageY); + }); - // [width, height] = [window.innerWidth, window.innerHeight]; - // console.log("Window resized: (", width, ", ", height, ")"); + canvas.addEventListener("click", (e) => { window.wasmBindings.click(context, e.pageX, e.pageY); }); }); + +// export function cursor(name) { +// document.body.style.cursor = name; +// } diff --git a/.gitattributes b/src/.gitattributes similarity index 100% rename from .gitattributes rename to src/.gitattributes diff --git a/src/activities/mod.rs b/src/activities/mod.rs new file mode 100644 index 0000000..f1b255c --- /dev/null +++ b/src/activities/mod.rs @@ -0,0 +1,3 @@ +mod test_activity; + +pub use test_activity::TestActivity; diff --git a/src/activities/test_activity.rs b/src/activities/test_activity.rs new file mode 100644 index 0000000..52711f1 --- /dev/null +++ b/src/activities/test_activity.rs @@ -0,0 +1,166 @@ +use crate::{ + app::{Activity, App, AppState}, + log, + render::Renderer, +}; + +pub struct TestActivity { + elements: Vec, +} + +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, 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 = 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, + ); + } +} diff --git a/src/app.rs b/src/app.rs deleted file mode 100644 index 8ddd4a0..0000000 --- a/src/app.rs +++ /dev/null @@ -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>, - pub(crate) renderer: Renderer, - pub(crate) current_activity: Option, -} - -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); - } -} diff --git a/src/app/cursors.rs b/src/app/cursors.rs new file mode 100644 index 0000000..217e127 --- /dev/null +++ b/src/app/cursors.rs @@ -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", + } + } +} diff --git a/src/app/mod.rs b/src/app/mod.rs new file mode 100644 index 0000000..d5d2bea --- /dev/null +++ b/src/app/mod.rs @@ -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>, + pub(crate) root_view: Option>, + pub(crate) renderer: Renderer, + // pub(crate) current_activity: Option, + 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); + // } + } +} diff --git a/src/draw.rs b/src/draw.rs deleted file mode 100644 index 1354ffb..0000000 --- a/src/draw.rs +++ /dev/null @@ -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)); -} diff --git a/src/lib.rs b/src/lib.rs index a594688..afba90c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ +// mod activities; mod app; mod render; +mod views; use wasm_bindgen::{Clamped, prelude::*}; use web_sys::ImageData; @@ -8,7 +10,10 @@ use web_sys::ImageData; use render::buffer::ImgBuffer; use render::rand::Rnd; -use crate::{app::App, render::Renderer}; +use crate::{ + app::{App, Cursor, set_cursor}, + render::Renderer, +}; #[wasm_bindgen] extern "C" { @@ -28,6 +33,7 @@ pub fn init(canvas: &web_sys::HtmlCanvasElement, width: u32, height: u32) -> App canvas.set_height(cheight); log!("WASM Successfully initialized!"); + // set_cursor(Cursor::Auto); let mut renderer = Renderer { ctx: canvas @@ -65,17 +71,13 @@ pub fn draw(app: &mut App) { } #[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); // draw::draw(ctx); - - app.renderer.circle(x, y, 20, (255, 255, 255)); - - let data = ImageData::new_with_u8_clamped_array_and_sh( - Clamped(&app.renderer.img.data), - app.renderer.img.width(), - app.renderer.img.height(), - ) - .unwrap(); - app.renderer.ctx.put_image_data(&data, 0., 0.).unwrap(); + app.l_click(x, y); +} + +#[wasm_bindgen] +pub fn mouse_move(app: &mut App, x: f32, y: f32) { + app.mouse_move(x, y); } diff --git a/src/render/mod.rs b/src/render/mod.rs index faa1fc2..05930dd 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -4,7 +4,7 @@ mod 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) { 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; (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) diff --git a/src/render/renderer.rs b/src/render/renderer.rs index 16a7d67..5f8bb0a 100644 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -1,10 +1,7 @@ use wasm_bindgen::{Clamped, prelude::*}; use web_sys::ImageData; -use crate::{ - log, - render::{buffer::ImgBuffer, calc_resolution, rand::Rnd}, -}; +use crate::render::{RESOLUTION, buffer::ImgBuffer, calc_resolution, rand::Rnd}; #[wasm_bindgen] pub struct Renderer { @@ -72,6 +69,10 @@ impl Renderer { .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 { @@ -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 { (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 bottomy = bottomy.min(self.canvas_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); + // let e = (self.canvas_height as f32 / self.actual_height 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; diff --git a/src/views/color_rect_view.rs b/src/views/color_rect_view.rs new file mode 100644 index 0000000..a04e39e --- /dev/null +++ b/src/views/color_rect_view.rs @@ -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.)) + } +} diff --git a/src/views/mod.rs b/src/views/mod.rs new file mode 100644 index 0000000..a591b08 --- /dev/null +++ b/src/views/mod.rs @@ -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 { + Box::new(VerticalLayout::new(vec![ + Box::new(ColorRectView::new(12, 34, 56)), + Box::new(ColorRectView::new(20, 60, 80)), + ])) +} diff --git a/src/views/vertical_layout.rs b/src/views/vertical_layout.rs new file mode 100644 index 0000000..da42031 --- /dev/null +++ b/src/views/vertical_layout.rs @@ -0,0 +1,59 @@ +use crate::views::{Bounds, View}; + +pub struct VerticalLayout { + pub views: Vec>, +} + +impl VerticalLayout { + pub fn new(views: Vec>) -> 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)) + } +}