From 88f178e368a5a7402629f4091960ddcce0bb04ee Mon Sep 17 00:00:00 2001 From: boreddevnl Date: Tue, 17 Mar 2026 14:41:13 +0100 Subject: [PATCH] checkpoint: BoredWord application --- disk.img | Bin 67108864 -> 67108864 bytes src/sys/syscall.c | 77 ++ src/userland/gui/word.c | 1516 +++++++++++++++++++++++++++++++++++ src/userland/libc/libui.c | 11 + src/userland/libc/libui.h | 2 + src/userland/libc/syscall.c | 12 + src/wm/explorer.c | 4 + src/wm/font_manager.c | 104 ++- src/wm/font_manager.h | 7 +- src/wm/graphics.c | 154 ++++ src/wm/graphics.h | 3 + src/wm/wm.c | 141 +++- src/wm/wm.h | 1 + 13 files changed, 1976 insertions(+), 56 deletions(-) create mode 100644 src/userland/gui/word.c diff --git a/disk.img b/disk.img index b819479821734fcead9efd36984559d9426fa12b..26b400993eda641610648418149d2ab9a0fb040b 100644 GIT binary patch delta 11704 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 delta 27708 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 diff --git a/src/sys/syscall.c b/src/sys/syscall.c index fd8a08e..8f6454e 100644 --- a/src/sys/syscall.c +++ b/src/sys/syscall.c @@ -497,6 +497,83 @@ static uint64_t syscall_handler_inner(registers_t *regs) { } } + asm volatile("push %0; popfq" : : "r"(rflags)); + } + } else if (cmd == 18) { // GUI_CMD_DRAW_STRING_SCALED_SLOPED + Window *win = (Window *)arg2; + uint64_t coords = arg3; + int ux = coords & 0xFFFFFFFF; + int uy = coords >> 32; + const char *user_str = (const char *)arg4; + + // Unpack color, scale, slope from arg5 + uint64_t packed1 = arg5; + uint32_t color = packed1 & 0xFFFFFFFF; + uint32_t scale_bits = packed1 >> 32; + float scale = *(float*)&scale_bits; + + // Slope is passed via arg6 in the system call, but syscall5 only takes 5 args. + // Oh right, we only have syscall5. Let's make a packed struct or just use a generic pointer for coords. + // Even better, let's just make it a pointer to a struct. + // Wait, I will just use `regs->r9` (arg6) directly since the syscall handler has access to all registers: + uint64_t arg6 = regs->r9; + uint32_t slope_bits = arg6 & 0xFFFFFFFF; + float slope = *(float*)&slope_bits; + + if (win && user_str) { + extern void draw_string_scaled_sloped(int x, int y, const char *str, uint32_t color, float scale, float slope); + extern void graphics_set_render_target(uint32_t *buffer, int w, int h); + + // Copy string safely to kernel stack buffer + char kernel_str[256]; + int i = 0; + while (i < 255 && user_str[i]) { + kernel_str[i] = user_str[i]; + i++; + } + kernel_str[i] = 0; + + uint64_t rflags; + asm volatile("pushfq; pop %0; cli" : "=r"(rflags)); + + ttf_font_t *font = win->font ? (ttf_font_t*)win->font : graphics_get_current_ttf(); + + if (win->pixels) { + if (ux >= -100 && ux < win->w && uy >= -100 && uy < (win->h - 20)) { + graphics_set_render_target(win->pixels, win->w, win->h - 20); + if (font) { + int baseline = uy + font_manager_get_font_ascent_scaled(font, scale) - 2; + int cur_x = ux; + const char *s = kernel_str; + while (*s) { + extern void font_manager_render_char_sloped(ttf_font_t *font, int x, int y, char c, uint32_t color, float scale, float slope, void (*put_pixel_fn)(int, int, uint32_t)); + font_manager_render_char_sloped(font, cur_x, baseline, *s, color, scale, slope, put_pixel); + char buf[2] = {*s, 0}; + cur_x += font_manager_get_string_width_scaled(font, buf, scale); + s++; + } + } else { + draw_string_scaled_sloped(ux, uy, kernel_str, color, scale, slope); + } + graphics_set_render_target(NULL, 0, 0); + } + } else { + if (font) { + int baseline = win->y + uy + font_manager_get_font_ascent_scaled(font, scale) - 2; + int cur_x = win->x + ux; + const char *s = kernel_str; + while (*s) { + extern void font_manager_render_char_sloped(ttf_font_t *font, int x, int y, char c, uint32_t color, float scale, float slope, void (*put_pixel_fn)(int, int, uint32_t)); + font_manager_render_char_sloped(font, cur_x, baseline, *s, color, scale, slope, put_pixel); + char buf[2] = {*s, 0}; + cur_x += font_manager_get_string_width_scaled(font, buf, scale); + s++; + } + } else { + draw_string_scaled_sloped(win->x + ux, win->y + uy, kernel_str, color, scale, slope); + } + } + asm volatile("push %0; popfq" : : "r"(rflags)); } } else if (cmd == GUI_CMD_DRAW_IMAGE) { diff --git a/src/userland/gui/word.c b/src/userland/gui/word.c new file mode 100644 index 0000000..1a7552e --- /dev/null +++ b/src/userland/gui/word.c @@ -0,0 +1,1516 @@ +// 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 "libc/syscall.h" +#include "libc/libui.h" +#include "libc/stdlib.h" +#include + +#define COLOR_DARK_PANEL 0xFF202020 +#define COLOR_DARK_BG 0xFF121212 +#define COLOR_DARK_TEXT 0xFFE0E0E0 +#define COLOR_DARK_TITLEBAR 0xFF303030 +#define COLOR_BLACK 0xFF000000 +#define COLOR_BLUE 0xFF4A90E2 +#define COLOR_WHITE 0xFFFFFFFF +#define COLOR_TOOLBAR_BTN 0xFF404040 +#define COLOR_TOOLBAR_BTN_ACTIVE 0xFF707070 + +#define MAX_FONTS 16 +static char font_names[MAX_FONTS][64]; +static int font_count = 0; +static int current_font_idx = 0; +static float current_font_size = 15.0f; +static uint32_t current_text_color = COLOR_BLACK; + +static int active_dialog = 0; +static char dialog_input[256]; +static int dialog_input_len = 0; +static int active_dropdown = 0; + +static uint32_t const palette[] = { COLOR_BLACK, 0xFFE74C3C, 0xFF3498DB, 0xFF2ECC71, 0xFF95A5A6 }; + +static _Bool is_bold = 0; +static _Bool is_italic = 0; +static _Bool is_underline = 0; +static int align_mode = 0; +static float line_spacing = 1.0f; + +static _Bool is_dragging = 0; +static _Bool selection_started = 0; +static int sel_start_para = -1, sel_start_run = -1, sel_start_pos = -1; +static int sel_end_para = -1, sel_end_run = -1, sel_end_pos = -1; + +#define MAX_PARAGRAPHS 256 +#define MAX_RUNS_PER_PARAGRAPH 64 +#define MAX_RUN_TEXT 128 + +typedef struct { + char text[MAX_RUN_TEXT]; + int len; + _Bool bold; + _Bool italic; + _Bool underline; + int font_idx; + float font_size; + uint32_t color; +} TextRun; + +typedef struct { + TextRun runs[MAX_RUNS_PER_PARAGRAPH]; + int run_count; + int align; + float spacing; +} Paragraph; + +#define MAX_UNDO_STATES 10 +typedef struct { + Paragraph paragraphs[MAX_PARAGRAPHS]; + int para_count; + int cursor_para; + int cursor_run; + int cursor_pos; +} UndoState; + +static UndoState undo_stack[MAX_UNDO_STATES]; +static int undo_head = 0; +static int undo_tail = 0; + +static Paragraph paragraphs[MAX_PARAGRAPHS]; +static int para_count = 1; +static int cursor_para = 0; +static int cursor_run = 0; +static int cursor_pos = 0; + +static char open_filename[256] = ""; +static _Bool file_modified = 0; +static int scroll_y = 0; + +static _Bool is_in_selection(int p, int r, int c); + +static int win_w = 800; +static int win_h = 600; + +static size_t string_len(const char *str) { + size_t l = 0; + while(str[l]) l++; + return l; +} + +static void string_copy(char *dest, const char *src) { + while(*src) *dest++ = *src++; + *dest = 0; +} + +static void load_fonts(void) { + FAT32_FileInfo entries[MAX_FONTS]; + int count = sys_list("/Library/Fonts", entries, MAX_FONTS); + font_count = 0; + for(int i = 0; i < count; i++) { + if (!entries[i].is_directory) { + int len = string_len(entries[i].name); + if (len > 4 && + entries[i].name[len-4] == '.' && + entries[i].name[len-3] == 't' && + entries[i].name[len-2] == 't' && + entries[i].name[len-1] == 'f') { + string_copy(font_names[font_count], entries[i].name); + font_count++; + if (font_count >= MAX_FONTS) break; + } + } + } + if (font_count == 0) { + string_copy(font_names[0], "firamono.ttf"); + font_count = 1; + } +} + +static int last_set_font_idx = -1; + +static void set_active_font(ui_window_t win, int idx) { + if (idx < 0 || idx >= font_count) return; + if (idx != last_set_font_idx) { + char full_path[128]; + string_copy(full_path, "/Library/Fonts/"); + char *d = full_path + string_len(full_path); + string_copy(d, font_names[idx]); + ui_set_font(win, full_path); + last_set_font_idx = idx; + } +} + +static void save_undo_state(void) { + UndoState *s = &undo_stack[undo_head]; + s->para_count = para_count; + s->cursor_para = cursor_para; + s->cursor_run = cursor_run; + s->cursor_pos = cursor_pos; + for(int i=0; iparagraphs[i] = paragraphs[i]; + } + undo_head = (undo_head + 1) % MAX_UNDO_STATES; + if (undo_head == undo_tail) { + undo_tail = (undo_tail + 1) % MAX_UNDO_STATES; + } +} + +static void perform_undo(void) { + if (undo_head == undo_tail) return; + undo_head = (undo_head - 1 + MAX_UNDO_STATES) % MAX_UNDO_STATES; + UndoState *s = &undo_stack[undo_head]; + + para_count = s->para_count; + cursor_para = s->cursor_para; + cursor_run = s->cursor_run; + cursor_pos = s->cursor_pos; + for(int i=0; iparagraphs[i]; + } +} + +static void init_doc(void) { + para_count = 1; + cursor_para = 0; + cursor_run = 0; + cursor_pos = 0; + scroll_y = 0; + + for (int i=0; ilen = 0; + r->text[0] = 0; + r->bold = is_bold; + r->italic = is_italic; + r->underline = is_underline; + r->font_idx = current_font_idx; + r->font_size = current_font_size; + r->color = current_text_color; +} + +static void handle_arrows(char c) { + if (c == 17) { + if (cursor_para > 0) { + cursor_para--; + cursor_run = paragraphs[cursor_para].run_count - 1; + if (cursor_run < 0) cursor_run = 0; + cursor_pos = paragraphs[cursor_para].runs[cursor_run].len; + } + } else if (c == 18) { + if (cursor_para < para_count - 1) { + cursor_para++; + cursor_run = 0; + cursor_pos = 0; + } + } else if (c == 19) { + if (cursor_pos > 0) { + cursor_pos--; + } else if (cursor_run > 0) { + cursor_run--; + cursor_pos = paragraphs[cursor_para].runs[cursor_run].len; + } else if (cursor_para > 0) { + cursor_para--; + cursor_run = paragraphs[cursor_para].run_count - 1; + if (cursor_run < 0) cursor_run = 0; + cursor_pos = paragraphs[cursor_para].runs[cursor_run].len; + } + } else if (c == 20) { + if (cursor_pos < paragraphs[cursor_para].runs[cursor_run].len) { + cursor_pos++; + } else if (cursor_run < paragraphs[cursor_para].run_count - 1) { + cursor_run++; + cursor_pos = 0; + } else if (cursor_para < para_count - 1) { + cursor_para++; + cursor_run = 0; + cursor_pos = 0; + } + } +} + +static void split_run(int p_idx, int r_idx, int pos) { + Paragraph *p = ¶graphs[p_idx]; + if (pos <= 0 || pos >= p->runs[r_idx].len) return; + if (p->run_count >= MAX_RUNS_PER_PARAGRAPH) return; + + for (int i = p->run_count; i > r_idx + 1; i--) { + p->runs[i] = p->runs[i-1]; + } + p->run_count++; + + TextRun *r1 = &p->runs[r_idx]; + TextRun *r2 = &p->runs[r_idx+1]; + + r2->bold = r1->bold; + r2->italic = r1->italic; + r2->underline = r1->underline; + r2->font_idx = r1->font_idx; + r2->font_size = r1->font_size; + r2->color = r1->color; + + r2->len = r1->len - pos; + for(int i=0; ilen; i++) r2->text[i] = r1->text[pos + i]; + r2->text[r2->len] = 0; + + r1->len = pos; + r1->text[pos] = 0; +} + +static void delete_selection(void) { + if (sel_start_para == -1 || sel_end_para == -1) return; + + int s_p = sel_start_para, s_r = sel_start_run, s_c = sel_start_pos; + int e_p = sel_end_para, e_r = sel_end_run, e_c = sel_end_pos; + + if (e_p < s_p || (e_p == s_p && e_r < s_r) || (e_p == s_p && e_r == s_r && e_c < s_c)) { + s_p = sel_end_para; s_r = sel_end_run; s_c = sel_end_pos; + e_p = sel_start_para; e_r = sel_start_run; e_c = sel_start_pos; + } + + if (s_p == e_p && s_r == e_r && s_c == e_c) { + sel_start_para = -1; sel_end_para = -1; + return; + } + + save_undo_state(); + + split_run(e_p, e_r, e_c); + split_run(s_p, s_r, s_c); + + if (s_c > 0) s_r++; + if (s_p == e_p && e_r >= s_r) e_r++; + + for (int p = s_p; p <= e_p; p++) { + Paragraph *para = ¶graphs[p]; + int start_r = (p == s_p) ? s_r : 0; + int end_r = (p == e_p) ? e_r - 1 : para->run_count - 1; + + for (int r = start_r; r <= end_r; r++) { + if (r >= para->run_count) break; + para->runs[r].len = 0; + } + } + + cursor_para = s_p; + cursor_run = s_r; + cursor_pos = 0; + + + sel_start_para = -1; sel_end_para = -1; + file_modified = 1; +} + +static void insert_char(char c) { + if (sel_start_para != -1) { + delete_selection(); + if (c == '\b') return; + } + + if (c < 32 && c != '\n' && c != '\b') { + handle_arrows(c); + return; + } + + if (c == '\b') { + save_undo_state(); + if (cursor_pos > 0) { + TextRun *r = ¶graphs[cursor_para].runs[cursor_run]; + for(int i=cursor_pos-1; ilen; i++) r->text[i] = r->text[i+1]; + r->len--; + cursor_pos--; + + if (r->len == 0 && paragraphs[cursor_para].run_count > 1) { + for(int i = cursor_run; i < paragraphs[cursor_para].run_count - 1; i++) { + paragraphs[cursor_para].runs[i] = paragraphs[cursor_para].runs[i+1]; + } + paragraphs[cursor_para].run_count--; + if (cursor_run >= paragraphs[cursor_para].run_count) { + cursor_run = paragraphs[cursor_para].run_count - 1; + } + cursor_pos = paragraphs[cursor_para].runs[cursor_run].len; + } + } else if (cursor_run > 0) { + if (paragraphs[cursor_para].runs[cursor_run].len == 0 && paragraphs[cursor_para].run_count > 1) { + for(int i = cursor_run; i < paragraphs[cursor_para].run_count - 1; i++) { + paragraphs[cursor_para].runs[i] = paragraphs[cursor_para].runs[i+1]; + } + paragraphs[cursor_para].run_count--; + } + cursor_run--; + if (cursor_run < 0) cursor_run = 0; + TextRun *r = ¶graphs[cursor_para].runs[cursor_run]; + if (r->len > 0) { + r->len--; + cursor_pos = r->len; + + if (r->len == 0 && paragraphs[cursor_para].run_count > 1) { + for(int i = cursor_run; i < paragraphs[cursor_para].run_count - 1; i++) { + paragraphs[cursor_para].runs[i] = paragraphs[cursor_para].runs[i+1]; + } + paragraphs[cursor_para].run_count--; + if (cursor_run >= paragraphs[cursor_para].run_count) cursor_run = paragraphs[cursor_para].run_count - 1; + if (cursor_run < 0) cursor_run = 0; + cursor_pos = paragraphs[cursor_para].runs[cursor_run].len; + } + } else { + cursor_pos = 0; + } + } else if (cursor_para > 0) { + Paragraph *prev = ¶graphs[cursor_para - 1]; + Paragraph *curr = ¶graphs[cursor_para]; + + cursor_para--; + cursor_run = prev->run_count - 1; + if (cursor_run < 0) cursor_run = 0; + TextRun *r = &prev->runs[cursor_run]; + cursor_pos = r->len; + + for (int i = 0; i < curr->run_count; i++) { + if (curr->runs[i].len == 0 && i == 0 && curr->run_count == 1) continue; + if (prev->run_count < MAX_RUNS_PER_PARAGRAPH) { + prev->runs[prev->run_count] = curr->runs[i]; + prev->run_count++; + } + } + + for(int i=cursor_para+1; irun_count == 0) prev->run_count = 1; + } + file_modified = 1; + return; + } + + if (c == '\n') { + save_undo_state(); + if (para_count >= MAX_PARAGRAPHS) return; + for(int i=para_count; i>cursor_para+1; i--) paragraphs[i] = paragraphs[i-1]; + para_count++; + + Paragraph *next = ¶graphs[cursor_para+1]; + next->align = align_mode; + next->spacing = line_spacing; + + next->run_count = 1; + TextRun *nr = &next->runs[0]; + nr->len = 0; + nr->text[0] = 0; + nr->bold = is_bold; + nr->italic = is_italic; + nr->underline = is_underline; + nr->font_idx = current_font_idx; + nr->font_size = current_font_size; + nr->color = current_text_color; + + cursor_para++; + cursor_run = 0; + cursor_pos = 0; + file_modified = 1; + return; + } + + Paragraph *p = ¶graphs[cursor_para]; + if (p->run_count == 0) p->run_count = 1; + TextRun *r = &p->runs[cursor_run]; + + if (r->bold != is_bold || r->italic != is_italic || r->underline != is_underline || + r->font_idx != current_font_idx || r->font_size != current_font_size || r->color != current_text_color) { + + if (cursor_pos > 0 && cursor_pos < r->len) { + split_run(cursor_para, cursor_run, cursor_pos); + } + + if (cursor_pos == 0 && r->len > 0) { + if (p->run_count < MAX_RUNS_PER_PARAGRAPH) { + for(int i = p->run_count; i > cursor_run; i--) p->runs[i] = p->runs[i-1]; + p->run_count++; + r = &p->runs[cursor_run]; + r->len = 0; r->text[0] = 0; + } + } else if (cursor_pos == r->len && r->len > 0) { + if (p->run_count < MAX_RUNS_PER_PARAGRAPH) { + cursor_run++; + for(int i = p->run_count; i > cursor_run; i--) p->runs[i] = p->runs[i-1]; + p->run_count++; + r = &p->runs[cursor_run]; + r->len = 0; r->text[0] = 0; + cursor_pos = 0; + } + } + + r->bold = is_bold; + r->italic = is_italic; + r->underline = is_underline; + r->font_idx = current_font_idx; + r->font_size = current_font_size; + r->color = current_text_color; + } + + if (c == ' ') save_undo_state(); + + if (r->len < MAX_RUN_TEXT - 1) { + for(int i=r->len; i>cursor_pos; i--) r->text[i] = r->text[i-1]; + r->text[cursor_pos] = c; + r->len++; + cursor_pos++; + file_modified = 1; + } +} + +static bool str_contains(const char *haystack, const char *needle) { + if (!haystack || !needle) return false; + int hlen = string_len(haystack); + int nlen = string_len(needle); + for(int i=0; i<=hlen-nlen; i++) { + bool match = true; + for(int j=0; j>\nendobj\n"); + + xref[obj_count++] = offset; + WRITE_STR("2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n"); + + xref[obj_count++] = offset; + WRITE_STR("3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Contents 4 0 R /Resources << /Font << " + "/F1 5 0 R /F2 6 0 R /F3 7 0 R /F4 8 0 R " + "/F5 9 0 R /F6 10 0 R /F7 11 0 R /F8 12 0 R " + "/F9 13 0 R /F10 14 0 R /F11 15 0 R /F12 16 0 R >> >> >>\nendobj\n"); + + char stream[8192]; + stream[0] = 0; + int slen = 0; + + #define S_WRITE(s) do { string_copy(stream + slen, s); slen += string_len(s); } while(0) + + S_WRITE("BT\n"); + + float cur_y = 800.0f; + + for(int p=0; prun_count; r++) { + TextRun *run_m = ¶->runs[r]; + if (run_m->len > 0) { + const char *ttf_m = font_names[run_m->font_idx]; + float char_w = (str_contains(ttf_m, "mono") || str_contains(ttf_m, "fira")) ? (run_m->font_size * 0.6f) : (run_m->font_size * 0.45f); + tw += (int)(char_w * run_m->len); + if (run_m->bold) tw += run_m->len; + } + } + + int px = 20; + if (para->align == 1) px = (595 - tw) / 2; + else if (para->align == 2) px = 595 - 20 - tw; + + char tm_buf[64]; + int tlen = 0; + string_copy(tm_buf, "1 0 0 1 "); tlen += 8; + char num[16]; + itoa(px, num); + string_copy(tm_buf + tlen, num); tlen += string_len(num); + tm_buf[tlen++] = ' '; + itoa((int)cur_y, num); + string_copy(tm_buf + tlen, num); tlen += string_len(num); + string_copy(tm_buf + tlen, " Tm\n"); tlen += 4; + tm_buf[tlen] = 0; + S_WRITE(tm_buf); + + char align_cmt[32]; + int a_len = 0; + string_copy(align_cmt, "%ALIGN_0\n"); + align_cmt[7] = '0' + para->align; + S_WRITE(align_cmt); + + for(int r=0; rrun_count; r++) { + TextRun *run = ¶->runs[r]; + if (run->len > 0) { + int base_idx = 0; + const char *ttf = font_names[run->font_idx]; + if (str_contains(ttf, "mono") || str_contains(ttf, "console") || str_contains(ttf, "fira")) base_idx = 8; + else if (str_contains(ttf, "serif") || str_contains(ttf, "times") || str_contains(ttf, "georgia")) base_idx = 4; + + int style = 0; + if (run->bold && run->italic) style = 3; + else if (run->italic) style = 2; + else if (run->bold) style = 1; + + int fkey = base_idx + style + 1; + + char fbuf[128]; + int flen = 0; + string_copy(fbuf, "/F"); flen = 2; + char keynum[16]; itoa(fkey, keynum); + string_copy(fbuf + flen, keynum); flen += string_len(keynum); + fbuf[flen++] = ' '; + char sizenum[16]; itoa((int)run->font_size, sizenum); + string_copy(fbuf + flen, sizenum); flen += string_len(sizenum); + string_copy(fbuf + flen, " Tf\n"); flen += 4; + + int rr = (run->color >> 16) & 0xFF; + int gg = (run->color >> 8) & 0xFF; + int bb = run->color & 0xFF; + append_pdf_float(fbuf, &flen, rr); + append_pdf_float(fbuf, &flen, gg); + append_pdf_float(fbuf, &flen, bb); + fbuf[flen++] = 'r'; fbuf[flen++] = 'g'; fbuf[flen++] = '\n'; + + string_copy(fbuf + flen, "%FMT_"); flen += 5; + fbuf[flen++] = run->bold ? '1' : '0'; + fbuf[flen++] = run->italic ? '1' : '0'; + fbuf[flen++] = run->underline ? '1' : '0'; + uint32_t c = run->color; + for(int i=7; i>=0; i--) { int nibble = c & 0xF; fbuf[flen+i] = (nibble < 10) ? ('0' + nibble) : ('A' + (nibble - 10)); c >>= 4; } + flen += 8; + for(int i=0; i<(int)string_len(sizenum); i++) fbuf[flen++] = sizenum[i]; + fbuf[flen++] = '\n'; + fbuf[flen] = 0; + + S_WRITE(fbuf); + + S_WRITE("("); + for(int i=0; ilen; i++) { + if (run->text[i] == '(' || run->text[i] == ')' || run->text[i] == '\\') { + stream[slen++] = '\\'; + } + stream[slen++] = run->text[i]; + } + stream[slen] = 0; + S_WRITE(") Tj\n"); + } + } + + float max_lh = 15.0f; + for(int r=0; rrun_count; r++) { + if (para->runs[r].font_size > max_lh) max_lh = para->runs[r].font_size; + } + cur_y -= (max_lh + 5.0f); + } + S_WRITE("ET\n"); + + xref[obj_count++] = offset; + sys_write_fs(fd, "4 0 obj\n<< /Length ", 19); offset += 19; + char num[16]; + itoa(slen, num); + sys_write_fs(fd, num, string_len(num)); offset += string_len(num); + sys_write_fs(fd, " >>\nstream\n", 11); offset += 11; + + sys_write_fs(fd, stream, slen); offset += slen; + + sys_write_fs(fd, "\nendstream\nendobj\n", 18); offset += 18; + + const char *base_fonts[12] = { + "Helvetica", "Helvetica-Bold", "Helvetica-Oblique", "Helvetica-BoldOblique", + "Times-Roman", "Times-Bold", "Times-Italic", "Times-BoldItalic", + "Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique" + }; + for(int i=0; i<12; i++) { + xref[obj_count++] = offset; + char num[16]; + itoa(i + 5, num); + WRITE_STR(num); + WRITE_STR(" 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /"); + WRITE_STR(base_fonts[i]); + WRITE_STR(" >>\nendobj\n"); + } + + int xref_offset = offset; + WRITE_STR("xref\n0 17\n0000000000 65535 f \n"); + for(int i=1; i<17; i++) { + char entry[32]; + int w = 0; + char num[16]; + itoa(xref[i], num); + int pad = 10 - (int)string_len(num); + for(int p=0; p>\nstartxref\n"); + char xnum[16]; + itoa(xref_offset, xnum); + WRITE_STR(xnum); + WRITE_STR("\n%%EOF\n"); + + sys_close(fd); + file_modified = 0; +} + +static void load_file(const char* path) { + int fd = sys_open(path, "r"); + if (fd < 0) return; + int size = sys_size(fd); + if(size <= 0) { sys_close(fd); return; } + + if (size > 65535) size = 65535; + + static char buf[65536]; + sys_read(fd, buf, size); + sys_close(fd); + buf[size] = 0; + + init_doc(); + string_copy(open_filename, path); + cursor_para = 0; cursor_run = 0; cursor_pos = 0; + para_count = 1; paragraphs[0].run_count = 0; + + if (buf[0] == '%' && buf[1] == 'P' && buf[2] == 'D' && buf[3] == 'F') { + int i=0; + + is_bold = 0; is_italic = 0; is_underline = 0; + current_font_size = 15.0f; current_text_color = COLOR_BLACK; current_font_idx = 0; + + while(i < size) { + if(buf[i] == '%' && i+7 < size && buf[i+1] == 'A' && buf[i+2] == 'L' && buf[i+3] == 'I' && buf[i+4] == 'G' && buf[i+5] == 'N' && buf[i+6] == '_') { + align_mode = buf[i+7] - '0'; + paragraphs[cursor_para].align = align_mode; + i += 8; + while(i < size && buf[i] != '\n') i++; + } + else if (buf[i] == '/' && i+1 < size && buf[i+1] == 'F') { + int start = i; + i += 2; + int fkey = 0; while(i < size && buf[i] >= '0' && buf[i] <= '9') { fkey = fkey * 10 + (buf[i++] - '0'); } + while(i < size && buf[i] == ' ') i++; + int fsize = 0; while(i < size && buf[i] >= '0' && buf[i] <= '9') { fsize = fsize * 10 + (buf[i++] - '0'); } + while(i < size && buf[i] == ' ') i++; + if (i + 1 < size && buf[i] == 'T' && buf[i+1] == 'f') { + if (fsize >= 8) current_font_size = (float)fsize; + if (fkey >= 1 && fkey <= 12) { + int base = (fkey - 1) / 4; + int style = (fkey - 1) % 4; + is_bold = (style == 1 || style == 3); + is_italic = (style == 2 || style == 3); + const char *target = (base == 2) ? "mono" : (base == 1 ? "serif" : "sans"); + for(int f=0; f= '0' && hex <= '9') val = hex - '0'; + else if (hex >= 'A' && hex <= 'F') val = hex - 'A' + 10; + else if (hex >= 'a' && hex <= 'f') val = hex - 'a' + 10; + c = (c << 4) | val; + } + current_text_color = c; + + int fsize = 0; + while(i < size && buf[i] != '\n' && buf[i] >= '0' && buf[i] <= '9') { + fsize = fsize * 10 + (buf[i++] - '0'); + } + if (fsize >= 8) current_font_size = (float)fsize; + } + while(i < size && buf[i] != '\n') i++; + } + else if(buf[i] == '(') { + i++; + while(i < size && buf[i] != ')') { + if (buf[i] == '\\' && i+1 < size) i++; + insert_char(buf[i]); + i++; + } + } + else if ((buf[i] == 'T' && buf[i+1] == 'd') || (buf[i] == 'T' && buf[i+1] == 'm')) { + insert_char('\n'); + i += 2; + } + else { + i++; + } + } + } else { + for(int i=0; i= 3 && icon_type <= 6) { + for(int i=0; i<4; i++) { + int lw = (i%2 == 0) ? 12 : 8; + if (icon_type == 6) lw = 12; + int lx = x + 6; + if (icon_type == 4) lx = x + 6 + (12-lw)/2; + if (icon_type == 5) lx = x + 6 + (12-lw); + ui_draw_rect(win, lx, y+6 + i*3, lw, 2, COLOR_WHITE); + } + } else if (icon_type == 7) { + ui_draw_rect(win, x+6, y+11, 12, 2, COLOR_WHITE); + } else if (icon_type == 8) { + ui_draw_rect(win, x+6, y+11, 12, 2, COLOR_WHITE); + ui_draw_rect(win, x+11, y+6, 2, 12, COLOR_WHITE); + } else if (icon_type == 9) { + ui_draw_rect(win, x+12, y+12, 6, 2, COLOR_WHITE); + ui_draw_rect(win, x+18, y+14, 2, 4, COLOR_WHITE); + ui_draw_rect(win, x+12, y+18, 6, 2, COLOR_WHITE); + ui_draw_rect(win, x+10, y+10, 2, 6, COLOR_WHITE); + ui_draw_rect(win, x+8, y+12, 2, 2, COLOR_WHITE); + } else if (icon_type == 10) { + int cx = x + w/2; int cy = y + h/2; + ui_draw_rect(win, cx-6, cy-6, 12, 12, COLOR_WHITE); + ui_draw_rect(win, cx-5, cy-5, 10, 10, COLOR_DARK_BG); + ui_draw_rect(win, cx-4, cy-6, 8, 4, COLOR_WHITE); + ui_draw_rect(win, cx-2, cy+2, 4, 4, COLOR_WHITE); + } +} + +static void draw_toolbar(ui_window_t win) { + ui_draw_rect(win, 0, 0, win_w, 40, COLOR_DARK_PANEL); + + draw_btn_icon(win, 10, 8, 24, 24, 0, is_bold); + draw_btn_icon(win, 40, 8, 24, 24, 1, is_italic); + draw_btn_icon(win, 70, 8, 24, 24, 2, is_underline); + + ui_draw_rect(win, 100, 8, 2, 24, COLOR_DARK_BG); + + draw_btn_icon(win, 110, 8, 24, 24, 3, align_mode == 0); + draw_btn_icon(win, 140, 8, 24, 24, 4, align_mode == 1); + draw_btn_icon(win, 170, 8, 24, 24, 5, align_mode == 2); + draw_btn_icon(win, 200, 8, 24, 24, 6, align_mode == 3); + + ui_draw_rect(win, 230, 8, 2, 24, COLOR_DARK_BG); + + ui_draw_rounded_rect_filled(win, 240, 8, 120, 24, 4, COLOR_TOOLBAR_BTN); + ui_draw_string(win, 245, 12, font_names[current_font_idx], COLOR_WHITE); + ui_draw_rect(win, 240+105, 18, 4, 2, COLOR_WHITE); + + ui_draw_rounded_rect_filled(win, 365, 8, 24, 24, 4, current_text_color); + ui_draw_rect(win, 365, 8, 24, 24, COLOR_WHITE); + ui_draw_rounded_rect_filled(win, 366, 9, 22, 22, 3, current_text_color); + + draw_btn_icon(win, 395, 8, 24, 24, 7, 0); + + char size_str[16]; + int isize = (int)current_font_size; + int didx = 0; + if(isize >= 10) { size_str[didx++] = (isize/10) + '0'; } + size_str[didx++] = (isize%10) + '0'; + size_str[didx] = 0; + ui_draw_string(win, 425, 12, size_str, COLOR_WHITE); + + draw_btn_icon(win, 445, 8, 24, 24, 8, 0); + + draw_btn_icon(win, 485, 8, 30, 24, 9, 0); + + draw_btn_icon(win, win_w - 50, 8, 40, 24, 10, file_modified); +} + +static void draw_dropdowns(ui_window_t win) { + if (active_dropdown == 1) { + ui_draw_rect(win, 240, 32, 120, font_count * 20, COLOR_DARK_PANEL); + ui_draw_rect(win, 240, 32, 120, font_count * 20, COLOR_DARK_BG); + for(int i=0; irun_count) { + int line_w = 0; + int max_h = 16; + int end_run = start_run; + int end_char = start_char; + + 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 = ¶->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 > doc_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 cur_x = doc_x + 10; + if (para->align == 1) { + cur_x = doc_x + 10 + (doc_w - 20 - line_w) / 2; + } else if (para->align == 2) { + cur_x = doc_x + 10 + (doc_w - 20 - line_w); + } + + int d_run = start_run; + int d_char = start_char; + + int line_cursor_x = -1; + + while(d_run < end_run || (d_run == end_run && d_char < end_char)) { + TextRun *run = ¶->runs[d_run]; + int chars_to_draw; + if (d_run == end_run) chars_to_draw = end_char - d_char; + else chars_to_draw = run->len - d_char; + + if (p == cursor_para && d_run == cursor_run && cursor_pos >= d_char && cursor_pos <= d_char + chars_to_draw) { + char sub[128]; + for(int i=0; itext[d_char + i]; + sub[cursor_pos - d_char] = 0; + line_cursor_x = cur_x + ui_get_string_width_scaled(sub, run->font_size); + } + + if (chars_to_draw > 0) { + set_active_font(win, run->font_idx); + + int run_h = ui_get_font_height_scaled(run->font_size); + int y_offset = 0; + if (max_h > run_h) y_offset = max_h - run_h; + + if (cur_y + max_h > 40 && cur_y < win_h) { + for(int i=0; itext[d_char + i], 0}; + _Bool in_sel = is_in_selection(p, d_run, d_char + i); + uint32_t text_col = in_sel ? COLOR_WHITE : run->color; + int cw = ui_get_string_width_scaled(buf, run->font_size); + + if (in_sel) { + ui_draw_rect(win, cur_x, cur_y + y_offset + 4, cw, run_h, COLOR_BLUE); + } + + if (run->italic) { + ui_draw_string_scaled_sloped(win, cur_x, cur_y + y_offset, buf, text_col, run->font_size, 0.2f); + if (run->bold) ui_draw_string_scaled_sloped(win, cur_x+1, cur_y + y_offset, buf, text_col, run->font_size, 0.2f); + } else { + ui_draw_string_scaled(win, cur_x, cur_y + y_offset, buf, text_col, run->font_size); + if (run->bold) ui_draw_string_scaled(win, cur_x+1, cur_y + y_offset, buf, text_col, run->font_size); + } + + if (run->underline) { + ui_draw_rect(win, cur_x, cur_y + max_h - 2, cw, 1, text_col); + } + cur_x += cw; + } + } else { + char buf[128]; + for(int i=0; itext[d_char + i]; + buf[chars_to_draw] = 0; + cur_x += ui_get_string_width_scaled(buf, run->font_size); + } + } + + d_char = 0; + d_run++; + } + + if (p == cursor_para && (d_run == cursor_run || (end_run == cursor_run && end_char == cursor_pos))) { + if (line_cursor_x == -1) line_cursor_x = cur_x; + } + + if (line_cursor_x != -1 && cur_y > 40 && cur_y < win_h) { + int target_h = max_h; + if (cursor_para < para_count && cursor_run < paragraphs[cursor_para].run_count) { + set_active_font(win, paragraphs[cursor_para].runs[cursor_run].font_idx); + int fh = ui_get_font_height_scaled(paragraphs[cursor_para].runs[cursor_run].font_size); + int c_offset = 0; if (max_h > fh) c_offset = max_h - fh; + ui_draw_rect(win, line_cursor_x, cur_y + c_offset, 2, fh, COLOR_BLACK); + } else { + ui_draw_rect(win, line_cursor_x, cur_y, 2, max_h, COLOR_BLACK); + } + } + + cur_y += (int)(max_h * para->spacing) + 4; + + 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++; + } + } + } + } + + set_active_font(win, 0); +} + +static void update_selection(int p, int r, int char_pos) { + sel_end_para = p; + sel_end_run = r; + sel_end_pos = char_pos; +} + +static void apply_style_to_selection(void) { + if (sel_start_para == -1 || sel_end_para == -1) { + Paragraph *p = ¶graphs[cursor_para]; + if (p->run_count == 0) p->run_count = 1; + TextRun *r = &p->runs[cursor_run]; + + if (r->len == 0) { + r->bold = is_bold; + r->italic = is_italic; + r->underline = is_underline; + r->font_idx = current_font_idx; + r->font_size = current_font_size; + r->color = current_text_color; + } else { + if (r->bold == is_bold && r->italic == is_italic && r->underline == is_underline && + r->font_idx == current_font_idx && r->font_size == current_font_size && r->color == current_text_color) { + return; + } + + if (p->run_count < MAX_RUNS_PER_PARAGRAPH) { + if (cursor_pos > 0 && cursor_pos < r->len) { + split_run(cursor_para, cursor_run, cursor_pos); + cursor_run++; + } + + if (cursor_pos > 0) { + cursor_run++; + } + for(int i = p->run_count; i > cursor_run; i--) p->runs[i] = p->runs[i-1]; + p->run_count++; + + TextRun *nr = &p->runs[cursor_run]; + nr->len = 0; + nr->text[0] = 0; + nr->bold = is_bold; + nr->italic = is_italic; + nr->underline = is_underline; + nr->font_idx = current_font_idx; + nr->font_size = current_font_size; + nr->color = current_text_color; + + cursor_pos = 0; + } + } + return; + } + + int s_p = sel_start_para, s_r = sel_start_run, s_c = sel_start_pos; + int e_p = sel_end_para, e_r = sel_end_run, e_c = sel_end_pos; + + if (e_p < s_p || (e_p == s_p && e_r < s_r) || (e_p == s_p && e_r == s_r && e_c < s_c)) { + s_p = sel_end_para; s_r = sel_end_run; s_c = sel_end_pos; + e_p = sel_start_para; e_r = sel_start_run; e_c = sel_start_pos; + } + + if (s_p == e_p && s_r == e_r && s_c == e_c) return; + + save_undo_state(); + + split_run(e_p, e_r, e_c); + split_run(s_p, s_r, s_c); + + if (s_c > 0) s_r++; + if (s_p == e_p && e_r >= s_r) e_r++; + + sel_start_para = s_p; sel_start_run = s_r; sel_start_pos = 0; + sel_end_para = e_p; sel_end_run = e_r - 1; + if (sel_end_run >= 0 && sel_end_run < paragraphs[sel_end_para].run_count) { + sel_end_pos = paragraphs[sel_end_para].runs[sel_end_run].len; + } else { + sel_end_pos = 0; + } + + for (int p = s_p; p <= e_p; p++) { + Paragraph *para = ¶graphs[p]; + int start_r = (p == s_p) ? s_r : 0; + int end_r = (p == e_p) ? e_r - 1 : para->run_count - 1; + + for (int r = start_r; r <= end_r; r++) { + if (r >= para->run_count) break; + TextRun *run = ¶->runs[r]; + run->bold = is_bold; + run->italic = is_italic; + run->underline = is_underline; + run->font_idx = current_font_idx; + run->font_size = current_font_size; + run->color = current_text_color; + } + } + + file_modified = 1; +} + +static void handle_click(ui_window_t win, int x, int y) { + if (active_dialog == 1) { + int dw = 300; int dh = 150; + int dx = (win_w - dw)/2; int dy = (win_h - dh)/2; + if (y >= dy+100 && y <= dy+130) { + if (x >= dx+10 && x <= dx+110) { active_dialog = 0; } + else if (x >= dx+dw-110 && x <= dx+dw-10) { + string_copy(open_filename, dialog_input); + export_pdf(); + active_dialog = 0; + } + } + return; + } + + if (active_dropdown == 1) { + if (x >= 240 && x < 360 && y >= 32 && y < 32 + font_count*20) { + current_font_idx = (y - 32) / 20; + apply_style_to_selection(); + } + active_dropdown = 0; + return; + } + + if (active_dropdown == 2) { + int p_count = sizeof(palette)/sizeof(uint32_t); + if (x >= 365 && x < 405 && y >= 32 && y < 32 + p_count*20) { + current_text_color = palette[(y - 32) / 20]; + apply_style_to_selection(); + } + active_dropdown = 0; + return; + } + + if (y < 40) { + if (x >= 10 && x < 34) { is_bold = !is_bold; active_dropdown = 0; apply_style_to_selection(); } + else if (x >= 40 && x < 64) { is_italic = !is_italic; active_dropdown = 0; apply_style_to_selection(); } + else if (x >= 70 && x < 94) { is_underline = !is_underline; active_dropdown = 0; apply_style_to_selection(); } + + else if (x >= 110 && x < 134) { align_mode = 0; active_dropdown = 0; if (cursor_para != -1) paragraphs[cursor_para].align = 0; } + else if (x >= 140 && x < 164) { align_mode = 1; active_dropdown = 0; if (cursor_para != -1) paragraphs[cursor_para].align = 1; } + else if (x >= 170 && x < 194) { align_mode = 2; active_dropdown = 0; if (cursor_para != -1) paragraphs[cursor_para].align = 2; } + else if (x >= 200 && x < 224) { align_mode = 3; active_dropdown = 0; if (cursor_para != -1) paragraphs[cursor_para].align = 3; } + + else if (x >= 240 && x < 360) { + active_dropdown = 1; + } + else if (x >= 365 && x < 389) { + active_dropdown = 2; + } + else if (x >= 395 && x < 419) { + if (current_font_size > 8.0f) current_font_size -= 1.0f; + active_dropdown = 0; + apply_style_to_selection(); + } + else if (x >= 445 && x < 469) { + if (current_font_size < 72.0f) current_font_size += 1.0f; + active_dropdown = 0; + apply_style_to_selection(); + } + else if (x >= 485 && x < 515) { + perform_undo(); + active_dropdown = 0; + } + else if (x >= win_w - 50 && x < win_w - 10) { + active_dialog = 1; + string_copy(dialog_input, open_filename[0] ? open_filename : "document.pdf"); + dialog_input_len = string_len(dialog_input); + active_dropdown = 0; + } + } else { + int doc_x = 20; + int doc_y = 50 - scroll_y; + int doc_w = win_w - 40; + int target_y = y; + int target_x = x; + int cur_y = doc_y + 10; + + for(int p=0; prun_count) { + int line_w = 0; int max_h = 16; + int end_run = start_run; int end_char = start_char; + 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 = ¶->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 > doc_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 (target_y >= cur_y && target_y < cur_y + line_h) { + int cur_x = doc_x + 10; + if (para->align == 1) cur_x = doc_x + 10 + (doc_w - 20 - line_w) / 2; + else if (para->align == 2) cur_x = doc_x + 10 + (doc_w - 20 - line_w); + + int d_run = start_run; + int d_char = start_char; + while(d_run < end_run || (d_run == end_run && d_char < end_char)) { + TextRun *run = ¶->runs[d_run]; + set_active_font(win, run->font_idx); + int chars_to_draw = (d_run == end_run) ? (end_char - d_char) : (run->len - d_char); + for(int i=0; itext[d_char + i], 0}; + int cw = ui_get_string_width_scaled(buf, run->font_size); + if (target_x >= cur_x && target_x < cur_x + cw/2) { + cursor_para = p; cursor_run = d_run; cursor_pos = d_char + i; + if (selection_started) { + sel_start_para = p; sel_start_run = d_run; sel_start_pos = d_char + i; + sel_end_para = -1; + selection_started = 0; + } else if (is_dragging) { + update_selection(p, d_run, d_char + i); + } + return; + } else if (target_x >= cur_x + cw/2 && target_x < cur_x + cw) { + cursor_para = p; cursor_run = d_run; cursor_pos = d_char + i + 1; + if (selection_started) { + sel_start_para = p; sel_start_run = d_run; sel_start_pos = d_char + i + 1; + sel_end_para = -1; + selection_started = 0; + } else if (is_dragging) { + update_selection(p, d_run, d_char + i + 1); + } + return; + } + cur_x += cw; + } + d_char = 0; d_run++; + } + if (target_x >= cur_x) { + cursor_para = p; + if (end_run < para->run_count) { + cursor_run = end_run; cursor_pos = end_char; + } else { + cursor_run = end_run - 1; if(cursor_run<0) cursor_run=0; + cursor_pos = para->runs[cursor_run].len; + } + if (selection_started) { + sel_start_para = cursor_para; sel_start_run = cursor_run; sel_start_pos = cursor_pos; sel_end_para = -1; selection_started = 0; + } else if (is_dragging) { + update_selection(cursor_para, cursor_run, cursor_pos); + } + } else if (target_x < cur_x - line_w) { + cursor_para = p; cursor_run = start_run; cursor_pos = start_char; + if (selection_started) { + sel_start_para = cursor_para; sel_start_run = cursor_run; sel_start_pos = cursor_pos; sel_end_para = -1; selection_started = 0; + } else if (is_dragging) { + update_selection(cursor_para, cursor_run, cursor_pos); + } + } + return; + } + + cur_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++; } + } + } + } + + cursor_para = para_count - 1; + cursor_run = paragraphs[cursor_para].run_count - 1; + if (cursor_run < 0) cursor_run = 0; + cursor_pos = paragraphs[cursor_para].runs[cursor_run].len; + if (selection_started) { + sel_start_para = cursor_para; sel_start_run = cursor_run; sel_start_pos = cursor_pos; sel_end_para = -1; selection_started = 0; + } else if (is_dragging) { + update_selection(cursor_para, cursor_run, cursor_pos); + } + } +} + +static _Bool is_in_selection(int p, int r, int c) { + if (sel_start_para == -1 || sel_end_para == -1) return 0; + + int s_p = sel_start_para, s_r = sel_start_run, s_c = sel_start_pos; + int e_p = sel_end_para, e_r = sel_end_run, e_c = sel_end_pos; + + if (e_p < s_p || (e_p == s_p && e_r < s_r) || (e_p == s_p && e_r == s_r && e_c < s_c)) { + s_p = sel_end_para; s_r = sel_end_run; s_c = sel_end_pos; + e_p = sel_start_para; e_r = sel_start_run; e_c = sel_start_pos; + } + + if (p < s_p || p > e_p) return 0; + if (p == s_p && p == e_p) { + if (r < s_r || r > e_r) return 0; + if (r == s_r && r == e_r) return c >= s_c && c < e_c; + if (r == s_r) return c >= s_c; + if (r == e_r) return c < e_c; + return 1; + } + + if (p == s_p) { + if (r < s_r) return 0; + if (r == s_r) return c >= s_c; + return 1; + } + if (p == e_p) { + if (r > e_r) return 0; + if (r == e_r) return c < e_c; + return 1; + } + return 1; +} + +int main(int argc, char **argv) { + (void)argc; + (void)argv; + ui_window_t win = ui_window_create("BoredWord", 100, 100, win_w, win_h); + if (!win) return 1; + + load_fonts(); + set_active_font(win, 0); + init_doc(); + + if (argc > 1) { + load_file(argv[1]); + } + + gui_event_t ev; + _Bool needs_repaint = 1; + + while(1) { + while (ui_get_event(win, &ev)) { + if (ev.type == GUI_EVENT_PAINT) { + needs_repaint = 1; + } else if (ev.type == GUI_EVENT_MOUSE_DOWN) { + if (ev.arg1 >= 0 && ev.arg1 < win_w && ev.arg2 >= 0 && ev.arg2 < win_h) { + if (ev.arg2 < 40 || active_dialog == 1 || active_dropdown != 0) { + handle_click(win, ev.arg1, ev.arg2); + } else { + is_dragging = 1; + selection_started = 1; + sel_start_para = -1; sel_end_para = -1; + handle_click(win, ev.arg1, ev.arg2); + selection_started = 0; + + if (cursor_para != -1 && cursor_run != -1) { + TextRun *r = ¶graphs[cursor_para].runs[cursor_run]; + is_bold = r->bold; + is_italic = r->italic; + is_underline = r->underline; + current_font_idx = r->font_idx; + current_font_size = r->font_size; + current_text_color = r->color; + align_mode = paragraphs[cursor_para].align; + } + } + } + needs_repaint = 1; + } else if (ev.type == GUI_EVENT_MOUSE_UP) { + is_dragging = 0; + needs_repaint = 1; + } else if (ev.type == GUI_EVENT_MOUSE_MOVE) { + if (is_dragging && ev.arg2 >= 40 && active_dialog == 0 && active_dropdown == 0) { + handle_click(win, ev.arg1, ev.arg2); + needs_repaint = 1; + } + } else if (ev.type == GUI_EVENT_CLICK) { + needs_repaint = 1; + } else if (ev.type == GUI_EVENT_KEY) { + if (active_dialog == 1) { + char c = (char)ev.arg1; + if (c == '\b' && dialog_input_len > 0) { + dialog_input[--dialog_input_len] = 0; + } else if (c >= 32 && c < 127 && dialog_input_len < 127) { + dialog_input[dialog_input_len++] = c; + dialog_input[dialog_input_len] = 0; + } + } else { + insert_char((char)ev.arg1); + } + needs_repaint = 1; + } else if (ev.type == GUI_EVENT_CLOSE) { + sys_exit(0); + } + } + + if (needs_repaint) { + draw_toolbar(win); + draw_document(win); + draw_dropdowns(win); + draw_dialogs(win); + ui_mark_dirty(win, 0, 0, win_w, win_h); + needs_repaint = 0; + } else { + sys_yield(); + } + } + return 0; +} diff --git a/src/userland/libc/libui.c b/src/userland/libc/libui.c index cd1dd3b..7366992 100644 --- a/src/userland/libc/libui.c +++ b/src/userland/libc/libui.c @@ -6,6 +6,7 @@ extern uint64_t syscall3(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3); extern uint64_t syscall4(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4); extern uint64_t syscall5(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4, uint64_t arg5); +extern uint64_t syscall6(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4, uint64_t arg5, uint64_t arg6); // sys_gui uses syscall #3 #define SYS_GUI 3 @@ -71,6 +72,16 @@ void ui_draw_string_scaled(ui_window_t win, int x, int y, const char *str, uint3 syscall5(SYS_GUI, GUI_CMD_DRAW_STRING_SCALED, (uint64_t)win, coords, (uint64_t)str, packed_arg5); } +void ui_draw_string_scaled_sloped(ui_window_t win, int x, int y, const char *str, uint32_t color, float scale, float slope) { + uint64_t coords = ((uint64_t)x & 0xFFFFFFFF) | ((uint64_t)y << 32); + // Pack color into lower 32, scale (as uint32_t representation) into upper 32 + uint32_t scale_bits = *(uint32_t*)&scale; + uint32_t slope_bits = *(uint32_t*)&slope; + uint64_t packed_arg5 = ((uint64_t)scale_bits << 32) | (color & 0xFFFFFFFF); + + syscall6(SYS_GUI, GUI_CMD_DRAW_STRING_SCALED_SLOPED, (uint64_t)win, coords, (uint64_t)str, packed_arg5, (uint64_t)slope_bits); +} + uint32_t ui_get_string_width_scaled(const char *str, float scale) { uint32_t scale_bits = *(uint32_t*)&scale; return (uint32_t)syscall4(SYS_GUI, GUI_CMD_GET_STRING_WIDTH_SCALED, (uint64_t)str, (uint64_t)scale_bits, 0); diff --git a/src/userland/libc/libui.h b/src/userland/libc/libui.h index dea0d2d..e96c237 100644 --- a/src/userland/libc/libui.h +++ b/src/userland/libc/libui.h @@ -21,6 +21,7 @@ #define GUI_CMD_GET_FONT_HEIGHT_SCALED 13 #define GUI_CMD_WINDOW_SET_TITLE 15 #define GUI_CMD_SET_FONT 16 +#define GUI_CMD_DRAW_STRING_SCALED_SLOPED 18 // Event Types #define GUI_EVENT_NONE 0 @@ -62,6 +63,7 @@ void ui_get_screen_size(uint64_t *out_w, uint64_t *out_h); void ui_draw_string_bitmap(ui_window_t win, int x, int y, const char *str, uint32_t color); void ui_draw_string_scaled(ui_window_t win, int x, int y, const char *str, uint32_t color, float scale); +void ui_draw_string_scaled_sloped(ui_window_t win, int x, int y, const char *str, uint32_t color, float scale, float slope); uint32_t ui_get_string_width_scaled(const char *str, float scale); uint32_t ui_get_font_height_scaled(float scale); void ui_window_set_title(ui_window_t win, const char *title); diff --git a/src/userland/libc/syscall.c b/src/userland/libc/syscall.c index b613aab..9d51460 100644 --- a/src/userland/libc/syscall.c +++ b/src/userland/libc/syscall.c @@ -59,6 +59,18 @@ uint64_t syscall5(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, return ret; } +uint64_t syscall6(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4, uint64_t arg5, uint64_t arg6) { + uint64_t ret; + register uint64_t r10 asm("r10") = arg4; + register uint64_t r8 asm("r8") = arg5; + register uint64_t r9 asm("r9") = arg6; + asm volatile("syscall" + : "=a"(ret) + : "a"(sys_num), "D"(arg1), "S"(arg2), "d"(arg3), "r"(r10), "r"(r8), "r"(r9) + : "rcx", "r11", "memory"); + return ret; +} + void sys_exit(int status) { syscall1(SYS_EXIT, (uint64_t)status); diff --git a/src/wm/explorer.c b/src/wm/explorer.c index f297f97..88b423e 100644 --- a/src/wm/explorer.c +++ b/src/wm/explorer.c @@ -750,6 +750,8 @@ static void explorer_open_target(const char *path) { } else { if (explorer_str_ends_with(path, ".elf")) { process_create_elf(path, NULL); + } else if (explorer_str_ends_with(path, ".pdf")) { + process_create_elf("A:/bin/word.elf", path); } else if (explorer_is_markdown_file(path)) { process_create_elf("A:/bin/markdown.elf", path); } else if (explorer_str_ends_with(path, ".pnt")) { @@ -842,6 +844,8 @@ static void explorer_draw_file_icon(int x, int y, bool is_dir, uint32_t color, c if (full_path[explorer_strlen(full_path) - 1] != '/') explorer_strcat(full_path, "/"); explorer_strcat(full_path, filename); draw_image_icon(x + 5, y + 5, full_path); + } else if (explorer_str_ends_with(filename, ".pdf")) { + draw_pdf_icon(x + 5, y + 5, ""); } else if (explorer_str_ends_with(filename, ".elf")) { draw_elf_icon(x + 5, y + 5, ""); } else { diff --git a/src/wm/font_manager.c b/src/wm/font_manager.c index a4ac6b3..e7fab66 100644 --- a/src/wm/font_manager.c +++ b/src/wm/font_manager.c @@ -17,12 +17,10 @@ float kfabsf(float x) { } float kpowf(float b, float e) { - // Very simplified pow for stb_truetype's needs if (e == 0) return 1.0f; if (e == 1) return b; if (e == 0.5f) return ksqrtf(b); - // Fallback/log-based would be complex, let's see if this suffices float res = 1.0f; for (int i = 0; i < (int)e; i++) res *= b; return res; @@ -33,13 +31,11 @@ float kfmodf(float x, float y) { } float kcosf(float x) { - // Taylor series for cos(x) around 0 float x2 = x * x; return 1.0f - (x2 / 2.0f) + (x2 * x2 / 24.0f) - (x2 * x2 * x2 / 720.0f); } float kacosf(float x) { - // Very rough approximation for acos(x) if (x >= 1.0f) return 0; if (x <= -1.0f) return 3.14159f; return 1.57079f - x - (x*x*x)/6.0f; @@ -59,6 +55,15 @@ static inline uint32_t alpha_blend(uint32_t bg, uint32_t fg, uint8_t alpha) { static ttf_font_t *default_font = NULL; +#define MAX_LOADED_FONTS 8 +typedef struct { + char path[128]; + ttf_font_t *font; +} loaded_font_t; + +static loaded_font_t loaded_fonts[MAX_LOADED_FONTS]; +static int loaded_font_count = 0; + #define FONT_CACHE_SIZE 2048 typedef struct { char c; @@ -67,15 +72,22 @@ typedef struct { unsigned char *bitmap; } font_cache_entry_t; -// Cache is disabled for now due to race conditions and collisions -// static font_cache_entry_t g_font_cache[FONT_CACHE_SIZE]; bool font_manager_init(void) { - // We'll load a default font later if available return true; } ttf_font_t* font_manager_load(const char *path, float size) { + (void)size; + + for(int i=0; ivalid) { serial_write("[FONT] Failed to open font file: "); @@ -126,6 +138,13 @@ ttf_font_t* font_manager_load(const char *path, float size) { stbtt_GetFontVMetrics(info, &font->ascent, &font->descent, &font->line_gap); if (!default_font) default_font = font; + + if (loaded_font_count < MAX_LOADED_FONTS) { + int i=0; while(path[i] && i<127) { loaded_fonts[loaded_font_count].path[i] = path[i]; i++; } + loaded_fonts[loaded_font_count].path[i] = 0; + loaded_fonts[loaded_font_count].font = font; + loaded_font_count++; + } return font; } @@ -145,16 +164,16 @@ void font_manager_render_char_scaled(ttf_font_t *font, int x, int y, char c, uin unsigned char *bitmap = NULL; int w, h, xoff, yoff; - float real_scale = stbtt_ScaleForPixelHeight(info, scale); // Convert pixel size back to stbtt scale + float real_scale = stbtt_ScaleForPixelHeight(info, scale); int codepoint = (unsigned char)c; - if (codepoint == 128) codepoint = 0x2014; // — (—) - if (codepoint == 129) codepoint = 0x2013; // – (–) - if (codepoint == 130) codepoint = 0x2022; // • (•) - if (codepoint == 131) codepoint = 0x2026; // … (…) - if (codepoint == 132) codepoint = 0x2122; // ™ (™) - if (codepoint == 133) codepoint = 0x20AC; // € (€) - if (codepoint == 134) codepoint = 0x00B7; // · (·) + if (codepoint == 128) codepoint = 0x2014; + if (codepoint == 129) codepoint = 0x2013; + if (codepoint == 130) codepoint = 0x2022; + if (codepoint == 131) codepoint = 0x2026; + if (codepoint == 132) codepoint = 0x2122; + if (codepoint == 133) codepoint = 0x20AC; + if (codepoint == 134) codepoint = 0x00B7; bitmap = stbtt_GetCodepointBitmap(info, 0, real_scale, codepoint, &w, &h, &xoff, &yoff); @@ -174,6 +193,46 @@ void font_manager_render_char_scaled(ttf_font_t *font, int x, int y, char c, uin } } +void font_manager_render_char_sloped(ttf_font_t *font, int x, int y, char c, uint32_t color, float scale, float slope, void (*put_pixel_fn)(int, int, uint32_t)) { + if (!font) font = default_font; + if (!font) return; + + stbtt_fontinfo *info = (stbtt_fontinfo *)font->info; + + unsigned char *bitmap = NULL; + int w, h, xoff, yoff; + + float real_scale = stbtt_ScaleForPixelHeight(info, scale); + + int codepoint = (unsigned char)c; + if (codepoint == 128) codepoint = 0x2014; + if (codepoint == 129) codepoint = 0x2013; + if (codepoint == 130) codepoint = 0x2022; + if (codepoint == 131) codepoint = 0x2026; + if (codepoint == 132) codepoint = 0x2122; + if (codepoint == 133) codepoint = 0x20AC; + if (codepoint == 134) codepoint = 0x00B7; + + bitmap = stbtt_GetCodepointBitmap(info, 0, real_scale, codepoint, &w, &h, &xoff, &yoff); + + if (bitmap) { + for (int row = 0; row < h; row++) { + int slant_offset = (int)((h - row) * slope); + + for (int col = 0; col < w; col++) { + unsigned char alpha = bitmap[row * w + col]; + if (alpha > 0) { + int px = x + col + xoff + slant_offset; + int py = y + row + yoff; + uint32_t bg = graphics_get_pixel(px, py); + put_pixel_fn(px, py, alpha_blend(bg, color, alpha)); + } + } + } + stbtt_FreeBitmap(bitmap, NULL); + } +} + int font_manager_get_string_width(ttf_font_t *font, const char *s) { if (!font) font = default_font; if (!font) return 0; @@ -211,15 +270,14 @@ int font_manager_get_string_width_scaled(ttf_font_t *font, const char *s, float while (*s) { int advance, lsb; int codepoint = (unsigned char)*s; - if (codepoint == 128) codepoint = 0x2014; // — (—) - if (codepoint == 129) codepoint = 0x2013; // – (–) - if (codepoint == 130) codepoint = 0x2022; // • (•) - if (codepoint == 131) codepoint = 0x2026; // … (…) - if (codepoint == 132) codepoint = 0x2122; // ™ (™) - if (codepoint == 133) codepoint = 0x20AC; // € (€) - if (codepoint == 134) codepoint = 0x00B7; // · (·) + if (codepoint == 128) codepoint = 0x2014; + if (codepoint == 129) codepoint = 0x2013; + if (codepoint == 130) codepoint = 0x2022; + if (codepoint == 131) codepoint = 0x2026; + if (codepoint == 132) codepoint = 0x2122; + if (codepoint == 133) codepoint = 0x20AC; + if (codepoint == 134) codepoint = 0x00B7; stbtt_GetCodepointHMetrics(info, codepoint, &advance, &lsb); - // Round per-character to match draw_string's accumulation width += (int)(advance * real_scale + 0.5f); s++; } diff --git a/src/wm/font_manager.h b/src/wm/font_manager.h index b96a74d..6493961 100644 --- a/src/wm/font_manager.h +++ b/src/wm/font_manager.h @@ -5,7 +5,6 @@ #include #include -// stb_truetype math stubs extern float ksqrtf(float x); extern float kpowf(float b, float e); extern float kfmodf(float x, float y); @@ -24,21 +23,18 @@ extern float kfabsf(float x); #define STBTT_assert(x) ((void)0) -// Memory management #define STBTT_malloc(x,u) kmalloc(x) #define STBTT_free(x,u) kfree(x) -// String functions #define STBTT_memcpy mem_memcpy #define STBTT_memset mem_memset -// Data types typedef uint64_t STBTT_ptrsize; typedef struct { void *data; size_t size; - void *info; // stbtt_fontinfo + void *info; float scale; float pixel_height; int ascent; @@ -50,6 +46,7 @@ bool font_manager_init(void); ttf_font_t* font_manager_load(const char *path, float size); void font_manager_render_char(ttf_font_t *font, int x, int y, char c, uint32_t color, void (*put_pixel_fn)(int, int, uint32_t)); void font_manager_render_char_scaled(ttf_font_t *font, int x, int y, char c, uint32_t color, float scale, void (*put_pixel_fn)(int, int, uint32_t)); +void font_manager_render_char_sloped(ttf_font_t *font, int x, int y, char c, uint32_t color, float scale, float slope, void (*put_pixel_fn)(int, int, uint32_t)); int font_manager_get_string_width(ttf_font_t *font, const char *s); int font_manager_get_string_width_scaled(ttf_font_t *font, const char *s, float scale); diff --git a/src/wm/graphics.c b/src/wm/graphics.c index 83446f7..6034138 100644 --- a/src/wm/graphics.c +++ b/src/wm/graphics.c @@ -6,6 +6,7 @@ #include "font.h" #include "io.h" #include "font_manager.h" +#include "../mem/memory_manager.h" static struct limine_framebuffer *g_fb = NULL; static uint32_t g_bg_color = 0xFF696969; @@ -329,6 +330,128 @@ void draw_rounded_rect_filled(int x, int y, int w, int h, int radius, uint32_t c } } +static uint32_t blend_color_alpha(uint32_t bottom, uint32_t top, int alpha) { + if (alpha <= 0) return bottom; + if (alpha >= 255) return top; + + int rb = (bottom >> 16) & 0xFF; + int gb = (bottom >> 8) & 0xFF; + int bb = bottom & 0xFF; + + int rt = (top >> 16) & 0xFF; + int gt = (top >> 8) & 0xFF; + int bt = top & 0xFF; + + int rr = rb + (((rt - rb) * alpha) >> 8); + int gg = gb + (((gt - gb) * alpha) >> 8); + int bb_new = bb + (((bt - bb) * alpha) >> 8); + + return (rr << 16) | (gg << 8) | bb_new; +} + +void draw_rounded_rect_blurred(int x, int y, int w, int h, int radius, uint32_t tint_color, int blur_radius, int alpha) { + if (!g_fb) return; + int sw = get_screen_width(); + int sh = get_screen_height(); + + if (x < 0) { w += x; x = 0; } + if (y < 0) { h += y; y = 0; } + if (x + w > sw) w = sw - x; + if (y + h > sh) h = sh - y; + if (w <= 0 || h <= 0) return; + + if (radius > w / 2) radius = w / 2; + if (radius > h / 2) radius = h / 2; + if (radius < 1) radius = 1; + + uint32_t *tmp_buf = (uint32_t *)kmalloc(w * h * sizeof(uint32_t)); + if (!tmp_buf) { + draw_rounded_rect_filled(x, y, w, h, radius, tint_color); + return; + } + + for (int r = 0; r < h; r++) { + int g_y = y + r; + for (int c = 0; c < w; c++) { + int g_x = x + c; + + int r_sum = 0, g_sum = 0, b_sum = 0, count = 0; + int start_kx = g_x - blur_radius; + int end_kx = g_x + blur_radius; + if (start_kx < 0) start_kx = 0; + if (end_kx >= sw) end_kx = sw - 1; + + for (int kx = start_kx; kx <= end_kx; kx++) { + uint32_t pixel = g_back_buffer[g_y * sw + kx]; + r_sum += (pixel >> 16) & 0xFF; + g_sum += (pixel >> 8) & 0xFF; + b_sum += pixel & 0xFF; + count++; + } + if(count == 0) count = 1; + uint32_t out_pixel = ((r_sum / count) << 16) | ((g_sum / count) << 8) | (b_sum / count); + tmp_buf[r * w + c] = out_pixel; + } + } + + for (int c = 0; c < w; c++) { + for (int r = 0; r < h; r++) { + int g_y = y + r; + int g_x = x + c; + + if (g_clip_enabled) { + if (g_x < g_clip_x || g_x >= g_clip_x + g_clip_w || + g_y < g_clip_y || g_y >= g_clip_y + g_clip_h) { + continue; + } + } + + bool in_corner = false; + int dx = 0, dy = 0; + if (c < radius && r < radius) { + dx = radius - c - 1; dy = radius - r - 1; + in_corner = true; + } else if (c >= w - radius && r < radius) { + dx = c - (w - radius); dy = radius - r - 1; + in_corner = true; + } else if (c < radius && r >= h - radius) { + dx = radius - c - 1; dy = r - (h - radius); + in_corner = true; + } else if (c >= w - radius && r >= h - radius) { + dx = c - (w - radius); dy = r - (h - radius); + in_corner = true; + } + + if (in_corner) { + if (dx*dx + dy*dy >= radius*radius) { + continue; + } + } + + int r_sum = 0, g_sum = 0, b_sum = 0, count = 0; + int start_kr = r - blur_radius; + int end_kr = r + blur_radius; + if (start_kr < 0) start_kr = 0; + if (end_kr >= h) end_kr = h - 1; + + for (int kr = start_kr; kr <= end_kr; kr++) { + uint32_t pixel = tmp_buf[kr * w + c]; + r_sum += (pixel >> 16) & 0xFF; + g_sum += (pixel >> 8) & 0xFF; + b_sum += pixel & 0xFF; + count++; + } + if(count == 0) count = 1; + uint32_t blurred_pixel = ((r_sum / count) << 16) | ((g_sum / count) << 8) | (b_sum / count); + + uint32_t final_pixel = blend_color_alpha(blurred_pixel, tint_color, alpha); + g_back_buffer[g_y * sw + g_x] = final_pixel; + } + } + + kfree(tmp_buf); +} + void draw_char(int x, int y, char c, uint32_t color) { if (g_current_ttf) { font_manager_render_char(g_current_ttf, x, y, c, color, put_pixel); @@ -463,6 +586,37 @@ void draw_string_scaled(int x, int y, const char *s, uint32_t color, float scale } } +void draw_string_sloped(int x, int y, const char *s, uint32_t color, float slope) { + if (g_current_ttf) draw_string_scaled_sloped(x, y, s, color, g_current_ttf->pixel_height, slope); + else draw_string_scaled(x, y, s, color, 15.0f); // Fast fallback if no ttf +} + +void draw_string_scaled_sloped(int x, int y, const char *s, uint32_t color, float scale, float slope) { + if (!s) return; + int cur_x = x; + + if (g_current_ttf) { + int baseline = y + font_manager_get_font_ascent_scaled(g_current_ttf, scale) - 2; + int line_height = font_manager_get_font_line_height_scaled(g_current_ttf, scale); + + while (*s) { + if (*s == '\n') { + cur_x = x; + baseline += line_height; + } else { + font_manager_render_char_sloped(g_current_ttf, cur_x, baseline, *s, color, scale, slope, put_pixel); + char buf[2] = {*s, 0}; + cur_x += font_manager_get_string_width_scaled(g_current_ttf, buf, scale); + } + s++; + } + return; + } + + // Fallback to normal draw_string_scaled if no TTF + draw_string_scaled(x, y, s, color, scale); +} + void draw_desktop_background(void) { if (!g_fb) return; diff --git a/src/wm/graphics.h b/src/wm/graphics.h index 5953a11..d4b36a1 100644 --- a/src/wm/graphics.h +++ b/src/wm/graphics.h @@ -21,10 +21,13 @@ uint32_t graphics_get_pixel(int x, int y); void draw_rect(int x, int y, int w, int h, uint32_t color); 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_blurred(int x, int y, int w, int h, int radius, uint32_t tint_color, int blur_radius, int alpha); void draw_char(int x, int y, char c, uint32_t color); void draw_char_bitmap(int x, int y, char c, uint32_t color); void draw_string(int x, int y, const char *s, uint32_t color); void draw_string_scaled(int x, int y, const char *s, uint32_t color, float scale); +void draw_string_sloped(int x, int y, const char *s, uint32_t color, float slope); +void draw_string_scaled_sloped(int x, int y, const char *s, uint32_t color, float scale, float slope); void draw_desktop_background(void); void graphics_set_bg_color(uint32_t color); void graphics_set_bg_pattern(const uint32_t *pattern); // 128x128 pattern diff --git a/src/wm/wm.c b/src/wm/wm.c index f1317ac..e24faa5 100644 --- a/src/wm/wm.c +++ b/src/wm/wm.c @@ -129,6 +129,7 @@ int desktop_max_cols = 23; int mouse_speed = 10; static int mouse_accum_x = 0; static int mouse_accum_y = 0; +Window *active_mouse_capture_win = NULL; // Helper to check if string ends with suffix static bool str_ends_with(const char *str, const char *suffix) { @@ -508,6 +509,33 @@ void draw_document_icon(int x, int y, const char *label) { draw_icon_label(x, y, label); } +void draw_pdf_icon(int x, int y, const char *label) { + uint32_t icon_buf[48 * 48]; + for (int i = 0; i < 48 * 48; i++) icon_buf[i] = 0xFFFF00FF; + graphics_set_render_target(icon_buf, 48, 48); + + // Document shape + draw_rounded_rect_filled(4, 4, 40, 40, 8, 0xFFFFFFFF); + // Red banner + draw_rounded_rect_filled(8, 8, 32, 14, 4, 0xFFDF2020); + // PDF text roughly (simplified to lines for now) + draw_rect(14, 25, 20, 2, 0xFFBBBBBB); + draw_rect(14, 33, 14, 2, 0xFFBBBBBB); + + graphics_set_render_target(NULL, 0, 0); + int dx = x + 24, dy = y + 12; + for (int ty = 0; ty < 32; ty++) { + for (int tx = 0; tx < 32; tx++) { + int src_x = tx * 48 / 32; + int src_y = ty * 48 / 32; + uint32_t c1 = icon_buf[src_y * 48 + src_x]; + if (c1 != 0xFFFF00FF) put_pixel(dx + tx, dy + ty, c1); + } + } + + draw_icon_label(x, y, label); +} + void draw_elf_icon(int x, int y, const char *label) { uint32_t icon_buf[48 * 48]; for (int i = 0; i < 48 * 48; i++) icon_buf[i] = 0xFFFF00FF; @@ -912,6 +940,23 @@ static long long isqrt(long long n) { return x; } +static void draw_dock_word(int x, int y) { + // Rich blue document style + draw_rounded_rect_filled(x, y, 48, 48, 10, 0xFF4A90E2); + draw_rounded_rect_filled(x + 1, y + 1, 46, 28, 9, 0xFF5D9CE6); + draw_rounded_rect_filled(x + 1, y + 24, 46, 23, 9, 0xFF3A80D2); + + // White document page in center + draw_rounded_rect_filled(x + 8, y + 8, 32, 32, 4, 0xFFFFFFFF); + // Blue header edge + draw_rounded_rect_filled(x + 8, y + 8, 32, 8, 4, 0xFF2868B8); + + // Text lines using dark grey + draw_rect(x + 14, y + 22, 20, 2, 0xFF666666); + draw_rect(x + 14, y + 27, 20, 2, 0xFF666666); + draw_rect(x + 14, y + 32, 14, 2, 0xFF666666); +} + static void draw_dock_notepad(int x, int y) { draw_rounded_rect_filled(x, y, 48, 48, 10, 0xFFCC9A00); draw_rounded_rect_filled(x + 1, y + 1, 46, 28, 9, 0xFFFFD700); @@ -1225,7 +1270,21 @@ void wm_paint(void) { asm volatile("pushfq; pop %0; cli" : "=r"(rflags)); DirtyRect dirty = graphics_get_dirty_rect(); + if (dirty.active) { + int d_h = 60; + int d_y = sh - d_h - 6; + int d_item_sz = 48; + int d_space = 10; + int d_tw = 10 * (d_item_sz + d_space); + int d_bg_x = (sw - d_tw) / 2 - 12; + int d_bg_w = d_tw + 24; + + if (!(dirty.x >= d_bg_x + d_bg_w || dirty.x + dirty.w <= d_bg_x || + dirty.y >= d_y + d_h || dirty.y + dirty.h <= d_y)) { + graphics_mark_dirty(d_bg_x - 10, d_y - 10, d_bg_w + 20, d_h + 20); + dirty = graphics_get_dirty_rect(); + } graphics_set_clipping(dirty.x, dirty.y, dirty.w, dirty.h); } else { graphics_clear_clipping(); @@ -1274,6 +1333,7 @@ void wm_paint(void) { draw_image_icon(icon->x, icon->y, full_path); draw_icon_label(icon->x, icon->y, icon->name); } + else if (str_ends_with(icon->name, ".pdf")) draw_pdf_icon(icon->x, icon->y, icon->name); else draw_document_icon(icon->x, icon->y, icon->name); } } @@ -1324,10 +1384,12 @@ void wm_paint(void) { int dock_y = sh - dock_h - 6; int dock_item_size = 48; int dock_spacing = 10; - int total_dock_width = 10 * (dock_item_size + dock_spacing); + int total_dock_width = 11 * (dock_item_size + dock_spacing); int dock_bg_x = (sw - total_dock_width) / 2 - 12; int dock_bg_w = total_dock_width + 24; - draw_rounded_rect_filled(dock_bg_x, dock_y, dock_bg_w, dock_h, 18, COLOR_DOCK_BG); + + // Draw blurred dock background with reduced radius and tint + draw_rounded_rect_blurred(dock_bg_x, dock_y, dock_bg_w, dock_h, 18, COLOR_DOCK_BG, 5, 140); int dock_x = (sw - total_dock_width) / 2; int dock_item_y = dock_y + 6; @@ -1351,7 +1413,8 @@ void wm_paint(void) { draw_dock_taskman(dock_x, dock_item_y); dock_x += dock_item_size + dock_spacing; draw_dock_clock(dock_x, dock_item_y); - // Editor removed from dock + dock_x += dock_item_size + dock_spacing; + draw_dock_word(dock_x, dock_item_y); // Desktop Context Menu (with rounded corners) if (desktop_menu_visible) { @@ -1533,6 +1596,10 @@ void wm_remove_window(Window *win) { } window_count--; + if (active_mouse_capture_win == win) { + active_mouse_capture_win = NULL; + } + // Mark for redraw while protected force_redraw = true; } else { @@ -1905,7 +1972,7 @@ void wm_handle_right_click(int x, int y) { int dock_y = sh - dock_h - 6; int dock_item_size = 48; int dock_spacing = 10; - int total_dock_width = 10 * (dock_item_size + dock_spacing); + int total_dock_width = 11 * (dock_item_size + dock_spacing); int dock_bg_x = (sw - total_dock_width) / 2 - 12; int dock_bg_w = total_dock_width + 24; @@ -1926,6 +1993,7 @@ void wm_handle_right_click(int x, int y) { else if (item == 7) start_menu_pending_app = "Browser"; else if (item == 8) start_menu_pending_app = "Task Manager"; else if (item == 9) start_menu_pending_app = "Clock"; + else if (item == 10) start_menu_pending_app = "Word Processor"; } } else { wm_handle_click(mx, my); @@ -2048,6 +2116,10 @@ void wm_handle_right_click(int x, int y) { Window *existing = wm_find_window_by_title("Txtedit"); if (existing) wm_bring_to_front(existing); else process_create_elf("/bin/txtedit.elf", NULL); + } else if (str_starts_with(start_menu_pending_app, "Word Processor")) { + Window *existing = wm_find_window_by_title("Word Processor"); + if (existing) wm_bring_to_front(existing); + else process_create_elf("/bin/word.elf", NULL); } else if (str_starts_with(start_menu_pending_app, "Terminal")) { cmd_reset(); wm_bring_to_front(&win_cmd); } else if (str_starts_with(start_menu_pending_app, "Calculator")) { @@ -2160,6 +2232,8 @@ void wm_handle_right_click(int x, int y) { process_create_elf("/bin/paint.elf", path); } else if (str_ends_with(icon->name, ".md")) { process_create_elf("/bin/markdown.elf", path); + } else if (str_ends_with(icon->name, ".pdf")) { + process_create_elf("/bin/word.elf", path); } else if (is_image_file(icon->name)) { process_create_elf("/bin/viewer.elf", path); } else { @@ -2385,46 +2459,57 @@ void wm_handle_right_click(int x, int y) { } } if (topmost && topmost->data) { + active_mouse_capture_win = topmost; if (my >= topmost->y + 20) syscall_send_mouse_down_event(topmost, mx - topmost->x, my - topmost->y - 20); + } else { + active_mouse_capture_win = NULL; } } if (!left && prev_left) { - // Left button released - send MOUSE_UP event to topmost window - Window *topmost = NULL; - int topmost_z = -1; - for (int w = 0; w < window_count; w++) { - Window *win = all_windows[w]; - if (win->visible && rect_contains(win->x, win->y, win->w, win->h, mx, my)) { - if (win->z_index > topmost_z) { - topmost = win; - topmost_z = win->z_index; + // Left button released - send MOUSE_UP event to captured or topmost window + Window *target = active_mouse_capture_win; + if (!target) { + int topmost_z = -1; + for (int w = 0; w < window_count; w++) { + Window *win = all_windows[w]; + if (win->visible && rect_contains(win->x, win->y, win->w, win->h, mx, my)) { + if (win->z_index > topmost_z) { + target = win; + topmost_z = win->z_index; + } } } } - if (topmost && topmost->data) { - if (my >= topmost->y + 20) - syscall_send_mouse_up_event(topmost, mx - topmost->x, my - topmost->y - 20); + + if (target && target->data) { + int rel_y = my - target->y - 20; + // Provide coordinates clamped if escaping bounds slightly on UP? Usually raw is fine. + syscall_send_mouse_up_event(target, mx - target->x, rel_y); } + active_mouse_capture_win = NULL; } if (dx != 0 || dy != 0) { - // Mouse moved - send MOUSE_MOVE event to topmost window - Window *topmost = NULL; - int topmost_z = -1; - for (int w = 0; w < window_count; w++) { - Window *win = all_windows[w]; - if (win->visible && rect_contains(win->x, win->y, win->w, win->h, mx, my)) { - if (win->z_index > topmost_z) { - topmost = win; - topmost_z = win->z_index; + // Mouse moved - send MOUSE_MOVE event to captured window (if dragging) or topmost + Window *target = active_mouse_capture_win; + if (!target) { + int topmost_z = -1; + for (int w = 0; w < window_count; w++) { + Window *win = all_windows[w]; + if (win->visible && rect_contains(win->x, win->y, win->w, win->h, mx, my)) { + if (win->z_index > topmost_z) { + target = win; + topmost_z = win->z_index; + } } } } - if (topmost && topmost->data) { - if (my >= topmost->y + 20) - syscall_send_mouse_move_event(topmost, mx - topmost->x, my - topmost->y - 20, buttons); + + if (target && target->data) { + int rel_y = my - target->y - 20; + syscall_send_mouse_move_event(target, mx - target->x, rel_y, buttons); } } diff --git a/src/wm/wm.h b/src/wm/wm.h index 1825d0b..320302d 100644 --- a/src/wm/wm.h +++ b/src/wm/wm.h @@ -99,6 +99,7 @@ void draw_traffic_light(int x, int y); void draw_icon(int x, int y, const char *label); void draw_folder_icon(int x, int y, const char *label); void draw_document_icon(int x, int y, const char *label); +void draw_pdf_icon(int x, int y, const char *label); void draw_elf_icon(int x, int y, const char *label); void draw_image_icon(int x, int y, const char *label); void draw_notepad_icon(int x, int y, const char *label);