mirror of
https://github.com/BoredDevNL/BoredOS.git
synced 2026-05-15 10:48:38 +00:00
feat: .tar application loading
This commit is contained in:
parent
63749b8734
commit
b7020152c1
8 changed files with 323 additions and 117 deletions
110
Makefile
110
Makefile
|
|
@ -132,58 +132,56 @@ $(KERNEL_ELF): $(OBJ_FILES)
|
|||
$(LD) $(LDFLAGS) -o $@ $(OBJ_FILES)
|
||||
$(MAKE) -C $(SRC_DIR)/userland
|
||||
|
||||
$(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
|
||||
$(BUILD_DIR)/initrd.tar: $(KERNEL_ELF)
|
||||
rm -rf $(BUILD_DIR)/initrd
|
||||
mkdir -p $(BUILD_DIR)/initrd/bin
|
||||
mkdir -p $(BUILD_DIR)/initrd/Library/images/Wallpapers
|
||||
mkdir -p $(BUILD_DIR)/initrd/Library/images/gif
|
||||
mkdir -p $(BUILD_DIR)/initrd/Library/Fonts/Emoji
|
||||
mkdir -p $(BUILD_DIR)/initrd/Library/DOOM
|
||||
mkdir -p $(BUILD_DIR)/initrd/docs
|
||||
|
||||
@for f in $(SRC_DIR)/userland/bin/*.elf; do \
|
||||
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/bin/; fi \
|
||||
done
|
||||
@for f in $(SRC_DIR)/images/wallpapers/*; do \
|
||||
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/images/Wallpapers/; fi \
|
||||
done
|
||||
@for f in $(SRC_DIR)/images/gif/*.gif; do \
|
||||
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/images/gif/; fi \
|
||||
done
|
||||
@for f in $(SRC_DIR)/fonts/*.ttf; do \
|
||||
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/; fi \
|
||||
done
|
||||
@for f in $(SRC_DIR)/fonts/Emoji/*.ttf; do \
|
||||
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/Emoji/; fi \
|
||||
done
|
||||
@if [ -f $(SRC_DIR)/userland/games/doom/doom1.wad ]; then cp $(SRC_DIR)/userland/games/doom/doom1.wad $(BUILD_DIR)/initrd/Library/DOOM/; fi
|
||||
@for f in $$(find docs -name '*.md' 2>/dev/null); do \
|
||||
if [ -f "$$f" ]; then \
|
||||
dir=$$(dirname "$$f"); \
|
||||
mkdir -p $(BUILD_DIR)/initrd/"$$dir"; \
|
||||
cp "$$f" $(BUILD_DIR)/initrd/"$$dir"/; \
|
||||
fi \
|
||||
done
|
||||
@if [ -f README.md ]; then cp README.md $(BUILD_DIR)/initrd/; fi
|
||||
@if [ -f LICENSE ]; then cp LICENSE $(BUILD_DIR)/initrd/; fi
|
||||
|
||||
cd $(BUILD_DIR)/initrd && COPYFILE_DISABLE=1 tar --exclude="._*" -cf ../initrd.tar *
|
||||
|
||||
$(ISO_IMAGE): $(KERNEL_ELF) $(BUILD_DIR)/initrd.tar limine.conf limine-setup
|
||||
rm -rf $(ISO_DIR)
|
||||
mkdir -p $(ISO_DIR)
|
||||
mkdir -p $(ISO_DIR)/EFI/BOOT
|
||||
|
||||
cp $(KERNEL_ELF) $(ISO_DIR)/
|
||||
cp limine.conf $(ISO_DIR)/
|
||||
mkdir -p $(ISO_DIR)/bin
|
||||
@for f in $(SRC_DIR)/userland/bin/*.elf; do \
|
||||
if [ -f "$$f" ]; then \
|
||||
basename=$$(basename "$$f"); \
|
||||
cp "$$f" $(ISO_DIR)/bin/; \
|
||||
echo " module_path: boot():/bin/$$basename" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
|
||||
@if [ -f README.md ]; then cp README.md $(ISO_DIR)/; fi
|
||||
@if [ -f $(SRC_DIR)/userland/games/doom/doom1.wad ]; then \
|
||||
mkdir -p $(ISO_DIR)/Library/DOOM; \
|
||||
cp $(SRC_DIR)/userland/games/doom/doom1.wad $(ISO_DIR)/Library/DOOM/; \
|
||||
echo " module_path: boot():/Library/DOOM/doom1.wad" >> $(ISO_DIR)/limine.conf; \
|
||||
fi
|
||||
cp $(BUILD_DIR)/initrd.tar $(ISO_DIR)/
|
||||
echo " module_path: boot():/initrd.tar" >> $(ISO_DIR)/limine.conf
|
||||
|
||||
mkdir -p $(ISO_DIR)/Library/images/Wallpapers
|
||||
@for f in $(SRC_DIR)/images/wallpapers/*; do \
|
||||
if [ -f "$$f" ]; then \
|
||||
basename=$$(basename "$$f"); \
|
||||
cp "$$f" $(ISO_DIR)/Library/images/Wallpapers/; \
|
||||
echo " module_path: boot():/Library/images/Wallpapers/$$basename" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
@if [ -f splash.jpg ]; then cp splash.jpg $(ISO_DIR)/; fi
|
||||
|
||||
mkdir -p $(ISO_DIR)/Library/images/gif
|
||||
@for f in $(SRC_DIR)/images/gif/*.gif; do \
|
||||
if [ -f "$$f" ]; then \
|
||||
basename=$$(basename "$$f"); \
|
||||
cp "$$f" $(ISO_DIR)/Library/images/gif/; \
|
||||
echo " module_path: boot():/Library/images/gif/$$basename" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
|
||||
mkdir -p $(ISO_DIR)/docs
|
||||
@for f in $$(find docs -name '*.md'); do \
|
||||
if [ -f "$$f" ]; then \
|
||||
dir=$$(dirname "$$f"); \
|
||||
mkdir -p $(ISO_DIR)/"$$dir"; \
|
||||
cp "$$f" $(ISO_DIR)/"$$dir"/; \
|
||||
echo " module_path: boot():/$$f" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
|
||||
cp limine/limine-bios.sys $(ISO_DIR)/
|
||||
cp limine/limine-bios-cd.bin $(ISO_DIR)/
|
||||
cp limine/limine-uefi-cd.bin $(ISO_DIR)/
|
||||
|
|
@ -191,34 +189,6 @@ $(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
|
|||
cp limine/BOOTX64.EFI $(ISO_DIR)/EFI/BOOT/
|
||||
cp limine/BOOTIA32.EFI $(ISO_DIR)/EFI/BOOT/
|
||||
|
||||
mkdir -p $(ISO_DIR)/Library/Fonts
|
||||
@for f in $(SRC_DIR)/fonts/*.ttf; do \
|
||||
if [ -f "$$f" ]; then \
|
||||
basename=$$(basename "$$f"); \
|
||||
cp "$$f" $(ISO_DIR)/Library/Fonts/; \
|
||||
echo " module_path: boot():/Library/Fonts/$$basename" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
|
||||
mkdir -p $(ISO_DIR)/Library/Fonts/Emoji
|
||||
@for f in $(SRC_DIR)/fonts/Emoji/*.ttf; do \
|
||||
if [ -f "$$f" ]; then \
|
||||
basename=$$(basename "$$f"); \
|
||||
cp "$$f" $(ISO_DIR)/Library/Fonts/Emoji/; \
|
||||
echo " module_path: boot():/Library/Fonts/Emoji/$$basename" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
|
||||
@if [ -f README.md ]; then \
|
||||
cp README.md $(ISO_DIR)/; \
|
||||
echo " module_path: boot():/README.md" >> $(ISO_DIR)/limine.conf; \
|
||||
fi
|
||||
|
||||
@if [ -f LICENSE ]; then \
|
||||
cp LICENSE $(ISO_DIR)/; \
|
||||
echo " module_path: boot():/LICENSE" >> $(ISO_DIR)/limine.conf; \
|
||||
fi
|
||||
|
||||
$(XORRISO) -as mkisofs -R -J -b limine-bios-cd.bin \
|
||||
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||||
--efi-boot limine-uefi-cd.bin \
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include "wm.h"
|
||||
#include "io.h"
|
||||
#include "fat32.h"
|
||||
#include "tar.h"
|
||||
#include "memory_manager.h"
|
||||
#include "platform.h"
|
||||
#include "wallpaper.h"
|
||||
|
|
@ -210,6 +211,15 @@ void kmain(void) {
|
|||
if (fs_starts_with(clean_path, "boot():")) clean_path += 7;
|
||||
else if (fs_starts_with(clean_path, "boot:///")) clean_path += 8;
|
||||
|
||||
int len = 0;
|
||||
while(clean_path[len]) len++;
|
||||
|
||||
if (len >= 4 && clean_path[len-4] == '.' && clean_path[len-3] == 't' && clean_path[len-2] == 'a' && clean_path[len-1] == 'r') {
|
||||
serial_write("[DEBUG] Parsing TAR initrd: ");
|
||||
serial_write(clean_path);
|
||||
serial_write("\n");
|
||||
tar_parse(mod->address, mod->size);
|
||||
} else {
|
||||
char dir_path[256];
|
||||
int last_slash = -1;
|
||||
for (int j = 0; clean_path[j]; j++) {
|
||||
|
|
@ -228,6 +238,7 @@ void kmain(void) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize fonts now that FAT32 and modules are loaded
|
||||
uint64_t current_rsp;
|
||||
|
|
|
|||
128
src/fs/tar.c
Normal file
128
src/fs/tar.c
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
|
||||
// This header needs to maintain in any file it is present in, as per the GPL license terms.
|
||||
#include "tar.h"
|
||||
#include "fat32.h"
|
||||
|
||||
// The standard TAR header block is 512 bytes.
|
||||
struct tar_header {
|
||||
char filename[100];
|
||||
char mode[8];
|
||||
char uid[8];
|
||||
char gid[8];
|
||||
char size[12];
|
||||
char mtime[12];
|
||||
char chksum[8];
|
||||
char typeflag;
|
||||
char linkname[100];
|
||||
char magic[6];
|
||||
char version[2];
|
||||
char uname[32];
|
||||
char gname[32];
|
||||
char devmajor[8];
|
||||
char devminor[8];
|
||||
char prefix[155];
|
||||
char pad[12];
|
||||
} __attribute__((packed));
|
||||
|
||||
// Helper: parse tar octal field representation
|
||||
static uint64_t tar_parse_octal(const char *str, int size) {
|
||||
uint64_t result = 0;
|
||||
while (size-- > 0) {
|
||||
if (*str >= '0' && *str <= '7') {
|
||||
result = (result << 3) + (*str - '0');
|
||||
}
|
||||
str++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helper: Make directories sequentially for nested paths
|
||||
static void tar_mkdir_recursive(const char *path) {
|
||||
char temp[256];
|
||||
int i = 0;
|
||||
if (path[0] == '/') {
|
||||
temp[0] = '/';
|
||||
i = 1;
|
||||
}
|
||||
while (path[i] && i < 255) {
|
||||
temp[i] = path[i];
|
||||
if (path[i] == '/') {
|
||||
temp[i] = '\0';
|
||||
fat32_mkdir(temp);
|
||||
temp[i] = '/';
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (i > 0 && temp[i - 1] != '/') {
|
||||
temp[i] = '\0';
|
||||
fat32_mkdir(temp);
|
||||
}
|
||||
}
|
||||
|
||||
void tar_parse(void *archive, uint64_t archive_size) {
|
||||
uint8_t *ptr = (uint8_t *)archive;
|
||||
uint8_t *end = ptr + archive_size;
|
||||
|
||||
while (ptr + 512 <= end) {
|
||||
struct tar_header *header = (struct tar_header *)ptr;
|
||||
|
||||
// End of archive is marked by empty blocks
|
||||
if (header->filename[0] == '\0') {
|
||||
break;
|
||||
}
|
||||
|
||||
uint64_t file_size = tar_parse_octal(header->size, 11);
|
||||
|
||||
char full_path[256];
|
||||
// Ensure path starts with a '/' for VFS consistency
|
||||
if (header->filename[0] != '/') {
|
||||
full_path[0] = '/';
|
||||
int j = 0;
|
||||
while (header->filename[j] && j < 254) {
|
||||
full_path[j + 1] = header->filename[j];
|
||||
j++;
|
||||
}
|
||||
full_path[j + 1] = '\0';
|
||||
} else {
|
||||
int j = 0;
|
||||
while (header->filename[j] && j < 255) {
|
||||
full_path[j] = header->filename[j];
|
||||
j++;
|
||||
}
|
||||
full_path[j] = '\0';
|
||||
}
|
||||
|
||||
if (header->typeflag == '5') {
|
||||
// It's a directory
|
||||
tar_mkdir_recursive(full_path);
|
||||
} else if (header->typeflag == '0' || header->typeflag == '\0') {
|
||||
// It's a normal file
|
||||
// First ensure the parent directory exists
|
||||
char parent_path[256];
|
||||
int last_slash = -1;
|
||||
for (int j = 0; full_path[j]; j++) {
|
||||
parent_path[j] = full_path[j];
|
||||
if (full_path[j] == '/') {
|
||||
last_slash = j;
|
||||
}
|
||||
}
|
||||
if (last_slash > 0) {
|
||||
parent_path[last_slash] = '\0';
|
||||
tar_mkdir_recursive(parent_path);
|
||||
}
|
||||
|
||||
// Extract the file data block directly into the VFS
|
||||
FAT32_FileHandle *fh = fat32_open(full_path, "w");
|
||||
if (fh && fh->valid) {
|
||||
fat32_write(fh, ptr + 512, file_size);
|
||||
fat32_close(fh);
|
||||
}
|
||||
}
|
||||
|
||||
// Advance pointer to the next file header
|
||||
// Header block (512) + File data (padded to 512-byte multiples)
|
||||
uint64_t data_blocks = (file_size + 511) / 512;
|
||||
ptr += 512 + (data_blocks * 512);
|
||||
}
|
||||
}
|
||||
13
src/fs/tar.h
Normal file
13
src/fs/tar.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
|
||||
// This header needs to maintain in any file it is present in, as per the GPL license terms.
|
||||
#ifndef TAR_H
|
||||
#define TAR_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Parse a TAR archive located in memory and extract its contents into the current filesystem (fatal32 RAM disk).
|
||||
void tar_parse(void *archive, uint64_t archive_size);
|
||||
|
||||
#endif
|
||||
|
|
@ -1553,6 +1553,7 @@ static void browser_paint(void) {
|
|||
if (pixels) ui_draw_image(win_browser, el->x, draw_y, el->img_w, el->img_h, pixels);
|
||||
else ui_draw_rect(win_browser, el->x, draw_y, 100, 80, 0xFFCCCCCC);
|
||||
} else if (el->tag == TAG_INPUT) {
|
||||
browser_ctx.use_light_theme = true;
|
||||
char visible[128];
|
||||
int v_len = 0;
|
||||
int max_v = (el->w - 10) / 8;
|
||||
|
|
@ -1568,20 +1569,27 @@ static void browser_paint(void) {
|
|||
if (tb.cursor_pos < 0) tb.cursor_pos = 0;
|
||||
tb.focused = (focused_element == i);
|
||||
widget_textbox_draw(&browser_ctx, &tb);
|
||||
browser_ctx.use_light_theme = false;
|
||||
} else if (el->tag == TAG_BUTTON) {
|
||||
browser_ctx.use_light_theme = true;
|
||||
widget_button_t btn;
|
||||
widget_button_init(&btn, el->x, draw_y, el->w, el->h, el->attr_value);
|
||||
widget_button_draw(&browser_ctx, &btn);
|
||||
browser_ctx.use_light_theme = false;
|
||||
} else if (el->tag == TAG_RADIO) {
|
||||
browser_ctx.use_light_theme = true;
|
||||
widget_checkbox_t cb;
|
||||
widget_checkbox_init(&cb, el->x, draw_y, el->w, el->h, "", true);
|
||||
cb.checked = el->checked;
|
||||
widget_checkbox_draw(&browser_ctx, &cb);
|
||||
browser_ctx.use_light_theme = false;
|
||||
} else if (el->tag == TAG_CHECKBOX) {
|
||||
browser_ctx.use_light_theme = true;
|
||||
widget_checkbox_t cb;
|
||||
widget_checkbox_init(&cb, el->x, draw_y, el->w, el->h, "", false);
|
||||
cb.checked = el->checked;
|
||||
widget_checkbox_draw(&browser_ctx, &cb);
|
||||
browser_ctx.use_light_theme = false;
|
||||
} else if (el->tag == TAG_HR) {
|
||||
ui_draw_rect(win_browser, el->x, draw_y + el->h / 2, el->w, 2, 0xFF888888);
|
||||
ui_draw_rect(win_browser, el->x, draw_y + (el->h / 2) + 2, el->w, 1, 0xFFFFFFFF);
|
||||
|
|
@ -1733,15 +1741,38 @@ int main(int argc, char **argv) {
|
|||
if (el->tag == TAG_RADIO) {
|
||||
for (int k = 0; k < element_count; k++) {
|
||||
if (elements[k].tag == TAG_RADIO && elements[k].form_id == el->form_id && str_iequals(elements[k].input_name, el->input_name)) {
|
||||
if (elements[k].checked) {
|
||||
elements[k].checked = false;
|
||||
widget_checkbox_t cb;
|
||||
widget_checkbox_init(&cb, elements[k].x, elements[k].y - scroll_y + URL_BAR_H, elements[k].w, elements[k].h, "", true);
|
||||
cb.checked = false;
|
||||
browser_ctx.use_light_theme = true;
|
||||
widget_checkbox_draw(&browser_ctx, &cb);
|
||||
browser_ctx.use_light_theme = false;
|
||||
ui_mark_dirty(win_browser, cb.x, cb.y, cb.w, cb.h);
|
||||
}
|
||||
}
|
||||
}
|
||||
el->checked = true;
|
||||
needs_repaint = true; found = true; break;
|
||||
widget_checkbox_t cb;
|
||||
widget_checkbox_init(&cb, el->x, el->y - scroll_y + URL_BAR_H, el->w, el->h, "", true);
|
||||
cb.checked = true;
|
||||
browser_ctx.use_light_theme = true;
|
||||
widget_checkbox_draw(&browser_ctx, &cb);
|
||||
browser_ctx.use_light_theme = false;
|
||||
ui_mark_dirty(win_browser, cb.x, cb.y, cb.w, cb.h);
|
||||
found = true; break;
|
||||
}
|
||||
if (el->tag == TAG_CHECKBOX) {
|
||||
el->checked = !el->checked;
|
||||
needs_repaint = true; found = true; break;
|
||||
widget_checkbox_t cb;
|
||||
widget_checkbox_init(&cb, el->x, el->y - scroll_y + URL_BAR_H, el->w, el->h, "", false);
|
||||
cb.checked = el->checked;
|
||||
browser_ctx.use_light_theme = true;
|
||||
widget_checkbox_draw(&browser_ctx, &cb);
|
||||
browser_ctx.use_light_theme = false;
|
||||
ui_mark_dirty(win_browser, cb.x, cb.y, cb.w, cb.h);
|
||||
found = true; break;
|
||||
}
|
||||
if (el->tag == TAG_BUTTON) {
|
||||
int fid = el->form_id;
|
||||
|
|
|
|||
|
|
@ -296,6 +296,12 @@ static void load_wallpapers(void) {
|
|||
|
||||
wallpaper_count++;
|
||||
}
|
||||
|
||||
for (int i = 0; i < wallpaper_count; i++) {
|
||||
int tx = (i % 3) * (WALLPAPER_THUMB_W + 15);
|
||||
int ty = (i / 3) * (WALLPAPER_THUMB_H + 30);
|
||||
widget_button_init(&btn_wp_thumbs[i], 8 + tx, 306 + ty, WALLPAPER_THUMB_W + 8, WALLPAPER_THUMB_H + 20, "");
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t parse_rgb_separate(const char *r, const char *g, const char *b) {
|
||||
|
|
@ -471,7 +477,7 @@ static void control_panel_paint_wallpaper(ui_window_t win) {
|
|||
|
||||
for (int i = 0; i < wallpaper_count; i++) {
|
||||
int tx = (i % 3) * (WALLPAPER_THUMB_W + 15);
|
||||
int ty = (i / 3) * (WALLPAPER_THUMB_H + 25);
|
||||
int ty = (i / 3) * (WALLPAPER_THUMB_H + 30);
|
||||
|
||||
widget_button_draw(&settings_ctx, &btn_wp_thumbs[i]);
|
||||
if (wallpapers[i].valid) {
|
||||
|
|
@ -481,7 +487,7 @@ static void control_panel_paint_wallpaper(ui_window_t win) {
|
|||
}
|
||||
}
|
||||
}
|
||||
ui_draw_string(win, button_x + tx + 8, button_y + ty + WALLPAPER_THUMB_H + 6, wallpapers[i].name, COLOR_DARK_TEXT);
|
||||
ui_draw_string(win, button_x + tx + 8, button_y + ty + WALLPAPER_THUMB_H + 8, wallpapers[i].name, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,18 +37,32 @@ void widget_button_init(widget_button_t *btn, int x, int y, int w, int h, const
|
|||
}
|
||||
|
||||
void widget_button_draw(widget_context_t *ctx, widget_button_t *btn) {
|
||||
uint32_t bg_color = MAC_BTN_BG_NORMAL;
|
||||
uint32_t border_color = MAC_BTN_BORDER;
|
||||
uint32_t normal_bg = MAC_BTN_BG_NORMAL;
|
||||
uint32_t hover_bg = MAC_BTN_BG_HOVER;
|
||||
uint32_t pressed_bg = MAC_BTN_BG_PRESSED;
|
||||
uint32_t text_color = COLOR_WHITE;
|
||||
|
||||
if (ctx->use_light_theme) {
|
||||
border_color = 0xFFB0B0B0;
|
||||
normal_bg = 0xFFEAEAEA;
|
||||
hover_bg = 0xFFD0D0D0;
|
||||
pressed_bg = 0xFFB0B0B0;
|
||||
text_color = COLOR_BLACK;
|
||||
}
|
||||
|
||||
uint32_t bg_color = normal_bg;
|
||||
if (btn->pressed) {
|
||||
bg_color = MAC_BTN_BG_PRESSED;
|
||||
bg_color = pressed_bg;
|
||||
} else if (btn->hovered) {
|
||||
bg_color = MAC_BTN_BG_HOVER;
|
||||
bg_color = hover_bg;
|
||||
}
|
||||
|
||||
if (ctx->draw_rounded_rect_filled) {
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, btn->x, btn->y, btn->w, btn->h, 6, MAC_BTN_BORDER);
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, btn->x, btn->y, btn->w, btn->h, 6, border_color);
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, btn->x + 1, btn->y + 1, btn->w - 2, btn->h - 2, 5, bg_color);
|
||||
} else if (ctx->draw_rect) {
|
||||
ctx->draw_rect(ctx->user_data, btn->x, btn->y, btn->w, btn->h, MAC_BTN_BORDER);
|
||||
ctx->draw_rect(ctx->user_data, btn->x, btn->y, btn->w, btn->h, border_color);
|
||||
ctx->draw_rect(ctx->user_data, btn->x + 1, btn->y + 1, btn->w - 2, btn->h - 2, bg_color);
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +70,7 @@ void widget_button_draw(widget_context_t *ctx, widget_button_t *btn) {
|
|||
int len = string_len(btn->text);
|
||||
int tx = btn->x + (btn->w - (len * 8)) / 2;
|
||||
int ty = btn->y + (btn->h - 8) / 2;
|
||||
ctx->draw_string(ctx->user_data, tx, ty, btn->text, COLOR_WHITE);
|
||||
ctx->draw_string(ctx->user_data, tx, ty, btn->text, text_color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -194,13 +208,23 @@ void widget_textbox_init(widget_textbox_t *tb, int x, int y, int w, int h, char
|
|||
}
|
||||
|
||||
void widget_textbox_draw(widget_context_t *ctx, widget_textbox_t *tb) {
|
||||
uint32_t border_color = MAC_BTN_BORDER;
|
||||
uint32_t bg_color = COLOR_BLACK;
|
||||
uint32_t text_color = COLOR_WHITE;
|
||||
|
||||
if (ctx->use_light_theme) {
|
||||
border_color = 0xFFA0A0A0;
|
||||
bg_color = 0xFFFFFFFF;
|
||||
text_color = COLOR_BLACK;
|
||||
}
|
||||
|
||||
// Background and border
|
||||
if (ctx->draw_rounded_rect_filled) {
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, tb->x, tb->y, tb->w, tb->h, 4, MAC_BTN_BORDER);
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, tb->x + 1, tb->y + 1, tb->w - 2, tb->h - 2, 3, COLOR_BLACK); // dark background
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, tb->x, tb->y, tb->w, tb->h, 4, border_color);
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, tb->x + 1, tb->y + 1, tb->w - 2, tb->h - 2, 3, bg_color); // background
|
||||
} else if (ctx->draw_rect) {
|
||||
ctx->draw_rect(ctx->user_data, tb->x, tb->y, tb->w, tb->h, MAC_BTN_BORDER);
|
||||
ctx->draw_rect(ctx->user_data, tb->x + 1, tb->y + 1, tb->w - 2, tb->h - 2, COLOR_BLACK);
|
||||
ctx->draw_rect(ctx->user_data, tb->x, tb->y, tb->w, tb->h, border_color);
|
||||
ctx->draw_rect(ctx->user_data, tb->x + 1, tb->y + 1, tb->w - 2, tb->h - 2, bg_color);
|
||||
}
|
||||
|
||||
if (ctx->draw_string && tb->text) {
|
||||
|
|
@ -217,7 +241,7 @@ void widget_textbox_draw(widget_context_t *ctx, widget_textbox_t *tb) {
|
|||
if (text_w > max_w) scroll_x = text_w - max_w;
|
||||
|
||||
// Very basic simple drawing, without proper clipping since context lacks it
|
||||
ctx->draw_string(ctx->user_data, tb->x + 5, tb->y + (tb->h - 8) / 2, tb->text, COLOR_WHITE);
|
||||
ctx->draw_string(ctx->user_data, tb->x + 5, tb->y + (tb->h - 8) / 2, tb->text, text_color);
|
||||
|
||||
if (tb->focused && ctx->draw_rect) {
|
||||
int cx = 0;
|
||||
|
|
@ -236,7 +260,7 @@ void widget_textbox_draw(widget_context_t *ctx, widget_textbox_t *tb) {
|
|||
|
||||
if (cx > max_w) cx = max_w; // clamped to visible end
|
||||
|
||||
ctx->draw_rect(ctx->user_data, tb->x + 5 + cx, tb->y + (tb->h - 12) / 2, 2, 12, COLOR_WHITE);
|
||||
ctx->draw_rect(ctx->user_data, tb->x + 5 + cx, tb->y + (tb->h - 12) / 2, 2, 12, text_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -281,26 +305,36 @@ void widget_dropdown_init(widget_dropdown_t *dd, int x, int y, int w, int h, con
|
|||
}
|
||||
|
||||
void widget_dropdown_draw(widget_context_t *ctx, widget_dropdown_t *dd) {
|
||||
uint32_t border_color = MAC_BTN_BORDER;
|
||||
uint32_t bg_color = MAC_BTN_BG_NORMAL;
|
||||
uint32_t text_color = COLOR_WHITE;
|
||||
|
||||
if (ctx->use_light_theme) {
|
||||
border_color = 0xFFB0B0B0;
|
||||
bg_color = 0xFFE0E0E0;
|
||||
text_color = COLOR_BLACK;
|
||||
}
|
||||
|
||||
if (ctx->draw_rounded_rect_filled) {
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, dd->x, dd->y, dd->w, dd->h, 4, MAC_BTN_BORDER);
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, dd->x + 1, dd->y + 1, dd->w - 2, dd->h - 2, 3, MAC_BTN_BG_NORMAL);
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, dd->x, dd->y, dd->w, dd->h, 4, border_color);
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, dd->x + 1, dd->y + 1, dd->w - 2, dd->h - 2, 3, bg_color);
|
||||
} else if (ctx->draw_rect) {
|
||||
ctx->draw_rect(ctx->user_data, dd->x, dd->y, dd->w, dd->h, MAC_BTN_BORDER);
|
||||
ctx->draw_rect(ctx->user_data, dd->x + 1, dd->y + 1, dd->w - 2, dd->h - 2, MAC_BTN_BG_NORMAL);
|
||||
ctx->draw_rect(ctx->user_data, dd->x, dd->y, dd->w, dd->h, border_color);
|
||||
ctx->draw_rect(ctx->user_data, dd->x + 1, dd->y + 1, dd->w - 2, dd->h - 2, bg_color);
|
||||
}
|
||||
|
||||
if (ctx->draw_string && dd->items && dd->item_count > 0 && dd->selected_idx >= 0 && dd->selected_idx < dd->item_count) {
|
||||
ctx->draw_string(ctx->user_data, dd->x + 5, dd->y + (dd->h - 8) / 2, dd->items[dd->selected_idx], COLOR_WHITE);
|
||||
ctx->draw_string(ctx->user_data, dd->x + dd->w - 12, dd->y + (dd->h - 8) / 2, "v", COLOR_WHITE);
|
||||
ctx->draw_string(ctx->user_data, dd->x + 5, dd->y + (dd->h - 8) / 2, dd->items[dd->selected_idx], text_color);
|
||||
ctx->draw_string(ctx->user_data, dd->x + dd->w - 12, dd->y + (dd->h - 8) / 2, "v", text_color);
|
||||
}
|
||||
|
||||
if (dd->is_open && ctx->draw_rect && dd->items) {
|
||||
int menu_h = dd->item_count * dd->h;
|
||||
ctx->draw_rect(ctx->user_data, dd->x, dd->y + dd->h, dd->w, menu_h, MAC_BTN_BORDER);
|
||||
ctx->draw_rect(ctx->user_data, dd->x + 1, dd->y + dd->h + 1, dd->w - 2, menu_h - 2, MAC_BTN_BG_NORMAL);
|
||||
ctx->draw_rect(ctx->user_data, dd->x, dd->y + dd->h, dd->w, menu_h, border_color);
|
||||
ctx->draw_rect(ctx->user_data, dd->x + 1, dd->y + dd->h + 1, dd->w - 2, menu_h - 2, bg_color);
|
||||
for (int i = 0; i < dd->item_count; i++) {
|
||||
if (ctx->draw_string) {
|
||||
ctx->draw_string(ctx->user_data, dd->x + 5, dd->y + dd->h + i * dd->h + (dd->h - 8)/2, dd->items[i], COLOR_WHITE);
|
||||
ctx->draw_string(ctx->user_data, dd->x + 5, dd->y + dd->h + i * dd->h + (dd->h - 8)/2, dd->items[i], text_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -344,27 +378,39 @@ void widget_checkbox_draw(widget_context_t *ctx, widget_checkbox_t *cb) {
|
|||
int box_size = 14;
|
||||
int box_y = cb->y + (cb->h - box_size) / 2;
|
||||
|
||||
uint32_t border_color = MAC_BTN_BORDER;
|
||||
uint32_t bg_color = MAC_BTN_BG_NORMAL;
|
||||
uint32_t inner_color = COLOR_WHITE;
|
||||
uint32_t text_color = COLOR_WHITE;
|
||||
|
||||
if (ctx->use_light_theme) {
|
||||
border_color = 0xFF909090;
|
||||
bg_color = 0xFFFFFFFF;
|
||||
inner_color = 0xFF404040;
|
||||
text_color = COLOR_BLACK;
|
||||
}
|
||||
|
||||
if (ctx->draw_rounded_rect_filled) {
|
||||
int radius = cb->is_radio ? box_size / 2 : 3;
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, cb->x, box_y, box_size, box_size, radius, MAC_BTN_BORDER);
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, cb->x + 1, box_y + 1, box_size - 2, box_size - 2, radius - 1, MAC_BTN_BG_NORMAL);
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, cb->x, box_y, box_size, box_size, radius, border_color);
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, cb->x + 1, box_y + 1, box_size - 2, box_size - 2, radius - 1, bg_color);
|
||||
|
||||
if (cb->checked) {
|
||||
int inner = box_size - 6;
|
||||
int inner_rad = cb->is_radio ? inner / 2 : 2;
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, cb->x + 3, box_y + 3, inner, inner, inner_rad, COLOR_WHITE);
|
||||
ctx->draw_rounded_rect_filled(ctx->user_data, cb->x + 3, box_y + 3, inner, inner, inner_rad, inner_color);
|
||||
}
|
||||
} else if (ctx->draw_rect) {
|
||||
ctx->draw_rect(ctx->user_data, cb->x, box_y, box_size, box_size, MAC_BTN_BORDER);
|
||||
ctx->draw_rect(ctx->user_data, cb->x + 1, box_y + 1, box_size - 2, box_size - 2, MAC_BTN_BG_NORMAL);
|
||||
ctx->draw_rect(ctx->user_data, cb->x, box_y, box_size, box_size, border_color);
|
||||
ctx->draw_rect(ctx->user_data, cb->x + 1, box_y + 1, box_size - 2, box_size - 2, bg_color);
|
||||
if (cb->checked) {
|
||||
int inner = box_size - 6;
|
||||
ctx->draw_rect(ctx->user_data, cb->x + 3, box_y + 3, inner, inner, COLOR_WHITE);
|
||||
ctx->draw_rect(ctx->user_data, cb->x + 3, box_y + 3, inner, inner, inner_color);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->draw_string && cb->text) {
|
||||
ctx->draw_string(ctx->user_data, cb->x + box_size + 8, cb->y + (cb->h - 8) / 2, cb->text, COLOR_WHITE);
|
||||
ctx->draw_string(ctx->user_data, cb->x + box_size + 8, cb->y + (cb->h - 8) / 2, cb->text, text_color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ typedef struct {
|
|||
void (*draw_string)(void *user_data, int x, int y, const char *str, uint32_t color);
|
||||
int (*measure_string_width)(void *user_data, const char *str);
|
||||
void (*mark_dirty)(void *user_data, int x, int y, int w, int h);
|
||||
bool use_light_theme;
|
||||
} widget_context_t;
|
||||
|
||||
// --- Button ---
|
||||
|
|
|
|||
Loading…
Reference in a new issue