mirror of
https://github.com/zoriya/flake.git
synced 2025-12-06 06:36:19 +00:00
Add mpris in bar
This commit is contained in:
@@ -5,6 +5,7 @@ import * as network from "../modules/network.js";
|
||||
import * as bluetooth from "../modules/bluetooth.js";
|
||||
import * as battery from "../modules/battery.js";
|
||||
import * as notifications from "../modules/notifications.js";
|
||||
import * as mpris from "../modules/mpris.js";
|
||||
|
||||
/**
|
||||
*@param {number} monitor
|
||||
@@ -44,6 +45,14 @@ export const Bar = (monitor) =>
|
||||
endWidget: Widget.Box({
|
||||
hpack: "end",
|
||||
children: [
|
||||
Widget.Box({
|
||||
className: "module",
|
||||
css: "margin-right: 48px",
|
||||
visible: mpris.activePlayer.bind().as((x) => !!x),
|
||||
children: mpris.activePlayer
|
||||
.bind()
|
||||
.as((player) => (player ? [mpris.LinePlayer({ player })] : [])),
|
||||
}),
|
||||
Widget.Button({
|
||||
onClicked: () => App.toggleWindow("quicksettings"),
|
||||
className: "module quicksettings",
|
||||
|
||||
166
modules/wm/ags/misc/circular-progress.js
Normal file
166
modules/wm/ags/misc/circular-progress.js
Normal file
@@ -0,0 +1,166 @@
|
||||
const { Gtk } = imports.gi;
|
||||
const Lang = imports.lang;
|
||||
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
|
||||
|
||||
// -- Styling --
|
||||
// min-height for diameter
|
||||
// min-width for trough stroke
|
||||
// padding for space between trough and progress
|
||||
// margin for space between widget and parent
|
||||
// background-color for trough color
|
||||
// color for progress color
|
||||
// -- Usage --
|
||||
// font size for progress value (0-100px) (hacky i know, but i want animations)
|
||||
export const AnimatedCircProg = ({
|
||||
initFrom = 0,
|
||||
initTo = 0,
|
||||
initAnimTime = 2900,
|
||||
initAnimPoints = 1,
|
||||
extraSetup = () => {},
|
||||
...rest
|
||||
}) =>
|
||||
Widget.DrawingArea({
|
||||
...rest,
|
||||
css: `${
|
||||
initFrom !== initTo
|
||||
? `font-size: ${initFrom}px; transition: ${initAnimTime}ms linear;`
|
||||
: ""
|
||||
}`,
|
||||
setup: (area) => {
|
||||
const styleContext = area.get_style_context();
|
||||
const width = styleContext.get_property(
|
||||
"min-height",
|
||||
Gtk.StateFlags.NORMAL,
|
||||
);
|
||||
const height = styleContext.get_property(
|
||||
"min-height",
|
||||
Gtk.StateFlags.NORMAL,
|
||||
);
|
||||
const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
|
||||
const marginLeft = styleContext.get_margin(Gtk.StateFlags.NORMAL).left;
|
||||
const marginRight = styleContext.get_margin(Gtk.StateFlags.NORMAL).right;
|
||||
const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
|
||||
const marginBottom = styleContext.get_margin(
|
||||
Gtk.StateFlags.NORMAL,
|
||||
).bottom;
|
||||
area.set_size_request(
|
||||
width + marginLeft + marginRight,
|
||||
height + marginTop + marginBottom,
|
||||
);
|
||||
area.connect(
|
||||
"draw",
|
||||
Lang.bind(area, (area, cr) => {
|
||||
const styleContext = area.get_style_context();
|
||||
const width = styleContext.get_property(
|
||||
"min-height",
|
||||
Gtk.StateFlags.NORMAL,
|
||||
);
|
||||
const height = styleContext.get_property(
|
||||
"min-height",
|
||||
Gtk.StateFlags.NORMAL,
|
||||
);
|
||||
const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
|
||||
const marginLeft = styleContext.get_margin(
|
||||
Gtk.StateFlags.NORMAL,
|
||||
).left;
|
||||
const marginRight = styleContext.get_margin(
|
||||
Gtk.StateFlags.NORMAL,
|
||||
).right;
|
||||
const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
|
||||
const marginBottom = styleContext.get_margin(
|
||||
Gtk.StateFlags.NORMAL,
|
||||
).bottom;
|
||||
area.set_size_request(
|
||||
width + marginLeft + marginRight,
|
||||
height + marginTop + marginBottom,
|
||||
);
|
||||
|
||||
const progressValue =
|
||||
styleContext.get_property("font-size", Gtk.StateFlags.NORMAL) /
|
||||
100.0;
|
||||
|
||||
const bg_stroke = styleContext.get_property(
|
||||
"min-width",
|
||||
Gtk.StateFlags.NORMAL,
|
||||
);
|
||||
const fg_stroke = bg_stroke - padding;
|
||||
const radius =
|
||||
Math.min(width, height) / 2.0 -
|
||||
Math.max(bg_stroke, fg_stroke) / 2.0;
|
||||
const center_x = width / 2.0 + marginLeft;
|
||||
const center_y = height / 2.0 + marginTop;
|
||||
const start_angle = -Math.PI / 2.0;
|
||||
const end_angle = start_angle + 2 * Math.PI * progressValue;
|
||||
const start_x = center_x + Math.cos(start_angle) * radius;
|
||||
const start_y = center_y + Math.sin(start_angle) * radius;
|
||||
const end_x = center_x + Math.cos(end_angle) * radius;
|
||||
const end_y = center_y + Math.sin(end_angle) * radius;
|
||||
|
||||
// Draw background
|
||||
const background_color = styleContext.get_property(
|
||||
"background-color",
|
||||
Gtk.StateFlags.NORMAL,
|
||||
);
|
||||
cr.setSourceRGBA(
|
||||
background_color.red,
|
||||
background_color.green,
|
||||
background_color.blue,
|
||||
background_color.alpha,
|
||||
);
|
||||
cr.arc(center_x, center_y, radius, 0, 2 * Math.PI);
|
||||
cr.setLineWidth(bg_stroke);
|
||||
cr.stroke();
|
||||
|
||||
if (progressValue == 0) return;
|
||||
|
||||
// Draw progress
|
||||
const color = styleContext.get_property(
|
||||
"color",
|
||||
Gtk.StateFlags.NORMAL,
|
||||
);
|
||||
cr.setSourceRGBA(color.red, color.green, color.blue, color.alpha);
|
||||
cr.arc(center_x, center_y, radius, start_angle, end_angle);
|
||||
cr.setLineWidth(fg_stroke);
|
||||
cr.stroke();
|
||||
|
||||
// Draw rounded ends for progress arcs
|
||||
cr.setLineWidth(0);
|
||||
cr.arc(start_x, start_y, fg_stroke / 2, 0, 0 - 0.01);
|
||||
cr.fill();
|
||||
cr.arc(end_x, end_y, fg_stroke / 2, 0, 0 - 0.01);
|
||||
cr.fill();
|
||||
}),
|
||||
);
|
||||
|
||||
// Init animation
|
||||
if (initFrom != initTo) {
|
||||
area.css = `font-size: ${initFrom}px; transition: ${initAnimTime}ms linear;`;
|
||||
Utils.timeout(
|
||||
20,
|
||||
() => {
|
||||
area.css = `font-size: ${initTo}px;`;
|
||||
},
|
||||
area,
|
||||
);
|
||||
const transitionDistance = initTo - initFrom;
|
||||
const oneStep = initAnimTime / initAnimPoints;
|
||||
area.css = `
|
||||
font-size: ${initFrom}px;
|
||||
transition: ${oneStep}ms linear;
|
||||
`;
|
||||
for (let i = 0; i < initAnimPoints; i++) {
|
||||
Utils.timeout(Math.max(10, i * oneStep), () => {
|
||||
if (!area) return;
|
||||
area.css = `${
|
||||
initFrom != initTo
|
||||
? "font-size: " +
|
||||
(initFrom + (transitionDistance / initAnimPoints) * (i + 1)) +
|
||||
"px;"
|
||||
: ""
|
||||
}`;
|
||||
});
|
||||
}
|
||||
} else area.css = "font-size: 0px;";
|
||||
extraSetup(area);
|
||||
},
|
||||
});
|
||||
@@ -1,6 +1,8 @@
|
||||
import { AnimatedCircProg } from "../misc/circular-progress.js";
|
||||
|
||||
const mpris = await Service.import("mpris");
|
||||
|
||||
/** @param {{player: import("../types/service/mpris").MprisPlayer} & import("../types/widgets/icon").IconProps} props */
|
||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/icon").IconProps} props */
|
||||
const PlayerIcon = ({ player, ...props }) =>
|
||||
Widget.Icon({
|
||||
size: 24,
|
||||
@@ -18,7 +20,7 @@ const PlayerIcon = ({ player, ...props }) =>
|
||||
...props,
|
||||
});
|
||||
|
||||
/** @param {{player: import("../types/service/mpris").MprisPlayer} & import("../types/widgets/label").LabelProps} props */
|
||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/label").LabelProps} props */
|
||||
const TitleLabel = ({ player, ...props }) =>
|
||||
Widget.Label({
|
||||
wrap: true,
|
||||
@@ -28,7 +30,7 @@ const TitleLabel = ({ player, ...props }) =>
|
||||
...props,
|
||||
});
|
||||
|
||||
/** @param {{player: import("../types/service/mpris").MprisPlayer} & import("../types/widgets/label").LabelProps} props */
|
||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/label").LabelProps} props */
|
||||
const ArtistLabel = ({ player, ...props }) =>
|
||||
Widget.Label({
|
||||
wrap: true,
|
||||
@@ -38,8 +40,11 @@ const ArtistLabel = ({ player, ...props }) =>
|
||||
...props,
|
||||
});
|
||||
|
||||
/** @param {{player: import("../types/service/mpris").MprisPlayer} & import("../types/widgets/button").ButtonProps} props */
|
||||
export const PlayPause = ({ player, ...props }) =>
|
||||
/** @param {{
|
||||
* player: import("types/service/mpris").MprisPlayer,
|
||||
* iconProps?: import("types/widgets/icon").IconProps,
|
||||
* } & import("types/widgets/button").ButtonProps} props */
|
||||
export const PlayPause = ({ player, iconProps = {}, ...props }) =>
|
||||
Widget.Button({
|
||||
child: Widget.Icon({
|
||||
icon: player.bind("play_back_status").as(
|
||||
@@ -50,13 +55,14 @@ export const PlayPause = ({ player, ...props }) =>
|
||||
Stopped: "media-playback-start-symbolic",
|
||||
})[x],
|
||||
),
|
||||
...iconProps,
|
||||
}),
|
||||
onClicked: () => player.playPause(),
|
||||
visible: player.bind("can_play"),
|
||||
...props,
|
||||
});
|
||||
|
||||
/** @param {{player: import("../types/service/mpris").MprisPlayer} & import("../types/widgets/button").ButtonProps} props */
|
||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/button").ButtonProps} props */
|
||||
const PreviousButton = ({ player, ...props }) =>
|
||||
Widget.Button({
|
||||
child: Widget.Icon({ icon: "media-skip-backward-symbolic" }),
|
||||
@@ -65,7 +71,7 @@ const PreviousButton = ({ player, ...props }) =>
|
||||
...props,
|
||||
});
|
||||
|
||||
/** @param {{player: import("../types/service/mpris").MprisPlayer} & import("../types/widgets/button").ButtonProps} props */
|
||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/button").ButtonProps} props */
|
||||
const NextButton = ({ player, ...props }) =>
|
||||
Widget.Button({
|
||||
child: Widget.Icon({ icon: "media-skip-forward-symbolic" }),
|
||||
@@ -74,7 +80,7 @@ const NextButton = ({ player, ...props }) =>
|
||||
...props,
|
||||
});
|
||||
|
||||
/** @param {{player: import("../types/service/mpris").MprisPlayer} & import("../types/widgets/slider").SliderProps} props */
|
||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/slider").SliderProps} props */
|
||||
const PositionSlider = ({ player, ...props }) =>
|
||||
Widget.Slider({
|
||||
className: "mpris-position-slider",
|
||||
@@ -95,10 +101,72 @@ const PositionSlider = ({ player, ...props }) =>
|
||||
...props,
|
||||
});
|
||||
|
||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/box").BoxProps} props */
|
||||
const PositionCircle = ({ player, child, ...props }) =>
|
||||
Widget.Box({
|
||||
child: Widget.Overlay({
|
||||
child: AnimatedCircProg({
|
||||
css: `
|
||||
min-width: 0.136rem;
|
||||
min-height: 1.636rem;
|
||||
padding: 0rem;
|
||||
`,
|
||||
vpack: "center",
|
||||
hpack: "center",
|
||||
className: "accent-rev",
|
||||
extraSetup: (self) => {
|
||||
function update() {
|
||||
const value = player.position / player.length;
|
||||
console.log(
|
||||
value,
|
||||
player.position,
|
||||
player.length,
|
||||
player.name,
|
||||
player.track_title,
|
||||
);
|
||||
self.css = `
|
||||
font-size: ${Math.max(value * 100, 0)}px;
|
||||
min-width: 0.136rem;
|
||||
min-height: 1.636rem;
|
||||
padding: 0rem;
|
||||
`;
|
||||
}
|
||||
self.hook(player, update);
|
||||
self.hook(player, update, "position");
|
||||
self.poll(1000, update);
|
||||
},
|
||||
}),
|
||||
overlays: [child],
|
||||
}),
|
||||
...props,
|
||||
});
|
||||
|
||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/box").BoxProps} props */
|
||||
export const LinePlayer = ({ player, ...props }) =>
|
||||
Widget.Box({
|
||||
children: [
|
||||
PositionCircle({
|
||||
player,
|
||||
child: PlayPause({ player, iconProps: { size: 10 } }),
|
||||
css: "margin-right: 12px;",
|
||||
}),
|
||||
Widget.Label({
|
||||
label: Utils.merge(
|
||||
[player.bind("track_title"), player.bind("track_artists")],
|
||||
(title, artists) => `${title} - ${artists.join(", ")}`,
|
||||
),
|
||||
maxWidthChars: 30,
|
||||
wrap: true,
|
||||
truncate: "end",
|
||||
hpack: "start",
|
||||
}),
|
||||
],
|
||||
...props,
|
||||
});
|
||||
|
||||
export const activePlayer = Variable(mpris.players[0]);
|
||||
mpris.connect("player-added", (_, bus) => {
|
||||
mpris.getPlayer(bus)?.connect("changed", (player) => {
|
||||
console.log("Player changed", bus, player);
|
||||
if (player?.play_back_status !== "Stopped") {
|
||||
activePlayer.value = player || mpris.players[0];
|
||||
} else {
|
||||
@@ -107,7 +175,7 @@ mpris.connect("player-added", (_, bus) => {
|
||||
});
|
||||
});
|
||||
|
||||
/** @param {{player?: import("../types/service/mpris").MprisPlayer | null} & import("../types/widgets/box").BoxProps} props */
|
||||
/** @param {{player?: import("types/service/mpris").MprisPlayer | null} & import("../types/widgets/box").BoxProps} props */
|
||||
export const MprisPlayer = ({ player, ...props }) => {
|
||||
if (!player) return Widget.Box({ visible: false });
|
||||
const colors = getMaterialColors(player);
|
||||
@@ -203,7 +271,7 @@ const ret = Variable({
|
||||
background: "#222222",
|
||||
onBackground: "#ffffff",
|
||||
});
|
||||
/** @param {import("../types/service/mpris").MprisPlayer} player */
|
||||
/** @param {import("types/service/mpris").MprisPlayer} player */
|
||||
export const getMaterialColors = (player) => {
|
||||
// TODO: Move that to a hook to allow graceful disconnections
|
||||
player.connect("changed", (player) => {
|
||||
|
||||
@@ -24,6 +24,10 @@ button {
|
||||
background-color: #94e2d5;
|
||||
color: #1e1e2e;
|
||||
}
|
||||
.accent-rev {
|
||||
color: #94e2d5;
|
||||
background-color: #1e1e2e;
|
||||
}
|
||||
.secondary {
|
||||
background-color: #cba6f7;
|
||||
color: #1e1e2e;
|
||||
|
||||
Reference in New Issue
Block a user