🔥 Prototype property editor

main
Shad Amethyst 2 years ago
parent bf483159bf
commit 461abcac43
Signed by: amethyst
GPG Key ID: D970C8DD1D6DEE36

@ -22,7 +22,10 @@ let mouse_x = 0, mouse_y = 0;
let mouse_down = false;
let hovered = false;
let start = performance.now();
export function draw() {
world.set_blink_duration((performance.now() - start) / 1000.0);
let zoom_factor = Math.ceil(Math.pow(2, zoom));
let tile_size = 10 * zoom_factor;
ctx.fillStyle = "#202027";

@ -25,11 +25,63 @@
<h2>Signal:</h2>
<div id="signal" class="indent">
<h3>Stack:</h3>
<ol id="stack"></ol>
<ol id="stack" class="stack"></ol>
</div>
<h2>Tile:</h2>
<div id="tile" class="indent">
<span>Type:</span> <span id="tile-name"></span>
<b>Type:</b> <span id="tile-name"></span>
<ul id="tile-properties">
</ul>
<template class="property-template" name="Orientation">
<select>
<option value="Any" default>Any</option>
<option value="Vertical">Vertical</option>
<option value="Horizontal">Horizontal</option>
</select>
</template>
<template class="property-template" name="Direction">
<select>
<option value="Up" default>Up</option>
<option value="Right">Right</option>
<option value="Down">Down</option>
<option value="Left">Left</option>
</select>
</template>
<template class="property-template" name="Value">
<div class="value-input number">
<select title="Value type">
<option value="Number" default>Number</option>
<option value="String">String</option>
</select>
<div class="value-number">
<input type="number" value="0" />
</div>
<div class="value-string">
"<input type="string" value="" />"
</div>
</div>
</template>
<template class="property-template" name="Uint">
<input type="number" min="0" value="0" />
</template>
<template class="property-template" name="Int">
<input type="number" min="0" value="0" />
</template>
<template class="property-template" name="Signal">
<div class="indent">
<h3>Direction:</h3>
<select class="direction">
<option value="Up" default>Up</option>
<option value="Right">Right</option>
<option value="Down">Down</option>
<option value="Left">Left</option>
</select>
<h3>Stack:</h3>
<ol class="stack"></ol>
</div>
</template>
</div>
</div>
</body>

@ -13,6 +13,9 @@ const signal_elem = document.getElementById("signal");
const state_elem = document.getElementById("state");
const tile_elem = document.getElementById("tile");
const tile_name_elem = document.getElementById("tile-name");
const properties_elem = document.getElementById("tile-properties");
let templates = new Map();
export function update_selected(x, y) {
coordinates.innerText = `(${x}, ${y})`;
@ -54,6 +57,38 @@ export function update_selected(x, y) {
let name = Object.keys(tile)[0];
tile_name_elem.innerText = name;
// Set properties
// TODO: only change the properties when necessary
while (properties_elem.children.length > 0) {
properties_elem.removeChild(properties_elem.firstChild);
}
let schema = full_tile.schema();
for (let [label, type, value] of schema_iter(schema, tile[name])) {
let input = create_input(type, value, on_change);
if (!input) continue;
let elem = document.createElement("li");
let label_elem = document.createElement("b");
label_elem.innerText = `${label}: `;
elem.appendChild(label_elem);
elem.appendChild(input);
properties_elem.appendChild(elem);
function on_change(new_value) {
// Traverse `tile` and change the corresponding schema element
tile[name] = schema_recurse(schema, tile[name], label, new_value);
let full_tile = world.get(x, y);
full_tile.tile = tile;
world.set(x, y, full_tile);
}
}
} else {
tile_elem.classList.remove("has-tile");
}
@ -72,4 +107,159 @@ export function init() {
// option.value = name;
// tile_name_elem.appendChild(option);
// }
document.querySelectorAll(".property-template").forEach((template) => {
templates.set(template.attributes?.name?.value ?? template.id, template);
});
}
function* schema_iter(schema, tile) {
if (Array.isArray(schema)) {
for (let n = 0; n < schema.length; n++) {
yield* schema_iter(schema[n], tile[n]);
}
} else if (typeof schema === "object") {
for (let name in schema) {
yield* schema_iter(schema[name], tile[name]);
}
} else if (typeof schema === "string") {
let res = schema.split(":");
res.push(tile);
yield res;
}
}
function schema_recurse(schema, tile, label, value) {
if (Array.isArray(schema)) {
for (let n = 0; n < schema.length; n++) {
tile[n] = schema_recurse(schema[n], tile[n], label, value);
}
return tile;
} else if (typeof schema === "object") {
for (let name in schema) {
tile[name] = schema_recurse(schema[name], tile[name], label, value);
}
return tile;
} else if (typeof schema === "string") {
if (schema.split(":")[0] === label) {
return value;
} else {
return tile;
}
}
}
function create_input(type, value, change_cb) {
let options = type.split("|");
for (let option of options) {
if (option === "Value") {
let res = templates.get("Value").content.cloneNode(true).querySelector("*");
let select_elem = res.querySelector("select");
let type = Object.keys(value)[0];
set_type(type);
function set_type(new_type) {
let map = new Map([["String", "string"], ["Number", "number"]]);
for (let [ty, cn] of map) {
if (ty === new_type) {
res.classList.add(cn);
} else {
res.classList.remove(cn);
}
}
type = new_type;
}
select_elem.addEventListener("change", () => {
set_type(select_elem.value);
if (type === "Number") {
value = {"Number": 0.0};
number_input.value = 0;
} else if (type === "String") {
value = {"String": ""};
string_input.value = "";
}
change_cb(value);
});
let number_input = res.querySelector(".value-number input");
if (type === "Number") {
number_input.value = value["Number"];
}
number_input.addEventListener("change", (evt) => {
if (type === "Number") {
value["Number"] = +number_input.value;
change_cb(value);
}
});
let string_input = res.querySelector(".value-string input");
if (type === "String") {
string_input.value = value["String"];
}
string_input.addEventListener("change", (evt) => {
if (type === "String") {
value["String"] = string_input.value;
change_cb(value);
}
});
return res;
} else if (option === "Uint" || option === "Int") {
let res = templates.get(option).content.cloneNode(true).querySelector("*");
if (value !== undefined) {
res.value = value;
}
res.addEventListener("change", () => {
change_cb(+res.value);
});
return res;
} else if (option === "Signal") {
let res = templates.get(option).content.cloneNode(true).querySelector("*");
let stack_elem = res.querySelector(".stack");
for (let elem of value.stack) {
let li = document.createElement("li");
if (elem["Number"]) {
li.innerText = elem["Number"];
} else if (elem["String"]) {
li.innerText = `"${elem["String"]}"`;
}
stack_elem.appendChild(li);
}
let direction_elem = res.querySelector(".direction");
direction_elem.value = value.direction;
return res;
}
if (templates.has(option)) {
let res = templates.get(option).content.cloneNode(true).querySelector("*");
if (value !== undefined) {
res.value = value;
}
res.addEventListener("change", () => {
change_cb(res.value);
});
return res;
}
}
}

@ -84,23 +84,69 @@ h3 {
color: #808080;
}
#stack {
.stack {
list-style: none;
padding-left: 0em;
margin: 0 0;
}
#stack > li {
.stack > li {
margin-top: 2px;
margin-bottom: 2px;
}
#stack > li::before {
.stack > li::before {
content: "\25b6";
margin-right: 0.5em;
color: #808080;
}
#tile-properties {
list-style-type: none;
padding-left: 0;
margin-top: 0;
}
#tile-properties > li {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
select, input {
background: rgba(0, 0, 0, 0.1);
color: inherit;
font-family: inherit;
font-size: inherit;
border: 1px solid #808080;
border-radius: 2px;
}
select:hover, input:hover, input:active {
color: white;
background: rgba(0, 0, 0, 0.4);
border: 1px solid #a0a0a0;
}
#tile-properties input[type="string"], #tile-properties input[type="number"] {
width: 6em;
}
.value-input {
display: inline;
}
.value-input .value-number, .value-input .value-string {
display: none;
}
.value-input.number .value-number, .value-input.string .value-string {
display: inline;
}
.value-input .value-number input, .value-input .value-string input {
display: inline;
}
.indent {
margin-left: 0.5em;
padding-left: calc(0.5em - 1px);

@ -270,7 +270,7 @@ impl FullTile {
.map(|(key, value)| (key, construct_value(value)))
.collect(),
),
TileSchema::Value(string) => Value::String(string),
TileSchema::Value(label, ty) => Value::String(format!("{}:{}", label, ty)),
}
}

@ -197,7 +197,8 @@ pub trait Tile: std::clone::Clone + std::fmt::Debug + Serialize + for<'d> Deseri
pub enum TileSchema {
Tuple(Vec<TileSchema>),
Map(HashMap<String, TileSchema>),
Value(String),
/// (label, type)
Value(String, String),
}
impl TileSchema {
@ -209,8 +210,8 @@ impl TileSchema {
Self::Map(HashMap::new())
}
pub fn value<S: ToString>(name: S) -> Self {
Self::Value(name.to_string())
pub fn value<S1: ToString, S2: ToString>(label: S1, ty: S2) -> Self {
Self::Value(label.to_string(), ty.to_string())
}
pub fn add<S: ToString>(mut self, name: S, value: TileSchema) -> Self {
@ -221,6 +222,15 @@ impl TileSchema {
self
}
pub fn push(mut self, value: TileSchema) -> Self {
match self {
Self::Tuple(ref mut vec) => vec.push(value),
_ => panic!("Invalid TileSchema builder: cannot push() to a non-Tuple"),
};
self
}
}
pub mod prelude {

@ -41,6 +41,11 @@ impl Tile for Push {
fn draw_simple(&self, ctx: DrawContext) -> TextChar {
TextChar::from_state('P', ctx.state)
}
fn schema(&self) -> TileSchema {
TileSchema::map()
.add("value", TileSchema::value("Value", "Value"))
}
}
/// Pops `n` values from the top of the stack, then lets the signal through
@ -87,6 +92,11 @@ impl Tile for Pop {
TextChar::from_state(ctx.blink.scroll(&format!("\u{2c63}\u{2c63}\u{2c63}{}", self.amount)), ctx.state)
}
}
fn schema(&self) -> TileSchema {
TileSchema::map()
.add("amount", TileSchema::value("Amount", "Uint"))
}
}
/// Reverses the order of the top `n` values from the stack, then lets the signal through
@ -138,6 +148,11 @@ impl Tile for Swap {
TextChar::from_state(ctx.blink.scroll(&format!("sss{}", self.amount)), ctx.state)
}
}
fn schema(&self) -> TileSchema {
TileSchema::map()
.add("amount", TileSchema::value("Amount", "Uint"))
}
}
/// Duplicates the nth element from the stack, pushing it back on top
@ -179,6 +194,11 @@ impl Tile for Duplicate {
TextChar::from_state(ctx.blink.scroll(&format!("ddd{}", self.offset)), ctx.state)
}
}
fn schema(&self) -> TileSchema {
TileSchema::map()
.add("offset", TileSchema::value("Offset", "Uint"))
}
}
// TODO: SwapN, RotateLeft, RotateRight, RotateLeftN, RotateRightN

@ -42,6 +42,11 @@ impl Tile for Store {
TextChar::from_state('\u{25cb}', ctx.state) // WHITE CIRCLE
}
}
fn schema(&self) -> TileSchema {
TileSchema::map()
.add("signal", TileSchema::value("Stored signal", "Signal|null"))
}
}
/// When a signal is received, reads a signal from a [`Store`] (at the reader's tail),
@ -97,6 +102,10 @@ impl Tile for Reader {
Direction::Up => TextChar::from_state('\u{25b3}', ctx.state), // WHITE UP-POINTING TRIANGLE
}
}
fn schema(&self) -> TileSchema {
TileSchema::value("Direction", "Direction")
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
@ -193,6 +202,12 @@ impl Tile for Stacker {
_ => TextChar::from_state('+', ctx.state),
}
}
fn schema(&self) -> TileSchema {
TileSchema::map()
.add("signal", TileSchema::value("Signal to stack", "Signal|null"))
.add("orientation", TileSchema::value("Orientation", "Orientation"))
}
}
/// Tries to convert a [`FullTile`] to a [`Store`]

@ -31,6 +31,17 @@ impl Tile for Teleporter {
fn draw_simple(&self, ctx: DrawContext) -> TextChar {
TextChar::from_state('T', ctx.state)
}
fn schema(&self) -> TileSchema {
TileSchema::map()
.add(
"coordinates",
TileSchema::tuple()
.push(TileSchema::value("Pane", "String"))
.push(TileSchema::value("X", "Uint"))
.push(TileSchema::value("Y", "Uint"))
)
}
}
/// Sends a signal through a virtual wire towards `coordinates`.

@ -45,7 +45,7 @@ impl Tile for Wire {
}
fn schema(&self) -> TileSchema {
TileSchema::value("Orientation")
TileSchema::value("Orientation", "Orientation")
}
}
@ -93,7 +93,7 @@ impl Tile for Diode {
}
fn schema(&self) -> TileSchema {
TileSchema::value("Direction")
TileSchema::value("Direction", "Direction")
}
}
@ -146,8 +146,8 @@ impl Tile for Resistor {
fn schema(&self) -> TileSchema {
TileSchema::map()
.add("direction", TileSchema::value("Direction"))
.add("signal", TileSchema::value("Signal|null"))
.add("direction", TileSchema::value("Direction", "Direction"))
.add("signal", TileSchema::value("Signal", "Signal|null"))
}
}

Loading…
Cancel
Save