diff --git a/disk.img b/disk.img index b819479..26b4009 100644 Binary files a/disk.img and b/disk.img differ 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);