Compare commits
	
		
			No commits in common. "master" and "old-web" have entirely different histories.
		
	
	
		| 
						 | 
				
			
			@ -0,0 +1,214 @@
 | 
			
		|||
import java.awt.image.*;
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.nio.charset.*;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import javax.imageio.*;
 | 
			
		||||
 | 
			
		||||
public class Bundle {
 | 
			
		||||
 | 
			
		||||
    // File loaded from disk
 | 
			
		||||
    static class File2 implements Comparable<File2> {
 | 
			
		||||
 | 
			
		||||
        // Instance fields
 | 
			
		||||
        byte[] data;     // Contents
 | 
			
		||||
        String filename; // Full path relative to root
 | 
			
		||||
 | 
			
		||||
        // Comparator
 | 
			
		||||
        public int compareTo(File2 o) {
 | 
			
		||||
            if (filename.equals("app/_boot.js"))
 | 
			
		||||
                return -1;
 | 
			
		||||
            if (o.filename.equals("app/_boot.js"))
 | 
			
		||||
                return 1;
 | 
			
		||||
            return filename.compareTo(o.filename);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Load all files in directory tree into memory
 | 
			
		||||
    static HashMap<String, File2> readFiles(String bundleTitle) {
 | 
			
		||||
        var dirs  = new ArrayDeque<File>();
 | 
			
		||||
        var root  = new File(".");
 | 
			
		||||
        var subs  = new ArrayDeque<String>();
 | 
			
		||||
        var files = new HashMap<String, File2>();
 | 
			
		||||
 | 
			
		||||
        // Process all subdirectories
 | 
			
		||||
        dirs.add(root);
 | 
			
		||||
        while (!dirs.isEmpty()) {
 | 
			
		||||
            var dir = dirs.remove();
 | 
			
		||||
 | 
			
		||||
            // Add all subdirectories
 | 
			
		||||
            for (var sub : dir.listFiles(f->f.isDirectory())) {
 | 
			
		||||
 | 
			
		||||
                // Exclusions
 | 
			
		||||
                if (dir == root && sub.getName().equals(".git"))
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
                // Add the directory for bundling
 | 
			
		||||
                dirs.add(sub);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Add all files
 | 
			
		||||
            for (var file : dir.listFiles(f->f.isFile())) {
 | 
			
		||||
                var file2 = new File2();
 | 
			
		||||
 | 
			
		||||
                // Read the file into memory
 | 
			
		||||
                try {
 | 
			
		||||
                    var stream = new FileInputStream(file);
 | 
			
		||||
                    file2.data = stream.readAllBytes();
 | 
			
		||||
                    stream.close();
 | 
			
		||||
                } catch (Exception e) {
 | 
			
		||||
                    throw new RuntimeException(e.getMessage());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Determine the file's full pathname
 | 
			
		||||
                subs.clear();
 | 
			
		||||
                subs.addFirst(file.getName());
 | 
			
		||||
                for (;;) {
 | 
			
		||||
                    file = file.getParentFile();
 | 
			
		||||
                    if (file.equals(root))
 | 
			
		||||
                        break;
 | 
			
		||||
                    subs.addFirst(file.getName());
 | 
			
		||||
                }
 | 
			
		||||
                file2.filename = String.join("/", subs);
 | 
			
		||||
 | 
			
		||||
                // Exclusions
 | 
			
		||||
                if (
 | 
			
		||||
                    file2.filename.startsWith(".git"           ) ||
 | 
			
		||||
                    file2.filename.startsWith(bundleTitle + "_") &&
 | 
			
		||||
                    file2.filename.endsWith  (".html"          )
 | 
			
		||||
                ) continue;
 | 
			
		||||
 | 
			
		||||
                // Add the file to the output
 | 
			
		||||
                files.put(file2.filename, file2);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return files;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Prepend manifest object to _boot.js
 | 
			
		||||
    static void manifest(HashMap<String, File2> files, String bundleName) {
 | 
			
		||||
 | 
			
		||||
        // Produce a sorted list of files
 | 
			
		||||
        var values = files.values().toArray(new File2[files.size()]);
 | 
			
		||||
        Arrays.sort(values);
 | 
			
		||||
 | 
			
		||||
        // Build a file manifest
 | 
			
		||||
        var manifest = new StringBuilder();
 | 
			
		||||
        manifest.append("\"use strict\";\nlet manifest=[");
 | 
			
		||||
        for (var file : values) {
 | 
			
		||||
            manifest.append(
 | 
			
		||||
                "[\"" + file.filename + "\"," + file.data.length + "]");
 | 
			
		||||
            if (file != values[values.length - 1])
 | 
			
		||||
                manifest.append(",");
 | 
			
		||||
        }
 | 
			
		||||
        manifest.append("],bundleName=\"" + bundleName + "\";");
 | 
			
		||||
 | 
			
		||||
        // Prepend the manifest to _boot.js
 | 
			
		||||
        var boot = files.get("app/_boot.js");
 | 
			
		||||
        boot.data = (
 | 
			
		||||
            manifest.toString() +
 | 
			
		||||
            new String(boot.data, StandardCharsets.UTF_8) +
 | 
			
		||||
            "\u0000"
 | 
			
		||||
        ).getBytes(StandardCharsets.UTF_8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Construct bundled blob
 | 
			
		||||
    static byte[] blob(HashMap<String, File2> files) {
 | 
			
		||||
 | 
			
		||||
        // Produce a sorted list of files
 | 
			
		||||
        var values = files.values().toArray(new File2[files.size()]);
 | 
			
		||||
        Arrays.sort(values);
 | 
			
		||||
 | 
			
		||||
        // Build the blob
 | 
			
		||||
        var blob = new ByteArrayOutputStream();
 | 
			
		||||
        for (var file : values) try {
 | 
			
		||||
            blob.write(file.data);
 | 
			
		||||
        } catch (Exception e) { }
 | 
			
		||||
 | 
			
		||||
        return blob.toByteArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Encode bundled blob as a .png
 | 
			
		||||
    static byte[] png(byte[] blob) {
 | 
			
		||||
 | 
			
		||||
        // Calculate the dimensions of the image
 | 
			
		||||
        int width  = (int) Math.ceil(Math.sqrt(blob.length));
 | 
			
		||||
        int height = (int) Math.ceil((double) blob.length / width);
 | 
			
		||||
 | 
			
		||||
        // Prepare the pixel data
 | 
			
		||||
        var pixels = new int[width * height];
 | 
			
		||||
        for (int x = 0; x < blob.length; x++) {
 | 
			
		||||
            int l = blob[x] & 0xFF;
 | 
			
		||||
            pixels[x] = 0xFF000000 | l << 16 | l << 8 | l;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Produce a BufferedImage containing the pixels
 | 
			
		||||
        var img = new BufferedImage(width, height,
 | 
			
		||||
            BufferedImage.TYPE_BYTE_GRAY);
 | 
			
		||||
        img.getRaster().setPixels(0, 0, width, height, pixels);
 | 
			
		||||
 | 
			
		||||
        // Encode the image as a PNG byte array
 | 
			
		||||
        var png = new ByteArrayOutputStream();
 | 
			
		||||
        try { ImageIO.write(img, "png", png); }
 | 
			
		||||
        catch (Exception e) { }
 | 
			
		||||
        return png.toByteArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Embed bundle .png into template.html as a data URL
 | 
			
		||||
    static void template(byte[] png, String bundleName) {
 | 
			
		||||
 | 
			
		||||
        // Encode the PNG as a data URL
 | 
			
		||||
        String url = "data:image/png;base64," +
 | 
			
		||||
            Base64.getMimeEncoder().encodeToString(png)
 | 
			
		||||
            .replaceAll("\\r\\n", "");
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
 | 
			
		||||
            // Read template.html into memory
 | 
			
		||||
            var inStream = new FileInputStream("app/template.html");
 | 
			
		||||
            String template =
 | 
			
		||||
                new String(inStream.readAllBytes(), StandardCharsets.UTF_8)
 | 
			
		||||
                .replace("src=\"\"", "src=\"" + url + "\"")
 | 
			
		||||
            ;
 | 
			
		||||
            inStream.close();
 | 
			
		||||
 | 
			
		||||
            // Write the output HTML file
 | 
			
		||||
            var outStream = new FileOutputStream(bundleName + ".html");
 | 
			
		||||
            outStream.write(template.getBytes(StandardCharsets.UTF_8));
 | 
			
		||||
            outStream.close();
 | 
			
		||||
        } catch (Exception e) { throw new RuntimeException(e.getMessage()); }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Determine the filename of the bundle
 | 
			
		||||
    static String bundleName(String name) {
 | 
			
		||||
        var calendar = Calendar.getInstance();
 | 
			
		||||
        return String.format("%s_%04d%02d%02d",
 | 
			
		||||
            name,
 | 
			
		||||
            calendar.get(Calendar.YEAR),
 | 
			
		||||
            calendar.get(Calendar.MONTH) + 1,
 | 
			
		||||
            calendar.get(Calendar.DAY_OF_MONTH)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Program entry point
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (args.length != 1) {
 | 
			
		||||
            System.err.println("Usage: Bundle <name>");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Program tasks
 | 
			
		||||
        String bundleName = bundleName(args[0]);
 | 
			
		||||
        var files = readFiles(args[0]);
 | 
			
		||||
        manifest(files, bundleName);
 | 
			
		||||
        var blob = blob(files);
 | 
			
		||||
        var png = png(blob);
 | 
			
		||||
        template(png, bundleName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,308 @@
 | 
			
		|||
/*
 | 
			
		||||
  The Bundle.java utility prepends a file manifest to this script before
 | 
			
		||||
  execution is started. This script runs within the context of an async
 | 
			
		||||
  function.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                  Bundle                                   //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Bundled file manager
 | 
			
		||||
let Bundle = globalThis.Bundle = new class Bundle extends Array {
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.debug        =
 | 
			
		||||
            location.protocol != "file:" && location.hash == "#debug";
 | 
			
		||||
        this.decoder      = new TextDecoder();
 | 
			
		||||
        this.encoder      = new TextEncoder();
 | 
			
		||||
        this.moduleCall   = (... a)=>this.module  (... a);
 | 
			
		||||
        this.resourceCall = (... a)=>this.resource(... a);
 | 
			
		||||
 | 
			
		||||
        // Generate the CRC32 lookup table
 | 
			
		||||
        this.crcLookup = new Uint32Array(256);
 | 
			
		||||
        for (let x = 0; x <= 255; x++) {
 | 
			
		||||
            let l = x;
 | 
			
		||||
            for (let j = 7; j >= 0; j--)
 | 
			
		||||
                l = ((l >>> 1) ^ (0xEDB88320 & -(l & 1)));
 | 
			
		||||
            this.crcLookup[x] = l;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Add a file to the bundle
 | 
			
		||||
    add(name, data) {
 | 
			
		||||
        this.push(this[name] = new BundledFile(name, data));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Export all bundled resources to a .ZIP file
 | 
			
		||||
    save() {
 | 
			
		||||
        let centrals = new Array(this.length);
 | 
			
		||||
        let locals   = new Array(this.length);
 | 
			
		||||
        let offset   = 0;
 | 
			
		||||
        let size     = 0;
 | 
			
		||||
 | 
			
		||||
        // Encode file and directory entries
 | 
			
		||||
        for (let x = 0; x < this.length; x++) {
 | 
			
		||||
            let file    = this[x];
 | 
			
		||||
            let sum     = this.crc32(file.data);
 | 
			
		||||
            locals  [x] = file.toZipHeader(sum);
 | 
			
		||||
            centrals[x] = file.toZipHeader(sum, offset);
 | 
			
		||||
            offset     += locals  [x].length;
 | 
			
		||||
            size       += centrals[x].length;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Encode end of central directory
 | 
			
		||||
        let end = [];
 | 
			
		||||
        this.writeInt(end, 4, 0x06054B50);  // Signature
 | 
			
		||||
        this.writeInt(end, 2, 0);           // Disk number
 | 
			
		||||
        this.writeInt(end, 2, 0);           // Central dir start disk
 | 
			
		||||
        this.writeInt(end, 2, this.length); // # central dir this disk
 | 
			
		||||
        this.writeInt(end, 2, this.length); // # central dir total
 | 
			
		||||
        this.writeInt(end, 4, size);        // Size of central dir
 | 
			
		||||
        this.writeInt(end, 4, offset);      // Offset of central dir
 | 
			
		||||
        this.writeInt(end, 2, 0);           // .ZIP comment length
 | 
			
		||||
 | 
			
		||||
        // Prompt the user to save the resulting file
 | 
			
		||||
        let a      = document.createElement("a");
 | 
			
		||||
        a.download = bundleName + ".zip";
 | 
			
		||||
        a.href     = URL.createObjectURL(new Blob(
 | 
			
		||||
            locals.concat(centrals).concat([Uint8Array.from(end)]),
 | 
			
		||||
            { type: "application/zip" }
 | 
			
		||||
        ));
 | 
			
		||||
        a.style.visibility = "hidden";
 | 
			
		||||
        document.body.appendChild(a);
 | 
			
		||||
        a.click();
 | 
			
		||||
        a.remove();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Write a byte array into an output buffer
 | 
			
		||||
    writeBytes(data, bytes) {
 | 
			
		||||
        //data.push(... bytes);
 | 
			
		||||
        for (let b of bytes)
 | 
			
		||||
            data.push(b);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Write an integer into an output buffer
 | 
			
		||||
    writeInt(data, size, value) {
 | 
			
		||||
        for (; size > 0; size--, value >>= 8)
 | 
			
		||||
            data.push(value & 0xFF);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Write a string of text as bytes into an output buffer
 | 
			
		||||
    writeString(data, text) {
 | 
			
		||||
        data.push(... this.encoder.encode(text));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Private Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Calculate the CRC32 checksum for a byte array
 | 
			
		||||
    crc32(data) {
 | 
			
		||||
        let c = 0xFFFFFFFF;
 | 
			
		||||
        for (let x = 0; x < data.length; x++)
 | 
			
		||||
            c = ((c >>> 8) ^ this.crcLookup[(c ^ data[x]) & 0xFF]);
 | 
			
		||||
        return ~c & 0xFFFFFFFF;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                BundledFile                                //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Individual bundled file
 | 
			
		||||
class BundledFile {
 | 
			
		||||
 | 
			
		||||
    constructor(name, data) {
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.data = data;
 | 
			
		||||
        this.name = name;
 | 
			
		||||
 | 
			
		||||
        // Resolve 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(".json" ) ? "application/json;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"
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Install a font from a bundled resource
 | 
			
		||||
    async installFont(name) {
 | 
			
		||||
        if (name === undefined) {
 | 
			
		||||
            name = "/" + this.name;
 | 
			
		||||
            name = name.substring(name.lastIndexOf("/") + 1);
 | 
			
		||||
        }
 | 
			
		||||
        let ret = new FontFace(name, "url('"+
 | 
			
		||||
            (Bundle.debug ? this.name : this.toDataURL()) + "'");
 | 
			
		||||
        await ret.load();
 | 
			
		||||
        document.fonts.add(ret);
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Install an image as a CSS icon from a bundled resource
 | 
			
		||||
    installImage(name, filename) {
 | 
			
		||||
        document.documentElement.style.setProperty("--" +
 | 
			
		||||
            name || this.name.replaceAll(/\/\./, "_"),
 | 
			
		||||
            "url('" + (Bundle.debug ?
 | 
			
		||||
                filename || this.name : this.toDataURL()) + "')");
 | 
			
		||||
        return name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Install a stylesheet from a bundled resource
 | 
			
		||||
    installStylesheet(enabled) {
 | 
			
		||||
        let ret        = document.createElement("link");
 | 
			
		||||
        ret.href       = Bundle.debug ? this.name : this.toDataURL();
 | 
			
		||||
        ret.rel        = "stylesheet";
 | 
			
		||||
        ret.type       = "text/css";
 | 
			
		||||
        ret.setEnabled = enabled=>{
 | 
			
		||||
            if (enabled)
 | 
			
		||||
                ret.removeAttribute("disabled");
 | 
			
		||||
            else ret.setAttribute("disabled", "");
 | 
			
		||||
        };
 | 
			
		||||
        ret.setEnabled(!!enabled);
 | 
			
		||||
        document.head.appendChild(ret);
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Encode the file data as a data URL
 | 
			
		||||
    toDataURL() {
 | 
			
		||||
        return "data:" + this.mime + ";base64," +
 | 
			
		||||
            btoa(String.fromCharCode(...this.data));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Interpret the file's contents as bundled script source data URL
 | 
			
		||||
    toScript() {
 | 
			
		||||
 | 
			
		||||
        // Process all URL strings prefixed with /**/
 | 
			
		||||
        let parts = this.toString().split("/**/");
 | 
			
		||||
        let src   = parts.shift();
 | 
			
		||||
        for (let part of parts) {
 | 
			
		||||
            let quote = part.indexOf("\"", 1);
 | 
			
		||||
 | 
			
		||||
            // Begin with the path of the current file
 | 
			
		||||
            let path = this.name.split("/");
 | 
			
		||||
            path.pop();
 | 
			
		||||
 | 
			
		||||
            // Navigate to the path of the target file
 | 
			
		||||
            let file = part.substring(1, quote).split("/");
 | 
			
		||||
            while (file.length > 0) {
 | 
			
		||||
                let sub = file.shift();
 | 
			
		||||
                switch (sub) {
 | 
			
		||||
                    case "..": path.pop(); // Fallthrough
 | 
			
		||||
                    case "." : break;
 | 
			
		||||
                    default  : path.push(sub);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Append the file as a data URL
 | 
			
		||||
            file = Bundle[path.join("/")];
 | 
			
		||||
            src += "\"" + file[
 | 
			
		||||
                file.mime.startsWith("text/javascript") ?
 | 
			
		||||
                "toScript" : "toDataURL"
 | 
			
		||||
            ]() + "\"" + part.substring(quote + 1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Encode the transformed source as a data URL
 | 
			
		||||
        return "data:" + this.mime + ";base64," + btoa(src);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Decode the file data as a UTF-8 string
 | 
			
		||||
    toString() {
 | 
			
		||||
        return Bundle.decoder.decode(this.data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Produce a .ZIP header for export
 | 
			
		||||
    toZipHeader(crc32, offset) {
 | 
			
		||||
        let central = offset !== undefined;
 | 
			
		||||
        let ret     = [];
 | 
			
		||||
        if (central) {
 | 
			
		||||
            Bundle.writeInt  (ret, 4, 0x02014B50);       // Signature
 | 
			
		||||
            Bundle.writeInt  (ret, 2, 20);               // Version created by
 | 
			
		||||
        } else
 | 
			
		||||
            Bundle.writeInt  (ret, 4, 0x04034B50);       // Signature
 | 
			
		||||
        Bundle.writeInt      (ret, 2, 20);               // Version required
 | 
			
		||||
        Bundle.writeInt      (ret, 2,  0);               // Bit flags
 | 
			
		||||
        Bundle.writeInt      (ret, 2,  0);               // Compression method
 | 
			
		||||
        Bundle.writeInt      (ret, 2,  0);               // Modified time
 | 
			
		||||
        Bundle.writeInt      (ret, 2,  0);               // Modified date
 | 
			
		||||
        Bundle.writeInt      (ret, 4, crc32);            // Checksum
 | 
			
		||||
        Bundle.writeInt      (ret, 4, this.data.length); // Compressed size
 | 
			
		||||
        Bundle.writeInt      (ret, 4, this.data.length); // Uncompressed size
 | 
			
		||||
        Bundle.writeInt      (ret, 2, this.name.length); // Filename length
 | 
			
		||||
        Bundle.writeInt      (ret, 2,  0);               // Extra field length
 | 
			
		||||
        if (central) {
 | 
			
		||||
            Bundle.writeInt  (ret, 2, 0);                // File comment length
 | 
			
		||||
            Bundle.writeInt  (ret, 2, 0);                // Disk number start
 | 
			
		||||
            Bundle.writeInt  (ret, 2, 0);                // Internal attributes
 | 
			
		||||
            Bundle.writeInt  (ret, 4, 0);                // External attributes
 | 
			
		||||
            Bundle.writeInt  (ret, 4, offset);           // Relative offset
 | 
			
		||||
        }
 | 
			
		||||
        Bundle.writeString   (ret, this.name);           // Filename
 | 
			
		||||
        if (!central)
 | 
			
		||||
            Bundle.writeBytes(ret, this.data);           // File data
 | 
			
		||||
        return Uint8Array.from(ret);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                               Boot Program                                //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// De-register the boot function
 | 
			
		||||
delete globalThis.a;
 | 
			
		||||
 | 
			
		||||
// Remove the bundle image element from the document
 | 
			
		||||
Bundle.src = arguments[0].src;
 | 
			
		||||
arguments[0].remove();
 | 
			
		||||
 | 
			
		||||
// Convert the file manifest into BundledFile objects
 | 
			
		||||
let buffer = arguments[1];
 | 
			
		||||
let offset = arguments[2] - manifest[0][1];
 | 
			
		||||
for (let entry of manifest) {
 | 
			
		||||
    Bundle.add(entry[0], buffer.subarray(offset, offset + entry[1]));
 | 
			
		||||
    offset += entry[1];
 | 
			
		||||
    if (Bundle.length == 1)
 | 
			
		||||
        offset++; // Skip null delimiter
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Begin program operations
 | 
			
		||||
import(Bundle.debug ? "./app/main.js" : Bundle["app/main.js"].toScript());
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,448 @@
 | 
			
		|||
import { Debugger } from /**/"./Debugger.js";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                    App                                    //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Web-based emulator application
 | 
			
		||||
class App extends Toolkit {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(options) {
 | 
			
		||||
        super({
 | 
			
		||||
            className: "tk tk-app",
 | 
			
		||||
            label    : "app.title",
 | 
			
		||||
            role     : "application",
 | 
			
		||||
            tagName  : "div",
 | 
			
		||||
            style    : {
 | 
			
		||||
                display      : "flex",
 | 
			
		||||
                flexDirection: "column"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        options = options || {};
 | 
			
		||||
        this.debugMode = true;
 | 
			
		||||
        this.dualSims  = false;
 | 
			
		||||
        this.core      = options.core;
 | 
			
		||||
        this.linkSims  = true;
 | 
			
		||||
        this.locales   = {};
 | 
			
		||||
        this.themes    = {};
 | 
			
		||||
        this.Toolkit   = Toolkit;
 | 
			
		||||
 | 
			
		||||
        // Configure themes
 | 
			
		||||
        if ("themes" in options)
 | 
			
		||||
            for (let theme of Object.entries(options.themes))
 | 
			
		||||
                this.addTheme(theme[0], theme[1]);
 | 
			
		||||
        if ("theme" in options)
 | 
			
		||||
            this.setTheme(options.theme);
 | 
			
		||||
 | 
			
		||||
        // Configure locales
 | 
			
		||||
        if ("locales" in options)
 | 
			
		||||
            for (let locale of options.locales)
 | 
			
		||||
                this.addLocale(locale);
 | 
			
		||||
        if ("locale" in options)
 | 
			
		||||
            this.setLocale(options.locale);
 | 
			
		||||
 | 
			
		||||
        // Configure widget
 | 
			
		||||
        this.localize(this);
 | 
			
		||||
 | 
			
		||||
        // Not presenting a standalone application
 | 
			
		||||
        if (!options.standalone)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Set up standalone widgets
 | 
			
		||||
        this.initMenuBar();
 | 
			
		||||
        this.desktop = new Toolkit.Desktop(this,
 | 
			
		||||
            { style: { flexGrow: 1 } });
 | 
			
		||||
        this.add(this.desktop);
 | 
			
		||||
 | 
			
		||||
        // Configure document for presentation
 | 
			
		||||
        document.body.className = "tk tk-body";
 | 
			
		||||
        window.addEventListener("resize", e=>
 | 
			
		||||
            this.element.style.height = window.innerHeight + "px");
 | 
			
		||||
        window.dispatchEvent(new Event("resize"));
 | 
			
		||||
        document.body.appendChild(this.element);
 | 
			
		||||
 | 
			
		||||
        // Configure debugger components
 | 
			
		||||
        this[0] = new Debugger(this, 0, this.core[0]);
 | 
			
		||||
        this[1] = new Debugger(this, 1, this.core[1]);
 | 
			
		||||
 | 
			
		||||
        // Configure subscription handling
 | 
			
		||||
        this.subscriptions = {
 | 
			
		||||
            [this.core[0].sim]: this[0],
 | 
			
		||||
            [this.core[1].sim]: this[1]
 | 
			
		||||
        };
 | 
			
		||||
        this.core.onsubscriptions = e=>this.onSubscriptions(e);
 | 
			
		||||
 | 
			
		||||
        // Temporary config debugging
 | 
			
		||||
        console.log("Memory keyboard commands:");
 | 
			
		||||
        console.log("  Ctrl+G: Goto");
 | 
			
		||||
        console.log("Disassembler keyboard commands:");
 | 
			
		||||
        console.log("  Ctrl+B: Toggle bytes column");
 | 
			
		||||
        console.log("  Ctrl+F: Fit columns");
 | 
			
		||||
        console.log("  Ctrl+G: Goto");
 | 
			
		||||
        console.log("  F10: Run to next");
 | 
			
		||||
        console.log("  F11: Single step");
 | 
			
		||||
        console.log("Call dasm(\"key\", value) in the console " +
 | 
			
		||||
            "to configure the disassembler:");
 | 
			
		||||
        console.log(this[0].getDasmConfig());
 | 
			
		||||
        window.dasm = (key, value)=>{
 | 
			
		||||
            let config = this[0].getDasmConfig();
 | 
			
		||||
            if (!key in config || typeof value != typeof config[key])
 | 
			
		||||
                return;
 | 
			
		||||
            if (typeof value == "number" && value != 1 && value != 0)
 | 
			
		||||
                return;
 | 
			
		||||
            config[key] = value;
 | 
			
		||||
            this[0].setDasmConfig(config);
 | 
			
		||||
            this[1].setDasmConfig(config);
 | 
			
		||||
            return this[0].getDasmConfig();
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Configure file menu
 | 
			
		||||
    initFileMenu(menuBar) {
 | 
			
		||||
        let menu, item;
 | 
			
		||||
 | 
			
		||||
        // Menu
 | 
			
		||||
        menuBar.add(menu = menuBar.file = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.file._" }));
 | 
			
		||||
 | 
			
		||||
        // Load ROM
 | 
			
		||||
        menu.add(item = menu.loadROM0 = new Toolkit.MenuItem(this, {
 | 
			
		||||
            text: "app.menu.file.loadROM"
 | 
			
		||||
        }));
 | 
			
		||||
        item.setSubstitution("sim", "");
 | 
			
		||||
        item.addEventListener("action",
 | 
			
		||||
            ()=>this.promptFile(f=>this.loadROM(0, f)));
 | 
			
		||||
        menu.add(item = menu.loadROM1 = new Toolkit.MenuItem(this, {
 | 
			
		||||
            text   : "app.menu.file.loadROM",
 | 
			
		||||
            visible: false
 | 
			
		||||
        }));
 | 
			
		||||
        item.setSubstitution("sim", " 2");
 | 
			
		||||
        item.addEventListener("action",
 | 
			
		||||
            ()=>this.promptFile(f=>this.loadROM(1, f)));
 | 
			
		||||
 | 
			
		||||
        // Debug Mode
 | 
			
		||||
        menu.add(item = menu.debugMode = new Toolkit.MenuItem(this, {
 | 
			
		||||
            checked: this.debugMode,
 | 
			
		||||
            enabled: false,
 | 
			
		||||
            text   : "app.menu.file.debugMode",
 | 
			
		||||
            type   : "check"
 | 
			
		||||
        }));
 | 
			
		||||
        item.addEventListener("action", e=>e.component.setChecked(true));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Configure Emulation menu
 | 
			
		||||
    initEmulationMenu(menuBar) {
 | 
			
		||||
        let menu, item;
 | 
			
		||||
 | 
			
		||||
        menuBar.add(menu = menuBar.emulation = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.emulation._" }));
 | 
			
		||||
 | 
			
		||||
        menu.add(item = menu.runPause = new Toolkit.MenuItem(this, {
 | 
			
		||||
            enabled: false,
 | 
			
		||||
            text   : "app.menu.emulation.run"
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        menu.add(item = menu.reset = new Toolkit.MenuItem(this, {
 | 
			
		||||
            enabled: false,
 | 
			
		||||
            text   : "app.menu.emulation.reset"
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        menu.add(item = menu.dualSims = new Toolkit.MenuItem(this, {
 | 
			
		||||
            checked: this.dualSims,
 | 
			
		||||
            text   : "app.menu.emulation.dualSims",
 | 
			
		||||
            type   : "check"
 | 
			
		||||
        }));
 | 
			
		||||
        item.addEventListener("action",
 | 
			
		||||
            e=>this.setDualSims(e.component.isChecked));
 | 
			
		||||
 | 
			
		||||
        menu.add(item = menu.linkSims = new Toolkit.MenuItem(this, {
 | 
			
		||||
            checked: this.linkSims,
 | 
			
		||||
            text   : "app.menu.emulation.linkSims",
 | 
			
		||||
            type   : "check",
 | 
			
		||||
            visible: this.dualSims
 | 
			
		||||
        }));
 | 
			
		||||
        item.addEventListener("action",
 | 
			
		||||
            e=>this.setLinkSims(e.component.isChecked));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Configure Debug menus
 | 
			
		||||
    initDebugMenu(menuBar, sim) {
 | 
			
		||||
        let menu, item;
 | 
			
		||||
 | 
			
		||||
        menuBar.add(menu = menuBar["debug" + sim] =
 | 
			
		||||
            new Toolkit.MenuItem(this, {
 | 
			
		||||
            text   : "app.menu.debug._",
 | 
			
		||||
            visible: sim == 0 || this.dualSims
 | 
			
		||||
        }));
 | 
			
		||||
        menu.setSubstitution("sim",
 | 
			
		||||
            sim == 1 || this.dualSims ? " " + (sim + 1) : "");
 | 
			
		||||
 | 
			
		||||
        menu.add(item = menu.console = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.debug.console", enabled: false }));
 | 
			
		||||
 | 
			
		||||
        menu.add(item = menu.memory = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.debug.memory" }));
 | 
			
		||||
        item.addEventListener("action",
 | 
			
		||||
            ()=>this.showWindow(this[sim].memoryWindow));
 | 
			
		||||
 | 
			
		||||
        menu.add(item = menu.cpu = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.debug.cpu" }));
 | 
			
		||||
        item.addEventListener("action",
 | 
			
		||||
            ()=>this.showWindow(this[sim].cpuWindow));
 | 
			
		||||
 | 
			
		||||
        menu.add(item = menu.breakpoints = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.debug.breakpoints", enabled: false }));
 | 
			
		||||
        menu.addSeparator();
 | 
			
		||||
        menu.add(item = menu.palettes = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.debug.palettes", enabled: false }));
 | 
			
		||||
        menu.add(item = menu.characters = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.debug.characters", enabled: false }));
 | 
			
		||||
        menu.add(item = menu.bgMaps = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.debug.bgMaps", enabled: false }));
 | 
			
		||||
        menu.add(item = menu.objects = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.debug.objects", enabled: false }));
 | 
			
		||||
        menu.add(item = menu.worlds = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.debug.worlds", enabled: false }));
 | 
			
		||||
        menu.add(item = menu.frameBuffers = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.debug.frameBuffers", enabled: false }));
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Configure Theme menu
 | 
			
		||||
    initThemeMenu(menuBar) {
 | 
			
		||||
        let menu, item;
 | 
			
		||||
 | 
			
		||||
        menuBar.add(menu = menuBar.theme = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.theme._" }));
 | 
			
		||||
 | 
			
		||||
        menu.add(item = menu.light = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.theme.light" }));
 | 
			
		||||
        item.addEventListener("action", e=>this.setTheme("light"));
 | 
			
		||||
 | 
			
		||||
        menu.add(item = menu.dark = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.theme.dark" }));
 | 
			
		||||
        item.addEventListener("action", e=>this.setTheme("dark"));
 | 
			
		||||
 | 
			
		||||
        menu.add(item = menu.virtual = new Toolkit.MenuItem(this,
 | 
			
		||||
            { text: "app.menu.theme.virtual" }));
 | 
			
		||||
        item.addEventListener("action", e=>this.setTheme("virtual"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set up the menu bar
 | 
			
		||||
    initMenuBar() {
 | 
			
		||||
        let menuBar = this.menuBar = new Toolkit.MenuBar(this,
 | 
			
		||||
            { label: "app.menu._" });
 | 
			
		||||
        this.initFileMenu     (menuBar);
 | 
			
		||||
        this.initEmulationMenu(menuBar);
 | 
			
		||||
        this.initDebugMenu    (menuBar, 0);
 | 
			
		||||
        this.initDebugMenu    (menuBar, 1);
 | 
			
		||||
        this.initThemeMenu    (menuBar);
 | 
			
		||||
        this.add(menuBar);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Event Handlers //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Subscriptions arrived from the core thread
 | 
			
		||||
    onSubscriptions(subscriptions) {
 | 
			
		||||
        for (let sim of Object.entries(subscriptions)) {
 | 
			
		||||
            let dbg = this.subscriptions[sim[0]];
 | 
			
		||||
            for (let sub of Object.entries(sim[1])) switch (sub[0]) {
 | 
			
		||||
                case "proregs": dbg.programRegisters.refresh(sub[1]); break;
 | 
			
		||||
                case "sysregs": dbg.systemRegisters .refresh(sub[1]); break;
 | 
			
		||||
                case "dasm"   : dbg.disassembler    .refresh(sub[1]); break;
 | 
			
		||||
                case "memory" : dbg.memory          .refresh(sub[1]); break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Register a locale JSON
 | 
			
		||||
    addLocale(locale) {
 | 
			
		||||
        if (!("id" in locale))
 | 
			
		||||
            throw "No id field in locale";
 | 
			
		||||
        this.locales[locale.id] = Toolkit.flatten(locale);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Register a theme stylesheet
 | 
			
		||||
    addTheme(id, stylesheet) {
 | 
			
		||||
        this.themes[id] = stylesheet;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Specify the language for localization management
 | 
			
		||||
    setLocale(id) {
 | 
			
		||||
        if (!(id in this.locales)) {
 | 
			
		||||
            let lang = id.substring(0, 2);
 | 
			
		||||
            id = "en-US";
 | 
			
		||||
            for (let key of Object.keys(this.locales)) {
 | 
			
		||||
                if (key.substring(0, 2) == lang) {
 | 
			
		||||
                    id = key;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        super.setLocale(this.locales[id]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify the active color theme
 | 
			
		||||
    setTheme(key) {
 | 
			
		||||
        if (!(key in this.themes))
 | 
			
		||||
            return;
 | 
			
		||||
        for (let tkey of Object.keys(this.themes))
 | 
			
		||||
            this.themes[tkey].setEnabled(tkey == key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Regenerate localized display text
 | 
			
		||||
    translate() {
 | 
			
		||||
        if (arguments.length != 0)
 | 
			
		||||
            return super.translate.apply(this, arguments);
 | 
			
		||||
        document.title = super.translate("app.title", this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Private Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Load a ROM for a simulation
 | 
			
		||||
    async loadROM(index, file) {
 | 
			
		||||
 | 
			
		||||
        // No file was given
 | 
			
		||||
        if (!file)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Load the file into memory
 | 
			
		||||
        try { file = new Uint8Array(await file.arrayBuffer()); }
 | 
			
		||||
        catch {
 | 
			
		||||
            alert(this.translate("error.fileRead"));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Validate file size
 | 
			
		||||
        if (
 | 
			
		||||
            file.length < 1024      ||
 | 
			
		||||
            file.length > 0x1000000 ||
 | 
			
		||||
            (file.length - 1 & file.length) != 0
 | 
			
		||||
        ) {
 | 
			
		||||
            alert(this.translate("error.romNotVB"));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Load the ROM into the simulation
 | 
			
		||||
        if (!(await this[index].sim.setROM(file, { refresh: true }))) {
 | 
			
		||||
            alert(this.translate("error.romNotVB"));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Seek the disassembler to PC
 | 
			
		||||
        this[index].disassembler.seek(0xFFFFFFF0, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Prompt the user to select a file
 | 
			
		||||
    promptFile(then) {
 | 
			
		||||
        let file = document.createElement("input");
 | 
			
		||||
        file.type = "file";
 | 
			
		||||
        file.addEventListener("input",
 | 
			
		||||
            e=>file.files[0] && then(file.files[0]));
 | 
			
		||||
        file.click();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Attempt to run until the next instruction
 | 
			
		||||
    async runNext(index) {
 | 
			
		||||
        let two = this.dualSims && this.linkSims;
 | 
			
		||||
 | 
			
		||||
        // Perform the operation
 | 
			
		||||
        let data = await this.core.runNext(
 | 
			
		||||
            this[index].sim.sim,
 | 
			
		||||
            two ? this[index ^ 1].sim.sim : 0, {
 | 
			
		||||
            refresh: true
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Update the disassemblers
 | 
			
		||||
        this[index].disassembler.pc = data.pc[0];
 | 
			
		||||
        this[index].disassembler.seek(data.pc[0]);
 | 
			
		||||
        if (two) {
 | 
			
		||||
            this[index ^ 1].disassembler.pc = data.pc[1];
 | 
			
		||||
            this[index ^ 1].disassembler.seek(data.pc[1]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether dual sims mode is active
 | 
			
		||||
    setDualSims(dualSims) {
 | 
			
		||||
        let sub = dualSims ? " 1" : "";
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.dualSims = dualSims = !!dualSims;
 | 
			
		||||
 | 
			
		||||
        // Configure menus
 | 
			
		||||
        this.menuBar.emulation.dualSims.setChecked(dualSims);
 | 
			
		||||
        this.menuBar.emulation.linkSims.setVisible(dualSims);
 | 
			
		||||
        this.menuBar.file.loadROM0.setSubstitution("sim", sub);
 | 
			
		||||
        this.menuBar.file.loadROM1.setVisible(dualSims);
 | 
			
		||||
        this.menuBar.debug0.setSubstitution("sim", sub);
 | 
			
		||||
        this.menuBar.debug1.setVisible(dualSims);
 | 
			
		||||
 | 
			
		||||
        // Configure debuggers
 | 
			
		||||
        this[0].setDualSims(dualSims);
 | 
			
		||||
        this[1].setDualSims(dualSims);
 | 
			
		||||
        this.core.connect(this[0].sim.sim,
 | 
			
		||||
            dualSims && this.linkSims ? this[1].sim.sim : 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether the sims are connected for communicatinos
 | 
			
		||||
    setLinkSims(linked) {
 | 
			
		||||
        linked = !!linked;
 | 
			
		||||
 | 
			
		||||
        // State is not changing
 | 
			
		||||
        if (linked == this.linkSims)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Link or un-link the sims
 | 
			
		||||
        if (this.dualSims)
 | 
			
		||||
            this.core.connect(this[0].sim.sim, linked ? this[1].sim.sim : 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Display a window
 | 
			
		||||
    showWindow(wnd) {
 | 
			
		||||
        wnd.setVisible(true);
 | 
			
		||||
        wnd.focus()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Execute one instruction
 | 
			
		||||
    async singleStep(index) {
 | 
			
		||||
        let two = this.dualSims && this.linkSims;
 | 
			
		||||
 | 
			
		||||
        // Perform the operation
 | 
			
		||||
        let data = await this.core.singleStep(
 | 
			
		||||
            this[index].sim.sim,
 | 
			
		||||
            two ? this[index ^ 1].sim.sim : 0, {
 | 
			
		||||
            refresh: true
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Update the disassemblers
 | 
			
		||||
        this[index].disassembler.pc = data.pc[0];
 | 
			
		||||
        this[index].disassembler.seek(data.pc[0]);
 | 
			
		||||
        if (two) {
 | 
			
		||||
            this[index ^ 1].disassembler.pc = data.pc[1];
 | 
			
		||||
            this[index ^ 1].disassembler.seek(data.pc[1]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export { App };
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,106 @@
 | 
			
		|||
import { Disassembler } from /**/"./Disassembler.js";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                    CPU                                    //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// CPU register editor and disassembler
 | 
			
		||||
class CPU extends Toolkit.SplitPane {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(app, sim) {
 | 
			
		||||
        super(app, {
 | 
			
		||||
            className: "tk tk-splitpane tk-cpu",
 | 
			
		||||
            edge     : Toolkit.SplitPane.RIGHT,
 | 
			
		||||
            style    : {
 | 
			
		||||
                position: "relative"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        this.sim = sim;
 | 
			
		||||
        this.initDasm();
 | 
			
		||||
 | 
			
		||||
        this.metrics = new Toolkit.Component(this.app, {
 | 
			
		||||
            className: "tk tk-mono",
 | 
			
		||||
            tagName  : "div",
 | 
			
		||||
            style    : {
 | 
			
		||||
                position  : "absolute",
 | 
			
		||||
                visibility: "hidden"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        let text = "";
 | 
			
		||||
        for (let x = 0; x < 16; x++) {
 | 
			
		||||
            if (x) text += "\n";
 | 
			
		||||
            let digit = x.toString(16);
 | 
			
		||||
            text += digit + "\n" + digit.toUpperCase();
 | 
			
		||||
        }
 | 
			
		||||
        this.metrics.element.innerText = text;
 | 
			
		||||
        this.splitter.append(this.metrics.element);
 | 
			
		||||
 | 
			
		||||
        this.setView(1, this.regs = new Toolkit.SplitPane(app, {
 | 
			
		||||
            className: "tk tk-splitpane",
 | 
			
		||||
            edge     : Toolkit.SplitPane.TOP
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        this.regs.setView(0, this.sysregs = new RegisterList(this, true ));
 | 
			
		||||
        this.regs.setView(1, this.proregs = new RegisterList(this, false));
 | 
			
		||||
 | 
			
		||||
        // Adjust split panes to the initial size of the System Registers pane
 | 
			
		||||
        let resize;
 | 
			
		||||
        let preshow = e=>this.onPreshow(resize);
 | 
			
		||||
        resize = new ResizeObserver(preshow);
 | 
			
		||||
        resize.observe(this.sysregs.viewport);
 | 
			
		||||
        resize.observe(this.metrics.element);
 | 
			
		||||
 | 
			
		||||
        this.metrics.addEventListener("resize", e=>this.metricsResize());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    initDasm() {
 | 
			
		||||
        this.dasm = new Disassembler(this.app, this.sim);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Event Handlers //////////////////////////////
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // Resize handler prior to first visibility
 | 
			
		||||
    onPreshow(resize) {
 | 
			
		||||
        this.metricsResize();
 | 
			
		||||
 | 
			
		||||
        // Once the list of registers is visible, stop listening
 | 
			
		||||
        if (this.isVisible()) {
 | 
			
		||||
            resize.disconnect();
 | 
			
		||||
            this.sysregs.view.element.style.display = "grid";
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Update the split panes
 | 
			
		||||
        let sys = this.sysregs.view.element;
 | 
			
		||||
        let pro = this.proregs.view.element;
 | 
			
		||||
        this.setValue(
 | 
			
		||||
            Math.max(sys.scrollWidth, pro.scrollWidth) +
 | 
			
		||||
            this.sysregs.vertical.getBounds().width
 | 
			
		||||
        );
 | 
			
		||||
        this.regs.setValue(
 | 
			
		||||
            this.sysregs[PSW].expansion.getBounds().bottom -
 | 
			
		||||
            sys.getBoundingClientRect().top
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export { CPU };
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,187 @@
 | 
			
		|||
import { Disassembler } from /**/"./Disassembler.js";
 | 
			
		||||
import { Memory       } from /**/"./Memory.js";
 | 
			
		||||
import { RegisterList } from /**/"./RegisterList.js";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Debugger {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(app, index, sim) {
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.app   = app;
 | 
			
		||||
        this.index = index;
 | 
			
		||||
        this.sim   = sim;
 | 
			
		||||
 | 
			
		||||
        // Configure components
 | 
			
		||||
        this.disassembler     = new Disassembler(this);
 | 
			
		||||
        this.memory           = new Memory      (this);
 | 
			
		||||
        this.programRegisters = new RegisterList(this, false);
 | 
			
		||||
        this.systemRegisters  = new RegisterList(this, true );
 | 
			
		||||
 | 
			
		||||
        // Configure windows
 | 
			
		||||
        this.initCPUWindow   ();
 | 
			
		||||
        this.initMemoryWindow();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set up the CPU window
 | 
			
		||||
    initCPUWindow() {
 | 
			
		||||
        let app = this.app;
 | 
			
		||||
 | 
			
		||||
        // Produce the window
 | 
			
		||||
        let wnd = this.cpuWindow = new app.Toolkit.Window(app, {
 | 
			
		||||
            width       : 400,
 | 
			
		||||
            height      : 300,
 | 
			
		||||
            className   : "tk tk-window tk-cpu" + (this.index==0?"":" two"),
 | 
			
		||||
            closeToolTip: "common.close",
 | 
			
		||||
            title       : "cpu._",
 | 
			
		||||
            visible     : false
 | 
			
		||||
        });
 | 
			
		||||
        wnd.setSubstitution("sim", this.index == 1 ? " 2" : "");
 | 
			
		||||
        wnd.addEventListener("close", ()=>wnd.setVisible(false));
 | 
			
		||||
        app.desktop.add(wnd);
 | 
			
		||||
 | 
			
		||||
        // Visibility override
 | 
			
		||||
        let that       = this;
 | 
			
		||||
        let setVisible = wnd.setVisible;
 | 
			
		||||
        wnd.setVisible = function(visible) {
 | 
			
		||||
            that.disassembler    .setSubscribed(visible);
 | 
			
		||||
            that.systemRegisters .setSubscribed(visible);
 | 
			
		||||
            that.programRegisters.setSubscribed(visible);
 | 
			
		||||
            setVisible.apply(wnd, arguments);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Auto-seek the initial view
 | 
			
		||||
        let onSeek = ()=>this.disassembler.seek(this.disassembler.pc, true);
 | 
			
		||||
        Toolkit.addResizeListener(this.disassembler.element, onSeek);
 | 
			
		||||
        wnd.addEventListener("firstshow", ()=>{
 | 
			
		||||
            app.desktop.center(wnd);
 | 
			
		||||
            Toolkit.removeResizeListener(this.disassembler.element, onSeek);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Window splitters
 | 
			
		||||
        let sptMain = new Toolkit.SplitPane(this.app, {
 | 
			
		||||
            className: "tk tk-splitpane tk-main",
 | 
			
		||||
            edge     : Toolkit.SplitPane.RIGHT
 | 
			
		||||
        });
 | 
			
		||||
        let sptRegs = new Toolkit.SplitPane(this.app, {
 | 
			
		||||
            className: "tk tk-splitpane tk-registers",
 | 
			
		||||
            edge     : Toolkit.SplitPane.TOP
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Configure window splitter initial size
 | 
			
		||||
        let resize = new ResizeObserver(()=>{
 | 
			
		||||
 | 
			
		||||
            // Measure register lists
 | 
			
		||||
            let sys    = this.systemRegisters .getPreferredSize();
 | 
			
		||||
            let pro    = this.programRegisters.getPreferredSize();
 | 
			
		||||
            let height = Math.ceil(Math.max(sys.height, pro.height));
 | 
			
		||||
            let width  = Math.ceil(Math.max(sys.width , pro.width )) +
 | 
			
		||||
                this.systemRegisters.vertical.getBounds().width;
 | 
			
		||||
 | 
			
		||||
            // Configure splitters
 | 
			
		||||
            if (sptMain.getValue() != width)
 | 
			
		||||
                sptMain.setValue(width);
 | 
			
		||||
            if (sptRegs.getValue() != height)
 | 
			
		||||
                sptRegs.setValue(height);
 | 
			
		||||
        });
 | 
			
		||||
        resize.observe(this.programRegisters.view.element);
 | 
			
		||||
        resize.observe(this.systemRegisters .view.element);
 | 
			
		||||
 | 
			
		||||
        // Stop monitoring splitter size when something receives focus
 | 
			
		||||
        let onFocus = e=>{
 | 
			
		||||
            resize.disconnect();
 | 
			
		||||
            wnd.removeEventListener("focus", onFocus, true);
 | 
			
		||||
        };
 | 
			
		||||
        sptMain.addEventListener("focus", onFocus, true);
 | 
			
		||||
 | 
			
		||||
        // Configure window layout
 | 
			
		||||
        sptMain.setView(0, this.disassembler);
 | 
			
		||||
        sptMain.setView(1, sptRegs);
 | 
			
		||||
        sptRegs.setView(0, this.systemRegisters);
 | 
			
		||||
        sptRegs.setView(1, this.programRegisters);
 | 
			
		||||
        wnd.append(sptMain);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set up the Memory window
 | 
			
		||||
    initMemoryWindow() {
 | 
			
		||||
        let app = this.app;
 | 
			
		||||
 | 
			
		||||
        // Produce the window
 | 
			
		||||
        let wnd = this.memoryWindow = new app.Toolkit.Window(app, {
 | 
			
		||||
            width       : 400,
 | 
			
		||||
            height      : 300,
 | 
			
		||||
            className   : "tk tk-window" + (this.index == 0 ? "" : " two"),
 | 
			
		||||
            closeToolTip: "common.close",
 | 
			
		||||
            title       : "memory._",
 | 
			
		||||
            visible     : false
 | 
			
		||||
        });
 | 
			
		||||
        wnd.setSubstitution("sim", this.index == 1 ? " 2" : "");
 | 
			
		||||
        wnd.addEventListener("close"    , ()=>wnd.setVisible(false));
 | 
			
		||||
        wnd.addEventListener("firstshow", ()=>app.desktop.center(wnd));
 | 
			
		||||
        app.desktop.add(wnd);
 | 
			
		||||
 | 
			
		||||
        // Visibility override
 | 
			
		||||
        let that       = this;
 | 
			
		||||
        let setVisible = wnd.setVisible;
 | 
			
		||||
        wnd.setVisible = function(visible) {
 | 
			
		||||
            that.memory.setSubscribed(visible);
 | 
			
		||||
            setVisible.apply(wnd, arguments);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Configure window layout
 | 
			
		||||
        wnd.append(this.memory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Retrieve the disassembler configuraiton
 | 
			
		||||
    getDasmConfig() {
 | 
			
		||||
        return this.disassembler.getConfig();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Attempt to run until the next instruction
 | 
			
		||||
    runNext() {
 | 
			
		||||
        this.app.runNext(this.index);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update the disassembler configuration
 | 
			
		||||
    setDasmConfig(config) {
 | 
			
		||||
        this.disassembler    .setConfig(config);
 | 
			
		||||
        this.memory          .dasmChanged();
 | 
			
		||||
        this.programRegisters.dasmChanged();
 | 
			
		||||
        this.systemRegisters .dasmChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether dual sims mode is active
 | 
			
		||||
    setDualSims(dualSims) {
 | 
			
		||||
 | 
			
		||||
        // Update substitutions for sim 1
 | 
			
		||||
        if (this.index == 0) {
 | 
			
		||||
            let sub = dualSims ? " 1" : "";
 | 
			
		||||
            this.cpuWindow   .setSubstitution("sim", sub);
 | 
			
		||||
            this.memoryWindow.setSubstitution("sim", sub);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Hide windows for sim 2
 | 
			
		||||
        else if (!dualSims) {
 | 
			
		||||
            this.cpuWindow   .close(false);
 | 
			
		||||
            this.memoryWindow.close(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Execute one instruction
 | 
			
		||||
    singleStep() {
 | 
			
		||||
        this.app.singleStep(this.index);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export { Debugger };
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,958 @@
 | 
			
		|||
import { Util } from /**/"../app/Util.js";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Opcode definition
 | 
			
		||||
class Opdef {
 | 
			
		||||
    constructor(format, mnemonic, signExtend) {
 | 
			
		||||
        this.format     = format;
 | 
			
		||||
        this.mnemonic   = mnemonic;
 | 
			
		||||
        this.signExtend = !!signExtend;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Top-level opcode definition lookup table by opcode
 | 
			
		||||
let OPDEFS = [
 | 
			
		||||
    new Opdef(1, "MOV"  ), new Opdef(1, "ADD"   ), new Opdef(1, "SUB"  ),
 | 
			
		||||
    new Opdef(1, "CMP"  ), new Opdef(1, "SHL"   ), new Opdef(1, "SHR"  ),
 | 
			
		||||
    new Opdef(1, "JMP"  ), new Opdef(1, "SAR"   ), new Opdef(1, "MUL"  ),
 | 
			
		||||
    new Opdef(1, "DIV"  ), new Opdef(1, "MULU"  ), new Opdef(1, "DIVU" ),
 | 
			
		||||
    new Opdef(1, "OR"   ), new Opdef(1, "AND"   ), new Opdef(1, "XOR"  ),
 | 
			
		||||
    new Opdef(1, "NOT"  ), new Opdef(2, "MOV" ,1), new Opdef(2, "ADD",1),
 | 
			
		||||
    new Opdef(2, "SETF" ), new Opdef(2, "CMP" ,1), new Opdef(2, "SHL"  ),
 | 
			
		||||
    new Opdef(2, "SHR"  ), new Opdef(2, "CLI"   ), new Opdef(2, "SAR"  ),
 | 
			
		||||
    new Opdef(2, "TRAP" ), new Opdef(2, "RETI"  ), new Opdef(2, "HALT" ),
 | 
			
		||||
    new Opdef(0, null   ), new Opdef(2, "LDSR"  ), new Opdef(2, "STSR" ),
 | 
			
		||||
    new Opdef(2, "SEI"  ), new Opdef(2, null    ), new Opdef(3, "Bcond"),
 | 
			
		||||
    new Opdef(3, "Bcond"), new Opdef(3, "Bcond" ), new Opdef(3, "Bcond"),
 | 
			
		||||
    new Opdef(3, "Bcond"), new Opdef(3, "Bcond" ), new Opdef(3, "Bcond"),
 | 
			
		||||
    new Opdef(3, "Bcond"), new Opdef(5,"MOVEA",1), new Opdef(5,"ADDI",1),
 | 
			
		||||
    new Opdef(4, "JR"   ), new Opdef(4, "JAL"   ), new Opdef(5, "ORI"  ),
 | 
			
		||||
    new Opdef(5, "ANDI" ), new Opdef(5, "XORI"  ), new Opdef(5, "MOVHI"),
 | 
			
		||||
    new Opdef(6, "LD.B" ), new Opdef(6, "LD.H"  ), new Opdef(0, null   ),
 | 
			
		||||
    new Opdef(6, "LD.W" ), new Opdef(6, "ST.B"  ), new Opdef(6, "ST.H" ),
 | 
			
		||||
    new Opdef(0, null   ), new Opdef(6, "ST.W"  ), new Opdef(6, "IN.B" ),
 | 
			
		||||
    new Opdef(6, "IN.H" ), new Opdef(6, "CAXI"  ), new Opdef(6, "IN.W" ),
 | 
			
		||||
    new Opdef(6, "OUT.B"), new Opdef(6, "OUT.H" ), new Opdef(7, null   ),
 | 
			
		||||
    new Opdef(6, "OUT.W")
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// Bit string mnemonic lookup table by sub-opcode
 | 
			
		||||
let BITSTRINGS = [
 | 
			
		||||
    "SCH0BSU", "SCH0BSD", "SCH1BSU", "SCH1BSD",
 | 
			
		||||
    null     , null     , null     , null     ,
 | 
			
		||||
    "ORBSU"  , "ANDBSU" , "XORBSU" , "MOVBSU" ,
 | 
			
		||||
    "ORNBSU" , "ANDNBSU", "XORNBSU", "NOTBSU"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// Floating-point/Nintendo mnemonic lookup table by sub-opcode
 | 
			
		||||
let FLOATENDOS = [
 | 
			
		||||
    "CMPF.S", null    , "CVT.WS", "CVT.SW" ,
 | 
			
		||||
    "ADDF.S", "SUBF.S", "MULF.S", "DIVF.S" ,
 | 
			
		||||
    "XB"    , "XH"    , "REV"   , "TRNC.SW",
 | 
			
		||||
    "MPYHW"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// Program register names
 | 
			
		||||
let PROREGS = { 2: "hp", 3: "sp", 4: "gp", 5: "tp", 31: "lp" };
 | 
			
		||||
 | 
			
		||||
// System register names
 | 
			
		||||
let SYSREGS = [
 | 
			
		||||
    "EIPC", "EIPSW", "FEPC", "FEPSW",
 | 
			
		||||
    "ECR" , "PSW"  , "PIR" , "TKCW" ,
 | 
			
		||||
    null  , null   , null  , null   ,
 | 
			
		||||
    null  , null   , null  , null   ,
 | 
			
		||||
    null  , null   , null  , null   ,
 | 
			
		||||
    null  , null   , null  , null   ,
 | 
			
		||||
    "CHCW", "ADTRE", null  , null   ,
 | 
			
		||||
    null  , null   , null  , null
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// Condition mnemonics
 | 
			
		||||
let CONDS = [
 | 
			
		||||
    "V" , ["C" , "L" ], ["E" , "Z" ], "NH",
 | 
			
		||||
    "N" , "T"         , "LT"        , "LE",
 | 
			
		||||
    "NV", ["NC", "NL"], ["NE", "NZ"], "H" ,
 | 
			
		||||
    "P" , "F"         , "GE"        , "GT"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// Output setting keys
 | 
			
		||||
const SETTINGS = [
 | 
			
		||||
    "bcondMerged", "branchAddress", "condCase", "condCL", "condEZ",
 | 
			
		||||
    "condNames", "hexCaps", "hexDollar", "hexSuffix", "imm5OtherHex",
 | 
			
		||||
    "imm5ShiftHex", "imm5TrapHex", "imm16AddiLargeHex", "imm16AddiSmallHex",
 | 
			
		||||
    "imm16MoveHex", "imm16OtherHex", "jmpBrackets", "memoryLargeHex",
 | 
			
		||||
    "memorySmallHex", "memoryInside", "mnemonicCaps", "operandReverse",
 | 
			
		||||
    "proregCaps", "proregNames", "setfMerged", "sysregCaps", "sysregNames"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                   Line                                    //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// One line of output
 | 
			
		||||
class Line {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(parent, first) {
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.first  = first;
 | 
			
		||||
        this.parent = parent;
 | 
			
		||||
 | 
			
		||||
        // Configure labels
 | 
			
		||||
        this.lblAddress  = this.label("tk-address" , first);
 | 
			
		||||
        this.lblBytes    = this.label("tk-bytes"   , first);
 | 
			
		||||
        this.lblMnemonic = this.label("tk-mnemonic", first);
 | 
			
		||||
        this.lblOperands = this.label("tk-operands", false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Update the elements' display
 | 
			
		||||
    refresh(row, isPC) {
 | 
			
		||||
 | 
			
		||||
        // The row is not available
 | 
			
		||||
        if (!row) {
 | 
			
		||||
            this.lblAddress .innerText = "--------";
 | 
			
		||||
            this.lblBytes   .innerText = "";
 | 
			
		||||
            this.lblMnemonic.innerText = "---";
 | 
			
		||||
            this.lblOperands.innerText = "";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Update labels with the disassembled row's contents
 | 
			
		||||
        else {
 | 
			
		||||
            this.lblAddress .innerText = row.address;
 | 
			
		||||
            this.lblBytes   .innerText = row.bytes;
 | 
			
		||||
            this.lblMnemonic.innerText = row.mnemonic;
 | 
			
		||||
            this.lblOperands.innerText = row.operands;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Update style according to selection
 | 
			
		||||
        let method = row && isPC ? "add" : "remove";
 | 
			
		||||
        this.lblAddress .classList[method]("tk-selected");
 | 
			
		||||
        this.lblBytes   .classList[method]("tk-selected");
 | 
			
		||||
        this.lblMnemonic.classList[method]("tk-selected");
 | 
			
		||||
        this.lblOperands.classList[method]("tk-selected");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether the elements on this line are visible
 | 
			
		||||
    setVisible(visible) {
 | 
			
		||||
 | 
			
		||||
        // Column elements
 | 
			
		||||
        let columns = [
 | 
			
		||||
            this.lblAddress,
 | 
			
		||||
            this.lblBytes,
 | 
			
		||||
            this.lblMnemonic,
 | 
			
		||||
            this.lblOperands
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        // Column elements on the first row
 | 
			
		||||
        if (this.first) {
 | 
			
		||||
            columns[0] = columns[0].parentNode; // Address
 | 
			
		||||
            columns[1] = columns[1].parentNode; // Bytes
 | 
			
		||||
            columns[2] = columns[2].parentNode; // Mnemonic
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Column visibility
 | 
			
		||||
        visible = [
 | 
			
		||||
            visible,                         // Address
 | 
			
		||||
            visible && this.parent.hasBytes, // Bytes
 | 
			
		||||
            visible,                         // Mnemonic
 | 
			
		||||
            visible                          // Operands
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        // Configure elements
 | 
			
		||||
        for (let x = 0; x < 4; x++)
 | 
			
		||||
            columns[x].style.display = visible[x] ? "block" : "none";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Private Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Create a display label
 | 
			
		||||
    label(className, first) {
 | 
			
		||||
 | 
			
		||||
        // Create the label element
 | 
			
		||||
        let label = document.createElement("div");
 | 
			
		||||
        label.className = "tk " + className;
 | 
			
		||||
 | 
			
		||||
        // The label is part of the first row of output
 | 
			
		||||
        let element = label;
 | 
			
		||||
        if (first) {
 | 
			
		||||
 | 
			
		||||
            // Create a container element
 | 
			
		||||
            element = document.createElement("div");
 | 
			
		||||
            element.append(label);
 | 
			
		||||
            element.max = 0;
 | 
			
		||||
 | 
			
		||||
            // Ensure the container can always fit the column contents
 | 
			
		||||
            Toolkit.addResizeListener(element, ()=>{
 | 
			
		||||
                let width = Math.ceil(label.getBoundingClientRect().width);
 | 
			
		||||
                if (width <= element.max)
 | 
			
		||||
                    return;
 | 
			
		||||
                element.max            = width;
 | 
			
		||||
                element.style.minWidth = width + "px";
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Configure elements
 | 
			
		||||
        this.parent.view.append(element);
 | 
			
		||||
        return label;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                               Disassembler                                //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Text disassembler for NVC
 | 
			
		||||
class Disassembler extends Toolkit.ScrollPane {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(debug) {
 | 
			
		||||
        super(debug.app, {
 | 
			
		||||
            className : "tk tk-scrollpane tk-disassembler",
 | 
			
		||||
            horizontal: Toolkit.ScrollPane.AS_NEEDED,
 | 
			
		||||
            focusable : true,
 | 
			
		||||
            tabStop   : true,
 | 
			
		||||
            tagName   : "div",
 | 
			
		||||
            vertical  : Toolkit.ScrollPane.NEVER
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.address      = Util.u32(0xFFFFFFF0);
 | 
			
		||||
        this.app          = debug.app;
 | 
			
		||||
        this.columns      = [ 0, 0, 0, 0 ];
 | 
			
		||||
        this.data         = [];
 | 
			
		||||
        this.debug        = debug;
 | 
			
		||||
        this.hasBytes     = true;
 | 
			
		||||
        this.isSubscribed = false;
 | 
			
		||||
        this.lines        = null;
 | 
			
		||||
        this.pc           = this.address;
 | 
			
		||||
        this.pending      = [];
 | 
			
		||||
        this.rows         = [];
 | 
			
		||||
        this.scroll       = 0;
 | 
			
		||||
        this.sim          = debug.sim;
 | 
			
		||||
 | 
			
		||||
        // Default output settings
 | 
			
		||||
        this.setConfig({
 | 
			
		||||
            bcondMerged      : true,
 | 
			
		||||
            branchAddress    : true,
 | 
			
		||||
            condCase         : false,
 | 
			
		||||
            condCL           : 1,
 | 
			
		||||
            condEZ           : 1,
 | 
			
		||||
            condNames        : true,
 | 
			
		||||
            hexCaps          : true,
 | 
			
		||||
            hexDollar        : false,
 | 
			
		||||
            hexSuffix        : false,
 | 
			
		||||
            imm5OtherHex     : false,
 | 
			
		||||
            imm5ShiftHex     : false,
 | 
			
		||||
            imm5TrapHex      : false,
 | 
			
		||||
            imm16AddiLargeHex: true,
 | 
			
		||||
            imm16AddiSmallHex: false,
 | 
			
		||||
            imm16MoveHex     : true,
 | 
			
		||||
            imm16OtherHex    : true,
 | 
			
		||||
            jmpBrackets      : true,
 | 
			
		||||
            memoryLargeHex   : true,
 | 
			
		||||
            memorySmallHex   : false,
 | 
			
		||||
            memoryInside     : false,
 | 
			
		||||
            mnemonicCaps     : true,
 | 
			
		||||
            operandReverse   : false,
 | 
			
		||||
            proregCaps       : false,
 | 
			
		||||
            proregNames      : true,
 | 
			
		||||
            setfMerged       : false,
 | 
			
		||||
            sysregCaps       : true,
 | 
			
		||||
            sysregNames      : true
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Configure viewport
 | 
			
		||||
        this.viewport.classList.add("tk-mono");
 | 
			
		||||
 | 
			
		||||
        // Configure view
 | 
			
		||||
        let view = document.createElement("div");
 | 
			
		||||
        view.className = "tk tk-view";
 | 
			
		||||
        Object.assign(view.style, {
 | 
			
		||||
            display            : "grid",
 | 
			
		||||
            gridTemplateColumns: "repeat(3, max-content) auto"
 | 
			
		||||
        });
 | 
			
		||||
        this.setView(view);
 | 
			
		||||
 | 
			
		||||
        // Font-measuring element
 | 
			
		||||
        this.metrics = new Toolkit.Component(this.app, {
 | 
			
		||||
            className: "tk tk-metrics tk-mono",
 | 
			
		||||
            tagName  : "div",
 | 
			
		||||
            style    : {
 | 
			
		||||
                position  : "absolute",
 | 
			
		||||
                visibility: "hidden"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        this.metrics.element.innerText = "X";
 | 
			
		||||
        this.append(this.metrics.element);
 | 
			
		||||
 | 
			
		||||
        // First row always exists
 | 
			
		||||
        this.lines = [ new Line(this, true) ];
 | 
			
		||||
 | 
			
		||||
        // Configure event handlers
 | 
			
		||||
        Toolkit.addResizeListener(this.viewport, e=>this.onResize(e));
 | 
			
		||||
        this.addEventListener("keydown", e=>this.onKeyDown   (e));
 | 
			
		||||
        this.addEventListener("wheel"  , e=>this.onMouseWheel(e));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Event Handlers //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Key press
 | 
			
		||||
    onKeyDown(e) {
 | 
			
		||||
        let tall = this.tall(false);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // Ctrl key is pressed
 | 
			
		||||
        if (e.ctrlKey) switch (e.key) {
 | 
			
		||||
 | 
			
		||||
            // Toggle bytes column
 | 
			
		||||
            case "b": case "B":
 | 
			
		||||
                this.showBytes(!this.hasBytes);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            // Fit columns
 | 
			
		||||
            case "f": case "F":
 | 
			
		||||
                this.fitColumns();
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            // Goto
 | 
			
		||||
            case "g": case "G":
 | 
			
		||||
                this.promptGoto();
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            default: return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Ctrl key is not pressed
 | 
			
		||||
        else switch (e.key) {
 | 
			
		||||
 | 
			
		||||
            // Navigation
 | 
			
		||||
            case "ArrowDown" : this.fetch(+1   , true); break;
 | 
			
		||||
            case "ArrowUp"   : this.fetch(-1   , true); break;
 | 
			
		||||
            case "PageDown"  : this.fetch(+tall, true); break;
 | 
			
		||||
            case "PageUp"    : this.fetch(-tall, true); break;
 | 
			
		||||
 | 
			
		||||
            // View control
 | 
			
		||||
            case "ArrowLeft" : this.horizontal.setValue(
 | 
			
		||||
                this.horizontal.value - this.horizontal.increment); break;
 | 
			
		||||
            case "ArrowRight": this.horizontal.setValue(
 | 
			
		||||
                this.horizontal.value + this.horizontal.increment); break;
 | 
			
		||||
 | 
			
		||||
            // Single step
 | 
			
		||||
            case "F10":
 | 
			
		||||
                this.debug.runNext();
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            // Single step
 | 
			
		||||
            case "F11":
 | 
			
		||||
                this.debug.singleStep();
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            default: return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Configure event
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Mouse wheel
 | 
			
		||||
    onMouseWheel(e) {
 | 
			
		||||
 | 
			
		||||
        // User agent scaling action
 | 
			
		||||
        if (e.ctrlKey)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // No rotation has occurred
 | 
			
		||||
        let offset = Math.sign(e.deltaY) * 3;
 | 
			
		||||
        if (offset == 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Update the display address
 | 
			
		||||
        this.fetch(offset, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Viewport resized
 | 
			
		||||
    onResize(e) {
 | 
			
		||||
        let fetch = false;
 | 
			
		||||
        let tall  = this.tall(true);
 | 
			
		||||
 | 
			
		||||
        // Add additional lines to the output
 | 
			
		||||
        for (let x = 0; x < tall; x++) {
 | 
			
		||||
            if (x >= this.lines.length) {
 | 
			
		||||
                fetch = true;
 | 
			
		||||
                this.lines.push(new Line(this));
 | 
			
		||||
            }
 | 
			
		||||
            this.lines[x].setVisible(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Remove extra lines from the output
 | 
			
		||||
        for (let x = tall; x < this.lines.length; x++)
 | 
			
		||||
            this.lines[x].setVisible(false);
 | 
			
		||||
 | 
			
		||||
        // Configure horizontal scroll bar
 | 
			
		||||
        if (this.metrics)
 | 
			
		||||
            this.horizontal.setIncrement(this.metrics.getBounds().width);
 | 
			
		||||
 | 
			
		||||
        // Update the display
 | 
			
		||||
        if (fetch)
 | 
			
		||||
            this.fetch(0, true);
 | 
			
		||||
        else this.refresh();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Produce disassembly text
 | 
			
		||||
    disassemble(rows) {
 | 
			
		||||
 | 
			
		||||
        // Produce a deep copy of the input list
 | 
			
		||||
        let copy = new Array(rows.length);
 | 
			
		||||
        for (let x = 0; x < rows.length; x++) {
 | 
			
		||||
            copy[x] = {};
 | 
			
		||||
            Object.assign(copy[x], rows[x]);
 | 
			
		||||
        }
 | 
			
		||||
        rows = copy;
 | 
			
		||||
 | 
			
		||||
        // Process all rows
 | 
			
		||||
        for (let row of rows) {
 | 
			
		||||
            row.operands = [];
 | 
			
		||||
 | 
			
		||||
            // Read instruction bits from the bus
 | 
			
		||||
            let bits0 = row.bytes[1] << 8 | row.bytes[0];
 | 
			
		||||
            let bits1;
 | 
			
		||||
            if (row.bytes.length == 4)
 | 
			
		||||
                bits1 = row.bytes[3] << 8 | row.bytes[2];
 | 
			
		||||
 | 
			
		||||
            // Working variables
 | 
			
		||||
            let opcode = bits0 >> 10;
 | 
			
		||||
            let opdef  = OPDEFS[opcode];
 | 
			
		||||
 | 
			
		||||
            // Sub-opcode mnemonics
 | 
			
		||||
            if (row.opcode == 0b011111)
 | 
			
		||||
                row.mnemonic = BITSTRINGS[bits0       & 31] || "---";
 | 
			
		||||
            else if (row.opcode == 0b111110)
 | 
			
		||||
                row.mnemonic = FLOATENDOS[bits1 >> 10 & 63] || "---";
 | 
			
		||||
            else row.mnemonic = opdef.mnemonic;
 | 
			
		||||
 | 
			
		||||
            // Processing by format
 | 
			
		||||
            switch (opdef.format) {
 | 
			
		||||
                case 1: this.formatI  (row, bits0       ); break;
 | 
			
		||||
                case 3: this.formatIII(row, bits0       ); break;
 | 
			
		||||
                case 4: this.formatIV (row, bits0, bits1); break;
 | 
			
		||||
                case 6: this.formatVI (row, bits0, bits1); break;
 | 
			
		||||
                case 7: this.formatVII(row, bits0       ); break;
 | 
			
		||||
                case 2:
 | 
			
		||||
                    this.formatII(row, bits0, opdef.signExtend); break;
 | 
			
		||||
                case 5:
 | 
			
		||||
                    this.formatV (row, bits0, bits1, opdef.signExtend);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Format bytes
 | 
			
		||||
            let text = [];
 | 
			
		||||
            for (let x = 0; x < row.bytes.length; x++)
 | 
			
		||||
                text.push(row.bytes[x].toString(16).padStart(2, "0"));
 | 
			
		||||
            row.bytes = text.join(" ");
 | 
			
		||||
 | 
			
		||||
            // Post-processing
 | 
			
		||||
            row.address = row.address.toString(16).padStart(8, "0");
 | 
			
		||||
            if (this.hexCaps) {
 | 
			
		||||
                row.address = row.address.toUpperCase();
 | 
			
		||||
                row.bytes   = row.bytes  .toUpperCase();
 | 
			
		||||
            }
 | 
			
		||||
            if (!this.mnemonicCaps)
 | 
			
		||||
                row.mnemonic = row.mnemonic.toLowerCase();
 | 
			
		||||
            if (this.operandReverse)
 | 
			
		||||
                row.operands.reverse();
 | 
			
		||||
            row.operands = row.operands.join(", ");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return rows;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Retrieve all output settings in an object
 | 
			
		||||
    getConfig() {
 | 
			
		||||
        let ret = {};
 | 
			
		||||
        for (let key of SETTINGS)
 | 
			
		||||
            ret[key] = this[key];
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update with disassembly state from the core
 | 
			
		||||
    refresh(data = 0) {
 | 
			
		||||
        let bias;
 | 
			
		||||
 | 
			
		||||
        // Scrolling prefresh
 | 
			
		||||
        if (typeof data == "number")
 | 
			
		||||
            bias = 16 + data;
 | 
			
		||||
 | 
			
		||||
        // Received data from the core thread
 | 
			
		||||
        else {
 | 
			
		||||
            this.data    = data.rows;
 | 
			
		||||
            this.pc      = data.pc;
 | 
			
		||||
            if (this.data.length == 0)
 | 
			
		||||
                return;
 | 
			
		||||
            this.address = this.data[0].address;
 | 
			
		||||
            this.rows    = this.disassemble(this.data);
 | 
			
		||||
            bias         = 16 +
 | 
			
		||||
                (data.scroll === null ? 0 : this.scroll - data.scroll);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Update elements
 | 
			
		||||
        let count = Math.min(this.tall(true), this.data.length);
 | 
			
		||||
        for (let y = 0; y < count; y++) {
 | 
			
		||||
            let index = bias + y;
 | 
			
		||||
            let line  = this.data[index];
 | 
			
		||||
            let row   = this.rows[index];
 | 
			
		||||
            this.lines[y].refresh(row, line && line.address == this.pc);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Refesh scroll pane
 | 
			
		||||
        this.update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Bring an address into view
 | 
			
		||||
    seek(address, force) {
 | 
			
		||||
 | 
			
		||||
        // Check if the address is already in the view
 | 
			
		||||
        if (!force) {
 | 
			
		||||
            let bias  = 16;
 | 
			
		||||
            let tall  = this.tall(false);
 | 
			
		||||
            let count = Math.min(tall, this.data.length);
 | 
			
		||||
 | 
			
		||||
            // The address is currently visible in the output
 | 
			
		||||
            for (let y = 0; y < count; y++) {
 | 
			
		||||
                let row = this.data[bias + y];
 | 
			
		||||
                if (!row || Util.u32(address - row.address) >= row.size)
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
                // The address is on this row
 | 
			
		||||
                this.refresh();
 | 
			
		||||
                return; 
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Place the address at a particular position in the view
 | 
			
		||||
        this.address = address;
 | 
			
		||||
        this.fetch(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update output settings
 | 
			
		||||
    setConfig(config) {
 | 
			
		||||
 | 
			
		||||
        // Update settings
 | 
			
		||||
        for (let key of SETTINGS)
 | 
			
		||||
            if (key in config)
 | 
			
		||||
                this[key] = config[key];
 | 
			
		||||
 | 
			
		||||
        // Regenerate output
 | 
			
		||||
        this.refresh({
 | 
			
		||||
            pc    : this.pc,
 | 
			
		||||
            rows  : this.data,
 | 
			
		||||
            scroll: null
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Subscribe to or unsubscribe from core updates
 | 
			
		||||
    setSubscribed(subscribed) {
 | 
			
		||||
        subscribed = !!subscribed;
 | 
			
		||||
 | 
			
		||||
        // Nothing to change
 | 
			
		||||
        if (subscribed == this.isSubscribed)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.isSubscribed = subscribed;
 | 
			
		||||
 | 
			
		||||
        // Subscribe to core updates
 | 
			
		||||
        if (subscribed)
 | 
			
		||||
            this.fetch(0);
 | 
			
		||||
 | 
			
		||||
        // Unsubscribe from core updates
 | 
			
		||||
        else this.sim.unsubscribe("dasm");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Private Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Select a condition's name
 | 
			
		||||
    cond(cond) {
 | 
			
		||||
        let ret = CONDS[cond];
 | 
			
		||||
        switch (cond) {
 | 
			
		||||
            case 1: case  9: return CONDS[cond][this.condCL];
 | 
			
		||||
            case 2: case 10: return CONDS[cond][this.condEZ];
 | 
			
		||||
        }
 | 
			
		||||
        return CONDS[cond];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Retrieve disassembly data from the core
 | 
			
		||||
    async fetch(scroll, prefresh) {
 | 
			
		||||
        let row;
 | 
			
		||||
 | 
			
		||||
        // Scrolling relative to the current view
 | 
			
		||||
        if (scroll) {
 | 
			
		||||
            if (prefresh)
 | 
			
		||||
                this.refresh(scroll);
 | 
			
		||||
            this.scroll = Util.s32(this.scroll + scroll);
 | 
			
		||||
            row         = -scroll;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Jumping to an address directly
 | 
			
		||||
        else row = scroll === null ? Math.floor(this.tall(false) / 3) + 16 : 0;
 | 
			
		||||
 | 
			
		||||
        // Retrieve data from the core
 | 
			
		||||
        this.refresh(
 | 
			
		||||
            await this.sim.disassemble(
 | 
			
		||||
                this.address,
 | 
			
		||||
                row,
 | 
			
		||||
                this.tall(true) + 32,
 | 
			
		||||
                scroll === null ? null : this.scroll, {
 | 
			
		||||
                subscribe: this.isSubscribed && "dasm"
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Shrink all columns to their minimum size
 | 
			
		||||
    fitColumns() {
 | 
			
		||||
        let line = this.lines[0];
 | 
			
		||||
        for (let column of [ "lblAddress", "lblBytes", "lblMnemonic" ] ) {
 | 
			
		||||
            let element = line[column].parentNode;
 | 
			
		||||
            element.max = 0;
 | 
			
		||||
            element.style.removeProperty("min-width");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Represent a hexadecimal value
 | 
			
		||||
    hex(value, digits) {
 | 
			
		||||
        let sign = Util.s32(value) < 0 ? "-" : "";
 | 
			
		||||
        let ret  = Math.abs(Util.u32(value)).toString(16).padStart(digits,"0");
 | 
			
		||||
        if (this.hexCaps)
 | 
			
		||||
            ret = ret.toUpperCase();
 | 
			
		||||
        if (this.hexSuffix)
 | 
			
		||||
            ret = ("abcdefABCDEF".indexOf(ret[0]) == -1 ? "" : "0") +
 | 
			
		||||
                ret + "h";
 | 
			
		||||
        else ret = (this.hexDollar ? "$" : "0x") + ret;
 | 
			
		||||
        return sign + ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Prompt the user to specify a new address
 | 
			
		||||
    promptGoto() {
 | 
			
		||||
 | 
			
		||||
        // Receive input from the user
 | 
			
		||||
        let address = prompt(this.app.translate("common.gotoPrompt"));
 | 
			
		||||
        if (address == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Process the input as an address in hexadecimal
 | 
			
		||||
        address = parseInt(address, 16);
 | 
			
		||||
        if (isNaN(address))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Move the selection and refresh the display
 | 
			
		||||
        this.seek(Util.u32(address));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Select a program register name
 | 
			
		||||
    proreg(index) {
 | 
			
		||||
        let ret = this.proregNames && PROREGS[index] || "r" + index;
 | 
			
		||||
        return this.proregCaps ? ret.toUpperCase() : ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether or not to show the bytes column
 | 
			
		||||
    showBytes(show) {
 | 
			
		||||
        let tall = this.tall(true);
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.hasBytes = show;
 | 
			
		||||
 | 
			
		||||
        // Configure elements
 | 
			
		||||
        this.view.style.gridTemplateColumns =
 | 
			
		||||
            "repeat(" + (show ? 3 : 2) + ", max-content) auto";
 | 
			
		||||
        for (let x = 0; x < tall; x++)
 | 
			
		||||
            this.lines[x].setVisible(true);
 | 
			
		||||
 | 
			
		||||
        // Measure scroll pane
 | 
			
		||||
        this.update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Measure how many rows of output are visible
 | 
			
		||||
    tall(partial) {
 | 
			
		||||
        let lineHeight = !this.metrics ? 0 :
 | 
			
		||||
            Math.ceil(this.metrics.getBounds().height);
 | 
			
		||||
        return lineHeight <= 0 ? 1 : Math.max(1, Math[partial?"ceil":"floor"](
 | 
			
		||||
            this.getBounds().height / lineHeight));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //////////////////////////// Decoding Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Disassemble a Format I instruction
 | 
			
		||||
    formatI(row, bits0) {
 | 
			
		||||
        let reg1 = this.proreg(bits0 & 31);
 | 
			
		||||
 | 
			
		||||
        // JMP
 | 
			
		||||
        if (row.mnemonic == "JMP") {
 | 
			
		||||
            if (this.jmpBrackets)
 | 
			
		||||
                reg1 = "[" + reg1 +  "]";
 | 
			
		||||
            row.operands.push(reg1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Other instructions
 | 
			
		||||
        else {
 | 
			
		||||
            let reg2 = this.proreg(bits0 >> 5 & 31);
 | 
			
		||||
            row.operands.push(reg1, reg2);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Disassemble a Format II instruction
 | 
			
		||||
    formatII(row, bits0, signExtend) {
 | 
			
		||||
 | 
			
		||||
        // Bit-string instructions are zero-operand
 | 
			
		||||
        if (bits0 >> 10 == 0b011111)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Processing by mnemonic
 | 
			
		||||
        switch (row.mnemonic) {
 | 
			
		||||
 | 
			
		||||
            // Zero-operand
 | 
			
		||||
            case "---" : // Fallthrough
 | 
			
		||||
            case "CLI" : // Fallthrough
 | 
			
		||||
            case "HALT": // Fallthrough
 | 
			
		||||
            case "RETI": // Fallthrough
 | 
			
		||||
            case "SEI" : return;
 | 
			
		||||
 | 
			
		||||
            // Distinct notation
 | 
			
		||||
            case "LDSR": return this.ldstsr(row, bits0, true );
 | 
			
		||||
            case "SETF": return this.setf  (row, bits0       );
 | 
			
		||||
            case "STSR": return this.ldstsr(row, bits0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Retrieve immediate operand
 | 
			
		||||
        let imm = bits0 & 31;
 | 
			
		||||
        if (signExtend)
 | 
			
		||||
            imm = Util.signExtend(bits0, 5);
 | 
			
		||||
 | 
			
		||||
        // TRAP instruction is one-operand
 | 
			
		||||
        if (row.mnemonic == "TRAP") {
 | 
			
		||||
            row.operands.push(this.trapHex ?
 | 
			
		||||
                this.hex(imm, 1) : imm.toString());
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Processing by mnemonic
 | 
			
		||||
        let hex = this.imm5OtherHex;
 | 
			
		||||
        switch (row.mnemonic) {
 | 
			
		||||
            case "SAR": // Fallthrough
 | 
			
		||||
            case "SHL": // Fallthrough
 | 
			
		||||
            case "SHR": hex = this.imm5ShiftHex;
 | 
			
		||||
        }
 | 
			
		||||
        imm = hex ? this.hex(imm, 1) : imm.toString();
 | 
			
		||||
 | 
			
		||||
        // Two-operand instruction
 | 
			
		||||
        let reg2 = this.proreg(bits0 >> 5 & 31);
 | 
			
		||||
        row.operands.push(imm, reg2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Disassemble a Format III instruction
 | 
			
		||||
    formatIII(row, bits0) {
 | 
			
		||||
        let cond = this.cond(bits0 >> 9 & 15);
 | 
			
		||||
        let disp = Util.signExtend(bits0 & 0x1FF, 9);
 | 
			
		||||
 | 
			
		||||
        // Condition merged with mnemonic
 | 
			
		||||
        if (this.bcondMerged) {
 | 
			
		||||
            switch (cond) {
 | 
			
		||||
                case "F": row.mnemonic = "NOP"; return;
 | 
			
		||||
                case "T": row.mnemonic = "BR" ; break;
 | 
			
		||||
                default : row.mnemonic = "B" + cond;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Condition as operand
 | 
			
		||||
        else {
 | 
			
		||||
            if (!this.condCaps)
 | 
			
		||||
                cond = cond.toLowerCase();
 | 
			
		||||
            row.operands.push(cond);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Operand as destination address
 | 
			
		||||
        if (this.branchAddress) {
 | 
			
		||||
            disp = Util.u32(row.address + disp & 0xFFFFFFFE)
 | 
			
		||||
                .toString(16).padStart(8, "0");
 | 
			
		||||
            if (this.hexCaps)
 | 
			
		||||
                disp = disp.toUpperCase();
 | 
			
		||||
            row.operands.push(disp);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Operand as displacement
 | 
			
		||||
        else {
 | 
			
		||||
            let sign = disp < 0 ? "-" : disp > 0 ? "+" : "";
 | 
			
		||||
            let rel  = this.hex(Math.abs(disp), 1);
 | 
			
		||||
            row.operands.push(sign + rel);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Disassemble a Format IV instruction
 | 
			
		||||
    formatIV(row, bits0, bits1) {
 | 
			
		||||
        let disp = Util.signExtend(bits0 << 16 | bits1, 26);
 | 
			
		||||
 | 
			
		||||
        // Operand as destination address
 | 
			
		||||
        if (this.branchAddress) {
 | 
			
		||||
            disp = Util.u32(row.address + disp & 0xFFFFFFFE)
 | 
			
		||||
                .toString(16).padStart(8, "0");
 | 
			
		||||
            if (this.hexCaps)
 | 
			
		||||
                disp = disp.toUpperCase();
 | 
			
		||||
            row.operands.push(disp);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Operand as displacement
 | 
			
		||||
        else {
 | 
			
		||||
            let sign = disp < 0 ? "-" : disp > 0 ? "+" : "";
 | 
			
		||||
            let rel  = this.hex(Math.abs(disp), 1);
 | 
			
		||||
            row.operands.push(sign + rel);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Disassemble a Format V instruction
 | 
			
		||||
    formatV(row, bits0, bits1, signExtend) {
 | 
			
		||||
        let imm  = signExtend ? Util.signExtend(bits1) : bits1;
 | 
			
		||||
        let reg1 = this.proreg(bits0      & 31);
 | 
			
		||||
        let reg2 = this.proreg(bits0 >> 5 & 31);
 | 
			
		||||
 | 
			
		||||
        if (
 | 
			
		||||
            row.mnemonic == "ADDI" ?
 | 
			
		||||
                Math.abs(imm) <= 256 ?
 | 
			
		||||
                this.imm16AddiSmallHex :
 | 
			
		||||
                this.imm16AddiLargeHex
 | 
			
		||||
            : row.mnemonic == "MOVEA" || row.mnemonic == "MOVHI" ?
 | 
			
		||||
                this.imm16MoveHex
 | 
			
		||||
            :
 | 
			
		||||
                this.imm16OtherHex
 | 
			
		||||
        ) imm = this.hex(imm, 4);
 | 
			
		||||
 | 
			
		||||
        row.operands.push(imm, reg1, reg2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Disassemble a Format VI instruction
 | 
			
		||||
    formatVI(row, bits0, bits1) {
 | 
			
		||||
        let disp = Util.signExtend(bits1);
 | 
			
		||||
        let reg1 = this.proreg(bits0      & 31);
 | 
			
		||||
        let reg2 = this.proreg(bits0 >> 5 & 31);
 | 
			
		||||
        let sign =
 | 
			
		||||
            disp <  0                       ? "-" :
 | 
			
		||||
            disp == 0 || !this.memoryInside ? ""  :
 | 
			
		||||
        "+";
 | 
			
		||||
 | 
			
		||||
        // Displacement is hexadecimal
 | 
			
		||||
        disp = Math.abs(disp);
 | 
			
		||||
        if (disp == 0)
 | 
			
		||||
            disp = ""
 | 
			
		||||
        else if (disp <= 256 ? this.memorySmallHex : this.memoryLargeHex) 
 | 
			
		||||
            disp = this.hex(disp, 1);
 | 
			
		||||
 | 
			
		||||
        // Format the displacement figure according to its presentation
 | 
			
		||||
        disp = this.memoryInside ?
 | 
			
		||||
            sign == "" ? "" : " " + sign + " " + disp :
 | 
			
		||||
            sign + disp
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
        // Apply operands
 | 
			
		||||
        row.operands.push(this.memoryInside ?
 | 
			
		||||
            "[" + reg1 + disp + "]" :
 | 
			
		||||
            disp + "[" + reg1 + "]",
 | 
			
		||||
        reg2);
 | 
			
		||||
 | 
			
		||||
        // Swap operands for output and store instructions
 | 
			
		||||
        switch (row.mnemonic) {
 | 
			
		||||
            case "OUT.B": case "OUT.H": case "OUT.W":
 | 
			
		||||
            case "ST.B" : case "ST.H" : case "ST.W" :
 | 
			
		||||
                row.operands.reverse();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Disassemble a Format VII instruction
 | 
			
		||||
    formatVII(row, bits0) {
 | 
			
		||||
        let reg1 = this.proreg(bits0      & 31);
 | 
			
		||||
        let reg2 = this.proreg(bits0 >> 5 & 31);
 | 
			
		||||
 | 
			
		||||
        // Invalid sub-opcode is zero-operand
 | 
			
		||||
        if (row.mnemonic == "---")
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Processing by mnemonic
 | 
			
		||||
        switch (row.mnemonic) {
 | 
			
		||||
            case "XB": // Fallthrough
 | 
			
		||||
            case "XH": break;
 | 
			
		||||
            default  : row.operands.push(reg1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        row.operands.push(reg2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Format an LDSR or STSR instruction
 | 
			
		||||
    ldstsr(row, bits0, reverse) {
 | 
			
		||||
 | 
			
		||||
        // System register
 | 
			
		||||
        let sysreg = bits0 & 31;
 | 
			
		||||
        sysreg = this.sysregNames && SYSREGS[sysreg] || sysreg.toString();
 | 
			
		||||
        if (!this.sysregCaps)
 | 
			
		||||
            sysreg = sysreg.toLowerCase();
 | 
			
		||||
 | 
			
		||||
        // Program register
 | 
			
		||||
        let reg2 = this.proreg(bits0 >> 5 & 31);
 | 
			
		||||
 | 
			
		||||
        // Operands
 | 
			
		||||
        row.operands.push(sysreg, reg2);
 | 
			
		||||
        if (reverse)
 | 
			
		||||
            row.operands.reverse();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Format a SETF instruction
 | 
			
		||||
    setf(row, bits0) {
 | 
			
		||||
        let cond = this.cond  (bits0      & 15);
 | 
			
		||||
        let reg2 = this.proreg(bits0 >> 5 & 31);
 | 
			
		||||
 | 
			
		||||
        // Condition merged with mnemonic
 | 
			
		||||
        if (!this.bcondMerged) {
 | 
			
		||||
            row.mnemonic += cond;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Condition as operand
 | 
			
		||||
        else {
 | 
			
		||||
            if (!this.condCaps)
 | 
			
		||||
                cond = cond.toLowerCase();
 | 
			
		||||
            row.operands.push(cond);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        row.operands.push(reg2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export { Disassembler };
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,517 @@
 | 
			
		|||
import { Util } from /**/"./Util.js";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Bus indexes
 | 
			
		||||
const MEMORY = 0;
 | 
			
		||||
 | 
			
		||||
// Text to hex digit conversion
 | 
			
		||||
const DIGITS = {
 | 
			
		||||
    "0": 0, "1": 1, "2":  2, "3":  3, "4":  4, "5":  5, "6":  6, "7":  7,
 | 
			
		||||
    "8": 8, "9": 9, "A": 10, "B": 11, "C": 12, "D": 13, "E": 14, "F": 15
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                   Line                                    //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// One line of output
 | 
			
		||||
class Line {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(parent, index) {
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.index  = index;
 | 
			
		||||
        this.parent = parent;
 | 
			
		||||
 | 
			
		||||
        // Address label
 | 
			
		||||
        this.lblAddress = document.createElement("div");
 | 
			
		||||
        this.lblAddress.className = "tk tk-address";
 | 
			
		||||
        parent.view.appendChild(this.lblAddress);
 | 
			
		||||
 | 
			
		||||
        // Byte labels
 | 
			
		||||
        this.lblBytes = new Array(16);
 | 
			
		||||
        for (let x = 0; x < 16; x++) {
 | 
			
		||||
            let lbl = this.lblBytes[x] = document.createElement("div");
 | 
			
		||||
            lbl.className = "tk tk-byte tk-" + x.toString(16);
 | 
			
		||||
            parent.view.appendChild(lbl);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Update the elements' display
 | 
			
		||||
    refresh() {
 | 
			
		||||
        let bus         = this.parent[this.parent.bus];
 | 
			
		||||
        let address     = this.parent.mask(bus.address + this.index * 16);
 | 
			
		||||
        let data        = bus.data;
 | 
			
		||||
        let dataAddress = bus.dataAddress;
 | 
			
		||||
        let hexCaps     = this.parent.dasm.hexCaps;
 | 
			
		||||
        let offset      =
 | 
			
		||||
            (this.parent.row(address) - this.parent.row(dataAddress)) * 16;
 | 
			
		||||
 | 
			
		||||
        // Format the line's address
 | 
			
		||||
        let text = address.toString(16).padStart(8, "0");
 | 
			
		||||
        if (hexCaps)
 | 
			
		||||
            text = text.toUpperCase();
 | 
			
		||||
        this.lblAddress.innerText = text;
 | 
			
		||||
 | 
			
		||||
        // The line's data is not available
 | 
			
		||||
        if (offset < 0 || offset >= data.length) {
 | 
			
		||||
            for (let lbl of this.lblBytes) {
 | 
			
		||||
                lbl.innerText = "--";
 | 
			
		||||
                lbl.classList.remove("tk-selected");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // The line's data is available
 | 
			
		||||
        else for (let x = 0; x < 16; x++, offset++) {
 | 
			
		||||
            let lbl  = this.lblBytes[x];
 | 
			
		||||
                text = data[offset].toString(16).padStart(2, "0");
 | 
			
		||||
 | 
			
		||||
            // The byte is the current selection
 | 
			
		||||
            if (Util.u32(address + x) == bus.selection) {
 | 
			
		||||
                lbl.classList.add("tk-selected");
 | 
			
		||||
                if (this.parent.digit !== null)
 | 
			
		||||
                    text = this.parent.digit.toString(16);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // The byte is not the current selection
 | 
			
		||||
            else lbl.classList.remove("tk-selected");
 | 
			
		||||
 | 
			
		||||
            // Update the label's text
 | 
			
		||||
            if (hexCaps)
 | 
			
		||||
                text = text.toUpperCase();
 | 
			
		||||
            lbl.innerText = text;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether the elements on this line are visible
 | 
			
		||||
    setVisible(visible) {
 | 
			
		||||
        visible = visible ? "block" : "none";
 | 
			
		||||
        this.lblAddress.style.display = visible;
 | 
			
		||||
        for (let lbl of this.lblBytes)
 | 
			
		||||
            lbl.style.display = visible;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                  Memory                                   //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Memory hex editor
 | 
			
		||||
class Memory extends Toolkit.Component {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(debug) {
 | 
			
		||||
        super(debug.app, {
 | 
			
		||||
            className : "tk tk-memory",
 | 
			
		||||
            tagName   : "div",
 | 
			
		||||
            style     : {
 | 
			
		||||
                alignItems      : "stretch",
 | 
			
		||||
                display         : "grid",
 | 
			
		||||
                gridTemplateRows: "auto",
 | 
			
		||||
                position        : "relative"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.app          = debug.app;
 | 
			
		||||
        this.bus          = MEMORY;
 | 
			
		||||
        this.dasm         = debug.disassembler;
 | 
			
		||||
        this.debug        = debug;
 | 
			
		||||
        this.digit        = null;
 | 
			
		||||
        this.isSubscribed = false;
 | 
			
		||||
        this.lines        = [];
 | 
			
		||||
        this.sim          = debug.sim;
 | 
			
		||||
 | 
			
		||||
        // Initialize bus
 | 
			
		||||
        this[MEMORY] = {
 | 
			
		||||
            address    : 0x05000000,
 | 
			
		||||
            data       : [],
 | 
			
		||||
            dataAddress: 0x05000000,
 | 
			
		||||
            selection  : 0x05000000
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Configure editor pane
 | 
			
		||||
        this.editor = new Toolkit.ScrollPane(this.app, {
 | 
			
		||||
            className : "tk tk-scrollpane tk-editor",
 | 
			
		||||
            horizontal: Toolkit.ScrollPane.AS_NEEDED,
 | 
			
		||||
            focusable : true,
 | 
			
		||||
            tabStop   : true,
 | 
			
		||||
            tagName   : "div",
 | 
			
		||||
            vertical  : Toolkit.ScrollPane.NEVER
 | 
			
		||||
        });
 | 
			
		||||
        this.append(this.editor);
 | 
			
		||||
 | 
			
		||||
        // Configure view
 | 
			
		||||
        this.view = document.createElement("div");
 | 
			
		||||
        this.view.className = "tk tk-view";
 | 
			
		||||
        Object.assign(this.view.style, {
 | 
			
		||||
            display            : "grid",
 | 
			
		||||
            gridTemplateColumns: "repeat(17, max-content)"
 | 
			
		||||
        });
 | 
			
		||||
        this.editor.setView(this.view);
 | 
			
		||||
 | 
			
		||||
        // Font-measuring element
 | 
			
		||||
        this.metrics = new Toolkit.Component(this.app, {
 | 
			
		||||
            className: "tk tk-metrics tk-mono",
 | 
			
		||||
            tagName  : "div",
 | 
			
		||||
            style    : {
 | 
			
		||||
                position  : "absolute",
 | 
			
		||||
                visibility: "hidden"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        this.metrics.element.innerText = "X";
 | 
			
		||||
        this.append(this.metrics.element);
 | 
			
		||||
 | 
			
		||||
        // Configure event handlers
 | 
			
		||||
        Toolkit.addResizeListener(this.editor.viewport, e=>this.onResize(e));
 | 
			
		||||
        this.addEventListener("keydown"    , e=>this.onKeyDown    (e));
 | 
			
		||||
        this.addEventListener("pointerdown", e=>this.onPointerDown(e));
 | 
			
		||||
        this.addEventListener("wheel"      , e=>this.onMouseWheel (e));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Event Handlers //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Typed a digit
 | 
			
		||||
    onDigit(digit) {
 | 
			
		||||
        let bus = this[this.bus];
 | 
			
		||||
 | 
			
		||||
        // Begin an edit
 | 
			
		||||
        if (this.digit === null) {
 | 
			
		||||
            this.digit = digit;
 | 
			
		||||
            this.setSelection(bus.selection, true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Complete an edit
 | 
			
		||||
        else {
 | 
			
		||||
            this.digit = this.digit << 4 | digit;
 | 
			
		||||
            this.setSelection(bus.selection + 1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Key press
 | 
			
		||||
    onKeyDown(e) {
 | 
			
		||||
        let bus = this[this.bus];
 | 
			
		||||
        let key = e.key;
 | 
			
		||||
 | 
			
		||||
        // A hex digit was entered
 | 
			
		||||
        if (key.toUpperCase() in DIGITS) {
 | 
			
		||||
            this.onDigit(DIGITS[key.toUpperCase()]);
 | 
			
		||||
            key = "digit";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Ctrl key is pressed
 | 
			
		||||
        if (e.ctrlKey) switch (key) {
 | 
			
		||||
 | 
			
		||||
            // Goto
 | 
			
		||||
            case "g": case "G":
 | 
			
		||||
                this.promptGoto();
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            default: return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Ctrl key is not pressed
 | 
			
		||||
        else switch (key) {
 | 
			
		||||
 | 
			
		||||
            // Arrow key navigation
 | 
			
		||||
            case "ArrowDown" : this.setSelection(bus.selection + 16); break;
 | 
			
		||||
            case "ArrowLeft" : this.setSelection(bus.selection -  1); break;
 | 
			
		||||
            case "ArrowRight": this.setSelection(bus.selection +  1); break;
 | 
			
		||||
            case "ArrowUp"   : this.setSelection(bus.selection - 16); break;
 | 
			
		||||
 | 
			
		||||
            // Commit current edit
 | 
			
		||||
            case "Enter":
 | 
			
		||||
            case " ":
 | 
			
		||||
                if (this.digit !== null)
 | 
			
		||||
                    this.setSelection(bus.selection);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            // Page key navigation
 | 
			
		||||
            case "PageDown":
 | 
			
		||||
                this.setSelection(bus.selection + this.tall(false) * 16);
 | 
			
		||||
                break;
 | 
			
		||||
            case "PageUp":
 | 
			
		||||
                this.setSelection(bus.selection - this.tall(false) * 16);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            // Hex digit: already processed
 | 
			
		||||
            case "digit": break;
 | 
			
		||||
 | 
			
		||||
            default: return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Configure event
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Mouse wheel
 | 
			
		||||
    onMouseWheel(e) {
 | 
			
		||||
 | 
			
		||||
        // User agent scaling action
 | 
			
		||||
        if (e.ctrlKey)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // No rotation has occurred
 | 
			
		||||
        let offset = Math.sign(e.deltaY) * 48;
 | 
			
		||||
        if (offset == 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Update the display address
 | 
			
		||||
        this.fetch(this[this.bus].address + offset, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Pointer down
 | 
			
		||||
    onPointerDown(e) {
 | 
			
		||||
 | 
			
		||||
        // Common handling
 | 
			
		||||
        this.editor.focus();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
 | 
			
		||||
        // Not a click action
 | 
			
		||||
        if (e.button != 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Determine the row that was clicked on
 | 
			
		||||
        let lineHeight = !this.metrics ? 0 :
 | 
			
		||||
            Math.max(0, Math.ceil(this.metrics.getBounds().height));
 | 
			
		||||
        if (lineHeight == 0)
 | 
			
		||||
            return;
 | 
			
		||||
        let y = Math.floor(
 | 
			
		||||
            (e.y - this.view.getBoundingClientRect().top) / lineHeight);
 | 
			
		||||
 | 
			
		||||
        // Determine the column that was clicked on
 | 
			
		||||
        let columns = this.lines[0].lblBytes;
 | 
			
		||||
        let bndCur  = columns[0].getBoundingClientRect();
 | 
			
		||||
        if (e.x >= bndCur.left) for (let x = 0; x < 16; x++) {
 | 
			
		||||
            let bndNext = x == 15 ? null :
 | 
			
		||||
                columns[x + 1].getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
            // The current column was clicked: update the selection
 | 
			
		||||
            if (e.x < (x == 15 ? bndCur.right :
 | 
			
		||||
                bndCur.right + (bndNext.left - bndCur.right) / 2)) {
 | 
			
		||||
                this.setSelection(this[this.bus].address + y * 16 + x);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Advance to the next column
 | 
			
		||||
            bndCur = bndNext;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Viewport resized
 | 
			
		||||
    onResize(e) {
 | 
			
		||||
        let fetch = false;
 | 
			
		||||
        let tall  = this.tall(true);
 | 
			
		||||
 | 
			
		||||
        // Add additional lines to the output
 | 
			
		||||
        for (let x = 0; x < tall; x++) {
 | 
			
		||||
            if (x >= this.lines.length) {
 | 
			
		||||
                fetch = true;
 | 
			
		||||
                this.lines.push(new Line(this, x));
 | 
			
		||||
            }
 | 
			
		||||
            this.lines[x].setVisible(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Remove extra lines from the output
 | 
			
		||||
        for (let x = tall; x < this.lines.length; x++)
 | 
			
		||||
            this.lines[x].setVisible(false);
 | 
			
		||||
 | 
			
		||||
        // Configure horizontal scroll bar
 | 
			
		||||
        if (this.metrics) this.editor.horizontal
 | 
			
		||||
            .setIncrement(this.metrics.getBounds().width);
 | 
			
		||||
 | 
			
		||||
        // Update the display
 | 
			
		||||
        if (fetch)
 | 
			
		||||
            this.fetch(this[this.bus].address, true);
 | 
			
		||||
        else this.refresh();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Update with memory state from the core
 | 
			
		||||
    refresh(data) {
 | 
			
		||||
        let bus = this[this.bus];
 | 
			
		||||
 | 
			
		||||
        // Update with data from the core thread
 | 
			
		||||
        if (data) {
 | 
			
		||||
            bus.data        = data.bytes;
 | 
			
		||||
            bus.dataAddress = data.address;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Update elements
 | 
			
		||||
        for (let y = 0, tall = this.tall(true); y < tall; y++)
 | 
			
		||||
            this.lines[y].refresh();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Subscribe to or unsubscribe from core updates
 | 
			
		||||
    setSubscribed(subscribed) {
 | 
			
		||||
        subscribed = !!subscribed;
 | 
			
		||||
 | 
			
		||||
        // Nothing to change
 | 
			
		||||
        if (subscribed == this.isSubscribed)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.isSubscribed = subscribed;
 | 
			
		||||
 | 
			
		||||
        // Subscribe to core updates
 | 
			
		||||
        if (subscribed)
 | 
			
		||||
            this.fetch(this[this.bus].address);
 | 
			
		||||
 | 
			
		||||
        // Unsubscribe from core updates
 | 
			
		||||
        else this.sim.unsubscribe("memory");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // The disassembler configuration has changed
 | 
			
		||||
    dasmChanged() {
 | 
			
		||||
        this.refresh();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Private Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Retrieve memory data from the core
 | 
			
		||||
    async fetch(address, prefresh) {
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this[this.bus].address = address = this.mask(address);
 | 
			
		||||
 | 
			
		||||
        // Update the view immediately
 | 
			
		||||
        if (prefresh)
 | 
			
		||||
            this.refresh();
 | 
			
		||||
 | 
			
		||||
        // Retrieve data from the core
 | 
			
		||||
        this.refresh(
 | 
			
		||||
            await this.sim.read(
 | 
			
		||||
                address - 16 * 16,
 | 
			
		||||
                (this.tall(true) + 32) * 16, {
 | 
			
		||||
                subscribe: this.isSubscribed && "memory"
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Mask an address according to the current bus
 | 
			
		||||
    mask(address) {
 | 
			
		||||
        return Util.u32(address);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Prompt the user to specify a new address
 | 
			
		||||
    promptGoto() {
 | 
			
		||||
 | 
			
		||||
        // Receive input from the user
 | 
			
		||||
        let address = prompt(this.app.translate("common.gotoPrompt"));
 | 
			
		||||
        if (address == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Process the input as an address in hexadecimal
 | 
			
		||||
        address = parseInt(address, 16);
 | 
			
		||||
        if (isNaN(address))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // The address is not currently visible in the output
 | 
			
		||||
        let tall = this.tall(false);
 | 
			
		||||
        if (Util.u32(address - this.address) >= tall * 16)
 | 
			
		||||
            this.fetch((address & 0xFFFFFFF0) - Math.floor(tall / 3) * 16);
 | 
			
		||||
 | 
			
		||||
        // Move the selection and refresh the display
 | 
			
		||||
        this.setSelection(address);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Determine which row relative to top the selection is on
 | 
			
		||||
    row(address) {
 | 
			
		||||
        let row = address - this[this.bus].address & 0xFFFFFFF0;
 | 
			
		||||
        row = Util.s32(row);
 | 
			
		||||
        return row / 16;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify which byte is selected
 | 
			
		||||
    setSelection(address, noCommit) {
 | 
			
		||||
        let bus   = this[this.bus];
 | 
			
		||||
        let fetch = false;
 | 
			
		||||
 | 
			
		||||
        // Commit a pending data entry
 | 
			
		||||
        if (!noCommit && this.digit !== null) {
 | 
			
		||||
            this.write(this.digit);
 | 
			
		||||
            this.digit = null;
 | 
			
		||||
            fetch = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        bus.selection = address = this.mask(address);
 | 
			
		||||
 | 
			
		||||
        // Working variables
 | 
			
		||||
        let row = this.row(address);
 | 
			
		||||
 | 
			
		||||
        // The new address is above the top line of output
 | 
			
		||||
        if (row < 0) {
 | 
			
		||||
            this.fetch(bus.address + row * 16 & 0xFFFFFFF0, true);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // The new address is below the bottom line of output
 | 
			
		||||
        let tall = this.tall(false);
 | 
			
		||||
        if (row >= tall) {
 | 
			
		||||
            this.fetch(address - tall * 16 + 16 & 0xFFFFFFF0, true);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Update the display
 | 
			
		||||
        if (fetch)
 | 
			
		||||
            this.fetch(bus.address, true);
 | 
			
		||||
        else this.refresh();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Measure how many rows of output are visible
 | 
			
		||||
    tall(partial) {
 | 
			
		||||
        let lineHeight = !this.metrics ? 0 :
 | 
			
		||||
            Math.ceil(this.metrics.getBounds().height);
 | 
			
		||||
        return lineHeight <= 0 ? 1 : Math.max(1, Math[partial?"ceil":"floor"](
 | 
			
		||||
            this.editor.getBounds().height / lineHeight));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Write a value to the core thread
 | 
			
		||||
    write(value) {
 | 
			
		||||
        let bus    = this[this.bus];
 | 
			
		||||
        let offset = (this.row(bus.selection) + 16) * 16;
 | 
			
		||||
        if (offset < bus.data.length)
 | 
			
		||||
            bus.data[offset | bus.selection & 15] = value;
 | 
			
		||||
        this.sim.write(
 | 
			
		||||
            bus.selection,
 | 
			
		||||
            Uint8Array.from([ value ]), {
 | 
			
		||||
            refresh: true
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export { Memory };
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,889 @@
 | 
			
		|||
import { Util } from /**/"./Util.js";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Value types
 | 
			
		||||
const HEX      = 0;
 | 
			
		||||
const SIGNED   = 1;
 | 
			
		||||
const UNSIGNED = 2;
 | 
			
		||||
const FLOAT    = 3;
 | 
			
		||||
 | 
			
		||||
// System register indexes
 | 
			
		||||
const ADTRE = 25;
 | 
			
		||||
const CHCW  = 24;
 | 
			
		||||
const ECR   =  4;
 | 
			
		||||
const EIPC  =  0;
 | 
			
		||||
const EIPSW =  1;
 | 
			
		||||
const FEPC  =  2;
 | 
			
		||||
const FEPSW =  3;
 | 
			
		||||
const PC    = -1;
 | 
			
		||||
const PIR   =  6;
 | 
			
		||||
const PSW   =  5;
 | 
			
		||||
const TKCW  =  7;
 | 
			
		||||
 | 
			
		||||
// Program register names
 | 
			
		||||
const PROREGS = {
 | 
			
		||||
    [ 2]: "hp",
 | 
			
		||||
    [ 3]: "sp",
 | 
			
		||||
    [ 4]: "gp",
 | 
			
		||||
    [ 5]: "tp",
 | 
			
		||||
    [31]: "lp"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// System register names
 | 
			
		||||
const SYSREGS = {
 | 
			
		||||
    [ADTRE]: "ADTRE",
 | 
			
		||||
    [CHCW ]: "CHCW",
 | 
			
		||||
    [ECR  ]: "ECR",
 | 
			
		||||
    [EIPC ]: "EIPC",
 | 
			
		||||
    [EIPSW]: "EIPSW",
 | 
			
		||||
    [FEPC ]: "FEPC",
 | 
			
		||||
    [FEPSW]: "FEPSW",
 | 
			
		||||
    [PC   ]: "PC",
 | 
			
		||||
    [PIR  ]: "PIR",
 | 
			
		||||
    [PSW  ]: "PSW",
 | 
			
		||||
    [TKCW ]: "TKCW",
 | 
			
		||||
    [29   ]: "29",
 | 
			
		||||
    [30   ]: "30",
 | 
			
		||||
    [31   ]: "31"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Expansion control types
 | 
			
		||||
const BIT = 0;
 | 
			
		||||
const INT = 1;
 | 
			
		||||
 | 
			
		||||
// Produce a template object for register expansion controls
 | 
			
		||||
function ctrl(name, shift, size, disabled) {
 | 
			
		||||
    return {
 | 
			
		||||
        disabled: !!disabled,
 | 
			
		||||
        name    : name,
 | 
			
		||||
        shift   : shift,
 | 
			
		||||
        size    : size
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Program register epansion controls
 | 
			
		||||
const EXP_PROGRAM = [
 | 
			
		||||
    ctrl("cpu.hex"     , true , HEX     ),
 | 
			
		||||
    ctrl("cpu.signed"  , false, SIGNED  ),
 | 
			
		||||
    ctrl("cpu.unsigned", false, UNSIGNED),
 | 
			
		||||
    ctrl("cpu.float"   , false, FLOAT   )
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// CHCW expansion controls
 | 
			
		||||
const EXP_CHCW = [
 | 
			
		||||
    ctrl("ICE", 1, 1)
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// ECR expansion controls
 | 
			
		||||
const EXP_ECR = [
 | 
			
		||||
    ctrl("FECC", 16, 16),
 | 
			
		||||
    ctrl("EICC",  0, 16)
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// PIR expansion controls
 | 
			
		||||
const EXP_PIR = [
 | 
			
		||||
    ctrl("PT", 0, 16, true)
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// PSW expansion controls
 | 
			
		||||
const EXP_PSW = [
 | 
			
		||||
    ctrl("CY",  3, 1), ctrl("FRO",  9, 1),
 | 
			
		||||
    ctrl("OV",  2, 1), ctrl("FIV",  8, 1),
 | 
			
		||||
    ctrl("S" ,  1, 1), ctrl("FZD",  7, 1),
 | 
			
		||||
    ctrl("Z" ,  0, 1), ctrl("FOV",  6, 1),
 | 
			
		||||
    ctrl("NP", 15, 1), ctrl("FUD",  5, 1),
 | 
			
		||||
    ctrl("EP", 14, 1), ctrl("FPR",  4, 1),
 | 
			
		||||
    ctrl("ID", 12, 1), ctrl("I"  , 16, 4),
 | 
			
		||||
    ctrl("AE", 13, 1)
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// TKCW expansion controls
 | 
			
		||||
const EXP_TKCW = [
 | 
			
		||||
    ctrl("FIT", 7, 1, true), ctrl("FUT", 4, 1, true),
 | 
			
		||||
    ctrl("FZT", 6, 1, true), ctrl("FPT", 3, 1, true),
 | 
			
		||||
    ctrl("FVT", 5, 1, true), ctrl("OTM", 8, 1, true),
 | 
			
		||||
    ctrl("RDI", 2, 1, true), ctrl("RD" , 0, 2, true)
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                 Register                                  //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// One register within a register list
 | 
			
		||||
class Register {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(list, index, andMask, orMask) {
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.andMask    = andMask;
 | 
			
		||||
        this.app        = list.app;
 | 
			
		||||
        this.controls   = [];
 | 
			
		||||
        this.dasm       = list.dasm;
 | 
			
		||||
        this.format     = HEX;
 | 
			
		||||
        this.index      = index;
 | 
			
		||||
        this.isExpanded = null;
 | 
			
		||||
        this.list       = list;
 | 
			
		||||
        this.metrics    = { width: 0, height: 0 };
 | 
			
		||||
        this.orMask     = orMask;
 | 
			
		||||
        this.sim        = list.sim;
 | 
			
		||||
        this.system     = list.system;
 | 
			
		||||
        this.value      = 0x00000000;
 | 
			
		||||
 | 
			
		||||
        // Establish elements
 | 
			
		||||
        let row = document.createElement("tr");
 | 
			
		||||
        let cell;
 | 
			
		||||
        list.view.append(row);
 | 
			
		||||
 | 
			
		||||
        // Processing by type
 | 
			
		||||
        this[this.system ? "initSystem" : "initProgram"]();
 | 
			
		||||
 | 
			
		||||
        // Expansion button
 | 
			
		||||
        this.btnExpand = new Toolkit.Component(this.app, {
 | 
			
		||||
            className: "tk tk-expand tk-mono",
 | 
			
		||||
            tagName  : "div"
 | 
			
		||||
        });
 | 
			
		||||
        row .append(cell = document.createElement("td"));
 | 
			
		||||
        cell.className   = "tk";
 | 
			
		||||
        cell.style.width = "1px";
 | 
			
		||||
        cell.append(this.btnExpand.element);
 | 
			
		||||
 | 
			
		||||
        // Name label
 | 
			
		||||
        this.lblName = document.createElement("div");
 | 
			
		||||
        Object.assign(this.lblName, {
 | 
			
		||||
            className: "tk tk-name",
 | 
			
		||||
            id       : Toolkit.id(),
 | 
			
		||||
            innerText: this.dasm.sysregCaps?this.name:this.name.toLowerCase()
 | 
			
		||||
        });
 | 
			
		||||
        this.lblName.style.userSelect = "none";
 | 
			
		||||
        row .append(cell = document.createElement("td"));
 | 
			
		||||
        cell.className = "tk";
 | 
			
		||||
        cell.append(this.lblName);
 | 
			
		||||
 | 
			
		||||
        // Value text box
 | 
			
		||||
        this.txtValue = new Toolkit.TextBox(this.app, {
 | 
			
		||||
            className: "tk tk-textbox tk-mono",
 | 
			
		||||
            maxLength: 8
 | 
			
		||||
        });
 | 
			
		||||
        this.txtValue.setAttribute("aria-labelledby", this.lblName.id);
 | 
			
		||||
        this.txtValue.setAttribute("digits", "8");
 | 
			
		||||
        this.txtValue.addEventListener("action", e=>this.onValue());
 | 
			
		||||
        row .append(cell = document.createElement("td"));
 | 
			
		||||
        Object.assign(cell.style, {
 | 
			
		||||
            textAlign: "right",
 | 
			
		||||
            width    : "1px"
 | 
			
		||||
        });
 | 
			
		||||
        cell.className = "tk";
 | 
			
		||||
        cell.append(this.txtValue.element);
 | 
			
		||||
 | 
			
		||||
        // Expansion area
 | 
			
		||||
        if (this.expansion != null)
 | 
			
		||||
            this.list.view.append(this.expansion);
 | 
			
		||||
 | 
			
		||||
        // Enable expansion function
 | 
			
		||||
        if (this.expansion != null) {
 | 
			
		||||
            let key     = e=>this.expandKeyDown    (e);
 | 
			
		||||
            let pointer = e=>this.expandPointerDown(e);
 | 
			
		||||
            this.btnExpand.setAttribute("aria-controls", this.expansion.id);
 | 
			
		||||
            this.btnExpand.setAttribute("aria-labelledby", this.lblName.id);
 | 
			
		||||
            this.btnExpand.setAttribute("role", "button");
 | 
			
		||||
            this.btnExpand.setAttribute("tabindex", "0");
 | 
			
		||||
            this.btnExpand.addEventListener("keydown"    , key    );
 | 
			
		||||
            this.btnExpand.addEventListener("pointerdown", pointer);
 | 
			
		||||
            this.lblName  .addEventListener("pointerdown", pointer);
 | 
			
		||||
            this.setExpanded(this.system && this.index == PSW);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Expansion function is unavailable
 | 
			
		||||
        else this.btnExpand.setAttribute("aria-hidden", "true");
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set up a program register
 | 
			
		||||
    initProgram() {
 | 
			
		||||
        this.name = PROREGS[this.index] || "r" + this.index.toString();
 | 
			
		||||
        this.initExpansion(EXP_PROGRAM);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set up a system register
 | 
			
		||||
    initSystem() {
 | 
			
		||||
        this.name = SYSREGS[this.index] || this.index.toString();
 | 
			
		||||
 | 
			
		||||
        switch (this.index) {
 | 
			
		||||
            case CHCW :
 | 
			
		||||
                this.initExpansion(EXP_CHCW); break;
 | 
			
		||||
            case ECR  :
 | 
			
		||||
                this.initExpansion(EXP_ECR ); break;
 | 
			
		||||
            case EIPSW: case FEPSW: case PSW:
 | 
			
		||||
                this.initExpansion(EXP_PSW ); break;
 | 
			
		||||
            case PIR  :
 | 
			
		||||
                this.initExpansion(EXP_PIR ); break;
 | 
			
		||||
            case TKCW :
 | 
			
		||||
                this.initExpansion(EXP_TKCW); break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Initialize expansion controls
 | 
			
		||||
    initExpansion(controls) {
 | 
			
		||||
        let two = this.index == ECR || this.index == PIR;
 | 
			
		||||
 | 
			
		||||
        // Establish expansion element
 | 
			
		||||
        let exp = this.expansion = document.createElement("tr");
 | 
			
		||||
        exp.contents = new Toolkit.Component(this.app, {
 | 
			
		||||
            className: "tk tk-expansion",
 | 
			
		||||
            id       : Toolkit.id(),
 | 
			
		||||
            tagName  : "div",
 | 
			
		||||
            style    : {
 | 
			
		||||
                display            : "grid",
 | 
			
		||||
                gridTemplateColumns:
 | 
			
		||||
                    this.system ? "repeat(2, max-content)" : "max-content"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        let cell = document.createElement("td");
 | 
			
		||||
        cell.className = "tk";
 | 
			
		||||
        cell.colSpan   = "3";
 | 
			
		||||
        cell.append(exp.contents.element);
 | 
			
		||||
        exp.append(cell);
 | 
			
		||||
        exp = exp.contents;
 | 
			
		||||
 | 
			
		||||
        // Produce program register controls
 | 
			
		||||
        if (!this.system) {
 | 
			
		||||
            let group = new Toolkit.Group();
 | 
			
		||||
            exp.append(group);
 | 
			
		||||
 | 
			
		||||
            // Process all controls
 | 
			
		||||
            for (let template of controls) {
 | 
			
		||||
 | 
			
		||||
                // Create control
 | 
			
		||||
                let ctrl = new Toolkit.Radio(this.app, {
 | 
			
		||||
                    group   : group,
 | 
			
		||||
                    selected: template.shift,
 | 
			
		||||
                    text    : template.name
 | 
			
		||||
                });
 | 
			
		||||
                ctrl.format = template.size;
 | 
			
		||||
 | 
			
		||||
                // Configure event handler
 | 
			
		||||
                ctrl.addEventListener("action",
 | 
			
		||||
                    e=>this.setFormat(e.component.format));
 | 
			
		||||
 | 
			
		||||
                // Add the control to the element
 | 
			
		||||
                let box = document.createElement("div");
 | 
			
		||||
                box.append(ctrl.element);
 | 
			
		||||
                exp.append(box);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Process all control templates
 | 
			
		||||
        for (let template of controls) {
 | 
			
		||||
            let box, ctrl;
 | 
			
		||||
 | 
			
		||||
            // Not using an inner two-column layout
 | 
			
		||||
            if (!two)
 | 
			
		||||
                exp.append(box = document.createElement("div"));
 | 
			
		||||
 | 
			
		||||
            // Bit check box
 | 
			
		||||
            if (template.size == 1) {
 | 
			
		||||
                box.classList.add("tk-bit");
 | 
			
		||||
 | 
			
		||||
                // Create control
 | 
			
		||||
                ctrl = new Toolkit.CheckBox(this.app, {
 | 
			
		||||
                    text         : "name",
 | 
			
		||||
                    substitutions: { name: template.name }
 | 
			
		||||
                });
 | 
			
		||||
                ctrl.mask = 1 << template.shift;
 | 
			
		||||
                box.append(ctrl.element);
 | 
			
		||||
 | 
			
		||||
                // Disable control
 | 
			
		||||
                if (template.disabled)
 | 
			
		||||
                    ctrl.setEnabled(false);
 | 
			
		||||
 | 
			
		||||
                // Configure event handler
 | 
			
		||||
                ctrl.addEventListener("action", e=>this.onBit(e.component));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Number text box
 | 
			
		||||
            else {
 | 
			
		||||
                if (!two)
 | 
			
		||||
                    box.classList.add("tk-number");
 | 
			
		||||
 | 
			
		||||
                // Create label
 | 
			
		||||
                let label = document.createElement("label");
 | 
			
		||||
                Object.assign(label, {
 | 
			
		||||
                    className: "tk tk-label",
 | 
			
		||||
                    innerText: template.name,
 | 
			
		||||
                });
 | 
			
		||||
                if (!two) Object.assign(box.style, {
 | 
			
		||||
                    columnGap          : "2px",
 | 
			
		||||
                    display            : "grid",
 | 
			
		||||
                    gridTemplateColumns: "max-content auto"
 | 
			
		||||
                });
 | 
			
		||||
                (two ? exp : box).append(label);
 | 
			
		||||
 | 
			
		||||
                // Create control
 | 
			
		||||
                ctrl = new Toolkit.TextBox(this.app, {
 | 
			
		||||
                    id   : Toolkit.id(),
 | 
			
		||||
                    style: { height: "1em" }
 | 
			
		||||
                });
 | 
			
		||||
                label.htmlFor = ctrl.id;
 | 
			
		||||
                (two ? exp : box).append(ctrl.element);
 | 
			
		||||
 | 
			
		||||
                // Control is a hex field
 | 
			
		||||
                if (template.size == 16) {
 | 
			
		||||
                    ctrl.element.classList.add("tk-mono");
 | 
			
		||||
                    ctrl.setAttribute("digits", 4);
 | 
			
		||||
                    ctrl.setMaxLength(4);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Disable control
 | 
			
		||||
                if (template.disabled) {
 | 
			
		||||
                    ctrl.setEnabled(false);
 | 
			
		||||
                    (two ? label : box).setAttribute("disabled", "true");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Configure event handler
 | 
			
		||||
                ctrl.addEventListener("action", e=>this.onNumber(e.component));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Object.assign(ctrl, template);
 | 
			
		||||
            this.controls.push(ctrl);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Event Handlers //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Expand button key press
 | 
			
		||||
    expandKeyDown(e) {
 | 
			
		||||
 | 
			
		||||
        // Processing by key
 | 
			
		||||
        switch (e.key) {
 | 
			
		||||
            case "Enter":
 | 
			
		||||
            case " ":
 | 
			
		||||
                this.setExpanded(!this.isExpanded);
 | 
			
		||||
                break;
 | 
			
		||||
            default: return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Configure event
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Expand button pointer down
 | 
			
		||||
    expandPointerDown(e) {
 | 
			
		||||
 | 
			
		||||
        // Focus management
 | 
			
		||||
        this.btnExpand.focus();
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (e.button != 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Configure event
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
 | 
			
		||||
        // Configure expansion area
 | 
			
		||||
        this.setExpanded(!this.isExpanded);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Expansion bit check box
 | 
			
		||||
    onBit(ctrl) {
 | 
			
		||||
        this.setValue(ctrl.isSelected ?
 | 
			
		||||
            this.value | ctrl.mask :
 | 
			
		||||
            this.value & Util.u32(~ctrl.mask)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Expansion number text box
 | 
			
		||||
    onNumber(ctrl) {
 | 
			
		||||
        let mask  = (1 << ctrl.size) - 1 << ctrl.shift;
 | 
			
		||||
        let value = parseInt(ctrl.getText(), ctrl.size == 16 ? 16 : 10);
 | 
			
		||||
        this.setValue(isNaN(value) ? this.value :
 | 
			
		||||
            this.value & Util.u32(~mask) | value << ctrl.shift & mask);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Register value
 | 
			
		||||
    onValue() {
 | 
			
		||||
        let text = this.txtValue.getText();
 | 
			
		||||
        let value;
 | 
			
		||||
 | 
			
		||||
        // Processing by type
 | 
			
		||||
        switch (this.format) {
 | 
			
		||||
 | 
			
		||||
            // Unsigned hexadecimal
 | 
			
		||||
            case HEX:
 | 
			
		||||
                value = parseInt(text, 16);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            // Decimal
 | 
			
		||||
            case SIGNED:
 | 
			
		||||
            case UNSIGNED:
 | 
			
		||||
                value = parseInt(text);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            // Float
 | 
			
		||||
            case FLOAT:
 | 
			
		||||
                value = parseFloat(text);
 | 
			
		||||
                if (isNaN(value))
 | 
			
		||||
                    break;
 | 
			
		||||
                value = Util.fromF32(value);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Assign the new value
 | 
			
		||||
        this.setValue(isNaN(value) ? this.value : value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Disassembler settings have been updated
 | 
			
		||||
    dasmChanged() {
 | 
			
		||||
        let dasm = this.list.dasm;
 | 
			
		||||
        let name = this.name;
 | 
			
		||||
 | 
			
		||||
        // Program register name
 | 
			
		||||
        if (!this.system) {
 | 
			
		||||
            if (!dasm.proregNames)
 | 
			
		||||
                name = "r" + this.index.toString();
 | 
			
		||||
            if (dasm.proregCaps)
 | 
			
		||||
                name = name.toUpperCase();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // System register name
 | 
			
		||||
        else {
 | 
			
		||||
            if (!dasm.sysregCaps)
 | 
			
		||||
                name = name.toLowerCase();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Common processing
 | 
			
		||||
        this.lblName.innerText = name;
 | 
			
		||||
        this.refresh(this.value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update the value returned from the core
 | 
			
		||||
    refresh(value) {
 | 
			
		||||
        let text;
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.value = value = Util.u32(value);
 | 
			
		||||
 | 
			
		||||
        // Value text box
 | 
			
		||||
        switch (this.format) {
 | 
			
		||||
 | 
			
		||||
            // Unsigned hexadecimal
 | 
			
		||||
            case HEX:
 | 
			
		||||
                text = value.toString(16).padStart(8, "0");
 | 
			
		||||
                if (this.dasm.hexCaps)
 | 
			
		||||
                    text = text.toUpperCase();
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            // Signed decimal
 | 
			
		||||
            case SIGNED:
 | 
			
		||||
                text = Util.s32(value).toString();
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            // Unsigned decial
 | 
			
		||||
            case UNSIGNED:
 | 
			
		||||
                text = Util.u32(value).toString();
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            // Float
 | 
			
		||||
            case FLOAT:
 | 
			
		||||
                if ((value & 0x7F800000) != 0x7F800000) {
 | 
			
		||||
                    text = Util.toF32(value).toFixed(5).replace(/0+$/, "");
 | 
			
		||||
                    if (text.endsWith("."))
 | 
			
		||||
                        text += "0";
 | 
			
		||||
                } else text = "NaN";
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
        this.txtValue.setText(text);
 | 
			
		||||
 | 
			
		||||
        // No further processing for program registers
 | 
			
		||||
        if (!this.system)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Process all expansion controls
 | 
			
		||||
        for (let ctrl of this.controls) {
 | 
			
		||||
 | 
			
		||||
            // Bit check box
 | 
			
		||||
            if (ctrl.size == 1) {
 | 
			
		||||
                ctrl.setSelected(value & ctrl.mask);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Integer text box
 | 
			
		||||
            text = value >> ctrl.shift & (1 << ctrl.size) - 1;
 | 
			
		||||
            text = ctrl.size != 16 ? text.toString() :
 | 
			
		||||
                text.toString(16).padStart(4, "0");
 | 
			
		||||
            if (this.dasm.hexCaps)
 | 
			
		||||
                text = text.toUpperCase();
 | 
			
		||||
            ctrl.setText(text);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether the expansion area is visible
 | 
			
		||||
    setExpanded(expanded) {
 | 
			
		||||
        expanded = !!expanded;
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (this.expansion == null || expanded === this.isExpanded)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.isExpanded = expanded;
 | 
			
		||||
 | 
			
		||||
        // Configure elements
 | 
			
		||||
        let key = expanded ? "common.collapse" : "common.expand";
 | 
			
		||||
        this.btnExpand.setAttribute("aria-expanded", expanded);
 | 
			
		||||
        this.btnExpand.setToolTip(key);
 | 
			
		||||
        this.expansion.style.display =
 | 
			
		||||
            expanded ? "table-row" : "none";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify the font metrics
 | 
			
		||||
    setMetrics(width, height) {
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.metrics = { width: width, height: height };
 | 
			
		||||
 | 
			
		||||
        // Height
 | 
			
		||||
        height += "px";
 | 
			
		||||
        this.txtValue.element.style.height = height;
 | 
			
		||||
        for (let ctrl of this.controls.filter(c=>c.size > 1))
 | 
			
		||||
            ctrl.element.style.height = height;
 | 
			
		||||
 | 
			
		||||
        // Hexadecimal formatting
 | 
			
		||||
        if (this.format == HEX) {
 | 
			
		||||
            this.txtValue.element.style.width = (width * 8) + "px";
 | 
			
		||||
            this.txtValue.setMaxLength(8);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Decimal formatting
 | 
			
		||||
        else {
 | 
			
		||||
            this.txtValue.element.style.removeProperty("width");
 | 
			
		||||
            this.txtValue.setMaxLength(null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Expansion text boxes
 | 
			
		||||
        for (let box of this.controls.filter(c=>c.size > 1)) {
 | 
			
		||||
            box.element.style.height = height;
 | 
			
		||||
            if (box.size == 16)
 | 
			
		||||
                box.element.style.width = (width * 4) + "px";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Private Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Specify the formatting type of the register value
 | 
			
		||||
    setFormat(format) {
 | 
			
		||||
        if (format == this.format)
 | 
			
		||||
            return;
 | 
			
		||||
        this.format = format;
 | 
			
		||||
        this.txtValue.element
 | 
			
		||||
            .classList[format == HEX ? "add" : "remove"]("tk-mono");
 | 
			
		||||
        this.setMetrics(this.metrics.width, this.metrics.height);
 | 
			
		||||
        this.refresh(this.value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify a new value for the register
 | 
			
		||||
    async setValue(value) {
 | 
			
		||||
 | 
			
		||||
        // Update the display with the new value immediately
 | 
			
		||||
        value = Util.u32(value & this.andMask | this.orMask);
 | 
			
		||||
        let matched = value == this.value;
 | 
			
		||||
        this.refresh(value);
 | 
			
		||||
        if (matched)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Update the new value in the core
 | 
			
		||||
        let options = { refresh: true };
 | 
			
		||||
        this.refresh(await (
 | 
			
		||||
           !this.system ?
 | 
			
		||||
                this.sim.setProgramRegister(this.index, value, options) :
 | 
			
		||||
            this.index == PC ?
 | 
			
		||||
                this.sim.setProgramCounter (            value, options) :
 | 
			
		||||
                this.sim.setSystemRegister (this.index, value, options)
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                               RegisterList                                //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Scrolling list of registers
 | 
			
		||||
class RegisterList extends Toolkit.ScrollPane {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(debug, system) {
 | 
			
		||||
        super(debug.app, {
 | 
			
		||||
            className: "tk tk-scrollpane tk-reglist " +
 | 
			
		||||
                (system ? "tk-system" : "tk-program"),
 | 
			
		||||
            vertical : Toolkit.ScrollPane.ALWAYS
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.app          = debug.app;
 | 
			
		||||
        this.dasm         = debug.disassembler;
 | 
			
		||||
        this.method       = system?"getSystemRegisters":"getProgramRegisters";
 | 
			
		||||
        this.registers    = [];
 | 
			
		||||
        this.sim          = debug.sim;
 | 
			
		||||
        this.subscription = system ? "sysregs" : "proregs";
 | 
			
		||||
        this.system       = system;
 | 
			
		||||
 | 
			
		||||
        // Configure view element
 | 
			
		||||
        this.setView(new Toolkit.Component(debug.app, {
 | 
			
		||||
            className: "tk tk-list",
 | 
			
		||||
            tagName  : "table",
 | 
			
		||||
            style    : {
 | 
			
		||||
                width: "100%"
 | 
			
		||||
            }
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        // Font-measuring element
 | 
			
		||||
        let text = "";
 | 
			
		||||
        for (let x = 0; x < 16; x++) {
 | 
			
		||||
            if (x != 0) text += "\n";
 | 
			
		||||
            let digit = x.toString(16);
 | 
			
		||||
            text += digit + "\n" + digit.toUpperCase();
 | 
			
		||||
        }
 | 
			
		||||
        this.metrics = new Toolkit.Component(this.app, {
 | 
			
		||||
            className: "tk tk-mono",
 | 
			
		||||
            tagName  : "div",
 | 
			
		||||
            style    : {
 | 
			
		||||
                position  : "absolute",
 | 
			
		||||
                visibility: "hidden"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        this.metrics.element.innerText = text;
 | 
			
		||||
        this.metrics.addEventListener("resize", e=>this.onMetrics());
 | 
			
		||||
        this.viewport.append(this.metrics.element);
 | 
			
		||||
 | 
			
		||||
        // Processing by type
 | 
			
		||||
        this[system ? "initSystem" : "initProgram"]();
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        this.addEventListener("keydown", e=>this.onKeyDown   (e));
 | 
			
		||||
        this.addEventListener("wheel"  , e=>this.onMouseWheel(e));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Initialize a list of program registers
 | 
			
		||||
    initProgram() {
 | 
			
		||||
        this.add(new Register(this, 0, 0x00000000, 0x00000000));
 | 
			
		||||
        for (let x = 1; x < 32; x++)
 | 
			
		||||
            this.add(new Register(this, x, 0xFFFFFFFF, 0x00000000));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Initialie a list of system registers
 | 
			
		||||
    initSystem() {
 | 
			
		||||
        this.add(new Register(this, PC   , 0xFFFFFFFE, 0x00000000));
 | 
			
		||||
        this.add(new Register(this, PSW  , 0x000FF3FF, 0x00000000));
 | 
			
		||||
        this.add(new Register(this, ADTRE, 0xFFFFFFFE, 0x00000000));
 | 
			
		||||
        this.add(new Register(this, CHCW , 0x00000002, 0x00000000));
 | 
			
		||||
        this.add(new Register(this, ECR  , 0xFFFFFFFF, 0x00000000));
 | 
			
		||||
        this.add(new Register(this, EIPC , 0xFFFFFFFE, 0x00000000));
 | 
			
		||||
        this.add(new Register(this, EIPSW, 0x000FF3FF, 0x00000000));
 | 
			
		||||
        this.add(new Register(this, FEPC , 0xFFFFFFFE, 0x00000000));
 | 
			
		||||
        this.add(new Register(this, FEPSW, 0x000FF3FF, 0x00000000));
 | 
			
		||||
        this.add(new Register(this, PIR  , 0x00000000, 0x00005346));
 | 
			
		||||
        this.add(new Register(this, TKCW , 0x00000000, 0x000000E0));
 | 
			
		||||
        this.add(new Register(this, 29   , 0xFFFFFFFF, 0x00000000));
 | 
			
		||||
        this.add(new Register(this, 30   , 0x00000000, 0x00000004));
 | 
			
		||||
        this.add(new Register(this, 31   , 0xFFFFFFFF, 0x00000000));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Event Handlers //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Key press
 | 
			
		||||
    onKeyDown(e) {
 | 
			
		||||
 | 
			
		||||
        // Processing by key
 | 
			
		||||
        switch (e.key) {
 | 
			
		||||
            case "ArrowDown":
 | 
			
		||||
                this.vertical.setValue(this.vertical.value +
 | 
			
		||||
                    this.vertical.increment);
 | 
			
		||||
                break;
 | 
			
		||||
            case "ArrowLeft":
 | 
			
		||||
                this.horizontal.setValue(this.horizontal.value -
 | 
			
		||||
                    this.horizontal.increment);
 | 
			
		||||
                break;
 | 
			
		||||
            case "ArrowRight":
 | 
			
		||||
                this.horizontal.setValue(this.horizontal.value +
 | 
			
		||||
                    this.horizontal.increment);
 | 
			
		||||
                break;
 | 
			
		||||
            case "ArrowUp":
 | 
			
		||||
                this.vertical.setValue(this.vertical.value -
 | 
			
		||||
                    this.vertical.increment);
 | 
			
		||||
                break;
 | 
			
		||||
            case "PageDown":
 | 
			
		||||
                this.vertical.setValue(this.vertical.value +
 | 
			
		||||
                    this.vertical.extent);
 | 
			
		||||
                break;
 | 
			
		||||
            case "PageUp":
 | 
			
		||||
                this.vertical.setValue(this.vertical.value -
 | 
			
		||||
                    this.vertical.extent);
 | 
			
		||||
                break;
 | 
			
		||||
            default: return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Configure event
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Metrics element resized
 | 
			
		||||
    onMetrics() {
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (!this.metrics)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Measure the dimensions of one hex character
 | 
			
		||||
        let bounds = this.metrics.getBounds();
 | 
			
		||||
        if (bounds.height <= 0)
 | 
			
		||||
            return;
 | 
			
		||||
        let width  = Math.ceil(bounds.width);
 | 
			
		||||
        let height = Math.ceil(bounds.height / 32);
 | 
			
		||||
 | 
			
		||||
        // Resize all text boxes
 | 
			
		||||
        for (let reg of this.registers)
 | 
			
		||||
            reg.setMetrics(width, height);
 | 
			
		||||
 | 
			
		||||
        // Update scroll bars
 | 
			
		||||
        this.horizontal.setIncrement(height);
 | 
			
		||||
        this.vertical  .setIncrement(height);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Mouse wheel
 | 
			
		||||
    onMouseWheel(e) {
 | 
			
		||||
 | 
			
		||||
        // User agent scaling action
 | 
			
		||||
        if (e.ctrlKey)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // No rotation has occurred
 | 
			
		||||
        let offset = Math.sign(e.deltaY) * 3;
 | 
			
		||||
        if (offset == 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Update the display address
 | 
			
		||||
        this.vertical.setValue(this.vertical.value +
 | 
			
		||||
            this.vertical.increment * offset);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Update with CPU state from the core
 | 
			
		||||
    refresh(registers) {
 | 
			
		||||
 | 
			
		||||
        // System registers
 | 
			
		||||
        if (this.system) {
 | 
			
		||||
            for (let reg of Object.entries(SYSREGS))
 | 
			
		||||
                this[reg[0]].refresh(registers[reg[1].toLowerCase()]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Program registers
 | 
			
		||||
        else for (let x = 0; x < 32; x++)
 | 
			
		||||
            this[x].refresh(registers[x]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Subscribe to or unsubscribe from core updates
 | 
			
		||||
    setSubscribed(subscribed) {
 | 
			
		||||
        subscribed = !!subscribed;
 | 
			
		||||
 | 
			
		||||
        // Nothing to change
 | 
			
		||||
        if (subscribed == this.isSubscribed)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.isSubscribed = subscribed;
 | 
			
		||||
 | 
			
		||||
        // Subscribe to core updates
 | 
			
		||||
        if (subscribed)
 | 
			
		||||
            this.fetch();
 | 
			
		||||
 | 
			
		||||
        // Unsubscribe from core updates
 | 
			
		||||
        else this.sim.unsubscribe(this.subscription);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Disassembler settings have been updated
 | 
			
		||||
    dasmChanged() {
 | 
			
		||||
        for (let reg of this.registers)
 | 
			
		||||
            reg.dasmChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Determine the initial size of the register list
 | 
			
		||||
    getPreferredSize() {
 | 
			
		||||
        let ret = {
 | 
			
		||||
            height: 0,
 | 
			
		||||
            width : 0
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (!this.view)
 | 
			
		||||
            return ret;
 | 
			
		||||
 | 
			
		||||
        // Measure the view element
 | 
			
		||||
        ret.width = this.view.element.scrollWidth;
 | 
			
		||||
 | 
			
		||||
        // Locate the bottom of PSW
 | 
			
		||||
        if (this.system && this[PSW].expansion) {
 | 
			
		||||
            ret.height = this[PSW].expansion.getBoundingClientRect().bottom -
 | 
			
		||||
                this.view.getBounds().top;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Private Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Add a register to the list
 | 
			
		||||
    add(reg) {
 | 
			
		||||
        this[reg.index] = reg;
 | 
			
		||||
        this.registers.push(reg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Retrieve CPU state from the core
 | 
			
		||||
    async fetch() {
 | 
			
		||||
        this.refresh(
 | 
			
		||||
            await this.sim[this.method]({
 | 
			
		||||
                subscribe: this.isSubscribed && this.subscription
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export { RegisterList };
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
let F32 = new Float32Array(               1);
 | 
			
		||||
let S32 = new Int32Array  (F32.buffer, 0, 1);
 | 
			
		||||
let U32 = new Uint32Array (F32.buffer, 0, 1);
 | 
			
		||||
 | 
			
		||||
// Interpret a floating short as a 32-bit integer
 | 
			
		||||
function fromF32(x) {
 | 
			
		||||
    F32[0] = x;
 | 
			
		||||
    return S32[0];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Interpret a 32-bit integer as a floating short
 | 
			
		||||
function toF32(x) {
 | 
			
		||||
    S32[0] = x;
 | 
			
		||||
    return F32[0];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Represent a value as a signed 32-bit integer
 | 
			
		||||
function s32(x) {
 | 
			
		||||
    S32[0] = x;
 | 
			
		||||
    return S32[0];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Sign-extend a value with a given number of bits
 | 
			
		||||
function signExtend(value, bits) {
 | 
			
		||||
    bits = 32 - bits;
 | 
			
		||||
    S32[0] = value << bits;
 | 
			
		||||
    return S32[0] >> bits;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Represent a value as an unsigned 32-bit integer
 | 
			
		||||
function u32(x) {
 | 
			
		||||
    U32[0] = x;
 | 
			
		||||
    return U32[0];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export let Util = {
 | 
			
		||||
    fromF32   : fromF32,
 | 
			
		||||
    toF32     : toF32,
 | 
			
		||||
    s32       : s32,
 | 
			
		||||
    signExtend: signExtend,
 | 
			
		||||
    u32       : u32
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,196 @@
 | 
			
		|||
import { Sim } from /**/"./Sim.js";
 | 
			
		||||
 | 
			
		||||
let url = u=>u.startsWith("data:")?u:new URL(u,import.meta.url).toString();
 | 
			
		||||
 | 
			
		||||
let RESTRICT   = {};
 | 
			
		||||
let WASM_URL   = url(/**/"./core.wasm"    );
 | 
			
		||||
let WORKER_URL = url(/**/"./CoreWorker.js");
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                   Core                                    //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Environment manager for simulated Virtual Boys
 | 
			
		||||
class Core {
 | 
			
		||||
 | 
			
		||||
    //////////////////////////////// Constants ////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // States
 | 
			
		||||
    static IDLE    = 0;
 | 
			
		||||
    static RUNNING = 1;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Static Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Create a new instance of Core
 | 
			
		||||
    static create(options) {
 | 
			
		||||
        return new Core(RESTRICT).init(options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Stub constructor
 | 
			
		||||
    constructor(restrict) {
 | 
			
		||||
        if (restrict != RESTRICT) {
 | 
			
		||||
            throw "Cannot instantiate Core directly. " +
 | 
			
		||||
                "Use Core.create() instead.";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Substitute constructor
 | 
			
		||||
    async init(options = {}) {
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.length           = 0;
 | 
			
		||||
        this.onsubscriptions  = null;
 | 
			
		||||
        this.resolutions      = [];
 | 
			
		||||
        this.state            = Core.IDLE;
 | 
			
		||||
        this.worker           = new Worker(WORKER_URL);
 | 
			
		||||
        this.worker.onmessage = e=>this.onMessage(e.data);
 | 
			
		||||
 | 
			
		||||
        // Issue a create command
 | 
			
		||||
        if ("sims" in options)
 | 
			
		||||
            await this.create(options.sims, WASM_URL);
 | 
			
		||||
 | 
			
		||||
        // Only initialize the WebAssembly module
 | 
			
		||||
        else this.send("init", false, { wasm: WASM_URL });
 | 
			
		||||
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Event Handlers //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Worker message received
 | 
			
		||||
    onMessage(data) {
 | 
			
		||||
 | 
			
		||||
        // Process a promised response
 | 
			
		||||
        if ("response" in data)
 | 
			
		||||
            this.resolutions.shift()(data.response);
 | 
			
		||||
 | 
			
		||||
        // Process subscriptions
 | 
			
		||||
        if (this.onsubscriptions && data.subscriptions)
 | 
			
		||||
            this.onsubscriptions(data.subscriptions);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Associate two simulations as peers, or remove an association
 | 
			
		||||
    connect(a, b, options = {}) {
 | 
			
		||||
        return this.send({
 | 
			
		||||
            command: "connect",
 | 
			
		||||
            respond: !("respond" in options) || !!options.respond,
 | 
			
		||||
            sims   : [ a, b ]
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create and initialize new simulations
 | 
			
		||||
    async create(sims, wasm) {
 | 
			
		||||
        let numSims  = sims===undefined ? 1 : Math.max(0, parseInt(sims) || 0);
 | 
			
		||||
 | 
			
		||||
        // Execute the command in the core thread
 | 
			
		||||
        let response = await this.send({
 | 
			
		||||
            command: "create",
 | 
			
		||||
            sims   : numSims,
 | 
			
		||||
            wasm   : wasm
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Process the core thread's response
 | 
			
		||||
        let ret = [];
 | 
			
		||||
        for (let x = 0; x < numSims; x++, this.length++)
 | 
			
		||||
            ret.push(this[this.length] =
 | 
			
		||||
                new Sim(this, response[x], this.length));
 | 
			
		||||
        return sims === undefined ? ret[0] : ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Delete a simulation
 | 
			
		||||
    destroy(sim, options = {}) {
 | 
			
		||||
 | 
			
		||||
        // Configure simulation
 | 
			
		||||
            sim = this[sim] || sim;
 | 
			
		||||
        if (sim.core != this)
 | 
			
		||||
            return;
 | 
			
		||||
        let ptr = sim.destroy();
 | 
			
		||||
 | 
			
		||||
        // State management
 | 
			
		||||
        for (let x = sim.index + 1; x < this.length; x++)
 | 
			
		||||
            (this[x - 1] = this[x]).index--;
 | 
			
		||||
        delete this[--this.length];
 | 
			
		||||
 | 
			
		||||
        // Execute the command on the core thread
 | 
			
		||||
        return this.send({
 | 
			
		||||
            command: "destroy",
 | 
			
		||||
            respond: !("respond" in options) || !!options.respond,
 | 
			
		||||
            sim    : ptr
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Attempt to run until the next instruction
 | 
			
		||||
    runNext(a, b, options = {}) {
 | 
			
		||||
        return this.send({
 | 
			
		||||
            command: "runNext",
 | 
			
		||||
            refresh: !!options.refresh,
 | 
			
		||||
            respond: !("respond" in options) || !!options.respond,
 | 
			
		||||
            sims   : [ a, b ]
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Execute one instruction
 | 
			
		||||
    singleStep(a, b, options = {}) {
 | 
			
		||||
        return this.send({
 | 
			
		||||
            command: "singleStep",
 | 
			
		||||
            refresh: !!options.refresh,
 | 
			
		||||
            respond: !("respond" in options) || !!options.respond,
 | 
			
		||||
            sims   : [ a, b ]
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Unsubscribe from frame data
 | 
			
		||||
    unsubscribe(key, sim = 0) {
 | 
			
		||||
        this.send({
 | 
			
		||||
            command: "unsubscribe",
 | 
			
		||||
            key    : key,
 | 
			
		||||
            respond: false,
 | 
			
		||||
            sim    : sim
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Private Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Send a message to the Worker
 | 
			
		||||
    send(data = {}, transfers = []) {
 | 
			
		||||
 | 
			
		||||
        // Create the message object
 | 
			
		||||
        Object.assign(data, {
 | 
			
		||||
            respond: !("respond" in data) || !!data.respond,
 | 
			
		||||
            run    : !("run"     in data) || !!data.run
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Do not wait on a response
 | 
			
		||||
        if (!data.respond)
 | 
			
		||||
            this.worker.postMessage(data, transfers);
 | 
			
		||||
 | 
			
		||||
        // Wait for the response to come back
 | 
			
		||||
        else return new Promise((resolve, reject)=>{
 | 
			
		||||
            this.resolutions.push(response=>resolve(response));
 | 
			
		||||
            this.worker.postMessage(data, transfers);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
export { Core };
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,377 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Un-sign a 32-bit integer
 | 
			
		||||
// Emscripten is sign-extending uint32_t and Firefox can't import in Workers
 | 
			
		||||
let u32 = (()=>{
 | 
			
		||||
    let U32 = new Uint32Array(1);
 | 
			
		||||
    return x=>{ U32[0] = x; return U32[0]; };
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                CoreWorker                                 //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Thread manager for Core commands
 | 
			
		||||
new class CoreWorker {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Stub constructor
 | 
			
		||||
    constructor() {
 | 
			
		||||
        onmessage = async e=>{
 | 
			
		||||
            await this.init(e.data.wasm);
 | 
			
		||||
            onmessage = e=>this.onCommand(e.data, false);
 | 
			
		||||
            onmessage(e);
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Substitute constructor
 | 
			
		||||
    async init(wasm) {
 | 
			
		||||
 | 
			
		||||
        // Load the WebAssembly module
 | 
			
		||||
        let imports = {
 | 
			
		||||
            env: { emscripten_notify_memory_growth: ()=>this.onMemory() }
 | 
			
		||||
        };
 | 
			
		||||
        this.wasm = await (typeof wasm == "string" ?
 | 
			
		||||
            WebAssembly.instantiateStreaming(fetch(wasm), imports) :
 | 
			
		||||
            WebAssembly.instantiate         (      wasm , imports)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.api           = this.wasm.instance.exports;
 | 
			
		||||
        this.frameData     = null;
 | 
			
		||||
        this.isRunning     = false;
 | 
			
		||||
        this.memory        = this.api.memory.buffer;
 | 
			
		||||
        this.ptrSize       = this.api.PointerSize();
 | 
			
		||||
        this.ptrType       = this.ptrSize == 4 ? Uint32Array : Uint64Array;
 | 
			
		||||
        this.subscriptions = {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Event Handlers //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Message from audio thread
 | 
			
		||||
    onAudio(frames) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Message from main thread
 | 
			
		||||
    onCommand(data) {
 | 
			
		||||
 | 
			
		||||
        // Subscribe to the command
 | 
			
		||||
        if (data.subscribe) {
 | 
			
		||||
            let sub = data.sim || 0;
 | 
			
		||||
            sub = this.subscriptions[sub] || (this.subscriptions[sub] = {});
 | 
			
		||||
            sub = sub[data.subscribe] = {};
 | 
			
		||||
            Object.assign(sub, data);
 | 
			
		||||
            delete sub.promised;
 | 
			
		||||
            delete sub.run;
 | 
			
		||||
            delete sub.subscribe;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Execute the command
 | 
			
		||||
        if (data.run)
 | 
			
		||||
            this[data.command](data);
 | 
			
		||||
 | 
			
		||||
        // Process all subscriptions to refresh any debugging interfaces
 | 
			
		||||
        if (data.refresh)
 | 
			
		||||
            this.doSubscriptions(data.sim ? [ data.sim ] : data.sims);
 | 
			
		||||
 | 
			
		||||
        // Reply to the main thread
 | 
			
		||||
        if (data.respond) {
 | 
			
		||||
            postMessage({
 | 
			
		||||
                response: data.response
 | 
			
		||||
            }, data.transfers);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Memory growth
 | 
			
		||||
    onMemory() {
 | 
			
		||||
        this.memory = this.api.memory.buffer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //////////////////////////////// Commands /////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Associate two simulations as peers, or remove an association
 | 
			
		||||
    connect(data) {
 | 
			
		||||
        this.api.vbConnect(data.sims[0], data.sims[1]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Allocate and initialize a new simulation
 | 
			
		||||
    create(data) {
 | 
			
		||||
        let ptr        = this.api.Create(data.sims);
 | 
			
		||||
        data.response  = new this.ptrType(this.memory, ptr, data.sims).slice();
 | 
			
		||||
        data.transfers = [ data.response.buffer ];
 | 
			
		||||
        this.api.Free(ptr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Delete a simulation
 | 
			
		||||
    destroy(data) {
 | 
			
		||||
        this.api.Destroy(data.sim);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Locate instructions for disassembly
 | 
			
		||||
    disassemble(data) {
 | 
			
		||||
        let decode; // Address of next row
 | 
			
		||||
        let index;  // Index in list of next row
 | 
			
		||||
        let rows = new Array(data.rows);
 | 
			
		||||
        let pc   = u32(this.api.vbGetProgramCounter(data.sim));
 | 
			
		||||
        let row;    // Located output row
 | 
			
		||||
 | 
			
		||||
        // The target address is before or on the first row of output
 | 
			
		||||
        if (data.row <= 0) {
 | 
			
		||||
            decode = u32(data.target - 4 * Math.max(0, data.row + 10));
 | 
			
		||||
 | 
			
		||||
            // Locate the target row
 | 
			
		||||
            for (;;) {
 | 
			
		||||
                row    = this.dasmRow(data.sim, decode, pc);
 | 
			
		||||
                if (u32(data.target - decode) < row.size)
 | 
			
		||||
                    break;
 | 
			
		||||
                decode = u32(decode + row.size);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Locate the first row of output
 | 
			
		||||
            for (index = data.row; index < 0; index++) {
 | 
			
		||||
                decode = u32(decode + row.size);
 | 
			
		||||
                row    = this.dasmRow(data.sim, decode, pc);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Prepare to process remaining rows
 | 
			
		||||
            decode  = u32(decode + row.size);
 | 
			
		||||
            rows[0] = row;
 | 
			
		||||
            index   = 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // The target address is after the first row of output
 | 
			
		||||
        else {
 | 
			
		||||
            let circle = new Array(data.row + 1);
 | 
			
		||||
            let count  = Math.min(data.row + 1, data.rows);
 | 
			
		||||
            let src    = 0;
 | 
			
		||||
            decode     = u32(data.target - 4 * (data.row + 10));
 | 
			
		||||
 | 
			
		||||
            // Locate the target row
 | 
			
		||||
            for (;;) {
 | 
			
		||||
                row    = circle[src] = this.dasmRow(data.sim, decode, pc);
 | 
			
		||||
                decode = u32(decode + row.size);
 | 
			
		||||
                if (u32(data.target - row.address) < row.size)
 | 
			
		||||
                    break;
 | 
			
		||||
                src    = (src + 1) % circle.length;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Copy entries from the circular buffer to the output list
 | 
			
		||||
            for (index = 0; index < count; index++) {
 | 
			
		||||
                src         = (src + 1) % circle.length;
 | 
			
		||||
                rows[index] = circle[src];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Locate any remaining rows
 | 
			
		||||
        for (; index < data.rows; index++) {
 | 
			
		||||
            let row = rows[index] = this.dasmRow(data.sim, decode, pc);
 | 
			
		||||
            decode  = u32(decode + row.size);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Respond to main thread
 | 
			
		||||
        data.response = {
 | 
			
		||||
            pc    : pc,
 | 
			
		||||
            rows  : rows,
 | 
			
		||||
            scroll: data.scroll
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Retrieve all CPU program registers
 | 
			
		||||
    getProgramRegisters(data) {
 | 
			
		||||
        let ret = data.response = new Uint32Array(32);
 | 
			
		||||
        for (let x = 0; x < 32; x++)
 | 
			
		||||
            ret[x] = this.api.vbGetProgramRegister(data.sim, x);
 | 
			
		||||
        data.transfers = [ ret.buffer ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Retrieve the value of a system register
 | 
			
		||||
    getSystemRegister(data) {
 | 
			
		||||
        data.response = u32(this.api.vbGetSystemRegister(data.sim, data.id));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Retrieve all CPU system registers (including PC)
 | 
			
		||||
    getSystemRegisters(data) {
 | 
			
		||||
        data.response = {
 | 
			
		||||
            adtre: u32(this.api.vbGetSystemRegister(data.sim, 25)),
 | 
			
		||||
            chcw : u32(this.api.vbGetSystemRegister(data.sim, 24)),
 | 
			
		||||
            ecr  : u32(this.api.vbGetSystemRegister(data.sim,  4)),
 | 
			
		||||
            eipc : u32(this.api.vbGetSystemRegister(data.sim,  0)),
 | 
			
		||||
            eipsw: u32(this.api.vbGetSystemRegister(data.sim,  1)),
 | 
			
		||||
            fepc : u32(this.api.vbGetSystemRegister(data.sim,  2)),
 | 
			
		||||
            fepsw: u32(this.api.vbGetSystemRegister(data.sim,  3)),
 | 
			
		||||
            pc   : u32(this.api.vbGetProgramCounter(data.sim    )),
 | 
			
		||||
            pir  : u32(this.api.vbGetSystemRegister(data.sim,  6)),
 | 
			
		||||
            psw  : u32(this.api.vbGetSystemRegister(data.sim,  5)),
 | 
			
		||||
            tkcw : u32(this.api.vbGetSystemRegister(data.sim,  7)),
 | 
			
		||||
            [29] : u32(this.api.vbGetSystemRegister(data.sim, 29)),
 | 
			
		||||
            [30] : u32(this.api.vbGetSystemRegister(data.sim, 30)),
 | 
			
		||||
            [31] : u32(this.api.vbGetSystemRegister(data.sim, 31))
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Read bytes from the simulation
 | 
			
		||||
    read(data) {
 | 
			
		||||
        let ptr    = this.api.Malloc(data.length);
 | 
			
		||||
        this.api.ReadBuffer(data.sim, ptr, data.address, data.length);
 | 
			
		||||
        let buffer = new Uint8Array(this.memory, ptr, data.length).slice();
 | 
			
		||||
        this.api.Free(ptr);
 | 
			
		||||
        data.response  = {
 | 
			
		||||
            address: data.address,
 | 
			
		||||
            bytes  : buffer
 | 
			
		||||
        };
 | 
			
		||||
        data.transfers = [ buffer.buffer ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Attempt to execute until the following instruction
 | 
			
		||||
    runNext(data) {
 | 
			
		||||
        this.api.RunNext(data.sims[0], data.sims[1]);
 | 
			
		||||
        let pc = [ u32(this.api.vbGetProgramCounter(data.sims[0])) ];
 | 
			
		||||
        if (data.sims[1])
 | 
			
		||||
            pc.push(u32(this.api.vbGetProgramCounter(data.sims[1])));
 | 
			
		||||
        data.response = {
 | 
			
		||||
            pc: pc
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify a new value for PC
 | 
			
		||||
    setProgramCounter(data) {
 | 
			
		||||
        data.response =
 | 
			
		||||
            u32(this.api.vbSetProgramCounter(data.sim, data.value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify a new value for a program register
 | 
			
		||||
    setProgramRegister(data) {
 | 
			
		||||
        data.response = this.api.vbSetProgramRegister
 | 
			
		||||
            (data.sim, data.id, data.value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify a ROM buffer
 | 
			
		||||
    setROM(data) {
 | 
			
		||||
        let ptr    = this.api.Malloc(data.rom.length);
 | 
			
		||||
        let buffer = new Uint8Array(this.memory, ptr, data.rom.length);
 | 
			
		||||
        for (let x = 0; x < data.rom.length; x++)
 | 
			
		||||
            buffer[x] = data.rom[x];
 | 
			
		||||
        data.response = !!this.api.SetROM(data.sim, ptr, data.rom.length);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify a new value for a system register
 | 
			
		||||
    setSystemRegister(data) {
 | 
			
		||||
        data.response = u32(this.api.vbSetSystemRegister
 | 
			
		||||
            (data.sim, data.id, data.value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Execute one instruction
 | 
			
		||||
    singleStep(data) {
 | 
			
		||||
        this.api.SingleStep(data.sims[0], data.sims[1]);
 | 
			
		||||
        let pc = [ u32(this.api.vbGetProgramCounter(data.sims[0])) ];
 | 
			
		||||
        if (data.sims[1])
 | 
			
		||||
            pc.push(u32(this.api.vbGetProgramCounter(data.sims[1])));
 | 
			
		||||
        data.response = {
 | 
			
		||||
            pc: pc
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Unsubscribe from frame data
 | 
			
		||||
    unsubscribe(data) {
 | 
			
		||||
        let sim = data.sim || 0;
 | 
			
		||||
        if (sim in this.subscriptions) {
 | 
			
		||||
            let subs = this.subscriptions[sim];
 | 
			
		||||
            delete subs[data.key];
 | 
			
		||||
            if (Object.keys(subs).length == 0)
 | 
			
		||||
                delete this.subscriptions[sim];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Write bytes to the simulation
 | 
			
		||||
    write(data) {
 | 
			
		||||
        let ptr    = this.api.Malloc(data.bytes.length);
 | 
			
		||||
        let buffer = new Uint8Array(this.memory, ptr, data.bytes.length);
 | 
			
		||||
        for (let x = 0; x < data.bytes.length; x++)
 | 
			
		||||
            buffer[x] = data.bytes[x];
 | 
			
		||||
        this.api.WriteBuffer(data.sim, ptr, data.address, data.bytes.length);
 | 
			
		||||
        this.api.Free(ptr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Private Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Retrieve basic information for a row of disassembly
 | 
			
		||||
    dasmRow(sim, address, pc) {
 | 
			
		||||
        let bits   = this.api.vbRead(sim, address, 3 /* VB_U16 */);
 | 
			
		||||
        let opcode = bits >> 10 & 63;
 | 
			
		||||
        let size   = (
 | 
			
		||||
            opcode <  0b101000 || // Formats I through III
 | 
			
		||||
            opcode == 0b110010 || // Illegal
 | 
			
		||||
            opcode == 0b110110    // Illegal
 | 
			
		||||
        ) ? 2 : 4;
 | 
			
		||||
 | 
			
		||||
        // Establish row information
 | 
			
		||||
        let row = {
 | 
			
		||||
            address: address,
 | 
			
		||||
            bytes  : [ bits & 0xFF, bits >> 8 ],
 | 
			
		||||
            size   : u32(address + 2) == pc ? 2 : size
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Read additional bytes
 | 
			
		||||
        if (size == 4) {
 | 
			
		||||
            bits = this.api.vbRead(sim, address + 2, 3 /* VB_U16 */);
 | 
			
		||||
            row.bytes.push(bits & 0xFF, bits >> 8);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return row;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Process subscriptions and send a message to the main thread
 | 
			
		||||
    doSubscriptions(sims) {
 | 
			
		||||
        let message   = { subscriptions: {} };
 | 
			
		||||
        let transfers = [];
 | 
			
		||||
 | 
			
		||||
        // Process all simulations
 | 
			
		||||
        for (let sim of sims) {
 | 
			
		||||
 | 
			
		||||
            // There are no subscriptions for this sim
 | 
			
		||||
            if (!(sim in this.subscriptions))
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            // Working variables
 | 
			
		||||
            let subs = message.subscriptions[sim] = {};
 | 
			
		||||
 | 
			
		||||
            // Process all subscriptions
 | 
			
		||||
            for (let sub of Object.entries(this.subscriptions[sim])) {
 | 
			
		||||
 | 
			
		||||
                // Run the command
 | 
			
		||||
                this[sub[1].command](sub[1]);
 | 
			
		||||
 | 
			
		||||
                // Add the response to the message
 | 
			
		||||
                if (sub[1].response) {
 | 
			
		||||
                    subs[sub[0]] = sub[1].response;
 | 
			
		||||
                    delete sub[1].response;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Add the transferable objects to the message
 | 
			
		||||
                if (sub[1].transfers) {
 | 
			
		||||
                    transfers.push(... sub[1].transfers);
 | 
			
		||||
                    delete sub[1].transfers;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Send the message to the main thread
 | 
			
		||||
        if (Object.keys(message).length != 0)
 | 
			
		||||
            postMessage(message, transfers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}();
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,151 @@
 | 
			
		|||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                    Sim                                    //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// One simulated Virtual Boy
 | 
			
		||||
class Sim {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(core, sim, index) {
 | 
			
		||||
        this.core  = core;
 | 
			
		||||
        this.index = index;
 | 
			
		||||
        this.sim   = sim;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Locate CPU instructions
 | 
			
		||||
    disassemble(target, row, rows, scroll, options = {}) {
 | 
			
		||||
        return this.core.send({
 | 
			
		||||
            command  : "disassemble",
 | 
			
		||||
            row      : row,
 | 
			
		||||
            rows     : rows,
 | 
			
		||||
            scroll   : scroll,
 | 
			
		||||
            sim      : this.sim,
 | 
			
		||||
            subscribe: options.subscribe,
 | 
			
		||||
            target   : target
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Retrieve all CPU program registers
 | 
			
		||||
    getProgramRegisters(options = {}) {
 | 
			
		||||
        return this.core.send({
 | 
			
		||||
            command  : "getProgramRegisters",
 | 
			
		||||
            sim      : this.sim,
 | 
			
		||||
            subscribe: options.subscribe
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Retrieve the value of a system register
 | 
			
		||||
    getSystemRegister(id, options = {}) {
 | 
			
		||||
        return this.core.send({
 | 
			
		||||
            command  : "getSystemRegister",
 | 
			
		||||
            id       : id,
 | 
			
		||||
            sim      : this.sim,
 | 
			
		||||
            subscribe: options.subscribe
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Retrieve all CPU system registers (including PC)
 | 
			
		||||
    getSystemRegisters(options = {}) {
 | 
			
		||||
        return this.core.send({
 | 
			
		||||
            command  : "getSystemRegisters",
 | 
			
		||||
            sim      : this.sim,
 | 
			
		||||
            subscribe: options.subscribe
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Read multiple bytes from the bus
 | 
			
		||||
    read(address, length, options = {}) {
 | 
			
		||||
        return this.core.send({
 | 
			
		||||
            address  : address,
 | 
			
		||||
            command  : "read",
 | 
			
		||||
            debug    : !("debug" in options) || !!options.debug,
 | 
			
		||||
            length   : length,
 | 
			
		||||
            sim      : this.sim,
 | 
			
		||||
            subscribe: options.subscribe
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify a new value for PC
 | 
			
		||||
    setProgramCounter(value, options = {}) {
 | 
			
		||||
        return this.core.send({
 | 
			
		||||
            command: "setProgramCounter",
 | 
			
		||||
            refresh: !!options.refresh,
 | 
			
		||||
            sim    : this.sim,
 | 
			
		||||
            value  : value
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify a new value for a program register
 | 
			
		||||
    setProgramRegister(id, value, options = {}) {
 | 
			
		||||
        return this.core.send({
 | 
			
		||||
            command: "setProgramRegister",
 | 
			
		||||
            id     : id,
 | 
			
		||||
            refresh: !!options.refresh,
 | 
			
		||||
            sim    : this.sim,
 | 
			
		||||
            value  : value
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify the current ROM buffer
 | 
			
		||||
    setROM(rom, options = {}) {
 | 
			
		||||
        return this.core.send({
 | 
			
		||||
            command: "setROM",
 | 
			
		||||
            rom    : rom,
 | 
			
		||||
            refresh: !!options.refresh,
 | 
			
		||||
            sim    : this.sim
 | 
			
		||||
        }, [ rom.buffer ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify a new value for a system register
 | 
			
		||||
    setSystemRegister(id, value, options = {}) {
 | 
			
		||||
        return this.core.send({
 | 
			
		||||
            command: "setSystemRegister",
 | 
			
		||||
            id     : id,
 | 
			
		||||
            refresh: !!options.refresh,
 | 
			
		||||
            sim    : this.sim,
 | 
			
		||||
            value  : value
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Ubsubscribe from frame data
 | 
			
		||||
    unsubscribe(key) {
 | 
			
		||||
        return this.core.unsubscribe(key, this.sim);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Write multiple bytes to the bus
 | 
			
		||||
    write(address, bytes, options = {}) {
 | 
			
		||||
        return this.core.send({
 | 
			
		||||
            address  : address,
 | 
			
		||||
            command  : "write",
 | 
			
		||||
            bytes    : bytes,
 | 
			
		||||
            debug    : !("debug" in options) || !!options.debug,
 | 
			
		||||
            refresh  : !!options.refresh,
 | 
			
		||||
            sim      : this.sim,
 | 
			
		||||
            subscribe: options.subscribe
 | 
			
		||||
        },
 | 
			
		||||
            bytes        instanceof ArrayBuffer ? [ bytes        ] :
 | 
			
		||||
            bytes.buffer instanceof ArrayBuffer ? [ bytes.buffer ] :
 | 
			
		||||
            undefined
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // The simulation has been destroyed
 | 
			
		||||
    destroy() {
 | 
			
		||||
        let sim   = this.sim;
 | 
			
		||||
        this.core = null;
 | 
			
		||||
        this.sim  = 0;
 | 
			
		||||
        return sim;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { Sim };
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,74 @@
 | 
			
		|||
{
 | 
			
		||||
    "id": "en-US",
 | 
			
		||||
 | 
			
		||||
    "common": {
 | 
			
		||||
        "close"     : "Close",
 | 
			
		||||
        "collapse"  : "Collapse",
 | 
			
		||||
        "expand"    : "Expand",
 | 
			
		||||
        "gotoPrompt": "Enter the address to go to:"
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    "error": {
 | 
			
		||||
        "fileRead": "An error occurred when reading the file.",
 | 
			
		||||
        "romNotVB": "The selected file is not a Virtual Boy ROM."
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    "app": {
 | 
			
		||||
        "title": "Virtual Boy Emulator",
 | 
			
		||||
 | 
			
		||||
        "menu": {
 | 
			
		||||
            "_": "Application menu bar",
 | 
			
		||||
 | 
			
		||||
            "file": {
 | 
			
		||||
                "_"        : "File",
 | 
			
		||||
                "loadROM"  : "Load ROM{sim}...",
 | 
			
		||||
                "debugMode": "Debug mode"
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            "emulation": {
 | 
			
		||||
                "_"       : "Emulation",
 | 
			
		||||
                "run"     : "Run",
 | 
			
		||||
                "reset"   : "Reset",
 | 
			
		||||
                "dualSims": "Dual sims",
 | 
			
		||||
                "linkSims": "Link sims"
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            "debug": {
 | 
			
		||||
                "_"           : "Debug{sim}",
 | 
			
		||||
                "console"     : "Console",
 | 
			
		||||
                "memory"      : "Memory",
 | 
			
		||||
                "cpu"         : "CPU",
 | 
			
		||||
                "breakpoints" : "Breakpoints",
 | 
			
		||||
                "palettes"    : "Palettes",
 | 
			
		||||
                "characters"  : "Characters",
 | 
			
		||||
                "bgMaps"      : "BG maps",
 | 
			
		||||
                "objects"     : "Objects",
 | 
			
		||||
                "worlds"      : "Worlds",
 | 
			
		||||
                "frameBuffers": "Frame buffers"
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            "theme": {
 | 
			
		||||
                "_"      : "Theme",
 | 
			
		||||
                "light"  : "Light",
 | 
			
		||||
                "dark"   : "Dark",
 | 
			
		||||
                "virtual": "Virtual"
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    "cpu": {
 | 
			
		||||
        "_"              : "CPU{sim}",
 | 
			
		||||
        "float"          : "Float",
 | 
			
		||||
        "hex"            : "Hex",
 | 
			
		||||
        "signed"         : "Signed",
 | 
			
		||||
        "splitHorizontal": "Program and system registers splitter",
 | 
			
		||||
        "splitVertical"  : "Disassembler and registers splitter",
 | 
			
		||||
        "unsigned"       : "Unsigned"
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    "memory": {
 | 
			
		||||
        "_": "Memory{sim}"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
// Global theme assets
 | 
			
		||||
Bundle["app/theme/kiosk.css"].installStylesheet(true);
 | 
			
		||||
await Bundle["app/theme/inconsolata.woff2"].installFont(
 | 
			
		||||
    "Inconsolata SemiExpanded Medium");
 | 
			
		||||
await Bundle["app/theme/roboto.woff2"].installFont("Roboto");
 | 
			
		||||
Bundle["app/theme/check.svg"   ].installImage("tk-check"   , "check.svg"   );
 | 
			
		||||
Bundle["app/theme/close.svg"   ].installImage("tk-close"   , "close.svg"   );
 | 
			
		||||
Bundle["app/theme/collapse.svg"].installImage("tk-collapse", "collapse.svg");
 | 
			
		||||
Bundle["app/theme/expand.svg"  ].installImage("tk-expand"  , "expand.svg"  );
 | 
			
		||||
Bundle["app/theme/radio.svg"   ].installImage("tk-radio"   , "radio.svg"   );
 | 
			
		||||
Bundle["app/theme/scroll.svg"  ].installImage("tk-scroll"  , "scroll.svg"  );
 | 
			
		||||
 | 
			
		||||
// Module imports
 | 
			
		||||
import { Core    } from /**/"./core/Core.js";
 | 
			
		||||
import { Toolkit } from /**/"./toolkit/Toolkit.js";
 | 
			
		||||
import { App     } from /**/"./app/App.js";
 | 
			
		||||
 | 
			
		||||
// Begin application
 | 
			
		||||
let dark = matchMedia("(prefers-color-scheme: dark)").matches;
 | 
			
		||||
let url  = u=>u.startsWith("data:")?u:new URL(u,import.meta.url).toString();
 | 
			
		||||
new App({
 | 
			
		||||
    core      : await Core.create({ sims: 2 }),
 | 
			
		||||
    locale    : navigator.language,
 | 
			
		||||
    standalone: true,
 | 
			
		||||
    theme     : dark ? "dark" : "light",
 | 
			
		||||
    locales   : [
 | 
			
		||||
        await (await fetch(url(/**/"./locale/en-US.json"))).json()
 | 
			
		||||
    ],
 | 
			
		||||
    themes    : {
 | 
			
		||||
        dark   : Bundle["app/theme/dark.css"   ].installStylesheet( dark),
 | 
			
		||||
        light  : Bundle["app/theme/light.css"  ].installStylesheet(!dark),
 | 
			
		||||
        virtual: Bundle["app/theme/virtual.css"].installStylesheet(false)
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <title>Virtual Boy Emulator</title>
 | 
			
		||||
    <link rel="icon" href="data:;base64,iVBORw0KGgo=">
 | 
			
		||||
    <script>window.a=async(b,c,d,e)=>{e=document.createElement('canvas');e.width=c;e.height=d;e=e.getContext('2d');e.drawImage(b,0,0);c=e.getImageData(0,0,c,d).data.filter((z,y)=>!(y&3));d=c.indexOf(0);Object.getPrototypeOf(a).constructor(String.fromCharCode(...c.slice(0,d)))(b,c,d)}</script>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
    <img alt="" style="display: none;" onload="a(this,width,height)" src="">
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 2.6458 2.6458" version="1.1">
 | 
			
		||||
  <g>
 | 
			
		||||
    <path style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 1.05832,1.653625 1.3229,-1.3229 v 0.66145 l -1.3229,1.3229 -0.79374,-0.79374 v -0.66145 z" />
 | 
			
		||||
  </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 459 B  | 
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 2.9104166 2.9104167" version="1.1">
 | 
			
		||||
  <g>
 | 
			
		||||
    <path style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 0.52916666,0.52916665 0.396875,0 0.52916664,0.52916665 0.5291667,-0.52916665 0.396875,0 0,0.396875 L 1.8520833,1.4552083 2.38125,1.984375 l 0,0.396875 -0.396875,0 L 1.4552083,1.8520834 0.92604166,2.38125 l -0.396875,0 0,-0.396875 L 1.0583333,1.4552083 0.52916666,0.92604165 Z" />
 | 
			
		||||
  </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 652 B  | 
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 2.9104166 2.9104168">
 | 
			
		||||
  <g>
 | 
			
		||||
    <rect style="opacity:1;fill:#000000;stroke-width:0.264583;stroke-linecap:square" width="1.3229167" height="0.26458332" x="0.79375005" y="1.3229167" />
 | 
			
		||||
  </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 367 B  | 
| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
:root {
 | 
			
		||||
    --tk-control               : #333333;
 | 
			
		||||
    --tk-control-active        : #555555;
 | 
			
		||||
    --tk-control-border        : #cccccc;
 | 
			
		||||
    --tk-control-highlight     : #444444;
 | 
			
		||||
    --tk-control-shadow        : #9b9b9b;
 | 
			
		||||
    --tk-control-text          : #cccccc;
 | 
			
		||||
    --tk-desktop               : #111111;
 | 
			
		||||
    --tk-selected              : #008542;
 | 
			
		||||
    --tk-selected-blur         : #325342;
 | 
			
		||||
    --tk-selected-blur-text    : #ffffff;
 | 
			
		||||
    --tk-selected-text         : #ffffff;
 | 
			
		||||
    --tk-splitter-focus        : #ffffff99;
 | 
			
		||||
    --tk-window                : #222222;
 | 
			
		||||
    --tk-window-blur-close     : #d9aeae;
 | 
			
		||||
    --tk-window-blur-close-text: #eeeeee;
 | 
			
		||||
    --tk-window-blur-title     : #9fafb9;
 | 
			
		||||
    --tk-window-blur-title2    : #c0b2ab;
 | 
			
		||||
    --tk-window-blur-title-text: #444444;
 | 
			
		||||
    --tk-window-close          : #ee9999;
 | 
			
		||||
    --tk-window-close-text     : #ffffff;
 | 
			
		||||
    --tk-window-text           : #cccccc;
 | 
			
		||||
    --tk-window-title          : #80ccff;
 | 
			
		||||
    --tk-window-title2         : #ffb894;
 | 
			
		||||
    --tk-window-title-text     : #000000;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 2.9104166 2.9104168">
 | 
			
		||||
  <g>
 | 
			
		||||
    <rect style="opacity:1;fill:#000000;stroke-width:0.264583;stroke-linecap:square" width="0.26458332" height="1.3229167" x="1.3229167" y="0.79375005" />
 | 
			
		||||
    <rect style="opacity:1;fill:#000000;stroke-width:0.264583;stroke-linecap:square" width="1.3229167" height="0.26458332" x="0.79375005" y="1.3229167" />
 | 
			
		||||
  </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 522 B  | 
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,761 @@
 | 
			
		|||
:root {
 | 
			
		||||
    --tk-font-dialog: "Roboto", sans-serif;
 | 
			
		||||
    --tk-font-mono  : "Inconsolata SemiExpanded Medium", monospace;
 | 
			
		||||
    --tk-font-size  : 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk {
 | 
			
		||||
    font-family: var(--tk-font-dialog);
 | 
			
		||||
    font-size  : var(--tk-font-size);
 | 
			
		||||
    line-height: 1em;
 | 
			
		||||
    margin     : 0;
 | 
			
		||||
    outline    : none;
 | 
			
		||||
    padding    : 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.tk {
 | 
			
		||||
    border        : none;
 | 
			
		||||
    border-spacing: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-body {
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-app {
 | 
			
		||||
    /* Height managed through resize listener */
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-mono {
 | 
			
		||||
    font-family: var(--tk-font-mono);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/********************************** Button ***********************************/
 | 
			
		||||
 | 
			
		||||
.tk-button > * {
 | 
			
		||||
    background: var(--tk-control);
 | 
			
		||||
    border    : 1px solid   var(--tk-control-shadow);
 | 
			
		||||
    box-shadow: 1px 1px 0 0 var(--tk-control-shadow);
 | 
			
		||||
    color     : var(--tk-control-text);
 | 
			
		||||
    margin    : 0 1px 1px 0;
 | 
			
		||||
    padding   : 3px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-button:focus > * {
 | 
			
		||||
    background: var(--tk-control-active);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-button.active > * {
 | 
			
		||||
    box-shadow: none;
 | 
			
		||||
    margin    : 1px 0 0 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-button[aria-disabled="true"] > * {
 | 
			
		||||
    color: var(--tk-control-shadow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/********************************* Check Box *********************************/
 | 
			
		||||
 | 
			
		||||
.tk-checkbox {
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    column-gap : 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-checkbox .tk-icon {
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    background : var(--tk-window);
 | 
			
		||||
    border     : 1px solid var(--tk-control-shadow);
 | 
			
		||||
    box-sizing : border-box;
 | 
			
		||||
    display    : flex;
 | 
			
		||||
    height     : 12px;
 | 
			
		||||
    width      : 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-checkbox .tk-icon:before {
 | 
			
		||||
    background   : transparent;
 | 
			
		||||
    content      : "";
 | 
			
		||||
    height       : 100%;
 | 
			
		||||
    display      : block;
 | 
			
		||||
    mask-image   : var(--tk-check);
 | 
			
		||||
    mask-position: center;
 | 
			
		||||
    mask-repeat  : no-repeat;
 | 
			
		||||
    mask-size    : contain;
 | 
			
		||||
    width        : 100%;
 | 
			
		||||
    -webkit-mask-image   : var(--tk-check);
 | 
			
		||||
    -webkit-mask-position: center;
 | 
			
		||||
    -webkit-mask-repeat  : no-repeat;
 | 
			
		||||
    -webkit-mask-size    : contain;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-checkbox[aria-checked="true"] .tk-icon:before {
 | 
			
		||||
    background: var(--tk-window-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-checkbox:focus .tk-icon {
 | 
			
		||||
    background: var(--tk-control-active);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-checkbox[aria-checked="true"]:focus .tk-icon:before {
 | 
			
		||||
    background: var(--tk-control-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-checkbox.active:focus .tk-icon:before {
 | 
			
		||||
    background: var(--tk-control-shadow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/****************************** Drop-Down List *******************************/
 | 
			
		||||
 | 
			
		||||
.tk-dropdown {
 | 
			
		||||
    background   : var(--tk-window);
 | 
			
		||||
    border       : 1px solid var(--tk-control-shadow);
 | 
			
		||||
    border-radius: 0;
 | 
			
		||||
    color        : var(--tk-window-text);
 | 
			
		||||
    margin       : 0;
 | 
			
		||||
    padding      : 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/********************************* Menu Bar **********************************/
 | 
			
		||||
 | 
			
		||||
.tk-menu-bar {
 | 
			
		||||
    background   : var(--tk-control);
 | 
			
		||||
    border-bottom: 1px solid var(--tk-control-border);
 | 
			
		||||
    color        : var(--tk-control-text);
 | 
			
		||||
    column-gap   : 1px;
 | 
			
		||||
    display      : flex;
 | 
			
		||||
    flex-wrap    : wrap;
 | 
			
		||||
    padding      : 2px;
 | 
			
		||||
    user-select  : none;
 | 
			
		||||
    white-space  : nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-menu {
 | 
			
		||||
    background    : var(--tk-control);
 | 
			
		||||
    border        : 1px solid   var(--tk-control-border);
 | 
			
		||||
    box-shadow    : 1px 1px 0 0 var(--tk-control-border);
 | 
			
		||||
    display       : flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    padding       : 3px;
 | 
			
		||||
    row-gap       : 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-menu-item > * {
 | 
			
		||||
    background: var(--tk-control);
 | 
			
		||||
    border    : 1px solid transparent;
 | 
			
		||||
    column-gap: 4px;
 | 
			
		||||
    display   : flex;
 | 
			
		||||
    margin    : 0 1px 1px 0;
 | 
			
		||||
    padding   : 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-menu-item > * > .tk-icon {
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    display   : none;
 | 
			
		||||
    height    : 1em;
 | 
			
		||||
    width     : 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-menu-item > * > .tk-text {
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-menu-item[aria-disabled="true"] > * > .tk-text {
 | 
			
		||||
    color: var(--tk-control-shadow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-menu-item:not(.active, [aria-disabled="true"]):hover > *,
 | 
			
		||||
.tk-menu-item:not(.active):focus > * {
 | 
			
		||||
    border-color: var(--tk-control-shadow);
 | 
			
		||||
    box-shadow  : 1px 1px 0 0 var(--tk-control-shadow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-menu.icons > .tk-menu-item > * > .tk-icon {
 | 
			
		||||
    display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-menu-item[role="menuitemcheckbox"] > * > .tk-icon {
 | 
			
		||||
    border: 1px solid var(--tk-control-border);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-menu-item[role="menuitemcheckbox"] > * > .tk-icon:before {
 | 
			
		||||
    background   : transparent;
 | 
			
		||||
    content      : "";
 | 
			
		||||
    height       : 100%;
 | 
			
		||||
    display      : block;
 | 
			
		||||
    mask-image   : var(--tk-check);
 | 
			
		||||
    mask-position: center;
 | 
			
		||||
    mask-repeat  : no-repeat;
 | 
			
		||||
    mask-size    : contain;
 | 
			
		||||
    width        : 100%;
 | 
			
		||||
    -webkit-mask-image   : var(--tk-check);
 | 
			
		||||
    -webkit-mask-position: center;
 | 
			
		||||
    -webkit-mask-repeat  : no-repeat;
 | 
			
		||||
    -webkit-mask-size    : contain;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-menu-item[role="menuitemcheckbox"][aria-checked="true"]
 | 
			
		||||
    > * > .tk-icon:before {
 | 
			
		||||
    background: var(--tk-control-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-menu-item[role="menuitemcheckbox"][aria-disabled="true"]
 | 
			
		||||
    > * > .tk-icon {
 | 
			
		||||
    border: 1px solid var(--tk-control-shadow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-menu-item[role="menuitemcheckbox"][aria-disabled="true"]
 | 
			
		||||
    > * > .tk-icon:before {
 | 
			
		||||
    background: var(--tk-control-shadow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-menu-item:not(.active):focus > * {
 | 
			
		||||
    background: var(--tk-control-active);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-menu-item.active > * {
 | 
			
		||||
    background  : var(--tk-control-active);
 | 
			
		||||
    border-color: var(--tk-control-shadow);
 | 
			
		||||
    margin      : 1px 0 0 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-menu-separator {
 | 
			
		||||
    border       : 0 solid var(--tk-control-shadow);
 | 
			
		||||
    border-width : 1px 0 0 0;
 | 
			
		||||
    height       : 0;
 | 
			
		||||
    margin-bottom: 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*********************************** Radio ***********************************/
 | 
			
		||||
 | 
			
		||||
.tk-radio {
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    column-gap : 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-radio .tk-icon {
 | 
			
		||||
    align-items  : center;
 | 
			
		||||
    background   : var(--tk-window);
 | 
			
		||||
    border       : 1px solid var(--tk-control-shadow);
 | 
			
		||||
    border-radius: 50%;
 | 
			
		||||
    box-sizing   : border-box;
 | 
			
		||||
    display      : flex;
 | 
			
		||||
    height       : 10px;
 | 
			
		||||
    width        : 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-radio .tk-icon:before {
 | 
			
		||||
    background   : transparent;
 | 
			
		||||
    content      : "";
 | 
			
		||||
    height       : 100%;
 | 
			
		||||
    display      : block;
 | 
			
		||||
    mask-image   : var(--tk-radio);
 | 
			
		||||
    mask-position: center;
 | 
			
		||||
    mask-repeat  : no-repeat;
 | 
			
		||||
    mask-size    : contain;
 | 
			
		||||
    width        : 100%;
 | 
			
		||||
    -webkit-mask-image   : var(--tk-radio);
 | 
			
		||||
    -webkit-mask-position: center;
 | 
			
		||||
    -webkit-mask-repeat  : no-repeat;
 | 
			
		||||
    -webkit-mask-size    : contain;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-radio[aria-checked="true"] .tk-icon:before {
 | 
			
		||||
    background: var(--tk-window-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-radio:focus .tk-icon {
 | 
			
		||||
    background: var(--tk-control-active);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-radio[aria-checked="true"]:focus .tk-icon:before {
 | 
			
		||||
    background: var(--tk-control-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-radio.active[aria-checked="false"]:focus .tk-icon:before {
 | 
			
		||||
    background: var(--tk-control-shadow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/******************************** Scroll Bar *********************************/
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar {
 | 
			
		||||
    background: var(--tk-control-highlight);
 | 
			
		||||
    box-shadow: 0 0 0 1px var(--tk-control-shadow) inset;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar .tk-thumb,
 | 
			
		||||
.tk-scrollbar .tk-unit-down,
 | 
			
		||||
.tk-scrollbar .tk-unit-up {
 | 
			
		||||
    background: var(--tk-control);
 | 
			
		||||
    border    : 1px solid var(--tk-control-border);
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    color     : var(--tk-control-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar:focus .tk-thumb,
 | 
			
		||||
.tk-scrollbar:focus .tk-unit-down,
 | 
			
		||||
.tk-scrollbar:focus .tk-unit-up {
 | 
			
		||||
    background: var(--tk-control-active);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar .tk-unit-down,
 | 
			
		||||
.tk-scrollbar .tk-unit-up {
 | 
			
		||||
    height: 13px;
 | 
			
		||||
    width : 13px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar .tk-unit-down:before,
 | 
			
		||||
.tk-scrollbar .tk-unit-up:before {
 | 
			
		||||
    background   : currentColor;
 | 
			
		||||
    content      : "";
 | 
			
		||||
    display      : block;
 | 
			
		||||
    height       : 100%;
 | 
			
		||||
    mask-image   : var(--tk-scroll);
 | 
			
		||||
    mask-position: center;
 | 
			
		||||
    mask-repeat  : no-repeat;
 | 
			
		||||
    mask-size    : 100%;
 | 
			
		||||
    width        : 100%;
 | 
			
		||||
    -webkit-mask-image   : var(--tk-scroll);
 | 
			
		||||
    -webkit-mask-position: center;
 | 
			
		||||
    -webkit-mask-repeat  : no-repeat;
 | 
			
		||||
    -webkit-mask-size    : 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar[aria-orientation="horizontal"] .tk-unit-down:before {
 | 
			
		||||
    transform: rotate(-90deg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar[aria-orientation="horizontal"] .tk-unit-up:before {
 | 
			
		||||
    transform: rotate(90deg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar[aria-orientation="vertical"] .tk-unit-down:before {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar[aria-orientation="vertical"] .tk-unit-up:before {
 | 
			
		||||
    transform: rotate(180deg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar .tk-unit-down.tk-active:before,
 | 
			
		||||
.tk-scrollbar .tk-unit-up.tk-active:before {
 | 
			
		||||
    mask-size: calc(100% - 2px);
 | 
			
		||||
    -webkit-mask-size: calc(100% - 2px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar[aria-disabled="true"] .tk-unit-down,
 | 
			
		||||
.tk-scrollbar[aria-disabled="true"] .tk-unit-up,
 | 
			
		||||
.tk-scrollbar.tk-full .tk-unit-down,
 | 
			
		||||
.tk-scrollbar.tk-full .tk-unit-up  {
 | 
			
		||||
    background: var(--tk-control);
 | 
			
		||||
    border-color: var(--tk-control-shadow);
 | 
			
		||||
    color     : var(--tk-control-shadow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar .tk-block-down,
 | 
			
		||||
.tk-scrollbar .tk-block-up {
 | 
			
		||||
    background  : var(--tk-control-highlight);
 | 
			
		||||
    border-color: var(--tk-control-shadow);
 | 
			
		||||
    border-style: solid;
 | 
			
		||||
    border-width: 0 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar[aria-orientation="horizontal"] .tk-block-down,
 | 
			
		||||
.tk-scrollbar[aria-orientation="horizontal"] .tk-block-up {
 | 
			
		||||
    border-width: 1px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar .tk-block-down.tk-active,
 | 
			
		||||
.tk-scrollbar .tk-block-up.tk-active {
 | 
			
		||||
    background: var(--tk-control-shadow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar[aria-disabled="true"] .tk-thumb,
 | 
			
		||||
.tk-scrollbar[aria-disabled="true"] .tk-block-down,
 | 
			
		||||
.tk-scrollbar[aria-disabled="true"] .tk-block-up,
 | 
			
		||||
.tk-scrollbar.tk-full .tk-thumb,
 | 
			
		||||
.tk-scrollbar.tk-full .tk-block-down,
 | 
			
		||||
.tk-scrollbar.tk-full .tk-block-up {
 | 
			
		||||
    visibility: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/******************************** Scroll Pane ********************************/
 | 
			
		||||
 | 
			
		||||
.tk-scrollpane {
 | 
			
		||||
    background: var(--tk-control);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollpane > .tk-scrollbar {
 | 
			
		||||
    border: 0 solid var(--tk-control);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollpane > .tk-scrollbar[aria-orientation="horizontal"] {
 | 
			
		||||
    border-width: 1px 0 0 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollpane > .tk-scrollbar[aria-orientation="vertical"] {
 | 
			
		||||
    border-width: 0 0 0 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/******************************** Split Pane *********************************/
 | 
			
		||||
 | 
			
		||||
.tk-splitpane > [role="separator"][aria-orientation="horizontal"] {
 | 
			
		||||
    cursor: ns-resize;
 | 
			
		||||
    height: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-splitpane > [role="separator"][aria-orientation="vertical"] {
 | 
			
		||||
    cursor: ew-resize;
 | 
			
		||||
    width : 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-splitpane > [role="separator"]:focus {
 | 
			
		||||
    background: var(--tk-splitter-focus);
 | 
			
		||||
    z-index   : 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/********************************* Text Box **********************************/
 | 
			
		||||
 | 
			
		||||
.tk-textbox {
 | 
			
		||||
    background: var(--tk-window);
 | 
			
		||||
    border    : 1px solid var(--tk-control-shadow);
 | 
			
		||||
    color     : var(--tk-window-text);
 | 
			
		||||
    margin    : 0;
 | 
			
		||||
    padding   : 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/********************************** Windows **********************************/
 | 
			
		||||
 | 
			
		||||
.tk-desktop {
 | 
			
		||||
    background: var(--tk-desktop);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-window > * {
 | 
			
		||||
    border    : 1px solid var(--tk-control-border);
 | 
			
		||||
    box-shadow: 1px 1px 0 0 var(--tk-control-border);
 | 
			
		||||
    margin    : 0 1px 1px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-window > * > .tk-nw {left : -1px; top   : -1px; height: 8px; width : 8px; }
 | 
			
		||||
.tk-window > * > .tk-n  {left :  7px; top   : -1px; right : 8px; height: 3px; }
 | 
			
		||||
.tk-window > * > .tk-ne {right:  0px; top   : -1px; height: 8px; width : 8px; }
 | 
			
		||||
.tk-window > * > .tk-w  {left : -1px; top   :  7px; width : 3px; bottom: 8px; }
 | 
			
		||||
.tk-window > * > .tk-e  {right:  0px; top   :  7px; width : 3px; bottom: 8px; }
 | 
			
		||||
.tk-window > * > .tk-sw {left : -1px; bottom:  0px; height: 8px; width : 8px; }
 | 
			
		||||
.tk-window > * > .tk-s  {left :  7px; bottom:  0px; right : 8px; height: 3px; }
 | 
			
		||||
.tk-window > * > .tk-se {right:  0px; bottom:  0px; height: 8px; width : 8px; }
 | 
			
		||||
 | 
			
		||||
.tk-window > * > .tk-title {
 | 
			
		||||
    align-items  : center;
 | 
			
		||||
    background   : var(--tk-window-blur-title);
 | 
			
		||||
    border-bottom: 1px solid var(--tk-control-shadow);
 | 
			
		||||
    box-sizing   : border-box;
 | 
			
		||||
    color        : var(--tk-window-blur-title-text);
 | 
			
		||||
    overflow     : hidden;
 | 
			
		||||
    padding      : 1px;
 | 
			
		||||
    position     : relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-window.two > * > .tk-title {
 | 
			
		||||
    background: var(--tk-window-blur-title2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-window > * > .tk-title .tk-text {
 | 
			
		||||
    cursor       : default;
 | 
			
		||||
    flex-basis   : 0;
 | 
			
		||||
    font-weight  : bold;
 | 
			
		||||
    min-width    : 0;
 | 
			
		||||
    overflow     : hidden;
 | 
			
		||||
    padding      : 1px 1px 1px calc(1em + 3px);
 | 
			
		||||
    text-align   : center;
 | 
			
		||||
    text-overflow: ellipsis;
 | 
			
		||||
    user-select  : none;
 | 
			
		||||
    white-space  : nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-window > * > .tk-title .tk-close {
 | 
			
		||||
    background: var(--tk-window-blur-close);
 | 
			
		||||
    border    : 1px solid var(--tk-control-shadow);
 | 
			
		||||
    color     : var(--tk-window-blur-close-text);
 | 
			
		||||
    height    : calc(1em - 1px);
 | 
			
		||||
    margin    : 1px 1px 1px 0;
 | 
			
		||||
    overflow  : none;
 | 
			
		||||
    width     : calc(1em - 1px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-window > * > .tk-title .tk-close:before {
 | 
			
		||||
    background   : currentColor;
 | 
			
		||||
    content      : "";
 | 
			
		||||
    display      : block;
 | 
			
		||||
    height       : 100%;
 | 
			
		||||
    width        : 100%;
 | 
			
		||||
    mask-image   : var(--tk-close);
 | 
			
		||||
    mask-position: center;
 | 
			
		||||
    mask-repeat  : no-repeat;
 | 
			
		||||
    mask-size    : 100%;
 | 
			
		||||
    -webkit-mask-image   : var(--tk-close);
 | 
			
		||||
    -webkit-mask-position: center;
 | 
			
		||||
    -webkit-mask-repeat  : no-repeat;
 | 
			
		||||
    -webkit-mask-size    : 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-window > * > .tk-title .tk-close.active:before {
 | 
			
		||||
    mask-size: calc(100% - 2px);
 | 
			
		||||
    -webkit-mask-size: calc(100% - 2px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-window:focus-within > * > .tk-title {
 | 
			
		||||
    background: var(--tk-window-title);
 | 
			
		||||
    color     : var(--tk-window-title-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-window.two:focus-within > * > .tk-title {
 | 
			
		||||
    background: var(--tk-window-title2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-window:focus-within > * > .tk-title .tk-close {
 | 
			
		||||
    background: var(--tk-window-close);
 | 
			
		||||
    color     : var(--tk-window-close-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-window > * > .tk-client {
 | 
			
		||||
    background: var(--tk-control);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/************************************ CPU ************************************/
 | 
			
		||||
 | 
			
		||||
.tk-cpu .tk-main {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    width : 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-cpu .tk-main      > .tk-a,
 | 
			
		||||
.tk-cpu .tk-registers > .tk-a,
 | 
			
		||||
.tk-cpu .tk-registers > .tk-b {
 | 
			
		||||
    box-shadow: 0 0 0 1px var(--tk-control),0 0 0 2px var(--tk-control-shadow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-cpu .tk-main      > .tk-a              { margin       :  3px;      }
 | 
			
		||||
.tk-cpu .tk-main      > [role="separator"] { margin       :  1px -2px; }
 | 
			
		||||
.tk-cpu .tk-main      > .tk-b              { margin       :  3px;      }
 | 
			
		||||
.tk-cpu .tk-registers > .tk-a              { margin-bottom:  3px;      }
 | 
			
		||||
.tk-cpu .tk-registers > [role="separator"] { margin       : -2px;      }
 | 
			
		||||
.tk-cpu .tk-registers > .tk-b              { margin-top   :  3px;      }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.tk-disassembler .tk-viewport {
 | 
			
		||||
    background: var(--tk-window);
 | 
			
		||||
    color     : var(--tk-window-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-disassembler .tk-view {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-disassembler .tk-metrics {
 | 
			
		||||
    padding-bottom: 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-disassembler .tk {
 | 
			
		||||
    cursor     : default;
 | 
			
		||||
    font-family: var(--tk-font-mono);
 | 
			
		||||
    user-select: none;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-disassembler .tk-bytes,
 | 
			
		||||
.tk-disassembler .tk-mnemonic,
 | 
			
		||||
.tk-disassembler .tk-operands {
 | 
			
		||||
    padding: 0 0 1px calc(1.2em - 1px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-disassembler .tk-address {
 | 
			
		||||
    padding-left: 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-disassembler .tk-operands {
 | 
			
		||||
    padding-right: 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-disassembler .tk-selected {
 | 
			
		||||
    background: var(--tk-selected-blur);
 | 
			
		||||
    color     : var(--tk-selected-blur-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-disassembler:focus-within .tk-selected {
 | 
			
		||||
    background: var(--tk-selected);
 | 
			
		||||
    color     : var(--tk-selected-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.tk-reglist .tk-viewport {
 | 
			
		||||
    background: var(--tk-window);
 | 
			
		||||
    color     : var(--tk-window-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-reglist .tk-list {
 | 
			
		||||
    align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-reglist .tk-expand {
 | 
			
		||||
    align-items    : center;
 | 
			
		||||
    border-radius  : 2px;
 | 
			
		||||
    display        : flex;
 | 
			
		||||
    height         : 11px;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    width          : 11px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-reglist .tk-expand:before {
 | 
			
		||||
    content      : "";
 | 
			
		||||
    height       : 100%;
 | 
			
		||||
    display      : block;
 | 
			
		||||
    mask-position: center;
 | 
			
		||||
    mask-repeat  : no-repeat;
 | 
			
		||||
    mask-size    : contain;
 | 
			
		||||
    width        : 100%;
 | 
			
		||||
    -webkit-mask-position: center;
 | 
			
		||||
    -webkit-mask-repeat  : no-repeat;
 | 
			
		||||
    -webkit-mask-size    : contain;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-reglist .tk-expand:focus {
 | 
			
		||||
    background: var(--tk-control-active);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-reglist .tk-expand[aria-expanded]:before {
 | 
			
		||||
    background: var(--tk-window-text);
 | 
			
		||||
}
 | 
			
		||||
.tk-reglist .tk-expand[aria-expanded]:focus:before {
 | 
			
		||||
    background: var(--tk-control-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-reglist .tk-expand[aria-expanded="false"]:before {
 | 
			
		||||
    mask-image: var(--tk-expand);
 | 
			
		||||
    -webkit-mask-image: var(--tk-expand);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-reglist .tk-expand[aria-expanded="true"]:before {
 | 
			
		||||
    mask-image: var(--tk-collapse);
 | 
			
		||||
    -webkit-mask-image: var(--tk-collapse);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-reglist .tk-name {
 | 
			
		||||
    padding: 0 0.5em 0 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-reglist .tk-textbox {
 | 
			
		||||
    background: transparent;
 | 
			
		||||
    border    : none;
 | 
			
		||||
    padding   : 0;
 | 
			
		||||
    width     : 1.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-reglist.tk-program .tk-textbox:not(.tk-mono) {
 | 
			
		||||
    text-align: right;
 | 
			
		||||
    width     : 6em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-reglist .tk-expansion {
 | 
			
		||||
    align-items  : center;
 | 
			
		||||
    column-gap   : 0.8em;
 | 
			
		||||
    margin-bottom: 2px;
 | 
			
		||||
    padding      : 2px 0 0 1.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-reglist .tk-expansion .tk-number .tk-label {
 | 
			
		||||
    align-items    : center;
 | 
			
		||||
    display        : flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    min-width      : 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-reglist .tk-expansion .tk-checkbox[aria-disabled="true"][aria-checked="true"]
 | 
			
		||||
    .tk-icon:before {
 | 
			
		||||
    background: var(--tk-control-shadow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-reglist .tk-expansion .tk-checkbox[aria-disabled="true"] .tk-contents,
 | 
			
		||||
.tk-reglist .tk-expansion .tk-number[disabled] *,
 | 
			
		||||
.tk-reglist .tk-expansion .tk-label[disabled],
 | 
			
		||||
.tk-reglist .tk-expansion .tk-textbox[disabled] {
 | 
			
		||||
    color: var(--tk-control-shadow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/********************************** Memory ***********************************/
 | 
			
		||||
 | 
			
		||||
.tk-window .tk-memory {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    width : 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-memory .tk-editor {
 | 
			
		||||
    box-shadow: 0 0 0 1px var(--tk-control),0 0 0 2px var(--tk-control-shadow);
 | 
			
		||||
    height    : calc(100% - 6px);
 | 
			
		||||
    margin    : 3px;
 | 
			
		||||
    width     : calc(100% - 6px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-memory .tk-viewport {
 | 
			
		||||
    background: var(--tk-window);
 | 
			
		||||
    color     : var(--tk-window-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-memory .tk-metrics,
 | 
			
		||||
.tk-memory .tk-view * {
 | 
			
		||||
    padding-bottom: 1px;
 | 
			
		||||
    cursor        : default;
 | 
			
		||||
    font-family   : var(--tk-font-mono);
 | 
			
		||||
    user-select   : none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-memory .tk-byte {
 | 
			
		||||
    border    : 0 solid transparent;
 | 
			
		||||
    padding   : 0 1px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-memory .tk-byte:not(.tk-15) {
 | 
			
		||||
    margin-right: calc(0.6em - 1px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-memory .tk-address,
 | 
			
		||||
.tk-memory .tk-byte.tk-7 {
 | 
			
		||||
    margin-right: calc(1.2em - 1px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-memory .tk-byte.tk-selected {
 | 
			
		||||
    background: var(--tk-selected-blur);
 | 
			
		||||
    color     : var(--tk-selected-blur-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-memory .tk-editor:focus-within .tk-byte.tk-selected {
 | 
			
		||||
    background: var(--tk-selected);
 | 
			
		||||
    color     : var(--tk-selected-text);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
:root {
 | 
			
		||||
    --tk-control               : #eeeeee;
 | 
			
		||||
    --tk-control-active        : #cccccc;
 | 
			
		||||
    --tk-control-border        : #000000;
 | 
			
		||||
    --tk-control-highlight     : #f8f8f8;
 | 
			
		||||
    --tk-control-shadow        : #6c6c6c;
 | 
			
		||||
    --tk-control-text          : #000000;
 | 
			
		||||
    --tk-desktop               : #cccccc;
 | 
			
		||||
    --tk-selected              : #008542;
 | 
			
		||||
    --tk-selected-blur         : #325342;
 | 
			
		||||
    --tk-selected-blur-text    : #ffffff;
 | 
			
		||||
    --tk-selected-text         : #ffffff;
 | 
			
		||||
    --tk-splitter-focus        : #00000080;
 | 
			
		||||
    --tk-window                : #ffffff;
 | 
			
		||||
    --tk-window-blur-close     : #d9aeae;
 | 
			
		||||
    --tk-window-blur-close-text: #eeeeee;
 | 
			
		||||
    --tk-window-blur-title     : #aac4d5;
 | 
			
		||||
    --tk-window-blur-title2    : #dbc4b8;
 | 
			
		||||
    --tk-window-blur-title-text: #444444;
 | 
			
		||||
    --tk-window-close          : #ee9999;
 | 
			
		||||
    --tk-window-close-text     : #ffffff;
 | 
			
		||||
    --tk-window-text           : #000000;
 | 
			
		||||
    --tk-window-title          : #80ccff;
 | 
			
		||||
    --tk-window-title2         : #ffb894;
 | 
			
		||||
    --tk-window-title-text     : #000000;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 2.6458332 2.6458332" version="1.1">
 | 
			
		||||
  <g>
 | 
			
		||||
    <circle style="opacity:1;fill:#000000;stroke-width:0.264583;stroke-linecap:square" cx="1.3229166" cy="1.3229166" r="0.66145831" />
 | 
			
		||||
  </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 361 B  | 
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 2.9104166 2.9104167" version="1.1">
 | 
			
		||||
  <g>
 | 
			
		||||
    <path style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 1.4552083,0.66145833 0.52916666,1.5874999 V 2.2489583 L 1.4552083,1.3229166 2.38125,2.2489583 V 1.5874999 Z" />
 | 
			
		||||
  </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 484 B  | 
| 
						 | 
				
			
			@ -0,0 +1,70 @@
 | 
			
		|||
:root {
 | 
			
		||||
    --tk-control               : #000000;
 | 
			
		||||
    --tk-control-active        : #550000;
 | 
			
		||||
    --tk-control-border        : #ff0000;
 | 
			
		||||
    --tk-control-highlight     : #550000;
 | 
			
		||||
    --tk-control-shadow        : #aa0000;
 | 
			
		||||
    --tk-control-text          : #ff0000;
 | 
			
		||||
    --tk-desktop               : #000000;
 | 
			
		||||
    --tk-selected              : #550000;
 | 
			
		||||
    --tk-selected-blur         : #550000;
 | 
			
		||||
    --tk-selected-blur-text    : #ff0000;
 | 
			
		||||
    --tk-selected-text         : #ff0000;
 | 
			
		||||
    --tk-splitter-focus        : #ff000099;
 | 
			
		||||
    --tk-window                : #000000;
 | 
			
		||||
    --tk-window-blur-close     : #000000;
 | 
			
		||||
    --tk-window-blur-close-text: #aa0000;
 | 
			
		||||
    --tk-window-blur-title     : #000000;
 | 
			
		||||
    --tk-window-blur-title2    : #000000;
 | 
			
		||||
    --tk-window-blur-title-text: #aa0000;
 | 
			
		||||
    --tk-window-close          : #550000;
 | 
			
		||||
    --tk-window-close-text     : #ff0000;
 | 
			
		||||
    --tk-window-text           : #ff0000;
 | 
			
		||||
    --tk-window-title          : #550000;
 | 
			
		||||
    --tk-window-title2         : #550000;
 | 
			
		||||
    --tk-window-title-text     : #ff0000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input {
 | 
			
		||||
    filter: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxmaWx0ZXIgaWQ9InYiPjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VHcmFwaGljIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMSAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMSAwIiAvPjwvZmlsdGVyPjwvc3ZnPg==#v");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/******************************** Scroll Bar *********************************/
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar .tk-thumb,
 | 
			
		||||
.tk-scrollbar .tk-unit-down,
 | 
			
		||||
.tk-scrollbar .tk-unit-up {
 | 
			
		||||
    background  : #aa0000;
 | 
			
		||||
    border-color: #550000;
 | 
			
		||||
    color       : #000000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar:focus .tk-thumb,
 | 
			
		||||
.tk-scrollbar:focus .tk-unit-down,
 | 
			
		||||
.tk-scrollbar:focus .tk-unit-up {
 | 
			
		||||
    background  : #ff0000;
 | 
			
		||||
    border-color: #aa0000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar[aria-disabled="true"] .tk-thumb,
 | 
			
		||||
.tk-scrollbar[aria-disabled="true"] .tk-unit-down,
 | 
			
		||||
.tk-scrollbar[aria-disabled="true"] .tk-unit-up,
 | 
			
		||||
.tk-scrollbar.tk-full .tk-thumb,
 | 
			
		||||
.tk-scrollbar.tk-full .tk-unit-down,
 | 
			
		||||
.tk-scrollbar.tk-full .tk-unit-up {
 | 
			
		||||
    background  : #550000;
 | 
			
		||||
    border-color: #aa0000;
 | 
			
		||||
    color       : #aa0000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-scrollbar .tk-block-down,
 | 
			
		||||
.tk-scrollbar .tk-block-up {
 | 
			
		||||
    background  : #550000;
 | 
			
		||||
    border-color: #aa0000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tk-window > * > .tk-client > .tk-memory {
 | 
			
		||||
    box-shadow: 0 0 0 1px #000000, 0 0 0 2px #ff0000;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,387 @@
 | 
			
		|||
import { Component } from /**/"./Component.js";
 | 
			
		||||
let Toolkit;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                  Button                                   //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Push, toggle or radio button
 | 
			
		||||
class Button extends Component {
 | 
			
		||||
    static Component = Component;
 | 
			
		||||
 | 
			
		||||
    //////////////////////////////// Constants ////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Types
 | 
			
		||||
    static BUTTON = 0;
 | 
			
		||||
    static RADIO  = 1;
 | 
			
		||||
    static TOGGLE = 2;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(gui, options) {
 | 
			
		||||
        super(gui, options, {
 | 
			
		||||
            className: "tk tk-button",
 | 
			
		||||
            focusable: true,
 | 
			
		||||
            role     : "button",
 | 
			
		||||
            tagName  : "div",
 | 
			
		||||
            style    : {
 | 
			
		||||
                display   : "inline-block",
 | 
			
		||||
                userSelect: "none"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        options         = options || {};
 | 
			
		||||
        this.attribute  = options.attribute || "aria-pressed";
 | 
			
		||||
        this.group      = null;
 | 
			
		||||
        this.isEnabled  = null;
 | 
			
		||||
        this.isSelected = false;
 | 
			
		||||
        this.text       = null;
 | 
			
		||||
        this.type       = Button.BUTTON;
 | 
			
		||||
 | 
			
		||||
        // Configure contents
 | 
			
		||||
        this.contents = document.createElement("div");
 | 
			
		||||
        this.append(this.contents);
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        this.setEnabled(!("enabled" in options) || options.enabled);
 | 
			
		||||
        if ("group"    in options)
 | 
			
		||||
            options.group.add(this);
 | 
			
		||||
        this.setText   (options.text);
 | 
			
		||||
        this.setType   (options.type);
 | 
			
		||||
        if ("selected" in options)
 | 
			
		||||
            this.setSelected(options.selected);
 | 
			
		||||
 | 
			
		||||
        // Configure event handlers
 | 
			
		||||
        this.addEventListener("keydown"    , e=>this.onKeyDown    (e));
 | 
			
		||||
        this.addEventListener("pointerdown", e=>this.onPointerDown(e));
 | 
			
		||||
        this.addEventListener("pointermove", e=>this.onPointerMove(e));
 | 
			
		||||
        this.addEventListener("pointerup"  , e=>this.onPointerUp  (e));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Event Handlers //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Key press
 | 
			
		||||
    onKeyDown(e) {
 | 
			
		||||
 | 
			
		||||
        // Processing by key
 | 
			
		||||
        switch (e.key) {
 | 
			
		||||
            case "Enter": // Fallthrough
 | 
			
		||||
            case " "    :
 | 
			
		||||
                this.click();
 | 
			
		||||
                break;
 | 
			
		||||
            default: return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Configure event
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Pointer down
 | 
			
		||||
    onPointerDown(e) {
 | 
			
		||||
        this.focus();
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (
 | 
			
		||||
            !this.isEnabled ||
 | 
			
		||||
            this.element.hasPointerCapture(e.pointerId) ||
 | 
			
		||||
            e.button != 0
 | 
			
		||||
        ) return;
 | 
			
		||||
 | 
			
		||||
        // Configure event
 | 
			
		||||
        this.element.setPointerCapture(e.pointerId);
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        this.element.classList.add("active");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Pointer move
 | 
			
		||||
    onPointerMove(e) {
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (!this.element.hasPointerCapture(e.pointerId))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Configure event
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        this.element.classList[
 | 
			
		||||
            Toolkit.isInside(this.element, e) ? "add" : "remove"]("active");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Pointer up
 | 
			
		||||
    onPointerUp(e) {
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (
 | 
			
		||||
            !this.isEnabled ||
 | 
			
		||||
            e.button != 0   ||
 | 
			
		||||
            !this.element.hasPointerCapture(e.pointerId)
 | 
			
		||||
        ) return;
 | 
			
		||||
 | 
			
		||||
        // Configure event
 | 
			
		||||
        this.element.releasePointerCapture(e.pointerId);
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        this.element.classList.remove("active");
 | 
			
		||||
 | 
			
		||||
        // Item is an action
 | 
			
		||||
        let bounds = this.getBounds();
 | 
			
		||||
        if (this.menu == null && Toolkit.isInside(this.element, e))
 | 
			
		||||
            this.click();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Programmatically activate the button
 | 
			
		||||
    click() {
 | 
			
		||||
        if (this instanceof Toolkit.CheckBox)
 | 
			
		||||
            this.setSelected(this instanceof Toolkit.Radio||!this.isSelected);
 | 
			
		||||
        this.event("action");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether the button can be activated
 | 
			
		||||
    setEnabled(enabled) {
 | 
			
		||||
        this.isEnabled = enabled = !!enabled;
 | 
			
		||||
        this.setAttribute("aria-disabled", enabled ? null : "true");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether the toggle or radio button is selected
 | 
			
		||||
    setSelected(selected) {
 | 
			
		||||
        selected = !!selected;
 | 
			
		||||
 | 
			
		||||
        // Take no action
 | 
			
		||||
        if (selected == this.isSelected)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Processing by button type
 | 
			
		||||
        switch (this.type) {
 | 
			
		||||
            case Button.RADIO :
 | 
			
		||||
                if (selected && this.group != null)
 | 
			
		||||
                    this.group.deselect();
 | 
			
		||||
                // Fallthrough
 | 
			
		||||
            case Button.TOGGLE:
 | 
			
		||||
                this.isSelected = selected;
 | 
			
		||||
                this.setAttribute(this.attribute, selected);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify the widget's display text
 | 
			
		||||
    setText(text) {
 | 
			
		||||
        this.text = (text || "").toString().trim();
 | 
			
		||||
        this.translate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify what kind of button this is
 | 
			
		||||
    setType(type) {
 | 
			
		||||
        switch (type) {
 | 
			
		||||
            case Button.BUTTON:
 | 
			
		||||
                this.type = type;
 | 
			
		||||
                this.setAttribute(this.attribute, null);
 | 
			
		||||
                this.setSelected(false);
 | 
			
		||||
                break;
 | 
			
		||||
            case Button.RADIO : // Fallthrough
 | 
			
		||||
            case Button.TOGGLE:
 | 
			
		||||
                this.type = type;
 | 
			
		||||
                this.setAttribute(this.attribute, this.isSelected);
 | 
			
		||||
                this.setSelected(this.isSelected);
 | 
			
		||||
                break;
 | 
			
		||||
            default: return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Update the global Toolkit object
 | 
			
		||||
    static setToolkit(toolkit) {
 | 
			
		||||
        Toolkit = toolkit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Regenerate localized display text
 | 
			
		||||
    translate() {
 | 
			
		||||
        super.translate();
 | 
			
		||||
        if (this.contents != null)
 | 
			
		||||
            this.contents.innerText = this.gui.translate(this.text, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                 CheckBox                                  //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// On/off toggle box
 | 
			
		||||
class CheckBox extends Button {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(app, options) {
 | 
			
		||||
 | 
			
		||||
        // Default options override
 | 
			
		||||
        let uptions = {};
 | 
			
		||||
        Object.assign(uptions, options || {});
 | 
			
		||||
        for (let entry of Object.entries({
 | 
			
		||||
            attribute: "aria-checked",
 | 
			
		||||
            className: "tk tk-checkbox",
 | 
			
		||||
            role     : "checkbox",
 | 
			
		||||
            style    : {},
 | 
			
		||||
            type     : Button.TOGGLE
 | 
			
		||||
        })) if (!(entry[0] in uptions))
 | 
			
		||||
            uptions[entry[0]] = entry[1];
 | 
			
		||||
 | 
			
		||||
        // Default styles override
 | 
			
		||||
        for (let entry of Object.entries({
 | 
			
		||||
            display            : "inline-grid",
 | 
			
		||||
            gridTemplateColumns: "max-content auto"
 | 
			
		||||
        })) if (!(entry[0] in uptions.style))
 | 
			
		||||
            uptions.style[entry[0]] = entry[1];
 | 
			
		||||
 | 
			
		||||
        // Component overrides
 | 
			
		||||
        super(app, uptions);
 | 
			
		||||
        this.contents.classList.add("tk-contents");
 | 
			
		||||
 | 
			
		||||
        // Configure icon
 | 
			
		||||
        this.icon = document.createElement("div");
 | 
			
		||||
        this.icon.className = "tk tk-icon";
 | 
			
		||||
        this.icon.setAttribute("aria-hidden", "true");
 | 
			
		||||
        this.prepend(this.icon);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                   Radio                                   //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Single selection box
 | 
			
		||||
class Radio extends CheckBox {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(app, options) {
 | 
			
		||||
 | 
			
		||||
        // Default options override
 | 
			
		||||
        let uptions = {};
 | 
			
		||||
        Object.assign(uptions, options || {});
 | 
			
		||||
        for (let entry of Object.entries({
 | 
			
		||||
            className: "tk tk-radio",
 | 
			
		||||
            role     : "radio",
 | 
			
		||||
            type     : Button.RADIO
 | 
			
		||||
        })) if (!(entry[0] in uptions))
 | 
			
		||||
            uptions[entry[0]] = entry[1];
 | 
			
		||||
 | 
			
		||||
        // Component overrides
 | 
			
		||||
        super(app, uptions);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                   Group                                   //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Radio button or menu item group
 | 
			
		||||
class Group extends Component {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(app) {
 | 
			
		||||
        super(app, {
 | 
			
		||||
            tagName: "div",
 | 
			
		||||
            style  : {
 | 
			
		||||
                height  : "0",
 | 
			
		||||
                position: "absolute",
 | 
			
		||||
                width   : "0"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.items = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Add an item
 | 
			
		||||
    add(item) {
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (!Toolkit.isComponent(item) || this.items.indexOf(item) != -1)
 | 
			
		||||
            return item;
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        this.setAttribute("role",
 | 
			
		||||
            item instanceof Toolkit.Radio ? "radiogroup" : "group");
 | 
			
		||||
 | 
			
		||||
        // Configure item
 | 
			
		||||
        if (item.group != null)
 | 
			
		||||
            item.group.remove(item);
 | 
			
		||||
        item.group = this;
 | 
			
		||||
 | 
			
		||||
        // Add the item to the collection
 | 
			
		||||
        item.id = item.id || Toolkit.id();
 | 
			
		||||
        this.items.push(item);
 | 
			
		||||
        this.setAttribute("aria-owns", this.items.map(i=>i.id).join(" "));
 | 
			
		||||
 | 
			
		||||
        return item;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove all items
 | 
			
		||||
    clear() {
 | 
			
		||||
        this.items.splice();
 | 
			
		||||
        this.setAttribute("aria-owns", "");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Un-check all items in the group
 | 
			
		||||
    deselect() {
 | 
			
		||||
        for (let item of this.items)
 | 
			
		||||
            if (item.isSelected && "setSelected" in item)
 | 
			
		||||
                item.setSelected(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove an item
 | 
			
		||||
    remove(item) {
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        let index = this.items.indexOf(item);
 | 
			
		||||
        if (index == -1)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Remove the item from the collection
 | 
			
		||||
        this.items.splice(index, 1);
 | 
			
		||||
        this.setAttribute("aria-owns", this.items.map(i=>i.id).join(" "));
 | 
			
		||||
        item.group = null;
 | 
			
		||||
 | 
			
		||||
        return item;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export { Button, CheckBox, Group, Radio };
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,312 @@
 | 
			
		|||
let Toolkit;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                 Component                                 //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Abstract class representing a distinct UI element
 | 
			
		||||
class Component {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(gui, options, defaults) {
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.children       = [];
 | 
			
		||||
        this.gui            = gui || this;
 | 
			
		||||
        this.label          = null;
 | 
			
		||||
        this.resizeObserver = null;
 | 
			
		||||
        this.substitutions  = {};
 | 
			
		||||
        this.toolTip        = null;
 | 
			
		||||
 | 
			
		||||
        // Configure default options
 | 
			
		||||
        let uptions = options || {};
 | 
			
		||||
        options = {};
 | 
			
		||||
        Object.assign(options, uptions);
 | 
			
		||||
        options.style  = options.style  || {};
 | 
			
		||||
        defaults       = defaults       || {};
 | 
			
		||||
        defaults.style = defaults.style || {};
 | 
			
		||||
        for (let key of Object.keys(defaults))
 | 
			
		||||
            if (!(key in options))
 | 
			
		||||
                options[key] = defaults[key];
 | 
			
		||||
        for (let key of Object.keys(defaults.style))
 | 
			
		||||
            if (!(key in options.style))
 | 
			
		||||
                options.style[key] = defaults.style[key];
 | 
			
		||||
        this.visibility = !!options.visibility;
 | 
			
		||||
 | 
			
		||||
        // Configure element
 | 
			
		||||
        this.element = document.createElement(
 | 
			
		||||
            ("tagName"  in options ? options.tagName : null) || "div");
 | 
			
		||||
        if (Object.keys(options.style).length != 0)
 | 
			
		||||
            Object.assign(this.element.style, options.style);
 | 
			
		||||
        if ("className" in options && options.className)
 | 
			
		||||
            this.element.className = options.className;
 | 
			
		||||
        if ("focusable" in options)
 | 
			
		||||
            this.setFocusable(options.focusable, options.tabStop);
 | 
			
		||||
        if ("id"        in options)
 | 
			
		||||
            this.setId(options.id);
 | 
			
		||||
        if ("role"      in options && options.role     )
 | 
			
		||||
            this.element.setAttribute("role", options.role);
 | 
			
		||||
        if ("visible"   in options)
 | 
			
		||||
            this.setVisible(options.visible);
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        this.setAttribute("name", options.name || "");
 | 
			
		||||
        this.setLabel    (options.label        || "");
 | 
			
		||||
        this.setToolTip  (options.toolTip      || "");
 | 
			
		||||
 | 
			
		||||
        // Configure substitutions
 | 
			
		||||
        if ("substitutions" in options) {
 | 
			
		||||
            for (let sub of Object.entries(options.substitutions))
 | 
			
		||||
                this.setSubstitution(sub[0], sub[1], true);
 | 
			
		||||
            this.translate();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Add a child component
 | 
			
		||||
    add(component) {
 | 
			
		||||
 | 
			
		||||
        // The component is already a child of this component
 | 
			
		||||
        let index = this.children.indexOf(component);
 | 
			
		||||
        if (index != -1)
 | 
			
		||||
            return index;
 | 
			
		||||
 | 
			
		||||
        // The component has a different parent already
 | 
			
		||||
        if (component.parent != null)
 | 
			
		||||
            component.parent.remove(component);
 | 
			
		||||
 | 
			
		||||
        // Add the child component to this component
 | 
			
		||||
        component.parent = this;
 | 
			
		||||
        this.children.push(component);
 | 
			
		||||
        if ("addHook" in this)
 | 
			
		||||
            this.addHook(component);
 | 
			
		||||
        else this.append(component);
 | 
			
		||||
        if ("addedHook" in component)
 | 
			
		||||
            component.addedHook(this);
 | 
			
		||||
        return this.children.length - 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Listen for events
 | 
			
		||||
    addEventListener(type, listener, useCapture) {
 | 
			
		||||
        let callback = e=>{
 | 
			
		||||
            e.component = this;
 | 
			
		||||
            return listener(e);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Register the listener for the event type
 | 
			
		||||
        this.element.addEventListener(type, callback, useCapture);
 | 
			
		||||
 | 
			
		||||
        // Listen for resize events on the element
 | 
			
		||||
        if (type == "resize" && this.resizeObserver == null) {
 | 
			
		||||
            this.resizeObserver = new ResizeObserver(
 | 
			
		||||
                ()=>this.event("resize"));
 | 
			
		||||
            this.resizeObserver.observe(this.element);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return callback;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add a DOM element as a sibling after this component
 | 
			
		||||
    after(child) {
 | 
			
		||||
        let element = child instanceof Element ? child : child.element;
 | 
			
		||||
        this.element.after(element);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add a DOM element to the end of this component's children
 | 
			
		||||
    append(child) {
 | 
			
		||||
        let element = child instanceof Element ? child : child.element;
 | 
			
		||||
        this.element.append(element);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add a DOM element as a sibling before this component
 | 
			
		||||
    before(child) {
 | 
			
		||||
        let element = child instanceof Element ? child : child.element;
 | 
			
		||||
        this.element.before(element);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Request non-focus on this component
 | 
			
		||||
    blur() {
 | 
			
		||||
        this.element.blur();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Determine whether this component contains another or an element
 | 
			
		||||
    contains(child) {
 | 
			
		||||
 | 
			
		||||
        // Child is an element
 | 
			
		||||
        if (child instanceof Element)
 | 
			
		||||
            return this.element.contains(child);
 | 
			
		||||
 | 
			
		||||
        // Child is a component
 | 
			
		||||
        for (let component = child; component; component = component.parent)
 | 
			
		||||
            if (component == this)
 | 
			
		||||
                return true;
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Request focus on the component
 | 
			
		||||
    focus() {
 | 
			
		||||
        this.element.focus();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Retrieve the current DOM position of the element
 | 
			
		||||
    getBounds() {
 | 
			
		||||
        return this.element.getBoundingClientRect();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Determine whether this component currently has focus
 | 
			
		||||
    hasFocus() {
 | 
			
		||||
        return document.activeElement == this.element;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Determine whether the component is visible
 | 
			
		||||
    isVisible() {
 | 
			
		||||
 | 
			
		||||
        // Common visibility test
 | 
			
		||||
        if (
 | 
			
		||||
            !document.contains(this.element) ||
 | 
			
		||||
            this.parent && !this.parent.isVisible()
 | 
			
		||||
        ) return false;
 | 
			
		||||
 | 
			
		||||
        // Overridden visibility test
 | 
			
		||||
        if ("visibleHook" in this) {
 | 
			
		||||
            if (!this.visibleHook())
 | 
			
		||||
                return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Default visibility test
 | 
			
		||||
        else {
 | 
			
		||||
            let style = getComputedStyle(this.element);
 | 
			
		||||
            if (style.display == "none" || style.visibility == "hidden")
 | 
			
		||||
                return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add a DOM element to the beginning of this component's children
 | 
			
		||||
    prepend(child) {
 | 
			
		||||
        let element = child instanceof Element ? child : child.element;
 | 
			
		||||
        this.element.prepend(element);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove a child component
 | 
			
		||||
    remove(component) {
 | 
			
		||||
        let index = this.children.indexOf(component);
 | 
			
		||||
 | 
			
		||||
        // The component does not belong to this component
 | 
			
		||||
        if (index == -1)
 | 
			
		||||
            return -1;
 | 
			
		||||
 | 
			
		||||
        // Remove the child component from this component
 | 
			
		||||
        this.children.splice(index, 1);
 | 
			
		||||
        if ("removeHook" in this)
 | 
			
		||||
            this.removeHook(component);
 | 
			
		||||
        else component.element.remove();
 | 
			
		||||
        if ("removedHook" in component)
 | 
			
		||||
            component.removedHook(this);
 | 
			
		||||
        return index;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove an event listener
 | 
			
		||||
    removeEventListener(type, listener, useCapture) {
 | 
			
		||||
        this.element.removeEventListener(type, listener, useCapture);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify an HTML attribute's value
 | 
			
		||||
    setAttribute(name, value) {
 | 
			
		||||
        value =
 | 
			
		||||
            value === false ? false :
 | 
			
		||||
            value === null || value === undefined ? "" :
 | 
			
		||||
            value.toString().trim()
 | 
			
		||||
        ;
 | 
			
		||||
        if (value === "")
 | 
			
		||||
            this.element.removeAttribute(name);
 | 
			
		||||
        else this.element.setAttribute(name, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether or not the element is focusable
 | 
			
		||||
    setFocusable(focusable, tabStop) {
 | 
			
		||||
        if (!focusable)
 | 
			
		||||
            this.element.removeAttribute("tabindex");
 | 
			
		||||
        else this.element.setAttribute("tabindex",
 | 
			
		||||
            tabStop || tabStop === undefined ? "0" : "-1");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify a localization key for the accessible name label
 | 
			
		||||
    setLabel(key) {
 | 
			
		||||
        this.label = key;
 | 
			
		||||
        this.translate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify the DOM Id for this element
 | 
			
		||||
    setId(id) {
 | 
			
		||||
        this.id = id = id || null;
 | 
			
		||||
        this.setAttribute("id", id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify text to substitute within localized contexts
 | 
			
		||||
    setSubstitution(key, text, noTranslate) {
 | 
			
		||||
        let ret = this.substitutions[key] || null;
 | 
			
		||||
 | 
			
		||||
        // Providing new text
 | 
			
		||||
        if (text !== null)
 | 
			
		||||
            this.substitutions[key] = text.toString();
 | 
			
		||||
 | 
			
		||||
        // Removing an association
 | 
			
		||||
        else if (key in this.substitutions)
 | 
			
		||||
            delete this.substitutions[key];
 | 
			
		||||
 | 
			
		||||
        // Update display text
 | 
			
		||||
        if (!noTranslate)
 | 
			
		||||
            this.translate();
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify a localization key for the tool tip text
 | 
			
		||||
    setToolTip(key) {
 | 
			
		||||
        this.toolTip = key;
 | 
			
		||||
        this.translate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether the component is visible
 | 
			
		||||
    setVisible(visible) {
 | 
			
		||||
        let prop = this.visibility ? "visibility" : "display";
 | 
			
		||||
        if (!!visible)
 | 
			
		||||
            this.element.style.removeProperty(prop);
 | 
			
		||||
        else this.element.style[prop] = this.visibility ? "hidden" : "none";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Dispatch an event
 | 
			
		||||
    event(type, fields) {
 | 
			
		||||
        this.element.dispatchEvent(Toolkit.event(type, this, fields));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update the global Toolkit object
 | 
			
		||||
    static setToolkit(toolkit) {
 | 
			
		||||
        Toolkit = toolkit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Regenerate localized display text
 | 
			
		||||
    translate() {
 | 
			
		||||
        if (this.label)
 | 
			
		||||
            this.setAttribute("aria-label", this.gui.translate(this.label, this));
 | 
			
		||||
        if (this.toolTip)
 | 
			
		||||
            this.setAttribute("title", this.gui.translate(this.toolTip, this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export { Component };
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,125 @@
 | 
			
		|||
import { Component } from /**/"./Component.js";
 | 
			
		||||
let Toolkit;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                 DropDown                                  //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Text entry field
 | 
			
		||||
class DropDown extends Component {
 | 
			
		||||
    static Component = Component;
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(gui, options) {
 | 
			
		||||
        super(gui, options, {
 | 
			
		||||
            className: "tk tk-dropdown",
 | 
			
		||||
            tagName  : "select"
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.isEnabled = null;
 | 
			
		||||
        this.options   = [];
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        options = options || {};
 | 
			
		||||
        this.setEnabled(!("enabled" in options) || options.enabled);
 | 
			
		||||
        if ("options" in options)
 | 
			
		||||
            this.setOptions(options.options);
 | 
			
		||||
        this.setSelectedIndex(
 | 
			
		||||
            ("selectedIndex" in options ? options : this).selectedIndex);
 | 
			
		||||
 | 
			
		||||
        // Configure event handlers
 | 
			
		||||
        this.addEventListener("keydown"    , e=>e.stopPropagation());
 | 
			
		||||
        this.addEventListener("pointerdown", e=>e.stopPropagation());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Programmatically change the selection
 | 
			
		||||
    change() {
 | 
			
		||||
        this.element.dispatchEvent(this.event("input"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Retrieve the current selection index
 | 
			
		||||
    getSelectedIndex() {
 | 
			
		||||
        return this.element.selectedIndex;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether the button can be activated
 | 
			
		||||
    setEnabled(enabled) {
 | 
			
		||||
        this.isEnabled = enabled = !!enabled;
 | 
			
		||||
        this.setAttribute("disabled", enabled ? null : "true");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify the list contents
 | 
			
		||||
    setOptions(options) {
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (!Array.isArray(options))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Erase the list of options
 | 
			
		||||
        this.options.splice(0);
 | 
			
		||||
        this.element.replaceChildren();
 | 
			
		||||
 | 
			
		||||
        // Add options from the input
 | 
			
		||||
        for (let option of options) {
 | 
			
		||||
            if (typeof option != "string")
 | 
			
		||||
                continue;
 | 
			
		||||
            this.options.push(option);
 | 
			
		||||
            this.element.add(document.createElement("option"));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Update the display text
 | 
			
		||||
        this.translate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify the current selection
 | 
			
		||||
    setSelectedIndex(index) {
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (typeof index != "number" || isNaN(index))
 | 
			
		||||
            return this.element.selectedIndex;
 | 
			
		||||
        index = Math.round(index);
 | 
			
		||||
        if (index < -1 || index >= this.options.length)
 | 
			
		||||
            return this.element.selectedIndex;
 | 
			
		||||
 | 
			
		||||
        // Configure element and instance fields
 | 
			
		||||
        return this.element.selectedIndex = index;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Update the global Toolkit object
 | 
			
		||||
    static setToolkit(toolkit) {
 | 
			
		||||
        Toolkit = toolkit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Regenerate localized display text
 | 
			
		||||
    translate() {
 | 
			
		||||
        super.translate();
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (!this.options)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Update the list items
 | 
			
		||||
        for (let x = 0; x < this.options.length; x++) {
 | 
			
		||||
            this.element.item(x).innerText =
 | 
			
		||||
                this.gui.translate(this.options[x], this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export { DropDown };
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,748 @@
 | 
			
		|||
import { Component } from /**/"./Component.js";
 | 
			
		||||
let Toolkit;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                   Menu                                    //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Pop-up menu container, child of MenuItem
 | 
			
		||||
class Menu extends Component {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(gui, options) {
 | 
			
		||||
        super(gui, options, {
 | 
			
		||||
            className : "tk tk-menu",
 | 
			
		||||
            role      : "menu",
 | 
			
		||||
            tagName   : "div",
 | 
			
		||||
            visibility: true,
 | 
			
		||||
            visible   : false,
 | 
			
		||||
            style     : {
 | 
			
		||||
                position: "absolute",
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Trap pointer events
 | 
			
		||||
        this.addEventListener("pointerdown", e=>{
 | 
			
		||||
            e.stopPropagation();
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Replacement behavior for parent.add()
 | 
			
		||||
    addedHook(parent) {
 | 
			
		||||
        this.setAttribute("aria-labelledby", parent.id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                               MenuSeparator                               //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Separator between groups of menu items
 | 
			
		||||
class MenuSeparator extends Component {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(gui, options) {
 | 
			
		||||
        super(gui, options, {
 | 
			
		||||
            className: "tk tk-menu-separator",
 | 
			
		||||
            role     : "separator",
 | 
			
		||||
            tagName  : "div"
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                 MenuItem                                  //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Individual menu selection
 | 
			
		||||
class MenuItem extends Component {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(gui, options) {
 | 
			
		||||
        super(gui, options, {
 | 
			
		||||
            className: "tk tk-menu-item",
 | 
			
		||||
            focusable: true,
 | 
			
		||||
            tabStop  : false,
 | 
			
		||||
            tagName  : "div"
 | 
			
		||||
        });
 | 
			
		||||
        options = options || {};
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.isEnabled  = null;
 | 
			
		||||
        this.isExpanded = false;
 | 
			
		||||
        this.menu       = null;
 | 
			
		||||
        this.menuBar    = null;
 | 
			
		||||
        this.text       = null;
 | 
			
		||||
        this.type       = null;
 | 
			
		||||
 | 
			
		||||
        // Configure element
 | 
			
		||||
        this.contents = document.createElement("div");
 | 
			
		||||
        this.append(this.contents);
 | 
			
		||||
        this.eicon = document.createElement("div");
 | 
			
		||||
        this.eicon.className = "tk tk-icon";
 | 
			
		||||
        this.contents.append(this.eicon);
 | 
			
		||||
        this.etext = document.createElement("div");
 | 
			
		||||
        this.etext.className = "tk tk-text";
 | 
			
		||||
        this.contents.append(this.etext);
 | 
			
		||||
 | 
			
		||||
        // Configure event handlers
 | 
			
		||||
        this.addEventListener("blur"       , e=>this.onBlur       (e));
 | 
			
		||||
        this.addEventListener("keydown"    , e=>this.onKeyDown    (e));
 | 
			
		||||
        this.addEventListener("pointerdown", e=>this.onPointerDown(e));
 | 
			
		||||
        this.addEventListener("pointermove", e=>this.onPointerMove(e));
 | 
			
		||||
        this.addEventListener("pointerup"  , e=>this.onPointerUp  (e));
 | 
			
		||||
 | 
			
		||||
        // Configure widget
 | 
			
		||||
        this.gui.localize(this);
 | 
			
		||||
        this.setEnabled("enabled" in options ? !!options.enabled : true);
 | 
			
		||||
        this.setId     (Toolkit.id());
 | 
			
		||||
        this.setText   (options.text);
 | 
			
		||||
        this.setType   (options.type, options.checked);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Event Handlers //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Focus lost
 | 
			
		||||
    onBlur(e) {
 | 
			
		||||
 | 
			
		||||
        // An item in a different menu is receiving focus
 | 
			
		||||
        if (this.menu != null) {
 | 
			
		||||
            if (
 | 
			
		||||
                !this     .contains(e.relatedTarget) &&
 | 
			
		||||
                !this.menu.contains(e.relatedTarget)
 | 
			
		||||
            ) this.setExpanded(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Item is an action
 | 
			
		||||
        else if (e.component == this)
 | 
			
		||||
            this.element.classList.remove("active");
 | 
			
		||||
 | 
			
		||||
        // Simulate a bubbling event sequence
 | 
			
		||||
        if (this.parent)
 | 
			
		||||
            this.parent.onBlur(e);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Key press
 | 
			
		||||
    onKeyDown(e) {
 | 
			
		||||
 | 
			
		||||
        // Processing by key
 | 
			
		||||
        switch (e.key) {
 | 
			
		||||
 | 
			
		||||
            case "ArrowDown":
 | 
			
		||||
 | 
			
		||||
                // Error checking
 | 
			
		||||
                if (!this.parent)
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                // Top-level: open the menu and focus its first item
 | 
			
		||||
                if (this.parent == this.menuBar) {
 | 
			
		||||
                    if (this.menu == null)
 | 
			
		||||
                        return;
 | 
			
		||||
                    this.setExpanded(true);
 | 
			
		||||
                    this.listItems()[0].focus();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Sub-menu: cycle to the next sibling
 | 
			
		||||
                else {
 | 
			
		||||
                    let items = this.parent.listItems();
 | 
			
		||||
                    items[(items.indexOf(this) + 1) % items.length].focus();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case "ArrowLeft":
 | 
			
		||||
 | 
			
		||||
                // Error checking
 | 
			
		||||
                if (!this.parent)
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                // Sub-menu: close and focus parent
 | 
			
		||||
                if (
 | 
			
		||||
                    this.parent        != this.menuBar &&
 | 
			
		||||
                    this.parent.parent != this.menuBar
 | 
			
		||||
                ) {
 | 
			
		||||
                    this.parent.setExpanded(false);
 | 
			
		||||
                    this.parent.focus();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Top-level: cycle to previous sibling
 | 
			
		||||
                else {
 | 
			
		||||
                    let menu  = this.parent == this.menuBar ?
 | 
			
		||||
                        this : this.parent;
 | 
			
		||||
                    let items = this.menuBar.listItems();
 | 
			
		||||
                    let prev  = items[(items.indexOf(menu) +
 | 
			
		||||
                        items.length - 1) % items.length];
 | 
			
		||||
                    if (menu.isExpanded)
 | 
			
		||||
                        prev.setExpanded(true);
 | 
			
		||||
                    prev.focus();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case "ArrowRight":
 | 
			
		||||
 | 
			
		||||
                // Error checking
 | 
			
		||||
                if (!this.parent)
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                // Sub-menu: open the menu and focus its first item
 | 
			
		||||
                if (this.menu != null && this.parent != this.menuBar) {
 | 
			
		||||
                    this.setExpanded(true);
 | 
			
		||||
                    (this.listItems()[0] || this).focus();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Top level: cycle to next sibling
 | 
			
		||||
                else {
 | 
			
		||||
                    let menu = this;
 | 
			
		||||
                    while (menu.parent != this.menuBar)
 | 
			
		||||
                        menu = menu.parent;
 | 
			
		||||
                    let expanded = this.menuBar.expandedMenu() != null;
 | 
			
		||||
                    let items = this.menuBar.listItems();
 | 
			
		||||
                    let next = items[(items.indexOf(menu) + 1) % items.length];
 | 
			
		||||
                    next.focus();
 | 
			
		||||
                    if (expanded)
 | 
			
		||||
                        next.setExpanded(true);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case "ArrowUp":
 | 
			
		||||
 | 
			
		||||
                // Error checking
 | 
			
		||||
                if (!this.parent)
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                // Top-level: open the menu and focus its last item
 | 
			
		||||
                if (this.parent == this.menuBar) {
 | 
			
		||||
                    if (this.menu == null)
 | 
			
		||||
                        return;
 | 
			
		||||
                    this.setExpanded(true);
 | 
			
		||||
                    let items = this.listItems();
 | 
			
		||||
                    (items[items.length - 1] || this).focus();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Sub-menu: cycle to previous sibling
 | 
			
		||||
                else {
 | 
			
		||||
                    let items = this.parent.listItems();
 | 
			
		||||
                    items[(items.indexOf(this) +
 | 
			
		||||
                        items.length - 1) % items.length].focus();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case "End":
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    // Error checking
 | 
			
		||||
                    if (!this.parent)
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    // Focus last sibling
 | 
			
		||||
                    let expanded = this.isExpanded &&
 | 
			
		||||
                        this.parent == this.menuBar;
 | 
			
		||||
                    let items = this.parent.listItems();
 | 
			
		||||
                    let last = items[items.length - 1] || this;
 | 
			
		||||
                    last.focus();
 | 
			
		||||
                    if (expanded)
 | 
			
		||||
                        last.setExpanded(true);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case "Enter":
 | 
			
		||||
            case " ":
 | 
			
		||||
 | 
			
		||||
                // Do nothing
 | 
			
		||||
                if (!this.isEnabled)
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                // Action item: activate the menu item
 | 
			
		||||
                if (this.menu == null)
 | 
			
		||||
                    this.activate(this.type == "check" && e.key == " ");
 | 
			
		||||
 | 
			
		||||
                // Sub-menu: open the menu and focus its first item
 | 
			
		||||
                else {
 | 
			
		||||
                    this.setExpanded(true);
 | 
			
		||||
                    let items = this.listItems();
 | 
			
		||||
                    if (items[0])
 | 
			
		||||
                        items[0].focus();
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case "Escape":
 | 
			
		||||
 | 
			
		||||
                // Error checking
 | 
			
		||||
                if (!this.parent)
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                // Top-level (not specified by WAI-ARIA)
 | 
			
		||||
                if (this.parent == this.menuBar) {
 | 
			
		||||
                    if (this.isExpanded)
 | 
			
		||||
                        this.setExpanded(false);
 | 
			
		||||
                    else this.menuBar.exit();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Sub-menu: close and focus parent
 | 
			
		||||
                else {
 | 
			
		||||
                    this.parent.setExpanded(false);
 | 
			
		||||
                    this.parent.focus();
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case "Home":
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    // Error checking
 | 
			
		||||
                    if (!this.parent)
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    // Focus first sibling
 | 
			
		||||
                    let expanded = this.isExpanded &&
 | 
			
		||||
                        this.parent == this.menuBar;
 | 
			
		||||
                    let first = this.parent.listItems()[0] || this;
 | 
			
		||||
                    first.focus();
 | 
			
		||||
                    if (expanded)
 | 
			
		||||
                        first.setExpanded(true);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            // Do not handle the event
 | 
			
		||||
            default: return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // The event was handled
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Pointer press
 | 
			
		||||
    onPointerDown(e) {
 | 
			
		||||
        this.focus();
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (
 | 
			
		||||
            !this.isEnabled ||
 | 
			
		||||
            this.element.hasPointerCapture(e.pointerId) ||
 | 
			
		||||
            e.button != 0
 | 
			
		||||
        ) return;
 | 
			
		||||
 | 
			
		||||
        // Configure event
 | 
			
		||||
        if (this.menu == null)
 | 
			
		||||
            this.element.setPointerCapture(e.pointerId);
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        if (this.menu != null)
 | 
			
		||||
            this.setExpanded(!this.isExpanded);
 | 
			
		||||
        else this.element.classList.add("active");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Pointer move
 | 
			
		||||
    onPointerMove(e) {
 | 
			
		||||
 | 
			
		||||
        // Hovering over a menu when a sibling menu is already open
 | 
			
		||||
        let expanded = this.parent && this.parent.expandedMenu();
 | 
			
		||||
        if (this.menu != null && expanded != null && expanded != this) {
 | 
			
		||||
 | 
			
		||||
            // Configure component
 | 
			
		||||
            this.setExpanded(true);
 | 
			
		||||
            this.focus();
 | 
			
		||||
 | 
			
		||||
            // Configure event
 | 
			
		||||
            e.stopPropagation();
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Not dragging
 | 
			
		||||
        if (!this.element.hasPointerCapture(e.pointerId))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Configure event
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
 | 
			
		||||
        // Not an action item
 | 
			
		||||
        if (this.menu != null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Check if the cursor is within the bounds of the component
 | 
			
		||||
        this.element.classList[
 | 
			
		||||
            Toolkit.isInside(this.element, e) ? "add" : "remove"]("active");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Pointer release
 | 
			
		||||
    onPointerUp(e) {
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (
 | 
			
		||||
            !this.isEnabled ||
 | 
			
		||||
            e.button != 0 ||
 | 
			
		||||
            (this.parent && this.parent.hasFocus() ?
 | 
			
		||||
                this.menu != null :
 | 
			
		||||
                !this.element.hasPointerCapture(e.pointerId)
 | 
			
		||||
            )
 | 
			
		||||
        ) return;
 | 
			
		||||
 | 
			
		||||
        // Configure event
 | 
			
		||||
        this.element.releasePointerCapture(e.pointerId);
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
 | 
			
		||||
        // Item is an action
 | 
			
		||||
        let bounds = this.getBounds();
 | 
			
		||||
        if (this.menu == null && Toolkit.isInside(this.element, e))
 | 
			
		||||
            this.activate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Invoke an action command
 | 
			
		||||
    activate(noExit) {
 | 
			
		||||
        if (this.menu != null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        if (this.type == "check")
 | 
			
		||||
            this.setChecked(!this.isChecked);
 | 
			
		||||
 | 
			
		||||
        if (!noExit)
 | 
			
		||||
            this.menuBar.exit();
 | 
			
		||||
 | 
			
		||||
        this.element.dispatchEvent(Toolkit.event("action", this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add a separator between groups of menu items
 | 
			
		||||
    addSeparator(options) {
 | 
			
		||||
        let sep = new Toolkit.MenuSeparator(this, options);
 | 
			
		||||
        this.add(sep);
 | 
			
		||||
        return sep;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Produce a list of child items
 | 
			
		||||
    listItems(invisible) {
 | 
			
		||||
        return this.children.filter(c=>
 | 
			
		||||
            c instanceof Toolkit.MenuItem &&
 | 
			
		||||
            (invisible || c.isVisible())
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether the menu item is checked
 | 
			
		||||
    setChecked(checked) {
 | 
			
		||||
        if (this.type != "check")
 | 
			
		||||
            return;
 | 
			
		||||
        this.isChecked = !!checked;
 | 
			
		||||
        this.setAttribute("aria-checked", this.isChecked);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether the menu item can be activated
 | 
			
		||||
    setEnabled(enabled) {
 | 
			
		||||
        this.isEnabled = enabled = !!enabled;
 | 
			
		||||
        this.setAttribute("aria-disabled", enabled ? null : "true");
 | 
			
		||||
        if (!enabled)
 | 
			
		||||
            this.setExpanded(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether the sub-menu is open
 | 
			
		||||
    setExpanded(expanded) {
 | 
			
		||||
 | 
			
		||||
        // State is not changing
 | 
			
		||||
        expanded = !!expanded;
 | 
			
		||||
        if (this.menu == null || expanded === this.isExpanded)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Position the sub-menu
 | 
			
		||||
        if (expanded) {
 | 
			
		||||
            let bndGUI    = this.gui .getBounds();
 | 
			
		||||
            let bndMenu   = this.menu.getBounds();
 | 
			
		||||
            let bndThis   = this     .getBounds();
 | 
			
		||||
            let bndParent = !this.parent ? bndThis : (
 | 
			
		||||
                this.parent == this.menuBar ? this.parent : this.parent.menu
 | 
			
		||||
            ).getBounds();
 | 
			
		||||
            this.menu.element.style.left = Math.max(0,
 | 
			
		||||
                Math.min(
 | 
			
		||||
                    (this.parent && this.parent == this.menuBar ?
 | 
			
		||||
                        bndThis.left : bndThis.right) - bndParent.left,
 | 
			
		||||
                    bndGUI.right - bndMenu.width
 | 
			
		||||
                )
 | 
			
		||||
            ) + "px";
 | 
			
		||||
            this.menu.element.style.top = Math.max(0,
 | 
			
		||||
                Math.min(
 | 
			
		||||
                    (this.parent && this.parent == this.menuBar ?
 | 
			
		||||
                        bndThis.bottom : bndThis.top) - bndParent.top,
 | 
			
		||||
                    bndGUI.bottom - bndMenu.height
 | 
			
		||||
                )
 | 
			
		||||
            ) + "px";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Close all open sub-menus
 | 
			
		||||
        else for (let child of this.listItems())
 | 
			
		||||
            child.setExpanded(false);
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        this.isExpanded = expanded;
 | 
			
		||||
        this.setAttribute("aria-expanded", expanded);
 | 
			
		||||
        this.menu.setVisible(expanded);
 | 
			
		||||
        if (expanded)
 | 
			
		||||
            this.element.classList.add("active");
 | 
			
		||||
        else this.element.classList.remove("active");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify the widget's display text
 | 
			
		||||
    setText(text) {
 | 
			
		||||
        this.text = (text || "").toString().trim();
 | 
			
		||||
        this.translate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify what kind of menu item this is
 | 
			
		||||
    setType(type, arg) {
 | 
			
		||||
        this.type = type = (type || "").toString().trim() || "normal";
 | 
			
		||||
        switch (type) {
 | 
			
		||||
            case "check":
 | 
			
		||||
                this.setAttribute("role", "menuitemcheckbox");
 | 
			
		||||
                this.setChecked(arg);
 | 
			
		||||
                break;
 | 
			
		||||
            default: // normal
 | 
			
		||||
                this.setAttribute("role", "menuitem");
 | 
			
		||||
                this.setAttribute("aria-checked", null);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
        if (this.parent && "checkIcons" in this.parent)
 | 
			
		||||
            this.parent.checkIcons();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Replacement behavior for add()
 | 
			
		||||
    addHook(component) {
 | 
			
		||||
 | 
			
		||||
        // Convert to sub-menu
 | 
			
		||||
        if (this.menu == null) {
 | 
			
		||||
            this.menu = new Toolkit.Menu(this);
 | 
			
		||||
            this.after(this.menu);
 | 
			
		||||
            this.setAttribute("aria-haspopup", "menu");
 | 
			
		||||
            this.setAttribute("aria-expanded", "false");
 | 
			
		||||
            if (this.parent && "checkIcons" in this.parent)
 | 
			
		||||
                this.parent.checkIcons();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add the child component
 | 
			
		||||
        component.menuBar = this.menuBar;
 | 
			
		||||
        this.menu.append(component);
 | 
			
		||||
        if (component instanceof Toolkit.MenuItem && component.menu != null)
 | 
			
		||||
            this.menu.append(component.menu);
 | 
			
		||||
 | 
			
		||||
        // Configure icon mode
 | 
			
		||||
        this.checkIcons();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check whether any child menu items contain icons
 | 
			
		||||
    checkIcons() {
 | 
			
		||||
        if (this.menu == null)
 | 
			
		||||
            return;
 | 
			
		||||
        if (this.children.filter(c=>
 | 
			
		||||
            c instanceof Toolkit.MenuItem &&
 | 
			
		||||
            c.menu == null &&
 | 
			
		||||
            c.type != "normal"
 | 
			
		||||
        ).length != 0)
 | 
			
		||||
            this.menu.element.classList.add("icons");
 | 
			
		||||
        else this.menu.element.classList.remove("icons");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Replacement behavior for remove()
 | 
			
		||||
    removeHook(component) {
 | 
			
		||||
 | 
			
		||||
        // Remove the child component
 | 
			
		||||
        component.element.remove();
 | 
			
		||||
        if (component instanceof Toolkit.MenuItem && component.menu != null)
 | 
			
		||||
            component.menu.element.remove();
 | 
			
		||||
 | 
			
		||||
        // Convert to action item
 | 
			
		||||
        if (this.children.length == 0) {
 | 
			
		||||
            this.menu.element.remove();
 | 
			
		||||
            this.menu = null;
 | 
			
		||||
            this.setAttribute("aria-haspopup", null);
 | 
			
		||||
            this.setAttribute("aria-expanded", "false");
 | 
			
		||||
            if (this.parent && "checkIcons" in this.parent)
 | 
			
		||||
                this.parent.checkIcons();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Regenerate localized display text
 | 
			
		||||
    translate() {
 | 
			
		||||
        super.translate();
 | 
			
		||||
        if (!("contents" in this))
 | 
			
		||||
            return;
 | 
			
		||||
        this.etext.innerText = this.gui.translate(this.text, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Private Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Retrieve the currently expanded sub-menu, if any
 | 
			
		||||
    expandedMenu() {
 | 
			
		||||
        return this.children.filter(c=>c.isExpanded)[0] || null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                  MenuBar                                  //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Application menu bar
 | 
			
		||||
class MenuBar extends Component {
 | 
			
		||||
    static Component = Component;
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(gui, options) {
 | 
			
		||||
        super(gui, options, {
 | 
			
		||||
            className: "tk tk-menu-bar",
 | 
			
		||||
            focusable: false,
 | 
			
		||||
            tagName  : "div",
 | 
			
		||||
            tabStop  : true,
 | 
			
		||||
            role     : "menubar",
 | 
			
		||||
            style    : {
 | 
			
		||||
                position: "relative",
 | 
			
		||||
                zIndex  : "1"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.focusTarget = null;
 | 
			
		||||
        this.menuBar     = this;
 | 
			
		||||
 | 
			
		||||
        // Configure event handlers
 | 
			
		||||
        this.addEventListener("blur"   , e=>this.onBlur   (e), true);
 | 
			
		||||
        this.addEventListener("focus"  , e=>this.onFocus  (e), true);
 | 
			
		||||
        this.addEventListener("keydown", e=>this.onKeyDown(e), true);
 | 
			
		||||
 | 
			
		||||
        // Configure widget
 | 
			
		||||
        this.gui.localize(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Event Handlers //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Focus lost
 | 
			
		||||
    onBlur(e) {
 | 
			
		||||
        if (this.contains(e.relatedTarget))
 | 
			
		||||
            return;
 | 
			
		||||
        let items = this.listItems();
 | 
			
		||||
        if (items[0])
 | 
			
		||||
            items[0].setFocusable(true, true);
 | 
			
		||||
        let menu = this.expandedMenu();
 | 
			
		||||
        if (menu != null)
 | 
			
		||||
            menu.setExpanded(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Focus gained
 | 
			
		||||
    onFocus(e) {
 | 
			
		||||
        if (this.contains(e.relatedTarget))
 | 
			
		||||
            return;
 | 
			
		||||
        let items = this.listItems();
 | 
			
		||||
        if (items[0])
 | 
			
		||||
            items[0].setFocusable(true, false);
 | 
			
		||||
        this.focusTarget = e.relatedTarget;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Key pressed
 | 
			
		||||
    onKeyDown(e) {
 | 
			
		||||
        if (e.key != "Tab")
 | 
			
		||||
            return;
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        this.exit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Produce a list of child items
 | 
			
		||||
    listItems(invisible) {
 | 
			
		||||
        return this.children.filter(c=>
 | 
			
		||||
            c instanceof Toolkit.MenuItem &&
 | 
			
		||||
            (invisible || c.isVisible())
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Replacement behavior for add()
 | 
			
		||||
    addHook(component) {
 | 
			
		||||
        component.menuBar = this.menuBar;
 | 
			
		||||
        this.append(component);
 | 
			
		||||
        if (component instanceof Toolkit.MenuItem && component.menu != null)
 | 
			
		||||
            this.append(component.menu);
 | 
			
		||||
        let items = this.listItems();
 | 
			
		||||
        if (items[0])
 | 
			
		||||
            items[0].setFocusable(true, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Return control to the application
 | 
			
		||||
    exit() {
 | 
			
		||||
        this.onBlur({ relatedTarget: null });
 | 
			
		||||
        if (this.focusTarget)
 | 
			
		||||
            this.focusTarget.focus();
 | 
			
		||||
        else document.activeElement.blur();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Replacement behavior for remove()
 | 
			
		||||
    removeHook(component) {
 | 
			
		||||
        component.element.remove();
 | 
			
		||||
        if (component instanceof Toolkit.MenuItem && component.menu != null)
 | 
			
		||||
            component.menu.element.remove();
 | 
			
		||||
        let items = this.listItems();
 | 
			
		||||
        if (items[0])
 | 
			
		||||
            items[0].setFocusable(true, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update the global Toolkit object
 | 
			
		||||
    static setToolkit(toolkit) {
 | 
			
		||||
        Toolkit = toolkit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Private Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Retrieve the currently expanded menu, if any
 | 
			
		||||
    expandedMenu() {
 | 
			
		||||
        return this.children.filter(c=>c.isExpanded)[0] || null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export { Menu, MenuBar, MenuItem, MenuSeparator };
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,145 @@
 | 
			
		|||
import { Component } from /**/"./Component.js";
 | 
			
		||||
let Toolkit;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                  TextBox                                  //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Text entry field
 | 
			
		||||
class TextBox extends Component {
 | 
			
		||||
    static Component = Component;
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(gui, options) {
 | 
			
		||||
        super(gui, options, {
 | 
			
		||||
            className: "tk tk-textbox",
 | 
			
		||||
            tagName  : "input"
 | 
			
		||||
        });
 | 
			
		||||
        this.element.type = "text";
 | 
			
		||||
        this.setAttribute("spellcheck", "false");
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.isEnabled = null;
 | 
			
		||||
        this.maxLength = null;
 | 
			
		||||
        this.pattern   = null;
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        options = options || {};
 | 
			
		||||
        this.setEnabled(!("enabled" in options) || options.enabled);
 | 
			
		||||
        if ("maxLength" in options)
 | 
			
		||||
            this.setMaxLength(options.maxLength);
 | 
			
		||||
        if ("pattern" in options)
 | 
			
		||||
            this.setPattern(options.pattern);
 | 
			
		||||
        this.setText   (options.text);
 | 
			
		||||
 | 
			
		||||
        // Configure event handlers
 | 
			
		||||
        this.addEventListener("blur"       , e=>this.commit      ( ));
 | 
			
		||||
        this.addEventListener("pointerdown", e=>e.stopPropagation( ));
 | 
			
		||||
        this.addEventListener("keydown"    , e=>this.onKeyDown   (e));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Event Handlers //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Key press
 | 
			
		||||
    onKeyDown(e) {
 | 
			
		||||
 | 
			
		||||
        // Processing by key
 | 
			
		||||
        switch (e.key) {
 | 
			
		||||
            case "ArrowLeft":
 | 
			
		||||
            case "ArrowRight":
 | 
			
		||||
                e.stopPropagation();
 | 
			
		||||
                return;
 | 
			
		||||
            case "Enter":
 | 
			
		||||
                this.commit();
 | 
			
		||||
                break;
 | 
			
		||||
            default: return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Configure event
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Programmatically commit the text box
 | 
			
		||||
    commit() {
 | 
			
		||||
        this.event("action");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Retrieve the control's value
 | 
			
		||||
    getText() {
 | 
			
		||||
        return this.element.value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether the button can be activated
 | 
			
		||||
    setEnabled(enabled) {
 | 
			
		||||
        this.isEnabled = enabled = !!enabled;
 | 
			
		||||
        this.setAttribute("disabled", enabled ? null : "true");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify the maximum length of the text
 | 
			
		||||
    setMaxLength(length) {
 | 
			
		||||
 | 
			
		||||
        // Remove limitation
 | 
			
		||||
        if (length === null) {
 | 
			
		||||
            this.maxLength = null;
 | 
			
		||||
            this.setAttribute("maxlength", null);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (typeof length != "number" || isNaN(length))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Range checking
 | 
			
		||||
        length = Math.floor(length);
 | 
			
		||||
        if (length < 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        this.maxLength = length;
 | 
			
		||||
        this.setAttribute("maxlength", length);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify a regex pattern for valid text characters
 | 
			
		||||
    setPattern(pattern) {
 | 
			
		||||
        /*
 | 
			
		||||
        Disabled because user agents may not prevent invalid input
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (pattern && typeof pattern != "string")
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        this.pattern = pattern = pattern || null;
 | 
			
		||||
        this.setAttribute("pattern", pattern);
 | 
			
		||||
        */
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify the widget's display text
 | 
			
		||||
    setText(text = "") {
 | 
			
		||||
        this.element.value = text.toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Update the global Toolkit object
 | 
			
		||||
    static setToolkit(toolkit) {
 | 
			
		||||
        Toolkit = toolkit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export { TextBox };
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,337 @@
 | 
			
		|||
import { Component                              } from /**/"./Component.js";
 | 
			
		||||
import { Button, CheckBox, Group, Radio         } from /**/"./Button.js"   ;
 | 
			
		||||
import { DropDown                               } from /**/"./DropDown.js" ;
 | 
			
		||||
import { Menu, MenuBar, MenuItem, MenuSeparator } from /**/"./MenuBar.js"  ;
 | 
			
		||||
import { ScrollBar, ScrollPane, SplitPane       } from /**/"./ScrollBar.js";
 | 
			
		||||
import { TextBox                                } from /**/"./TextBox.js"  ;
 | 
			
		||||
import { Desktop, Window                        } from /**/"./Window.js"   ;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                  Toolkit                                  //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Top-level user interface manager
 | 
			
		||||
let Toolkit = globalThis.Toolkit = (class GUI extends Component {
 | 
			
		||||
 | 
			
		||||
    static initializer() {
 | 
			
		||||
 | 
			
		||||
        // Static state
 | 
			
		||||
        this.nextId = 0;
 | 
			
		||||
 | 
			
		||||
        // Locale presets
 | 
			
		||||
        this.NO_LOCALE = { id: "(Null)" };
 | 
			
		||||
 | 
			
		||||
        // Component classes
 | 
			
		||||
        this.components = [];
 | 
			
		||||
        Button   .setToolkit(this); this.components.push(Button   .Component);
 | 
			
		||||
        Component.setToolkit(this); this.components.push(          Component);
 | 
			
		||||
        DropDown .setToolkit(this); this.components.push(DropDown .Component);
 | 
			
		||||
        MenuBar  .setToolkit(this); this.components.push(MenuBar  .Component);
 | 
			
		||||
        ScrollBar.setToolkit(this); this.components.push(ScrollBar.Component);
 | 
			
		||||
        TextBox  .setToolkit(this); this.components.push(TextBox  .Component);
 | 
			
		||||
        Window   .setToolkit(this); this.components.push(Window   .Component);
 | 
			
		||||
        this.Button        = Button;
 | 
			
		||||
        this.CheckBox      = CheckBox;
 | 
			
		||||
        this.Component     = Component;
 | 
			
		||||
        this.Desktop       = Desktop;
 | 
			
		||||
        this.DropDown      = DropDown;
 | 
			
		||||
        this.Group         = Group;
 | 
			
		||||
        this.Menu          = Menu;
 | 
			
		||||
        this.MenuBar       = MenuBar;
 | 
			
		||||
        this.MenuItem      = MenuItem;
 | 
			
		||||
        this.MenuSeparator = MenuSeparator;
 | 
			
		||||
        this.Radio         = Radio;
 | 
			
		||||
        this.ScrollBar     = ScrollBar;
 | 
			
		||||
        this.ScrollPane    = ScrollPane;
 | 
			
		||||
        this.SplitPane     = SplitPane;
 | 
			
		||||
        this.TextBox       = TextBox;
 | 
			
		||||
        this.Window        = Window;
 | 
			
		||||
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Static Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Monitor resize events on an element
 | 
			
		||||
    static addResizeListener(element, listener) {
 | 
			
		||||
 | 
			
		||||
        // Establish a ResizeObserver
 | 
			
		||||
        if (!("resizeListeners" in element)) {
 | 
			
		||||
            element.resizeListeners = [];
 | 
			
		||||
            element.resizeObserver  = new ResizeObserver(
 | 
			
		||||
                (e,o)=>element.dispatchEvent(this.event("resize")));
 | 
			
		||||
            element.resizeObserver.observe(element);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Associate the listener
 | 
			
		||||
        if (element.resizeListeners.indexOf(listener) == -1) {
 | 
			
		||||
            element.resizeListeners.push(listener);
 | 
			
		||||
            element.addEventListener("resize", listener);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Stop monitoring resize events on an element
 | 
			
		||||
    static clearResizeListeners(element) {
 | 
			
		||||
        while ("resizeListeners" in element)
 | 
			
		||||
            this.removeResizeListener(element, element.resizeListeners[0]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Produce a custom event object
 | 
			
		||||
    static event(type, component, fields) {
 | 
			
		||||
        let event = new Event(type, {
 | 
			
		||||
            bubbles   : true,
 | 
			
		||||
            cancelable: true
 | 
			
		||||
        });
 | 
			
		||||
        if (component)
 | 
			
		||||
            event.component = component;
 | 
			
		||||
        if (fields)
 | 
			
		||||
            Object.assign(event, fields);
 | 
			
		||||
        return event;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Produce a unique element ID
 | 
			
		||||
    static id() {
 | 
			
		||||
        return "tk" + (this.nextId++);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Determine whether an object is a component
 | 
			
		||||
    // The user agent may not resolve imports to the same classes
 | 
			
		||||
    static isComponent(o) {
 | 
			
		||||
        return !!this.components.find(c=>o instanceof c);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Determine whether a pointer event is inside an element
 | 
			
		||||
    static isInside(element, e) {
 | 
			
		||||
        let bounds = element.getBoundingClientRect();
 | 
			
		||||
        return (
 | 
			
		||||
            e.offsetX >= 0 && e.offsetX < bounds.width &&
 | 
			
		||||
            e.offsetY >= 0 && e.offsetY < bounds.height
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Generate a list of focusable child elements
 | 
			
		||||
    static listFocusables(element) {
 | 
			
		||||
        return Array.from(element.querySelectorAll(
 | 
			
		||||
            "*:not(*:not(a[href], area, button, details, input, " +
 | 
			
		||||
            "textarea, select, [tabindex='0'])):not([disabled])"
 | 
			
		||||
        )).filter(e=>{
 | 
			
		||||
            for (; e instanceof Element; e = e.parentNode) {
 | 
			
		||||
                let style = getComputedStyle(e);
 | 
			
		||||
                if (style.display == "none" || style.visibility == "hidden")
 | 
			
		||||
                    return false;
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Stop monitoring resize events on an element
 | 
			
		||||
    static removeResizeListener(element, listener) {
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (!("resizeListeners" in element))
 | 
			
		||||
            return;
 | 
			
		||||
        let index = element.resizeListeners.indexOf(listener);
 | 
			
		||||
        if (index == -1)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Remove the listener
 | 
			
		||||
        element.removeEventListener("resize", element.resizeListeners[index]);
 | 
			
		||||
        element.resizeListeners.splice(index, 1);
 | 
			
		||||
 | 
			
		||||
        // No more listeners: delete the ResizeObserver
 | 
			
		||||
        if (element.resizeListeners.length == 0) {
 | 
			
		||||
            element.resizeObserver.unobserve(element);
 | 
			
		||||
            delete element.resizeListeners;
 | 
			
		||||
            delete element.resizeObserver;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Compute pointer event screen coordinates
 | 
			
		||||
    static screenCoords(e) {
 | 
			
		||||
        return {
 | 
			
		||||
            x: e.screenX / window.devicePixelRatio,
 | 
			
		||||
            y: e.screenY / window.devicePixelRatio
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(options) {
 | 
			
		||||
        super(null, options);
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.locale    = Toolkit.NO_LOCALE;
 | 
			
		||||
        this.localized = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Specify the locale to use for translated strings
 | 
			
		||||
    setLocale(locale) {
 | 
			
		||||
        this.locale = locale || Toolkit.NO_LOCALE;
 | 
			
		||||
        for (let component of this.localized)
 | 
			
		||||
            component.translate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Translate a string in the selected locale
 | 
			
		||||
    translate(key, component) {
 | 
			
		||||
 | 
			
		||||
        // Front-end method
 | 
			
		||||
        if (key === undefined) {
 | 
			
		||||
            super.translate();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Working variables
 | 
			
		||||
        let subs = component ? component.substitutions : {};
 | 
			
		||||
        key      = (key || "").toString().trim();
 | 
			
		||||
 | 
			
		||||
        // Error checking
 | 
			
		||||
        if (this.locale == null || key == "")
 | 
			
		||||
            return key;
 | 
			
		||||
 | 
			
		||||
        // Resolve the key first in the substitutions then in the locale
 | 
			
		||||
        let text = key;
 | 
			
		||||
        key = key.toLowerCase();
 | 
			
		||||
        if (key in subs)
 | 
			
		||||
            text = subs[key];
 | 
			
		||||
        else if (key in this.locale)
 | 
			
		||||
            text = this.locale[key];
 | 
			
		||||
        else return "!" + text.toUpperCase();
 | 
			
		||||
 | 
			
		||||
        // Process all substitutions
 | 
			
		||||
        for (;;) {
 | 
			
		||||
 | 
			
		||||
            // Working variables
 | 
			
		||||
            let sIndex =  0;
 | 
			
		||||
            let rIndex = -1;
 | 
			
		||||
            let lIndex = -1;
 | 
			
		||||
            let zIndex = -1;
 | 
			
		||||
 | 
			
		||||
            // Locate the inner-most {} or [] pair
 | 
			
		||||
            for (;;) {
 | 
			
		||||
                let match = Toolkit.subCtrl(text, sIndex);
 | 
			
		||||
 | 
			
		||||
                // No control characters found
 | 
			
		||||
                if (match == -1)
 | 
			
		||||
                    break;
 | 
			
		||||
                sIndex = match + 1;
 | 
			
		||||
 | 
			
		||||
                // Processing by control character
 | 
			
		||||
                switch (text.charAt(match)) {
 | 
			
		||||
 | 
			
		||||
                    // Opening a substitution group
 | 
			
		||||
                    case "{": rIndex = match; continue;
 | 
			
		||||
                    case "[": lIndex = match; continue;
 | 
			
		||||
 | 
			
		||||
                    // Closing a recursion group
 | 
			
		||||
                    case "}":
 | 
			
		||||
                        if (rIndex != -1) {
 | 
			
		||||
                            lIndex = -1;
 | 
			
		||||
                            zIndex = match;
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    // Closing a literal group
 | 
			
		||||
                    case "]":
 | 
			
		||||
                        if (lIndex != -1) {
 | 
			
		||||
                            rIndex = -1;
 | 
			
		||||
                            zIndex = match;
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Process a recursion substitution
 | 
			
		||||
            if (rIndex != -1) {
 | 
			
		||||
                text =
 | 
			
		||||
                    text.substring(0, rIndex) +
 | 
			
		||||
                    this.translate(
 | 
			
		||||
                        text.substring(rIndex + 1, zIndex),
 | 
			
		||||
                        component
 | 
			
		||||
                    ) +
 | 
			
		||||
                    text.substring(zIndex + 1)
 | 
			
		||||
                ;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Process a literal substitution
 | 
			
		||||
            else if (lIndex != -1) {
 | 
			
		||||
                text =
 | 
			
		||||
                    text.substring(0, lIndex) +
 | 
			
		||||
                    text.substring(lIndex + 1, zIndex)
 | 
			
		||||
                        .replaceAll("{", "{{")
 | 
			
		||||
                        .replaceAll("}", "}}")
 | 
			
		||||
                        .replaceAll("[", "[[")
 | 
			
		||||
                        .replaceAll("]", "]]")
 | 
			
		||||
                    +
 | 
			
		||||
                    text.substring(zIndex + 1)
 | 
			
		||||
                ;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // No more substitutions
 | 
			
		||||
            else break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Unescape all remaining control characters
 | 
			
		||||
        return (text
 | 
			
		||||
            .replaceAll("{{", "{")
 | 
			
		||||
            .replaceAll("}}", "}")
 | 
			
		||||
            .replaceAll("[[", "[")
 | 
			
		||||
            .replaceAll("]]", "]")
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Reduce an object to a single level of depth
 | 
			
		||||
    static flatten(obj, ret = {}, id) {
 | 
			
		||||
        for (let entry of Object.entries(obj)) {
 | 
			
		||||
            let key   = (id ? id + "." : "") + entry[0].toLowerCase();
 | 
			
		||||
            let value = entry[1];
 | 
			
		||||
            if (value instanceof Object)
 | 
			
		||||
                this.flatten(value, ret, key);
 | 
			
		||||
            else ret[key] = value;
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Register a component for localization management
 | 
			
		||||
    localize(component) {
 | 
			
		||||
        if (this.localized.indexOf(component) != -1)
 | 
			
		||||
            return;
 | 
			
		||||
        this.localized.push(component);
 | 
			
		||||
        component.translate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Locate a substitution control character in a string
 | 
			
		||||
    static subCtrl(text, index) {
 | 
			
		||||
        for (; index < text.length; index++) {
 | 
			
		||||
            let c = text.charAt(index);
 | 
			
		||||
            if ("{}[]".indexOf(c) == -1)
 | 
			
		||||
                continue;
 | 
			
		||||
            if (index < text.length - 1 || text.charAt(index + 1) != c)
 | 
			
		||||
                return index;
 | 
			
		||||
            index++;
 | 
			
		||||
        }
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}).initializer();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export { Toolkit };
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,699 @@
 | 
			
		|||
import { Component } from /**/"./Component.js";
 | 
			
		||||
let Toolkit;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                  Window                                   //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Standalone movable dialog
 | 
			
		||||
class Window extends Component {
 | 
			
		||||
    static Component = Component;
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(gui, options) {
 | 
			
		||||
        super(gui, options, {
 | 
			
		||||
            className : "tk tk-window",
 | 
			
		||||
            focusable : true,
 | 
			
		||||
            role      : "dialog",
 | 
			
		||||
            tabStop   : false,
 | 
			
		||||
            tagName   : "div",
 | 
			
		||||
            visibility: true,
 | 
			
		||||
            style     : {
 | 
			
		||||
                position: "absolute"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Configure instance fields
 | 
			
		||||
        this.firstShown = false;
 | 
			
		||||
        this.lastFocus  = null;
 | 
			
		||||
 | 
			
		||||
        // DOM container
 | 
			
		||||
        this.contents = document.createElement("div");
 | 
			
		||||
        this.contents.style.display       = "flex";
 | 
			
		||||
        this.contents.style.flexDirection = "column";
 | 
			
		||||
        this.element.append(this.contents);
 | 
			
		||||
 | 
			
		||||
        // Sizing borders
 | 
			
		||||
        this.borders = {}
 | 
			
		||||
        this.border("n" ); this.border("w" );
 | 
			
		||||
        this.border("e" ); this.border("s" );
 | 
			
		||||
        this.border("nw"); this.border("ne");
 | 
			
		||||
        this.border("sw"); this.border("se");
 | 
			
		||||
 | 
			
		||||
        // Title bar
 | 
			
		||||
        this.titleBar = document.createElement("div");
 | 
			
		||||
        this.titleBar.className     = "tk tk-title";
 | 
			
		||||
        this.titleBar.style.display = "flex";
 | 
			
		||||
        this.contents.append(this.titleBar);
 | 
			
		||||
        this.titleBar.addEventListener(
 | 
			
		||||
            "pointerdown", e=>this.onTitlePointerDown(e));
 | 
			
		||||
        this.titleBar.addEventListener(
 | 
			
		||||
            "pointermove", e=>this.onTitlePointerMove(e));
 | 
			
		||||
        this.titleBar.addEventListener(
 | 
			
		||||
            "pointerup"  , e=>this.onTitlePointerUp  (e));
 | 
			
		||||
 | 
			
		||||
        // Title bar text
 | 
			
		||||
        this.titleText = document.createElement("div");
 | 
			
		||||
        this.titleText.className      = "tk tk-text";
 | 
			
		||||
        this.titleText.id             = Toolkit.id();
 | 
			
		||||
        this.titleText.style.flexGrow = "1";
 | 
			
		||||
        this.titleText.style.position = "relative";
 | 
			
		||||
        this.titleBar.append(this.titleText);
 | 
			
		||||
        this.setAttribute("aria-labelledby", this.titleText.id);
 | 
			
		||||
 | 
			
		||||
        // Close button
 | 
			
		||||
        this.titleClose = document.createElement("div");
 | 
			
		||||
        this.titleClose.className = "tk tk-close";
 | 
			
		||||
        this.titleClose.setAttribute("aria-hidden", "true");
 | 
			
		||||
        this.titleBar.append(this.titleClose);
 | 
			
		||||
        this.titleClose.addEventListener(
 | 
			
		||||
            "pointerdown", e=>this.onClosePointerDown(e));
 | 
			
		||||
        this.titleClose.addEventListener(
 | 
			
		||||
            "pointermove", e=>this.onClosePointerMove(e));
 | 
			
		||||
        this.titleClose.addEventListener(
 | 
			
		||||
            "pointerup"  , e=>this.onClosePointerUp  (e));
 | 
			
		||||
 | 
			
		||||
        // Window client area
 | 
			
		||||
        this.client = document.createElement("div");
 | 
			
		||||
        this.client.className       = "tk tk-client";
 | 
			
		||||
        this.client.style.flexGrow  = "1";
 | 
			
		||||
        this.client.style.minHeight = "0";
 | 
			
		||||
        this.client.style.minWidth  = "0";
 | 
			
		||||
        this.client.style.overflow  = "hidden";
 | 
			
		||||
        this.client.style.position  = "relative";
 | 
			
		||||
        this.contents.append(this.client);
 | 
			
		||||
 | 
			
		||||
        // User agent behavior override
 | 
			
		||||
        let observer = new ResizeObserver(
 | 
			
		||||
            ()=>this.titleBar.style.width =
 | 
			
		||||
                this.client.getBoundingClientRect().width + "px"
 | 
			
		||||
        );
 | 
			
		||||
        observer.observe(this.client);
 | 
			
		||||
 | 
			
		||||
        // Configure element
 | 
			
		||||
        this.setAttribute("aria-modal", "false");
 | 
			
		||||
        this.setBounds(
 | 
			
		||||
            options.x    , options.y,
 | 
			
		||||
            options.width, options.height
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Configure component
 | 
			
		||||
        this.gui.localize(this);
 | 
			
		||||
        this.setTitle       (options.title       );
 | 
			
		||||
        this.setCloseToolTip(options.closeToolTip);
 | 
			
		||||
        this.addEventListener("focus"      , e=>this.onFocus(e), true);
 | 
			
		||||
        this.addEventListener("keydown"    , e=>this.onWindowKeyDown    (e));
 | 
			
		||||
        this.addEventListener("pointerdown", e=>this.onWindowPointerDown(e));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Event Handlers //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Border pointer down
 | 
			
		||||
    onBorderPointerDown(e, edge) {
 | 
			
		||||
        if (e.target.hasPointerCapture(e.pointerId) || e.button != 0)
 | 
			
		||||
            return;
 | 
			
		||||
        e.target.setPointerCapture(e.pointerId);
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        let bndClient  = this.client.getBoundingClientRect();
 | 
			
		||||
        let bndWindow  = this       .getBounds            ();
 | 
			
		||||
        let bndDesktop = this.parent ? this.parent.getBounds() : bndWindow;
 | 
			
		||||
        let coords     = Toolkit.screenCoords(e);
 | 
			
		||||
        this.drag = {
 | 
			
		||||
            clickX     : coords.x,
 | 
			
		||||
            clickY     : coords.y,
 | 
			
		||||
            mode       : "resize",
 | 
			
		||||
            pointerId  : e.pointerId,
 | 
			
		||||
            startHeight: bndClient.height,
 | 
			
		||||
            startWidth : bndClient.width,
 | 
			
		||||
            startX     : bndWindow.x - bndDesktop.x,
 | 
			
		||||
            startY     : bndWindow.y - bndDesktop.y,
 | 
			
		||||
            target     : e.target
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Border pointer move
 | 
			
		||||
    onBorderPointerMove(e, edge) {
 | 
			
		||||
        if (!e.target.hasPointerCapture(e.pointerId))
 | 
			
		||||
            return;
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        let bndWindow = this.getBounds();
 | 
			
		||||
        this["resize" + edge.toUpperCase()](
 | 
			
		||||
            Toolkit.screenCoords(e),
 | 
			
		||||
            this.client  .getBoundingClientRect(),
 | 
			
		||||
            this.parent ? this.parent.getBounds() : bndWindow,
 | 
			
		||||
            bndWindow,
 | 
			
		||||
            this.titleBar.getBoundingClientRect()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Border pointer up
 | 
			
		||||
    onBorderPointerUp(e, edge) {
 | 
			
		||||
        if (!e.target.hasPointerCapture(e.pointerId) || e.button != 0)
 | 
			
		||||
            return;
 | 
			
		||||
        e.target.releasePointerCapture(e.pointerId);
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Close pointer down
 | 
			
		||||
    onClosePointerDown(e) {
 | 
			
		||||
        if (this.titleClose.hasPointerCapture(e.pointerId) || e.button != 0)
 | 
			
		||||
            return;
 | 
			
		||||
        this.titleClose.setPointerCapture(e.pointerId);
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        this.titleClose.classList.add("active");
 | 
			
		||||
        this.drag = {
 | 
			
		||||
            mode: "close",
 | 
			
		||||
            x   : e.offsetX,
 | 
			
		||||
            y   : e.offsetY
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Close pointer move
 | 
			
		||||
    onClosePointerMove(e) {
 | 
			
		||||
        if (!this.titleClose.hasPointerCapture(e.pointerId))
 | 
			
		||||
            return;
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        if (Toolkit.isInside(this.titleClose, e))
 | 
			
		||||
            this.titleClose.classList.add("active");
 | 
			
		||||
        else this.titleClose.classList.remove("active");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Close pointer up
 | 
			
		||||
    onClosePointerUp(e) {
 | 
			
		||||
        if (!this.titleClose.hasPointerCapture(e.pointerId) || e.button != 0)
 | 
			
		||||
            return;
 | 
			
		||||
        this.titleClose.releasePointerCapture(e.pointerId);
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        this.titleClose.classList.remove("active");
 | 
			
		||||
        if (Toolkit.isInside(this.titleClose, e))
 | 
			
		||||
            this.element.dispatchEvent(Toolkit.event("close", this));
 | 
			
		||||
        this.drag = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Focus capture
 | 
			
		||||
    onFocus(e) {
 | 
			
		||||
 | 
			
		||||
        // Bring this window to the foreground of its siblings
 | 
			
		||||
        if (!this.contains(e.relatedTarget) && this.parent)
 | 
			
		||||
            this.parent.bringToFront(this);
 | 
			
		||||
 | 
			
		||||
        // The target is not the window itself
 | 
			
		||||
        if (e.target != this.element) {
 | 
			
		||||
            this.lastFocus = e.target;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Select the first focusable child
 | 
			
		||||
        if (this.lastFocus == null)
 | 
			
		||||
            this.lastFocus = Toolkit.listFocusables(this.element)[0] || null;
 | 
			
		||||
 | 
			
		||||
        // Send focus to the most recently focused element
 | 
			
		||||
        if (this.lastFocus)
 | 
			
		||||
            this.lastFocus.focus();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Title pointer down
 | 
			
		||||
    onTitlePointerDown(e) {
 | 
			
		||||
        if (this.titleBar.hasPointerCapture(e.pointerId) || e.button != 0)
 | 
			
		||||
            return;
 | 
			
		||||
        this.titleBar.setPointerCapture(e.pointerId);
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        let bndWindow  = this.getBounds();
 | 
			
		||||
        let bndDesktop = this.parent ? this.parent.getBounds() : bndWindow;
 | 
			
		||||
        let coords     = Toolkit.screenCoords(e);
 | 
			
		||||
        this.drag = {
 | 
			
		||||
            clickX   : coords.x,
 | 
			
		||||
            clickY   : coords.y,
 | 
			
		||||
            mode     : "move",
 | 
			
		||||
            pointerId: e.pointerId,
 | 
			
		||||
            startX   : bndWindow.x - bndDesktop.x,
 | 
			
		||||
            startY   : bndWindow.y - bndDesktop.y
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Title pointer move
 | 
			
		||||
    onTitlePointerMove(e) {
 | 
			
		||||
        if (!this.titleBar.hasPointerCapture(e.pointerId))
 | 
			
		||||
            return;
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        let coords = Toolkit.screenCoords(e);
 | 
			
		||||
        let valid  = this.getValidLocations(
 | 
			
		||||
            this.drag.startX + coords.x - this.drag.clickX,
 | 
			
		||||
            this.drag.startY + coords.y - this.drag.clickY
 | 
			
		||||
        );
 | 
			
		||||
        this.setLocation(valid.x, valid.y);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Title pointer up
 | 
			
		||||
    onTitlePointerUp(e) {
 | 
			
		||||
        if (!this.titleBar.hasPointerCapture(e.pointerId) || e.button != 0)
 | 
			
		||||
            return;
 | 
			
		||||
        this.titleBar.releasePointerCapture(e.pointerId);
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        this.drag = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Window key press
 | 
			
		||||
    onWindowKeyDown(e) {
 | 
			
		||||
 | 
			
		||||
        // Process by key
 | 
			
		||||
        switch (e.key) {
 | 
			
		||||
 | 
			
		||||
            // Undo un-committed bounds modifications
 | 
			
		||||
            case "Escape":
 | 
			
		||||
 | 
			
		||||
                // Not dragging
 | 
			
		||||
                if (this.drag == null)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                // Moving
 | 
			
		||||
                if (this.drag.mode == "move") {
 | 
			
		||||
                    this.titleBar.releasePointerCapture(this.drag.pointerId);
 | 
			
		||||
                    this.setLocation(this.drag.startX, this.drag.startY);
 | 
			
		||||
                    this.drag = null;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Resizing
 | 
			
		||||
                else if (this.drag.mode == "resize") {
 | 
			
		||||
                    this.drag.target
 | 
			
		||||
                        .releasePointerCapture(this.drag.pointerId);
 | 
			
		||||
                    this.setBounds(
 | 
			
		||||
                        this.drag.startX    , this.drag.startY,
 | 
			
		||||
                        this.drag.startWidth, this.drag.startHeight
 | 
			
		||||
                    );
 | 
			
		||||
                    this.drag = null;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            // Transfer focus to another element
 | 
			
		||||
            case "Tab":
 | 
			
		||||
 | 
			
		||||
            default: return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // The event was handled
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Window pointer down
 | 
			
		||||
    onWindowPointerDown(e) {
 | 
			
		||||
        this.focus(e);
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Add a DOM element to this component's element
 | 
			
		||||
    append(child) {
 | 
			
		||||
        let element = child instanceof Element ? child : child.element;
 | 
			
		||||
        this.client.append(element);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Position the window in the center of the parent Desktop
 | 
			
		||||
    center() {
 | 
			
		||||
        if (!this.parent)
 | 
			
		||||
            return;
 | 
			
		||||
        let bndParent = this.parent.getBounds();
 | 
			
		||||
        let bndWindow = this       .getBounds();
 | 
			
		||||
        this.setLocation(
 | 
			
		||||
            Math.max(Math.floor((bndParent.width  - bndWindow.width ) / 2), 0),
 | 
			
		||||
            Math.max(Math.floor((bndParent.height - bndWindow.height) / 2), 0)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Programmatically close the window
 | 
			
		||||
    close() {
 | 
			
		||||
        this.event("close");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add a DOM element to the beginning of this component's children
 | 
			
		||||
    prepend(child) {
 | 
			
		||||
        let element = child instanceof Element ? child : child.element;
 | 
			
		||||
        this.element.prepend(element);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify a new position and size for the window
 | 
			
		||||
    setBounds(x, y, width, height) {
 | 
			
		||||
        this.setSize(width, height);
 | 
			
		||||
        this.setLocation(x, y);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify the over text for the close button
 | 
			
		||||
    setCloseToolTip(key) {
 | 
			
		||||
        this.closeToolTip = key;
 | 
			
		||||
        this.translate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify a new position for the window
 | 
			
		||||
    setLocation(x, y) {
 | 
			
		||||
        Object.assign(this.element.style, {
 | 
			
		||||
            left: Math.round(parseFloat(x) || 0) + "px",
 | 
			
		||||
            top : Math.round(parseFloat(y) || 0) + "px"
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify a new size for the window
 | 
			
		||||
    setSize(width, height) {
 | 
			
		||||
        Object.assign(this.client.style, {
 | 
			
		||||
            width : Math.max(Math.round(parseFloat(width ) || 0, 32)) + "px",
 | 
			
		||||
            height: Math.max(Math.round(parseFloat(height) || 0, 32)) + "px"
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify the window title text
 | 
			
		||||
    setTitle(key) {
 | 
			
		||||
        this.title = key;
 | 
			
		||||
        this.translate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify whether the component is visible
 | 
			
		||||
    setVisible(visible) {
 | 
			
		||||
        super.setVisible(visible);
 | 
			
		||||
        if (!visible || this.firstShown)
 | 
			
		||||
            return;
 | 
			
		||||
        this.firstShown = true;
 | 
			
		||||
        this.event("firstshow", this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Package Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Ensure the window is partially visible within its desktop
 | 
			
		||||
    contain() {
 | 
			
		||||
        let valid = this.getValidLocations();
 | 
			
		||||
        this.setLocation(valid.x, valid.y);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Determine the range of valid window coordinates
 | 
			
		||||
    getValidLocations(x, y) {
 | 
			
		||||
 | 
			
		||||
        // Measure the bounding boxes of the relevant elements
 | 
			
		||||
        let bndClient   = this.client  .getBoundingClientRect();
 | 
			
		||||
        let bndWindow   = this         .getBounds            ();
 | 
			
		||||
        let bndTitleBar = this.titleBar.getBoundingClientRect();
 | 
			
		||||
        let bndDesktop  = this.parent ? this.parent.getBounds() : bndWindow;
 | 
			
		||||
 | 
			
		||||
        // Compute the minimum and maximum valid window coordinates
 | 
			
		||||
        let ret = {
 | 
			
		||||
            maxX: bndDesktop .width  - bndTitleBar.height -
 | 
			
		||||
                  bndTitleBar.x      + bndWindow  .x,
 | 
			
		||||
            maxY: bndDesktop .height - bndClient  .y      +
 | 
			
		||||
                  bndWindow  .y,
 | 
			
		||||
            minX: bndTitleBar.height - bndWindow  .width  +
 | 
			
		||||
                  bndWindow  .right  - bndTitleBar.right,
 | 
			
		||||
            minY: 0
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Compute the effective "best" window coordinates
 | 
			
		||||
        ret.x = Math.max(ret.minX, Math.min(ret.maxX,
 | 
			
		||||
            x === undefined ? bndWindow.x - bndDesktop.x : x));
 | 
			
		||||
        ret.y = Math.max(ret.minY, Math.min(ret.maxY,
 | 
			
		||||
            y === undefined ? bndWindow.y - bndDesktop.y : y));
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update the global Toolkit object
 | 
			
		||||
    static setToolkit(toolkit) {
 | 
			
		||||
        Toolkit = toolkit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Regenerate localized display text
 | 
			
		||||
    translate() {
 | 
			
		||||
        if (!this.titleText)
 | 
			
		||||
            return;
 | 
			
		||||
        this.titleText.innerText = this.gui.translate(this.title, this);
 | 
			
		||||
        if (this.closeToolTip)
 | 
			
		||||
            this.titleClose.setAttribute("title",
 | 
			
		||||
                this.gui.translate(this.closeToolTip, this));
 | 
			
		||||
        else this.titleClose.removeAttribute("title");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Private Methods /////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Produce a border element and add it to the window
 | 
			
		||||
    border(edge) {
 | 
			
		||||
        let border = this.borders[edge] = document.createElement("div");
 | 
			
		||||
        border.className      = "tk tk-" + edge;
 | 
			
		||||
        border.style.cursor   = edge + "-resize";
 | 
			
		||||
        border.style.position = "absolute";
 | 
			
		||||
        this.contents.append(border);
 | 
			
		||||
        border.addEventListener(
 | 
			
		||||
            "pointerdown", e=>this.onBorderPointerDown(e, edge));
 | 
			
		||||
        border.addEventListener(
 | 
			
		||||
            "pointermove", e=>this.onBorderPointerMove(e, edge));
 | 
			
		||||
        border.addEventListener(
 | 
			
		||||
            "pointerup"  , e=>this.onBorderPointerUp  (e, edge));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Compute client bounds when resizing on the east border
 | 
			
		||||
    constrainE(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
 | 
			
		||||
        let w = this.drag.startWidth + coords.x - this.drag.clickX;
 | 
			
		||||
        w = Math.max(w, bndTitleBar.height * 4);
 | 
			
		||||
        if (bndClient.x - bndDesktop.x < 0)
 | 
			
		||||
            w = Math.max(w, bndDesktop.x - bndClient.x + bndTitleBar.height);
 | 
			
		||||
        return w;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Compute client bounds when resizing on the north border
 | 
			
		||||
    constrainN(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
 | 
			
		||||
        let delta = coords.y - this.drag.clickY;
 | 
			
		||||
        let y     = this.drag.startY      + delta;
 | 
			
		||||
        let h     = this.drag.startHeight - delta;
 | 
			
		||||
        let min   = Math.max(0, bndClient.bottom - bndDesktop.bottom);
 | 
			
		||||
        if (h < min) {
 | 
			
		||||
            delta = min - h;
 | 
			
		||||
            h    += delta;
 | 
			
		||||
            y    -= delta;
 | 
			
		||||
        }
 | 
			
		||||
        if (y < 0) {
 | 
			
		||||
            h += y;
 | 
			
		||||
            y  = 0;
 | 
			
		||||
        }
 | 
			
		||||
        return {
 | 
			
		||||
            height: h,
 | 
			
		||||
            y     : y
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Compute client bounds when resizing on the south border
 | 
			
		||||
    constrainS(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
 | 
			
		||||
        return Math.max(0, this.drag.startHeight+coords.y-this.drag.clickY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Compute client bounds when resizing on the west border
 | 
			
		||||
    constrainW(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
 | 
			
		||||
        let delta = coords.x - this.drag.clickX;
 | 
			
		||||
        let x     = this.drag.startX     + delta;
 | 
			
		||||
        let w     = this.drag.startWidth - delta;
 | 
			
		||||
        let min   = bndTitleBar.height * 4;
 | 
			
		||||
        if (bndClient.right - bndDesktop.right > 0) {
 | 
			
		||||
            min = Math.max(min, bndClient.right -
 | 
			
		||||
                bndDesktop.right + bndTitleBar.height);
 | 
			
		||||
        }
 | 
			
		||||
        if (w < min) {
 | 
			
		||||
            delta = min - w;
 | 
			
		||||
            w    += delta;
 | 
			
		||||
            x    -= delta;
 | 
			
		||||
        }
 | 
			
		||||
        return {
 | 
			
		||||
            x    : x,
 | 
			
		||||
            width: w
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Resize on the east border
 | 
			
		||||
    resizeE(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
 | 
			
		||||
        this.setSize(
 | 
			
		||||
            this.constrainE(coords,bndClient,bndDesktop,bndWindow,bndTitleBar),
 | 
			
		||||
            this.drag.startHeight
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Resize on the north border
 | 
			
		||||
    resizeN(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
 | 
			
		||||
        let con = this.constrainN(coords,
 | 
			
		||||
            bndClient, bndDesktop, bndWindow, bndTitleBar);
 | 
			
		||||
        this.setBounds(
 | 
			
		||||
            this.drag.startX    , con.y,
 | 
			
		||||
            this.drag.startWidth, con.height
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Resize on the northeast border
 | 
			
		||||
    resizeNE(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
 | 
			
		||||
        let con = this.constrainN(coords,
 | 
			
		||||
            bndClient, bndDesktop, bndWindow, bndTitleBar);
 | 
			
		||||
        this.setBounds(
 | 
			
		||||
            this.drag.startX, con.y,
 | 
			
		||||
            this.constrainE(coords,bndClient,bndDesktop,bndWindow,bndTitleBar),
 | 
			
		||||
            con.height
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Resize on the northwest border
 | 
			
		||||
    resizeNW(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
 | 
			
		||||
        let conN = this.constrainN(coords,
 | 
			
		||||
            bndClient, bndDesktop, bndWindow, bndTitleBar);
 | 
			
		||||
        let conW = this.constrainW(coords,
 | 
			
		||||
            bndClient, bndDesktop, bndWindow, bndTitleBar);
 | 
			
		||||
        this.setBounds(conW.x, conN.y, conW.width, conN.height);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Resize on the south border
 | 
			
		||||
    resizeS(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
 | 
			
		||||
        this.setSize(
 | 
			
		||||
            this.drag.startWidth,
 | 
			
		||||
            this.constrainS(coords,bndClient,bndDesktop,bndWindow,bndTitleBar),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Resize on the southeast border
 | 
			
		||||
    resizeSE(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
 | 
			
		||||
        this.setSize(
 | 
			
		||||
            this.constrainE(coords,bndClient,bndDesktop,bndWindow,bndTitleBar),
 | 
			
		||||
            this.constrainS(coords,bndClient,bndDesktop,bndWindow,bndTitleBar)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Resize on the southwest border
 | 
			
		||||
    resizeSW(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
 | 
			
		||||
        let con = this.constrainW(coords,
 | 
			
		||||
            bndClient, bndDesktop, bndWindow, bndTitleBar);
 | 
			
		||||
        this.setBounds(
 | 
			
		||||
            con.x    , this.drag.startY,
 | 
			
		||||
            con.width,
 | 
			
		||||
            this.constrainS(coords,bndClient,bndDesktop,bndWindow,bndTitleBar)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Resize on the west border
 | 
			
		||||
    resizeW(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
 | 
			
		||||
        let con = this.constrainW(coords,
 | 
			
		||||
            bndClient, bndDesktop, bndWindow, bndTitleBar);
 | 
			
		||||
        this.setBounds(
 | 
			
		||||
            con.x    , this.drag.startY,
 | 
			
		||||
            con.width, this.drag.startHeight
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
//                                  Desktop                                  //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Parent container for encapsulating groups of Windows
 | 
			
		||||
class Desktop extends Component {
 | 
			
		||||
 | 
			
		||||
    ///////////////////////// Initialization Methods //////////////////////////
 | 
			
		||||
 | 
			
		||||
    constructor(gui, options) {
 | 
			
		||||
        super(gui, options, {
 | 
			
		||||
            className: "tk tk-desktop",
 | 
			
		||||
            role     : "group",
 | 
			
		||||
            tagName  : "div",
 | 
			
		||||
            style    : {
 | 
			
		||||
                position: "relative"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Configure event handlers
 | 
			
		||||
        this.addEventListener("resize", e=>this.onResize(e));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Event Handlers //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Element resized
 | 
			
		||||
    onResize(e) {
 | 
			
		||||
        for (let wnd of this.children)
 | 
			
		||||
            wnd.contain();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ///////////////////////////// Public Methods //////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // Re-order windows to bring a particular one to the foreground
 | 
			
		||||
    bringToFront(wnd) {
 | 
			
		||||
 | 
			
		||||
        // The window is not a child of this Desktop
 | 
			
		||||
        let index = this.children.indexOf(wnd);
 | 
			
		||||
        if (index == -1)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // The window is already in the foreground
 | 
			
		||||
        let afters = this.children.slice(index + 1).map(c=>c.element);
 | 
			
		||||
        if (afters.length == 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Record scroll pane positions
 | 
			
		||||
        let scrolls = [];
 | 
			
		||||
        for (let after of afters)
 | 
			
		||||
        for (let scroll of
 | 
			
		||||
            after.querySelectorAll(".tk-scrollpane > .tk-viewport")) {
 | 
			
		||||
            scrolls.push({
 | 
			
		||||
                element: scroll,
 | 
			
		||||
                left   : scroll.scrollLeft,
 | 
			
		||||
                top    : scroll.scrollTop
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Update window collection
 | 
			
		||||
        wnd.element.before(... this.children.slice(index+1).map(c=>c.element));
 | 
			
		||||
        this.children.splice(index, 1);
 | 
			
		||||
        this.children.push(wnd);
 | 
			
		||||
 | 
			
		||||
        // Restore scroll pane positions
 | 
			
		||||
        for (let scroll of scrolls) {
 | 
			
		||||
            Object.assign(scroll.element, {
 | 
			
		||||
                scrollLeft: scroll.left,
 | 
			
		||||
                scrollTop : scroll.top
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Position a window in the center of the viewable area
 | 
			
		||||
    center(wnd) {
 | 
			
		||||
 | 
			
		||||
        // The window is not a child of the desktop pane
 | 
			
		||||
        if (this.children.indexOf(wnd) == -1)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        let bndDesktop = this.getBounds();
 | 
			
		||||
        let bndWindow  = wnd .getBounds();
 | 
			
		||||
        wnd.setLocation(
 | 
			
		||||
            Math.max(0, Math.round((bndDesktop.width - bndWindow.width) / 2)),
 | 
			
		||||
            Math.max(0, Math.round((bndDesktop.height-bndWindow.height) / 2))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export { Desktop, Window };
 | 
			
		||||
							
								
								
									
										211
									
								
								core/bus.c
								
								
								
								
							
							
						
						
									
										211
									
								
								core/bus.c
								
								
								
								
							| 
						 | 
				
			
			@ -3,154 +3,121 @@
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/********************************* Constants *********************************/
 | 
			
		||||
/***************************** Utility Functions *****************************/
 | 
			
		||||
 | 
			
		||||
/* Memory access address masks by data type */
 | 
			
		||||
static const uint32_t TYPE_MASKS[] = {
 | 
			
		||||
    0x07FFFFFF, /* S8  */
 | 
			
		||||
    0x07FFFFFF, /* U8  */
 | 
			
		||||
    0x07FFFFFE, /* S16 */
 | 
			
		||||
    0x07FFFFFE, /* U16 */
 | 
			
		||||
    0x07FFFFFC  /* S32 */
 | 
			
		||||
};
 | 
			
		||||
/* Read a data unit from a memory buffer */
 | 
			
		||||
static int32_t busReadBuffer(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 */
 | 
			
		||||
    #else
 | 
			
		||||
        switch (type) {
 | 
			
		||||
            case VB_S8 : return (int8_t) *mem;
 | 
			
		||||
            case VB_U8 : return          *mem;
 | 
			
		||||
            case VB_S16: return (int16_t ) (int8_t) mem[1] <<  8 | mem[0];
 | 
			
		||||
            case VB_U16: return (uint16_t)          mem[1] <<  8 | mem[0];
 | 
			
		||||
        }
 | 
			
		||||
        return (int32_t ) mem[3] << 24 | (uint32_t) mem[2] << 16 |
 | 
			
		||||
               (uint32_t) mem[1] <<  8 |            mem[0];
 | 
			
		||||
    #endif
 | 
			
		||||
 | 
			
		||||
/*************************** Sub-Module Functions ****************************/
 | 
			
		||||
 | 
			
		||||
/* Read a typed value from a buffer in host memory */
 | 
			
		||||
static int32_t busReadBuffer(uint8_t *data, int type) {
 | 
			
		||||
 | 
			
		||||
    /* Processing by data type */
 | 
			
		||||
    switch (type) {
 | 
			
		||||
 | 
			
		||||
        /* Generic implementation */
 | 
			
		||||
        #ifndef VB_LITTLE_ENDIAN
 | 
			
		||||
            case VB_S8 : return ((int8_t *)data)[0];
 | 
			
		||||
            case VB_U8 : return            data [0];
 | 
			
		||||
            case VB_S16: return (int32_t) ((int8_t *)data)[1] << 8 | data[0];
 | 
			
		||||
            case VB_U16: return (int32_t)            data [1] << 8 | data[0];
 | 
			
		||||
            case VB_S32: return
 | 
			
		||||
                (int32_t) data[3] << 24 | (int32_t) data[2] << 16 |
 | 
			
		||||
                (int32_t) data[1] <<  8 |           data[0];
 | 
			
		||||
 | 
			
		||||
        /* Little-endian host */
 | 
			
		||||
        #else
 | 
			
		||||
            case VB_S8 : return *(int8_t   *) data;
 | 
			
		||||
            case VB_U8 : return *             data;
 | 
			
		||||
            case VB_S16: return *(int16_t  *) data;
 | 
			
		||||
            case VB_U16: return *(uint16_t *) data;
 | 
			
		||||
            case VB_S32: return *(int32_t  *) data;
 | 
			
		||||
        #endif
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 0; /* Unreachable */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Write a typed value to a buffer in host memory */
 | 
			
		||||
static void busWriteBuffer(uint8_t *data, int type, int32_t value) {
 | 
			
		||||
/* Write a data unit to a memory buffer */
 | 
			
		||||
static void busWriteBuffer(uint8_t *mem, int type, int32_t value) {
 | 
			
		||||
 | 
			
		||||
    /* Processing by data type */
 | 
			
		||||
    switch (type) {
 | 
			
		||||
    /* 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 */
 | 
			
		||||
        #ifndef VB_LITTLE_ENDIAN
 | 
			
		||||
            case VB_S32: data[3] = value >> 24;
 | 
			
		||||
                         data[2] = value >> 16; /* Fallthrough */
 | 
			
		||||
            case VB_S16:                        /* Fallthrough */
 | 
			
		||||
            case VB_U16: data[1] = value >>  8; /* Fallthrough */
 | 
			
		||||
            case VB_S8 :                        /* Fallthrough */
 | 
			
		||||
            case VB_U8 : data[0] = value;
 | 
			
		||||
 | 
			
		||||
        /* Little-endian host */
 | 
			
		||||
        #else
 | 
			
		||||
            case VB_S8 : /* Fallthrough */
 | 
			
		||||
            case VB_U8 : *             data = value; return;
 | 
			
		||||
            case VB_S16: /* Fallthrough */
 | 
			
		||||
            case VB_U16: *(uint16_t *) data = value; return;
 | 
			
		||||
            case VB_S32: *(int32_t  *) data = value; return;
 | 
			
		||||
        #endif
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    /* Generic implementation */
 | 
			
		||||
    #else
 | 
			
		||||
        switch (type) {
 | 
			
		||||
            case VB_S32:
 | 
			
		||||
                mem[3] = value >> 24;
 | 
			
		||||
                mem[2] = value >> 16;
 | 
			
		||||
                /* Fallthrough */
 | 
			
		||||
            case VB_S16:
 | 
			
		||||
            case VB_U16:
 | 
			
		||||
                mem[1] = value >>  8;
 | 
			
		||||
        }
 | 
			
		||||
        mem[0] = value;
 | 
			
		||||
    #endif
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/***************************** Library Functions *****************************/
 | 
			
		||||
/***************************** Module Functions ******************************/
 | 
			
		||||
 | 
			
		||||
/* Read a typed value from the simulation bus */
 | 
			
		||||
static void busRead(VB *sim, uint32_t address, int type, int32_t *value) {
 | 
			
		||||
/* Read a data unit from the bus */
 | 
			
		||||
static int32_t busRead(VB *sim, uint32_t address, int type, int debug) {
 | 
			
		||||
 | 
			
		||||
    /* Working variables */
 | 
			
		||||
    address &= TYPE_MASKS[type];
 | 
			
		||||
    *value   = 0;
 | 
			
		||||
    /* Force address alignment */
 | 
			
		||||
    address &= ~((uint32_t) TYPE_SIZES[type] - 1);
 | 
			
		||||
 | 
			
		||||
    /* Process by address range */
 | 
			
		||||
    switch (address >> 24) {
 | 
			
		||||
        case 0: break; /* VIP */
 | 
			
		||||
        case 1: break; /* VSU */
 | 
			
		||||
        case 2: break; /* Misc. I/O */
 | 
			
		||||
        case 3: break; /* Unmapped */
 | 
			
		||||
        case 4: break; /* Game Pak expansion */
 | 
			
		||||
 | 
			
		||||
        case 5: /* WRAM */
 | 
			
		||||
            *value = busReadBuffer(&sim->wram[address & 0x0000FFFF], type);
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case 6: /* Game Pak RAM */
 | 
			
		||||
            if (sim->cart.ram != NULL) {
 | 
			
		||||
                *value = busReadBuffer(
 | 
			
		||||
                    &sim->cart.ram[address & sim->cart.ramMask], type);
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case 7: /* Game Pak ROM */
 | 
			
		||||
            if (sim->cart.rom != NULL) {
 | 
			
		||||
                *value = busReadBuffer(
 | 
			
		||||
                    &sim->cart.rom[address & sim->cart.romMask], type);
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
    switch (address >> 24 & 7) {
 | 
			
		||||
        case 0 : return 0; /* VIP */
 | 
			
		||||
        case 1 : return 0 * debug; /* VSU */
 | 
			
		||||
        case 2 : return 0; /* Miscellaneous hardware */
 | 
			
		||||
        case 3 : return 0; /* Unmapped */
 | 
			
		||||
        case 4 : return 0; /* Game pak expansion */
 | 
			
		||||
        case 5 : return /* WRAM */
 | 
			
		||||
            busReadBuffer(&sim->wram[address & 0xFFFF], type);
 | 
			
		||||
        case 6 : return sim->cart.sram == NULL ? 0 : /* Game pak RAM */
 | 
			
		||||
            busReadBuffer(&sim->cart.sram
 | 
			
		||||
                [address & (sim->cart.sramSize - 1)], type);
 | 
			
		||||
        default: return sim->cart.rom  == NULL ? 0 : /* Game pak ROM */
 | 
			
		||||
            busReadBuffer(&sim->cart.rom
 | 
			
		||||
                [address & (sim->cart.romSize  - 1)], type);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Write a typed value to the simulation bus */
 | 
			
		||||
static void busWrite(VB*sim,uint32_t address,int type,int32_t value,int debug){
 | 
			
		||||
/* Write a data unit to the bus */
 | 
			
		||||
static void busWrite(
 | 
			
		||||
    VB *sim, uint32_t address, int type, uint32_t value, int debug) {
 | 
			
		||||
 | 
			
		||||
    /* Working variables */
 | 
			
		||||
    address &= TYPE_MASKS[type];
 | 
			
		||||
    /* Force address alignment */
 | 
			
		||||
    address &= ~((uint32_t) TYPE_SIZES[type] - 1);
 | 
			
		||||
 | 
			
		||||
    /* Process by address range */
 | 
			
		||||
    switch (address >> 24) {
 | 
			
		||||
        case 0: break; /* VIP */
 | 
			
		||||
        case 1: break; /* VSU */
 | 
			
		||||
        case 2: break; /* Misc. I/O */
 | 
			
		||||
        case 3: break; /* Unmapped */
 | 
			
		||||
        case 4: break; /* Game Pak expansion */
 | 
			
		||||
 | 
			
		||||
        case 5: /* WRAM */
 | 
			
		||||
            busWriteBuffer(&sim->wram[address & 0x0000FFFF], type, value);
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case 6: /* Game Pak RAM */
 | 
			
		||||
            if (sim->cart.ram != NULL) {
 | 
			
		||||
                busWriteBuffer(
 | 
			
		||||
                    &sim->cart.ram[address & sim->cart.ramMask], type, value);
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case 7: /* Game Pak ROM */
 | 
			
		||||
            if (debug && sim->cart.rom != NULL) {
 | 
			
		||||
                busWriteBuffer(
 | 
			
		||||
                    &sim->cart.rom[address & sim->cart.romMask], type, value);
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
    switch (address >> 24 & 7) {
 | 
			
		||||
        case 0 : return; /* VIP */
 | 
			
		||||
        case 1 : return; /* VSU */
 | 
			
		||||
        case 2 : return; /* Miscellaneous hardware */
 | 
			
		||||
        case 3 : return; /* Unmapped */
 | 
			
		||||
        case 4 : return; /* Game pak expansion */
 | 
			
		||||
        case 5 : /* WRAM */
 | 
			
		||||
            busWriteBuffer(&sim->wram[address & 0xFFFF], type, value);
 | 
			
		||||
            return;
 | 
			
		||||
        case 6 : /* Cartridge RAM */
 | 
			
		||||
            if (sim->cart.sram != NULL)
 | 
			
		||||
                busWriteBuffer(&sim->cart.sram
 | 
			
		||||
                    [address & (sim->cart.sramSize - 1)], type, value);
 | 
			
		||||
            return;
 | 
			
		||||
        default: /* Cartridge ROM */
 | 
			
		||||
            if (debug && sim->cart.rom != NULL)
 | 
			
		||||
                busWriteBuffer(&sim->cart.rom
 | 
			
		||||
                    [address & (sim->cart.romSize - 1)], type, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif /* VBAPI */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										3272
									
								
								core/cpu.c
								
								
								
								
							
							
						
						
									
										3272
									
								
								core/cpu.c
								
								
								
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										553
									
								
								core/vb.c
								
								
								
								
							
							
						
						
									
										553
									
								
								core/vb.c
								
								
								
								
							| 
						 | 
				
			
			@ -1,138 +1,49 @@
 | 
			
		|||
#ifndef VBAPI
 | 
			
		||||
#define VBAPI
 | 
			
		||||
#ifdef VB_EXPORT
 | 
			
		||||
    #define VBAPI VB_EXPORT
 | 
			
		||||
#else
 | 
			
		||||
    #define VBAPI
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/* Header includes */
 | 
			
		||||
#include <float.h>
 | 
			
		||||
#include <vb.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*********************************** Types ***********************************/
 | 
			
		||||
/********************************* Constants *********************************/
 | 
			
		||||
 | 
			
		||||
/* Simulation state */
 | 
			
		||||
struct VB {
 | 
			
		||||
 | 
			
		||||
    /* Game Pak */
 | 
			
		||||
    struct {
 | 
			
		||||
        uint8_t *ram;     /* Save RAM */
 | 
			
		||||
        uint8_t *rom;     /* Program ROM */
 | 
			
		||||
        uint32_t ramMask; /* Size of SRAM - 1 */
 | 
			
		||||
        uint32_t romMask; /* Size of ROM - 1 */
 | 
			
		||||
    } cart;
 | 
			
		||||
 | 
			
		||||
    /* CPU */
 | 
			
		||||
    struct {
 | 
			
		||||
 | 
			
		||||
        /* Cache Control Word */
 | 
			
		||||
        struct {
 | 
			
		||||
            uint8_t ice; /* Instruction Cache Enable */
 | 
			
		||||
        } chcw;
 | 
			
		||||
 | 
			
		||||
        /* Exception Cause Register */
 | 
			
		||||
        struct {
 | 
			
		||||
            uint16_t eicc; /* Exception/Interrupt Cause Code */
 | 
			
		||||
            uint16_t fecc; /* Fatal Error Cause Code */
 | 
			
		||||
        } ecr;
 | 
			
		||||
 | 
			
		||||
        /* Program Status Word */
 | 
			
		||||
        struct {
 | 
			
		||||
            uint8_t ae;  /* Address Trap Enable */
 | 
			
		||||
            uint8_t cy;  /* Carry */
 | 
			
		||||
            uint8_t ep;  /* Exception Pending */
 | 
			
		||||
            uint8_t fiv; /* Floating Invalid */
 | 
			
		||||
            uint8_t fov; /* Floating Overflow */
 | 
			
		||||
            uint8_t fpr; /* Floading Precision */
 | 
			
		||||
            uint8_t fro; /* Floating Reserved Operand */
 | 
			
		||||
            uint8_t fud; /* Floading Underflow */
 | 
			
		||||
            uint8_t fzd; /* Floating Zero Divide */
 | 
			
		||||
            uint8_t i;   /* Interrupt Level */
 | 
			
		||||
            uint8_t id;  /* Interrupt Disable */
 | 
			
		||||
            uint8_t np;  /* NMI Pending */
 | 
			
		||||
            uint8_t ov;  /* Overflow */
 | 
			
		||||
            uint8_t s;   /* Sign */
 | 
			
		||||
            uint8_t z;   /* Zero */
 | 
			
		||||
        } psw;
 | 
			
		||||
 | 
			
		||||
        /* Other registers */
 | 
			
		||||
        uint32_t adtre;       /* Address Trap Register for Execution */
 | 
			
		||||
        uint32_t eipc;        /* Exception/Interrupt PC */
 | 
			
		||||
        uint32_t eipsw;       /* Exception/Interrupt PSW */
 | 
			
		||||
        uint32_t fepc;        /* Fatal Error PC */
 | 
			
		||||
        uint32_t fepsw;       /* Fatal Error PSW */
 | 
			
		||||
        uint32_t pc;          /* Program Counter */
 | 
			
		||||
        int32_t  program[32]; /* Program registers */
 | 
			
		||||
        uint32_t sr29;        /* System register 29 */
 | 
			
		||||
        uint32_t sr31;        /* System register 31 */
 | 
			
		||||
 | 
			
		||||
        /* Working data */
 | 
			
		||||
        union {
 | 
			
		||||
            struct {
 | 
			
		||||
                uint32_t dest;
 | 
			
		||||
                uint64_t src;
 | 
			
		||||
            } bs;   /* Arithmetic bit strings */
 | 
			
		||||
            struct {
 | 
			
		||||
                uint32_t address;
 | 
			
		||||
                int32_t  value;
 | 
			
		||||
            } data; /* Data accesses */
 | 
			
		||||
        } aux;
 | 
			
		||||
 | 
			
		||||
        /* Other state */
 | 
			
		||||
        uint32_t clocks;    /* Master clocks to wait */
 | 
			
		||||
        uint16_t code[2];   /* Instruction code units */
 | 
			
		||||
        uint16_t exception; /* Exception cause code */
 | 
			
		||||
        int      halt;      /* CPU is halting */
 | 
			
		||||
        uint16_t irq;       /* Interrupt request lines */
 | 
			
		||||
        int      length;    /* Instruction code length */
 | 
			
		||||
        uint32_t nextPC;    /* Address of next instruction */
 | 
			
		||||
        int      operation; /* Current operation ID */
 | 
			
		||||
        int      step;      /* Operation sub-task ID */
 | 
			
		||||
    } cpu;
 | 
			
		||||
 | 
			
		||||
    /* Other system state */
 | 
			
		||||
    uint8_t wram[0x10000]; /* System RAM */
 | 
			
		||||
 | 
			
		||||
    /* Application data */
 | 
			
		||||
    vbOnException onException; /* CPU exception */
 | 
			
		||||
    vbOnExecute   onExecute;   /* CPU instruction execute */
 | 
			
		||||
    vbOnFetch     onFetch;     /* CPU instruction fetch */
 | 
			
		||||
    vbOnRead      onRead;      /* CPU instruction read */
 | 
			
		||||
    vbOnWrite     onWrite;     /* CPU instruction write */
 | 
			
		||||
    void         *tag;         /* User data */
 | 
			
		||||
};
 | 
			
		||||
/* Type sizes */
 | 
			
		||||
static const uint8_t TYPE_SIZES[] = { 1, 1, 2, 2, 4 };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/***************************** Library Functions *****************************/
 | 
			
		||||
/********************************** Macros ***********************************/
 | 
			
		||||
 | 
			
		||||
/* Sign-extend an integer of variable width */
 | 
			
		||||
static int32_t SignExtend(int32_t value, int32_t bits) {
 | 
			
		||||
    #ifndef VB_SIGNED_PROPAGATE
 | 
			
		||||
        value &= ~((uint32_t) 0xFFFFFFFF << bits);
 | 
			
		||||
        bits   = (int32_t) 1 << (bits - (int32_t) 1);
 | 
			
		||||
        return (value ^ bits) - bits;
 | 
			
		||||
    #else
 | 
			
		||||
        return value << (32 - bits) >> (32 - bits);
 | 
			
		||||
    #endif
 | 
			
		||||
}
 | 
			
		||||
/* Sign-extend a value of some number of bits to 32 bits */
 | 
			
		||||
#define SignExtend(v,b) \
 | 
			
		||||
    ((v) | (((v) & (1 << ((b) - 1))) ? (uint32_t) 0xFFFFFFFF << (b) : 0))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/******************************** Sub-Modules ********************************/
 | 
			
		||||
 | 
			
		||||
/*************************** Subsystem Components ****************************/
 | 
			
		||||
 | 
			
		||||
/* Component includes */
 | 
			
		||||
#include "bus.c"
 | 
			
		||||
#include "cpu.c"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/***************************** Library Functions *****************************/
 | 
			
		||||
/***************************** Module Functions ******************************/
 | 
			
		||||
 | 
			
		||||
/* Process a simulation for a given number of clocks */
 | 
			
		||||
/* Process a simulation for some number of clocks */
 | 
			
		||||
static int sysEmulate(VB *sim, uint32_t clocks) {
 | 
			
		||||
    return
 | 
			
		||||
        cpuEmulate(sim, clocks)
 | 
			
		||||
    ;
 | 
			
		||||
    int broke;
 | 
			
		||||
    broke = cpuEmulate(sim, clocks);
 | 
			
		||||
    return broke;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Determine how many clocks are guaranteed to process */
 | 
			
		||||
/* Determine the number of clocks before a break condititon could occur */
 | 
			
		||||
static uint32_t sysUntil(VB *sim, uint32_t clocks) {
 | 
			
		||||
    clocks = cpuUntil(sim, clocks);
 | 
			
		||||
    return clocks;
 | 
			
		||||
| 
						 | 
				
			
			@ -140,252 +51,258 @@ static uint32_t sysUntil(VB *sim, uint32_t clocks) {
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/******************************* API Commands ********************************/
 | 
			
		||||
/******************************* API Functions *******************************/
 | 
			
		||||
 | 
			
		||||
/* Associate two simulations as peers, or remove an association */
 | 
			
		||||
void vbConnect(VB *sim1, VB *sim2) {
 | 
			
		||||
 | 
			
		||||
    /* Disconnect */
 | 
			
		||||
    if (sim2 == NULL) {
 | 
			
		||||
        if (sim1->peer != NULL)
 | 
			
		||||
            sim1->peer->peer = NULL;
 | 
			
		||||
        sim1->peer = NULL;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Disconnect any existing link associations */
 | 
			
		||||
    if (sim1->peer != NULL && sim1->peer != sim2)
 | 
			
		||||
        sim1->peer->peer = NULL;
 | 
			
		||||
    if (sim2->peer != NULL && sim2->peer != sim1)
 | 
			
		||||
        sim2->peer->peer = NULL;
 | 
			
		||||
 | 
			
		||||
    /* Link the two simulations */
 | 
			
		||||
    sim1->peer = sim2;
 | 
			
		||||
    sim2->peer = sim1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Process one simulation */
 | 
			
		||||
VBAPI int vbEmulate(VB *sim, uint32_t *clocks) {
 | 
			
		||||
    int      brk;   /* A callback requested a break */
 | 
			
		||||
    uint32_t until; /* Clocks guaranteed to process */
 | 
			
		||||
    while (*clocks != 0) {
 | 
			
		||||
        until    = sysUntil(sim, *clocks);
 | 
			
		||||
        brk      = sysEmulate(sim, until);
 | 
			
		||||
        *clocks -= until;
 | 
			
		||||
        if (brk)
 | 
			
		||||
            return brk; /* TODO: return 1 */
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
int vbEmulate(VB *sim, uint32_t *clocks) {
 | 
			
		||||
    int      broke; /* The simulation requested an application break */
 | 
			
		||||
    uint32_t until; /* Maximum clocks before a break could happen */
 | 
			
		||||
 | 
			
		||||
    /* Process the simulation until a break condition occurs */
 | 
			
		||||
    do {
 | 
			
		||||
        until   = *clocks;
 | 
			
		||||
        until   = sysUntil  (sim, until);
 | 
			
		||||
        broke   = sysEmulate(sim, until);
 | 
			
		||||
       *clocks -= until;
 | 
			
		||||
    } while (!broke && *clocks > 0);
 | 
			
		||||
 | 
			
		||||
    return broke;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Process multiple simulations */
 | 
			
		||||
VBAPI int vbEmulateEx(VB **sims, int count, uint32_t *clocks) {
 | 
			
		||||
    int      brk;   /* A callback requested a break */
 | 
			
		||||
    uint32_t until; /* Clocks guaranteed to process */
 | 
			
		||||
int vbEmulateMulti(VB **sims, int count, uint32_t *clocks) {
 | 
			
		||||
    int      broke; /* The simulation requested an application break */
 | 
			
		||||
    uint32_t until; /* Maximum clocks before a break could happen */
 | 
			
		||||
    int      x;     /* Iterator */
 | 
			
		||||
    while (*clocks != 0) {
 | 
			
		||||
 | 
			
		||||
    /* Process simulations until a break condition occurs */
 | 
			
		||||
    do {
 | 
			
		||||
        broke = 0;
 | 
			
		||||
        until = *clocks;
 | 
			
		||||
        for (x = count - 1; x >= 0; x--)
 | 
			
		||||
            until = sysUntil(sims[x], until);
 | 
			
		||||
 | 
			
		||||
        brk = 0;
 | 
			
		||||
        for (x = count - 1; x >= 0; x--)
 | 
			
		||||
            brk |= sysEmulate(sims[x], until);
 | 
			
		||||
 | 
			
		||||
        for (x = 0; x < count; x++)
 | 
			
		||||
            until  = sysUntil  (sims[x], until);
 | 
			
		||||
        for (x = 0; x < count; x++)
 | 
			
		||||
            broke |= sysEmulate(sims[x], until);
 | 
			
		||||
        *clocks -= until;
 | 
			
		||||
        if (brk)
 | 
			
		||||
            return brk; /* TODO: return 1 */
 | 
			
		||||
    } while (!broke && *clocks > 0);
 | 
			
		||||
 | 
			
		||||
    return broke;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Retrieve a current breakpoint callback */
 | 
			
		||||
void* vbGetCallback(VB *sim, int type) {
 | 
			
		||||
    void **field; /* Pointer to field within simulation */
 | 
			
		||||
 | 
			
		||||
    /* Select the field to update */
 | 
			
		||||
    switch (type) {
 | 
			
		||||
        case VB_ONEXCEPTION: field = (void *) &sim->onException; break;
 | 
			
		||||
        case VB_ONEXECUTE  : field = (void *) &sim->onExecute  ; break;
 | 
			
		||||
        case VB_ONFETCH    : field = (void *) &sim->onFetch    ; break;
 | 
			
		||||
        case VB_ONREAD     : field = (void *) &sim->onRead     ; break;
 | 
			
		||||
        case VB_ONWRITE    : field = (void *) &sim->onWrite    ; break;
 | 
			
		||||
        default: return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Retrieve the simulation field */
 | 
			
		||||
    return *field;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Retrieve the value of PC */
 | 
			
		||||
uint32_t vbGetProgramCounter(VB *sim) {
 | 
			
		||||
    return sim->cpu.pc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Retrieve the value of a program register */
 | 
			
		||||
int32_t vbGetProgramRegister(VB *sim, int id) {
 | 
			
		||||
    return id < 1 || id > 31 ? 0 : sim->cpu.program[id];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Retrieve the ROM buffer */
 | 
			
		||||
void* vbGetROM(VB *sim, uint32_t *size) {
 | 
			
		||||
    if (size != NULL)
 | 
			
		||||
        *size = sim->cart.romSize;
 | 
			
		||||
    return sim->cart.rom;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Retrieve the SRAM buffer */
 | 
			
		||||
void* vbGetSRAM(VB *sim, uint32_t *size) {
 | 
			
		||||
    if (size != NULL)
 | 
			
		||||
        *size = sim->cart.sramSize;
 | 
			
		||||
    return sim->cart.sram;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Retrieve the value of a system register */
 | 
			
		||||
uint32_t vbGetSystemRegister(VB *sim, int id) {
 | 
			
		||||
    switch (id) {
 | 
			
		||||
        case VB_ADTRE: return sim->cpu.adtre;
 | 
			
		||||
        case VB_CHCW : return sim->cpu.chcw.ice << 1;
 | 
			
		||||
        case VB_EIPC : return sim->cpu.eipc;
 | 
			
		||||
        case VB_EIPSW: return sim->cpu.eipsw;
 | 
			
		||||
        case VB_FEPC : return sim->cpu.fepc;
 | 
			
		||||
        case VB_FEPSW: return sim->cpu.fepsw;
 | 
			
		||||
        case VB_PIR  : return 0x00005346;
 | 
			
		||||
        case VB_TKCW : return 0x000000E0;
 | 
			
		||||
        case 29      : return sim->cpu.sr29;
 | 
			
		||||
        case 30      : return 0x00000004;
 | 
			
		||||
        case 31      : return sim->cpu.sr31;
 | 
			
		||||
        case VB_ECR  : return
 | 
			
		||||
            (uint32_t) sim->cpu.ecr.fecc << 16 | sim->cpu.ecr.eicc;
 | 
			
		||||
        case VB_PSW  : return
 | 
			
		||||
            (uint32_t) sim->cpu.psw.i   << 16 |
 | 
			
		||||
            (uint32_t) sim->cpu.psw.np  << 15 |
 | 
			
		||||
            (uint32_t) sim->cpu.psw.ep  << 14 |
 | 
			
		||||
            (uint32_t) sim->cpu.psw.ae  << 13 |
 | 
			
		||||
            (uint32_t) sim->cpu.psw.id  << 12 |
 | 
			
		||||
            (uint32_t) sim->cpu.psw.fro <<  9 |
 | 
			
		||||
            (uint32_t) sim->cpu.psw.fiv <<  8 |
 | 
			
		||||
            (uint32_t) sim->cpu.psw.fzd <<  7 |
 | 
			
		||||
            (uint32_t) sim->cpu.psw.fov <<  6 |
 | 
			
		||||
            (uint32_t) sim->cpu.psw.fud <<  5 |
 | 
			
		||||
            (uint32_t) sim->cpu.psw.fpr <<  4 |
 | 
			
		||||
            (uint32_t) sim->cpu.psw.cy  <<  3 |
 | 
			
		||||
            (uint32_t) sim->cpu.psw.ov  <<  2 |
 | 
			
		||||
            (uint32_t) sim->cpu.psw.s   <<  1 |
 | 
			
		||||
            (uint32_t) sim->cpu.psw.z
 | 
			
		||||
        ;
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Retrieve the game pack RAM buffer */
 | 
			
		||||
VBAPI void* vbGetCartRAM(VB *sim, uint32_t *size) {
 | 
			
		||||
    if (size != NULL)
 | 
			
		||||
        *size = sim->cart.ram == NULL ? 0 : sim->cart.ramMask + 1;
 | 
			
		||||
    return sim->cart.ram;
 | 
			
		||||
}
 | 
			
		||||
/* Prepare a simulation state instance for use */
 | 
			
		||||
void vbInit(VB *sim) {
 | 
			
		||||
 | 
			
		||||
/* Retrieve the game pack ROM buffer */
 | 
			
		||||
VBAPI void* vbGetCartROM(VB *sim, uint32_t *size) {
 | 
			
		||||
    if (size != NULL)
 | 
			
		||||
        *size = sim->cart.rom == NULL ? 0 : sim->cart.romMask + 1;
 | 
			
		||||
    return sim->cart.rom;
 | 
			
		||||
}
 | 
			
		||||
    /* Breakpoint callbacks */
 | 
			
		||||
    sim->onException = NULL;
 | 
			
		||||
    sim->onExecute   = NULL;
 | 
			
		||||
    sim->onFetch     = NULL;
 | 
			
		||||
    sim->onRead      = NULL;
 | 
			
		||||
    sim->onWrite     = NULL;
 | 
			
		||||
 | 
			
		||||
/* Retrieve the exception callback handle */
 | 
			
		||||
VBAPI vbOnException vbGetExceptionCallback(VB *sim) {
 | 
			
		||||
    return sim->onException;
 | 
			
		||||
}
 | 
			
		||||
    /* System */
 | 
			
		||||
    sim->peer = NULL;
 | 
			
		||||
 | 
			
		||||
/* Retrieve the execute callback handle */
 | 
			
		||||
VBAPI vbOnExecute vbGetExecuteCallback(VB *sim) {
 | 
			
		||||
    return sim->onExecute;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Retrieve the fetch callback handle */
 | 
			
		||||
VBAPI vbOnFetch vbGetFetchCallback(VB *sim) {
 | 
			
		||||
    return sim->onFetch;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Retrieve the value of the program counter */
 | 
			
		||||
VBAPI uint32_t vbGetProgramCounter(VB *sim) {
 | 
			
		||||
    return sim->cpu.pc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Retrieve the value in a program register */
 | 
			
		||||
VBAPI int32_t vbGetProgramRegister(VB *sim, int index) {
 | 
			
		||||
    return index < 1 || index > 31 ? 0 : sim->cpu.program[index];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Retrieve the read callback handle */
 | 
			
		||||
VBAPI vbOnRead vbGetReadCallback(VB *sim) {
 | 
			
		||||
    return sim->onRead;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Retrieve the value in a system register */
 | 
			
		||||
VBAPI uint32_t vbGetSystemRegister(VB *sim, int index) {
 | 
			
		||||
    return index < 0 || index > 31 ? 0 : cpuGetSystemRegister(sim, index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Retrieve a simulation's userdata pointer */
 | 
			
		||||
VBAPI void* vbGetUserData(VB *sim) {
 | 
			
		||||
    return sim->tag;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Retrieve the write callback handle */
 | 
			
		||||
VBAPI vbOnWrite vbGetWriteCallback(VB *sim) {
 | 
			
		||||
    return sim->onWrite;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Initialize a simulation instance */
 | 
			
		||||
VBAPI VB* vbInit(VB *sim) {
 | 
			
		||||
    sim->cart.ram      = NULL;
 | 
			
		||||
    /* Cartridge */
 | 
			
		||||
    sim->cart.rom      = NULL;
 | 
			
		||||
    sim->onExecute     = NULL;
 | 
			
		||||
    sim->onFetch       = NULL;
 | 
			
		||||
    sim->onRead        = NULL;
 | 
			
		||||
    sim->onWrite       = NULL;
 | 
			
		||||
    sim->cart.romSize  = 0;
 | 
			
		||||
    sim->cart.sram     = NULL;
 | 
			
		||||
    sim->cart.sramSize = 0;
 | 
			
		||||
 | 
			
		||||
    /* Everything else */
 | 
			
		||||
    vbReset(sim);
 | 
			
		||||
    return sim;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Read a value from the memory bus */
 | 
			
		||||
VBAPI int32_t vbRead(VB *sim, uint32_t address, int type) {
 | 
			
		||||
    int32_t value;
 | 
			
		||||
    if (type < 0 || type > 4)
 | 
			
		||||
        return 0;
 | 
			
		||||
    busRead(sim, address, type, &value);
 | 
			
		||||
    return value;
 | 
			
		||||
/* Read a data unit from the bus */
 | 
			
		||||
int32_t vbRead(VB *sim, uint32_t address, int type, int debug) {
 | 
			
		||||
    return type < 0 || type >= (int) sizeof TYPE_SIZES ? 0 :
 | 
			
		||||
        busRead(sim, address, type, debug);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Simulate a hardware reset */
 | 
			
		||||
VBAPI VB* vbReset(VB *sim) {
 | 
			
		||||
void vbReset(VB *sim) {
 | 
			
		||||
    uint32_t x; /* Iterator */
 | 
			
		||||
 | 
			
		||||
    /* Subsystem components */
 | 
			
		||||
    cpuReset(sim);
 | 
			
		||||
 | 
			
		||||
    /* WRAM (the hardware does not do this) */
 | 
			
		||||
    for (x = 0; x < 0x10000; x++)
 | 
			
		||||
        sim->wram[x] = 0x00;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    /* CPU (normal) */
 | 
			
		||||
    sim->cpu.exception = 0;
 | 
			
		||||
    sim->cpu.halt      = 0;
 | 
			
		||||
    sim->cpu.irq       = 0;
 | 
			
		||||
    sim->cpu.pc        = 0xFFFFFFF0;
 | 
			
		||||
    cpuSetSystemRegister(sim, VB_ECR, 0x0000FFF0, 1);
 | 
			
		||||
    cpuSetSystemRegister(sim, VB_PSW, 0x00008000, 1);
 | 
			
		||||
/* Specify a breakpoint callback */
 | 
			
		||||
void* vbSetCallback(VB *sim, int type, void *callback) {
 | 
			
		||||
    void **field; /* Pointer to field within simulation */
 | 
			
		||||
    void  *prev;  /* Previous value within field */
 | 
			
		||||
 | 
			
		||||
    /* CPU (extra, hardware does not do this) */
 | 
			
		||||
    sim->cpu.adtre = 0x00000000;
 | 
			
		||||
    sim->cpu.eipc  = 0x00000000;
 | 
			
		||||
    sim->cpu.eipsw = 0x00000000;
 | 
			
		||||
    sim->cpu.fepc  = 0x00000000;
 | 
			
		||||
    sim->cpu.fepsw = 0x00000000;
 | 
			
		||||
    sim->cpu.sr29  = 0x00000000;
 | 
			
		||||
    sim->cpu.sr31  = 0x00000000;
 | 
			
		||||
    cpuSetSystemRegister(sim, VB_CHCW, 0x00000000, 1);
 | 
			
		||||
    for (x = 0; x < 32; x++)
 | 
			
		||||
        sim->cpu.program[x] = 0x00000000;
 | 
			
		||||
    /* Select the field to update */
 | 
			
		||||
    switch (type) {
 | 
			
		||||
        case VB_ONEXCEPTION: field = (void *) &sim->onException; break;
 | 
			
		||||
        case VB_ONEXECUTE  : field = (void *) &sim->onExecute  ; break;
 | 
			
		||||
        case VB_ONFETCH    : field = (void *) &sim->onFetch    ; break;
 | 
			
		||||
        case VB_ONREAD     : field = (void *) &sim->onRead     ; break;
 | 
			
		||||
        case VB_ONWRITE    : field = (void *) &sim->onWrite    ; break;
 | 
			
		||||
        return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* CPU (other) */
 | 
			
		||||
    /* Update the simulation field */
 | 
			
		||||
     prev  = *field;
 | 
			
		||||
    *field = callback;
 | 
			
		||||
    return prev;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Specify a new value for PC */
 | 
			
		||||
uint32_t vbSetProgramCounter(VB *sim, uint32_t value) {
 | 
			
		||||
    value             &= 0xFFFFFFFE;
 | 
			
		||||
    sim->cpu.busWait   = 0;
 | 
			
		||||
    sim->cpu.causeCode = 0;
 | 
			
		||||
    sim->cpu.clocks    = 0;
 | 
			
		||||
    sim->cpu.nextPC    = 0xFFFFFFF0;
 | 
			
		||||
    sim->cpu.operation = CPU_FETCH;
 | 
			
		||||
    sim->cpu.step      = 0;
 | 
			
		||||
 | 
			
		||||
    return sim;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Specify a game pak RAM buffer */
 | 
			
		||||
VBAPI int vbSetCartRAM(VB *sim, void *sram, uint32_t size) {
 | 
			
		||||
    if (sram != NULL) {
 | 
			
		||||
        if (size < 16 || size > 0x1000000 || (size & (size - 1)) != 0)
 | 
			
		||||
            return 1;
 | 
			
		||||
        sim->cart.ramMask = size - 1;
 | 
			
		||||
    }
 | 
			
		||||
    sim->cart.ram = sram;
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Specify a game pak ROM buffer */
 | 
			
		||||
VBAPI int vbSetCartROM(VB *sim, void *rom, uint32_t size) {
 | 
			
		||||
    if (rom != NULL) {
 | 
			
		||||
        if (size < 16 || size > 0x1000000 || (size & (size - 1)) != 0)
 | 
			
		||||
            return 1;
 | 
			
		||||
        sim->cart.romMask = size - 1;
 | 
			
		||||
    }
 | 
			
		||||
    sim->cart.rom = rom;
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Specify a new exception callback handle */
 | 
			
		||||
VBAPI vbOnException vbSetExceptionCallback(VB *sim, vbOnException callback) {
 | 
			
		||||
    vbOnException prev = sim->onException;
 | 
			
		||||
    sim->onException = callback;
 | 
			
		||||
    return prev;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Specify a new execute callback handle */
 | 
			
		||||
VBAPI vbOnExecute vbSetExecuteCallback(VB *sim, vbOnExecute callback) {
 | 
			
		||||
    vbOnExecute prev = sim->onExecute;
 | 
			
		||||
    sim->onExecute = callback;
 | 
			
		||||
    return prev;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Specify a new fetch callback handle */
 | 
			
		||||
VBAPI vbOnFetch vbSetFetchCallback(VB *sim, vbOnFetch callback) {
 | 
			
		||||
    vbOnFetch prev = sim->onFetch;
 | 
			
		||||
    sim->onFetch = callback;
 | 
			
		||||
    return prev;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Specify a new value for the program counter */
 | 
			
		||||
VBAPI uint32_t vbSetProgramCounter(VB *sim, uint32_t value) {
 | 
			
		||||
    sim->cpu.operation = CPU_FETCH;
 | 
			
		||||
    sim->cpu.pc        = sim->cpu.nextPC = value & 0xFFFFFFFE;
 | 
			
		||||
    sim->cpu.step      = 0;
 | 
			
		||||
    return sim->cpu.pc;
 | 
			
		||||
    sim->cpu.fetch     = 0;
 | 
			
		||||
    sim->cpu.pc        = value;
 | 
			
		||||
    sim->cpu.state     = CPU_FETCH;
 | 
			
		||||
    sim->cpu.substring = 0;
 | 
			
		||||
    return value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Specify a new value for a program register */
 | 
			
		||||
VBAPI int32_t vbSetProgramRegister(VB *sim, int index, int32_t value) {
 | 
			
		||||
    return index < 1 || index > 31 ? 0 : (sim->cpu.program[index] = value);
 | 
			
		||||
int32_t vbSetProgramRegister(VB *sim, int id, int32_t value) {
 | 
			
		||||
    return id < 1 || id > 31 ? 0 : (sim->cpu.program[id] = value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Specify a new read callback handle */
 | 
			
		||||
VBAPI vbOnRead vbSetReadCallback(VB *sim, vbOnRead callback) {
 | 
			
		||||
    vbOnRead prev = sim->onRead;
 | 
			
		||||
    sim->onRead = callback;
 | 
			
		||||
    return prev;
 | 
			
		||||
/* Supply a ROM buffer */
 | 
			
		||||
int vbSetROM(VB *sim, void *rom, uint32_t size) {
 | 
			
		||||
 | 
			
		||||
    /* Check the buffer size */
 | 
			
		||||
    if (size < 1024 || size > 0x1000000 || ((size - 1) & size) != 0)
 | 
			
		||||
        return 0;
 | 
			
		||||
 | 
			
		||||
    /* Configure the ROM buffer */
 | 
			
		||||
    sim->cart.rom     = (uint8_t *) rom;
 | 
			
		||||
    sim->cart.romSize = size;
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Supply an SRAM buffer */
 | 
			
		||||
int vbSetSRAM(VB *sim, void *sram, uint32_t size) {
 | 
			
		||||
 | 
			
		||||
    /* Check the buffer size */
 | 
			
		||||
    if (size == 0 || ((size - 1) & size) != 0)
 | 
			
		||||
        return 0;
 | 
			
		||||
 | 
			
		||||
    /* Configure the SRAM buffer */
 | 
			
		||||
    sim->cart.sram     = (uint8_t *) sram;
 | 
			
		||||
    sim->cart.sramSize = size;
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Specify a new value for a system register */
 | 
			
		||||
VBAPI uint32_t vbSetSystemRegister(VB *sim, int index, uint32_t value) {
 | 
			
		||||
    return index < 0 || index > 31 ? 0 :
 | 
			
		||||
        cpuSetSystemRegister(sim, index, value, 1);
 | 
			
		||||
uint32_t vbSetSystemRegister(VB *sim, int id, uint32_t value) {
 | 
			
		||||
    return cpuSetSystemRegister(sim, id, value, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Specify a new write callback handle */
 | 
			
		||||
VBAPI vbOnWrite vbSetWriteCallback(VB *sim, vbOnWrite callback) {
 | 
			
		||||
    vbOnWrite prev = sim->onWrite;
 | 
			
		||||
    sim->onWrite = callback;
 | 
			
		||||
    return prev;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Determine the size of a simulation instance */
 | 
			
		||||
VBAPI size_t vbSizeOf() {
 | 
			
		||||
    return sizeof (VB);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Specify a simulation's userdata pointer */
 | 
			
		||||
VBAPI void* vbSetUserData(VB *sim, void *tag) {
 | 
			
		||||
    void *prev = sim->tag;
 | 
			
		||||
    sim->tag = tag;
 | 
			
		||||
    return prev;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Write a value to the memory bus */
 | 
			
		||||
VBAPI int32_t vbWrite(VB *sim, uint32_t address, int type, int32_t value) {
 | 
			
		||||
    if (type < 0 || type > 4)
 | 
			
		||||
        return 0;
 | 
			
		||||
    busWrite(sim, address, type, value, 1);
 | 
			
		||||
    return vbRead(sim, address, type);
 | 
			
		||||
/* Write a data unit to the bus */
 | 
			
		||||
void vbWrite(VB *sim, uint32_t address, int type, int32_t value, int debug) {
 | 
			
		||||
    if (type >= 0 && type < (int32_t) sizeof TYPE_SIZES)
 | 
			
		||||
        busWrite(sim, address, type, value, debug);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										203
									
								
								core/vb.h
								
								
								
								
							
							
						
						
									
										203
									
								
								core/vb.h
								
								
								
								
							| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
#ifndef VB_H_
 | 
			
		||||
#define VB_H_
 | 
			
		||||
#ifndef __VB_H__
 | 
			
		||||
#define __VB_H__
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
extern "C" {
 | 
			
		||||
| 
						 | 
				
			
			@ -17,15 +17,15 @@ extern "C" {
 | 
			
		|||
 | 
			
		||||
/********************************* Constants *********************************/
 | 
			
		||||
 | 
			
		||||
/* Callback IDs */
 | 
			
		||||
#define VB_EXCEPTION 0
 | 
			
		||||
#define VB_EXECUTE   1
 | 
			
		||||
#define VB_FETCH     2
 | 
			
		||||
#define VB_FRAME     3
 | 
			
		||||
#define VB_READ      4
 | 
			
		||||
#define VB_WRITE     5
 | 
			
		||||
/* Memory access types */
 | 
			
		||||
#define VB_CANCEL -1
 | 
			
		||||
#define VB_S8      0
 | 
			
		||||
#define VB_U8      1
 | 
			
		||||
#define VB_S16     2
 | 
			
		||||
#define VB_U16     3
 | 
			
		||||
#define VB_S32     4
 | 
			
		||||
 | 
			
		||||
/* System registers */
 | 
			
		||||
/* System register IDs */
 | 
			
		||||
#define VB_ADTRE 25
 | 
			
		||||
#define VB_CHCW  24
 | 
			
		||||
#define VB_ECR    4
 | 
			
		||||
| 
						 | 
				
			
			@ -37,62 +37,157 @@ extern "C" {
 | 
			
		|||
#define VB_PSW    5
 | 
			
		||||
#define VB_TKCW   7
 | 
			
		||||
 | 
			
		||||
/* Memory access data types */
 | 
			
		||||
#define VB_S8  0
 | 
			
		||||
#define VB_U8  1
 | 
			
		||||
#define VB_S16 2
 | 
			
		||||
#define VB_U16 3
 | 
			
		||||
#define VB_S32 4
 | 
			
		||||
/* PC types */
 | 
			
		||||
#define VB_PC      0
 | 
			
		||||
#define VB_PC_FROM 1
 | 
			
		||||
#define VB_PC_TO   2
 | 
			
		||||
 | 
			
		||||
/* Breakpoint callback types */
 | 
			
		||||
#define VB_ONEXCEPTION 0
 | 
			
		||||
#define VB_ONEXECUTE   1
 | 
			
		||||
#define VB_ONFETCH     2
 | 
			
		||||
#define VB_ONREAD      3
 | 
			
		||||
#define VB_ONWRITE     4
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*********************************** Types ***********************************/
 | 
			
		||||
 | 
			
		||||
/* Simulation state */
 | 
			
		||||
/* Forward references */
 | 
			
		||||
typedef struct VB VB;
 | 
			
		||||
 | 
			
		||||
/* Callbacks */
 | 
			
		||||
typedef int (*vbOnException)(VB *sim, uint16_t *cause);
 | 
			
		||||
typedef int (*vbOnExecute  )(VB *sim, uint32_t address, const uint16_t *code, int length);
 | 
			
		||||
typedef int (*vbOnFetch    )(VB *sim, int fetch, uint32_t address, int32_t *value, uint32_t *cycles);
 | 
			
		||||
typedef int (*vbOnRead     )(VB *sim, uint32_t address, int type, int32_t *value, uint32_t *cycles);
 | 
			
		||||
typedef int (*vbOnWrite    )(VB *sim, uint32_t address, int type, int32_t *value, uint32_t *cycles, int *cancel);
 | 
			
		||||
/* Memory access */
 | 
			
		||||
typedef struct {
 | 
			
		||||
    uint32_t address; /* Bus address being accessed */
 | 
			
		||||
    uint32_t clocks;  /* Number of clocks required to complete */
 | 
			
		||||
    int32_t  value;   /* Value read (callback's responsibility) or to write */
 | 
			
		||||
    int8_t   type;    /* Data type of value */
 | 
			
		||||
} VB_ACCESS;
 | 
			
		||||
 | 
			
		||||
/* CPU instruction */
 | 
			
		||||
typedef struct {
 | 
			
		||||
 | 
			
		||||
    /* Public fields */
 | 
			
		||||
    uint32_t address; /* Bus address */
 | 
			
		||||
    uint16_t bits[2]; /* Binary instruction code */
 | 
			
		||||
    uint8_t  size;    /* Size in bytes of the instruction */
 | 
			
		||||
 | 
			
		||||
    /* Implementation fields */
 | 
			
		||||
    int32_t aux[2]; /* Auxiliary storage for CAXI and bit strings */
 | 
			
		||||
    uint8_t id;     /* Internal operation ID */
 | 
			
		||||
} VB_INSTRUCTION;
 | 
			
		||||
 | 
			
		||||
/* Breakpoint callbacks */
 | 
			
		||||
typedef int (*VB_EXCEPTIONPROC)(VB *, uint16_t);
 | 
			
		||||
typedef int (*VB_EXECUTEPROC  )(VB *, VB_INSTRUCTION *);
 | 
			
		||||
typedef int (*VB_FETCHPROC    )(VB *, int, VB_ACCESS *);
 | 
			
		||||
typedef int (*VB_READPROC     )(VB *, VB_ACCESS *);
 | 
			
		||||
typedef int (*VB_WRITEPROC    )(VB *, VB_ACCESS *);
 | 
			
		||||
 | 
			
		||||
/* Simulation state */
 | 
			
		||||
struct VB {
 | 
			
		||||
 | 
			
		||||
    /* Game pak */
 | 
			
		||||
    struct {
 | 
			
		||||
        uint8_t *rom;      /* Active ROM buffer */
 | 
			
		||||
        uint32_t romSize;  /* Size of ROM data */
 | 
			
		||||
        uint8_t *sram;     /* Active SRAM buffer */
 | 
			
		||||
        uint32_t sramSize; /* Size of SRAM data */
 | 
			
		||||
    } cart;
 | 
			
		||||
 | 
			
		||||
    /* CPU */
 | 
			
		||||
    struct {
 | 
			
		||||
 | 
			
		||||
        /* System registers */
 | 
			
		||||
        uint32_t adtre; /* Address trap register for execution */
 | 
			
		||||
        uint32_t eipc;  /* Exception/Interrupt PC  */
 | 
			
		||||
        uint32_t eipsw; /* Exception/Interrupt PSW */
 | 
			
		||||
        uint32_t fepc;  /* Fatal error PC */
 | 
			
		||||
        uint32_t fepsw; /* Fatal error PSW */
 | 
			
		||||
        uint32_t sr29;  /* Unknown system register */
 | 
			
		||||
        uint32_t sr31;  /* Unknown system register */
 | 
			
		||||
 | 
			
		||||
        /* Cache control word */
 | 
			
		||||
        struct {
 | 
			
		||||
            int8_t ice; /* Instruction cache enable */
 | 
			
		||||
        } chcw;
 | 
			
		||||
 | 
			
		||||
        /* Exception cause register */
 | 
			
		||||
        struct {
 | 
			
		||||
            uint16_t eicc; /* Exception/interrupt cause code */
 | 
			
		||||
            uint16_t fecc; /* Fatal error cause code */
 | 
			
		||||
        } ecr;
 | 
			
		||||
 | 
			
		||||
        /* Program status word */
 | 
			
		||||
        struct {
 | 
			
		||||
            int8_t ae;  /* Address trap enable */
 | 
			
		||||
            int8_t cy;  /* Carry */
 | 
			
		||||
            int8_t ep;  /* Exception pending */
 | 
			
		||||
            int8_t fiv; /* Floating invalid */
 | 
			
		||||
            int8_t fov; /* Floating overflow */
 | 
			
		||||
            int8_t fpr; /* Floating precision */
 | 
			
		||||
            int8_t fro; /* Floating reserved operand */
 | 
			
		||||
            int8_t fud; /* Floating underflow */
 | 
			
		||||
            int8_t fzd; /* Floating zero divide */
 | 
			
		||||
            int8_t i;   /* Interrupt level */
 | 
			
		||||
            int8_t id;  /* Interrupt disable */
 | 
			
		||||
            int8_t np;  /* NMI pending */
 | 
			
		||||
            int8_t ov;  /* Overflow */
 | 
			
		||||
            int8_t s;   /* Sign */
 | 
			
		||||
            int8_t z;   /* Zero */
 | 
			
		||||
        } psw;
 | 
			
		||||
 | 
			
		||||
        /* Other registers */
 | 
			
		||||
        uint32_t pc;          /* Program counter */
 | 
			
		||||
        int32_t  program[32]; /* program registers */
 | 
			
		||||
 | 
			
		||||
        /* Other fields */
 | 
			
		||||
        VB_ACCESS      access;    /* Memory access descriptor */
 | 
			
		||||
        VB_INSTRUCTION inst;      /* Instruction descriptor */
 | 
			
		||||
        uint8_t        irq[5];    /* Interrupt request lines */
 | 
			
		||||
        uint8_t        busWait;   /* Memory access counter */
 | 
			
		||||
        uint16_t       causeCode; /* Exception cause code */
 | 
			
		||||
        uint32_t       clocks;    /* Clocks until next action */
 | 
			
		||||
        int16_t        fetch;     /* Index of fetch unit */
 | 
			
		||||
        uint8_t        state;     /* Operations state */
 | 
			
		||||
        uint8_t        substring; /* A bit string operation is in progress */
 | 
			
		||||
    } cpu;
 | 
			
		||||
 | 
			
		||||
    /* Breakpoint callbacks */
 | 
			
		||||
    VB_EXCEPTIONPROC onException; /* CPU exception */
 | 
			
		||||
    VB_EXECUTEPROC   onExecute;   /* Instruction execute */
 | 
			
		||||
    VB_FETCHPROC     onFetch;     /* Instruction fetch */
 | 
			
		||||
    VB_READPROC      onRead;      /* Memory read */
 | 
			
		||||
    VB_WRITEPROC     onWrite;     /* Memory write */
 | 
			
		||||
 | 
			
		||||
    /* Other fields */
 | 
			
		||||
    VB     *peer;          /* Communications peer */
 | 
			
		||||
    uint8_t wram[0x10000]; /* Main memory */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/******************************* API Commands ********************************/
 | 
			
		||||
 | 
			
		||||
VBAPI int           vbEmulate             (VB *sim, uint32_t *clocks);
 | 
			
		||||
VBAPI int           vbEmulateEx           (VB **sims, int count, uint32_t *clocks);
 | 
			
		||||
VBAPI void*         vbGetCallback         (VB *sim, int id);
 | 
			
		||||
VBAPI void*         vbGetCartRAM          (VB *sim, uint32_t *size);
 | 
			
		||||
VBAPI void*         vbGetCartROM          (VB *sim, uint32_t *size);
 | 
			
		||||
VBAPI vbOnException vbGetExceptionCallback(VB *sim);
 | 
			
		||||
VBAPI vbOnExecute   vbGetExecuteCallback  (VB *sim);
 | 
			
		||||
VBAPI vbOnFetch     vbGetFetchCallback    (VB *sim);
 | 
			
		||||
VBAPI uint32_t      vbGetProgramCounter   (VB *sim);
 | 
			
		||||
VBAPI int32_t       vbGetProgramRegister  (VB *sim, int index);
 | 
			
		||||
VBAPI vbOnRead      vbGetReadCallback     (VB *sim);
 | 
			
		||||
VBAPI uint32_t      vbGetSystemRegister   (VB *sim, int index);
 | 
			
		||||
VBAPI void*         vbGetUserData         (VB *sim);
 | 
			
		||||
VBAPI vbOnWrite     vbGetWriteCallback    (VB *sim);
 | 
			
		||||
VBAPI VB*           vbInit                (VB *sim);
 | 
			
		||||
VBAPI int32_t       vbRead                (VB *sim, uint32_t address, int type);
 | 
			
		||||
VBAPI VB*           vbReset               (VB *sim);
 | 
			
		||||
VBAPI int           vbSetCartRAM          (VB *sim, void *sram, uint32_t size);
 | 
			
		||||
VBAPI int           vbSetCartROM          (VB *sim, void *rom, uint32_t size);
 | 
			
		||||
VBAPI vbOnException vbSetExceptionCallback(VB *sim, vbOnException callback);
 | 
			
		||||
VBAPI vbOnExecute   vbSetExecuteCallback  (VB *sim, vbOnExecute callback);
 | 
			
		||||
VBAPI vbOnFetch     vbSetFetchCallback    (VB *sim, vbOnFetch callback);
 | 
			
		||||
VBAPI uint32_t      vbSetProgramCounter   (VB *sim, uint32_t value);
 | 
			
		||||
VBAPI int32_t       vbSetProgramRegister  (VB *sim, int index, int32_t value);
 | 
			
		||||
VBAPI vbOnRead      vbSetReadCallback     (VB *sim, vbOnRead callback);
 | 
			
		||||
VBAPI uint32_t      vbSetSystemRegister   (VB *sim, int index, uint32_t value);
 | 
			
		||||
VBAPI void*         vbSetUserData         (VB *sim, void *tag);
 | 
			
		||||
VBAPI vbOnWrite     vbSetWriteCallback    (VB *sim, vbOnWrite callback);
 | 
			
		||||
VBAPI size_t        vbSizeOf              ();
 | 
			
		||||
VBAPI int32_t       vbWrite               (VB *sim, uint32_t address, int type, int32_t value);
 | 
			
		||||
VBAPI void     vbConnect            (VB *sim1, VB *sim2);
 | 
			
		||||
VBAPI int      vbEmulate            (VB *sim, uint32_t *clocks);
 | 
			
		||||
VBAPI int      vbEmulateMulti       (VB **sims, int count, uint32_t *clocks);
 | 
			
		||||
VBAPI void*    vbGetCallback        (VB *sim, int type);
 | 
			
		||||
VBAPI uint32_t vbGetProgramCounter  (VB *sim);
 | 
			
		||||
VBAPI int32_t  vbGetProgramRegister (VB *sim, int id);
 | 
			
		||||
VBAPI void*    vbGetROM             (VB *sim, uint32_t *size);
 | 
			
		||||
VBAPI void*    vbGetSRAM            (VB *sim, uint32_t *size);
 | 
			
		||||
VBAPI uint32_t vbGetSystemRegister  (VB *sim, int id);
 | 
			
		||||
VBAPI void     vbInit               (VB *sim);
 | 
			
		||||
VBAPI int32_t  vbRead               (VB *sim, uint32_t address, int type, int debug);
 | 
			
		||||
VBAPI void     vbReset              (VB *sim);
 | 
			
		||||
VBAPI void*    vbSetCallback        (VB *sim, int type, void *callback);
 | 
			
		||||
VBAPI uint32_t vbSetProgramCounter  (VB *sim, uint32_t value);
 | 
			
		||||
VBAPI int32_t  vbSetProgramRegister (VB *sim, int id, int32_t value);
 | 
			
		||||
VBAPI int      vbSetROM             (VB *sim, void *rom, uint32_t size);
 | 
			
		||||
VBAPI int      vbSetSRAM            (VB *sim, void *sram, uint32_t size);
 | 
			
		||||
VBAPI uint32_t vbSetSystemRegister  (VB *sim, int id, uint32_t value);
 | 
			
		||||
VBAPI void     vbWrite              (VB *sim, uint32_t address, int type, int32_t value, int debug);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -100,4 +195,4 @@ VBAPI int32_t       vbWrite               (VB *sim, uint32_t address, int type,
 | 
			
		|||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#endif /* VB_H_ */
 | 
			
		||||
#endif /* __VB_H__ */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
Copyright (C) 2024 Guy Perfect
 | 
			
		||||
Copyright (C) 2022 Guy Perfect
 | 
			
		||||
 | 
			
		||||
This software is provided 'as-is', without any express or implied
 | 
			
		||||
warranty.  In no event will the authors be held liable for any damages
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										37
									
								
								makefile
								
								
								
								
							
							
						
						
									
										37
									
								
								makefile
								
								
								
								
							| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
.PHONY: help
 | 
			
		||||
help:
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Virtual Boy Emulator - October 10, 2024"
 | 
			
		||||
	@echo "Virtual Boy Emulator - April 20, 2022"
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Target build environment is any Debian with the following packages:"
 | 
			
		||||
	@echo "  emscripten"
 | 
			
		||||
| 
						 | 
				
			
			@ -27,35 +27,28 @@ build:
 | 
			
		|||
 | 
			
		||||
.PHONY: bundle
 | 
			
		||||
bundle:
 | 
			
		||||
	@java web/Bundle.java vbemu
 | 
			
		||||
	@java app/Bundle.java vbemu
 | 
			
		||||
 | 
			
		||||
.PHONY: clean
 | 
			
		||||
clean:
 | 
			
		||||
	@rm -f vbemu_*.html web/core/core.wasm
 | 
			
		||||
	@rm -f vbemu_*.html app/core/core.wasm
 | 
			
		||||
 | 
			
		||||
.PHONY: core
 | 
			
		||||
core:
 | 
			
		||||
#	GCC generic
 | 
			
		||||
	@gcc core/vb.c -I core -c -o /dev/null \
 | 
			
		||||
        -Werror -std=c90 -Wall -Wextra -Wpedantic
 | 
			
		||||
#	GCC compilation control
 | 
			
		||||
	@gcc core/vb.c -I core -c -o /dev/null \
 | 
			
		||||
        -Werror -std=c90 -Wall -Wextra -Wpedantic \
 | 
			
		||||
        -D VB_LITTLE_ENDIAN -D VB_SIGNED_PROPAGATE -D VB_DIV_GENERIC
 | 
			
		||||
#	Clang generic
 | 
			
		||||
	@emcc core/vb.c -I core -c -o /dev/null \
 | 
			
		||||
        -Werror -std=c90 -Wall -Wextra -Wpedantic
 | 
			
		||||
#	Clang compilation control
 | 
			
		||||
	@emcc core/vb.c -I core -c -o /dev/null \
 | 
			
		||||
        -Werror -std=c90 -Wall -Wextra -Wpedantic \
 | 
			
		||||
        -D VB_LITTLE_ENDIAN -D VB_SIGNED_PROPAGATE -D VB_DIV_GENERIC
 | 
			
		||||
	@gcc core/vb.c -I core \
 | 
			
		||||
        -fsyntax-only -Wall -Wextra -Werror -Wpedantic -std=c90
 | 
			
		||||
	@gcc core/vb.c -I core -D VB_LITTLEENDIAN \
 | 
			
		||||
        -fsyntax-only -Wall -Wextra -Werror -Wpedantic -std=c90
 | 
			
		||||
	@emcc core/vb.c -I core \
 | 
			
		||||
        -fsyntax-only -Wall -Wextra -Werror -Wpedantic -std=c90
 | 
			
		||||
	@emcc core/vb.c -I core -D VB_LITTLEENDIAN \
 | 
			
		||||
        -fsyntax-only -Wall -Wextra -Werror -Wpedantic -std=c90
 | 
			
		||||
 | 
			
		||||
.PHONY: wasm
 | 
			
		||||
wasm:
 | 
			
		||||
	@emcc -o web/core/core.wasm web/core/wasm.c core/vb.c -Icore \
 | 
			
		||||
        -D VB_LITTLE_ENDIAN -D VB_SIGNED_PROPAGATE \
 | 
			
		||||
        -D "VBAPI=__attribute__((used))" \
 | 
			
		||||
        --no-entry -O2 -flto -s WASM=1 \
 | 
			
		||||
	@emcc -o app/core/core.wasm wasm/wasm.c core/vb.c -Icore \
 | 
			
		||||
        -D VB_LITTLEENDIAN --no-entry -O2 -flto -s WASM=1 \
 | 
			
		||||
        -D "VB_EXPORT=__attribute__((used))" \
 | 
			
		||||
        -s EXPORTED_RUNTIME_METHODS=[] -s ALLOW_MEMORY_GROWTH \
 | 
			
		||||
        -s MAXIMUM_MEMORY=4GB -fno-strict-aliasing
 | 
			
		||||
	@rm -f web/core/*.wasm.tmp*
 | 
			
		||||
	@rm -f app/core/*.wasm.tmp*
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,126 @@
 | 
			
		|||
#include <stddef.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <emscripten/emscripten.h>
 | 
			
		||||
#include <vb.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/////////////////////////////// Module Commands ///////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Allocate and initialize multiple simulations
 | 
			
		||||
EMSCRIPTEN_KEEPALIVE VB** Create(int count) {
 | 
			
		||||
    VB **sims = malloc(count * sizeof (void *));
 | 
			
		||||
    for (int x = 0; x < count; x++)
 | 
			
		||||
        vbReset(sims[x] = malloc(sizeof (VB)));
 | 
			
		||||
    return sims;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Delete a simulation
 | 
			
		||||
EMSCRIPTEN_KEEPALIVE void Destroy(VB *sim) {
 | 
			
		||||
    free(&sim->cart.rom);
 | 
			
		||||
    free(&sim->cart.sram);
 | 
			
		||||
    free(sim);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Proxy for free()
 | 
			
		||||
EMSCRIPTEN_KEEPALIVE void Free(void *ptr) {
 | 
			
		||||
    free(ptr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Proxy for malloc()
 | 
			
		||||
EMSCRIPTEN_KEEPALIVE void* Malloc(int size) {
 | 
			
		||||
    return malloc(size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Determine the size in bytes of a pointer
 | 
			
		||||
EMSCRIPTEN_KEEPALIVE int PointerSize() {
 | 
			
		||||
    return sizeof (void *);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Read multiple bytes from the bus
 | 
			
		||||
EMSCRIPTEN_KEEPALIVE void ReadBuffer(
 | 
			
		||||
    VB* sim, uint8_t *dest, uint32_t address, uint32_t size) {
 | 
			
		||||
    for (; size > 0; address++, size--, dest++)
 | 
			
		||||
        *dest = vbRead(sim, address, VB_U8, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Supply a ROM buffer
 | 
			
		||||
EMSCRIPTEN_KEEPALIVE int SetROM(VB *sim, uint8_t *rom, uint32_t size) {
 | 
			
		||||
    uint8_t *prev = vbGetROM(sim, NULL);
 | 
			
		||||
    int      ret  = vbSetROM(sim, rom, size);
 | 
			
		||||
    if (ret) {
 | 
			
		||||
        free(prev);
 | 
			
		||||
        vbReset(sim);
 | 
			
		||||
    }
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write multiple bytes to the bus
 | 
			
		||||
EMSCRIPTEN_KEEPALIVE void WriteBuffer(
 | 
			
		||||
    VB* sim, uint8_t *src, uint32_t address, uint32_t size) {
 | 
			
		||||
    for (; size > 0; address++, size--, src++)
 | 
			
		||||
        vbWrite(sim, address, VB_U8, *src, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
////////////////////////////// Debugger Commands //////////////////////////////
 | 
			
		||||
 | 
			
		||||
// Attempt to execute until the following instruction
 | 
			
		||||
static uint32_t RunNextPC;
 | 
			
		||||
static int RunNextProcB(VB *sim, int fetch, VB_ACCESS *acc) {
 | 
			
		||||
    if (fetch == 0 && vbGetProgramCounter(sim) == RunNextPC)
 | 
			
		||||
        return 1;
 | 
			
		||||
    acc->value = vbRead(sim, acc->address, acc->type, 0);
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
static int RunNextProcA(VB *sim, VB_INSTRUCTION *inst) {
 | 
			
		||||
    RunNextPC = vbGetProgramCounter(sim) + inst->size;
 | 
			
		||||
    vbSetCallback(sim, VB_ONEXECUTE, NULL);
 | 
			
		||||
    vbSetCallback(sim, VB_ONFETCH, &RunNextProcB);
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
EMSCRIPTEN_KEEPALIVE void RunNext(VB *sim0, VB *sim1) {
 | 
			
		||||
    uint32_t clocks = 400000; // 1/50s
 | 
			
		||||
    VB      *sims[2];
 | 
			
		||||
 | 
			
		||||
    vbSetCallback(sim0, VB_ONEXECUTE, &RunNextProcA);
 | 
			
		||||
 | 
			
		||||
    if (sim1 != NULL) {
 | 
			
		||||
        sims[0] = sim0;
 | 
			
		||||
        sims[1] = sim1;
 | 
			
		||||
        vbEmulateMulti(sims, 2, &clocks);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    else vbEmulate(sim0, &clocks);
 | 
			
		||||
 | 
			
		||||
    vbSetCallback(sim0, VB_ONFETCH, NULL);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute one instruction
 | 
			
		||||
static uint32_t SingleStepPC;
 | 
			
		||||
static int SingleStepProc(VB *sim, int fetch, VB_ACCESS *acc) {
 | 
			
		||||
    if (fetch == 0 && vbGetProgramCounter(sim) != SingleStepPC)
 | 
			
		||||
        return 1;
 | 
			
		||||
    acc->value = vbRead(sim, acc->address, acc->type, 0);
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
EMSCRIPTEN_KEEPALIVE void SingleStep(VB *sim0, VB *sim1) {
 | 
			
		||||
    uint32_t clocks = 400000; // 1/50s
 | 
			
		||||
    VB      *sims[2];
 | 
			
		||||
 | 
			
		||||
    SingleStepPC = vbGetProgramCounter(sim0);
 | 
			
		||||
    vbSetCallback(sim0, VB_ONFETCH, &SingleStepProc);
 | 
			
		||||
 | 
			
		||||
    if (sim1 != NULL) {
 | 
			
		||||
        sims[0] = sim0;
 | 
			
		||||
        sims[1] = sim1;
 | 
			
		||||
        vbEmulateMulti(sims, 2, &clocks);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    else vbEmulate(sim0, &clocks);
 | 
			
		||||
 | 
			
		||||
    vbSetCallback(sim0, VB_ONFETCH, NULL);
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue