From 2ceefaf1caca799dbae9e240eee553ac513d9e55 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Mon, 18 May 2026 19:51:23 +0200 Subject: [PATCH] feat: refresh button in Files, svg.hpp arc handling --- programs/include/gui/desktop.hpp | 1 + programs/include/gui/svg.hpp | 160 ++++++++++++++++-- .../src/desktop/apps/filemanager/actions.cpp | 10 +- .../src/desktop/apps/filemanager/events.cpp | 20 ++- .../apps/filemanager/filemanager_internal.hpp | 2 + .../src/desktop/apps/filemanager/helpers.cpp | 1 + .../desktop/apps/filemanager/navigation.cpp | 11 ++ .../src/desktop/apps/filemanager/render.cpp | 31 ++-- programs/src/desktop/main.cpp | 1 + 9 files changed, 198 insertions(+), 39 deletions(-) diff --git a/programs/include/gui/desktop.hpp b/programs/include/gui/desktop.hpp index 044fe9b..a5e1cf0 100644 --- a/programs/include/gui/desktop.hpp +++ b/programs/include/gui/desktop.hpp @@ -115,6 +115,7 @@ struct DesktopState { SvgIcon icon_go_back; SvgIcon icon_go_forward; SvgIcon icon_home; + SvgIcon icon_refresh; SvgIcon icon_exec; SvgIcon icon_folder_lg; diff --git a/programs/include/gui/svg.hpp b/programs/include/gui/svg.hpp index 263d964..8dde7bf 100644 --- a/programs/include/gui/svg.hpp +++ b/programs/include/gui/svg.hpp @@ -123,6 +123,34 @@ struct SvgCssTable { } }; +// --------------------------------------------------------------------------- +// Integer sqrt (binary method, no floating point). +// Returns floor(sqrt(n)) for any uint64. +// --------------------------------------------------------------------------- +inline uint64_t svg_int64_sqrt(uint64_t n) { + if (n == 0) return 0; + uint64_t r = 0; + uint64_t bit = (uint64_t)1 << 62; + while (bit > n) bit >>= 2; + while (bit != 0) { + if (n >= r + bit) { + n -= r + bit; + r = (r >> 1) + bit; + } else { + r >>= 1; + } + bit >>= 2; + } + return r; +} + +// 16.16 fixed-point hypotenuse: sqrt(x*x + y*y). +inline fixed_t svg_fixed_hypot(fixed_t x, fixed_t y) { + int64_t sq = (int64_t)x * x + (int64_t)y * y; + if (sq < 0) sq = 0; + return (fixed_t)svg_int64_sqrt((uint64_t)sq); +} + // --------------------------------------------------------------------------- // Fixed-point number parser (NO floating point) // Parses strings like "3.25", "-0.5", ".1115", "16" @@ -566,6 +594,47 @@ inline void svg_path_to_edges(SvgEdgeList& el, const char* d, int dLen, *oy = fixed_mul(y - off_y, scale_y); }; + // Emit a circular arc on (cx, cy, r) from p1 to p2 by recursive + // chord-midpoint subdivision. Assumes the arc is <= 180deg; callers split + // large arcs at the far point first. Stack-based to avoid recursive lambdas. + auto emit_small_arc = [&](fixed_t cx, fixed_t cy, fixed_t r, + fixed_t p1x, fixed_t p1y, + fixed_t p2x, fixed_t p2y) { + constexpr int ARC_MAX_DEPTH = 4; // up to 16 chords per sub-arc + struct Seg { fixed_t ax, ay, bx, by; int depth; }; + Seg stk[16]; + int sp = 0; + stk[sp++] = { p1x, p1y, p2x, p2y, ARC_MAX_DEPTH }; + while (sp > 0) { + Seg s = stk[--sp]; + if (s.depth == 0) { + fixed_t sx0, sy0, sx1, sy1; + scale_pt(s.ax, s.ay, &sx0, &sy0); + scale_pt(s.bx, s.by, &sx1, &sy1); + el.add(sx0, sy0, sx1, sy1); + continue; + } + fixed_t mx = (s.ax + s.bx) / 2; + fixed_t my = (s.ay + s.by) / 2; + fixed_t vx = mx - cx; + fixed_t vy = my - cy; + fixed_t vlen = svg_fixed_hypot(vx, vy); + if (vlen == 0) { + // Degenerate: chord midpoint coincides with center. + fixed_t sx0, sy0, sx1, sy1; + scale_pt(s.ax, s.ay, &sx0, &sy0); + scale_pt(s.bx, s.by, &sx1, &sy1); + el.add(sx0, sy0, sx1, sy1); + continue; + } + fixed_t k = fixed_div(r, vlen); + fixed_t amx = cx + fixed_mul(vx, k); + fixed_t amy = cy + fixed_mul(vy, k); + stk[sp++] = { amx, amy, s.bx, s.by, s.depth - 1 }; + stk[sp++] = { s.ax, s.ay, amx, amy, s.depth - 1 }; + } + }; + while (pp.has_more()) { char cmd = '\0'; @@ -773,23 +842,90 @@ inline void svg_path_to_edges(SvgEdgeList& el, const char* d, int dLen, break; } case 'A': case 'a': { - // Arc command: consume parameters but approximate as a line - // (arcs are rare in these icons) - fixed_t rx = pp.read_number(); - fixed_t ry = pp.read_number(); - pp.read_number(); // x-rotation - pp.read_number(); // large-arc-flag - pp.read_number(); // sweep-flag + // Elliptical-arc command. We treat the arc as circular using + // r = min(|rx|, |ry|), which is exact for symbolic icons (all use + // equal axes) and a reasonable approximation otherwise. + fixed_t rx_in = pp.read_number(); + fixed_t ry_in = pp.read_number(); + pp.read_number(); // x-axis rotation (ignored) + fixed_t fa_f = pp.read_number(); // large-arc-flag + fixed_t fs_f = pp.read_number(); // sweep-flag fixed_t x = pp.read_number(); fixed_t y = pp.read_number(); if (cmd == 'a') { x += cur_x; y += cur_y; } - fixed_t sx0, sy0, sx1, sy1; - scale_pt(cur_x, cur_y, &sx0, &sy0); - scale_pt(x, y, &sx1, &sy1); - el.add(sx0, sy0, sx1, sy1); + + fixed_t r = (rx_in < 0) ? -rx_in : rx_in; + fixed_t ry_abs = (ry_in < 0) ? -ry_in : ry_in; + if (ry_abs < r) r = ry_abs; + + int fa = (fa_f != 0) ? 1 : 0; + int fs = (fs_f != 0) ? 1 : 0; + + fixed_t dx = x - cur_x; + fixed_t dy = y - cur_y; + if ((dx == 0 && dy == 0) || r == 0) { + // Per spec: degenerate arc becomes a line. + fixed_t sx0, sy0, sx1, sy1; + scale_pt(cur_x, cur_y, &sx0, &sy0); + scale_pt(x, y, &sx1, &sy1); + el.add(sx0, sy0, sx1, sy1); + cur_x = x; cur_y = y; + last_cmd = cmd; + break; + } + + // Endpoint-to-center: midpoint of chord, half-chord length, + // perpendicular height from chord to center. + fixed_t mx = cur_x + dx / 2; + fixed_t my = cur_y + dy / 2; + fixed_t half_chord = svg_fixed_hypot(dx / 2, dy / 2); + if (r < half_chord) r = half_chord; // SVG spec: snap radius up + int64_t r_sq = (int64_t)r * r; + int64_t hc_sq = (int64_t)half_chord * half_chord; + int64_t h_sq = r_sq - hc_sq; + if (h_sq < 0) h_sq = 0; + fixed_t h = (fixed_t)svg_int64_sqrt((uint64_t)h_sq); + + // Center = mid + sign * (h / chord_len) * (-dy, dx) + // sign: spec F.6.5 says -1 when fa == fs, else +1. + fixed_t chord_len = 2 * half_chord; + if (chord_len == 0) chord_len = 1; + fixed_t h_over_chord = fixed_div(h, chord_len); + int sign = (fa == fs) ? -1 : 1; + fixed_t off_x_perp = fixed_mul(-dy, h_over_chord); + fixed_t off_y_perp = fixed_mul( dx, h_over_chord); + if (sign < 0) { off_x_perp = -off_x_perp; off_y_perp = -off_y_perp; } + fixed_t cx_ctr = mx + off_x_perp; + fixed_t cy_ctr = my + off_y_perp; + + if (fa == 1) { + // Large arc: bisect at the far point (opposite side of center + // from chord midpoint), then walk each half as a small arc. + fixed_t vx = mx - cx_ctr; + fixed_t vy = my - cy_ctr; + fixed_t vlen = svg_fixed_hypot(vx, vy); + fixed_t am_x, am_y; + if (vlen == 0) { + // 180-degree arc: bisect using chord-perpendicular instead. + fixed_t k = fixed_div(r, chord_len); + fixed_t px = fixed_mul(-dy, k); + fixed_t py = fixed_mul( dx, k); + if (fs == 0) { px = -px; py = -py; } + am_x = cx_ctr + px; + am_y = cy_ctr + py; + } else { + fixed_t k = fixed_div(-r, vlen); + am_x = cx_ctr + fixed_mul(vx, k); + am_y = cy_ctr + fixed_mul(vy, k); + } + emit_small_arc(cx_ctr, cy_ctr, r, cur_x, cur_y, am_x, am_y); + emit_small_arc(cx_ctr, cy_ctr, r, am_x, am_y, x, y); + } else { + emit_small_arc(cx_ctr, cy_ctr, r, cur_x, cur_y, x, y); + } + cur_x = x; cur_y = y; last_cmd = cmd; - (void)rx; (void)ry; break; } case 'Z': case 'z': { diff --git a/programs/src/desktop/apps/filemanager/actions.cpp b/programs/src/desktop/apps/filemanager/actions.cpp index 2396b4c..c6e80ec 100644 --- a/programs/src/desktop/apps/filemanager/actions.cpp +++ b/programs/src/desktop/apps/filemanager/actions.cpp @@ -194,9 +194,13 @@ void filemanager_open_ctx_menu(FileManagerState* fm, int local_x, int local_y, i fm->ctx_open = false; return; } - if (fm->clipboard_has_data) - fm->ctx_items[fm->ctx_item_count++] = CTX_PASTE; - fm->ctx_items[fm->ctx_item_count++] = CTX_NEW_FOLDER; + if (filemanager_is_computer_view(fm)) { + fm->ctx_items[fm->ctx_item_count++] = CTX_REFRESH; + } else { + if (fm->clipboard_has_data) + fm->ctx_items[fm->ctx_item_count++] = CTX_PASTE; + fm->ctx_items[fm->ctx_item_count++] = CTX_NEW_FOLDER; + } } } diff --git a/programs/src/desktop/apps/filemanager/events.cpp b/programs/src/desktop/apps/filemanager/events.cpp index 95f6ae8..7999b38 100644 --- a/programs/src/desktop/apps/filemanager/events.cpp +++ b/programs/src/desktop/apps/filemanager/events.cpp @@ -51,6 +51,7 @@ void filemanager_on_mouse(Window* win, MouseEvent& ev) { case CTX_DELETE: filemanager_delete_selected(fm); break; case CTX_NEW_FOLDER: filemanager_new_folder(fm); break; case CTX_PROPERTIES: filemanager_show_properties(fm, target); break; + case CTX_REFRESH: filemanager_refresh(fm); break; } return; } @@ -79,21 +80,22 @@ void filemanager_on_mouse(Window* win, MouseEvent& ev) { // Toolbar button clicks if (local_y < FM_TOOLBAR_H) { // Navigation buttons - if (local_x >= 4 && local_x < 28) filemanager_go_back(fm); + 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 >= 120 && local_x < 144) { + 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; } // Action buttons - else if (local_x >= 160 && local_x < 184) filemanager_do_copy(fm); - else if (local_x >= 188 && local_x < 212) filemanager_do_cut(fm); - else if (local_x >= 216 && local_x < 240) filemanager_do_paste(fm); - else if (local_x >= 244 && local_x < 268) filemanager_start_rename(fm); - else if (local_x >= 272 && local_x < 296) filemanager_new_folder(fm); - else if (local_x >= 300 && local_x < 324) filemanager_delete_selected(fm); + 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; } diff --git a/programs/src/desktop/apps/filemanager/filemanager_internal.hpp b/programs/src/desktop/apps/filemanager/filemanager_internal.hpp index 313c5cf..ed45516 100644 --- a/programs/src/desktop/apps/filemanager/filemanager_internal.hpp +++ b/programs/src/desktop/apps/filemanager/filemanager_internal.hpp @@ -135,6 +135,7 @@ inline constexpr int CTX_RENAME = 4; inline constexpr int CTX_DELETE = 5; inline constexpr int CTX_NEW_FOLDER = 6; inline constexpr int CTX_PROPERTIES = 7; +inline constexpr int CTX_REFRESH = 8; inline bool filemanager_is_virtual_view(const FileManagerState* fm) { return fm && fm->virtual_view != FM_VIRTUAL_VIEW_NONE; @@ -224,6 +225,7 @@ void filemanager_navigate_to_history(FileManagerState* fm); void filemanager_go_back(FileManagerState* fm); void filemanager_go_forward(FileManagerState* fm); void filemanager_go_home(FileManagerState* fm); +void filemanager_refresh(FileManagerState* fm); void filemanager_start_pathbar(FileManagerState* fm); void filemanager_cancel_pathbar(FileManagerState* fm); diff --git a/programs/src/desktop/apps/filemanager/helpers.cpp b/programs/src/desktop/apps/filemanager/helpers.cpp index b3a6121..0722513 100644 --- a/programs/src/desktop/apps/filemanager/helpers.cpp +++ b/programs/src/desktop/apps/filemanager/helpers.cpp @@ -25,6 +25,7 @@ const char* ctx_label(int action) { case CTX_DELETE: return "Delete"; case CTX_NEW_FOLDER: return "New Folder"; case CTX_PROPERTIES: return "Properties"; + case CTX_REFRESH: return "Refresh"; default: return "?"; } } diff --git a/programs/src/desktop/apps/filemanager/navigation.cpp b/programs/src/desktop/apps/filemanager/navigation.cpp index a9f5dd4..3e963d3 100644 --- a/programs/src/desktop/apps/filemanager/navigation.cpp +++ b/programs/src/desktop/apps/filemanager/navigation.cpp @@ -137,6 +137,17 @@ void filemanager_go_home(FileManagerState* fm) { filemanager_push_history(fm); } +void filemanager_refresh(FileManagerState* fm) { + if (!fm) return; + if (filemanager_is_virtual_view(fm)) { + filemanager_read_virtual_view(fm, fm->virtual_view); + } else if (filemanager_is_computer_view(fm)) { + filemanager_read_drives(fm); + } else { + filemanager_read_dir(fm); + } +} + void filemanager_start_pathbar(FileManagerState* fm) { fm->pathbar_editing = true; if (filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm)) { diff --git a/programs/src/desktop/apps/filemanager/render.cpp b/programs/src/desktop/apps/filemanager/render.cpp index 83bf662..8005a14 100644 --- a/programs/src/desktop/apps/filemanager/render.cpp +++ b/programs/src/desktop/apps/filemanager/render.cpp @@ -163,16 +163,17 @@ void filemanager_draw_header(Canvas& c, FileManagerState* fm, Color toolbar_colo // ---- Toolbar (32px) ---- c.fill_rect(0, 0, c.w, FM_TOOLBAR_H, toolbar_color); - // Navigation buttons: Back, Forward, Up, Home + // Navigation buttons: Back, Forward, Up, Home, Refresh struct ToolBtn { int x; SvgIcon* icon; }; - ToolBtn nav_btns[4] = { - { 4, ds ? &ds->icon_go_back : nullptr }, - { 32, ds ? &ds->icon_go_forward : nullptr }, - { 60, ds ? &ds->icon_go_up : nullptr }, - { 88, ds ? &ds->icon_home : nullptr }, + ToolBtn nav_btns[5] = { + { 4, ds ? &ds->icon_go_back : nullptr }, + { 32, ds ? &ds->icon_go_forward : nullptr }, + { 60, ds ? &ds->icon_go_up : nullptr }, + { 88, ds ? &ds->icon_home : nullptr }, + { 116, ds ? &ds->icon_refresh : nullptr }, }; - for (int i = 0; i < 4; i++) { + for (int i = 0; i < 5; i++) { int bx = nav_btns[i].x; int by = 4; c.fill_rect(bx, by, 24, 24, btn_bg); @@ -185,7 +186,7 @@ void filemanager_draw_header(Canvas& c, FileManagerState* fm, Color toolbar_colo // View toggle button { - int bx = 120, by = 4; + int bx = 148, by = 4; c.fill_rect(bx, by, 24, 24, btn_bg); if (fm->grid_view) { for (int r = 0; r < 2; r++) @@ -198,7 +199,7 @@ void filemanager_draw_header(Canvas& c, FileManagerState* fm, Color toolbar_colo } // Separator line between nav group and action group - c.vline(152, 6, 20, colors::BORDER); + c.vline(180, 6, 20, colors::BORDER); // Action buttons: Copy, Cut, Paste, Rename, New Folder, Delete bool has_sel = fm->selected >= 0 && fm->selected < fm->entry_count @@ -211,13 +212,13 @@ void filemanager_draw_header(Canvas& c, FileManagerState* fm, Color toolbar_colo // Icon toolbar buttons: Copy, Cut, Paste, Rename, New Folder, Delete struct ActionBtn { int x; SvgIcon* icon; bool enabled; }; ActionBtn action_btns[] = { - { 160, ds ? &ds->icon_copy : nullptr, has_sel }, - { 188, ds ? &ds->icon_cut : nullptr, has_sel }, - { 216, ds ? &ds->icon_paste : nullptr, has_clip }, - { 244, ds ? &ds->icon_rename : nullptr, has_sel }, - { 272, ds ? &ds->icon_folder_new : nullptr, + { 188, ds ? &ds->icon_copy : nullptr, has_sel }, + { 216, ds ? &ds->icon_cut : nullptr, has_sel }, + { 244, ds ? &ds->icon_paste : nullptr, has_clip }, + { 272, ds ? &ds->icon_rename : nullptr, has_sel }, + { 300, ds ? &ds->icon_folder_new : nullptr, !filemanager_is_computer_view(fm) && !filemanager_is_virtual_view(fm) }, - { 300, ds ? &ds->icon_delete : nullptr, has_sel }, + { 328, ds ? &ds->icon_delete : nullptr, has_sel }, }; for (int i = 0; i < 6; i++) { diff --git a/programs/src/desktop/main.cpp b/programs/src/desktop/main.cpp index 3f87c92..acce627 100644 --- a/programs/src/desktop/main.cpp +++ b/programs/src/desktop/main.cpp @@ -197,6 +197,7 @@ void gui::desktop_init(DesktopState* ds) { ds->icon_go_back = svg_load("0:/icons/go-previous-symbolic.svg", 16, 16, defColor); ds->icon_go_forward = svg_load("0:/icons/go-next-symbolic.svg", 16, 16, defColor); ds->icon_home = svg_load("0:/icons/user-home.svg", 16, 16, defColor); + ds->icon_refresh = svg_load("0:/icons/view-refresh-symbolic.svg", 16, 16, defColor); ds->icon_exec = svg_load("0:/icons/utilities-terminal.svg", 16, 16, defColor); ds->icon_folder_lg = svg_load("0:/icons/folder.svg", 48, 48, defColor);