Webpack copy. add windows

This commit is contained in:
Alexander Bass 2024-03-06 23:41:02 -05:00
parent 8a38dded92
commit 1f6a95c253
31 changed files with 910 additions and 423 deletions

194
package-lock.json generated
View file

@ -7,6 +7,7 @@
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0", "@typescript-eslint/parser": "^6.0.0",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^6.10.0", "css-loader": "^6.10.0",
"eslint": "^8.44.0", "eslint": "^8.44.0",
"sass": "^1.71.0", "sass": "^1.71.0",
@ -262,6 +263,18 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@sindresorhus/merge-streams": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
"integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@types/eslint": { "node_modules/@types/eslint": {
"version": "8.56.2", "version": "8.56.2",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz",
@ -753,6 +766,45 @@
"url": "https://github.com/sponsors/epoberezkin" "url": "https://github.com/sponsors/epoberezkin"
} }
}, },
"node_modules/ajv-formats": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"dev": true,
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ajv-formats/node_modules/ajv": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
},
"node_modules/ajv-keywords": { "node_modules/ajv-keywords": {
"version": "3.5.2", "version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@ -1028,6 +1080,127 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true "dev": true
}, },
"node_modules/copy-webpack-plugin": {
"version": "12.0.2",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz",
"integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==",
"dev": true,
"dependencies": {
"fast-glob": "^3.3.2",
"glob-parent": "^6.0.1",
"globby": "^14.0.0",
"normalize-path": "^3.0.0",
"schema-utils": "^4.2.0",
"serialize-javascript": "^6.0.2"
},
"engines": {
"node": ">= 18.12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^5.1.0"
}
},
"node_modules/copy-webpack-plugin/node_modules/ajv": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/copy-webpack-plugin/node_modules/ajv-keywords": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
"peerDependencies": {
"ajv": "^8.8.2"
}
},
"node_modules/copy-webpack-plugin/node_modules/globby": {
"version": "14.0.1",
"resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz",
"integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==",
"dev": true,
"dependencies": {
"@sindresorhus/merge-streams": "^2.1.0",
"fast-glob": "^3.3.2",
"ignore": "^5.2.4",
"path-type": "^5.0.0",
"slash": "^5.1.0",
"unicorn-magic": "^0.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
},
"node_modules/copy-webpack-plugin/node_modules/path-type": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz",
"integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/copy-webpack-plugin/node_modules/schema-utils": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
"integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
"ajv-formats": "^2.1.1",
"ajv-keywords": "^5.1.0"
},
"engines": {
"node": ">= 12.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/copy-webpack-plugin/node_modules/slash": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
"integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
"dev": true,
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -2474,6 +2647,15 @@
"node": ">= 10.13.0" "node": ">= 10.13.0"
} }
}, },
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.8", "version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@ -2985,6 +3167,18 @@
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true "dev": true
}, },
"node_modules/unicorn-magic": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
"integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.0.13", "version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",

View file

@ -3,6 +3,7 @@
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0", "@typescript-eslint/parser": "^6.0.0",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^6.10.0", "css-loader": "^6.10.0",
"eslint": "^8.44.0", "eslint": "^8.44.0",
"sass": "^1.71.0", "sass": "^1.71.0",

View file

@ -72,6 +72,12 @@ export enum UiEvent {
// Ui Events // Ui Events
EditOn, EditOn,
EditOff, EditOff,
ConsoleOn,
ConsoleOff,
ExplainerOn,
ExplainerOff,
VideoOn,
VideoOff,
} }
interface UiEventMap { interface UiEventMap {

BIN
src/include/explainer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

38
src/include/index.html Normal file
View file

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script src="main.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Virtual 8-Bit Computer</title>
</head>
<body id="root">
<main>
<div id="grid">
<div id="title">VIRTUAL 8-BIT COMPUTER</div>
<div id="registers"></div>
<div id="labelcontainer">
<div id="registers_label">←REGISTERS</div>
<div id="memory_label">MEMORY↯</div>
</div>
<div id="memory"></div>
<div id="controls_bar">
<span id="controls_buttons"></span>
<label for="binary_upload" class="button">Load Binary</label>
<input id="binary_upload" name="binary_upload" id="binary_upload" style="display: none" type="file" />
<button type="button" id="save_button">Save</button>
<button type="button" id="edit_button"></button>
</div>
<span id="cycles"></span>
</div>
<div id="window_box">
<div id="instruction_explainer"></div>
<div id="printout"></div>
<div id="tv"></div>
</div>
</main>
<pre id="ISA"></pre>
</body>
</html>

BIN
src/include/pencil.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
src/include/texout.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
src/include/tv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -10,7 +10,7 @@ import { generate_isa } from "./isaGenerator";
import { UI } from "./ui"; import { UI } from "./ui";
import { u8 } from "./num"; import { u8 } from "./num";
import "./style.scss"; import "./style/style.scss";
import { CpuEvent } from "./events"; import { CpuEvent } from "./events";
declare global { declare global {

View file

@ -1,310 +0,0 @@
* {
box-sizing: border-box;
}
pre {
font-size: 0.5em;
}
:root {
--Border: #ffff00;
--mem-instruction: #3af78f;
--mem-memory: #ff26a8;
--mem-register: #9e0ef7;
--mem-constant: #19f7f0;
--mem-invalid: #bf2e2e;
}
img {
image-rendering: pixelated;
}
body {
color: #808080;
background-color: black;
font-size: 2.5em;
font-family: monospace;
}
#labelcontainer {
column-gap: 18px;
font-size: 0.85em;
display: flex;
align-items: center;
user-select: none;
grid-area: regmemlabel;
}
#main {
justify-content: center;
display: grid;
grid-template-columns: min-content max-content max-content min-content 500px;
grid-template-rows: min-content 1.5fr 10px 2fr 2fr min-content;
grid-template-areas:
"cycles registers regmemlabel . explainer "
"title . . ribbon explainer "
"title . . ribbon ."
"title . . ribbon printout "
"title . . . printout "
". buttons buttons . .";
#memory {
grid-column: 2/4;
grid-row: 2/6;
}
#cycles {
grid-area: cycles;
text-align: left;
align-self: center;
font-size: 0.48em;
}
#title {
grid-area: title;
writing-mode: vertical-lr;
text-align: left;
user-select: none;
transform: scale(-1, -1);
}
#ribbon_menu {
grid-area: ribbon;
}
}
#printout {
border: 4px dashed var(--Border);
grid-area: printout;
padding: 10px;
word-wrap: break-word;
word-break: break-all;
max-height: 200px;
overflow-x: scroll;
}
#instruction_explainer {
grid-area: explainer;
display: flex;
flex-direction: column;
gap: 5px;
border: 5px dashed var(--Border);
#expl_box {
padding-inline: 20px;
padding-block-start: 10px;
}
#expl_text {
font-size: 0.6em;
text-align: center;
vertical-align: center;
}
#expl_icon {
margin-inline-end: 0.5em;
font-size: 30px;
padding: 5px;
color: var(--color);
outline: 3px dashed var(--color);
}
}
.invalid {
--color: var(--mem-invalid);
}
.constant {
--color: var(--mem-constant);
}
.register {
--color: var(--mem-register);
}
.memory {
--color: var(--mem-memory);
}
.instruction {
--color: var(--mem-instruction);
}
div[contenteditable] {
&.caret_selected {
box-sizing: border-box;
outline: 2px solid red;
}
outline: none;
}
.pending_edit {
color: green;
}
#memory {
grid-area: memory;
display: grid;
grid-template-columns: repeat(16, min-content);
gap: 5px;
padding: 10px;
border: 5px solid yellow;
div {
user-select: none;
caret-color: transparent;
text-align: center;
color: var(--color);
}
.program_counter {
outline: 3px solid orange;
}
.instruction_argument,
.current_instruction {
outline: 3px dashed var(--color);
}
.recent_edit {
color: lime;
}
div.last_access {
color: orange;
}
.invalid {
&::after {
user-select: none;
float: right;
position: relative;
right: 0.5em;
width: 0px;
font-size: 0.5em;
content: "!";
}
}
}
div#main.editor {
#memory,
#registers {
border-style: dashed;
div {
cursor: text;
}
}
}
#ribbon_menu {
margin-inline: 8px;
.editor_toggle {
//TODO CHANGE COLORS WHen
&.off {
}
&.on {
}
}
}
#registers {
grid-area: registers;
border: 5px solid yellow;
border-bottom: none !important;
grid-template-columns: repeat(8, min-content);
max-width: fit-content;
display: grid;
column-gap: 5px;
color: lightgray;
padding: 10px;
div {
max-height: min-content;
// margin-block: auto;
}
}
button,
label.button {
border: 4px solid yellow;
color: gray;
margin: 10px;
margin-inline: 0px;
padding: 10px;
font-size: 0.8em;
font-family: monospace;
background-color: transparent;
cursor: pointer;
user-select: none;
}
button:disabled {
border: 4px solid rgb(255, 255, 128);
color: orange;
cursor: default;
}
input[type="range"]:disabled {
cursor: default;
}
button.no_style {
border: none;
color: inherit;
margin: 0;
padding: 0;
font-size: inherit;
background-color: inherit;
}
button:hover,
label.button:hover {
color: white;
}
#controls_bar {
grid-area: buttons;
display: flex;
gap: 10px;
#controls_buttons {
display: flex;
gap: inherit;
}
}
input[type="range"] {
background-color: transparent;
-webkit-appearance: none;
appearance: none;
margin: 18px 0;
}
input[type="range"]:focus {
outline: none;
}
// 2024 and we still have to do this
input[type="range"]::-webkit-slider-runnable-track {
height: 0.5em;
cursor: pointer;
background: yellow;
border-radius: 0px;
}
input[type="range"]::-webkit-slider-thumb {
border: 4px solid yellow;
background-color: black;
height: 42px;
width: 20px;
border-radius: 0px;
cursor: pointer;
margin-top: -18px;
-webkit-appearance: none;
}
input[type="range"]:focus::-webkit-slider-runnable-track {
background: yellow;
}
input[type="range"]::-moz-range-track {
height: 0.5em;
cursor: pointer;
background: yellow;
border-radius: 0px;
}
input[type="range"]::-moz-range-thumb {
border: 4px solid yellow;
background-color: black;
height: 36px;
width: 16px;
border-radius: 0px;
cursor: pointer;
}

101
src/style/buttons.scss Normal file
View file

@ -0,0 +1,101 @@
@use "sass:math";
button,
label.button {
border: 4px solid yellow;
color: gray;
margin: 10px;
margin-inline: 0px;
padding: 10px;
font-size: 0.8em;
font-family: monospace;
background-color: transparent;
cursor: pointer;
user-select: none;
}
button:disabled {
border: 4px solid rgb(255, 255, 128);
color: orange;
cursor: default;
}
input[type="range"]:disabled {
cursor: default;
}
button.nostyle {
border: none;
color: inherit;
margin: 0;
padding: 0;
font-size: inherit;
background-color: inherit;
}
button:hover,
label.button:hover {
color: white;
}
#controls_bar {
grid-area: buttons;
display: flex;
gap: 10px;
#controls_buttons {
display: flex;
gap: inherit;
}
}
input[type="range"] {
background-color: transparent;
-webkit-appearance: none;
appearance: none;
margin: 18px 0;
width: 100px;
}
input[type="range"]:focus {
outline: none;
}
// 2024 and we still have to do this
@mixin sliderTrack {
height: 0.5em;
cursor: pointer;
background: yellow;
border-radius: 0px;
}
@mixin sliderThumb($height, $width) {
border: 4px solid yellow;
background-color: black;
height: $height;
width: $width;
border-radius: 0px;
cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
@include sliderTrack;
}
input[type="range"]::-moz-range-track {
@include sliderTrack;
}
input[type="range"]::-webkit-slider-thumb {
@include sliderThumb(42px, 20px);
-webkit-appearance: none;
margin-top: -18px;
}
input[type="range"]::-moz-range-thumb {
@include sliderThumb(36px, 16px);
}
input[type="range"]:focus::-webkit-slider-runnable-track {
background: yellow;
}

View file

@ -0,0 +1,69 @@
#memory {
grid-template-columns: repeat(16, min-content);
.program_counter {
outline: 3px solid orange;
}
.instruction_argument,
.current_instruction {
outline: 3px dashed var(--color);
}
.invalid {
&::after {
user-select: none;
float: right;
position: relative;
right: 0.5em;
width: 0px;
font-size: 0.5em;
content: "!";
}
}
}
#registers {
border-bottom: none !important;
grid-template-columns: repeat(8, min-content);
color: lightgray;
}
#labelcontainer {
column-gap: 18px;
font-size: 0.85em;
display: flex;
align-items: center;
user-select: none;
}
.celled_viewer {
display: grid;
max-width: fit-content;
gap: 5px;
border: 5px solid yellow;
padding: 10px;
user-select: none;
div {
.pending_edit {
color: green;
}
&.recent_edit {
color: lime;
}
&.last_access {
color: orange;
}
user-select: none;
caret-color: transparent;
text-align: center;
color: var(--color);
&[contenteditable] {
&.caret_selected {
box-sizing: border-box;
outline: 2px solid red;
}
outline: none;
}
}
}

93
src/style/style.scss Normal file
View file

@ -0,0 +1,93 @@
@use "memory_registers";
@use "windows";
@use "buttons";
@use "vars";
* {
box-sizing: border-box;
}
pre {
font-size: 0.5em;
}
img {
image-rendering: pixelated;
}
body {
color: #808080;
background-color: black;
font-size: 2.5em;
font-family: monospace;
}
main {
display: flex;
justify-content: center;
}
#grid {
justify-content: center;
display: grid;
grid-template-columns: min-content min-content min-content;
grid-template-rows: min-content min-content min-content;
grid-template-areas:
"cycles registers regmemlabel"
"title memory memory"
". buttons buttons ";
#memory {
grid-area: memory;
// grid-column: 2/4;
// grid-row: 2/6;
}
#window_box {
grid-area: windowbox;
}
#registers {
grid-area: registers;
}
#labelcontainer {
grid-area: regmemlabel;
}
#cycles {
grid-area: cycles;
text-align: left;
align-self: center;
font-size: 0.48em;
user-select: none;
}
#title {
grid-area: title;
writing-mode: vertical-lr;
text-align: left;
user-select: none;
transform: scale(-1, -1);
}
}
.invalid {
--color: var(--mem-invalid);
}
.constant {
--color: var(--mem-constant);
}
.register {
--color: var(--mem-register);
}
.memory {
--color: var(--mem-memory);
}
.instruction {
--color: var(--mem-instruction);
}
div#main.editor {
#memory,
#registers {
border-style: dashed;
div {
cursor: text;
}
}
}

8
src/style/vars.scss Normal file
View file

@ -0,0 +1,8 @@
:root {
--border: #ffff00;
--mem-instruction: #3af78f;
--mem-memory: #ff26a8;
--mem-register: #9e0ef7;
--mem-constant: #19f7f0;
--mem-invalid: #bf2e2e;
}

108
src/style/windows.scss Normal file
View file

@ -0,0 +1,108 @@
#resize {
// background-color: red;
width: 100%;
height: 18px;
position: absolute;
bottom: 0;
border-bottom: 5px solid yellow;
cursor: s-resize;
}
#window_box {
display: flex;
flex-direction: column;
gap: 10px;
margin-left: 10px;
max-width: 500px;
.window {
overflow-y: hidden;
position: relative;
border: 5px Solid var(--border);
border-bottom: unset;
&.collapsed {
.window_title {
// border-bottom: unset;
}
}
.window_title {
position: sticky;
user-select: none;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 0.6em;
color: lightgray;
border-bottom: 5px solid var(--border);
background: repeating-linear-gradient(
to top,
transparent,
transparent 2px,
transparent 2px,
yellow 2px,
yellow 4px
),
repeating-linear-gradient(to right, transparent, transparent 2px, transparent 2px, yellow 2px, yellow 4px);
#text {
display: inline-block;
text-align: center;
height: 100%;
padding-inline: 10px;
background-color: black;
}
#collapse_button {
height: 23px !important;
aspect-ratio: 1;
border: 2px solid white;
background-color: black;
margin-right: 3px;
}
}
}
}
.window#tv {
height: unset;
#screen {
max-width: 100%;
aspect-ratio: 1;
}
}
#instruction_explainer {
grid-area: explainer;
display: flex;
flex-direction: column;
gap: 5px;
height: 400px;
#expl_box {
padding-inline: 20px;
padding-block-start: 10px;
}
#expl_text {
font-size: 0.6em;
text-align: center;
vertical-align: center;
}
#expl_icon {
margin-inline-end: 0.5em;
font-size: 30px;
padding: 5px;
color: var(--color);
outline: 3px dashed var(--color);
}
}
#printout {
grid-area: printout;
height: 500px;
word-wrap: break-word;
word-break: break-all;
overflow-x: hidden;
#printout_text {
padding-inline: 10px;
}
}

View file

@ -1,19 +1,16 @@
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events"; import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
import { $ } from "./etc"; import { $ } from "./etc";
import { InstructionExplainer } from "./ui/instructionExplainer"; import { InstructionExplainer } from "./ui/windows/instructionExplainer";
import { MemoryView } from "./ui/memoryView"; import { MemoryView } from "./ui/memoryView";
import { frequencyIndicator } from "./ui/frequencyIndicator"; import { frequencyIndicator } from "./ui/frequencyIndicator";
import { RegisterView } from "./ui/registerView"; import { RegisterView } from "./ui/registerView";
import { Screen } from "./ui/screen"; import { Screen } from "./ui/windows/screen";
import { Ribbon } from "./ui/ribbon"; import { EditButton } from "./ui/edit_button";
import { UiComponent, UiComponentConstructor } from "./ui/uiComponent"; import { UiComponent, UiComponentConstructor } from "./ui/uiComponent";
import { pausePlay } from "./ui/pausePlay"; import { pausePlay } from "./ui/pausePlay";
import { Printout } from "./ui/windows/printout";
export class UI { export class UI {
printout: HTMLElement;
auto_running: boolean;
events: UiEventHandler = new UiEventHandler(); events: UiEventHandler = new UiEventHandler();
private components: Array<UiComponent>; private components: Array<UiComponent>;
@ -30,27 +27,23 @@ export class UI {
this.register_component(frequencyIndicator, $("cycles")); this.register_component(frequencyIndicator, $("cycles"));
this.register_component(InstructionExplainer, $("instruction_explainer")); this.register_component(InstructionExplainer, $("instruction_explainer"));
this.register_component(RegisterView, $("registers")); this.register_component(RegisterView, $("registers"));
this.register_component(Screen, $("screen") as HTMLCanvasElement); this.register_component(Screen, $("tv"));
this.register_component(Ribbon, $("ribbon_menu")); this.register_component(Printout, $("printout"));
this.register_component(EditButton, $("edit_button"));
this.register_component(pausePlay, $("controls_buttons")); this.register_component(pausePlay, $("controls_buttons"));
this.printout = $("printout");
this.auto_running = false;
const pp_button = $("pause_play_button"); const pp_button = $("pause_play_button");
} }
private register_component(c: UiComponentConstructor, e: HTMLElement): void { private register_component(ctor: UiComponentConstructor, e: HTMLElement): void {
if (e === undefined) { if (e === undefined) {
console.log(c); console.log(ctor);
throw new Error("Could not find HTML element while registering UI component"); throw new Error("Could not find HTML element while registering UI component");
} }
const component = new c(e, this.events); const component = new ctor(e, this.events);
this.components.push(component); this.components.push(component);
} }
init_events(cpu_events: CpuEventHandler): void { init_events(cpu_events: CpuEventHandler): void {
cpu_events.listen(CpuEvent.Print, (char) => {
this.printout.textContent = (this.printout.textContent ?? "") + char;
});
cpu_events.listen(CpuEvent.Reset, () => { cpu_events.listen(CpuEvent.Reset, () => {
this.reset(); this.reset();
}); });
@ -64,6 +57,5 @@ export class UI {
for (const c of this.components) { for (const c of this.components) {
c.reset(); c.reset();
} }
this.printout.textContent = "";
} }
} }

View file

@ -19,6 +19,7 @@ export abstract class CelledViewer {
this.element = element; this.element = element;
this.width = width; this.width = width;
this.height = height; this.height = height;
this.element.classList.add("celled_viewer");
for (let i = 0; i < this.width * this.height; i++) { for (let i = 0; i < this.width * this.height; i++) {
const mem_cell_el = el("div"); const mem_cell_el = el("div");
mem_cell_el.append("0", "0"); mem_cell_el.append("0", "0");

41
src/ui/edit_button.ts Normal file
View file

@ -0,0 +1,41 @@
import { el, $ } from "../etc";
import { UiEventHandler, UiEvent } from "../events";
import { UiComponent } from "./uiComponent";
export class EditButton implements UiComponent {
element: HTMLElement;
events: UiEventHandler;
constructor(element: HTMLElement, event: UiEventHandler) {
this.element = element;
this.events = event;
const image = el("img");
image.src = "pencil.png";
image.style.width = "20px";
image.style.height = "20px";
this.element.classList.add("editor_toggle");
this.element.addEventListener("click", () => this.edit_toggle());
this.element.appendChild(image);
}
reset(): void {
const is_on = this.element.classList.contains("on");
if (is_on) {
this.edit_toggle();
}
}
edit_toggle(): void {
const is_on = this.element.classList.contains("on");
if (is_on) {
this.element.classList.remove("on");
$("root").classList.remove("editor");
this.element.classList.add("off");
this.events.dispatch(UiEvent.EditOff);
} else {
this.events.dispatch(UiEvent.EditOn);
$("root").classList.add("editor");
this.element.classList.add("on");
this.element.classList.remove("off");
}
}
}

View file

@ -24,6 +24,14 @@ export class EditorContext {
cell.addEventListener("keydown", (e) => { cell.addEventListener("keydown", (e) => {
this.keydown(e, i); this.keydown(e, i);
}); });
cell.addEventListener("input", (e) => {
const target = e.target as HTMLElement;
if (target === null) return;
const text = target.textContent ?? "";
if (text.length !== 2) {
target.textContent = text.substring(0, 2);
}
});
cell.addEventListener("focus", () => { cell.addEventListener("focus", () => {
if (!this.enabled) return; if (!this.enabled) return;
this.current_cell_info.old = cell.textContent ?? "00"; this.current_cell_info.old = cell.textContent ?? "00";

View file

@ -37,6 +37,9 @@ export class frequencyIndicator implements UiComponent {
this.element.textContent = `${value}hz`; this.element.textContent = `${value}hz`;
this.last_value = value; this.last_value = value;
} }
if (value === 0) {
this.element.textContent = "";
}
this.last_time = new_time; this.last_time = new_time;
this.count = 0; this.count = 0;
} }

View file

@ -1,5 +1,5 @@
import { el } from "../etc"; import { el } from "../etc";
import { UiEventHandler, CpuEventHandler, UiEvent } from "../events"; import { UiEventHandler, UiEvent } from "../events";
import { UiComponent } from "./uiComponent"; import { UiComponent } from "./uiComponent";
const MAX_SLIDER = 1000; const MAX_SLIDER = 1000;

View file

@ -1,60 +0,0 @@
import { el, $ } from "../etc";
import { UiEventHandler, UiEvent } from "../events";
import { UiComponent } from "./uiComponent";
function new_button(name: string, img_path: string, additional_class?: string): HTMLButtonElement {
const button = el("button", "", "no_style ribbon_button");
const image = el("img");
image.src = img_path;
image.width = 64;
image.height = 64;
if (additional_class !== undefined) {
button.classList.add(additional_class);
}
button.appendChild(image);
return button;
}
export class Ribbon implements UiComponent {
element: HTMLElement;
events: UiEventHandler;
edit_button: HTMLButtonElement;
console_button: HTMLButtonElement;
display_button: HTMLButtonElement;
explainer_button: HTMLButtonElement;
constructor(element: HTMLElement, event: UiEventHandler) {
this.element = element;
this.events = event;
this.edit_button = new_button("Edit", "pencil.png", "editor_toggle");
this.console_button = new_button("Console", "texout.png");
this.display_button = new_button("Video", "tv.png");
this.explainer_button = new_button("Explainer", "explainer.png");
this.edit_button.addEventListener("click", () => this.edit_toggle());
this.element.appendChild(this.edit_button);
this.element.appendChild(this.console_button);
this.element.appendChild(this.display_button);
this.element.appendChild(this.explainer_button);
}
reset(): void {
const is_on = this.edit_button.classList.contains("on");
if (is_on) {
this.edit_toggle();
}
}
edit_toggle(): void {
const is_on = this.edit_button.classList.contains("on");
if (is_on) {
this.edit_button.classList.remove("on");
$("main").classList.remove("editor");
this.edit_button.classList.add("off");
this.events.dispatch(UiEvent.EditOff);
} else {
this.events.dispatch(UiEvent.EditOn);
$("main").classList.add("editor");
this.edit_button.classList.add("on");
this.edit_button.classList.remove("off");
}
}
}

View file

@ -9,7 +9,9 @@ export interface UiComponent {
element: HTMLElement; element: HTMLElement;
/** Allows listening and emitting UiEvents*/ /** Allows listening and emitting UiEvents*/
events: UiEventHandler; events: UiEventHandler;
/** Completely reset the state of the component */
reset: () => void; reset: () => void;
soft_reset?: () => void;
/** Allows listening CPUEvents*/ /** Allows listening CPUEvents*/
init_cpu_events?: (c: CpuEventHandler) => void; init_cpu_events?: (c: CpuEventHandler) => void;
} }

82
src/ui/windowBox.ts Normal file
View file

@ -0,0 +1,82 @@
import { el } from "../etc";
export abstract class WindowBox {
element: HTMLElement;
readonly title: string;
title_bar: HTMLElement;
private resize: HTMLElement;
private collapse_button: HTMLButtonElement;
private collapsed: boolean = false;
private resize_func: (e: MouseEvent) => void;
constructor(element: HTMLElement, title: string, options?: { collapsed?: boolean }) {
this.element = element;
this.title = title;
this.element.classList.add("window");
this.title_bar = el("div", undefined, "window_title");
this.element.appendChild(this.title_bar);
const title_bar_text_box = el("div", "text");
title_bar_text_box.textContent = title;
this.title_bar.appendChild(title_bar_text_box);
this.resize = el("div", "resize");
this.element.appendChild(this.resize);
this.resize_func = this.resize_move.bind(this);
this.collapse_button = el("button", "collapse_button", "nostyle");
this.collapse_button.addEventListener("click", () => {
this.toggle_collapse();
});
this.title_bar.appendChild(this.collapse_button);
this.resize.addEventListener("mousedown", (e) => {
window.addEventListener("mousemove", this.resize_func);
});
window.addEventListener("mouseup", () => {
this.remove_resize_listeners();
});
window.addEventListener("mouseleave", () => {
this.remove_resize_listeners();
});
if (options?.collapsed) {
this.collapse();
}
}
collapse(): void {
this.element.classList.add("collapsed");
this.remove_resize_listeners();
this.resize.style.visibility = "hidden";
this.element.style.height = `${this.title_bar.offsetHeight + 4}px`;
this.collapsed = true;
}
toggle_collapse(): void {
if (this.collapsed) {
this.uncollapse();
} else {
this.collapse();
}
}
uncollapse(): void {
this.element.classList.remove("collapsed");
this.resize.style.visibility = "unset";
this.element.style.height = `${this.title_bar.offsetHeight + 10 + 200}px`;
this.collapsed = false;
}
remove_resize_listeners(): void {
window.removeEventListener("mousemove", this.resize_func);
}
resize_move(e: MouseEvent): void {
if (this.collapsed) {
this.uncollapse();
this.remove_resize_listeners();
return;
}
const distance_to_title = e.clientY - this.element.offsetTop - this.title_bar.offsetHeight + window.scrollY + 5;
if (distance_to_title <= 5) {
this.collapse();
return;
}
this.element.style.height = `${e.clientY - this.element.offsetTop + window.scrollY + 8}px`;
}
}

View file

@ -1,6 +1,6 @@
import { UiEventHandler, CpuEventHandler, CpuEvent } from "../events"; import { UiEventHandler, CpuEventHandler, CpuEvent } from "../../events";
import { u1, u2 } from "../num"; import { u1, u2 } from "../../num";
import { UiComponent } from "./uiComponent"; import { UiComponent } from "../uiComponent";
class BankIndicator implements UiComponent { class BankIndicator implements UiComponent {
element: HTMLElement; element: HTMLElement;

View file

@ -1,14 +1,14 @@
import { el, format_hex } from "../etc"; import { el, format_hex } from "../../etc";
import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events"; import { CpuEvent, CpuEventHandler, UiEventHandler } from "../../events";
import { Instruction, ParamType, ParameterType } from "../instructionSet"; import { Instruction, ParamType, ParameterType } from "../../instructionSet";
import { u8 } from "../num"; import { u8 } from "../../num";
import { UiComponent } from "./uiComponent"; import { WindowBox } from "../windowBox";
import { UiComponent } from "../uiComponent";
export class InstructionExplainer implements UiComponent { export class InstructionExplainer extends WindowBox implements UiComponent {
element: HTMLElement;
events: UiEventHandler; events: UiEventHandler;
constructor(element: HTMLElement, e: UiEventHandler) { constructor(element: HTMLElement, e: UiEventHandler) {
this.element = element; super(element, "Instruction Explainer");
this.events = e; this.events = e;
} }
add_instruction(instr: Instruction, pos: u8, byte: u8): void { add_instruction(instr: Instruction, pos: u8, byte: u8): void {
@ -29,7 +29,7 @@ export class InstructionExplainer implements UiComponent {
this.element.appendChild(instr_box); this.element.appendChild(instr_box);
} }
add_param(param: ParameterType, pos: u8, byte: u8): void { add_parameter(param: ParameterType, pos: u8, byte: u8): void {
const t = param.type; const t = param.type;
let name; let name;
if (t === ParamType.Const) { if (t === ParamType.Const) {
@ -51,7 +51,7 @@ export class InstructionExplainer implements UiComponent {
init_cpu_events(c: CpuEventHandler): void { init_cpu_events(c: CpuEventHandler): void {
c.listen(CpuEvent.ParameterParsed, ({ param, code, pos }) => { c.listen(CpuEvent.ParameterParsed, ({ param, code, pos }) => {
this.add_param(param, pos, code); this.add_parameter(param, pos, code);
}); });
c.listen(CpuEvent.InstructionParsed, ({ instr, code, pos }) => { c.listen(CpuEvent.InstructionParsed, ({ instr, code, pos }) => {
this.add_instruction(instr, pos, code); this.add_instruction(instr, pos, code);
@ -62,6 +62,6 @@ export class InstructionExplainer implements UiComponent {
} }
reset(): void { reset(): void {
this.element.innerHTML = ""; this.element.querySelectorAll("#expl_box").forEach((e) => e.remove());
} }
} }

View file

@ -0,0 +1,25 @@
import { el } from "../../etc";
import { CpuEvent, CpuEventHandler, UiEventHandler } from "../../events";
import { WindowBox } from "../windowBox";
import { UiComponent } from "../uiComponent";
export class Printout extends WindowBox implements UiComponent {
events: UiEventHandler;
text_box: HTMLElement;
constructor(element: HTMLElement, events: UiEventHandler) {
super(element, "Printout");
this.events = events;
this.text_box = el("div", "printout_text");
this.element.appendChild(this.text_box);
}
init_cpu_events(c: CpuEventHandler): void {
c.listen(CpuEvent.Print, (c) => {
this.text_box.textContent += c;
});
}
reset(): void {
this.text_box.textContent = "";
}
}

View file

@ -1,32 +1,39 @@
import { UiEventHandler, CpuEventHandler, CpuEvent } from "../events"; import { el } from "../../etc";
import { u4, u8 } from "../num"; import { UiEventHandler, CpuEventHandler, CpuEvent } from "../../events";
import { UiComponent } from "./uiComponent"; import { u4, u8 } from "../../num";
export class Screen implements UiComponent { import { UiComponent } from "../uiComponent";
element: HTMLCanvasElement; import { WindowBox } from "../windowBox";
export class Screen extends WindowBox implements UiComponent {
events: UiEventHandler; events: UiEventHandler;
screen: HTMLCanvasElement;
ctx: CanvasRenderingContext2D; ctx: CanvasRenderingContext2D;
scale: [number, number]; scale: [number, number];
constructor(element: HTMLElement, event: UiEventHandler) { constructor(element: HTMLElement, event: UiEventHandler) {
this.element = element as HTMLCanvasElement; super(element, "TV", { collapsed: true });
this.screen = el("canvas", "screen");
this.events = event; this.events = event;
const canvas_size = [512, 512]; const canvas_size = [512, 512];
const data_size = [16, 16]; const data_size = [16, 16];
this.scale = [canvas_size[0] / data_size[0], canvas_size[1] / data_size[1]]; this.scale = [canvas_size[0] / data_size[0], canvas_size[1] / data_size[1]];
[this.element.width, this.element.height] = canvas_size; [this.screen.width, this.screen.height] = canvas_size;
const ctx = this.element.getContext("2d"); const ctx = this.screen.getContext("2d");
if (ctx === null) { if (ctx === null) {
throw new Error("todo"); throw new Error("could not load screen");
} }
this.ctx = ctx; this.ctx = ctx;
// for (let x = 0; x < 16; x++) { this.element.appendChild(this.screen);
// for (let y = 0; y < 16; y++) { }
// this.setPixel(x as u4, y as u4, (x + 16 * y) as u8);
// } private test_pattern(): void {
// } for (let x = 0; x < 16; x++) {
for (let y = 0; y < 16; y++) {
this.setPixel(x as u4, y as u4, (x + 16 * y) as u8);
}
}
} }
reset(): void { reset(): void {
const ctx = this.element.getContext("2d"); const ctx = this.screen.getContext("2d");
if (ctx === null) { if (ctx === null) {
throw new Error("todo"); throw new Error("todo");
} }

74
svg.html Normal file
View file

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
html {
color: white;
background-color: black;
font-family: monospace;
font-size: 2em;
}
body {
display: flex;
justify-content: center;
margin-top: 100px;
align-content: center;
margin-inline: auto;
margin-block: auto;
}
#cont {
margin-top: 100px;
display: flex;
flex-direction: row;
}
#cpu,
#target {
margin-block: auto;
border: solid 3px yellow;
padding: 6px;
}
#targets {
display: flex;
flex-direction: column;
gap: 9px;
}
#connections {
}
div#target.gray {
border-color: gray;
}
svg polyline {
fill: transparent;
stroke-width: 3;
stroke-linecap: butt;
}
</style>
</head>
<body>
<div id="cont">
<div id="cpu">CPU</div>
<svg
id="connections"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100"
preserveAspectRatio="none"
width="100"
height="100%"
>
<polyline points="0,43 50,43 50,10, 100,10" stroke="yellow" />
<polyline points="0,48 64,48 64,37, 100,37" stroke="gray" />
<polyline points="0,53 64,53 64,63, 100,63" stroke="gray" />
<polyline points="0,58 50,58 50,90, 100,90" stroke="gray" />
</svg>
<div id="targets">
<div id="target">Main</div>
<div id="target" class="gray">Bank 1</div>
<div id="target" class="gray">Bank 2</div>
<div id="target" class="gray">VRAM</div>
</div>
</div>
</body>
</html>

View file

@ -3,7 +3,6 @@ Edit Mode
- Select where program counter is - Select where program counter is
Implement HCF Implement HCF
Move start/stop/auto logic into computer
Speed control slider behavior Speed control slider behavior
Speed control slider styling Speed control slider styling
@ -11,9 +10,6 @@ Overclock Box
error in instruction when number out of range (fix new Error("todo")) error in instruction when number out of range (fix new Error("todo"))
UI for screen (toggling (click an icon?))
UI for togging other UI elements
UI for showing which Memory bank is selected UI for showing which Memory bank is selected
VRAM select instruction VRAM select instruction

View file

@ -1,4 +1,7 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable no-undef */
const path = require("path"); const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");
const config = { const config = {
entry: "./src/index.ts", entry: "./src/index.ts",
@ -33,6 +36,11 @@ const config = {
filename: "main.js", filename: "main.js",
path: path.resolve(__dirname, "dist"), path: path.resolve(__dirname, "dist"),
}, },
plugins: [
new CopyPlugin({
patterns: [{ from: "./src/include", to: "./" }],
}),
],
}; };
module.exports = (env, argv) => { module.exports = (env, argv) => {
if (argv.mode === "development") { if (argv.mode === "development") {