From f553e3dfe4cbc14e7f10ecce66e0885f4dfb93c2 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Sun, 3 Aug 2025 12:53:52 -0300 Subject: [PATCH] :sparkles: feat(center-window/big-media): carousel player controls --- ags/widget/center-window/BigMedia.tsx | 385 +++++++++++--------------- 1 file changed, 169 insertions(+), 216 deletions(-) diff --git a/ags/widget/center-window/BigMedia.tsx b/ags/widget/center-window/BigMedia.tsx index 6c1bb95..804d450 100644 --- a/ags/widget/center-window/BigMedia.tsx +++ b/ags/widget/center-window/BigMedia.tsx @@ -2,239 +2,192 @@ import { timeout } from "ags/time"; import { Astal, Gtk } from "ags/gtk4"; import { Clipboard } from "../../scripts/clipboard"; import { getMediaUrl, player, setPlayer } from "../bar/Media"; -import { Accessor, createBinding, createConnection, For, With } from "ags"; -import { getPlayerIconFromBusName, pathToURI, variableToBoolean } from "../../scripts/utils"; +import { createBinding, For } from "ags"; +import { pathToURI, variableToBoolean } from "../../scripts/utils"; import AstalMpris from "gi://AstalMpris"; import AstalIO from "gi://AstalIO"; import Pango from "gi://Pango?version=1.0"; +import Adw from "gi://Adw?version=1"; +import { register } from "ags/gobject"; +let dragTimer: (AstalIO.Time|undefined); + export const BigMedia = () => { - let dragTimer: (AstalIO.Time|undefined); + const availablePlayers = createBinding(AstalMpris.get_default(), "players").as(pls => + pls.filter(p => p.available)); + + const carousel = { + const page = self.get_nth_page(num); + if(page instanceof PlayerWidget && player.get().busName !== page.player.busName) + setPlayer(page.player); + }}> + players.sort(pl => + pl.busName === player.get().busName ? -1 : 1))}> + + {(player: AstalMpris.Player) => } + + as Adw.Carousel; return pl.available)}> + visible={variableToBoolean(availablePlayers)}> - - {(player: AstalMpris.Player) => player.available && - + {carousel} + pls.length > 1)} transitionDuration={300} + transitionType={Gtk.RevealerTransitionType.SLIDE_UP}> - - - - `background-image: url("${pathToURI(art)}");`)} - hexpand={false} vexpand={false} widthRequest={132} heightRequest={128} - valign={Gtk.Align.START} halign={Gtk.Align.CENTER}> - - - - - - - - title ?? "No Title") - } label={ - createBinding(player, "title").as(title => title ?? "No Title") - } ellipsize={Pango.EllipsizeMode.END} maxWidthChars={25} - /> - artist ?? "No Artist") - } label={ - createBinding(player, "artist").as(artist => artist ?? "No Artist") - } ellipsize={Pango.EllipsizeMode.END} maxWidthChars={28} - /> - - - - - {(hasAlbumArt) => !hasAlbumArt && - - } - - - - - { - if(type === undefined || type === null) - return; - - if(!dragTimer) { - dragTimer = timeout(200, () => - player.position = Math.floor(value)); - - return; - } - - dragTimer.cancel(); - dragTimer = timeout(200, () => - player.position = Math.floor(value)); - }} - /> - - - - { - const sec = Math.floor(pos % 60); - return pos > 0 && player.length > 0 ? - `${Math.floor(pos / 60)}:${sec < 10 ? "0" : ""}${sec}` - : "0:00"; - })} $type="start" - /> - - - { - const url = getMediaUrl(player).get(); - url && Clipboard.getDefault().copyAsync(url); - }} - /> - - status !== AstalMpris.Shuffle.UNSUPPORTED)} iconName={ - createBinding(player, "shuffleStatus").as(status => status === AstalMpris.Shuffle.ON ? - "media-playlist-shuffle-symbolic" - : "media-playlist-consecutive-symbolic")} tooltipText={ - createBinding(player, "shuffleStatus").as(status => status === AstalMpris.Shuffle.ON ? - "Shuffle" - : "No shuffle")} onClicked={() => player.shuffle()} - /> - player.canGoPrevious && player.previous()} - /> - - status === AstalMpris.PlaybackStatus.PLAYING ? "Pause" : "Play")} - iconName={createBinding(player, "playbackStatus").as(status => - status === AstalMpris.PlaybackStatus.PLAYING ? - "media-playback-pause-symbolic" - : "media-playback-start-symbolic")} onClicked={() => player.play_pause()} - /> - player.canGoNext && player.next()} - /> - { - if(status === AstalMpris.Loop.TRACK) - return "media-playlist-repeat-song-symbolic"; - - if(status === AstalMpris.Loop.PLAYLIST) - return "media-playlist-repeat-symbolic"; - - return "loop-arrow-symbolic"; - })} visible={createBinding(player, "loopStatus").as(status => - status !== AstalMpris.Loop.UNSUPPORTED)} - tooltipText={createBinding(player, "loopStatus").as(status => { - if(status === AstalMpris.Loop.TRACK) - return "Loop song"; - - if(status === AstalMpris.Loop.PLAYLIST) - return "Loop playlist"; - - return "No loop"; - })} onClicked={() => player.loop()} - /> - - { /* bananananananana */ - const sec = Math.floor(len % 60); - return (len > 0 && Number.isFinite(len)) ? - `${Math.floor(len / 60)}:${sec < 10 ? "0" : ""}${sec}` - : "0:00"; - })} $type="end" - /> - - - } - + + as Gtk.Box; } -export function PlayerSelectButton({ player, reveal, halign = Gtk.Align.CENTER, valign = Gtk.Align.CENTER }: { - player: AstalMpris.Player, - reveal?: Accessor|boolean, - halign?: Gtk.Align; - valign?: Gtk.Align; -}) { - const availablePlayers = createBinding(AstalMpris.get_default(), "players").as(players => - players.filter(p => p.available)); +@register({ GTypeName: "PlayerWidget" }) +class PlayerWidget extends Gtk.Box { + #player!: AstalMpris.Player; - return - apls.length > 1)}> - {(show: boolean) => show && - - - - {(pl: AstalMpris.Player) => - setPlayer(pl)}> - - - - - - } - - - as Gtk.Popover - } $={(self) => { - const controllerMotion = Gtk.EventControllerMotion.new(); - self.add_controller(controllerMotion); + get player() { return this.#player; } - self.set_child( - - - { - self.add_css_class("reveal"); - return true; - }], - [controllerMotion, "leave", () => { - self.remove_css_class("reveal"); - return false; - }] - ) - }> + constructor({ player }: { player: AstalMpris.Player }) { + super(); - - - "go-down-symbolic"], - [self.popover, "closed", () => "go-next-symbolic"] - ) - } class={"arrow"} iconSize={Gtk.IconSize.NORMAL} - /> - - - as Gtk.Box - ); + this.setPlayer(player); + this.set_orientation(Gtk.Orientation.VERTICAL); + this.set_hexpand(true); + + this.append( + + + + `background-image: url("${pathToURI(art)}");`)} + hexpand={false} vexpand={false} widthRequest={132} heightRequest={128} + valign={Gtk.Align.START} halign={Gtk.Align.CENTER} + /> + as Gtk.Revealer + ); + + this.append( + + + title ?? "No Title") + } label={ + createBinding(player, "title").as(title => title ?? "No Title") + } ellipsize={Pango.EllipsizeMode.END} maxWidthChars={25} + /> + artist ?? "No Artist") + } label={ + createBinding(player, "artist").as(artist => artist ?? "No Artist") + } ellipsize={Pango.EllipsizeMode.END} maxWidthChars={28} + /> + as Gtk.Box + ); + + this.append( + + { + if(type === undefined || type === null) + return; + + if(!dragTimer) { + dragTimer = timeout(200, () => + player.position = Math.floor(value)); + + return; + } + + dragTimer.cancel(); + dragTimer = timeout(200, () => + player.position = Math.floor(value)); }} /> - } - - ; + as Gtk.Box + ); + + this.append( + + { + const sec = Math.floor(pos % 60); + return pos > 0 && player.length > 0 ? + `${Math.floor(pos / 60)}:${sec < 10 ? "0" : ""}${sec}` + : "0:00"; + })} $type="start" + /> + + + { + const url = getMediaUrl(player).get(); + url && Clipboard.getDefault().copyAsync(url); + }} + /> + + status !== AstalMpris.Shuffle.UNSUPPORTED)} iconName={ + createBinding(player, "shuffleStatus").as(status => status === AstalMpris.Shuffle.ON ? + "media-playlist-shuffle-symbolic" + : "media-playlist-consecutive-symbolic")} tooltipText={ + createBinding(player, "shuffleStatus").as(status => status === AstalMpris.Shuffle.ON ? + "Shuffle" + : "No shuffle")} onClicked={() => player.shuffle()} + /> + player.canGoPrevious && player.previous()} + /> + + status === AstalMpris.PlaybackStatus.PLAYING ? "Pause" : "Play")} + iconName={createBinding(player, "playbackStatus").as(status => + status === AstalMpris.PlaybackStatus.PLAYING ? + "media-playback-pause-symbolic" + : "media-playback-start-symbolic")} onClicked={() => player.play_pause()} + /> + player.canGoNext && player.next()} + /> + { + if(status === AstalMpris.Loop.TRACK) + return "media-playlist-repeat-song-symbolic"; + + if(status === AstalMpris.Loop.PLAYLIST) + return "media-playlist-repeat-symbolic"; + + return "loop-arrow-symbolic"; + })} visible={createBinding(player, "loopStatus").as(status => + status !== AstalMpris.Loop.UNSUPPORTED)} + tooltipText={createBinding(player, "loopStatus").as(status => { + if(status === AstalMpris.Loop.TRACK) + return "Loop song"; + + if(status === AstalMpris.Loop.PLAYLIST) + return "Loop playlist"; + + return "No loop"; + })} onClicked={() => player.loop()} + /> + + { /* bananananananana */ + const sec = Math.floor(len % 60); + return (len > 0 && Number.isFinite(len)) ? + `${Math.floor(len / 60)}:${sec < 10 ? "0" : ""}${sec}` + : "0:00"; + })} $type="end" + /> + as Gtk.CenterBox + ); + } + + setPlayer(player: AstalMpris.Player) { + this.#player = player; + } }