From 06040f2c8911e0365fa1025e9afa3a450d5a6c63 Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Sat, 20 Aug 2022 17:30:10 +0200 Subject: [PATCH] :sparkles: WIP left pane --- editor-solidjs/src/Editor.jsx | 35 +++++++++++- editor-solidjs/src/LeftPane.jsx | 76 +++++++++++++++++++++++++- editor-solidjs/src/LeftPane.module.css | 49 +++++++++++++++++ editor-solidjs/src/MiddlePane.jsx | 23 ++++++-- editor-solidjs/src/TilePreset.jsx | 37 +++++++++++++ editor-solidjs/src/index.jsx | 4 ++ stackline-wasm/src/lib.rs | 46 ++++++++++++++++ 7 files changed, 261 insertions(+), 9 deletions(-) create mode 100644 editor-solidjs/src/LeftPane.module.css create mode 100644 editor-solidjs/src/TilePreset.jsx diff --git a/editor-solidjs/src/Editor.jsx b/editor-solidjs/src/Editor.jsx index 4c3213f..f625493 100644 --- a/editor-solidjs/src/Editor.jsx +++ b/editor-solidjs/src/Editor.jsx @@ -9,7 +9,7 @@ import LeftPane from "./LeftPane.jsx"; import MiddlePane from "./MiddlePane.jsx"; import RightPane from "./RightPane.jsx"; -import {World, Signal} from "../stackline-wasm/stackline_wasm.js"; +import {World, Pane} from "../stackline-wasm/stackline_wasm.js"; let json = await (await fetch("/stackline-wasm/prime.json")).json(); @@ -30,6 +30,7 @@ export default function Editor() { selected: null, time: performance.now(), grid: true, + picked: null, }); setWorld((world) => { @@ -102,10 +103,38 @@ export default function Editor() { } } + let [left_pane, presets] = LeftPane({settings, setSettings}); + + let last_click = 0, last_selected = null; + const DOUBLE_CLICK_DURATION = 250; + function click() { + if ( + Date.now() - last_click < DOUBLE_CLICK_DURATION + && settings.selected[0] == last_selected[0] + && settings.selected[1] == last_selected[1] + ) { + if (settings.selected !== null && settings.picked !== null) { + let preset = presets[settings.picked]; + + let pane = Pane.deserialize(preset.serialized); + + setWorld((world) => { + pane.blit(...settings.selected, world); + + return world; + }); + + pane.free(); + } + } + last_selected = [...settings.selected]; + last_click = Date.now(); + } + return (
- - + {left_pane} +
); diff --git a/editor-solidjs/src/LeftPane.jsx b/editor-solidjs/src/LeftPane.jsx index f36ce3d..7d8d9f6 100644 --- a/editor-solidjs/src/LeftPane.jsx +++ b/editor-solidjs/src/LeftPane.jsx @@ -1,5 +1,77 @@ +import {createSignal} from "solid-js"; +import {createStore} from "solid-js/store"; + +import styles from "./LeftPane.module.css"; + +import {available_tiles, Pane, FullTile} from "../stackline-wasm/stackline_wasm.js"; +import TilePreset from "./TilePreset.jsx"; + +export const BASE_TILES = []; + +export class Tile { + constructor(serialized, name) { + this.serialized = serialized; + this.name = name; + } +} + +export function init() { + let pane = new Pane(1, 1); + BASE_TILES.push(new Tile(pane.serialize(), "Empty")); + pane.free(); + + for (let name of available_tiles().sort()) { + let pane = new Pane(1, 1); + + let tile = new FullTile(name); + pane.set(0, 0, tile); + + let serialized = pane.serialize(); + + pane.free(); + BASE_TILES.push(new Tile(serialized, name)); + } +} + export default function LeftPane(props) { - return (
+ let [preset, setPresets] = createStore(BASE_TILES); + let {settings, setSettings} = props; + let left_pane; + + function resize() { + let width = document.body.clientWidth / 4; + if (width >= 390) { + width = 390; + } else if (width >= 255) { + width = 255; + } else { + width = 125; + } + + left_pane.style.width = `${Math.round(width)}px`; + } + + function set_resize() { + setTimeout(() => { + window.addEventListener("resize", resize); + resize(); + }, 0); + } + + function remove_resize() { + window.removeEventListener("resize", resize); + } -
); + return [
+ + {(item, index) => { + return item.serialized} name={() => item.name} settings={settings} setSettings={setSettings} index={index} />; + }} + +
, preset]; } diff --git a/editor-solidjs/src/LeftPane.module.css b/editor-solidjs/src/LeftPane.module.css new file mode 100644 index 0000000..42c8da9 --- /dev/null +++ b/editor-solidjs/src/LeftPane.module.css @@ -0,0 +1,49 @@ +.LeftPane_container { + background: #18181b; + color: white; + padding: 1.5em 1em; + font-family: monospace; + font-size: 15px; + + overflow-y: scroll; +} + +.LeftPane { + width: 25vw; + display: grid; + grid-template-columns: repeat(auto-fill, calc(8em)); + grid-gap: 1em; + justify-content: space-evenly; +} + +.TilePreset { + width: calc(8em - 6px); + border: 1px solid #404040; + border-radius: 4px; + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + color: #d0d0d0; + padding: 3px; +} + +.TilePreset:hover { + border: 1px solid #808080; +} + +.preview { + font-family: "Stackline Classic", monospace; + font-size: 30px; + user-select: none; +} + +.TilePreset.picked { + color: white; + border: 4px solid #d0d0d0; + padding: 0px; +} + +.TilePreset.picked:hover { + border: 4px solid white; +} diff --git a/editor-solidjs/src/MiddlePane.jsx b/editor-solidjs/src/MiddlePane.jsx index 5e40d0e..2438531 100644 --- a/editor-solidjs/src/MiddlePane.jsx +++ b/editor-solidjs/src/MiddlePane.jsx @@ -236,7 +236,7 @@ export default function MiddlePane(props) { } } - let resize_listener = () => { + function resize_cb() { if (!canvas.element) return; setCanvas((obj) => { return { @@ -247,8 +247,22 @@ export default function MiddlePane(props) { }); } + let resize_listener = new ResizeObserver(resize_cb); + + function set_resize() { + setTimeout(() => { + resize_listener.observe(canvas.element); + }, 0); + } + + function remove_resize() { + setTimeout(() => { + resize_listener.unobserve(canvas.element); + }, 0); + } + createEffect(() => { - resize_listener(); + resize_cb(); loop(); }); @@ -262,8 +276,8 @@ export default function MiddlePane(props) { let _canvas; return (
{ @@ -310,6 +324,7 @@ export default function MiddlePane(props) { let dist = Math.sqrt((mouse.x - mouse.click_x) ** 2 + (mouse.y - mouse.click_y) ** 2); if (dist < 10) { setSettings("selected", getHovered()); + props.click(); } }} onMouseEnter={() => { diff --git a/editor-solidjs/src/TilePreset.jsx b/editor-solidjs/src/TilePreset.jsx new file mode 100644 index 0000000..dd5044e --- /dev/null +++ b/editor-solidjs/src/TilePreset.jsx @@ -0,0 +1,37 @@ +import {createMemo, createUniqueId} from "solid-js"; + +import {Pane} from "../stackline-wasm/stackline_wasm.js"; + +import styles from "./LeftPane.module.css"; + +export default function TilePreset(props) { + let {settings, setSettings, index} = props; + + let rendered = createMemo(() => { + let pane = Pane.deserialize(props.pane()); + + let text = pane.to_text(); + let width = pane.width; + + pane.free(); + + return {text, width}; + }); + + let id = createUniqueId(); + + return (
{ + if (settings.picked !== index()) { + setSettings("picked", index()); + } else { + setSettings("picked", null); + } + }} + > +
{rendered().text}
+
{props.name}
+
); +} diff --git a/editor-solidjs/src/index.jsx b/editor-solidjs/src/index.jsx index f2b7a5f..abd0fb7 100644 --- a/editor-solidjs/src/index.jsx +++ b/editor-solidjs/src/index.jsx @@ -6,8 +6,12 @@ import Editor from './Editor'; import init from "../stackline-wasm/stackline_wasm.js"; +import {init as LeftPane_init} from "./LeftPane.jsx"; + await init(); let font = await (new FontFace("Stackline Classic", "url(\"/font/StacklineClassic-Medium.otf\")")).load(); document.fonts.add(font); +LeftPane_init(); + render(() => , document.getElementById('root')); diff --git a/stackline-wasm/src/lib.rs b/stackline-wasm/src/lib.rs index 17c31ab..e108014 100644 --- a/stackline-wasm/src/lib.rs +++ b/stackline-wasm/src/lib.rs @@ -144,6 +144,7 @@ pub struct Pane(SLPane); #[wasm_bindgen] impl Pane { + #[wasm_bindgen(constructor)] pub fn empty(width: usize, height: usize) -> Option { SLPane::empty(width, height).map(|pane| Self(pane)) } @@ -185,6 +186,51 @@ impl Pane { self.0.set_position((x, y)); } } + + pub fn get(&self, x: usize, y: usize) -> Option { + self.0.get((x, y)).map(|tile| FullTile((*tile).clone())) + } + + pub fn set(&mut self, x: usize, y: usize, tile: FullTile) { + if let Some(target) = self.0.get_mut((x, y)) { + *target = tile.0; + } + } + + pub fn serialize(&self) -> JsValue { + JsValue::from_serde(&self.0).map_err(|err| { + err!("Error serializing Pane: {:?}", err); + }).unwrap_or(JsValue::NULL) + } + + pub fn deserialize(value: JsValue) -> Option { + value.into_serde::().map_err(|err| { + err!("Error deserializing Pane: {:?}", err); + }).ok().map(|pane| Pane(pane)) + } + + /// NOTE: `self` must not be part of world + pub fn blit(&self, x: i32, y: i32, world: &mut World) { + for pane in world.0.panes().values() { + if pane as *const SLPane == &self.0 as *const SLPane { + panic!("Cannot blit to a World containing self"); + } + } + + for (dx, dy, tile) in self.0.tiles_iter() { + let x = x + dx as i32; + let y = y + dy as i32; + world.set(x, y, FullTile((*tile).clone())); + } + } + + pub fn to_text(&self) -> String { + let mut surface = TextSurface::new(self.0.width().get(), self.0.height().get()); + + self.0.draw(0, 0, &mut surface, stackline::utils::Blink::default()); + + format!("{:#}", surface) + } } #[derive(Clone, Debug)]