Files
MontaukOS/template/sysroot/include/gui/draw.hpp
T

396 lines
12 KiB
C++

/*
* draw.hpp
* MontaukOS drawing primitives (lines, circles, rounded rects, cursor)
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include "gui/gui.hpp"
#include "gui/framebuffer.hpp"
namespace gui {
// Fast horizontal line
inline void draw_hline(Framebuffer& fb, int x, int y, int w, Color c) {
fb.fill_rect(x, y, w, 1, c);
}
// Fast vertical line
inline void draw_vline(Framebuffer& fb, int x, int y, int h, Color c) {
fb.fill_rect(x, y, 1, h, c);
}
// Rectangle outline
inline void draw_rect(Framebuffer& fb, int x, int y, int w, int h, Color c) {
if (w <= 0 || h <= 0) return;
draw_hline(fb, x, y, w, c);
draw_hline(fb, x, y + h - 1, w, c);
draw_vline(fb, x, y, h, c);
draw_vline(fb, x + w - 1, y, h, c);
}
// Filled rounded rectangle using corner circles
inline void fill_rounded_rect(Framebuffer& fb, int x, int y, int w, int h, int radius, Color c) {
if (radius <= 0) {
fb.fill_rect(x, y, w, h, c);
return;
}
// Clamp radius to half the smaller dimension
int max_r = gui_min(w / 2, h / 2);
if (radius > max_r) radius = max_r;
// Fill the center rectangle
fb.fill_rect(x + radius, y, w - 2 * radius, h, c);
// Fill the left and right strips (excluding corners)
fb.fill_rect(x, y + radius, radius, h - 2 * radius, c);
fb.fill_rect(x + w - radius, y + radius, radius, h - 2 * radius, c);
// Draw the four rounded corners using midpoint circle
int cx_tl = x + radius;
int cy_tl = y + radius;
int cx_tr = x + w - radius - 1;
int cy_tr = y + radius;
int cx_bl = x + radius;
int cy_bl = y + h - radius - 1;
int cx_br = x + w - radius - 1;
int cy_br = y + h - radius - 1;
int px = 0;
int py = radius;
int d = 1 - radius;
while (px <= py) {
// Top-left corner
draw_hline(fb, cx_tl - py, cy_tl - px, py - radius + radius, c);
draw_hline(fb, cx_tl - px, cy_tl - py, px, c);
// Top-right corner
draw_hline(fb, cx_tr + 1, cy_tr - px, py, c);
draw_hline(fb, cx_tr + 1, cy_tr - py, px, c);
// Bottom-left corner
draw_hline(fb, cx_bl - py, cy_bl + px, py, c);
draw_hline(fb, cx_bl - px, cy_bl + py, px, c);
// Bottom-right corner
draw_hline(fb, cx_br + 1, cy_br + px, py, c);
draw_hline(fb, cx_br + 1, cy_br + py, px, c);
if (d < 0) {
d += 2 * px + 3;
} else {
d += 2 * (px - py) + 5;
py--;
}
px++;
}
}
// Filled circle (midpoint algorithm)
inline void fill_circle(Framebuffer& fb, int cx, int cy, int r, Color c) {
if (r <= 0) return;
int x = 0;
int y = r;
int d = 1 - r;
// Draw initial horizontal lines
draw_hline(fb, cx - r, cy, 2 * r + 1, c);
while (x < y) {
if (d < 0) {
d += 2 * x + 3;
} else {
d += 2 * (x - y) + 5;
y--;
draw_hline(fb, cx - x, cy + y + 1, 2 * x + 1, c);
draw_hline(fb, cx - x, cy - y - 1, 2 * x + 1, c);
}
x++;
draw_hline(fb, cx - y, cy + x, 2 * y + 1, c);
draw_hline(fb, cx - y, cy - x, 2 * y + 1, c);
}
}
// Circle outline (midpoint algorithm)
inline void draw_circle(Framebuffer& fb, int cx, int cy, int r, Color c) {
if (r <= 0) {
fb.put_pixel(cx, cy, c);
return;
}
int x = 0;
int y = r;
int d = 1 - r;
while (x <= y) {
fb.put_pixel(cx + x, cy + y, c);
fb.put_pixel(cx - x, cy + y, c);
fb.put_pixel(cx + x, cy - y, c);
fb.put_pixel(cx - x, cy - y, c);
fb.put_pixel(cx + y, cy + x, c);
fb.put_pixel(cx - y, cy + x, c);
fb.put_pixel(cx + y, cy - x, c);
fb.put_pixel(cx - y, cy - x, c);
if (d < 0) {
d += 2 * x + 3;
} else {
d += 2 * (x - y) + 5;
y--;
}
x++;
}
}
// Bresenham line drawing
inline void draw_line(Framebuffer& fb, int x0, int y0, int x1, int y1, Color c) {
int dx = gui_abs(x1 - x0);
int dy = gui_abs(y1 - y0);
int sx = x0 < x1 ? 1 : -1;
int sy = y0 < y1 ? 1 : -1;
int err = dx - dy;
for (;;) {
fb.put_pixel(x0, y0, c);
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
// Drop shadow: offset darker rectangles below/right
inline void draw_shadow(Framebuffer& fb, int x, int y, int w, int h, int offset, Color shadow_color) {
// Bottom shadow strip
fb.fill_rect_alpha(x + offset, y + h, w, offset, shadow_color);
// Right shadow strip
fb.fill_rect_alpha(x + w, y + offset, offset, h, shadow_color);
// Corner
fb.fill_rect_alpha(x + w, y + h, offset, offset, shadow_color);
}
// 16x16 mouse cursor bitmaps
// Outline (black, where design has '1')
static constexpr uint16_t cursor_outline[16] = {
0x8000, // 1000000000000000
0xC000, // 1100000000000000
0xA000, // 1010000000000000
0x9000, // 1001000000000000
0x8800, // 1000100000000000
0x8400, // 1000010000000000
0x8200, // 1000001000000000
0x8100, // 1000000100000000
0x8080, // 1000000010000000
0x8040, // 1000000001000000
0x8780, // 1000011110000000 (changed: row 10 = 1 2 2 2 2 2 1 1 1 1)
0x9200, // 1001001000000000 (row 11 = 1 2 2 1 2 2 1)
0xA900, // 1010100100000000 (row 12 = 1 2 1 0 1 2 2 1)
0xC900, // 1100100100000000 (row 13 = 1 1 0 0 1 2 2 1)
0x8480, // 1000010010000000 (row 14 = 1 0 0 0 0 1 2 2 1)
0x0700, // 0000011100000000 (row 15 = 0 0 0 0 0 1 1 1)
};
// Fill (white, where design has '2')
static constexpr uint16_t cursor_fill[16] = {
0x0000, // row 0: no fill
0x0000, // row 1: no fill
0x4000, // row 2: 0100000000000000
0x6000, // row 3: 0110000000000000
0x7000, // row 4: 0111000000000000
0x7800, // row 5: 0111100000000000
0x7C00, // row 6: 0111110000000000
0x7E00, // row 7: 0111111000000000
0x7F00, // row 8: 0111111100000000
0x7F80, // row 9: 0111111110000000
0x7800, // row 10: 0111100000000000 (fill only in positions 1-5)
0x6C00, // row 11: 0110110000000000 (fill at positions 1,2 and 4,5)
0x4600, // row 12: 0100011000000000 (fill at position 1 and 5,6)
0x0600, // row 13: 0000011000000000 (fill at positions 5,6)
0x0300, // row 14: 0000001100000000 (fill at positions 6,7)
0x0000, // row 15: no fill
};
// Resize cursor: horizontal double arrow (left-right)
static constexpr uint16_t cursor_h_resize_outline[16] = {
0x0000, 0x0000, 0x0000, 0x0000,
0x0820, // 0000100000100000
0x1830, // 0001100000110000
0x3FF8, // 0011111111111000
0x7FFC, // 0111111111111100
0x3FF8, // 0011111111111000
0x1830, // 0001100000110000
0x0820, // 0000100000100000
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
};
static constexpr uint16_t cursor_h_resize_fill[16] = {
0x0000, 0x0000, 0x0000, 0x0000,
0x0000,
0x0000,
0x1FF0, // 0001111111110000
0x3FF8, // 0011111111111000
0x1FF0, // 0001111111110000
0x0000,
0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
};
// Resize cursor: vertical double arrow (up-down)
static constexpr uint16_t cursor_v_resize_outline[16] = {
0x0000, 0x0000,
0x0200, // 0000001000000000
0x0700, // 0000011100000000
0x0F80, // 0000111110000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0F80, // 0000111110000000
0x0700, // 0000011100000000
0x0200, // 0000001000000000
0x0000, 0x0000,
};
static constexpr uint16_t cursor_v_resize_fill[16] = {
0x0000, 0x0000, 0x0000,
0x0200, // 0000001000000000
0x0700, // 0000011100000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0700, // 0000011100000000
0x0200, // 0000001000000000
0x0000, 0x0000, 0x0000,
};
// Resize cursor: diagonal NW-SE double arrow
static constexpr uint16_t cursor_nwse_resize_outline[16] = {
0x0000, 0x0000,
0x7C00, // 0111110000000000
0x6000, // 0110000000000000
0x5000, // 0101000000000000
0x4800, // 0100100000000000
0x2400, // 0010010000000000
0x1200, // 0001001000000000
0x0900, // 0000100100000000
0x0480, // 0000010010000000
0x0240, // 0000001001000000
0x0140, // 0000000101000000
0x00C0, // 0000000011000000
0x07C0, // 0000011111000000
0x0000, 0x0000,
};
static constexpr uint16_t cursor_nwse_resize_fill[16] = {
0x0000, 0x0000, 0x0000,
0x1C00, // 0001110000000000
0x2800, // 0010100000000000
0x0400, // 0000010000000000
0x0200, // 0000001000000000
0x0100, // 0000000100000000
0x0080, // 0000000010000000
0x0040, // 0000000001000000
0x0280, // 0000001010000000
0x0380, // 0000001110000000
0x0000, 0x0000, 0x0000, 0x0000,
};
// Resize cursor: diagonal NE-SW double arrow
static constexpr uint16_t cursor_nesw_resize_outline[16] = {
0x0000, 0x0000,
0x07C0, // 0000011111000000
0x00C0, // 0000000011000000
0x0140, // 0000000101000000
0x0240, // 0000001001000000
0x0480, // 0000010010000000
0x0900, // 0000100100000000
0x1200, // 0001001000000000
0x2400, // 0010010000000000
0x4800, // 0100100000000000
0x5000, // 0101000000000000
0x6000, // 0110000000000000
0x7C00, // 0111110000000000
0x0000, 0x0000,
};
static constexpr uint16_t cursor_nesw_resize_fill[16] = {
0x0000, 0x0000, 0x0000,
0x0380, // 0000001110000000
0x0280, // 0000001010000000
0x0040, // 0000000001000000
0x0080, // 0000000010000000
0x0100, // 0000000100000000
0x0200, // 0000001000000000
0x0400, // 0000010000000000
0x2800, // 0010100000000000
0x1C00, // 0001110000000000
0x0000, 0x0000, 0x0000, 0x0000,
};
enum CursorStyle {
CURSOR_ARROW = 0,
CURSOR_RESIZE_H, // left-right
CURSOR_RESIZE_V, // up-down
CURSOR_RESIZE_NWSE, // diagonal NW-SE
CURSOR_RESIZE_NESW, // diagonal NE-SW
};
// Draw the mouse cursor at (x, y)
inline void draw_cursor(Framebuffer& fb, int x, int y, CursorStyle style = CURSOR_ARROW) {
const uint16_t* outline_data = cursor_outline;
const uint16_t* fill_data = cursor_fill;
int ox = 0, oy = 0; // hotspot offset for centered cursors
switch (style) {
case CURSOR_RESIZE_H:
outline_data = cursor_h_resize_outline;
fill_data = cursor_h_resize_fill;
ox = -8; oy = -8;
break;
case CURSOR_RESIZE_V:
outline_data = cursor_v_resize_outline;
fill_data = cursor_v_resize_fill;
ox = -8; oy = -8;
break;
case CURSOR_RESIZE_NWSE:
outline_data = cursor_nwse_resize_outline;
fill_data = cursor_nwse_resize_fill;
ox = -8; oy = -8;
break;
case CURSOR_RESIZE_NESW:
outline_data = cursor_nesw_resize_outline;
fill_data = cursor_nesw_resize_fill;
ox = -8; oy = -8;
break;
default:
break;
}
Color black = colors::BLACK;
Color white = colors::WHITE;
for (int row = 0; row < 16; row++) {
uint16_t outline = outline_data[row];
uint16_t fill = fill_data[row];
for (int col = 0; col < 16; col++) {
uint16_t mask = (uint16_t)(0x8000 >> col);
if (outline & mask) {
fb.put_pixel(x + ox + col, y + oy + row, black);
} else if (fill & mask) {
fb.put_pixel(x + ox + col, y + oy + row, white);
}
}
}
}
} // namespace gui