pvbemu/app/windows/MemoryWindow.js

223 lines
6.8 KiB
JavaScript
Raw Normal View History

2021-08-30 02:14:06 +00:00
"use strict";
// Hex editor style memory viewer
globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
// Object constructor
constructor(debug, options) {
super(debug.gui, options);
// Configure instance fields
this.address = 0xFFFFFFF0;
this.debug = debug;
this.rows = [new MemoryWindow.Row(this.client)];
// Configure element
this.element.setAttribute("window", "memory");
this.element.addEventListener("wheel", e=>this.onwheel(e));
2021-08-30 02:14:06 +00:00
// Configure client
this.client.setLayout("grid", { columns: "repeat(17, max-content)" });
2021-08-30 02:14:06 +00:00
this.client.element.style.gridAutoRows = "max-content";
this.client.setOverflow("auto", "hidden");
this.client.addResizeListener(b=>this.refresh(b.height));
// 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));
}
///////////////////////////// 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);
// Showing for the first time
if (this.address < 0)
this.seek(-this.address, Math.floor(rowCount / 3));
// Request bus data from the WebAssembly core
this.debug.core.postMessage({
command: "ReadBuffer",
debug : "Memory",
address: this.address,
sim : this.debug.sim,
size : rowCount * 16
});
// Configure elements
for (let y = this.rows.length; y < rowCount; y++)
this.rows[y] = new MemoryWindow.Row(this.client);
for (let y = rowCount; y < this.rows.length; y++)
this.rows[y].remove();
if (this.rows.length > rowCount)
this.rows.splice(rowCount, this.rows.length - rowCount);
}
///////////////////////////// Message Methods /////////////////////////////
// Message received
message(msg) {
switch (msg.command) {
case "ReadBuffer": this.readBuffer(msg); break;
}
}
// Received bytes from the bus
readBuffer(msg) {
let bytes = new Uint8Array(msg.buffer);
for (
let x = 0, address = this.address, offset = 0;
x < this.rows.length && offset < bytes.length;
x++, address = (address + 16 & 0xFFFFFFFF) >>> 0, offset += 16
) this.rows[x].update(address, bytes, offset);
}
///////////////////////////// Private Methods /////////////////////////////
// Determine the height in pixels of one row of output
lineHeight() {
2021-08-30 13:58:40 +00:00
return Math.max(1, this.rows[0].address.getBounds().height);
2021-08-30 02:14:06 +00:00
}
// 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);
2021-08-30 13:58:40 +00:00
return Math.max(1, ret);
2021-08-30 02:14:06 +00:00
}
// Key down event handler
onkeydown(e) {
// Control is pressed
if (e.ctrlKey) switch (e.key) {
case "g": case "G":
2021-08-30 13:58:40 +00:00
let addr = prompt(this.application.translate("{app.goto_}"));
2021-08-30 02:14:06 +00:00
if (addr === null)
break;
this.seek(
(parseInt(addr, 16) & 0xFFFFFFF0) >>> 0,
Math.floor(this.lines(true) / 3)
);
this.refresh();
break;
default: return;
}
// 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);
2021-08-30 02:14:06 +00:00
}
// Configure event
e.preventDefault();
e.stopPropagation();
}
// Mouse wheel event handler
onwheel(e) {
let lineHeight = this.lineHeight();
let sign = Math.sign(e.deltaY);
let mag = Math.abs (e.deltaY);
if (e.deltaMode == WheelEvent.DOM_DELTA_PIXEL)
mag = Math.max(1, Math.floor(mag / lineHeight));
this.address = (this.address + sign * mag * 16 & 0xFFFFFFFF) >>> 0;
this.refresh(null, lineHeight);
}
// Move to a new address positioned at a particular row of output
seek(address, index) {
this.address = (address - (index || 0) * 16 & 0xFFFFFFFF) >>> 0;
}
};
// One row of output
MemoryWindow.Row = class Row {
// Object constructor
constructor(client) {
// Configure instance fields
this.bytes = new Array(16);
this.client = client;
// Address label
this.address = client.add(client.newLabel({ text: "\u00a0" }));
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" }));
lbl.element.setAttribute("name", "byte");
}
}
///////////////////////////// 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(
("0000000" + address.toString(16).toUpperCase()).slice(-8));
for (let x = 0; x < 16; x++, offset++)
this.bytes[x].setText(
("0" + bytes[offset].toString(16).toUpperCase()).slice(-2));
}
};