Add editable memory cells
This commit is contained in:
parent
7e4d3c8da8
commit
8a38dded92
29
ISA.txt
29
ISA.txt
|
@ -1,29 +0,0 @@
|
|||
INSTRUCTIONS
|
||||
--- Control Flow ---
|
||||
0x00: NoOp - 0 Parameter
|
||||
0x10: Goto - 1 Parameter - moves the program counter to (P1) in program memory
|
||||
0x11: Goto if low bit - 2 Parameter - Moves the program counter to (P1) if register (P2)'s least significant bit is 1.
|
||||
--- Memory and register management ---
|
||||
0x20: Load to register - 2 Parameter - Loads into register (P1) the byte at processing memory location (P2)
|
||||
0x21: Write register to memory - 2 Parameter - Writes the byte in register (P1) to the processing memory location (P2)
|
||||
0x28: Copy Register -> Register - 2 Parameter - Copies byte from register (P1) to register (P2)
|
||||
0x2F: Assign value to register - 2 Parameter - Assigns register (P1) to value (P2)
|
||||
--- Operations ---
|
||||
0x30: increment register - 1 Parameter - Increments register (P1) by 1.
|
||||
0x31: decrement register - 1 Parameter - Decrements register (P1) by 1.
|
||||
0x40: Add registers - 2 Parameter - Adds the contents of (P1) and (P2) and stores result to register (P1). (Overflow will be taken mod 256)
|
||||
0x41: Reserved
|
||||
--- Bit Operations ---
|
||||
0x48: Bitwise and - 2 Parameter - Ands & each bit of register (P1) and register (P2) and stores result to register (P1)
|
||||
0x49: Bitwise or - you get the point
|
||||
0x4A: Bitwise not - 1 Parameter - Inverts each bit of register (P1)
|
||||
0x4B: Left bit shift - 2 Parameter - Shifts bits in register (P1) to the left by (P2) and stores result to register (P1)
|
||||
0x4C: right bit shift- 2 Parameter - same as left
|
||||
--- Comparison Operations ---
|
||||
0x50: Equals - 3 Parameter - If byte in register (P1) equals byte in register (P2), set byte in register (P3) to 0x01
|
||||
0x51: Less than - 3 Parameter - If byte in register (P1) less than byte in register (P2), set byte in register (P3) to 0x01
|
||||
0x52: Greater than - 3 Parameter - If byte in register (P1) greater than byte in register (P2), set byte in register (P3) to 0x01
|
||||
--- Development ---
|
||||
0xFE: Print byte as ASCII from register - 1 Parameter - Prints the ASCII byte in register (P1) to console
|
||||
0xFF: Print byte from register - 1 Parameter - Prints the byte in register (P1) to console
|
||||
0x66: Halt and Catch Fire - 0 Parameter - Fire! FIRE EVERYWHERE!!!!!
|
14
TODO
14
TODO
|
@ -1,14 +0,0 @@
|
|||
Live memory and register editing (Probably should pause autostep when it reaches the cell you're modifying)
|
||||
HCF flames
|
||||
add hcf
|
||||
Move start/stop/auto logic into computer
|
||||
Speed control slider behavior
|
||||
Speed control slider styling
|
||||
|
||||
UI for screen (toggling (click an icon?))
|
||||
UI for togging other UI elements
|
||||
|
||||
UI for showing which Memory bank is selected
|
||||
VRAM select instruction
|
||||
|
||||
Error log
|
33
index.html
33
index.html
|
@ -1,33 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script src="./dist/main.js"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Virtual 8-Bit Computer</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<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="instruction_explainer"></div>
|
||||
<div id="printout"></div>
|
||||
<div id="controls_bar">
|
||||
<button type="button" id="pause_play_button">Start</button>
|
||||
<button type="button" id="step_button">Step</button>
|
||||
<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>
|
||||
<input type="range" name="" id="delay_range" min="0" max="1500" />
|
||||
</div>
|
||||
<span id="cycles"></span>
|
||||
</div>
|
||||
<canvas id="screen"></canvas>
|
||||
<pre id="ISA"></pre>
|
||||
</body>
|
||||
</html>
|
|
@ -1,7 +1,7 @@
|
|||
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
|
||||
import { byte_array_to_js_source, format_hex } from "./etc";
|
||||
import { Instruction, ISA } from "./instructionSet";
|
||||
import { m256, u1, u2, u3, u8 } from "./num";
|
||||
import { m256, u2, u3, u8 } from "./num";
|
||||
|
||||
export type TempInstrState = {
|
||||
pos: u8;
|
||||
|
@ -178,6 +178,7 @@ export class Computer {
|
|||
for (let i = 0; i < cycle_count; i++) this.cycle();
|
||||
});
|
||||
ui.listen(UiEvent.RequestMemoryChange, ({ address, value }) => this.setMemory(address, value));
|
||||
ui.listen(UiEvent.RequestRegisterChange, ({ register_no, value }) => this.setRegister(register_no, value));
|
||||
}
|
||||
|
||||
load_memory(program: Array<u8>): void {
|
||||
|
|
27
src/etc.ts
27
src/etc.ts
|
@ -31,13 +31,20 @@ export const byte_array_to_js_source = (bytes: Array<u8>): string => {
|
|||
* @param type
|
||||
* @param id id attribute to set
|
||||
*/
|
||||
export function el<E extends keyof HTMLElementTagNameMap>(type: E, id?: string): HTMLElementTagNameMap[E];
|
||||
export function el(type: string, id?: string): HTMLElement | undefined {
|
||||
export function el<E extends keyof HTMLElementTagNameMap>(
|
||||
type: E,
|
||||
id?: string,
|
||||
class_list?: string
|
||||
): HTMLElementTagNameMap[E];
|
||||
export function el(type: string, id?: string, class_list?: string): HTMLElement | undefined {
|
||||
const element = document.createElement(type);
|
||||
if (id === undefined) {
|
||||
return element;
|
||||
if (id !== undefined) {
|
||||
element.id = id;
|
||||
}
|
||||
element.id = id;
|
||||
if (class_list !== undefined) {
|
||||
element.className = class_list;
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
|
@ -49,3 +56,13 @@ export function in_range(check: number, start: number, end: number): boolean {
|
|||
if (check >= start && check <= end) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function at<T>(l: Array<T>, i: number): T | null {
|
||||
if (i < 0) {
|
||||
return null;
|
||||
}
|
||||
if (i >= l.length) {
|
||||
return null;
|
||||
}
|
||||
return l[i];
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
import { EventHandler } from "./eventHandler";
|
||||
import { Instruction, ParameterType } from "./instructionSet";
|
||||
import { u1, u2, u3, u8 } from "./num";
|
||||
import { u2, u3, u8 } from "./num";
|
||||
|
||||
//
|
||||
// CPU Event Handler Definition
|
||||
|
@ -65,18 +65,28 @@ export const CpuEventHandler = EventHandler<CpuEvent> as CpuEventHandlerConstruc
|
|||
//
|
||||
|
||||
export enum UiEvent {
|
||||
// Maybe move these into a UI -> CPU signal system?
|
||||
RequestCpuCycle,
|
||||
RequestMemoryChange,
|
||||
RequestRegisterChange,
|
||||
// Ui Events
|
||||
EditOn,
|
||||
EditOff,
|
||||
}
|
||||
|
||||
interface UiEventMap {
|
||||
[UiEvent.RequestCpuCycle]: number;
|
||||
[UiEvent.RequestMemoryChange]: { address: u8; value: u8 };
|
||||
[UiEvent.RequestRegisterChange]: { register_no: u3; value: u8 };
|
||||
}
|
||||
|
||||
type VoidDataUiEventList = UiEvent.EditOn | UiEvent.EditOff;
|
||||
|
||||
export interface UiEventHandler extends EventHandler<UiEvent> {
|
||||
listen<E extends keyof UiEventMap>(type: E, listener: (ev: UiEventMap[E]) => void): void;
|
||||
dispatch<E extends keyof UiEventMap>(type: E, data: UiEventMap[E]): void;
|
||||
listen<E extends VoidDataUiEventList>(type: E, listener: () => void): void;
|
||||
dispatch<E extends VoidDataUiEventList>(type: E): void;
|
||||
}
|
||||
|
||||
interface UiEventHandlerConstructor {
|
||||
|
|
46
src/index.ts
46
src/index.ts
|
@ -22,26 +22,9 @@ declare global {
|
|||
}
|
||||
|
||||
function main(): void {
|
||||
// const program: Array<u8> = [
|
||||
// 0x2f, 0x00, 0xf0, 0x20, 0x07, 0x00, 0x50, 0x05, 0x06, 0x07, 0x11, 0x00, 0x05, 0xfe, 0x07, 0x30, 0x00, 0x10, 0x03,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57,
|
||||
// 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x00,
|
||||
// ];
|
||||
|
||||
const program: Array<u8> = [
|
||||
0x2f, 0x00, 0x00, 0x2f, 0x01, 0xff, 0x21, 0x01, 0x0d, 0xb1, 0x01, 0x21, 0x01, 0x00, 0x31, 0x01, 0xb1, 0x00, 0x10,
|
||||
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x19, 0x00, 0xf0, 0x14, 0x00, 0x01, 0x5e, 0x00, 0xf0, 0x01, 0x21, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
|
@ -55,24 +38,6 @@ function main(): void {
|
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57,
|
||||
0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
// const program = [
|
||||
// 0x01, 0x00, 0x01, 0x00, 0xa0, 0x10, 0xff, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0xa1,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57,
|
||||
// 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x00,
|
||||
// ];
|
||||
|
||||
const computer = new Computer();
|
||||
|
||||
const ui = new UI();
|
||||
|
@ -137,10 +102,5 @@ function main(): void {
|
|||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// at least you know it's bad
|
||||
try {
|
||||
main();
|
||||
} catch (e) {
|
||||
alert(e);
|
||||
}
|
||||
main();
|
||||
});
|
||||
|
|
|
@ -170,6 +170,34 @@ ISA.insertInstruction(0x13, {
|
|||
c.setRegister(register_no_2, c.getRegister(register_no_1));
|
||||
},
|
||||
});
|
||||
ISA.insertInstruction(0x14, {
|
||||
name: "Load RM -> R",
|
||||
desc: "Copy the byte in memory addressed by register (P1) to register (P2)",
|
||||
params: [
|
||||
new RegisParam("Copy the byte in the memory cell addressed in this register"),
|
||||
new RegisParam("To this register"),
|
||||
],
|
||||
execute(c, p) {
|
||||
const [register_no_1, register_no_2] = p;
|
||||
if (!isU3(register_no_1)) throw new Error("todo");
|
||||
if (!isU3(register_no_2)) throw new Error("todo");
|
||||
c.setRegister(register_no_2, c.getMemory(c.getRegister(register_no_1)));
|
||||
},
|
||||
});
|
||||
ISA.insertInstruction(0x15, {
|
||||
name: "Save R -> RM",
|
||||
desc: "Copy the byte in register (P1) to the memory cell addressed in register (P2)",
|
||||
params: [
|
||||
new RegisParam("Copy the value in this register"),
|
||||
new RegisParam("To the memory cell addressed in this register"),
|
||||
],
|
||||
execute(c, p) {
|
||||
const [register_no_1, register_no_2] = p;
|
||||
if (!isU3(register_no_1)) throw new Error("todo");
|
||||
if (!isU3(register_no_2)) throw new Error("todo");
|
||||
c.setMemory(c.getRegister(register_no_2), c.getRegister(register_no_1));
|
||||
},
|
||||
});
|
||||
|
||||
ISA.insertInstruction(0x17, {
|
||||
name: "Zero Register",
|
||||
|
@ -608,6 +636,7 @@ ISA.insertInstruction(0x50, {
|
|||
c.setRegister(register_no_1, m256(sum));
|
||||
},
|
||||
});
|
||||
|
||||
ISA.insertInstruction(0x51, {
|
||||
name: "Add",
|
||||
desc: "Adds to the byte in register (P1) with the value in register (P2)",
|
||||
|
@ -620,8 +649,9 @@ ISA.insertInstruction(0x51, {
|
|||
c.setRegister(register_no_1, m256(sum));
|
||||
},
|
||||
});
|
||||
|
||||
ISA.insertInstruction(0x52, {
|
||||
name: "Add",
|
||||
name: "Subtract",
|
||||
desc: "Subtracts from the value in register (P1) by the value in register (P2)",
|
||||
params: [new RegisParam("set this register to"), new RegisParam("it's difference with the value in this register")],
|
||||
execute(c, p) {
|
||||
|
@ -635,8 +665,9 @@ ISA.insertInstruction(0x52, {
|
|||
c.setRegister(register_no_1, m256(difference));
|
||||
},
|
||||
});
|
||||
|
||||
ISA.insertInstruction(0x53, {
|
||||
name: "Add",
|
||||
name: "Subtract",
|
||||
desc: "Subtracts from the value in register (P1) by the constant value (P2)",
|
||||
params: [new RegisParam("set this register to"), new ConstParam("it's difference with this constant")],
|
||||
execute(c, p) {
|
||||
|
|
|
@ -8,16 +8,6 @@ pre {
|
|||
|
||||
:root {
|
||||
--Border: #ffff00;
|
||||
// --mem-instruction: #adff2f;
|
||||
// --mem-register: #800080;
|
||||
// --mem-constant: #d3d3d3;
|
||||
// --mem-memory: #d3d3d3;
|
||||
// --mem-invalid: #ff0000;
|
||||
// --mem-instruction: #2f962a;
|
||||
// --mem-register: #dc21d1;
|
||||
// --mem-constant: #d3d3d3;
|
||||
// --mem-memory: #4d86f0;
|
||||
// --mem-invalid: #bf2e2e;
|
||||
--mem-instruction: #3af78f;
|
||||
--mem-memory: #ff26a8;
|
||||
--mem-register: #9e0ef7;
|
||||
|
@ -25,6 +15,10 @@ pre {
|
|||
--mem-invalid: #bf2e2e;
|
||||
}
|
||||
|
||||
img {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #808080;
|
||||
background-color: black;
|
||||
|
@ -44,13 +38,13 @@ body {
|
|||
#main {
|
||||
justify-content: center;
|
||||
display: grid;
|
||||
grid-template-columns: min-content max-content max-content 10px 500px;
|
||||
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 . . . explainer "
|
||||
"title . . . ."
|
||||
"title . . . printout "
|
||||
"title . . ribbon explainer "
|
||||
"title . . ribbon ."
|
||||
"title . . ribbon printout "
|
||||
"title . . . printout "
|
||||
". buttons buttons . .";
|
||||
#memory {
|
||||
|
@ -70,6 +64,9 @@ body {
|
|||
user-select: none;
|
||||
transform: scale(-1, -1);
|
||||
}
|
||||
#ribbon_menu {
|
||||
grid-area: ribbon;
|
||||
}
|
||||
}
|
||||
|
||||
#printout {
|
||||
|
@ -124,6 +121,17 @@ body {
|
|||
--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;
|
||||
|
@ -131,21 +139,29 @@ body {
|
|||
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;
|
||||
|
@ -159,10 +175,31 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
border-bottom: none !important;
|
||||
|
||||
grid-template-columns: repeat(8, min-content);
|
||||
max-width: fit-content;
|
||||
|
@ -191,6 +228,25 @@ label.button {
|
|||
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;
|
||||
|
@ -201,6 +257,10 @@ label.button:hover {
|
|||
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
#controls_buttons {
|
||||
display: flex;
|
||||
gap: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
|
@ -208,8 +268,8 @@ input[type="range"] {
|
|||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
margin: 18px 0;
|
||||
// width: 100%;
|
||||
}
|
||||
|
||||
input[type="range"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
|
57
src/ui.ts
57
src/ui.ts
|
@ -1,16 +1,13 @@
|
|||
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
|
||||
import { $, el, format_hex } from "./etc";
|
||||
import { $ } from "./etc";
|
||||
import { InstructionExplainer } from "./ui/instructionExplainer";
|
||||
import { MemoryView } from "./ui/memoryView";
|
||||
import { frequencyIndicator } from "./ui/frequencyIndicator";
|
||||
import { RegisterView } from "./ui/registerView";
|
||||
import { Screen } from "./ui/screen";
|
||||
import { UiComponent, UiComponentConstructor } from "./ui/uiComponent.js";
|
||||
// Certainly the messiest portion of this program
|
||||
// Needs to be broken into components
|
||||
// Breaking up into components has started but has yet to conclude
|
||||
|
||||
let delay = 100;
|
||||
import { Ribbon } from "./ui/ribbon";
|
||||
import { UiComponent, UiComponentConstructor } from "./ui/uiComponent";
|
||||
import { pausePlay } from "./ui/pausePlay";
|
||||
|
||||
export class UI {
|
||||
printout: HTMLElement;
|
||||
|
@ -34,32 +31,12 @@ export class UI {
|
|||
this.register_component(InstructionExplainer, $("instruction_explainer"));
|
||||
this.register_component(RegisterView, $("registers"));
|
||||
this.register_component(Screen, $("screen") as HTMLCanvasElement);
|
||||
this.register_component(Ribbon, $("ribbon_menu"));
|
||||
this.register_component(pausePlay, $("controls_buttons"));
|
||||
this.printout = $("printout");
|
||||
|
||||
this.auto_running = false;
|
||||
const pp_button = $("pause_play_button");
|
||||
|
||||
pp_button.addEventListener("click", () => {
|
||||
if (this.auto_running) {
|
||||
this.stop_auto();
|
||||
pp_button.textContent = "Starp";
|
||||
} else {
|
||||
this.start_auto();
|
||||
pp_button.textContent = "Storp";
|
||||
}
|
||||
});
|
||||
$("step_button").addEventListener("click", () => {
|
||||
if (this.auto_running) {
|
||||
this.stop_auto();
|
||||
}
|
||||
|
||||
this.events.dispatch(UiEvent.RequestCpuCycle, 1);
|
||||
});
|
||||
|
||||
$("delay_range").addEventListener("input", (e) => {
|
||||
delay = parseInt((e.target as HTMLInputElement).value, 10);
|
||||
// console.log(delay);
|
||||
});
|
||||
}
|
||||
private register_component(c: UiComponentConstructor, e: HTMLElement): void {
|
||||
if (e === undefined) {
|
||||
|
@ -79,34 +56,14 @@ export class UI {
|
|||
});
|
||||
|
||||
for (const c of this.components) {
|
||||
c.init_cpu_events(cpu_events);
|
||||
if (c.init_cpu_events) c.init_cpu_events(cpu_events);
|
||||
}
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.stop_auto();
|
||||
for (const c of this.components) {
|
||||
c.reset();
|
||||
}
|
||||
this.printout.textContent = "";
|
||||
}
|
||||
|
||||
start_auto(speed: number = 200): void {
|
||||
if (this.auto_running) {
|
||||
return;
|
||||
}
|
||||
this.auto_running = true;
|
||||
const loop = (): void => {
|
||||
if (this.auto_running === false) {
|
||||
return;
|
||||
}
|
||||
this.events.dispatch(UiEvent.RequestCpuCycle, 1);
|
||||
setTimeout(loop, delay);
|
||||
};
|
||||
loop();
|
||||
}
|
||||
|
||||
stop_auto(): void {
|
||||
this.auto_running = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import { NonEmptyArray, el, format_hex } from "../etc";
|
||||
import { u8 } from "../num";
|
||||
|
||||
// TODO, make generic
|
||||
interface GenericCell {
|
||||
el: HTMLElement;
|
||||
}
|
||||
|
@ -22,7 +21,7 @@ export abstract class CelledViewer {
|
|||
this.height = height;
|
||||
for (let i = 0; i < this.width * this.height; i++) {
|
||||
const mem_cell_el = el("div");
|
||||
mem_cell_el.textContent = "00";
|
||||
mem_cell_el.append("0", "0");
|
||||
this.element.appendChild(mem_cell_el);
|
||||
const mem_cell = { el: mem_cell_el };
|
||||
this.cells.push(mem_cell);
|
||||
|
@ -31,7 +30,7 @@ export abstract class CelledViewer {
|
|||
|
||||
reset(): void {
|
||||
for (let i = 0; i < this.height * this.width; i++) {
|
||||
this.cells[i].el.textContent = "00";
|
||||
this.set_cell_value(i as u8, 0);
|
||||
this.cells[i].el.className = "";
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +58,10 @@ export abstract class CelledViewer {
|
|||
}
|
||||
|
||||
set_cell_value(address: u8, value: u8): void {
|
||||
this.cells[address].el.textContent = format_hex(value);
|
||||
const str = format_hex(value);
|
||||
const a = str[0];
|
||||
const b = str[1];
|
||||
this.cells[address].el.textContent = "";
|
||||
this.cells[address].el.append(a, b);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,122 +1,112 @@
|
|||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function set_caret(el: any, pos: number): boolean {
|
||||
const selection = window.getSelection();
|
||||
const range = document.createRange();
|
||||
if (selection === null) {
|
||||
return false;
|
||||
}
|
||||
// This file was cobbled together and is the messiest part of this project
|
||||
|
||||
selection.removeAllRanges();
|
||||
range.selectNode(el);
|
||||
import { at } from "../etc";
|
||||
import { u8 } from "../num";
|
||||
|
||||
range.setStart(el, pos);
|
||||
range.setEnd(el, pos);
|
||||
range.collapse(true);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
el.focus();
|
||||
return true;
|
||||
}
|
||||
const HEX_CHARACTERS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
|
||||
|
||||
function get_caret(el: HTMLElement): null | number {
|
||||
const sel = window.getSelection();
|
||||
if (sel === null) {
|
||||
return null;
|
||||
}
|
||||
const pos = sel.getRangeAt(0).startOffset;
|
||||
const endPos = pos + Array.from(el.innerHTML.slice(0, pos)).length - el.innerHTML.slice(0, pos).split("").length;
|
||||
return endPos;
|
||||
}
|
||||
export class EditorContext {
|
||||
private list: Array<HTMLElement>;
|
||||
private width: number;
|
||||
private height: number;
|
||||
private enabled: boolean = false;
|
||||
private current_cell_info: { left?: string; right?: string; old?: string };
|
||||
private edit_callback: (n: number, value: u8) => void;
|
||||
constructor(list: Array<HTMLElement>, width: number, height: number, callback: (n: number, value: u8) => void) {
|
||||
this.list = list;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.edit_callback = callback;
|
||||
this.current_cell_info = {};
|
||||
|
||||
const hex_characters = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
|
||||
function replace_non_hex(c: string): string {
|
||||
if (hex_characters.includes(c)) {
|
||||
return c;
|
||||
}
|
||||
return "0";
|
||||
}
|
||||
for (const [i, cell] of this.list.entries()) {
|
||||
cell.setAttribute("spellcheck", "false");
|
||||
cell.addEventListener("keydown", (e) => {
|
||||
this.keydown(e, i);
|
||||
});
|
||||
cell.addEventListener("focus", () => {
|
||||
if (!this.enabled) return;
|
||||
this.current_cell_info.old = cell.textContent ?? "00";
|
||||
this.current_cell_info.left = undefined;
|
||||
this.current_cell_info.right = undefined;
|
||||
cell.classList.add("caret_selected");
|
||||
|
||||
function editable_constraints(e: Event): boolean {
|
||||
const target = e.target as HTMLDivElement;
|
||||
const text = target.innerHTML ?? "";
|
||||
if (text.length !== 2) {
|
||||
const pos = get_caret(target);
|
||||
const new_str = [...(target.textContent ?? "").substring(0, 2).padStart(2, "0").toUpperCase()]
|
||||
.map(replace_non_hex)
|
||||
.join("");
|
||||
target.innerHTML = "";
|
||||
// For the caret selection to work right, each character must be its own node, complicating this greatly
|
||||
target.append(new_str.substring(0, 1), new_str.substring(1));
|
||||
// Reset cursor position (I know there's an API for this, but this is a simpler, more robust solution)
|
||||
cell.textContent = cell.textContent ?? "00";
|
||||
});
|
||||
|
||||
if (pos !== null) {
|
||||
if (pos >= 2) {
|
||||
return true;
|
||||
}
|
||||
set_caret(target, pos);
|
||||
cell.addEventListener("blur", () => {
|
||||
const left = this.current_cell_info.left;
|
||||
const right = this.current_cell_info.right;
|
||||
cell.classList.remove("caret_selected");
|
||||
if (left === undefined || right === undefined) {
|
||||
cell.textContent = this.current_cell_info.old ?? "00";
|
||||
} else if (left !== undefined && right !== undefined) {
|
||||
const text = `${left}${right}`;
|
||||
cell.textContent = text;
|
||||
const val = Number.parseInt(text, 16) as u8;
|
||||
this.edit_callback(i, val);
|
||||
cell.classList.add("recent_edit");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function at<T>(l: Array<T>, i: number): T | null {
|
||||
if (i < 0) {
|
||||
return null;
|
||||
enable(): void {
|
||||
this.enabled = true;
|
||||
for (const cell of this.list) {
|
||||
cell.setAttribute("contenteditable", "true");
|
||||
}
|
||||
}
|
||||
if (i >= l.length) {
|
||||
return null;
|
||||
disable(): void {
|
||||
this.enabled = false;
|
||||
for (const cell of this.list) {
|
||||
cell.removeAttribute("contenteditable");
|
||||
cell.blur();
|
||||
}
|
||||
this.current_cell_info = {};
|
||||
}
|
||||
return l[i];
|
||||
}
|
||||
|
||||
export function make_editable(
|
||||
list: Array<HTMLElement>,
|
||||
width: number,
|
||||
height: number,
|
||||
on_edit: (n: number, value: string) => void
|
||||
): void {
|
||||
for (const [i, cell] of list.entries()) {
|
||||
cell.setAttribute("contenteditable", "true");
|
||||
cell.setAttribute("spellcheck", "false");
|
||||
const next: null | HTMLElement = at(list, i + 1);
|
||||
const prev: null | HTMLElement = at(list, i - 1);
|
||||
const up: null | HTMLElement = at(list, i - width);
|
||||
const down: null | HTMLElement = at(list, i + width);
|
||||
cell.addEventListener("keydown", (e) => {
|
||||
const caret_position = get_caret(cell);
|
||||
const k = e.key;
|
||||
if (k === "ArrowUp") {
|
||||
(up ?? prev)?.focus();
|
||||
cell.blur();
|
||||
} else if (k === "ArrowDown") {
|
||||
(down ?? next)?.focus();
|
||||
cell.blur();
|
||||
} else if ((k === "ArrowLeft" || k === "Backspace") && caret_position === 0) {
|
||||
prev?.focus();
|
||||
cell.blur();
|
||||
} else if (k === "ArrowRight" && caret_position === 1) {
|
||||
next?.focus();
|
||||
cell.blur();
|
||||
} else if (k === "Enter") {
|
||||
cell.blur();
|
||||
} else if (k === "Escape") {
|
||||
cell.blur();
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
});
|
||||
let previous_text = cell.textContent ?? "";
|
||||
cell.addEventListener("input", (e) => {
|
||||
const current_text = cell.textContent ?? "";
|
||||
if (current_text !== previous_text) {
|
||||
previous_text = cell.textContent ?? "";
|
||||
on_edit(i, current_text);
|
||||
}
|
||||
if (editable_constraints(e) === true) {
|
||||
private keydown(e: KeyboardEvent, cell_index: number): void {
|
||||
if (!this.enabled) return;
|
||||
const cell = e.target as HTMLElement;
|
||||
|
||||
const next: null | HTMLElement = at(this.list, cell_index + 1);
|
||||
const prev: null | HTMLElement = at(this.list, cell_index - 1);
|
||||
const up: null | HTMLElement = at(this.list, cell_index - this.width);
|
||||
const down: null | HTMLElement = at(this.list, cell_index + this.width);
|
||||
|
||||
const k = e.key;
|
||||
if (k === "ArrowUp") {
|
||||
(up ?? prev)?.focus();
|
||||
cell.blur();
|
||||
} else if (k === "ArrowDown") {
|
||||
(down ?? next)?.focus();
|
||||
cell.blur();
|
||||
} else if (k === "ArrowLeft" || k === "Backspace") {
|
||||
prev?.focus();
|
||||
cell.blur();
|
||||
} else if (k === "ArrowRight") {
|
||||
next?.focus();
|
||||
cell.blur();
|
||||
} else if (k === "Enter") {
|
||||
cell.blur();
|
||||
} else if (k === "Escape") {
|
||||
cell.blur();
|
||||
return;
|
||||
} else if (HEX_CHARACTERS.includes(k.toUpperCase())) {
|
||||
if (this.current_cell_info.left === undefined) {
|
||||
this.current_cell_info.left = k.toUpperCase();
|
||||
cell.innerHTML = `<span class="pending_edit">${this.current_cell_info.left}</span>0`;
|
||||
} else if (this.current_cell_info.right === undefined) {
|
||||
this.current_cell_info.right = k.toUpperCase();
|
||||
cell.textContent = `${this.current_cell_info.left}${this.current_cell_info.right}`;
|
||||
next?.focus();
|
||||
cell.blur();
|
||||
}
|
||||
});
|
||||
} else if (k === "Tab") {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
|
||||
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "../events";
|
||||
import { ParamType } from "../instructionSet";
|
||||
import { u8 } from "../num.js";
|
||||
import { UiComponent } from "./uiComponent";
|
||||
import { CelledViewer } from "./celledViewer";
|
||||
import { EditorContext } from "./editableHex";
|
||||
|
||||
type MemoryCell = {
|
||||
el: HTMLDivElement;
|
||||
|
@ -16,6 +17,23 @@ export class MemoryView extends CelledViewer implements UiComponent {
|
|||
super(16, 16, element);
|
||||
this.program_counter = 0;
|
||||
this.events = e;
|
||||
|
||||
const list = this.cells.map((c) => c.el);
|
||||
const editor = new EditorContext(list, this.width, this.height, (i, value) => {
|
||||
this.events.dispatch(UiEvent.RequestMemoryChange, { address: i as u8, value });
|
||||
});
|
||||
this.events.listen(UiEvent.EditOn, () => {
|
||||
editor.enable();
|
||||
for (const cell of this.cells) {
|
||||
cell.el.className = "";
|
||||
}
|
||||
});
|
||||
this.events.listen(UiEvent.EditOff, () => {
|
||||
editor.disable();
|
||||
for (const cell of this.cells) {
|
||||
cell.el.className = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
set_program_counter(position: u8): void {
|
||||
|
|
104
src/ui/pausePlay.ts
Normal file
104
src/ui/pausePlay.ts
Normal file
|
@ -0,0 +1,104 @@
|
|||
import { el } from "../etc";
|
||||
import { UiEventHandler, CpuEventHandler, UiEvent } from "../events";
|
||||
import { UiComponent } from "./uiComponent";
|
||||
|
||||
const MAX_SLIDER = 1000;
|
||||
|
||||
export class pausePlay implements UiComponent {
|
||||
element: HTMLElement;
|
||||
start_button: HTMLButtonElement;
|
||||
step_button: HTMLButtonElement;
|
||||
range: HTMLInputElement;
|
||||
events: UiEventHandler;
|
||||
on: boolean = false;
|
||||
cycle_delay: number;
|
||||
constructor(element: HTMLElement, events: UiEventHandler) {
|
||||
this.element = element;
|
||||
this.events = events;
|
||||
this.start_button = el("button", "pause_play_button");
|
||||
this.step_button = el("button", "step_button");
|
||||
this.range = el("input", "speed_range");
|
||||
this.range.max = MAX_SLIDER.toString();
|
||||
this.range.min = "0";
|
||||
this.range.type = "range";
|
||||
this.start_button.addEventListener("click", () => this.toggle());
|
||||
this.step_button.addEventListener("click", () => this.step());
|
||||
this.range.addEventListener("input", (e) => {
|
||||
const delay = MAX_SLIDER - parseInt((e.target as HTMLInputElement).value, 10) + 10;
|
||||
this.cycle_delay = delay;
|
||||
});
|
||||
this.start_button.textContent = "Start";
|
||||
this.step_button.textContent = "Step";
|
||||
this.element.appendChild(this.start_button);
|
||||
this.element.appendChild(this.step_button);
|
||||
this.element.appendChild(this.range);
|
||||
this.cycle_delay = 1000;
|
||||
this.range.value = "0";
|
||||
|
||||
this.events.listen(UiEvent.EditOn, () => {
|
||||
this.disable();
|
||||
});
|
||||
this.events.listen(UiEvent.EditOff, () => {
|
||||
this.enable();
|
||||
});
|
||||
}
|
||||
|
||||
disable(): void {
|
||||
this.stop();
|
||||
this.start_button.setAttribute("disabled", "true");
|
||||
this.step_button.setAttribute("disabled", "true");
|
||||
this.range.setAttribute("disabled", "true");
|
||||
}
|
||||
|
||||
enable(): void {
|
||||
this.start_button.removeAttribute("disabled");
|
||||
this.step_button.removeAttribute("disabled");
|
||||
this.range.removeAttribute("disabled");
|
||||
}
|
||||
|
||||
toggle(): void {
|
||||
if (this.on) {
|
||||
this.start_button.textContent = "Start";
|
||||
this.on = false;
|
||||
} else {
|
||||
this.on = true;
|
||||
this.cycle();
|
||||
this.start_button.textContent = "Storp";
|
||||
}
|
||||
}
|
||||
|
||||
private cycle(): void {
|
||||
const loop = (): void => {
|
||||
if (this.on === false) {
|
||||
return;
|
||||
}
|
||||
this.events.dispatch(UiEvent.RequestCpuCycle, 1);
|
||||
setTimeout(loop, this.cycle_delay);
|
||||
};
|
||||
loop();
|
||||
}
|
||||
private step(): void {
|
||||
if (this.on) {
|
||||
this.stop();
|
||||
} else {
|
||||
this.events.dispatch(UiEvent.RequestCpuCycle, 1);
|
||||
}
|
||||
}
|
||||
|
||||
start(): void {
|
||||
if (!this.on) {
|
||||
this.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
if (this.on) {
|
||||
this.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.stop();
|
||||
this.enable();
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
|
||||
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "../events";
|
||||
import { u3 } from "../num";
|
||||
import { CelledViewer } from "./celledViewer";
|
||||
import { EditorContext } from "./editableHex";
|
||||
import { UiComponent } from "./uiComponent";
|
||||
|
||||
export class RegisterView extends CelledViewer implements UiComponent {
|
||||
|
@ -7,6 +9,23 @@ export class RegisterView extends CelledViewer implements UiComponent {
|
|||
constructor(element: HTMLElement, e: UiEventHandler) {
|
||||
super(8, 1, element);
|
||||
this.events = e;
|
||||
|
||||
const list = this.cells.map((c) => c.el);
|
||||
const editor = new EditorContext(list, this.width, this.height, (i, value) => {
|
||||
this.events.dispatch(UiEvent.RequestRegisterChange, { register_no: i as u3, value });
|
||||
});
|
||||
this.events.listen(UiEvent.EditOn, () => {
|
||||
editor.enable();
|
||||
for (const cell of this.cells) {
|
||||
cell.el.className = "";
|
||||
}
|
||||
});
|
||||
this.events.listen(UiEvent.EditOff, () => {
|
||||
editor.disable();
|
||||
for (const cell of this.cells) {
|
||||
cell.el.className = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
init_cpu_events(c: CpuEventHandler): void {
|
||||
|
|
60
src/ui/ribbon.ts
Normal file
60
src/ui/ribbon.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,11 +7,11 @@ import { CpuEventHandler, UiEventHandler } from "../events";
|
|||
|
||||
export interface UiComponent {
|
||||
element: HTMLElement;
|
||||
/** Allows listening and emitting UiEvent's*/
|
||||
/** Allows listening and emitting UiEvents*/
|
||||
events: UiEventHandler;
|
||||
reset: () => void;
|
||||
/** Allows listening CPUEvent's*/
|
||||
init_cpu_events: (c: CpuEventHandler) => void;
|
||||
/** Allows listening CPUEvents*/
|
||||
init_cpu_events?: (c: CpuEventHandler) => void;
|
||||
}
|
||||
|
||||
export interface UiComponentConstructor {
|
||||
|
|
36
todo.md
Normal file
36
todo.md
Normal file
|
@ -0,0 +1,36 @@
|
|||
Edit Mode
|
||||
|
||||
- Select where program counter is
|
||||
|
||||
Implement HCF
|
||||
Move start/stop/auto logic into computer
|
||||
|
||||
Speed control slider behavior
|
||||
Speed control slider styling
|
||||
Overclock Box
|
||||
|
||||
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
|
||||
VRAM select instruction
|
||||
|
||||
Improve instruction explainer. Clearly show what is an instruction and what is a parameter
|
||||
|
||||
Verify mod256 behavior on negatives
|
||||
|
||||
UI showing CPU flag(s) (Carry)
|
||||
|
||||
Error log
|
||||
|
||||
Responsive layout
|
||||
|
||||
standardize names of all things
|
||||
|
||||
Documentation with standard names
|
||||
|
||||
Example Programs
|
||||
|
||||
Ui for togging your mother.
|
Loading…
Reference in a new issue