import { createBinding, For } from "ags"; import { register } from "ags/gobject"; import { Astal, Gtk } from "ags/gtk4"; import { Clipboard } from "../../../modules/clipboard"; import { pathToURI, variableToBoolean } from "../../../modules/utils"; import { tr } from "../../../i18n/intl"; import Media from "../../../modules/media"; import AstalMpris from "gi://AstalMpris"; import Pango from "gi://Pango?version=1.0"; import Adw from "gi://Adw?version=1"; import GLib from "gi://GLib?version=2.0"; export const BigMedia = () => { 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 && Media.getDefault().player.busName !== page.player.busName) Media.getDefault().player = page.player; }}> players.sort(pl => pl.busName === Media.getDefault().player.busName ? -1 : 1))}> {(player: AstalMpris.Player) => } as Adw.Carousel; return {carousel} pls.length > 1)} transitionDuration={300} transitionType={Gtk.RevealerTransitionType.SLIDE_UP}> as Gtk.Box; } @register({ GTypeName: "PlayerWidget" }) class PlayerWidget extends Gtk.Box { #player!: AstalMpris.Player; #copyClickTimeout?: GLib.Source; #dragTimer?: GLib.Source; get player() { return this.#player; } constructor({ player }: { player: AstalMpris.Player }) { super(); 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 ?? tr("media.no_title")) } label={ createBinding(player, "title").as(title => title ?? tr("media.no_title")) } ellipsize={Pango.EllipsizeMode.END} maxWidthChars={25} /> artist ?? tr("media.no_artist")) } label={ createBinding(player, "artist").as(artist => artist ?? tr("media.no_artist")) } ellipsize={Pango.EllipsizeMode.END} maxWidthChars={28} /> as Gtk.Box ); this.append( { if(type == null) return; if(!this.#dragTimer) { this.#dragTimer = setTimeout(() => player.position = Math.floor(value) , 200); return; } this.#dragTimer?.destroy(); this.#dragTimer = setTimeout(() => player.position = Math.floor(value) , 200); }} /> 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 = Media.accessMediaUrl(player).get(); // a widget that supports adding multiple icons and allows switching // through them would be pretty nice!! (i'll probably do this later) url && Clipboard.getDefault().copyAsync(url).then(() => { if(this.#copyClickTimeout && !this.#copyClickTimeout.is_destroyed()) this.#copyClickTimeout.destroy(); (self.get_child() as Gtk.Stack).set_visible_child_name("done-icon"); this.#copyClickTimeout = setTimeout(() => { (self.get_child() as Gtk.Stack).set_visible_child_name("copy-icon"); this.#copyClickTimeout!.destroy(); this.#copyClickTimeout = undefined; }, 1100); }).catch(() => { if(this.#copyClickTimeout && !this.#copyClickTimeout.is_destroyed()) this.#copyClickTimeout.destroy(); (self.get_child() as Gtk.Stack).set_visible_child_name("error-icon"); this.#copyClickTimeout = setTimeout(() => { (self.get_child() as Gtk.Stack).set_visible_child_name("copy-icon"); this.#copyClickTimeout!.destroy(); this.#copyClickTimeout = undefined; }, 900); }); }}> as Gtk.Widget } /> as Gtk.Widget } /> as Gtk.Widget } /> 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 ? tr("media.shuffle") : tr("media.follow_order"))} onClicked={() => player.shuffle()} /> player.canGoPrevious && player.previous()} /> status === AstalMpris.PlaybackStatus.PLAYING ? tr("media.pause") : tr("media.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 tr("media.song_loop"); if(status === AstalMpris.Loop.PLAYLIST) return tr("media.loop"); return tr("media.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; } }