Skip to content

Commit

Permalink
feat: enhance emulator UI with responsive canvas, implement full scre…
Browse files Browse the repository at this point in the history
…en mode and use the device pixel ratio for canvas rendering
  • Loading branch information
franciscodelahoz committed Nov 17, 2024
1 parent 432d7ec commit ca38ca4
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 54 deletions.
4 changes: 2 additions & 2 deletions src/html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
</header>
<main class="container">
<section class="emulator-view">
<canvas id="canvas" width="768" height="384"></canvas>
<canvas id="canvas"></canvas>
<div class="rom-controller">
<div id="file">Click to Load ROM File</div>
<div id="file-picker">Click to Load ROM File</div>
<button id="reset-rom-btn" class="default-button">Reset ROM</button>
</div>
</section>
Expand Down
2 changes: 0 additions & 2 deletions src/scripts/constants/chip8.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ export const screenDimensions = {
},
}

export const loresDisplayScale = 12;

export const defaultFontAppearance: EmulatorFontAppearance = 'octo';

export const defaultColorPaletteId: EmulatorColorPalette = 'default';
Expand Down
47 changes: 46 additions & 1 deletion src/scripts/emulator/emulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export class Chip8Emulator {

private emulationLoop: number = 0;

private resizeEventTimeout: number = 0;

constructor(props: Chip8EmulatorProps) {
this.canvas = props.canvas;

Expand All @@ -29,6 +31,7 @@ export class Chip8Emulator {

this.registerCpuEvents();
this.registerKeyboardEvents();
this.registerDisplayEvents();
}

private startEmulatorLoop() {
Expand Down Expand Up @@ -128,7 +131,10 @@ export class Chip8Emulator {
}

private registerCpuEvents() {
this.cpuInstance.addEventListener(Chip8CpuEvents.EXIT, this.handleExitInstruction.bind(this));
this.cpuInstance.addEventListener(
Chip8CpuEvents.EXIT,
this.handleExitInstruction.bind(this),
);
}

private registerKeyboardEvents() {
Expand All @@ -141,6 +147,23 @@ export class Chip8Emulator {
});
}

private registerDisplayEvents() {
window.addEventListener('resize', () => {
if (this.resizeEventTimeout) {
cancelAnimationFrame(this.resizeEventTimeout);
}

this.resizeEventTimeout = requestAnimationFrame(() => {
this.handleResizeCanvas();
});
});

document.addEventListener('keydown', async (e) => {
if (e.key !== '0') return;
this.toggleFullScreenMode();
});
}

public resetRom() {
this.cpuInstance.resetRom();
}
Expand All @@ -153,4 +176,26 @@ export class Chip8Emulator {
this.cpuInstance.setFontAppearance(fontAppearance);
this.cpuInstance.resetRom();
}

public handleResizeCanvas() {
this.displayInstance.calculateDisplayScale();

if (!this.emulationLoop) {
this.displayInstance.clearCanvas();

} else if (!this.cpuInstance.drawingFlag) {
this.displayInstance.render();
}
}

public async toggleFullScreenMode() {
if (document.fullscreenElement) {
await document.exitFullscreen();

} else {
await this.canvas.requestFullscreen();
}

this.handleResizeCanvas();
}
}
59 changes: 38 additions & 21 deletions src/scripts/emulator/interfaces/display.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { loresDisplayScale, screenDimensions } from '../../constants/chip8.constants';
import { screenDimensions } from '../../constants/chip8.constants';
import ColorPalettesManager from '../../database/managers/color-palettes.manager';

export class DisplayInterface {
Expand All @@ -10,18 +10,16 @@ export class DisplayInterface {

private rows: number = screenDimensions.chip8.rows;

private displayScale: number = loresDisplayScale;

private displayBuffers: Array<Array<Array<number>>> = [];

private displayWidth: number;

private displayHeight: number;

private planeColors: string[] = [];

private bitPlane: number = 1;

private xDisplayScale: number = 0;

private yDisplayScale: number = 0;

constructor(htmlCanvas: HTMLCanvasElement | null) {
if (!htmlCanvas) {
throw new Error('Unable to reach the canvas element');
Expand All @@ -35,17 +33,15 @@ export class DisplayInterface {
}

this.context = canvasContext;
this.context.imageSmoothingEnabled = false;

this.displayBuffers = [
this.createDisplayBuffer(),
this.createDisplayBuffer(),
];

this.displayWidth = this.columns * this.displayScale;
this.displayHeight = this.rows * this.displayScale;

this.context.canvas.width = this.displayWidth;
this.context.canvas.height = this.displayHeight;
this.setCanvasAspectRatio();
this.calculateDisplayScale();

this.planeColors = [
...ColorPalettesManager.getCurrentSelectedPalette(),
Expand All @@ -61,6 +57,7 @@ export class DisplayInterface {

for (let y = 0; y < this.rows; y += 1) {
displayBuffer.push([]);

for (let x = 0; x < this.columns; x += 1) {
displayBuffer[y].push(0);
}
Expand All @@ -69,19 +66,39 @@ export class DisplayInterface {
return displayBuffer;
}

setCanvasAspectRatio() {
this.canvas.style.aspectRatio = `${this.columns} / ${this.rows}`;
}

calculateDisplayScale() {
const { width, height } = this.canvas.getBoundingClientRect();

const dpr = window.devicePixelRatio || 1;

const horizontalScale = Math.floor(width / this.columns) * dpr;
const verticalScale = Math.floor(height / this.rows) * dpr;

const optimalScale = Math.max(horizontalScale, verticalScale);

this.xDisplayScale = optimalScale;
this.yDisplayScale = optimalScale;

this.canvas.width = this.xDisplayScale * this.columns;
this.canvas.height = this.yDisplayScale * this.rows;
}

setResolutionMode(hiresMode: boolean) {
if (hiresMode) {
this.columns = screenDimensions.schip.columns;
this.rows = screenDimensions.schip.rows;

} else {
this.columns = screenDimensions.chip8.columns;
this.rows = screenDimensions.chip8.rows;
}

const scaleX = this.canvas.width / this.columns;
const scaleY = this.canvas.height / this.rows;

this.displayScale = Math.min(scaleX, scaleY);
this.setCanvasAspectRatio();
this.calculateDisplayScale();

this.displayBuffers = [
this.createDisplayBuffer(),
Expand Down Expand Up @@ -125,7 +142,7 @@ export class DisplayInterface {

clearCanvas() {
this.context.fillStyle = this.planeColors[0];
this.context.fillRect(0, 0, this.displayWidth, this.displayHeight);
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
}

render() {
Expand All @@ -138,10 +155,10 @@ export class DisplayInterface {
this.context.fillStyle = drawColor;

this.context.fillRect(
x * this.displayScale,
y * this.displayScale,
this.displayScale,
this.displayScale,
x * this.xDisplayScale,
y * this.yDisplayScale,
this.xDisplayScale,
this.yDisplayScale,
);
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/scripts/ui/controls/emulator.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Chip8Emulator } from '../../emulator/emulator';

const input = document.getElementById('file') as HTMLElement | null;
const input = document.getElementById('file-picker') as HTMLElement | null;
const resetRomBtn = document.getElementById('reset-rom-btn') as HTMLElement | null;

async function getFileFromHandle(fileHandle: File | FileSystemFileHandle): Promise<File> {
if (fileHandle instanceof FileSystemFileHandle) {
return await fileHandle.getFile();

} else {
return fileHandle;
}
Expand Down Expand Up @@ -77,9 +78,11 @@ function initializeRomFileInputEventHandlers(emulatorInstance: Chip8Emulator) {
try {
if ('showOpenFilePicker' in window) {
await handleFilePickerEvent(emulatorInstance);

} else {
await handleFilePickerEventFallback(emulatorInstance);
}

} catch(error) {
if ((error as Error)?.name !== 'AbortError') {
return alert(`Error loading the ROM file.`);
Expand All @@ -94,6 +97,7 @@ function initializeResetRomButtonEventHandlers(emulatorInstance: Chip8Emulator)
resetRomBtn.addEventListener('click', () => {
try {
emulatorInstance.resetEmulation();

} catch(error) {
console.error(error);
}
Expand All @@ -104,6 +108,7 @@ function registerFileHandlerLoadRom(emulatorInstance: Chip8Emulator) {
window.launchQueue?.setConsumer(async (launchParams) => {
if (launchParams.files.length) {
const files = launchParams.files as Array<FileSystemFileHandle>;

await handleFileInput(files, emulatorInstance);
setNowPlayingRomName(files[0].name);
}
Expand Down
73 changes: 46 additions & 27 deletions src/styles/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
font-display: swap;
}

* {
box-sizing: border-box;
}

body {
margin: 0;
padding: 0;
Expand Down Expand Up @@ -67,12 +71,30 @@ body {
padding: 0.6rem;
align-items: center;
gap: 1rem;
width: clamp(390px, 60vw, 75vw);
}

.emulator-view canvas {
width: 100%;
border: 1.5px solid #767676;
height: auto;
border: 2px solid #ccc;
box-sizing: border-box;
aspect-ratio: auto;
image-rendering: pixelated;
background-color: #222222;
}

.emulator-view:fullscreen {
margin: 0;
padding: 0;
}

.emulator-view canvas:fullscreen {
width: 100vw;
height: 100vh;
border: none;
top: 0;
left: 0;
}

.rom-controller {
Expand All @@ -82,7 +104,7 @@ body {
width: 100%;
}

#file {
#file-picker {
user-select: none;
cursor: pointer;
font-family: Verdana;
Expand Down Expand Up @@ -161,10 +183,6 @@ body {
height: 1.9rem;
}

.keyboard-container {
padding: 0.6rem;
}

.keyboard {
display: grid;
grid-template-columns: repeat(4, 1fr);
Expand All @@ -173,6 +191,17 @@ body {
touch-action: none;
}

.keyboard-indicator {
position: absolute;
bottom: 0.01em;
right: 0.2em;
font-size: 0.55rem;
background-color: #333333;
font-family: Verdana;
border-radius: 30%;
pointer-events: none;
}

.key {
display: flex;
align-items: center;
Expand All @@ -188,6 +217,17 @@ body {
position: relative;
border: none;
color: #34ff66;
box-sizing: border-box;
}

.key:hover, .key:hover .keyboard-indicator {
background-color: #34ff66;
color: #222222;
}

.key.active, .key.active .keyboard-indicator {
background-color: #198033;
color: #e3e3e3;
}

input[type='range'] {
Expand Down Expand Up @@ -282,27 +322,6 @@ input[type='range']::-moz-range-thumb {
cursor: not-allowed;
}

.keyboard-indicator {
position: absolute;
bottom: 0.01em;
right: 0.2em;
font-size: 0.55rem;
background-color: #333333;
font-family: Verdana;
border-radius: 30%;
pointer-events: none;
}

.key:hover, .key:hover .keyboard-indicator {
background-color: #34ff66;
color: #222222;
}

.key.active, .key.active .keyboard-indicator {
background-color: #198033;
color: #e3e3e3;
}

#close-configurations-button {
font-size: 1.5rem;
transition: 0.5s;
Expand Down

0 comments on commit ca38ca4

Please sign in to comment.