sidebar: media pane
This commit is contained in:
parent
62b6bd20c5
commit
17ddfc6fb1
4 changed files with 306 additions and 77 deletions
|
|
@ -13,6 +13,57 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin button {
|
||||||
|
@include lib.element-decel;
|
||||||
|
|
||||||
|
background-color: color.change(scheme.$surface1, $alpha: 0.5);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: color.change(scheme.$surface2, $alpha: 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: color.change(scheme.$overlay0, $alpha: 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: scheme.$subtext0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin button-active {
|
||||||
|
@include lib.element-decel;
|
||||||
|
|
||||||
|
background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 50%), $alpha: 0.5);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 30%), $alpha: 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 20%), $alpha: 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin media-button {
|
||||||
|
@include lib.element-decel;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: scheme.$overlay2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
color: color.mix(scheme.$subtext1, scheme.$subtext0, 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: scheme.$subtext0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
@include font.mono;
|
@include font.mono;
|
||||||
|
|
||||||
|
|
@ -155,21 +206,8 @@
|
||||||
margin-bottom: lib.s(5);
|
margin-bottom: lib.s(5);
|
||||||
font-size: lib.s(24);
|
font-size: lib.s(24);
|
||||||
|
|
||||||
& > * {
|
& > button {
|
||||||
@include lib.element-decel;
|
@include media-button;
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
color: scheme.$overlay0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
color: scheme.$subtext0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
color: scheme.$overlay2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,20 +257,10 @@
|
||||||
@include lib.spacing;
|
@include lib.spacing;
|
||||||
|
|
||||||
& > button {
|
& > button {
|
||||||
|
@include button;
|
||||||
@include lib.rounded(10);
|
@include lib.rounded(10);
|
||||||
@include lib.element-decel;
|
|
||||||
|
|
||||||
padding: lib.s(5) lib.s(10);
|
padding: lib.s(5) lib.s(10);
|
||||||
background-color: color.change(scheme.$surface1, $alpha: 0.5);
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
background-color: color.change(scheme.$surface2, $alpha: 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: color.change(scheme.$overlay0, $alpha: 0.5);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -288,6 +316,95 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.audio .media {
|
||||||
|
@include lib.spacing(40, true);
|
||||||
|
|
||||||
|
.cover-art {
|
||||||
|
@include lib.rounded(10);
|
||||||
|
@include lib.element-decel;
|
||||||
|
@include lib.shadow(scheme.$mantle, $blur: 5, $spread: 2);
|
||||||
|
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
min-width: lib.s(256);
|
||||||
|
min-height: lib.s(256);
|
||||||
|
font-size: lib.s(96);
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: scheme.$base;
|
||||||
|
color: scheme.$subtext0;
|
||||||
|
margin-top: lib.s(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
margin: 0 lib.s(40);
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
@include lib.rounded(8);
|
||||||
|
@include lib.fluent-decel(1000ms);
|
||||||
|
|
||||||
|
min-height: lib.s(15);
|
||||||
|
background-color: scheme.$overlay0;
|
||||||
|
color: scheme.$subtext1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
margin-top: lib.s(5);
|
||||||
|
font-size: lib.s(13);
|
||||||
|
color: scheme.$subtext1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
font-size: lib.s(14);
|
||||||
|
margin-top: lib.s(20);
|
||||||
|
|
||||||
|
@include lib.spacing(3, true);
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: lib.s(18);
|
||||||
|
color: scheme.$text;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.artist {
|
||||||
|
color: scheme.$green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.album {
|
||||||
|
color: scheme.$subtext0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
margin-top: lib.s(-20);
|
||||||
|
margin-bottom: lib.s(5);
|
||||||
|
|
||||||
|
button {
|
||||||
|
@include media-button;
|
||||||
|
|
||||||
|
// Cause some nerd font icons don't have the correct width
|
||||||
|
&.needs-adjustment {
|
||||||
|
padding-right: lib.s(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.playback {
|
||||||
|
font-size: lib.s(32);
|
||||||
|
|
||||||
|
@include lib.spacing(40);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
margin: 0 lib.s(40);
|
||||||
|
margin-top: lib.s(-10);
|
||||||
|
font-size: lib.s(20);
|
||||||
|
|
||||||
|
@include lib.spacing(20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.networks {
|
.networks {
|
||||||
.list {
|
.list {
|
||||||
@include lib.spacing(10, true);
|
@include lib.spacing(10, true);
|
||||||
|
|
@ -305,16 +422,7 @@
|
||||||
background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 50%), $alpha: 0.4);
|
background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 50%), $alpha: 0.4);
|
||||||
|
|
||||||
& > button {
|
& > button {
|
||||||
background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 50%), $alpha: 0.5);
|
@include button-active;
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 30%), $alpha: 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 20%), $alpha: 0.5);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,27 +437,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
& > button {
|
& > button {
|
||||||
|
@include button;
|
||||||
@include lib.rounded(1000);
|
@include lib.rounded(1000);
|
||||||
@include lib.element-decel;
|
|
||||||
@include font.icon;
|
@include font.icon;
|
||||||
|
|
||||||
font-size: lib.s(18);
|
font-size: lib.s(18);
|
||||||
min-width: lib.s(30);
|
min-width: lib.s(30);
|
||||||
min-height: lib.s(30);
|
min-height: lib.s(30);
|
||||||
background-color: color.change(scheme.$surface1, $alpha: 0.5);
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
background-color: color.change(scheme.$surface2, $alpha: 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: color.change(scheme.$overlay0, $alpha: 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
color: scheme.$subtext0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -371,16 +465,7 @@
|
||||||
background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 50%), $alpha: 0.4);
|
background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 50%), $alpha: 0.4);
|
||||||
|
|
||||||
& > button {
|
& > button {
|
||||||
background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 50%), $alpha: 0.5);
|
@include button-active;
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 30%), $alpha: 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 20%), $alpha: 0.5);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -395,27 +480,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
& > button {
|
& > button {
|
||||||
|
@include button;
|
||||||
@include lib.rounded(1000);
|
@include lib.rounded(1000);
|
||||||
@include lib.element-decel;
|
|
||||||
@include font.icon;
|
@include font.icon;
|
||||||
|
|
||||||
font-size: lib.s(18);
|
font-size: lib.s(18);
|
||||||
min-width: lib.s(30);
|
min-width: lib.s(30);
|
||||||
min-height: lib.s(30);
|
min-height: lib.s(30);
|
||||||
background-color: color.change(scheme.$surface1, $alpha: 0.5);
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
background-color: color.change(scheme.$surface2, $alpha: 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: color.change(scheme.$overlay0, $alpha: 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
color: scheme.$subtext0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
src/modules/sidebar/audio.tsx
Normal file
8
src/modules/sidebar/audio.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import Media from "./modules/media";
|
||||||
|
|
||||||
|
export default () => (
|
||||||
|
<box vertical className="pane audio" name="audio">
|
||||||
|
<Media />
|
||||||
|
<box className="separator" />
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
|
@ -2,6 +2,7 @@ import type { Monitor } from "@/services/monitors";
|
||||||
import { bind, idle, register, Variable } from "astal";
|
import { bind, idle, register, Variable } from "astal";
|
||||||
import { App, Astal, Gdk, Gtk, Widget } from "astal/gtk3";
|
import { App, Astal, Gdk, Gtk, Widget } from "astal/gtk3";
|
||||||
import { sidebar as config } from "config";
|
import { sidebar as config } from "config";
|
||||||
|
import Audio from "./audio";
|
||||||
import Connectivity from "./connectivity";
|
import Connectivity from "./connectivity";
|
||||||
import Dashboard from "./dashboard";
|
import Dashboard from "./dashboard";
|
||||||
import NotifPane from "./notifpane";
|
import NotifPane from "./notifpane";
|
||||||
|
|
@ -21,7 +22,7 @@ export default class SideBar extends Widget.Window {
|
||||||
visible: false,
|
visible: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const panes = [<Dashboard />, <Connectivity />, <NotifPane />];
|
const panes = [<Dashboard />, <Audio />, <Connectivity />, <NotifPane />];
|
||||||
this.shown = Variable(panes[0].name);
|
this.shown = Variable(panes[0].name);
|
||||||
|
|
||||||
this.add(
|
this.add(
|
||||||
|
|
|
||||||
149
src/modules/sidebar/modules/media.tsx
Normal file
149
src/modules/sidebar/modules/media.tsx
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
import Players from "@/services/players";
|
||||||
|
import Slider from "@/widgets/slider";
|
||||||
|
import { bind, Variable } from "astal";
|
||||||
|
import { Gtk } from "astal/gtk3";
|
||||||
|
import AstalMpris from "gi://AstalMpris";
|
||||||
|
|
||||||
|
const lengthStr = (length: number) =>
|
||||||
|
`${Math.floor(length / 60)}:${Math.floor(length % 60)
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0")}`;
|
||||||
|
|
||||||
|
const noNull = (s: string | null) => s ?? "-";
|
||||||
|
|
||||||
|
const NoMedia = () => (
|
||||||
|
<box vertical className="media" name="none">
|
||||||
|
<box homogeneous halign={Gtk.Align.CENTER} className="cover-art">
|
||||||
|
<label xalign={0.4} label="" />
|
||||||
|
</box>
|
||||||
|
<box vertical className="progress">
|
||||||
|
<Slider value={bind(Variable(0))} />
|
||||||
|
<box className="time">
|
||||||
|
<label label="-1:-1" />
|
||||||
|
<box hexpand />
|
||||||
|
<label label="-1:-1" />
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
<box vertical className="details">
|
||||||
|
<label truncate className="title" label="No media" />
|
||||||
|
<label truncate className="artist" label="Try play some music!" />
|
||||||
|
<label truncate className="album" label="" />
|
||||||
|
</box>
|
||||||
|
<box vertical className="controls">
|
||||||
|
<box halign={Gtk.Align.CENTER} className="playback">
|
||||||
|
<button sensitive={false} cursor="pointer" label="" />
|
||||||
|
<button sensitive={false} cursor="pointer" label="" />
|
||||||
|
<button sensitive={false} cursor="pointer" label="" />
|
||||||
|
</box>
|
||||||
|
<box className="options">
|
||||||
|
<button sensitive={false} cursor="pointer" label="" />
|
||||||
|
<button sensitive={false} cursor="pointer" label="" />
|
||||||
|
<box hexpand />
|
||||||
|
<button className="needs-adjustment" sensitive={false} cursor="pointer" label="" />
|
||||||
|
<button className="needs-adjustment" sensitive={false} cursor="pointer" label="" />
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Player = ({ player }: { player: AstalMpris.Player }) => {
|
||||||
|
const position = Variable.derive([bind(player, "position"), bind(player, "length")], (p, l) => p / l);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box vertical className="media" name={player.busName} onDestroy={() => position.drop()}>
|
||||||
|
<box
|
||||||
|
homogeneous
|
||||||
|
halign={Gtk.Align.CENTER}
|
||||||
|
className="cover-art"
|
||||||
|
css={bind(player, "coverArt").as(a => `background-image: url("${a}");`)}
|
||||||
|
>
|
||||||
|
{bind(player, "coverArt").as(a => (a ? <box visible={false} /> : <label xalign={0.4} label="" />))}
|
||||||
|
</box>
|
||||||
|
<box vertical className="progress">
|
||||||
|
<Slider value={bind(position)} />
|
||||||
|
<box className="time">
|
||||||
|
<label label={bind(player, "position").as(lengthStr)} />
|
||||||
|
<box hexpand />
|
||||||
|
<label label={bind(player, "length").as(lengthStr)} />
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
<box vertical className="details">
|
||||||
|
<label truncate className="title" label={bind(player, "title").as(noNull)} />
|
||||||
|
<label truncate className="artist" label={bind(player, "artist").as(noNull)} />
|
||||||
|
<label truncate className="album" label={bind(player, "album").as(noNull)} />
|
||||||
|
</box>
|
||||||
|
<box vertical className="controls">
|
||||||
|
<box halign={Gtk.Align.CENTER} className="playback">
|
||||||
|
<button
|
||||||
|
sensitive={bind(player, "canGoPrevious")}
|
||||||
|
cursor="pointer"
|
||||||
|
onClicked={() => player.next()}
|
||||||
|
label=""
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
sensitive={bind(player, "canControl")}
|
||||||
|
cursor="pointer"
|
||||||
|
onClicked={() => player.play_pause()}
|
||||||
|
label={bind(player, "playbackStatus").as(s =>
|
||||||
|
s === AstalMpris.PlaybackStatus.PLAYING ? "" : ""
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
sensitive={bind(player, "canGoNext")}
|
||||||
|
cursor="pointer"
|
||||||
|
onClicked={() => player.next()}
|
||||||
|
label=""
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
<box className="options">
|
||||||
|
<button
|
||||||
|
sensitive={bind(player, "canSetFullscreen")}
|
||||||
|
cursor="pointer"
|
||||||
|
onClicked={() => player.toggle_fullscreen()}
|
||||||
|
label={bind(player, "fullscreen").as(f => (f ? "" : ""))}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
sensitive={bind(player, "canControl")}
|
||||||
|
cursor="pointer"
|
||||||
|
onClicked={() => player.shuffle()}
|
||||||
|
label={bind(player, "shuffleStatus").as(s => (s === AstalMpris.Shuffle.ON ? "" : ""))}
|
||||||
|
/>
|
||||||
|
<box hexpand />
|
||||||
|
<button
|
||||||
|
className="needs-adjustment"
|
||||||
|
sensitive={bind(player, "canControl")}
|
||||||
|
cursor="pointer"
|
||||||
|
onClicked={() => player.loop()}
|
||||||
|
label={bind(player, "loopStatus").as(l =>
|
||||||
|
l === AstalMpris.Loop.TRACK ? "" : l === AstalMpris.Loop.PLAYLIST ? "" : ""
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="needs-adjustment"
|
||||||
|
sensitive={bind(player, "canRaise")}
|
||||||
|
cursor="pointer"
|
||||||
|
onClicked={() => player.raise()}
|
||||||
|
label=""
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const active = Variable(Players.get_default().list[0]?.busName ?? "none");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box vertical>
|
||||||
|
<stack
|
||||||
|
transitionType={Gtk.StackTransitionType.SLIDE_LEFT_RIGHT}
|
||||||
|
transitionDuration={150}
|
||||||
|
shown={bind(active)}
|
||||||
|
>
|
||||||
|
<NoMedia />
|
||||||
|
{bind(Players.get_default(), "list").as(ps => ps.map(p => <Player player={p} />))}
|
||||||
|
</stack>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue