"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 = 0x05000000; this.debug = debug; this.rows = []; // Configure element this.element.setAttribute("window", "memory"); // Configure body this.body.element.setAttribute("filter", ""); // Configure client 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", ""); this.rows.push(this.hex.add(new MemoryWindow.Row(this.hex))); this.application.addComponent(this); } ///////////////////////////// Package Methods ///////////////////////////// // 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); } // 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({ 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] = this.hex.add(new MemoryWindow.Row(this.hex)); for (let y = rowCount; y < this.rows.length; y++) this.hex.remove(this.rows[y]); 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 ///////////////////////////// // 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(10, this.rows[0].address.getBounds().height); } // Determine the number of rows of output 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); } // Key down event handler onkeydown(e) { // Control is pressed if (e.ctrlKey) switch (e.key) { case "g": case "G": let addr = prompt(this.application.translate("{app.goto_}")); 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); } // 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)); // Configure element e.preventDefault(); e.stopPropagation(); // Specify the new address 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 extends Toolkit.Panel { // Object constructor 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); // Configure element this.element.setAttribute("role", "row"); // Address label 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] = this.add(parent.newLabel({ text: "\u00a0" })); lbl.element.setAttribute("role", "gridcell"); lbl.element.setAttribute("name", "byte"); } } ///////////////////////////// Package Methods ///////////////////////////// // 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)); } };