feat: Intel GPU fixes, add tcc compiler, spreadsheet improvements, paint app, file manager improvements

This commit is contained in:
2026-03-21 17:35:16 +01:00
parent 2fd9b7d027
commit 770dde5001
72 changed files with 45499 additions and 305 deletions
+486
View File
@@ -0,0 +1,486 @@
/*
* main.cpp
* Paint app -- global state, event loop, entry point
* Copyright (c) 2026 Daniel Hammer
*/
#include "paint.h"
// ============================================================================
// Global state definitions
// ============================================================================
int g_win_w = INIT_W;
int g_win_h = INIT_H;
uint32_t* g_canvas = nullptr;
int g_canvas_w = 640;
int g_canvas_h = 480;
uint32_t* g_undo_buf[UNDO_MAX];
int g_undo_count = 0;
int g_undo_pos = 0;
int g_scroll_x = 0;
int g_scroll_y = 0;
Tool g_tool = TOOL_PENCIL;
Color g_fg_color = Color::from_rgb(0x00, 0x00, 0x00);
Color g_bg_color = Color::from_rgb(0xFF, 0xFF, 0xFF);
int g_brush_idx = 0;
bool g_drawing = false;
int g_draw_x0 = 0, g_draw_y0 = 0;
int g_draw_x1 = 0, g_draw_y1 = 0;
int g_prev_x = 0, g_prev_y = 0;
TrueTypeFont* g_font = nullptr;
bool g_modified = false;
char g_filepath[256] = {};
// ============================================================================
// Helpers
// ============================================================================
int brush_size() {
return BRUSH_SIZES[g_brush_idx];
}
int canvas_x() {
if (g_canvas_w < g_win_w)
return (g_win_w - g_canvas_w) / 2 - g_scroll_x;
return -g_scroll_x;
}
int canvas_y() {
int area_y = TOOLBAR_H + COLOR_BAR_H;
int area_h = g_win_h - area_y - STATUS_BAR_H;
if (g_canvas_h < area_h)
return area_y + (area_h - g_canvas_h) / 2 - g_scroll_y;
return area_y - g_scroll_y;
}
bool screen_to_canvas(int sx, int sy, int* cx, int* cy) {
*cx = sx - canvas_x();
*cy = sy - canvas_y();
return *cx >= 0 && *cx < g_canvas_w && *cy >= 0 && *cy < g_canvas_h;
}
// ============================================================================
// Undo/Redo
// ============================================================================
void undo_push() {
// Discard any redo states after current position
for (int i = g_undo_pos; i < g_undo_count; i++) {
if (g_undo_buf[i]) {
montauk::mfree(g_undo_buf[i]);
g_undo_buf[i] = nullptr;
}
}
g_undo_count = g_undo_pos;
// If at capacity, shift everything down
if (g_undo_count >= UNDO_MAX) {
if (g_undo_buf[0]) montauk::mfree(g_undo_buf[0]);
for (int i = 1; i < UNDO_MAX; i++)
g_undo_buf[i - 1] = g_undo_buf[i];
g_undo_buf[UNDO_MAX - 1] = nullptr;
g_undo_count = UNDO_MAX - 1;
g_undo_pos = g_undo_count;
}
int sz = g_canvas_w * g_canvas_h * 4;
uint32_t* buf = (uint32_t*)montauk::malloc(sz);
if (buf) {
montauk::memcpy(buf, g_canvas, sz);
g_undo_buf[g_undo_count] = buf;
g_undo_count++;
g_undo_pos = g_undo_count;
}
}
void undo_do() {
if (g_undo_pos <= 0) return;
// Save current state as redo if we're at the top
if (g_undo_pos == g_undo_count && g_undo_count < UNDO_MAX) {
int sz = g_canvas_w * g_canvas_h * 4;
uint32_t* buf = (uint32_t*)montauk::malloc(sz);
if (buf) {
montauk::memcpy(buf, g_canvas, sz);
g_undo_buf[g_undo_count] = buf;
g_undo_count++;
}
}
g_undo_pos--;
int sz = g_canvas_w * g_canvas_h * 4;
montauk::memcpy(g_canvas, g_undo_buf[g_undo_pos], sz);
}
void redo_do() {
if (g_undo_pos >= g_undo_count - 1) return;
g_undo_pos++;
int sz = g_canvas_w * g_canvas_h * 4;
montauk::memcpy(g_canvas, g_undo_buf[g_undo_pos], sz);
}
// ============================================================================
// Toolbar hit testing
// ============================================================================
static bool handle_toolbar_click(int mx, int my) {
if (my >= TOOLBAR_H) return false;
// Walk the button layout to find which button was clicked
int bx = 4;
for (int i = 0; i < TOOL_COUNT; i++) {
int w = 48;
int x1 = bx + w;
if (mx >= bx && mx < x1) {
g_tool = (Tool)i;
return true;
}
bx = x1 + 4;
if (i == TOOL_ERASER || i == TOOL_FILLED_ELLIPSE || i == TOOL_EYEDROPPER)
bx += 8; // separator
}
// Brush size button
{
int w = 40;
int x1 = bx + w;
if (mx >= bx && mx < x1) {
g_brush_idx = (g_brush_idx + 1) % BRUSH_SIZE_COUNT;
return true;
}
}
return false;
}
// ============================================================================
// Color bar hit testing
// ============================================================================
static bool handle_colorbar_click(int mx, int my, bool right_click) {
int cbar_y = TOOLBAR_H;
if (my < cbar_y || my >= cbar_y + COLOR_BAR_H) return false;
int preview_sz = COLOR_BAR_H - 8;
int sw_x = 4 + preview_sz + 16;
for (int i = 0; i < PALETTE_COUNT; i++) {
int row = i >= 14 ? 1 : 0;
int col = i >= 14 ? i - 14 : i;
int sx = sw_x + col * (SWATCH_SIZE + 2);
int sy = cbar_y + 2 + row * (SWATCH_SIZE / 2 + 1);
int sh = SWATCH_SIZE / 2;
if (mx >= sx && mx < sx + SWATCH_SIZE && my >= sy && my < sy + sh) {
if (right_click)
g_bg_color = PALETTE[i];
else
g_fg_color = PALETTE[i];
return true;
}
}
return false;
}
// ============================================================================
// Scroll clamping
// ============================================================================
static void clamp_scroll() {
int area_h = g_win_h - TOOLBAR_H - COLOR_BAR_H - STATUS_BAR_H;
int max_sx = g_canvas_w - g_win_w + 40;
int max_sy = g_canvas_h - area_h + 40;
if (max_sx < 0) max_sx = 0;
if (max_sy < 0) max_sy = 0;
if (g_scroll_x < 0) g_scroll_x = 0;
if (g_scroll_x > max_sx) g_scroll_x = max_sx;
if (g_scroll_y < 0) g_scroll_y = 0;
if (g_scroll_y > max_sy) g_scroll_y = max_sy;
}
// ============================================================================
// Apply shape tool (commit line/rect/ellipse to canvas)
// ============================================================================
static void apply_shape() {
int bs = brush_size();
Color c = g_fg_color;
switch (g_tool) {
case TOOL_LINE:
canvas_draw_line(g_draw_x0, g_draw_y0, g_draw_x1, g_draw_y1, c, bs);
break;
case TOOL_RECT:
canvas_draw_rect(g_draw_x0, g_draw_y0, g_draw_x1, g_draw_y1, c, bs);
break;
case TOOL_FILLED_RECT:
canvas_fill_rect(g_draw_x0, g_draw_y0, g_draw_x1, g_draw_y1, c);
break;
case TOOL_ELLIPSE:
canvas_draw_ellipse(g_draw_x0, g_draw_y0, g_draw_x1, g_draw_y1, c, bs);
break;
case TOOL_FILLED_ELLIPSE:
canvas_fill_ellipse(g_draw_x0, g_draw_y0, g_draw_x1, g_draw_y1, c);
break;
default:
break;
}
}
// ============================================================================
// Entry point
// ============================================================================
extern "C" void _start() {
// Initialize undo pointers
for (int i = 0; i < UNDO_MAX; i++) g_undo_buf[i] = nullptr;
// Allocate canvas
int canvas_bytes = g_canvas_w * g_canvas_h * 4;
g_canvas = (uint32_t*)montauk::malloc(canvas_bytes);
if (!g_canvas) montauk::exit(1);
canvas_clear(g_bg_color);
// Load font
g_font = (TrueTypeFont*)montauk::malloc(sizeof(TrueTypeFont));
if (g_font) {
montauk::memset(g_font, 0, sizeof(TrueTypeFont));
if (!g_font->init("0:/fonts/Roboto-Medium.ttf")) {
montauk::mfree(g_font);
g_font = nullptr;
}
}
// Push initial undo state
undo_push();
// Create window
Montauk::WinCreateResult wres;
if (montauk::win_create("Paint", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
montauk::exit(1);
int win_id = wres.id;
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
render(pixels);
montauk::win_present(win_id);
while (true) {
Montauk::WinEvent ev;
int r = montauk::win_poll(win_id, &ev);
if (r < 0) break;
if (r == 0) {
montauk::sleep_ms(16);
continue;
}
// Close
if (ev.type == 3) break;
// Resize
if (ev.type == 2) {
g_win_w = ev.resize.w;
g_win_h = ev.resize.h;
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(win_id, g_win_w, g_win_h);
clamp_scroll();
render(pixels);
montauk::win_present(win_id);
continue;
}
bool redraw = false;
// ============================================================
// Keyboard
// ============================================================
if (ev.type == 0 && ev.key.pressed) {
auto& key = ev.key;
// Ctrl+Z: undo
if (key.ctrl && (key.ascii == 'z' || key.ascii == 'Z')) {
undo_do();
redraw = true;
}
// Ctrl+Y: redo
else if (key.ctrl && (key.ascii == 'y' || key.ascii == 'Y')) {
redo_do();
redraw = true;
}
// Ctrl+N: new canvas (clear)
else if (key.ctrl && (key.ascii == 'n' || key.ascii == 'N')) {
undo_push();
canvas_clear(g_bg_color);
g_modified = false;
redraw = true;
}
// Tool shortcuts
else if (!key.ctrl) {
switch (key.ascii) {
case 'p': case 'P': g_tool = TOOL_PENCIL; redraw = true; break;
case 'b': case 'B': g_tool = TOOL_BRUSH; redraw = true; break;
case 'e': case 'E': g_tool = TOOL_ERASER; redraw = true; break;
case 'l': case 'L': g_tool = TOOL_LINE; redraw = true; break;
case 'r': case 'R': g_tool = TOOL_RECT; redraw = true; break;
case 'o': case 'O': g_tool = TOOL_ELLIPSE; redraw = true; break;
case 'f': case 'F': g_tool = TOOL_FILL; redraw = true; break;
case 'i': case 'I': g_tool = TOOL_EYEDROPPER; redraw = true; break;
case '[':
if (g_brush_idx > 0) { g_brush_idx--; redraw = true; }
break;
case ']':
if (g_brush_idx < BRUSH_SIZE_COUNT - 1) { g_brush_idx++; redraw = true; }
break;
default: break;
}
}
// Escape: cancel shape drawing
if (key.scancode == 0x01) {
if (g_drawing) {
g_drawing = false;
redraw = true;
}
}
}
// ============================================================
// Mouse
// ============================================================
if (ev.type == 1) {
int mx = ev.mouse.x;
int my = ev.mouse.y;
uint8_t btns = ev.mouse.buttons;
uint8_t prev = ev.mouse.prev_buttons;
bool left_click = (btns & 1) && !(prev & 1);
bool left_held = (btns & 1);
bool left_released = !(btns & 1) && (prev & 1);
bool right_click = (btns & 2) && !(prev & 2);
// Scroll
if (ev.mouse.scroll != 0) {
g_scroll_y -= ev.mouse.scroll * 30;
clamp_scroll();
redraw = true;
}
// Toolbar click
if (left_click && my < TOOLBAR_H) {
if (handle_toolbar_click(mx, my))
redraw = true;
goto done_mouse;
}
// Color bar click
if ((left_click || right_click) && my >= TOOLBAR_H && my < TOOLBAR_H + COLOR_BAR_H) {
if (handle_colorbar_click(mx, my, right_click))
redraw = true;
goto done_mouse;
}
// Canvas interaction
{
int cx_pos, cy_pos;
bool on_canvas = screen_to_canvas(mx, my, &cx_pos, &cy_pos);
// Eyedropper
if (g_tool == TOOL_EYEDROPPER && left_click && on_canvas) {
uint32_t px = g_canvas[cy_pos * g_canvas_w + cx_pos];
g_fg_color = Color::from_rgb((px >> 16) & 0xFF, (px >> 8) & 0xFF, px & 0xFF);
redraw = true;
goto done_mouse;
}
// Fill tool
if (g_tool == TOOL_FILL && left_click && on_canvas) {
undo_push();
canvas_flood_fill(cx_pos, cy_pos, g_fg_color);
g_modified = true;
redraw = true;
goto done_mouse;
}
// Pencil / Brush / Eraser: freehand drawing
if ((g_tool == TOOL_PENCIL || g_tool == TOOL_BRUSH || g_tool == TOOL_ERASER)) {
Color draw_c = (g_tool == TOOL_ERASER) ? g_bg_color : g_fg_color;
int bs = brush_size();
if (g_tool == TOOL_PENCIL) bs = 1;
if (g_tool == TOOL_BRUSH) {
if (bs < 4) bs = 4;
}
if (left_click && on_canvas) {
undo_push();
g_drawing = true;
g_prev_x = cx_pos;
g_prev_y = cy_pos;
canvas_draw_brush(cx_pos, cy_pos, draw_c, bs);
g_modified = true;
redraw = true;
} else if (g_drawing && left_held) {
if (on_canvas && (cx_pos != g_prev_x || cy_pos != g_prev_y)) {
canvas_draw_line(g_prev_x, g_prev_y, cx_pos, cy_pos, draw_c, bs);
g_prev_x = cx_pos;
g_prev_y = cy_pos;
redraw = true;
}
}
if (left_released && g_drawing) {
g_drawing = false;
redraw = true;
}
goto done_mouse;
}
// Shape tools: line, rect, ellipse
if (g_tool == TOOL_LINE || g_tool == TOOL_RECT || g_tool == TOOL_FILLED_RECT ||
g_tool == TOOL_ELLIPSE || g_tool == TOOL_FILLED_ELLIPSE) {
if (left_click && on_canvas) {
undo_push();
g_drawing = true;
g_draw_x0 = cx_pos;
g_draw_y0 = cy_pos;
g_draw_x1 = cx_pos;
g_draw_y1 = cy_pos;
redraw = true;
} else if (g_drawing && left_held) {
if (on_canvas) {
g_draw_x1 = cx_pos;
g_draw_y1 = cy_pos;
redraw = true;
}
}
if (left_released && g_drawing) {
if (on_canvas) {
g_draw_x1 = cx_pos;
g_draw_y1 = cy_pos;
}
apply_shape();
g_drawing = false;
g_modified = true;
redraw = true;
}
goto done_mouse;
}
}
done_mouse:;
}
if (redraw) {
render(pixels);
montauk::win_present(win_id);
}
}
montauk::win_destroy(win_id);
montauk::exit(0);
}