diff --git a/editor/index.js b/editor/index.js index 0ed46fd..7b9c349 100644 --- a/editor/index.js +++ b/editor/index.js @@ -19,25 +19,43 @@ await Promise.all(promises); font = await font.loaded; document.fonts.add(font); -let world = World.new(); +let world = World.deserialize(await (await fetch("/stackline/tests/other/prime.json")).json()); +world.init(); + +// let world = World.new(); let cx = 0; let cy = 0; let zoom = 1; +let grid = true; -let pane = Pane.empty(5, 5); -world.set_pane("main", pane); - -let available = available_tiles(); -console.log(available); -for (let n = 0; n < available.length; n++) { - world.set( - n % 5, - ~~(n / 5), - new FullTile(available[n]) - ); -} +let click_x = 0, click_y = 0; +let mouse_x = 0, mouse_y = 0; +let mouse_down = false; +let hovered = false; +let selected = null; + +let running = null; + +let tile = world.get(4, 0); +tile.signal = new Signal(); +tile.state = "Active"; +world.set(4, 0, tile); + +// let pane = Pane.empty(5, 5); +// world.set_pane("main", pane); + +// let available = available_tiles(); +// console.log(available); +// for (let n = 0; n < available.length; n++) { +// world.set( +// n % 5, +// ~~(n / 5), +// new FullTile(available[n]) +// ); +// } console.log(world.toString()); +console.log(world.serialize()); resize(); window.addEventListener("resize", resize); @@ -49,14 +67,30 @@ function draw() { ctx.fillStyle = "#202027"; ctx.fillRect(0, 0, canvas.width, canvas.height); + function stroke_rect(x, y, width, height) { + ctx.lineWidth = zoom_factor; + ctx.strokeRect( + Math.round(x * tile_size + cx + canvas.width / 2) - zoom_factor * 1.5, + Math.round(y * tile_size + cy + canvas.height / 2) - zoom_factor * 1.5, + Math.round(width * tile_size) + zoom_factor * 2, + Math.round(height * tile_size) + zoom_factor * 2, + ); + } + + function fill_rect(x, y, width, height) { + ctx.fillRect( + Math.round(x * tile_size + cx + canvas.width / 2) - zoom_factor * 1, + Math.round(y * tile_size + cy + canvas.height / 2) - zoom_factor * 1, + Math.round(width * tile_size) + zoom_factor * 1, + Math.round(height * tile_size) + zoom_factor * 1, + ); + } + let panes = []; for (let name of world.panes()) { let pane = world.get_pane(name); - ctx.strokeStyle = "rgba(128, 128, 128, 0.5)"; - ctx.lineWidth = zoom_factor; - panes.push({ x: pane.x, y: pane.y, @@ -64,11 +98,12 @@ function draw() { height: pane.height }); - ctx.strokeRect( - Math.round(pane.x * tile_size + cx + canvas.width / 2) - zoom_factor * 1.5, - Math.round(pane.y * tile_size + cy + canvas.height / 2) - zoom_factor * 1.5, - Math.round(pane.width * tile_size) + zoom_factor * 3, - Math.round(pane.height * tile_size) + zoom_factor * 3, + ctx.strokeStyle = "rgba(128, 128, 128, 0.5)"; + stroke_rect( + pane.x, + pane.y, + pane.width, + pane.height, ); pane.free(); @@ -115,7 +150,7 @@ function draw() { Math.round(x2), Math.round(y2), ); - } else { + } else if (grid) { // Draw grid let inside = false; @@ -127,7 +162,7 @@ function draw() { } if (inside || width < 100 && height < 100) { - ctx.fillStyle = inside ? "#505050" : "#303030"; + ctx.fillStyle = inside ? "#404040" : "#303030"; ctx.fillRect( Math.round(x2 + tile_size / 2) - zoom_factor, @@ -139,6 +174,15 @@ function draw() { } } } + + if (hovered) { + let [x, y] = get_hovered(); + + ctx.strokeStyle = "rgba(230, 255, 230, 0.2)"; + ctx.fillStyle = "rgba(230, 255, 230, 0.07)"; + stroke_rect(x, y, 1, 1); + fill_rect(x, y, 1, 1); + } } function resize() { @@ -153,40 +197,85 @@ function loop() { window.requestAnimationFrame(loop); } -let mouse_x = 0, mouse_y = 0; -let mouse_down = false; - canvas.addEventListener("mousedown", (evt) => { - mouse_x = evt.clientX; - mouse_y = evt.clientY; + click_x = mouse_x = evt.clientX; + click_y = mouse_y = evt.clientY; mouse_down = true; }); canvas.addEventListener("mousemove", (evt) => { + hovered = true; + if (mouse_down) { // TODO: numerically stable solution cx += evt.clientX - mouse_x; cy += evt.clientY - mouse_y; - - mouse_x = evt.clientX; - mouse_y = evt.clientY; } + + mouse_x = evt.clientX; + mouse_y = evt.clientY; }); canvas.addEventListener("mouseup", (evt) => { mouse_down = false; + + let dist = Math.sqrt((mouse_x - click_x) ** 2 + (mouse_y - click_y) ** 2); + if (dist < 10) { + selected = get_hovered(); + } +}); + +canvas.addEventListener("mouseenter", (evt) => { + hovered = true; }); canvas.addEventListener("mouseleave", (evt) => { mouse_down = false; + hovered = false; }); canvas.addEventListener("wheel", (event) => { const ZOOM_STRENGTH = -0.005; + let old_zoom = zoom; zoom += event.deltaY * ZOOM_STRENGTH; zoom = Math.min(Math.max(zoom, 0.0), 4.0); + + let delta = Math.ceil(Math.pow(2, zoom)) / Math.ceil(Math.pow(2, old_zoom)); + cx *= delta; + cy *= delta; +}); + +window.addEventListener("keydown", (event) => { + if (hovered) { + if (event.code === "Space") { + if (running !== null) { + clearInterval(running); + running = null; + } else if (event.shiftKey) { + world.init(); + running = setInterval(() => { + world.step(); + }, 100); + } else { + world.init(); + world.step(); + } + } else if (event.code === "KeyG") { + grid = !grid; + } + } }); +function get_hovered() { + let zoom_factor = Math.ceil(Math.pow(2, zoom)); + let tile_size = 10 * zoom_factor; + + let x = Math.floor((mouse_x - canvas.width / 2 - cx) / tile_size); + let y = Math.floor((mouse_y - canvas.height / 2 - cy) / tile_size); + + return [x, y]; +} + function to_color(num) { let alpha = (num >> 24) & 0xff; let red = (num >> 16) & 0xff; diff --git a/stackline-wasm/src/lib.rs b/stackline-wasm/src/lib.rs index dde08e5..6b15b85 100644 --- a/stackline-wasm/src/lib.rs +++ b/stackline-wasm/src/lib.rs @@ -47,11 +47,34 @@ impl World { Self(SLWorld::new()) } + pub fn deserialize(serialized: &JsValue) -> Option { + serialized.into_serde().map_err(|err| { + err!("Error while deserializing World: {:?}", err); + }).ok().map(|world| World(world)) + } + + pub fn serialize(&self) -> JsValue { + JsValue::from_serde(&self.0).map_err(|err| { + err!("Error while serializing World: {:?}", err); + }).unwrap_or(JsValue::NULL) + } + /// Initializes the World, making it ready to run pub fn init(&mut self) { self.0.init(); } + #[inline] + pub fn step(&mut self) { + self.0.step(); + } + + pub fn run(&mut self, steps: usize) { + for _ in 0..steps { + self.step(); + } + } + pub fn set_blink_duration(&mut self, blink_duration: f64) { use std::time::Duration; self.0 @@ -221,6 +244,18 @@ impl FullTile { } } + #[wasm_bindgen(getter)] + pub fn state(&self) -> JsValue { + JsValue::from_serde(&self.0.state()).expect("Error while serializing State") + } + + #[wasm_bindgen(setter)] + pub fn set_state(&mut self, state: &JsValue) { + if let Ok(state) = state.into_serde() { + self.0.set_state(state); + } + } + pub fn schema(&self) -> JsValue { use serde_json::Value; use stackline::tile::{Tile, TileSchema};