feat: spreadsheet cell resizing
This commit is contained in:
@@ -213,6 +213,8 @@ namespace Montauk {
|
|||||||
return (int64_t)Sys_WinSetScale((int)frame->arg1);
|
return (int64_t)Sys_WinSetScale((int)frame->arg1);
|
||||||
case SYS_WINGETSCALE:
|
case SYS_WINGETSCALE:
|
||||||
return (int64_t)Sys_WinGetScale();
|
return (int64_t)Sys_WinGetScale();
|
||||||
|
case SYS_WINSETCURSOR:
|
||||||
|
return (int64_t)Sys_WinSetCursor((int)frame->arg1, (int)frame->arg2);
|
||||||
case SYS_MEMSTATS:
|
case SYS_MEMSTATS:
|
||||||
Sys_MemStats((MemStats*)frame->arg1);
|
Sys_MemStats((MemStats*)frame->arg1);
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ namespace Montauk {
|
|||||||
static constexpr uint64_t SYS_WINRESIZE = 64;
|
static constexpr uint64_t SYS_WINRESIZE = 64;
|
||||||
static constexpr uint64_t SYS_WINSETSCALE = 65;
|
static constexpr uint64_t SYS_WINSETSCALE = 65;
|
||||||
static constexpr uint64_t SYS_WINGETSCALE = 66;
|
static constexpr uint64_t SYS_WINGETSCALE = 66;
|
||||||
|
static constexpr uint64_t SYS_WINSETCURSOR = 68;
|
||||||
|
|
||||||
/* Process.hpp */
|
/* Process.hpp */
|
||||||
static constexpr uint64_t SYS_PROCLIST = 61;
|
static constexpr uint64_t SYS_PROCLIST = 61;
|
||||||
@@ -205,7 +206,8 @@ namespace Montauk {
|
|||||||
char title[64];
|
char title[64];
|
||||||
int32_t width, height;
|
int32_t width, height;
|
||||||
uint8_t dirty;
|
uint8_t dirty;
|
||||||
uint8_t _pad[3];
|
uint8_t cursor; // 0=arrow, 1=resize_h, 2=resize_v
|
||||||
|
uint8_t _pad[2];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WinCreateResult {
|
struct WinCreateResult {
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ namespace WinServer {
|
|||||||
info.width = g_slots[i].width;
|
info.width = g_slots[i].width;
|
||||||
info.height = g_slots[i].height;
|
info.height = g_slots[i].height;
|
||||||
info.dirty = g_slots[i].dirty ? 1 : 0;
|
info.dirty = g_slots[i].dirty ? 1 : 0;
|
||||||
|
info.cursor = g_slots[i].cursor;
|
||||||
g_slots[i].dirty = false; // clear dirty after read
|
g_slots[i].dirty = false; // clear dirty after read
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
@@ -246,6 +247,14 @@ namespace WinServer {
|
|||||||
return 0;
|
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) {
|
int SetScale(int scale) {
|
||||||
if (scale < 0) scale = 0;
|
if (scale < 0) scale = 0;
|
||||||
if (scale > 2) scale = 2;
|
if (scale > 2) scale = 2;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ namespace WinServer {
|
|||||||
Montauk::WinEvent events[MaxEvents];
|
Montauk::WinEvent events[MaxEvents];
|
||||||
int eventHead, eventTail;
|
int eventHead, eventTail;
|
||||||
bool dirty;
|
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,
|
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,
|
int Resize(int windowId, int callerPid, uint64_t ownerPml4, int newW, int newH,
|
||||||
uint64_t& heapNext, uint64_t& outVa);
|
uint64_t& heapNext, uint64_t& outVa);
|
||||||
void CleanupProcess(int pid);
|
void CleanupProcess(int pid);
|
||||||
|
int SetCursor(int windowId, int callerPid, int cursor);
|
||||||
int SetScale(int scale);
|
int SetScale(int scale);
|
||||||
int GetScale();
|
int GetScale();
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ namespace Montauk {
|
|||||||
return (r == 0) ? outVa : 0;
|
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) {
|
static int Sys_WinSetScale(int scale) {
|
||||||
return WinServer::SetScale(scale);
|
return WinServer::SetScale(scale);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ namespace Montauk {
|
|||||||
static constexpr uint64_t SYS_WINRESIZE = 64;
|
static constexpr uint64_t SYS_WINRESIZE = 64;
|
||||||
static constexpr uint64_t SYS_WINSETSCALE = 65;
|
static constexpr uint64_t SYS_WINSETSCALE = 65;
|
||||||
static constexpr uint64_t SYS_WINGETSCALE = 66;
|
static constexpr uint64_t SYS_WINGETSCALE = 66;
|
||||||
|
static constexpr uint64_t SYS_WINSETCURSOR = 68;
|
||||||
|
|
||||||
// Process management syscalls
|
// Process management syscalls
|
||||||
static constexpr uint64_t SYS_PROCLIST = 61;
|
static constexpr uint64_t SYS_PROCLIST = 61;
|
||||||
@@ -156,7 +157,8 @@ namespace Montauk {
|
|||||||
char title[64];
|
char title[64];
|
||||||
int32_t width, height;
|
int32_t width, height;
|
||||||
uint8_t dirty;
|
uint8_t dirty;
|
||||||
uint8_t _pad2[3];
|
uint8_t cursor; // 0=arrow, 1=resize_h, 2=resize_v
|
||||||
|
uint8_t _pad2[2];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WinCreateResult {
|
struct WinCreateResult {
|
||||||
|
|||||||
@@ -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
|
// Draw single character to buffer, returning advance width
|
||||||
int draw_char_to_buffer(uint32_t* pixels, int buf_w, int buf_h,
|
int draw_char_to_buffer(uint32_t* pixels, int buf_w, int buf_h,
|
||||||
int x, int baseline, int codepoint,
|
int x, int baseline, int codepoint,
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ struct Window {
|
|||||||
|
|
||||||
bool external; // true = shared-memory window from external process
|
bool external; // true = shared-memory window from external process
|
||||||
int ext_win_id; // window server ID (valid when external == true)
|
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 {
|
Rect titlebar_rect() const {
|
||||||
return {frame.x, frame.y, frame.w, TITLEBAR_HEIGHT};
|
return {frame.x, frame.y, frame.w, TITLEBAR_HEIGHT};
|
||||||
|
|||||||
@@ -329,5 +329,8 @@ namespace montauk {
|
|||||||
inline int win_getscale() {
|
inline int win_getscale() {
|
||||||
return (int)syscall0(Montauk::SYS_WINGETSCALE);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 last
|
||||||
draw_cursor(fb, ds->mouse.x, ds->mouse.y, cur_style);
|
draw_cursor(fb, ds->mouse.x, ds->mouse.y, cur_style);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -434,6 +434,32 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
|||||||
ds->ctx_menu_open = false;
|
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
|
// Handle scroll events on focused window
|
||||||
if (ev.scroll != 0 && ds->focused_window >= 0) {
|
if (ev.scroll != 0 && ds->focused_window >= 0) {
|
||||||
Window* win = &ds->windows[ds->focused_window];
|
Window* win = &ds->windows[ds->focused_window];
|
||||||
|
|||||||
@@ -114,10 +114,11 @@ void desktop_poll_external_windows(DesktopState* ds) {
|
|||||||
for (int i = 0; i < ds->window_count; i++) {
|
for (int i = 0; i < ds->window_count; i++) {
|
||||||
if (ds->windows[i].external && ds->windows[i].ext_win_id == extId) {
|
if (ds->windows[i].external && ds->windows[i].ext_win_id == extId) {
|
||||||
found = true;
|
found = true;
|
||||||
// Update dirty flag
|
// Update dirty flag and cursor
|
||||||
if (extWins[e].dirty) {
|
if (extWins[e].dirty) {
|
||||||
ds->windows[i].dirty = true;
|
ds->windows[i].dirty = true;
|
||||||
}
|
}
|
||||||
|
ds->windows[i].ext_cursor = extWins[e].cursor;
|
||||||
// Re-map if external app resized its buffer
|
// Re-map if external app resized its buffer
|
||||||
if (extWins[e].width != ds->windows[i].content_w ||
|
if (extWins[e].width != ds->windows[i].content_w ||
|
||||||
extWins[e].height != ds->windows[i].content_h) {
|
extWins[e].height != ds->windows[i].content_h) {
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ TrueTypeFont* g_font_bold = nullptr;
|
|||||||
|
|
||||||
bool g_fmt_dropdown_open = false;
|
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];
|
UndoEntry* g_undo[UNDO_MAX + 1];
|
||||||
int g_undo_count = 0;
|
int g_undo_count = 0;
|
||||||
int g_undo_pos = 0;
|
int g_undo_pos = 0;
|
||||||
@@ -499,6 +504,58 @@ extern "C" void _start() {
|
|||||||
uint8_t btns = ev.mouse.buttons;
|
uint8_t btns = ev.mouse.buttons;
|
||||||
uint8_t prev = ev.mouse.prev_buttons;
|
uint8_t prev = ev.mouse.prev_buttons;
|
||||||
bool clicked = (btns & 1) && !(prev & 1);
|
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 (clicked) {
|
||||||
if (handle_fmt_dropdown_click(mx, my)) {
|
if (handle_fmt_dropdown_click(mx, my)) {
|
||||||
@@ -526,6 +583,7 @@ extern "C" void _start() {
|
|||||||
redraw = true;
|
redraw = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
done_mouse:
|
||||||
|
|
||||||
if (redraw) {
|
if (redraw) {
|
||||||
render(pixels);
|
render(pixels);
|
||||||
|
|||||||
@@ -249,8 +249,17 @@ void render(uint32_t* pixels) {
|
|||||||
else
|
else
|
||||||
text_x = x + 6;
|
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
|
// Cell right border
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ static constexpr int TB_BTN_RAD = 3;
|
|||||||
static constexpr int MAX_COLS = 26; // A-Z
|
static constexpr int MAX_COLS = 26; // A-Z
|
||||||
static constexpr int MAX_ROWS = 100;
|
static constexpr int MAX_ROWS = 100;
|
||||||
static constexpr int DEF_COL_W = 100;
|
static constexpr int DEF_COL_W = 100;
|
||||||
|
static constexpr int MIN_COL_W = 30;
|
||||||
static constexpr int ROW_H = 26;
|
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 CELL_TEXT_MAX = 128;
|
||||||
static constexpr int FONT_SIZE = 18;
|
static constexpr int FONT_SIZE = 18;
|
||||||
@@ -163,6 +165,11 @@ extern TrueTypeFont* g_font_bold;
|
|||||||
|
|
||||||
extern bool g_fmt_dropdown_open;
|
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 UndoEntry* g_undo[UNDO_MAX + 1];
|
||||||
extern int g_undo_count;
|
extern int g_undo_count;
|
||||||
extern int g_undo_pos;
|
extern int g_undo_pos;
|
||||||
|
|||||||
Reference in New Issue
Block a user