From b1db74835134e856b6a753c397b5ba66c103efbd Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Wed, 4 Jun 2025 17:29:36 -0300 Subject: [PATCH] :sparkles: feat: use gtk icons instead of nerdfonts across the shell also removed hour count from recording widgets --- ags/app.ts | 1 + .../arrow-circular-top-right-symbolic.svg | 2 + ags/icons/circle-filled-symbolic.svg | 2 + ags/icons/hourglass-symbolic.svg | 2 + ags/icons/loop-arrow-symbolic.svg | 2 + ags/icons/minus-circle-filled-symbolic.svg | 2 + ags/icons/shield-danger-symbolic.svg | 2 + ags/icons/shield-safe-symbolic.svg | 2 + ags/icons/user-trash-symbolic.svg | 10 +++ ags/style.scss | 9 -- ags/style/_bar.scss | 30 +++---- ags/style/_center-window.scss | 4 +- ags/style/_control-center.scss | 51 ++++++----- ags/style/_functions.scss | 2 +- ags/style/_logout-menu.scss | 4 +- ags/widget/Notification.ts | 2 +- ags/widget/bar/Media.ts | 78 ++++++++++------- ags/widget/bar/Status.ts | 76 ++++++++-------- ags/widget/center-window/BigMedia.ts | 86 +++++++++++++------ ags/widget/control-center/NotifHistory.ts | 9 +- ags/widget/control-center/QuickActions.ts | 46 ++++++---- ags/widget/control-center/Sliders.ts | 27 +++--- ags/widget/control-center/pages/Bluetooth.ts | 62 ++++++++++--- ags/widget/control-center/pages/Network.ts | 11 +-- ags/widget/control-center/pages/Page.ts | 30 +++++-- ags/widget/control-center/tiles/Bluetooth.ts | 5 +- .../control-center/tiles/DoNotDisturb.ts | 2 +- ags/widget/control-center/tiles/Network.ts | 11 ++- ags/widget/control-center/tiles/NightLight.ts | 2 +- ags/widget/control-center/tiles/Recording.ts | 7 +- ags/widget/control-center/tiles/Tile.ts | 13 +-- ags/window/LogoutMenu.ts | 24 ++++-- 32 files changed, 375 insertions(+), 241 deletions(-) create mode 100644 ags/icons/arrow-circular-top-right-symbolic.svg create mode 100644 ags/icons/circle-filled-symbolic.svg create mode 100644 ags/icons/hourglass-symbolic.svg create mode 100644 ags/icons/loop-arrow-symbolic.svg create mode 100644 ags/icons/minus-circle-filled-symbolic.svg create mode 100644 ags/icons/shield-danger-symbolic.svg create mode 100644 ags/icons/shield-safe-symbolic.svg create mode 100644 ags/icons/user-trash-symbolic.svg diff --git a/ags/app.ts b/ags/app.ts index c566afc..1405e6f 100644 --- a/ags/app.ts +++ b/ags/app.ts @@ -38,6 +38,7 @@ const runnerPlugins: Array = [ App.start({ instanceName: "astal", + icons: "icons/", requestHandler: (request: string, response: (result: any) => void): void => { response(handleArguments(request)); }, diff --git a/ags/icons/arrow-circular-top-right-symbolic.svg b/ags/icons/arrow-circular-top-right-symbolic.svg new file mode 100644 index 0000000..9daa946 --- /dev/null +++ b/ags/icons/arrow-circular-top-right-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/ags/icons/circle-filled-symbolic.svg b/ags/icons/circle-filled-symbolic.svg new file mode 100644 index 0000000..d157bb6 --- /dev/null +++ b/ags/icons/circle-filled-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/ags/icons/hourglass-symbolic.svg b/ags/icons/hourglass-symbolic.svg new file mode 100644 index 0000000..b7fb825 --- /dev/null +++ b/ags/icons/hourglass-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/ags/icons/loop-arrow-symbolic.svg b/ags/icons/loop-arrow-symbolic.svg new file mode 100644 index 0000000..e643b76 --- /dev/null +++ b/ags/icons/loop-arrow-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/ags/icons/minus-circle-filled-symbolic.svg b/ags/icons/minus-circle-filled-symbolic.svg new file mode 100644 index 0000000..9aab303 --- /dev/null +++ b/ags/icons/minus-circle-filled-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/ags/icons/shield-danger-symbolic.svg b/ags/icons/shield-danger-symbolic.svg new file mode 100644 index 0000000..83c692f --- /dev/null +++ b/ags/icons/shield-danger-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/ags/icons/shield-safe-symbolic.svg b/ags/icons/shield-safe-symbolic.svg new file mode 100644 index 0000000..c24dc50 --- /dev/null +++ b/ags/icons/shield-safe-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/ags/icons/user-trash-symbolic.svg b/ags/icons/user-trash-symbolic.svg new file mode 100644 index 0000000..b6f8bd1 --- /dev/null +++ b/ags/icons/user-trash-symbolic.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ags/style.scss b/ags/style.scss index d649442..33c00bf 100644 --- a/ags/style.scss +++ b/ags/style.scss @@ -242,15 +242,6 @@ selection { background: colors.$bg-tertiary; } -label.nf, -button.nf label { - font-size: 12px; - font-family: "Symbols Nerd Font Mono", "Noto Sans Nerd Font Mono", - "0xProto Nerd Font Mono", "Fira Code Nerd Font Mono", - "Symbols Nerd Font", "Noto Sans Nerd Font", "Fira Code Nerd Font", - "Font Awesome"; -} - trough { background: functions.toRGB(color.adjust($color: wal.$color1, $lightness: -20%)); border-radius: 8px; diff --git a/ags/style/_bar.scss b/ags/style/_bar.scss index dd8d039..01b9911 100644 --- a/ags/style/_bar.scss +++ b/ags/style/_bar.scss @@ -149,7 +149,7 @@ box-shadow: inset 0 0 0 300px rgba(colors.$fg-primary, .2); } - & .icon { + & icon { font-size: 14px; } @@ -162,8 +162,8 @@ & > button { margin: 4px 1px; - & label { - font-size: 8px; + & icon { + font-size: 10px; } } } @@ -211,23 +211,23 @@ & > box { padding: 0 8px; - - & .nf { - margin: { - right: 3px; - left: 2px; - }; - - font-size: 12px; + & icon { + font-size: 14px; } & .status-icons { padding-left: 4px; - & .notification-count { - font-size: 5px; - margin-left: -3px; - margin-top: 1px; + & revealer > eventbox > box { + background: rgba($color: colors.$bg-tertiary, $alpha: .7); + border-radius: 12px; + margin: 4px 0; + padding: 2px 6px; + } + + & icon.notification-count { + font-size: 6px; + margin-top: -14px; } } } diff --git a/ags/style/_center-window.scss b/ags/style/_center-window.scss index c27d1c0..b077c66 100644 --- a/ags/style/_center-window.scss +++ b/ags/style/_center-window.scss @@ -51,11 +51,11 @@ & .bottom { & .controls { - margin-top: 5px; + margin-top: 9px; & button { padding: 7px; & label { - font-size: 10px; + font-size: 9px; } } } diff --git a/ags/style/_control-center.scss b/ags/style/_control-center.scss index 76c9f86..2a9a3d9 100644 --- a/ags/style/_control-center.scss +++ b/ags/style/_control-center.scss @@ -23,11 +23,19 @@ } & .quickactions { + margin-bottom: .8em; + & .hostname { font-size: 15px; font-weight: 600; } + & > box:not(.button-row) icon { + font-size: 12px; + color: colors.$fg-disabled; + margin-right: 3px; + } + & .uptime { font-size: 10.1px; font-family: "Symbols Nerd Font Mono"; @@ -46,31 +54,19 @@ } & .sliders { - padding: 0; - & > box { - margin: 8px 0; - - &:first-child { - margin-top: 0; - } - - &:last-child { - margin-bottom: 0; - } + icon { + font-size: 16px; } - label.nf, - button.nf label { - margin-right: 8px; - font-size: 15px; - } - - button.more { - @include mixins.hover-shadow; + button { + @include mixins.hover-shadow2; padding: 4px; border-radius: 16px; - margin-left: 6px; + + icon { + font-size: 14px; + } } & .page .content { @@ -133,6 +129,16 @@ & label { font-size: 14px; } + + & label.description { + font-size: 10px; + font-weight: 400; + color: colors.$fg-disabled; + } + + & icon { + font-size: 16px; + } } & .bottom-buttons button { @@ -176,14 +182,15 @@ box.history { & > .button-row { margin-top: 12px; + & button { padding: 6px; - & label.nf { + & icon { font-size: 16px; } - & label:not(.nf) { + & label { font-size: 12px; font-weight: 600; } diff --git a/ags/style/_functions.scss b/ags/style/_functions.scss index 18bc08f..2f0382d 100644 --- a/ags/style/_functions.scss +++ b/ags/style/_functions.scss @@ -2,7 +2,7 @@ /** - * GTK3 only supports sRGB color space, unfortunatly + * GTK3 only supports sRGB color space, unfortunately */ @function toRGB($color) { @return rgba( diff --git a/ags/style/_logout-menu.scss b/ags/style/_logout-menu.scss index 92c5988..617a48f 100644 --- a/ags/style/_logout-menu.scss +++ b/ags/style/_logout-menu.scss @@ -17,8 +17,8 @@ margin: 0 150px; & > button { - & label { - font-size: 96px; + & icon { + font-size: 128px; } margin: { diff --git a/ags/widget/Notification.ts b/ags/widget/Notification.ts index e9cefc2..20756d3 100644 --- a/ags/widget/Notification.ts +++ b/ags/widget/Notification.ts @@ -92,7 +92,7 @@ export function NotificationWidget(notification: AstalNotifd.Notification|number label: GLib.DateTime.new_from_unix_local(notification.time).format("%H:%M"), } as Widget.LabelProps), new Widget.Button({ - className: "close nf", + className: "close", onClick: () => onClose && onClose(notification), image: new Widget.Icon({ className: "close icon", diff --git a/ags/widget/bar/Media.ts b/ags/widget/bar/Media.ts index 2b43872..2b50554 100644 --- a/ags/widget/bar/Media.ts +++ b/ags/widget/bar/Media.ts @@ -1,16 +1,15 @@ -import { bind, execAsync, GLib } from "astal"; +import { bind } from "astal"; import { Gtk, Widget } from "astal/gtk3"; import AstalMpris from "gi://AstalMpris"; import { Separator, SeparatorProps } from "../Separator"; import { Windows } from "../../windows"; +import { Clipboard } from "../../scripts/clipboard"; const playerIcons = { - spotify: '󰓇', - clapper: '󰿎', - mpv: '', - spotube: '󰋋', - firefox: '󰈹' + spotify: "spotify-symbolic", + mpv: "mpv-symbolic", + Clapper: "com.github.rafostar.Clapper-symbolic" } export function Media(): Gtk.Widget { @@ -28,35 +27,46 @@ export function Media(): Gtk.Widget { children: bind(AstalMpris.get_default(), "players").as((players: Array) => players[0] ? [ new Widget.Button({ - className: "link nf", - label: "󰌹", + className: "link", + image: new Widget.Icon({ + icon: "edit-paste-symbolic" + } as Widget.IconProps), tooltipText: "Copy link to Clipboard", - visible: bind(players[0], "metadata").as((_metadata: GLib.HashTable) => - players[0].get_meta("xesam:url") === null), - onClick: () => execAsync(`sh -c "wl-copy \\"$(playerctl metadata 'xesam:url')\\""`) + visible: bind(players[0], "metadata").as((meta) => + Boolean(meta["xesam:url"]?.get_string()[0])), + onClick: () => Clipboard.getDefault().copyAsync( + players[0].metadata["xesam:url"].get_string()[0] + ) } as Widget.ButtonProps), new Widget.Button({ - className: "previous nf", - label: "󰒮", + className: "previous", + image: new Widget.Icon({ + icon: "media-skip-backward-symbolic" + } as Widget.IconProps), tooltipText: "Previous", onClick: () => players[0].canGoPrevious && players[0].previous() } as Widget.ButtonProps), new Widget.Button({ - className: "pause nf", - tooltipText: bind(players[0], "playback_status").as((status: AstalMpris.PlaybackStatus) => - status === AstalMpris.PlaybackStatus.PLAYING ? "Pause" : "Play"), - label: bind(players[0], "playbackStatus").as((status: AstalMpris.PlaybackStatus) => - status === AstalMpris.PlaybackStatus.PLAYING ? "󰏤" : "󰐊"), - onClick: () => { - players[0].playbackStatus === AstalMpris.PlaybackStatus.PAUSED ? - players[0].play() - : - players[0].pause() - } + className: "play-pause", + tooltipText: bind(players[0], "playback_status").as((status) => + status === AstalMpris.PlaybackStatus.PLAYING ? + "Pause" + : "Play"), + image: new Widget.Icon({ + icon: bind(players[0], "playbackStatus").as((status: AstalMpris.PlaybackStatus) => + status === AstalMpris.PlaybackStatus.PLAYING ? + "media-playback-pause-symbolic" + : "media-playback-start-symbolic") + } as Widget.IconProps), + onClick: () => players[0].playbackStatus === AstalMpris.PlaybackStatus.PAUSED ? + players[0].play() + : players[0].pause() } as Widget.ButtonProps), new Widget.Button({ - className: "next nf", - label: "󰒭", + className: "next", + image: new Widget.Icon({ + icon: "media-skip-forward-symbolic" + } as Widget.IconProps), tooltipText: "Next", onClick: () => players[0].canGoNext && players[0].next() } as Widget.ButtonProps) @@ -80,13 +90,17 @@ export function Media(): Gtk.Widget { spacing: 4, children: bind(AstalMpris.get_default(), "players").as((players: Array) => players[0] ? [ - new Widget.Label({ - className: "icon nf", - label: bind(players[0], "busName").as((busName: string) => { - const playerName: string = busName.split('.')[busName.split('.').length-1]; - return playerIcons[playerName.toLowerCase() as keyof typeof playerIcons] || "󰎇"; + new Widget.Icon({ + icon: bind(players[0], "busName").as((busName: string) => { + const splitName = busName.split('.').filter(str => str !== ""); + + return playerIcons[ + Object.keys(playerIcons).filter(icon => + splitName[splitName.length - 1].includes(icon))?.[0] as keyof typeof playerIcons + ] + ?? "folder-music-symbolic"; }) - } as Widget.LabelProps), + } as Widget.IconProps), new Widget.Label({ className: "title", label: bind(players[0], "title").as((title: string) => title || "No Title"), diff --git a/ags/widget/bar/Status.ts b/ags/widget/bar/Status.ts index d57c8ab..15b2564 100644 --- a/ags/widget/bar/Status.ts +++ b/ags/widget/bar/Status.ts @@ -24,13 +24,15 @@ export function Status(): Gtk.Widget { className: "sink", endpoint: Wireplumber.getDefault().getDefaultSink(), icon: bind(Wireplumber.getDefault().getDefaultSink(), "volumeIcon").as(icon => - !Wireplumber.getDefault().isMutedSink() && Wireplumber.getDefault().getSinkVolume() > 0 ? icon : "audio-volume-muted-symbolic"), + !Wireplumber.getDefault().isMutedSink() && Wireplumber.getDefault().getSinkVolume() > 0 ? + icon : "audio-volume-muted-symbolic"), }), volumeStatus({ className: "source", endpoint: Wireplumber.getDefault().getDefaultSource(), icon: bind(Wireplumber.getDefault().getDefaultSource(), "volumeIcon").as(icon => - !Wireplumber.getDefault().isMutedSource() && Wireplumber.getDefault().getSourceVolume() > 0 ? icon : "microphone-sensitivity-muted-symbolic"), + !Wireplumber.getDefault().isMutedSource() && Wireplumber.getDefault().getSourceVolume() > 0 ? + icon : "microphone-sensitivity-muted-symbolic"), }), StatusIcons() ] @@ -69,9 +71,10 @@ function StatusIcons(): Gtk.Widget { bind(AstalBluetooth.get_default(), "isConnected") ], (powered, connected) => { return powered ? ( - connected ? "󰂱" - : "󰂯" - ) : "󰂲" + connected ? + "bluetooth-active-symbolic" + : "bluetooth-symbolic" + ) : "bluetooth-disabled-symbolic" }); const networkIcon: Variable = Variable.derive([ @@ -82,15 +85,15 @@ function StatusIcons(): Gtk.Widget { (primary, wired, wifi) => { switch(primary) { case AstalNetwork.Primary.WIRED: return wired ? - "󰛳" - : "󰛵"; + "network-wired-symbolic" + : "network-wired-no-route-symbolic"; case AstalNetwork.Primary.WIFI: return wifi ? - "󰤨" - : "󰤭"; + "network-wireless-signal-excellent-symbolic" + : "network-wireless-offline-symbolic"; } - return "󰲊"; + return "network-no-route-symbolic"; }); const recordingTimer: Variable = Variable.derive([ @@ -103,18 +106,15 @@ function StatusIcons(): Gtk.Widget { const startedAtSeconds = dateTime.to_unix() - Recording.getDefault().startedAt!.to_unix(); if(startedAtSeconds <= 0) return "00:00"; - const hours = Math.floor(startedAtSeconds / 120); const minutes = Math.floor(startedAtSeconds / 60); const seconds = Math.floor(startedAtSeconds % 60); - return `${ hours > 0 ? `${hours < 10 ? `0${hours}` : hours }:` : "" - }${ minutes < 10 ? `0${minutes}` : minutes - }:${ seconds < 10 ? `0${seconds}` : seconds }`; + return `${ minutes < 10 ? `0${minutes}` : minutes }:${ seconds < 10 ? `0${seconds}` : seconds }`; }); return new Widget.Box({ className: "status-icons", - spacing: 3, + spacing: 8, children: [ new Widget.Revealer({ revealChild: bind(Recording.getDefault(), "recording"), @@ -127,10 +127,11 @@ function StatusIcons(): Gtk.Widget { tooltipText: tr("control_center.tiles.recording.enabled_desc"), child: new Widget.Box({ children: [ - new Widget.Label({ - className: "recording nf state", - label: '󰻃' - } as Widget.LabelProps), + new Widget.Icon({ + className: "recording state", + icon: "media-record-symbolic", + css: "margin-right: 4px;" + } as Widget.IconProps), new Widget.Label({ className: "rec-time", label: recordingTimer() @@ -139,32 +140,31 @@ function StatusIcons(): Gtk.Widget { } as Widget.BoxProps) } as Widget.EventBoxProps) } as Widget.RevealerProps), - new Widget.Label({ - className: "bluetooth nf state", + new Widget.Icon({ + className: "bluetooth state", visible: bind(AstalBluetooth.get_default(), "adapter").as(Boolean), - label: bluetoothIcon(), + icon: bluetoothIcon(), onDestroy: () => bluetoothIcon.drop() - } as Widget.LabelProps), - new Widget.Label({ - className: "network nf state", - label: networkIcon(), + } as Widget.IconProps), + new Widget.Icon({ + className: "network state", + icon: networkIcon(), onDestroy: () => networkIcon.drop() - } as Widget.LabelProps), + } as Widget.IconProps), new Widget.Box({ children: [ - new Widget.Label({ - className: "bell nf state", - label: bind(Notifications.getDefault().getNotifd(), "dontDisturb").as((dnd: boolean) => - dnd ? "󰂠" : "󰂚") - } as Widget.LabelProps), - new Widget.Label({ - className: "notification-count nf", - xalign: 0, - yalign: 0.25, + new Widget.Icon({ + className: "bell state", + icon: bind(Notifications.getDefault().getNotifd(), "dontDisturb").as((dnd) => + dnd ? "minus-circle-filled-symbolic" + : "preferences-system-notifications-symbolic") + } as Widget.IconProps), + new Widget.Icon({ + className: "notification-count", visible: bind(Notifications.getDefault(), "history").as(history => history.length > 0), - label: '󰧞' - } as Widget.LabelProps) + icon: "circle-filled-symbolic" + } as Widget.IconProps) ] } as Widget.BoxProps) ] diff --git a/ags/widget/center-window/BigMedia.ts b/ags/widget/center-window/BigMedia.ts index 52d9587..1d1731e 100644 --- a/ags/widget/center-window/BigMedia.ts +++ b/ags/widget/center-window/BigMedia.ts @@ -96,59 +96,89 @@ export function BigMedia(): Gtk.Widget { className: "controls button-row", children: [ new Widget.Button({ - className: "link nf", - label: "󰌹", + className: "link", + image: new Widget.Icon({ + icon: "edit-paste-symbolic" + } as Widget.IconProps), tooltipText: "Copy link to Clipboard", visible: bind(players[0], "metadata").as((_meta: GLib.HashTable) => players[0].get_meta("xesam:url") === null), onClick: () => execAsync(`sh -c "wl-copy \\"$(playerctl metadata 'xesam:url')\\""`) } as Widget.ButtonProps), new Widget.Button({ - className: "shuffle nf", - visible: bind(players[0], "shuffleStatus").as((shuffleStatus: AstalMpris.Shuffle) => + className: "shuffle", + visible: bind(players[0], "shuffleStatus").as((shuffleStatus) => shuffleStatus !== AstalMpris.Shuffle.UNSUPPORTED), - label: bind(players[0], "shuffleStatus").as((shuffleStatus: AstalMpris.Shuffle) => - shuffleStatus === AstalMpris.Shuffle.ON ? "󰒝" : "󰒞"), - tooltipText: "Toggle Shuffle", + image: new Widget.Icon({ + icon: bind(players[0], "shuffleStatus").as((shuffleStatus) => + shuffleStatus === AstalMpris.Shuffle.ON ? + "media-playlist-shuffle-symbolic" + : "media-playlist-consecutive-symbolic") + } as Widget.IconProps), + tooltipText: bind(players[0], "shuffleStatus").as((shuffleStatus) => + shuffleStatus === AstalMpris.Shuffle.ON ? + "Shuffle" + : "No shuffle"), onClick: () => players[0].shuffle() } as Widget.ButtonProps), new Widget.Button({ - className: "previous nf", - label: "󰒮", + className: "previous", + image: new Widget.Icon({ + icon: "media-skip-backward-symbolic" + } as Widget.IconProps), tooltipText: "Previous", onClick: () => players[0].canGoPrevious && players[0].previous() } as Widget.ButtonProps), new Widget.Button({ - className: "pause nf", - tooltipText: bind(players[0], "playback_status").as((status: AstalMpris.PlaybackStatus) => + className: "pause", + tooltipText: bind(players[0], "playback_status").as((status) => status === AstalMpris.PlaybackStatus.PLAYING ? "Pause" : "Play"), - label: bind(players[0], "playbackStatus").as((status: AstalMpris.PlaybackStatus) => - status === AstalMpris.PlaybackStatus.PLAYING ? "󰏤" : "󰐊"), - onClick: () => { - players[0].playbackStatus === AstalMpris.PlaybackStatus.PAUSED ? - players[0].play() - : - players[0].pause() - } + image: new Widget.Icon({ + icon: bind(players[0], "playbackStatus").as((status) => + status === AstalMpris.PlaybackStatus.PLAYING ? + "media-playback-pause-symbolic" + : "media-playback-start-symbolic"), + } as Widget.IconProps), + onClick: () => players[0].playbackStatus === AstalMpris.PlaybackStatus.PAUSED ? + players[0].play() + : players[0].pause() } as Widget.ButtonProps), new Widget.Button({ - className: "next nf", - label: "󰒭", + className: "next", + image: new Widget.Icon({ + icon: "media-skip-forward-symbolic" + } as Widget.IconProps), tooltipText: "Next", onClick: () => players[0].canGoNext && players[0].next() } as Widget.ButtonProps), new Widget.Button({ - className: "repeat nf", - visible: bind(players[0], "loopStatus").as((loopStatus: AstalMpris.Loop) => + className: "repeat", + visible: bind(players[0], "loopStatus").as((loopStatus) => loopStatus !== AstalMpris.Loop.UNSUPPORTED), - label: bind(players[0], "loopStatus").as((loopStatus: AstalMpris.Loop) => { + image: new Widget.Icon({ + icon: bind(players[0], "loopStatus").as((loopStatus) => { + switch(loopStatus) { + case AstalMpris.Loop.TRACK: + return "media-playlist-repeat-song-symbolic"; + + case AstalMpris.Loop.PLAYLIST: + return "media-playlist-repeat-symbolic"; + } + + return "loop-arrow-symbolic"; + }) + } as Widget.IconProps), + tooltipText: bind(players[0], "loopStatus").as((loopStatus) => { switch(loopStatus) { - case AstalMpris.Loop.TRACK: return "󰑘"; - case AstalMpris.Loop.PLAYLIST: return "󰑖"; - default: return "󰑗"; + case AstalMpris.Loop.TRACK: + return "Loop song"; + + case AstalMpris.Loop.PLAYLIST: + return "Loop playlist"; } + + return "No loop"; }), - tooltipText: "Toggle Loop", onClick: () => players[0].loop() } as Widget.ButtonProps) ] diff --git a/ags/widget/control-center/NotifHistory.ts b/ags/widget/control-center/NotifHistory.ts index f0ad01f..a572805 100644 --- a/ags/widget/control-center/NotifHistory.ts +++ b/ags/widget/control-center/NotifHistory.ts @@ -47,11 +47,10 @@ export const NotifHistory = () => { className: "clear-all", child: new Widget.Box({ children: [ - new Widget.Label({ - className: "nf", - css: "margin-right: 6px", - label: "󰎟" - } as Widget.LabelProps), + new Widget.Icon({ + css: "margin-right: 6px;", + icon: "edit-clear-all-symbolic" + } as Widget.IconProps), new Widget.Label({ label: tr("clear") } as Widget.LabelProps) diff --git a/ags/widget/control-center/QuickActions.ts b/ags/widget/control-center/QuickActions.ts index 88b5e22..58218f2 100644 --- a/ags/widget/control-center/QuickActions.ts +++ b/ags/widget/control-center/QuickActions.ts @@ -7,8 +7,9 @@ import { Wallpaper } from "../../scripts/wallpaper"; function LockButton(): Widget.Button { return new Widget.Button({ - className: "nf", - label: "󰌾", + image: new Widget.Icon({ + icon: "system-lock-screen-symbolic" + } as Widget.IconProps), onClick: () => { Windows.close("control-center"); AstalHyprland.get_default().dispatch("exec", "hyprlock"); @@ -18,8 +19,9 @@ function LockButton(): Widget.Button { function ColorPickerButton(): Widget.Button { return new Widget.Button({ - className: "nf", - label: "󰴱", + image: new Widget.Icon({ + icon: "color-select-symbolic" + } as Widget.IconProps), onClick: () => AstalHyprland.get_default().dispatch( "exec", "sh $HOME/.config/hypr/scripts/color-picker.sh" @@ -29,8 +31,9 @@ function ColorPickerButton(): Widget.Button { function ScreenshotButton(): Widget.Button { return new Widget.Button({ - className: "nf", - label: "󰹑", + image: new Widget.Icon({ + icon: "applets-screenshooter-symbolic" + } as Widget.IconProps), onClick: () => { Windows.close("control-center"); execAsync(`sh ${GLib.get_user_config_dir()}/hypr/scripts/screenshot.sh`); @@ -40,8 +43,9 @@ function ScreenshotButton(): Widget.Button { function SelectWallpaperButton(): Widget.Button { return new Widget.Button({ - className: "nf", - label: "󰸉", + image: new Widget.Icon({ + icon: "preferences-desktop-wallpaper-symbolic" + } as Widget.IconProps), onClick: () => { Windows.close("control-center"); Wallpaper.getDefault().pickWallpaper(); @@ -51,8 +55,9 @@ function SelectWallpaperButton(): Widget.Button { function LogoutButton(): Widget.Button { return new Widget.Button({ - className: "nf", - label: "󰗽", + image: new Widget.Icon({ + icon: "system-shutdown-symbolic" + } as Widget.IconProps), onClick: () => Windows.open("logout-menu") } as Widget.ButtonProps); } @@ -76,13 +81,20 @@ export const QuickActions = () => { tooltipText: "Host name", label: GLib.get_host_name() } as Widget.LabelProps), - new Widget.Label({ - className: "uptime", - xalign: 0, - tooltipText: "Uptime", - onDestroy: () => uptime.drop(), - label: uptime().as((uptime: string) => `󰥔 ${uptime}`) - } as Widget.LabelProps) + new Widget.Box({ + children: [ + new Widget.Icon({ + icon: "hourglass-symbolic" + } as Widget.IconProps), + new Widget.Label({ + className: "uptime", + xalign: 0, + tooltipText: "Uptime", + onDestroy: () => uptime.drop(), + label: uptime() + } as Widget.LabelProps) + ] + } as Widget.BoxProps) ] } as Widget.BoxProps), new Widget.Box({ diff --git a/ags/widget/control-center/Sliders.ts b/ags/widget/control-center/Sliders.ts index 7170a8d..156134a 100644 --- a/ags/widget/control-center/Sliders.ts +++ b/ags/widget/control-center/Sliders.ts @@ -12,20 +12,18 @@ export function Sliders() { className: "sliders", orientation: Gtk.Orientation.VERTICAL, expand: true, + spacing: 10, children: [ new Widget.Box({ className: "sink speaker", + spacing: 3, children: bind(Wireplumber.getWireplumber(), "defaultSpeaker").as((sink) => [ new Widget.Button({ - className: "nf", onClick: () => Wireplumber.getDefault().toggleMuteSink(), - children: [ - new Widget.Icon ({ - icon: bind(sink, "volumeIcon").as((icon) => - !Wireplumber.getDefault().isMutedSink() && Wireplumber.getDefault().getSinkVolume() > 0 ? icon : "audio-volume-muted-symbolic"), - css: "margin-right: 10px;" - } as Widget.IconProps), - ] + image: new Widget.Icon ({ + icon: bind(sink, "volumeIcon").as((icon) => + !Wireplumber.getDefault().isMutedSink() && Wireplumber.getDefault().getSinkVolume() > 0 ? icon : "audio-volume-muted-symbolic"), + } as Widget.IconProps), } as Widget.ButtonProps), new Widget.Slider({ drawValue: false, @@ -46,17 +44,14 @@ export function Sliders() { } as Widget.BoxProps), new Widget.Box({ className: "source microphone", + spacing: 3, children: bind(Wireplumber.getWireplumber(), "defaultMicrophone").as((source) => [ new Widget.Button({ - className: "nf", onClick: () => Wireplumber.getDefault().toggleMuteSource(), - children: [ - new Widget.Icon ({ - icon: bind(source, "volumeIcon").as((icon) => - !Wireplumber.getDefault().isMutedSource() && Wireplumber.getDefault().getSourceVolume() > 0 ? icon : "microphone-sensitivity-muted-symbolic"), - css: "margin-right: 10px;" - } as Widget.IconProps), - ] + image: new Widget.Icon ({ + icon: bind(source, "volumeIcon").as((icon) => + !Wireplumber.getDefault().isMutedSource() && Wireplumber.getDefault().getSourceVolume() > 0 ? icon : "microphone-sensitivity-muted-symbolic"), + } as Widget.IconProps), } as Widget.ButtonProps), new Widget.Slider({ drawValue: false, diff --git a/ags/widget/control-center/pages/Bluetooth.ts b/ags/widget/control-center/pages/Bluetooth.ts index 8a2defc..516c118 100644 --- a/ags/widget/control-center/pages/Bluetooth.ts +++ b/ags/widget/control-center/pages/Bluetooth.ts @@ -1,10 +1,12 @@ -import { bind, Variable } from "astal"; +import { bind, Gio, Variable } from "astal"; import { Gtk, Widget } from "astal/gtk3"; import AstalBluetooth from "gi://AstalBluetooth"; import { Page, PageButton } from "./Page"; import { tr } from "../../../i18n/intl"; import AstalHyprland from "gi://AstalHyprland"; import { Windows } from "../../../windows"; +import { Notifications } from "../../../scripts/notifications"; +import AstalNotifd from "gi://AstalNotifd"; export const BluetoothPage: (() => Page) = () => new Page({ id: "bluetooth", @@ -13,9 +15,13 @@ export const BluetoothPage: (() => Page) = () => new Page({ className: "bluetooth", headerButtons: [ new Widget.Button({ - className: "discover nf", - label: bind(AstalBluetooth.get_default().adapter, "discovering").as((discovering) => - !discovering ? '󰑓' : '󰙦'), + className: "discover", + image: new Widget.Icon({ + icon: bind(AstalBluetooth.get_default().adapter, "discovering").as((discovering) => + !discovering ? + "arrow-circular-top-right-symbolic" + : "media-playback-stop-symbolic") + } as Widget.IconProps), tooltipText: bind(AstalBluetooth.get_default().adapter, "discovering").as((discovering) => !discovering ? tr("control_center.pages.bluetooth.start_discovering") @@ -117,8 +123,11 @@ function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget { bind(dev, "trusted") ], (connected, paired, trusted) => paired ? [ new Widget.Button({ - className: "nf", - label: connected ? '󰅖' : "󰢃", + image: new Widget.Icon({ + icon: connected ? + "list-remove-symbolic" + : "user-trash-symbolic" + } as Widget.IconProps), tooltipText: tr(connected ? "disconnect" : "control_center.pages.bluetooth.unpair_device"), onClick: () => { if(!connected) { @@ -130,8 +139,11 @@ function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget { }, } as Widget.ButtonProps), new Widget.Button({ - className: "nf", - label: trusted ? "󰫜" : "󰫚", + image: new Widget.Icon({ + icon: trusted ? + "shield-safe-symbolic" + : "shield-danger-symbolic" + } as Widget.IconProps), tooltipText: tr(`control_center.pages.bluetooth.${trusted ? "un": ""}trust_device`), onClick: () => dev.set_trusted(!trusted) } as Widget.ButtonProps) @@ -141,15 +153,36 @@ function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget { className: bind(dev, "connected").as((connected) => connected ? "connected" : ""), title: bind(dev, "alias").as(alias => alias ?? "Unknown Device"), icon: dev.icon ?? "bluetooth-active-symbolic", + description: bind(dev, "connecting").as(connecting => + connecting ? `${tr("connecting")}...` : ""), tooltipText: bind(dev, "connected").as(connected => !connected ? tr("connect") : ""), onDestroy: () => devActions.drop(), onClick: () => { if(dev.connected) return; - if(!dev.paired) dev.pair(); - dev.connect_device(null); + let skipConnection: boolean = false; + if(!dev.paired) + (async () => dev.pair())().catch((err: Gio.IOErrorEnum) => { + skipConnection = true; + Notifications.getDefault().sendNotification({ + appName: "bluetooth", + summary: "Device pairing error", + body: `Couldn't connect to ${dev.alias ?? dev.name}, an error occurred: ${err.message || err.stack}`, + urgency: AstalNotifd.Urgency.NORMAL + }) + }); + + if(!skipConnection) + (async () => dev.connect_device(null))().catch((err: Gio.IOErrorEnum) => + Notifications.getDefault().sendNotification({ + appName: "bluetooth", + summary: "Device connection error", + body: `Couldn't connect to ${dev.alias ?? dev.name}, an error occurred: ${err.message || err.stack}`, + urgency: AstalNotifd.Urgency.NORMAL + }) + ); }, endWidget: new Widget.Box({ visible: bind(dev, "batteryPercentage").as((batt: number) => @@ -160,12 +193,13 @@ function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget { children: [ new Widget.Label({ halign: Gtk.Align.END, - label: bind(dev, "batteryPercentage").as((bat: number) => - `${Math.floor(bat * 100)}%`) + label: bind(dev, "batteryPercentage").as((batt: number) => + `${Math.floor(batt * 100)}%`) } as Widget.LabelProps), new Widget.Icon({ - icon: "battery-symbolic", - css: "font-size: 18px; margin-left: 6px;" + icon: bind(dev, "batteryPercentage").as(batt => + `battery-level-${Math.floor(batt * 100)}-symbolic`), + css: "font-size: 16px; margin-left: 6px;" } as Widget.IconProps) ] } as Widget.BoxProps) diff --git a/ags/widget/control-center/pages/Network.ts b/ags/widget/control-center/pages/Network.ts index efd62ee..ca0176c 100644 --- a/ags/widget/control-center/pages/Network.ts +++ b/ags/widget/control-center/pages/Network.ts @@ -13,11 +13,12 @@ export const PageNetwork: (() => Page) = () => new Page({ className: "network", headerButtons: [ new Widget.Button({ - className: "reload nf", - label: "󰑓", - visible: bind(AstalNetwork.get_default(), "primary").as( - (primary: AstalNetwork.Primary) => primary === AstalNetwork.Primary.WIFI - ), + className: "reload", + image: new Widget.Icon({ + icon: "arrow-circular-top-right-symbolic" + } as Widget.IconProps), + visible: bind(AstalNetwork.get_default(), "primary").as((primary) => + primary === AstalNetwork.Primary.WIFI), tooltipText: "Re-scan connections", onClick: () => AstalNetwork.get_default().wifi.scan() } as Widget.ButtonProps) diff --git a/ags/widget/control-center/pages/Page.ts b/ags/widget/control-center/pages/Page.ts index 74a5573..5098ea2 100644 --- a/ags/widget/control-center/pages/Page.ts +++ b/ags/widget/control-center/pages/Page.ts @@ -177,6 +177,7 @@ export function PageButton(props: { icon?: string | Binding; title: string | Binding; endWidget?: Gtk.Widget | Binding; + description?: string | Binding; extraButtons?: Array | Binding>; onDestroy?: (self: Widget.Box) => void; onClick?: (self: Widget.Button) => void; @@ -203,13 +204,28 @@ export function PageButton(props: { visible: props.icon, css: "font-size: 20px; margin-right: 6px;" } as Widget.IconProps), - new Widget.Label({ - className: "title", - halign: Gtk.Align.START, - hexpand: true, - truncate: true, - label: props.title - } as Widget.LabelProps), + new Widget.Box({ + orientation: Gtk.Orientation.VERTICAL, + expand: true, + children: [ + new Widget.Label({ + className: "title", + xalign: 0, + truncate: true, + label: props.title + } as Widget.LabelProps), + new Widget.Label({ + className: "description", + xalign: 0, + visible: (props.description instanceof Binding) ? + props.description.as(Boolean) + : Boolean(props.description), + label: props.description, + truncate: true, + tooltipText: props.description + } as Widget.LabelProps) + ] + } as Widget.BoxProps), new Widget.Box({ visible: (props.endWidget instanceof Binding) ? props.endWidget.as(Boolean) diff --git a/ags/widget/control-center/tiles/Bluetooth.ts b/ags/widget/control-center/tiles/Bluetooth.ts index 6c94af0..655ab06 100644 --- a/ags/widget/control-center/tiles/Bluetooth.ts +++ b/ags/widget/control-center/tiles/Bluetooth.ts @@ -11,7 +11,10 @@ export const TileBluetooth = () => { bind(AstalBluetooth.get_default(), "isConnected") ], (powered: boolean, isConnected: boolean) => - powered ? ( isConnected ? "󰂱" : "󰂯" ) : "󰂲" + powered ? ( isConnected ? + "bluetooth-active-symbolic" + : "bluetooth-symbolic" + ) : "bluetooth-disabled-symbolic" ); return Tile({ title: "Bluetooth", diff --git a/ags/widget/control-center/tiles/DoNotDisturb.ts b/ags/widget/control-center/tiles/DoNotDisturb.ts index f1226b7..5a08588 100644 --- a/ags/widget/control-center/tiles/DoNotDisturb.ts +++ b/ags/widget/control-center/tiles/DoNotDisturb.ts @@ -9,7 +9,7 @@ export const TileDND = Tile({ (dnd: boolean) => dnd ? tr("control_center.tiles.enabled") : tr("control_center.tiles.disabled")), onToggledOff: () => Notifications.getDefault().getNotifd().dontDisturb = false, onToggledOn: () => Notifications.getDefault().getNotifd().dontDisturb = true, - icon: "󰍶", + icon: "minus-circle-filled-symbolic", iconSize: 16, toggleState: Notifications.getDefault().getNotifd().dontDisturb }); diff --git a/ags/widget/control-center/tiles/Network.ts b/ags/widget/control-center/tiles/Network.ts index 62a2ef0..a68daaa 100644 --- a/ags/widget/control-center/tiles/Network.ts +++ b/ags/widget/control-center/tiles/Network.ts @@ -33,8 +33,7 @@ export const TileNetwork = () => new Widget.Box({ onToggledOn: () => wifi.set_enabled(true), onToggledOff: () => wifi.set_enabled(false), onClickMore: () => TilesPages?.toggle(PageNetwork()), - icon: "󰤨", - iconSize: 16, + icon: "network-wireless-signal-excellent-symbolic", toggleState: bind(wifi, "enabled") } as TileProps)(); @@ -57,12 +56,12 @@ export const TileNetwork = () => new Widget.Box({ icon: bind(wired, "internet").as((internet: AstalNetwork.Internet) => { switch(internet) { case AstalNetwork.Internet.CONNECTED: - return '󰛳'; + return "network-wired-symbolic"; case AstalNetwork.Internet.DISCONNECTED: - return '󰲛'; + return "network-wired-disconnected-symbolic"; } - return "󰛵"; + return "network-wired-no-route-symbolic"; }), iconSize: 16, toggleState: bind(wired, "internet").as((internet: AstalNetwork.Internet) => @@ -78,7 +77,7 @@ export const TileNetwork = () => new Widget.Box({ onToggledOn: () => execAsync("nmcli n on"), onToggledOff: () => execAsync("nmcli n off"), onClickMore: () => TilesPages?.toggle(PageNetwork()), - icon: "󰲛", + icon: "network-wired-disconnected-symbolic", iconSize: 16, toggleState: bind(wired, "internet").as((internet: AstalNetwork.Internet) => internet === AstalNetwork.Internet.CONNECTING || internet === AstalNetwork.Internet.CONNECTED) diff --git a/ags/widget/control-center/tiles/NightLight.ts b/ags/widget/control-center/tiles/NightLight.ts index 37e468c..55238d6 100644 --- a/ags/widget/control-center/tiles/NightLight.ts +++ b/ags/widget/control-center/tiles/NightLight.ts @@ -10,7 +10,7 @@ import { Widget } from "astal/gtk3"; export const TileNightLight = () => isInstalled("hyprsunset") ? Tile({ title: tr("control_center.tiles.night_light.title"), - icon: "󰖔", + icon: "weather-clear-night-symbolic", description: Variable.derive([ bind(NightLight.getDefault(), "temperature"), bind(NightLight.getDefault(), "gamma") diff --git a/ags/widget/control-center/tiles/Recording.ts b/ags/widget/control-center/tiles/Recording.ts index d53bbd2..47c7826 100644 --- a/ags/widget/control-center/tiles/Recording.ts +++ b/ags/widget/control-center/tiles/Recording.ts @@ -18,19 +18,16 @@ export const TileRecording = () => { const startedAtSeconds = dateTime.to_unix() - Recording.getDefault().startedAt!.to_unix(); if(startedAtSeconds <= 0) return "00:00"; - const hours = Math.floor(startedAtSeconds / 120); const minutes = Math.floor(startedAtSeconds / 60); const seconds = Math.floor(startedAtSeconds % 60); - return `${ hours > 0 ? `${hours < 10 ? `0${hours}` : hours }:` : "" - }${ minutes < 10 ? `0${minutes}` : minutes - }:${ seconds < 10 ? `0${seconds}` : seconds }`; + return `${ minutes < 10 ? `0${minutes}` : minutes }:${ seconds < 10 ? `0${seconds}` : seconds }`; }); return Tile({ title: tr("control_center.tiles.recording.title") || "Screen Recording", description: description(), - icon: "󰻂", + icon: "media-record-symbolic", visible: wfRecorderInstalled, onDestroy: () => description.drop(), onToggledOff: () => Recording.getDefault().stopRecording(), diff --git a/ags/widget/control-center/tiles/Tile.ts b/ags/widget/control-center/tiles/Tile.ts index 9ea2371..5261829 100644 --- a/ags/widget/control-center/tiles/Tile.ts +++ b/ags/widget/control-center/tiles/Tile.ts @@ -65,11 +65,14 @@ export function Tile(props: TileProps): (() => Gtk.Widget) { expand: true, hexpand: true, children: [ - new Widget.Label({ - className: "icon nf", - label: props.icon || "icon", - css: `label { font-size: ${props.iconSize || 12}px; }` - } as Widget.LabelProps), + new Widget.Icon({ + className: "icon", + icon: props.icon, + visible: (props.icon instanceof Binding) ? + props.icon.as(Boolean) + : Boolean(props.icon), + css: `font-size: ${props.iconSize ?? 16}px;` + } as Widget.IconProps), new Widget.Box({ className: "text", orientation: Gtk.Orientation.VERTICAL, diff --git a/ags/window/LogoutMenu.ts b/ags/window/LogoutMenu.ts index e15931d..ce043ad 100644 --- a/ags/window/LogoutMenu.ts +++ b/ags/window/LogoutMenu.ts @@ -52,8 +52,10 @@ export const LogoutMenu = (mon: number) => new Widget.Window({ height_request: 360, children: [ new Widget.Button({ - className: "poweroff nf", - label: "󰐥", + className: "poweroff", + image: new Widget.Icon({ + icon: "system-shutdown-symbolic" + } as Widget.IconProps), onClick: () => AskPopup({ title: "Power Off", text: "Are you sure you want to power off? Unsaved work will be lost.", @@ -64,8 +66,10 @@ export const LogoutMenu = (mon: number) => new Widget.Window({ }) } as Widget.ButtonProps), new Widget.Button({ - className: "reboot nf", - label: "󰜉", + className: "reboot", + image: new Widget.Icon({ + icon: "arrow-circular-top-right-symbolic" + } as Widget.IconProps), onClick: () => AskPopup({ title: "Reboot", text: "Are you sure you want to Reboot? Unsaved work will be lost.", @@ -76,8 +80,10 @@ export const LogoutMenu = (mon: number) => new Widget.Window({ }) } as Widget.ButtonProps), new Widget.Button({ - className: "suspend nf", - label: "󰤄", + className: "suspend", + image: new Widget.Icon({ + icon: "weather-clear-night-symbolic" + } as Widget.IconProps), onClick: () => AskPopup({ title: "Suspend", text: "Are you sure you want to Suspend?", @@ -85,8 +91,10 @@ export const LogoutMenu = (mon: number) => new Widget.Window({ }) } as Widget.ButtonProps), new Widget.Button({ - className: "logout nf", - label: "󰗽", + className: "logout", + image: new Widget.Icon({ + icon: "system-log-out-symbolic" + } as Widget.IconProps), onClick: () => AskPopup({ title: "Log out", text: "Are you sure you want to log out? Your session will be ended.",