All files / src touchscreen.js

100% Statements 56/56
92% Branches 23/25
100% Functions 12/12
100% Lines 50/50

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108    137x     20x 20x         848x 848x 848x 848x   848x 848x       1309x 1309x 1309x 1309x       216x               180x 180x 180x 180x       1451x 1409x       10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 8x 8x 8x 8x   40x 10x       7x 7x       54x       6x       51x   10x 10x                     31x 31x   7x   3x 3x   51x      
import { Fifo } from "./binary.js";
 
const PollHz = 8; // Made up
 
function doScale(val, scale, margin) {
    val = (val - margin) / (1 - 2 * margin);
    return val * scale;
}
 
export class TouchScreen {
    constructor(scheduler, cyclesPerSecond) {
        this.scheduler = scheduler;
        this.pollCycles = cyclesPerSecond / PollHz;
        this.outBuffer = new Fifo(16);
        this.pollTask = this.scheduler.newTask(() => this.poll());
        /** @type {import("./replay.js").InputLog | null} */
        this.inputLog = null;
        this.reset();
    }
 
    reset() {
        this.mouse = { x: 0, y: 0, button: 0 };
        this.mode = 0;
        this.outBuffer.clear();
        this.pollTask.cancel();
    }
 
    snapshotState() {
        return {
            mode: this.mode,
            outBuffer: this.outBuffer.toArray(),
            pollTaskOffset: this.pollTask.offset(),
        };
    }
 
    restoreState(state) {
        this.mode = state.mode;
        this.outBuffer.clear();
        for (const byte of state.outBuffer) this.store(byte);
        this.pollTask.restoreOffset(state.pollTaskOffset);
    }
 
    tryReceive(rts) {
        if (this.outBuffer.size && rts) return this.outBuffer.get();
        return -1;
    }
 
    doRead() {
        const scaleX = 120,
            marginX = 0.13;
        const scaleY = 100,
            marginY = 0.03;
        const mouse = this.inputLog ? this.inputLog.sample("touch", this.mouse) : this.mouse;
        const scaledX = doScale(mouse.x, scaleX, marginX);
        const scaledY = doScale(1 - mouse.y, scaleY, marginY);
        const toSend = [0x4f, 0x4f, 0x4f, 0x4f];
        const x = Math.min(255, Math.max(0, scaledX)) | 0;
        const y = Math.min(255, Math.max(0, scaledY)) | 0;
        if (mouse.button) {
            toSend[0] = 0x40 | ((x & 0xf0) >>> 4);
            toSend[1] = 0x40 | (x & 0x0f);
            toSend[2] = 0x40 | ((y & 0xf0) >>> 4);
            toSend[3] = 0x40 | (y & 0x0f);
        }
        for (let i = 0; i < 4; ++i) this.store(toSend[i]);
        this.store(".".charCodeAt(0));
    }
 
    poll() {
        this.doRead();
        this.pollTask.reschedule(this.pollCycles);
    }
 
    store(byte) {
        this.outBuffer.put(byte);
    }
 
    onMouse(x, y, button) {
        this.mouse = { x: x, y: y, button: button };
    }
 
    onTransmit(val) {
        switch (String.fromCharCode(val)) {
            case "M":
                this.mode = 0;
                break;
            case "0":
            case "1":
            case "2":
            case "3":
            case "4":
            case "5":
            case "6":
            case "7":
            case "8":
            case "9":
                this.mode = 10 * this.mode + val - "0".charCodeAt(0);
                break;
            case ".":
                break;
            case "?":
                Eif (this.mode === 1) this.doRead();
                break;
        }
        this.pollTask.ensureScheduled(this.mode === 129 || this.mode === 130, this.pollCycles);
    }
}