FEAT: libwidget.c

This commit is contained in:
boreddevnl 2026-03-22 22:07:30 +01:00
parent 4e8ea5acd2
commit 63749b8734
12 changed files with 1318 additions and 909 deletions

View file

@ -11,7 +11,7 @@ LDFLAGS = -m elf_x86_64 -nostdlib -static -no-pie -Ttext=0x40000000 --no-dynamic
BIN_DIR = bin BIN_DIR = bin
LIBC_SOURCES = $(wildcard libc/*.c) LIBC_SOURCES = $(wildcard libc/*.c)
LIBC_OBJS = $(patsubst libc/%.c, $(BIN_DIR)/%.o, $(LIBC_SOURCES)) $(BIN_DIR)/crt0.o LIBC_OBJS = $(patsubst libc/%.c, $(BIN_DIR)/%.o, $(LIBC_SOURCES)) $(BIN_DIR)/crt0.o $(BIN_DIR)/libwidget.o
VPATH = cli gui sys games libc net VPATH = cli gui sys games libc net
vpath %.c cli gui sys games libc net vpath %.c cli gui sys games libc net
@ -34,6 +34,9 @@ $(BIN_DIR)/crt0.o: crt0.asm
$(BIN_DIR)/%.o: libc/%.c $(BIN_DIR)/%.o: libc/%.c
$(CC) $(CFLAGS) -c $< -o $@ $(CC) $(CFLAGS) -c $< -o $@
$(BIN_DIR)/libwidget.o: ../wm/libwidget.c
$(CC) $(CFLAGS) -c $< -o $@
$(BIN_DIR)/stb_image.o: stb_image.c $(BIN_DIR)/stb_image.o: stb_image.c
$(CC) $(CFLAGS) -c $< -o $@ $(CC) $(CFLAGS) -c $< -o $@

View file

@ -4,6 +4,7 @@
#include "libc/syscall.h" #include "libc/syscall.h"
#include "libc/libui.h" #include "libc/libui.h"
#include "libc/stdlib.h" #include "libc/stdlib.h"
#include "../../wm/libwidget.h"
#include <stddef.h> #include <stddef.h>
#define COLOR_DARK_PANEL 0xFF202020 #define COLOR_DARK_PANEL 0xFF202020
@ -90,8 +91,30 @@ static char open_filename[256] = "";
static _Bool file_modified = 0; static _Bool file_modified = 0;
static int scroll_y = 0; static int scroll_y = 0;
static _Bool is_dragging_scrollbar = 0; static widget_scrollbar_t doc_scrollbar;
static int scrollbar_drag_offset_y = 0;
static void word_draw_rect(void *user_data, int x, int y, int w, int h, uint32_t color) {
ui_draw_rect((ui_window_t)user_data, x, y, w, h, color);
}
static void word_draw_rounded_rect_filled(void *user_data, int x, int y, int w, int h, int r, uint32_t color) {
ui_draw_rounded_rect_filled((ui_window_t)user_data, x, y, w, h, r, color);
}
static void word_draw_string(void *user_data, int x, int y, const char *str, uint32_t color) {
ui_draw_string((ui_window_t)user_data, x, y, str, color);
}
static widget_context_t word_ctx = {
.user_data = 0,
.draw_rect = word_draw_rect,
.draw_rounded_rect_filled = word_draw_rounded_rect_filled,
.draw_string = word_draw_string,
.mark_dirty = NULL
};
static void word_on_scroll(void *user_data, int new_scroll_y) {
(void)user_data;
scroll_y = new_scroll_y;
}
static _Bool is_in_selection(int p, int r, int c); static _Bool is_in_selection(int p, int r, int c);
@ -1301,20 +1324,13 @@ static void draw_document(ui_window_t win) {
set_active_font(win, 0); set_active_font(win, 0);
int content_h = current_page * (page_h + 20) + page_h + 20; int content_h = current_page * (page_h + 20) + page_h + 20;
if (content_h > win_h - 40) { doc_scrollbar.x = win_w - 12;
int sb_x = win_w - 12; doc_scrollbar.y = 40;
int sb_w = 12; doc_scrollbar.w = 12;
int sb_h = win_h - 40; doc_scrollbar.h = win_h - 40;
float ratio = (float)(win_h - 40) / (float)content_h; doc_scrollbar.on_scroll = word_on_scroll;
int thumb_h = (int)(sb_h * ratio); widget_scrollbar_update(&doc_scrollbar, content_h, scroll_y);
if (thumb_h < 20) thumb_h = 20; widget_scrollbar_draw(&word_ctx, &doc_scrollbar);
int max_scroll = content_h - (win_h - 40);
if (scroll_y > max_scroll) scroll_y = max_scroll;
int thumb_y = 40 + (int)(((float)scroll_y / max_scroll) * (sb_h - thumb_h));
ui_draw_rect(win, sb_x, 40, sb_w, sb_h, 0xFF303030);
ui_draw_rounded_rect_filled(win, sb_x+2, thumb_y+2, sb_w-4, thumb_h-4, 4, 0xFF606060);
}
} }
static void ensure_cursor_visible(ui_window_t win) { static void ensure_cursor_visible(ui_window_t win) {
@ -1645,32 +1661,8 @@ static void handle_click(ui_window_t win, int x, int y) {
} }
} }
content_h = dummy_page * (page_h + 20) + page_h + 20; content_h = dummy_page * (page_h + 20) + page_h + 20;
widget_scrollbar_update(&doc_scrollbar, content_h, scroll_y);
int sb_x = win_w - 12; if (widget_scrollbar_handle_mouse(&doc_scrollbar, x, y, true, NULL)) {
int sb_w = 12;
int sb_h = win_h - 40;
int thumb_y = 40;
int thumb_h = 0;
int max_scroll = 0;
if (content_h > win_h - 40) {
float ratio = (float)(win_h - 40) / (float)content_h;
thumb_h = (int)(sb_h * ratio);
if (thumb_h < 20) thumb_h = 20;
max_scroll = content_h - (win_h - 40);
if (scroll_y > max_scroll) scroll_y = max_scroll;
thumb_y = 40 + (int)(((float)scroll_y / max_scroll) * (sb_h - thumb_h));
}
if (content_h > win_h - 40 && x >= sb_x && x < sb_x + sb_w) {
if (y >= thumb_y && y < thumb_y + thumb_h) {
is_dragging_scrollbar = 1;
scrollbar_drag_offset_y = y - thumb_y;
} else {
if (y < thumb_y) scroll_y -= (win_h - 40);
else scroll_y += (win_h - 40);
if (scroll_y < 0) scroll_y = 0;
if (scroll_y > max_scroll) scroll_y = max_scroll;
}
return; return;
} }
@ -1848,12 +1840,15 @@ int main(int argc, char **argv) {
(void)argv; (void)argv;
ui_window_t win = ui_window_create("BoredWord", 100, 100, win_w, win_h); ui_window_t win = ui_window_create("BoredWord", 100, 100, win_w, win_h);
if (!win) return 1; if (!win) return 1;
word_ctx.user_data = (void*)win;
ui_window_set_resizable(win, 1); ui_window_set_resizable(win, 1);
load_fonts(); load_fonts();
set_active_font(win, 0); set_active_font(win, 0);
init_doc(); init_doc();
widget_scrollbar_init(&doc_scrollbar, win_w - 12, 40, 12, win_h - 40);
if (argc > 1) { if (argc > 1) {
load_file(win, argv[1]); load_file(win, argv[1]);
} }
@ -1892,61 +1887,12 @@ int main(int argc, char **argv) {
needs_repaint = 1; needs_repaint = 1;
} else if (ev.type == GUI_EVENT_MOUSE_UP) { } else if (ev.type == GUI_EVENT_MOUSE_UP) {
is_dragging = 0; is_dragging = 0;
is_dragging_scrollbar = 0; widget_scrollbar_handle_mouse(&doc_scrollbar, ev.arg1, ev.arg2, false, NULL);
needs_repaint = 1; needs_repaint = 1;
} else if (ev.type == GUI_EVENT_MOUSE_MOVE) { } else if (ev.type == GUI_EVENT_MOUSE_MOVE) {
if (is_dragging_scrollbar) { if (doc_scrollbar.is_dragging) {
int pw, ph; get_page_size(&pw, &ph); widget_scrollbar_handle_mouse(&doc_scrollbar, ev.arg1, ev.arg2, true, NULL);
int doc_view_w = win_w - 40; needs_repaint = 1;
float scale = (float)doc_view_w / (float)pw; if (scale > 1.0f) scale = 1.0f;
int page_w = (int)(pw * scale);
int page_h = (int)(ph * scale);
int content_h = 0;
int dummy_y = 10; int dummy_page = 0;
for(int p=0; p<para_count; p++) {
Paragraph *para = &paragraphs[p]; int start_run = 0; int start_char = 0;
while(start_run < para->run_count) {
int max_h = 16; int end_run = start_run; int end_char = start_char; int line_w = 0;
int r_idx = start_run; int c_idx = start_char; int last_space_run = -1; int last_space_char = -1; int last_space_w = 0;
while(r_idx < para->run_count) {
TextRun *run = &para->runs[r_idx]; set_active_font(win, run->font_idx);
int fh = ui_get_font_height_scaled(run->font_size); if (fh > max_h) max_h = fh;
while(c_idx < run->len) {
char buf[2] = {run->text[c_idx], 0}; int cw = ui_get_string_width_scaled(buf, run->font_size);
if (run->text[c_idx] == ' ') { last_space_run = r_idx; last_space_char = c_idx; last_space_w = line_w + cw; }
if (line_w + cw > page_w - 20) break;
line_w += cw; c_idx++;
}
if (c_idx < run->len) break;
r_idx++; c_idx = 0;
}
if (r_idx < para->run_count || (r_idx == para->run_count - 1 && c_idx < para->runs[r_idx].len)) {
if (last_space_run != -1 && (last_space_run > start_run || last_space_char > start_char)) { end_run = last_space_run; end_char = last_space_char; line_w = last_space_w; }
else { end_run = r_idx; end_char = c_idx; }
} else { end_run = para->run_count; end_char = 0; }
int line_h = (int)(max_h * para->spacing) + 4;
if (dummy_y + line_h > dummy_page * (page_h + 20) + page_h - 10) { dummy_page++; dummy_y = dummy_page * (page_h + 20) + 10; }
dummy_y += line_h; start_run = end_run; start_char = end_char;
if (start_run < para->run_count && para->runs[start_run].text[start_char] == ' ') { start_char++; if (start_char >= para->runs[start_run].len) { start_char = 0; start_run++; } }
}
}
content_h = dummy_page * (page_h + 20) + page_h + 20;
if (content_h > win_h - 40) {
int sb_h = win_h - 40;
float ratio = (float)(win_h - 40) / (float)content_h;
int thumb_h = (int)(sb_h * ratio);
if (thumb_h < 20) thumb_h = 20;
int max_scroll = content_h - (win_h - 40);
int new_thumb_y = ev.arg2 - scrollbar_drag_offset_y;
if (new_thumb_y < 40) new_thumb_y = 40;
if (new_thumb_y > 40 + sb_h - thumb_h) new_thumb_y = 40 + sb_h - thumb_h;
scroll_y = (int)(((float)(new_thumb_y - 40) / (sb_h - thumb_h)) * max_scroll);
needs_repaint = 1;
}
} else if (is_dragging && ev.arg2 >= 40 && active_dialog == 0 && active_dropdown == 0) { } else if (is_dragging && ev.arg2 >= 40 && active_dialog == 0 && active_dropdown == 0) {
handle_click(win, ev.arg1, ev.arg2); handle_click(win, ev.arg1, ev.arg2);
needs_repaint = 1; needs_repaint = 1;

View file

@ -137,6 +137,38 @@ static int scroll_y = 0;
static int total_content_height = 0; static int total_content_height = 0;
static int focused_element = -1; static int focused_element = -1;
#include "../../wm/libwidget.h"
static void browser_draw_rect(void *user_data, int x, int y, int w, int h, uint32_t color) {
ui_draw_rect((ui_window_t)user_data, x, y, w, h, color);
}
static void browser_draw_rounded_rect_filled(void *user_data, int x, int y, int w, int h, int r, uint32_t color) {
ui_draw_rounded_rect_filled((ui_window_t)user_data, x, y, w, h, r, color);
}
static void browser_draw_string(void *user_data, int x, int y, const char *str, uint32_t color) {
ui_draw_string((ui_window_t)user_data, x, y, str, color);
}
static int browser_measure_string_width(void *user_data, const char *str) {
(void)user_data;
return (int)ui_get_string_width(str);
}
static void browser_mark_dirty(void *user_data, int x, int y, int w, int h) {
ui_mark_dirty((ui_window_t)user_data, x, y, w, h);
}
static widget_context_t browser_ctx = {
.draw_rect = browser_draw_rect,
.draw_rounded_rect_filled = browser_draw_rounded_rect_filled,
.draw_string = browser_draw_string,
.measure_string_width = browser_measure_string_width,
.mark_dirty = browser_mark_dirty
};
static widget_scrollbar_t browser_scrollbar;
static widget_textbox_t url_tb;
static widget_button_t btn_back;
static widget_button_t btn_home;
static void parse_html(const char *html); static void parse_html(const char *html);
static void parse_html_incremental(const char *html, int safe_len); static void parse_html_incremental(const char *html, int safe_len);
static void browser_reflow(void); static void browser_reflow(void);
@ -1506,6 +1538,7 @@ static void parse_html_incremental(const char *html, int safe_len) {
} }
static void browser_paint(void) { static void browser_paint(void) {
browser_ctx.user_data = (void *)win_browser;
ui_draw_rect(win_browser, 0, 0, win_w, win_h, COLOR_BG); ui_draw_rect(win_browser, 0, 0, win_w, win_h, COLOR_BG);
for (int i = 0; i < element_count; i++) { for (int i = 0; i < element_count; i++) {
@ -1520,53 +1553,35 @@ static void browser_paint(void) {
if (pixels) ui_draw_image(win_browser, el->x, draw_y, el->img_w, el->img_h, pixels); 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 ui_draw_rect(win_browser, el->x, draw_y, 100, 80, 0xFFCCCCCC);
} else if (el->tag == TAG_INPUT) { } else if (el->tag == TAG_INPUT) {
ui_draw_rect(win_browser, el->x, draw_y, el->w, el->h, 0xFFFFFFFF); char visible[128];
uint32_t border = (focused_element == i) ? 0xFF0000FF : 0xFF808080;
ui_draw_rect(win_browser, el->x, draw_y, el->w, 1, border);
ui_draw_rect(win_browser, el->x, draw_y + el->h - 1, el->w, 1, border);
ui_draw_rect(win_browser, el->x, draw_y, 1, el->h, border);
ui_draw_rect(win_browser, el->x + el->w - 1, draw_y, 1, el->h, border);
char visible[64];
int v_len = 0; int v_len = 0;
int max_v = (el->w - 10) / 8; int max_v = (el->w - 10) / 8;
if (max_v > 63) max_v = 63; if (max_v > 127) max_v = 127;
for (int k = el->input_scroll; el->attr_value[k] && v_len < max_v; k++) { for (int k = el->input_scroll; el->attr_value[k] && v_len < max_v; k++) {
visible[v_len++] = el->attr_value[k]; visible[v_len++] = el->attr_value[k];
} }
visible[v_len] = 0; visible[v_len] = 0;
ui_draw_string(win_browser, el->x + 5, draw_y + 2, visible, (focused_element == i) ? 0xFF000000 : 0xFF808080);
if (focused_element == i) { widget_textbox_t tb;
int cursor_pos = el->input_cursor - el->input_scroll; widget_textbox_init(&tb, el->x, draw_y, el->w, el->h, visible, max_v);
if (cursor_pos >= 0 && cursor_pos < max_v) { tb.cursor_pos = el->input_cursor - el->input_scroll;
char sub[64]; if (tb.cursor_pos < 0) tb.cursor_pos = 0;
int k; tb.focused = (focused_element == i);
for (k = 0; k < cursor_pos && visible[k]; k++) sub[k] = visible[k]; widget_textbox_draw(&browser_ctx, &tb);
sub[k] = 0;
int cx = ui_get_string_width(sub);
ui_draw_rect(win_browser, el->x + 5 + cx, draw_y + 16, 8, 2, 0xFF000000);
}
}
} else if (el->tag == TAG_BUTTON) { } else if (el->tag == TAG_BUTTON) {
ui_draw_rect(win_browser, el->x, draw_y, el->w, el->h, 0xFFDDDDDD); widget_button_t btn;
ui_draw_rect(win_browser, el->x, draw_y, el->w, 1, 0xFFFFFFFF); widget_button_init(&btn, el->x, draw_y, el->w, el->h, el->attr_value);
ui_draw_rect(win_browser, el->x, draw_y + el->h - 1, el->w, 1, 0xFF888888); widget_button_draw(&browser_ctx, &btn);
ui_draw_rect(win_browser, el->x, draw_y, 1, el->h, 0xFFFFFFFF);
ui_draw_rect(win_browser, el->x + el->w - 1, draw_y, 1, el->h, 0xFF888888);
ui_draw_string(win_browser, el->x + 10, draw_y + 4, el->attr_value, 0xFF000000);
} else if (el->tag == TAG_RADIO) { } else if (el->tag == TAG_RADIO) {
ui_draw_rounded_rect_filled(win_browser, el->x, draw_y, el->w, el->h, el->w/2, 0xFF808080); widget_checkbox_t cb;
ui_draw_rounded_rect_filled(win_browser, el->x + 1, draw_y + 1, el->w - 2, el->h - 2, (el->w-2)/2, 0xFFFFFFFF); widget_checkbox_init(&cb, el->x, draw_y, el->w, el->h, "", true);
if (el->checked) { cb.checked = el->checked;
ui_draw_rounded_rect_filled(win_browser, el->x + 4, draw_y + 4, el->w - 8, el->h - 8, (el->w-8)/2, 0xFF000000); widget_checkbox_draw(&browser_ctx, &cb);
}
} else if (el->tag == TAG_CHECKBOX) { } else if (el->tag == TAG_CHECKBOX) {
ui_draw_rect(win_browser, el->x, draw_y, el->w, el->h, 0xFF808080); widget_checkbox_t cb;
ui_draw_rect(win_browser, el->x + 1, draw_y + 1, el->w - 2, el->h - 2, 0xFFFFFFFF); widget_checkbox_init(&cb, el->x, draw_y, el->w, el->h, "", false);
if (el->checked) { cb.checked = el->checked;
ui_draw_rect(win_browser, el->x + 4, draw_y + 4, el->w - 8, el->h - 8, 0xFF000000); widget_checkbox_draw(&browser_ctx, &cb);
}
} else if (el->tag == TAG_HR) { } 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, el->w, 2, 0xFF888888);
ui_draw_rect(win_browser, el->x, draw_y + (el->h / 2) + 2, el->w, 1, 0xFFFFFFFF); ui_draw_rect(win_browser, el->x, draw_y + (el->h / 2) + 2, el->w, 1, 0xFFFFFFFF);
@ -1588,40 +1603,26 @@ static void browser_paint(void) {
} }
ui_draw_rect(win_browser, 0, 0, win_w, URL_BAR_H, COLOR_URL_BAR); ui_draw_rect(win_browser, 0, 0, win_w, URL_BAR_H, COLOR_URL_BAR);
ui_draw_string(win_browser, 10, 8, url_input_buffer, COLOR_URL_TEXT);
if (focused_element == -1) { widget_textbox_init(&url_tb, 10, 5, win_w - SCROLL_BAR_W - BTN_W*2 - BTN_PAD*2 - 20, 20, url_input_buffer, 511);
char sub[512]; url_tb.cursor_pos = url_cursor;
int k; url_tb.focused = (focused_element == -1);
for (k = 0; k < url_cursor && url_input_buffer[k]; k++) sub[k] = url_input_buffer[k]; widget_textbox_draw(&browser_ctx, &url_tb);
sub[k] = 0;
int cx = ui_get_string_width(sub);
ui_draw_rect(win_browser, 10 + cx, 22, 8, 2, COLOR_URL_TEXT);
}
// Back button // Back button
int btn_y = (URL_BAR_H - BTN_H) / 2; int btn_y = (URL_BAR_H - BTN_H) / 2;
uint32_t back_col = history_count > 0 ? 0xFF505050 : 0xFF404040; widget_button_init(&btn_back, BACK_BTN_X, btn_y, BTN_W, BTN_H, "<");
ui_draw_rect(win_browser, BACK_BTN_X, btn_y, BTN_W, BTN_H, back_col); widget_button_draw(&browser_ctx, &btn_back);
ui_draw_rect(win_browser, BACK_BTN_X, btn_y, BTN_W, 1, 0xFF606060);
ui_draw_rect(win_browser, BACK_BTN_X, btn_y, 1, BTN_H, 0xFF606060);
ui_draw_rect(win_browser, BACK_BTN_X, btn_y + BTN_H - 1, BTN_W, 1, 0xFF202020);
ui_draw_rect(win_browser, BACK_BTN_X + BTN_W - 1, btn_y, 1, BTN_H, 0xFF202020);
ui_draw_string(win_browser, BACK_BTN_X + 10, btn_y + 4, "<", history_count > 0 ? 0xFFFFFFFF : 0xFF808080);
// Home button // Home button
ui_draw_rect(win_browser, HOME_BTN_X, btn_y, BTN_W, BTN_H, 0xFF505050); widget_button_init(&btn_home, HOME_BTN_X, btn_y, BTN_W, BTN_H, "H");
ui_draw_rect(win_browser, HOME_BTN_X, btn_y, BTN_W, 1, 0xFF606060); widget_button_draw(&browser_ctx, &btn_home);
ui_draw_rect(win_browser, HOME_BTN_X, btn_y, 1, BTN_H, 0xFF606060);
ui_draw_rect(win_browser, HOME_BTN_X, btn_y + BTN_H - 1, BTN_W, 1, 0xFF202020);
ui_draw_rect(win_browser, HOME_BTN_X + BTN_W - 1, btn_y, 1, BTN_H, 0xFF202020);
ui_draw_string(win_browser, HOME_BTN_X + 10, btn_y + 4, "H", 0xFFFFFFFF);
// Scroll bar // Scroll bar
ui_draw_rect(win_browser, win_w - SCROLL_BAR_W, URL_BAR_H, SCROLL_BAR_W, win_h - URL_BAR_H, COLOR_SCROLL_BG); int viewport_h = win_h - URL_BAR_H;
int thumb_h = (win_h - URL_BAR_H) * (win_h - URL_BAR_H) / (total_content_height > win_h ? total_content_height : win_h); widget_scrollbar_init(&browser_scrollbar, win_w - SCROLL_BAR_W, URL_BAR_H, SCROLL_BAR_W, viewport_h);
if (thumb_h < 20) thumb_h = 20; widget_scrollbar_update(&browser_scrollbar, total_content_height, scroll_y);
int thumb_y = URL_BAR_H + (scroll_y * (win_h - URL_BAR_H - thumb_h)) / (total_content_height > win_h - URL_BAR_H ? total_content_height - (win_h - URL_BAR_H) : 1); widget_scrollbar_draw(&browser_ctx, &browser_scrollbar);
ui_draw_rect(win_browser, win_w - SCROLL_BAR_W + 2, thumb_y, SCROLL_BAR_W - 4, thumb_h, COLOR_SCROLL_BTN);
} }
static void navigate(const char *url) { static void navigate(const char *url) {
@ -1670,34 +1671,52 @@ int main(int argc, char **argv) {
continue; continue;
} }
else if (ev.type == GUI_EVENT_CLICK) { else if (ev.type == GUI_EVENT_CLICK || ev.type == GUI_EVENT_MOUSE_DOWN || ev.type == GUI_EVENT_MOUSE_UP || ev.type == GUI_EVENT_MOUSE_MOVE) {
int mx = ev.arg1; int mx = ev.arg1;
if (mx >= win_w - SCROLL_BAR_W) { int my = ev.arg2;
if (ev.arg2 < URL_BAR_H + (win_h - URL_BAR_H)/2) scroll_y -= 100; bool is_down = (ev.type == GUI_EVENT_MOUSE_DOWN || (ev.type == GUI_EVENT_MOUSE_MOVE && browser_scrollbar.is_dragging));
else scroll_y += 100; bool is_click = (ev.type == GUI_EVENT_CLICK);
if (scroll_y < 0) scroll_y = 0;
needs_repaint = true; if (widget_scrollbar_handle_mouse(&browser_scrollbar, mx, my, is_down, &browser_ctx)) {
if (scroll_y != browser_scrollbar.scroll_y) {
scroll_y = browser_scrollbar.scroll_y;
needs_repaint = true;
}
if (ev.type == GUI_EVENT_MOUSE_MOVE) continue;
if (is_down || is_click) continue;
}
if (ev.type != GUI_EVENT_CLICK && ev.type != GUI_EVENT_MOUSE_DOWN) continue;
if (my < URL_BAR_H) {
if (widget_textbox_handle_mouse(&url_tb, mx, my, is_click, NULL)) {
focused_element = -1;
needs_repaint = true;
continue;
}
if (widget_button_handle_mouse(&btn_back, mx, my, is_down, is_click, NULL)) {
if (is_click && history_count > 0) {
history_count--;
int j=0; while(history_stack[history_count][j]) { url_input_buffer[j] = history_stack[history_count][j]; j++; } url_input_buffer[j] = 0; url_cursor = j;
navigate(url_input_buffer); scroll_y = 0; focused_element = -1;
}
needs_repaint = true; continue;
}
if (widget_button_handle_mouse(&btn_home, mx, my, is_down, is_click, NULL)) {
if (is_click) {
if (history_count < HISTORY_MAX) { int j=0; while(url_input_buffer[j]) { history_stack[history_count][j] = url_input_buffer[j]; j++; } history_stack[history_count][j] = 0; history_count++; }
const char *home = "http://find.boreddev.nl";
int j=0; while(home[j]) { url_input_buffer[j] = home[j]; j++; } url_input_buffer[j] = 0; url_cursor = j;
navigate(url_input_buffer); scroll_y = 0; focused_element = -1;
}
needs_repaint = true; continue;
}
if (is_click) {
focused_element = -1; needs_repaint = true;
}
continue; continue;
} }
if (ev.arg2 < URL_BAR_H) { my = ev.arg2 - URL_BAR_H + scroll_y;
// Check back button
if (mx >= BACK_BTN_X && mx < BACK_BTN_X + BTN_W && history_count > 0) {
history_count--;
int j=0; while(history_stack[history_count][j]) { url_input_buffer[j] = history_stack[history_count][j]; j++; } url_input_buffer[j] = 0; url_cursor = j;
navigate(url_input_buffer); scroll_y = 0; focused_element = -1;
needs_repaint = true; continue;
}
// Check home button
if (mx >= HOME_BTN_X && mx < HOME_BTN_X + BTN_W) {
if (history_count < HISTORY_MAX) { int j=0; while(url_input_buffer[j]) { history_stack[history_count][j] = url_input_buffer[j]; j++; } history_stack[history_count][j] = 0; history_count++; }
const char *home = "http://find.boreddev.nl";
int j=0; while(home[j]) { url_input_buffer[j] = home[j]; j++; } url_input_buffer[j] = 0; url_cursor = j;
navigate(url_input_buffer); scroll_y = 0; focused_element = -1;
needs_repaint = true; continue;
}
focused_element = -1; needs_repaint = true; continue;
}
int my = ev.arg2 - URL_BAR_H + scroll_y;
bool found = false; bool found = false;
for (int i = 0; i < element_count; i++) { for (int i = 0; i < element_count; i++) {
RenderElement *el = &elements[i]; RenderElement *el = &elements[i];

View file

@ -3,6 +3,7 @@
// This header needs to maintain in any file it is present in, as per the GPL license terms. // This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "syscall.h" #include "syscall.h"
#include "libui.h" #include "libui.h"
#include "../../wm/libwidget.h"
#include <stdbool.h> #include <stdbool.h>
#include "stdlib.h" #include "stdlib.h"
@ -25,6 +26,33 @@ static long long calc_decimal_divisor = 10;
static char display_buffer[1024]; static char display_buffer[1024];
static int display_buf_len = 0; static int display_buf_len = 0;
static widget_button_t buttons[20];
static const char *labels[] = {
"C", "sqr", "rt", "/",
"7", "8", "9", "*",
"4", "5", "6", "-",
"1", "2", "3", "+",
"0", ".", "BS", "="
};
static void calc_draw_rect(void *user_data, int x, int y, int w, int h, uint32_t color) {
ui_draw_rect((ui_window_t)user_data, x, y, w, h, color);
}
static void calc_draw_rounded_rect_filled(void *user_data, int x, int y, int w, int h, int r, uint32_t color) {
ui_draw_rounded_rect_filled((ui_window_t)user_data, x, y, w, h, r, color);
}
static void calc_draw_string(void *user_data, int x, int y, const char *str, uint32_t color) {
ui_draw_string((ui_window_t)user_data, x, y, str, color);
}
static widget_context_t calc_ctx = {
.user_data = 0,
.draw_rect = calc_draw_rect,
.draw_rounded_rect_filled = calc_draw_rounded_rect_filled,
.draw_string = calc_draw_string,
.mark_dirty = NULL
};
static long long isqrt(long long n) { static long long isqrt(long long n) {
if (n < 0) return -1; if (n < 0) return -1;
if (n == 0) return 0; if (n == 0) return 0;
@ -97,27 +125,8 @@ static void calculator_paint(void) {
int text_x = w - 15 - text_w; int text_x = w - 15 - text_w;
ui_draw_string(win_calculator, text_x, 18, display_buffer, COLOR_DARK_TEXT); ui_draw_string(win_calculator, text_x, 18, display_buffer, COLOR_DARK_TEXT);
const char *labels[] = {
"C", "sqr", "rt", "/",
"7", "8", "9", "*",
"4", "5", "6", "-",
"1", "2", "3", "+",
"0", ".", "BS", "="
};
int bw = 35;
int bh = 25;
int gap = 5;
int start_x = 10;
int start_y = 40;
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
int r = i / 4; widget_button_draw(&calc_ctx, &buttons[i]);
int c = i % 4;
ui_draw_rounded_rect_filled(win_calculator, start_x + c*(bw+gap), start_y + r*(bh+gap), bw, bh, 4, COLOR_DARK_BORDER);
int label_x = start_x + c*(bw+gap) + 5;
int label_y = start_y + r*(bh+gap) + 9;
ui_draw_string(win_calculator, label_x, label_y, labels[i], COLOR_DARK_TEXT);
} }
} }
@ -135,104 +144,92 @@ static void do_op(void) {
} }
} }
static void calculator_click(int x, int y) { static void handle_button_click(int idx) {
int bw = 35; const char *labels_map[] = {
int bh = 25; "C", "s", "r", "/",
int gap = 5; "7", "8", "9", "*",
int start_x = 10; "4", "5", "6", "-",
int start_y = 35; // Matches the hitboxes "1", "2", "3", "+",
"0", ".", "B", "="
};
char lbl = labels_map[idx][0];
for (int i = 0; i < 20; i++) { if (lbl >= '0' && lbl <= '9') {
int r = i / 4; if (calc_new_entry || calc_error) {
int c = i % 4; calc_curr = (lbl - '0') * SCALE;
int bx = start_x + c*(bw+gap); calc_new_entry = false;
int by = start_y + r*(bh+gap); calc_decimal_mode = false;
} else {
if (x >= bx && x < bx + bw && y >= by && y < by + bh) { if (calc_decimal_mode) {
const char *labels[] = { if (calc_decimal_divisor <= SCALE) {
"C", "s", "r", "/", long long digit_val = ((long long)(lbl - '0') * SCALE) / calc_decimal_divisor;
"7", "8", "9", "*", if (calc_curr >= 0) calc_curr += digit_val;
"4", "5", "6", "-", else calc_curr -= digit_val;
"1", "2", "3", "+", calc_decimal_divisor *= 10;
"0", ".", "B", "=" }
};
char lbl = labels[i][0];
if (lbl >= '0' && lbl <= '9') {
if (calc_new_entry || calc_error) {
calc_curr = (lbl - '0') * SCALE;
calc_new_entry = false;
calc_decimal_mode = false;
} else {
if (calc_decimal_mode) {
if (calc_decimal_divisor <= SCALE) {
long long digit_val = ((long long)(lbl - '0') * SCALE) / calc_decimal_divisor;
if (calc_curr >= 0) calc_curr += digit_val;
else calc_curr -= digit_val;
calc_decimal_divisor *= 10;
}
} else {
if (calc_curr >= 0) calc_curr = calc_curr * 10 + (lbl - '0') * SCALE;
else calc_curr = calc_curr * 10 - (lbl - '0') * SCALE;
}
}
calc_error = false;
} else if (lbl == '.') {
if (calc_new_entry) {
calc_curr = 0;
calc_new_entry = false;
}
if (!calc_decimal_mode) {
calc_decimal_mode = true;
calc_decimal_divisor = 10;
}
} else if (lbl == 'C') {
calc_curr = 0; calc_acc = 0; calc_op = 0;
calc_new_entry = true; calc_error = false; calc_decimal_mode = false;
} else if (lbl == 'B') {
if (!calc_new_entry && !calc_error) {
if (calc_decimal_mode) {
if (calc_decimal_divisor > 10) {
calc_decimal_divisor /= 10;
long long unit = SCALE / calc_decimal_divisor;
calc_curr = (calc_curr / (unit * 10)) * (unit * 10);
} else {
calc_decimal_mode = false;
calc_decimal_divisor = 10;
calc_curr = (calc_curr / SCALE) * SCALE;
}
} else {
calc_curr = (calc_curr / SCALE / 10) * SCALE;
}
}
} else if (lbl == 's') { // sqr
calc_curr = (calc_curr * calc_curr) / SCALE; calc_new_entry = true;
} else if (lbl == 'r') { // rt
long long s = isqrt(calc_curr);
if (s == -1) calc_error = true;
else calc_curr = s * 1000;
calc_new_entry = true;
} else if (lbl == '=') {
do_op();
calc_curr = calc_acc; calc_op = 0; calc_new_entry = true; calc_decimal_mode = false;
} else { } else {
if (!calc_new_entry) { if (calc_curr >= 0) calc_curr = calc_curr * 10 + (lbl - '0') * SCALE;
if (calc_op) do_op(); else calc_curr = calc_curr * 10 - (lbl - '0') * SCALE;
else calc_acc = calc_curr;
}
calc_op = lbl; calc_new_entry = true; calc_decimal_mode = false;
} }
update_display();
calculator_paint();
ui_mark_dirty(win_calculator, 0, 0, 180, 230);
return;
} }
calc_error = false;
} else if (lbl == '.') {
if (calc_new_entry) {
calc_curr = 0;
calc_new_entry = false;
}
if (!calc_decimal_mode) {
calc_decimal_mode = true;
calc_decimal_divisor = 10;
}
} else if (lbl == 'C') {
calc_curr = 0; calc_acc = 0; calc_op = 0;
calc_new_entry = true; calc_error = false; calc_decimal_mode = false;
} else if (lbl == 'B') {
if (!calc_new_entry && !calc_error) {
if (calc_decimal_mode) {
if (calc_decimal_divisor > 10) {
calc_decimal_divisor /= 10;
long long unit = SCALE / calc_decimal_divisor;
calc_curr = (calc_curr / (unit * 10)) * (unit * 10);
} else {
calc_decimal_mode = false;
calc_decimal_divisor = 10;
calc_curr = (calc_curr / SCALE) * SCALE;
}
} else {
calc_curr = (calc_curr / SCALE / 10) * SCALE;
}
}
} else if (lbl == 's') { // sqr
calc_curr = (calc_curr * calc_curr) / SCALE; calc_new_entry = true;
} else if (lbl == 'r') { // rt
long long s = isqrt(calc_curr);
if (s == -1) calc_error = true;
else calc_curr = s * 1000;
calc_new_entry = true;
} else if (lbl == '=') {
do_op();
calc_curr = calc_acc; calc_op = 0; calc_new_entry = true; calc_decimal_mode = false;
} else {
if (!calc_new_entry) {
if (calc_op) do_op();
else calc_acc = calc_curr;
}
calc_op = lbl; calc_new_entry = true; calc_decimal_mode = false;
} }
} }
int main(void) { int main(void) {
win_calculator = ui_window_create("Calculator", 200, 200, 180, 230); win_calculator = ui_window_create("Calculator", 200, 200, 180, 230);
calc_ctx.user_data = (void *)win_calculator;
int bw = 35, bh = 25, gap = 5, start_x = 10, start_y = 40;
for (int i = 0; i < 20; i++) {
int r = i / 4;
int c = i % 4;
widget_button_init(&buttons[i], start_x + c*(bw+gap), start_y + r*(bh+gap), bw, bh, labels[i]);
}
calc_curr = 0; calc_curr = 0;
calc_acc = 0; calc_acc = 0;
@ -249,8 +246,21 @@ int main(void) {
if (ev.type == GUI_EVENT_PAINT) { if (ev.type == GUI_EVENT_PAINT) {
calculator_paint(); calculator_paint();
ui_mark_dirty(win_calculator, 0, 0, 180, 230); ui_mark_dirty(win_calculator, 0, 0, 180, 230);
} else if (ev.type == GUI_EVENT_CLICK) { } else if (ev.type == GUI_EVENT_CLICK || ev.type == GUI_EVENT_MOUSE_DOWN || ev.type == GUI_EVENT_MOUSE_UP) {
calculator_click(ev.arg1, ev.arg2); bool needs_paint = false;
for (int i=0; i<20; i++) {
if (widget_button_handle_mouse(&buttons[i], ev.arg1, ev.arg2, ev.type == GUI_EVENT_MOUSE_DOWN, ev.type == GUI_EVENT_CLICK, NULL)) {
needs_paint = true;
if (ev.type == GUI_EVENT_CLICK) {
handle_button_click(i);
update_display();
}
}
}
if (needs_paint) {
calculator_paint();
ui_mark_dirty(win_calculator, 0, 0, 180, 230);
}
} else if (ev.type == GUI_EVENT_CLOSE) { } else if (ev.type == GUI_EVENT_CLOSE) {
sys_exit(0); sys_exit(0);
} }

File diff suppressed because it is too large Load diff

View file

@ -42,6 +42,28 @@ static int dropdown_menu_item_height = 25;
#define FILE_CONTEXT_MENU_HEIGHT 50 #define FILE_CONTEXT_MENU_HEIGHT 50
#define CONTEXT_MENU_ITEM_HEIGHT 25 #define CONTEXT_MENU_ITEM_HEIGHT 25
static void wm_draw_rect(void *user_data, int x, int y, int w, int h, uint32_t color) {
(void)user_data; draw_rect(x, y, w, h, color);
}
static void wm_draw_rounded_rect_filled(void *user_data, int x, int y, int w, int h, int r, uint32_t color) {
(void)user_data; draw_rounded_rect_filled(x, y, w, h, r, color);
}
static void wm_draw_string(void *user_data, int x, int y, const char *str, uint32_t color) {
(void)user_data; draw_string(x, y, str, color);
}
static int wm_measure_string_width(void *user_data, const char *str) {
(void)user_data;
return font_manager_get_string_width(graphics_get_current_ttf(), str);
}
static widget_context_t wm_widget_ctx = {
.user_data = NULL,
.draw_rect = wm_draw_rect,
.draw_rounded_rect_filled = wm_draw_rounded_rect_filled,
.draw_string = wm_draw_string,
.measure_string_width = wm_measure_string_width,
.mark_dirty = NULL
};
static char clipboard_path[FAT32_MAX_PATH] = ""; static char clipboard_path[FAT32_MAX_PATH] = "";
static int clipboard_action = 0; static int clipboard_action = 0;
#define FILE_CONTEXT_ITEMS 2 #define FILE_CONTEXT_ITEMS 2
@ -950,16 +972,15 @@ static void explorer_paint(Window *win) {
draw_string(path_x + 6 + path_label_w + 6, offset_y + 8, state->current_path, COLOR_DARK_TEXT); draw_string(path_x + 6 + path_label_w + 6, offset_y + 8, state->current_path, COLOR_DARK_TEXT);
int dropdown_btn_x = win->x + win->w - 90; int dropdown_btn_x = win->x + win->w - 90;
draw_rounded_rect_filled(dropdown_btn_x, offset_y + 3, 35, 22, 5, COLOR_DARK_PANEL); widget_button_init(&state->btn_dropdown, dropdown_btn_x, offset_y + 3, 35, 22, "...");
draw_string(dropdown_btn_x + 10, offset_y + 8, "...", COLOR_DARK_TEXT); widget_button_init(&state->btn_back, win->x + win->w - 40, offset_y + 3, 30, 22, "<");
widget_button_init(&state->btn_up, win->x + win->w - 160, offset_y + 3, 30, 22, "^");
widget_button_init(&state->btn_fwd, win->x + win->w - 125, offset_y + 3, 30, 22, "v");
draw_rounded_rect_filled(win->x + win->w - 40, offset_y + 3, 30, 22, 5, COLOR_DARK_PANEL); widget_button_draw(&wm_widget_ctx, &state->btn_dropdown);
draw_string(win->x + win->w - 32, offset_y + 8, "<", COLOR_DARK_TEXT); widget_button_draw(&wm_widget_ctx, &state->btn_back);
widget_button_draw(&wm_widget_ctx, &state->btn_up);
draw_rounded_rect_filled(win->x + win->w - 160, offset_y + 3, 30, 22, 5, COLOR_DARK_PANEL); widget_button_draw(&wm_widget_ctx, &state->btn_fwd);
draw_string(win->x + win->w - 150, offset_y + 8, "^", COLOR_DARK_TEXT);
draw_rounded_rect_filled(win->x + win->w - 125, offset_y + 3, 30, 22, 5, COLOR_DARK_PANEL);
draw_string(win->x + win->w - 115, offset_y + 8, "v", COLOR_DARK_TEXT);
int content_start_y = offset_y + 30; int content_start_y = offset_y + 30;
@ -1043,25 +1064,15 @@ static void explorer_paint(Window *win) {
draw_string(dlg_x + 10, dlg_y + 10, "Create New File", COLOR_WHITE); draw_string(dlg_x + 10, dlg_y + 10, "Create New File", COLOR_WHITE);
draw_rounded_rect_filled(dlg_x + 10, dlg_y + 35, 280, 20, 4, COLOR_DARK_BG); widget_textbox_init(&state->dialog_textbox, dlg_x + 10, dlg_y + 35, 280, 25, state->dialog_input, DIALOG_INPUT_MAX);
draw_string(dlg_x + 15, dlg_y + 40, state->dialog_input, COLOR_WHITE); state->dialog_textbox.focused = true;
{ int max_w = 265; state->dialog_textbox.cursor_pos = state->dialog_input_cursor;
ttf_font_t *ttf_ = graphics_get_current_ttf(); widget_textbox_draw(&wm_widget_ctx, &state->dialog_textbox);
int total_w = font_manager_get_string_width(ttf_, state->dialog_input);
int scroll_x = 0;
if (total_w > max_w) scroll_x = total_w - max_w;
char sub_[128]; int k_=0;
for(k_=0; k_<state->dialog_input_cursor && state->dialog_input[k_]; k_++) sub_[k_]=state->dialog_input[k_];
sub_[k_]=0;
int cx_ = font_manager_get_string_width(ttf_, sub_) - scroll_x;
if (cx_ < 0) cx_ = 0;
if (cx_ > max_w) cx_ = max_w;
draw_rect(dlg_x+15+cx_, dlg_y+39, 2, 12, COLOR_WHITE); }
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER); widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Create");
draw_string(dlg_x + 70, dlg_y + 72, "Create", COLOR_WHITE); widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER); widget_button_draw(&wm_widget_ctx, &state->btn_primary);
draw_string(dlg_x + 185, dlg_y + 72, "Cancel", COLOR_WHITE); widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
} else if (state->dialog_state == DIALOG_CREATE_FOLDER) { } else if (state->dialog_state == DIALOG_CREATE_FOLDER) {
int dlg_x = win->x + win->w / 2 - 150; int dlg_x = win->x + win->w / 2 - 150;
int dlg_y = win->y + win->h / 2 - 60; int dlg_y = win->y + win->h / 2 - 60;
@ -1070,25 +1081,15 @@ static void explorer_paint(Window *win) {
draw_string(dlg_x + 10, dlg_y + 10, "Create New Folder", COLOR_WHITE); draw_string(dlg_x + 10, dlg_y + 10, "Create New Folder", COLOR_WHITE);
draw_rounded_rect_filled(dlg_x + 10, dlg_y + 35, 280, 20, 4, COLOR_DARK_BG); widget_textbox_init(&state->dialog_textbox, dlg_x + 10, dlg_y + 35, 280, 25, state->dialog_input, DIALOG_INPUT_MAX);
draw_string(dlg_x + 15, dlg_y + 40, state->dialog_input, COLOR_WHITE); state->dialog_textbox.focused = true;
{ int max_w = 265; state->dialog_textbox.cursor_pos = state->dialog_input_cursor;
ttf_font_t *ttf_ = graphics_get_current_ttf(); widget_textbox_draw(&wm_widget_ctx, &state->dialog_textbox);
int total_w = font_manager_get_string_width(ttf_, state->dialog_input);
int scroll_x = 0;
if (total_w > max_w) scroll_x = total_w - max_w;
char sub_[128]; int k_=0;
for(k_=0; k_<state->dialog_input_cursor && state->dialog_input[k_]; k_++) sub_[k_]=state->dialog_input[k_];
sub_[k_]=0;
int cx_ = font_manager_get_string_width(ttf_, sub_) - scroll_x;
if (cx_ < 0) cx_ = 0;
if (cx_ > max_w) cx_ = max_w;
draw_rect(dlg_x+15+cx_, dlg_y+39, 2, 12, COLOR_WHITE); }
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER); widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Create");
draw_string(dlg_x + 70, dlg_y + 72, "Create", COLOR_WHITE); widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER); widget_button_draw(&wm_widget_ctx, &state->btn_primary);
draw_string(dlg_x + 185, dlg_y + 72, "Cancel", COLOR_WHITE); widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
} else if (state->dialog_state == DIALOG_DELETE_CONFIRM) { } else if (state->dialog_state == DIALOG_DELETE_CONFIRM) {
int dlg_x = win->x + win->w / 2 - 150; int dlg_x = win->x + win->w / 2 - 150;
int dlg_y = win->y + win->h / 2 - 60; int dlg_y = win->y + win->h / 2 - 60;
@ -1107,8 +1108,14 @@ static void explorer_paint(Window *win) {
} }
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, 0xFF8B2020); draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, 0xFF8B2020);
draw_string(dlg_x + 68, dlg_y + 72, "Delete", COLOR_WHITE); draw_string(dlg_x + 68, dlg_y + 72, "Delete", COLOR_WHITE);
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
draw_string(dlg_x + 185, dlg_y + 72, "Cancel", COLOR_WHITE); // Use libwidget only for the cancel button, or do we want to use libwidget for the delete button too?
// Let's use libwidget but the delete button needs red styling so maybe just keep it manual or make it secondary.
// Actually wait, I will use libwidget for both and let the text dictate the action, we can't style individual buttons yet.
widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Delete");
widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
widget_button_draw(&wm_widget_ctx, &state->btn_primary);
widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
} else if (state->dialog_state == DIALOG_REPLACE_CONFIRM) { } else if (state->dialog_state == DIALOG_REPLACE_CONFIRM) {
int dlg_x = win->x + win->w / 2 - 150; int dlg_x = win->x + win->w / 2 - 150;
int dlg_y = win->y + win->h / 2 - 60; int dlg_y = win->y + win->h / 2 - 60;
@ -1120,10 +1127,10 @@ static void explorer_paint(Window *win) {
draw_string(dlg_x + 10, dlg_y + 35, "Replace existing file?", 0xFFAAAAAA); draw_string(dlg_x + 10, dlg_y + 35, "Replace existing file?", 0xFFAAAAAA);
draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA); draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA);
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER); widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Replace");
draw_string(dlg_x + 63, dlg_y + 77, "Replace", COLOR_WHITE); widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER); widget_button_draw(&wm_widget_ctx, &state->btn_primary);
draw_string(dlg_x + 185, dlg_y + 77, "Cancel", COLOR_WHITE); widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
} else if (state->dialog_state == DIALOG_REPLACE_MOVE_CONFIRM) { } else if (state->dialog_state == DIALOG_REPLACE_MOVE_CONFIRM) {
int dlg_x = win->x + win->w / 2 - 150; int dlg_x = win->x + win->w / 2 - 150;
int dlg_y = win->y + win->h / 2 - 60; int dlg_y = win->y + win->h / 2 - 60;
@ -1150,10 +1157,10 @@ static void explorer_paint(Window *win) {
draw_string(dlg_x + 10, dlg_y + 35, "Overwrite existing file?", 0xFFAAAAAA); draw_string(dlg_x + 10, dlg_y + 35, "Overwrite existing file?", 0xFFAAAAAA);
draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA); draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA);
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER); widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Overwrite");
draw_string(dlg_x + 57, dlg_y + 77, "Overwrite", COLOR_WHITE); widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER); widget_button_draw(&wm_widget_ctx, &state->btn_primary);
draw_string(dlg_x + 185, dlg_y + 77, "Cancel", COLOR_WHITE); widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
} else if (state->dialog_state == DIALOG_ERROR) { } else if (state->dialog_state == DIALOG_ERROR) {
int dlg_x = win->x + win->w / 2 - 150; int dlg_x = win->x + win->w / 2 - 150;
int dlg_y = win->y + win->h / 2 - 60; int dlg_y = win->y + win->h / 2 - 60;
@ -1163,32 +1170,23 @@ static void explorer_paint(Window *win) {
draw_string(dlg_x + 10, dlg_y + 10, "Error", 0xFFFF6B6B); draw_string(dlg_x + 10, dlg_y + 10, "Error", 0xFFFF6B6B);
draw_string(dlg_x + 10, dlg_y + 40, state->dialog_input, 0xFFAAAAAA); draw_string(dlg_x + 10, dlg_y + 40, state->dialog_input, 0xFFAAAAAA);
draw_rounded_rect_filled(dlg_x + 110, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER); widget_button_init(&state->btn_primary, dlg_x + 110, dlg_y + 70, 80, 25, "OK");
draw_string(dlg_x + 138, dlg_y + 77, "OK", COLOR_WHITE); widget_button_draw(&wm_widget_ctx, &state->btn_primary);
} else if (state->dialog_state == DIALOG_RENAME) { } else if (state->dialog_state == DIALOG_RENAME) {
int dlg_x = win->x + win->w / 2 - 150; int dlg_x = win->x + win->w / 2 - 150;
int dlg_y = win->y + win->h / 2 - 60; int dlg_y = win->y + win->h / 2 - 60;
draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL); draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL);
draw_string(dlg_x + 10, dlg_y + 10, "Rename", COLOR_WHITE); draw_string(dlg_x + 10, dlg_y + 10, "Rename", COLOR_WHITE);
draw_rounded_rect_filled(dlg_x + 10, dlg_y + 35, 280, 20, 4, COLOR_DARK_BG); widget_textbox_init(&state->dialog_textbox, dlg_x + 10, dlg_y + 35, 280, 25, state->dialog_input, DIALOG_INPUT_MAX);
draw_string(dlg_x + 15, dlg_y + 40, state->dialog_input, COLOR_WHITE); state->dialog_textbox.focused = true;
{ int max_w = 265; state->dialog_textbox.cursor_pos = state->dialog_input_cursor;
ttf_font_t *ttf_ = graphics_get_current_ttf(); widget_textbox_draw(&wm_widget_ctx, &state->dialog_textbox);
int total_w = font_manager_get_string_width(ttf_, state->dialog_input);
int scroll_x = 0; widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Rename");
if (total_w > max_w) scroll_x = total_w - max_w; widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
char sub_[128]; int k_=0; widget_button_draw(&wm_widget_ctx, &state->btn_primary);
for(k_=0; k_<state->dialog_input_cursor && state->dialog_input[k_]; k_++) sub_[k_]=state->dialog_input[k_]; widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
sub_[k_]=0;
int cx_ = font_manager_get_string_width(ttf_, sub_) - scroll_x;
if (cx_ < 0) cx_ = 0;
if (cx_ > max_w) cx_ = max_w;
draw_rect(dlg_x+15+cx_, dlg_y+39, 2, 12, COLOR_WHITE); }
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
draw_string(dlg_x + 68, dlg_y + 72, "Rename", COLOR_WHITE);
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
draw_string(dlg_x + 185, dlg_y + 72, "Cancel", COLOR_WHITE);
} }
if (state->file_context_menu_visible) { if (state->file_context_menu_visible) {
@ -1220,6 +1218,9 @@ static void explorer_paint(Window *win) {
} }
#define WIDGET_CLICKED(btn, cx, cy) ((cx) >= (btn)->x && (cx) < (btn)->x + (btn)->w && (cy) >= (btn)->y && (cy) < (btn)->y + (btn)->h)
#define TEXTBOX_CLICKED(tb, cx, cy) ((cx) >= (tb)->x && (cx) < (tb)->x + (tb)->w && (cy) >= (tb)->y && (cy) < (tb)->y + (tb)->h)
static void explorer_handle_click(Window *win, int x, int y) { static void explorer_handle_click(Window *win, int x, int y) {
ExplorerState *state = (ExplorerState*)win->data; ExplorerState *state = (ExplorerState*)win->data;
if (state->file_context_menu_visible) { if (state->file_context_menu_visible) {
@ -1228,106 +1229,90 @@ static void explorer_handle_click(Window *win, int x, int y) {
} }
if (state->dialog_state == DIALOG_CREATE_FILE || state->dialog_state == DIALOG_CREATE_FOLDER) { if (state->dialog_state == DIALOG_CREATE_FILE || state->dialog_state == DIALOG_CREATE_FOLDER) {
int dlg_x = win->w / 2 - 150; if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
int dlg_y = win->h / 2 - 80; state->btn_primary.pressed = false;
if (state->dialog_state == DIALOG_CREATE_FILE) dialog_confirm_create_file(win);
if (x >= dlg_x + 50 && x < dlg_x + 130 && else dialog_confirm_create_folder(win);
y >= dlg_y + 65 && y < dlg_y + 90) {
if (state->dialog_state == DIALOG_CREATE_FILE) {
dialog_confirm_create_file(win);
} else {
dialog_confirm_create_folder(win);
}
return; return;
} }
if (x >= dlg_x + 170 && x < dlg_x + 250 && if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y)) {
y >= dlg_y + 65 && y < dlg_y + 90) { state->btn_secondary.pressed = false;
dialog_close(win); dialog_close(win);
return; return;
} }
if (x >= dlg_x + 10 && x < dlg_x + 290 && if (TEXTBOX_CLICKED(&state->dialog_textbox, win->x + x, win->y + y)) {
y >= dlg_y + 35 && y < dlg_y + 55) { state->dialog_input_cursor = (win->x + x - state->dialog_textbox.x - 5) / 8;
state->dialog_input_cursor = (x - dlg_x - 15) / 8;
if (state->dialog_input_cursor > (int)explorer_strlen(state->dialog_input)) { if (state->dialog_input_cursor > (int)explorer_strlen(state->dialog_input)) {
state->dialog_input_cursor = explorer_strlen(state->dialog_input); state->dialog_input_cursor = explorer_strlen(state->dialog_input);
} }
if (state->dialog_input_cursor < 0) state->dialog_input_cursor = 0;
return; return;
} }
return; return;
} else if (state->dialog_state == DIALOG_DELETE_CONFIRM) { } else if (state->dialog_state == DIALOG_DELETE_CONFIRM) {
int dlg_x = win->w / 2 - 150; if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
int dlg_y = win->h / 2 - 80; state->btn_primary.pressed = false;
if (x >= dlg_x + 50 && x < dlg_x + 130 &&
y >= dlg_y + 65 && y < dlg_y + 90) {
dialog_confirm_delete(win); dialog_confirm_delete(win);
return; return;
} }
if (x >= dlg_x + 170 && x < dlg_x + 250 && if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y)) {
y >= dlg_y + 65 && y < dlg_y + 90) { state->btn_secondary.pressed = false;
dialog_close(win); dialog_close(win);
return; return;
} }
return; return;
} else if (state->dialog_state == DIALOG_REPLACE_CONFIRM) { } else if (state->dialog_state == DIALOG_REPLACE_CONFIRM) {
int dlg_x = win->w / 2 - 150; if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
int dlg_y = win->h / 2 - 80; state->btn_primary.pressed = false;
if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 70 && y < dlg_y + 95) {
dialog_confirm_replace(win); dialog_confirm_replace(win);
return; return;
} }
if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 70 && y < dlg_y + 95) { if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y + 20)) {
state->btn_secondary.pressed = false;
dialog_close(win); dialog_close(win);
return; return;
} }
return; return;
} else if (state->dialog_state == DIALOG_REPLACE_MOVE_CONFIRM) { } else if (state->dialog_state == DIALOG_REPLACE_MOVE_CONFIRM) {
int dlg_x = win->w / 2 - 150; if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
int dlg_y = win->h / 2 - 80; state->btn_primary.pressed = false;
if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 70 && y < dlg_y + 95) {
dialog_confirm_replace_move(win); dialog_confirm_replace_move(win);
return; return;
} }
if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 70 && y < dlg_y + 95) { if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y + 20)) {
state->btn_secondary.pressed = false;
dialog_close(win); dialog_close(win);
return; return;
} }
return; return;
} else if (state->dialog_state == DIALOG_CREATE_REPLACE_CONFIRM) { } else if (state->dialog_state == DIALOG_CREATE_REPLACE_CONFIRM) {
int dlg_x = win->w / 2 - 150; if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
int dlg_y = win->h / 2 - 80; state->btn_primary.pressed = false;
if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 70 && y < dlg_y + 95) {
dialog_force_create_file(win); dialog_force_create_file(win);
return; return;
} }
if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 70 && y < dlg_y + 95) { if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y + 20)) {
state->btn_secondary.pressed = false;
dialog_close(win); dialog_close(win);
return; return;
} }
return; return;
} else if (state->dialog_state == DIALOG_ERROR) { } else if (state->dialog_state == DIALOG_ERROR) {
int dlg_x = win->w / 2 - 150; if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
int dlg_y = win->h / 2 - 80; state->btn_primary.pressed = false;
if (x >= dlg_x + 110 && x < dlg_x + 190 && y >= dlg_y + 70 && y < dlg_y + 95) {
dialog_close(win); dialog_close(win);
return; return;
} }
return; return;
} else if (state->dialog_state == DIALOG_RENAME) { } else if (state->dialog_state == DIALOG_RENAME) {
int dlg_x = win->w / 2 - 150; if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
int dlg_y = win->h / 2 - 80; state->btn_primary.pressed = false;
if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 65 && y < dlg_y + 90) {
char new_path[FAT32_MAX_PATH]; char new_path[FAT32_MAX_PATH];
explorer_strcpy(new_path, state->current_path); explorer_strcpy(new_path, state->current_path);
if (new_path[explorer_strlen(new_path)-1] != '/') explorer_strcat(new_path, "/"); if (new_path[explorer_strlen(new_path)-1] != '/') explorer_strcat(new_path, "/");
@ -1338,16 +1323,17 @@ static void explorer_handle_click(Window *win, int x, int y) {
return; return;
} }
if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 65 && y < dlg_y + 90) { if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y + 20)) {
state->btn_secondary.pressed = false;
dialog_close(win); dialog_close(win);
return; return;
} }
if (TEXTBOX_CLICKED(&state->dialog_textbox, win->x + x, win->y + y + 20)) {
if (x >= dlg_x + 10 && x < dlg_x + 290 && y >= dlg_y + 35 && y < dlg_y + 55) { state->dialog_input_cursor = (win->x + x - state->dialog_textbox.x - 5) / 8;
state->dialog_input_cursor = (x - dlg_x - 15) / 8;
if (state->dialog_input_cursor > (int)explorer_strlen(state->dialog_input)) { if (state->dialog_input_cursor > (int)explorer_strlen(state->dialog_input)) {
state->dialog_input_cursor = explorer_strlen(state->dialog_input); state->dialog_input_cursor = explorer_strlen(state->dialog_input);
} }
if (state->dialog_input_cursor < 0) state->dialog_input_cursor = 0;
return; return;
} }
return; return;
@ -1420,27 +1406,27 @@ static void explorer_handle_click(Window *win, int x, int y) {
return; return;
} }
if (x >= win->w - 90 && x < win->w - 55 && if (WIDGET_CLICKED(&state->btn_dropdown, win->x + x, win->y + y + 20)) {
y >= button_y && y < button_y + 22) { state->btn_dropdown.pressed = false;
dropdown_menu_toggle(win); dropdown_menu_toggle(win);
state->drive_menu_visible = false; state->drive_menu_visible = false;
return; return;
} }
if (x >= win->w - 40 && x < win->w - 10 && if (WIDGET_CLICKED(&state->btn_back, win->x + x, win->y + y + 20)) {
y >= button_y && y < button_y + 22) { state->btn_back.pressed = false;
explorer_navigate_to(win, ".."); explorer_navigate_to(win, "..");
return; return;
} }
if (x >= win->w - 160 && x < win->w - 130 && if (WIDGET_CLICKED(&state->btn_up, win->x + x, win->y + y + 20)) {
y >= button_y && y < button_y + 22) { state->btn_up.pressed = false;
if (state->explorer_scroll_row > 0) state->explorer_scroll_row--; if (state->explorer_scroll_row > 0) state->explorer_scroll_row--;
return; return;
} }
if (x >= win->w - 125 && x < win->w - 95 && if (WIDGET_CLICKED(&state->btn_fwd, win->x + x, win->y + y + 20)) {
y >= button_y && y < button_y + 22) { state->btn_fwd.pressed = false;
int total_rows = (state->item_count + EXPLORER_COLS - 1) / EXPLORER_COLS; int total_rows = (state->item_count + EXPLORER_COLS - 1) / EXPLORER_COLS;
if (total_rows == 0) total_rows = 1; if (total_rows == 0) total_rows = 1;
if (state->explorer_scroll_row < total_rows - (EXPLORER_ROWS - 1)) state->explorer_scroll_row++; if (state->explorer_scroll_row < total_rows - (EXPLORER_ROWS - 1)) state->explorer_scroll_row++;

View file

@ -7,6 +7,7 @@
#include "wm.h" #include "wm.h"
#include "fat32.h" #include "fat32.h"
#include <stddef.h> #include <stddef.h>
#include "libwidget.h"
// External windows references (for opening other apps) // External windows references (for opening other apps)
extern Window win_explorer; extern Window win_explorer;
@ -55,6 +56,16 @@ typedef struct {
int file_context_menu_y; int file_context_menu_y;
int file_context_menu_item; int file_context_menu_item;
// GUI widgets
widget_button_t btn_primary;
widget_button_t btn_secondary;
widget_button_t btn_dropdown;
widget_button_t btn_back;
widget_button_t btn_up;
widget_button_t btn_fwd;
widget_textbox_t dialog_textbox;
} ExplorerState; } ExplorerState;
void explorer_init(void); void explorer_init(void);

View file

@ -287,28 +287,39 @@ static int isqrt(int n) {
void draw_rounded_rect(int x, int y, int w, int h, int radius, uint32_t color) { void draw_rounded_rect(int x, int y, int w, int h, int radius, uint32_t color) {
if (radius > w / 2) radius = w / 2; if (radius > w / 2) radius = w / 2;
if (radius > h / 2) radius = h / 2; if (radius > h / 2) radius = h / 2;
if (radius < 1) radius = 1; if (radius < 1) {
// Draw a simple rect outline if no radius
draw_rect(x, y, w, 1, color);
draw_rect(x, y + h - 1, w, 1, color);
draw_rect(x, y + 1, 1, h - 2, color);
draw_rect(x + w - 1, y + 1, 1, h - 2, color);
return;
}
// Draw top and bottom edges // Draw top and bottom straight edges
draw_rect(x + radius, y, w - 2*radius, 1, color); draw_rect(x + radius, y, w - 2*radius, 1, color);
draw_rect(x + radius, y + h - 1, w - 2*radius, 1, color); draw_rect(x + radius, y + h - 1, w - 2*radius, 1, color);
// Draw left and right edges // Draw left and right straight edges
draw_rect(x, y + radius, 1, h - 2*radius, color); draw_rect(x, y + radius, 1, h - 2*radius, color);
draw_rect(x + w - 1, y + radius, 1, h - 2*radius, color); draw_rect(x + w - 1, y + radius, 1, h - 2*radius, color);
// Draw corner circles using integer approximation // Draw four corner arcs
for (int i = 0; i < radius; i++) { for (int dy = 0; dy < radius; dy++) {
int j = isqrt(radius*radius - i*i); int y_dist = radius - 1 - dy;
int dx = isqrt(radius*radius - y_dist*y_dist);
int next_dx = (dy < radius - 1) ? isqrt(radius*radius - (y_dist - 1)*(y_dist - 1)) : radius;
// Top-left corner for (int i = dx; i < next_dx && i <= radius; i++) {
put_pixel(x + radius - i - 1, y + radius - j, color); // Top-left
// Top-right corner put_pixel(x + radius - 1 - i, y + dy, color);
put_pixel(x + w - radius + i, y + radius - j, color); // Top-right
// Bottom-left corner put_pixel(x + w - radius + i, y + dy, color);
put_pixel(x + radius - i - 1, y + h - radius + j - 1, color); // Bottom-left
// Bottom-right corner put_pixel(x + radius - 1 - i, y + h - 1 - dy, color);
put_pixel(x + w - radius + i, y + h - radius + j - 1, color); // Bottom-right
put_pixel(x + w - radius + i, y + h - 1 - dy, color);
}
} }
} }
@ -316,25 +327,21 @@ void draw_rounded_rect(int x, int y, int w, int h, int radius, uint32_t color) {
void draw_rounded_rect_filled(int x, int y, int w, int h, int radius, uint32_t color) { void draw_rounded_rect_filled(int x, int y, int w, int h, int radius, uint32_t color) {
if (radius > w / 2) radius = w / 2; if (radius > w / 2) radius = w / 2;
if (radius > h / 2) radius = h / 2; if (radius > h / 2) radius = h / 2;
if (radius < 1) radius = 1; if (radius < 1) {
draw_rect(x, y, w, h, color);
return;
}
// Draw main rectangle body (center part without corners) // Draw main rectangle body
draw_rect(x + radius, y, w - 2*radius, h, color); draw_rect(x, y + radius, w, h - 2*radius, color);
draw_rect(x, y + radius, radius, h - 2*radius, color);
draw_rect(x + w - radius, y + radius, radius, h - 2*radius, color);
// Draw rounded top and bottom caps
for (int dy = 0; dy < radius; dy++) { for (int dy = 0; dy < radius; dy++) {
int dx_top = isqrt(radius*radius - (radius - dy) * (radius - dy)); int y_dist = radius - 1 - dy;
int dx = isqrt(radius*radius - y_dist*y_dist);
int dx_bottom = isqrt(radius*radius - dy*dy); draw_rect(x + radius - dx, y + dy, w - 2*radius + 2*dx, 1, color);
draw_rect(x + radius - dx, y + h - 1 - dy, w - 2*radius + 2*dx, 1, color);
draw_rect(x + radius - dx_top, y + dy, dx_top, 1, color);
draw_rect(x + w - radius, y + dy, dx_top, 1, color);
draw_rect(x + radius - dx_bottom, y + h - radius + dy, dx_bottom, 1, color);
draw_rect(x + w - radius, y + h - radius + dy, dx_bottom, 1, color);
} }
} }

380
src/wm/libwidget.c Normal file
View file

@ -0,0 +1,380 @@
#include "libwidget.h"
#include <stddef.h>
#define COLOR_GRAY 0xFFC0C0C0
#define COLOR_LTGRAY 0xFFDFDFDF
#define COLOR_DKGRAY 0xFF808080
#define COLOR_WHITE 0xFFFFFFFF
#define COLOR_BLACK 0xFF000000
#define COLOR_SCROLLBAR_BG 0xFF2A2A2A
#define COLOR_SCROLLBAR_THUMB 0xFF505050
#define COLOR_SCROLLBAR_THUMB_HOVER 0xFF707070
#define COLOR_SCROLLBAR_THUMB_DRAG 0xFF909090
static size_t string_len(const char *str) {
size_t l = 0;
while(str && str[l]) l++;
return l;
}
#define MAC_BTN_BORDER 0xFF4A4A4C
#define MAC_BTN_BG_NORMAL 0xFF353537
#define MAC_BTN_BG_HOVER 0xFF454547
#define MAC_BTN_BG_PRESSED 0xFF555557
// --- Button Implementation ---
void widget_button_init(widget_button_t *btn, int x, int y, int w, int h, const char *text) {
btn->x = x;
btn->y = y;
btn->w = w;
btn->h = h;
btn->text = text;
btn->pressed = false;
btn->hovered = false;
btn->on_click = NULL;
}
void widget_button_draw(widget_context_t *ctx, widget_button_t *btn) {
uint32_t bg_color = MAC_BTN_BG_NORMAL;
if (btn->pressed) {
bg_color = MAC_BTN_BG_PRESSED;
} else if (btn->hovered) {
bg_color = MAC_BTN_BG_HOVER;
}
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 + 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 + 1, btn->y + 1, btn->w - 2, btn->h - 2, bg_color);
}
if (btn->text && ctx->draw_string) {
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);
}
}
bool widget_button_handle_mouse(widget_button_t *btn, int mx, int my, bool mouse_down, bool mouse_clicked, void *user_data) {
bool in_bounds = (mx >= btn->x && mx < btn->x + btn->w && my >= btn->y && my < btn->y + btn->h);
btn->hovered = in_bounds;
if (mouse_clicked && in_bounds) {
btn->pressed = true;
return true;
}
if (!mouse_down && btn->pressed) {
btn->pressed = false;
if (in_bounds && btn->on_click) {
btn->on_click(user_data);
}
return true;
}
return in_bounds;
}
// --- Scrollbar Implementation ---
void widget_scrollbar_init(widget_scrollbar_t *sb, int x, int y, int w, int h) {
sb->x = x;
sb->y = y;
sb->w = w;
sb->h = h;
sb->content_height = h;
sb->scroll_y = 0;
sb->is_dragging = false;
sb->on_scroll = NULL;
}
void widget_scrollbar_update(widget_scrollbar_t *sb, int content_height, int scroll_y) {
sb->content_height = content_height;
sb->scroll_y = scroll_y;
}
void widget_scrollbar_draw(widget_context_t *ctx, widget_scrollbar_t *sb) {
// Only draw thumb if content is larger than view
if (sb->content_height > sb->h) {
int thumb_h = (sb->h * sb->h) / sb->content_height;
if (thumb_h < 20) thumb_h = 20;
int max_scroll = sb->content_height - sb->h;
if (sb->scroll_y > max_scroll) sb->scroll_y = max_scroll;
if (sb->scroll_y < 0) sb->scroll_y = 0;
int thumb_y = sb->y + (sb->scroll_y * (sb->h - thumb_h)) / max_scroll;
uint32_t color = 0xFF888888; // Subtle gray thumb for mac style
if (sb->is_dragging) color = 0xFF666666;
if (ctx->draw_rounded_rect_filled) {
// Pill shaped thumb with margin
int margin = 2;
int radius = (sb->w - margin*2) / 2;
ctx->draw_rounded_rect_filled(ctx->user_data, sb->x + margin, thumb_y + margin, sb->w - margin*2, thumb_h - margin*2, radius, color);
} else if (ctx->draw_rect) {
ctx->draw_rect(ctx->user_data, sb->x, thumb_y, sb->w, thumb_h, color);
}
}
}
bool widget_scrollbar_handle_mouse(widget_scrollbar_t *sb, int mx, int my, bool mouse_down, void *user_data) {
if (sb->content_height <= sb->h) return false;
int thumb_h = (sb->h * sb->h) / sb->content_height;
if (thumb_h < 20) thumb_h = 20;
int max_scroll = sb->content_height - sb->h;
if (sb->scroll_y > max_scroll) sb->scroll_y = max_scroll;
if (sb->scroll_y < 0) sb->scroll_y = 0;
int thumb_y = sb->y + (sb->scroll_y * (sb->h - thumb_h)) / max_scroll;
bool in_thumb = (mx >= sb->x && mx < sb->x + sb->w && my >= thumb_y && my < thumb_y + thumb_h);
bool in_track = (mx >= sb->x && mx < sb->x + sb->w && my >= sb->y && my < sb->y + sb->h);
if (mouse_down && !sb->is_dragging) {
if (in_thumb) {
sb->is_dragging = true;
sb->drag_start_my = my;
sb->drag_start_scroll_y = sb->scroll_y;
return true;
} else if (in_track) {
// Page scroll
if (my < thumb_y) {
sb->scroll_y -= sb->h;
} else {
sb->scroll_y += sb->h;
}
if (sb->scroll_y < 0) sb->scroll_y = 0;
if (sb->scroll_y > max_scroll) sb->scroll_y = max_scroll;
if (sb->on_scroll) sb->on_scroll(user_data, sb->scroll_y);
return true;
}
} else if (!mouse_down) {
sb->is_dragging = false;
}
if (sb->is_dragging && mouse_down) {
int dy = my - sb->drag_start_my;
int track_h = sb->h - thumb_h;
if (track_h > 0) {
float ratio = (float)max_scroll / (float)track_h;
int new_scroll = sb->drag_start_scroll_y + (int)(dy * ratio);
if (new_scroll < 0) new_scroll = 0;
if (new_scroll > max_scroll) new_scroll = max_scroll;
if (new_scroll != sb->scroll_y) {
sb->scroll_y = new_scroll;
if (sb->on_scroll) sb->on_scroll(user_data, sb->scroll_y);
}
}
return true;
}
return in_track || sb->is_dragging;
}
// --- TextBox Implementation ---
void widget_textbox_init(widget_textbox_t *tb, int x, int y, int w, int h, char *buffer, int max_len) {
tb->x = x; tb->y = y; tb->w = w; tb->h = h;
tb->text = buffer;
tb->max_len = max_len;
tb->cursor_pos = string_len(buffer);
tb->focused = false;
tb->on_change = NULL;
}
void widget_textbox_draw(widget_context_t *ctx, widget_textbox_t *tb) {
// 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
} 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);
}
if (ctx->draw_string && tb->text) {
int max_w = tb->w - 15;
int scroll_x = 0;
int text_w = 0;
if (ctx->measure_string_width) {
text_w = ctx->measure_string_width(ctx->user_data, tb->text);
} else {
text_w = string_len(tb->text) * 8;
}
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);
if (tb->focused && ctx->draw_rect) {
int cx = 0;
if (ctx->measure_string_width) {
// measure up to cursor
char tmp[256];
int k = 0;
for (k = 0; k < tb->cursor_pos && tb->text[k]; k++) {
tmp[k] = tb->text[k];
}
tmp[k] = 0;
cx = ctx->measure_string_width(ctx->user_data, tmp);
} else {
cx = tb->cursor_pos * 8;
}
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);
}
}
}
bool widget_textbox_handle_mouse(widget_textbox_t *tb, int mx, int my, bool mouse_clicked, void *user_data) {
bool in_bounds = (mx >= tb->x && mx < tb->x + tb->w && my >= tb->y && my < tb->y + tb->h);
if (mouse_clicked) {
tb->focused = in_bounds;
}
return in_bounds;
}
bool widget_textbox_handle_key(widget_textbox_t *tb, char c, void *user_data) {
if (!tb->focused || !tb->text) return false;
int len = string_len(tb->text);
if (c == '\b') { // backspace
if (len > 0) {
tb->text[len - 1] = '\0';
tb->cursor_pos = len - 1;
if (tb->on_change) tb->on_change(user_data);
}
} else if (c >= 32 && c < 127) {
if (len < tb->max_len - 1) {
tb->text[len] = c;
tb->text[len + 1] = '\0';
tb->cursor_pos = len + 1;
if (tb->on_change) tb->on_change(user_data);
}
}
return true;
}
// --- Dropdown Implementation ---
void widget_dropdown_init(widget_dropdown_t *dd, int x, int y, int w, int h, const char **items, int count) {
dd->x = x; dd->y = y; dd->w = w; dd->h = h;
dd->items = items;
dd->item_count = count;
dd->selected_idx = 0;
dd->is_open = false;
dd->on_select = NULL;
}
void widget_dropdown_draw(widget_context_t *ctx, widget_dropdown_t *dd) {
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);
} 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);
}
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);
}
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);
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);
}
}
}
}
bool widget_dropdown_handle_mouse(widget_dropdown_t *dd, int mx, int my, bool mouse_clicked, void *user_data) {
if (!mouse_clicked) return false;
if (dd->is_open) {
int menu_h = dd->item_count * dd->h;
if (mx >= dd->x && mx < dd->x + dd->w && my >= dd->y + dd->h && my < dd->y + dd->h + menu_h) {
int clicked_idx = (my - (dd->y + dd->h)) / dd->h;
if (clicked_idx >= 0 && clicked_idx < dd->item_count) {
dd->selected_idx = clicked_idx;
dd->is_open = false;
if (dd->on_select) dd->on_select(user_data, clicked_idx);
return true;
}
}
dd->is_open = false;
}
if (mx >= dd->x && mx < dd->x + dd->w && my >= dd->y && my < dd->y + dd->h) {
dd->is_open = !dd->is_open;
return true;
}
return false;
}
// --- Checkbox / Radio Implementation ---
void widget_checkbox_init(widget_checkbox_t *cb, int x, int y, int w, int h, const char *text, bool is_radio) {
cb->x = x; cb->y = y; cb->w = w; cb->h = h;
cb->text = text;
cb->checked = false;
cb->is_radio = is_radio;
cb->on_toggle = NULL;
}
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;
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);
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);
}
} 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);
if (cb->checked) {
int inner = box_size - 6;
ctx->draw_rect(ctx->user_data, cb->x + 3, box_y + 3, inner, inner, COLOR_WHITE);
}
}
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);
}
}
bool widget_checkbox_handle_mouse(widget_checkbox_t *cb, int mx, int my, bool mouse_clicked, void *user_data) {
if (!mouse_clicked) return false;
if (mx >= cb->x && mx < cb->x + cb->w && my >= cb->y && my < cb->y + cb->h) {
cb->checked = !cb->checked;
if (cb->on_toggle) cb->on_toggle(user_data, cb->checked);
return true;
}
return false;
}

92
src/wm/libwidget.h Normal file
View file

@ -0,0 +1,92 @@
#ifndef LIBWIDGET_H
#define LIBWIDGET_H
#include <stdint.h>
#include <stdbool.h>
// Widget Context for abstract drawing backend
typedef struct {
void *user_data;
void (*draw_rect)(void *user_data, int x, int y, int w, int h, uint32_t color);
void (*draw_rounded_rect_filled)(void *user_data, int x, int y, int w, int h, int r, uint32_t color);
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);
} widget_context_t;
// --- Button ---
typedef struct {
int x, y, w, h;
const char *text;
bool pressed;
bool hovered;
void (*on_click)(void *user_data);
} widget_button_t;
void widget_button_init(widget_button_t *btn, int x, int y, int w, int h, const char *text);
void widget_button_draw(widget_context_t *ctx, widget_button_t *btn);
// Returns true if event was consumed
bool widget_button_handle_mouse(widget_button_t *btn, int mx, int my, bool mouse_down, bool mouse_clicked, void *user_data);
// --- Scrollbar ---
typedef struct {
int x, y, w, h;
int content_height;
int scroll_y;
bool is_dragging;
int drag_start_my;
int drag_start_scroll_y;
void (*on_scroll)(void *user_data, int new_scroll_y);
} widget_scrollbar_t;
void widget_scrollbar_init(widget_scrollbar_t *sb, int x, int y, int w, int h);
void widget_scrollbar_update(widget_scrollbar_t *sb, int content_height, int scroll_y);
void widget_scrollbar_draw(widget_context_t *ctx, widget_scrollbar_t *sb);
// Returns true if event was consumed
bool widget_scrollbar_handle_mouse(widget_scrollbar_t *sb, int mx, int my, bool mouse_down, void *user_data);
// --- TextBox ---
typedef struct {
int x, y, w, h;
char *text;
int max_len;
int cursor_pos;
bool focused;
void (*on_change)(void *user_data);
} widget_textbox_t;
void widget_textbox_init(widget_textbox_t *tb, int x, int y, int w, int h, char *buffer, int max_len);
void widget_textbox_draw(widget_context_t *ctx, widget_textbox_t *tb);
bool widget_textbox_handle_mouse(widget_textbox_t *tb, int mx, int my, bool mouse_clicked, void *user_data);
bool widget_textbox_handle_key(widget_textbox_t *tb, char c, void *user_data);
// --- Dropdown ---
typedef struct {
int x, y, w, h;
const char **items;
int item_count;
int selected_idx;
bool is_open;
void (*on_select)(void *user_data, int new_idx);
} widget_dropdown_t;
void widget_dropdown_init(widget_dropdown_t *dd, int x, int y, int w, int h, const char **items, int count);
void widget_dropdown_draw(widget_context_t *ctx, widget_dropdown_t *dd);
bool widget_dropdown_handle_mouse(widget_dropdown_t *dd, int mx, int my, bool mouse_clicked, void *user_data);
// --- Checkbox / Radio ---
typedef struct {
int x, y, w, h;
const char *text;
bool checked;
bool is_radio;
void (*on_toggle)(void *user_data, bool new_state);
} widget_checkbox_t;
void widget_checkbox_init(widget_checkbox_t *cb, int x, int y, int w, int h, const char *text, bool is_radio);
void widget_checkbox_draw(widget_context_t *ctx, widget_checkbox_t *cb);
bool widget_checkbox_handle_mouse(widget_checkbox_t *cb, int mx, int my, bool mouse_clicked, void *user_data);
#endif