From 743eca2051086898c18e5a7e35da1d40959a5c08 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Mon, 6 Jul 2026 21:07:19 +0200 Subject: [PATCH] feat: use the shared mtk scrollbar in charmap Replace charmap's hand-drawn scrollbar with the mtk scrollbar widgets (scrollbar_track_rect / scrollbar_thumb_rect / draw_scrollbar), matching the Devices and Music apps: standard 12px track colored SCROLLBAR_BG/FG with a draggable thumb. Adds thumb drag, track-click-to-jump, and hover highlight (mirroring devexplorer's handling); the tile grid reserves an SB_GUTTER on the right sized from mtk::SCROLLBAR_W. The bar auto-hides when the set fits. Co-Authored-By: Claude Opus 4.8 --- programs/src/charmap/main.cpp | 69 ++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/programs/src/charmap/main.cpp b/programs/src/charmap/main.cpp index a8f3e11..9e5a601 100644 --- a/programs/src/charmap/main.cpp +++ b/programs/src/charmap/main.cpp @@ -204,7 +204,9 @@ static constexpr int FOOTER_H = 84; static constexpr int GRID_PAD = 12; static constexpr int CELL_GAP = 6; static constexpr int CELL_TARGET = 54; // desired tile edge in px -static constexpr int SCROLLBAR_W = 8; +// Gutter reserved on the right of the tile area for the shared MTK scrollbar +// (its 12px track plus a little breathing room from the last tile column). +static constexpr int SB_GUTTER = mtk::SCROLLBAR_W + 6; static constexpr int TAB_SIZE = 16; static constexpr int CELL_SIZE = 26; // glyph font size inside a tile @@ -223,6 +225,9 @@ static int g_hover = -1; // hovered glyph index, or -1 static bool g_copy_hover = false; static bool g_copy_pressed = false; static int g_scroll = 0; // vertical scroll offset in px +static bool g_sb_hover = false; // pointer over the scrollbar track +static bool g_sb_dragging = false; // dragging the scrollbar thumb +static int g_sb_drag_offset = 0; // grab point within the thumb static uint64_t g_toast_until = 0; static char g_toast[48] = {}; @@ -255,7 +260,7 @@ static Layout compute_layout(int w, int h) { if (L.grid.w < CELL_TARGET) L.grid.w = CELL_TARGET; if (L.grid.h < 1) L.grid.h = 1; - int avail = L.grid.w - SCROLLBAR_W; + int avail = L.grid.w - SB_GUTTER; if (avail < CELL_TARGET) avail = CELL_TARGET; L.cols = (avail + CELL_GAP) / (CELL_TARGET + CELL_GAP); if (L.cols < 1) L.cols = 1; @@ -286,7 +291,7 @@ static Rect tile_rect(const Layout& L, int i) { static int hit_tile(const Layout& L, int mx, int my) { if (my < L.grid.y || my >= L.grid.y + L.grid.h) return -1; - if (mx < L.grid.x || mx >= L.grid.x + L.grid.w - SCROLLBAR_W) return -1; + if (mx < L.grid.x || mx >= L.grid.x + L.grid.w - SB_GUTTER) return -1; for (int i = 0; i < cur_set().count; i++) { Rect r = tile_rect(L, i); if (r.contains(mx, my)) return i; @@ -380,16 +385,10 @@ static void render(Canvas& c) { draw_centered_glyph(c, r, set.glyphs[i].byte, glyph, CELL_SIZE); } - // ---- Scrollbar ---- - if (L.max_scroll > 0 && L.content_h > 0) { - int track_x = L.grid.x + L.grid.w - SCROLLBAR_W + 2; - int track_h = L.grid.h; - int thumb_h = track_h * L.grid.h / L.content_h; - if (thumb_h < 24) thumb_h = 24; - int thumb_y = L.grid.y + (track_h - thumb_h) * g_scroll / L.max_scroll; - c.fill_rounded_rect(track_x, thumb_y, SCROLLBAR_W - 4, thumb_h, - (SCROLLBAR_W - 4) / 2, g_theme.border); - } + // ---- Scrollbar (shared MTK widget; no-ops when the set fits) ---- + Rect sb_track = mtk::scrollbar_track_rect(L.grid); + Rect sb_thumb = mtk::scrollbar_thumb_rect(sb_track, L.content_h, L.grid.h, g_scroll); + mtk::draw_scrollbar(c, sb_track, sb_thumb, g_sb_hover, g_sb_dragging, g_theme); // ---- Tab bar (opaque) — canonical MontaukOS top-tab style, matching the // Desktop Settings panel (surface bar, window_bg active tab, accent @@ -457,6 +456,15 @@ static bool handle_mouse(const montauk::abi::WinEvent& ev, int w, int h) { int mx = ev.mouse.x, my = ev.mouse.y; + bool left_down = (ev.mouse.buttons & 1) != 0; + bool pressed = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1); + bool released = !(ev.mouse.buttons & 1) && (ev.mouse.prev_buttons & 1); + + // Scrollbar geometry (shared MTK widget). + Rect sb_track = mtk::scrollbar_track_rect(L.grid); + Rect sb_thumb = mtk::scrollbar_thumb_rect(sb_track, L.content_h, L.grid.h, g_scroll); + bool scrollable = !sb_track.empty() && !sb_thumb.empty(); + // Wheel scroll. if (ev.mouse.scroll != 0) { g_scroll -= ev.mouse.scroll * (L.cell_h + CELL_GAP) / 2; @@ -464,6 +472,21 @@ static bool handle_mouse(const montauk::abi::WinEvent& ev, int w, int h) { redraw = true; } + // Release ends any active thumb drag. + if (released && g_sb_dragging) { g_sb_dragging = false; redraw = true; } + + // Active drag follows the pointer. + if (g_sb_dragging && left_down && scrollable) { + g_scroll = mtk::scrollbar_offset_from_thumb_top( + sb_track, L.content_h, L.grid.h, sb_thumb.h, my - g_sb_drag_offset); + clamp_scroll(L); + redraw = true; + } + + // Thumb hover highlight (only meaningful when the set scrolls). + bool sb_hover_now = scrollable && sb_track.contains(mx, my); + if (sb_hover_now != g_sb_hover) { g_sb_hover = sb_hover_now; redraw = true; } + int tile = hit_tile(L, mx, my); if (tile != g_hover) { g_hover = tile; redraw = true; } @@ -472,11 +495,21 @@ static bool handle_mouse(const montauk::abi::WinEvent& ev, int w, int h) { bool copy_hov = L.copy_btn.contains(mx, my); if (copy_hov != g_copy_hover) { g_copy_hover = copy_hov; redraw = true; } - bool pressed = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1); - bool released = !(ev.mouse.buttons & 1) && (ev.mouse.prev_buttons & 1); - if (pressed) { - if (tab >= 0 && tab != g_set) { + if (scrollable && sb_thumb.contains(mx, my)) { + // Grab the thumb. + g_sb_dragging = true; + g_sb_drag_offset = my - sb_thumb.y; + redraw = true; + } else if (scrollable && sb_track.contains(mx, my)) { + // Click the track: jump the thumb under the pointer, then drag. + g_sb_dragging = true; + g_sb_drag_offset = sb_thumb.h / 2; + g_scroll = mtk::scrollbar_offset_from_thumb_top( + sb_track, L.content_h, L.grid.h, sb_thumb.h, my - sb_thumb.h / 2); + clamp_scroll(L); + redraw = true; + } else if (tab >= 0 && tab != g_set) { g_set = tab; g_sel = 0; g_scroll = 0; @@ -573,6 +606,8 @@ extern "C" void _start() { if (ev.type == 3) break; // close if (ev.type == 2 || ev.type == 4) { // resize / scale g_hover = -1; + g_sb_hover = false; + g_sb_dragging = false; redraw = true; } else if (ev.type == 1) { redraw = handle_mouse(ev, win.width, win.height);