Compare commits

...

2 Commits

Author SHA1 Message Date
2858eb2d27 function call typo 2022-12-21 23:53:27 +01:00
7c9d7d2d5b switching to composition API 2022-12-21 23:45:22 +01:00
11 changed files with 441 additions and 576 deletions

View File

@ -1,72 +1,55 @@
<script setup>
import MainMenu from "./components/MainMenu.vue";
import CanvasBoard from "./components/CanvasBoard.vue";
import MenuReset from "./components/MenuReset.vue";
import { globalStore } from "./stores/index.js";
import { nextTick, onBeforeUnmount, onMounted, ref } from "vue";
const store = globalStore();
const windowWidth = ref(window.innerWidth);
const toggleMainMenu = () => {
store.setMainMenu(!store.mainMenu);
};
const onResize = () => {
nextTick(() => {
windowWidth.value = window.innerWidth;
store.canvasWidth = window.innerWidth;
store.canvasHeight = window.innerHeight;
store.setBoardWidth();
store.setBoardHeight();
});
};
onMounted(() => {
nextTick(() => {
window.addEventListener("resize", onResize);
});
});
onBeforeUnmount(() => {
window.removeEventListener("resize", onResize);
});
</script>
<template> <template>
<div id="main"> <div id="main">
<h1 id="main-title"> <h1 id="main-title">
<span id="burger-toggle" @click="toggleMainMenu">{{ <span id="burger-toggle" @click="toggleMainMenu">
mainMenu == true ? "▼" : "☰" {{ store.mainMenu == true ? "▼" : "☰" }}
}}</span> </span>
Cellular Automata Explorer Cellular Automata Explorer
</h1> </h1>
<div id="container"> <div id="container">
<MainMenu v-if="mainMenu || windowWidth >= 800" /> <MainMenu v-if="store.mainMenu || windowWidth >= 800" />
<CanvasBoard /> <CanvasBoard />
</div> </div>
<MenuReset row-title="" /> <MenuReset row-title="" />
</div> </div>
</template> </template>
<script>
import MainMenu from "./components/MainMenu.vue";
import CanvasBoard from "./components/CanvasBoard.vue";
import MenuReset from "./components/MenuReset.vue";
import { mapState, mapWritableState, mapActions } from "pinia";
import { globalStore } from "./stores/index.js";
export default {
name: "App",
components: {
MainMenu,
MenuReset,
CanvasBoard,
},
data() {
return {
windowWidth: window.innerWidth,
};
},
computed: {
...mapState(globalStore, ["mainMenu", "activeSubMenu"]),
...mapWritableState(globalStore, ["canvasWidth", "canvasHeight"]),
},
mounted() {
this.$nextTick(() => {
window.addEventListener("resize", this.onResize);
});
},
beforeUnmount() {
window.removeEventListener("resize", this.onResize);
},
methods: {
...mapActions(globalStore, [
"setBoardWidth",
"setBoardHeight",
"setMainMenu",
]),
toggleMainMenu() {
this.setMainMenu(!this.mainMenu);
},
onResize() {
this.$nextTick(() => {
this.windowWidth = window.innerWidth;
this.canvasWidth = window.innerWidth;
this.canvasHeight = window.innerHeight;
this.setBoardWidth();
this.setBoardHeight();
});
},
},
};
</script>
<style scope> <style scope>
:root { :root {
--dark1: #000000; --dark1: #000000;

View File

@ -1,19 +1,5 @@
<template> <script setup>
<canvas import { onMounted, watch } from "vue";
id="board-canvas"
ref="board-canvas"
:width="canvasWidth"
:height="canvasHeight"
/>
<canvas
id="work-canvas"
ref="work-canvas"
:width="canvasWidth"
:height="canvasHeight"
/>
</template>
<script>
import { mapActions, mapState, mapWritableState } from "pinia";
import { globalStore } from "../stores/index.js"; import { globalStore } from "../stores/index.js";
import { import {
create1dState, create1dState,
@ -31,224 +17,214 @@
import { getRandomInt } from "../modules/common.js"; import { getRandomInt } from "../modules/common.js";
import { boardToPic, picToBoard } from "../modules/picture.js"; import { boardToPic, picToBoard } from "../modules/picture.js";
export default { const store = globalStore();
name: "CanvasBoard",
data() { // TODO: Do we really need to declare a work canvas in this scope?
return { // Do we really need to declare a canvas here at all?
board: null, let canvas = null;
canvas: null, let workCanvas = null;
workCanvas: null, let workCtx = null;
workCtx: null, let ctx = null;
ctx: null, const available2dRules = {
available2dRules: { conway: conwayRules,
conway: conwayRules, overpopulation: overpopulationRules,
overpopulation: overpopulationRules, loneliness: lonelinessRules,
loneliness: lonelinessRules, threeborn: threebornRules,
threeborn: threebornRules, highlife: highLifeRules,
highlife: highLifeRules, serviette: servietteRules,
serviette: servietteRules, };
},
}; // used to determine the dimensions of the board
}, const max = () => {
computed: { return Math.max(store.boardWidth, store.boardHeight);
...mapState(globalStore, { };
loop: "loop",
cellProperties: "cellProperties", const selectedRules = () => {
ruleset: "ruleset1d", return available2dRules[store.selected2dRules.id];
refreshRate: "refreshRate", };
initial1dState: "initial1dState",
drawingDirection: "drawingDirection", watch(
canDraw: "canDraw", () => store.draw1d,
getDraw1d: "draw1d", (value) => {
getDraw2d: "draw2d", if (value == true) draw1d();
getDraw2dLast: "draw2dLast", }
getDraw2dPicture: "draw2dpicture", );
boardWidth: "boardWidth",
boardHeight: "boardHeight", watch(
selected2dRules: "selected2dRules", () => store.draw2d,
picture: "picture", (value) => {
}), if (value == true) draw2dNew();
...mapWritableState(globalStore, { }
lastBoard: "lastBoard", );
canvasWidth: "canvasWidth",
canvasHeight: "canvasHeight", watch(
getReset: "reset", () => store.draw2dLast,
}), async (value) => {
// used to determine the dimensions of the board if (value == true) await draw2dLast();
max() { }
return Math.max(this.boardWidth, this.boardHeight); );
},
selectedRules() { watch(
return this.available2dRules[this.selected2dRules.id]; () => store.draw2dpicture,
}, (value) => {
}, if (value == true) draw2dPicture();
watch: { }
getDraw1d(value) { );
if (value == true) this.draw1d();
}, watch(
getDraw2d(value) { () => store.reset,
if (value == true) this.draw2dNew(); (value) => {
}, if (value == true) reset();
async getDraw2dLast(value) { }
if (value == true) await this.draw2dLast(); );
},
getDraw2dPicture(value) { onMounted(() => {
if (value == true) this.draw2dPicture(); canvas = Object.freeze(document.getElementById("board-canvas"));
}, workCanvas = Object.freeze(document.getElementById("work-canvas"));
getReset(value) { ctx = canvas.getContext("2d", { willReadFrequently: true });
if (value == true) this.reset(); workCtx = workCanvas.getContext("2d", {
}, willReadFrequently: true,
}, });
mounted() { store.canvasWidth = canvas.parentElement.clientWidth;
this.canvas = Object.freeze(document.getElementById("board-canvas")); store.canvasHeight = canvas.parentElement.clientHeight;
this.workCanvas = Object.freeze(document.getElementById("work-canvas")); store.setBoardWidth();
this.ctx = this.canvas.getContext("2d", { willReadFrequently: true }); store.setBoardHeight();
this.workCtx = this.workCanvas.getContext("2d", { });
willReadFrequently: true,
// draws the board on the canvas
const drawCanvas = async (board, width, height) => {
const d = store.cellProperties.size;
// bool to RGBA colors
const img = await boardToPic(board, width, height, store.cellProperties);
// rescale and draw
ctx.save();
ctx.clearRect(0, 0, store.canvasWidth, store.canvasHeight);
workCtx.putImageData(img, 0, 0);
ctx.imageSmoothingEnabled = false;
ctx.scale(d, d);
ctx.drawImage(workCanvas, 0, 0, store.canvasWidth, store.canvasHeight);
ctx.restore();
};
// create a first state, either a single living cell
// at the center or random ones
const compute1dInitialState = () => {
if (store.initial1dState === "onecell")
return create1dStateOneCell(store.boardWidth);
return create1dState(store.boardWidth, getRandomInt, [0, 2]);
};
// initialize board with random cells
const randomInitialState = () => {
return create2dState(
store.boardWidth,
store.boardHeight,
getRandomInt,
[0, 2]
);
};
// draw elementary automaton on the canvas based on selected ruleset
const draw1d = () => {
const initialState = compute1dInitialState();
const board = createBoard(initialState, store.ruleset1d.rules, max());
store.lastBoard = Object.freeze(board);
// TODO: the board clearly could be an object
drawCanvas(store.lastBoard, store.boardWidth, store.boardHeight);
store.toggleStop();
};
// draw 2D automaton on the canvas in a loop
const draw2d = (board) => {
const newBoard = evolve2d(board, selectedRules());
drawCanvas(newBoard, store.boardWidth, store.boardHeight);
store.lastBoard = Object.freeze(newBoard);
};
// draw 2d automaton in a loop, starting from passed state
const draw2dNext = async (board) => {
setTimeout(() => {
requestAnimationFrame(() => {
if (!store.canDraw) return;
draw2d(board);
return draw2dNext(store.lastBoard);
}); });
this.canvasWidth = this.canvas.parentElement.clientWidth; }, store.refreshRate);
this.canvasHeight = this.canvas.parentElement.clientHeight; };
this.setBoardWidth();
this.setBoardHeight();
},
methods: {
...mapActions(globalStore, [
"toggleStop",
"setBoardWidth",
"setBoardHeight",
]),
// draws the board on the canvas
async drawCanvas(board, width, height) {
const d = this.cellProperties.size;
// bool to RGBA colors
const img = await boardToPic(board, width, height, this.cellProperties);
// rescale and draw
this.ctx.save();
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.workCtx.putImageData(img, 0, 0);
this.ctx.imageSmoothingEnabled = false;
this.ctx.scale(d, d);
this.ctx.drawImage(
this.workCanvas,
0,
0,
this.canvasWidth,
this.canvasHeight
);
this.ctx.restore();
},
// create a first state, either a single living cell
// at the center or random ones
compute1dInitialState() {
if (this.initial1dState === "onecell")
return create1dStateOneCell(this.boardWidth);
return create1dState(this.boardWidth, getRandomInt, [0, 2]);
},
// initialize board with random cells
randomInitialState() {
return create2dState(
this.boardWidth,
this.boardHeight,
getRandomInt,
[0, 2]
);
},
// draw elementary automaton on the canvas based on selected ruleset
draw1d() {
const initialState = this.compute1dInitialState();
const board = createBoard(initialState, this.ruleset.rules, this.max);
this.lastBoard = Object.freeze(board);
this.drawCanvas(this.lastBoard, this.boardWidth, this.boardHeight);
this.toggleStop();
},
// draw 2D automaton on the canvas in a loop
draw2d(board) {
const newBoard = evolve2d(board, this.selectedRules);
this.drawCanvas(newBoard, this.boardWidth, this.boardHeight);
this.lastBoard = Object.freeze(newBoard);
},
// draw 2d automaton in a loop, starting from passed state
async draw2dNext(board, time) {
setTimeout(() => {
requestAnimationFrame(() => {
if (!this.canDraw) return;
this.draw2d(board);
return this.draw2dNext(this.lastBoard);
});
}, this.refreshRate);
},
// draw 2d automaton from a new state
async draw2dNew() {
if (!this.canDraw) return;
const initialState = this.randomInitialState();
let board = evolve2d(initialState, this.selectedRules);
if (this.loop) return this.draw2dNext(board);
else this.draw2d(board);
this.toggleStop();
},
// draw 2d automaton from the last known generated board
async draw2dLast() {
if (!this.canDraw) return;
if (this.loop) return this.draw2dNext(this.lastBoard);
else this.draw2d(this.lastBoard);
this.toggleStop();
},
// draw 2d automaton from an uploaded picture.
// use the picture representation as an initial state
draw2dPicture() {
// draw image on canvas
this.ctx.fillStyle = "black";
this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
this.ctx.drawImage(
this.picture,
Math.floor((this.canvasWidth - this.picture.width) / 2),
Math.floor((this.canvasHeight - this.picture.height) / 2),
this.picture.width,
this.picture.height
);
// get image data from canvas // draw 2d automaton from a new state
const imgData = this.ctx.getImageData( const draw2dNew = async () => {
0, if (!store.canDraw) return;
0, const initialState = randomInitialState();
this.canvasWidth, const board = evolve2d(initialState, selectedRules());
this.canvasHeight if (store.loop) return draw2dNext(board);
); else draw2d(board);
store.toggleStop();
};
// draw the image back on the work canvas with the dimensions of the board // draw 2d automaton from the last known generated board
this.workCtx.drawImage( const draw2dLast = async () => {
this.picture, if (!store.canDraw) return;
0, if (store.loop) return draw2dNext(store.lastBoard);
0, else draw2d(store.lastBoard);
this.boardWidth, store.toggleStop();
this.boardHeight };
);
// get the resized image data from work canvas // draw 2d automaton from an uploaded picture.
const resized = this.workCtx.getImageData( // use the picture representation as an initial state
0, const draw2dPicture = () => {
0, // draw image on canvas
this.boardWidth, ctx.fillStyle = "black";
this.boardHeight ctx.fillRect(0, 0, store.canvasWidth, store.canvasHeight);
); ctx.drawImage(
store.picture,
Math.floor((store.canvasWidth - store.picture.width) / 2),
Math.floor((store.canvasHeight - store.picture.height) / 2),
store.picture.width,
store.picture.height
);
// convert the image into a 2D board of boolean based on pixel value // draw the image back on the work canvas with the dimensions of the board
this.lastBoard = Object.freeze( workCtx.drawImage(store.picture, 0, 0, store.boardWidth, store.boardHeight);
picToBoard(resized.data, this.boardWidth, this.boardHeight)
);
this.toggleStop(); // get the resized image data from work canvas
}, const resized = workCtx.getImageData(
// stop drawing routines and clear the canvas 0,
reset() { 0,
this.toggleStop(); store.boardWidth,
this.lastBoard = {}; store.boardHeight
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); );
this.getReset = 0;
}, // convert the image into a 2D board of boolean based on pixel value
}, store.lastBoard = Object.freeze(
picToBoard(resized.data, store.boardWidth, store.boardHeight)
);
store.toggleStop();
};
// stop drawing routines and clear the canvas
const reset = () => {
store.toggleStop();
store.lastBoard = null;
ctx.clearRect(0, 0, store.canvasWidth, store.canvasHeight);
store.reset = false;
}; };
</script> </script>
<template>
<canvas
id="board-canvas"
ref="board-canvas"
:width="store.canvasWidth"
:height="store.canvasHeight"
/>
<canvas
id="work-canvas"
ref="work-canvas"
:width="store.canvasWidth"
:height="store.canvasHeight"
/>
</template>
<style> <style>
#canvas-board { #canvas-board {
flex: 1; flex: 1;

View File

@ -7,20 +7,11 @@
</div> </div>
</template> </template>
<script> <script setup>
import MenuCellProperties from "./MenuCellProperties.vue"; import MenuCellProperties from "./MenuCellProperties.vue";
import MenuGeneralOptions from "./MenuGeneralOptions.vue"; import MenuGeneralOptions from "./MenuGeneralOptions.vue";
import MenuElementaryCA from "./MenuElementaryCA.vue"; import MenuElementaryCA from "./MenuElementaryCA.vue";
import Menu2dCA from "./Menu2dCA.vue"; import Menu2dCA from "./Menu2dCA.vue";
export default {
name: "MainMenu",
components: {
MenuCellProperties,
MenuGeneralOptions,
MenuElementaryCA,
Menu2dCA,
},
};
</script> </script>
<style> <style>

View File

@ -1,3 +1,48 @@
<script setup>
import MenuRow from "./MenuRow.vue";
import { globalStore } from "../stores/index.js";
import { preset2dRules } from "../modules/preset.js";
import { shallowRef } from "vue";
const store = globalStore();
const uploadedPicture = shallowRef(null);
const img = new Image();
// TODO : I have no idea why this works
const preparePicture = () => {
const file = uploadedPicture.value.files[0];
if (!file || file.type.indexOf("image/") !== 0) return;
if (FileReader && file) {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (event) => {
img.onload = () => {
store.picture.width = img.width;
store.picture.height = img.height;
};
store.picture.src = event.target.result;
store.toggle2dDrawFromPicture();
};
reader.onerror = () => {
console.log(reader.error);
};
}
};
const update2dRules = (event) => {
const elem = event.target;
const id = elem.value;
const newRuleset = preset2dRules.find((ruleset) => {
return ruleset.id === id;
});
store.selected2dRules = newRuleset;
};
</script>
<template> <template>
<MenuRow row-title="2D Cellular Automaton"> <MenuRow row-title="2D Cellular Automaton">
<div class="form-field"> <div class="form-field">
@ -6,16 +51,16 @@
type="button" type="button"
name="start2d" name="start2d"
value="start" value="start"
@click="toggleDraw2d()" @click="store.toggleDraw2d()"
/> />
</div> </div>
<div class="form-field"> <div class="form-field">
<label>Start from last result</label> <label>Start from last result</label>
<input type="button" value="start" @click="toggleDraw2dLast()" /> <input type="button" value="start" @click="store.toggleDraw2dLast()" />
</div> </div>
<div class="form-field"> <div class="form-field">
<label>Start from picture</label><br /> <label>Start from picture</label><br />
<input type="file" @change="preparePicture" /> <input ref="uploadedPicture" type="file" @change="preparePicture" />
</div> </div>
<div class="form-field"> <div class="form-field">
<label> <label>
@ -23,7 +68,7 @@
<br /> <br />
<select <select
name="preset2dRules" name="preset2dRules"
:value="selected2dRules.id" :value="store.selected2dRules.id"
@input="update2dRules" @input="update2dRules"
> >
<option <option
@ -38,58 +83,3 @@
</div> </div>
</MenuRow> </MenuRow>
</template> </template>
<script>
import MenuRow from "./MenuRow.vue";
import { mapActions, mapWritableState } from "pinia";
import { globalStore } from "../stores/index.js";
import { preset2dRules } from "../modules/preset.js";
export default {
name: "Menu2dCA",
components: {
MenuRow,
},
data() {
return {
uploadedFile: "",
preset2dRules: preset2dRules,
};
},
computed: {
...mapWritableState(globalStore, ["picture", "selected2dRules"]),
},
methods: {
...mapActions(globalStore, [
"toggleDraw2dLast",
"toggleDraw2d",
"toggle2dDrawFromPicture",
]),
preparePicture(event) {
const files = event.target.files;
this.picture = new Image();
if (FileReader && files && files.length) {
const reader = new FileReader();
reader.onload = () => {
this.picture.src = Object.freeze(reader.result);
this.toggle2dDrawFromPicture();
};
reader.onerror = () => {
console.log(reader.error);
};
reader.readAsDataURL(files[0]);
}
},
update2dRules(event) {
const elem = event.target;
const id = elem.value;
const newRuleset = this.preset2dRules.find((ruleset) => {
return ruleset.id === id;
});
this.selected2dRules = newRuleset;
},
},
};
</script>

View File

@ -1,3 +1,24 @@
<script setup>
import { globalStore } from "../stores/index.js";
import MenuRow from "./MenuRow.vue";
const store = globalStore();
const updateCellProperties = (event) => {
const elem = event.target;
store.cellProperties[elem.name] = elem.value;
store.setBoardWidth();
store.setBoardHeight();
};
const switchColor = () => {
[store.cellProperties["liveColor"], store.cellProperties["deadColor"]] = [
store.cellProperties["deadColor"],
store.cellProperties["liveColor"],
];
};
</script>
<template> <template>
<MenuRow row-title="Cell Properties"> <MenuRow row-title="Cell Properties">
<form> <form>
@ -6,7 +27,7 @@
<input <input
name="liveColor" name="liveColor"
type="color" type="color"
:value="cellProperties.liveColor" :value="store.cellProperties.liveColor"
@input="updateCellProperties" @input="updateCellProperties"
/> />
</div> </div>
@ -15,7 +36,7 @@
<input <input
name="deadColor" name="deadColor"
type="color" type="color"
:value="cellProperties.deadColor" :value="store.cellProperties.deadColor"
@input="updateCellProperties" @input="updateCellProperties"
/> />
</div> </div>
@ -28,7 +49,7 @@
name="size" name="size"
type="number" type="number"
min="1" min="1"
:value="cellProperties.size" :value="store.cellProperties.size"
@click="updateCellProperties" @click="updateCellProperties"
/> />
</div> </div>
@ -36,40 +57,6 @@
</MenuRow> </MenuRow>
</template> </template>
<script>
import { mapActions, mapWritableState } from "pinia";
import { globalStore } from "../stores/index.js";
import MenuRow from "./MenuRow.vue";
export default {
name: "MenuCellProperties",
components: {
MenuRow,
},
computed: {
...mapWritableState(globalStore, ["cellProperties"]),
},
methods: {
...mapActions(globalStore, ["setBoardHeight", "setBoardWidth"]),
getCellProperties(event) {
const elem = event.target;
const prop = this.cellProperties;
return prop[elem.name];
},
updateCellProperties(event) {
const elem = event.target;
this.cellProperties[elem.name] = elem.value;
this.setBoardWidth();
this.setBoardHeight();
},
switchColor() {
[this.cellProperties["liveColor"], this.cellProperties["deadColor"]] = [
this.cellProperties["deadColor"],
this.cellProperties["liveColor"],
];
},
},
};
</script>
<style scoped> <style scoped>
a { a {
font-weight: bold; font-weight: bold;

View File

@ -1,3 +1,36 @@
<script setup>
import { presetRuleset, initialStates } from "../modules/preset.js";
import { globalStore } from "../stores/index.js";
import MenuRow from "./MenuRow.vue";
const store = globalStore();
const copyRuleset = () => {
const newRuleset = JSON.stringify(store.ruleset1d);
navigator.clipboard.writeText(newRuleset);
};
const updateSingleRule = (event) => {
const elem = event.target;
const value = elem.checked ? 1 : 0;
store.ruleset1d.rules[elem.name] = value;
};
const updateRuleset = (event) => {
const elem = event.target;
const name = elem.value;
const newRuleset = presetRuleset.find((ruleset) => {
return ruleset.name === name;
});
store.ruleset1d = newRuleset;
};
const updateInitialState = (event) => {
const elem = event.target;
store.initial1dState = elem.value;
};
</script>
<template> <template>
<MenuRow row-title="Elementary Automaton"> <MenuRow row-title="Elementary Automaton">
<form> <form>
@ -6,7 +39,7 @@
type="button" type="button"
name="start" name="start"
value="start" value="start"
@click="toggleDraw1d()" @click="store.toggleDraw1d()"
/> />
</div> </div>
<div class="form-field"> <div class="form-field">
@ -15,7 +48,7 @@
<br /> <br />
<select <select
name="initialStates" name="initialStates"
:value="initialState" :value="store.initial1dState"
@input="updateInitialState" @input="updateInitialState"
> >
<option <option
@ -37,7 +70,7 @@
<br /> <br />
<select <select
name="ruleset-elementary" name="ruleset-elementary"
:value="ruleset.name" :value="store.ruleset1d.name"
@input="updateRuleset" @input="updateRuleset"
> >
<option <option
@ -54,7 +87,7 @@
<a style="cursor: pointer" @click="copyRuleset">copy rules</a> <a style="cursor: pointer" @click="copyRuleset">copy rules</a>
</div> </div>
<div <div
v-for="(rule, name, index) in ruleset.rules" v-for="(rule, name, index) in store.ruleset1d.rules"
:key="'rule-' + index" :key="'rule-' + index"
class="form-field" class="form-field"
> >
@ -73,68 +106,6 @@
</MenuRow> </MenuRow>
</template> </template>
<script>
import { mapActions, mapWritableState } from "pinia";
import { presetRuleset, initialStates } from "../modules/preset.js";
import { globalStore } from "../stores/index.js";
import MenuRow from "./MenuRow.vue";
export default {
name: "MenuElementaryCA",
components: {
MenuRow,
},
data() {
return {
presetRuleset: presetRuleset,
initialStates: initialStates,
};
},
computed: {
...mapWritableState(globalStore, {
initialState: "initial1dState",
ruleset: "ruleset1d",
}),
rules1dFileName() {
// TODO: broken
return (
Object.keys(this.ruleset)
.map((index) => {
return this.ruleset[index];
})
.join("_") + ".json"
);
},
},
methods: {
...mapActions(globalStore, ["toggleDraw1d"]),
copyRuleset() {
const newRuleset = JSON.stringify(this.ruleset);
navigator.clipboard.writeText(newRuleset);
},
isCurrentPreset(event) {
const elem = event.target;
return this.initialState === elem.value;
},
updateSingleRule(event) {
const elem = event.target;
const value = elem.checked ? 1 : 0;
this.ruleset.rules[elem.name] = value;
},
updateRuleset(event) {
const elem = event.target;
const name = elem.value;
const newRuleset = this.presetRuleset.find((ruleset) => {
return ruleset.name === name;
});
this.ruleset = newRuleset;
},
updateInitialState(event) {
const elem = event.target;
this.initialState = elem.value;
},
},
};
</script>
<style> <style>
.menu-row a { .menu-row a {
color: white; color: white;

View File

@ -1,3 +1,31 @@
<script setup>
import { globalStore } from "../stores/index.js";
import MenuRow from "./MenuRow.vue";
const store = globalStore();
const updateCanvasHeight = (event) => {
const elem = event.target;
store.canvasHeight = elem.value;
};
const updateCanvasWidth = (event) => {
const elem = event.target;
store.canvasWidth = elem.value;
};
const updateRefreshRate = (event) => {
const elem = event.target;
store.refreshRate = elem.value;
};
const updateDrawingDirection = (event) => {
const elem = event.target;
const value = elem.checked ? "x" : "y";
store.drawingDirection = value;
};
</script>
<template> <template>
<MenuRow row-title="General Options"> <MenuRow row-title="General Options">
<form> <form>
@ -11,7 +39,7 @@
name="canvasWidth" name="canvasWidth"
type="number" type="number"
step="10" step="10"
:value="canvasWidth" :value="store.canvasWidth"
@input="updateCanvasWidth" @input="updateCanvasWidth"
/> />
</div> </div>
@ -22,7 +50,7 @@
name="canvasHeight" name="canvasHeight"
type="number" type="number"
step="10" step="10"
:value="canvasHeight" :value="store.canvasHeight"
@input="updateCanvasHeight" @input="updateCanvasHeight"
/> />
</div> </div>
@ -36,7 +64,7 @@
type="number" type="number"
min="100" min="100"
step="100" step="100"
:value="refreshRate" :value="store.refreshRate"
@input="updateRefreshRate" @input="updateRefreshRate"
/> />
</div> </div>
@ -45,8 +73,8 @@
>Invert Drawing Direction >Invert Drawing Direction
<input <input
type="checkbox" type="checkbox"
:checked="drawingDirection === 'x'" :checked="store.drawingDirection === 'x'"
:value="drawingDirection" :value="store.drawingDirection"
@input="updateDrawingDirection" @input="updateDrawingDirection"
/> />
</label> </label>
@ -54,42 +82,3 @@
</form> </form>
</MenuRow> </MenuRow>
</template> </template>
<script>
import { mapWritableState } from "pinia";
import { globalStore } from "../stores/index.js";
import MenuRow from "./MenuRow.vue";
export default {
name: "MenuGeneralOptions",
components: {
MenuRow,
},
computed: {
...mapWritableState(globalStore, [
"canvasWidth",
"canvasHeight",
"refreshRate",
"drawingDirection",
]),
},
methods: {
updateCanvasHeight: function (event) {
const elem = event.target;
this.canvasHeight = elem.value;
},
updateCanvasWidth: function (event) {
const elem = event.target;
this.canvasWidth = elem.value;
},
updateRefreshRate: function (event) {
const elem = event.target;
this.refreshRate = elem.value;
},
updateDrawingDirection: function (event) {
const elem = event.target;
const value = elem.checked ? "x" : "y";
this.drawingDirection = value;
},
},
};
</script>

View File

@ -1,13 +1,19 @@
<script setup>
import { globalStore } from "../stores/index.js";
const store = globalStore();
</script>
<template> <template>
<div class="form-field"> <div class="form-field">
<div class="form-field"> <div class="form-field">
<label> <label>
Loop Loop
<input <input
:value="loop" :value="store.loop"
type="checkbox" type="checkbox"
:checked="loop" :checked="store.loop"
@input="toggleLoop()" @input="store.toggleLoop()"
/> />
</label> </label>
</div> </div>
@ -16,43 +22,25 @@
name="next" name="next"
class="next" class="next"
value="Next" value="Next"
@click="toggleNext()" @click="store.toggleNext()"
/> />
<input <input
type="button" type="button"
name="stop" name="stop"
class="stop" class="stop"
value="stop" value="stop"
@click="toggleStop()" @click="store.toggleStop()"
/> />
<input <input
type="button" type="button"
name="reset" name="reset"
class="reset" class="reset"
value="reset" value="reset"
@click="toggleReset()" @click="store.toggleReset()"
/> />
</div> </div>
</template> </template>
<script>
import { mapState, mapActions } from "pinia";
import { globalStore } from "../stores/index.js";
export default {
name: "MenuReset",
computed: {
...mapState(globalStore, ["loop"]),
},
methods: {
...mapActions(globalStore, [
"toggleReset",
"toggleStop",
"toggleLoop",
"toggleNext",
]),
},
};
</script>
<style scoped> <style scoped>
.form-field { .form-field {
display: flex; display: flex;

View File

@ -1,3 +1,44 @@
<script setup>
import { computed, defineProps, onBeforeUnmount, ref } from "vue";
import { globalStore } from "../stores/index.js";
const store = globalStore();
const props = defineProps({
rowTitle: {
type: String,
default: "",
},
});
const content = ref(null);
const isActive = computed(() => {
return props.rowTitle == store.activeSubMenu;
});
const storeActiveSubMenu = () => {
window.addEventListener("click", onWindowClick);
store.setActiveSubMenu(props.rowTitle);
};
// hides submenu when click is detected outside from it
const onWindowClick = (event) => {
const form = content.value;
if (form != null) {
if (!form.contains(event.target)) {
store.setActiveSubMenu("");
store.setMainMenu(false);
}
return;
}
};
onBeforeUnmount(() => {
window.removeEventListener("click", onWindowClick);
});
</script>
<template> <template>
<div class="menu-row"> <div class="menu-row">
<h2 :id="rowTitle" @click.stop="storeActiveSubMenu"> <h2 :id="rowTitle" @click.stop="storeActiveSubMenu">
@ -9,57 +50,6 @@
</div> </div>
</template> </template>
<script>
import { mapActions, mapState } from "pinia";
import { globalStore } from "../stores/index.js";
export default {
name: "MenuRow",
props: {
rowTitle: {
type: String,
default: "",
},
},
computed: {
...mapState(globalStore, ["activeSubMenu"]),
isActive() {
return this.rowTitle == this.activeSubMenu;
},
},
beforeUnmount() {
window.removeEventListener("click", this.onWindowClick);
},
methods: {
...mapActions(globalStore, [
"setActiveSubMenu",
"toggleMainMenu",
"setMainMenu",
]),
onKeyDown: function (event) {
// escape
if (event.keyCode == 27) {
this.setActiveSubMenu("");
}
},
storeActiveSubMenu() {
window.addEventListener("click", this.onWindowClick);
this.setActiveSubMenu(this.rowTitle);
},
// hides submenu when click is detected outside from it
onWindowClick(event) {
const form = this.$refs.content;
if (form != null) {
if (!form.contains(event.target)) {
this.setActiveSubMenu("");
this.setMainMenu(false);
}
return;
}
},
},
};
</script>
<style> <style>
.menu-row h2 { .menu-row h2 {
font-size: medium; font-size: medium;

View File

@ -57,7 +57,7 @@ export function boardToPic(board, width, height, cellProperties) {
const img = new ImageData(width, height); const img = new ImageData(width, height);
const colors = [hexToRGB(live), hexToRGB(dead)]; const colors = [hexToRGB(live), hexToRGB(dead)];
board.flat().reduce((acc, cell, index) => { board.flat().reduce((acc, cell, index) => {
const color = colors[cell]; const color = cell === 1 ? colors[0] : colors[1];
const i = index * 4; const i = index * 4;
acc[i] = color[0]; acc[i] = color[0];
acc[i + 1] = color[1]; acc[i + 1] = color[1];

View File

@ -40,7 +40,7 @@ export const globalStore = defineStore("globalStore", {
draw2dpicture: false, draw2dpicture: false,
reset: false, reset: false,
canDraw: true, canDraw: true,
picture: null, picture: new Image(),
mainMenu: false, mainMenu: false,
activeSubMenu: "", activeSubMenu: "",
loop: false, loop: false,