[AGS] Bar: Done (WiFi still missing, will be added at some later point)
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
$fg-color: #{"@theme_fg_color"};
|
||||
$bg-color: #{"@theme_bg_color"};
|
||||
|
||||
box.players-box {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
box.player {
|
||||
padding: 0.6rem;
|
||||
|
||||
.cover-art {
|
||||
min-width: 100px;
|
||||
min-height: 100px;
|
||||
border-radius: 9px;
|
||||
margin-right: 0.6rem;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
scale {
|
||||
padding: 0;
|
||||
margin: 0.4rem 0;
|
||||
border-radius: 20px;
|
||||
|
||||
trough {
|
||||
min-height: 8px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
highlight {
|
||||
background-color: $fg-color;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
slider {
|
||||
all: unset;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
centerbox.actions {
|
||||
min-width: 220px;
|
||||
|
||||
button {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
padding: 0.4rem;
|
||||
margin: 0 0.2rem;
|
||||
}
|
||||
}
|
||||
}
|
154
config/astal/components/QuickActions/modules/Player/Player.tsx
Normal file
154
config/astal/components/QuickActions/modules/Player/Player.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import { bind } from "astal";
|
||||
import { Gtk } from "astal/gtk4";
|
||||
import AstalMpris from "gi://AstalMpris";
|
||||
import Pango from "gi://Pango?version=1.0";
|
||||
const ALIGN = Gtk.Align;
|
||||
|
||||
const mpris = AstalMpris.get_default();
|
||||
mpris.connect("player-added", p => {
|
||||
print("Player added:", p);
|
||||
});
|
||||
|
||||
const PlayerModule = () => {
|
||||
return (
|
||||
<box vertical cssClasses={ [ 'players-box' ] }>
|
||||
<label label={"Music Players"} halign={ALIGN.CENTER} cssClasses={[ 'title-2' ]}></label>
|
||||
<Gtk.Separator marginTop={3} marginBottom={5}></Gtk.Separator>
|
||||
<box cssClasses={["players"]}>
|
||||
{bind(mpris, "players").as(players => {
|
||||
return players.map(player => {
|
||||
return <PlayerItem player={player}></PlayerItem>;
|
||||
});
|
||||
})}
|
||||
</box>
|
||||
<label label={"No playback active"} visible={bind(mpris, "players").as( players => players.length === 0 )}></label>
|
||||
</box>
|
||||
);
|
||||
};
|
||||
|
||||
// TODO: Update widths
|
||||
const pbStatus = AstalMpris.PlaybackStatus;
|
||||
const PlayerItem = ({ player }: { player: AstalMpris.Player }) => {
|
||||
return (
|
||||
<box cssClasses={["player"]}>
|
||||
<image
|
||||
cssClasses={["cover-art"]}
|
||||
file={bind(player, "coverArt")}
|
||||
hexpand
|
||||
vexpand
|
||||
></image>
|
||||
<box vertical>
|
||||
<label
|
||||
label={bind(player, "title").as(
|
||||
title => title ?? "Unknown title",
|
||||
)}
|
||||
cssClasses={["title"]}
|
||||
halign={ALIGN.START}
|
||||
valign={ALIGN.START}
|
||||
maxWidthChars={30}
|
||||
ellipsize={Pango.EllipsizeMode.END}
|
||||
></label>
|
||||
<label
|
||||
label={bind(player, "artist").as(
|
||||
artist => artist ?? "Unknown artist",
|
||||
)}
|
||||
halign={ALIGN.START}
|
||||
valign={ALIGN.START}
|
||||
maxWidthChars={30}
|
||||
ellipsize={Pango.EllipsizeMode.END}
|
||||
></label>
|
||||
<slider
|
||||
visible={bind(player, "length").as(l => l > 0)}
|
||||
value={bind(player, "position")}
|
||||
min={0}
|
||||
max={bind(player, "length")}
|
||||
onChangeValue={v =>
|
||||
player.set_position(v.get_value())
|
||||
}
|
||||
></slider>
|
||||
<centerbox
|
||||
cssClasses={["actions"]}
|
||||
startWidget={
|
||||
<label
|
||||
label={bind(player, "position").as(v =>
|
||||
secondsToFriendlyTime(v),
|
||||
)}
|
||||
hexpand
|
||||
cssClasses={["position"]}
|
||||
></label>
|
||||
}
|
||||
centerWidget={
|
||||
<box>
|
||||
<button
|
||||
visible={bind(player, "canGoPrevious")}
|
||||
child={
|
||||
<image
|
||||
iconName={
|
||||
"media-skip-backward-symbolic"
|
||||
}
|
||||
></image>
|
||||
}
|
||||
onClicked={() => player.previous()}
|
||||
></button>
|
||||
<button
|
||||
visible={bind(player, "canControl")}
|
||||
child={
|
||||
<image
|
||||
iconName={bind(
|
||||
player,
|
||||
"playbackStatus",
|
||||
).as(status => {
|
||||
if (status === pbStatus.PLAYING) {
|
||||
return "media-playback-pause-symbolic";
|
||||
} else {
|
||||
return "media-playback-start-symbolic";
|
||||
}
|
||||
})}
|
||||
></image>
|
||||
}
|
||||
onClicked={() => player.play_pause()}
|
||||
></button>
|
||||
<button
|
||||
visible={bind(player, "canGoNext")}
|
||||
child={
|
||||
<image
|
||||
iconName={"media-skip-forward-symbolic"}
|
||||
></image>
|
||||
}
|
||||
onClicked={() => player.next()}
|
||||
></button>
|
||||
</box>
|
||||
}
|
||||
endWidget={
|
||||
<label
|
||||
cssClasses={["length"]}
|
||||
hexpand
|
||||
label={bind(player, "length").as(v =>
|
||||
secondsToFriendlyTime(v),
|
||||
)}
|
||||
></label>
|
||||
}
|
||||
></centerbox>
|
||||
</box>
|
||||
</box>
|
||||
);
|
||||
};
|
||||
|
||||
const secondsToFriendlyTime = (time: number) => {
|
||||
const minutes = Math.floor(time / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const seconds = Math.floor(time % 60);
|
||||
if (hours > 0) {
|
||||
return `${hours}:${expandTime(minutes)}:${expandTime(seconds)}`;
|
||||
} else {
|
||||
return `${minutes}:${expandTime(seconds)}`;
|
||||
}
|
||||
};
|
||||
|
||||
const expandTime = (time: number): string => {
|
||||
return time < 10 ? `0${time}` : "" + time;
|
||||
};
|
||||
|
||||
export default {
|
||||
PlayerModule,
|
||||
};
|
Reference in New Issue
Block a user