diff options
Diffstat (limited to 'src/modules/mediadisplay')
| -rw-r--r-- | src/modules/mediadisplay/index.tsx | 188 | ||||
| -rw-r--r-- | src/modules/mediadisplay/visualiser.tsx | 71 |
2 files changed, 0 insertions, 259 deletions
diff --git a/src/modules/mediadisplay/index.tsx b/src/modules/mediadisplay/index.tsx deleted file mode 100644 index 307087c..0000000 --- a/src/modules/mediadisplay/index.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import type { Monitor } from "@/services/monitors"; -import Players from "@/services/players"; -import { lengthStr } from "@/utils/strings"; -import { bind, Variable } from "astal"; -import { App, Astal, Gtk } from "astal/gtk3"; -import AstalMpris from "gi://AstalMpris"; -import Visualiser from "./visualiser"; - -type Selected = Variable<AstalMpris.Player | null>; - -const bindIcon = (player: AstalMpris.Player) => - bind(player, "identity").as(i => { - const icon = `caelestia-${i?.toLowerCase().replaceAll(" ", "-")}-symbolic`; - return Astal.Icon.lookup_icon(icon) ? icon : "caelestia-media-generic-symbolic"; - }); - -const PlayerButton = ({ - player, - selected, - showDropdown, -}: { - player: AstalMpris.Player; - selected: Selected; - showDropdown: Variable<boolean>; -}) => ( - <button - cursor="pointer" - onClicked={() => { - showDropdown.set(false); - selected.set(player); - }} - > - <box className="identity" halign={Gtk.Align.CENTER}> - <label label={bind(player, "identity").as(i => i ?? "-")} /> - <label label="•" /> - <label label={bind(player, "title").as(t => t ?? "-")} /> - </box> - </button> -); - -const Selector = ({ player, selected }: { player?: AstalMpris.Player; selected: Selected }) => { - const showDropdown = Variable(false); - - return ( - <box vertical valign={Gtk.Align.START} className="selector"> - <button - sensitive={bind(Players.get_default(), "list").as(ps => ps.length > 1)} - cursor="pointer" - onClicked={() => showDropdown.set(!showDropdown.get())} - > - <box className="identity" halign={Gtk.Align.CENTER}> - <icon icon={player ? bindIcon(player) : "caelestia-media-none-symbolic"} /> - <label label={player ? bind(player, "identity").as(i => i ?? "") : "No media"} /> - </box> - </button> - <revealer - transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN} - transitionDuration={150} - revealChild={bind(showDropdown)} - > - <box vertical className="list"> - {bind(Players.get_default(), "list").as(ps => - ps - .filter(p => p !== player) - .map(p => <PlayerButton player={p} selected={selected} showDropdown={showDropdown} />) - )} - </box> - </revealer> - </box> - ); -}; - -const NoMedia = ({ selected }: { selected: Selected }) => ( - <box> - <box homogeneous halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} className="cover-art"> - <label xalign={0.36} label="" /> - </box> - <box> - <box vertical className="details"> - <label truncate xalign={0} className="title" label="No media" /> - <label truncate xalign={0} className="artist" label="Try play something!" /> - <box halign={Gtk.Align.START} className="controls"> - <button sensitive={false} label="skip_previous" /> - <button sensitive={false} label="play_arrow" /> - <button sensitive={false} label="skip_next" /> - </box> - </box> - <box className="center-module"> - <overlay - expand - overlay={<label halign={Gtk.Align.CENTER} valign={Gtk.Align.END} className="time" label="-1:-1" />} - > - <Visualiser /> - </overlay> - </box> - <Selector selected={selected} /> - </box> - </box> -); - -const Player = ({ player, selected }: { player: AstalMpris.Player; selected: Selected }) => { - const time = Variable.derive( - [bind(player, "position"), bind(player, "length")], - (p, l) => lengthStr(p) + " / " + lengthStr(l) - ); - - return ( - <box> - <box - homogeneous - halign={Gtk.Align.CENTER} - valign={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.36} label="" />))} - </box> - <box> - <box vertical className="details"> - <label truncate xalign={0} className="title" label={bind(player, "title").as(t => t ?? "-")} /> - <label truncate xalign={0} className="artist" label={bind(player, "artist").as(t => t ?? "-")} /> - <box halign={Gtk.Align.START} className="controls"> - <button - sensitive={bind(player, "canGoPrevious")} - cursor="pointer" - onClicked={() => player.next()} - label="skip_previous" - /> - <button - sensitive={bind(player, "canControl")} - cursor="pointer" - onClicked={() => player.play_pause()} - label={bind(player, "playbackStatus").as(s => - s === AstalMpris.PlaybackStatus.PLAYING ? "pause" : "play_arrow" - )} - /> - <button - sensitive={bind(player, "canGoNext")} - cursor="pointer" - onClicked={() => player.next()} - label="skip_next" - /> - </box> - </box> - <box className="center-module"> - <overlay - expand - overlay={ - <label - halign={Gtk.Align.CENTER} - valign={Gtk.Align.END} - className="time" - label={bind(time)} - onDestroy={() => time.drop()} - /> - } - > - <Visualiser /> - </overlay> - </box> - <Selector player={player} selected={selected} /> - </box> - </box> - ); -}; - -export default ({ monitor }: { monitor: Monitor }) => { - const selected = Variable(Players.get_default().lastPlayer); - selected.observe(Players.get_default(), "notify::last-player", () => Players.get_default().lastPlayer); - - return ( - <window - application={App} - name={`mediadisplay${monitor.id}`} - namespace="caelestia-mediadisplay" - monitor={monitor.id} - anchor={Astal.WindowAnchor.LEFT | Astal.WindowAnchor.RIGHT | Astal.WindowAnchor.BOTTOM} - exclusivity={Astal.Exclusivity.EXCLUSIVE} - visible={false} - > - <box className="mediadisplay" onDestroy={() => selected.drop()}> - {bind(selected).as(p => - p ? <Player player={p} selected={selected} /> : <NoMedia selected={selected} /> - )} - </box> - </window> - ); -}; diff --git a/src/modules/mediadisplay/visualiser.tsx b/src/modules/mediadisplay/visualiser.tsx deleted file mode 100644 index d788e7b..0000000 --- a/src/modules/mediadisplay/visualiser.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { Gtk } from "astal/gtk3"; -import cairo from "cairo"; -import AstalCava from "gi://AstalCava"; -import PangoCairo from "gi://PangoCairo"; - -export default () => ( - <drawingarea - className="visualiser" - setup={self => { - const cava = AstalCava.get_default(); - - if (cava) { - cava.set_stereo(true); - cava.set_noise_reduction(0.77); - cava.set_input(AstalCava.Input.PIPEWIRE); - - self.hook(cava, "notify::values", () => self.queue_draw()); - self.connect("size-allocate", () => { - const width = self.get_allocated_width(); - const barWidth = self - .get_style_context() - .get_property("min-width", Gtk.StateFlags.NORMAL) as number; - const gaps = self.get_style_context().get_margin(Gtk.StateFlags.NORMAL).right; - const bars = Math.floor((width - gaps) / (barWidth + gaps)); - if (bars > 0) cava.set_bars(bars % 2 ? bars : bars - 1); - }); - } - - self.connect("draw", (_, cr: cairo.Context) => { - const { width, height } = self.get_allocation(); - - if (!cava) { - // Show error text if cava unavailable - const fg = self.get_style_context().get_color(Gtk.StateFlags.NORMAL); - cr.setSourceRGBA(fg.red, fg.green, fg.blue, fg.alpha); - const layout = self.create_pango_layout("Visualiser module requires Cava"); - const [w, h] = layout.get_pixel_size(); - cr.moveTo((width - w) / 2, (height - h) / 2); - cr.setAntialias(cairo.Antialias.BEST); - PangoCairo.show_layout(cr, layout); - - return; - } - - const bg = self.get_style_context().get_background_color(Gtk.StateFlags.NORMAL); - cr.setSourceRGBA(bg.red, bg.green, bg.blue, bg.alpha); - const barWidth = self.get_style_context().get_property("min-width", Gtk.StateFlags.NORMAL) as number; - const gaps = self.get_style_context().get_margin(Gtk.StateFlags.NORMAL).right; - - const values = cava.get_values(); - const len = values.length - 1; - const radius = barWidth / 2; - const xOff = (width - len * (barWidth + gaps) - gaps) / 2 - radius; - const center = height / 2; - const half = len / 2; - - const renderPill = (x: number, value: number) => { - x = x * (barWidth + gaps) + xOff; - value *= center; - cr.arc(x, center + value, radius, 0, Math.PI); - cr.arc(x, center - value, radius, Math.PI, Math.PI * 2); - cr.fill(); - }; - - // Render channels facing each other - for (let i = half - 1; i >= 0; i--) renderPill(half - i, values[i]); - for (let i = half; i < len; i++) renderPill(i + 1, values[i]); - }); - }} - /> -); |