feat(center-window/big-media): carousel player controls

This commit is contained in:
retrozinndev
2025-08-03 12:53:52 -03:00
parent 9eb40e032d
commit f553e3dfe4
+60 -107
View File
@@ -2,24 +2,61 @@ 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 = <Adw.Carousel orientation={Gtk.Orientation.HORIZONTAL} spacing={6}
onPageChanged={(self, num) => {
const page = self.get_nth_page(num);
if(page instanceof PlayerWidget && player.get().busName !== page.player.busName)
setPlayer(page.player);
}}>
<For each={availablePlayers.as(players => players.sort(pl =>
pl.busName === player.get().busName ? -1 : 1))}>
{(player: AstalMpris.Player) => <PlayerWidget player={player} />}
</For>
</Adw.Carousel> as Adw.Carousel;
return <Gtk.Box class={"big-media"} orientation={Gtk.Orientation.VERTICAL} widthRequest={255}
visible={player(pl => pl.available)}>
visible={variableToBoolean(availablePlayers)}>
<With value={player}>
{(player: AstalMpris.Player) => player.available &&
<Gtk.Box orientation={Gtk.Orientation.VERTICAL} hexpand>
{carousel}
<Gtk.Revealer revealChild={availablePlayers.as(pls => pls.length > 1)} transitionDuration={300}
transitionType={Gtk.RevealerTransitionType.SLIDE_UP}>
<Adw.CarouselIndicatorDots orientation={Gtk.Orientation.HORIZONTAL} carousel={carousel} />
</Gtk.Revealer>
</Gtk.Box> as Gtk.Box;
}
@register({ GTypeName: "PlayerWidget" })
class PlayerWidget extends Gtk.Box {
#player!: AstalMpris.Player;
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(
<Gtk.Revealer hexpand={false} revealChild={
createBinding(player, "artUrl").as(Boolean)
} transitionType={Gtk.RevealerTransitionType.SLIDE_LEFT} transitionDuration={300}>
@@ -27,13 +64,12 @@ export const BigMedia = () => {
<Gtk.Box class={"image"} css={createBinding(player, "artUrl").as((art) =>
`background-image: url("${pathToURI(art)}");`)}
hexpand={false} vexpand={false} widthRequest={132} heightRequest={128}
valign={Gtk.Align.START} halign={Gtk.Align.CENTER}>
<PlayerSelectButton player={player} halign={Gtk.Align.END}
valign={Gtk.Align.END} />
</Gtk.Box>
</Gtk.Revealer>
valign={Gtk.Align.START} halign={Gtk.Align.CENTER}
/>
</Gtk.Revealer> as Gtk.Revealer
);
this.append(
<Gtk.Box class={"info"} orientation={Gtk.Orientation.VERTICAL}
valign={Gtk.Align.CENTER} vexpand hexpand>
@@ -49,18 +85,10 @@ export const BigMedia = () => {
createBinding(player, "artist").as(artist => artist ?? "No Artist")
} ellipsize={Pango.EllipsizeMode.END} maxWidthChars={28}
/>
</Gtk.Box>
<Gtk.Box>
<With value={createBinding(player, "artUrl").as(Boolean)}>
{(hasAlbumArt) => !hasAlbumArt &&
<PlayerSelectButton player={player} reveal={true}
halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}
/>
}
</With>
</Gtk.Box>
</Gtk.Box> as Gtk.Box
);
this.append(
<Gtk.Box class={"progress"} hexpand visible={createBinding(player, "canSeek")}>
<Astal.Slider hexpand max={createBinding(player, "length").as(Math.floor)}
value={createBinding(player, "position").as(Math.floor)}
@@ -80,8 +108,10 @@ export const BigMedia = () => {
player.position = Math.floor(value));
}}
/>
</Gtk.Box>
</Gtk.Box> as Gtk.Box
);
this.append(
<Gtk.CenterBox class={"bottom"} hexpand marginBottom={6}>
<Gtk.Label class={"elapsed"} xalign={0} yalign={0}
halign={Gtk.Align.START} label={createBinding(player, "position").as(pos => {
@@ -153,88 +183,11 @@ export const BigMedia = () => {
: "0:00";
})} $type="end"
/>
</Gtk.CenterBox>
</Gtk.Box>
}
</With>
</Gtk.Box> as Gtk.Box;
}
export function PlayerSelectButton({ player, reveal, halign = Gtk.Align.CENTER, valign = Gtk.Align.CENTER }: {
player: AstalMpris.Player,
reveal?: Accessor<boolean>|boolean,
halign?: Gtk.Align;
valign?: Gtk.Align;
}) {
const availablePlayers = createBinding(AstalMpris.get_default(), "players").as(players =>
players.filter(p => p.available));
return <Gtk.Box vexpand={false} valign={valign} halign={halign}>
<With value={availablePlayers.as(apls => apls.length > 1)}>
{(show: boolean) => show &&
<Gtk.MenuButton halign={Gtk.Align.CENTER} hexpand
class={"player-select"} popover={
<Gtk.Popover class={"players-list"} hasArrow={false}>
<Gtk.Box orientation={Gtk.Orientation.VERTICAL}>
<For each={availablePlayers}>
{(pl: AstalMpris.Player) =>
<Gtk.Button class={"player"} onClicked={() => setPlayer(pl)}>
<Gtk.Box>
<Gtk.Image iconName={createBinding(pl, "busName").as(
getPlayerIconFromBusName
)} />
<Gtk.Label label={createBinding(pl, "identity")}
hexpand={false} class={"identity"} singleLineMode
maxWidthChars={8}
/>
</Gtk.Box>
</Gtk.Button>
}
</For>
</Gtk.Box>
</Gtk.Popover> as Gtk.Popover
} $={(self) => {
const controllerMotion = Gtk.EventControllerMotion.new();
self.add_controller(controllerMotion);
self.set_child(
<Gtk.Box class={"player"}>
<Gtk.Image iconName={createBinding(player, "busName").as(
getPlayerIconFromBusName)}
/>
<Gtk.Revealer transitionType={Gtk.RevealerTransitionType.SLIDE_LEFT}
transitionDuration={280} revealChild={reveal ?? createConnection(false,
[controllerMotion, "enter", () => {
self.add_css_class("reveal");
return true;
}],
[controllerMotion, "leave", () => {
self.remove_css_class("reveal");
return false;
}]
)
}>
<Gtk.Box>
<Gtk.Label label={createBinding(player, "identity")}
class={"identity"} maxWidthChars={6}
ellipsize={Pango.EllipsizeMode.END}
tooltipText={createBinding(player, "identity")}
/>
<Gtk.Image iconName={
createConnection("go-next-symbolic",
[self.popover, "show", () => "go-down-symbolic"],
[self.popover, "closed", () => "go-next-symbolic"]
)
} class={"arrow"} iconSize={Gtk.IconSize.NORMAL}
/>
</Gtk.Box>
</Gtk.Revealer>
</Gtk.Box> as Gtk.Box
</Gtk.CenterBox> as Gtk.CenterBox
);
}}
/>
}
</With>
</Gtk.Box>;
setPlayer(player: AstalMpris.Player) {
this.#player = player;
}
}