diff --git a/kernel/src/Api/Syscall.cpp b/kernel/src/Api/Syscall.cpp index 6d7b507..c459016 100644 --- a/kernel/src/Api/Syscall.cpp +++ b/kernel/src/Api/Syscall.cpp @@ -213,6 +213,8 @@ namespace Montauk { return (int64_t)Sys_WinSetScale((int)frame->arg1); case SYS_WINGETSCALE: return (int64_t)Sys_WinGetScale(); + case SYS_WINSETCURSOR: + return (int64_t)Sys_WinSetCursor((int)frame->arg1, (int)frame->arg2); case SYS_MEMSTATS: Sys_MemStats((MemStats*)frame->arg1); return 0; diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index f6fb508..87f5ad9 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -124,6 +124,7 @@ namespace Montauk { static constexpr uint64_t SYS_WINRESIZE = 64; static constexpr uint64_t SYS_WINSETSCALE = 65; static constexpr uint64_t SYS_WINGETSCALE = 66; + static constexpr uint64_t SYS_WINSETCURSOR = 68; /* Process.hpp */ static constexpr uint64_t SYS_PROCLIST = 61; @@ -205,7 +206,8 @@ namespace Montauk { char title[64]; int32_t width, height; uint8_t dirty; - uint8_t _pad[3]; + uint8_t cursor; // 0=arrow, 1=resize_h, 2=resize_v + uint8_t _pad[2]; }; struct WinCreateResult { diff --git a/kernel/src/Api/WinServer.cpp b/kernel/src/Api/WinServer.cpp index 60d1399..4179958 100644 --- a/kernel/src/Api/WinServer.cpp +++ b/kernel/src/Api/WinServer.cpp @@ -153,6 +153,7 @@ namespace WinServer { info.width = g_slots[i].width; info.height = g_slots[i].height; info.dirty = g_slots[i].dirty ? 1 : 0; + info.cursor = g_slots[i].cursor; g_slots[i].dirty = false; // clear dirty after read count++; } @@ -246,6 +247,14 @@ namespace WinServer { return 0; } + int SetCursor(int windowId, int callerPid, int cursor) { + if (windowId < 0 || windowId >= MaxWindows) return -1; + WindowSlot& slot = g_slots[windowId]; + if (!slot.used || slot.ownerPid != callerPid) return -1; + slot.cursor = (uint8_t)cursor; + return 0; + } + int SetScale(int scale) { if (scale < 0) scale = 0; if (scale > 2) scale = 2; diff --git a/kernel/src/Api/WinServer.hpp b/kernel/src/Api/WinServer.hpp index 0758db5..3563b6c 100644 --- a/kernel/src/Api/WinServer.hpp +++ b/kernel/src/Api/WinServer.hpp @@ -27,6 +27,7 @@ namespace WinServer { Montauk::WinEvent events[MaxEvents]; int eventHead, eventTail; bool dirty; + uint8_t cursor; // cursor style requested by app (0=arrow, 1=resize_h, 2=resize_v) }; int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h, @@ -40,6 +41,7 @@ namespace WinServer { int Resize(int windowId, int callerPid, uint64_t ownerPml4, int newW, int newH, uint64_t& heapNext, uint64_t& outVa); void CleanupProcess(int pid); + int SetCursor(int windowId, int callerPid, int cursor); int SetScale(int scale); int GetScale(); diff --git a/kernel/src/Api/Window.hpp b/kernel/src/Api/Window.hpp index 0d03afe..d5c294d 100644 --- a/kernel/src/Api/Window.hpp +++ b/kernel/src/Api/Window.hpp @@ -65,6 +65,10 @@ namespace Montauk { return (r == 0) ? outVa : 0; } + static int Sys_WinSetCursor(int windowId, int cursor) { + return WinServer::SetCursor(windowId, Sched::GetCurrentPid(), cursor); + } + static int Sys_WinSetScale(int scale) { return WinServer::SetScale(scale); } diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index 110ad79..6675088 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -77,6 +77,7 @@ namespace Montauk { static constexpr uint64_t SYS_WINRESIZE = 64; static constexpr uint64_t SYS_WINSETSCALE = 65; static constexpr uint64_t SYS_WINGETSCALE = 66; + static constexpr uint64_t SYS_WINSETCURSOR = 68; // Process management syscalls static constexpr uint64_t SYS_PROCLIST = 61; @@ -156,7 +157,8 @@ namespace Montauk { char title[64]; int32_t width, height; uint8_t dirty; - uint8_t _pad2[3]; + uint8_t cursor; // 0=arrow, 1=resize_h, 2=resize_v + uint8_t _pad2[2]; }; struct WinCreateResult { diff --git a/programs/include/gui/truetype.hpp b/programs/include/gui/truetype.hpp index f438438..26f3e9d 100644 --- a/programs/include/gui/truetype.hpp +++ b/programs/include/gui/truetype.hpp @@ -277,6 +277,57 @@ struct TrueTypeFont { } } + // Draw text to buffer with clip rectangle (pixels outside clip_x..clip_x+clip_w are not drawn) + void draw_to_buffer_clipped(uint32_t* pixels, int buf_w, int buf_h, + int x, int y, const char* text, + Color color, int pixel_size, + int clip_x, int clip_y, int clip_w, int clip_h) { + if (!valid) return; + GlyphCache* gc = get_cache(pixel_size); + int cx = x; + int baseline = y + gc->ascent; + int clip_x2 = clip_x + clip_w; + int clip_y2 = clip_y + clip_h; + + for (int i = 0; text[i]; i++) { + CachedGlyph* g = get_glyph(gc, (unsigned char)text[i]); + if (!g) continue; + + if (g->bitmap) { + int gx = cx + g->xoff; + int gy = baseline + g->yoff; + for (int row = 0; row < g->height; row++) { + int dy = gy + row; + if (dy < clip_y || dy >= clip_y2 || dy < 0 || dy >= buf_h) continue; + for (int col = 0; col < g->width; col++) { + int dx = gx + col; + if (dx < clip_x || dx >= clip_x2 || dx < 0 || dx >= buf_w) continue; + uint8_t alpha = g->bitmap[row * g->width + col]; + if (alpha == 0) continue; + + if (alpha == 255) { + pixels[dy * buf_w + dx] = + 0xFF000000 | ((uint32_t)color.r << 16) | + ((uint32_t)color.g << 8) | color.b; + } else { + uint32_t dst = pixels[dy * buf_w + dx]; + uint8_t dr = (dst >> 16) & 0xFF; + uint8_t dg = (dst >> 8) & 0xFF; + uint8_t db = dst & 0xFF; + uint32_t a = alpha, inv_a = 255 - alpha; + uint32_t rr = (a * color.r + inv_a * dr + 128) / 255; + uint32_t gg = (a * color.g + inv_a * dg + 128) / 255; + uint32_t bb = (a * color.b + inv_a * db + 128) / 255; + pixels[dy * buf_w + dx] = + 0xFF000000 | (rr << 16) | (gg << 8) | bb; + } + } + } + } + cx += g->advance; + } + } + // Draw single character to buffer, returning advance width int draw_char_to_buffer(uint32_t* pixels, int buf_w, int buf_h, int x, int baseline, int codepoint, diff --git a/programs/include/gui/window.hpp b/programs/include/gui/window.hpp index 5047f73..7c67c1d 100644 --- a/programs/include/gui/window.hpp +++ b/programs/include/gui/window.hpp @@ -66,6 +66,7 @@ struct Window { bool external; // true = shared-memory window from external process int ext_win_id; // window server ID (valid when external == true) + uint8_t ext_cursor; // cursor style requested by external app (0=arrow, 1=resize_h, 2=resize_v) Rect titlebar_rect() const { return {frame.x, frame.y, frame.w, TITLEBAR_HEIGHT}; diff --git a/programs/include/montauk/syscall.h b/programs/include/montauk/syscall.h index 8c9ddae..4a823e7 100644 --- a/programs/include/montauk/syscall.h +++ b/programs/include/montauk/syscall.h @@ -329,5 +329,8 @@ namespace montauk { inline int win_getscale() { return (int)syscall0(Montauk::SYS_WINGETSCALE); } + inline int win_setcursor(int id, int cursor) { + return (int)syscall2(Montauk::SYS_WINSETCURSOR, (uint64_t)id, (uint64_t)cursor); + } } diff --git a/programs/src/desktop/compose.cpp b/programs/src/desktop/compose.cpp index 2d6595d..3f9a0db 100644 --- a/programs/src/desktop/compose.cpp +++ b/programs/src/desktop/compose.cpp @@ -149,6 +149,18 @@ void gui::desktop_compose(DesktopState* ds) { } } + // Check if focused external window requests a cursor style + if (cur_style == CURSOR_ARROW && ds->focused_window >= 0) { + Window* fwin = &ds->windows[ds->focused_window]; + if (fwin->external && fwin->ext_cursor > 0) { + Rect cr = fwin->content_rect(); + if (cr.contains(ds->mouse.x, ds->mouse.y)) { + if (fwin->ext_cursor == 1) cur_style = CURSOR_RESIZE_H; + else if (fwin->ext_cursor == 2) cur_style = CURSOR_RESIZE_V; + } + } + } + // Draw cursor last draw_cursor(fb, ds->mouse.x, ds->mouse.y, cur_style); } diff --git a/programs/src/desktop/input.cpp b/programs/src/desktop/input.cpp index e5ed94e..b3a3b2b 100644 --- a/programs/src/desktop/input.cpp +++ b/programs/src/desktop/input.cpp @@ -434,6 +434,32 @@ void gui::desktop_handle_mouse(DesktopState* ds) { ds->ctx_menu_open = false; } + // Forward continuous mouse events to focused window (hover, drag, release) + if (!left_pressed && ds->focused_window >= 0) { + Window* win = &ds->windows[ds->focused_window]; + if (win->state != WIN_MINIMIZED && win->state != WIN_CLOSED) { + Rect cr = win->content_rect(); + // Forward if mouse is over content (hover/move) or button is held/released (drag continuity) + if (cr.contains(mx, my) || left_held || left_released) { + if (win->external) { + Montauk::WinEvent wev; + montauk::memset(&wev, 0, sizeof(wev)); + wev.type = 1; // mouse + wev.mouse.x = mx - cr.x; + wev.mouse.y = my - cr.y; + wev.mouse.scroll = 0; + wev.mouse.buttons = buttons; + wev.mouse.prev_buttons = prev; + montauk::win_sendevent(win->ext_win_id, &wev); + } else if (win->on_mouse) { + ev.x = mx; + ev.y = my; + win->on_mouse(win, ev); + } + } + } + } + // Handle scroll events on focused window if (ev.scroll != 0 && ds->focused_window >= 0) { Window* win = &ds->windows[ds->focused_window]; diff --git a/programs/src/desktop/main.cpp b/programs/src/desktop/main.cpp index ed1bb7b..221f867 100644 --- a/programs/src/desktop/main.cpp +++ b/programs/src/desktop/main.cpp @@ -114,10 +114,11 @@ void desktop_poll_external_windows(DesktopState* ds) { for (int i = 0; i < ds->window_count; i++) { if (ds->windows[i].external && ds->windows[i].ext_win_id == extId) { found = true; - // Update dirty flag + // Update dirty flag and cursor if (extWins[e].dirty) { ds->windows[i].dirty = true; } + ds->windows[i].ext_cursor = extWins[e].cursor; // Re-map if external app resized its buffer if (extWins[e].width != ds->windows[i].content_w || extWins[e].height != ds->windows[i].content_h) { diff --git a/programs/src/spreadsheet/main.cpp b/programs/src/spreadsheet/main.cpp index 10b06f8..9267415 100644 --- a/programs/src/spreadsheet/main.cpp +++ b/programs/src/spreadsheet/main.cpp @@ -51,6 +51,11 @@ TrueTypeFont* g_font_bold = nullptr; bool g_fmt_dropdown_open = false; +bool g_col_resizing = false; +int g_col_resize_idx = -1; +int g_col_resize_start_x = 0; +int g_col_resize_start_w = 0; + UndoEntry* g_undo[UNDO_MAX + 1]; int g_undo_count = 0; int g_undo_pos = 0; @@ -499,6 +504,58 @@ extern "C" void _start() { uint8_t btns = ev.mouse.buttons; uint8_t prev = ev.mouse.prev_buttons; bool clicked = (btns & 1) && !(prev & 1); + bool left_held = (btns & 1); + bool left_released = !(btns & 1) && (prev & 1); + + int pbh = g_pathbar_open ? PATHBAR_H : 0; + int header_y = TOOLBAR_H + pbh + FORMULA_BAR_H; + + // Update cursor style: resize_h when hovering near column border in header + { + int cursor = 0; // arrow + if (g_col_resizing) { + cursor = 1; // resize_h while dragging + } else if (my >= header_y && my < header_y + COL_HEADER_H) { + for (int c = 0; c < MAX_COLS; c++) { + int cx = col_x(c) - g_scroll_x + g_col_widths[c] - 1; + if (mx >= cx - COL_RESIZE_GRAB && mx <= cx + COL_RESIZE_GRAB) { + cursor = 1; // resize_h + break; + } + } + } + montauk::win_setcursor(win_id, cursor); + } + + // Column resize: drag in progress + if (g_col_resizing) { + if (left_held) { + int delta = mx - g_col_resize_start_x; + int new_w = g_col_resize_start_w + delta; + if (new_w < MIN_COL_W) new_w = MIN_COL_W; + g_col_widths[g_col_resize_idx] = new_w; + clamp_scroll(); + redraw = true; + } + if (left_released) { + g_col_resizing = false; + } + goto done_mouse; + } + + // Column resize: start drag (click near right edge of column header) + if (clicked && my >= header_y && my < header_y + COL_HEADER_H) { + for (int c = 0; c < MAX_COLS; c++) { + int cx = col_x(c) - g_scroll_x + g_col_widths[c] - 1; + if (mx >= cx - COL_RESIZE_GRAB && mx <= cx + COL_RESIZE_GRAB) { + g_col_resizing = true; + g_col_resize_idx = c; + g_col_resize_start_x = mx; + g_col_resize_start_w = g_col_widths[c]; + goto done_mouse; + } + } + } if (clicked) { if (handle_fmt_dropdown_click(mx, my)) { @@ -526,6 +583,7 @@ extern "C" void _start() { redraw = true; } } + done_mouse: if (redraw) { render(pixels); diff --git a/programs/src/spreadsheet/render.cpp b/programs/src/spreadsheet/render.cpp index 8c1dc4b..a6648c0 100644 --- a/programs/src/spreadsheet/render.cpp +++ b/programs/src/spreadsheet/render.cpp @@ -249,8 +249,17 @@ void render(uint32_t* pixels) { else text_x = x + 6; - if (text_x >= ROW_HEADER_W - tw && text_x < g_win_w) - cf->draw_to_buffer(pixels, g_win_w, g_win_h, text_x, y + (ROW_H - FONT_SIZE) / 2, cell->display, col, FONT_SIZE); + { + // Clip text to cell boundaries + int cell_left = x < ROW_HEADER_W ? ROW_HEADER_W : x; + int cell_right = x + g_col_widths[c] - 1; + if (cell_right > g_win_w) cell_right = g_win_w; + if (text_x < g_win_w && cell_left < cell_right) + cf->draw_to_buffer_clipped(pixels, g_win_w, g_win_h, + text_x, y + (ROW_H - FONT_SIZE) / 2, + cell->display, col, FONT_SIZE, + cell_left, y, cell_right - cell_left, ROW_H); + } } // Cell right border diff --git a/programs/src/spreadsheet/spreadsheet.h b/programs/src/spreadsheet/spreadsheet.h index c4edd5f..51a5777 100644 --- a/programs/src/spreadsheet/spreadsheet.h +++ b/programs/src/spreadsheet/spreadsheet.h @@ -38,7 +38,9 @@ static constexpr int TB_BTN_RAD = 3; static constexpr int MAX_COLS = 26; // A-Z static constexpr int MAX_ROWS = 100; static constexpr int DEF_COL_W = 100; +static constexpr int MIN_COL_W = 30; static constexpr int ROW_H = 26; +static constexpr int COL_RESIZE_GRAB = 5; // pixels from border edge for grab zone static constexpr int CELL_TEXT_MAX = 128; static constexpr int FONT_SIZE = 18; @@ -163,6 +165,11 @@ extern TrueTypeFont* g_font_bold; extern bool g_fmt_dropdown_open; +extern bool g_col_resizing; +extern int g_col_resize_idx; +extern int g_col_resize_start_x; +extern int g_col_resize_start_w; + extern UndoEntry* g_undo[UNDO_MAX + 1]; extern int g_undo_count; extern int g_undo_pos;