Adding registers panes of CPU window
This commit is contained in:
parent
3dc8b93f94
commit
ff07e1907f
|
@ -9,3 +9,4 @@
|
|||
*.class binary
|
||||
*.dll binary
|
||||
*.wasm binary
|
||||
*.woff2 binary
|
||||
|
|
|
@ -90,6 +90,9 @@ globalThis.App = class App {
|
|||
item = menu.newMenuItem({ text: "{memory._}" });
|
||||
item.addClickListener(
|
||||
()=>this.debuggers[0].memory.setVisible(true, true));
|
||||
item = menu.newMenuItem({ text: "{cpu._}" });
|
||||
item.addClickListener(
|
||||
()=>this.debuggers[0].cpu.setVisible(true, true));
|
||||
|
||||
// Theme menu
|
||||
menu = this.mainMenu.newMenu({ text: "{menu.theme._}" });
|
||||
|
@ -159,11 +162,12 @@ globalThis.App = class App {
|
|||
return;
|
||||
}
|
||||
|
||||
// Send the ROM to the WebAssembly core module
|
||||
this.core.postMessage({
|
||||
command: "SetROM",
|
||||
rom : file,
|
||||
sim : 0
|
||||
});
|
||||
}, file);
|
||||
}
|
||||
|
||||
// Specify the current color theme
|
||||
|
|
|
@ -12,16 +12,25 @@ globalThis.Debugger = class Debugger {
|
|||
this.gui = app.gui;
|
||||
this.sim = sim;
|
||||
|
||||
// Configure Memory window
|
||||
// Memory window
|
||||
this.memory = new MemoryWindow(this, {
|
||||
title : "{sim}{memory._}",
|
||||
center : true,
|
||||
height : 300,
|
||||
visible: false,
|
||||
width : 400
|
||||
});
|
||||
this.memory.addCloseListener(e=>this.memory.setVisible(false));
|
||||
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(msg) {
|
||||
switch (msg.debug) {
|
||||
case "CPU" : this.cpu .message(msg); break;
|
||||
case "Memory": this.memory.message(msg); break;
|
||||
}
|
||||
}
|
||||
|
||||
// Reload all output
|
||||
refresh() {
|
||||
this.cpu .refresh();
|
||||
this.memory.refresh();
|
||||
}
|
||||
|
||||
|
|
|
@ -25,12 +25,38 @@
|
|||
// Message received
|
||||
onmessage(msg) {
|
||||
switch (msg.command) {
|
||||
case "Init" : this.init (msg); break;
|
||||
case "ReadBuffer": this.readBuffer(msg); break;
|
||||
case "SetROM" : this.setROM (msg); break;
|
||||
case "GetRegisters": this.getRegisters(msg); break;
|
||||
case "Init" : this.init (msg); break;
|
||||
case "ReadBuffer" : this.readBuffer (msg); break;
|
||||
case "SetRegister" : this.setRegister (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
|
||||
async init(msg) {
|
||||
|
||||
|
@ -48,13 +74,29 @@
|
|||
// Read multiple data units from the bus
|
||||
readBuffer(msg) {
|
||||
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(
|
||||
buffer.pointer, buffer.pointer + msg.size);
|
||||
this.free(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
|
||||
setROM(msg) {
|
||||
let rom = new Uint8Array(msg.rom);
|
||||
|
|
24
app/_boot.js
24
app/_boot.js
|
@ -52,14 +52,15 @@ globalThis.Bundle = class BundledFile {
|
|||
|
||||
// Detect the MIME type
|
||||
this.mime =
|
||||
name.endsWith(".css" ) ? "text/css;charset=UTF-8" :
|
||||
name.endsWith(".frag") ? "text/plain;charset=UTF-8" :
|
||||
name.endsWith(".js" ) ? "text/javascript;charset=UTF-8" :
|
||||
name.endsWith(".png" ) ? "image/png" :
|
||||
name.endsWith(".svg" ) ? "image/svg+xml;charset=UTF-8" :
|
||||
name.endsWith(".txt" ) ? "text/plain;charset=UTF-8" :
|
||||
name.endsWith(".vert") ? "text/plain;charset=UTF-8" :
|
||||
name.endsWith(".wasm") ? "application/wasm" :
|
||||
name.endsWith(".css" ) ? "text/css;charset=UTF-8" :
|
||||
name.endsWith(".frag" ) ? "text/plain;charset=UTF-8" :
|
||||
name.endsWith(".js" ) ? "text/javascript;charset=UTF-8" :
|
||||
name.endsWith(".png" ) ? "image/png" :
|
||||
name.endsWith(".svg" ) ? "image/svg+xml;charset=UTF-8" :
|
||||
name.endsWith(".txt" ) ? "text/plain;charset=UTF-8" :
|
||||
name.endsWith(".vert" ) ? "text/plain;charset=UTF-8" :
|
||||
name.endsWith(".wasm" ) ? "application/wasm" :
|
||||
name.endsWith(".woff2") ? "font/woff2" :
|
||||
"application/octet-stream"
|
||||
;
|
||||
|
||||
|
@ -304,11 +305,18 @@ let run = async function() {
|
|||
await Bundle.run("app/toolkit/Panel.js");
|
||||
await Bundle.run("app/toolkit/Application.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/MenuBar.js");
|
||||
await Bundle.run("app/toolkit/MenuItem.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/windows/CPUWindow.js");
|
||||
await Bundle.run("app/windows/Register.js");
|
||||
await Bundle.run("app/windows/MemoryWindow.js");
|
||||
await App.create();
|
||||
};
|
||||
|
|
|
@ -9,8 +9,21 @@
|
|||
romNotVB : "The selected file is not a Virtual Boy ROM.",
|
||||
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",
|
||||
hexEditor: "Hex viewer"
|
||||
},
|
||||
menu: {
|
||||
_ : "Main application menu",
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,17 +1,23 @@
|
|||
:root {
|
||||
--control : #222222;
|
||||
--control-focus : #444444;
|
||||
--control-shadow : #999999;
|
||||
--control-text : #cccccc;
|
||||
--desktop : #111111;
|
||||
--window-blur : #555555;
|
||||
--window-blur-text : #cccccc;
|
||||
--window-close-blur : #998080;
|
||||
--window-close-blur-border : #999999;
|
||||
--window-close-blur-text : #ffffff;
|
||||
--window-close-focus : #ee9999;
|
||||
--window-close-focus-border: #999999;
|
||||
--window-close-focus-text : #ffffff;
|
||||
--window-focus : #007ACC;
|
||||
--window-focus-text : #ffffff;
|
||||
--close : #ee9999;
|
||||
--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-text : #cccccc;
|
||||
--desktop : #111111;
|
||||
--splitter-focus : #0099ff99;
|
||||
--title : #007ACC;
|
||||
--title-blur : #555555;
|
||||
--title-blur-text : #cccccc;
|
||||
--title-text : #ffffff;
|
||||
--window : #222222;
|
||||
--window-border : #cccccc;
|
||||
--window-disabled : #888888;
|
||||
--window-text : #cccccc;
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
:root {
|
||||
--font-dialog: "Roboto-Regular", sans-serif;
|
||||
--font-hex : "Inconsolata_SemiExpanded-Regular", monospace;
|
||||
--font-hex : "Inconsolata_SemiExpanded-Medium", monospace;
|
||||
--font-size : 12px;
|
||||
}
|
||||
|
||||
|
@ -13,8 +13,15 @@
|
|||
}
|
||||
|
||||
body {
|
||||
margin : 0;
|
||||
overflow: hidden;
|
||||
background: var(--window);
|
||||
overflow : hidden;
|
||||
}
|
||||
|
||||
* {
|
||||
background: transparent;
|
||||
border : none;
|
||||
margin : 0;
|
||||
padding : 0;
|
||||
}
|
||||
|
||||
*:focus {
|
||||
|
@ -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 **********************************/
|
||||
|
||||
|
@ -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 ***********************************/
|
||||
|
||||
[role="dialog"] {
|
||||
/*background: #cc0000;*/
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
[role="dialog"] [name="body"] {
|
||||
background: var(--control);
|
||||
box-shadow:
|
||||
0 0 0 1px var(--control),
|
||||
0 0 0 2px var(--control-text),
|
||||
1px 1px 0 2px var(--control-text)
|
||||
0 0 0 2px var(--window-border),
|
||||
1px 1px 0 2px var(--window-border)
|
||||
;
|
||||
row-gap : 3px;
|
||||
}
|
||||
|
||||
[role="dialog"] [name="title-bar"] {
|
||||
min-height: calc(1em + 5px);
|
||||
}
|
||||
|
||||
[role="dialog"][focus="true"] [name="title-bar"] {
|
||||
box-shadow:
|
||||
0 0 0 1px var(--window-focus),
|
||||
0 1px 0 1px var(--control-shadow)
|
||||
align-items: center;
|
||||
background : var(--title);
|
||||
column-gap : 1px;
|
||||
padding : 0 0 1px 0;
|
||||
margin : 0 0 2px 0;
|
||||
box-shadow :
|
||||
-0.5px -0.5px 0 0.5px var(--title ),
|
||||
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"] {
|
||||
background: var(--title-blur);
|
||||
box-shadow:
|
||||
0 0 0 1px var(--window-blur),
|
||||
0 1px 0 1px var(--control-shadow)
|
||||
-0.5px -0.5px 0 0.5px var(--title-blur ),
|
||||
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-close-box"] {
|
||||
align-self : stretch;
|
||||
min-width : calc(1em + 5px);
|
||||
width : calc(1em + 5px);
|
||||
[role="dialog"] [name="title-bar"] [name="icon"] {
|
||||
height: 15px;
|
||||
width : 15px;
|
||||
}
|
||||
|
||||
[role="dialog"] [name="title"] {
|
||||
color : var(--window-focus-text);
|
||||
font-weight : bold;
|
||||
line-height : calc(1em + 1px);
|
||||
overflow : hidden;
|
||||
padding : 2px;
|
||||
text-align : center;
|
||||
text-overflow: ellipsis;
|
||||
white-space : nowrap;
|
||||
[role="dialog"] [name="title-bar"] [name="title"] {
|
||||
color : var(--title-text);
|
||||
font-size : var(--font-size);
|
||||
font-weight: bold;
|
||||
text-align : center;
|
||||
}
|
||||
|
||||
[role="dialog"][focus="false"] [name="title"] {
|
||||
color: var(--window-blur-text);
|
||||
[role="dialog"][focus="false"] [name="title-bar"] [name="title"] {
|
||||
color: var(--title-blur-text);
|
||||
}
|
||||
|
||||
[role="dialog"] [name="title-close-box"] {
|
||||
align-items : center;
|
||||
display : flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
[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';
|
||||
[role="dialog"] [name="title-bar"] [name="close"] {
|
||||
background : var(--close);
|
||||
border : 1px solid var(--close-border);
|
||||
box-sizing : border-box;
|
||||
box-shadow : none;
|
||||
color : var(--close-text);
|
||||
font-size : 13px;
|
||||
font-weight: bold;
|
||||
height : 15px;
|
||||
line-height: 13px;
|
||||
margin : 0;
|
||||
padding : 0;
|
||||
position : relative;
|
||||
text-align : center;
|
||||
width : 15px;
|
||||
}
|
||||
|
||||
[role="dialog"] [name="title-close"][active]:after {
|
||||
margin: 1px -1px -1px 1px;
|
||||
[role="dialog"][focus="false"] [name="title-bar"] [name="close"] {
|
||||
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"] {
|
||||
background: var(--control);
|
||||
box-shadow: 0 0 0 1px var(--control);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/******************************* Memory Window *******************************/
|
||||
|
||||
[window="memory"] [name="client"] {
|
||||
align-items: start;
|
||||
column-gap : calc(var(--font-size) / 2);
|
||||
[role="dialog"][window="memory"] [name="wrap-hex"] {
|
||||
background : var(--window);
|
||||
box-shadow : 0 0 0 1px var(--control-shadow);
|
||||
font-family: var(--font-hex);
|
||||
margin : 1px;
|
||||
padding : 1px;
|
||||
}
|
||||
|
||||
[window="memory"] [name="client"] > *:not(:nth-child(1n+1)) {
|
||||
text-align: center;
|
||||
[role="dialog"][window="memory"] [name="hex"] [role="row"] {
|
||||
column-gap: calc(var(--font-size) / 2);
|
||||
}
|
||||
|
||||
[window="memory"] [name="client"] > *:nth-child(17n+2),
|
||||
[window="memory"] [name="client"] > *:nth-child(17n+10) {
|
||||
[role="dialog"][window="memory"] [name="hex"] [role="row"] > *:nth-child( 2),
|
||||
[role="dialog"][window="memory"] [name="hex"] [role="row"] > *:nth-child(10) {
|
||||
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"] {
|
||||
align-self: start;
|
||||
[role="dialog"][window="cpu"] [name="wrap-main"] {
|
||||
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 {
|
||||
--control : #eeeeee;
|
||||
--control-focus : #cccccc;
|
||||
--control-shadow : #999999;
|
||||
--control-text : #000000;
|
||||
--desktop : #cccccc;
|
||||
--window-blur : #cccccc;
|
||||
--window-blur-text : #444444;
|
||||
--window-close-blur : #d4c4c4;
|
||||
--window-close-blur-border : #999999;
|
||||
--window-close-blur-text : #ffffff;
|
||||
--window-close-focus : #ee9999;
|
||||
--window-close-focus-border: #999999;
|
||||
--window-close-focus-text : #ffffff;
|
||||
--window-focus : #80ccff;
|
||||
--window-focus-text : #000000;
|
||||
--close : #ee9999;
|
||||
--close-blur : #d4c4c4;
|
||||
--close-blur-border: #999999;
|
||||
--close-blur-text : #ffffff;
|
||||
--close-border : #999999;
|
||||
--close-text : #ffffff;
|
||||
--control : #eeeeee;
|
||||
--control-disabled : #888888;
|
||||
--control-focus : #cccccc;
|
||||
--control-shadow : #999999;
|
||||
--control-text : #000000;
|
||||
--desktop : #cccccc;
|
||||
--splitter-focus : #0099ff99;
|
||||
--title : #80ccff;
|
||||
--title-blur : #cccccc;
|
||||
--title-blur-text : #444444;
|
||||
--title-text : #000000;
|
||||
--window : #ffffff;
|
||||
--window-border : #000000;
|
||||
--window-disabled : #aaaaaa;
|
||||
--window-text : #000000;
|
||||
}
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
:root {
|
||||
--control : #000000;
|
||||
--control-focus : #550000;
|
||||
--control-shadow : #aa0000;
|
||||
--control-text : #ff0000;
|
||||
--desktop : #000000;
|
||||
--window-blur : #000000;
|
||||
--window-blur-text : #aa0000;
|
||||
--window-close-blur : #550000;
|
||||
--window-close-blur-border : #aa0000;
|
||||
--window-close-blur-text : #aa0000;
|
||||
--window-close-focus : #aa0000;
|
||||
--window-close-focus-border: #ff0000;
|
||||
--window-close-focus-text : #ff0000;
|
||||
--window-focus : #550000;
|
||||
--window-focus-text : #ff0000;
|
||||
--close : #aa0000;
|
||||
--close-blur : #550000;
|
||||
--close-blur-border: #aa0000;
|
||||
--close-blur-text : #aa0000;
|
||||
--close-border : #ff0000;
|
||||
--close-text : #ff0000;
|
||||
--control : #000000;
|
||||
--control-disabled : #aa0000;
|
||||
--control-focus : #550000;
|
||||
--control-shadow : #aa0000;
|
||||
--control-text : #ff0000;
|
||||
--desktop : #000000;
|
||||
--splitter-focus : #ff000099;
|
||||
--title : #550000;
|
||||
--title-blur : #000000;
|
||||
--title-blur-text : #aa0000;
|
||||
--title-text : #ff0000;
|
||||
--window : #000000;
|
||||
--window-border : #ff0000;
|
||||
--window-disabled : #aa0000;
|
||||
--window-text : #ff0000;
|
||||
}
|
||||
|
||||
[filter="true"] {
|
||||
[filter] {
|
||||
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
|
||||
onpropagation(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
for (let listener of this.propagationListeners)
|
||||
listener(e, this);
|
||||
|
|
|
@ -45,43 +45,51 @@ Toolkit.Button = class Button extends Toolkit.Component {
|
|||
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
|
||||
focus() {
|
||||
this.element.focus();
|
||||
}
|
||||
|
||||
// Retrieve the button's accessible name
|
||||
// Retrieve the component's accessible name
|
||||
getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
// Retrieve the button's display text
|
||||
// Retrieve the component's display text
|
||||
getText() {
|
||||
return this.text;
|
||||
}
|
||||
|
||||
// Retrieve the button's tool tip text
|
||||
// Retrieve the component's tool tip text
|
||||
getToolTip() {
|
||||
return this.toolTip;
|
||||
}
|
||||
|
||||
// Determine whether the button is enabled
|
||||
// Determine whether the component is enabled
|
||||
isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
// Determine whether the button is focusable
|
||||
// Determine whether the component is focusable
|
||||
isFocusable() {
|
||||
return this.focusable;
|
||||
}
|
||||
|
||||
// Specify whether the button is enabled
|
||||
// Specify whether the component is enabled
|
||||
setEnabled(enabled) {
|
||||
this.enabled = enabled = !!enabled;
|
||||
this.element.setAttribute("aria-disabled", !enabled);
|
||||
}
|
||||
|
||||
// Specify whether the button can receive focus
|
||||
// Specify whether the component can receive focus
|
||||
setFocusable(focusable) {
|
||||
this.focusable = focusable = !!focusable;
|
||||
if (focusable)
|
||||
|
@ -89,19 +97,19 @@ Toolkit.Button = class Button extends Toolkit.Component {
|
|||
else this.element.removeAttribute("tabindex");
|
||||
}
|
||||
|
||||
// Specify the button's accessible name
|
||||
// Specify the component's accessible name
|
||||
setName(name) {
|
||||
this.name = name || "";
|
||||
this.localize();
|
||||
}
|
||||
|
||||
// Specify the button's display text
|
||||
// Specify the component's display text
|
||||
setText(text) {
|
||||
this.text = text || "";
|
||||
this.localize();
|
||||
}
|
||||
|
||||
// Specify the button's tool tip text
|
||||
// Specify the component's tool tip text
|
||||
setToolTip(toolTip) {
|
||||
this.toolTip = toolTip || "";
|
||||
this.localize();
|
||||
|
@ -133,14 +141,6 @@ Toolkit.Button = class Button extends Toolkit.Component {
|
|||
|
||||
///////////////////////////// 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
|
||||
onkeydown(e) {
|
||||
|
||||
|
@ -152,7 +152,7 @@ Toolkit.Button = class Button extends Toolkit.Component {
|
|||
switch (e.key) {
|
||||
case " ":
|
||||
case "Enter":
|
||||
this.activate(e);
|
||||
this.click(e);
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
|
@ -166,7 +166,6 @@ Toolkit.Button = class Button extends Toolkit.Component {
|
|||
onpointerdown(e) {
|
||||
|
||||
// Configure event
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Configure focus
|
||||
|
@ -221,11 +220,11 @@ Toolkit.Button = class Button extends Toolkit.Component {
|
|||
// Configure element
|
||||
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"))
|
||||
return;
|
||||
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
|
||||
this.element.component = this;
|
||||
this.setSize(
|
||||
"width" in options ? options.width : null,
|
||||
"height" in options ? options.height : null
|
||||
);
|
||||
this.setVisible(this.visible);
|
||||
}
|
||||
|
||||
|
@ -58,11 +62,11 @@ Toolkit.Component = class Component {
|
|||
}
|
||||
|
||||
// Specify the location and size of the component
|
||||
setBounds(left, top, width, height) {
|
||||
setBounds(left, top, width, height, minimum) {
|
||||
this.setLeft (left );
|
||||
this.setTop (top );
|
||||
this.setWidth (width );
|
||||
this.setHeight(height);
|
||||
this.setWidth (width , minimum);
|
||||
this.setHeight(height, minimum);
|
||||
}
|
||||
|
||||
// Specify the display CSS property of the visible element
|
||||
|
@ -71,12 +75,19 @@ Toolkit.Component = class Component {
|
|||
this.setVisible(this.visible);
|
||||
}
|
||||
|
||||
// Specify the height of the element
|
||||
setHeight(height) {
|
||||
if (height === null)
|
||||
// Specify the height of the component
|
||||
setHeight(height, minimum) {
|
||||
if (height === null) {
|
||||
this.element.style.removeProperty("min-height");
|
||||
this.element.style.removeProperty("height");
|
||||
else this.element.style.height =
|
||||
typeof height == "number" ? height + "px" : height
|
||||
} else {
|
||||
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
|
||||
|
@ -100,9 +111,9 @@ Toolkit.Component = class Component {
|
|||
}
|
||||
|
||||
// Specify both the width and the height of the component
|
||||
setSize(width, height) {
|
||||
this.setHeight(height);
|
||||
this.setWidth (width );
|
||||
setSize(width, height, minimum) {
|
||||
this.setHeight(height, minimum);
|
||||
this.setWidth (width , minimum);
|
||||
}
|
||||
|
||||
// Specify the vertical position of the component
|
||||
|
@ -123,12 +134,19 @@ Toolkit.Component = class Component {
|
|||
} else this.element.style.display = "none";
|
||||
}
|
||||
|
||||
// Specify the width of the element
|
||||
setWidth(width) {
|
||||
if (width === null)
|
||||
// Specify the width of the component
|
||||
setWidth(width, minimum) {
|
||||
if (width === null) {
|
||||
this.element.style.removeProperty("min-width");
|
||||
this.element.style.removeProperty("width");
|
||||
else this.element.style.width =
|
||||
typeof width == "number" ? width + "px" : width ;
|
||||
} else {
|
||||
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
|
||||
constructor(application, options) {
|
||||
super(application, "div", options);
|
||||
super(application, options&&options.label ? "label" : "div", options);
|
||||
options = options || {};
|
||||
|
||||
// Configure instance fields
|
||||
this.focusable = "focusable" in options ? !!options.focusable : false;
|
||||
this.localized = "localized" in options ? !!options.localized : false;
|
||||
this.text = options.text || "";
|
||||
|
||||
|
@ -17,7 +18,8 @@ Toolkit.Label = class Label extends Toolkit.Component {
|
|||
this.element.style.userSelect = "none";
|
||||
|
||||
// Configure properties
|
||||
this.setText(this.text);
|
||||
this.setFocusable(this.focusable);
|
||||
this.setText (this.text);
|
||||
if (this.localized)
|
||||
this.application.addComponent(this);
|
||||
}
|
||||
|
@ -26,17 +28,43 @@ Toolkit.Label = class Label extends Toolkit.Component {
|
|||
|
||||
///////////////////////////// Public Methods //////////////////////////////
|
||||
|
||||
// Request focus on the appropriate element
|
||||
focus() {
|
||||
if (this.focusable)
|
||||
this.element.focus();
|
||||
}
|
||||
|
||||
// Retrieve the label's display text
|
||||
getText() {
|
||||
return this.text;
|
||||
}
|
||||
|
||||
// Determine whether the component is focusable
|
||||
isFocusable() {
|
||||
return this.focusable;
|
||||
}
|
||||
|
||||
// Specify the label's display text
|
||||
setText(text) {
|
||||
this.text = text || "";
|
||||
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 /////////////////////////////
|
||||
|
|
|
@ -15,7 +15,7 @@ Toolkit.MenuBar = class MenuBar extends Toolkit.Panel {
|
|||
|
||||
// Configure element
|
||||
this.element.style.position = "relative";
|
||||
this.element.style.zIndex = "1";
|
||||
this.element.style.zIndex = "2";
|
||||
this.element.setAttribute("role", "menubar");
|
||||
this.setLayout("flex", {
|
||||
direction: "row",
|
||||
|
|
|
@ -15,22 +15,23 @@ Toolkit.Panel = class Panel extends Toolkit.Component {
|
|||
this.children = [];
|
||||
this.columns = null;
|
||||
this.direction = "row";
|
||||
this.focusable = "focusable" in options ? !!options.focusable:false;
|
||||
this.hollow = "hollow" in options ? !!options.hollow : true;
|
||||
this.layout = null;
|
||||
this.name = options.name || "";
|
||||
this.overflowX = options.overflowX || "hidden";
|
||||
this.overflowY = options.overflowY || "hidden";
|
||||
this.rows = null;
|
||||
this.wrap = false;
|
||||
|
||||
// Configure element
|
||||
if ("noShrink" in options ? !options.noShrink : true) {
|
||||
this.element.style.minHeight = "0";
|
||||
this.element.style.minWidth = "0";
|
||||
}
|
||||
this.setOverflow(this.overflowX, this.overflowY);
|
||||
|
||||
// Configure layout
|
||||
options = options || {};
|
||||
this.setLayout(options.layout || null, options);
|
||||
// Configure properties
|
||||
this.setFocusable(this.focusable);
|
||||
this.setHollow (this.hollow);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
newButton(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
|
||||
newLabel(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);
|
||||
}
|
||||
|
||||
// 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
|
||||
newWindow(options) {
|
||||
return new Toolkit.Window(this.application, options);
|
||||
|
@ -115,6 +157,31 @@ Toolkit.Panel = class Panel extends Toolkit.Component {
|
|||
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
|
||||
setLayout(layout, options) {
|
||||
|
||||
|
@ -122,6 +189,7 @@ Toolkit.Panel = class Panel extends Toolkit.Component {
|
|||
this.layout = layout;
|
||||
|
||||
// Processing by layout
|
||||
options = options || {};
|
||||
switch (layout) {
|
||||
case "block" : this.setBlockLayout (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
|
||||
setOverflow(x, y) {
|
||||
this.overflowX = x || "hidden";
|
||||
this.overflowY = y || "hidden";
|
||||
this.element.style.overflow = this.overflowX + " " + this.overflowY;
|
||||
this.element.style.overflowX = this.overflowX = x || "hidden";
|
||||
this.element.style.overflowY = this.overflowY = y || this.overflowX;
|
||||
}
|
||||
|
||||
// Specify the semantic role of the panel
|
||||
|
@ -153,7 +227,15 @@ Toolkit.Panel = class Panel extends Toolkit.Component {
|
|||
// Move a window to the foreground
|
||||
bringToFront(wnd) {
|
||||
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
|
||||
this.closeListeners = [];
|
||||
this.dragBounds = null;
|
||||
this.dragClient = null;
|
||||
this.dragCursor = { x: 0, y: 0 };
|
||||
this.dragEdge = null;
|
||||
this.dragPointer = null;
|
||||
this.initialCenter = "center" in options ? !!options.center : false;
|
||||
this.initialHeight = options.height || 64;
|
||||
this.initialWidth = options.width || 64;
|
||||
this.lastFocus = this.element;
|
||||
this.shown = this.visible;
|
||||
this.title = options.title || "";
|
||||
|
||||
// Configure element
|
||||
this.setLayout("flex", {
|
||||
alignCross: "stretch",
|
||||
direction : "column",
|
||||
overflowX : "visible",
|
||||
overflowY : "visible"
|
||||
});
|
||||
this.setLayout("grid", { columns: "auto" });
|
||||
this.setRole("dialog");
|
||||
this.setBounds(0, 0, 64, 64);
|
||||
this.setLocation(0, 0);
|
||||
this.element.style.position = "absolute";
|
||||
this.element.setAttribute("aria-modal", "false");
|
||||
this.element.setAttribute("focus" , "false");
|
||||
this.element.setAttribute("tabindex" , "-1" );
|
||||
this.element.setAttribute("tabindex" , "0" );
|
||||
this.element.addEventListener(
|
||||
"blur" , e=>this.onblur (e), { capture: true });
|
||||
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("pointerup" , e=>this.onpointerup (e));
|
||||
|
||||
// Configure body container
|
||||
// Primary visible container
|
||||
this.body = this.add(this.newPanel({
|
||||
layout : "flex",
|
||||
alignCross: "stretch",
|
||||
direction : "column",
|
||||
overflowX : "visible",
|
||||
overflowY : "visible"
|
||||
layout : "grid",
|
||||
overflowX: "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");
|
||||
|
||||
// Configure title bar
|
||||
// Title bar
|
||||
this.titleBar = this.body.add(this.newPanel({
|
||||
layout : "flex",
|
||||
alignCross: "center",
|
||||
direction : "row",
|
||||
noShrink : true,
|
||||
overflowX : "visible",
|
||||
overflowY : "visible"
|
||||
layout : "grid",
|
||||
columns: "max-content auto max-content",
|
||||
hollow : false
|
||||
}));
|
||||
this.titleBar.element.setAttribute("name", "title-bar");
|
||||
|
||||
// Configure title icon element
|
||||
this.titleIcon = this.titleBar.add(this.newPanel({}));
|
||||
this.titleIcon.element.setAttribute("name", "title-icon");
|
||||
this.titleIcon.element.style.removeProperty("min-width");
|
||||
// Title bar icon
|
||||
this.titleIcon = this.titleBar.add(this.newPanel());
|
||||
this.titleIcon.element.setAttribute("name", "icon");
|
||||
this.titleIcon.element.setAttribute("aria-hidden", "true");
|
||||
|
||||
// Configure title text element
|
||||
this.titleElement = this.titleBar.add(this.newLabel({}));
|
||||
this.titleElement.element.setAttribute("name", "title");
|
||||
this.titleElement.element.style.cursor = "default";
|
||||
this.titleElement.element.style.flexGrow = "1";
|
||||
this.titleElement.element.style.userSelect = "none";
|
||||
this.element.setAttribute("aria-labelledby", this.titleElement.id);
|
||||
|
||||
// Configure title close element
|
||||
this.titleCloseBox = this.titleBar.add(this.newPanel({}));
|
||||
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}"
|
||||
// Title bar title
|
||||
this.title = this.titleBar.add(this.newLabel({
|
||||
localized: true,
|
||||
text : options.title
|
||||
}));
|
||||
this.titleClose.element.setAttribute("name", "title-close");
|
||||
this.title.element.setAttribute("name", "title");
|
||||
this.title.setProperty("sim", "");
|
||||
|
||||
// Title bar close
|
||||
this.titleClose = this.titleBar.add(this.newButton({
|
||||
toolTip: "{app.close}"
|
||||
}));
|
||||
this.titleClose.element.setAttribute("name", "close");
|
||||
this.titleClose.element.setAttribute("aria-hidden", "true");
|
||||
this.titleClose.addClickListener(e=>this.onclose(e));
|
||||
|
||||
// Configure client area
|
||||
this.client = this.body.add(this.newPanel({
|
||||
overflowX: "hidden",
|
||||
overflowY: "hidden"
|
||||
}));
|
||||
this.client.element.style.flexGrow = "1";
|
||||
// Client area
|
||||
this.client = this.body.add(this.newPanel());
|
||||
this.client.element.setAttribute("name", "client");
|
||||
this.client.element.addEventListener(
|
||||
"pointerdown", e=>this.onclientdown(e));
|
||||
|
||||
// Configure properties
|
||||
this.setTitle(this.title);
|
||||
if (this.shown)
|
||||
this.setClientSize(this.initialHeight, this.initialWidth);
|
||||
application.addComponent(this);
|
||||
this.setSize(options.width, options.height);
|
||||
}
|
||||
|
||||
|
||||
|
@ -117,20 +90,19 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
|||
this.closeListeners.push(listener);
|
||||
}
|
||||
|
||||
// Specify the size of the client rectangle in pixels
|
||||
setClientSize(width, height) {
|
||||
let bounds = this.getBounds();
|
||||
let client = this.client.getBounds();
|
||||
this.setSize(
|
||||
width + bounds.width - client.width,
|
||||
height + bounds.height - client.height
|
||||
);
|
||||
// Retrieve the window's title text
|
||||
getTitle() {
|
||||
return this.title.getText();
|
||||
}
|
||||
|
||||
// Specify the height of the component
|
||||
setHeight(height, minimum) {
|
||||
this.client && this.client.setHeight(height, minimum);
|
||||
}
|
||||
|
||||
// Specify the window's title text
|
||||
setTitle(title) {
|
||||
this.title = title || "";
|
||||
this.localize();
|
||||
this.title.setText(title);
|
||||
}
|
||||
|
||||
// Specify whether the component is visible
|
||||
|
@ -146,6 +118,12 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
|||
this.firstShow();
|
||||
}
|
||||
|
||||
// Specify the width of the component
|
||||
setWidth(width, minimum) {
|
||||
this.client && this.client.setWidth(
|
||||
Math.max(64, width || 0), minimum);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Package Methods /////////////////////////////
|
||||
|
@ -155,13 +133,23 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
|||
if (this.lastFocus != this)
|
||||
this.lastFocus.focus();
|
||||
else this.element.focus();
|
||||
this.parent.bringToFront(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// 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
|
||||
contain(x, y, desktop, bounds, client) {
|
||||
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
|
||||
firstShow() {
|
||||
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
|
||||
|
@ -243,13 +209,6 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
|||
this.element.setAttribute("focus", "false");
|
||||
}
|
||||
|
||||
// Client pointer down event handler
|
||||
onclientdown(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.focus();
|
||||
}
|
||||
|
||||
// Window close
|
||||
onclose(e) {
|
||||
for (let listener of this.closeListeners)
|
||||
|
@ -258,52 +217,45 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
|||
|
||||
// Focus gained event capture
|
||||
onfocus(e) {
|
||||
|
||||
// Configure element
|
||||
this.element.setAttribute("focus", "true");
|
||||
|
||||
// Working variables
|
||||
let target = e.target;
|
||||
|
||||
// Delegate focus to the most recently focused component
|
||||
if (target == this.element)
|
||||
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;
|
||||
|
||||
// Transfer focus to the correct component
|
||||
if (target != e.target)
|
||||
target.focus();
|
||||
this.parent.bringToFront(this);
|
||||
}
|
||||
|
||||
// Key down event handler
|
||||
// Key pressed event handler
|
||||
onkeydown(e) {
|
||||
|
||||
// Processing by key
|
||||
switch (e.key) {
|
||||
default: return;
|
||||
}
|
||||
// Only listening for Escape while dragging
|
||||
if (e.key != "Escape" || this.dragPointer === null)
|
||||
return;
|
||||
|
||||
// Configure event
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Restore the window's position
|
||||
let desktop = this.parent.getBounds();
|
||||
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
|
||||
onpointerdown(e) {
|
||||
|
||||
// Configure event
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Configure element
|
||||
this.focus();
|
||||
|
||||
// Error checking
|
||||
if (
|
||||
e.button != 0 ||
|
||||
|
@ -312,12 +264,19 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
|||
|
||||
// Configure instance fields
|
||||
this.dragBounds = this.getBounds();
|
||||
this.dragEdge = this.edge(e, this.dragBounds);
|
||||
this.dragClient = this.client.getBounds();
|
||||
this.dragCursor.x = e.x;
|
||||
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
|
||||
this.element.setPointerCapture(e.pointerId);
|
||||
this.dragPointer = e.pointerId;
|
||||
}
|
||||
|
||||
// Pointer move event handler
|
||||
|
@ -337,12 +296,11 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
|||
}
|
||||
|
||||
// Working variables
|
||||
let rX = e.x - this.dragCursor.x;
|
||||
let rY = e.y - this.dragCursor.y;
|
||||
let bounds = this.getBounds();
|
||||
let desktop = this.parent.getBounds();
|
||||
let client = this.client.getBounds();
|
||||
let minHeight = bounds.height - client.height;
|
||||
let rX = e.x - this.dragCursor.x;
|
||||
let rY = e.y - this.dragCursor.y;
|
||||
let bounds = this.getBounds();
|
||||
let desktop = this.parent.getBounds();
|
||||
let client = this.client.getBounds();
|
||||
|
||||
// Move the window
|
||||
if (this.dragEdge == null) {
|
||||
|
@ -358,7 +316,7 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
|||
if (this.dragEdge.startsWith("n")) {
|
||||
let maxTop = desktop.height - client.y + bounds.y;
|
||||
let top = this.dragBounds.y - desktop.y + rY;
|
||||
let height = this.dragBounds.height - rY;
|
||||
let height = this.dragClient.height - rY;
|
||||
|
||||
// Restrict window bounds
|
||||
if (top > maxTop) {
|
||||
|
@ -369,9 +327,9 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
|||
height += top;
|
||||
top = 0;
|
||||
}
|
||||
if (height < minHeight) {
|
||||
top -= minHeight - height;
|
||||
height = minHeight;
|
||||
if (height < 0) {
|
||||
top += height;
|
||||
height = 0;
|
||||
}
|
||||
|
||||
// Configure element
|
||||
|
@ -383,7 +341,7 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
|||
if (this.dragEdge.endsWith("w")) {
|
||||
let maxLeft = desktop.width - 16;
|
||||
let left = this.dragBounds.x - desktop.x + rX;
|
||||
let width = this.dragBounds.width - rX;
|
||||
let width = this.dragClient.width - rX;
|
||||
|
||||
// Restrict window bounds
|
||||
if (left > maxLeft) {
|
||||
|
@ -402,11 +360,11 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
|||
|
||||
// Resizing on the east edge
|
||||
if (this.dragEdge.endsWith("e")) {
|
||||
let width = this.dragBounds.width + rX;
|
||||
let width = this.dragClient.width + rX;
|
||||
|
||||
// Restrict window bounds
|
||||
width = Math.max(64, width);
|
||||
width = Math.max(width, -this.dragBounds.x + 16);
|
||||
width = Math.max(width, -this.dragClient.x + 16);
|
||||
|
||||
// Configure element
|
||||
this.setWidth(width);
|
||||
|
@ -414,10 +372,10 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
|||
|
||||
// Resizing on the south edge
|
||||
if (this.dragEdge.startsWith("s")) {
|
||||
let height = this.dragBounds.height + rY;
|
||||
let height = this.dragClient.height + rY;
|
||||
|
||||
// Restrict window bounds
|
||||
height = Math.max(minHeight, height);
|
||||
height = Math.max(0, height);
|
||||
|
||||
// Configure element
|
||||
this.setHeight(height);
|
||||
|
@ -438,6 +396,7 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
|||
|
||||
// Configure element
|
||||
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);
|
||||
|
||||
// Configure instance fields
|
||||
this.address = 0xFFFFFFF0;
|
||||
this.address = 0x05000000;
|
||||
this.debug = debug;
|
||||
this.rows = [new MemoryWindow.Row(this.client)];
|
||||
this.rows = [];
|
||||
|
||||
// Configure element
|
||||
this.element.setAttribute("window", "memory");
|
||||
this.element.addEventListener("wheel", e=>this.onwheel(e));
|
||||
|
||||
// Configure body
|
||||
this.body.element.setAttribute("filter", "");
|
||||
|
||||
// Configure client
|
||||
this.client.setLayout("grid", { columns: "repeat(17, max-content)" });
|
||||
this.client.element.style.gridAutoRows = "max-content";
|
||||
this.client.setOverflow("auto", "hidden");
|
||||
this.client.setLayout("grid");
|
||||
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
|
||||
this.setProperty("sim", "");
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Public Methods //////////////////////////////
|
||||
|
||||
// The window is being displayed for the first time
|
||||
firstShow() {
|
||||
super.firstShow();
|
||||
this.seek(this.address, Math.floor(this.lines(true) / 3));
|
||||
this.rows.push(this.hex.add(new MemoryWindow.Row(this.hex)));
|
||||
this.application.addComponent(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Package Methods /////////////////////////////
|
||||
|
||||
// Update the display with current emulation data
|
||||
refresh(clientHeight, lineHeight) {
|
||||
clientHeight = clientHeight || this.client.getBounds().height;
|
||||
lineHeight = lineHeight || this.lineHeight();
|
||||
let rowCount = this.lines(false, clientHeight, lineHeight);
|
||||
// Update display text with localized strings
|
||||
localize() {
|
||||
let hex = "";
|
||||
if (this.application)
|
||||
hex = this.application.translate("{memory.hexEditor}");
|
||||
this.hex.element.setAttribute("aria-label", hex);
|
||||
}
|
||||
|
||||
// Showing for the first time
|
||||
if (this.address < 0)
|
||||
this.seek(-this.address, Math.floor(rowCount / 3));
|
||||
// Update the display with current emulation data
|
||||
refresh(gridHeight, lineHeight) {
|
||||
|
||||
// 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
|
||||
this.debug.core.postMessage({
|
||||
|
@ -61,9 +82,9 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|||
|
||||
// Configure elements
|
||||
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++)
|
||||
this.rows[y].remove();
|
||||
this.hex.remove(this.rows[y]);
|
||||
if (this.rows.length > rowCount)
|
||||
this.rows.splice(rowCount, this.rows.length - rowCount);
|
||||
}
|
||||
|
@ -93,17 +114,23 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|||
|
||||
///////////////////////////// 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
|
||||
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
|
||||
lines(fullyVisible, clientHeight, lineHeight) {
|
||||
clientHeight = clientHeight || this.client.getBounds().height;
|
||||
lineHeight = lineHeight || this.lineHeight();
|
||||
let ret = clientHeight / lineHeight;
|
||||
ret = fullyVisible ? Math.floor(ret) : Math.ceil(ret);
|
||||
lines(fullyVisible, gridHeight, lineHeight) {
|
||||
gridHeight = gridHeight || this.client.getBounds().height;
|
||||
lineHeight = lineHeight || this.lineHeight();
|
||||
let ret = gridHeight / lineHeight;
|
||||
ret = fullyVisible ? Math.floor(ret) : Math.ceil(ret);
|
||||
return Math.max(1, ret);
|
||||
}
|
||||
|
||||
|
@ -127,29 +154,24 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|||
|
||||
// Processing by key
|
||||
else switch (e.key) {
|
||||
|
||||
case "ArrowDown":
|
||||
this.address = (this.address + 16 & 0xFFFFFFFF) >>> 0;
|
||||
this.refresh();
|
||||
break;
|
||||
|
||||
case "ArrowUp":
|
||||
this.address = (this.address - 16 & 0xFFFFFFFF) >>> 0;
|
||||
this.refresh();
|
||||
break;
|
||||
|
||||
case "PageUp":
|
||||
this.address = (this.address - 16 * this.lines(true) &
|
||||
0xFFFFFFFF) >>> 0;
|
||||
this.refresh();
|
||||
break;
|
||||
|
||||
case "PageDown":
|
||||
this.address = (this.address + 16 * this.lines(true) &
|
||||
0xFFFFFFFF) >>> 0;
|
||||
this.refresh();
|
||||
break;
|
||||
|
||||
default: return super.onkeydown(e);
|
||||
}
|
||||
|
||||
|
@ -165,6 +187,12 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|||
let mag = Math.abs (e.deltaY);
|
||||
if (e.deltaMode == WheelEvent.DOM_DELTA_PIXEL)
|
||||
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.refresh(null, lineHeight);
|
||||
}
|
||||
|
@ -177,23 +205,34 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|||
};
|
||||
|
||||
// One row of output
|
||||
MemoryWindow.Row = class Row {
|
||||
MemoryWindow.Row = class Row extends Toolkit.Panel {
|
||||
|
||||
// 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
|
||||
this.bytes = new Array(16);
|
||||
this.client = client;
|
||||
this.bytes = new Array(16);
|
||||
|
||||
// Configure element
|
||||
this.element.setAttribute("role", "row");
|
||||
|
||||
// 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");
|
||||
|
||||
// Byte labels
|
||||
for (let x = 0; x < 16; 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");
|
||||
}
|
||||
|
||||
|
@ -203,13 +242,6 @@ MemoryWindow.Row = class Row {
|
|||
|
||||
///////////////////////////// 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(address, bytes, offset) {
|
||||
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. */
|
||||
#ifdef VBAPI
|
||||
|
||||
|
||||
|
||||
/**************************** Component Functions ****************************/
|
||||
|
||||
/* Read a data unit from a memory buffer */
|
||||
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 */
|
||||
#ifdef VB_BIGENDIAN
|
||||
#else
|
||||
switch (type) {
|
||||
case VB_S8 : return (int8_t) *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 |
|
||||
(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
|
||||
|
||||
}
|
||||
|
@ -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 */
|
||||
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 */
|
||||
#ifdef VB_BIGENDIAN
|
||||
#else
|
||||
switch (type) {
|
||||
case VB_S32:
|
||||
mem[3] = value >> 24;
|
||||
|
@ -72,14 +76,6 @@ static void busWriteMemory(uint8_t *mem, int type, int32_t value) {
|
|||
mem[1] = value >> 8;
|
||||
}
|
||||
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
|
||||
|
||||
}
|
||||
|
@ -114,6 +110,4 @@ static void busWrite(
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#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 *******************************/
|
||||
|
||||
/* Retrieve the value of PC */
|
||||
uint32_t vbGetProgramCounter(VB *emu) {
|
||||
return emu->cpu.pc;
|
||||
uint32_t vbGetProgramCounter(VB *emu, int type) {
|
||||
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 */
|
||||
|
@ -64,6 +69,7 @@ uint32_t vbGetSystemRegister(VB *emu, int id) {
|
|||
case VB_PIR : return 0x00005346;
|
||||
case VB_TKCW : return 0x000000E0;
|
||||
case 29 : return emu->cpu.sr29;
|
||||
case 30 : return 0x00000004;
|
||||
case 31 : return emu->cpu.sr31;
|
||||
case VB_ECR : return
|
||||
(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 */
|
||||
void vbReset(VB *emu) {
|
||||
uint32_t x;
|
||||
uint32_t x; /* Iterator */
|
||||
|
||||
/* Initialize CPU registers */
|
||||
/* CPU registers */
|
||||
emu->cpu.pc = 0xFFFFFFF0;
|
||||
vbSetSystemRegister(emu, VB_ECR, 0x0000FFF0);
|
||||
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++)
|
||||
emu->cpu.program[x] = 0;
|
||||
emu->cpu.adtre = 0;
|
||||
emu->cpu.eipc = 0;
|
||||
emu->cpu.eipsw = 0;
|
||||
emu->cpu.fepc = 0;
|
||||
emu->cpu.fepsw = 0;
|
||||
emu->cpu.sr29 = 0;
|
||||
emu->cpu.sr31 = 0;
|
||||
emu->cpu.program[x] = 0x00000000;
|
||||
emu->cpu.adtre = 0x00000000;
|
||||
emu->cpu.eipc = 0x00000000;
|
||||
emu->cpu.eipsw = 0x00000000;
|
||||
emu->cpu.fepc = 0x00000000;
|
||||
emu->cpu.fepsw = 0x00000000;
|
||||
emu->cpu.sr29 = 0x00000000;
|
||||
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++)
|
||||
emu->wram[x] = 0;
|
||||
|
||||
emu->wram[x] = 0x00;
|
||||
}
|
||||
|
||||
/* Specify a new value for PC */
|
||||
|
|
13
core/vb.h
13
core/vb.h
|
@ -17,7 +17,7 @@ extern "C" {
|
|||
|
||||
/********************************* Constants *********************************/
|
||||
|
||||
/* Value types */
|
||||
/* Memory access types */
|
||||
#define VB_S8 0
|
||||
#define VB_U8 1
|
||||
#define VB_S16 2
|
||||
|
@ -36,6 +36,11 @@ extern "C" {
|
|||
#define VB_PSW 5
|
||||
#define VB_TKCW 7
|
||||
|
||||
/* PC types */
|
||||
#define VB_PC 0
|
||||
#define VB_PC_FROM 1
|
||||
#define VB_PC_TO 2
|
||||
|
||||
|
||||
|
||||
/*********************************** Types ***********************************/
|
||||
|
@ -96,11 +101,13 @@ struct VB {
|
|||
|
||||
/* Other registers */
|
||||
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 */
|
||||
|
||||
/* Other fields */
|
||||
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 */
|
||||
} cpu;
|
||||
|
||||
|
@ -112,7 +119,7 @@ struct VB {
|
|||
|
||||
/**************************** 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 void* vbGetROM (VB *emu, uint32_t *size);
|
||||
VBAPI void* vbGetSRAM (VB *emu, uint32_t *size);
|
||||
|
|
6
makefile
6
makefile
|
@ -1,7 +1,7 @@
|
|||
.PHONY: help
|
||||
help:
|
||||
@echo
|
||||
@echo "Virtual Boy Emulator - August 30, 2021"
|
||||
@echo "Virtual Boy Emulator - September 1, 2021"
|
||||
@echo
|
||||
@echo "Target build environment is any Debian with the following packages:"
|
||||
@echo " emscripten"
|
||||
|
@ -37,12 +37,12 @@ clean:
|
|||
core:
|
||||
@gcc core/vb.c -I core \
|
||||
-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
|
||||
|
||||
.PHONY: 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=[] \
|
||||
-s ALLOW_MEMORY_GROWTH -s MAXIMUM_MEMORY=4GB -fno-strict-aliasing
|
||||
@rm -f *.wasm.tmp*
|
||||
|
|
|
@ -36,8 +36,8 @@ EMSCRIPTEN_KEEPALIVE void ReadBuffer(
|
|||
//////////////////////////////// Core Commands ////////////////////////////////
|
||||
|
||||
// Retrieve the value of PC
|
||||
EMSCRIPTEN_KEEPALIVE uint32_t GetProgramCounter(int sim) {
|
||||
return vbGetProgramCounter(&sims[sim]);
|
||||
EMSCRIPTEN_KEEPALIVE uint32_t GetProgramCounter(int sim, int type) {
|
||||
return vbGetProgramCounter(&sims[sim], type);
|
||||
}
|
||||
|
||||
// Retrieve the value of a program register
|
||||
|
|
Loading…
Reference in New Issue