From c1d3d05c3469ee674ce37f517eaf23be9de82980 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Tue, 26 May 2026 14:40:24 +0200 Subject: [PATCH] feat: add scroll bar to Music app --- programs/src/music/main.cpp | 112 ++++++++++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 10 deletions(-) diff --git a/programs/src/music/main.cpp b/programs/src/music/main.cpp index 7ea799e..7014bfd 100644 --- a/programs/src/music/main.cpp +++ b/programs/src/music/main.cpp @@ -39,6 +39,8 @@ static constexpr int FONT_SIZE_LG = 20; static constexpr int LIST_TOP = TOOLBAR_H; static constexpr int LIST_ITEM_H = 32; +static constexpr int LIST_SB_W = 12; +static constexpr int LIST_SB_MIN_TH = 20; static constexpr int INFO_H = 56; static constexpr int PROGRESS_H = 24; static constexpr int TRANSPORT_H = 44; @@ -144,6 +146,13 @@ struct PlayerState { // Progress dragging bool dragging_progress; + // List scrollbar + bool sb_dragging; + int sb_drag_offset; + bool sb_hovered; + int mouse_x; + int mouse_y; + // Transport icons (dark for normal, white for active/colored backgrounds) SvgIcon ico_rewind; SvgIcon ico_play; @@ -1047,6 +1056,26 @@ static int visible_items() { return count > 0 ? count : 0; } +static bool list_scrollbar_visible() { + return g.filtered_count > visible_items() && visible_items() > 0; +} + +static void list_scrollbar_rects(int W, Rect* track, Rect* thumb) { + int list_h = list_bottom() - LIST_TOP; + int visible = visible_items(); + int tx = W - LIST_SB_W; + *track = {tx, LIST_TOP, LIST_SB_W, list_h}; + + int max_scroll = g.filtered_count - visible; + if (max_scroll < 1) max_scroll = 1; + int th = (visible * list_h) / g.filtered_count; + if (th < LIST_SB_MIN_TH) th = LIST_SB_MIN_TH; + if (th > list_h) th = list_h; + int range = list_h - th; + int ty = LIST_TOP + (g.scroll_y * range) / max_scroll; + *thumb = {tx + 1, ty, LIST_SB_W - 2, th}; +} + static Rect content_panel_rect() { int y = list_bottom() + 1; return {0, LIST_TOP, g.win_w, y - LIST_TOP}; @@ -1135,6 +1164,10 @@ static void render(uint32_t* pixels) { px_hline(pixels, W, H, 0, list_end, W, BORDER_COLOR); if (!g.show_visualizer) { + bool sb_vis = list_scrollbar_visible(); + int row_w = sb_vis ? (W - LIST_SB_W) : W; + int tag_right = sb_vis ? (W - LIST_SB_W) : W; + for (int i = 0; i < visible_count && (i + g.scroll_y) < g.filtered_count; i++) { int idx = filtered_raw_at(i + g.scroll_y); if (idx < 0) break; @@ -1149,7 +1182,7 @@ static void render(uint32_t* pixels) { bg = g_list_hover; if (bg.r != BG_COLOR.r || bg.g != BG_COLOR.g || bg.b != BG_COLOR.b) - px_fill(pixels, W, H, 0, iy, W, LIST_ITEM_H, bg); + px_fill(pixels, W, H, 0, iy, row_w, LIST_ITEM_H, bg); if (idx == g.selected_item && !(idx == g.current_track && g.play_state != PlayState::Stopped)) px_fill(pixels, W, H, 0, iy, 3, LIST_ITEM_H, g_accent); @@ -1168,16 +1201,19 @@ static void render(uint32_t* pixels) { const char* tag = g.files[idx].is_mp3 ? "MP3" : "WAV"; int tw = text_w(tag, FONT_SIZE_SM); - px_text(pixels, W, H, W - tw - 12, iy + (LIST_ITEM_H - fh_sm) / 2, + px_text(pixels, W, H, tag_right - tw - 12, iy + (LIST_ITEM_H - fh_sm) / 2, tag, DIM_TEXT, FONT_SIZE_SM); } - if (g.filtered_count > visible_count && visible_count > 0) { - int list_h = list_end - LIST_TOP; - int sb_h = (visible_count * list_h) / g.filtered_count; - if (sb_h < 20) sb_h = 20; - int sb_y = LIST_TOP + (g.scroll_y * (list_h - sb_h)) / (g.filtered_count - visible_count); - px_fill_rounded(pixels, W, H, W - 6, sb_y, 4, sb_h, 2, TRACK_BG); + if (sb_vis) { + Rect track, thumb; + list_scrollbar_rects(W, &track, &thumb); + px_fill(pixels, W, H, track.x, track.y, track.w, track.h, + Color::from_rgb(0xF0, 0xF0, 0xF0)); + Color tc = (g.sb_hovered || g.sb_dragging) + ? Color::from_rgb(0xA0, 0xA0, 0xA0) + : Color::from_rgb(0xC0, 0xC0, 0xC0); + px_fill(pixels, W, H, thumb.x, thumb.y, thumb.w, thumb.h, tc); } if (g.filtered_count == 0) { @@ -1620,10 +1656,64 @@ extern "C" void _start() { // Mouse if (ev.type == 1) { + g.mouse_x = ev.mouse.x; + g.mouse_y = ev.mouse.y; bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1); bool released = !(ev.mouse.buttons & 1) && (ev.mouse.prev_buttons & 1); + bool held = (ev.mouse.buttons & 1) != 0; - if (clicked) { + // List scrollbar drag/hover + bool sb_consume = false; + if (!g.show_visualizer && list_scrollbar_visible()) { + Rect sb_track, sb_thumb; + list_scrollbar_rects(g.win_w, &sb_track, &sb_thumb); + bool in_track = sb_track.contains(ev.mouse.x, ev.mouse.y); + + if (g.sb_dragging && held) { + int list_h = sb_track.h; + int range = list_h - sb_thumb.h; + int max_scroll = g.filtered_count - visible_items(); + if (range > 0 && max_scroll > 0) { + int new_ty = ev.mouse.y - g.sb_drag_offset; + int rel = new_ty - LIST_TOP; + if (rel < 0) rel = 0; + if (rel > range) rel = range; + int new_scroll = (rel * max_scroll + range / 2) / range; + if (new_scroll != g.scroll_y) { + g.scroll_y = new_scroll; + clamp_list_scroll(); + redraw = true; + } + } + sb_consume = true; + } else if (g.sb_dragging && released) { + g.sb_dragging = false; + redraw = true; + sb_consume = true; + } else if (clicked && in_track) { + if (sb_thumb.contains(ev.mouse.x, ev.mouse.y)) { + g.sb_dragging = true; + g.sb_drag_offset = ev.mouse.y - sb_thumb.y; + } else { + int visible = visible_items(); + int delta = visible > 1 ? visible - 1 : 1; + if (ev.mouse.y < sb_thumb.y) g.scroll_y -= delta; + else g.scroll_y += delta; + clamp_list_scroll(); + } + redraw = true; + sb_consume = true; + } + if (in_track != g.sb_hovered) { + g.sb_hovered = in_track; + redraw = true; + } + } else if (g.sb_hovered) { + g.sb_hovered = false; + redraw = true; + } + + if (clicked && !sb_consume) { if (handle_click(ev.mouse.x, ev.mouse.y)) redraw = true; } @@ -1645,7 +1735,9 @@ extern "C" void _start() { // Hover tracking for file list int lb = list_bottom(); - if (!g.show_visualizer && ev.mouse.y >= LIST_TOP && ev.mouse.y < lb) { + int hover_right = list_scrollbar_visible() ? (g.win_w - LIST_SB_W) : g.win_w; + if (!g.show_visualizer && ev.mouse.y >= LIST_TOP && ev.mouse.y < lb + && ev.mouse.x >= 0 && ev.mouse.x < hover_right) { int new_hover = filtered_raw_at((ev.mouse.y - LIST_TOP) / LIST_ITEM_H + g.scroll_y); if (new_hover != g.hovered_item) { g.hovered_item = new_hover;