"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.pending = { mode: null }; this.rows = []; // Configure element this.element.setAttribute("window", "memory"); // Configure body this.body.element.setAttribute("filter", ""); // Configure client this.client.setLayout("grid"); // 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, name : "{memory.hexEditor}", overflowX: "auto", overflowY: "hidden" })); this.hex.element.setAttribute("role", "grid"); this.hex.element.setAttribute("name", "hex"); this.hex.element.addEventListener("keydown", e=>this.onkeyhex(e)); this.hex.element.addEventListener("wheel" , e=>this.onwheel (e)); this.hex.addResizeListener(b=>this.onresize()); // Configure properties this.setProperty("sim", ""); this.rows.push(this.hex.add(new MemoryWindow.Row(this.hex))); this.application.addComponent(this); } ///////////////////////////// Public Methods ////////////////////////////// // Update the display with current emulation data refresh(address) { // Do nothing while closed or already waiting to refresh if (!this.isVisible() || this.pending.mode !== null) return; // Working variables address = address === undefined ? this.address : address; let lines = this.lines(false); // Configure pending state this.pending.mode = "first"; this.pending.address = null; this.pending.line = null; // Request bus data from the WebAssembly core this.debug.core.postMessage({ command: "ReadBuffer", sim : this.debug.sim, debug : "Memory", address: address, lines : lines, size : lines * 16 }); } // Specify whether the component is visible setVisible(visible, focus) { let prev = this.visible visible = !!visible; super.setVisible(visible, focus); if (visible && !prev) this.refresh(); } ///////////////////////////// Message Methods ///////////////////////////// // Message received message(msg) { switch (msg.command) { case "ReadBuffer": this.readBuffer(msg); break; } } // Received bytes from the bus readBuffer(msg) { let buffer = new Uint8Array(msg.buffer); let lines = Math.min(msg.lines, this.rows.length); // Configure instance fields this.address = msg.address; // Update display for ( let x = 0, address = msg.address, offset = 0; x < lines && offset < buffer.length; x++, address = (address + 16 & 0xFFFFFFF0) >>> 0, offset += 16 ) this.rows[x].update(address, buffer, offset); // Check for pending display updates let address = this.pending.address === null ? this.address : this.pending.address; let line = this.pending.line === null ? 0 : this.pending.line ; let mode = this.pending.mode; this.pending.mode = null; switch (mode) { case "first": case null : return; case "refresh": case "scroll" : case "seek" : this.refresh((address + line * 16 & 0xFFFFFFF0) >>> 0); } } ///////////////////////////// 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) { let gridHeight = this.hex.getBounds().height; let lineHeight = this.lineHeight(); let ret = gridHeight / lineHeight; ret = fullyVisible ? Math.floor(ret) : Math.ceil(ret); return Math.max(1, ret); } // Key down event handler onkeyhex(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) ); break; default: return; } // Processing by key else switch (e.key) { case "ArrowDown": this.scroll( 1 ); break; case "ArrowUp" : this.scroll(-1 ); break; case "PageDown" : this.scroll( this.lines(true)); break; case "PageUp" : this.scroll(-this.lines(true)); break; default : return; } // Configure event e.preventDefault(); e.stopPropagation(); } // Resize event handler onresize() { let lines = this.lines(false); for (let y = this.rows.length; y < lines; y++) this.rows[y] = this.hex.add(new MemoryWindow.Row(this.hex)); for (let y = lines; y < this.rows.length; y++) this.hex.remove(this.rows[y]); if (this.rows.length > lines) this.rows.splice(lines, this.rows.length - lines); this.refresh(); } // Mouse wheel event handler onwheel(e) { 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 / this.lineHeight())); // Configure element e.preventDefault(); e.stopPropagation(); // Configure display this.scroll(sign * mag); } // Move to a new address relative to the current address scroll(lines) { switch (this.pending.mode) { case "first" : case "refresh": this.pending.mode = "scroll"; this.pending.line = lines; break; case "scroll": case "seek" : this.pending.mode = "scroll"; this.pending.line += lines; break; case null: this.refresh((this.address + lines * 16 & 0xFFFFFFF0) >>> 0); } } // Move to a new address positioned at a particular row of output seek(address, line) { switch (this.pending.mode) { case "first" : case "refresh": this.pending.mode = "seek"; this.pending.address = address; this.pending.line = line; break; case "scroll": case "seek" : this.pending.mode = "seek"; this.pending.address = address; this.pending.line += line; break; case null: this.refresh((address - line * 16 & 0xFFFFFFF0) >>> 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)); } };