in src/gemini_95/index.ts [988:1161]
function initSimplePaintApp(windowElement: HTMLDivElement): void {
const canvas = windowElement.querySelector('#paint-canvas') as HTMLCanvasElement;
const toolbar = windowElement.querySelector('.paint-toolbar') as HTMLDivElement;
const contentArea = windowElement.querySelector('.window-content') as HTMLDivElement;
const colorSwatches = windowElement.querySelectorAll('.paint-color-swatch') as NodeListOf<HTMLButtonElement>;
const sizeButtons = windowElement.querySelectorAll('.paint-size-button') as NodeListOf<HTMLButtonElement>;
const clearButton = windowElement.querySelector('.paint-clear-button') as HTMLButtonElement;
if (!canvas || !toolbar || !contentArea || !clearButton) {
console.error("Paint elements not found!");
return;
}
const ctx = canvas.getContext('2d');
if (!ctx) {
console.error("Could not get 2D context for paint canvas");
return;
}
let isDrawing = false;
let lastX = 0;
let lastY = 0;
// Set initial context properties
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
let currentStrokeStyle = ctx.strokeStyle;
let currentLineWidth = ctx.lineWidth;
// Function to resize canvas and redraw background
function resizeCanvas() {
const rect = contentArea.getBoundingClientRect();
const toolbarHeight = toolbar.offsetHeight;
const newWidth = Math.floor(rect.width);
const newHeight = Math.floor(rect.height - toolbarHeight - 2); // Adjust for borders/padding
if (canvas.width === newWidth && canvas.height === newHeight) {
return;
}
canvas.width = newWidth;
canvas.height = newHeight;
// Redraw white background
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Re-apply context settings
ctx.strokeStyle = currentStrokeStyle;
ctx.lineWidth = currentLineWidth;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
console.log(`Canvas resized to ${canvas.width}x${canvas.height}`);
}
// Use ResizeObserver for robust resizing
const resizeObserver = new ResizeObserver(() => resizeCanvas());
resizeObserver.observe(contentArea);
paintResizeObserverMap.set(contentArea, resizeObserver);
resizeCanvas(); // Initial size setup
// --- Drawing Event Listeners ---
function startDrawing(e: MouseEvent | TouchEvent) {
isDrawing = true;
const pos = getMousePos(canvas, e);
[lastX, lastY] = [pos.x, pos.y];
ctx.beginPath();
ctx.moveTo(lastX, lastY);
}
function draw(e: MouseEvent | TouchEvent) {
if (!isDrawing) return;
e.preventDefault(); // Prevent page scrolling on touch devices
const pos = getMousePos(canvas, e);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
[lastX, lastY] = [pos.x, pos.y];
}
function stopDrawing() {
if (!isDrawing) return;
isDrawing = false;
}
// Helper to get mouse/touch position relative to canvas
function getMousePos(canvasDom: HTMLCanvasElement, event: MouseEvent | TouchEvent): { x: number, y: number } {
const rect = canvasDom.getBoundingClientRect();
let clientX, clientY;
if (event instanceof MouseEvent) {
clientX = event.clientX;
clientY = event.clientY;
} else {
clientX = event.touches[0].clientX;
clientY = event.touches[0].clientY;
}
return {
x: clientX - rect.left,
y: clientY - rect.top
};
}
// Attach drawing listeners
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseleave', stopDrawing);
canvas.addEventListener('touchstart', startDrawing, { passive: false }); // Need passive: false for preventDefault
canvas.addEventListener('touchmove', draw, { passive: false }); // Need passive: false for preventDefault
canvas.addEventListener('touchend', stopDrawing);
canvas.addEventListener('touchcancel', stopDrawing);
// --- Toolbar Event Listeners ---
colorSwatches.forEach(swatch => {
swatch.addEventListener('click', () => {
ctx.strokeStyle = swatch.dataset.color || 'black';
currentStrokeStyle = ctx.strokeStyle;
colorSwatches.forEach(s => s.classList.remove('active'));
swatch.classList.add('active');
// If eraser (white) is selected, use wider line
if (swatch.dataset.color === 'white') {
const largeSizeButton = Array.from(sizeButtons).find(b => b.dataset.size === '10');
if (largeSizeButton) {
ctx.lineWidth = parseInt(largeSizeButton.dataset.size || '10', 10);
currentLineWidth = ctx.lineWidth;
sizeButtons.forEach(s => s.classList.remove('active'));
largeSizeButton.classList.add('active');
}
} else {
const activeSizeButton = Array.from(sizeButtons).find(b => b.classList.contains('active'));
if (activeSizeButton) {
ctx.lineWidth = parseInt(activeSizeButton.dataset.size || '2', 10);
currentLineWidth = ctx.lineWidth;
}
}
});
});
sizeButtons.forEach(button => {
button.addEventListener('click', () => {
ctx.lineWidth = parseInt(button.dataset.size || '2', 10);
currentLineWidth = ctx.lineWidth;
sizeButtons.forEach(s => s.classList.remove('active'));
button.classList.add('active');
// Ensure a color is active if switching size while not erasing
const eraser = Array.from(colorSwatches).find(s => s.dataset.color === 'white');
if (!eraser?.classList.contains('active')) {
if (!Array.from(colorSwatches).some(s => s.classList.contains('active'))) {
const blackSwatch = Array.from(colorSwatches).find(s => s.dataset.color === 'black');
blackSwatch?.classList.add('active');
ctx.strokeStyle = 'black';
currentStrokeStyle = ctx.strokeStyle;
}
}
});
});
clearButton.addEventListener('click', () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
});
// Set initial active buttons
(windowElement.querySelector('.paint-color-swatch[data-color="black"]') as HTMLButtonElement)?.classList.add('active');
(windowElement.querySelector('.paint-size-button[data-size="2"]') as HTMLButtonElement)?.classList.add('active');
console.log("Simple Paint App Initialized");
}