From 6b44b3ccc9a0b3d4c7bf4f435272d0b78db7588d Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Thu, 4 Jun 2026 07:47:42 +0200 Subject: [PATCH] feat: extend Files app marquee selection and highlight to list view --- kernel/src/Api/BuildNo.hpp | 2 +- .../src/desktop/apps/filemanager/events.cpp | 68 +++++++++++++------ .../src/desktop/apps/filemanager/render.cpp | 58 +++++++++------- 3 files changed, 85 insertions(+), 43 deletions(-) diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index f5b58a1..87a4b36 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 48 +#define MONTAUK_BUILD_NUMBER 49 diff --git a/programs/src/desktop/apps/filemanager/events.cpp b/programs/src/desktop/apps/filemanager/events.cpp index c3ec687..2792591 100644 --- a/programs/src/desktop/apps/filemanager/events.cpp +++ b/programs/src/desktop/apps/filemanager/events.cpp @@ -153,6 +153,7 @@ void filemanager_on_mouse(Window* win, MouseEvent& ev) { 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 && @@ -166,15 +167,24 @@ void filemanager_on_mouse(Window* win, MouseEvent& ev) { 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 in grid view ---- + // ---- Marquee drag (grid or list view) ---- if (fm->marquee_active) { - int list_y = FM_TOOLBAR_H + FM_PATHBAR_H; + 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; @@ -187,31 +197,43 @@ void filemanager_on_mouse(Window* win, MouseEvent& ev) { fm->marquee_cur_y = rel_y; } - // Recompute hit-set every frame so icons highlight live during the drag. - int cols = (cw - FM_SCROLLBAR_W) / FM_GRID_CELL_W; - if (cols < 1) cols = 1; - 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 < 64 ? fm->entry_count : 64; - 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 (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; + if (rx1 < cell_x || rx0 >= cx1) continue; + if (ry1 < cell_y || ry0 >= cy1) continue; - fm->multi_selected[i] = true; - fm->multi_count++; + 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; @@ -262,7 +284,15 @@ void filemanager_on_mouse(Window* win, MouseEvent& ev) { int idx = rel_y / FM_ITEM_H; if (idx >= 0 && idx < fm->entry_count) { target_idx = idx; - fm->selected = 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; } } } diff --git a/programs/src/desktop/apps/filemanager/render.cpp b/programs/src/desktop/apps/filemanager/render.cpp index 35edcf0..4f144ac 100644 --- a/programs/src/desktop/apps/filemanager/render.cpp +++ b/programs/src/desktop/apps/filemanager/render.cpp @@ -105,6 +105,34 @@ static void draw_centered_grid_label(Canvas& c, int cell_x, int y, const char* l c.text(tx, y, label, color); } +// Marquee overlay (translucent accent wash + darker outline). Marquee +// coordinates are stored in content space (y = rel_y, anchored to scroll), so +// list_y is the top of the scrollable area for the active view. +static void draw_marquee(Canvas& c, FileManagerState* fm, int list_y) { + if (!fm->marquee_active) return; + DesktopState* ds = fm->desktop; + + 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; + + int sy0 = list_y + ry0 - fm->scrollbar.scroll_offset; + int sy1 = list_y + ry1 - fm->scrollbar.scroll_offset; + int max_x = c.w - FM_SCROLLBAR_W; + if (rx1 > max_x) rx1 = max_x; + if (sy0 < list_y) sy0 = list_y; + if (sy1 > c.h) sy1 = c.h; + int mw = rx1 - rx0; + int mh = sy1 - sy0; + if (mw > 0 && mh > 0) { + Color accent = ds ? ds->settings.accent_color : colors::ACCENT; + Color wash = Color::from_rgba(accent.r, accent.g, accent.b, 80); + c.fill_rect_alpha(rx0, sy0, mw, mh, wash); + c.rect(rx0, sy0, mw, mh, gui::mtk::darken(accent, 48)); + } +} + int filemanager_grid_row_height(FileManagerState* fm, int row, int cols) { if (!fm || cols <= 0 || row < 0) return FM_GRID_CELL_H; @@ -408,27 +436,7 @@ void filemanager_on_draw(Window* win, Framebuffer& fb) { // Marquee drawn on top of cells: a translucent accent wash so icons // and labels remain visible underneath, plus a darker outline. - if (fm->marquee_active) { - 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; - - int sy0 = list_y + ry0 - fm->scrollbar.scroll_offset; - int sy1 = list_y + ry1 - fm->scrollbar.scroll_offset; - int max_x = c.w - FM_SCROLLBAR_W; - if (rx1 > max_x) rx1 = max_x; - if (sy0 < list_y) sy0 = list_y; - if (sy1 > c.h) sy1 = c.h; - int mw = rx1 - rx0; - int mh = sy1 - sy0; - if (mw > 0 && mh > 0) { - Color accent = ds ? ds->settings.accent_color : colors::ACCENT; - Color wash = Color::from_rgba(accent.r, accent.g, accent.b, 80); - c.fill_rect_alpha(rx0, sy0, mw, mh, wash); - c.rect(rx0, sy0, mw, mh, gui::mtk::darken(accent, 48)); - } - } + draw_marquee(c, fm, list_y); } else { // ---- List View ---- @@ -471,8 +479,9 @@ void filemanager_on_draw(Window* win, Framebuffer& fb) { int iy = list_y + (i - scroll_items) * FM_ITEM_H - (fm->scrollbar.scroll_offset % FM_ITEM_H); if (iy + FM_ITEM_H <= list_y || iy >= c.h) continue; - // Highlight selected - if (i == fm->selected) { + // Highlight selected (single or marquee multi-selection) + bool row_selected = (i == fm->selected) || (i < 64 && fm->multi_selected[i]); + if (row_selected) { int sy = gui_max(iy, list_y); int sh = gui_min(iy + FM_ITEM_H, c.h) - sy; if (sh > 0) @@ -592,6 +601,9 @@ void filemanager_on_draw(Window* win, Framebuffer& fb) { c.text(type_col_x, ty, type_str, dim); } } + + // Marquee overlay drawn on top of the rows. + draw_marquee(c, fm, list_y); } // ---- Scrollbar ----