Files
MontaukOS/programs/src/desktop/apps/filemanager/events.cpp
T

499 lines
20 KiB
C++

/*
* events.cpp
* Mouse and keyboard handling for the embedded desktop file manager
* Copyright (c) 2026 Daniel Hammer
*/
#include "filemanager_internal.hpp"
namespace filemanager {
void filemanager_on_mouse(Window* win, MouseEvent& ev) {
FileManagerState* fm = (FileManagerState*)win->app_data;
if (!fm) return;
Rect cr = win->content_rect();
int local_x = ev.x - cr.x;
int local_y = ev.y - cr.y;
int cw = cr.w;
// Scrollbar interaction
MouseEvent local_ev = ev;
local_ev.x = local_x;
local_ev.y = local_y;
fm->scrollbar.handle_mouse(local_ev);
// ---- Context menu interaction ----
if (fm->ctx_open) {
mtk::ContextMenuState state = filemanager_context_state(fm);
mtk::ContextMenuItem items[8];
filemanager_build_context_items(fm, items, 8);
int action = mtk::context_menu_handle_mouse(state, items,
fm->ctx_item_count,
local_x, local_y,
ev.buttons, ev.prev_buttons,
cw, win->content_h);
filemanager_store_context_state(fm, state);
if (action >= 0) {
int target = fm->ctx_target_idx;
// Select target if needed
if (target >= 0 && target < fm->entry_count)
fm->selected = target;
switch (action) {
case CTX_OPEN: filemanager_open_entry(fm, target); break;
case CTX_COPY: filemanager_do_copy(fm); break;
case CTX_CUT: filemanager_do_cut(fm); break;
case CTX_PASTE: filemanager_do_paste(fm); break;
case CTX_RENAME: filemanager_start_rename(fm); break;
case CTX_DELETE: filemanager_delete_selected(fm); break;
case CTX_NEW_FOLDER: filemanager_new_folder(fm); break;
case CTX_PROPERTIES: {
if (fm->multi_count > 1 && fm->entry_count > 0) {
int* sel = (int*)montauk::malloc((uint64_t)fm->entry_count * sizeof(int));
if (sel) {
int n = filemanager_collect_selection(fm, sel, fm->entry_count);
if (n > 0) filemanager_show_multi_properties(fm, sel, n);
montauk::mfree(sel);
}
} else {
filemanager_show_properties(fm, target);
}
break;
}
case CTX_REFRESH: filemanager_refresh(fm); break;
}
return;
}
if (action == mtk::CONTEXT_MENU_DISMISSED) return;
return;
}
// ---- Cancel rename on click outside rename area ----
if (fm->rename_active && ev.left_pressed()) {
filemanager_finish_rename(fm);
}
if (ev.left_pressed()) {
// Click on path bar area
if (local_y >= FM_TOOLBAR_H && local_y < FM_TOOLBAR_H + FM_PATHBAR_H) {
if (!fm->pathbar_editing)
filemanager_start_pathbar(fm);
return;
}
// Clicking anywhere else while pathbar is editing commits the path
if (fm->pathbar_editing) {
filemanager_commit_pathbar(fm);
}
// Toolbar button clicks
if (local_y < FM_TOOLBAR_H) {
// Navigation buttons
if (local_x >= 4 && local_x < 28) filemanager_go_back(fm);
else if (local_x >= 32 && local_x < 56) filemanager_go_forward(fm);
else if (local_x >= 60 && local_x < 84) filemanager_go_up(fm);
else if (local_x >= 88 && local_x < 112) filemanager_go_home(fm);
else if (local_x >= 116 && local_x < 140) filemanager_refresh(fm);
else if (local_x >= 148 && local_x < 172) {
fm->grid_view = !fm->grid_view;
fm->scrollbar.scroll_offset = 0;
fm->marquee_active = false;
filemanager_clear_multi_selection(fm);
}
// Action buttons
else if (local_x >= 188 && local_x < 212) filemanager_do_copy(fm);
else if (local_x >= 216 && local_x < 240) filemanager_do_cut(fm);
else if (local_x >= 244 && local_x < 268) filemanager_do_paste(fm);
else if (local_x >= 272 && local_x < 296) filemanager_start_rename(fm);
else if (local_x >= 300 && local_x < 324) filemanager_new_folder(fm);
else if (local_x >= 328 && local_x < 352) filemanager_delete_selected(fm);
return;
}
// File clicks (grid vs list)
if (fm->grid_view) {
int list_y = FM_TOOLBAR_H + FM_PATHBAR_H;
if (local_y >= list_y && local_x < cw - FM_SCROLLBAR_W) {
int cols = (cw - FM_SCROLLBAR_W) / FM_GRID_CELL_W;
if (cols < 1) cols = 1;
int rel_y = local_y - list_y + fm->scrollbar.scroll_offset;
int clicked_idx = filemanager_grid_index_at(fm, cols, local_x, rel_y);
if (clicked_idx >= 0 && clicked_idx < fm->entry_count) {
uint64_t now = montauk::get_milliseconds();
filemanager_clear_multi_selection(fm);
if (fm->last_click_item == clicked_idx &&
(now - fm->last_click_time) < 400) {
filemanager_open_entry(fm, clicked_idx);
fm->last_click_item = -1;
fm->last_click_time = 0;
} else {
fm->selected = clicked_idx;
fm->last_click_item = clicked_idx;
fm->last_click_time = now;
}
} else {
// Background click in grid area: start marquee drag
fm->selected = -1;
filemanager_clear_multi_selection(fm);
fm->marquee_active = true;
fm->marquee_start_x = local_x;
fm->marquee_start_y = rel_y;
fm->marquee_cur_x = local_x;
fm->marquee_cur_y = rel_y;
}
}
} else {
// List view clicks
int list_y = FM_TOOLBAR_H + FM_PATHBAR_H + FM_HEADER_H;
if (local_y >= list_y && local_x < cw - FM_SCROLLBAR_W) {
int rel_y = local_y - list_y + fm->scrollbar.scroll_offset;
int clicked_idx = rel_y / FM_ITEM_H;
if (clicked_idx >= 0 && clicked_idx < fm->entry_count) {
uint64_t now = montauk::get_milliseconds();
filemanager_clear_multi_selection(fm);
// Double-click detection
if (fm->last_click_item == clicked_idx &&
(now - fm->last_click_time) < 400) {
filemanager_open_entry(fm, clicked_idx);
fm->last_click_item = -1;
fm->last_click_time = 0;
} else {
fm->selected = clicked_idx;
fm->last_click_item = clicked_idx;
fm->last_click_time = now;
}
} else {
// Background click in list area: start marquee drag
fm->selected = -1;
filemanager_clear_multi_selection(fm);
fm->marquee_active = true;
fm->marquee_start_x = local_x;
fm->marquee_start_y = rel_y;
fm->marquee_cur_x = local_x;
fm->marquee_cur_y = rel_y;
}
}
}
}
// ---- Marquee drag (grid or list view) ----
if (fm->marquee_active) {
int list_y = fm->grid_view
? FM_TOOLBAR_H + FM_PATHBAR_H
: FM_TOOLBAR_H + FM_PATHBAR_H + FM_HEADER_H;
int rel_y = local_y - list_y + fm->scrollbar.scroll_offset;
int clamp_x = local_x;
if (clamp_x < 0) clamp_x = 0;
int max_x = cw - FM_SCROLLBAR_W - 1;
if (clamp_x > max_x) clamp_x = max_x;
if (rel_y < 0) rel_y = 0;
if (ev.left_held()) {
fm->marquee_cur_x = clamp_x;
fm->marquee_cur_y = rel_y;
}
int rx0 = fm->marquee_start_x < fm->marquee_cur_x ? fm->marquee_start_x : fm->marquee_cur_x;
int ry0 = fm->marquee_start_y < fm->marquee_cur_y ? fm->marquee_start_y : fm->marquee_cur_y;
int rx1 = fm->marquee_start_x > fm->marquee_cur_x ? fm->marquee_start_x : fm->marquee_cur_x;
int ry1 = fm->marquee_start_y > fm->marquee_cur_y ? fm->marquee_start_y : fm->marquee_cur_y;
// Recompute hit-set every frame so entries highlight live during the drag.
filemanager_clear_multi_selection(fm);
int limit = fm->entry_count;
if (fm->grid_view) {
int cols = (cw - FM_SCROLLBAR_W) / FM_GRID_CELL_W;
if (cols < 1) cols = 1;
for (int i = 0; i < limit; i++) {
int col = i % cols;
int row = i / cols;
int cell_x = col * FM_GRID_CELL_W;
int cell_y = filemanager_grid_row_y(fm, row, cols);
int cell_h = filemanager_grid_row_height(fm, row, cols);
int cx1 = cell_x + FM_GRID_CELL_W;
int cy1 = cell_y + cell_h;
if (rx1 < cell_x || rx0 >= cx1) continue;
if (ry1 < cell_y || ry0 >= cy1) continue;
fm->multi_selected[i] = true;
fm->multi_count++;
}
} else {
// List rows span the full width, so select any row whose vertical
// extent overlaps the marquee box.
for (int i = 0; i < limit; i++) {
int cell_y = i * FM_ITEM_H;
int cy1 = cell_y + FM_ITEM_H;
if (ry1 < cell_y || ry0 >= cy1) continue;
fm->multi_selected[i] = true;
fm->multi_count++;
}
}
fm->selected = -1;
if (!ev.left_held()) {
// Release: collapse a single-item marquee to a normal selection.
if (fm->multi_count == 1) {
for (int i = 0; i < limit; i++) {
if (fm->multi_selected[i]) { fm->selected = i; break; }
}
filemanager_clear_multi_selection(fm);
}
fm->marquee_active = false;
}
}
// ---- Right-click: open context menu ----
if (ev.right_pressed()) {
filemanager_cancel_rename(fm);
// Determine what was right-clicked
int target_idx = -1;
if (local_y >= FM_TOOLBAR_H + FM_PATHBAR_H) {
if (fm->grid_view) {
int list_y = FM_TOOLBAR_H + FM_PATHBAR_H;
if (local_x < cw - FM_SCROLLBAR_W) {
int cols = (cw - FM_SCROLLBAR_W) / FM_GRID_CELL_W;
if (cols < 1) cols = 1;
int rel_y = local_y - list_y + fm->scrollbar.scroll_offset;
int idx = filemanager_grid_index_at(fm, cols, local_x, rel_y);
if (idx >= 0 && idx < fm->entry_count) {
target_idx = idx;
// Preserve multi-selection if right-clicking inside it,
// otherwise replace it with a single selection.
if (!(fm->multi_count > 0 && fm->multi_selected[idx])) {
filemanager_clear_multi_selection(fm);
fm->selected = idx;
}
} else {
filemanager_clear_multi_selection(fm);
fm->selected = -1;
}
}
} else {
int list_y = FM_TOOLBAR_H + FM_PATHBAR_H + FM_HEADER_H;
if (local_y >= list_y && local_x < cw - FM_SCROLLBAR_W) {
int rel_y = local_y - list_y + fm->scrollbar.scroll_offset;
int idx = rel_y / FM_ITEM_H;
if (idx >= 0 && idx < fm->entry_count) {
target_idx = idx;
// Preserve multi-selection if right-clicking inside it,
// otherwise replace it with a single selection.
if (!(fm->multi_count > 0 && fm->multi_selected[idx])) {
filemanager_clear_multi_selection(fm);
fm->selected = idx;
}
} else {
filemanager_clear_multi_selection(fm);
fm->selected = -1;
}
}
}
}
filemanager_open_ctx_menu(fm, local_x, local_y, target_idx);
}
// Scroll handling
if (ev.scroll != 0) {
int list_y_start = fm->grid_view
? FM_TOOLBAR_H + FM_PATHBAR_H
: FM_TOOLBAR_H + FM_PATHBAR_H + FM_HEADER_H;
int scroll_step = fm->grid_view ? FM_GRID_CELL_H : FM_ITEM_H;
if (local_y >= list_y_start) {
fm->scrollbar.scroll_offset -= ev.scroll * scroll_step;
int ms = fm->scrollbar.max_scroll();
if (fm->scrollbar.scroll_offset < 0) fm->scrollbar.scroll_offset = 0;
if (fm->scrollbar.scroll_offset > ms) fm->scrollbar.scroll_offset = ms;
}
}
}
void filemanager_on_key(Window* win, const montauk::abi::KeyEvent& key) {
FileManagerState* fm = (FileManagerState*)win->app_data;
if (!fm || !key.pressed) return;
// ---- Path bar editing key handling ----
if (fm->pathbar_editing) {
if (key.ascii == '\n' || key.ascii == '\r') {
filemanager_commit_pathbar(fm);
} else if (key.scancode == 0x01) {
filemanager_cancel_pathbar(fm);
} else if (key.ascii == '\b' || key.scancode == 0x0E) {
if (fm->pathbar_cursor > 0) {
for (int i = fm->pathbar_cursor - 1; i < fm->pathbar_len - 1; i++)
fm->pathbar_buf[i] = fm->pathbar_buf[i + 1];
fm->pathbar_len--;
fm->pathbar_cursor--;
fm->pathbar_buf[fm->pathbar_len] = '\0';
}
} else if (key.scancode == 0x53) {
if (fm->pathbar_cursor < fm->pathbar_len) {
for (int i = fm->pathbar_cursor; i < fm->pathbar_len - 1; i++)
fm->pathbar_buf[i] = fm->pathbar_buf[i + 1];
fm->pathbar_len--;
fm->pathbar_buf[fm->pathbar_len] = '\0';
}
} else if (key.scancode == 0x4B) {
if (fm->pathbar_cursor > 0) fm->pathbar_cursor--;
} else if (key.scancode == 0x4D) {
if (fm->pathbar_cursor < fm->pathbar_len) fm->pathbar_cursor++;
} else if (key.scancode == 0x47) {
fm->pathbar_cursor = 0;
} else if (key.scancode == 0x4F) {
fm->pathbar_cursor = fm->pathbar_len;
} else if ((unsigned char)key.ascii >= 32
&& (unsigned char)key.ascii != 127 && fm->pathbar_len < 254) {
for (int i = fm->pathbar_len; i > fm->pathbar_cursor; i--)
fm->pathbar_buf[i] = fm->pathbar_buf[i - 1];
fm->pathbar_buf[fm->pathbar_cursor] = key.ascii;
fm->pathbar_cursor++;
fm->pathbar_len++;
fm->pathbar_buf[fm->pathbar_len] = '\0';
}
return;
}
// ---- Rename mode key handling ----
if (fm->rename_active) {
if (key.ascii == '\n' || key.ascii == '\r') {
filemanager_finish_rename(fm);
} else if (key.scancode == 0x01) {
// Escape
filemanager_cancel_rename(fm);
} else if (key.ascii == '\b' || key.scancode == 0x0E) {
// Backspace
if (fm->rename_cursor > 0) {
for (int i = fm->rename_cursor - 1; i < fm->rename_len - 1; i++)
fm->rename_buf[i] = fm->rename_buf[i + 1];
fm->rename_len--;
fm->rename_cursor--;
fm->rename_buf[fm->rename_len] = '\0';
}
} else if (key.scancode == 0x53) {
// Delete key
if (fm->rename_cursor < fm->rename_len) {
for (int i = fm->rename_cursor; i < fm->rename_len - 1; i++)
fm->rename_buf[i] = fm->rename_buf[i + 1];
fm->rename_len--;
fm->rename_buf[fm->rename_len] = '\0';
}
} else if (key.scancode == 0x4B) {
// Left arrow
if (fm->rename_cursor > 0) fm->rename_cursor--;
} else if (key.scancode == 0x4D) {
// Right arrow
if (fm->rename_cursor < fm->rename_len) fm->rename_cursor++;
} else if (key.scancode == 0x47) {
// Home
fm->rename_cursor = 0;
} else if (key.scancode == 0x4F) {
// End
fm->rename_cursor = fm->rename_len;
} else if ((unsigned char)key.ascii >= 32
&& (unsigned char)key.ascii != 127) {
// Printable character (reject / and other FS-unsafe chars)
if (key.ascii != '/' && key.ascii != '\\' && fm->rename_len < 62) {
for (int i = fm->rename_len; i > fm->rename_cursor; i--)
fm->rename_buf[i] = fm->rename_buf[i - 1];
fm->rename_buf[fm->rename_cursor] = key.ascii;
fm->rename_cursor++;
fm->rename_len++;
fm->rename_buf[fm->rename_len] = '\0';
}
}
return;
}
// ---- Context menu: Escape to close ----
if (fm->ctx_open && key.scancode == 0x01) {
filemanager_close_ctx_menu(fm);
return;
}
// ---- Keyboard shortcuts ----
if (key.ctrl) {
if (key.ascii == 'c' || key.ascii == 'C') {
filemanager_do_copy(fm);
return;
}
if (key.ascii == 'x' || key.ascii == 'X') {
filemanager_do_cut(fm);
return;
}
if (key.ascii == 'v' || key.ascii == 'V') {
filemanager_do_paste(fm);
return;
}
}
// F2 = rename
if (key.scancode == 0x3C) {
filemanager_start_rename(fm);
return;
}
if (key.ascii == '\b' || key.scancode == 0x0E) {
filemanager_go_up(fm);
} else if (key.scancode == 0x48) {
// Up arrow
if (fm->grid_view) {
Rect cr_r = win->content_rect();
int cols = (cr_r.w - FM_SCROLLBAR_W) / FM_GRID_CELL_W;
if (cols < 1) cols = 1;
if (fm->selected >= cols) fm->selected -= cols;
} else {
if (fm->selected > 0) fm->selected--;
}
} else if (key.scancode == 0x50) {
// Down arrow
if (fm->grid_view) {
Rect cr_r = win->content_rect();
int cols = (cr_r.w - FM_SCROLLBAR_W) / FM_GRID_CELL_W;
if (cols < 1) cols = 1;
if (fm->selected + cols < fm->entry_count) fm->selected += cols;
} else {
if (fm->selected < fm->entry_count - 1) fm->selected++;
}
} else if (key.scancode == 0x4B && !key.alt && fm->grid_view) {
// Left arrow (grid view only)
if (fm->selected > 0) fm->selected--;
} else if (key.scancode == 0x4D && !key.alt && fm->grid_view) {
// Right arrow (grid view only)
if (fm->selected < fm->entry_count - 1) fm->selected++;
} else if (key.ascii == '\n' || key.ascii == '\r') {
if (fm->selected >= 0 && fm->selected < fm->entry_count)
filemanager_open_entry(fm, fm->selected);
} else if (key.scancode == 0x53) {
// Delete key
filemanager_delete_selected(fm);
} else if (key.alt && key.scancode == 0x4B) {
// Alt+Left: go back
filemanager_go_back(fm);
} else if (key.alt && key.scancode == 0x4D) {
// Alt+Right: go forward
filemanager_go_forward(fm);
}
}
void filemanager_on_close(Window* win) {
if (win->app_data) {
FileManagerState* fm = (FileManagerState*)win->app_data;
filemanager_free_arrays(fm);
montauk::mfree(win->app_data);
win->app_data = nullptr;
}
}
} // namespace filemanager