feat: refresh button in Files, svg.hpp arc handling

This commit is contained in:
2026-05-18 19:51:23 +02:00
parent 96abe9a5cd
commit 2ceefaf1ca
9 changed files with 198 additions and 39 deletions
+1
View File
@@ -115,6 +115,7 @@ struct DesktopState {
SvgIcon icon_go_back; SvgIcon icon_go_back;
SvgIcon icon_go_forward; SvgIcon icon_go_forward;
SvgIcon icon_home; SvgIcon icon_home;
SvgIcon icon_refresh;
SvgIcon icon_exec; SvgIcon icon_exec;
SvgIcon icon_folder_lg; SvgIcon icon_folder_lg;
+144 -8
View File
@@ -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) // Fixed-point number parser (NO floating point)
// Parses strings like "3.25", "-0.5", ".1115", "16" // 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); *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()) { while (pp.has_more()) {
char cmd = '\0'; char cmd = '\0';
@@ -773,23 +842,90 @@ inline void svg_path_to_edges(SvgEdgeList& el, const char* d, int dLen,
break; break;
} }
case 'A': case 'a': { case 'A': case 'a': {
// Arc command: consume parameters but approximate as a line // Elliptical-arc command. We treat the arc as circular using
// (arcs are rare in these icons) // r = min(|rx|, |ry|), which is exact for symbolic icons (all use
fixed_t rx = pp.read_number(); // equal axes) and a reasonable approximation otherwise.
fixed_t ry = pp.read_number(); fixed_t rx_in = pp.read_number();
pp.read_number(); // x-rotation fixed_t ry_in = pp.read_number();
pp.read_number(); // large-arc-flag pp.read_number(); // x-axis rotation (ignored)
pp.read_number(); // sweep-flag 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 x = pp.read_number();
fixed_t y = pp.read_number(); fixed_t y = pp.read_number();
if (cmd == 'a') { x += cur_x; y += cur_y; } if (cmd == 'a') { x += cur_x; y += cur_y; }
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; fixed_t sx0, sy0, sx1, sy1;
scale_pt(cur_x, cur_y, &sx0, &sy0); scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(x, y, &sx1, &sy1); scale_pt(x, y, &sx1, &sy1);
el.add(sx0, sy0, sx1, sy1); el.add(sx0, sy0, sx1, sy1);
cur_x = x; cur_y = y; cur_x = x; cur_y = y;
last_cmd = cmd; last_cmd = cmd;
(void)rx; (void)ry; 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;
break; break;
} }
case 'Z': case 'z': { case 'Z': case 'z': {
@@ -194,11 +194,15 @@ void filemanager_open_ctx_menu(FileManagerState* fm, int local_x, int local_y, i
fm->ctx_open = false; fm->ctx_open = false;
return; return;
} }
if (filemanager_is_computer_view(fm)) {
fm->ctx_items[fm->ctx_item_count++] = CTX_REFRESH;
} else {
if (fm->clipboard_has_data) if (fm->clipboard_has_data)
fm->ctx_items[fm->ctx_item_count++] = CTX_PASTE; fm->ctx_items[fm->ctx_item_count++] = CTX_PASTE;
fm->ctx_items[fm->ctx_item_count++] = CTX_NEW_FOLDER; fm->ctx_items[fm->ctx_item_count++] = CTX_NEW_FOLDER;
} }
} }
}
void filemanager_close_ctx_menu(FileManagerState* fm) { void filemanager_close_ctx_menu(FileManagerState* fm) {
fm->ctx_open = false; fm->ctx_open = false;
@@ -51,6 +51,7 @@ void filemanager_on_mouse(Window* win, MouseEvent& ev) {
case CTX_DELETE: filemanager_delete_selected(fm); break; case CTX_DELETE: filemanager_delete_selected(fm); break;
case CTX_NEW_FOLDER: filemanager_new_folder(fm); break; case CTX_NEW_FOLDER: filemanager_new_folder(fm); break;
case CTX_PROPERTIES: filemanager_show_properties(fm, target); break; case CTX_PROPERTIES: filemanager_show_properties(fm, target); break;
case CTX_REFRESH: filemanager_refresh(fm); break;
} }
return; return;
} }
@@ -83,17 +84,18 @@ void filemanager_on_mouse(Window* win, MouseEvent& ev) {
else if (local_x >= 32 && local_x < 56) filemanager_go_forward(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 >= 60 && local_x < 84) filemanager_go_up(fm);
else if (local_x >= 88 && local_x < 112) filemanager_go_home(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 >= 116 && local_x < 140) filemanager_refresh(fm);
else if (local_x >= 148 && local_x < 172) {
fm->grid_view = !fm->grid_view; fm->grid_view = !fm->grid_view;
fm->scrollbar.scroll_offset = 0; fm->scrollbar.scroll_offset = 0;
} }
// Action buttons // Action buttons
else if (local_x >= 160 && local_x < 184) filemanager_do_copy(fm); else if (local_x >= 188 && local_x < 212) 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_cut(fm);
else if (local_x >= 216 && local_x < 240) filemanager_do_paste(fm); else if (local_x >= 244 && local_x < 268) 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_start_rename(fm);
else if (local_x >= 272 && local_x < 296) filemanager_new_folder(fm); else if (local_x >= 300 && local_x < 324) filemanager_new_folder(fm);
else if (local_x >= 300 && local_x < 324) filemanager_delete_selected(fm); else if (local_x >= 328 && local_x < 352) filemanager_delete_selected(fm);
return; return;
} }
@@ -135,6 +135,7 @@ inline constexpr int CTX_RENAME = 4;
inline constexpr int CTX_DELETE = 5; inline constexpr int CTX_DELETE = 5;
inline constexpr int CTX_NEW_FOLDER = 6; inline constexpr int CTX_NEW_FOLDER = 6;
inline constexpr int CTX_PROPERTIES = 7; inline constexpr int CTX_PROPERTIES = 7;
inline constexpr int CTX_REFRESH = 8;
inline bool filemanager_is_virtual_view(const FileManagerState* fm) { inline bool filemanager_is_virtual_view(const FileManagerState* fm) {
return fm && fm->virtual_view != FM_VIRTUAL_VIEW_NONE; 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_back(FileManagerState* fm);
void filemanager_go_forward(FileManagerState* fm); void filemanager_go_forward(FileManagerState* fm);
void filemanager_go_home(FileManagerState* fm); void filemanager_go_home(FileManagerState* fm);
void filemanager_refresh(FileManagerState* fm);
void filemanager_start_pathbar(FileManagerState* fm); void filemanager_start_pathbar(FileManagerState* fm);
void filemanager_cancel_pathbar(FileManagerState* fm); void filemanager_cancel_pathbar(FileManagerState* fm);
@@ -25,6 +25,7 @@ const char* ctx_label(int action) {
case CTX_DELETE: return "Delete"; case CTX_DELETE: return "Delete";
case CTX_NEW_FOLDER: return "New Folder"; case CTX_NEW_FOLDER: return "New Folder";
case CTX_PROPERTIES: return "Properties"; case CTX_PROPERTIES: return "Properties";
case CTX_REFRESH: return "Refresh";
default: return "?"; default: return "?";
} }
} }
@@ -137,6 +137,17 @@ void filemanager_go_home(FileManagerState* fm) {
filemanager_push_history(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) { void filemanager_start_pathbar(FileManagerState* fm) {
fm->pathbar_editing = true; fm->pathbar_editing = true;
if (filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm)) { if (filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm)) {
@@ -163,16 +163,17 @@ void filemanager_draw_header(Canvas& c, FileManagerState* fm, Color toolbar_colo
// ---- Toolbar (32px) ---- // ---- Toolbar (32px) ----
c.fill_rect(0, 0, c.w, FM_TOOLBAR_H, toolbar_color); 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; }; struct ToolBtn { int x; SvgIcon* icon; };
ToolBtn nav_btns[4] = { ToolBtn nav_btns[5] = {
{ 4, ds ? &ds->icon_go_back : nullptr }, { 4, ds ? &ds->icon_go_back : nullptr },
{ 32, ds ? &ds->icon_go_forward : nullptr }, { 32, ds ? &ds->icon_go_forward : nullptr },
{ 60, ds ? &ds->icon_go_up : nullptr }, { 60, ds ? &ds->icon_go_up : nullptr },
{ 88, ds ? &ds->icon_home : 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 bx = nav_btns[i].x;
int by = 4; int by = 4;
c.fill_rect(bx, by, 24, 24, btn_bg); 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 // View toggle button
{ {
int bx = 120, by = 4; int bx = 148, by = 4;
c.fill_rect(bx, by, 24, 24, btn_bg); c.fill_rect(bx, by, 24, 24, btn_bg);
if (fm->grid_view) { if (fm->grid_view) {
for (int r = 0; r < 2; r++) 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 // 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 // Action buttons: Copy, Cut, Paste, Rename, New Folder, Delete
bool has_sel = fm->selected >= 0 && fm->selected < fm->entry_count 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 // Icon toolbar buttons: Copy, Cut, Paste, Rename, New Folder, Delete
struct ActionBtn { int x; SvgIcon* icon; bool enabled; }; struct ActionBtn { int x; SvgIcon* icon; bool enabled; };
ActionBtn action_btns[] = { ActionBtn action_btns[] = {
{ 160, ds ? &ds->icon_copy : nullptr, has_sel }, { 188, ds ? &ds->icon_copy : nullptr, has_sel },
{ 188, ds ? &ds->icon_cut : nullptr, has_sel }, { 216, ds ? &ds->icon_cut : nullptr, has_sel },
{ 216, ds ? &ds->icon_paste : nullptr, has_clip }, { 244, ds ? &ds->icon_paste : nullptr, has_clip },
{ 244, ds ? &ds->icon_rename : nullptr, has_sel }, { 272, ds ? &ds->icon_rename : nullptr, has_sel },
{ 272, ds ? &ds->icon_folder_new : nullptr, { 300, ds ? &ds->icon_folder_new : nullptr,
!filemanager_is_computer_view(fm) && !filemanager_is_virtual_view(fm) }, !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++) { for (int i = 0; i < 6; i++) {
+1
View File
@@ -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_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_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_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_exec = svg_load("0:/icons/utilities-terminal.svg", 16, 16, defColor);
ds->icon_folder_lg = svg_load("0:/icons/folder.svg", 48, 48, defColor); ds->icon_folder_lg = svg_load("0:/icons/folder.svg", 48, 48, defColor);