feat: refresh button in Files, svg.hpp arc handling
This commit is contained in:
@@ -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;
|
||||
|
||||
+148
-12
@@ -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': {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 "?";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user