diff --git a/Makefile b/Makefile index 17cc90b..3c5de23 100644 --- a/Makefile +++ b/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 \ diff --git a/src/core/main.c b/src/core/main.c index 37987f3..4e3d8ed 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -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,21 +211,31 @@ void kmain(void) { if (fs_starts_with(clean_path, "boot():")) clean_path += 7; else if (fs_starts_with(clean_path, "boot:///")) clean_path += 8; - char dir_path[256]; - int last_slash = -1; - for (int j = 0; clean_path[j]; j++) { - if (clean_path[j] == '/') last_slash = j; - } - if (last_slash > 0) { - for (int j = 0; j < last_slash; j++) dir_path[j] = clean_path[j]; - dir_path[last_slash] = '\0'; - fat32_mkdir_recursive(dir_path); - } + int len = 0; + while(clean_path[len]) len++; - FAT32_FileHandle *fh = fat32_open(clean_path, "w"); - if (fh && fh->valid) { - fat32_write(fh, mod->address, mod->size); - fat32_close(fh); + 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++) { + if (clean_path[j] == '/') last_slash = j; + } + if (last_slash > 0) { + for (int j = 0; j < last_slash; j++) dir_path[j] = clean_path[j]; + dir_path[last_slash] = '\0'; + fat32_mkdir_recursive(dir_path); + } + + FAT32_FileHandle *fh = fat32_open(clean_path, "w"); + if (fh && fh->valid) { + fat32_write(fh, mod->address, mod->size); + fat32_close(fh); + } } } } diff --git a/src/fs/tar.c b/src/fs/tar.c new file mode 100644 index 0000000..e4da1d5 --- /dev/null +++ b/src/fs/tar.c @@ -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); + } +} diff --git a/src/fs/tar.h b/src/fs/tar.h new file mode 100644 index 0000000..dc9539d --- /dev/null +++ b/src/fs/tar.h @@ -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 +#include + +// 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 diff --git a/src/userland/gui/browser.c b/src/userland/gui/browser.c index c28bbbf..191aea3 100644 --- a/src/userland/gui/browser.c +++ b/src/userland/gui/browser.c @@ -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)) { - elements[k].checked = false; + 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; diff --git a/src/userland/gui/settings.c b/src/userland/gui/settings.c index 79415df..38b136c 100644 --- a/src/userland/gui/settings.c +++ b/src/userland/gui/settings.c @@ -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); } } diff --git a/src/wm/libwidget.c b/src/wm/libwidget.c index a5e877b..fa82695 100644 --- a/src/wm/libwidget.c +++ b/src/wm/libwidget.c @@ -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); } } diff --git a/src/wm/libwidget.h b/src/wm/libwidget.h index 14702eb..cfd3697 100644 --- a/src/wm/libwidget.h +++ b/src/wm/libwidget.h @@ -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 ---