feat: GPT, FAT32 driver, disks app, userspace adapted for multiple drives, and more
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# Makefile for devexplorer (standalone Device Explorer) on MontaukOS
|
||||
# Copyright (c) 2026 Daniel Hammer
|
||||
|
||||
MAKEFLAGS += -rR
|
||||
.SUFFIXES:
|
||||
|
||||
# ---- Toolchain ----
|
||||
|
||||
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
|
||||
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
|
||||
CXX := $(TOOLCHAIN_PREFIX)g++
|
||||
else
|
||||
CXX := g++
|
||||
endif
|
||||
|
||||
# ---- Paths ----
|
||||
|
||||
PROG_INC := ../../include
|
||||
LINK_LD := ../../link.ld
|
||||
BINDIR := ../../bin
|
||||
OBJDIR := obj
|
||||
LIBDIR := ../../lib
|
||||
|
||||
# ---- Compiler flags ----
|
||||
|
||||
CXXFLAGS := \
|
||||
-std=gnu++20 \
|
||||
-g -O2 -pipe \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-Wno-unused-parameter \
|
||||
-Wno-unused-function \
|
||||
-nostdinc \
|
||||
-ffreestanding \
|
||||
-fno-stack-protector \
|
||||
-fno-stack-check \
|
||||
-fno-PIC \
|
||||
-fno-rtti \
|
||||
-fno-exceptions \
|
||||
-ffunction-sections \
|
||||
-fdata-sections \
|
||||
-m64 \
|
||||
-march=x86-64 \
|
||||
-msse \
|
||||
-msse2 \
|
||||
-mno-red-zone \
|
||||
-mcmodel=small \
|
||||
-I $(PROG_INC) \
|
||||
-isystem $(PROG_INC)/libc \
|
||||
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
|
||||
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include
|
||||
|
||||
# ---- Linker flags ----
|
||||
|
||||
LDFLAGS := \
|
||||
-nostdlib \
|
||||
-static \
|
||||
-Wl,--build-id=none \
|
||||
-Wl,--gc-sections \
|
||||
-Wl,-m,elf_x86_64 \
|
||||
-z max-page-size=0x1000 \
|
||||
-T $(LINK_LD)
|
||||
|
||||
# ---- Source files ----
|
||||
|
||||
SRCS := main.cpp render.cpp diskdetail.cpp stb_truetype_impl.cpp
|
||||
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/os/devexplorer.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/os
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* devexplorer.h
|
||||
* Shared header for the MontaukOS Device Explorer
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <gui/gui.hpp>
|
||||
#include <gui/truetype.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
}
|
||||
|
||||
using namespace gui;
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int INIT_W = 640;
|
||||
static constexpr int INIT_H = 460;
|
||||
static constexpr int TOOLBAR_H = 36;
|
||||
static constexpr int CAT_H = 28;
|
||||
static constexpr int ITEM_H = 24;
|
||||
static constexpr int MAX_DEVS = 64;
|
||||
static constexpr int POLL_MS = 2000;
|
||||
static constexpr int INDENT = 28;
|
||||
static constexpr int FONT_SIZE = 18;
|
||||
|
||||
static constexpr int NUM_CATEGORIES = 9;
|
||||
|
||||
static constexpr Color BG_COLOR = Color::from_rgb(0xFF, 0xFF, 0xFF);
|
||||
static constexpr Color TOOLBAR_BG = Color::from_rgb(0xF5, 0xF5, 0xF5);
|
||||
static constexpr Color BORDER_COLOR = Color::from_rgb(0xCC, 0xCC, 0xCC);
|
||||
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 WHITE = Color::from_rgb(0xFF, 0xFF, 0xFF);
|
||||
static constexpr Color ACCENT_COLOR = Color::from_rgb(0x33, 0x66, 0xCC);
|
||||
|
||||
// ============================================================================
|
||||
// Category data
|
||||
// ============================================================================
|
||||
|
||||
static const char* category_names[] = {
|
||||
"CPU", // 0
|
||||
"Interrupt", // 1
|
||||
"Timer", // 2
|
||||
"Input", // 3
|
||||
"USB", // 4
|
||||
"Network", // 5
|
||||
"Display", // 6
|
||||
"Storage", // 7
|
||||
"PCI", // 8
|
||||
};
|
||||
|
||||
static const Color category_colors[] = {
|
||||
Color::from_rgb(0x33, 0x66, 0xCC), // CPU - blue
|
||||
Color::from_rgb(0x88, 0x44, 0xAA), // Interrupt - purple
|
||||
Color::from_rgb(0x22, 0x88, 0x22), // Timer - green
|
||||
Color::from_rgb(0xCC, 0x88, 0x00), // Input - amber
|
||||
Color::from_rgb(0x00, 0x88, 0x88), // USB - teal
|
||||
Color::from_rgb(0xCC, 0x55, 0x22), // Network - orange
|
||||
Color::from_rgb(0x44, 0x66, 0xCC), // Display - indigo
|
||||
Color::from_rgb(0x99, 0x55, 0x00), // Storage - brown
|
||||
Color::from_rgb(0x66, 0x66, 0x66), // PCI - gray
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Display row model
|
||||
// ============================================================================
|
||||
|
||||
enum RowType { ROW_CATEGORY, ROW_DEVICE };
|
||||
|
||||
struct DisplayRow {
|
||||
RowType type;
|
||||
int category;
|
||||
int dev_index;
|
||||
};
|
||||
|
||||
static constexpr int MAX_DISPLAY_ROWS = MAX_DEVS + NUM_CATEGORIES;
|
||||
|
||||
// ============================================================================
|
||||
// Disk detail window
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int DD_TAB_COUNT = 2;
|
||||
static const char* dd_tab_labels[DD_TAB_COUNT] = { "General", "Features" };
|
||||
|
||||
static constexpr int DD_INIT_W = 440;
|
||||
static constexpr int DD_INIT_H = 420;
|
||||
static constexpr int DD_TAB_BAR_H = 32;
|
||||
|
||||
struct DiskDetailState {
|
||||
Montauk::DiskInfo info;
|
||||
int active_tab;
|
||||
int win_id;
|
||||
int win_w, win_h;
|
||||
uint32_t* pixels;
|
||||
bool open;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Main state
|
||||
// ============================================================================
|
||||
|
||||
struct DevExplorerState {
|
||||
Montauk::DevInfo devs[MAX_DEVS];
|
||||
int dev_count;
|
||||
bool collapsed[NUM_CATEGORIES];
|
||||
int selected_row;
|
||||
int scroll_y;
|
||||
uint64_t last_poll_ms;
|
||||
|
||||
int last_click_row;
|
||||
uint64_t last_click_ms;
|
||||
|
||||
DiskDetailState detail;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Global state (extern — defined in main.cpp)
|
||||
// ============================================================================
|
||||
|
||||
extern int g_win_w, g_win_h;
|
||||
extern DevExplorerState g_state;
|
||||
extern TrueTypeFont* g_font;
|
||||
|
||||
// ============================================================================
|
||||
// Function declarations — render.cpp
|
||||
// ============================================================================
|
||||
|
||||
void render(uint32_t* pixels);
|
||||
|
||||
// ============================================================================
|
||||
// Function declarations — main.cpp
|
||||
// ============================================================================
|
||||
|
||||
int build_display_rows(DevExplorerState* de, DisplayRow* rows);
|
||||
|
||||
// ============================================================================
|
||||
// Function declarations — diskdetail.cpp
|
||||
// ============================================================================
|
||||
|
||||
void render_disk_detail();
|
||||
void open_disk_detail(int port, const char* model);
|
||||
void close_disk_detail();
|
||||
bool handle_detail_mouse(int mx, int my, bool clicked);
|
||||
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
* diskdetail.cpp
|
||||
* Disk detail popup window
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "px.h"
|
||||
|
||||
// ============================================================================
|
||||
// Helpers
|
||||
// ============================================================================
|
||||
|
||||
static void fmt_u64(char* buf, int bufsize, uint64_t v) {
|
||||
if (v == 0) { buf[0] = '0'; buf[1] = '\0'; return; }
|
||||
char tmp[24]; int i = 0;
|
||||
while (v > 0) { tmp[i++] = '0' + (int)(v % 10); v /= 10; }
|
||||
int j = 0;
|
||||
while (i > 0 && j < bufsize - 1) buf[j++] = tmp[--i];
|
||||
buf[j] = '\0';
|
||||
}
|
||||
|
||||
static void dd_table_row(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int col1_w, int row_h,
|
||||
const char* key, const char* value,
|
||||
Color key_col, Color val_col) {
|
||||
int fh = font_h();
|
||||
int ty = y + (row_h - fh) / 2;
|
||||
px_text(px, bw, bh, x + 8, ty, key, key_col);
|
||||
px_text(px, bw, bh, x + col1_w + 8, ty, value, val_col);
|
||||
}
|
||||
|
||||
static void dd_feature_row(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, int row_h,
|
||||
const char* label, bool supported, const char* extra) {
|
||||
int fh = font_h();
|
||||
int ty = y + (row_h - fh) / 2;
|
||||
|
||||
int dot_y = y + (row_h - 8) / 2;
|
||||
Color dot_col = supported ? Color::from_rgb(0x22, 0x88, 0x22)
|
||||
: Color::from_rgb(0xBB, 0xBB, 0xBB);
|
||||
px_fill_rounded(px, bw, bh, x + 10, dot_y, 8, 8, 4, dot_col);
|
||||
|
||||
Color text_col = supported ? TEXT_COLOR : Color::from_rgb(0xAA, 0xAA, 0xAA);
|
||||
px_text(px, bw, bh, x + 26, ty, label, text_col);
|
||||
|
||||
if (extra && extra[0]) {
|
||||
int ew = text_w(extra);
|
||||
px_text(px, bw, bh, x + w - ew - 12, ty, extra, DIM_TEXT);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tab content: General
|
||||
// ============================================================================
|
||||
|
||||
static void dd_draw_general(uint32_t* px, int bw, int bh, DiskDetailState* dd) {
|
||||
char line[128];
|
||||
int fh = font_h();
|
||||
int x = 0;
|
||||
int y = DD_TAB_BAR_H + 12;
|
||||
int w = bw;
|
||||
int col1_w = 110;
|
||||
int row_h = fh + 8;
|
||||
Color key_col = DIM_TEXT;
|
||||
Color val_col = TEXT_COLOR;
|
||||
Color hdr_col = ACCENT_COLOR;
|
||||
Color border = Color::from_rgb(0xE0, 0xE0, 0xE0);
|
||||
|
||||
// Identification section
|
||||
px_text(px, bw, bh, x + 8, y, "Identification", hdr_col);
|
||||
y += fh + 8;
|
||||
px_hline(px, bw, bh, x + 8, y, w - 16, border);
|
||||
y += 1;
|
||||
|
||||
int rowIdx = 0;
|
||||
auto table_row = [&](const char* key, const char* val) {
|
||||
if (rowIdx++ % 2 == 0)
|
||||
px_fill(px, bw, bh, x + 8, y, w - 16, row_h,
|
||||
Color::from_rgb(0xF7, 0xF7, 0xF7));
|
||||
dd_table_row(px, bw, bh, x, y, col1_w, row_h, key, val, key_col, val_col);
|
||||
y += row_h;
|
||||
};
|
||||
|
||||
table_row("Model", dd->info.model);
|
||||
table_row("Serial", dd->info.serial);
|
||||
table_row("Firmware", dd->info.firmware);
|
||||
|
||||
const char* typeStr = "Unknown";
|
||||
if (dd->info.type == 1) typeStr = "SATA";
|
||||
else if (dd->info.type == 2) typeStr = "SATAPI";
|
||||
table_row("Type", typeStr);
|
||||
|
||||
snprintf(line, sizeof(line), "%d", (int)dd->info.port);
|
||||
table_row("AHCI Port", line);
|
||||
|
||||
px_hline(px, bw, bh, x + 8, y, w - 16, border);
|
||||
y += 12;
|
||||
|
||||
// Capacity section
|
||||
px_text(px, bw, bh, x + 8, y, "Capacity", hdr_col);
|
||||
y += fh + 8;
|
||||
px_hline(px, bw, bh, x + 8, y, w - 16, border);
|
||||
y += 1;
|
||||
|
||||
rowIdx = 0;
|
||||
uint64_t totalBytes = dd->info.sectorCount * (uint64_t)dd->info.sectorSizeLog;
|
||||
uint64_t totalMB = totalBytes / (1024 * 1024);
|
||||
uint64_t totalGB = totalMB / 1024;
|
||||
if (totalGB > 0) {
|
||||
int fracGB = (int)((totalMB % 1024) * 10 / 1024);
|
||||
snprintf(line, sizeof(line), "%d.%d GiB", (int)totalGB, fracGB);
|
||||
} else {
|
||||
snprintf(line, sizeof(line), "%d MiB", (int)totalMB);
|
||||
}
|
||||
table_row("Size", line);
|
||||
|
||||
char secbuf[24];
|
||||
fmt_u64(secbuf, sizeof(secbuf), dd->info.sectorCount);
|
||||
table_row("Sectors", secbuf);
|
||||
|
||||
snprintf(line, sizeof(line), "%d bytes", (int)dd->info.sectorSizeLog);
|
||||
table_row("Logical", line);
|
||||
|
||||
snprintf(line, sizeof(line), "%d bytes", (int)dd->info.sectorSizePhys);
|
||||
table_row("Physical", line);
|
||||
|
||||
px_hline(px, bw, bh, x + 8, y, w - 16, border);
|
||||
y += 12;
|
||||
|
||||
// Interface section
|
||||
px_text(px, bw, bh, x + 8, y, "Interface", hdr_col);
|
||||
y += fh + 8;
|
||||
px_hline(px, bw, bh, x + 8, y, w - 16, border);
|
||||
y += 1;
|
||||
|
||||
rowIdx = 0;
|
||||
const char* sataSpeed = "Unknown";
|
||||
if (dd->info.sataGen == 1) sataSpeed = "SATA I (1.5 Gb/s)";
|
||||
else if (dd->info.sataGen == 2) sataSpeed = "SATA II (3.0 Gb/s)";
|
||||
else if (dd->info.sataGen == 3) sataSpeed = "SATA III (6.0 Gb/s)";
|
||||
table_row("Link Speed", sataSpeed);
|
||||
|
||||
if (dd->info.rpm == 0)
|
||||
table_row("Media", "Not reported");
|
||||
else if (dd->info.rpm == 1)
|
||||
table_row("Media", "Solid State (SSD)");
|
||||
else {
|
||||
snprintf(line, sizeof(line), "%d RPM", (int)dd->info.rpm);
|
||||
table_row("Media", line);
|
||||
}
|
||||
|
||||
px_hline(px, bw, bh, x + 8, y, w - 16, border);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tab content: Features
|
||||
// ============================================================================
|
||||
|
||||
static void dd_draw_features(uint32_t* px, int bw, int bh, DiskDetailState* dd) {
|
||||
char line[64];
|
||||
int fh = font_h();
|
||||
int x = 0;
|
||||
int y = DD_TAB_BAR_H + 12;
|
||||
int w = bw;
|
||||
int row_h = fh + 10;
|
||||
Color hdr_col = ACCENT_COLOR;
|
||||
Color border = Color::from_rgb(0xE0, 0xE0, 0xE0);
|
||||
|
||||
px_text(px, bw, bh, x + 8, y, "Supported Features", hdr_col);
|
||||
y += fh + 8;
|
||||
px_hline(px, bw, bh, x + 8, y, w - 16, border);
|
||||
y += 4;
|
||||
|
||||
dd_feature_row(px, bw, bh, x, y, w, row_h, "48-bit LBA", dd->info.supportsLba48, nullptr);
|
||||
y += row_h;
|
||||
|
||||
if (dd->info.supportsNcq) {
|
||||
snprintf(line, sizeof(line), "Depth: %d", (int)dd->info.ncqDepth);
|
||||
dd_feature_row(px, bw, bh, x, y, w, row_h, "Native Command Queuing (NCQ)", true, line);
|
||||
} else {
|
||||
dd_feature_row(px, bw, bh, x, y, w, row_h, "Native Command Queuing (NCQ)", false, nullptr);
|
||||
}
|
||||
y += row_h;
|
||||
|
||||
dd_feature_row(px, bw, bh, x, y, w, row_h, "TRIM (Data Set Management)", dd->info.supportsTrim, nullptr);
|
||||
y += row_h;
|
||||
|
||||
dd_feature_row(px, bw, bh, x, y, w, row_h, "S.M.A.R.T.", dd->info.supportsSmart, nullptr);
|
||||
y += row_h;
|
||||
|
||||
dd_feature_row(px, bw, bh, x, y, w, row_h, "Write Cache", dd->info.supportsWriteCache, nullptr);
|
||||
y += row_h;
|
||||
|
||||
dd_feature_row(px, bw, bh, x, y, w, row_h, "Read Look-Ahead", dd->info.supportsReadAhead, nullptr);
|
||||
y += row_h;
|
||||
|
||||
y += 4;
|
||||
px_hline(px, bw, bh, x + 8, y, w - 16, border);
|
||||
y += 12;
|
||||
|
||||
// Legend
|
||||
Color legend_col = Color::from_rgb(0x88, 0x88, 0x88);
|
||||
int ly = y + (fh - 8) / 2;
|
||||
px_fill_rounded(px, bw, bh, x + 10, ly, 8, 8, 4, Color::from_rgb(0x22, 0x88, 0x22));
|
||||
px_text(px, bw, bh, x + 26, y, "Supported", legend_col);
|
||||
|
||||
px_fill_rounded(px, bw, bh, x + 120, ly, 8, 8, 4, Color::from_rgb(0xBB, 0xBB, 0xBB));
|
||||
px_text(px, bw, bh, x + 136, y, "Not supported", legend_col);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Render
|
||||
// ============================================================================
|
||||
|
||||
void render_disk_detail() {
|
||||
auto& dd = g_state.detail;
|
||||
if (!dd.open) return;
|
||||
|
||||
uint32_t* px = dd.pixels;
|
||||
int bw = dd.win_w, bh = dd.win_h;
|
||||
int fh = font_h();
|
||||
|
||||
px_fill(px, bw, bh, 0, 0, bw, bh, BG_COLOR);
|
||||
|
||||
// Tab bar
|
||||
px_fill(px, bw, bh, 0, 0, bw, DD_TAB_BAR_H, TOOLBAR_BG);
|
||||
px_hline(px, bw, bh, 0, DD_TAB_BAR_H - 1, bw, BORDER_COLOR);
|
||||
|
||||
int tab_w = bw / DD_TAB_COUNT;
|
||||
for (int i = 0; i < DD_TAB_COUNT; i++) {
|
||||
int tx = i * tab_w;
|
||||
bool active = (i == dd.active_tab);
|
||||
|
||||
if (active) {
|
||||
px_fill(px, bw, bh, tx, 0, tab_w, DD_TAB_BAR_H, BG_COLOR);
|
||||
px_fill(px, bw, bh, tx + 4, DD_TAB_BAR_H - 3, tab_w - 8, 3, ACCENT_COLOR);
|
||||
}
|
||||
|
||||
int tw = text_w(dd_tab_labels[i]);
|
||||
Color tc = active ? ACCENT_COLOR : DIM_TEXT;
|
||||
px_text(px, bw, bh, tx + (tab_w - tw) / 2, (DD_TAB_BAR_H - fh) / 2,
|
||||
dd_tab_labels[i], tc);
|
||||
}
|
||||
|
||||
// Tab content
|
||||
switch (dd.active_tab) {
|
||||
case 0: dd_draw_general(px, bw, bh, &dd); break;
|
||||
case 1: dd_draw_features(px, bw, bh, &dd); break;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Window management
|
||||
// ============================================================================
|
||||
|
||||
void open_disk_detail(int port, const char* model) {
|
||||
auto& dd = g_state.detail;
|
||||
|
||||
if (dd.open) close_disk_detail();
|
||||
|
||||
char title[64];
|
||||
int i = 0;
|
||||
const char* prefix = "Disk: ";
|
||||
while (prefix[i]) { title[i] = prefix[i]; i++; }
|
||||
int j = 0;
|
||||
while (model[j] && i < 62) { title[i++] = model[j++]; }
|
||||
title[i] = '\0';
|
||||
|
||||
dd.win_w = DD_INIT_W;
|
||||
dd.win_h = DD_INIT_H;
|
||||
dd.active_tab = 0;
|
||||
montauk::memset(&dd.info, 0, sizeof(dd.info));
|
||||
montauk::diskinfo(&dd.info, port);
|
||||
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create(title, dd.win_w, dd.win_h, &wres) < 0 || wres.id < 0)
|
||||
return;
|
||||
|
||||
dd.win_id = wres.id;
|
||||
dd.pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
dd.open = true;
|
||||
|
||||
render_disk_detail();
|
||||
montauk::win_present(dd.win_id);
|
||||
}
|
||||
|
||||
void close_disk_detail() {
|
||||
auto& dd = g_state.detail;
|
||||
if (!dd.open) return;
|
||||
montauk::win_destroy(dd.win_id);
|
||||
dd.open = false;
|
||||
}
|
||||
|
||||
bool handle_detail_mouse(int mx, int my, bool clicked) {
|
||||
auto& dd = g_state.detail;
|
||||
if (!clicked) return false;
|
||||
|
||||
if (my >= 0 && my < DD_TAB_BAR_H) {
|
||||
int tab_w = dd.win_w / DD_TAB_COUNT;
|
||||
int tab = mx / tab_w;
|
||||
if (tab >= 0 && tab < DD_TAB_COUNT)
|
||||
dd.active_tab = tab;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* MontaukOS Device Explorer — entry point, event loop, state
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "devexplorer.h"
|
||||
|
||||
// ============================================================================
|
||||
// Global state definitions
|
||||
// ============================================================================
|
||||
|
||||
int g_win_w = INIT_W;
|
||||
int g_win_h = INIT_H;
|
||||
|
||||
DevExplorerState g_state;
|
||||
TrueTypeFont* g_font = nullptr;
|
||||
|
||||
// ============================================================================
|
||||
// Display row building
|
||||
// ============================================================================
|
||||
|
||||
int build_display_rows(DevExplorerState* de, DisplayRow* rows) {
|
||||
int count = 0;
|
||||
for (int cat = 0; cat < NUM_CATEGORIES; cat++) {
|
||||
int cat_count = 0;
|
||||
for (int d = 0; d < de->dev_count; d++)
|
||||
if (de->devs[d].category == cat) cat_count++;
|
||||
if (cat_count == 0) continue;
|
||||
|
||||
rows[count].type = ROW_CATEGORY;
|
||||
rows[count].category = cat;
|
||||
rows[count].dev_index = -1;
|
||||
count++;
|
||||
|
||||
if (!de->collapsed[cat]) {
|
||||
for (int d = 0; d < de->dev_count; d++) {
|
||||
if (de->devs[d].category == cat) {
|
||||
rows[count].type = ROW_DEVICE;
|
||||
rows[count].category = cat;
|
||||
rows[count].dev_index = d;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Toolbar hit testing
|
||||
// ============================================================================
|
||||
|
||||
static bool handle_toolbar_click(int mx, int my) {
|
||||
if (my >= TOOLBAR_H) return false;
|
||||
|
||||
int btn_w = 80, btn_h = 26;
|
||||
int btn_x = 8;
|
||||
int btn_y = (TOOLBAR_H - btn_h) / 2;
|
||||
if (mx >= btn_x && mx < btn_x + btn_w && my >= btn_y && my < btn_y + btn_h) {
|
||||
g_state.last_poll_ms = 0; // force refresh
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// List click handling
|
||||
// ============================================================================
|
||||
|
||||
static bool handle_list_click(int mx, int my) {
|
||||
auto& de = g_state;
|
||||
int list_y = TOOLBAR_H;
|
||||
if (my < list_y) return false;
|
||||
|
||||
DisplayRow rows[MAX_DISPLAY_ROWS];
|
||||
int row_count = build_display_rows(&de, rows);
|
||||
|
||||
uint64_t now = montauk::get_milliseconds();
|
||||
int cur_y = list_y;
|
||||
for (int i = de.scroll_y; i < row_count; i++) {
|
||||
int row_h = (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||
if (my >= cur_y && my < cur_y + row_h) {
|
||||
if (rows[i].type == ROW_CATEGORY) {
|
||||
int cat = rows[i].category;
|
||||
de.collapsed[cat] = !de.collapsed[cat];
|
||||
de.selected_row = -1;
|
||||
de.last_click_row = -1;
|
||||
} else {
|
||||
int di = rows[i].dev_index;
|
||||
bool is_double = (de.last_click_row == i)
|
||||
&& (now - de.last_click_ms < 400);
|
||||
|
||||
if (is_double && de.devs[di].category == 7) {
|
||||
int port = (int)de.devs[di]._pad[0];
|
||||
open_disk_detail(port, de.devs[di].name);
|
||||
de.last_click_row = -1;
|
||||
} else {
|
||||
de.selected_row = i;
|
||||
de.last_click_row = i;
|
||||
de.last_click_ms = now;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
cur_y += row_h;
|
||||
if (cur_y >= g_win_h) break;
|
||||
}
|
||||
de.selected_row = -1;
|
||||
de.last_click_row = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Key handling
|
||||
// ============================================================================
|
||||
|
||||
static bool handle_key(const Montauk::KeyEvent& key) {
|
||||
if (!key.pressed) return false;
|
||||
|
||||
auto& de = g_state;
|
||||
DisplayRow rows[MAX_DISPLAY_ROWS];
|
||||
int row_count = build_display_rows(&de, rows);
|
||||
if (row_count == 0) return true;
|
||||
|
||||
if (key.scancode == 0x48) { // Up
|
||||
if (de.selected_row <= 0) de.selected_row = 0;
|
||||
else de.selected_row--;
|
||||
if (de.selected_row < de.scroll_y)
|
||||
de.scroll_y = de.selected_row;
|
||||
} else if (key.scancode == 0x50) { // Down
|
||||
if (de.selected_row < row_count - 1)
|
||||
de.selected_row++;
|
||||
int list_h = g_win_h - TOOLBAR_H;
|
||||
int cur_h = 0, last_visible = de.scroll_y;
|
||||
for (int i = de.scroll_y; i < row_count; i++) {
|
||||
int rh = (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||
if (cur_h + rh > list_h) break;
|
||||
cur_h += rh;
|
||||
last_visible = i;
|
||||
}
|
||||
if (de.selected_row > last_visible)
|
||||
de.scroll_y += (de.selected_row - last_visible);
|
||||
} else if (key.scancode == 0x4B) { // Left — collapse
|
||||
if (de.selected_row >= 0 && de.selected_row < row_count) {
|
||||
int cat = rows[de.selected_row].category;
|
||||
if (!de.collapsed[cat]) {
|
||||
de.collapsed[cat] = true;
|
||||
if (rows[de.selected_row].type == ROW_DEVICE) {
|
||||
for (int i = de.selected_row - 1; i >= 0; i--) {
|
||||
if (rows[i].type == ROW_CATEGORY && rows[i].category == cat) {
|
||||
de.selected_row = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int new_count = build_display_rows(&de, rows);
|
||||
if (de.selected_row >= new_count)
|
||||
de.selected_row = new_count - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (key.scancode == 0x4D) { // Right — expand
|
||||
if (de.selected_row >= 0 && de.selected_row < row_count) {
|
||||
int cat = rows[de.selected_row].category;
|
||||
de.collapsed[cat] = false;
|
||||
}
|
||||
} else if (key.scancode == 0x1C) { // Enter — toggle category
|
||||
if (de.selected_row >= 0 && de.selected_row < row_count) {
|
||||
if (rows[de.selected_row].type == ROW_CATEGORY) {
|
||||
int cat = rows[de.selected_row].category;
|
||||
de.collapsed[cat] = !de.collapsed[cat];
|
||||
}
|
||||
}
|
||||
} else if (key.scancode == 0x01) { // Escape
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Entry point
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void _start() {
|
||||
montauk::memset(&g_state, 0, sizeof(g_state));
|
||||
g_state.selected_row = -1;
|
||||
g_state.last_click_row = -1;
|
||||
|
||||
for (int i = 0; i < NUM_CATEGORIES; i++)
|
||||
g_state.collapsed[i] = false;
|
||||
|
||||
// Load font
|
||||
{
|
||||
TrueTypeFont* f = (TrueTypeFont*)montauk::malloc(sizeof(TrueTypeFont));
|
||||
if (f) {
|
||||
montauk::memset(f, 0, sizeof(TrueTypeFont));
|
||||
if (!f->init("0:/fonts/Roboto-Medium.ttf")) { montauk::mfree(f); f = nullptr; }
|
||||
}
|
||||
g_font = f;
|
||||
}
|
||||
|
||||
// Initial device poll
|
||||
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)
|
||||
montauk::exit(1);
|
||||
|
||||
int win_id = wres.id;
|
||||
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
|
||||
while (true) {
|
||||
Montauk::WinEvent ev;
|
||||
bool redraw_main = false;
|
||||
bool redraw_detail = false;
|
||||
|
||||
// Poll for device refresh
|
||||
uint64_t now = montauk::get_milliseconds();
|
||||
if (now - g_state.last_poll_ms >= POLL_MS) {
|
||||
g_state.dev_count = montauk::devlist(g_state.devs, MAX_DEVS);
|
||||
g_state.last_poll_ms = now;
|
||||
redraw_main = true;
|
||||
}
|
||||
|
||||
// Poll disk detail window
|
||||
if (g_state.detail.open) {
|
||||
int dr = montauk::win_poll(g_state.detail.win_id, &ev);
|
||||
if (dr > 0) {
|
||||
if (ev.type == 3) { // close
|
||||
close_disk_detail();
|
||||
redraw_main = true;
|
||||
} else if (ev.type == 1) {
|
||||
bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
|
||||
if (handle_detail_mouse(ev.mouse.x, ev.mouse.y, clicked))
|
||||
redraw_detail = true;
|
||||
} else if (ev.type == 2) { // resize
|
||||
g_state.detail.win_w = ev.resize.w;
|
||||
g_state.detail.win_h = ev.resize.h;
|
||||
g_state.detail.pixels = (uint32_t*)(uintptr_t)montauk::win_resize(
|
||||
g_state.detail.win_id, ev.resize.w, ev.resize.h);
|
||||
redraw_detail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Poll main window
|
||||
int r = montauk::win_poll(win_id, &ev);
|
||||
|
||||
if (r < 0) break;
|
||||
if (r == 0) {
|
||||
if (redraw_main) { render(pixels); montauk::win_present(win_id); }
|
||||
if (redraw_detail) { render_disk_detail(); montauk::win_present(g_state.detail.win_id); }
|
||||
if (!redraw_main && !redraw_detail) montauk::sleep_ms(16);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev.type == 3) break; // close
|
||||
|
||||
// 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);
|
||||
redraw_main = true;
|
||||
}
|
||||
|
||||
// Keyboard
|
||||
if (ev.type == 0 && ev.key.pressed) {
|
||||
if (!handle_key(ev.key) && ev.key.scancode == 0x01)
|
||||
break;
|
||||
redraw_main = true;
|
||||
}
|
||||
|
||||
// Mouse
|
||||
if (ev.type == 1) {
|
||||
int mx = ev.mouse.x;
|
||||
int my = ev.mouse.y;
|
||||
bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
|
||||
|
||||
if (ev.mouse.scroll != 0) {
|
||||
g_state.scroll_y += ev.mouse.scroll;
|
||||
if (g_state.scroll_y < 0) g_state.scroll_y = 0;
|
||||
redraw_main = true;
|
||||
}
|
||||
|
||||
if (clicked) {
|
||||
if (handle_toolbar_click(mx, my) || handle_list_click(mx, my))
|
||||
redraw_main = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (redraw_main) { render(pixels); montauk::win_present(win_id); }
|
||||
if (redraw_detail) { render_disk_detail(); montauk::win_present(g_state.detail.win_id); }
|
||||
}
|
||||
|
||||
close_disk_detail();
|
||||
montauk::win_destroy(win_id);
|
||||
montauk::exit(0);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* px.h
|
||||
* Shared pixel-level drawing helpers for the MontaukOS Device Explorer
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "devexplorer.h"
|
||||
|
||||
inline 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;
|
||||
}
|
||||
|
||||
inline 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;
|
||||
}
|
||||
|
||||
inline 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; }
|
||||
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 in_corner = false;
|
||||
int cx_off = 0, cy_off = 0;
|
||||
if (col < r && row < r) {
|
||||
cx_off = r - col; cy_off = r - row; in_corner = true;
|
||||
} else if (col >= w - r && row < r) {
|
||||
cx_off = col - (w - r - 1); cy_off = r - row; in_corner = true;
|
||||
} else if (col < r && row >= h - r) {
|
||||
cx_off = r - col; cy_off = row - (h - r - 1); in_corner = true;
|
||||
} else if (col >= w - r && row >= h - r) {
|
||||
cx_off = col - (w - r - 1); cy_off = row - (h - r - 1); in_corner = true;
|
||||
}
|
||||
if (in_corner && cx_off * cx_off + cy_off * cy_off > r * r) continue;
|
||||
px[dy * bw + dx] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void px_text(uint32_t* px, int bw, int bh,
|
||||
int x, int y, const char* text, Color c) {
|
||||
if (g_font)
|
||||
g_font->draw_to_buffer(px, bw, bh, x, y, text, c, FONT_SIZE);
|
||||
}
|
||||
|
||||
inline int text_w(const char* text) {
|
||||
return g_font ? g_font->measure_text(text, FONT_SIZE) : 0;
|
||||
}
|
||||
|
||||
inline int font_h() {
|
||||
if (!g_font) return 16;
|
||||
auto* cache = g_font->get_cache(FONT_SIZE);
|
||||
return cache->ascent - cache->descent;
|
||||
}
|
||||
|
||||
inline 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);
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* render.cpp
|
||||
* MontaukOS Device Explorer — main window rendering
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "px.h"
|
||||
|
||||
// ============================================================================
|
||||
// Triangle drawing helpers
|
||||
// ============================================================================
|
||||
|
||||
static void draw_triangle_right(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int size, Color col) {
|
||||
uint32_t v = col.to_pixel();
|
||||
int half = size / 2;
|
||||
for (int row = 0; row < size; row++) {
|
||||
int dist = (row <= half) ? row : (size - 1 - row);
|
||||
for (int col_px = 0; col_px <= dist; col_px++) {
|
||||
int dx = x + col_px, dy = y + row;
|
||||
if (dx >= 0 && dx < bw && dy >= 0 && dy < bh)
|
||||
px[dy * bw + dx] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_triangle_down(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int size, Color col) {
|
||||
uint32_t v = col.to_pixel();
|
||||
int half = size / 2;
|
||||
for (int row = 0; row < half + 1; row++) {
|
||||
int w = size - row * 2;
|
||||
for (int col_px = 0; col_px < w; col_px++) {
|
||||
int dx = x + row + col_px, dy = y + row;
|
||||
if (dx >= 0 && dx < bw && dy >= 0 && dy < bh)
|
||||
px[dy * bw + dx] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Render: main device list
|
||||
// ============================================================================
|
||||
|
||||
static void render_toolbar(uint32_t* px) {
|
||||
auto& de = g_state;
|
||||
int fh = font_h();
|
||||
|
||||
px_fill(px, g_win_w, g_win_h, 0, 0, g_win_w, TOOLBAR_H, TOOLBAR_BG);
|
||||
px_hline(px, g_win_w, g_win_h, 0, TOOLBAR_H - 1, g_win_w, BORDER_COLOR);
|
||||
|
||||
// "Refresh" button
|
||||
int btn_w = 80, btn_h = 26;
|
||||
int btn_x = 8;
|
||||
int btn_y = (TOOLBAR_H - btn_h) / 2;
|
||||
px_button(px, g_win_w, g_win_h, btn_x, btn_y, btn_w, btn_h,
|
||||
"Refresh", ACCENT_COLOR, WHITE, 4);
|
||||
|
||||
// Device count on right
|
||||
char count_str[24];
|
||||
snprintf(count_str, sizeof(count_str), "%d devices", de.dev_count);
|
||||
int cw = text_w(count_str);
|
||||
px_text(px, g_win_w, g_win_h, g_win_w - cw - 12, (TOOLBAR_H - fh) / 2,
|
||||
count_str, TEXT_COLOR);
|
||||
}
|
||||
|
||||
static void render_list(uint32_t* px) {
|
||||
auto& de = g_state;
|
||||
int fh = font_h();
|
||||
|
||||
DisplayRow rows[MAX_DISPLAY_ROWS];
|
||||
int row_count = build_display_rows(&de, rows);
|
||||
|
||||
int list_y = TOOLBAR_H;
|
||||
int list_h = g_win_h - list_y;
|
||||
if (list_h < 1) return;
|
||||
|
||||
// Compute total content height
|
||||
int total_h = 0;
|
||||
for (int i = 0; i < row_count; i++)
|
||||
total_h += (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||
|
||||
int max_scroll_px = total_h - list_h;
|
||||
if (max_scroll_px < 0) max_scroll_px = 0;
|
||||
|
||||
// Compute scroll pixel offset
|
||||
int scroll_px = 0;
|
||||
for (int i = 0; i < de.scroll_y && i < row_count; i++)
|
||||
scroll_px += (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||
if (scroll_px > max_scroll_px) {
|
||||
scroll_px = max_scroll_px;
|
||||
de.scroll_y = 0;
|
||||
int acc = 0;
|
||||
for (int i = 0; i < row_count; i++) {
|
||||
int rh = (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||
if (acc + rh > max_scroll_px) break;
|
||||
acc += rh;
|
||||
de.scroll_y = i + 1;
|
||||
}
|
||||
}
|
||||
if (de.scroll_y < 0) de.scroll_y = 0;
|
||||
if (de.selected_row >= row_count) de.selected_row = row_count - 1;
|
||||
|
||||
// Draw rows
|
||||
int first_visible = de.scroll_y;
|
||||
int cur_y = list_y;
|
||||
Color menu_hover = Color::from_rgb(0xD0, 0xE0, 0xF8);
|
||||
|
||||
for (int i = first_visible; i < row_count; i++) {
|
||||
int row_h = (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||
if (cur_y >= g_win_h) break;
|
||||
|
||||
if (rows[i].type == ROW_CATEGORY) {
|
||||
int cat = rows[i].category;
|
||||
px_fill(px, g_win_w, g_win_h, 0, cur_y, g_win_w, CAT_H,
|
||||
Color::from_rgb(0xF0, 0xF0, 0xF0));
|
||||
|
||||
if (i == de.selected_row)
|
||||
px_fill(px, g_win_w, g_win_h, 0, cur_y, g_win_w, CAT_H, menu_hover);
|
||||
|
||||
// Expand/collapse triangle
|
||||
int tri_x = 10;
|
||||
int tri_y = cur_y + (CAT_H - 8) / 2;
|
||||
Color tri_color = Color::from_rgb(0x55, 0x55, 0x55);
|
||||
if (de.collapsed[cat])
|
||||
draw_triangle_right(px, g_win_w, g_win_h, tri_x, tri_y, 8, tri_color);
|
||||
else
|
||||
draw_triangle_down(px, g_win_w, g_win_h, tri_x, tri_y, 8, tri_color);
|
||||
|
||||
// Colored dot
|
||||
int dot_x = 24;
|
||||
int dot_y = cur_y + (CAT_H - 8) / 2;
|
||||
Color cat_col = (cat >= 0 && cat < NUM_CATEGORIES)
|
||||
? category_colors[cat] : TEXT_COLOR;
|
||||
px_fill_rounded(px, g_win_w, g_win_h, dot_x, dot_y, 8, 8, 4, cat_col);
|
||||
|
||||
// Category name
|
||||
const char* cat_name = (cat >= 0 && cat < NUM_CATEGORIES)
|
||||
? category_names[cat] : "?";
|
||||
int text_y = cur_y + (CAT_H - fh) / 2;
|
||||
px_text(px, g_win_w, g_win_h, 36, text_y, cat_name,
|
||||
Color::from_rgb(0x33, 0x33, 0x33));
|
||||
|
||||
// Device count
|
||||
int cat_count = 0;
|
||||
for (int d = 0; d < de.dev_count; d++)
|
||||
if (de.devs[d].category == cat) cat_count++;
|
||||
char cnt_buf[16];
|
||||
snprintf(cnt_buf, sizeof(cnt_buf), "(%d)", cat_count);
|
||||
int name_w = text_w(cat_name);
|
||||
px_text(px, g_win_w, g_win_h, 36 + name_w + 8, text_y, cnt_buf,
|
||||
Color::from_rgb(0x88, 0x88, 0x88));
|
||||
|
||||
// Bottom border
|
||||
px_hline(px, g_win_w, g_win_h, 0, cur_y + CAT_H - 1, g_win_w,
|
||||
Color::from_rgb(0xE0, 0xE0, 0xE0));
|
||||
} else {
|
||||
int di = rows[i].dev_index;
|
||||
|
||||
if (i == de.selected_row)
|
||||
px_fill(px, g_win_w, g_win_h, 0, cur_y, g_win_w, ITEM_H, menu_hover);
|
||||
|
||||
int text_y = cur_y + (ITEM_H - fh) / 2;
|
||||
|
||||
// Device name (indented)
|
||||
px_text(px, g_win_w, g_win_h, INDENT + 10, text_y,
|
||||
de.devs[di].name, TEXT_COLOR);
|
||||
|
||||
// Detail string (right column)
|
||||
int detail_x = g_win_w / 2 + 20;
|
||||
px_text(px, g_win_w, g_win_h, detail_x, text_y,
|
||||
de.devs[di].detail, DIM_TEXT);
|
||||
}
|
||||
|
||||
cur_y += row_h;
|
||||
}
|
||||
|
||||
// Scrollbar
|
||||
if (total_h > list_h) {
|
||||
int sb_x = g_win_w - 6;
|
||||
int thumb_h = (list_h * list_h) / total_h;
|
||||
if (thumb_h < 20) thumb_h = 20;
|
||||
int thumb_y = list_y + (scroll_px * (list_h - thumb_h)) / max_scroll_px;
|
||||
px_fill(px, g_win_w, g_win_h, sb_x, list_y, 4, list_h,
|
||||
Color::from_rgb(0xE0, 0xE0, 0xE0));
|
||||
px_fill(px, g_win_w, g_win_h, sb_x, thumb_y, 4, thumb_h,
|
||||
Color::from_rgb(0xAA, 0xAA, 0xAA));
|
||||
}
|
||||
}
|
||||
|
||||
void render(uint32_t* pixels) {
|
||||
px_fill(pixels, g_win_w, g_win_h, 0, 0, g_win_w, g_win_h, BG_COLOR);
|
||||
render_toolbar(pixels);
|
||||
render_list(pixels);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* stb_truetype_impl.cpp
|
||||
* Single compilation unit for stb_truetype in MontaukOS freestanding environment
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <montauk/heap.h>
|
||||
#include <montauk/string.h>
|
||||
#include <gui/stb_math.h>
|
||||
|
||||
// Override all stb_truetype dependencies before including the implementation
|
||||
|
||||
#define STBTT_ifloor(x) ((int) stb_floor(x))
|
||||
#define STBTT_iceil(x) ((int) stb_ceil(x))
|
||||
#define STBTT_sqrt(x) stb_sqrt(x)
|
||||
#define STBTT_pow(x,y) stb_pow(x,y)
|
||||
#define STBTT_fmod(x,y) stb_fmod(x,y)
|
||||
#define STBTT_cos(x) stb_cos(x)
|
||||
#define STBTT_acos(x) stb_acos(x)
|
||||
#define STBTT_fabs(x) stb_fabs(x)
|
||||
|
||||
#define STBTT_malloc(x,u) ((void)(u), montauk::malloc(x))
|
||||
#define STBTT_free(x,u) ((void)(u), montauk::mfree(x))
|
||||
|
||||
#define STBTT_memcpy(d,s,n) montauk::memcpy(d,s,n)
|
||||
#define STBTT_memset(d,v,n) montauk::memset(d,v,n)
|
||||
|
||||
#define STBTT_strlen(x) montauk::slen(x)
|
||||
|
||||
#define STBTT_assert(x) ((void)(x))
|
||||
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include <gui/stb_truetype.h>
|
||||
Reference in New Issue
Block a user