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 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 21:07:19 +02:00
parent fdf586305b
commit 743eca2051
+52 -17
View File
@@ -204,7 +204,9 @@ static constexpr int FOOTER_H = 84;
static constexpr int GRID_PAD = 12; static constexpr int GRID_PAD = 12;
static constexpr int CELL_GAP = 6; static constexpr int CELL_GAP = 6;
static constexpr int CELL_TARGET = 54; // desired tile edge in px 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 TAB_SIZE = 16;
static constexpr int CELL_SIZE = 26; // glyph font size inside a tile 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_hover = false;
static bool g_copy_pressed = false; static bool g_copy_pressed = false;
static int g_scroll = 0; // vertical scroll offset in px 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 uint64_t g_toast_until = 0;
static char g_toast[48] = {}; 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.w < CELL_TARGET) L.grid.w = CELL_TARGET;
if (L.grid.h < 1) L.grid.h = 1; 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; if (avail < CELL_TARGET) avail = CELL_TARGET;
L.cols = (avail + CELL_GAP) / (CELL_TARGET + CELL_GAP); L.cols = (avail + CELL_GAP) / (CELL_TARGET + CELL_GAP);
if (L.cols < 1) L.cols = 1; 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) { 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 (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++) { for (int i = 0; i < cur_set().count; i++) {
Rect r = tile_rect(L, i); Rect r = tile_rect(L, i);
if (r.contains(mx, my)) return 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); draw_centered_glyph(c, r, set.glyphs[i].byte, glyph, CELL_SIZE);
} }
// ---- Scrollbar ---- // ---- Scrollbar (shared MTK widget; no-ops when the set fits) ----
if (L.max_scroll > 0 && L.content_h > 0) { Rect sb_track = mtk::scrollbar_track_rect(L.grid);
int track_x = L.grid.x + L.grid.w - SCROLLBAR_W + 2; Rect sb_thumb = mtk::scrollbar_thumb_rect(sb_track, L.content_h, L.grid.h, g_scroll);
int track_h = L.grid.h; mtk::draw_scrollbar(c, sb_track, sb_thumb, g_sb_hover, g_sb_dragging, g_theme);
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);
}
// ---- Tab bar (opaque) — canonical MontaukOS top-tab style, matching the // ---- Tab bar (opaque) — canonical MontaukOS top-tab style, matching the
// Desktop Settings panel (surface bar, window_bg active tab, accent // 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; 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. // Wheel scroll.
if (ev.mouse.scroll != 0) { if (ev.mouse.scroll != 0) {
g_scroll -= ev.mouse.scroll * (L.cell_h + CELL_GAP) / 2; 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; 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); int tile = hit_tile(L, mx, my);
if (tile != g_hover) { g_hover = tile; redraw = true; } 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); bool copy_hov = L.copy_btn.contains(mx, my);
if (copy_hov != g_copy_hover) { g_copy_hover = copy_hov; redraw = true; } 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 (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_set = tab;
g_sel = 0; g_sel = 0;
g_scroll = 0; g_scroll = 0;
@@ -573,6 +606,8 @@ extern "C" void _start() {
if (ev.type == 3) break; // close if (ev.type == 3) break; // close
if (ev.type == 2 || ev.type == 4) { // resize / scale if (ev.type == 2 || ev.type == 4) { // resize / scale
g_hover = -1; g_hover = -1;
g_sb_hover = false;
g_sb_dragging = false;
redraw = true; redraw = true;
} else if (ev.type == 1) { } else if (ev.type == 1) {
redraw = handle_mouse(ev, win.width, win.height); redraw = handle_mouse(ev, win.width, win.height);