Adding registers panes of CPU window
This commit is contained in:
parent
3dc8b93f94
commit
ff07e1907f
|
@ -9,3 +9,4 @@
|
||||||
*.class binary
|
*.class binary
|
||||||
*.dll binary
|
*.dll binary
|
||||||
*.wasm binary
|
*.wasm binary
|
||||||
|
*.woff2 binary
|
||||||
|
|
|
@ -90,6 +90,9 @@ globalThis.App = class App {
|
||||||
item = menu.newMenuItem({ text: "{memory._}" });
|
item = menu.newMenuItem({ text: "{memory._}" });
|
||||||
item.addClickListener(
|
item.addClickListener(
|
||||||
()=>this.debuggers[0].memory.setVisible(true, true));
|
()=>this.debuggers[0].memory.setVisible(true, true));
|
||||||
|
item = menu.newMenuItem({ text: "{cpu._}" });
|
||||||
|
item.addClickListener(
|
||||||
|
()=>this.debuggers[0].cpu.setVisible(true, true));
|
||||||
|
|
||||||
// Theme menu
|
// Theme menu
|
||||||
menu = this.mainMenu.newMenu({ text: "{menu.theme._}" });
|
menu = this.mainMenu.newMenu({ text: "{menu.theme._}" });
|
||||||
|
@ -159,11 +162,12 @@ globalThis.App = class App {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send the ROM to the WebAssembly core module
|
||||||
this.core.postMessage({
|
this.core.postMessage({
|
||||||
command: "SetROM",
|
command: "SetROM",
|
||||||
rom : file,
|
rom : file,
|
||||||
sim : 0
|
sim : 0
|
||||||
});
|
}, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the current color theme
|
// Specify the current color theme
|
||||||
|
|
|
@ -12,16 +12,25 @@ globalThis.Debugger = class Debugger {
|
||||||
this.gui = app.gui;
|
this.gui = app.gui;
|
||||||
this.sim = sim;
|
this.sim = sim;
|
||||||
|
|
||||||
// Configure Memory window
|
// Memory window
|
||||||
this.memory = new MemoryWindow(this, {
|
this.memory = new MemoryWindow(this, {
|
||||||
title : "{sim}{memory._}",
|
title : "{sim}{memory._}",
|
||||||
center : true,
|
|
||||||
height : 300,
|
height : 300,
|
||||||
visible: false,
|
visible: false,
|
||||||
width : 400
|
width : 400
|
||||||
});
|
});
|
||||||
this.memory.addCloseListener(e=>this.memory.setVisible(false));
|
this.memory.addCloseListener(e=>this.memory.setVisible(false));
|
||||||
app.desktop.add(this.memory);
|
app.desktop.add(this.memory);
|
||||||
|
|
||||||
|
// CPU window
|
||||||
|
this.cpu = new CPUWindow(this, {
|
||||||
|
title : "{sim}{cpu._}",
|
||||||
|
height : 300,
|
||||||
|
visible: false,
|
||||||
|
width : 400
|
||||||
|
});
|
||||||
|
this.cpu.addCloseListener(e=>this.cpu.setVisible(false));
|
||||||
|
app.desktop.add(this.cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,12 +40,14 @@ globalThis.Debugger = class Debugger {
|
||||||
// Message received from emulation thread
|
// Message received from emulation thread
|
||||||
message(msg) {
|
message(msg) {
|
||||||
switch (msg.debug) {
|
switch (msg.debug) {
|
||||||
|
case "CPU" : this.cpu .message(msg); break;
|
||||||
case "Memory": this.memory.message(msg); break;
|
case "Memory": this.memory.message(msg); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload all output
|
// Reload all output
|
||||||
refresh() {
|
refresh() {
|
||||||
|
this.cpu .refresh();
|
||||||
this.memory.refresh();
|
this.memory.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,38 @@
|
||||||
// Message received
|
// Message received
|
||||||
onmessage(msg) {
|
onmessage(msg) {
|
||||||
switch (msg.command) {
|
switch (msg.command) {
|
||||||
|
case "GetRegisters": this.getRegisters(msg); break;
|
||||||
case "Init" : this.init (msg); break;
|
case "Init" : this.init (msg); break;
|
||||||
case "ReadBuffer" : this.readBuffer (msg); break;
|
case "ReadBuffer" : this.readBuffer (msg); break;
|
||||||
|
case "SetRegister" : this.setRegister (msg); break;
|
||||||
case "SetROM" : this.setROM (msg); break;
|
case "SetROM" : this.setROM (msg); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve the values of all the CPU registers
|
||||||
|
getRegisters(msg) {
|
||||||
|
msg.pc = this.core.GetProgramCounter(msg.sim, 0);
|
||||||
|
msg.pcFrom = this.core.GetProgramCounter(msg.sim, 1);
|
||||||
|
msg.pcTo = this.core.GetProgramCounter(msg.sim, 2);
|
||||||
|
msg.adtre = this.core.GetSystemRegister(msg.sim, 25);
|
||||||
|
msg.chcw = this.core.GetSystemRegister(msg.sim, 24);
|
||||||
|
msg.ecr = this.core.GetSystemRegister(msg.sim, 4);
|
||||||
|
msg.eipc = this.core.GetSystemRegister(msg.sim, 0);
|
||||||
|
msg.eipsw = this.core.GetSystemRegister(msg.sim, 1);
|
||||||
|
msg.fepc = this.core.GetSystemRegister(msg.sim, 2);
|
||||||
|
msg.fepsw = this.core.GetSystemRegister(msg.sim, 3);
|
||||||
|
msg.pir = this.core.GetSystemRegister(msg.sim, 6);
|
||||||
|
msg.psw = this.core.GetSystemRegister(msg.sim, 5);
|
||||||
|
msg.tkcw = this.core.GetSystemRegister(msg.sim, 7);
|
||||||
|
msg.sr29 = this.core.GetSystemRegister(msg.sim, 29);
|
||||||
|
msg.sr30 = this.core.GetSystemRegister(msg.sim, 30);
|
||||||
|
msg.sr31 = this.core.GetSystemRegister(msg.sim, 31);
|
||||||
|
msg.program = new Array(32);
|
||||||
|
for (let x = 0; x <= 31; x++)
|
||||||
|
msg.program[x] = this.core.GetProgramRegister(msg.sim, x);
|
||||||
|
postMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the WebAssembly core module
|
// Initialize the WebAssembly core module
|
||||||
async init(msg) {
|
async init(msg) {
|
||||||
|
|
||||||
|
@ -48,13 +74,29 @@
|
||||||
// Read multiple data units from the bus
|
// Read multiple data units from the bus
|
||||||
readBuffer(msg) {
|
readBuffer(msg) {
|
||||||
let buffer = this.malloc(Uint8Array, msg.size);
|
let buffer = this.malloc(Uint8Array, msg.size);
|
||||||
this.core.ReadBuffer(msg.sim, buffer.pointer, msg.address, msg.size);
|
this.core.ReadBuffer(msg.sim, buffer.pointer,
|
||||||
|
msg.address, msg.size, msg.debug ? 1 : 0);
|
||||||
msg.buffer = this.core.memory.buffer.slice(
|
msg.buffer = this.core.memory.buffer.slice(
|
||||||
buffer.pointer, buffer.pointer + msg.size);
|
buffer.pointer, buffer.pointer + msg.size);
|
||||||
this.free(buffer);
|
this.free(buffer);
|
||||||
postMessage(msg, msg.buffer);
|
postMessage(msg, msg.buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Specify a new value for a register
|
||||||
|
setRegister(msg) {
|
||||||
|
switch (msg.type) {
|
||||||
|
case "pc" : msg.value =
|
||||||
|
this.core.SetProgramCounter (msg.sim, msg.value);
|
||||||
|
break;
|
||||||
|
case "program": msg.value =
|
||||||
|
this.core.SetProgramRegister(msg.sim, msg.id, msg.value);
|
||||||
|
break;
|
||||||
|
case "system" : msg.value =
|
||||||
|
this.core.SetSystemRegister (msg.sim, msg.id, msg.value);
|
||||||
|
}
|
||||||
|
postMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
// Supply a ROM buffer
|
// Supply a ROM buffer
|
||||||
setROM(msg) {
|
setROM(msg) {
|
||||||
let rom = new Uint8Array(msg.rom);
|
let rom = new Uint8Array(msg.rom);
|
||||||
|
|
|
@ -60,6 +60,7 @@ globalThis.Bundle = class BundledFile {
|
||||||
name.endsWith(".txt" ) ? "text/plain;charset=UTF-8" :
|
name.endsWith(".txt" ) ? "text/plain;charset=UTF-8" :
|
||||||
name.endsWith(".vert" ) ? "text/plain;charset=UTF-8" :
|
name.endsWith(".vert" ) ? "text/plain;charset=UTF-8" :
|
||||||
name.endsWith(".wasm" ) ? "application/wasm" :
|
name.endsWith(".wasm" ) ? "application/wasm" :
|
||||||
|
name.endsWith(".woff2") ? "font/woff2" :
|
||||||
"application/octet-stream"
|
"application/octet-stream"
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -304,11 +305,18 @@ let run = async function() {
|
||||||
await Bundle.run("app/toolkit/Panel.js");
|
await Bundle.run("app/toolkit/Panel.js");
|
||||||
await Bundle.run("app/toolkit/Application.js");
|
await Bundle.run("app/toolkit/Application.js");
|
||||||
await Bundle.run("app/toolkit/Button.js");
|
await Bundle.run("app/toolkit/Button.js");
|
||||||
|
await Bundle.run("app/toolkit/ButtonGroup.js");
|
||||||
|
await Bundle.run("app/toolkit/CheckBox.js");
|
||||||
await Bundle.run("app/toolkit/Label.js");
|
await Bundle.run("app/toolkit/Label.js");
|
||||||
await Bundle.run("app/toolkit/MenuBar.js");
|
await Bundle.run("app/toolkit/MenuBar.js");
|
||||||
await Bundle.run("app/toolkit/MenuItem.js");
|
await Bundle.run("app/toolkit/MenuItem.js");
|
||||||
await Bundle.run("app/toolkit/Menu.js");
|
await Bundle.run("app/toolkit/Menu.js");
|
||||||
|
await Bundle.run("app/toolkit/RadioButton.js");
|
||||||
|
await Bundle.run("app/toolkit/Splitter.js");
|
||||||
|
await Bundle.run("app/toolkit/TextBox.js");
|
||||||
await Bundle.run("app/toolkit/Window.js");
|
await Bundle.run("app/toolkit/Window.js");
|
||||||
|
await Bundle.run("app/windows/CPUWindow.js");
|
||||||
|
await Bundle.run("app/windows/Register.js");
|
||||||
await Bundle.run("app/windows/MemoryWindow.js");
|
await Bundle.run("app/windows/MemoryWindow.js");
|
||||||
await App.create();
|
await App.create();
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,8 +9,21 @@
|
||||||
romNotVB : "The selected file is not a Virtual Boy ROM.",
|
romNotVB : "The selected file is not a Virtual Boy ROM.",
|
||||||
readFileError: "Unable to read the selected file."
|
readFileError: "Unable to read the selected file."
|
||||||
},
|
},
|
||||||
|
cpu: {
|
||||||
|
_ : "CPU",
|
||||||
|
disassembler: "Disassembler",
|
||||||
|
float_ : "Float",
|
||||||
|
hex : "Hex",
|
||||||
|
pcFrom : "From",
|
||||||
|
pcTo : "To",
|
||||||
|
mainSplit : "Disassembler and registers splitter",
|
||||||
|
regsSplit : "System registers and program registers splitter",
|
||||||
|
signed : "Signed",
|
||||||
|
unsigned : "Unsigned"
|
||||||
|
},
|
||||||
memory: {
|
memory: {
|
||||||
_: "Memory"
|
_ : "Memory",
|
||||||
|
hexEditor: "Hex viewer"
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
_ : "Main application menu",
|
_ : "Main application menu",
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,17 +1,23 @@
|
||||||
:root {
|
:root {
|
||||||
--control : #222222;
|
--close : #ee9999;
|
||||||
--control-focus : #444444;
|
--close-blur : #998080;
|
||||||
|
--close-blur-border: #999999;
|
||||||
|
--close-blur-text : #ffffff;
|
||||||
|
--close-border : #999999;
|
||||||
|
--close-text : #ffffff;
|
||||||
|
--control : #333333;
|
||||||
|
--control-disabled : #999999;
|
||||||
|
--control-focus : #555555;
|
||||||
--control-shadow : #999999;
|
--control-shadow : #999999;
|
||||||
--control-text : #cccccc;
|
--control-text : #cccccc;
|
||||||
--desktop : #111111;
|
--desktop : #111111;
|
||||||
--window-blur : #555555;
|
--splitter-focus : #0099ff99;
|
||||||
--window-blur-text : #cccccc;
|
--title : #007ACC;
|
||||||
--window-close-blur : #998080;
|
--title-blur : #555555;
|
||||||
--window-close-blur-border : #999999;
|
--title-blur-text : #cccccc;
|
||||||
--window-close-blur-text : #ffffff;
|
--title-text : #ffffff;
|
||||||
--window-close-focus : #ee9999;
|
--window : #222222;
|
||||||
--window-close-focus-border: #999999;
|
--window-border : #cccccc;
|
||||||
--window-close-focus-text : #ffffff;
|
--window-disabled : #888888;
|
||||||
--window-focus : #007ACC;
|
--window-text : #cccccc;
|
||||||
--window-focus-text : #ffffff;
|
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--font-dialog: "Roboto-Regular", sans-serif;
|
--font-dialog: "Roboto-Regular", sans-serif;
|
||||||
--font-hex : "Inconsolata_SemiExpanded-Regular", monospace;
|
--font-hex : "Inconsolata_SemiExpanded-Medium", monospace;
|
||||||
--font-size : 12px;
|
--font-size : 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,10 +13,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin : 0;
|
background: var(--window);
|
||||||
overflow : hidden;
|
overflow : hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
background: transparent;
|
||||||
|
border : none;
|
||||||
|
margin : 0;
|
||||||
|
padding : 0;
|
||||||
|
}
|
||||||
|
|
||||||
*:focus {
|
*:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
@ -48,6 +55,117 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/********************************* Checkbox **********************************/
|
||||||
|
|
||||||
|
[role="checkbox"] {
|
||||||
|
column-gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="checkbox"] [name="check"] {
|
||||||
|
background : var(--window);
|
||||||
|
border : 1px solid var(--control-shadow);
|
||||||
|
color : var(--window-text);
|
||||||
|
font-size : calc(var(--font-size) - 1px);
|
||||||
|
line-height: calc(var(--font-size) - 2px);
|
||||||
|
height : calc(var(--font-size) - 2px);
|
||||||
|
overflow : hidden;
|
||||||
|
position : relative;
|
||||||
|
width : calc(var(--font-size) - 2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="checkbox"] [name="check"]:after {
|
||||||
|
bottom : 0;
|
||||||
|
left : 0;
|
||||||
|
line-height: 100%;
|
||||||
|
position : absolute;
|
||||||
|
right : 0;
|
||||||
|
text-align : center;
|
||||||
|
top : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="checkbox"][active] [name="check"]:after,
|
||||||
|
[role="checkbox"][aria-checked="true"] [name="check"]:after {
|
||||||
|
content: "\2713";
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="checkbox"][active] [name="check"]:after {
|
||||||
|
color: var(--window);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="checkbox"]:focus [name="check"] {
|
||||||
|
background: var(--control-focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="checkbox"][aria-disabled="true"] [name="check"] {
|
||||||
|
border-color: var(--window-disabled);
|
||||||
|
color : var(--window-disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="checkbox"] [name="label"] {
|
||||||
|
color: var(--control-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="checkbox"][aria-disabled="true"] [name="label"] {
|
||||||
|
color: var(--control-disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/********************************* Checkbox **********************************/
|
||||||
|
|
||||||
|
[role="radio"] {
|
||||||
|
column-gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="radio"] [name="check"] {
|
||||||
|
background : var(--window);
|
||||||
|
border : 1px solid var(--control-shadow);
|
||||||
|
border-radius: 50%;
|
||||||
|
color : var(--window-text);
|
||||||
|
height : calc(var(--font-size) - 2px);
|
||||||
|
overflow : hidden;
|
||||||
|
position : relative;
|
||||||
|
width : calc(var(--font-size) - 2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="radio"] [name="check"]:after {
|
||||||
|
background : currentColor;
|
||||||
|
border-radius: 50%;
|
||||||
|
bottom : 0;
|
||||||
|
left : 0;
|
||||||
|
position : absolute;
|
||||||
|
margin : 30%;
|
||||||
|
right : 0;
|
||||||
|
top : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="radio"][active] [name="check"]:after,
|
||||||
|
[role="radio"][aria-checked="true"] [name="check"]:after {
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="radio"][active] [name="check"]:after {
|
||||||
|
color: var(--window);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="radio"]:focus [name="check"] {
|
||||||
|
background: var(--control-focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="radio"][aria-disabled="true"] [name="check"] {
|
||||||
|
border-color: var(--window-disabled);
|
||||||
|
color : var(--window-disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="radio"] [name="label"] {
|
||||||
|
color: var(--control-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="radio"][aria-disabled="true"] [name="label"] {
|
||||||
|
color: var(--control-disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/********************************** MenuBar **********************************/
|
/********************************** MenuBar **********************************/
|
||||||
|
|
||||||
|
@ -94,132 +212,265 @@ body {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/********************************* Splitter **********************************/
|
||||||
|
|
||||||
|
[role="separator"][tabindex] {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="separator"][tabindex]:focus {
|
||||||
|
background: var(--splitter-focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/********************************** TextBox **********************************/
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
background : var(--window);
|
||||||
|
border : 1px solid var(--control-shadow);
|
||||||
|
color : var(--window-text);
|
||||||
|
font-size : var(--font-size);
|
||||||
|
line-height: var(--font-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/********************************** Window ***********************************/
|
/********************************** Window ***********************************/
|
||||||
|
|
||||||
[role="dialog"] {
|
[role="dialog"] {
|
||||||
/*background: #cc0000;*/
|
padding: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[role="dialog"] [name="body"] {
|
[role="dialog"] [name="body"] {
|
||||||
background: var(--control);
|
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 0 0 1px var(--control),
|
0 0 0 2px var(--window-border),
|
||||||
0 0 0 2px var(--control-text),
|
1px 1px 0 2px var(--window-border)
|
||||||
1px 1px 0 2px var(--control-text)
|
|
||||||
;
|
;
|
||||||
row-gap : 3px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[role="dialog"] [name="title-bar"] {
|
[role="dialog"] [name="title-bar"] {
|
||||||
min-height: calc(1em + 5px);
|
align-items: center;
|
||||||
}
|
background : var(--title);
|
||||||
|
column-gap : 1px;
|
||||||
[role="dialog"][focus="true"] [name="title-bar"] {
|
padding : 0 0 1px 0;
|
||||||
|
margin : 0 0 2px 0;
|
||||||
box-shadow :
|
box-shadow :
|
||||||
0 0 0 1px var(--window-focus),
|
-0.5px -0.5px 0 0.5px var(--title ),
|
||||||
0 1px 0 1px var(--control-shadow)
|
0.5px -0.5px 0 0.5px var(--title ),
|
||||||
|
0 0 0 1px var(--control-shadow)
|
||||||
;
|
;
|
||||||
background : var(--window-focus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[role="dialog"][focus="false"] [name="title-bar"] {
|
[role="dialog"][focus="false"] [name="title-bar"] {
|
||||||
|
background: var(--title-blur);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 0 0 1px var(--window-blur),
|
-0.5px -0.5px 0 0.5px var(--title-blur ),
|
||||||
0 1px 0 1px var(--control-shadow)
|
0.5px -0.5px 0 0.5px var(--title-blur ),
|
||||||
|
0 0 0 1px var(--control-shadow)
|
||||||
;
|
;
|
||||||
background: var(--window-blur);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[role="dialog"] [name="title-icon"],
|
[role="dialog"] [name="title-bar"] [name="icon"] {
|
||||||
[role="dialog"] [name="title-close-box"] {
|
height: 15px;
|
||||||
align-self : stretch;
|
width : 15px;
|
||||||
min-width : calc(1em + 5px);
|
|
||||||
width : calc(1em + 5px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[role="dialog"] [name="title"] {
|
[role="dialog"] [name="title-bar"] [name="title"] {
|
||||||
color : var(--window-focus-text);
|
color : var(--title-text);
|
||||||
|
font-size : var(--font-size);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
line-height : calc(1em + 1px);
|
|
||||||
overflow : hidden;
|
|
||||||
padding : 2px;
|
|
||||||
text-align : center;
|
text-align : center;
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space : nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[role="dialog"][focus="false"] [name="title"] {
|
[role="dialog"][focus="false"] [name="title-bar"] [name="title"] {
|
||||||
color: var(--window-blur-text);
|
color: var(--title-blur-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[role="dialog"] [name="title-close-box"] {
|
[role="dialog"] [name="title-bar"] [name="close"] {
|
||||||
align-items : center;
|
background : var(--close);
|
||||||
display : flex;
|
border : 1px solid var(--close-border);
|
||||||
justify-content: center;
|
box-sizing : border-box;
|
||||||
}
|
box-shadow : none;
|
||||||
|
color : var(--close-text);
|
||||||
[role="dialog"] [name="title-close"] {
|
|
||||||
align-items : center;
|
|
||||||
background : var(--window-close-focus);
|
|
||||||
box-shadow : 0 0 0 1px var(--window-close-focus-border);
|
|
||||||
color : var(--window-close-focus-text);
|
|
||||||
display : flex;
|
|
||||||
height : 13px;
|
|
||||||
justify-content: center;
|
|
||||||
overflow : hidden;
|
|
||||||
padding : 0;
|
|
||||||
width : 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[role="dialog"][focus="false"] [name="title-close"] {
|
|
||||||
background: var(--window-close-blur);
|
|
||||||
box-shadow: 0 0 0 1px var(--window-close-blur-border);
|
|
||||||
color : var(--window-close-blur-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
[role="dialog"] [name="title-close"][active] {
|
|
||||||
box-shadow: 0 0 0 1px var(--window-close-focus-border);
|
|
||||||
margin : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[role="dialog"] [name="title-close"]:after {
|
|
||||||
content : '\00d7';
|
|
||||||
font-size : 13px;
|
font-size : 13px;
|
||||||
|
font-weight: bold;
|
||||||
|
height : 15px;
|
||||||
line-height: 13px;
|
line-height: 13px;
|
||||||
|
margin : 0;
|
||||||
|
padding : 0;
|
||||||
|
position : relative;
|
||||||
|
text-align : center;
|
||||||
|
width : 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[role="dialog"] [name="title-close"][active]:after {
|
[role="dialog"][focus="false"] [name="title-bar"] [name="close"] {
|
||||||
margin: 1px -1px -1px 1px;
|
background : var(--close-blur);
|
||||||
|
border-color: var(--close-blur-border);
|
||||||
|
color : var(--close-blur-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"] [name="title-bar"] [name="close"]:after {
|
||||||
|
content : "\00d7";
|
||||||
|
position: absolute;
|
||||||
|
inset : 0 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"] [name="title-bar"] [name="close"][active]:after {
|
||||||
|
inset: 1px -1px -1px 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[role="dialog"] [name="client"] {
|
[role="dialog"] [name="client"] {
|
||||||
background: var(--control);
|
background: var(--control);
|
||||||
|
box-shadow: 0 0 0 1px var(--control);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/******************************* Memory Window *******************************/
|
/******************************* Memory Window *******************************/
|
||||||
|
|
||||||
[window="memory"] [name="client"] {
|
[role="dialog"][window="memory"] [name="wrap-hex"] {
|
||||||
align-items: start;
|
background : var(--window);
|
||||||
|
box-shadow : 0 0 0 1px var(--control-shadow);
|
||||||
|
font-family: var(--font-hex);
|
||||||
|
margin : 1px;
|
||||||
|
padding : 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="memory"] [name="hex"] [role="row"] {
|
||||||
column-gap: calc(var(--font-size) / 2);
|
column-gap: calc(var(--font-size) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
[window="memory"] [name="client"] > *:not(:nth-child(1n+1)) {
|
[role="dialog"][window="memory"] [name="hex"] [role="row"] > *:nth-child( 2),
|
||||||
text-align: center;
|
[role="dialog"][window="memory"] [name="hex"] [role="row"] > *:nth-child(10) {
|
||||||
}
|
|
||||||
|
|
||||||
[window="memory"] [name="client"] > *:nth-child(17n+2),
|
|
||||||
[window="memory"] [name="client"] > *:nth-child(17n+10) {
|
|
||||||
margin-left: calc(var(--font-size) / 2);
|
margin-left: calc(var(--font-size) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
[window="memory"] [name="address"],
|
|
||||||
[window="memory"] [name="byte"] {
|
|
||||||
font-family: var(--font-hex);
|
|
||||||
line-height: 1em;
|
/******************************** CPU Window *********************************/
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="wrap-disassembler"],
|
||||||
|
[role="dialog"][window="cpu"] [name="wrap-system-registers"],
|
||||||
|
[role="dialog"][window="cpu"] [name="wrap-program-registers"] {
|
||||||
|
background: var(--window);
|
||||||
|
color : var(--window-text);
|
||||||
|
box-shadow: 0 0 0 1px var(--control-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
[window="memory"] [name="address"] {
|
[role="dialog"][window="cpu"] [name="wrap-main"] {
|
||||||
align-self: start;
|
box-shadow : 0 0 0 1px var(--control-shadow);
|
||||||
|
margin-left: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="wrap-disassembler"] {
|
||||||
|
margin: 1px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="split-main"] {
|
||||||
|
margin-right: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="wrap-registers"] {
|
||||||
|
box-shadow:
|
||||||
|
0 -1px 0 0 var(--control-shadow),
|
||||||
|
0 1px 0 0 var(--control-shadow)
|
||||||
|
;
|
||||||
|
margin: 1px 0;
|
||||||
|
width : 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="wrap-system-registers"] {
|
||||||
|
height: 143px;
|
||||||
|
margin: 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="wrap-program-registers"] {
|
||||||
|
margin: 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="disassembler"],
|
||||||
|
[role="dialog"][window="cpu"] [name="system-registers"],
|
||||||
|
[role="dialog"][window="cpu"] [name="program-registers"] {
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="expand"] {
|
||||||
|
column-gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="expand"] [name="check"] {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="expand"][aria-checked="false"]
|
||||||
|
[name="check"]:after {
|
||||||
|
content: "+";
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="expand"][aria-checked="true"]
|
||||||
|
[name="check"]:after {
|
||||||
|
content: "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="wrap-registers"] input[type="text"] {
|
||||||
|
border : none;
|
||||||
|
font-family : var(--font-hex);
|
||||||
|
margin-right: 1px;
|
||||||
|
text-align : right;
|
||||||
|
width : 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="wrap-registers"]
|
||||||
|
[format="float"] [name="value"],
|
||||||
|
[role="dialog"][window="cpu"] [name="wrap-registers"]
|
||||||
|
[format="signed"] [name="value"],
|
||||||
|
[role="dialog"][window="cpu"] [name="wrap-registers"] [format="unsigned"]
|
||||||
|
[name="value"] {
|
||||||
|
font-family: var(--dialog-font);
|
||||||
|
width : 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="wrap-registers"] [name="expansion"] {
|
||||||
|
margin-left: calc(var(--font-size) * 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [register="psw" ],
|
||||||
|
[role="dialog"][window="cpu"] [register="tkcw"] {
|
||||||
|
column-gap: var(--font-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [register="ecr" ],
|
||||||
|
[role="dialog"][window="cpu"] [register="pir" ],
|
||||||
|
[role="dialog"][window="cpu"] [register="psw" ] [name="I" ],
|
||||||
|
[role="dialog"][window="cpu"] [register="tkcw"] [name="RD"] {
|
||||||
|
column-gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [register="psw" ] [name="I" ] input[type="text"],
|
||||||
|
[role="dialog"][window="cpu"] [register="tkcw"] [name="RD"] input[type="text"]{
|
||||||
|
font-family: var(--font-dialog);
|
||||||
|
text-align : left;
|
||||||
|
width : 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [register="ecr"] input[type="text"],
|
||||||
|
[role="dialog"][window="cpu"] [register="pir"] input[type="text"] {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="expand"][aria-disabled="true"]
|
||||||
|
[name="check"]:after {
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [aria-disabled="true"] * {
|
||||||
|
color: var(--window-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [aria-disabled="true"] [name="check"] {
|
||||||
|
border-color: var(--control-shadow);
|
||||||
|
color : var(--window-text);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,23 @@
|
||||||
:root {
|
:root {
|
||||||
|
--close : #ee9999;
|
||||||
|
--close-blur : #d4c4c4;
|
||||||
|
--close-blur-border: #999999;
|
||||||
|
--close-blur-text : #ffffff;
|
||||||
|
--close-border : #999999;
|
||||||
|
--close-text : #ffffff;
|
||||||
--control : #eeeeee;
|
--control : #eeeeee;
|
||||||
|
--control-disabled : #888888;
|
||||||
--control-focus : #cccccc;
|
--control-focus : #cccccc;
|
||||||
--control-shadow : #999999;
|
--control-shadow : #999999;
|
||||||
--control-text : #000000;
|
--control-text : #000000;
|
||||||
--desktop : #cccccc;
|
--desktop : #cccccc;
|
||||||
--window-blur : #cccccc;
|
--splitter-focus : #0099ff99;
|
||||||
--window-blur-text : #444444;
|
--title : #80ccff;
|
||||||
--window-close-blur : #d4c4c4;
|
--title-blur : #cccccc;
|
||||||
--window-close-blur-border : #999999;
|
--title-blur-text : #444444;
|
||||||
--window-close-blur-text : #ffffff;
|
--title-text : #000000;
|
||||||
--window-close-focus : #ee9999;
|
--window : #ffffff;
|
||||||
--window-close-focus-border: #999999;
|
--window-border : #000000;
|
||||||
--window-close-focus-text : #ffffff;
|
--window-disabled : #aaaaaa;
|
||||||
--window-focus : #80ccff;
|
--window-text : #000000;
|
||||||
--window-focus-text : #000000;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,27 @@
|
||||||
:root {
|
:root {
|
||||||
|
--close : #aa0000;
|
||||||
|
--close-blur : #550000;
|
||||||
|
--close-blur-border: #aa0000;
|
||||||
|
--close-blur-text : #aa0000;
|
||||||
|
--close-border : #ff0000;
|
||||||
|
--close-text : #ff0000;
|
||||||
--control : #000000;
|
--control : #000000;
|
||||||
|
--control-disabled : #aa0000;
|
||||||
--control-focus : #550000;
|
--control-focus : #550000;
|
||||||
--control-shadow : #aa0000;
|
--control-shadow : #aa0000;
|
||||||
--control-text : #ff0000;
|
--control-text : #ff0000;
|
||||||
--desktop : #000000;
|
--desktop : #000000;
|
||||||
--window-blur : #000000;
|
--splitter-focus : #ff000099;
|
||||||
--window-blur-text : #aa0000;
|
--title : #550000;
|
||||||
--window-close-blur : #550000;
|
--title-blur : #000000;
|
||||||
--window-close-blur-border : #aa0000;
|
--title-blur-text : #aa0000;
|
||||||
--window-close-blur-text : #aa0000;
|
--title-text : #ff0000;
|
||||||
--window-close-focus : #aa0000;
|
--window : #000000;
|
||||||
--window-close-focus-border: #ff0000;
|
--window-border : #ff0000;
|
||||||
--window-close-focus-text : #ff0000;
|
--window-disabled : #aa0000;
|
||||||
--window-focus : #550000;
|
--window-text : #ff0000;
|
||||||
--window-focus-text : #ff0000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[filter="true"] {
|
[filter] {
|
||||||
filter: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxmaWx0ZXIgaWQ9InYiPjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VHcmFwaGljIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMSAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMSAwIiAvPjwvZmlsdGVyPjwvc3ZnPg==#v");
|
filter: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxmaWx0ZXIgaWQ9InYiPjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VHcmFwaGljIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMSAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMSAwIiAvPjwvZmlsdGVyPjwvc3ZnPg==#v");
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,7 +200,6 @@ Toolkit.Application = class Application extends Toolkit.Panel {
|
||||||
|
|
||||||
// A pointer or mouse down even has propagated
|
// A pointer or mouse down even has propagated
|
||||||
onpropagation(e) {
|
onpropagation(e) {
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
for (let listener of this.propagationListeners)
|
for (let listener of this.propagationListeners)
|
||||||
listener(e, this);
|
listener(e, this);
|
||||||
|
|
|
@ -45,43 +45,51 @@ Toolkit.Button = class Button extends Toolkit.Component {
|
||||||
this.clickListeners.push(listener);
|
this.clickListeners.push(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The button was activated
|
||||||
|
click(e) {
|
||||||
|
if (!this.enabled)
|
||||||
|
return;
|
||||||
|
for (let listener of this.clickListeners)
|
||||||
|
listener(e);
|
||||||
|
}
|
||||||
|
|
||||||
// Request focus on the appropriate element
|
// Request focus on the appropriate element
|
||||||
focus() {
|
focus() {
|
||||||
this.element.focus();
|
this.element.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the button's accessible name
|
// Retrieve the component's accessible name
|
||||||
getName() {
|
getName() {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the button's display text
|
// Retrieve the component's display text
|
||||||
getText() {
|
getText() {
|
||||||
return this.text;
|
return this.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the button's tool tip text
|
// Retrieve the component's tool tip text
|
||||||
getToolTip() {
|
getToolTip() {
|
||||||
return this.toolTip;
|
return this.toolTip;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine whether the button is enabled
|
// Determine whether the component is enabled
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
return this.enabled;
|
return this.enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine whether the button is focusable
|
// Determine whether the component is focusable
|
||||||
isFocusable() {
|
isFocusable() {
|
||||||
return this.focusable;
|
return this.focusable;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify whether the button is enabled
|
// Specify whether the component is enabled
|
||||||
setEnabled(enabled) {
|
setEnabled(enabled) {
|
||||||
this.enabled = enabled = !!enabled;
|
this.enabled = enabled = !!enabled;
|
||||||
this.element.setAttribute("aria-disabled", !enabled);
|
this.element.setAttribute("aria-disabled", !enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify whether the button can receive focus
|
// Specify whether the component can receive focus
|
||||||
setFocusable(focusable) {
|
setFocusable(focusable) {
|
||||||
this.focusable = focusable = !!focusable;
|
this.focusable = focusable = !!focusable;
|
||||||
if (focusable)
|
if (focusable)
|
||||||
|
@ -89,19 +97,19 @@ Toolkit.Button = class Button extends Toolkit.Component {
|
||||||
else this.element.removeAttribute("tabindex");
|
else this.element.removeAttribute("tabindex");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the button's accessible name
|
// Specify the component's accessible name
|
||||||
setName(name) {
|
setName(name) {
|
||||||
this.name = name || "";
|
this.name = name || "";
|
||||||
this.localize();
|
this.localize();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the button's display text
|
// Specify the component's display text
|
||||||
setText(text) {
|
setText(text) {
|
||||||
this.text = text || "";
|
this.text = text || "";
|
||||||
this.localize();
|
this.localize();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the button's tool tip text
|
// Specify the component's tool tip text
|
||||||
setToolTip(toolTip) {
|
setToolTip(toolTip) {
|
||||||
this.toolTip = toolTip || "";
|
this.toolTip = toolTip || "";
|
||||||
this.localize();
|
this.localize();
|
||||||
|
@ -133,14 +141,6 @@ Toolkit.Button = class Button extends Toolkit.Component {
|
||||||
|
|
||||||
///////////////////////////// Private Methods /////////////////////////////
|
///////////////////////////// Private Methods /////////////////////////////
|
||||||
|
|
||||||
// The button was activated
|
|
||||||
activate(e) {
|
|
||||||
if (!this.enabled)
|
|
||||||
return;
|
|
||||||
for (let listener of this.clickListeners)
|
|
||||||
listener(e, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key down event handler
|
// Key down event handler
|
||||||
onkeydown(e) {
|
onkeydown(e) {
|
||||||
|
|
||||||
|
@ -152,7 +152,7 @@ Toolkit.Button = class Button extends Toolkit.Component {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case " ":
|
case " ":
|
||||||
case "Enter":
|
case "Enter":
|
||||||
this.activate(e);
|
this.click(e);
|
||||||
break;
|
break;
|
||||||
default: return;
|
default: return;
|
||||||
}
|
}
|
||||||
|
@ -166,7 +166,6 @@ Toolkit.Button = class Button extends Toolkit.Component {
|
||||||
onpointerdown(e) {
|
onpointerdown(e) {
|
||||||
|
|
||||||
// Configure event
|
// Configure event
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// Configure focus
|
// Configure focus
|
||||||
|
@ -221,11 +220,11 @@ Toolkit.Button = class Button extends Toolkit.Component {
|
||||||
// Configure element
|
// Configure element
|
||||||
this.element.releasePointerCapture(e.pointerId);
|
this.element.releasePointerCapture(e.pointerId);
|
||||||
|
|
||||||
// Activate the menu item if it is active
|
// Activate the component if it is active
|
||||||
if (!this.element.hasAttribute("active"))
|
if (!this.element.hasAttribute("active"))
|
||||||
return;
|
return;
|
||||||
this.element.removeAttribute("active");
|
this.element.removeAttribute("active");
|
||||||
this.activate(e);
|
this.click(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Grouping manager for mutually-exclusive controls
|
||||||
|
Toolkit.ButtonGroup = class ButtonGroup {
|
||||||
|
|
||||||
|
// Object constructor
|
||||||
|
constructor() {
|
||||||
|
this.components = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Public Methods //////////////////////////////
|
||||||
|
|
||||||
|
// Add a component to the group
|
||||||
|
add(component) {
|
||||||
|
if (this.components.indexOf(component) != -1)
|
||||||
|
return component;
|
||||||
|
this.components.push(component);
|
||||||
|
if ("setGroup" in component)
|
||||||
|
component.setGroup(this);
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select only one button in the group
|
||||||
|
setChecked(component) {
|
||||||
|
for (let comp of this.components) {
|
||||||
|
if ("setChecked" in comp)
|
||||||
|
comp.setChecked(comp == component, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a component from the group
|
||||||
|
remove(component) {
|
||||||
|
let index = this.components.indexOf(component);
|
||||||
|
if (index == -1)
|
||||||
|
return false;
|
||||||
|
this.components.splice(index, 1);
|
||||||
|
if ("setGroup" in component)
|
||||||
|
component.setGroup(null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
|
@ -0,0 +1,190 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// On/off toggle checkbox
|
||||||
|
Toolkit.CheckBox = class CheckBox extends Toolkit.Panel {
|
||||||
|
|
||||||
|
// Object constructor
|
||||||
|
constructor(application, options) {
|
||||||
|
super(application, options);
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.changeListeners = [];
|
||||||
|
this.checked = false;
|
||||||
|
this.enabled = "enabled" in options ? !!options.enabled : true;
|
||||||
|
this.text = options.text || "";
|
||||||
|
|
||||||
|
// Configure element
|
||||||
|
this.setLayout("grid", {
|
||||||
|
columns : "max-content max-content"
|
||||||
|
});
|
||||||
|
this.setDisplay("inline-grid");
|
||||||
|
this.setHollow(false);
|
||||||
|
this.setOverflow("visible", "visible");
|
||||||
|
this.element.setAttribute("tabindex", "0");
|
||||||
|
this.element.setAttribute("role", "checkbox");
|
||||||
|
this.element.setAttribute("aria-checked", "false");
|
||||||
|
this.element.style.alignItems = "center";
|
||||||
|
this.element.addEventListener("keydown" , e=>this.onkeydown (e));
|
||||||
|
this.element.addEventListener("pointerdown", e=>this.onpointerdown(e));
|
||||||
|
this.element.addEventListener("pointermove", e=>this.onpointermove(e));
|
||||||
|
this.element.addEventListener("pointerup" , e=>this.onpointerup (e));
|
||||||
|
|
||||||
|
// Configure check box
|
||||||
|
this.check = this.add(this.newLabel());
|
||||||
|
this.check.element.setAttribute("name", "check");
|
||||||
|
this.check.element.setAttribute("aria-hidden", "true");
|
||||||
|
|
||||||
|
// Configure label
|
||||||
|
this.label = this.add(this.newLabel({ localized: true }));
|
||||||
|
this.label.element.setAttribute("name", "label");
|
||||||
|
this.element.setAttribute("aria-labelledby", this.label.id);
|
||||||
|
|
||||||
|
// Configure properties
|
||||||
|
this.setChecked(options.checked);
|
||||||
|
this.setEnabled(this.enabled);
|
||||||
|
this.setText (this.text );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Public Methods //////////////////////////////
|
||||||
|
|
||||||
|
// Add a callback for change events
|
||||||
|
addChangeListener(listener) {
|
||||||
|
if (this.changeListeners.indexOf(listener) == -1)
|
||||||
|
this.changeListeners.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request focus on the appropriate element
|
||||||
|
focus() {
|
||||||
|
this.element.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine whether the component is checked
|
||||||
|
isChecked() {
|
||||||
|
return this.checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine whether the component is enabled
|
||||||
|
isEnabled() {
|
||||||
|
return this.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify whether the component is checked
|
||||||
|
setChecked(checked, e) {
|
||||||
|
checked = !!checked;
|
||||||
|
if (checked == this.checked)
|
||||||
|
return;
|
||||||
|
this.checked = checked;
|
||||||
|
this.element.setAttribute("aria-checked", checked);
|
||||||
|
if (e === undefined)
|
||||||
|
return;
|
||||||
|
for (let listener of this.changeListeners)
|
||||||
|
listener(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify whether the component is enabled
|
||||||
|
setEnabled(enabled) {
|
||||||
|
this.enabled = enabled = !!enabled;
|
||||||
|
this.element.setAttribute("aria-disabled", !enabled);
|
||||||
|
if (enabled)
|
||||||
|
this.element.setAttribute("tabindex", "0");
|
||||||
|
else this.element.removeAttribute("tabindex");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify the component's display text
|
||||||
|
setText(text) {
|
||||||
|
this.text = text = text || "";
|
||||||
|
this.label.setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Private Methods /////////////////////////////
|
||||||
|
|
||||||
|
// Key down event handler
|
||||||
|
onkeydown(e) {
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (!this.enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Ignore the key
|
||||||
|
if (e.key != " ")
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Configure event
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Toggle the checked state
|
||||||
|
this.setChecked(!this.checked, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer down event handler
|
||||||
|
onpointerdown(e) {
|
||||||
|
|
||||||
|
// Configure event
|
||||||
|
//e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Configure focus
|
||||||
|
if (this.enabled)
|
||||||
|
this.focus();
|
||||||
|
else return;
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (e.button != 0 || this.element.hasPointerCapture(e.captureId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Configure element
|
||||||
|
this.element.setPointerCapture(e.pointerId);
|
||||||
|
this.element.setAttribute("active", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer move event handler
|
||||||
|
onpointermove(e) {
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (!this.element.hasPointerCapture(e))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Configure event
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Working variables
|
||||||
|
let bounds = this.getBounds();
|
||||||
|
let active =
|
||||||
|
e.x >= bounds.x && e.x < bounds.x + bounds.width &&
|
||||||
|
e.y >= bounds.y && e.y < bounds.y + bounds.height
|
||||||
|
;
|
||||||
|
|
||||||
|
// Configure element
|
||||||
|
if (active)
|
||||||
|
this.element.setAttribute("active", "");
|
||||||
|
else this.element.removeAttribute("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer up event handler
|
||||||
|
onpointerup(e) {
|
||||||
|
|
||||||
|
// Configure event
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (!this.element.hasPointerCapture(e.pointerId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Configure element
|
||||||
|
this.element.releasePointerCapture(e.pointerId);
|
||||||
|
|
||||||
|
// Activate the component if it is active
|
||||||
|
if (!this.element.hasAttribute("active"))
|
||||||
|
return;
|
||||||
|
this.element.removeAttribute("active");
|
||||||
|
this.setChecked(!this.checked, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
|
@ -21,6 +21,10 @@ Toolkit.Component = class Component {
|
||||||
|
|
||||||
// Configure component
|
// Configure component
|
||||||
this.element.component = this;
|
this.element.component = this;
|
||||||
|
this.setSize(
|
||||||
|
"width" in options ? options.width : null,
|
||||||
|
"height" in options ? options.height : null
|
||||||
|
);
|
||||||
this.setVisible(this.visible);
|
this.setVisible(this.visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,11 +62,11 @@ Toolkit.Component = class Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the location and size of the component
|
// Specify the location and size of the component
|
||||||
setBounds(left, top, width, height) {
|
setBounds(left, top, width, height, minimum) {
|
||||||
this.setLeft (left );
|
this.setLeft (left );
|
||||||
this.setTop (top );
|
this.setTop (top );
|
||||||
this.setWidth (width );
|
this.setWidth (width , minimum);
|
||||||
this.setHeight(height);
|
this.setHeight(height, minimum);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the display CSS property of the visible element
|
// Specify the display CSS property of the visible element
|
||||||
|
@ -71,12 +75,19 @@ Toolkit.Component = class Component {
|
||||||
this.setVisible(this.visible);
|
this.setVisible(this.visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the height of the element
|
// Specify the height of the component
|
||||||
setHeight(height) {
|
setHeight(height, minimum) {
|
||||||
if (height === null)
|
if (height === null) {
|
||||||
|
this.element.style.removeProperty("min-height");
|
||||||
this.element.style.removeProperty("height");
|
this.element.style.removeProperty("height");
|
||||||
else this.element.style.height =
|
} else {
|
||||||
typeof height == "number" ? height + "px" : height
|
height = typeof height == "number" ?
|
||||||
|
Math.max(0, height) + "px" : height;
|
||||||
|
this.element.style.height = height;
|
||||||
|
if (minimum)
|
||||||
|
this.element.style.minHeight = height;
|
||||||
|
else this.element.style.removeProperty("min-height");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the horizontal position of the component
|
// Specify the horizontal position of the component
|
||||||
|
@ -100,9 +111,9 @@ Toolkit.Component = class Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify both the width and the height of the component
|
// Specify both the width and the height of the component
|
||||||
setSize(width, height) {
|
setSize(width, height, minimum) {
|
||||||
this.setHeight(height);
|
this.setHeight(height, minimum);
|
||||||
this.setWidth (width );
|
this.setWidth (width , minimum);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the vertical position of the component
|
// Specify the vertical position of the component
|
||||||
|
@ -123,12 +134,19 @@ Toolkit.Component = class Component {
|
||||||
} else this.element.style.display = "none";
|
} else this.element.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the width of the element
|
// Specify the width of the component
|
||||||
setWidth(width) {
|
setWidth(width, minimum) {
|
||||||
if (width === null)
|
if (width === null) {
|
||||||
|
this.element.style.removeProperty("min-width");
|
||||||
this.element.style.removeProperty("width");
|
this.element.style.removeProperty("width");
|
||||||
else this.element.style.width =
|
} else {
|
||||||
typeof width == "number" ? width + "px" : width ;
|
width = typeof width == "number" ?
|
||||||
|
Math.max(0, width) + "px" : width;
|
||||||
|
this.element.style.width = width;
|
||||||
|
if (minimum)
|
||||||
|
this.element.style.minWidth = width;
|
||||||
|
else this.element.style.removeProperty("min-width");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,11 @@ Toolkit.Label = class Label extends Toolkit.Component {
|
||||||
|
|
||||||
// Object constructor
|
// Object constructor
|
||||||
constructor(application, options) {
|
constructor(application, options) {
|
||||||
super(application, "div", options);
|
super(application, options&&options.label ? "label" : "div", options);
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
// Configure instance fields
|
// Configure instance fields
|
||||||
|
this.focusable = "focusable" in options ? !!options.focusable : false;
|
||||||
this.localized = "localized" in options ? !!options.localized : false;
|
this.localized = "localized" in options ? !!options.localized : false;
|
||||||
this.text = options.text || "";
|
this.text = options.text || "";
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ Toolkit.Label = class Label extends Toolkit.Component {
|
||||||
this.element.style.userSelect = "none";
|
this.element.style.userSelect = "none";
|
||||||
|
|
||||||
// Configure properties
|
// Configure properties
|
||||||
|
this.setFocusable(this.focusable);
|
||||||
this.setText (this.text);
|
this.setText (this.text);
|
||||||
if (this.localized)
|
if (this.localized)
|
||||||
this.application.addComponent(this);
|
this.application.addComponent(this);
|
||||||
|
@ -26,17 +28,43 @@ Toolkit.Label = class Label extends Toolkit.Component {
|
||||||
|
|
||||||
///////////////////////////// Public Methods //////////////////////////////
|
///////////////////////////// Public Methods //////////////////////////////
|
||||||
|
|
||||||
|
// Request focus on the appropriate element
|
||||||
|
focus() {
|
||||||
|
if (this.focusable)
|
||||||
|
this.element.focus();
|
||||||
|
}
|
||||||
|
|
||||||
// Retrieve the label's display text
|
// Retrieve the label's display text
|
||||||
getText() {
|
getText() {
|
||||||
return this.text;
|
return this.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine whether the component is focusable
|
||||||
|
isFocusable() {
|
||||||
|
return this.focusable;
|
||||||
|
}
|
||||||
|
|
||||||
// Specify the label's display text
|
// Specify the label's display text
|
||||||
setText(text) {
|
setText(text) {
|
||||||
this.text = text || "";
|
this.text = text || "";
|
||||||
this.localize();
|
this.localize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Specify whether the component is focusable
|
||||||
|
setFocusable(focusable) {
|
||||||
|
this.focusable = focusable = !!focusable;
|
||||||
|
if (focusable) {
|
||||||
|
this.element.setAttribute("tabindex", "0");
|
||||||
|
this.localized && this.application &&
|
||||||
|
this.application.addComponent(this);
|
||||||
|
} else {
|
||||||
|
this.element.removeAttribute("aria-label");
|
||||||
|
this.element.removeAttribute("tabindex");
|
||||||
|
this.localized && this.application &&
|
||||||
|
this.application.removeComponent(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////// Package Methods /////////////////////////////
|
///////////////////////////// Package Methods /////////////////////////////
|
||||||
|
|
|
@ -15,7 +15,7 @@ Toolkit.MenuBar = class MenuBar extends Toolkit.Panel {
|
||||||
|
|
||||||
// Configure element
|
// Configure element
|
||||||
this.element.style.position = "relative";
|
this.element.style.position = "relative";
|
||||||
this.element.style.zIndex = "1";
|
this.element.style.zIndex = "2";
|
||||||
this.element.setAttribute("role", "menubar");
|
this.element.setAttribute("role", "menubar");
|
||||||
this.setLayout("flex", {
|
this.setLayout("flex", {
|
||||||
direction: "row",
|
direction: "row",
|
||||||
|
|
|
@ -15,22 +15,23 @@ Toolkit.Panel = class Panel extends Toolkit.Component {
|
||||||
this.children = [];
|
this.children = [];
|
||||||
this.columns = null;
|
this.columns = null;
|
||||||
this.direction = "row";
|
this.direction = "row";
|
||||||
|
this.focusable = "focusable" in options ? !!options.focusable:false;
|
||||||
|
this.hollow = "hollow" in options ? !!options.hollow : true;
|
||||||
this.layout = null;
|
this.layout = null;
|
||||||
|
this.name = options.name || "";
|
||||||
this.overflowX = options.overflowX || "hidden";
|
this.overflowX = options.overflowX || "hidden";
|
||||||
this.overflowY = options.overflowY || "hidden";
|
this.overflowY = options.overflowY || "hidden";
|
||||||
this.rows = null;
|
this.rows = null;
|
||||||
this.wrap = false;
|
this.wrap = false;
|
||||||
|
|
||||||
// Configure element
|
// Configure properties
|
||||||
if ("noShrink" in options ? !options.noShrink : true) {
|
this.setFocusable(this.focusable);
|
||||||
this.element.style.minHeight = "0";
|
this.setHollow (this.hollow);
|
||||||
this.element.style.minWidth = "0";
|
|
||||||
}
|
|
||||||
this.setOverflow(this.overflowX, this.overflowY);
|
|
||||||
|
|
||||||
// Configure layout
|
|
||||||
options = options || {};
|
|
||||||
this.setLayout (options.layout || null, options);
|
this.setLayout (options.layout || null, options);
|
||||||
|
this.setName (this.name);
|
||||||
|
this.setOverflow (this.overflowX, this.overflowY);
|
||||||
|
if (this.application && this.focusable)
|
||||||
|
this.application.addComponent(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,11 +55,37 @@ Toolkit.Panel = class Panel extends Toolkit.Component {
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Request focus on the appropriate element
|
||||||
|
focus() {
|
||||||
|
if (this.focusable)
|
||||||
|
this.element.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the component's accessible name
|
||||||
|
getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine whether the component is focusable
|
||||||
|
isFocusable() {
|
||||||
|
return this.focusable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine whether the component is hollow
|
||||||
|
isHollow() {
|
||||||
|
return this.hollow;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a Button and associate it with the application
|
// Create a Button and associate it with the application
|
||||||
newButton(options) {
|
newButton(options) {
|
||||||
return new Toolkit.Button(this.application, options);
|
return new Toolkit.Button(this.application, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a CheckBox and associate it with the application
|
||||||
|
newCheckBox(options) {
|
||||||
|
return new Toolkit.CheckBox(this.application, options);
|
||||||
|
}
|
||||||
|
|
||||||
// Create a Label and associate it with the application
|
// Create a Label and associate it with the application
|
||||||
newLabel(options) {
|
newLabel(options) {
|
||||||
return new Toolkit.Label(this.application, options);
|
return new Toolkit.Label(this.application, options);
|
||||||
|
@ -74,6 +101,21 @@ Toolkit.Panel = class Panel extends Toolkit.Component {
|
||||||
return new Toolkit.Panel(this.application, options);
|
return new Toolkit.Panel(this.application, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a RadioButton and associate it with the application
|
||||||
|
newRadioButton(options) {
|
||||||
|
return new Toolkit.RadioButton(this.application, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a Splitter and associate it with the application
|
||||||
|
newSplitter(options) {
|
||||||
|
return new Toolkit.Splitter(this.application, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a TextBox and associate it with the application
|
||||||
|
newTextBox(options) {
|
||||||
|
return new Toolkit.TextBox(this.application, options);
|
||||||
|
}
|
||||||
|
|
||||||
// Create a Window and associate it with the application
|
// Create a Window and associate it with the application
|
||||||
newWindow(options) {
|
newWindow(options) {
|
||||||
return new Toolkit.Window(this.application, options);
|
return new Toolkit.Window(this.application, options);
|
||||||
|
@ -115,6 +157,31 @@ Toolkit.Panel = class Panel extends Toolkit.Component {
|
||||||
this.children.splice(index, 1);
|
this.children.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Specify whether the component is focusable
|
||||||
|
setFocusable(focusable) {
|
||||||
|
this.focusable = focusable = !!focusable;
|
||||||
|
if (focusable) {
|
||||||
|
this.element.setAttribute("tabindex", "0");
|
||||||
|
this.application && this.application.addComponent(this);
|
||||||
|
} else {
|
||||||
|
this.element.removeAttribute("aria-label");
|
||||||
|
this.element.removeAttribute("tabindex");
|
||||||
|
this.application && this.application.removeComponent(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify whether the component is hollow
|
||||||
|
setHollow(hollow) {
|
||||||
|
this.hollow = hollow = !!hollow;
|
||||||
|
if (hollow) {
|
||||||
|
this.element.style.minHeight = "0";
|
||||||
|
this.element.style.minWidth = "0";
|
||||||
|
} else {
|
||||||
|
this.element.style.removeProperty("min-height");
|
||||||
|
this.element.style.removeProperty("min-width" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Configure the element's layout
|
// Configure the element's layout
|
||||||
setLayout(layout, options) {
|
setLayout(layout, options) {
|
||||||
|
|
||||||
|
@ -122,6 +189,7 @@ Toolkit.Panel = class Panel extends Toolkit.Component {
|
||||||
this.layout = layout;
|
this.layout = layout;
|
||||||
|
|
||||||
// Processing by layout
|
// Processing by layout
|
||||||
|
options = options || {};
|
||||||
switch (layout) {
|
switch (layout) {
|
||||||
case "block" : this.setBlockLayout (options); break;
|
case "block" : this.setBlockLayout (options); break;
|
||||||
case "desktop": this.setDesktopLayout(options); break;
|
case "desktop": this.setDesktopLayout(options); break;
|
||||||
|
@ -132,11 +200,17 @@ Toolkit.Panel = class Panel extends Toolkit.Component {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Specify the component's accessible name
|
||||||
|
setName(name) {
|
||||||
|
this.name = name || "";
|
||||||
|
if (this.focusable)
|
||||||
|
this.localize();
|
||||||
|
}
|
||||||
|
|
||||||
// Configure the panel's overflow scrolling behavior
|
// Configure the panel's overflow scrolling behavior
|
||||||
setOverflow(x, y) {
|
setOverflow(x, y) {
|
||||||
this.overflowX = x || "hidden";
|
this.element.style.overflowX = this.overflowX = x || "hidden";
|
||||||
this.overflowY = y || "hidden";
|
this.element.style.overflowY = this.overflowY = y || this.overflowX;
|
||||||
this.element.style.overflow = this.overflowX + " " + this.overflowY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the semantic role of the panel
|
// Specify the semantic role of the panel
|
||||||
|
@ -153,7 +227,15 @@ Toolkit.Panel = class Panel extends Toolkit.Component {
|
||||||
// Move a window to the foreground
|
// Move a window to the foreground
|
||||||
bringToFront(wnd) {
|
bringToFront(wnd) {
|
||||||
for (let child of this.children)
|
for (let child of this.children)
|
||||||
child.element.style.zIndex = child == wnd ? "0" : "1";
|
child.element.style.zIndex = child == wnd ? "1" : "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update display text with localized strings
|
||||||
|
localize() {
|
||||||
|
let name = this.name;
|
||||||
|
if (this.application)
|
||||||
|
name = this.application.translate(name, this);
|
||||||
|
this.element.setAttribute("aria-label", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Select-only radio button
|
||||||
|
Toolkit.RadioButton = class RadioButton extends Toolkit.CheckBox {
|
||||||
|
|
||||||
|
// Object constructor
|
||||||
|
constructor(application, options) {
|
||||||
|
super(application, options);
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.group = null;
|
||||||
|
|
||||||
|
// Configure element
|
||||||
|
this.element.setAttribute("role", "radio");
|
||||||
|
|
||||||
|
// Configure properties
|
||||||
|
this.setGroup(options.group || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Public Methods //////////////////////////////
|
||||||
|
|
||||||
|
// Retrieve the enclosing ButtonGroup
|
||||||
|
getGroup() {
|
||||||
|
return this.group;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify whether the component is checked (overrides superclass)
|
||||||
|
setChecked(checked, e) {
|
||||||
|
checked = !!checked;
|
||||||
|
if (e instanceof Event && !checked || checked == this.checked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.checked = checked;
|
||||||
|
this.element.setAttribute("aria-checked", checked);
|
||||||
|
if (this.group != null && e != this.group)
|
||||||
|
this.group.setChecked(this);
|
||||||
|
|
||||||
|
if (e === undefined)
|
||||||
|
return;
|
||||||
|
for (let listener of this.changeListeners)
|
||||||
|
listener(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify the enclosing ButtonGroup
|
||||||
|
setGroup(group) {
|
||||||
|
group = group || null;
|
||||||
|
if (group == this.group)
|
||||||
|
return;
|
||||||
|
if (this.group != null)
|
||||||
|
this.group.remove(this);
|
||||||
|
this.group = group;
|
||||||
|
if (group != null)
|
||||||
|
group.add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
|
@ -0,0 +1,301 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Interactive splitter
|
||||||
|
Toolkit.Splitter = class Splitter extends Toolkit.Component {
|
||||||
|
|
||||||
|
// Object constructor
|
||||||
|
constructor(application, options) {
|
||||||
|
super(application, "div", options);
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.component = options.component || null;
|
||||||
|
this.dragPointer = null;
|
||||||
|
this.dragPos = 0;
|
||||||
|
this.dragSize = 0;
|
||||||
|
this.orientation = options.orientation || "horizontal";
|
||||||
|
this.name = options.name || "";
|
||||||
|
this.edge = options.edge ||
|
||||||
|
(this.orientation == "horizontal" ? "top" : "left");
|
||||||
|
|
||||||
|
// Configure element
|
||||||
|
this.element.setAttribute("role" , "separator");
|
||||||
|
this.element.setAttribute("tabindex" , "0");
|
||||||
|
this.element.setAttribute("aria-valuemin", "0");
|
||||||
|
this.element.addEventListener("keydown" , e=>this.onkeydown (e));
|
||||||
|
this.element.addEventListener("pointerdown", e=>this.onpointerdown(e));
|
||||||
|
this.element.addEventListener("pointermove", e=>this.onpointermove(e));
|
||||||
|
this.element.addEventListener("pointerup" , e=>this.onpointerup (e));
|
||||||
|
|
||||||
|
// Configure properties
|
||||||
|
this.setComponent (this.component );
|
||||||
|
this.setName (this.name );
|
||||||
|
this.setOrientation(this.orientation);
|
||||||
|
this.application.addComponent(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Public Methods //////////////////////////////
|
||||||
|
|
||||||
|
// Request focus on the appropriate element
|
||||||
|
focus() {
|
||||||
|
this.element.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the component managed by this Splitter
|
||||||
|
getComponent() {
|
||||||
|
return this.component;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the component's accessible name
|
||||||
|
getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the component's orientation
|
||||||
|
getOrientation() {
|
||||||
|
return this.orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the current and maximum separator values
|
||||||
|
measure() {
|
||||||
|
let max = 0;
|
||||||
|
let now = 0;
|
||||||
|
|
||||||
|
// Mesure the component
|
||||||
|
if (this.component != null && this.parent != null) {
|
||||||
|
let component = this.component.getBounds();
|
||||||
|
let bounds = this.getBounds();
|
||||||
|
let panel = this.parent.getBounds();
|
||||||
|
|
||||||
|
// Horizontal Splitter
|
||||||
|
if (this.orientation == "horizontal") {
|
||||||
|
max = panel.height - bounds.height;
|
||||||
|
now = Math.max(0, Math.min(max, component.height));
|
||||||
|
this.component.setSize(null, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical Splitter
|
||||||
|
else {
|
||||||
|
max = panel.width - bounds.width;
|
||||||
|
now = Math.max(0, Math.min(max, component.width));
|
||||||
|
this.component.setSize(now, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure element
|
||||||
|
this.element.setAttribute("aria-valuemax", max);
|
||||||
|
this.element.setAttribute("aria-valuenow", now);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify the component managed by this Splitter
|
||||||
|
setComponent(component) {
|
||||||
|
this.component = component = component || null;
|
||||||
|
this.element.setAttribute("aria-controls",
|
||||||
|
component == null ? "" : component.id);
|
||||||
|
this.measure();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify the component's accessible name
|
||||||
|
setName(name) {
|
||||||
|
this.name = name || "";
|
||||||
|
this.localize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify the component's orientation
|
||||||
|
setOrientation(orientation) {
|
||||||
|
switch (orientation) {
|
||||||
|
case "horizontal":
|
||||||
|
this.orientation = "horizontal";
|
||||||
|
this.setSize(null, 3, true);
|
||||||
|
this.element.setAttribute("aria-orientation", "horizontal");
|
||||||
|
this.element.style.cursor = "ew-resize";
|
||||||
|
break;
|
||||||
|
case "vertical":
|
||||||
|
this.orientation = "vertical";
|
||||||
|
this.setSize(3, null, true);
|
||||||
|
this.element.setAttribute("aria-orientation", "vertical");
|
||||||
|
this.element.style.cursor = "ns-resize";
|
||||||
|
}
|
||||||
|
this.measure();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Package Methods /////////////////////////////
|
||||||
|
|
||||||
|
// Update display text with localized strings
|
||||||
|
localize() {
|
||||||
|
let name = this.name;
|
||||||
|
if (this.application) {
|
||||||
|
name = this.application.translate(name, this);
|
||||||
|
}
|
||||||
|
this.element.setAttribute("aria-label", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Private Methods /////////////////////////////
|
||||||
|
|
||||||
|
// Key press event handler
|
||||||
|
onkeydown(e) {
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (this.component == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let pos = this.component.getBounds();
|
||||||
|
let size = this .getBounds();
|
||||||
|
let max = this.parent .getBounds();
|
||||||
|
if (this.orientation == "horizontal") {
|
||||||
|
max = max .height;
|
||||||
|
pos = pos .height;
|
||||||
|
size = size.height;
|
||||||
|
} else {
|
||||||
|
max = max .width;
|
||||||
|
pos = pos .width;
|
||||||
|
size = size.width;
|
||||||
|
}
|
||||||
|
if (this.edge == "top" || this.edge == "left")
|
||||||
|
max -= size;
|
||||||
|
|
||||||
|
// Processing by key
|
||||||
|
if (this.component != null) switch (e.key) {
|
||||||
|
case "ArrowDown":
|
||||||
|
switch (this.edge) {
|
||||||
|
case "bottom":
|
||||||
|
this.component.setSize(null, Math.min(max, pos - 6));
|
||||||
|
break;
|
||||||
|
case "top":
|
||||||
|
this.component.setSize(null, Math.min(max, pos + 6));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "ArrowLeft":
|
||||||
|
switch (this.edge) {
|
||||||
|
case "left":
|
||||||
|
this.component.setSize(Math.min(max, pos - 6), null);
|
||||||
|
break;
|
||||||
|
case "right":
|
||||||
|
this.component.setSize(Math.min(max, pos + 6), null);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "ArrowRight":
|
||||||
|
switch (this.edge) {
|
||||||
|
case "left":
|
||||||
|
this.component.setSize(Math.min(max, pos + 6), null);
|
||||||
|
break;
|
||||||
|
case "right":
|
||||||
|
this.component.setSize(Math.min(max, pos - 6), null);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "ArrowUp":
|
||||||
|
switch (this.edge) {
|
||||||
|
case "bottom":
|
||||||
|
this.component.setSize(null, Math.min(max, pos + 6));
|
||||||
|
break;
|
||||||
|
case "top":
|
||||||
|
this.component.setSize(null, Math.min(max, pos - 6));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Escape":
|
||||||
|
if (this.dragPointer === null)
|
||||||
|
return;
|
||||||
|
this.element.releasePointerCapture(this.dragPointer);
|
||||||
|
this.dragPointer = null;
|
||||||
|
if (this.orientation == "horizontal")
|
||||||
|
this.component.setHeight(null, this.dragSize);
|
||||||
|
else this.component.setWidth(this.dragSize, null);
|
||||||
|
break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure event
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer down event handler
|
||||||
|
onpointerdown(e) {
|
||||||
|
|
||||||
|
// Request focus
|
||||||
|
this.focus();
|
||||||
|
|
||||||
|
// Configure event
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (
|
||||||
|
this.component == null ||
|
||||||
|
e.button != 0 ||
|
||||||
|
this.element.hasPointerCapture(e.pointerId)
|
||||||
|
) return;
|
||||||
|
|
||||||
|
// Capture the pointer
|
||||||
|
this.element.setPointerCapture(e.pointerId);
|
||||||
|
this.dragPointer = e.pointerId;
|
||||||
|
let bounds = this.component.getBounds();
|
||||||
|
if (this.orientation == "horizontal") {
|
||||||
|
this.dragPos = e.y;
|
||||||
|
this.dragSize = bounds.height;
|
||||||
|
} else {
|
||||||
|
this.dragPos = e.x;
|
||||||
|
this.dragSize = bounds.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer move event handler
|
||||||
|
onpointermove(e) {
|
||||||
|
|
||||||
|
// Configure event
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (
|
||||||
|
this.component == null ||
|
||||||
|
!this.element.hasPointerCapture(e.pointerId)
|
||||||
|
) return;
|
||||||
|
|
||||||
|
// Resize the component
|
||||||
|
let bounds = this.getBounds();
|
||||||
|
let panel = this.parent.getBounds();
|
||||||
|
switch (this.edge) {
|
||||||
|
case "bottom":
|
||||||
|
this.component.setSize(null, Math.max(0, Math.min(
|
||||||
|
this.dragSize - e.y + this.dragPos,
|
||||||
|
panel.height - bounds.height
|
||||||
|
)));
|
||||||
|
break;
|
||||||
|
case "left":
|
||||||
|
this.component.setSize(Math.max(0, Math.min(
|
||||||
|
this.dragSize + e.x - this.dragPos,
|
||||||
|
panel.width - bounds.width
|
||||||
|
)), null);
|
||||||
|
break;
|
||||||
|
case "right":
|
||||||
|
this.component.setSize(Math.max(0, Math.min(
|
||||||
|
this.dragSize - e.x + this.dragPos,
|
||||||
|
panel.width - bounds.width
|
||||||
|
)), null);
|
||||||
|
break;
|
||||||
|
case "top":
|
||||||
|
this.component.setSize(null, Math.max(0, Math.min(
|
||||||
|
this.dragSize + e.y - this.dragPos,
|
||||||
|
panel.height - bounds.height
|
||||||
|
)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.measure();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer up event handler
|
||||||
|
onpointerup(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
this.element.releasePointerCapture(e.pointerId);
|
||||||
|
this.dragPointer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
|
@ -0,0 +1,138 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Toolkit.TextBox = class TextBox extends Toolkit.Component {
|
||||||
|
|
||||||
|
constructor(application, options) {
|
||||||
|
super(application, "input", options);
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.changeListeners = [];
|
||||||
|
this.commitListeners = [];
|
||||||
|
this.enabled = "enabled" in options ? !!options.enabled : true;
|
||||||
|
this.name = options.name || "";
|
||||||
|
this.lastCommit = "";
|
||||||
|
|
||||||
|
// Configure element
|
||||||
|
this.element.size = 1;
|
||||||
|
this.element.type = "text";
|
||||||
|
this.element.addEventListener("blur" , e=>this.commit (e));
|
||||||
|
this.element.addEventListener("input" , e=>this.onchange (e));
|
||||||
|
this.element.addEventListener("keydown", e=>this.onkeydown(e));
|
||||||
|
|
||||||
|
// Configure properties
|
||||||
|
this.setEnabled(this.enabled);
|
||||||
|
this.setName (this.name );
|
||||||
|
this.setText (options.text || "");
|
||||||
|
this.application.addComponent(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Public Methods //////////////////////////////
|
||||||
|
|
||||||
|
// Add a callback for change events
|
||||||
|
addChangeListener(listener) {
|
||||||
|
if (this.changeListeners.indexOf(listener) == -1)
|
||||||
|
this.changeListeners.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a callback for commit events
|
||||||
|
addCommitListener(listener) {
|
||||||
|
if (this.commitListeners.indexOf(listener) == -1)
|
||||||
|
this.commitListeners.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request focus on the appropriate element
|
||||||
|
focus() {
|
||||||
|
this.element.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the component's accessible name
|
||||||
|
getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the component's display text
|
||||||
|
getText() {
|
||||||
|
return this.element.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine whether the component is enabled
|
||||||
|
isEnabled() {
|
||||||
|
return this.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify whether the component is enabled
|
||||||
|
setEnabled(enabled) {
|
||||||
|
this.enabled = enabled = !!enabled;
|
||||||
|
this.element.setAttribute("aria-disabled", !enabled);
|
||||||
|
if (enabled)
|
||||||
|
this.element.removeAttribute("disabled");
|
||||||
|
else this.element.setAttribute("disabled", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify the component's accessible name
|
||||||
|
setName(name) {
|
||||||
|
this.name = name || "";
|
||||||
|
this.localize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify the component's display text
|
||||||
|
setText(text) {
|
||||||
|
text = !text && text !== 0 ? "" : "" + text;
|
||||||
|
this.lastCommit = text;
|
||||||
|
this.element.value = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Package Methods /////////////////////////////
|
||||||
|
|
||||||
|
// Update display text with localized strings
|
||||||
|
localize() {
|
||||||
|
let name = this.name;
|
||||||
|
if (this.application)
|
||||||
|
name = this.application.translate(name, this);
|
||||||
|
this.element.setAttribute("aria-label", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Private Methods /////////////////////////////
|
||||||
|
|
||||||
|
// Input finalized
|
||||||
|
commit(e) {
|
||||||
|
let text = this.element.value || "";
|
||||||
|
if (!this.enabled || text == this.lastCommit)
|
||||||
|
return;
|
||||||
|
this.lastCommit = text;
|
||||||
|
for (let listener of this.commitListeners)
|
||||||
|
listener(e, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text changed event handler
|
||||||
|
onchange(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!this.enabled)
|
||||||
|
return;
|
||||||
|
for (let listener of this.changeListeners)
|
||||||
|
listener(e, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key press event handler
|
||||||
|
onkeydown(e) {
|
||||||
|
|
||||||
|
// Configure event
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (!this.enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// The Enter key was pressed
|
||||||
|
if (e.key == "Enter")
|
||||||
|
this.commit(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
|
@ -11,28 +11,22 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
// Configure instance fields
|
// Configure instance fields
|
||||||
this.closeListeners = [];
|
this.closeListeners = [];
|
||||||
this.dragBounds = null;
|
this.dragBounds = null;
|
||||||
|
this.dragClient = null;
|
||||||
this.dragCursor = { x: 0, y: 0 };
|
this.dragCursor = { x: 0, y: 0 };
|
||||||
this.dragEdge = null;
|
this.dragEdge = null;
|
||||||
|
this.dragPointer = null;
|
||||||
this.initialCenter = "center" in options ? !!options.center : false;
|
this.initialCenter = "center" in options ? !!options.center : false;
|
||||||
this.initialHeight = options.height || 64;
|
|
||||||
this.initialWidth = options.width || 64;
|
|
||||||
this.lastFocus = this.element;
|
this.lastFocus = this.element;
|
||||||
this.shown = this.visible;
|
this.shown = this.visible;
|
||||||
this.title = options.title || "";
|
|
||||||
|
|
||||||
// Configure element
|
// Configure element
|
||||||
this.setLayout("flex", {
|
this.setLayout("grid", { columns: "auto" });
|
||||||
alignCross: "stretch",
|
|
||||||
direction : "column",
|
|
||||||
overflowX : "visible",
|
|
||||||
overflowY : "visible"
|
|
||||||
});
|
|
||||||
this.setRole("dialog");
|
this.setRole("dialog");
|
||||||
this.setBounds(0, 0, 64, 64);
|
this.setLocation(0, 0);
|
||||||
this.element.style.position = "absolute";
|
this.element.style.position = "absolute";
|
||||||
this.element.setAttribute("aria-modal", "false");
|
this.element.setAttribute("aria-modal", "false");
|
||||||
this.element.setAttribute("focus" , "false");
|
this.element.setAttribute("focus" , "false");
|
||||||
this.element.setAttribute("tabindex" , "-1" );
|
this.element.setAttribute("tabindex" , "0" );
|
||||||
this.element.addEventListener(
|
this.element.addEventListener(
|
||||||
"blur" , e=>this.onblur (e), { capture: true });
|
"blur" , e=>this.onblur (e), { capture: true });
|
||||||
this.element.addEventListener(
|
this.element.addEventListener(
|
||||||
|
@ -42,69 +36,48 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
this.element.addEventListener("pointermove", e=>this.onpointermove(e));
|
this.element.addEventListener("pointermove", e=>this.onpointermove(e));
|
||||||
this.element.addEventListener("pointerup" , e=>this.onpointerup (e));
|
this.element.addEventListener("pointerup" , e=>this.onpointerup (e));
|
||||||
|
|
||||||
// Configure body container
|
// Primary visible container
|
||||||
this.body = this.add(this.newPanel({
|
this.body = this.add(this.newPanel({
|
||||||
layout : "flex",
|
layout : "grid",
|
||||||
alignCross: "stretch",
|
|
||||||
direction : "column",
|
|
||||||
overflowX: "visible",
|
overflowX: "visible",
|
||||||
overflowY : "visible"
|
overflowY: "visible",
|
||||||
|
rows : "max-content auto"
|
||||||
}));
|
}));
|
||||||
this.body.element.style.flexGrow = "1";
|
|
||||||
this.body.element.style.margin = "3px";
|
|
||||||
this.body.element.setAttribute("name", "body");
|
this.body.element.setAttribute("name", "body");
|
||||||
|
|
||||||
// Configure title bar
|
// Title bar
|
||||||
this.titleBar = this.body.add(this.newPanel({
|
this.titleBar = this.body.add(this.newPanel({
|
||||||
layout : "flex",
|
layout : "grid",
|
||||||
alignCross: "center",
|
columns: "max-content auto max-content",
|
||||||
direction : "row",
|
hollow : false
|
||||||
noShrink : true,
|
|
||||||
overflowX : "visible",
|
|
||||||
overflowY : "visible"
|
|
||||||
}));
|
}));
|
||||||
this.titleBar.element.setAttribute("name", "title-bar");
|
this.titleBar.element.setAttribute("name", "title-bar");
|
||||||
|
|
||||||
// Configure title icon element
|
// Title bar icon
|
||||||
this.titleIcon = this.titleBar.add(this.newPanel({}));
|
this.titleIcon = this.titleBar.add(this.newPanel());
|
||||||
this.titleIcon.element.setAttribute("name", "title-icon");
|
this.titleIcon.element.setAttribute("name", "icon");
|
||||||
this.titleIcon.element.style.removeProperty("min-width");
|
this.titleIcon.element.setAttribute("aria-hidden", "true");
|
||||||
|
|
||||||
// Configure title text element
|
// Title bar title
|
||||||
this.titleElement = this.titleBar.add(this.newLabel({}));
|
this.title = this.titleBar.add(this.newLabel({
|
||||||
this.titleElement.element.setAttribute("name", "title");
|
localized: true,
|
||||||
this.titleElement.element.style.cursor = "default";
|
text : options.title
|
||||||
this.titleElement.element.style.flexGrow = "1";
|
}));
|
||||||
this.titleElement.element.style.userSelect = "none";
|
this.title.element.setAttribute("name", "title");
|
||||||
this.element.setAttribute("aria-labelledby", this.titleElement.id);
|
this.title.setProperty("sim", "");
|
||||||
|
|
||||||
// Configure title close element
|
// Title bar close
|
||||||
this.titleCloseBox = this.titleBar.add(this.newPanel({}));
|
this.titleClose = this.titleBar.add(this.newButton({
|
||||||
this.titleCloseBox.element.setAttribute("name", "title-close-box");
|
|
||||||
this.titleCloseBox.element.style.removeProperty("min-width");
|
|
||||||
this.titleClose = this.titleCloseBox.add(this.newButton({
|
|
||||||
focusable: false,
|
|
||||||
name : "{app.close}",
|
|
||||||
toolTip: "{app.close}"
|
toolTip: "{app.close}"
|
||||||
}));
|
}));
|
||||||
this.titleClose.element.setAttribute("name", "title-close");
|
this.titleClose.element.setAttribute("name", "close");
|
||||||
|
this.titleClose.element.setAttribute("aria-hidden", "true");
|
||||||
this.titleClose.addClickListener(e=>this.onclose(e));
|
this.titleClose.addClickListener(e=>this.onclose(e));
|
||||||
|
|
||||||
// Configure client area
|
// Client area
|
||||||
this.client = this.body.add(this.newPanel({
|
this.client = this.body.add(this.newPanel());
|
||||||
overflowX: "hidden",
|
|
||||||
overflowY: "hidden"
|
|
||||||
}));
|
|
||||||
this.client.element.style.flexGrow = "1";
|
|
||||||
this.client.element.setAttribute("name", "client");
|
this.client.element.setAttribute("name", "client");
|
||||||
this.client.element.addEventListener(
|
this.setSize(options.width, options.height);
|
||||||
"pointerdown", e=>this.onclientdown(e));
|
|
||||||
|
|
||||||
// Configure properties
|
|
||||||
this.setTitle(this.title);
|
|
||||||
if (this.shown)
|
|
||||||
this.setClientSize(this.initialHeight, this.initialWidth);
|
|
||||||
application.addComponent(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -117,20 +90,19 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
this.closeListeners.push(listener);
|
this.closeListeners.push(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the size of the client rectangle in pixels
|
// Retrieve the window's title text
|
||||||
setClientSize(width, height) {
|
getTitle() {
|
||||||
let bounds = this.getBounds();
|
return this.title.getText();
|
||||||
let client = this.client.getBounds();
|
}
|
||||||
this.setSize(
|
|
||||||
width + bounds.width - client.width,
|
// Specify the height of the component
|
||||||
height + bounds.height - client.height
|
setHeight(height, minimum) {
|
||||||
);
|
this.client && this.client.setHeight(height, minimum);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the window's title text
|
// Specify the window's title text
|
||||||
setTitle(title) {
|
setTitle(title) {
|
||||||
this.title = title || "";
|
this.title.setText(title);
|
||||||
this.localize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify whether the component is visible
|
// Specify whether the component is visible
|
||||||
|
@ -146,6 +118,12 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
this.firstShow();
|
this.firstShow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Specify the width of the component
|
||||||
|
setWidth(width, minimum) {
|
||||||
|
this.client && this.client.setWidth(
|
||||||
|
Math.max(64, width || 0), minimum);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////// Package Methods /////////////////////////////
|
///////////////////////////// Package Methods /////////////////////////////
|
||||||
|
@ -155,13 +133,23 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
if (this.lastFocus != this)
|
if (this.lastFocus != this)
|
||||||
this.lastFocus.focus();
|
this.lastFocus.focus();
|
||||||
else this.element.focus();
|
else this.element.focus();
|
||||||
this.parent.bringToFront(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////// Private Methods /////////////////////////////
|
///////////////////////////// Private Methods /////////////////////////////
|
||||||
|
|
||||||
|
// Position the window in the center of the desktop
|
||||||
|
center() {
|
||||||
|
let bounds = this.getBounds();
|
||||||
|
let desktop = this.parent.getBounds();
|
||||||
|
this.contain(
|
||||||
|
Math.floor((desktop.width - bounds.width ) / 2),
|
||||||
|
Math.ceil ((desktop.height - bounds.height) / 2),
|
||||||
|
desktop, bounds
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Position the window using a tentative location in the desktop
|
// Position the window using a tentative location in the desktop
|
||||||
contain(x, y, desktop, bounds, client) {
|
contain(x, y, desktop, bounds, client) {
|
||||||
desktop = desktop || this.parent.getBounds();
|
desktop = desktop || this.parent.getBounds();
|
||||||
|
@ -213,28 +201,6 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
// The window is being displayed for the first time
|
// The window is being displayed for the first time
|
||||||
firstShow() {
|
firstShow() {
|
||||||
this.shown = true;
|
this.shown = true;
|
||||||
|
|
||||||
// Configure the initial size of the window
|
|
||||||
this.setClientSize(this.initialWidth, this.initialHeight);
|
|
||||||
|
|
||||||
// Configure the initial position of the window
|
|
||||||
if (!this.initialCenter)
|
|
||||||
return;
|
|
||||||
let bounds = this.getBounds();
|
|
||||||
let desktop = this.parent.getBounds();
|
|
||||||
this.contain(
|
|
||||||
Math.floor((desktop.width - bounds.width ) / 2),
|
|
||||||
Math.ceil ((desktop.height - bounds.height) / 2),
|
|
||||||
desktop, bounds
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update display text with localized strings
|
|
||||||
localize() {
|
|
||||||
let title = this.title;
|
|
||||||
if (this.application)
|
|
||||||
title = this.application.translate(title, this);
|
|
||||||
this.titleElement.element.innerText = title;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focus lost event capture
|
// Focus lost event capture
|
||||||
|
@ -243,13 +209,6 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
this.element.setAttribute("focus", "false");
|
this.element.setAttribute("focus", "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client pointer down event handler
|
|
||||||
onclientdown(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
this.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Window close
|
// Window close
|
||||||
onclose(e) {
|
onclose(e) {
|
||||||
for (let listener of this.closeListeners)
|
for (let listener of this.closeListeners)
|
||||||
|
@ -258,52 +217,45 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
|
|
||||||
// Focus gained event capture
|
// Focus gained event capture
|
||||||
onfocus(e) {
|
onfocus(e) {
|
||||||
|
|
||||||
// Configure element
|
|
||||||
this.element.setAttribute("focus", "true");
|
this.element.setAttribute("focus", "true");
|
||||||
|
|
||||||
// Working variables
|
|
||||||
let target = e.target;
|
let target = e.target;
|
||||||
|
|
||||||
// Delegate focus to the most recently focused component
|
|
||||||
if (target == this.element)
|
if (target == this.element)
|
||||||
target = this.lastFocus;
|
target = this.lastFocus;
|
||||||
|
|
||||||
// The component is not visible: focus on self instead
|
|
||||||
if ("component" in target && !target.component.isVisible())
|
|
||||||
target = this.element;
|
|
||||||
|
|
||||||
// Configure instance fields
|
|
||||||
this.lastFocus = target;
|
this.lastFocus = target;
|
||||||
|
|
||||||
// Transfer focus to the correct component
|
|
||||||
if (target != e.target)
|
if (target != e.target)
|
||||||
target.focus();
|
target.focus();
|
||||||
|
this.parent.bringToFront(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key down event handler
|
// Key pressed event handler
|
||||||
onkeydown(e) {
|
onkeydown(e) {
|
||||||
|
|
||||||
// Processing by key
|
// Only listening for Escape while dragging
|
||||||
switch (e.key) {
|
if (e.key != "Escape" || this.dragPointer === null)
|
||||||
default: return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
// Configure event
|
// Restore the window's position
|
||||||
e.preventDefault();
|
let desktop = this.parent.getBounds();
|
||||||
e.stopPropagation();
|
this.setLocation(
|
||||||
|
this.dragBounds.x - desktop.x,
|
||||||
|
this.dragBounds.y - desktop.y
|
||||||
|
);
|
||||||
|
|
||||||
|
// Restore the window's size
|
||||||
|
if (this.dragEdge != null)
|
||||||
|
this.setSize(this.dragClient.width, this.dragClient.height);
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.element.releasePointerCapture(this.dragPointer);
|
||||||
|
this.dragPointer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pointer down event handler
|
// Pointer down event handler
|
||||||
onpointerdown(e) {
|
onpointerdown(e) {
|
||||||
|
|
||||||
// Configure event
|
// Configure event
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// Configure element
|
|
||||||
this.focus();
|
|
||||||
|
|
||||||
// Error checking
|
// Error checking
|
||||||
if (
|
if (
|
||||||
e.button != 0 ||
|
e.button != 0 ||
|
||||||
|
@ -312,12 +264,19 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
|
|
||||||
// Configure instance fields
|
// Configure instance fields
|
||||||
this.dragBounds = this.getBounds();
|
this.dragBounds = this.getBounds();
|
||||||
this.dragEdge = this.edge(e, this.dragBounds);
|
this.dragClient = this.client.getBounds();
|
||||||
this.dragCursor.x = e.x;
|
this.dragCursor.x = e.x;
|
||||||
this.dragCursor.y = e.y;
|
this.dragCursor.y = e.y;
|
||||||
|
this.dragEdge = this.edge(e, this.dragBounds);
|
||||||
|
|
||||||
|
// Don't perform a move if the cursor isn't in the title bar
|
||||||
|
let title = this.titleBar.getBounds();
|
||||||
|
if (this.dragEdge == null && e.y >= title.y + title.height)
|
||||||
|
return;
|
||||||
|
|
||||||
// Configure element
|
// Configure element
|
||||||
this.element.setPointerCapture(e.pointerId);
|
this.element.setPointerCapture(e.pointerId);
|
||||||
|
this.dragPointer = e.pointerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pointer move event handler
|
// Pointer move event handler
|
||||||
|
@ -342,7 +301,6 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
let bounds = this.getBounds();
|
let bounds = this.getBounds();
|
||||||
let desktop = this.parent.getBounds();
|
let desktop = this.parent.getBounds();
|
||||||
let client = this.client.getBounds();
|
let client = this.client.getBounds();
|
||||||
let minHeight = bounds.height - client.height;
|
|
||||||
|
|
||||||
// Move the window
|
// Move the window
|
||||||
if (this.dragEdge == null) {
|
if (this.dragEdge == null) {
|
||||||
|
@ -358,7 +316,7 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
if (this.dragEdge.startsWith("n")) {
|
if (this.dragEdge.startsWith("n")) {
|
||||||
let maxTop = desktop.height - client.y + bounds.y;
|
let maxTop = desktop.height - client.y + bounds.y;
|
||||||
let top = this.dragBounds.y - desktop.y + rY;
|
let top = this.dragBounds.y - desktop.y + rY;
|
||||||
let height = this.dragBounds.height - rY;
|
let height = this.dragClient.height - rY;
|
||||||
|
|
||||||
// Restrict window bounds
|
// Restrict window bounds
|
||||||
if (top > maxTop) {
|
if (top > maxTop) {
|
||||||
|
@ -369,9 +327,9 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
height += top;
|
height += top;
|
||||||
top = 0;
|
top = 0;
|
||||||
}
|
}
|
||||||
if (height < minHeight) {
|
if (height < 0) {
|
||||||
top -= minHeight - height;
|
top += height;
|
||||||
height = minHeight;
|
height = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure element
|
// Configure element
|
||||||
|
@ -383,7 +341,7 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
if (this.dragEdge.endsWith("w")) {
|
if (this.dragEdge.endsWith("w")) {
|
||||||
let maxLeft = desktop.width - 16;
|
let maxLeft = desktop.width - 16;
|
||||||
let left = this.dragBounds.x - desktop.x + rX;
|
let left = this.dragBounds.x - desktop.x + rX;
|
||||||
let width = this.dragBounds.width - rX;
|
let width = this.dragClient.width - rX;
|
||||||
|
|
||||||
// Restrict window bounds
|
// Restrict window bounds
|
||||||
if (left > maxLeft) {
|
if (left > maxLeft) {
|
||||||
|
@ -402,11 +360,11 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
|
|
||||||
// Resizing on the east edge
|
// Resizing on the east edge
|
||||||
if (this.dragEdge.endsWith("e")) {
|
if (this.dragEdge.endsWith("e")) {
|
||||||
let width = this.dragBounds.width + rX;
|
let width = this.dragClient.width + rX;
|
||||||
|
|
||||||
// Restrict window bounds
|
// Restrict window bounds
|
||||||
width = Math.max(64, width);
|
width = Math.max(64, width);
|
||||||
width = Math.max(width, -this.dragBounds.x + 16);
|
width = Math.max(width, -this.dragClient.x + 16);
|
||||||
|
|
||||||
// Configure element
|
// Configure element
|
||||||
this.setWidth(width);
|
this.setWidth(width);
|
||||||
|
@ -414,10 +372,10 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
|
|
||||||
// Resizing on the south edge
|
// Resizing on the south edge
|
||||||
if (this.dragEdge.startsWith("s")) {
|
if (this.dragEdge.startsWith("s")) {
|
||||||
let height = this.dragBounds.height + rY;
|
let height = this.dragClient.height + rY;
|
||||||
|
|
||||||
// Restrict window bounds
|
// Restrict window bounds
|
||||||
height = Math.max(minHeight, height);
|
height = Math.max(0, height);
|
||||||
|
|
||||||
// Configure element
|
// Configure element
|
||||||
this.setHeight(height);
|
this.setHeight(height);
|
||||||
|
@ -438,6 +396,7 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
|
|
||||||
// Configure element
|
// Configure element
|
||||||
this.element.releasePointerCapture(e.pointerId);
|
this.element.releasePointerCapture(e.pointerId);
|
||||||
|
this.dragPointer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,292 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// CPU register and disassembler display
|
||||||
|
(globalThis.CPUWindow = class CPUWindow extends Toolkit.Window {
|
||||||
|
|
||||||
|
// Static initializer
|
||||||
|
static initializer() {
|
||||||
|
|
||||||
|
// System register IDs
|
||||||
|
this.ADTRE = 25;
|
||||||
|
this.CHCW = 24;
|
||||||
|
this.ECR = 4;
|
||||||
|
this.EIPC = 0;
|
||||||
|
this.EIPSW = 1;
|
||||||
|
this.FEPC = 2;
|
||||||
|
this.FEPSW = 3;
|
||||||
|
this.PC = -1;
|
||||||
|
this.PIR = 6;
|
||||||
|
this.PSW = 5;
|
||||||
|
this.TKCW = 7;
|
||||||
|
|
||||||
|
// Program register names
|
||||||
|
this.PROGRAM = {
|
||||||
|
[ 2]: "hp",
|
||||||
|
[ 3]: "sp",
|
||||||
|
[ 4]: "gp",
|
||||||
|
[ 5]: "tp",
|
||||||
|
[31]: "lp"
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object constructor
|
||||||
|
constructor(debug, options) {
|
||||||
|
super(debug.gui, options);
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.address = 0xFFFFFFF0;
|
||||||
|
this.debug = debug;
|
||||||
|
|
||||||
|
// Configure properties
|
||||||
|
this.setProperty("sim", "");
|
||||||
|
|
||||||
|
// Configure elements
|
||||||
|
|
||||||
|
this.initDisassembler();
|
||||||
|
this.initSystemRegisters();
|
||||||
|
this.initProgramRegisters();
|
||||||
|
this.initWindow();
|
||||||
|
|
||||||
|
// Layout components
|
||||||
|
|
||||||
|
// Disassembler on the left
|
||||||
|
this.mainWrap.add(this.dasmWrap);
|
||||||
|
|
||||||
|
// Registers on the right
|
||||||
|
this.regs = this.newPanel({
|
||||||
|
layout: "grid",
|
||||||
|
rows : "max-content max-content auto"
|
||||||
|
});
|
||||||
|
this.regs.element.setAttribute("name", "wrap-registers");
|
||||||
|
|
||||||
|
// Splitter between disassembler and registers
|
||||||
|
this.mainSplit = this.newSplitter({
|
||||||
|
component : this.regs,
|
||||||
|
orientation: "vertical",
|
||||||
|
edge : "right",
|
||||||
|
name : "{cpu.mainSplit}"
|
||||||
|
});
|
||||||
|
this.mainSplit.element.setAttribute("name", "split-main");
|
||||||
|
this.mainSplit.element.style.width = "3px";
|
||||||
|
this.mainSplit.element.style.minWidth = "3px";
|
||||||
|
this.mainSplit.element.style.cursor = "ew-resize";
|
||||||
|
this.mainWrap.add(this.mainSplit);
|
||||||
|
|
||||||
|
// Registers on the right
|
||||||
|
this.mainWrap.add(this.regs);
|
||||||
|
|
||||||
|
// System registers on top
|
||||||
|
this.regs.add(this.sysWrap);
|
||||||
|
|
||||||
|
// Splitter between system registers and program registers
|
||||||
|
this.regsSplit = this.regs.add(this.newSplitter({
|
||||||
|
component : this.sysWrap,
|
||||||
|
orientation: "horizontal",
|
||||||
|
edge : "top",
|
||||||
|
name : "{cpu.regsSplit}"
|
||||||
|
}));
|
||||||
|
this.regsSplit.element.style.height = "3px";
|
||||||
|
this.regsSplit.element.style.minHeight = "3px";
|
||||||
|
this.regsSplit.element.style.cursor = "ns-resize";
|
||||||
|
|
||||||
|
// Program registers on the bottom
|
||||||
|
this.regs.add(this.proWrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize disassembler pane
|
||||||
|
initDisassembler() {
|
||||||
|
|
||||||
|
// Wrapping element to hide overflowing scrollbar
|
||||||
|
this.dasmWrap = this.newPanel({
|
||||||
|
layout : "grid",
|
||||||
|
overflowX: "hidden",
|
||||||
|
overflowY: "hidden"
|
||||||
|
});
|
||||||
|
this.dasmWrap.element.setAttribute("name", "wrap-disassembler");
|
||||||
|
|
||||||
|
// Main element
|
||||||
|
this.dasm = this.dasmWrap.add(this.newPanel({
|
||||||
|
focusable: true,
|
||||||
|
name : "{cpu.disassembler}",
|
||||||
|
overflowX: "auto",
|
||||||
|
overflowY: "hidden"
|
||||||
|
}));
|
||||||
|
this.dasm.element.setAttribute("name", "disassembler");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize program registers pane
|
||||||
|
initProgramRegisters() {
|
||||||
|
|
||||||
|
// Wrapping element to hide overflowing scrollbar
|
||||||
|
this.proWrap = this.newPanel({
|
||||||
|
layout : "grid",
|
||||||
|
overflow: "hidden"
|
||||||
|
});
|
||||||
|
this.proWrap.element.setAttribute("name", "wrap-program-registers");
|
||||||
|
this.proWrap.element.style.flexGrow = "1";
|
||||||
|
|
||||||
|
// Main element
|
||||||
|
this.proRegs = this.proWrap.add(this.newPanel({
|
||||||
|
overflowX: "auto",
|
||||||
|
overflowY: "scroll"
|
||||||
|
}));
|
||||||
|
this.proRegs.element.setAttribute("name", "program-registers");
|
||||||
|
|
||||||
|
// List of registers
|
||||||
|
this.proRegs.registers = {};
|
||||||
|
for (let x = 0; x <= 31; x++)
|
||||||
|
this.addRegister(false, x, CPUWindow.PROGRAM[x] || "r" + x);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize system registers pane
|
||||||
|
initSystemRegisters() {
|
||||||
|
|
||||||
|
// Wrapping element to hide overflowing scrollbar
|
||||||
|
this.sysWrap = this.newPanel({
|
||||||
|
layout : "grid",
|
||||||
|
overflow: "hidden"
|
||||||
|
});
|
||||||
|
this.sysWrap.element.setAttribute("name", "wrap-system-registers");
|
||||||
|
|
||||||
|
// Main element
|
||||||
|
this.sysRegs = this.sysWrap.add(this.newPanel({
|
||||||
|
overflowX: "auto",
|
||||||
|
overflowY: "scroll"
|
||||||
|
}));
|
||||||
|
this.sysRegs.element.setAttribute("name", "system-registers");
|
||||||
|
|
||||||
|
// List of registers
|
||||||
|
this.sysRegs.registers = {};
|
||||||
|
this.addRegister(true, CPUWindow.PC , "PC" );
|
||||||
|
this.addRegister(true, CPUWindow.PSW , "PSW" );
|
||||||
|
this.addRegister(true, CPUWindow.ADTRE, "ADTRE");
|
||||||
|
this.addRegister(true, CPUWindow.CHCW , "CHCW" );
|
||||||
|
this.addRegister(true, CPUWindow.ECR , "ECR" );
|
||||||
|
this.addRegister(true, CPUWindow.EIPC , "EIPC" );
|
||||||
|
this.addRegister(true, CPUWindow.EIPSW, "EIPSW");
|
||||||
|
this.addRegister(true, CPUWindow.FEPC , "FEPC" );
|
||||||
|
this.addRegister(true, CPUWindow.FEPSW, "FEPSW");
|
||||||
|
this.addRegister(true, CPUWindow.PIR , "PIR" );
|
||||||
|
this.addRegister(true, CPUWindow.TKCW , "TKCW" );
|
||||||
|
this.addRegister(true, 29 , "29" );
|
||||||
|
this.addRegister(true, 30 , "30" );
|
||||||
|
this.addRegister(true, 31 , "31" );
|
||||||
|
this.sysRegs.registers[CPUWindow.PSW].setExpanded(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize window and client
|
||||||
|
initWindow() {
|
||||||
|
|
||||||
|
// Configure element
|
||||||
|
this.element.setAttribute("window", "cpu");
|
||||||
|
|
||||||
|
// Configure body
|
||||||
|
this.body.element.setAttribute("filter", "");
|
||||||
|
|
||||||
|
// Configure client
|
||||||
|
this.client.setLayout("grid", {
|
||||||
|
columns: "auto"
|
||||||
|
});
|
||||||
|
this.client.addResizeListener(b=>this.onresize(b));
|
||||||
|
|
||||||
|
// Configure main wrapper
|
||||||
|
this.mainWrap = this.client.add(this.newPanel({
|
||||||
|
layout : "grid",
|
||||||
|
columns: "auto max-content max-content"
|
||||||
|
}));
|
||||||
|
this.mainWrap.element.setAttribute("name", "wrap-main");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Public Methods //////////////////////////////
|
||||||
|
|
||||||
|
// The window is being displayed for the first time
|
||||||
|
firstShow() {
|
||||||
|
super.firstShow();
|
||||||
|
this.center();
|
||||||
|
this.mainSplit.measure();
|
||||||
|
this.regsSplit.measure();
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Package Methods /////////////////////////////
|
||||||
|
|
||||||
|
// Update the display with current emulation data
|
||||||
|
refresh(clientHeight, lineHeight, registers) {
|
||||||
|
if (!registers) {
|
||||||
|
this.debug.core.postMessage({
|
||||||
|
command: "GetRegisters",
|
||||||
|
debug : "CPU",
|
||||||
|
sim : this.debug.sim
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Message Methods /////////////////////////////
|
||||||
|
|
||||||
|
// Message received
|
||||||
|
message(msg) {
|
||||||
|
switch (msg.command) {
|
||||||
|
case "GetRegisters": this.getRegisters(msg); break;
|
||||||
|
case "SetRegister" : this.setRegister (msg); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieved all register values
|
||||||
|
getRegisters(msg) {
|
||||||
|
this.sysRegs.registers[CPUWindow.PC ]
|
||||||
|
.setValue(msg.pc, msg.pcFrom, msg.pcTo);
|
||||||
|
this.sysRegs.registers[CPUWindow.PSW ].setValue(msg.psw );
|
||||||
|
this.sysRegs.registers[CPUWindow.ADTRE].setValue(msg.adtre);
|
||||||
|
this.sysRegs.registers[CPUWindow.CHCW ].setValue(msg.chcw );
|
||||||
|
this.sysRegs.registers[CPUWindow.ECR ].setValue(msg.ecr );
|
||||||
|
this.sysRegs.registers[CPUWindow.EIPC ].setValue(msg.eipc );
|
||||||
|
this.sysRegs.registers[CPUWindow.EIPSW].setValue(msg.eipsw);
|
||||||
|
this.sysRegs.registers[CPUWindow.FEPC ].setValue(msg.fepc );
|
||||||
|
this.sysRegs.registers[CPUWindow.FEPSW].setValue(msg.fepsw);
|
||||||
|
this.sysRegs.registers[CPUWindow.PIR ].setValue(msg.pir );
|
||||||
|
this.sysRegs.registers[CPUWindow.TKCW ].setValue(msg.tkcw );
|
||||||
|
this.sysRegs.registers[29 ].setValue(msg.sr29 );
|
||||||
|
this.sysRegs.registers[30 ].setValue(msg.sr30 );
|
||||||
|
this.sysRegs.registers[31 ].setValue(msg.sr31 );
|
||||||
|
for (let x = 0; x <= 31; x++)
|
||||||
|
this.proRegs.registers[x].setValue(msg.program[x]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified a register value
|
||||||
|
setRegister(msg) {
|
||||||
|
(msg.type == "program" ? this.proRegs : this.sysRegs)
|
||||||
|
.registers[msg.id].setValue(msg.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Private Methods /////////////////////////////
|
||||||
|
|
||||||
|
// Insert a register control to a register list
|
||||||
|
addRegister(system, id, name) {
|
||||||
|
let list = system ? this.sysRegs : this.proRegs;
|
||||||
|
let reg = new CPUWindow.Register(this.debug, list, system, id, name);
|
||||||
|
list.registers[id] = reg;
|
||||||
|
list.add(reg);
|
||||||
|
if (reg.expands)
|
||||||
|
list.add(reg.expansion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize event handler
|
||||||
|
onresize(bounds) {
|
||||||
|
if (!this.isVisible())
|
||||||
|
return;
|
||||||
|
//this.regs.setHeight(bounds.height);
|
||||||
|
this.mainSplit.measure();
|
||||||
|
this.regsSplit.measure();
|
||||||
|
}
|
||||||
|
|
||||||
|
}).initializer();
|
|
@ -8,47 +8,68 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
||||||
super(debug.gui, options);
|
super(debug.gui, options);
|
||||||
|
|
||||||
// Configure instance fields
|
// Configure instance fields
|
||||||
this.address = 0xFFFFFFF0;
|
this.address = 0x05000000;
|
||||||
this.debug = debug;
|
this.debug = debug;
|
||||||
this.rows = [new MemoryWindow.Row(this.client)];
|
this.rows = [];
|
||||||
|
|
||||||
// Configure element
|
// Configure element
|
||||||
this.element.setAttribute("window", "memory");
|
this.element.setAttribute("window", "memory");
|
||||||
this.element.addEventListener("wheel", e=>this.onwheel(e));
|
|
||||||
|
// Configure body
|
||||||
|
this.body.element.setAttribute("filter", "");
|
||||||
|
|
||||||
// Configure client
|
// Configure client
|
||||||
this.client.setLayout("grid", { columns: "repeat(17, max-content)" });
|
this.client.setLayout("grid");
|
||||||
this.client.element.style.gridAutoRows = "max-content";
|
|
||||||
this.client.setOverflow("auto", "hidden");
|
|
||||||
this.client.addResizeListener(b=>this.refresh(b.height));
|
this.client.addResizeListener(b=>this.refresh(b.height));
|
||||||
|
|
||||||
|
// Wrapping element to hide overflowing scrollbar
|
||||||
|
this.hexWrap = this.client.add(this.newPanel({
|
||||||
|
layout : "grid",
|
||||||
|
columns: "auto"
|
||||||
|
}));
|
||||||
|
this.hexWrap.element.setAttribute("name", "wrap-hex");
|
||||||
|
|
||||||
|
// Configure hex viewer
|
||||||
|
this.hex = this.hexWrap.add(this.client.newPanel({
|
||||||
|
focusable: true,
|
||||||
|
layout : "block",
|
||||||
|
hollow : false,
|
||||||
|
overflowX: "auto",
|
||||||
|
overflowY: "hidden"
|
||||||
|
}));
|
||||||
|
this.hex.element.setAttribute("role", "grid");
|
||||||
|
this.hex.element.setAttribute("name", "hex");
|
||||||
|
this.hex.element.addEventListener("wheel", e=>this.onwheel(e));
|
||||||
|
|
||||||
// Configure properties
|
// Configure properties
|
||||||
this.setProperty("sim", "");
|
this.setProperty("sim", "");
|
||||||
}
|
this.rows.push(this.hex.add(new MemoryWindow.Row(this.hex)));
|
||||||
|
this.application.addComponent(this);
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////// Public Methods //////////////////////////////
|
|
||||||
|
|
||||||
// The window is being displayed for the first time
|
|
||||||
firstShow() {
|
|
||||||
super.firstShow();
|
|
||||||
this.seek(this.address, Math.floor(this.lines(true) / 3));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////// Package Methods /////////////////////////////
|
///////////////////////////// Package Methods /////////////////////////////
|
||||||
|
|
||||||
// Update the display with current emulation data
|
// Update display text with localized strings
|
||||||
refresh(clientHeight, lineHeight) {
|
localize() {
|
||||||
clientHeight = clientHeight || this.client.getBounds().height;
|
let hex = "";
|
||||||
lineHeight = lineHeight || this.lineHeight();
|
if (this.application)
|
||||||
let rowCount = this.lines(false, clientHeight, lineHeight);
|
hex = this.application.translate("{memory.hexEditor}");
|
||||||
|
this.hex.element.setAttribute("aria-label", hex);
|
||||||
|
}
|
||||||
|
|
||||||
// Showing for the first time
|
// Update the display with current emulation data
|
||||||
if (this.address < 0)
|
refresh(gridHeight, lineHeight) {
|
||||||
this.seek(-this.address, Math.floor(rowCount / 3));
|
|
||||||
|
// Do nothing while closed
|
||||||
|
if (!this.isVisible())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Working variables
|
||||||
|
gridHeight = gridHeight || this.hex.getBounds().height;
|
||||||
|
lineHeight = lineHeight || this.lineHeight();
|
||||||
|
let rowCount = this.lines(false, gridHeight, lineHeight);
|
||||||
|
|
||||||
// Request bus data from the WebAssembly core
|
// Request bus data from the WebAssembly core
|
||||||
this.debug.core.postMessage({
|
this.debug.core.postMessage({
|
||||||
|
@ -61,9 +82,9 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
||||||
|
|
||||||
// Configure elements
|
// Configure elements
|
||||||
for (let y = this.rows.length; y < rowCount; y++)
|
for (let y = this.rows.length; y < rowCount; y++)
|
||||||
this.rows[y] = new MemoryWindow.Row(this.client);
|
this.rows[y] = this.hex.add(new MemoryWindow.Row(this.hex));
|
||||||
for (let y = rowCount; y < this.rows.length; y++)
|
for (let y = rowCount; y < this.rows.length; y++)
|
||||||
this.rows[y].remove();
|
this.hex.remove(this.rows[y]);
|
||||||
if (this.rows.length > rowCount)
|
if (this.rows.length > rowCount)
|
||||||
this.rows.splice(rowCount, this.rows.length - rowCount);
|
this.rows.splice(rowCount, this.rows.length - rowCount);
|
||||||
}
|
}
|
||||||
|
@ -93,16 +114,22 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
||||||
|
|
||||||
///////////////////////////// Private Methods /////////////////////////////
|
///////////////////////////// Private Methods /////////////////////////////
|
||||||
|
|
||||||
|
// The window is being displayed for the first time
|
||||||
|
firstShow() {
|
||||||
|
super.firstShow();
|
||||||
|
this.center();
|
||||||
|
}
|
||||||
|
|
||||||
// Determine the height in pixels of one row of output
|
// Determine the height in pixels of one row of output
|
||||||
lineHeight() {
|
lineHeight() {
|
||||||
return Math.max(1, this.rows[0].address.getBounds().height);
|
return Math.max(10, this.rows[0].address.getBounds().height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the number of rows of output
|
// Determine the number of rows of output
|
||||||
lines(fullyVisible, clientHeight, lineHeight) {
|
lines(fullyVisible, gridHeight, lineHeight) {
|
||||||
clientHeight = clientHeight || this.client.getBounds().height;
|
gridHeight = gridHeight || this.client.getBounds().height;
|
||||||
lineHeight = lineHeight || this.lineHeight();
|
lineHeight = lineHeight || this.lineHeight();
|
||||||
let ret = clientHeight / lineHeight;
|
let ret = gridHeight / lineHeight;
|
||||||
ret = fullyVisible ? Math.floor(ret) : Math.ceil(ret);
|
ret = fullyVisible ? Math.floor(ret) : Math.ceil(ret);
|
||||||
return Math.max(1, ret);
|
return Math.max(1, ret);
|
||||||
}
|
}
|
||||||
|
@ -127,29 +154,24 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
||||||
|
|
||||||
// Processing by key
|
// Processing by key
|
||||||
else switch (e.key) {
|
else switch (e.key) {
|
||||||
|
|
||||||
case "ArrowDown":
|
case "ArrowDown":
|
||||||
this.address = (this.address + 16 & 0xFFFFFFFF) >>> 0;
|
this.address = (this.address + 16 & 0xFFFFFFFF) >>> 0;
|
||||||
this.refresh();
|
this.refresh();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "ArrowUp":
|
case "ArrowUp":
|
||||||
this.address = (this.address - 16 & 0xFFFFFFFF) >>> 0;
|
this.address = (this.address - 16 & 0xFFFFFFFF) >>> 0;
|
||||||
this.refresh();
|
this.refresh();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "PageUp":
|
case "PageUp":
|
||||||
this.address = (this.address - 16 * this.lines(true) &
|
this.address = (this.address - 16 * this.lines(true) &
|
||||||
0xFFFFFFFF) >>> 0;
|
0xFFFFFFFF) >>> 0;
|
||||||
this.refresh();
|
this.refresh();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "PageDown":
|
case "PageDown":
|
||||||
this.address = (this.address + 16 * this.lines(true) &
|
this.address = (this.address + 16 * this.lines(true) &
|
||||||
0xFFFFFFFF) >>> 0;
|
0xFFFFFFFF) >>> 0;
|
||||||
this.refresh();
|
this.refresh();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: return super.onkeydown(e);
|
default: return super.onkeydown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,6 +187,12 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
||||||
let mag = Math.abs (e.deltaY);
|
let mag = Math.abs (e.deltaY);
|
||||||
if (e.deltaMode == WheelEvent.DOM_DELTA_PIXEL)
|
if (e.deltaMode == WheelEvent.DOM_DELTA_PIXEL)
|
||||||
mag = Math.max(1, Math.floor(mag / lineHeight));
|
mag = Math.max(1, Math.floor(mag / lineHeight));
|
||||||
|
|
||||||
|
// Configure element
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Specify the new address
|
||||||
this.address = (this.address + sign * mag * 16 & 0xFFFFFFFF) >>> 0;
|
this.address = (this.address + sign * mag * 16 & 0xFFFFFFFF) >>> 0;
|
||||||
this.refresh(null, lineHeight);
|
this.refresh(null, lineHeight);
|
||||||
}
|
}
|
||||||
|
@ -177,23 +205,34 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
||||||
};
|
};
|
||||||
|
|
||||||
// One row of output
|
// One row of output
|
||||||
MemoryWindow.Row = class Row {
|
MemoryWindow.Row = class Row extends Toolkit.Panel {
|
||||||
|
|
||||||
// Object constructor
|
// Object constructor
|
||||||
constructor(client) {
|
constructor(parent) {
|
||||||
|
super(parent.application, {
|
||||||
|
layout : "grid",
|
||||||
|
columns : "repeat(17, max-content)",
|
||||||
|
hollow : false,
|
||||||
|
overflowX: "visible",
|
||||||
|
overflowY: "visible"
|
||||||
|
});
|
||||||
|
|
||||||
// Configure instance fields
|
// Configure instance fields
|
||||||
this.bytes = new Array(16);
|
this.bytes = new Array(16);
|
||||||
this.client = client;
|
|
||||||
|
// Configure element
|
||||||
|
this.element.setAttribute("role", "row");
|
||||||
|
|
||||||
// Address label
|
// Address label
|
||||||
this.address = client.add(client.newLabel({ text: "\u00a0" }));
|
this.address = this.add(parent.newLabel({ text: "\u00a0" }));
|
||||||
|
this.address.element.setAttribute("role", "gridcell");
|
||||||
this.address.element.setAttribute("name", "address");
|
this.address.element.setAttribute("name", "address");
|
||||||
|
|
||||||
// Byte labels
|
// Byte labels
|
||||||
for (let x = 0; x < 16; x++) {
|
for (let x = 0; x < 16; x++) {
|
||||||
let lbl = this.bytes[x] =
|
let lbl = this.bytes[x] =
|
||||||
client.add(client.newLabel({ text: "\u00a0" }));
|
this.add(parent.newLabel({ text: "\u00a0" }));
|
||||||
|
lbl.element.setAttribute("role", "gridcell");
|
||||||
lbl.element.setAttribute("name", "byte");
|
lbl.element.setAttribute("name", "byte");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,13 +242,6 @@ MemoryWindow.Row = class Row {
|
||||||
|
|
||||||
///////////////////////////// Package Methods /////////////////////////////
|
///////////////////////////// Package Methods /////////////////////////////
|
||||||
|
|
||||||
// Remove components from the memory window
|
|
||||||
remove() {
|
|
||||||
this.client.remove(this.address);
|
|
||||||
for (let bytel of this.bytes)
|
|
||||||
this.client.remove(bytel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the output labels with emulation state content
|
// Update the output labels with emulation state content
|
||||||
update(address, bytes, offset) {
|
update(address, bytes, offset) {
|
||||||
this.address.setText(
|
this.address.setText(
|
||||||
|
|
|
@ -0,0 +1,496 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// List item for CPU window register lists
|
||||||
|
(CPUWindow.Register = class Register extends Toolkit.Panel {
|
||||||
|
|
||||||
|
// Static initializer
|
||||||
|
static initializer() {
|
||||||
|
let buffer = new ArrayBuffer(4);
|
||||||
|
this.F32 = new Float32Array(buffer);
|
||||||
|
this.S32 = new Int32Array (buffer);
|
||||||
|
this.U32 = new Uint32Array (buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object constructor
|
||||||
|
constructor(debug, parent, system, id, name) {
|
||||||
|
super(parent.application, {
|
||||||
|
layout : "grid",
|
||||||
|
columns : "auto max-content",
|
||||||
|
overflowX: "visible",
|
||||||
|
overflowY: "visible"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.debug = debug;
|
||||||
|
this.expanded = false;
|
||||||
|
this.expansion = null;
|
||||||
|
this.fields = {};
|
||||||
|
this.format = "hex";
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.parent = parent;
|
||||||
|
this.system = system;
|
||||||
|
this.value = 0x00000000;
|
||||||
|
|
||||||
|
// Determine whether the register has expansion fields
|
||||||
|
this.expands = !system;
|
||||||
|
switch (id) {
|
||||||
|
case CPUWindow.CHCW:
|
||||||
|
case CPUWindow.ECR:
|
||||||
|
case CPUWindow.EIPSW:
|
||||||
|
case CPUWindow.FEPSW:
|
||||||
|
case CPUWindow.PC:
|
||||||
|
case CPUWindow.PIR:
|
||||||
|
case CPUWindow.PSW:
|
||||||
|
case CPUWindow.TKCW:
|
||||||
|
this.expands = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure element
|
||||||
|
this.element.setAttribute("name", "register");
|
||||||
|
this.element.setAttribute("format", this.format);
|
||||||
|
|
||||||
|
// Name/expansion "check box"
|
||||||
|
this.chkExpand = this.add(this.newCheckBox({
|
||||||
|
enabled: this.expands,
|
||||||
|
text : name
|
||||||
|
}));
|
||||||
|
this.chkExpand.element.setAttribute("name", "expand");
|
||||||
|
this.chkExpand.element.setAttribute("aria-expanded", "false");
|
||||||
|
this.chkExpand.addChangeListener(e=>
|
||||||
|
this.setExpanded(this.chkExpand.isChecked()));
|
||||||
|
|
||||||
|
// Value text box
|
||||||
|
this.txtValue = this.add(this.newTextBox({ text: "00000000" }));
|
||||||
|
this.txtValue.element.setAttribute("name", "value");
|
||||||
|
this.txtValue.addCommitListener(e=>this.onvalue());
|
||||||
|
|
||||||
|
// Expansion controls
|
||||||
|
if (!system)
|
||||||
|
this.expansionProgram();
|
||||||
|
else switch (id) {
|
||||||
|
case CPUWindow.CHCW : this.expansionCHCW(); break;
|
||||||
|
case CPUWindow.ECR : this.expansionECR (); break;
|
||||||
|
case CPUWindow.EIPSW:
|
||||||
|
case CPUWindow.FEPSW:
|
||||||
|
case CPUWindow.PSW : this.expansionPSW (); break;
|
||||||
|
case CPUWindow.PC : this.expansionPC (); break;
|
||||||
|
case CPUWindow.PIR : this.expansionPIR (); break;
|
||||||
|
case CPUWindow.TKCW : this.expansionTKCW(); break;
|
||||||
|
}
|
||||||
|
if (this.expands) {
|
||||||
|
this.chkExpand.element.setAttribute(
|
||||||
|
"aria-controls", this.expansion.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Static Methods //////////////////////////////
|
||||||
|
|
||||||
|
// Force a 32-bit integer to be signed
|
||||||
|
static asSigned(value) {
|
||||||
|
this.U32[0] = value >>> 0;
|
||||||
|
return this.S32[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpret a 32-bit integer as a float
|
||||||
|
static intBitsToFloat(value) {
|
||||||
|
this.U32[0] = value;
|
||||||
|
value = this.F32[0];
|
||||||
|
return Number.isFinite(value) ? value : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpret a float as a 32-bit integer
|
||||||
|
static floatToIntBits(value) {
|
||||||
|
if (!Number.isFinite(value))
|
||||||
|
return 0;
|
||||||
|
this.F32[0] = value;
|
||||||
|
return this.U32[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Package Methods /////////////////////////////
|
||||||
|
|
||||||
|
// Specify whether the expansion fields are visible
|
||||||
|
setExpanded(expanded) {
|
||||||
|
this.expanded = expanded = !!expanded;
|
||||||
|
this.expansion.setVisible(expanded);
|
||||||
|
this.chkExpand.setChecked(expanded);
|
||||||
|
this.chkExpand.element.setAttribute("aria-expanded", expanded);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify the display mode of the register value
|
||||||
|
setFormat(format) {
|
||||||
|
this.format = format;
|
||||||
|
this.setValue(this.value);
|
||||||
|
this.element.setAttribute("format", format.replace("_", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the value of the register
|
||||||
|
setValue(value, pcFrom, pcTo) {
|
||||||
|
this.value = value;
|
||||||
|
|
||||||
|
// Value text box
|
||||||
|
let text;
|
||||||
|
switch (this.format) {
|
||||||
|
case "float":
|
||||||
|
text = CPUWindow.Register.intBitsToFloat(value).toString();
|
||||||
|
if (text.indexOf(".") == -1)
|
||||||
|
text += ".0";
|
||||||
|
break;
|
||||||
|
case "hex":
|
||||||
|
text = ("0000000" +
|
||||||
|
(value >>> 0).toString(16).toUpperCase()).slice(-8);
|
||||||
|
break;
|
||||||
|
case "signed":
|
||||||
|
text = CPUWindow.Register.asSigned(value).toString();
|
||||||
|
break;
|
||||||
|
case "unsigned":
|
||||||
|
text = (value >>> 0).toString();
|
||||||
|
}
|
||||||
|
this.txtValue.setText(text);
|
||||||
|
|
||||||
|
// Expansion fields
|
||||||
|
for (let field of Object.values(this.fields)) {
|
||||||
|
switch (field.type) {
|
||||||
|
case "bit":
|
||||||
|
field.setChecked(value >> field.bit & 1);
|
||||||
|
break;
|
||||||
|
case "decimal":
|
||||||
|
field.setText(value >> field.bit & field.mask);
|
||||||
|
break;
|
||||||
|
case "hex":
|
||||||
|
let digits = Math.max(1, Math.ceil(field.width / 4));
|
||||||
|
field.setText(("0".repeat(digits) +
|
||||||
|
(value >> field.bit & field.mask)
|
||||||
|
.toString(16).toUpperCase()
|
||||||
|
).slice(-digits));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special fields for PC
|
||||||
|
if (pcFrom === undefined)
|
||||||
|
return;
|
||||||
|
this.txtFrom.setText(("0000000" +
|
||||||
|
(pcFrom >>> 0).toString(16).toUpperCase()).slice(-8));
|
||||||
|
this.txtTo .setText(("0000000" +
|
||||||
|
(pcTo >>> 0).toString(16).toUpperCase()).slice(-8));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Private Methods /////////////////////////////
|
||||||
|
|
||||||
|
// Add a field component to the expansion area
|
||||||
|
addField(type, name, bit, width, readonly) {
|
||||||
|
let field, label, panel;
|
||||||
|
|
||||||
|
// Processing by type
|
||||||
|
switch (type) {
|
||||||
|
|
||||||
|
// Bit
|
||||||
|
case "bit":
|
||||||
|
field = this.newCheckBox({
|
||||||
|
enabled: !readonly,
|
||||||
|
text : name
|
||||||
|
});
|
||||||
|
field.addChangeListener(e=>this.onbit(field));
|
||||||
|
this.expansion.add(field);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Decimal number
|
||||||
|
case "decimal":
|
||||||
|
|
||||||
|
// Field
|
||||||
|
field = this.newTextBox({
|
||||||
|
enabled: !readonly,
|
||||||
|
name : name
|
||||||
|
});
|
||||||
|
field.addCommitListener(e=>this.onnumber(field));
|
||||||
|
label = this.newLabel({
|
||||||
|
label: true,
|
||||||
|
text : name
|
||||||
|
});
|
||||||
|
label.element.htmlFor = field.id;
|
||||||
|
if (readonly)
|
||||||
|
label.element.setAttribute("aria-disabled", "true");
|
||||||
|
|
||||||
|
// Enclose in a panel
|
||||||
|
panel = this.newPanel({
|
||||||
|
layout : "flex",
|
||||||
|
alignCross: "center",
|
||||||
|
alignMain : "start",
|
||||||
|
direction : "row"
|
||||||
|
});
|
||||||
|
panel.element.setAttribute("name", name);
|
||||||
|
panel.add(label);
|
||||||
|
panel.add(field);
|
||||||
|
this.expansion.add(panel);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Hexadecimal number
|
||||||
|
case "hex":
|
||||||
|
field = this.newTextBox({
|
||||||
|
enabled: !readonly,
|
||||||
|
name : name
|
||||||
|
});
|
||||||
|
field.addCommitListener(e=>this.onnumber(field));
|
||||||
|
label = this.newLabel({
|
||||||
|
label: true,
|
||||||
|
text : name
|
||||||
|
});
|
||||||
|
label.element.htmlFor = field.id;
|
||||||
|
if (readonly)
|
||||||
|
label.element.setAttribute("aria-disabled", "true");
|
||||||
|
this.expansion.add(label);
|
||||||
|
this.expansion.add(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure field
|
||||||
|
field.bit = bit;
|
||||||
|
field.mask = (1 << width) - 1;
|
||||||
|
field.type = type;
|
||||||
|
field.width = width;
|
||||||
|
this.fields[name] = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expansion controls for CHCW
|
||||||
|
expansionCHCW() {
|
||||||
|
|
||||||
|
// Expansion area
|
||||||
|
this.expansion = this.newPanel({
|
||||||
|
layout : "block",
|
||||||
|
overflowX: "visible",
|
||||||
|
overflowY: "visible",
|
||||||
|
visible : false
|
||||||
|
});
|
||||||
|
this.expansion.element.setAttribute("name", "expansion");
|
||||||
|
this.expansion.element.setAttribute("register", "chcw");
|
||||||
|
|
||||||
|
// Fields
|
||||||
|
this.addField("bit", "ICE", 1, 1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expansion controls for ECR
|
||||||
|
expansionECR() {
|
||||||
|
|
||||||
|
// Expansion area
|
||||||
|
this.expansion = this.newPanel({
|
||||||
|
layout : "grid",
|
||||||
|
columns : "max-content auto",
|
||||||
|
overflowX: "visible",
|
||||||
|
overflowY: "visible",
|
||||||
|
visible : false
|
||||||
|
});
|
||||||
|
this.expansion.element.setAttribute("name", "expansion");
|
||||||
|
this.expansion.element.setAttribute("register", "ecr");
|
||||||
|
this.expansion.element.style.justifyContent = "start";
|
||||||
|
|
||||||
|
// Fields
|
||||||
|
this.addField("hex", "FECC", 16, 16, false);
|
||||||
|
this.addField("hex", "EICC", 0, 16, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expansion controls for PC
|
||||||
|
expansionPC() {
|
||||||
|
|
||||||
|
// Expansion area
|
||||||
|
this.expansion = this.newPanel({
|
||||||
|
layout : "grid",
|
||||||
|
columns : "auto max-content",
|
||||||
|
overflowX: "visible",
|
||||||
|
overflowY: "visible",
|
||||||
|
visible : false
|
||||||
|
});
|
||||||
|
this.expansion.element.setAttribute("name", "expansion");
|
||||||
|
|
||||||
|
// From text box
|
||||||
|
let lbl = this.expansion.add(this.newLabel({
|
||||||
|
localized: true,
|
||||||
|
text : "{cpu.pcFrom}"
|
||||||
|
}));
|
||||||
|
lbl.element.setAttribute("name", "indent");
|
||||||
|
this.txtFrom = this.expansion.add(this.newTextBox());
|
||||||
|
this.txtFrom.element.setAttribute("name", "value");
|
||||||
|
|
||||||
|
// To text box
|
||||||
|
lbl = this.expansion.add(this.newLabel({
|
||||||
|
localized: true,
|
||||||
|
text : "{cpu.pcTo}"
|
||||||
|
}));
|
||||||
|
lbl.element.setAttribute("name", "indent");
|
||||||
|
this.txtTo = this.expansion.add(this.newTextBox());
|
||||||
|
this.txtTo.element.setAttribute("name", "value");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expansion controls for PIR
|
||||||
|
expansionPIR() {
|
||||||
|
|
||||||
|
// Expansion area
|
||||||
|
this.expansion = this.newPanel({
|
||||||
|
layout : "grid",
|
||||||
|
columns : "max-content auto",
|
||||||
|
overflowX: "visible",
|
||||||
|
overflowY: "visible",
|
||||||
|
visible : false
|
||||||
|
});
|
||||||
|
this.expansion.element.setAttribute("name", "expansion");
|
||||||
|
this.expansion.element.setAttribute("register", "pir");
|
||||||
|
|
||||||
|
// Fields
|
||||||
|
this.addField("hex", "PT", 0, 16, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expansion controls for program registers
|
||||||
|
expansionProgram() {
|
||||||
|
|
||||||
|
// Expansion area
|
||||||
|
this.expansion = this.newPanel({
|
||||||
|
layout : "grid",
|
||||||
|
columns : "auto",
|
||||||
|
overflowX: "visible",
|
||||||
|
overflowY: "visible",
|
||||||
|
visible : false
|
||||||
|
});
|
||||||
|
this.expansion.element.setAttribute("role", "radiogroup");
|
||||||
|
this.expansion.element.setAttribute("name", "expansion");
|
||||||
|
this.expansion.element.setAttribute("register", "program");
|
||||||
|
this.expansion.element.style.justifyContent = "start";
|
||||||
|
|
||||||
|
// Format selections
|
||||||
|
let group = new Toolkit.ButtonGroup();
|
||||||
|
for (let opt of [ "hex", "signed", "unsigned", "float_" ]) {
|
||||||
|
let fmt = group.add(this.expansion.add(this.newRadioButton({
|
||||||
|
checked: opt == "hex",
|
||||||
|
text : "{cpu." + opt + "}",
|
||||||
|
})));
|
||||||
|
fmt.addChangeListener(
|
||||||
|
(opt=>e=>this.setFormat(opt))
|
||||||
|
(opt.replace("_", ""))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expansion controls for EIPSW, FEPSW and PSW
|
||||||
|
expansionPSW() {
|
||||||
|
|
||||||
|
// Expansion area
|
||||||
|
this.expansion = this.newPanel({
|
||||||
|
layout : "grid",
|
||||||
|
columns : "max-content auto",
|
||||||
|
overflowX: "visible",
|
||||||
|
overflowY: "visible",
|
||||||
|
visible : false
|
||||||
|
});
|
||||||
|
this.expansion.element.setAttribute("name", "expansion");
|
||||||
|
this.expansion.element.setAttribute("register", "psw");
|
||||||
|
this.expansion.element.style.justifyContent = "start";
|
||||||
|
|
||||||
|
// Fields
|
||||||
|
this.addField("bit" , "CY" , 3, 1, false);
|
||||||
|
this.addField("bit" , "FRO", 9, 1, false);
|
||||||
|
this.addField("bit" , "OV" , 2, 1, false);
|
||||||
|
this.addField("bit" , "FIV", 8, 1, false);
|
||||||
|
this.addField("bit" , "S" , 1, 1, false);
|
||||||
|
this.addField("bit" , "FZD", 7, 1, false);
|
||||||
|
this.addField("bit" , "Z" , 0, 1, false);
|
||||||
|
this.addField("bit" , "FOV", 6, 1, false);
|
||||||
|
this.addField("bit" , "NP" , 15, 1, false);
|
||||||
|
this.addField("bit" , "FUD", 5, 1, false);
|
||||||
|
this.addField("bit" , "EP" , 14, 1, false);
|
||||||
|
this.addField("bit" , "FPR", 4, 1, false);
|
||||||
|
this.addField("bit" , "ID" , 12, 1, false);
|
||||||
|
this.addField("decimal", "I" , 16, 4, false);
|
||||||
|
this.addField("bit" , "AE" , 13, 1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expansion controls for TKCW
|
||||||
|
expansionTKCW() {
|
||||||
|
|
||||||
|
// Expansion area
|
||||||
|
this.expansion = this.newPanel({
|
||||||
|
layout : "grid",
|
||||||
|
columns : "max-content auto",
|
||||||
|
overflowX: "visible",
|
||||||
|
overflowY: "visible",
|
||||||
|
visible : false
|
||||||
|
});
|
||||||
|
this.expansion.element.setAttribute("name", "expansion");
|
||||||
|
this.expansion.element.setAttribute("register", "tkcw");
|
||||||
|
this.expansion.element.style.justifyContent = "start";
|
||||||
|
|
||||||
|
// Fields
|
||||||
|
this.addField("bit" , "FIT", 7, 1, true);
|
||||||
|
this.addField("bit" , "FUT", 4, 1, true);
|
||||||
|
this.addField("bit" , "FZT", 6, 1, true);
|
||||||
|
this.addField("bit" , "FPT", 3, 1, true);
|
||||||
|
this.addField("bit" , "FVT", 5, 1, true);
|
||||||
|
this.addField("bit" , "OTM", 8, 1, true);
|
||||||
|
this.addField("bit" , "RDI", 2, 1, true);
|
||||||
|
this.addField("decimal", "RD" , 0, 2, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bit check box change event handler
|
||||||
|
onbit(field) {
|
||||||
|
let mask = 1 << field.bit;
|
||||||
|
let value = this.value;
|
||||||
|
if (field.isChecked())
|
||||||
|
value = (value | mask & 0xFFFFFFFF) >>> 0;
|
||||||
|
else value = (value & ~mask & 0xFFFFFFFF) >>> 0;
|
||||||
|
this.setRegister(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number text box commit event handler
|
||||||
|
onnumber(field) {
|
||||||
|
let value = parseInt(field.getText(),
|
||||||
|
field.type == "decimal" ? 10 : 16);
|
||||||
|
if (value == NaN)
|
||||||
|
value = this.value;
|
||||||
|
this.setRegister((
|
||||||
|
this.value & ~(field.mask << field.bit) |
|
||||||
|
(value & field.mask) << field.bit
|
||||||
|
) >>> 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value text box commit event handler
|
||||||
|
onvalue() {
|
||||||
|
|
||||||
|
// Process the entered value
|
||||||
|
let value = this.txtValue.getText();
|
||||||
|
switch (this.format) {
|
||||||
|
case "float":
|
||||||
|
value = parseFloat(value);
|
||||||
|
if (Number.isFinite(value))
|
||||||
|
value = CPUWindow.Register.floatToIntBits(value);
|
||||||
|
break;
|
||||||
|
case "hex":
|
||||||
|
value = parseInt(value, 16);
|
||||||
|
break;
|
||||||
|
case "signed":
|
||||||
|
case "unsigned":
|
||||||
|
value = parseInt(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the value
|
||||||
|
if (!Number.isFinite(value))
|
||||||
|
this.setValue(this.value);
|
||||||
|
else this.setRegister((value & 0xFFFFFFFF) >>> 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the value of the register
|
||||||
|
setRegister(value) {
|
||||||
|
this.debug.core.postMessage({
|
||||||
|
command: "SetRegister",
|
||||||
|
debug : "CPU",
|
||||||
|
id : this.id,
|
||||||
|
sim : this.debug.sim,
|
||||||
|
type : this.system ? this.id == -1 ? "pc" : "system" : "program",
|
||||||
|
value : value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}).initializer();
|
46
core/bus.c
46
core/bus.c
|
@ -1,15 +1,21 @@
|
||||||
/* This file is included into vb.c and cannot be compiled on its own. */
|
/* This file is included into vb.c and cannot be compiled on its own. */
|
||||||
#ifdef VBAPI
|
#ifdef VBAPI
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**************************** Component Functions ****************************/
|
|
||||||
|
|
||||||
/* Read a data unit from a memory buffer */
|
/* Read a data unit from a memory buffer */
|
||||||
static int32_t busReadMemory(uint8_t *mem, int type) {
|
static int32_t busReadMemory(uint8_t *mem, int type) {
|
||||||
|
|
||||||
|
/* Little-endian implementation */
|
||||||
|
#ifdef VB_LITTLEENDIAN
|
||||||
|
switch (type) {
|
||||||
|
case VB_S8 : return *(int8_t *)mem;
|
||||||
|
case VB_U8 : return * mem;
|
||||||
|
case VB_S16: return *(int16_t *)mem;
|
||||||
|
case VB_U16: return *(uint16_t *)mem;
|
||||||
|
}
|
||||||
|
return *(int32_t *)mem;
|
||||||
|
|
||||||
/* Generic implementation */
|
/* Generic implementation */
|
||||||
#ifdef VB_BIGENDIAN
|
#else
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case VB_S8 : return (int8_t) *mem;
|
case VB_S8 : return (int8_t) *mem;
|
||||||
case VB_U8 : return *mem;
|
case VB_U8 : return *mem;
|
||||||
|
@ -18,16 +24,6 @@ static int32_t busReadMemory(uint8_t *mem, int type) {
|
||||||
}
|
}
|
||||||
return (int32_t ) mem[3] << 24 | (uint32_t) mem[2] << 16 |
|
return (int32_t ) mem[3] << 24 | (uint32_t) mem[2] << 16 |
|
||||||
(uint32_t) mem[1] << 8 | mem[0];
|
(uint32_t) mem[1] << 8 | mem[0];
|
||||||
|
|
||||||
/* Little-endian implementation */
|
|
||||||
#else
|
|
||||||
switch (type) {
|
|
||||||
case VB_S8 : return *(int8_t *)mem;
|
|
||||||
case VB_U8 : return * mem;
|
|
||||||
case VB_S16: return *(int16_t *)mem;
|
|
||||||
case VB_U16: return *(uint16_t *)mem;
|
|
||||||
}
|
|
||||||
return *(int32_t *)mem;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -60,8 +56,16 @@ static int32_t busRead(VB *emu, uint32_t address, int type, int debug) {
|
||||||
/* Write a data unit to a memory buffer */
|
/* Write a data unit to a memory buffer */
|
||||||
static void busWriteMemory(uint8_t *mem, int type, int32_t value) {
|
static void busWriteMemory(uint8_t *mem, int type, int32_t value) {
|
||||||
|
|
||||||
|
/* Little-endian implementation */
|
||||||
|
#ifdef VB_LITTLEENDIAN
|
||||||
|
switch (type) {
|
||||||
|
case VB_S16: case VB_U16: *(uint16_t *)mem = value; return;
|
||||||
|
case VB_S8 : case VB_U8 : * mem = value; return;
|
||||||
|
}
|
||||||
|
*(int32_t *)mem = value;
|
||||||
|
|
||||||
/* Generic implementation */
|
/* Generic implementation */
|
||||||
#ifdef VB_BIGENDIAN
|
#else
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case VB_S32:
|
case VB_S32:
|
||||||
mem[3] = value >> 24;
|
mem[3] = value >> 24;
|
||||||
|
@ -72,14 +76,6 @@ static void busWriteMemory(uint8_t *mem, int type, int32_t value) {
|
||||||
mem[1] = value >> 8;
|
mem[1] = value >> 8;
|
||||||
}
|
}
|
||||||
mem[0] = value;
|
mem[0] = value;
|
||||||
|
|
||||||
/* Little-endian implementation */
|
|
||||||
#else
|
|
||||||
switch (type) {
|
|
||||||
case VB_S16: case VB_U16: *(uint16_t *)mem = value; return;
|
|
||||||
case VB_S8 : case VB_U8 : * mem = value; return;
|
|
||||||
}
|
|
||||||
*(int32_t *)mem = value;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -114,6 +110,4 @@ static void busWrite(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* VBAPI */
|
#endif /* VBAPI */
|
||||||
|
|
39
core/vb.c
39
core/vb.c
|
@ -29,8 +29,13 @@ static const uint8_t TYPE_SIZES[] = { 1, 1, 2, 2, 4 };
|
||||||
/******************************* API Functions *******************************/
|
/******************************* API Functions *******************************/
|
||||||
|
|
||||||
/* Retrieve the value of PC */
|
/* Retrieve the value of PC */
|
||||||
uint32_t vbGetProgramCounter(VB *emu) {
|
uint32_t vbGetProgramCounter(VB *emu, int type) {
|
||||||
return emu->cpu.pc;
|
switch (type) {
|
||||||
|
case VB_PC : return emu->cpu.pc;
|
||||||
|
case VB_PC_FROM: return emu->cpu.pcFrom;
|
||||||
|
case VB_PC_TO : return emu->cpu.pcTo;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Retrieve the value of a program register */
|
/* Retrieve the value of a program register */
|
||||||
|
@ -64,6 +69,7 @@ uint32_t vbGetSystemRegister(VB *emu, int id) {
|
||||||
case VB_PIR : return 0x00005346;
|
case VB_PIR : return 0x00005346;
|
||||||
case VB_TKCW : return 0x000000E0;
|
case VB_TKCW : return 0x000000E0;
|
||||||
case 29 : return emu->cpu.sr29;
|
case 29 : return emu->cpu.sr29;
|
||||||
|
case 30 : return 0x00000004;
|
||||||
case 31 : return emu->cpu.sr31;
|
case 31 : return emu->cpu.sr31;
|
||||||
case VB_ECR : return
|
case VB_ECR : return
|
||||||
(uint32_t) emu->cpu.ecr.fecc << 16 | emu->cpu.ecr.eicc;
|
(uint32_t) emu->cpu.ecr.fecc << 16 | emu->cpu.ecr.eicc;
|
||||||
|
@ -109,28 +115,29 @@ int32_t vbRead(VB *emu, uint32_t address, int type, int debug) {
|
||||||
|
|
||||||
/* Simulate a hardware reset */
|
/* Simulate a hardware reset */
|
||||||
void vbReset(VB *emu) {
|
void vbReset(VB *emu) {
|
||||||
uint32_t x;
|
uint32_t x; /* Iterator */
|
||||||
|
|
||||||
/* Initialize CPU registers */
|
/* CPU registers */
|
||||||
emu->cpu.pc = 0xFFFFFFF0;
|
emu->cpu.pc = 0xFFFFFFF0;
|
||||||
vbSetSystemRegister(emu, VB_ECR, 0x0000FFF0);
|
vbSetSystemRegister(emu, VB_ECR, 0x0000FFF0);
|
||||||
vbSetSystemRegister(emu, VB_PSW, 0x00008000);
|
vbSetSystemRegister(emu, VB_PSW, 0x00008000);
|
||||||
|
|
||||||
/* Initialize extra CPU registers (the hardware does not do this) */
|
/* Extra CPU registers (the hardware does not do this) */
|
||||||
for (x = 0; x < 32; x++)
|
for (x = 0; x < 32; x++)
|
||||||
emu->cpu.program[x] = 0;
|
emu->cpu.program[x] = 0x00000000;
|
||||||
emu->cpu.adtre = 0;
|
emu->cpu.adtre = 0x00000000;
|
||||||
emu->cpu.eipc = 0;
|
emu->cpu.eipc = 0x00000000;
|
||||||
emu->cpu.eipsw = 0;
|
emu->cpu.eipsw = 0x00000000;
|
||||||
emu->cpu.fepc = 0;
|
emu->cpu.fepc = 0x00000000;
|
||||||
emu->cpu.fepsw = 0;
|
emu->cpu.fepsw = 0x00000000;
|
||||||
emu->cpu.sr29 = 0;
|
emu->cpu.sr29 = 0x00000000;
|
||||||
emu->cpu.sr31 = 0;
|
emu->cpu.sr31 = 0x00000000;
|
||||||
|
emu->cpu.pcFrom = 0xFFFFFFF0;
|
||||||
|
emu->cpu.pcTo = 0xFFFFFFF0;
|
||||||
|
|
||||||
/* Erase WRAM (the hardware does not do this) */
|
/* WRAM (the hardware does not do this) */
|
||||||
for (x = 0; x < 0x10000; x++)
|
for (x = 0; x < 0x10000; x++)
|
||||||
emu->wram[x] = 0;
|
emu->wram[x] = 0x00;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Specify a new value for PC */
|
/* Specify a new value for PC */
|
||||||
|
|
13
core/vb.h
13
core/vb.h
|
@ -17,7 +17,7 @@ extern "C" {
|
||||||
|
|
||||||
/********************************* Constants *********************************/
|
/********************************* Constants *********************************/
|
||||||
|
|
||||||
/* Value types */
|
/* Memory access types */
|
||||||
#define VB_S8 0
|
#define VB_S8 0
|
||||||
#define VB_U8 1
|
#define VB_U8 1
|
||||||
#define VB_S16 2
|
#define VB_S16 2
|
||||||
|
@ -36,6 +36,11 @@ extern "C" {
|
||||||
#define VB_PSW 5
|
#define VB_PSW 5
|
||||||
#define VB_TKCW 7
|
#define VB_TKCW 7
|
||||||
|
|
||||||
|
/* PC types */
|
||||||
|
#define VB_PC 0
|
||||||
|
#define VB_PC_FROM 1
|
||||||
|
#define VB_PC_TO 2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*********************************** Types ***********************************/
|
/*********************************** Types ***********************************/
|
||||||
|
@ -96,11 +101,13 @@ struct VB {
|
||||||
|
|
||||||
/* Other registers */
|
/* Other registers */
|
||||||
uint32_t pc; /* Program counter */
|
uint32_t pc; /* Program counter */
|
||||||
|
uint32_t pcFrom; /* Source of most recent jump */
|
||||||
|
uint32_t pcTo; /* Destination of most recent jump */
|
||||||
int32_t program[32]; /* program registers */
|
int32_t program[32]; /* program registers */
|
||||||
|
|
||||||
/* Other fields */
|
/* Other fields */
|
||||||
uint32_t clocks; /* Clocks until next action */
|
uint32_t clocks; /* Clocks until next action */
|
||||||
uint8_t fatch; /* Index of fetch unit */
|
uint8_t fetch; /* Index of fetch unit */
|
||||||
uint8_t state; /* Operations state */
|
uint8_t state; /* Operations state */
|
||||||
} cpu;
|
} cpu;
|
||||||
|
|
||||||
|
@ -112,7 +119,7 @@ struct VB {
|
||||||
|
|
||||||
/**************************** Function Prototypes ****************************/
|
/**************************** Function Prototypes ****************************/
|
||||||
|
|
||||||
VBAPI uint32_t vbGetProgramCounter (VB *emu);
|
VBAPI uint32_t vbGetProgramCounter (VB *emu, int type);
|
||||||
VBAPI int32_t vbGetProgramRegister (VB *emu, int id);
|
VBAPI int32_t vbGetProgramRegister (VB *emu, int id);
|
||||||
VBAPI void* vbGetROM (VB *emu, uint32_t *size);
|
VBAPI void* vbGetROM (VB *emu, uint32_t *size);
|
||||||
VBAPI void* vbGetSRAM (VB *emu, uint32_t *size);
|
VBAPI void* vbGetSRAM (VB *emu, uint32_t *size);
|
||||||
|
|
6
makefile
6
makefile
|
@ -1,7 +1,7 @@
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help:
|
help:
|
||||||
@echo
|
@echo
|
||||||
@echo "Virtual Boy Emulator - August 30, 2021"
|
@echo "Virtual Boy Emulator - September 1, 2021"
|
||||||
@echo
|
@echo
|
||||||
@echo "Target build environment is any Debian with the following packages:"
|
@echo "Target build environment is any Debian with the following packages:"
|
||||||
@echo " emscripten"
|
@echo " emscripten"
|
||||||
|
@ -37,12 +37,12 @@ clean:
|
||||||
core:
|
core:
|
||||||
@gcc core/vb.c -I core \
|
@gcc core/vb.c -I core \
|
||||||
-fsyntax-only -Wall -Wextra -Werror -Wpedantic -std=c90
|
-fsyntax-only -Wall -Wextra -Werror -Wpedantic -std=c90
|
||||||
@gcc core/vb.c -I core -D VB_BIGENDIAN \
|
@gcc core/vb.c -I core -D VB_LITTLEENDIAN \
|
||||||
-fsyntax-only -Wall -Wextra -Werror -Wpedantic -std=c90
|
-fsyntax-only -Wall -Wextra -Werror -Wpedantic -std=c90
|
||||||
|
|
||||||
.PHONY: wasm
|
.PHONY: wasm
|
||||||
wasm:
|
wasm:
|
||||||
@emcc -o core.wasm wasm/wasm.c core/vb.c -Icore \
|
@emcc -o core.wasm wasm/wasm.c core/vb.c -Icore -D VB_LITTLEENDIAN \
|
||||||
--no-entry -O2 -flto -s WASM=1 -s EXPORTED_RUNTIME_METHODS=[] \
|
--no-entry -O2 -flto -s WASM=1 -s EXPORTED_RUNTIME_METHODS=[] \
|
||||||
-s ALLOW_MEMORY_GROWTH -s MAXIMUM_MEMORY=4GB -fno-strict-aliasing
|
-s ALLOW_MEMORY_GROWTH -s MAXIMUM_MEMORY=4GB -fno-strict-aliasing
|
||||||
@rm -f *.wasm.tmp*
|
@rm -f *.wasm.tmp*
|
||||||
|
|
|
@ -36,8 +36,8 @@ EMSCRIPTEN_KEEPALIVE void ReadBuffer(
|
||||||
//////////////////////////////// Core Commands ////////////////////////////////
|
//////////////////////////////// Core Commands ////////////////////////////////
|
||||||
|
|
||||||
// Retrieve the value of PC
|
// Retrieve the value of PC
|
||||||
EMSCRIPTEN_KEEPALIVE uint32_t GetProgramCounter(int sim) {
|
EMSCRIPTEN_KEEPALIVE uint32_t GetProgramCounter(int sim, int type) {
|
||||||
return vbGetProgramCounter(&sims[sim]);
|
return vbGetProgramCounter(&sims[sim], type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the value of a program register
|
// Retrieve the value of a program register
|
||||||
|
|
Loading…
Reference in New Issue