WIP left pane

main
Shad Amethyst 2 years ago
parent 0b7596fc1c
commit 06040f2c89
Signed by: amethyst
GPG Key ID: D970C8DD1D6DEE36

@ -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 (
<div class={styles.Editor} onMount={mount()} onCleanup={cleanup()}>
<LeftPane />
<MiddlePane settings={settings} world={world} setSettings={setSettings} />
{left_pane}
<MiddlePane settings={settings} world={world} setSettings={setSettings} click={click} />
<RightPane settings={settings} world={world} setWorld={setWorld} setSettings={setSettings} />
</div>
);

@ -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 (<div id="left-pane">
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);
}
</div>);
return [<div class={styles.LeftPane_container}><div
class={styles.LeftPane}
ref={left_pane}
onMount={set_resize()}
onCleanup={remove_resize()}
>
<For each={BASE_TILES}>
{(item, index) => {
return <TilePreset pane={() => item.serialized} name={() => item.name} settings={settings} setSettings={setSettings} index={index} />;
}}
</For>
</div></div>, preset];
}

@ -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;
}

@ -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 (<div
class={styles.MiddlePane}
onMount={window.addEventListener("resize", resize_listener)}
onCleanup={window.removeEventListener("resize", resize_listener)}
onMount={set_resize()}
onCleanup={remove_resize()}
>
<canvas
ref={(el) => {
@ -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={() => {

@ -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 (<div
class={styles.TilePreset}
class={settings.picked === index() ? styles.picked : ""}
onClick={() => {
if (settings.picked !== index()) {
setSettings("picked", index());
} else {
setSettings("picked", null);
}
}}
>
<pre class={styles.preview} style={{width: 30 * rendered().width + "px"}} id={id}>{rendered().text}</pre>
<div>{props.name}</div>
</div>);
}

@ -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(() => <Editor />, document.getElementById('root'));

@ -144,6 +144,7 @@ pub struct Pane(SLPane);
#[wasm_bindgen]
impl Pane {
#[wasm_bindgen(constructor)]
pub fn empty(width: usize, height: usize) -> Option<Pane> {
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<FullTile> {
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<Pane> {
value.into_serde::<SLPane>().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)]

Loading…
Cancel
Save