feat: add scroll bar to Music app

This commit is contained in:
2026-05-26 14:40:24 +02:00
parent 60c1e6858a
commit c1d3d05c34
+102 -10
View File
@@ -39,6 +39,8 @@ static constexpr int FONT_SIZE_LG = 20;
static constexpr int LIST_TOP = TOOLBAR_H; static constexpr int LIST_TOP = TOOLBAR_H;
static constexpr int LIST_ITEM_H = 32; 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 INFO_H = 56;
static constexpr int PROGRESS_H = 24; static constexpr int PROGRESS_H = 24;
static constexpr int TRANSPORT_H = 44; static constexpr int TRANSPORT_H = 44;
@@ -144,6 +146,13 @@ struct PlayerState {
// Progress dragging // Progress dragging
bool dragging_progress; 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) // Transport icons (dark for normal, white for active/colored backgrounds)
SvgIcon ico_rewind; SvgIcon ico_rewind;
SvgIcon ico_play; SvgIcon ico_play;
@@ -1047,6 +1056,26 @@ static int visible_items() {
return count > 0 ? count : 0; 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() { static Rect content_panel_rect() {
int y = list_bottom() + 1; int y = list_bottom() + 1;
return {0, LIST_TOP, g.win_w, y - LIST_TOP}; 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); px_hline(pixels, W, H, 0, list_end, W, BORDER_COLOR);
if (!g.show_visualizer) { 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++) { for (int i = 0; i < visible_count && (i + g.scroll_y) < g.filtered_count; i++) {
int idx = filtered_raw_at(i + g.scroll_y); int idx = filtered_raw_at(i + g.scroll_y);
if (idx < 0) break; if (idx < 0) break;
@@ -1149,7 +1182,7 @@ static void render(uint32_t* pixels) {
bg = g_list_hover; bg = g_list_hover;
if (bg.r != BG_COLOR.r || bg.g != BG_COLOR.g || bg.b != BG_COLOR.b) 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)) 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); 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"; const char* tag = g.files[idx].is_mp3 ? "MP3" : "WAV";
int tw = text_w(tag, FONT_SIZE_SM); 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); tag, DIM_TEXT, FONT_SIZE_SM);
} }
if (g.filtered_count > visible_count && visible_count > 0) { if (sb_vis) {
int list_h = list_end - LIST_TOP; Rect track, thumb;
int sb_h = (visible_count * list_h) / g.filtered_count; list_scrollbar_rects(W, &track, &thumb);
if (sb_h < 20) sb_h = 20; px_fill(pixels, W, H, track.x, track.y, track.w, track.h,
int sb_y = LIST_TOP + (g.scroll_y * (list_h - sb_h)) / (g.filtered_count - visible_count); Color::from_rgb(0xF0, 0xF0, 0xF0));
px_fill_rounded(pixels, W, H, W - 6, sb_y, 4, sb_h, 2, TRACK_BG); 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) { if (g.filtered_count == 0) {
@@ -1620,10 +1656,64 @@ extern "C" void _start() {
// Mouse // Mouse
if (ev.type == 1) { 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 clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
bool released = !(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)) if (handle_click(ev.mouse.x, ev.mouse.y))
redraw = true; redraw = true;
} }
@@ -1645,7 +1735,9 @@ extern "C" void _start() {
// Hover tracking for file list // Hover tracking for file list
int lb = list_bottom(); 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); int new_hover = filtered_raw_at((ev.mouse.y - LIST_TOP) / LIST_ITEM_H + g.scroll_y);
if (new_hover != g.hovered_item) { if (new_hover != g.hovered_item) {
g.hovered_item = new_hover; g.hovered_item = new_hover;