feat: filesystem and Files app support for >64 file entries, 64-bit sizes, async ops, GUI toolkit improvements to devexplorer app
This commit is contained in:
@@ -16,6 +16,7 @@ int g_win_h = INIT_H;
|
||||
|
||||
DevExplorerState g_state;
|
||||
TrueTypeFont* g_font = nullptr;
|
||||
mtk::Theme g_theme;
|
||||
|
||||
static void refresh_devices(DevExplorerState* de) {
|
||||
if (de == nullptr) return;
|
||||
@@ -63,6 +64,71 @@ int build_display_rows(DevExplorerState* de, DisplayRow* rows) {
|
||||
return count;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Scroll metrics
|
||||
// ============================================================================
|
||||
|
||||
// Computes pixel-space scroll geometry and clamps de->scroll_y so the list can
|
||||
// never scroll past its content. Shared by render.cpp and the input handlers.
|
||||
ScrollMetrics compute_scroll_metrics(DevExplorerState* de,
|
||||
const DisplayRow* rows, int row_count) {
|
||||
ScrollMetrics m;
|
||||
m.list_y = TOOLBAR_H;
|
||||
m.list_h = g_win_h - m.list_y;
|
||||
if (m.list_h < 0) m.list_h = 0;
|
||||
|
||||
int total_h = 0;
|
||||
for (int i = 0; i < row_count; i++)
|
||||
total_h += (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||
m.total_h = total_h;
|
||||
|
||||
int max_scroll_px = total_h - m.list_h;
|
||||
if (max_scroll_px < 0) max_scroll_px = 0;
|
||||
m.max_scroll_px = max_scroll_px;
|
||||
|
||||
if (de->scroll_y < 0) de->scroll_y = 0;
|
||||
if (de->scroll_y > row_count) de->scroll_y = row_count;
|
||||
|
||||
int scroll_px = 0;
|
||||
for (int i = 0; i < de->scroll_y && i < row_count; i++)
|
||||
scroll_px += (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||
|
||||
// If the first visible row would push content past the end, snap scroll_y
|
||||
// back to the furthest row that keeps the list fully populated.
|
||||
if (scroll_px > max_scroll_px) {
|
||||
de->scroll_y = 0;
|
||||
int acc = 0;
|
||||
for (int i = 0; i < row_count; i++) {
|
||||
int rh = (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||
if (acc + rh > max_scroll_px) break;
|
||||
acc += rh;
|
||||
de->scroll_y = i + 1;
|
||||
}
|
||||
scroll_px = acc;
|
||||
}
|
||||
|
||||
m.scroll_px = scroll_px;
|
||||
return m;
|
||||
}
|
||||
|
||||
// Maps a target pixel offset (from the dragged thumb) back to the nearest row
|
||||
// index, keeping scroll_y row-aligned like the keyboard and wheel navigation.
|
||||
void set_scroll_from_px(DevExplorerState* de, const DisplayRow* rows,
|
||||
int row_count, int target_px) {
|
||||
if (target_px < 0) target_px = 0;
|
||||
|
||||
int acc = 0;
|
||||
int best_k = 0;
|
||||
int best_diff = target_px; // distance for scroll_y == 0
|
||||
for (int i = 0; i < row_count; i++) {
|
||||
int rh = (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||
acc += rh;
|
||||
int diff = gui_abs(acc - target_px);
|
||||
if (diff < best_diff) { best_diff = diff; best_k = i + 1; }
|
||||
}
|
||||
de->scroll_y = best_k;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Toolbar hit testing
|
||||
// ============================================================================
|
||||
@@ -70,10 +136,7 @@ int build_display_rows(DevExplorerState* de, DisplayRow* rows) {
|
||||
static bool handle_toolbar_click(int mx, int my) {
|
||||
if (my >= TOOLBAR_H) return false;
|
||||
|
||||
int btn_w = 80, btn_h = 26;
|
||||
int btn_x = 8;
|
||||
int btn_y = (TOOLBAR_H - btn_h) / 2;
|
||||
if (mx >= btn_x && mx < btn_x + btn_w && my >= btn_y && my < btn_y + btn_h) {
|
||||
if (refresh_button_rect().contains(mx, my)) {
|
||||
g_state.last_poll_ms = 0; // force refresh
|
||||
return true;
|
||||
}
|
||||
@@ -127,6 +190,92 @@ static bool handle_list_click(int mx, int my) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mouse handling (wheel, draggable scrollbar, clicks)
|
||||
// ============================================================================
|
||||
|
||||
static bool handle_main_mouse(const Montauk::WinEvent& ev) {
|
||||
auto& de = g_state;
|
||||
int mx = ev.mouse.x;
|
||||
int my = ev.mouse.y;
|
||||
bool changed = false;
|
||||
|
||||
bool left_down = (ev.mouse.buttons & 1) != 0;
|
||||
bool just_clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
|
||||
bool just_released = !(ev.mouse.buttons & 1) && (ev.mouse.prev_buttons & 1);
|
||||
|
||||
de.mouse_x = mx;
|
||||
de.mouse_y = my;
|
||||
|
||||
DisplayRow rows[MAX_DISPLAY_ROWS];
|
||||
int row_count = build_display_rows(&de, rows);
|
||||
ScrollMetrics m = compute_scroll_metrics(&de, rows, row_count);
|
||||
|
||||
Rect viewport = { 0, m.list_y, g_win_w, m.list_h };
|
||||
Rect track = mtk::scrollbar_track_rect(viewport);
|
||||
Rect thumb = mtk::scrollbar_thumb_rect(track, m.total_h, m.list_h, m.scroll_px);
|
||||
|
||||
// Mouse wheel scrolls by rows.
|
||||
if (ev.mouse.scroll != 0) {
|
||||
de.scroll_y += ev.mouse.scroll;
|
||||
if (de.scroll_y < 0) de.scroll_y = 0;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Release ends any active drag.
|
||||
if (just_released && de.sb_dragging) {
|
||||
de.sb_dragging = false;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Active drag follows the pointer even if it leaves the track horizontally.
|
||||
if (de.sb_dragging && left_down && !track.empty() && !thumb.empty()) {
|
||||
int new_top = my - de.sb_drag_offset;
|
||||
int target_px = mtk::scrollbar_offset_from_thumb_top(
|
||||
track, m.total_h, m.list_h, thumb.h, new_top);
|
||||
set_scroll_from_px(&de, rows, row_count, target_px);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Hover highlight on the thumb; only redraw when it changes. The track is
|
||||
// only interactive when the list actually scrolls (thumb is present).
|
||||
bool scrollable = !track.empty() && !thumb.empty();
|
||||
bool hover_now = scrollable && track.contains(mx, my);
|
||||
if (hover_now != de.sb_hover) {
|
||||
de.sb_hover = hover_now;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Hover highlight on the Refresh button.
|
||||
bool btn_hover_now = refresh_button_rect().contains(mx, my);
|
||||
if (btn_hover_now != de.btn_hover) {
|
||||
de.btn_hover = btn_hover_now;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (just_clicked) {
|
||||
if (scrollable && thumb.contains(mx, my)) {
|
||||
// Grab the thumb.
|
||||
de.sb_dragging = true;
|
||||
de.sb_drag_offset = my - thumb.y;
|
||||
changed = true;
|
||||
} else if (scrollable && track.contains(mx, my)) {
|
||||
// Click on the track: center the thumb under the pointer and drag.
|
||||
de.sb_dragging = true;
|
||||
de.sb_drag_offset = thumb.h / 2;
|
||||
int new_top = my - de.sb_drag_offset;
|
||||
int target_px = mtk::scrollbar_offset_from_thumb_top(
|
||||
track, m.total_h, m.list_h, thumb.h, new_top);
|
||||
set_scroll_from_px(&de, rows, row_count, target_px);
|
||||
changed = true;
|
||||
} else if (handle_toolbar_click(mx, my) || handle_list_click(mx, my)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Key handling
|
||||
// ============================================================================
|
||||
@@ -205,15 +354,15 @@ extern "C" void _start() {
|
||||
for (int i = 0; i < NUM_CATEGORIES; i++)
|
||||
g_state.collapsed[i] = false;
|
||||
|
||||
// Load font
|
||||
{
|
||||
TrueTypeFont* f = (TrueTypeFont*)montauk::malloc(sizeof(TrueTypeFont));
|
||||
if (f) {
|
||||
montauk::memset(f, 0, sizeof(TrueTypeFont));
|
||||
if (!f->init("0:/fonts/Roboto-Medium.ttf")) { montauk::mfree(f); f = nullptr; }
|
||||
}
|
||||
g_font = f;
|
||||
}
|
||||
// Load fonts. fonts::init() loads the shared system font (Roboto-Medium at
|
||||
// UI_SIZE 18) which both the device list (via g_font) and the MTK widgets
|
||||
// (button, scrollbar) render with, so point g_font at it to avoid a second
|
||||
// copy of the same face.
|
||||
gui::fonts::init();
|
||||
g_font = gui::fonts::system_font;
|
||||
|
||||
// Theme for MTK widgets, built once from the user's accent color.
|
||||
g_theme = mtk::make_theme();
|
||||
|
||||
// Initial device poll
|
||||
refresh_devices(&g_state);
|
||||
@@ -290,20 +439,8 @@ extern "C" void _start() {
|
||||
|
||||
// Mouse
|
||||
if (ev.type == 1) {
|
||||
int mx = ev.mouse.x;
|
||||
int my = ev.mouse.y;
|
||||
bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
|
||||
|
||||
if (ev.mouse.scroll != 0) {
|
||||
g_state.scroll_y += ev.mouse.scroll;
|
||||
if (g_state.scroll_y < 0) g_state.scroll_y = 0;
|
||||
if (handle_main_mouse(ev))
|
||||
redraw_main = true;
|
||||
}
|
||||
|
||||
if (clicked) {
|
||||
if (handle_toolbar_click(mx, my) || handle_list_click(mx, my))
|
||||
redraw_main = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (redraw_main) { render(pixels); win.present(); }
|
||||
|
||||
Reference in New Issue
Block a user