feat: Intel GPU fixes, add tcc compiler, spreadsheet improvements, paint app, file manager improvements
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# Makefile for paint (standalone Window Server app) on MontaukOS
|
||||
# Copyright (c) 2026 Daniel Hammer
|
||||
|
||||
MAKEFLAGS += -rR
|
||||
.SUFFIXES:
|
||||
|
||||
# ---- Toolchain ----
|
||||
|
||||
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
|
||||
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
|
||||
CXX := $(TOOLCHAIN_PREFIX)g++
|
||||
else
|
||||
CXX := g++
|
||||
endif
|
||||
|
||||
# ---- Paths ----
|
||||
|
||||
PROG_INC := ../../include
|
||||
LINK_LD := ../../link.ld
|
||||
BINDIR := ../../bin
|
||||
OBJDIR := obj
|
||||
LIBDIR := ../../lib
|
||||
|
||||
# ---- Compiler flags ----
|
||||
|
||||
CXXFLAGS := \
|
||||
-std=gnu++20 \
|
||||
-g -O2 -pipe \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-Wno-unused-parameter \
|
||||
-Wno-unused-function \
|
||||
-nostdinc \
|
||||
-ffreestanding \
|
||||
-fno-stack-protector \
|
||||
-fno-stack-check \
|
||||
-fno-PIC \
|
||||
-fno-rtti \
|
||||
-fno-exceptions \
|
||||
-ffunction-sections \
|
||||
-fdata-sections \
|
||||
-m64 \
|
||||
-march=x86-64 \
|
||||
-msse \
|
||||
-msse2 \
|
||||
-mno-red-zone \
|
||||
-mcmodel=small \
|
||||
-I $(PROG_INC) \
|
||||
-isystem $(PROG_INC)/libc \
|
||||
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
|
||||
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include
|
||||
|
||||
# ---- Linker flags ----
|
||||
|
||||
LDFLAGS := \
|
||||
-nostdlib \
|
||||
-static \
|
||||
-Wl,--build-id=none \
|
||||
-Wl,--gc-sections \
|
||||
-Wl,-m,elf_x86_64 \
|
||||
-z max-page-size=0x1000 \
|
||||
-T $(LINK_LD)
|
||||
|
||||
# ---- Source files ----
|
||||
|
||||
SRCS := main.cpp helpers.cpp drawing.cpp render.cpp stb_truetype_impl.cpp
|
||||
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/apps/paint/paint.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/apps/paint
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* drawing.cpp
|
||||
* Canvas drawing operations for Paint app
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "paint.h"
|
||||
|
||||
// ============================================================================
|
||||
// Basic canvas operations
|
||||
// ============================================================================
|
||||
|
||||
void canvas_put_pixel(int x, int y, Color c) {
|
||||
if (x < 0 || x >= g_canvas_w || y < 0 || y >= g_canvas_h) return;
|
||||
g_canvas[y * g_canvas_w + x] = c.to_pixel();
|
||||
}
|
||||
|
||||
void canvas_clear(Color c) {
|
||||
uint32_t v = c.to_pixel();
|
||||
int total = g_canvas_w * g_canvas_h;
|
||||
for (int i = 0; i < total; i++)
|
||||
g_canvas[i] = v;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Brush (filled circle stamp)
|
||||
// ============================================================================
|
||||
|
||||
void canvas_draw_brush(int cx, int cy, Color c, int size) {
|
||||
if (size <= 1) {
|
||||
canvas_put_pixel(cx, cy, c);
|
||||
return;
|
||||
}
|
||||
int r = size / 2;
|
||||
int r2 = r * r;
|
||||
for (int dy = -r; dy <= r; dy++)
|
||||
for (int dx = -r; dx <= r; dx++)
|
||||
if (dx * dx + dy * dy <= r2)
|
||||
canvas_put_pixel(cx + dx, cy + dy, c);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Bresenham line
|
||||
// ============================================================================
|
||||
|
||||
void canvas_draw_line(int x0, int y0, int x1, int y1, Color c, int thickness) {
|
||||
int dx = x1 - x0;
|
||||
int dy = y1 - y0;
|
||||
if (dx < 0) dx = -dx;
|
||||
if (dy < 0) dy = -dy;
|
||||
|
||||
int sx = x0 < x1 ? 1 : -1;
|
||||
int sy = y0 < y1 ? 1 : -1;
|
||||
int err = dx - dy;
|
||||
|
||||
while (true) {
|
||||
canvas_draw_brush(x0, y0, c, thickness);
|
||||
if (x0 == x1 && y0 == y1) break;
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -dy) { err -= dy; x0 += sx; }
|
||||
if (e2 < dx) { err += dx; y0 += sy; }
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Rectangle
|
||||
// ============================================================================
|
||||
|
||||
void canvas_draw_rect(int x0, int y0, int x1, int y1, Color c, int thickness) {
|
||||
if (x0 > x1) { int t = x0; x0 = x1; x1 = t; }
|
||||
if (y0 > y1) { int t = y0; y0 = y1; y1 = t; }
|
||||
|
||||
for (int t = 0; t < thickness; t++) {
|
||||
for (int x = x0 + t; x <= x1 - t; x++) {
|
||||
canvas_put_pixel(x, y0 + t, c);
|
||||
canvas_put_pixel(x, y1 - t, c);
|
||||
}
|
||||
for (int y = y0 + t; y <= y1 - t; y++) {
|
||||
canvas_put_pixel(x0 + t, y, c);
|
||||
canvas_put_pixel(x1 - t, y, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void canvas_fill_rect(int x0, int y0, int x1, int y1, Color c) {
|
||||
if (x0 > x1) { int t = x0; x0 = x1; x1 = t; }
|
||||
if (y0 > y1) { int t = y0; y0 = y1; y1 = t; }
|
||||
|
||||
for (int y = y0; y <= y1; y++)
|
||||
for (int x = x0; x <= x1; x++)
|
||||
canvas_put_pixel(x, y, c);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Ellipse (midpoint algorithm)
|
||||
// ============================================================================
|
||||
|
||||
static void ellipse_plot4(int cx, int cy, int x, int y, Color c, int thickness) {
|
||||
if (thickness <= 1) {
|
||||
canvas_put_pixel(cx + x, cy + y, c);
|
||||
canvas_put_pixel(cx - x, cy + y, c);
|
||||
canvas_put_pixel(cx + x, cy - y, c);
|
||||
canvas_put_pixel(cx - x, cy - y, c);
|
||||
} else {
|
||||
canvas_draw_brush(cx + x, cy + y, c, thickness);
|
||||
canvas_draw_brush(cx - x, cy + y, c, thickness);
|
||||
canvas_draw_brush(cx + x, cy - y, c, thickness);
|
||||
canvas_draw_brush(cx - x, cy - y, c, thickness);
|
||||
}
|
||||
}
|
||||
|
||||
void canvas_draw_ellipse(int x0, int y0, int x1, int y1, Color c, int thickness) {
|
||||
if (x0 > x1) { int t = x0; x0 = x1; x1 = t; }
|
||||
if (y0 > y1) { int t = y0; y0 = y1; y1 = t; }
|
||||
|
||||
int cx = (x0 + x1) / 2;
|
||||
int cy = (y0 + y1) / 2;
|
||||
int a = (x1 - x0) / 2;
|
||||
int b = (y1 - y0) / 2;
|
||||
if (a == 0 || b == 0) {
|
||||
canvas_draw_line(x0, y0, x1, y1, c, thickness);
|
||||
return;
|
||||
}
|
||||
|
||||
long long a2 = (long long)a * a;
|
||||
long long b2 = (long long)b * b;
|
||||
int x = 0, y = b;
|
||||
long long d1 = b2 - a2 * b + a2 / 4;
|
||||
|
||||
while (a2 * y > b2 * x) {
|
||||
ellipse_plot4(cx, cy, x, y, c, thickness);
|
||||
if (d1 < 0) {
|
||||
d1 += b2 * (2 * x + 3);
|
||||
} else {
|
||||
d1 += b2 * (2 * x + 3) + a2 * (-2 * y + 2);
|
||||
y--;
|
||||
}
|
||||
x++;
|
||||
}
|
||||
|
||||
long long d2 = b2 * ((long long)(x * 2 + 1) * (x * 2 + 1)) / 4
|
||||
+ a2 * ((long long)(y - 1) * (y - 1))
|
||||
- a2 * b2;
|
||||
while (y >= 0) {
|
||||
ellipse_plot4(cx, cy, x, y, c, thickness);
|
||||
if (d2 > 0) {
|
||||
d2 += a2 * (-2 * y + 3);
|
||||
} else {
|
||||
d2 += b2 * (2 * x + 2) + a2 * (-2 * y + 3);
|
||||
x++;
|
||||
}
|
||||
y--;
|
||||
}
|
||||
}
|
||||
|
||||
void canvas_fill_ellipse(int x0, int y0, int x1, int y1, Color c) {
|
||||
if (x0 > x1) { int t = x0; x0 = x1; x1 = t; }
|
||||
if (y0 > y1) { int t = y0; y0 = y1; y1 = t; }
|
||||
|
||||
int cx = (x0 + x1) / 2;
|
||||
int cy = (y0 + y1) / 2;
|
||||
int a = (x1 - x0) / 2;
|
||||
int b = (y1 - y0) / 2;
|
||||
if (a == 0 || b == 0) return;
|
||||
|
||||
for (int dy = -b; dy <= b; dy++) {
|
||||
// x^2/a^2 + y^2/b^2 <= 1 => x <= a * sqrt(1 - y^2/b^2)
|
||||
long long x_extent = (long long)a * a * ((long long)b * b - (long long)dy * dy);
|
||||
if (x_extent < 0) continue;
|
||||
// Integer sqrt approximation
|
||||
long long denom = (long long)b * b;
|
||||
int w = 0;
|
||||
while ((long long)(w + 1) * (w + 1) * denom <= x_extent) w++;
|
||||
for (int dx = -w; dx <= w; dx++)
|
||||
canvas_put_pixel(cx + dx, cy + dy, c);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Flood fill (iterative scanline)
|
||||
// ============================================================================
|
||||
|
||||
void canvas_flood_fill(int sx, int sy, Color fill_color) {
|
||||
if (sx < 0 || sx >= g_canvas_w || sy < 0 || sy >= g_canvas_h) return;
|
||||
|
||||
uint32_t target = g_canvas[sy * g_canvas_w + sx];
|
||||
uint32_t fill_px = fill_color.to_pixel();
|
||||
if (target == fill_px) return;
|
||||
|
||||
// Simple stack-based flood fill with a bounded stack
|
||||
static constexpr int STACK_MAX = 65536;
|
||||
struct Pt { int16_t x, y; };
|
||||
Pt* stack = (Pt*)montauk::malloc(STACK_MAX * sizeof(Pt));
|
||||
if (!stack) return;
|
||||
|
||||
int sp = 0;
|
||||
stack[sp++] = {(int16_t)sx, (int16_t)sy};
|
||||
g_canvas[sy * g_canvas_w + sx] = fill_px;
|
||||
|
||||
while (sp > 0) {
|
||||
Pt p = stack[--sp];
|
||||
int x = p.x, y = p.y;
|
||||
|
||||
// Scan left
|
||||
int left = x;
|
||||
while (left > 0 && g_canvas[y * g_canvas_w + left - 1] == target) {
|
||||
left--;
|
||||
g_canvas[y * g_canvas_w + left] = fill_px;
|
||||
}
|
||||
|
||||
// Scan right
|
||||
int right = x;
|
||||
while (right < g_canvas_w - 1 && g_canvas[y * g_canvas_w + right + 1] == target) {
|
||||
right++;
|
||||
g_canvas[y * g_canvas_w + right] = fill_px;
|
||||
}
|
||||
|
||||
// Check above and below
|
||||
for (int dir = -1; dir <= 1; dir += 2) {
|
||||
int ny = y + dir;
|
||||
if (ny < 0 || ny >= g_canvas_h) continue;
|
||||
bool in_span = false;
|
||||
for (int px = left; px <= right; px++) {
|
||||
if (g_canvas[ny * g_canvas_w + px] == target) {
|
||||
if (!in_span && sp < STACK_MAX) {
|
||||
g_canvas[ny * g_canvas_w + px] = fill_px;
|
||||
stack[sp++] = {(int16_t)px, (int16_t)ny};
|
||||
in_span = true;
|
||||
}
|
||||
} else {
|
||||
in_span = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
montauk::mfree(stack);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* helpers.cpp
|
||||
* Pixel drawing helpers for Paint app
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "paint.h"
|
||||
|
||||
void px_fill(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, int h, Color c) {
|
||||
uint32_t v = c.to_pixel();
|
||||
int x0 = x < 0 ? 0 : x, y0 = y < 0 ? 0 : y;
|
||||
int x1 = x + w > bw ? bw : x + w;
|
||||
int y1 = y + h > bh ? bh : y + h;
|
||||
for (int row = y0; row < y1; row++)
|
||||
for (int col = x0; col < x1; col++)
|
||||
px[row * bw + col] = v;
|
||||
}
|
||||
|
||||
void px_hline(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, Color c) {
|
||||
if (y < 0 || y >= bh) return;
|
||||
uint32_t v = c.to_pixel();
|
||||
int x0 = x < 0 ? 0 : x;
|
||||
int x1 = x + w > bw ? bw : x + w;
|
||||
for (int col = x0; col < x1; col++)
|
||||
px[y * bw + col] = v;
|
||||
}
|
||||
|
||||
void px_vline(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int h, Color c) {
|
||||
if (x < 0 || x >= bw) return;
|
||||
uint32_t v = c.to_pixel();
|
||||
int y0 = y < 0 ? 0 : y;
|
||||
int y1 = y + h > bh ? bh : y + h;
|
||||
for (int row = y0; row < y1; row++)
|
||||
px[row * bw + x] = v;
|
||||
}
|
||||
|
||||
void px_rect(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, int h, Color c) {
|
||||
px_hline(px, bw, bh, x, y, w, c);
|
||||
px_hline(px, bw, bh, x, y + h - 1, w, c);
|
||||
px_vline(px, bw, bh, x, y, h, c);
|
||||
px_vline(px, bw, bh, x + w - 1, y, h, c);
|
||||
}
|
||||
|
||||
void px_fill_rounded(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, int h, int r, Color c) {
|
||||
uint32_t v = c.to_pixel();
|
||||
for (int row = 0; row < h; row++) {
|
||||
int py = y + row;
|
||||
if (py < 0 || py >= bh) continue;
|
||||
int inset = 0;
|
||||
if (row < r) {
|
||||
int dy = r - 1 - row;
|
||||
if (r == 3) {
|
||||
if (dy >= 2) inset = 2;
|
||||
else if (dy >= 1) inset = 1;
|
||||
} else {
|
||||
for (int i = r; i > 0; i--) {
|
||||
int dx = r - i;
|
||||
if (dx * dx + dy * dy < r * r) { inset = i; break; }
|
||||
}
|
||||
}
|
||||
} else if (row >= h - r) {
|
||||
int dy = row - (h - r);
|
||||
if (r == 3) {
|
||||
if (dy >= 2) inset = 2;
|
||||
else if (dy >= 1) inset = 1;
|
||||
} else {
|
||||
for (int i = r; i > 0; i--) {
|
||||
int dx = r - i;
|
||||
if (dx * dx + dy * dy < r * r) { inset = i; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
int x0 = x + inset;
|
||||
int x1 = x + w - inset;
|
||||
if (x0 < 0) x0 = 0;
|
||||
if (x1 > bw) x1 = bw;
|
||||
for (int col = x0; col < x1; col++)
|
||||
px[py * bw + col] = v;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
[app]
|
||||
name = "Paint"
|
||||
binary = "paint.elf"
|
||||
icon = "paint.svg"
|
||||
|
||||
[menu]
|
||||
category = "Applications"
|
||||
visible = true
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* paint.h
|
||||
* Shared header for the MontaukOS Paint app
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <gui/gui.hpp>
|
||||
#include <gui/truetype.hpp>
|
||||
#include <gui/stb_math.h>
|
||||
|
||||
extern "C" {
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
}
|
||||
|
||||
using namespace gui;
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int INIT_W = 800;
|
||||
static constexpr int INIT_H = 600;
|
||||
static constexpr int TOOLBAR_H = 36;
|
||||
static constexpr int TB_BTN_SIZE = 24;
|
||||
static constexpr int TB_BTN_Y = 6;
|
||||
static constexpr int TB_BTN_RAD = 3;
|
||||
static constexpr int STATUS_BAR_H = 24;
|
||||
static constexpr int COLOR_BAR_H = 36;
|
||||
|
||||
static constexpr int SWATCH_SIZE = 20;
|
||||
static constexpr int SWATCH_PAD = 4;
|
||||
static constexpr int SWATCH_Y = 8;
|
||||
|
||||
static constexpr int MAX_CANVAS_W = 2048;
|
||||
static constexpr int MAX_CANVAS_H = 2048;
|
||||
|
||||
static constexpr int FONT_SIZE = 16;
|
||||
|
||||
// UI colors
|
||||
static constexpr Color BG_COLOR = Color::from_rgb(0xE0, 0xE0, 0xE0);
|
||||
static constexpr Color TOOLBAR_BG = Color::from_rgb(0xF5, 0xF5, 0xF5);
|
||||
static constexpr Color TB_BTN_BG = Color::from_rgb(0xE8, 0xE8, 0xE8);
|
||||
static constexpr Color TB_BTN_ACTIVE = Color::from_rgb(0xC0, 0xD0, 0xE8);
|
||||
static constexpr Color TB_SEP_COLOR = Color::from_rgb(0xCC, 0xCC, 0xCC);
|
||||
static constexpr Color HEADER_TEXT = Color::from_rgb(0x55, 0x55, 0x55);
|
||||
static constexpr Color STATUS_BG = Color::from_rgb(0x2B, 0x3E, 0x50);
|
||||
static constexpr Color STATUS_TEXT = Color::from_rgb(0xFF, 0xFF, 0xFF);
|
||||
static constexpr Color CANVAS_BORDER = Color::from_rgb(0x99, 0x99, 0x99);
|
||||
static constexpr Color SELECT_BORDER = Color::from_rgb(0x36, 0x7B, 0xF0);
|
||||
|
||||
// ============================================================================
|
||||
// Tool types
|
||||
// ============================================================================
|
||||
|
||||
enum Tool : uint8_t {
|
||||
TOOL_PENCIL = 0,
|
||||
TOOL_BRUSH,
|
||||
TOOL_ERASER,
|
||||
TOOL_LINE,
|
||||
TOOL_RECT,
|
||||
TOOL_FILLED_RECT,
|
||||
TOOL_ELLIPSE,
|
||||
TOOL_FILLED_ELLIPSE,
|
||||
TOOL_FILL,
|
||||
TOOL_EYEDROPPER,
|
||||
TOOL_COUNT
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Palette colors
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int PALETTE_COUNT = 28;
|
||||
static constexpr Color PALETTE[PALETTE_COUNT] = {
|
||||
// Row 1: basic colors
|
||||
Color::from_rgb(0x00, 0x00, 0x00), // Black
|
||||
Color::from_rgb(0x7F, 0x7F, 0x7F), // Gray
|
||||
Color::from_rgb(0x88, 0x00, 0x15), // Dark red
|
||||
Color::from_rgb(0xED, 0x1C, 0x24), // Red
|
||||
Color::from_rgb(0xFF, 0x7F, 0x27), // Orange
|
||||
Color::from_rgb(0xFF, 0xF2, 0x00), // Yellow
|
||||
Color::from_rgb(0x22, 0xB1, 0x4C), // Green
|
||||
Color::from_rgb(0x00, 0xA2, 0xE8), // Light blue
|
||||
Color::from_rgb(0x3F, 0x48, 0xCC), // Blue
|
||||
Color::from_rgb(0xA3, 0x49, 0xA4), // Purple
|
||||
Color::from_rgb(0xB9, 0x7A, 0x57), // Brown
|
||||
Color::from_rgb(0xFF, 0xAE, 0xC9), // Pink
|
||||
Color::from_rgb(0xB5, 0xE6, 0x1D), // Lime
|
||||
Color::from_rgb(0x99, 0xD9, 0xEA), // Sky
|
||||
// Row 2: lighter variants and white
|
||||
Color::from_rgb(0xFF, 0xFF, 0xFF), // White
|
||||
Color::from_rgb(0xC3, 0xC3, 0xC3), // Light gray
|
||||
Color::from_rgb(0xB9, 0x5B, 0x5B), // Salmon
|
||||
Color::from_rgb(0xFF, 0xC9, 0x0E), // Gold
|
||||
Color::from_rgb(0xFE, 0xF0, 0x82), // Light yellow
|
||||
Color::from_rgb(0xA8, 0xE6, 0x1D), // Bright lime
|
||||
Color::from_rgb(0x70, 0xDB, 0x93), // Sea green
|
||||
Color::from_rgb(0x7E, 0xC8, 0xE3), // Powder blue
|
||||
Color::from_rgb(0x00, 0x78, 0xD7), // Bright blue
|
||||
Color::from_rgb(0xC8, 0xBF, 0xE7), // Lavender
|
||||
Color::from_rgb(0xD9, 0x9E, 0x82), // Tan
|
||||
Color::from_rgb(0xDE, 0xCE, 0xEF), // Light lavender
|
||||
Color::from_rgb(0xE0, 0xE0, 0xD0), // Cream
|
||||
Color::from_rgb(0xF0, 0xD0, 0xB0), // Peach
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Brush sizes
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int BRUSH_SIZES[] = { 1, 2, 4, 8, 12, 20 };
|
||||
static constexpr int BRUSH_SIZE_COUNT = 6;
|
||||
|
||||
// ============================================================================
|
||||
// Global state (extern -- defined in main.cpp)
|
||||
// ============================================================================
|
||||
|
||||
extern int g_win_w, g_win_h;
|
||||
|
||||
// Canvas
|
||||
extern uint32_t* g_canvas;
|
||||
extern int g_canvas_w, g_canvas_h;
|
||||
|
||||
// Undo
|
||||
static constexpr int UNDO_MAX = 16;
|
||||
extern uint32_t* g_undo_buf[UNDO_MAX];
|
||||
extern int g_undo_count;
|
||||
extern int g_undo_pos;
|
||||
|
||||
// View
|
||||
extern int g_scroll_x, g_scroll_y;
|
||||
|
||||
// Tool state
|
||||
extern Tool g_tool;
|
||||
extern Color g_fg_color;
|
||||
extern Color g_bg_color;
|
||||
extern int g_brush_idx;
|
||||
|
||||
// Drawing state
|
||||
extern bool g_drawing;
|
||||
extern int g_draw_x0, g_draw_y0;
|
||||
extern int g_draw_x1, g_draw_y1;
|
||||
extern int g_prev_x, g_prev_y;
|
||||
|
||||
// Font
|
||||
extern TrueTypeFont* g_font;
|
||||
|
||||
// Modified flag
|
||||
extern bool g_modified;
|
||||
extern char g_filepath[256];
|
||||
|
||||
// ============================================================================
|
||||
// Function declarations -- helpers.cpp
|
||||
// ============================================================================
|
||||
|
||||
void px_fill(uint32_t* px, int bw, int bh, int x, int y, int w, int h, Color c);
|
||||
void px_hline(uint32_t* px, int bw, int bh, int x, int y, int w, Color c);
|
||||
void px_vline(uint32_t* px, int bw, int bh, int x, int y, int h, Color c);
|
||||
void px_rect(uint32_t* px, int bw, int bh, int x, int y, int w, int h, Color c);
|
||||
void px_fill_rounded(uint32_t* px, int bw, int bh, int x, int y, int w, int h, int r, Color c);
|
||||
|
||||
// ============================================================================
|
||||
// Function declarations -- drawing.cpp
|
||||
// ============================================================================
|
||||
|
||||
void canvas_put_pixel(int x, int y, Color c);
|
||||
void canvas_draw_line(int x0, int y0, int x1, int y1, Color c, int thickness);
|
||||
void canvas_draw_rect(int x0, int y0, int x1, int y1, Color c, int thickness);
|
||||
void canvas_fill_rect(int x0, int y0, int x1, int y1, Color c);
|
||||
void canvas_draw_ellipse(int x0, int y0, int x1, int y1, Color c, int thickness);
|
||||
void canvas_fill_ellipse(int x0, int y0, int x1, int y1, Color c);
|
||||
void canvas_flood_fill(int x, int y, Color fill_color);
|
||||
void canvas_draw_brush(int x, int y, Color c, int size);
|
||||
void canvas_clear(Color c);
|
||||
|
||||
// ============================================================================
|
||||
// Function declarations -- render.cpp
|
||||
// ============================================================================
|
||||
|
||||
void render(uint32_t* pixels);
|
||||
|
||||
// ============================================================================
|
||||
// Function declarations -- main.cpp
|
||||
// ============================================================================
|
||||
|
||||
void undo_push();
|
||||
void undo_do();
|
||||
void redo_do();
|
||||
int canvas_x();
|
||||
int canvas_y();
|
||||
bool screen_to_canvas(int sx, int sy, int* cx, int* cy);
|
||||
int brush_size();
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* render.cpp
|
||||
* Rendering -- toolbar, canvas, color bar, status bar
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "paint.h"
|
||||
|
||||
static const char* tool_names[TOOL_COUNT] = {
|
||||
"Pencil", "Brush", "Eraser", "Line",
|
||||
"Rect", "FRect", "Ellipse", "FEllip",
|
||||
"Fill", "Pick"
|
||||
};
|
||||
|
||||
void render(uint32_t* pixels) {
|
||||
px_fill(pixels, g_win_w, g_win_h, 0, 0, g_win_w, g_win_h, BG_COLOR);
|
||||
|
||||
// ================================================================
|
||||
// Toolbar
|
||||
// ================================================================
|
||||
px_fill(pixels, g_win_w, g_win_h, 0, 0, g_win_w, TOOLBAR_H, TOOLBAR_BG);
|
||||
px_hline(pixels, g_win_w, g_win_h, 0, TOOLBAR_H - 1, g_win_w, TB_SEP_COLOR);
|
||||
|
||||
int bx = 4;
|
||||
auto tb_btn = [&](int w, bool active, const char* label) {
|
||||
Color bg = active ? TB_BTN_ACTIVE : TB_BTN_BG;
|
||||
px_fill_rounded(pixels, g_win_w, g_win_h, bx, TB_BTN_Y, w, TB_BTN_SIZE, TB_BTN_RAD, bg);
|
||||
if (g_font && label[0]) {
|
||||
int tw = g_font->measure_text(label, FONT_SIZE);
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
bx + (w - tw) / 2, TB_BTN_Y + (TB_BTN_SIZE - FONT_SIZE) / 2,
|
||||
label, HEADER_TEXT, FONT_SIZE);
|
||||
}
|
||||
bx += w + 4;
|
||||
};
|
||||
auto tb_sep = [&]() {
|
||||
px_vline(pixels, g_win_w, g_win_h, bx, 6, TOOLBAR_H - 12, TB_SEP_COLOR);
|
||||
bx += 8;
|
||||
};
|
||||
|
||||
// Tool buttons
|
||||
for (int i = 0; i < TOOL_COUNT; i++) {
|
||||
int w = 48;
|
||||
if (i == TOOL_FILLED_RECT || i == TOOL_FILLED_ELLIPSE) w = 48;
|
||||
tb_btn(w, g_tool == (Tool)i, tool_names[i]);
|
||||
if (i == TOOL_ERASER || i == TOOL_FILLED_ELLIPSE || i == TOOL_EYEDROPPER)
|
||||
tb_sep();
|
||||
}
|
||||
|
||||
// Brush size indicator
|
||||
{
|
||||
int bs = brush_size();
|
||||
char sz_label[16];
|
||||
snprintf(sz_label, 16, "%dpx", bs);
|
||||
tb_btn(40, false, sz_label);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Color bar (below toolbar)
|
||||
// ================================================================
|
||||
int cbar_y = TOOLBAR_H;
|
||||
px_fill(pixels, g_win_w, g_win_h, 0, cbar_y, g_win_w, COLOR_BAR_H, TOOLBAR_BG);
|
||||
px_hline(pixels, g_win_w, g_win_h, 0, cbar_y + COLOR_BAR_H - 1, g_win_w, TB_SEP_COLOR);
|
||||
|
||||
// FG/BG color preview
|
||||
int preview_x = 4;
|
||||
int preview_y = cbar_y + 4;
|
||||
int preview_sz = COLOR_BAR_H - 8;
|
||||
|
||||
// BG behind (offset right-down)
|
||||
px_fill(pixels, g_win_w, g_win_h, preview_x + 10, preview_y + 6, preview_sz - 4, preview_sz - 4, g_bg_color);
|
||||
px_rect(pixels, g_win_w, g_win_h, preview_x + 10, preview_y + 6, preview_sz - 4, preview_sz - 4, Color::from_rgb(0x80, 0x80, 0x80));
|
||||
// FG in front (offset left-up)
|
||||
px_fill(pixels, g_win_w, g_win_h, preview_x, preview_y, preview_sz - 4, preview_sz - 4, g_fg_color);
|
||||
px_rect(pixels, g_win_w, g_win_h, preview_x, preview_y, preview_sz - 4, preview_sz - 4, Color::from_rgb(0x80, 0x80, 0x80));
|
||||
|
||||
// Palette swatches
|
||||
int sw_x = preview_x + 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;
|
||||
|
||||
px_fill(pixels, g_win_w, g_win_h, sx, sy, SWATCH_SIZE, sh, PALETTE[i]);
|
||||
|
||||
// Highlight if selected
|
||||
bool is_fg = (PALETTE[i].r == g_fg_color.r && PALETTE[i].g == g_fg_color.g && PALETTE[i].b == g_fg_color.b);
|
||||
bool is_bg = (PALETTE[i].r == g_bg_color.r && PALETTE[i].g == g_bg_color.g && PALETTE[i].b == g_bg_color.b);
|
||||
if (is_fg)
|
||||
px_rect(pixels, g_win_w, g_win_h, sx - 1, sy - 1, SWATCH_SIZE + 2, sh + 2, SELECT_BORDER);
|
||||
else if (is_bg)
|
||||
px_rect(pixels, g_win_w, g_win_h, sx - 1, sy - 1, SWATCH_SIZE + 2, sh + 2, Color::from_rgb(0x99, 0x99, 0x99));
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Canvas area
|
||||
// ================================================================
|
||||
int area_y = TOOLBAR_H + COLOR_BAR_H;
|
||||
|
||||
// Canvas position (centered in area if smaller, otherwise scrolled)
|
||||
int cx = canvas_x();
|
||||
int cy = canvas_y();
|
||||
|
||||
// Draw canvas border
|
||||
px_rect(pixels, g_win_w, g_win_h, cx - 1, cy - 1, g_canvas_w + 2, g_canvas_h + 2, CANVAS_BORDER);
|
||||
|
||||
// Blit canvas to screen with clipping
|
||||
int clip_x0 = 0;
|
||||
int clip_y0 = area_y;
|
||||
int clip_x1 = g_win_w;
|
||||
int clip_y1 = g_win_h - STATUS_BAR_H;
|
||||
|
||||
for (int row = 0; row < g_canvas_h; row++) {
|
||||
int sy = cy + row;
|
||||
if (sy < clip_y0 || sy >= clip_y1) continue;
|
||||
for (int col = 0; col < g_canvas_w; col++) {
|
||||
int sx = cx + col;
|
||||
if (sx < clip_x0 || sx >= clip_x1) continue;
|
||||
pixels[sy * g_win_w + sx] = g_canvas[row * g_canvas_w + col];
|
||||
}
|
||||
}
|
||||
|
||||
// Shape preview overlay (for line/rect/ellipse tools while dragging)
|
||||
if (g_drawing && (g_tool == TOOL_LINE || g_tool == TOOL_RECT || g_tool == TOOL_FILLED_RECT ||
|
||||
g_tool == TOOL_ELLIPSE || g_tool == TOOL_FILLED_ELLIPSE)) {
|
||||
// Draw preview directly on screen pixels using canvas coords mapped to screen
|
||||
int sx0 = cx + g_draw_x0;
|
||||
int sy0 = cy + g_draw_y0;
|
||||
int sx1 = cx + g_draw_x1;
|
||||
int sy1 = cy + g_draw_y1;
|
||||
|
||||
// Simple preview: draw XOR-style dashed outline
|
||||
int rx0 = sx0 < sx1 ? sx0 : sx1;
|
||||
int ry0 = sy0 < sy1 ? sy0 : sy1;
|
||||
int rx1 = sx0 > sx1 ? sx0 : sx1;
|
||||
int ry1 = sy0 > sy1 ? sy0 : sy1;
|
||||
|
||||
Color preview_c = Color::from_rgb(0x80, 0x80, 0x80);
|
||||
if (g_tool == TOOL_LINE) {
|
||||
// Dashed line preview
|
||||
int dx = sx1 - sx0, dy = sy1 - sy0;
|
||||
int adx = dx < 0 ? -dx : dx;
|
||||
int ady = dy < 0 ? -dy : dy;
|
||||
int step_x = dx < 0 ? -1 : 1;
|
||||
int step_y = dy < 0 ? -1 : 1;
|
||||
int err = adx - ady;
|
||||
int lx = sx0, ly = sy0;
|
||||
int cnt = 0;
|
||||
while (true) {
|
||||
if ((cnt / 4) % 2 == 0 &&
|
||||
lx >= clip_x0 && lx < clip_x1 && ly >= clip_y0 && ly < clip_y1)
|
||||
pixels[ly * g_win_w + lx] = preview_c.to_pixel();
|
||||
if (lx == sx1 && ly == sy1) break;
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -ady) { err -= ady; lx += step_x; }
|
||||
if (e2 < adx) { err += adx; ly += step_y; }
|
||||
cnt++;
|
||||
}
|
||||
} else {
|
||||
// Dashed rectangle/ellipse outline
|
||||
for (int x = rx0; x <= rx1; x++) {
|
||||
if (((x - rx0) / 4) % 2 == 0) {
|
||||
if (ry0 >= clip_y0 && ry0 < clip_y1 && x >= clip_x0 && x < clip_x1)
|
||||
pixels[ry0 * g_win_w + x] = preview_c.to_pixel();
|
||||
if (ry1 >= clip_y0 && ry1 < clip_y1 && x >= clip_x0 && x < clip_x1)
|
||||
pixels[ry1 * g_win_w + x] = preview_c.to_pixel();
|
||||
}
|
||||
}
|
||||
for (int y = ry0; y <= ry1; y++) {
|
||||
if (((y - ry0) / 4) % 2 == 0) {
|
||||
if (rx0 >= clip_x0 && rx0 < clip_x1 && y >= clip_y0 && y < clip_y1)
|
||||
pixels[y * g_win_w + rx0] = preview_c.to_pixel();
|
||||
if (rx1 >= clip_x0 && rx1 < clip_x1 && y >= clip_y0 && y < clip_y1)
|
||||
pixels[y * g_win_w + rx1] = preview_c.to_pixel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Status bar
|
||||
// ================================================================
|
||||
int sy = g_win_h - STATUS_BAR_H;
|
||||
px_fill(pixels, g_win_w, g_win_h, 0, sy, g_win_w, STATUS_BAR_H, STATUS_BG);
|
||||
|
||||
if (g_font) {
|
||||
char status[128];
|
||||
snprintf(status, 128, " %s - %dx%d", tool_names[g_tool], g_canvas_w, g_canvas_h);
|
||||
int sty = sy + (STATUS_BAR_H - FONT_SIZE) / 2;
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h, 6, sty, status, STATUS_TEXT, FONT_SIZE);
|
||||
|
||||
// Right side: cursor position if on canvas
|
||||
char right[64];
|
||||
if (g_drawing) {
|
||||
snprintf(right, 64, "(%d, %d) ", g_draw_x1, g_draw_y1);
|
||||
} else {
|
||||
snprintf(right, 64, "%s ", g_modified ? "Modified" : "");
|
||||
}
|
||||
int rw = g_font->measure_text(right, FONT_SIZE);
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h, g_win_w - rw - 6, sty, right, STATUS_TEXT, FONT_SIZE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* stb_truetype_impl.cpp
|
||||
* Single compilation unit for stb_truetype in MontaukOS freestanding environment
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <montauk/heap.h>
|
||||
#include <montauk/string.h>
|
||||
#include <gui/stb_math.h>
|
||||
|
||||
// Override all stb_truetype dependencies before including the implementation
|
||||
|
||||
#define STBTT_ifloor(x) ((int) stb_floor(x))
|
||||
#define STBTT_iceil(x) ((int) stb_ceil(x))
|
||||
#define STBTT_sqrt(x) stb_sqrt(x)
|
||||
#define STBTT_pow(x,y) stb_pow(x,y)
|
||||
#define STBTT_fmod(x,y) stb_fmod(x,y)
|
||||
#define STBTT_cos(x) stb_cos(x)
|
||||
#define STBTT_acos(x) stb_acos(x)
|
||||
#define STBTT_fabs(x) stb_fabs(x)
|
||||
|
||||
#define STBTT_malloc(x,u) ((void)(u), montauk::malloc(x))
|
||||
#define STBTT_free(x,u) ((void)(u), montauk::mfree(x))
|
||||
|
||||
#define STBTT_memcpy(d,s,n) montauk::memcpy(d,s,n)
|
||||
#define STBTT_memset(d,v,n) montauk::memset(d,v,n)
|
||||
|
||||
#define STBTT_strlen(x) montauk::slen(x)
|
||||
|
||||
#define STBTT_assert(x) ((void)(x))
|
||||
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include <gui/stb_truetype.h>
|
||||
Reference in New Issue
Block a user