Created
October 13, 2023 15:02
-
-
Save mindfulvector/df3b0698aa214712bea892275a13d3a8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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