feat: MTK scrollbar widget, Wikipedia app improvements

This commit is contained in:
2026-05-14 16:10:42 +02:00
parent 44f6e6f212
commit 005bd33f32
3 changed files with 477 additions and 91 deletions
+116
View File
@@ -774,6 +774,122 @@ inline void draw_separator(Canvas& c, int x, int y, int w, const Theme& theme) {
c.hline(x, y, w, theme.border);
}
static constexpr int SCROLLBAR_W = 12;
static constexpr int SCROLLBAR_OUTER_PAD = 4;
static constexpr int SCROLLBAR_TRACK_PAD = 8;
static constexpr int SCROLLBAR_THUMB_INSET = 2;
static constexpr int SCROLLBAR_MIN_THUMB = 24;
inline bool scrollbar_needed(int content_extent, int view_extent) {
return content_extent > view_extent && view_extent > 0;
}
inline int scrollbar_max_offset(int content_extent, int view_extent) {
int max_offset = content_extent - view_extent;
return max_offset > 0 ? max_offset : 0;
}
inline Rect scrollbar_track_rect(const Rect& viewport,
int width = SCROLLBAR_W,
int outer_pad = SCROLLBAR_OUTER_PAD,
int track_pad = SCROLLBAR_TRACK_PAD) {
int h = gui_max(viewport.h - track_pad * 2, 0);
int usable_w = viewport.w - outer_pad;
if (usable_w <= 0 || h <= 0) return {0, 0, 0, 0};
int actual_w = gui_clamp(width, 1, usable_w);
return {
viewport.x + viewport.w - actual_w - outer_pad,
viewport.y + track_pad,
actual_w,
h
};
}
inline int scrollbar_thumb_extent(int content_extent,
int view_extent,
int track_extent,
int min_thumb = SCROLLBAR_MIN_THUMB) {
if (!scrollbar_needed(content_extent, view_extent) || track_extent <= 0)
return track_extent;
int thumb = (int)(((int64_t)view_extent * track_extent) / content_extent);
return gui_clamp(thumb, gui_min(min_thumb, track_extent), track_extent);
}
inline Rect scrollbar_thumb_rect(const Rect& track,
int content_extent,
int view_extent,
int offset,
int min_thumb = SCROLLBAR_MIN_THUMB,
int thumb_inset = SCROLLBAR_THUMB_INSET) {
if (track.empty() || !scrollbar_needed(content_extent, view_extent))
return {0, 0, 0, 0};
int max_offset = scrollbar_max_offset(content_extent, view_extent);
int thumb_h = scrollbar_thumb_extent(content_extent, view_extent,
track.h, min_thumb);
int travel = gui_max(track.h - thumb_h, 0);
int clamped_offset = gui_clamp(offset, 0, max_offset);
int thumb_y = track.y + (max_offset > 0
? (int)(((int64_t)clamped_offset * travel) / max_offset)
: 0);
int thumb_w = gui_max(track.w - thumb_inset * 2, 1);
return {track.x + thumb_inset, thumb_y, thumb_w, thumb_h};
}
inline int scrollbar_offset_from_thumb_top(const Rect& track,
int content_extent,
int view_extent,
int thumb_extent,
int thumb_top) {
int max_offset = scrollbar_max_offset(content_extent, view_extent);
int travel = gui_max(track.h - thumb_extent, 0);
if (track.empty() || max_offset <= 0 || travel <= 0)
return 0;
int rel = gui_clamp(thumb_top - track.y, 0, travel);
return (int)(((int64_t)rel * max_offset + travel / 2) / travel);
}
inline Color scrollbar_thumb_color(const Theme& theme,
bool hovered,
bool dragging) {
Color base = mix(theme.border, theme.text_muted, 54);
if (dragging) return mix(base, theme.accent, 118);
if (hovered) return mix(base, theme.accent, 42);
return base;
}
inline void draw_scrollbar(Canvas& c,
const Rect& track,
const Rect& thumb,
bool hovered,
bool dragging,
const Theme& theme) {
if (track.empty() || thumb.empty()) return;
if (hovered || dragging) {
int rail_w = track.w > 6 ? track.w - 6 : track.w;
if (rail_w < 1) rail_w = 1;
Rect rail = {
track.x + (track.w - rail_w) / 2,
track.y,
rail_w,
track.h
};
Color rail_fill = dragging
? mix(theme.window_bg, theme.accent, 34)
: mix(theme.window_bg, theme.text_muted, 24);
c.fill_rounded_rect(rail.x, rail.y, rail.w, rail.h,
gui_max(theme.radius_sm, rail.w / 2),
rail_fill);
}
Color thumb_fill = scrollbar_thumb_color(theme, hovered, dragging);
int radius = gui_max(theme.radius_sm, thumb.w / 2);
c.fill_rounded_rect(thumb.x, thumb.y, thumb.w, thumb.h, radius, thumb_fill);
}
inline Rect tab_rect(const Rect& bar, int count, int index) {
if (count <= 0) return {bar.x, bar.y, 0, bar.h};
int slot_w = bar.w / count;