diff options
author | Aylur <[email protected]> | 2025-01-16 17:37:00 +0100 |
---|---|---|
committer | Aylur <[email protected]> | 2025-01-16 17:37:04 +0100 |
commit | 9e8634d892c559c5b44565a68bf35b13cbcb5572 (patch) | |
tree | 36a8b911e919959cdf64d3c64646f5066c6a2523 /examples/js | |
parent | bc796ac226800c43e724e27f53f410c157acaffe (diff) |
add: gtk3 ts popover example
closes #224
closes #157
Diffstat (limited to 'examples/js')
21 files changed, 0 insertions, 979 deletions
diff --git a/examples/js/.gitignore b/examples/js/.gitignore deleted file mode 100644 index d53b85b..0000000 --- a/examples/js/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -@girs/ -tsconfig.json -env.d.ts -dist/ -node_modules/ -package.json -package-lock.json diff --git a/examples/js/applauncher/README.md b/examples/js/applauncher/README.md deleted file mode 100644 index 682adf1..0000000 --- a/examples/js/applauncher/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Applauncher - - - -Using [Apps](https://aylur.github.io/astal/guide/libraries/apps). diff --git a/examples/js/applauncher/app.ts b/examples/js/applauncher/app.ts deleted file mode 100644 index d6c9e1c..0000000 --- a/examples/js/applauncher/app.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { App } from "astal/gtk3" -import style from "./style.scss" -import Applauncher from "./widget/Applauncher" - -App.start({ - instanceName: "launcher", - css: style, - main: Applauncher, -}) diff --git a/examples/js/applauncher/style.scss b/examples/js/applauncher/style.scss deleted file mode 100644 index ba13eed..0000000 --- a/examples/js/applauncher/style.scss +++ /dev/null @@ -1 +0,0 @@ -@use "./widget/Applauncher.scss" diff --git a/examples/js/applauncher/widget/Applauncher.scss b/examples/js/applauncher/widget/Applauncher.scss deleted file mode 100644 index ae2453d..0000000 --- a/examples/js/applauncher/widget/Applauncher.scss +++ /dev/null @@ -1,59 +0,0 @@ -@use "sass:string"; - -@function gtkalpha($c, $a) { - @return string.unquote("alpha(#{$c},#{$a})"); -} - -// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss -$fg-color: #{"@theme_fg_color"}; -$bg-color: #{"@theme_bg_color"}; - -window#launcher { - all: unset; - - box.Applauncher { - background-color: $bg-color; - border-radius: 11px; - margin: 1rem; - padding: .8rem; - box-shadow: 2px 3px 8px 0 gtkalpha(black, .4); - - entry { - margin-bottom: .8rem; - } - - button { - min-width: 0; - min-height: 0; - padding: .5rem; - - icon { - font-size: 3em; - margin-right: .3rem; - } - - label.name { - font-weight: bold; - font-size: 1.1em - } - - label.description { - color: gtkalpha($fg-color, .8); - } - } - - box.not-found { - padding: 1rem; - - icon { - font-size: 6em; - color: gtkalpha($fg-color, .7); - } - - label { - color: gtkalpha($fg-color, .9); - font-size: 1.2em; - } - } - } -} diff --git a/examples/js/applauncher/widget/Applauncher.tsx b/examples/js/applauncher/widget/Applauncher.tsx deleted file mode 100644 index c7bac68..0000000 --- a/examples/js/applauncher/widget/Applauncher.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import Apps from "gi://AstalApps" -import { App, Astal, Gdk, Gtk } from "astal/gtk3" -import { Variable } from "astal" - -const MAX_ITEMS = 8 - -function hide() { - App.get_window("launcher")!.hide() -} - -function AppButton({ app }: { app: Apps.Application }) { - return <button - className="AppButton" - onClicked={() => { hide(); app.launch() }}> - <box> - <icon icon={app.iconName} /> - <box valign={Gtk.Align.CENTER} vertical> - <label - className="name" - truncate - xalign={0} - label={app.name} - /> - {app.description && <label - className="description" - wrap - xalign={0} - label={app.description} - />} - </box> - </box> - </button> -} - -export default function Applauncher() { - const { CENTER } = Gtk.Align - const apps = new Apps.Apps() - - const text = Variable("") - const list = text(text => apps.fuzzy_query(text).slice(0, MAX_ITEMS)) - const onEnter = () => { - apps.fuzzy_query(text.get())?.[0].launch() - hide() - } - - return <window - name="launcher" - anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.BOTTOM} - exclusivity={Astal.Exclusivity.IGNORE} - keymode={Astal.Keymode.ON_DEMAND} - application={App} - onShow={() => text.set("")} - onKeyPressEvent={function (self, event: Gdk.Event) { - if (event.get_keyval()[1] === Gdk.KEY_Escape) - self.hide() - }}> - <box> - <eventbox widthRequest={4000} expand onClick={hide} /> - <box hexpand={false} vertical> - <eventbox heightRequest={100} onClick={hide} /> - <box widthRequest={500} className="Applauncher" vertical> - <entry - placeholderText="Search" - text={text()} - onChanged={self => text.set(self.text)} - onActivate={onEnter} - /> - <box spacing={6} vertical> - {list.as(list => list.map(app => ( - <AppButton app={app} /> - )))} - </box> - <box - halign={CENTER} - className="not-found" - vertical - visible={list.as(l => l.length === 0)}> - <icon icon="system-search-symbolic" /> - <label label="No match found" /> - </box> - </box> - <eventbox expand onClick={hide} /> - </box> - <eventbox widthRequest={4000} expand onClick={hide} /> - </box> - </window> -} diff --git a/examples/js/media-player/README.md b/examples/js/media-player/README.md deleted file mode 100644 index 4e3d237..0000000 --- a/examples/js/media-player/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Media Player - - - -Using [Mpris](https://aylur.github.io/astal/guide/libraries/mpris). diff --git a/examples/js/media-player/app.ts b/examples/js/media-player/app.ts deleted file mode 100644 index 5b7558a..0000000 --- a/examples/js/media-player/app.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { App, Widget } from "astal/gtk3" -import style from "./style.scss" -import MprisPlayers from "./widget/MediaPlayer" - -App.start({ - instanceName: "players", - css: style, - main: () => { - new Widget.Window({}, MprisPlayers()) - } -}) diff --git a/examples/js/media-player/style.scss b/examples/js/media-player/style.scss deleted file mode 100644 index 2e2f625..0000000 --- a/examples/js/media-player/style.scss +++ /dev/null @@ -1 +0,0 @@ -@use "./widget/MediaPlayer.scss"; diff --git a/examples/js/media-player/widget/MediaPlayer.scss b/examples/js/media-player/widget/MediaPlayer.scss deleted file mode 100644 index e1597c2..0000000 --- a/examples/js/media-player/widget/MediaPlayer.scss +++ /dev/null @@ -1,56 +0,0 @@ -// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss -$fg-color: #{"@theme_fg_color"}; -$bg-color: #{"@theme_bg_color"}; - -window { - all: unset; -} - -box.MediaPlayer { - padding: .6rem; - background-color: $bg-color; - - box.cover-art { - min-width: 120px; - min-height: 120px; - border-radius: 9px; - margin-right: .6rem; - background-size: contain; - background-position: center; - } - - box.title { - label { - font-weight: bold; - font-size: 1.1em; - } - } - - scale { - padding: 0; - margin: .4rem 0; - - trough { - min-height: 8px; - } - - highlight { - background-color: $fg-color; - } - - slider { - all: unset; - } - } - - centerbox.actions { - min-width: 220px; - - button { - min-width: 0; - min-height: 0; - padding: .4rem; - margin: 0 .2rem; - } - } -} diff --git a/examples/js/media-player/widget/MediaPlayer.tsx b/examples/js/media-player/widget/MediaPlayer.tsx deleted file mode 100644 index 06c7e77..0000000 --- a/examples/js/media-player/widget/MediaPlayer.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { Astal, Gtk } from "astal/gtk3" -import Mpris from "gi://AstalMpris" -import { bind } from "astal" - -function lengthStr(length: number) { - const min = Math.floor(length / 60) - const sec = Math.floor(length % 60) - const sec0 = sec < 10 ? "0" : "" - return `${min}:${sec0}${sec}` -} - - -function MediaPlayer({ player }: { player: Mpris.Player }) { - const { START, END } = Gtk.Align - - const title = bind(player, "title").as(t => - t || "Unknown Track") - - const artist = bind(player, "artist").as(a => - a || "Unknown Artist") - - const coverArt = bind(player, "coverArt").as(c => - `background-image: url('${c}')`) - - const playerIcon = bind(player, "entry").as(e => - Astal.Icon.lookup_icon(e) ? e : "audio-x-generic-symbolic") - - const position = bind(player, "position").as(p => player.length > 0 - ? p / player.length : 0) - - const playIcon = bind(player, "playbackStatus").as(s => - s === Mpris.PlaybackStatus.PLAYING - ? "media-playback-pause-symbolic" - : "media-playback-start-symbolic" - ) - - return <box className="MediaPlayer"> - <box className="cover-art" css={coverArt} /> - <box vertical> - <box className="title"> - <label truncate hexpand halign={START} label={title} /> - <icon icon={playerIcon} /> - </box> - <label halign={START} valign={START} vexpand wrap label={artist} /> - <slider - visible={bind(player, "length").as(l => l > 0)} - onDragged={({ value }) => player.position = value * player.length} - value={position} - /> - <centerbox className="actions"> - <label - hexpand - className="position" - halign={START} - visible={bind(player, "length").as(l => l > 0)} - label={bind(player, "position").as(lengthStr)} - /> - <box> - <button - onClicked={() => player.previous()} - visible={bind(player, "canGoPrevious")}> - <icon icon="media-skip-backward-symbolic" /> - </button> - <button - onClicked={() => player.play_pause()} - visible={bind(player, "canControl")}> - <icon icon={playIcon} /> - </button> - <button - onClicked={() => player.next()} - visible={bind(player, "canGoNext")}> - <icon icon="media-skip-forward-symbolic" /> - </button> - </box> - <label - className="length" - hexpand - halign={END} - visible={bind(player, "length").as(l => l > 0)} - label={bind(player, "length").as(l => l > 0 ? lengthStr(l) : "0:00")} - /> - </centerbox> - </box> - </box> -} - -export default function MprisPlayers() { - const mpris = Mpris.get_default() - return <box vertical> - {bind(mpris, "players").as(arr => arr.map(player => ( - <MediaPlayer player={player} /> - )))} - </box> -} diff --git a/examples/js/notifications/README.md b/examples/js/notifications/README.md deleted file mode 100644 index 60dad60..0000000 --- a/examples/js/notifications/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Notifications Popups - - - -A replacement for dunst and other daemons using [Notifd](https://aylur.github.io/astal/guide/libraries/notifd). diff --git a/examples/js/notifications/app.ts b/examples/js/notifications/app.ts deleted file mode 100644 index ed53292..0000000 --- a/examples/js/notifications/app.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { App } from "astal/gtk3" -import style from "./style.scss" -import NotificationPopups from "./notifications/NotificationPopups" - -App.start({ - instanceName: "notifications", - css: style, - main: () => App.get_monitors().map(NotificationPopups), -}) diff --git a/examples/js/notifications/notifications/Notification.scss b/examples/js/notifications/notifications/Notification.scss deleted file mode 100644 index a32f08b..0000000 --- a/examples/js/notifications/notifications/Notification.scss +++ /dev/null @@ -1,125 +0,0 @@ -@use "sass:string"; - -@function gtkalpha($c, $a) { - @return string.unquote("alpha(#{$c},#{$a})"); -} - -// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss -$fg-color: #{"@theme_fg_color"}; -$bg-color: #{"@theme_bg_color"}; -$error: red; - -window.NotificationPopups { - all: unset; -} - -eventbox.Notification { - - &:first-child>box { - margin-top: 1rem; - } - - &:last-child>box { - margin-bottom: 1rem; - } - - // eventboxes can not take margins so we style its inner box instead - >box { - min-width: 400px; - border-radius: 13px; - background-color: $bg-color; - margin: .5rem 1rem .5rem 1rem; - box-shadow: 2px 3px 8px 0 gtkalpha(black, .4); - border: 1pt solid gtkalpha($fg-color, .03); - } - - &.critical>box { - border: 1pt solid gtkalpha($error, .4); - - .header { - - .app-name { - color: gtkalpha($error, .8); - - } - - .app-icon { - color: gtkalpha($error, .6); - } - } - } - - .header { - padding: .5rem; - color: gtkalpha($fg-color, 0.5); - - .app-icon { - margin: 0 .4rem; - } - - .app-name { - margin-right: .3rem; - font-weight: bold; - - &:first-child { - margin-left: .4rem; - } - } - - .time { - margin: 0 .4rem; - } - - button { - padding: .2rem; - min-width: 0; - min-height: 0; - } - } - - separator { - margin: 0 .4rem; - background-color: gtkalpha($fg-color, .1); - } - - .content { - margin: 1rem; - margin-top: .5rem; - - .summary { - font-size: 1.2em; - color: $fg-color; - } - - .body { - color: gtkalpha($fg-color, 0.8); - } - - .image { - border: 1px solid gtkalpha($fg-color, .02); - margin-right: .5rem; - border-radius: 9px; - min-width: 100px; - min-height: 100px; - background-size: cover; - background-position: center; - } - } - - .actions { - margin: 1rem; - margin-top: 0; - - button { - margin: 0 .3rem; - - &:first-child { - margin-left: 0; - } - - &:last-child { - margin-right: 0; - } - } - } -} diff --git a/examples/js/notifications/notifications/Notification.tsx b/examples/js/notifications/notifications/Notification.tsx deleted file mode 100644 index 5149d5b..0000000 --- a/examples/js/notifications/notifications/Notification.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { GLib } from "astal" -import { Gtk, Astal } from "astal/gtk3" -import { type EventBox } from "astal/gtk3/widget" -import Notifd from "gi://AstalNotifd" - -const isIcon = (icon: string) => - !!Astal.Icon.lookup_icon(icon) - -const fileExists = (path: string) => - GLib.file_test(path, GLib.FileTest.EXISTS) - -const time = (time: number, format = "%H:%M") => GLib.DateTime - .new_from_unix_local(time) - .format(format)! - -const urgency = (n: Notifd.Notification) => { - const { LOW, NORMAL, CRITICAL } = Notifd.Urgency - // match operator when? - switch (n.urgency) { - case LOW: return "low" - case CRITICAL: return "critical" - case NORMAL: - default: return "normal" - } -} - -type Props = { - setup(self: EventBox): void - onHoverLost(self: EventBox): void - notification: Notifd.Notification -} - -export default function Notification(props: Props) { - const { notification: n, onHoverLost, setup } = props - const { START, CENTER, END } = Gtk.Align - - return <eventbox - className={`Notification ${urgency(n)}`} - setup={setup} - onHoverLost={onHoverLost}> - <box vertical> - <box className="header"> - {(n.appIcon || n.desktopEntry) && <icon - className="app-icon" - visible={Boolean(n.appIcon || n.desktopEntry)} - icon={n.appIcon || n.desktopEntry} - />} - <label - className="app-name" - halign={START} - truncate - label={n.appName || "Unknown"} - /> - <label - className="time" - hexpand - halign={END} - label={time(n.time)} - /> - <button onClicked={() => n.dismiss()}> - <icon icon="window-close-symbolic" /> - </button> - </box> - <Gtk.Separator visible /> - <box className="content"> - {n.image && fileExists(n.image) && <box - valign={START} - className="image" - css={`background-image: url('${n.image}')`} - />} - {n.image && isIcon(n.image) && <box - expand={false} - valign={START} - className="icon-image"> - <icon icon={n.image} expand halign={CENTER} valign={CENTER} /> - </box>} - <box vertical> - <label - className="summary" - halign={START} - xalign={0} - label={n.summary} - truncate - /> - {n.body && <label - className="body" - wrap - useMarkup - halign={START} - xalign={0} - justifyFill - label={n.body} - />} - </box> - </box> - {n.get_actions().length > 0 && <box className="actions"> - {n.get_actions().map(({ label, id }) => ( - <button - hexpand - onClicked={() => n.invoke(id)}> - <label label={label} halign={CENTER} hexpand /> - </button> - ))} - </box>} - </box> - </eventbox> -} diff --git a/examples/js/notifications/notifications/NotificationPopups.tsx b/examples/js/notifications/notifications/NotificationPopups.tsx deleted file mode 100644 index 13fdd88..0000000 --- a/examples/js/notifications/notifications/NotificationPopups.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { Astal, Gtk, Gdk } from "astal/gtk3" -import Notifd from "gi://AstalNotifd" -import Notification from "./Notification" -import { type Subscribable } from "astal/binding" -import { Variable, bind, timeout } from "astal" - -// see comment below in constructor -const TIMEOUT_DELAY = 5000 - -// The purpose if this class is to replace Variable<Array<Widget>> -// with a Map<number, Widget> type in order to track notification widgets -// by their id, while making it conviniently bindable as an array -class NotifiationMap implements Subscribable { - // the underlying map to keep track of id widget pairs - private map: Map<number, Gtk.Widget> = new Map() - - // it makes sense to use a Variable under the hood and use its - // reactivity implementation instead of keeping track of subscribers ourselves - private var: Variable<Array<Gtk.Widget>> = Variable([]) - - // notify subscribers to rerender when state changes - private notifiy() { - this.var.set([...this.map.values()].reverse()) - } - - constructor() { - const notifd = Notifd.get_default() - - /** - * uncomment this if you want to - * ignore timeout by senders and enforce our own timeout - * note that if the notification has any actions - * they might not work, since the sender already treats them as resolved - */ - // notifd.ignoreTimeout = true - - notifd.connect("notified", (_, id) => { - this.set(id, Notification({ - notification: notifd.get_notification(id)!, - - // once hovering over the notification is done - // destroy the widget without calling notification.dismiss() - // so that it acts as a "popup" and we can still display it - // in a notification center like widget - // but clicking on the close button will close it - onHoverLost: () => this.delete(id), - - // notifd by default does not close notifications - // until user input or the timeout specified by sender - // which we set to ignore above - setup: () => timeout(TIMEOUT_DELAY, () => { - /** - * uncomment this if you want to "hide" the notifications - * after TIMEOUT_DELAY - */ - // this.delete(id) - }) - })) - }) - - // notifications can be closed by the outside before - // any user input, which have to be handled too - notifd.connect("resolved", (_, id) => { - this.delete(id) - }) - } - - private set(key: number, value: Gtk.Widget) { - // in case of replacecment destroy previous widget - this.map.get(key)?.destroy() - this.map.set(key, value) - this.notifiy() - } - - private delete(key: number) { - this.map.get(key)?.destroy() - this.map.delete(key) - this.notifiy() - } - - // needed by the Subscribable interface - get() { - return this.var.get() - } - - // needed by the Subscribable interface - subscribe(callback: (list: Array<Gtk.Widget>) => void) { - return this.var.subscribe(callback) - } -} - -export default function NotificationPopups(gdkmonitor: Gdk.Monitor) { - const { TOP, RIGHT } = Astal.WindowAnchor - const notifs = new NotifiationMap() - - return <window - className="NotificationPopups" - gdkmonitor={gdkmonitor} - exclusivity={Astal.Exclusivity.EXCLUSIVE} - anchor={TOP | RIGHT}> - <box vertical noImplicitDestroy> - {bind(notifs)} - </box> - </window> -} diff --git a/examples/js/notifications/style.scss b/examples/js/notifications/style.scss deleted file mode 100644 index 7ef0168..0000000 --- a/examples/js/notifications/style.scss +++ /dev/null @@ -1 +0,0 @@ -@use "./notifications/Notification.scss"; diff --git a/examples/js/simple-bar/README.md b/examples/js/simple-bar/README.md deleted file mode 100644 index f92b20e..0000000 --- a/examples/js/simple-bar/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Simple Bar Example - - - -A simple bar for Hyprland using - -- [Battery library](https://aylur.github.io/astal/guide/libraries/battery). -- [Hyprland library](https://aylur.github.io/astal/guide/libraries/hyprland). -- [Mpris library](https://aylur.github.io/astal/guide/libraries/mpris). -- [Network library](https://aylur.github.io/astal/guide/libraries/network). -- [Tray library](https://aylur.github.io/astal/guide/libraries/tray). -- [WirePlumber library](https://aylur.github.io/astal/guide/libraries/wireplumber). diff --git a/examples/js/simple-bar/app.ts b/examples/js/simple-bar/app.ts deleted file mode 100644 index 4b7ea48..0000000 --- a/examples/js/simple-bar/app.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { App } from "astal/gtk3" -import style from "./style.scss" -import Bar from "./widget/Bar" - -App.start({ - css: style, - instanceName: "js", - requestHandler(request, res) { - print(request) - res("ok") - }, - main: () => App.get_monitors().map(Bar), -}) diff --git a/examples/js/simple-bar/style.scss b/examples/js/simple-bar/style.scss deleted file mode 100644 index f5f771a..0000000 --- a/examples/js/simple-bar/style.scss +++ /dev/null @@ -1,106 +0,0 @@ -@use "sass:color"; - -$bg: #212223; -$fg: #f1f1f1; -$accent: #378DF7; -$radius: 7px; - -window.Bar { - border: none; - box-shadow: none; - background-color: $bg; - color: $fg; - font-size: 1.1em; - font-weight: bold; - - label { - margin: 0 8px; - } - - .Workspaces { - button { - all: unset; - background-color: transparent; - - &:hover label { - background-color: color.adjust($fg, $alpha: -0.84); - border-color: color.adjust($accent, $alpha: -0.8); - } - - &:active label { - background-color: color.adjust($fg, $alpha: -0.8) - } - } - - label { - transition: 200ms; - padding: 0 8px; - margin: 2px; - border-radius: $radius; - border: 1pt solid transparent; - } - - .focused label { - color: $accent; - border-color: $accent; - } - } - - .SysTray { - margin-right: 8px; - - button { - padding: 0 4px; - } - } - - .FocusedClient { - color: $accent; - } - - .Media .Cover { - min-height: 1.2em; - min-width: 1.2em; - border-radius: $radius; - background-position: center; - background-size: contain; - } - - .Battery label { - padding-left: 0; - margin-left: 0; - } - - .AudioSlider { - * { - all: unset; - } - - icon { - margin-right: .6em; - } - - & { - margin: 0 1em; - } - - trough { - background-color: color.adjust($fg, $alpha: -0.8); - border-radius: $radius; - } - - highlight { - background-color: $accent; - min-height: .8em; - border-radius: $radius; - } - - slider { - background-color: $fg; - border-radius: $radius; - min-height: 1em; - min-width: 1em; - margin: -.2em; - } - } -} diff --git a/examples/js/simple-bar/widget/Bar.tsx b/examples/js/simple-bar/widget/Bar.tsx deleted file mode 100644 index 6592f6a..0000000 --- a/examples/js/simple-bar/widget/Bar.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { App } from "astal/gtk3" -import { Variable, GLib, bind } from "astal" -import { Astal, Gtk, Gdk } from "astal/gtk3" -import Hyprland from "gi://AstalHyprland" -import Mpris from "gi://AstalMpris" -import Battery from "gi://AstalBattery" -import Wp from "gi://AstalWp" -import Network from "gi://AstalNetwork" -import Tray from "gi://AstalTray" - -function SysTray() { - const tray = Tray.get_default() - - return <box className="SysTray"> - {bind(tray, "items").as(items => items.map(item => ( - <menubutton - tooltipMarkup={bind(item, "tooltipMarkup")} - usePopover={false} - actionGroup={bind(item, "actionGroup").as(ag => ["dbusmenu", ag])} - menuModel={bind(item, "menuModel")}> - <icon gicon={bind(item, "gicon")} /> - </menubutton> - )))} - </box> -} - -function Wifi() { - const network = Network.get_default() - const wifi = bind(network, "wifi") - - return <box visible={wifi.as(Boolean)}> - {wifi.as(wifi => wifi && ( - <icon - tooltipText={bind(wifi, "ssid").as(String)} - className="Wifi" - icon={bind(wifi, "iconName")} - /> - ))} - </box> - -} - -function AudioSlider() { - const speaker = Wp.get_default()?.audio.defaultSpeaker! - - return <box className="AudioSlider" css="min-width: 140px"> - <icon icon={bind(speaker, "volumeIcon")} /> - <slider - hexpand - onDragged={({ value }) => speaker.volume = value} - value={bind(speaker, "volume")} - /> - </box> -} - -function BatteryLevel() { - const bat = Battery.get_default() - - return <box className="Battery" - visible={bind(bat, "isPresent")}> - <icon icon={bind(bat, "batteryIconName")} /> - <label label={bind(bat, "percentage").as(p => - `${Math.floor(p * 100)} %` - )} /> - </box> -} - -function Media() { - const mpris = Mpris.get_default() - - return <box className="Media"> - {bind(mpris, "players").as(ps => ps[0] ? ( - <box> - <box - className="Cover" - valign={Gtk.Align.CENTER} - css={bind(ps[0], "coverArt").as(cover => - `background-image: url('${cover}');` - )} - /> - <label - label={bind(ps[0], "title").as(() => - `${ps[0].title} - ${ps[0].artist}` - )} - /> - </box> - ) : ( - "Nothing Playing" - ))} - </box> -} - -function Workspaces() { - const hypr = Hyprland.get_default() - - return <box className="Workspaces"> - {bind(hypr, "workspaces").as(wss => wss - .filter(ws => !(ws.id >= -99 && ws.id <= -2)) // filter out special workspaces - .sort((a, b) => a.id - b.id) - .map(ws => ( - <button - className={bind(hypr, "focusedWorkspace").as(fw => - ws === fw ? "focused" : "")} - onClicked={() => ws.focus()}> - {ws.id} - </button> - )) - )} - </box> -} - -function FocusedClient() { - const hypr = Hyprland.get_default() - const focused = bind(hypr, "focusedClient") - - return <box - className="Focused" - visible={focused.as(Boolean)}> - {focused.as(client => ( - client && <label label={bind(client, "title").as(String)} /> - ))} - </box> -} - -function Time({ format = "%H:%M - %A %e." }) { - const time = Variable<string>("").poll(1000, () => - GLib.DateTime.new_now_local().format(format)!) - - return <label - className="Time" - onDestroy={() => time.drop()} - label={time()} - /> -} - -export default function Bar(monitor: Gdk.Monitor) { - const { TOP, LEFT, RIGHT } = Astal.WindowAnchor - - return <window - className="Bar" - gdkmonitor={monitor} - exclusivity={Astal.Exclusivity.EXCLUSIVE} - anchor={TOP | LEFT | RIGHT}> - <centerbox> - <box hexpand halign={Gtk.Align.START}> - <Workspaces /> - <FocusedClient /> - </box> - <box> - <Media /> - </box> - <box hexpand halign={Gtk.Align.END} > - <SysTray /> - <Wifi /> - <AudioSlider /> - <BatteryLevel /> - <Time /> - </box> - </centerbox> - </window> -} |