feat: new Window Server helpers and app migrations
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* standalone.hpp
|
||||
* MontaukOS helpers for standalone Window Server apps
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <montauk/syscall.h>
|
||||
#include "gui/canvas.hpp"
|
||||
#include "gui/svg.hpp"
|
||||
#include "gui/truetype.hpp"
|
||||
|
||||
namespace gui {
|
||||
|
||||
struct WsWindow {
|
||||
int id;
|
||||
uint32_t* pixels;
|
||||
int width;
|
||||
int height;
|
||||
int scale_factor;
|
||||
bool closed;
|
||||
|
||||
WsWindow()
|
||||
: id(-1), pixels(nullptr), width(0), height(0), scale_factor(1), closed(false) {}
|
||||
|
||||
bool create(const char* title, int w, int h) {
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create(title, w, h, &wres) < 0 || wres.id < 0)
|
||||
return false;
|
||||
|
||||
id = wres.id;
|
||||
pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
width = w;
|
||||
height = h;
|
||||
scale_factor = montauk::win_getscale();
|
||||
closed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int poll(Montauk::WinEvent* ev) {
|
||||
if (id < 0 || closed) return -1;
|
||||
|
||||
int r = montauk::win_poll(id, ev);
|
||||
if (r <= 0) return r;
|
||||
|
||||
if (ev->type == 2) {
|
||||
width = ev->resize.w;
|
||||
height = ev->resize.h;
|
||||
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(id, width, height);
|
||||
} else if (ev->type == 3) {
|
||||
closed = true;
|
||||
} else if (ev->type == 4) {
|
||||
scale_factor = ev->scale.scale;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
Canvas canvas() const {
|
||||
return Canvas(pixels, width, height);
|
||||
}
|
||||
|
||||
void present() const {
|
||||
if (id >= 0)
|
||||
montauk::win_present(id);
|
||||
}
|
||||
|
||||
void set_cursor(int cursor) const {
|
||||
if (id >= 0)
|
||||
montauk::win_setcursor(id, cursor);
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (id >= 0)
|
||||
montauk::win_destroy(id);
|
||||
id = -1;
|
||||
pixels = nullptr;
|
||||
width = 0;
|
||||
height = 0;
|
||||
closed = true;
|
||||
}
|
||||
};
|
||||
|
||||
inline int text_width(TrueTypeFont* font, const char* text, int size) {
|
||||
if (font && font->valid)
|
||||
return font->measure_text(text, size);
|
||||
int len = 0;
|
||||
while (text && text[len]) len++;
|
||||
return len * FONT_WIDTH;
|
||||
}
|
||||
|
||||
inline int text_height(TrueTypeFont* font, int size) {
|
||||
if (font && font->valid) {
|
||||
GlyphCache* cache = font->get_cache(size);
|
||||
if (cache)
|
||||
return cache->ascent - cache->descent;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
inline void draw_text(Canvas& c, TrueTypeFont* font, int x, int y,
|
||||
const char* text, Color color, int size) {
|
||||
if (!text || !text[0]) return;
|
||||
if (font && font->valid)
|
||||
font->draw_to_buffer(c.pixels, c.w, c.h, x, y, text, color, size);
|
||||
}
|
||||
|
||||
inline void fill_circle(Canvas& c, int cx, int cy, int r, Color color) {
|
||||
if (r <= 0) return;
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
if (dx * dx + dy * dy <= r * r)
|
||||
c.put_pixel(cx + dx, cy + dy, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void draw_button(Canvas& c, TrueTypeFont* font, int x, int y, int w, int h,
|
||||
const char* label, Color bg, Color fg, int radius,
|
||||
int size = 16) {
|
||||
c.fill_rounded_rect(x, y, w, h, radius, bg);
|
||||
int tw = text_width(font, label, size);
|
||||
int th = text_height(font, size);
|
||||
draw_text(c, font, x + (w - tw) / 2, y + (h - th) / 2, label, fg, size);
|
||||
}
|
||||
|
||||
inline void draw_icon(Canvas& c, int x, int y, const SvgIcon& icon) {
|
||||
c.icon(x, y, icon);
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
+63
-145
@@ -8,6 +8,7 @@
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <gui/gui.hpp>
|
||||
#include <gui/standalone.hpp>
|
||||
#include <gui/truetype.hpp>
|
||||
|
||||
extern "C" {
|
||||
@@ -84,90 +85,6 @@ static int g_scroll = 0;
|
||||
static char g_status[80];
|
||||
static uint64_t g_status_time = 0;
|
||||
|
||||
// ============================================================================
|
||||
// Pixel helpers
|
||||
// ============================================================================
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static 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 dy = y + row;
|
||||
if (dy < 0 || dy >= bh) continue;
|
||||
for (int col = 0; col < w; col++) {
|
||||
int dx = x + col;
|
||||
if (dx < 0 || dx >= bw) continue;
|
||||
bool skip = false;
|
||||
int cx2, cy2;
|
||||
if (col < r && row < r) { cx2 = r - col - 1; cy2 = r - row - 1; if (cx2*cx2 + cy2*cy2 >= r*r) skip = true; }
|
||||
else if (col >= w - r && row < r) { cx2 = col - (w - r); cy2 = r - row - 1; if (cx2*cx2 + cy2*cy2 >= r*r) skip = true; }
|
||||
else if (col < r && row >= h - r) { cx2 = r - col - 1; cy2 = row - (h - r); if (cx2*cx2 + cy2*cy2 >= r*r) skip = true; }
|
||||
else if (col >= w - r && row >= h - r) { cx2 = col - (w - r); cy2 = row - (h - r); if (cx2*cx2 + cy2*cy2 >= r*r) skip = true; }
|
||||
if (!skip) px[dy * bw + dx] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static void px_circle(uint32_t* px, int bw, int bh,
|
||||
int cx, int cy, int r, Color c) {
|
||||
uint32_t v = c.to_pixel();
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
int py = cy + dy;
|
||||
if (py < 0 || py >= bh) continue;
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
int ppx = cx + dx;
|
||||
if (ppx < 0 || ppx >= bw) continue;
|
||||
if (dx * dx + dy * dy <= r * r)
|
||||
px[py * bw + ppx] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void px_text(uint32_t* px, int bw, int bh,
|
||||
int x, int y, const char* text, Color c, int size = FONT_SIZE) {
|
||||
if (g_font)
|
||||
g_font->draw_to_buffer(px, bw, bh, x, y, text, c, size);
|
||||
}
|
||||
|
||||
static int text_w(const char* text, int size = FONT_SIZE) {
|
||||
return g_font ? g_font->measure_text(text, size) : 0;
|
||||
}
|
||||
|
||||
static int font_h(int size = FONT_SIZE) {
|
||||
if (!g_font) return 14;
|
||||
auto* cache = g_font->get_cache(size);
|
||||
return cache->ascent - cache->descent;
|
||||
}
|
||||
|
||||
static void px_button(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, int h,
|
||||
const char* label, Color bg, Color fg, int r = BTN_RAD) {
|
||||
px_fill_rounded(px, bw, bh, x, y, w, h, r, bg);
|
||||
int tw = text_w(label, FONT_SIZE_SM);
|
||||
int fh = font_h(FONT_SIZE_SM);
|
||||
px_text(px, bw, bh, x + (w - tw) / 2, y + (h - fh) / 2 - 1, label, fg, FONT_SIZE_SM);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Bluetooth helpers
|
||||
// ============================================================================
|
||||
@@ -283,33 +200,33 @@ static const char* rssi_bar(int8_t rssi) {
|
||||
// Render
|
||||
// ============================================================================
|
||||
|
||||
static void render(uint32_t* pixels) {
|
||||
static void render(Canvas& canvas) {
|
||||
int W = g_win_w;
|
||||
int H = g_win_h;
|
||||
|
||||
// Background
|
||||
px_fill(pixels, W, H, 0, 0, W, H, BG_COLOR);
|
||||
canvas.fill(BG_COLOR);
|
||||
|
||||
// Tab bar
|
||||
int tab_y = 0;
|
||||
px_fill(pixels, W, H, 0, tab_y, W, TAB_H, TAB_BG);
|
||||
px_hline(pixels, W, H, 0, tab_y + TAB_H - 1, W, BORDER);
|
||||
canvas.fill_rect(0, tab_y, W, TAB_H, TAB_BG);
|
||||
canvas.hline(0, tab_y + TAB_H - 1, W, BORDER);
|
||||
|
||||
int tab_w = W / 2;
|
||||
const char* tab_labels[] = { "Devices", "Scan" };
|
||||
for (int i = 0; i < 2; i++) {
|
||||
// Active tab gets white background to merge with content area
|
||||
if (g_tab == i)
|
||||
px_fill(pixels, W, H, i * tab_w, tab_y, tab_w, TAB_H, BG_COLOR);
|
||||
canvas.fill_rect(i * tab_w, tab_y, tab_w, TAB_H, BG_COLOR);
|
||||
|
||||
Color tc = (g_tab == i) ? TAB_ACTIVE : TAB_INACTIVE;
|
||||
int tw = text_w(tab_labels[i], FONT_SIZE);
|
||||
int tw = text_width(g_font, tab_labels[i], FONT_SIZE);
|
||||
int tx = i * tab_w + (tab_w - tw) / 2;
|
||||
px_text(pixels, W, H, tx, tab_y + (TAB_H - font_h(FONT_SIZE)) / 2,
|
||||
draw_text(canvas, g_font, tx, tab_y + (TAB_H - text_height(g_font, FONT_SIZE)) / 2,
|
||||
tab_labels[i], tc, FONT_SIZE);
|
||||
// Active indicator underline
|
||||
if (g_tab == i) {
|
||||
px_fill(pixels, W, H, i * tab_w + 4, tab_y + TAB_H - 3, tab_w - 8, 3, TAB_ACTIVE);
|
||||
canvas.fill_rect(i * tab_w + 4, tab_y + TAB_H - 3, tab_w - 8, 3, TAB_ACTIVE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,8 +240,9 @@ static void render(uint32_t* pixels) {
|
||||
// Connected devices list
|
||||
if (g_device_count == 0) {
|
||||
const char* msg = "No connected devices";
|
||||
int mw = text_w(msg, FONT_SIZE);
|
||||
px_text(pixels, W, H, (W - mw) / 2, content_y + list_h / 2 - font_h(FONT_SIZE) / 2,
|
||||
int mw = text_width(g_font, msg, FONT_SIZE);
|
||||
draw_text(canvas, g_font,
|
||||
(W - mw) / 2, content_y + list_h / 2 - text_height(g_font, FONT_SIZE) / 2,
|
||||
msg, DIM_TEXT, FONT_SIZE);
|
||||
} else {
|
||||
for (int i = 0; i < g_device_count; i++) {
|
||||
@@ -333,32 +251,31 @@ static void render(uint32_t* pixels) {
|
||||
|
||||
// Hover highlight
|
||||
if (g_hover_row == i)
|
||||
px_fill(pixels, W, H, 0, ry, W, ROW_H, ROW_HOVER);
|
||||
canvas.fill_rect(0, ry, W, ROW_H, ROW_HOVER);
|
||||
|
||||
// Connected indicator dot
|
||||
Color dot_c = g_devices[i].connected ? GREEN : DIM_TEXT;
|
||||
px_circle(pixels, W, H, PAD + 6, ry + ROW_H / 2, 5, dot_c);
|
||||
fill_circle(canvas, PAD + 6, ry + ROW_H / 2, 5, dot_c);
|
||||
|
||||
// Device name
|
||||
px_text(pixels, W, H, PAD + 20, ry + 8,
|
||||
g_device_names[i], TEXT_COLOR, FONT_SIZE);
|
||||
draw_text(canvas, g_font, PAD + 20, ry + 8, g_device_names[i], TEXT_COLOR, FONT_SIZE);
|
||||
|
||||
// Address below name
|
||||
char addr_str[24];
|
||||
format_addr(addr_str, sizeof(addr_str), g_devices[i].bdAddr);
|
||||
px_text(pixels, W, H, PAD + 20, ry + 8 + font_h(FONT_SIZE) + 2,
|
||||
draw_text(canvas, g_font, PAD + 20, ry + 8 + text_height(g_font, FONT_SIZE) + 2,
|
||||
addr_str, DIM_TEXT, FONT_SIZE_SM);
|
||||
|
||||
// Disconnect button
|
||||
if (g_devices[i].connected) {
|
||||
int bx = W - PAD - BTN_W;
|
||||
int by = ry + (ROW_H - BTN_H) / 2;
|
||||
px_button(pixels, W, H, bx, by, BTN_W, BTN_H,
|
||||
"Disconnect", RED, WHITE);
|
||||
draw_button(canvas, g_font, bx, by, BTN_W, BTN_H,
|
||||
"Disconnect", RED, WHITE, BTN_RAD, FONT_SIZE_SM);
|
||||
}
|
||||
|
||||
// Divider
|
||||
px_hline(pixels, W, H, PAD, ry + ROW_H - 1, W - 2 * PAD, BORDER);
|
||||
canvas.hline(PAD, ry + ROW_H - 1, W - 2 * PAD, BORDER);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -368,11 +285,11 @@ static void render(uint32_t* pixels) {
|
||||
int scan_btn_w = 100;
|
||||
int scan_btn_x = (W - scan_btn_w) / 2;
|
||||
if (g_scanning) {
|
||||
px_button(pixels, W, H, scan_btn_x, scan_btn_y, scan_btn_w, BTN_H,
|
||||
"Scanning...", BORDER, DIM_TEXT);
|
||||
draw_button(canvas, g_font, scan_btn_x, scan_btn_y, scan_btn_w, BTN_H,
|
||||
"Scanning...", BORDER, DIM_TEXT, BTN_RAD, FONT_SIZE_SM);
|
||||
} else {
|
||||
px_button(pixels, W, H, scan_btn_x, scan_btn_y, scan_btn_w, BTN_H,
|
||||
"Scan", ACCENT, WHITE);
|
||||
draw_button(canvas, g_font, scan_btn_x, scan_btn_y, scan_btn_w, BTN_H,
|
||||
"Scan", ACCENT, WHITE, BTN_RAD, FONT_SIZE_SM);
|
||||
}
|
||||
|
||||
int list_top = scan_btn_y + BTN_H + 12;
|
||||
@@ -380,8 +297,9 @@ static void render(uint32_t* pixels) {
|
||||
|
||||
if (g_scan_count == 0 && !g_scanning) {
|
||||
const char* msg = "Press Scan to find devices";
|
||||
int mw = text_w(msg, FONT_SIZE);
|
||||
px_text(pixels, W, H, (W - mw) / 2, list_top + scan_list_h / 2 - font_h(FONT_SIZE) / 2,
|
||||
int mw = text_width(g_font, msg, FONT_SIZE);
|
||||
draw_text(canvas, g_font,
|
||||
(W - mw) / 2, list_top + scan_list_h / 2 - text_height(g_font, FONT_SIZE) / 2,
|
||||
msg, DIM_TEXT, FONT_SIZE);
|
||||
} else {
|
||||
for (int i = 0; i < g_scan_count; i++) {
|
||||
@@ -390,16 +308,15 @@ static void render(uint32_t* pixels) {
|
||||
|
||||
// Hover highlight
|
||||
if (g_hover_row == i)
|
||||
px_fill(pixels, W, H, 0, ry, W, ROW_H, ROW_HOVER);
|
||||
canvas.fill_rect(0, ry, W, ROW_H, ROW_HOVER);
|
||||
|
||||
// Device type indicator
|
||||
const char* type_str = device_class_str(g_scan[i].classOfDevice);
|
||||
px_circle(pixels, W, H, PAD + 6, ry + ROW_H / 2, 5, ACCENT);
|
||||
fill_circle(canvas, PAD + 6, ry + ROW_H / 2, 5, ACCENT);
|
||||
|
||||
// Device name (or address if unnamed)
|
||||
const char* display_name = g_scan[i].name[0] ? g_scan[i].name : "Unknown Device";
|
||||
px_text(pixels, W, H, PAD + 20, ry + 4,
|
||||
display_name, TEXT_COLOR, FONT_SIZE);
|
||||
draw_text(canvas, g_font, PAD + 20, ry + 4, display_name, TEXT_COLOR, FONT_SIZE);
|
||||
|
||||
// Type + signal below name
|
||||
char detail[80];
|
||||
@@ -407,7 +324,7 @@ static void render(uint32_t* pixels) {
|
||||
format_addr(addr_str, sizeof(addr_str), g_scan[i].bdAddr);
|
||||
snprintf(detail, sizeof(detail), "%s | %s | %s",
|
||||
type_str, addr_str, rssi_bar(g_scan[i].rssi));
|
||||
px_text(pixels, W, H, PAD + 20, ry + 4 + font_h(FONT_SIZE) + 2,
|
||||
draw_text(canvas, g_font, PAD + 20, ry + 4 + text_height(g_font, FONT_SIZE) + 2,
|
||||
detail, DIM_TEXT, FONT_SIZE_SM);
|
||||
|
||||
// Connect/Disconnect button
|
||||
@@ -415,27 +332,27 @@ static void render(uint32_t* pixels) {
|
||||
int bx = W - PAD - BTN_W;
|
||||
int by = ry + (ROW_H - BTN_H) / 2;
|
||||
if (conn) {
|
||||
px_button(pixels, W, H, bx, by, BTN_W, BTN_H,
|
||||
"Disconnect", RED, WHITE);
|
||||
draw_button(canvas, g_font, bx, by, BTN_W, BTN_H,
|
||||
"Disconnect", RED, WHITE, BTN_RAD, FONT_SIZE_SM);
|
||||
} else {
|
||||
px_button(pixels, W, H, bx, by, BTN_W, BTN_H,
|
||||
"Connect", ACCENT, WHITE);
|
||||
draw_button(canvas, g_font, bx, by, BTN_W, BTN_H,
|
||||
"Connect", ACCENT, WHITE, BTN_RAD, FONT_SIZE_SM);
|
||||
}
|
||||
|
||||
// Divider
|
||||
px_hline(pixels, W, H, PAD, ry + ROW_H - 1, W - 2 * PAD, BORDER);
|
||||
canvas.hline(PAD, ry + ROW_H - 1, W - 2 * PAD, BORDER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Status bar
|
||||
int status_y = H - STATUS_H;
|
||||
px_fill(pixels, W, H, 0, status_y, W, STATUS_H, TAB_BG);
|
||||
px_hline(pixels, W, H, 0, status_y, W, BORDER);
|
||||
canvas.fill_rect(0, status_y, W, STATUS_H, TAB_BG);
|
||||
canvas.hline(0, status_y, W, BORDER);
|
||||
|
||||
// Status bar content: adapter info on the left, status message on the right
|
||||
uint64_t now = montauk::get_milliseconds();
|
||||
int sy = status_y + (STATUS_H - font_h(FONT_SIZE_SM)) / 2;
|
||||
int sy = status_y + (STATUS_H - text_height(g_font, FONT_SIZE_SM)) / 2;
|
||||
|
||||
if (g_adapter_ok) {
|
||||
char adapter_info[96];
|
||||
@@ -444,15 +361,15 @@ static void render(uint32_t* pixels) {
|
||||
const char* name = g_adapter.name[0] ? g_adapter.name : "Adapter";
|
||||
snprintf(adapter_info, sizeof(adapter_info), "%s | %s | %d connected",
|
||||
name, addr_str, g_device_count);
|
||||
px_text(pixels, W, H, PAD, sy, adapter_info, DIM_TEXT, FONT_SIZE_SM);
|
||||
draw_text(canvas, g_font, PAD, sy, adapter_info, DIM_TEXT, FONT_SIZE_SM);
|
||||
} else {
|
||||
px_text(pixels, W, H, PAD, sy, "No adapter found", RED, FONT_SIZE_SM);
|
||||
draw_text(canvas, g_font, PAD, sy, "No adapter found", RED, FONT_SIZE_SM);
|
||||
}
|
||||
|
||||
// Temporary status message (right-aligned, fades after 5s)
|
||||
if (g_status[0] && (now - g_status_time) < 5000) {
|
||||
int sw = text_w(g_status, FONT_SIZE_SM);
|
||||
px_text(pixels, W, H, W - PAD - sw, sy, g_status, DIM_TEXT, FONT_SIZE_SM);
|
||||
int sw = text_width(g_font, g_status, FONT_SIZE_SM);
|
||||
draw_text(canvas, g_font, W - PAD - sw, sy, g_status, DIM_TEXT, FONT_SIZE_SM);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,34 +486,34 @@ extern "C" void _start() {
|
||||
refresh_devices();
|
||||
g_status[0] = 0;
|
||||
|
||||
// Create window
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create("Bluetooth", WIN_W, WIN_H, &wres) < 0 || wres.id < 0)
|
||||
WsWindow win;
|
||||
if (!win.create("Bluetooth", WIN_W, WIN_H))
|
||||
montauk::exit(1);
|
||||
|
||||
int win_id = wres.id;
|
||||
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
Canvas canvas = win.canvas();
|
||||
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
render(canvas);
|
||||
win.present();
|
||||
|
||||
// Periodic refresh timer
|
||||
uint64_t last_refresh = montauk::get_milliseconds();
|
||||
|
||||
while (true) {
|
||||
Montauk::WinEvent ev;
|
||||
int r = montauk::win_poll(win_id, &ev);
|
||||
int r = win.poll(&ev);
|
||||
|
||||
if (r < 0) break;
|
||||
|
||||
// Handle pending scan (non-blocking: kick it off after render)
|
||||
if (g_scanning) {
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
finish_scan();
|
||||
refresh_devices();
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -607,8 +524,9 @@ extern "C" void _start() {
|
||||
refresh_adapter();
|
||||
refresh_devices();
|
||||
last_refresh = now;
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
}
|
||||
montauk::sleep_ms(16);
|
||||
continue;
|
||||
@@ -620,9 +538,8 @@ extern "C" void _start() {
|
||||
|
||||
// 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);
|
||||
g_win_w = win.width;
|
||||
g_win_h = win.height;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
@@ -660,11 +577,12 @@ extern "C" void _start() {
|
||||
}
|
||||
|
||||
if (redraw) {
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
}
|
||||
}
|
||||
|
||||
montauk::win_destroy(win_id);
|
||||
win.destroy();
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "devexplorer.h"
|
||||
#include <gui/standalone.hpp>
|
||||
|
||||
// ============================================================================
|
||||
// Global state definitions
|
||||
@@ -203,16 +204,14 @@ extern "C" void _start() {
|
||||
g_state.dev_count = montauk::devlist(g_state.devs, MAX_DEVS);
|
||||
g_state.last_poll_ms = montauk::get_milliseconds();
|
||||
|
||||
// Create window
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create("Devices", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
|
||||
WsWindow win;
|
||||
if (!win.create("Devices", INIT_W, INIT_H))
|
||||
montauk::exit(1);
|
||||
|
||||
int win_id = wres.id;
|
||||
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
uint32_t* pixels = win.pixels;
|
||||
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
win.present();
|
||||
|
||||
while (true) {
|
||||
Montauk::WinEvent ev;
|
||||
@@ -249,11 +248,11 @@ extern "C" void _start() {
|
||||
}
|
||||
|
||||
// Poll main window
|
||||
int r = montauk::win_poll(win_id, &ev);
|
||||
int r = win.poll(&ev);
|
||||
|
||||
if (r < 0) break;
|
||||
if (r == 0) {
|
||||
if (redraw_main) { render(pixels); montauk::win_present(win_id); }
|
||||
if (redraw_main) { render(pixels); win.present(); }
|
||||
if (redraw_detail) { render_disk_detail(); montauk::win_present(g_state.detail.win_id); }
|
||||
if (!redraw_main && !redraw_detail) montauk::sleep_ms(16);
|
||||
continue;
|
||||
@@ -263,9 +262,9 @@ extern "C" void _start() {
|
||||
|
||||
// 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);
|
||||
g_win_w = win.width;
|
||||
g_win_h = win.height;
|
||||
pixels = win.pixels;
|
||||
redraw_main = true;
|
||||
}
|
||||
|
||||
@@ -294,11 +293,11 @@ extern "C" void _start() {
|
||||
}
|
||||
}
|
||||
|
||||
if (redraw_main) { render(pixels); montauk::win_present(win_id); }
|
||||
if (redraw_main) { render(pixels); win.present(); }
|
||||
if (redraw_detail) { render_disk_detail(); montauk::win_present(g_state.detail.win_id); }
|
||||
}
|
||||
|
||||
close_disk_detail();
|
||||
montauk::win_destroy(win_id);
|
||||
win.destroy();
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
+12
-13
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "disks.h"
|
||||
#include <gui/standalone.hpp>
|
||||
|
||||
// ============================================================================
|
||||
// Global state definitions
|
||||
@@ -341,16 +342,14 @@ extern "C" void _start() {
|
||||
|
||||
disktool_refresh();
|
||||
|
||||
// Create window
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create("Disks", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
|
||||
WsWindow win;
|
||||
if (!win.create("Disks", INIT_W, INIT_H))
|
||||
montauk::exit(1);
|
||||
|
||||
int win_id = wres.id;
|
||||
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
uint32_t* pixels = win.pixels;
|
||||
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
win.present();
|
||||
|
||||
while (true) {
|
||||
Montauk::WinEvent ev;
|
||||
@@ -400,12 +399,12 @@ extern "C" void _start() {
|
||||
}
|
||||
|
||||
// Poll main window
|
||||
int r = montauk::win_poll(win_id, &ev);
|
||||
int r = win.poll(&ev);
|
||||
|
||||
if (r < 0) break;
|
||||
if (r == 0) {
|
||||
// Even with no main event, dialog may have triggered redraws
|
||||
if (redraw_main) { render(pixels); montauk::win_present(win_id); }
|
||||
if (redraw_main) { render(pixels); win.present(); }
|
||||
if (redraw_dlg) { render_format_window(); montauk::win_present(g_state.fmt_dlg.win_id); }
|
||||
if (redraw_np) { render_newpart_window(); montauk::win_present(g_state.np_dlg.win_id); }
|
||||
if (!redraw_main && !redraw_dlg && !redraw_np) montauk::sleep_ms(16);
|
||||
@@ -416,9 +415,9 @@ extern "C" void _start() {
|
||||
|
||||
// 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);
|
||||
g_win_w = win.width;
|
||||
g_win_h = win.height;
|
||||
pixels = win.pixels;
|
||||
redraw_main = true;
|
||||
}
|
||||
|
||||
@@ -448,13 +447,13 @@ extern "C" void _start() {
|
||||
}
|
||||
}
|
||||
|
||||
if (redraw_main) { render(pixels); montauk::win_present(win_id); }
|
||||
if (redraw_main) { render(pixels); win.present(); }
|
||||
if (redraw_dlg) { render_format_window(); montauk::win_present(g_state.fmt_dlg.win_id); }
|
||||
if (redraw_np) { render_newpart_window(); montauk::win_present(g_state.np_dlg.win_id); }
|
||||
}
|
||||
|
||||
close_newpart_dialog();
|
||||
close_format_dialog();
|
||||
montauk::win_destroy(win_id);
|
||||
win.destroy();
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <gui/gui.hpp>
|
||||
#include <gui/standalone.hpp>
|
||||
#include <gui/truetype.hpp>
|
||||
|
||||
using namespace gui;
|
||||
@@ -75,21 +76,6 @@ static TrueTypeFont* g_ui_font = nullptr;
|
||||
static stbtt_fontinfo g_preview_info;
|
||||
static uint8_t* g_preview_data = nullptr;
|
||||
|
||||
// ============================================================================
|
||||
// Helpers
|
||||
// ============================================================================
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static const char* basename(const char* path) {
|
||||
const char* last = path;
|
||||
for (const char* p = path; *p; p++) {
|
||||
@@ -248,14 +234,12 @@ static int calc_content_height() {
|
||||
// Rendering
|
||||
// ============================================================================
|
||||
|
||||
static void render(uint32_t* pixels) {
|
||||
px_fill(pixels, g_win_w, g_win_h, 0, 0, g_win_w, g_win_h, BG_COLOR);
|
||||
static void render(Canvas& canvas) {
|
||||
canvas.fill(BG_COLOR);
|
||||
|
||||
if (!g_load_ok) {
|
||||
if (g_ui_font) {
|
||||
g_ui_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
PADDING, g_win_h / 2 - 8, "Error: could not load font", ERR_COLOR, 15);
|
||||
}
|
||||
draw_text(canvas, g_ui_font, PADDING, g_win_h / 2 - 8,
|
||||
"Error: could not load font", ERR_COLOR, 15);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -279,27 +263,25 @@ static void render(uint32_t* pixels) {
|
||||
char label[16] = {};
|
||||
int_to_str(label, PREVIEW_SIZES[s]);
|
||||
str_append(label, "px", 16);
|
||||
g_ui_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
PADDING, y, label, LABEL_COLOR, UI_FONT_SZ);
|
||||
draw_text(canvas, g_ui_font, PADDING, y, label, LABEL_COLOR, UI_FONT_SZ);
|
||||
y += g_ui_font->get_line_height(UI_FONT_SZ) + 4;
|
||||
}
|
||||
|
||||
draw_cached_text(pixels, g_win_w, g_win_h,
|
||||
draw_cached_text(canvas.pixels, canvas.w, canvas.h,
|
||||
PADDING, y, PANGRAM, TEXT_COLOR, s);
|
||||
y += lh;
|
||||
|
||||
draw_cached_text(pixels, g_win_w, g_win_h,
|
||||
draw_cached_text(canvas.pixels, canvas.w, canvas.h,
|
||||
PADDING, y, UPPER, TEXT_COLOR, s);
|
||||
y += lh;
|
||||
|
||||
draw_cached_text(pixels, g_win_w, g_win_h,
|
||||
draw_cached_text(canvas.pixels, canvas.w, canvas.h,
|
||||
PADDING, y, LOWER_NUM, TEXT_COLOR, s);
|
||||
y += lh;
|
||||
|
||||
y += SECTION_GAP;
|
||||
if (y >= 0 && y < g_win_h) {
|
||||
px_fill(pixels, g_win_w, g_win_h,
|
||||
PADDING, y, g_win_w - 2 * PADDING, 1, SEPARATOR);
|
||||
canvas.fill_rect(PADDING, y, g_win_w - 2 * PADDING, 1, SEPARATOR);
|
||||
}
|
||||
y += 1 + SECTION_GAP;
|
||||
}
|
||||
@@ -312,8 +294,8 @@ static void render(uint32_t* pixels) {
|
||||
int thumb_h = (view_h * view_h) / g_content_h;
|
||||
if (thumb_h < 20) thumb_h = 20;
|
||||
int thumb_y = (g_scroll_y * (view_h - thumb_h)) / max_scroll;
|
||||
px_fill(pixels, g_win_w, g_win_h, sb_x, 0, 4, view_h, SCROLLBAR_BG);
|
||||
px_fill(pixels, g_win_w, g_win_h, sb_x, thumb_y, 4, thumb_h, SCROLLBAR_FG);
|
||||
canvas.fill_rect(sb_x, 0, 4, view_h, SCROLLBAR_BG);
|
||||
canvas.fill_rect(sb_x, thumb_y, 4, thumb_h, SCROLLBAR_FG);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,20 +360,17 @@ extern "C" void _start() {
|
||||
|
||||
g_content_h = calc_content_height();
|
||||
|
||||
// Create window
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create(title, INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
|
||||
WsWindow win;
|
||||
if (!win.create(title, INIT_W, INIT_H))
|
||||
montauk::exit(1);
|
||||
|
||||
int win_id = wres.id;
|
||||
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
Canvas canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
|
||||
while (true) {
|
||||
Montauk::WinEvent ev;
|
||||
int r = montauk::win_poll(win_id, &ev);
|
||||
int r = win.poll(&ev);
|
||||
|
||||
if (r < 0) break;
|
||||
|
||||
@@ -403,12 +382,12 @@ extern "C" void _start() {
|
||||
if (ev.type == 3) break;
|
||||
|
||||
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);
|
||||
g_win_w = win.width;
|
||||
g_win_h = win.height;
|
||||
clamp_scroll();
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -427,8 +406,9 @@ extern "C" void _start() {
|
||||
|
||||
if (redraw) {
|
||||
clamp_scroll();
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -437,13 +417,14 @@ extern "C" void _start() {
|
||||
if (ev.mouse.scroll != 0) {
|
||||
g_scroll_y -= ev.mouse.scroll * SCROLL_STEP;
|
||||
clamp_scroll();
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
montauk::win_destroy(win_id);
|
||||
win.destroy();
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <gui/gui.hpp>
|
||||
#include <gui/standalone.hpp>
|
||||
#include <gui/truetype.hpp>
|
||||
|
||||
extern "C" {
|
||||
@@ -90,60 +91,6 @@ static int tb_fit_x0, tb_fit_x1;
|
||||
static int tb_actual_x0, tb_actual_x1;
|
||||
static int tb_open_x0, tb_open_x1;
|
||||
|
||||
// ============================================================================
|
||||
// Helpers
|
||||
// ============================================================================
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static 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 dy = y + row;
|
||||
if (dy < 0 || dy >= bh) continue;
|
||||
for (int col = 0; col < w; col++) {
|
||||
int dx = x + col;
|
||||
if (dx < 0 || dx >= bw) continue;
|
||||
// Check corners
|
||||
bool skip = false;
|
||||
int cx, cy;
|
||||
if (col < r && row < r) { cx = r - col - 1; cy = r - row - 1; if (cx*cx + cy*cy >= r*r) skip = true; }
|
||||
else if (col >= w - r && row < r) { cx = col - (w - r); cy = r - row - 1; if (cx*cx + cy*cy >= r*r) skip = true; }
|
||||
else if (col < r && row >= h - r) { cx = r - col - 1; cy = row - (h - r); if (cx*cx + cy*cy >= r*r) skip = true; }
|
||||
else if (col >= w - r && row >= h - r) { cx = col - (w - r); cy = row - (h - r); if (cx*cx + cy*cy >= r*r) skip = true; }
|
||||
if (!skip) px[dy * bw + dx] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const char* basename(const char* path) {
|
||||
const char* last = path;
|
||||
for (const char* p = path; *p; p++) {
|
||||
@@ -313,12 +260,12 @@ static void zoom_actual() {
|
||||
// Rendering
|
||||
// ============================================================================
|
||||
|
||||
static void render(uint32_t* pixels) {
|
||||
static void render(Canvas& canvas) {
|
||||
int vp_y0 = TOOLBAR_H;
|
||||
int vp_y1 = g_win_h - STATUS_BAR_H;
|
||||
|
||||
// Fill viewport background
|
||||
px_fill(pixels, g_win_w, g_win_h, 0, vp_y0, g_win_w, vp_y1 - vp_y0, BG_COLOR);
|
||||
canvas.fill_rect(0, vp_y0, g_win_w, vp_y1 - vp_y0, BG_COLOR);
|
||||
|
||||
// Draw scaled image
|
||||
if (g_image && g_load_ok) {
|
||||
@@ -339,7 +286,7 @@ static void render(uint32_t* pixels) {
|
||||
if (src_y >= g_img_h) src_y = g_img_h - 1;
|
||||
|
||||
const uint32_t* src_row = &g_image[src_y * g_img_w];
|
||||
uint32_t* dst_row = &pixels[dy * g_win_w];
|
||||
uint32_t* dst_row = &canvas.pixels[dy * g_win_w];
|
||||
|
||||
for (int dx = draw_x0; dx < draw_x1; dx++) {
|
||||
int src_x = (int)((dx - g_pan_x) * inv_zoom);
|
||||
@@ -376,30 +323,29 @@ static void render(uint32_t* pixels) {
|
||||
}
|
||||
}
|
||||
} else if (!g_load_ok && g_font) {
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
20, vp_y0 + (vp_y1 - vp_y0) / 2 - 8, g_status, ERR_COLOR, 15);
|
||||
draw_text(canvas, g_font, 20, vp_y0 + (vp_y1 - vp_y0) / 2 - 8, g_status, ERR_COLOR, 15);
|
||||
}
|
||||
|
||||
// ---- 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, GRID_COLOR);
|
||||
canvas.fill_rect(0, 0, g_win_w, TOOLBAR_H, TOOLBAR_BG);
|
||||
canvas.hline(0, TOOLBAR_H - 1, g_win_w, GRID_COLOR);
|
||||
|
||||
int bx = 4;
|
||||
auto tb_btn = [&](int w, bool active, const char* label, int& x0_out, int& x1_out) {
|
||||
x0_out = bx;
|
||||
x1_out = bx + w;
|
||||
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);
|
||||
canvas.fill_rounded_rect(bx, TB_BTN_Y, w, TB_BTN_SIZE, TB_BTN_RAD, bg);
|
||||
if (g_font && label[0]) {
|
||||
int tw = g_font->measure_text(label, HEADER_FONT);
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
draw_text(canvas, g_font,
|
||||
bx + (w - tw) / 2, TB_BTN_Y + (TB_BTN_SIZE - HEADER_FONT) / 2,
|
||||
label, HEADER_TEXT, HEADER_FONT);
|
||||
}
|
||||
bx += w + 4;
|
||||
};
|
||||
auto tb_sep = [&]() {
|
||||
px_vline(pixels, g_win_w, g_win_h, bx, 6, TOOLBAR_H - 12, TB_SEP_COLOR);
|
||||
canvas.vline(bx, 6, TOOLBAR_H - 12, TB_SEP_COLOR);
|
||||
bx += 8;
|
||||
};
|
||||
|
||||
@@ -418,8 +364,7 @@ static void render(uint32_t* pixels) {
|
||||
snprintf(zoom_label, 16, "%d%%", pct);
|
||||
if (g_font) {
|
||||
int tw = g_font->measure_text(zoom_label, HEADER_FONT);
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
bx, TB_BTN_Y + (TB_BTN_SIZE - HEADER_FONT) / 2,
|
||||
draw_text(canvas, g_font, bx, TB_BTN_Y + (TB_BTN_SIZE - HEADER_FONT) / 2,
|
||||
zoom_label, HEADER_TEXT, HEADER_FONT);
|
||||
bx += tw + 8;
|
||||
}
|
||||
@@ -433,18 +378,16 @@ static void render(uint32_t* pixels) {
|
||||
|
||||
// ---- 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);
|
||||
canvas.fill_rect(0, sy, g_win_w, STATUS_BAR_H, STATUS_BG);
|
||||
|
||||
if (g_font) {
|
||||
int sty = sy + (STATUS_BAR_H - HEADER_FONT) / 2;
|
||||
|
||||
// Left: filename and dimensions
|
||||
if (g_load_ok && g_status[0]) {
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h, 8, sty,
|
||||
g_status, STATUS_TEXT, HEADER_FONT);
|
||||
draw_text(canvas, g_font, 8, sty, g_status, STATUS_TEXT, HEADER_FONT);
|
||||
} else if (!g_load_ok) {
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h, 8, sty,
|
||||
"No image loaded", STATUS_TEXT, HEADER_FONT);
|
||||
draw_text(canvas, g_font, 8, sty, "No image loaded", STATUS_TEXT, HEADER_FONT);
|
||||
}
|
||||
|
||||
// Right: zoom level
|
||||
@@ -453,8 +396,7 @@ static void render(uint32_t* pixels) {
|
||||
int pct = (int)(g_zoom * 100 + 0.5f);
|
||||
snprintf(right, 32, "%d%% ", pct);
|
||||
int rw = g_font->measure_text(right, HEADER_FONT);
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
g_win_w - rw - 8, sty, right, STATUS_TEXT, HEADER_FONT);
|
||||
draw_text(canvas, g_font, g_win_w - rw - 8, sty, right, STATUS_TEXT, HEADER_FONT);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -528,13 +470,11 @@ extern "C" void _start() {
|
||||
}
|
||||
}
|
||||
|
||||
// Create window
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create(title, INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
|
||||
WsWindow win;
|
||||
if (!win.create(title, INIT_W, INIT_H))
|
||||
montauk::exit(1);
|
||||
|
||||
int win_id = wres.id;
|
||||
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
Canvas canvas = win.canvas();
|
||||
|
||||
// Auto-fit on load if image is larger than viewport
|
||||
if (g_load_ok) {
|
||||
@@ -545,13 +485,13 @@ extern "C" void _start() {
|
||||
}
|
||||
}
|
||||
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
render(canvas);
|
||||
win.present();
|
||||
|
||||
// Event loop
|
||||
while (true) {
|
||||
Montauk::WinEvent ev;
|
||||
int r = montauk::win_poll(win_id, &ev);
|
||||
int r = win.poll(&ev);
|
||||
|
||||
if (r < 0) break;
|
||||
if (r == 0) { montauk::sleep_ms(16); continue; }
|
||||
@@ -560,12 +500,12 @@ extern "C" void _start() {
|
||||
|
||||
// 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);
|
||||
g_win_w = win.width;
|
||||
g_win_h = win.height;
|
||||
if (g_load_ok) clamp_pan();
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -646,12 +586,13 @@ extern "C" void _start() {
|
||||
}
|
||||
|
||||
if (redraw) {
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
}
|
||||
}
|
||||
|
||||
if (g_image) montauk::mfree(g_image);
|
||||
montauk::win_destroy(win_id);
|
||||
win.destroy();
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "installer.h"
|
||||
#include <gui/standalone.hpp>
|
||||
|
||||
// ============================================================================
|
||||
// Global state definitions
|
||||
@@ -21,6 +22,7 @@ TrueTypeFont* g_font = nullptr;
|
||||
uint32_t* g_pixels = nullptr;
|
||||
uint32_t* g_backbuf = nullptr;
|
||||
int g_win_id = -1;
|
||||
static WsWindow g_window;
|
||||
|
||||
void apply_scale(int scale) {
|
||||
switch (scale) {
|
||||
@@ -56,10 +58,10 @@ void add_log(const char* msg) {
|
||||
}
|
||||
|
||||
void flush_ui() {
|
||||
if (g_pixels && g_backbuf && g_win_id >= 0) {
|
||||
if (g_pixels && g_backbuf && g_window.id >= 0) {
|
||||
render(g_backbuf);
|
||||
montauk::memcpy(g_pixels, g_backbuf, g_win_w * g_win_h * 4);
|
||||
montauk::win_present(g_win_id);
|
||||
g_window.present();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,20 +275,16 @@ extern "C" void _start() {
|
||||
|
||||
installer_refresh_disks();
|
||||
|
||||
// Create window
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create("Install MontaukOS", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
|
||||
if (!g_window.create("Install MontaukOS", INIT_W, INIT_H))
|
||||
montauk::exit(1);
|
||||
|
||||
int win_id = wres.id;
|
||||
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
g_pixels = pixels;
|
||||
g_win_id = win_id;
|
||||
g_pixels = g_window.pixels;
|
||||
g_win_id = g_window.id;
|
||||
g_backbuf = (uint32_t*)montauk::malloc(g_win_w * g_win_h * 4);
|
||||
|
||||
render(g_backbuf);
|
||||
montauk::memcpy(pixels, g_backbuf, g_win_w * g_win_h * 4);
|
||||
montauk::win_present(win_id);
|
||||
montauk::memcpy(g_pixels, g_backbuf, g_win_w * g_win_h * 4);
|
||||
g_window.present();
|
||||
|
||||
bool install_triggered = false;
|
||||
|
||||
@@ -294,7 +292,7 @@ extern "C" void _start() {
|
||||
Montauk::WinEvent ev;
|
||||
bool redraw = false;
|
||||
|
||||
int r = montauk::win_poll(win_id, &ev);
|
||||
int r = g_window.poll(&ev);
|
||||
if (r < 0) break;
|
||||
|
||||
if (r == 0) {
|
||||
@@ -317,15 +315,14 @@ extern "C" void _start() {
|
||||
if (ev.type == 3) break;
|
||||
|
||||
if (ev.type == 4) {
|
||||
apply_scale(ev.scale.scale);
|
||||
apply_scale(g_window.scale_factor);
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
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);
|
||||
g_pixels = pixels;
|
||||
g_win_w = g_window.width;
|
||||
g_win_h = g_window.height;
|
||||
g_pixels = g_window.pixels;
|
||||
montauk::mfree(g_backbuf);
|
||||
g_backbuf = (uint32_t*)montauk::malloc(g_win_w * g_win_h * 4);
|
||||
redraw = true;
|
||||
@@ -348,6 +345,6 @@ extern "C" void _start() {
|
||||
if (redraw) flush_ui();
|
||||
}
|
||||
|
||||
montauk::win_destroy(win_id);
|
||||
g_window.destroy();
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
+12
-13
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "paint.h"
|
||||
#include <gui/standalone.hpp>
|
||||
|
||||
// ============================================================================
|
||||
// Global state definitions
|
||||
@@ -261,20 +262,18 @@ extern "C" void _start() {
|
||||
// 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)
|
||||
WsWindow win;
|
||||
if (!win.create("Paint", INIT_W, INIT_H))
|
||||
montauk::exit(1);
|
||||
|
||||
int win_id = wres.id;
|
||||
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
uint32_t* pixels = win.pixels;
|
||||
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
win.present();
|
||||
|
||||
while (true) {
|
||||
Montauk::WinEvent ev;
|
||||
int r = montauk::win_poll(win_id, &ev);
|
||||
int r = win.poll(&ev);
|
||||
|
||||
if (r < 0) break;
|
||||
|
||||
@@ -288,12 +287,12 @@ extern "C" void _start() {
|
||||
|
||||
// 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);
|
||||
g_win_w = win.width;
|
||||
g_win_h = win.height;
|
||||
pixels = win.pixels;
|
||||
clamp_scroll();
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
win.present();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -477,10 +476,10 @@ extern "C" void _start() {
|
||||
|
||||
if (redraw) {
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
win.present();
|
||||
}
|
||||
}
|
||||
|
||||
montauk::win_destroy(win_id);
|
||||
win.destroy();
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "pdfviewer.h"
|
||||
#include <gui/standalone.hpp>
|
||||
|
||||
// ============================================================================
|
||||
// Global state definitions
|
||||
@@ -131,20 +132,18 @@ extern "C" void _start() {
|
||||
snprintf(title, 64, "%s - PDF Viewer", fname);
|
||||
}
|
||||
|
||||
// Create window
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create(title, INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
|
||||
WsWindow win;
|
||||
if (!win.create(title, INIT_W, INIT_H))
|
||||
montauk::exit(1);
|
||||
|
||||
int win_id = wres.id;
|
||||
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
uint32_t* pixels = win.pixels;
|
||||
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
win.present();
|
||||
|
||||
while (true) {
|
||||
Montauk::WinEvent ev;
|
||||
int r = montauk::win_poll(win_id, &ev);
|
||||
int r = win.poll(&ev);
|
||||
|
||||
if (r < 0) break;
|
||||
|
||||
@@ -158,12 +157,12 @@ extern "C" void _start() {
|
||||
|
||||
// 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);
|
||||
g_win_w = win.width;
|
||||
g_win_h = win.height;
|
||||
pixels = win.pixels;
|
||||
clamp_scroll();
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
win.present();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -319,11 +318,11 @@ extern "C" void _start() {
|
||||
|
||||
if (redraw) {
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
win.present();
|
||||
}
|
||||
}
|
||||
|
||||
free_pdf();
|
||||
montauk::win_destroy(win_id);
|
||||
win.destroy();
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "spreadsheet.h"
|
||||
#include <gui/standalone.hpp>
|
||||
|
||||
// ============================================================================
|
||||
// Global state definitions
|
||||
@@ -255,20 +256,18 @@ extern "C" void _start() {
|
||||
snprintf(title, 64, "%s - Spreadsheet", fname);
|
||||
}
|
||||
|
||||
// Create window
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create(title, INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
|
||||
WsWindow win;
|
||||
if (!win.create(title, INIT_W, INIT_H))
|
||||
montauk::exit(1);
|
||||
|
||||
int win_id = wres.id;
|
||||
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
uint32_t* pixels = win.pixels;
|
||||
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
win.present();
|
||||
|
||||
while (true) {
|
||||
Montauk::WinEvent ev;
|
||||
int r = montauk::win_poll(win_id, &ev);
|
||||
int r = win.poll(&ev);
|
||||
|
||||
if (r < 0) break;
|
||||
|
||||
@@ -282,12 +281,12 @@ extern "C" void _start() {
|
||||
|
||||
// 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);
|
||||
g_win_w = win.width;
|
||||
g_win_h = win.height;
|
||||
pixels = win.pixels;
|
||||
clamp_scroll();
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
win.present();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -636,7 +635,7 @@ extern "C" void _start() {
|
||||
}
|
||||
}
|
||||
}
|
||||
montauk::win_setcursor(win_id, cursor);
|
||||
win.set_cursor(cursor);
|
||||
}
|
||||
|
||||
// Fill handle: drag in progress
|
||||
@@ -783,10 +782,10 @@ extern "C" void _start() {
|
||||
|
||||
if (redraw) {
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
win.present();
|
||||
}
|
||||
}
|
||||
|
||||
montauk::win_destroy(win_id);
|
||||
win.destroy();
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
+77
-136
@@ -8,6 +8,7 @@
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <gui/gui.hpp>
|
||||
#include <gui/standalone.hpp>
|
||||
#include <gui/truetype.hpp>
|
||||
|
||||
extern "C" {
|
||||
@@ -27,21 +28,17 @@ static constexpr int FONT_SIZE = 16;
|
||||
static constexpr int FONT_SIZE_LG = 28;
|
||||
|
||||
static constexpr int SLIDER_X = 24;
|
||||
static constexpr int SLIDER_W = WIN_W - 48;
|
||||
static constexpr int SLIDER_Y = 78;
|
||||
static constexpr int SLIDER_H = 8;
|
||||
static constexpr int KNOB_R = 10;
|
||||
|
||||
static constexpr int BTN_W = 48;
|
||||
static constexpr int BTN_H = 28;
|
||||
static constexpr int BTN_Y = 118;
|
||||
static constexpr int BTN_RAD = 6;
|
||||
|
||||
static constexpr Color BG_COLOR = Color::from_rgb(0xFF, 0xFF, 0xFF);
|
||||
static constexpr Color TEXT_COLOR = Color::from_rgb(0x22, 0x22, 0x22);
|
||||
static constexpr Color DIM_TEXT = Color::from_rgb(0x66, 0x66, 0x66);
|
||||
static constexpr Color ACCENT = Color::from_rgb(0x36, 0x7B, 0xF0);
|
||||
static constexpr Color ACCENT_HOVER = Color::from_rgb(0x2A, 0x62, 0xC8);
|
||||
static constexpr Color TRACK_BG = Color::from_rgb(0xDD, 0xDD, 0xDD);
|
||||
static constexpr Color WHITE = Color::from_rgb(0xFF, 0xFF, 0xFF);
|
||||
static constexpr Color MUTE_COLOR = Color::from_rgb(0xCC, 0x33, 0x33);
|
||||
@@ -51,95 +48,13 @@ static constexpr Color MUTE_COLOR = Color::from_rgb(0xCC, 0x33, 0x33);
|
||||
// ============================================================================
|
||||
|
||||
static TrueTypeFont* g_font = nullptr;
|
||||
static int g_win_w = WIN_W;
|
||||
static int g_win_h = WIN_H;
|
||||
static int g_volume = 80;
|
||||
static bool g_muted = false;
|
||||
static int g_pre_mute_vol = 80;
|
||||
static bool g_dragging = false;
|
||||
|
||||
// ============================================================================
|
||||
// Pixel helpers (same pattern as disks app)
|
||||
// ============================================================================
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static 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 dy = y + row;
|
||||
if (dy < 0 || dy >= bh) continue;
|
||||
for (int col = 0; col < w; col++) {
|
||||
int dx = x + col;
|
||||
if (dx < 0 || dx >= bw) continue;
|
||||
bool skip = false;
|
||||
int cx, cy;
|
||||
if (col < r && row < r) { cx = r - col - 1; cy = r - row - 1; if (cx*cx + cy*cy >= r*r) skip = true; }
|
||||
else if (col >= w - r && row < r) { cx = col - (w - r); cy = r - row - 1; if (cx*cx + cy*cy >= r*r) skip = true; }
|
||||
else if (col < r && row >= h - r) { cx = r - col - 1; cy = row - (h - r); if (cx*cx + cy*cy >= r*r) skip = true; }
|
||||
else if (col >= w - r && row >= h - r) { cx = col - (w - r); cy = row - (h - r); if (cx*cx + cy*cy >= r*r) skip = true; }
|
||||
if (!skip) px[dy * bw + dx] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void px_circle(uint32_t* px, int bw, int bh,
|
||||
int cx, int cy, int r, Color c) {
|
||||
uint32_t v = c.to_pixel();
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
int py = cy + dy;
|
||||
if (py < 0 || py >= bh) continue;
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
int ppx = cx + dx;
|
||||
if (ppx < 0 || ppx >= bw) continue;
|
||||
if (dx * dx + dy * dy <= r * r)
|
||||
px[py * bw + ppx] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void px_text(uint32_t* px, int bw, int bh,
|
||||
int x, int y, const char* text, Color c, int size = FONT_SIZE) {
|
||||
if (g_font)
|
||||
g_font->draw_to_buffer(px, bw, bh, x, y, text, c, size);
|
||||
}
|
||||
|
||||
static int text_w(const char* text, int size = FONT_SIZE) {
|
||||
return g_font ? g_font->measure_text(text, size) : 0;
|
||||
}
|
||||
|
||||
static int font_h(int size = FONT_SIZE) {
|
||||
if (!g_font) return 16;
|
||||
auto* cache = g_font->get_cache(size);
|
||||
return cache->ascent - cache->descent;
|
||||
}
|
||||
|
||||
static void px_button(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, int h,
|
||||
const char* label, Color bg, Color fg, int r) {
|
||||
px_fill_rounded(px, bw, bh, x, y, w, h, r, bg);
|
||||
int tw = text_w(label);
|
||||
int fh = font_h();
|
||||
px_text(px, bw, bh, x + (w - tw) / 2, y + (h - fh) / 2, label, fg);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Volume helpers
|
||||
// ============================================================================
|
||||
@@ -156,62 +71,80 @@ static void refresh_volume() {
|
||||
if (v >= 0) g_volume = v;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Layout helpers
|
||||
// ============================================================================
|
||||
|
||||
static int slider_w() {
|
||||
return g_win_w - 48;
|
||||
}
|
||||
|
||||
static int button_y() {
|
||||
return g_win_h - BTN_H - 18;
|
||||
}
|
||||
|
||||
static void get_button_rects(Rect* minus_btn, Rect* plus_btn, Rect* mute_btn) {
|
||||
int total_btn_w = BTN_W * 2 + 60 + 12 * 2;
|
||||
int bx = (g_win_w - total_btn_w) / 2;
|
||||
int by = button_y();
|
||||
if (minus_btn) *minus_btn = {bx, by, BTN_W, BTN_H};
|
||||
bx += BTN_W + 12;
|
||||
if (plus_btn) *plus_btn = {bx, by, BTN_W, BTN_H};
|
||||
bx += BTN_W + 12;
|
||||
if (mute_btn) *mute_btn = {bx, by, 60, BTN_H};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Render
|
||||
// ============================================================================
|
||||
|
||||
static void render(uint32_t* pixels) {
|
||||
int fh_lg = font_h(FONT_SIZE_LG);
|
||||
static void render(Canvas& canvas) {
|
||||
int fh_lg = text_height(g_font, FONT_SIZE_LG);
|
||||
int slider_fill_w = slider_w();
|
||||
|
||||
// Background
|
||||
px_fill(pixels, WIN_W, WIN_H, 0, 0, WIN_W, WIN_H, BG_COLOR);
|
||||
canvas.fill(BG_COLOR);
|
||||
|
||||
// Volume percentage (large, centered)
|
||||
char vol_str[8];
|
||||
snprintf(vol_str, sizeof(vol_str), "%d%%", g_muted ? 0 : g_volume);
|
||||
int vw = text_w(vol_str, FONT_SIZE_LG);
|
||||
int vw = text_width(g_font, vol_str, FONT_SIZE_LG);
|
||||
Color vol_color = g_muted ? MUTE_COLOR : ACCENT;
|
||||
px_text(pixels, WIN_W, WIN_H, (WIN_W - vw) / 2, 20, vol_str, vol_color, FONT_SIZE_LG);
|
||||
draw_text(canvas, g_font, (g_win_w - vw) / 2, 20, vol_str, vol_color, FONT_SIZE_LG);
|
||||
|
||||
// "Muted" label
|
||||
if (g_muted) {
|
||||
const char* muted_label = "Muted";
|
||||
int mw = text_w(muted_label);
|
||||
px_text(pixels, WIN_W, WIN_H, (WIN_W - mw) / 2,
|
||||
20 + fh_lg + 4, muted_label, MUTE_COLOR);
|
||||
int mw = text_width(g_font, muted_label, FONT_SIZE);
|
||||
draw_text(canvas, g_font, (g_win_w - mw) / 2,
|
||||
20 + fh_lg + 4, muted_label, MUTE_COLOR, FONT_SIZE);
|
||||
}
|
||||
|
||||
// Slider track
|
||||
px_fill_rounded(pixels, WIN_W, WIN_H, SLIDER_X, SLIDER_Y, SLIDER_W, SLIDER_H, 4, TRACK_BG);
|
||||
canvas.fill_rounded_rect(SLIDER_X, SLIDER_Y, slider_fill_w, SLIDER_H, 4, TRACK_BG);
|
||||
|
||||
// Filled portion
|
||||
int display_vol = g_muted ? 0 : g_volume;
|
||||
int fill_w = (display_vol * SLIDER_W) / 100;
|
||||
int fill_w = ((g_muted ? 0 : g_volume) * slider_fill_w) / 100;
|
||||
if (fill_w > 0)
|
||||
px_fill_rounded(pixels, WIN_W, WIN_H, SLIDER_X, SLIDER_Y, fill_w, SLIDER_H, 4, ACCENT);
|
||||
canvas.fill_rounded_rect(SLIDER_X, SLIDER_Y, fill_w, SLIDER_H, 4, ACCENT);
|
||||
|
||||
// Knob
|
||||
int knob_x = SLIDER_X + fill_w;
|
||||
int knob_y = SLIDER_Y + SLIDER_H / 2;
|
||||
px_circle(pixels, WIN_W, WIN_H, knob_x, knob_y, KNOB_R, ACCENT);
|
||||
px_circle(pixels, WIN_W, WIN_H, knob_x, knob_y, KNOB_R - 3, WHITE);
|
||||
fill_circle(canvas, knob_x, knob_y, KNOB_R, ACCENT);
|
||||
fill_circle(canvas, knob_x, knob_y, KNOB_R - 3, WHITE);
|
||||
|
||||
// Buttons: [-] and [+] and [Mute]
|
||||
int total_btn_w = BTN_W * 2 + 60 + 12 * 2; // minus, plus, mute + gaps
|
||||
int bx = (WIN_W - total_btn_w) / 2;
|
||||
|
||||
px_button(pixels, WIN_W, WIN_H, bx, BTN_Y, BTN_W, BTN_H,
|
||||
"-", Color::from_rgb(0xE0, 0xE0, 0xE0), TEXT_COLOR, BTN_RAD);
|
||||
bx += BTN_W + 12;
|
||||
|
||||
px_button(pixels, WIN_W, WIN_H, bx, BTN_Y, BTN_W, BTN_H,
|
||||
"+", Color::from_rgb(0xE0, 0xE0, 0xE0), TEXT_COLOR, BTN_RAD);
|
||||
bx += BTN_W + 12;
|
||||
|
||||
Rect minus_btn, plus_btn, mute_btn;
|
||||
get_button_rects(&minus_btn, &plus_btn, &mute_btn);
|
||||
draw_button(canvas, g_font, minus_btn.x, minus_btn.y, minus_btn.w, minus_btn.h,
|
||||
"-", Color::from_rgb(0xE0, 0xE0, 0xE0), TEXT_COLOR, BTN_RAD, FONT_SIZE);
|
||||
draw_button(canvas, g_font, plus_btn.x, plus_btn.y, plus_btn.w, plus_btn.h,
|
||||
"+", Color::from_rgb(0xE0, 0xE0, 0xE0), TEXT_COLOR, BTN_RAD, FONT_SIZE);
|
||||
Color mute_bg = g_muted ? MUTE_COLOR : Color::from_rgb(0xE0, 0xE0, 0xE0);
|
||||
Color mute_fg = g_muted ? WHITE : TEXT_COLOR;
|
||||
px_button(pixels, WIN_W, WIN_H, bx, BTN_Y, 60, BTN_H,
|
||||
"Mute", mute_bg, mute_fg, BTN_RAD);
|
||||
draw_button(canvas, g_font, mute_btn.x, mute_btn.y, mute_btn.w, mute_btn.h,
|
||||
"Mute", mute_bg, mute_fg, BTN_RAD, FONT_SIZE);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -219,7 +152,7 @@ static void render(uint32_t* pixels) {
|
||||
// ============================================================================
|
||||
|
||||
static int vol_from_slider_x(int mx) {
|
||||
int v = ((mx - SLIDER_X) * 100) / SLIDER_W;
|
||||
int v = ((mx - SLIDER_X) * 100) / slider_w();
|
||||
if (v < 0) v = 0;
|
||||
if (v > 100) v = 100;
|
||||
return v;
|
||||
@@ -228,7 +161,7 @@ static int vol_from_slider_x(int mx) {
|
||||
static bool handle_click(int mx, int my) {
|
||||
// Slider area (generous vertical hit zone)
|
||||
if (my >= SLIDER_Y - KNOB_R && my <= SLIDER_Y + SLIDER_H + KNOB_R &&
|
||||
mx >= SLIDER_X - KNOB_R && mx <= SLIDER_X + SLIDER_W + KNOB_R) {
|
||||
mx >= SLIDER_X - KNOB_R && mx <= SLIDER_X + slider_w() + KNOB_R) {
|
||||
g_muted = false;
|
||||
apply_volume(vol_from_slider_x(mx));
|
||||
g_dragging = true;
|
||||
@@ -236,28 +169,27 @@ static bool handle_click(int mx, int my) {
|
||||
}
|
||||
|
||||
// Buttons
|
||||
int total_btn_w = BTN_W * 2 + 60 + 12 * 2;
|
||||
int bx = (WIN_W - total_btn_w) / 2;
|
||||
Rect minus_btn, plus_btn, mute_btn;
|
||||
get_button_rects(&minus_btn, &plus_btn, &mute_btn);
|
||||
|
||||
if (my >= BTN_Y && my < BTN_Y + BTN_H) {
|
||||
int by = button_y();
|
||||
if (my >= by && my < by + BTN_H) {
|
||||
// [-] button
|
||||
if (mx >= bx && mx < bx + BTN_W) {
|
||||
if (minus_btn.contains(mx, my)) {
|
||||
g_muted = false;
|
||||
apply_volume(g_volume - 5);
|
||||
return true;
|
||||
}
|
||||
bx += BTN_W + 12;
|
||||
|
||||
// [+] button
|
||||
if (mx >= bx && mx < bx + BTN_W) {
|
||||
if (plus_btn.contains(mx, my)) {
|
||||
g_muted = false;
|
||||
apply_volume(g_volume + 5);
|
||||
return true;
|
||||
}
|
||||
bx += BTN_W + 12;
|
||||
|
||||
// [Mute] button
|
||||
if (mx >= bx && mx < bx + 60) {
|
||||
if (mute_btn.contains(mx, my)) {
|
||||
if (g_muted) {
|
||||
g_muted = false;
|
||||
apply_volume(g_pre_mute_vol);
|
||||
@@ -291,19 +223,22 @@ extern "C" void _start() {
|
||||
refresh_volume();
|
||||
|
||||
// Create window
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create("Volume", WIN_W, WIN_H, &wres) < 0 || wres.id < 0)
|
||||
WsWindow win;
|
||||
if (!win.create("Volume", WIN_W, WIN_H))
|
||||
montauk::exit(1);
|
||||
|
||||
int win_id = wres.id;
|
||||
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
g_win_w = win.width;
|
||||
g_win_h = win.height;
|
||||
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
{
|
||||
Canvas canvas = win.canvas();
|
||||
render(canvas);
|
||||
}
|
||||
win.present();
|
||||
|
||||
while (true) {
|
||||
while (win.id >= 0 && !win.closed) {
|
||||
Montauk::WinEvent ev;
|
||||
int r = montauk::win_poll(win_id, &ev);
|
||||
int r = win.poll(&ev);
|
||||
|
||||
if (r < 0) break;
|
||||
if (r == 0) { montauk::sleep_ms(16); continue; }
|
||||
@@ -311,6 +246,11 @@ extern "C" void _start() {
|
||||
bool redraw = false;
|
||||
|
||||
if (ev.type == 3) break; // close
|
||||
if (ev.type == 2) {
|
||||
g_win_w = win.width;
|
||||
g_win_h = win.height;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
// Keyboard
|
||||
if (ev.type == 0 && ev.key.pressed) {
|
||||
@@ -358,11 +298,12 @@ extern "C" void _start() {
|
||||
}
|
||||
|
||||
if (redraw) {
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
Canvas canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
}
|
||||
}
|
||||
|
||||
montauk::win_destroy(win_id);
|
||||
win.destroy();
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
+42
-147
@@ -10,6 +10,7 @@
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <gui/gui.hpp>
|
||||
#include <gui/standalone.hpp>
|
||||
#include <gui/svg.hpp>
|
||||
#include <gui/truetype.hpp>
|
||||
#include <tls/tls.hpp>
|
||||
@@ -75,101 +76,6 @@ static bool g_tls_ready = false;
|
||||
static uint32_t g_server_ip = 0;
|
||||
static tls::TrustAnchors g_tas = {nullptr, 0, 0};
|
||||
|
||||
// ============================================================================
|
||||
// Pixel buffer helpers
|
||||
// ============================================================================
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static void px_hline(uint32_t* px, int bw, int bh, int x, int y, int len, Color c) {
|
||||
if (y < 0 || y >= bh) return;
|
||||
uint32_t v = c.to_pixel();
|
||||
int x1 = x + len > bw ? bw : x + len;
|
||||
for (int col = x < 0 ? 0 : x; col < x1; col++)
|
||||
px[y * bw + col] = v;
|
||||
}
|
||||
|
||||
// Integer square root (Newton's method).
|
||||
static int isqrt(int n) {
|
||||
if (n <= 0) return 0;
|
||||
int x = n, y = (x + 1) / 2;
|
||||
while (y < x) { x = y; y = (x + n / x) / 2; }
|
||||
return x;
|
||||
}
|
||||
|
||||
// Fill a rounded rectangle directly onto the pixel buffer.
|
||||
static void px_fill_rounded(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, int h, int r, Color c) {
|
||||
if (r <= 0) { px_fill(px, bw, bh, x, y, w, h, c); return; }
|
||||
if (r > w / 2) r = w / 2;
|
||||
if (r > h / 2) r = h / 2;
|
||||
|
||||
uint32_t v = c.to_pixel();
|
||||
// Corner arc centers
|
||||
int cx_l = x + r;
|
||||
int cx_r = x + w - 1 - r;
|
||||
|
||||
for (int row = y; row < y + h; row++) {
|
||||
if (row < 0 || row >= bh) continue;
|
||||
|
||||
int left, right;
|
||||
if (row < y + r) {
|
||||
int dy = y + r - row; // 1..r
|
||||
int dx = isqrt(r * r - dy * dy);
|
||||
left = cx_l - dx;
|
||||
right = cx_r + dx + 1;
|
||||
} else if (row >= y + h - r) {
|
||||
int dy = row - (y + h - 1 - r); // 1..r
|
||||
int dx = isqrt(r * r - dy * dy);
|
||||
left = cx_l - dx;
|
||||
right = cx_r + dx + 1;
|
||||
} else {
|
||||
left = x;
|
||||
right = x + w;
|
||||
}
|
||||
|
||||
if (left < 0) left = 0;
|
||||
if (right > bw) right = bw;
|
||||
for (int col = left; col < right; col++)
|
||||
px[row * bw + col] = v;
|
||||
}
|
||||
}
|
||||
|
||||
// Alpha-composite an SvgIcon onto the pixel buffer.
|
||||
static void draw_icon(uint32_t* px, int bw, int bh,
|
||||
int dx, int dy, const SvgIcon& icon) {
|
||||
if (!icon.pixels) return;
|
||||
for (int row = 0; row < icon.height; row++) {
|
||||
int ty = dy + row;
|
||||
if (ty < 0 || ty >= bh) continue;
|
||||
for (int col = 0; col < icon.width; col++) {
|
||||
int tx = dx + col;
|
||||
if (tx < 0 || tx >= bw) continue;
|
||||
uint32_t s = icon.pixels[row * icon.width + col];
|
||||
uint8_t sa = (s >> 24) & 0xFF;
|
||||
if (sa == 0) continue;
|
||||
if (sa == 255) { px[ty * bw + tx] = s; continue; }
|
||||
uint8_t sr = (s >> 16) & 0xFF, sg = (s >> 8) & 0xFF, sb = s & 0xFF;
|
||||
uint32_t d = px[ty * bw + tx];
|
||||
uint8_t dr = (d >> 16) & 0xFF, dg = (d >> 8) & 0xFF, db = d & 0xFF;
|
||||
uint32_t inv = 255 - sa;
|
||||
px[ty * bw + tx] = 0xFF000000u
|
||||
| (((sr * sa + dr * inv) / 255u) << 16)
|
||||
| (((sg * sa + dg * inv) / 255u) << 8)
|
||||
| ((sb * sa + db * inv) / 255u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HTTP parsing
|
||||
// ============================================================================
|
||||
@@ -414,80 +320,62 @@ static constexpr Color WHITE_TEXT = Color::from_rgb(0xFF, 0xFF, 0xFF);
|
||||
|
||||
static constexpr int BTN_W = 110;
|
||||
static constexpr int BTN_H = 28;
|
||||
static constexpr int BTN_RADIUS = 6;
|
||||
static constexpr int WEATHER_BTN_RADIUS = 6;
|
||||
|
||||
static void render(uint32_t* pixels) {
|
||||
static void render(Canvas& canvas) {
|
||||
// ── Background ───────────────────────────────────────────────────────────
|
||||
px_fill(pixels, g_win_w, g_win_h, 0, 0,
|
||||
g_win_w, g_win_h - FOOTER_H, CONTENT_BG);
|
||||
px_fill(pixels, g_win_w, g_win_h, 0, g_win_h - FOOTER_H,
|
||||
g_win_w, FOOTER_H, FOOTER_BG);
|
||||
canvas.fill_rect(0, 0, g_win_w, g_win_h - FOOTER_H, CONTENT_BG);
|
||||
canvas.fill_rect(0, g_win_h - FOOTER_H, g_win_w, FOOTER_H, FOOTER_BG);
|
||||
|
||||
// Divider between content and location strip
|
||||
px_hline(pixels, g_win_w, g_win_h, 0, HEADER_H, g_win_w, DIVIDER);
|
||||
canvas.hline(0, HEADER_H, g_win_w, DIVIDER);
|
||||
// Divider above footer
|
||||
px_hline(pixels, g_win_w, g_win_h, 0, g_win_h - FOOTER_H, g_win_w, DIVIDER);
|
||||
canvas.hline(0, g_win_h - FOOTER_H, g_win_w, DIVIDER);
|
||||
|
||||
if (!g_font) return;
|
||||
|
||||
// ── Main content area (y=0..HEADER_H) ────────────────────────────────────
|
||||
if (g_phase == AppPhase::LOADING) {
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
20, HEADER_H / 2 - 9,
|
||||
draw_text(canvas, g_font, 20, HEADER_H / 2 - 9,
|
||||
"Fetching weather data...", HINT_TEXT, 18);
|
||||
|
||||
} else if (g_phase == AppPhase::ERR) {
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
20, 20, g_status, ERR_TEXT, 15);
|
||||
draw_text(canvas, g_font, 20, 20, g_status, ERR_TEXT, 15);
|
||||
|
||||
} else if (g_phase == AppPhase::IDLE) {
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
20, HEADER_H / 2 - 9,
|
||||
draw_text(canvas, g_font, 20, HEADER_H / 2 - 9,
|
||||
"Click Refresh to check weather.", HINT_TEXT, 18);
|
||||
|
||||
} else { // DONE
|
||||
// Weather icon
|
||||
draw_icon(pixels, g_win_w, g_win_h, ICON_X, ICON_Y, g_icon);
|
||||
draw_icon(canvas, ICON_X, ICON_Y, g_icon);
|
||||
|
||||
// Temperature (large bold)
|
||||
TrueTypeFont* temp_font = g_font_bold ? g_font_bold : g_font;
|
||||
temp_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
INFO_X, TEMP_Y, g_temp, DARK_TEXT, TEMP_SIZE);
|
||||
draw_text(canvas, temp_font, INFO_X, TEMP_Y, g_temp, DARK_TEXT, TEMP_SIZE);
|
||||
|
||||
// Weather description
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
INFO_X, DESC_Y, g_desc, DARK_TEXT, DESC_SIZE);
|
||||
draw_text(canvas, g_font, INFO_X, DESC_Y, g_desc, DARK_TEXT, DESC_SIZE);
|
||||
|
||||
// Feels like
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
INFO_X, FEELS_Y, g_feels, MID_TEXT, LABEL_SIZE);
|
||||
draw_text(canvas, g_font, INFO_X, FEELS_Y, g_feels, MID_TEXT, LABEL_SIZE);
|
||||
}
|
||||
|
||||
// ── Location strip (y=HEADER_H..g_win_h-FOOTER_H) ────────────────────────
|
||||
if (g_phase == AppPhase::DONE) {
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
20, HEADER_H + 14, g_location, DARK_TEXT, LABEL_SIZE);
|
||||
draw_text(canvas, g_font, 20, HEADER_H + 14, g_location, DARK_TEXT, LABEL_SIZE);
|
||||
}
|
||||
|
||||
// ── Refresh button (rounded, in footer) ───────────────────────────────────
|
||||
int btn_x = (g_win_w - BTN_W) / 2;
|
||||
int btn_y = g_win_h - FOOTER_H + (FOOTER_H - BTN_H) / 2;
|
||||
|
||||
px_fill_rounded(pixels, g_win_w, g_win_h,
|
||||
btn_x, btn_y, BTN_W, BTN_H, BTN_RADIUS, BTN_BG);
|
||||
|
||||
if (g_phase == AppPhase::LOADING) {
|
||||
const char* lbl = "Loading...";
|
||||
int sw = g_font->measure_text(lbl, 14);
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
btn_x + (BTN_W - sw) / 2, btn_y + (BTN_H - 14) / 2,
|
||||
lbl, WHITE_TEXT, 14);
|
||||
draw_button(canvas, g_font, btn_x, btn_y, BTN_W, BTN_H,
|
||||
"Loading...", BTN_BG, WHITE_TEXT, WEATHER_BTN_RADIUS, 14);
|
||||
} else {
|
||||
const char* lbl = "Refresh";
|
||||
int sw = g_font->measure_text(lbl, 15);
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
btn_x + (BTN_W - sw) / 2, btn_y + (BTN_H - 15) / 2,
|
||||
lbl, WHITE_TEXT, 15);
|
||||
draw_button(canvas, g_font, btn_x, btn_y, BTN_W, BTN_H,
|
||||
"Refresh", BTN_BG, WHITE_TEXT, WEATHER_BTN_RADIUS, 15);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,38 +402,43 @@ extern "C" void _start() {
|
||||
|
||||
apply_scale(montauk::win_getscale());
|
||||
|
||||
// Create window
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create("Weather", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
|
||||
WsWindow win;
|
||||
if (!win.create("Weather", INIT_W, INIT_H))
|
||||
montauk::exit(1);
|
||||
|
||||
int win_id = wres.id;
|
||||
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
Canvas canvas = win.canvas();
|
||||
|
||||
// Initial fetch on startup
|
||||
g_phase = AppPhase::LOADING;
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
render(canvas);
|
||||
win.present();
|
||||
do_fetch();
|
||||
|
||||
// Event loop
|
||||
while (true) {
|
||||
Montauk::WinEvent ev;
|
||||
int r = montauk::win_poll(win_id, &ev);
|
||||
int r = win.poll(&ev);
|
||||
|
||||
if (r < 0) break;
|
||||
|
||||
if (r == 0) {
|
||||
montauk::sleep_ms(16);
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev.type == 3) break; // close
|
||||
|
||||
if (ev.type == 4) {
|
||||
apply_scale(ev.scale.scale);
|
||||
apply_scale(win.scale_factor);
|
||||
}
|
||||
|
||||
if (ev.type == 2) {
|
||||
g_win_w = win.width;
|
||||
g_win_h = win.height;
|
||||
canvas = win.canvas();
|
||||
}
|
||||
|
||||
if (ev.type == 1) {
|
||||
@@ -558,18 +451,20 @@ extern "C" void _start() {
|
||||
if (mx >= btn_x && mx < btn_x + BTN_W &&
|
||||
my >= btn_y && my < btn_y + BTN_H) {
|
||||
g_phase = AppPhase::LOADING;
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
do_fetch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
}
|
||||
|
||||
if (g_icon.pixels) svg_free(g_icon);
|
||||
montauk::win_destroy(win_id);
|
||||
win.destroy();
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <gui/gui.hpp>
|
||||
#include <gui/standalone.hpp>
|
||||
#include <gui/truetype.hpp>
|
||||
#include <tls/tls.hpp>
|
||||
|
||||
@@ -81,48 +82,6 @@ static bool g_tls_ready = false;
|
||||
static uint32_t g_server_ip = 0;
|
||||
static tls::TrustAnchors g_tas = {nullptr, 0, 0};
|
||||
|
||||
// ============================================================================
|
||||
// Pixel buffer helpers
|
||||
// ============================================================================
|
||||
|
||||
static void px_fill(uint32_t* px, int bw, 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, y1 = y + h;
|
||||
if (x1 > bw) x1 = bw;
|
||||
if (y1 > g_win_h) y1 = g_win_h;
|
||||
for (int row = y0; row < y1; row++)
|
||||
for (int col = x0; col < x1; col++)
|
||||
px[row * bw + col] = v;
|
||||
}
|
||||
|
||||
static void px_hline(uint32_t* px, int bw, int x, int y, int len, Color c) {
|
||||
if (y < 0 || y >= g_win_h) return;
|
||||
uint32_t v = c.to_pixel();
|
||||
int x1 = x + len;
|
||||
if (x < 0) x = 0;
|
||||
if (x1 > bw) x1 = bw;
|
||||
for (int col = x; col < x1; col++)
|
||||
px[y * bw + col] = v;
|
||||
}
|
||||
|
||||
static void px_vline(uint32_t* px, int bw, int x, int y, int len, Color c) {
|
||||
if (x < 0 || x >= bw) return;
|
||||
uint32_t v = c.to_pixel();
|
||||
int y1 = y + len;
|
||||
if (y < 0) y = 0;
|
||||
if (y1 > g_win_h) y1 = g_win_h;
|
||||
for (int row = y; row < y1; row++)
|
||||
px[row * bw + x] = v;
|
||||
}
|
||||
|
||||
static void px_rect_outline(uint32_t* px, int bw, int x, int y, int w, int h, Color c) {
|
||||
px_hline(px, bw, x, y, w, c);
|
||||
px_hline(px, bw, x, y + h - 1, w, c);
|
||||
px_vline(px, bw, x, y, h, c);
|
||||
px_vline(px, bw, x + w - 1, y, h, c);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// UI scale
|
||||
// ============================================================================
|
||||
@@ -459,16 +418,16 @@ static void do_search(const char* query) {
|
||||
// Rendering
|
||||
// ============================================================================
|
||||
|
||||
static void render(uint32_t* pixels) {
|
||||
static void render(Canvas& canvas) {
|
||||
static constexpr Color TOOLBAR_BG = Color::from_rgb(0xF5, 0xF5, 0xF5);
|
||||
static constexpr Color HINT_COLOR = Color::from_rgb(0x99, 0x99, 0x99);
|
||||
|
||||
// Background
|
||||
px_fill(pixels, g_win_w, 0, 0, g_win_w, g_win_h, WINDOW_BG);
|
||||
canvas.fill(WINDOW_BG);
|
||||
|
||||
// ---- Toolbar ----
|
||||
px_fill(pixels, g_win_w, 0, 0, g_win_w, TOOLBAR_H, TOOLBAR_BG);
|
||||
px_hline(pixels, g_win_w, 0, TOOLBAR_H, g_win_w, BORDER);
|
||||
canvas.fill_rect(0, 0, g_win_w, TOOLBAR_H, TOOLBAR_BG);
|
||||
canvas.hline(0, TOOLBAR_H, g_win_w, BORDER);
|
||||
|
||||
// Search box geometry
|
||||
int sb_y = 8, sb_h = TOOLBAR_H - 16;
|
||||
@@ -477,26 +436,25 @@ static void render(uint32_t* pixels) {
|
||||
int sb_w = g_win_w - sb_x - btn_gap - btn_w - 8;
|
||||
if (sb_w < 80) sb_w = 80;
|
||||
|
||||
px_fill(pixels, g_win_w, sb_x, sb_y, sb_w, sb_h, WHITE);
|
||||
px_rect_outline(pixels, g_win_w, sb_x, sb_y, sb_w, sb_h, BORDER);
|
||||
canvas.fill_rect(sb_x, sb_y, sb_w, sb_h, WHITE);
|
||||
canvas.rect(sb_x, sb_y, sb_w, sb_h, BORDER);
|
||||
|
||||
// Search box text + cursor
|
||||
if (g_font) {
|
||||
int ty = sb_y + (sb_h - FONT_SIZE) / 2;
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
sb_x + 6, ty, g_query, TEXT_COLOR, FONT_SIZE);
|
||||
draw_text(canvas, g_font, sb_x + 6, ty, g_query, TEXT_COLOR, FONT_SIZE);
|
||||
int qw = g_font->measure_text(g_query, FONT_SIZE);
|
||||
int cx = sb_x + 6 + qw + 1;
|
||||
if (cx < sb_x + sb_w - 4)
|
||||
px_vline(pixels, g_win_w, cx, ty + 1, FONT_SIZE - 2, TEXT_COLOR);
|
||||
canvas.vline(cx, ty + 1, FONT_SIZE - 2, TEXT_COLOR);
|
||||
}
|
||||
|
||||
// Search button
|
||||
int btn_x = sb_x + sb_w + btn_gap;
|
||||
px_fill(pixels, g_win_w, btn_x, sb_y, btn_w, sb_h, ACCENT);
|
||||
canvas.fill_rect(btn_x, sb_y, btn_w, sb_h, ACCENT);
|
||||
if (g_font) {
|
||||
int stw = g_font->measure_text("Search", FONT_SIZE);
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
draw_text(canvas, g_font,
|
||||
btn_x + (btn_w - stw) / 2,
|
||||
sb_y + (sb_h - FONT_SIZE) / 2,
|
||||
"Search", WHITE, FONT_SIZE);
|
||||
@@ -508,17 +466,14 @@ static void render(uint32_t* pixels) {
|
||||
if (!g_font) return;
|
||||
|
||||
if (g_phase == AppPhase::IDLE) {
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
TEXT_PAD, cy + 16,
|
||||
draw_text(canvas, g_font, TEXT_PAD, cy + 16,
|
||||
"Type a topic and press Enter or click Search.",
|
||||
HINT_COLOR, FONT_SIZE);
|
||||
} else if (g_phase == AppPhase::LOADING) {
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
TEXT_PAD, cy + 16,
|
||||
draw_text(canvas, g_font, TEXT_PAD, cy + 16,
|
||||
"Searching Wikipedia...", HINT_COLOR, FONT_SIZE);
|
||||
} else if (g_phase == AppPhase::ERR) {
|
||||
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
TEXT_PAD, cy + 16, g_status, CLOSE_BTN, FONT_SIZE);
|
||||
draw_text(canvas, g_font, TEXT_PAD, cy + 16, g_status, CLOSE_BTN, FONT_SIZE);
|
||||
} else if (g_phase == AppPhase::DONE && g_line_count > 0) {
|
||||
int visible = ch / g_line_h; // approximate using body line height
|
||||
int y = cy + 8;
|
||||
@@ -528,7 +483,7 @@ static void render(uint32_t* pixels) {
|
||||
int lh = g_font->get_line_height(l.font_size) + 4;
|
||||
if (y + lh > g_win_h) break;
|
||||
if (l.text[0] != '\0') {
|
||||
l.font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
l.font->draw_to_buffer(canvas.pixels, canvas.w, canvas.h,
|
||||
TEXT_PAD, y, l.text, l.color, l.font_size);
|
||||
}
|
||||
y += lh;
|
||||
@@ -537,13 +492,12 @@ static void render(uint32_t* pixels) {
|
||||
// Scrollbar
|
||||
if (g_line_count > visible) {
|
||||
int sbx = g_win_w - SCROLLBAR_W;
|
||||
px_fill(pixels, g_win_w, sbx, cy, SCROLLBAR_W, ch, SCROLLBAR_BG);
|
||||
canvas.fill_rect(sbx, cy, SCROLLBAR_W, ch, SCROLLBAR_BG);
|
||||
int max_sc = g_line_count - visible;
|
||||
int thumb_h = (visible * ch) / g_line_count;
|
||||
if (thumb_h < 20) thumb_h = 20;
|
||||
int thumb_y = cy + (g_scroll_y * (ch - thumb_h)) / (max_sc > 0 ? max_sc : 1);
|
||||
px_fill(pixels, g_win_w, sbx + 2, thumb_y,
|
||||
SCROLLBAR_W - 4, thumb_h, SCROLLBAR_FG);
|
||||
canvas.fill_rect(sbx + 2, thumb_y, SCROLLBAR_W - 4, thumb_h, SCROLLBAR_FG);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -576,30 +530,28 @@ extern "C" void _start() {
|
||||
|
||||
apply_scale(montauk::win_getscale());
|
||||
|
||||
// Create window
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create("Wikipedia", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
|
||||
WsWindow win;
|
||||
if (!win.create("Wikipedia", INIT_W, INIT_H))
|
||||
montauk::exit(1);
|
||||
|
||||
int win_id = wres.id;
|
||||
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
Canvas canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
|
||||
bool search_pending = false;
|
||||
|
||||
while (true) {
|
||||
Montauk::WinEvent ev;
|
||||
int r = montauk::win_poll(win_id, &ev);
|
||||
int r = win.poll(&ev);
|
||||
|
||||
if (r < 0) break; // window closed / error
|
||||
|
||||
if (r == 0) {
|
||||
// No event — idle at ~60 fps
|
||||
montauk::sleep_ms(16);
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -607,26 +559,21 @@ extern "C" void _start() {
|
||||
if (ev.type == 3) break; // close
|
||||
|
||||
if (ev.type == 4) {
|
||||
apply_scale(ev.scale.scale);
|
||||
apply_scale(win.scale_factor);
|
||||
if (g_phase == AppPhase::DONE && g_line_count > 0) {
|
||||
build_display_lines(g_title, g_extract_buf, g_extract_len);
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.type == 2) {
|
||||
int new_w = ev.resize.w;
|
||||
int new_h = ev.resize.h;
|
||||
if (new_w > 0 && new_h > 0 && (new_w != g_win_w || new_h != g_win_h)) {
|
||||
uint64_t new_va = montauk::win_resize(win_id, new_w, new_h);
|
||||
if (new_va != 0) {
|
||||
pixels = (uint32_t*)(uintptr_t)new_va;
|
||||
g_win_w = new_w;
|
||||
g_win_h = new_h;
|
||||
if (win.width > 0 && win.height > 0) {
|
||||
g_win_w = win.width;
|
||||
g_win_h = win.height;
|
||||
canvas = win.canvas();
|
||||
if (g_phase == AppPhase::DONE && g_line_count > 0) {
|
||||
build_display_lines(g_title, g_extract_buf, g_extract_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (ev.type == 0 && ev.key.pressed) {
|
||||
uint8_t ascii = ev.key.ascii;
|
||||
@@ -684,15 +631,17 @@ extern "C" void _start() {
|
||||
if (search_pending && g_query[0] != '\0') {
|
||||
search_pending = false;
|
||||
g_phase = AppPhase::LOADING;
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
do_search(g_query); // blocking
|
||||
}
|
||||
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
canvas = win.canvas();
|
||||
render(canvas);
|
||||
win.present();
|
||||
}
|
||||
|
||||
montauk::win_destroy(win_id);
|
||||
win.destroy();
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
+14
-18
@@ -147,27 +147,23 @@ Events are delivered via `win_poll()` into a `WinEvent` struct:
|
||||
|
||||
### Drawing Helpers
|
||||
|
||||
Standalone apps typically define local pixel-drawing helpers since they work with raw `uint32_t*` buffers:
|
||||
For new standalone apps, prefer the shared helpers in `#include <gui/standalone.hpp>`. It provides:
|
||||
|
||||
- `gui::WsWindow` for `win_create()` / `win_poll()` / `win_resize()` / `win_present()`
|
||||
- `gui::Canvas` for drawing into the window buffer
|
||||
- immediate-mode helpers like `draw_text()`, `draw_button()`, and `fill_circle()`
|
||||
|
||||
Older apps in the tree still define local `px_*` helpers, but new code should not need to.
|
||||
|
||||
```cpp
|
||||
static void px_fill(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, int h, uint32_t color) {
|
||||
for (int row = y; row < y + h && row < bh; row++)
|
||||
for (int col = x; col < x + w && col < bw; col++)
|
||||
px[row * bw + col] = color;
|
||||
}
|
||||
gui::WsWindow win;
|
||||
win.create("My App", 400, 300);
|
||||
|
||||
static void px_hline(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, uint32_t color) {
|
||||
for (int col = x; col < x + w && col < bw; col++)
|
||||
if (y >= 0 && y < bh) px[y * bw + col] = color;
|
||||
}
|
||||
|
||||
static void px_text(uint32_t* px, int bw, int bh,
|
||||
int x, int y, const char* text, Color c, int size) {
|
||||
if (g_font)
|
||||
g_font->draw_to_buffer(px, bw, bh, x, y, text, c, size);
|
||||
}
|
||||
gui::Canvas c = win.canvas();
|
||||
c.fill(gui::colors::WHITE);
|
||||
draw_text(c, g_font, 20, 30, "Hello", gui::colors::TEXT_COLOR, 18);
|
||||
draw_button(c, g_font, 20, 60, 96, 28, "OK",
|
||||
gui::colors::ACCENT, gui::colors::WHITE, 6, 16);
|
||||
```
|
||||
|
||||
### Rounded Rectangles
|
||||
|
||||
@@ -207,11 +207,12 @@ struct WinCreateResult {
|
||||
**`WinEvent` struct:**
|
||||
```cpp
|
||||
struct WinEvent {
|
||||
int type; // 0=Key, 1=Mouse, 2=Resize, 3=Close
|
||||
int type; // 0=Key, 1=Mouse, 2=Resize, 3=Close, 4=Scale
|
||||
union {
|
||||
struct { uint8_t scancode; char ascii; bool pressed, shift, ctrl, alt; } key;
|
||||
struct { int x, y; uint8_t buttons, prev_buttons; int32_t scroll; } mouse;
|
||||
struct { int w, h; } resize;
|
||||
struct { int scale; } scale;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* standalone.hpp
|
||||
* MontaukOS helpers for standalone Window Server apps
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <montauk/syscall.h>
|
||||
#include "gui/canvas.hpp"
|
||||
#include "gui/svg.hpp"
|
||||
#include "gui/truetype.hpp"
|
||||
|
||||
namespace gui {
|
||||
|
||||
struct WsWindow {
|
||||
int id;
|
||||
uint32_t* pixels;
|
||||
int width;
|
||||
int height;
|
||||
int scale_factor;
|
||||
bool closed;
|
||||
|
||||
WsWindow()
|
||||
: id(-1), pixels(nullptr), width(0), height(0), scale_factor(1), closed(false) {}
|
||||
|
||||
bool create(const char* title, int w, int h) {
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create(title, w, h, &wres) < 0 || wres.id < 0)
|
||||
return false;
|
||||
|
||||
id = wres.id;
|
||||
pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
width = w;
|
||||
height = h;
|
||||
scale_factor = montauk::win_getscale();
|
||||
closed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int poll(Montauk::WinEvent* ev) {
|
||||
if (id < 0 || closed) return -1;
|
||||
|
||||
int r = montauk::win_poll(id, ev);
|
||||
if (r <= 0) return r;
|
||||
|
||||
if (ev->type == 2) {
|
||||
width = ev->resize.w;
|
||||
height = ev->resize.h;
|
||||
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(id, width, height);
|
||||
} else if (ev->type == 3) {
|
||||
closed = true;
|
||||
} else if (ev->type == 4) {
|
||||
scale_factor = ev->scale.scale;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
Canvas canvas() const {
|
||||
return Canvas(pixels, width, height);
|
||||
}
|
||||
|
||||
void present() const {
|
||||
if (id >= 0)
|
||||
montauk::win_present(id);
|
||||
}
|
||||
|
||||
void set_cursor(int cursor) const {
|
||||
if (id >= 0)
|
||||
montauk::win_setcursor(id, cursor);
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (id >= 0)
|
||||
montauk::win_destroy(id);
|
||||
id = -1;
|
||||
pixels = nullptr;
|
||||
width = 0;
|
||||
height = 0;
|
||||
closed = true;
|
||||
}
|
||||
};
|
||||
|
||||
inline int text_width(TrueTypeFont* font, const char* text, int size) {
|
||||
if (font && font->valid)
|
||||
return font->measure_text(text, size);
|
||||
int len = 0;
|
||||
while (text && text[len]) len++;
|
||||
return len * FONT_WIDTH;
|
||||
}
|
||||
|
||||
inline int text_height(TrueTypeFont* font, int size) {
|
||||
if (font && font->valid) {
|
||||
GlyphCache* cache = font->get_cache(size);
|
||||
if (cache)
|
||||
return cache->ascent - cache->descent;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
inline void draw_text(Canvas& c, TrueTypeFont* font, int x, int y,
|
||||
const char* text, Color color, int size) {
|
||||
if (!text || !text[0]) return;
|
||||
if (font && font->valid)
|
||||
font->draw_to_buffer(c.pixels, c.w, c.h, x, y, text, color, size);
|
||||
}
|
||||
|
||||
inline void fill_circle(Canvas& c, int cx, int cy, int r, Color color) {
|
||||
if (r <= 0) return;
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
if (dx * dx + dy * dy <= r * r)
|
||||
c.put_pixel(cx + dx, cy + dy, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void draw_button(Canvas& c, TrueTypeFont* font, int x, int y, int w, int h,
|
||||
const char* label, Color bg, Color fg, int radius,
|
||||
int size = 16) {
|
||||
c.fill_rounded_rect(x, y, w, h, radius, bg);
|
||||
int tw = text_width(font, label, size);
|
||||
int th = text_height(font, size);
|
||||
draw_text(c, font, x + (w - tw) / 2, y + (h - th) / 2, label, fg, size);
|
||||
}
|
||||
|
||||
inline void draw_icon(Canvas& c, int x, int y, const SvgIcon& icon) {
|
||||
c.icon(x, y, icon);
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
Reference in New Issue
Block a user