Ability to create and edit Panes in the editor

main
Shad Amethyst 2 years ago
parent 0ed5df3fcc
commit aa495b8747
Signed by: amethyst
GPG Key ID: D970C8DD1D6DEE36

@ -16,10 +16,27 @@ import Controls from "./Controls.jsx";
import {World, Pane} from "../stackline-wasm/stackline_wasm.js"; import {World, Pane} from "../stackline-wasm/stackline_wasm.js";
let json = await (await fetch("/prime.json")).json(); // let json = await (await fetch("/prime.json")).json();
let new_world = window.localStorage.getItem("world");
export default function Editor() { export default function Editor() {
let [world, setWorld] = createSignal(World.deserialize(json), {equals: false}); let start_world = window.localStorage.getItem("world");
try {
if (start_world) {
start_world = JSON.parse(start_world);
start_world = World.deserialize(start_world);
} else {
start_world = new World();
}
} catch (err) {
console.error(err);
start_world = new World();
}
let [world, setWorld] = createSignal(start_world, {equals: false});
start_world = null;
world.init = () => setWorld((world) => { world.init = () => setWorld((world) => {
world.init(); world.init();
@ -39,13 +56,13 @@ export default function Editor() {
}); });
setWorld((world) => { setWorld((world) => {
let tile = world.get(4, 0); // let tile = world.get(4, 0);
tile.signal = {direction: "Up", stack: []}; // tile.signal = {direction: "Up", stack: []};
tile.state = "Active"; // tile.state = "Active";
world.set(4, 0, tile); // world.set(4, 0, tile);
world.init(); // world.init();
console.log(world.toString()); // console.log(world.toString());
return world; return world;
}); });
@ -108,7 +125,7 @@ export default function Editor() {
} }
}); });
let [left_pane, presets] = LeftPane({settings, setSettings}); let [left_pane, presets] = LeftPane({settings, setSettings, world, setWorld});
let last_click = 0, last_selected = null; let last_click = 0, last_selected = null;
const DOUBLE_CLICK_DURATION = 250; const DOUBLE_CLICK_DURATION = 250;

@ -1,42 +1,16 @@
import {createSignal} from "solid-js"; import {onMount, onCleanup} from "solid-js";
import {createStore} from "solid-js/store"; import Tabbed from "./Tabbed.jsx";
import styles from "./LeftPane.module.css"; import Presets, {init} from "./Presets.jsx";
import Panes from "./Panes.jsx";
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()) { import styles from "./LeftPane.module.css";
let pane = new Pane(1, 1);
let tile = new FullTile(name);
pane.set(0, 0, tile);
let serialized = pane.serialize(); export {init};
pane.free();
BASE_TILES.push(new Tile(serialized, name));
}
}
export default function LeftPane(props) { export default function LeftPane(props) {
let [preset, setPresets] = createStore(BASE_TILES); let [preset_picker, presets] = Presets(props);
let {settings, setSettings} = props; let container;
let left_pane;
function resize() { function resize() {
let width = document.body.clientWidth / 4; let width = document.body.clientWidth / 4;
@ -48,30 +22,29 @@ export default function LeftPane(props) {
width = 125; width = 125;
} }
left_pane.style.width = `${Math.round(width)}px`; container.style.width = `calc(${Math.round(width)}px + 2em)`;
} }
function set_resize() { onMount(() => {
setTimeout(() => { setTimeout(() => {
window.addEventListener("resize", resize); window.addEventListener("resize", resize);
resize(); resize();
}, 0); }, 0);
} });
function remove_resize() { onCleanup(() => {
window.removeEventListener("resize", resize); window.removeEventListener("resize", resize);
} });
return [<div class={styles.LeftPane_container}><div return [<div class={styles.LeftPane_container}><div
class={styles.LeftPane} class={styles.LeftPane}
ref={left_pane} ref={container}
onMount={set_resize()}
onCleanup={remove_resize()}
> >
<For each={BASE_TILES}> <Tabbed>
{(item, index) => { <div title="Presets">{preset_picker}</div>
return <TilePreset pane={() => item.serialized} name={() => item.name} settings={settings} setSettings={setSettings} index={index} />; <div title="Panes">
}} <Panes world={props.world} setWorld={props.setWorld} />
</For> </div>
</div></div>, preset]; </Tabbed>
</div></div>, presets];
} }

@ -1,7 +1,7 @@
.LeftPane_container { .LeftPane_container {
background: #18181b; background: #18181b;
color: white; color: white;
padding: 1.5em 1em; /* padding: 1.5em 1em; */
font-family: monospace; font-family: monospace;
font-size: 15px; font-size: 15px;
flex-shrink: 0; flex-shrink: 0;
@ -11,6 +11,9 @@
.LeftPane { .LeftPane {
width: 25vw; width: 25vw;
}
.Presets {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, calc(8em)); grid-template-columns: repeat(auto-fill, calc(8em));
grid-gap: 1em; grid-gap: 1em;
@ -48,3 +51,7 @@
.TilePreset.picked:hover { .TilePreset.picked:hover {
border: 4px solid white; border: 4px solid white;
} }
.indent {
padding-left: 1em;
}

@ -0,0 +1,73 @@
import {createMemo} from "solid-js";
import Number from "./input/Number.jsx";
import styles from "./LeftPane.module.css";
import input_styles from "./input/input.module.css";
import {Pane} from "../stackline-wasm/stackline_wasm.js";
export default function Panes(props) {
let {world, setWorld} = props;
let panes = createMemo(() => {
let world = props.world();
let res = [];
for (let pane_name of world.panes()) {
let pane = world.get_pane(pane_name);
res.push({
x: pane.x,
y: pane.y,
width: pane.width,
height: pane.height,
name: pane_name,
});
pane.free();
}
return res;
});
return (<div>
<ol>
<For each={panes()}>
{(item, _index) => {
function setProp(name) {
return function(value) {
setWorld((world) => {
let pane = world.get_pane(item.name);
pane[name] = value;
world.set_pane(item.name, pane);
return world;
});
}
}
return <li>
<span>{item.name}</span>:
<div class={styles.indent}>
<div>X: <Number value={() => item.x} setValue={setProp("x")} /></div>
<div>Y: <Number value={() => item.y} setValue={setProp("y")} /></div>
<div>Width: <Number value={() => item.width} setValue={setProp("width")} /></div>
<div>Height: <Number value={() => item.height} setValue={setProp("height")} /></div>
</div>
</li>;
}}
</For>
</ol>
<button
class={input_styles.button}
aria-label="Add a new Pane to the World"
title="Add Pane"
onClick={() => {
setWorld((world) => {
let name = `pane-${world.panes().length + 1}`;
let pane = new Pane(1, 1);
world.set_pane(name, pane);
return world;
});
}}
>+</button>
</div>);
}

@ -0,0 +1,47 @@
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 Presets(props) {
let [presets, setPresets] = createStore(BASE_TILES);
let {settings, setSettings} = props;
return [<div class={styles.Presets}>
<For each={BASE_TILES}>
{(item, index) => {
return <TilePreset pane={() => item.serialized} name={() => item.name} settings={settings} setSettings={setSettings} index={index} />;
}}
</For>
</div>, presets];
}

@ -43,47 +43,6 @@ export default function Signal(props) {
} : null; } : null;
return <li><Value value={() => item} setValue={setValue} /></li>; return <li><Value value={() => item} setValue={setValue} /></li>;
// if (setSignal) {
// if ("Number" in item) {
// // Return number input for the `index()`-th element
// return <li>
// <input
// class={input_styles.input}
// type="number"
// value={item["Number"]}
// onChange={(evt) => {
// console.log("input");
// setSignal((signal) => {
// signal.stack[index()] = {"Number": +evt.currentTarget.value};
// return signal;
// });
// }}
// />
// </li>;
// } else if ("String" in item) {
// // Return string input for the `index()`-th element
// return <li>"
// <input
// class={input_styles.input}
// type="string"
// value={item["String"]}
// onChange={(evt) => {
// setSignal((signal) => {
// signal.stack[index()] = {"String": evt.currentTarget.value};
// return signal;
// });
// }}
// />
// "</li>;
// }
// } else {
// if (item?.["Number"]) {
// return <li>{item["Number"]}</li>;
// } else if (item?.["String"]) {
// return <li>"{item["String"]}"</li>;
// }
// }
}} }}
</For> </For>
<Show when={setSignal}> <Show when={setSignal}>

@ -6,6 +6,8 @@ export default function Tabbed(props) {
let children = props.children; let children = props.children;
if (!Array.isArray(children)) children = [children];
return <div class={styles.Tabbed}> return <div class={styles.Tabbed}>
<ol class={styles.header}> <ol class={styles.header}>
<For each={children.map(item => item.title)}> <For each={children.map(item => item.title)}>

@ -24,7 +24,7 @@
.header > li { .header > li {
box-sizing: border-box; box-sizing: border-box;
height: 2em; height: 2em;
padding: 0.5em; padding: 0.25em 0.5em;
color: #d0d0d0; color: #d0d0d0;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;

@ -43,6 +43,7 @@ pub fn available_tiles() -> Vec<JsValue> {
#[wasm_bindgen] #[wasm_bindgen]
impl World { impl World {
/// Creates a new World instance /// Creates a new World instance
#[wasm_bindgen(constructor)]
pub fn new() -> Self { pub fn new() -> Self {
Self(SLWorld::new()) Self(SLWorld::new())
} }
@ -159,11 +160,29 @@ impl Pane {
self.0.width().get() self.0.width().get()
} }
#[wasm_bindgen(setter)]
pub fn set_width(&mut self, width: usize) {
let height = self.0.height().get();
self.0.resize(width, height).unwrap_or_else(|| {
err!("Error while resizing Pane");
});
}
#[wasm_bindgen(getter)] #[wasm_bindgen(getter)]
pub fn height(&self) -> usize { pub fn height(&self) -> usize {
self.0.height().get() self.0.height().get()
} }
#[wasm_bindgen(setter)]
pub fn set_height(&mut self, height: usize) {
let width = self.0.width().get();
self.0.resize(width, height).unwrap_or_else(|| {
err!("Error while resizing Pane");
});
}
#[wasm_bindgen(getter)] #[wasm_bindgen(getter)]
pub fn position(&self) -> Vec<i32> { pub fn position(&self) -> Vec<i32> {
let (x, y) = self.0.position(); let (x, y) = self.0.position();
@ -180,6 +199,20 @@ impl Pane {
self.0.position().1 self.0.position().1
} }
#[wasm_bindgen(setter)]
pub fn set_x(&mut self, value: i32) {
let mut pos = self.0.position();
pos.0 = value;
self.0.set_position(pos);
}
#[wasm_bindgen(setter)]
pub fn set_y(&mut self, value: i32) {
let mut pos = self.0.position();
pos.1 = value;
self.0.set_position(pos);
}
#[wasm_bindgen(setter)] #[wasm_bindgen(setter)]
pub fn set_position(&mut self, position: &[i32]) { pub fn set_position(&mut self, position: &[i32]) {
if let [x, y] = position[..] { if let [x, y] = position[..] {

@ -522,6 +522,31 @@ impl Pane {
.map(move |(i, v)| (i % self.width, i / self.width, v)) .map(move |(i, v)| (i % self.width, i / self.width, v))
} }
pub fn resize(&mut self, width: usize, height: usize) -> Option<()> {
let width_nz: NonZeroUsize = width.try_into().ok()?;
let height_nz: NonZeroUsize = height.try_into().ok()?;
let tiles = std::mem::replace(&mut self.tiles, VecCell::from(vec![FullTile::default(); width * height]));
for (index, tile) in tiles.into_iter().enumerate() {
let x = index % self.width;
let y = index / self.width;
if x >= width || y >= height {
continue;
}
let new_index = y * width + x;
if let Some(slot) = self.tiles.get_mut(new_index) {
*slot = tile;
}
}
self.width = width_nz;
self.height = height_nz;
Some(())
}
/// Draws the Pane at `(dx + self.position.0, dy + self.position.1)` on a [`TextSurface`]. /// Draws the Pane at `(dx + self.position.0, dy + self.position.1)` on a [`TextSurface`].
/// Empty tiles will leave the `TextSurface` untouched, but tiles are free to modify the characters around them. /// Empty tiles will leave the `TextSurface` untouched, but tiles are free to modify the characters around them.
pub fn draw(&self, dx: i32, dy: i32, surface: &mut TextSurface, blink: Blink) { pub fn draw(&self, dx: i32, dy: i32, surface: &mut TextSurface, blink: Blink) {
@ -684,4 +709,34 @@ mod test {
HashSet::from_iter(vec![(0, 0), (1, 0)].into_iter()) HashSet::from_iter(vec![(0, 0), (1, 0)].into_iter())
); );
} }
#[test]
fn test_pane_resize() {
use crate::tile::Wire;
use Orientation::*;
for w in 3..6 {
for h in 3..6 {
let mut pane = test_tile_setup!(
2,
2,
[
Wire::new(Horizontal),
Wire::new(Vertical),
Wire::new(Any),
()
]
);
pane.resize(w, h);
assert_eq!(pane.width().get(), w);
assert_eq!(pane.height().get(), h);
assert_eq!(pane.get_as::<Wire>((0, 0)).unwrap(), Wire::new(Horizontal));
assert_eq!(pane.get_as::<Wire>((1, 0)).unwrap(), Wire::new(Vertical));
assert_eq!(pane.get_as::<Wire>((0, 1)).unwrap(), Wire::new(Any));
}
}
}
} }

Loading…
Cancel
Save