Add battery and notifications to QuickSettings popup
This commit is contained in:
parent
578797498e
commit
f6ddf3d4b7
|
|
@ -91,8 +91,7 @@ $module-bg: transparentize($base02, 0.4);
|
||||||
color: $base00;
|
color: $base00;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ws:hover,
|
.ws:hover {
|
||||||
.battery:hover {
|
|
||||||
background: transparentize($base05, 0.88);
|
background: transparentize($base05, 0.88);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,14 +253,23 @@ $module-bg: transparentize($base02, 0.4);
|
||||||
background: transparentize($base05, 0.88);
|
background: transparentize($base05, 0.88);
|
||||||
}
|
}
|
||||||
|
|
||||||
.battery-percent {
|
|
||||||
font-size: 1.6em;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.battery-level {
|
.battery-level {
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
margin-left: 3px;
|
}
|
||||||
|
|
||||||
|
.battery-section {
|
||||||
|
margin-top: 10px;
|
||||||
|
border-top: 1px solid $base02;
|
||||||
|
padding-top: 8px;
|
||||||
|
|
||||||
|
icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.battery-percent {
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.battery-status {
|
.battery-status {
|
||||||
|
|
@ -269,6 +277,49 @@ $module-bg: transparentize($base02, 0.4);
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notif-section {
|
||||||
|
margin-top: 10px;
|
||||||
|
border-top: 1px solid $base02;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notif-toggle {
|
||||||
|
padding: 4px 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: transparentize($base05, 0.88);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notif-clear {
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: transparentize($base05, 0.88);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notif-empty {
|
||||||
|
font-size: 0.85em;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quicksettings .notif-count {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notif-section .notif {
|
||||||
|
background: $base02;
|
||||||
|
}
|
||||||
|
|
||||||
.notifications {
|
.notifications {
|
||||||
.notif {
|
.notif {
|
||||||
background: $base01;
|
background: $base01;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { App, Astal, Gdk, Gtk } from "astal/gtk3"
|
||||||
import Workspaces from "./Workspaces"
|
import Workspaces from "./Workspaces"
|
||||||
import Logo from "./Logo"
|
import Logo from "./Logo"
|
||||||
import Clock from "./Clock"
|
import Clock from "./Clock"
|
||||||
import { BatteryWidget } from "./Battery"
|
|
||||||
import QuickSettings from "./QuickSettings"
|
import QuickSettings from "./QuickSettings"
|
||||||
|
|
||||||
const RADIUS = 12
|
const RADIUS = 12
|
||||||
|
|
@ -54,16 +53,11 @@ function Corner({ side }: { side: "left" | "right" }) {
|
||||||
|
|
||||||
export default function Bar(gdkmonitor: Gdk.Monitor) {
|
export default function Bar(gdkmonitor: Gdk.Monitor) {
|
||||||
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor
|
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor
|
||||||
const battery = BatteryWidget(gdkmonitor)
|
|
||||||
const quicksettings = QuickSettings(gdkmonitor)
|
const quicksettings = QuickSettings(gdkmonitor)
|
||||||
|
|
||||||
const right: Array<JSX.Element> = []
|
const right: Array<JSX.Element> = [quicksettings.button]
|
||||||
if (battery.button) right.push(battery.button)
|
|
||||||
right.push(quicksettings.button)
|
|
||||||
|
|
||||||
const popups: Array<JSX.Element> = []
|
const popups: Array<JSX.Element> = [quicksettings.popup]
|
||||||
if (battery.popup) popups.push(battery.popup)
|
|
||||||
popups.push(quicksettings.popup)
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
import Gtk from "gi://Gtk?version=3.0"
|
||||||
import AstalBattery from "gi://AstalBattery"
|
import AstalBattery from "gi://AstalBattery"
|
||||||
import { Variable, bind } from "astal"
|
import { type Binding, Variable, bind } from "astal"
|
||||||
import Dropdown from "./Dropdown"
|
|
||||||
|
|
||||||
const battery = AstalBattery.get_default()
|
const battery = AstalBattery.get_default()
|
||||||
|
|
||||||
|
|
@ -31,28 +31,20 @@ const status = Variable.derive(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
export function BatteryWidget(gdkmonitor: Gdk.Monitor) {
|
export const batteryIcon: Binding<string> | null = battery.isBattery
|
||||||
if (!battery.isBattery) return { button: null, popup: null }
|
? bind(battery, "icon-name")
|
||||||
|
: null
|
||||||
|
|
||||||
const open = Variable(false)
|
export const batteryPercent: Binding<string> | null = battery.isBattery
|
||||||
|
? bind(battery, "percentage").as((p) => `${Math.round(p * 100)}%`)
|
||||||
|
: null
|
||||||
|
|
||||||
const button = (
|
export function BatterySection() {
|
||||||
<button
|
if (!battery.isBattery) return <box />
|
||||||
className="battery"
|
|
||||||
onClicked={() => open.set(!open.get())}
|
|
||||||
>
|
|
||||||
<box>
|
|
||||||
<icon icon={bind(battery, "icon-name")} />
|
|
||||||
<label
|
|
||||||
className="battery-level"
|
|
||||||
label={bind(battery, "percentage").as((p) => `${Math.round(p * 100)}%`)}
|
|
||||||
/>
|
|
||||||
</box>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
|
|
||||||
const popup = (
|
return (
|
||||||
<Dropdown gdkmonitor={gdkmonitor} target={button} open={open} width={180}>
|
<box className="battery-section">
|
||||||
|
<icon icon={bind(battery, "icon-name")} />
|
||||||
<label
|
<label
|
||||||
className="battery-percent"
|
className="battery-percent"
|
||||||
label={bind(battery, "percentage").as((p) => `${Math.round(p * 100)}%`)}
|
label={bind(battery, "percentage").as((p) => `${Math.round(p * 100)}%`)}
|
||||||
|
|
@ -61,9 +53,9 @@ export function BatteryWidget(gdkmonitor: Gdk.Monitor) {
|
||||||
className="battery-status"
|
className="battery-status"
|
||||||
label={status()}
|
label={status()}
|
||||||
visible={status().as((text) => text !== "")}
|
visible={status().as((text) => text !== "")}
|
||||||
|
hexpand
|
||||||
|
halign={Gtk.Align.START}
|
||||||
/>
|
/>
|
||||||
</Dropdown>
|
</box>
|
||||||
)
|
)
|
||||||
|
|
||||||
return { button, popup }
|
|
||||||
}
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ function formatTime(n: Notifd.Notification) {
|
||||||
return GLib.DateTime.new_from_unix_local(n.time).format("%H:%M")!
|
return GLib.DateTime.new_from_unix_local(n.time).format("%H:%M")!
|
||||||
}
|
}
|
||||||
|
|
||||||
function NotificationCard(n: Notifd.Notification) {
|
export function NotificationCard(n: Notifd.Notification) {
|
||||||
const critical = n.urgency === Notifd.Urgency.CRITICAL
|
const critical = n.urgency === Notifd.Urgency.CRITICAL
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
|
import Gtk from "gi://Gtk?version=3.0"
|
||||||
import AstalWp from "gi://AstalWp"
|
import AstalWp from "gi://AstalWp"
|
||||||
|
import Notifd from "gi://AstalNotifd"
|
||||||
import { execAsync, Variable, bind } from "astal"
|
import { execAsync, Variable, bind } from "astal"
|
||||||
import Dropdown from "./Dropdown"
|
import Dropdown from "./Dropdown"
|
||||||
import { wifiIcon, WifiSection } from "./Wifi"
|
import { wifiIcon, WifiSection } from "./Wifi"
|
||||||
|
import { batteryIcon, batteryPercent, BatterySection } from "./Battery"
|
||||||
|
import { NotificationCard } from "./Notifications"
|
||||||
|
|
||||||
const speaker = AstalWp.get_default().audio.default_speaker!
|
const speaker = AstalWp.get_default().audio.default_speaker!
|
||||||
const mic = AstalWp.get_default().audio.default_microphone
|
const mic = AstalWp.get_default().audio.default_microphone
|
||||||
|
const notifd = Notifd.get_default()
|
||||||
|
|
||||||
|
const notifCount = bind(notifd, "notifications").as((ns) => ns.length)
|
||||||
|
|
||||||
const speakerIcon = Variable.derive(
|
const speakerIcon = Variable.derive(
|
||||||
[bind(speaker, "volume"), bind(speaker, "mute")],
|
[bind(speaker, "volume"), bind(speaker, "mute")],
|
||||||
|
|
@ -34,6 +41,11 @@ const actions = [
|
||||||
|
|
||||||
export default function QuickSettings(gdkmonitor: Gdk.Monitor) {
|
export default function QuickSettings(gdkmonitor: Gdk.Monitor) {
|
||||||
const open = Variable(false)
|
const open = Variable(false)
|
||||||
|
const notifOpen = Variable(false)
|
||||||
|
|
||||||
|
open.subscribe((v) => {
|
||||||
|
if (!v) notifOpen.set(false)
|
||||||
|
})
|
||||||
|
|
||||||
const button = (
|
const button = (
|
||||||
<button
|
<button
|
||||||
|
|
@ -48,9 +60,23 @@ export default function QuickSettings(gdkmonitor: Gdk.Monitor) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<box spacing={6}>
|
<box spacing={6}>
|
||||||
|
<box className="notif-indicator" spacing={3}>
|
||||||
|
<icon icon="preferences-system-notifications-symbolic" />
|
||||||
|
<label
|
||||||
|
className="notif-count"
|
||||||
|
label={notifCount.as((n) => `${n}`)}
|
||||||
|
visible={notifCount.as((n) => n > 0)}
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
{wifiIcon && <icon icon={wifiIcon} />}
|
||||||
<icon icon={speakerIcon()} />
|
<icon icon={speakerIcon()} />
|
||||||
{micIcon && <icon icon={micIcon()} />}
|
{micIcon && <icon icon={micIcon()} />}
|
||||||
{wifiIcon && <icon icon={wifiIcon} />}
|
{batteryIcon && batteryPercent && (
|
||||||
|
<box spacing={3}>
|
||||||
|
<icon icon={batteryIcon} />
|
||||||
|
<label className="battery-level" label={batteryPercent} />
|
||||||
|
</box>
|
||||||
|
)}
|
||||||
</box>
|
</box>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
@ -98,6 +124,51 @@ export default function QuickSettings(gdkmonitor: Gdk.Monitor) {
|
||||||
</box>
|
</box>
|
||||||
)}
|
)}
|
||||||
<WifiSection open={open} />
|
<WifiSection open={open} />
|
||||||
|
<BatterySection />
|
||||||
|
<box className="notif-section" vertical spacing={6}>
|
||||||
|
<box className="notif-header-row" spacing={6}>
|
||||||
|
<button
|
||||||
|
className="notif-toggle"
|
||||||
|
hexpand
|
||||||
|
halign={Gtk.Align.START}
|
||||||
|
onClicked={() => notifOpen.set(!notifOpen.get())}
|
||||||
|
>
|
||||||
|
<box spacing={6}>
|
||||||
|
<icon icon="preferences-system-notifications-symbolic" />
|
||||||
|
<label label="Notifications" />
|
||||||
|
<icon
|
||||||
|
icon={notifOpen().as((v) =>
|
||||||
|
v ? "pan-down-symbolic" : "pan-end-symbolic",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="notif-clear"
|
||||||
|
onClicked={() => [...notifd.notifications].forEach((n) => n.dismiss())}
|
||||||
|
>
|
||||||
|
<label label="Clear" />
|
||||||
|
</button>
|
||||||
|
</box>
|
||||||
|
<revealer
|
||||||
|
transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN}
|
||||||
|
transitionDuration={180}
|
||||||
|
revealChild={bind(notifOpen)}
|
||||||
|
>
|
||||||
|
<box className="notifications" vertical>
|
||||||
|
<label
|
||||||
|
className="notif-empty"
|
||||||
|
label="no notifications"
|
||||||
|
visible={notifCount.as((n) => n === 0)}
|
||||||
|
/>
|
||||||
|
{bind(notifd, "notifications").as((ns) =>
|
||||||
|
[...ns]
|
||||||
|
.sort((a, b) => b.time - a.time)
|
||||||
|
.map((n) => NotificationCard(n)),
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
</revealer>
|
||||||
|
</box>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue