Skip to content

Instantly share code, notes, and snippets.

@mindfulvector
Created October 13, 2023 15:02
Show Gist options
  • Save mindfulvector/df3b0698aa214712bea892275a13d3a8 to your computer and use it in GitHub Desktop.
Save mindfulvector/df3b0698aa214712bea892275a13d3a8 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body, html {
margin: 0;
overflow: hidden;
background-color: black; /* Set background color to black */
}
canvas {
display: block;
touch-action: none; /* Prevent default touch gestures on canvas */
}
#keyboardTrigger {
position: absolute;
top: -100px;
left: -100px;
opacity: 0;
}
</style>
<title>Terminal Emulator</title>
</head>
<body>
<textarea id="keyboardTrigger" autocapitalize="none"></textarea>
<canvas id="terminalCanvas"></canvas>
<script>
class TerminalEmulator {
constructor(canvasId, triggerId) {
this.typingAreas = [];
this.focusedIndex = -1;
this.cursorVisible = true;
this.canvas = document.getElementById(canvasId);
this.context = this.canvas.getContext('2d');
this.setupCanvas();
this.setupEvents(triggerId);
this.blinkCursor();
}
setupCanvas() {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
this.context.font = '16px monospace';
this.createFixedTypingAreas(); // Create two fixed typing areas
this.draw();
}
setupEvents(triggerId) {
const trigger = document.getElementById(triggerId);
// Trigger keyboard on page load
window.addEventListener('load', () => {
trigger.focus();
});
// Trigger keyboard when tapping the canvas
this.canvas.addEventListener('click', (event) => {
this.handleCanvasClick(event);
trigger.focus();
});
// Handle keyboard input
trigger.addEventListener('input', () => {
const inputValue = trigger.value;
trigger.value = ''; // Clear textarea
this.typeKey(inputValue);
});
trigger.addEventListener('keydown', (event) => {
// Handle Enter key
if (event.key === 'Enter') {
event.preventDefault();
this.typeKey('\n');
}
// Handle Backspace key
if (event.key === 'Backspace') {
event.preventDefault();
this.handleBackspace();
}
});
window.addEventListener('resize', () => {
this.setupCanvas();
});
}
createFixedTypingAreas() {
// Create two fixed typing areas at specific positions
const typingArea1 = {
name: 'Area 1',
x: 50,
y: 50,
width: 200,
height: 150,
color: this.getRandomColor(),
content: '',
};
const typingArea2 = {
name: 'Area 2',
x: 300,
y: 100,
width: 180,
height: 120,
color: this.getRandomColor(),
content: '',
};
this.typingAreas.push(typingArea1, typingArea2);
// Set the first one as focused initially
this.focusedIndex = 0;
}
getRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
typeKey(key) {
if (this.focusedIndex !== -1) {
// Handle Enter key as a newline
if (key === '\n') {
this.typingAreas[this.focusedIndex].content += '\n';
} else {
// Append the key to the focused typing area's content
this.typingAreas[this.focusedIndex].content += key;
}
this.draw();
}
}
handleBackspace() {
if (this.focusedIndex !== -1) {
const content = this.typingAreas[this.focusedIndex].content;
if (content.length > 0) {
// Remove the last character from the focused typing area's content
this.typingAreas[this.focusedIndex].content = content.slice(0, -1);
this.draw();
}
}
}
handleCanvasClick(event) {
// Find the clicked typing area
for (let i = 0; i < this.typingAreas.length; i++) {
const area = this.typingAreas[i];
if (
event.clientX >= area.x &&
event.clientX <= area.x + area.width &&
event.clientY >= area.y &&
event.clientY <= area.y + area.height
) {
// Set the clicked area as focused
this.focusedIndex = i;
this.draw();
return;
}
}
// If clicked outside any typing area, unfocus all
this.focusedIndex = -1;
this.draw();
}
print(text) {
if (this.focusedIndex !== -1) {
this.typingAreas[this.focusedIndex].content += text;
this.draw();
}
}
adjustTypingAreaSize() {
// Ensure each typing area fits within the available space
this.typingAreas.forEach((area) => {
const maxX = this.canvas.width - 20; // Leave some margin
const maxY = this.canvas.height - 20; // Leave some margin
if (area.x + area.width > maxX) {
area.width = maxX - area.x;
}
if (area.y + area.height > maxY) {
area.height = maxY - area.y;
}
});
}
drawDitheredGradient(x, y, width, height) {
const gradientWidth = 20; // Width of the gradient
const numPoints = gradientWidth * 2; // Number of points in the gradient
for (let i = 0; i < numPoints; i++) {
const gradientValue = i / numPoints; // Value between 0 and 1
const pixelX = x + Math.floor(width * gradientValue);
const pixelY = y + Math.floor(height * gradientValue);
this.context.fillStyle = `rgba(255, 255, 255, ${1 - gradientValue})`;
this.context.fillRect(pixelX, pixelY, 1, 1);
}
}
drawTypingAreaOutline(area) {
// Draw the typing area outline
this.context.strokeStyle = area.color;
this.context.lineWidth = 2;
this.context.strokeRect(area.x, area.y, area.width, area.height);
// Draw dithered gradient background for the area name box
this.drawDitheredGradient(area.x, area.y - 30, area.width, 30);
// Draw the area name box
this.context.fillStyle = area.color;
this.context.fillRect(area.x, area.y - 30, area.width, 30);
// Draw the area name text centered in the box
this.context.fillStyle = 'white';
this.context.textAlign = 'left';
this.context.fillText(area.name, area.x + 10, area.y - 10);
// Draw text within the box
this.context.fillStyle = area.color;
area.content.split('\n').forEach((line, index) => {
const y = area.y + 20 + index * 20;
if (y <= area.y + area.height) {
// Draw only within the typing area
this.context.fillText(line, area.x + 10, y);
}
});
}
draw() {
this.adjustTypingAreaSize(); // Adjust typing area size if needed
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Draw outlines and text for each typing area except the focused one
this.typingAreas.forEach((area, index) => {
if (index !== this.focusedIndex) {
this.drawTypingAreaOutline(area);
}
});
// Draw the outline and text for the focused typing area last
if (this.focusedIndex !== -1) {
this.drawTypingAreaOutline(this.typingAreas[this.focusedIndex]);
// Draw blinking cursor within the focused typing area
if (this.cursorVisible) {
const focusedArea = this.typingAreas[this.focusedIndex];
// Set cursorX based on the width of the last line
const lastLine = focusedArea.content.split('\n').pop();
const cursorX = focusedArea.x + 10 + this.context.measureText(lastLine).width;
const cursorY =
focusedArea.y + 20 + (focusedArea.content.split('\n').length - 1) * 20;
this.context.fillStyle = '#00FF00'; // Set text color to green
this.context.fillRect(cursorX, cursorY - 14, 2, 16);
}
}
}
blinkCursor() {
setInterval(() => {
this.cursorVisible = !this.cursorVisible;
this.draw();
}, 500);
}
}
const terminal = new TerminalEmulator('terminalCanvas', 'keyboardTrigger');
// Example usage:
terminal.print('Hello, world!');
terminal.print('This is a new line.');
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment