feat(media): add basic implementation for progress fill on media widget - #3818
Conversation
|
Nice one |
[P2] Timer callback mutates the scene outside a UI phasesrc/shell/bar/widgets/media_widget.cpp:262 The one-shot timer invokes syncProgress() directly. Timers run from TimerPollSource::dispatch(), outside PrepareFrame, Update, or Layout. syncProgress() then calls ProgressBar::setProgress(), which immediately changes the retained clip geometry. This bypasses the repository’s phase contract: callbacks should schedule work; state synchronization belongs in Update. It also samples m_progressBar->width() while a layout may be pending. Have the timer callback call requestUpdate() only. Sample MPRIS state, apply progress, and rearm the timer from doUpdate(). [P2] Capsule padding math is invalid for capsule groupssrc/shell/bar/widgets/media_widget.cpp:200-205 shouldShowBarCapsule() is true for both single-widget and grouped capsules, but the code always expands the media fill by one capsule padding on each side. Group padding belongs around the entire group, not every member. Because widget roots do not clip children, a supported configuration such as capsule_padding = 6 and group widget_spacing = 2 makes the fill overlap the next widget by 4 px. Accordion groups have the opposite failure: their dedicated clip excludes capsule padding, so the expanded fill cannot reach the capsule edges. The fill also uses rootNode->height() rather than the bar-owned capsuleCross, so custom The fill needs bar-owned capsule/member bounds, or it must remain confined to the widget root until a group-aware overlay API exists. |
304d869 to
9fd9ea3
Compare
|
Grouped @ItsLemmy here are some testing validations 😄 |
There was a problem hiding this comment.
Pull request overview
Adds an opt-in playback-progress background fill to the bar media widget, using an underlaid ProgressBar plus a timer-driven update cadence to avoid per-frame ticking.
Changes:
- Add
show_progressoption for the media widget (hidden for vertical bars and album-art-only mode). - Render a non-layout, non-hit-testable
ProgressBarbehind the widget content and sync it to MPRIS position/duration. - Add an English translation entry for the new setting.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/shell/bar/widgets/media_widget.h | Adds showProgress option, ProgressBar pointer, and timer member; declares syncProgress(). |
| src/shell/bar/widgets/media_widget.cpp | Builds and lays out the progress fill; updates progress via syncProgress() and a one-shot Timer. |
| src/shell/bar/widgets/media_widget_definition.cpp | Exposes show_progress setting and constrains it to horizontal/non-art-only cases. |
| assets/translations/en.json | Adds label/description strings for the new setting. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| area->addChild( | ||
| ui::progressBar( | ||
| {.out = &m_progressBar, | ||
| .fill = withOpacity(widgetForegroundOr(colorSpecFromRole(ColorRole::Primary)), 0.25F), |
|
Thanks |



Summary
Adds an opt-in
show_progressoption to the bar's media widget. When enabled, the widget'sbackground fills left-to-right in proportion to the current track's playback position, so the pill
itself doubles as a progress indicator.
The fill is a
ProgressBarchild of the widget's own root, sized to the widget content and widenedby the capsule padding so it spans the pill. It is drawn at
zIndex(-1), opted out of layout, andnot hit-testable, so it changes nothing about the widget's measured size or input behavior.
Defaults to off and hidden on vertical bars and in album-art-only mode, where a rectangular fill behind
a circular album-art disc doesn't read well.
Motivation
The media widget shows what's playing but gives no sense of where you are in the track. Getting
that today means opening the control center. A background fill answers it without
consuming any extra bar width.
Type of Change
Related Issue
None — this wasn't tracked by an existing issue.
Testing
Manual testing against a live debug shell with Spotify and browser MPRIS sources:
/proc/<pid>/statsampling that noadditional wakeups occur while paused.
lengthUs == 0): fill hidden, no divide-by-zero, no full bar.extension is gated on
shouldShowBarCapsule()).Manual Coverage
Screenshots / Videos
With capsule

Checklist
CONTRIBUTING.md.just formatwith clang-format v22+ installed, or this PR has no code changes.assets/translations/en.json, or this PR adds no new user-facing strings.Additional Notes
Why a timer instead of a frame tick: The fill only moves in whole pixels, and the widget is
80–220 px wide, so one pixel of travel takes
trackLength / widgetWidth. Roughly 0.9 s for a3-minute track in a 200 px widget. Driving this from
needsFrameTick()would redraw ~60x more oftento produce an identical image. Instead
syncProgress()arms a one-shotTimerwithand re-arms it from its own callback, so the cadence tracks the current track length and widget
width. It is armed only while
playbackStatus == "Playing"with a known duration, andstop()s onpause, stop, and when the option is off. A paused track costs zero wakeups.
Reused existing primitives, no new ones.:
ProgressBar(src/ui/controls/progress_bar.h)already renders a full-size fill revealed through a clip node, which keeps the leading end's rounded
cap instead of squaring off at low progress. Position comes from
MprisService::activePlayer(),which already returns a wall-clock-projected snapshot, so there's no extra D-Bus traffic.
Fill color: routes through
widgetForegroundOr(...)at 25% opacity, matching howbattery_widget.cppandsysmon_widget.cpptint their fills, so a[widget.media] coloroverrideapplies to the fill as well as the text. Because
ColorSpecstores a role rather than a resolvedcolor, theme switches are picked up automatically by
ProgressBar'spaletteChanged()connection.No customization options: I wanted this PR as a base for the function and further customization options would be better suited for a new PR.