diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..5938097 --- /dev/null +++ b/build.sh @@ -0,0 +1,7 @@ +wasm-pack build --release stackline-wasm --target web --out-dir ../editor-solidjs/stackline-wasm + +[ -d editor-solidjs/font ] && rm editor-solidjs/font +ln -s ../font editor-solidjs/font + +[ -f editor-solidjs/prime.json ] && rm editor-solidjs/prime.json +ln -s ../stackline/tests/other/prime.json editor-solidjs/prime.json diff --git a/editor-solidjs/.gitignore b/editor-solidjs/.gitignore index 75c2320..a62e2d7 100644 --- a/editor-solidjs/.gitignore +++ b/editor-solidjs/.gitignore @@ -2,3 +2,4 @@ node_modules dist stackline-wasm font +prime.json diff --git a/editor-solidjs/index.html b/editor-solidjs/index.html index c0c774b..0bae665 100644 --- a/editor-solidjs/index.html +++ b/editor-solidjs/index.html @@ -2,7 +2,7 @@ - + Stackline web Editor diff --git a/editor-solidjs/src/Editor.jsx b/editor-solidjs/src/Editor.jsx index 772f037..06bc64f 100644 --- a/editor-solidjs/src/Editor.jsx +++ b/editor-solidjs/src/Editor.jsx @@ -11,7 +11,7 @@ import RightPane from "./RightPane.jsx"; import {World, Pane} from "../stackline-wasm/stackline_wasm.js"; -let json = await (await fetch("/stackline-wasm/prime.json")).json(); +let json = await (await fetch("/prime.json")).json(); export default function Editor() { let [world, setWorld] = createSignal(World.deserialize(json), {equals: false}); diff --git a/editor-solidjs/src/Editor.module.css b/editor-solidjs/src/Editor.module.css index e7f1755..b78c79c 100644 --- a/editor-solidjs/src/Editor.module.css +++ b/editor-solidjs/src/Editor.module.css @@ -5,4 +5,5 @@ flex-direction: row; align-items: stretch; overflow-y: hidden; + flex-wrap: nowrap; } diff --git a/editor-solidjs/src/LeftPane.module.css b/editor-solidjs/src/LeftPane.module.css index 42c8da9..2e50180 100644 --- a/editor-solidjs/src/LeftPane.module.css +++ b/editor-solidjs/src/LeftPane.module.css @@ -4,6 +4,7 @@ padding: 1.5em 1em; font-family: monospace; font-size: 15px; + flex-shrink: 0; overflow-y: scroll; } diff --git a/editor-solidjs/src/MiddlePane.jsx b/editor-solidjs/src/MiddlePane.jsx index 8ee5bd0..f14088b 100644 --- a/editor-solidjs/src/MiddlePane.jsx +++ b/editor-solidjs/src/MiddlePane.jsx @@ -46,7 +46,9 @@ export default function MiddlePane(props) { y: 0, click_x: 0, click_y: 0, - hovering: false + hovering: false, + down: false, + touches: [], }); createEffect(() => { @@ -239,10 +241,18 @@ export default function MiddlePane(props) { function resize_cb() { if (!canvas.element) return; setCanvas((obj) => { + let dpr = window.devicePixelRatio ?? 1; + + let width = Math.round(container.clientWidth * dpr); + let height = Math.round(container.clientHeight * dpr); + + obj.element.style.width = `${width / dpr}px`; + obj.element.style.height = `${height / dpr}px`; + return { ...obj, - width: obj.element.width = canvas.element.clientWidth, - height: obj.element.height = canvas.element.clientHeight, + width: obj.element.width = width, + height: obj.element.height = height, }; }); } @@ -251,13 +261,13 @@ export default function MiddlePane(props) { function set_resize() { setTimeout(() => { - resize_listener.observe(canvas.element); + resize_listener.observe(container); }, 0); } function remove_resize() { setTimeout(() => { - resize_listener.unobserve(canvas.element); + resize_listener.unobserve(container); }, 0); } @@ -291,8 +301,9 @@ export default function MiddlePane(props) { onMount={() => {mounted = true}} onCleanup={() => {mounted = false}} onMouseDown={(evt) => { - let mouse_x = evt.clientX - container.offsetLeft; - let mouse_y = evt.clientY - container.offsetTop; + let dpr = window.devicePixelRatio ?? 1; + let mouse_x = (evt.clientX - container.offsetLeft) * dpr; + let mouse_y = (evt.clientY - container.offsetTop) * dpr; setMouse((mouse) => { return { ...mouse, @@ -306,21 +317,23 @@ export default function MiddlePane(props) { }); }} onMouseMove={(evt) => { + let dpr = window.devicePixelRatio ?? 1; setMouse("hovering", true); if (mouse.down) { // TODO: numerically stable solution - setView("cx", view.cx + (evt.clientX - container.offsetLeft) - mouse.x); - setView("cy", view.cy + (evt.clientY - container.offsetTop) - mouse.y); + setView("cx", view.cx + (evt.clientX - container.offsetLeft) * dpr - mouse.x); + setView("cy", view.cy + (evt.clientY - container.offsetTop) * dpr - mouse.y); } - setMouse("x", evt.clientX - container.offsetLeft); - setMouse("y", evt.clientY - container.offsetTop); + setMouse("x", (evt.clientX - container.offsetLeft) * dpr); + setMouse("y", (evt.clientY - container.offsetTop) * dpr); }} onMouseUp={(evt) => { + let dpr = window.devicePixelRatio ?? 1; setMouse("down", false); - setMouse("x", evt.clientX - container.offsetLeft); - setMouse("y", evt.clientY - container.offsetTop); + setMouse("x", (evt.clientX - container.offsetLeft) * dpr); + setMouse("y", (evt.clientY - container.offsetTop) * dpr); let dist = Math.sqrt((mouse.x - mouse.click_x) ** 2 + (mouse.y - mouse.click_y) ** 2); if (dist < 10) { @@ -335,9 +348,92 @@ export default function MiddlePane(props) { setMouse("down", false); setMouse("hovering", false); }} + onTouchStart={(evt) => { + let dpr = window.devicePixelRatio ?? 1; + setMouse("hovering", false); + + setMouse("touches", [...evt.touches].map(touch => ({ + x: (touch.clientX - container.offsetLeft) * dpr, + y: (touch.clientY - container.offsetTop) * dpr, + identifier: touch.identifier, + }))); + + if (evt.touches.length === 1) { + setMouse("down", true); + setMouse("x", mouse.touches[0].x); + setMouse("y", mouse.touches[0].y); + setMouse("click_x", mouse.touches[0].x); + setMouse("click_y", mouse.touches[0].y); + } else { + setMouse("down", false); + } + }} + onTouchEnd={(evt) => { + let identifiers = [...evt.changedTouches].map(t => t.identifier); + setMouse("touches", (arr) => { + return arr.filter(touch => !identifiers.includes(touch.identifier)); + }); + + if (evt.touches.length === 1) { + setMouse("down", true); + setMouse("x", mouse.touches[0].x); + setMouse("y", mouse.touches[0].y); + setMouse("click_x", mouse.touches[0].x); + setMouse("click_y", mouse.touches[0].y); + } else { + setMouse("down", false); + } + }} + oncapture:touchmove={(evt) => { + evt.preventDefault(); + let dpr = window.devicePixelRatio ?? 1; + let touches = [...evt.touches].map(touch => ({ + x: (touch.clientX - container.offsetLeft) * dpr, + y: (touch.clientY - container.offsetTop) * dpr, + identifier: touch.identifier, + })); + + if (evt.touches.length === 1) { + let mouse_x = (evt.touches[0].clientX - container.offsetLeft) * dpr; + let mouse_y = (evt.touches[0].clientY - container.offsetTop) * dpr; + + setView("cx", view.cx + mouse_x - mouse.x); + setView("cy", view.cy + mouse_y - mouse.y); + + setMouse("x", mouse_x); + setMouse("y", mouse_y); + } else if (evt.touches.length === 2) { + let mx = (touches[0].x + touches[1].x) / 2; + let my = (touches[0].y + touches[1].y) / 2; + let dx = mx - (mouse.touches[0].x + mouse.touches[1].x) / 2; + let dy = my - (mouse.touches[0].y + mouse.touches[1].y) / 2; + + let prev_dist = Math.sqrt( + (mouse.touches[0].x - mouse.touches[1].x) ** 2 + + (mouse.touches[0].y - mouse.touches[1].y) ** 2 + ); + + let dist = Math.sqrt( + (touches[0].x - touches[1].x) ** 2 + (touches[0].y - touches[1].y) ** 2 + ); + + let old_zoom = view.zoom; + setView("zoom", Math.min(Math.max(view.zoom + Math.log2(dist / prev_dist), 0.0), 4.0)); + + let delta = Math.ceil(Math.pow(2, view.zoom)) / Math.ceil(Math.pow(2, old_zoom)); + + mx -= canvas.width / 2; + my -= canvas.height / 2; + + setView("cx", (view.cx - mx) * delta + mx + dx); + setView("cy", (view.cy - my) * delta + my + dy); + } + + setMouse("touches", touches); + }} onWheel={(evt) => { let old_zoom = view.zoom; - let zoom = view.zoom + event.deltaY * ZOOM_STRENGTH; + let zoom = view.zoom + evt.deltaY * ZOOM_STRENGTH; zoom = Math.min(Math.max(zoom, 0.0), 4.0); setView("zoom", zoom); @@ -356,12 +452,13 @@ export default function MiddlePane(props) { setSettings("selected", getHovered()); }} onDragOver={(evt) => { + let dpr = window.devicePixelRatio ?? 1; evt.preventDefault(); container.classList.add(styles.dragging); setMouse("hovering", true); - setMouse("x", evt.clientX - container.offsetLeft); - setMouse("y", evt.clientY - container.offsetTop); + setMouse("x", (evt.clientX - container.offsetLeft) * dpr); + setMouse("y", (evt.clientY - container.offsetTop) * dpr); }} onDragLeave={(evt) => { setMouse("hovering", false); diff --git a/editor-solidjs/src/MiddlePane.module.css b/editor-solidjs/src/MiddlePane.module.css index feeae49..e106ac3 100644 --- a/editor-solidjs/src/MiddlePane.module.css +++ b/editor-solidjs/src/MiddlePane.module.css @@ -1,12 +1,17 @@ .MiddlePane { flex-grow: 1; + flex-shrink: 1; background: #202027; position: relative; + height: 100vh; + width: 100%; + overflow-x: hidden; + overflow-y: hidden; } .canvas { - width: 100%; - height: 100vh; + /* width: 100%; */ + /* height: 100vh; */ } .MiddlePane.dragging::after { diff --git a/editor-solidjs/src/RightPane.module.css b/editor-solidjs/src/RightPane.module.css index d5491a1..3928f99 100644 --- a/editor-solidjs/src/RightPane.module.css +++ b/editor-solidjs/src/RightPane.module.css @@ -6,6 +6,7 @@ padding: 1.5em 1em; font-family: monospace; font-size: 15px; + flex-shrink: 0; } .gray, .empty { diff --git a/editor-solidjs/src/TilePreset.jsx b/editor-solidjs/src/TilePreset.jsx index d78e42c..6b266f9 100644 --- a/editor-solidjs/src/TilePreset.jsx +++ b/editor-solidjs/src/TilePreset.jsx @@ -1,4 +1,4 @@ -import {createMemo, createUniqueId} from "solid-js"; +import {createEffect, createMemo, createUniqueId} from "solid-js"; import {Pane} from "../stackline-wasm/stackline_wasm.js"; @@ -21,6 +21,8 @@ export default function TilePreset(props) { let id = createUniqueId(); + let preview; + return (
-
{rendered().text}
+
{rendered().text}
{props.name}
); } diff --git a/editor-solidjs/src/index.css b/editor-solidjs/src/index.css index f6aaede..7550300 100644 --- a/editor-solidjs/src/index.css +++ b/editor-solidjs/src/index.css @@ -1,4 +1,5 @@ body { margin: 0; height: 100vh; + overflow: hidden; }