bar: embedded style

This commit is contained in:
2 * r + 2 * t 2025-04-02 18:43:01 +11:00
parent 06e570e19e
commit 33e1ab0785
4 changed files with 163 additions and 41 deletions

View file

@ -2,7 +2,7 @@ import Bar from "@/modules/bar";
import Launcher from "@/modules/launcher"; import Launcher from "@/modules/launcher";
import NotifPopups from "@/modules/notifpopups"; import NotifPopups from "@/modules/notifpopups";
import Osds from "@/modules/osds"; import Osds from "@/modules/osds";
import ScreenCorners from "@/modules/screencorners"; import ScreenCorners, { BarScreenCorners } from "@/modules/screencorners";
import Session from "@/modules/session"; import Session from "@/modules/session";
import SideBar from "@/modules/sidebar"; import SideBar from "@/modules/sidebar";
import Calendar from "@/services/calendar"; import Calendar from "@/services/calendar";
@ -81,6 +81,7 @@ App.start({
Monitors.get_default().forEach(m => <SideBar monitor={m} />); Monitors.get_default().forEach(m => <SideBar monitor={m} />);
Monitors.get_default().forEach(m => <Bar monitor={m} />); Monitors.get_default().forEach(m => <Bar monitor={m} />);
Monitors.get_default().forEach(m => <ScreenCorners monitor={m} />); Monitors.get_default().forEach(m => <ScreenCorners monitor={m} />);
Monitors.get_default().forEach(m => <BarScreenCorners monitor={m} />);
// Init services // Init services
timeout(1000, () => { timeout(1000, () => {

View file

@ -15,7 +15,6 @@
@include font.mono; @include font.mono;
font-size: lib.s(14); font-size: lib.s(14);
padding-right: lib.s(3);
label.icon { label.icon {
font-size: lib.s(18); font-size: lib.s(18);
@ -183,12 +182,18 @@
} }
&.gaps { &.gaps {
padding-right: lib.s(3);
.module { .module {
@include lib.rounded(8); @include lib.rounded(8);
background-color: scheme.$base; background-color: scheme.$base;
} }
.screen-corner {
background-color: transparent;
}
.os-icon { .os-icon {
@include lib.border(scheme.$yellow); @include lib.border(scheme.$yellow);
@ -242,4 +247,62 @@
} }
} }
} }
&.embedded {
$-rounding: 23;
margin: 0;
.module {
background-color: scheme.$mantle;
}
.screen-corner {
@include lib.rounded($-rounding);
}
&.horizontal {
.module {
padding: lib.s(10) lib.s(10);
padding-left: lib.s(15);
}
.before-spacer {
border-bottom-right-radius: lib.s($-rounding);
padding-right: lib.s(15);
}
.after-spacer {
border-bottom-left-radius: lib.s($-rounding);
}
.power {
padding-right: lib.s(12);
}
}
&.vertical {
.module {
padding: lib.s(8) lib.s(10);
}
.before-spacer {
border-bottom-right-radius: lib.s($-rounding);
padding-bottom: lib.s(15);
}
.after-spacer {
border-top-right-radius: lib.s($-rounding);
padding-top: lib.s(15);
}
.os-icon {
padding-top: lib.s(12);
}
.power {
padding-bottom: lib.s(12);
}
}
}
} }

View file

@ -10,7 +10,7 @@ import { setupCustomTooltip } from "@/utils/widgets";
import ScreenCorner from "@/widgets/screencorner"; import ScreenCorner from "@/widgets/screencorner";
import { execAsync, Variable } from "astal"; import { execAsync, Variable } from "astal";
import Binding, { bind, kebabify } from "astal/binding"; import Binding, { bind, kebabify } from "astal/binding";
import { App, Astal, Gtk } from "astal/gtk3"; import { App, Astal, Gtk, type Widget } from "astal/gtk3";
import { bar as config } from "config"; import { bar as config } from "config";
import AstalBattery from "gi://AstalBattery"; import AstalBattery from "gi://AstalBattery";
import AstalBluetooth from "gi://AstalBluetooth"; import AstalBluetooth from "gi://AstalBluetooth";
@ -20,6 +20,11 @@ import AstalNotifd from "gi://AstalNotifd";
import AstalTray from "gi://AstalTray"; import AstalTray from "gi://AstalTray";
import AstalWp from "gi://AstalWp"; import AstalWp from "gi://AstalWp";
interface SpacerClassNameProps {
beforeSpacer?: boolean;
afterSpacer?: boolean;
}
const hyprland = AstalHyprland.get_default(); const hyprland = AstalHyprland.get_default();
const getBatteryIcon = (perc: number) => { const getBatteryIcon = (perc: number) => {
@ -74,19 +79,22 @@ const switchPane = (name: string) => {
} }
}; };
const OSIcon = () => ( const spacerClassName = ({ beforeSpacer, afterSpacer }: SpacerClassNameProps) =>
`${beforeSpacer ? "before-spacer" : ""} ${afterSpacer ? "after-spacer" : ""}`;
const OSIcon = (props: SpacerClassNameProps) => (
<button <button
className="module os-icon" className={`module os-icon ${spacerClassName(props)}`}
onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("dashboard")} onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("dashboard")}
> >
{osIcon} {osIcon}
</button> </button>
); );
const ActiveWindow = () => ( const ActiveWindow = (props: SpacerClassNameProps) => (
<box <box
vertical={bind(config.vertical)} vertical={bind(config.vertical)}
className="module active-window" className={`module active-window ${spacerClassName(props)}`}
setup={self => { setup={self => {
const title = Variable(""); const title = Variable("");
const updateTooltip = (c: AstalHyprland.Client | null) => const updateTooltip = (c: AstalHyprland.Client | null) =>
@ -124,7 +132,7 @@ const ActiveWindow = () => (
</box> </box>
); );
const MediaPlaying = () => { const MediaPlaying = (props: SpacerClassNameProps) => {
const players = Players.get_default(); const players = Players.get_default();
const getLabel = (fallback = "") => const getLabel = (fallback = "") =>
players.lastPlayer ? `${players.lastPlayer.title} - ${players.lastPlayer.artist}` : fallback; players.lastPlayer ? `${players.lastPlayer.title} - ${players.lastPlayer.artist}` : fallback;
@ -141,7 +149,7 @@ const MediaPlaying = () => {
setupCustomTooltip(self, bind(label)); setupCustomTooltip(self, bind(label));
}} }}
> >
<box vertical={bind(config.vertical)} className="module media-playing"> <box vertical={bind(config.vertical)} className={`module media-playing ${spacerClassName(props)}`}>
<icon <icon
setup={self => setup={self =>
players.hookLastPlayer(self, "notify::identity", () => { players.hookLastPlayer(self, "notify::identity", () => {
@ -211,7 +219,7 @@ const Workspace = ({ idx }: { idx: number }) => {
); );
}; };
const Workspaces = () => ( const Workspaces = (props: SpacerClassNameProps) => (
<eventbox <eventbox
onScroll={(_, event) => { onScroll={(_, event) => {
const activeWs = hyprland.focusedClient?.workspace.name; const activeWs = hyprland.focusedClient?.workspace.name;
@ -220,7 +228,7 @@ const Workspaces = () => (
hyprland.dispatch("workspace", (event.delta_y < 0 ? "-" : "+") + 1); hyprland.dispatch("workspace", (event.delta_y < 0 ? "-" : "+") + 1);
}} }}
> >
<box vertical={bind(config.vertical)} className="module workspaces"> <box vertical={bind(config.vertical)} className={`module workspaces ${spacerClassName(props)}`}>
{bind(config.modules.workspaces.shown).as( {bind(config.modules.workspaces.shown).as(
n => Array.from({ length: n }).map((_, idx) => <Workspace idx={idx + 1} />) // Start from 1 n => Array.from({ length: n }).map((_, idx) => <Workspace idx={idx + 1} />) // Start from 1
)} )}
@ -241,10 +249,10 @@ const TrayItem = (item: AstalTray.TrayItem) => (
</menubutton> </menubutton>
); );
const Tray = () => ( const Tray = (props: SpacerClassNameProps) => (
<box <box
vertical={bind(config.vertical)} vertical={bind(config.vertical)}
className="module tray" className={`module tray ${spacerClassName(props)}`}
visible={bind(AstalTray.get_default(), "items").as(i => i.length > 0)} visible={bind(AstalTray.get_default(), "items").as(i => i.length > 0)}
> >
{bind(AstalTray.get_default(), "items").as(i => i.map(TrayItem))} {bind(AstalTray.get_default(), "items").as(i => i.map(TrayItem))}
@ -420,14 +428,14 @@ const Bluetooth = () => (
</box> </box>
); );
const StatusIcons = () => ( const StatusIcons = (props: SpacerClassNameProps) => (
<box vertical={bind(config.vertical)} className="module status-icons"> <box vertical={bind(config.vertical)} className={`module status-icons ${spacerClassName(props)}`}>
<Network /> <Network />
<Bluetooth /> <Bluetooth />
</box> </box>
); );
const PkgUpdates = () => ( const PkgUpdates = (props: SpacerClassNameProps) => (
<button <button
onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("packages")} onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("packages")}
setup={self => setup={self =>
@ -437,14 +445,14 @@ const PkgUpdates = () => (
) )
} }
> >
<box vertical={bind(config.vertical)} className="module pkg-updates"> <box vertical={bind(config.vertical)} className={`module pkg-updates ${spacerClassName(props)}`}>
<label className="icon" label="download" /> <label className="icon" label="download" />
<label label={bind(Updates.get_default(), "numUpdates").as(String)} /> <label label={bind(Updates.get_default(), "numUpdates").as(String)} />
</box> </box>
</button> </button>
); );
const NotifCount = () => ( const NotifCount = (props: SpacerClassNameProps) => (
<button <button
onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("notifpane")} onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("notifpane")}
setup={self => setup={self =>
@ -456,7 +464,7 @@ const NotifCount = () => (
) )
} }
> >
<box vertical={bind(config.vertical)} className="module notif-count"> <box vertical={bind(config.vertical)} className={`module notif-count ${spacerClassName(props)}`}>
<label <label
className="icon" className="icon"
label={bind(AstalNotifd.get_default(), "dontDisturb").as(d => (d ? "notifications_off" : "info"))} label={bind(AstalNotifd.get_default(), "dontDisturb").as(d => (d ? "notifications_off" : "info"))}
@ -474,10 +482,10 @@ const NotifCount = () => (
</button> </button>
); );
const Battery = () => { const Battery = (props: SpacerClassNameProps) => {
const className = Variable.derive( const className = Variable.derive(
[bind(AstalBattery.get_default(), "percentage"), bind(AstalBattery.get_default(), "charging")], [bind(AstalBattery.get_default(), "percentage"), bind(AstalBattery.get_default(), "charging")],
(p, c) => `module battery ${c ? "charging" : p < 0.2 ? "low" : ""}` (p, c) => `module battery ${c ? "charging" : p < 0.2 ? "low" : ""} ${spacerClassName(props)}`
); );
const tooltip = Variable.derive( const tooltip = Variable.derive(
[bind(AstalBattery.get_default(), "timeToEmpty"), bind(AstalBattery.get_default(), "timeToFull")], [bind(AstalBattery.get_default(), "timeToEmpty"), bind(AstalBattery.get_default(), "timeToFull")],
@ -500,14 +508,14 @@ const Battery = () => {
); );
}; };
const DateTime = () => ( const DateTime = (props: SpacerClassNameProps) => (
<button <button
onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("time")} onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("time")}
setup={self => setup={self =>
setupCustomTooltip(self, bindCurrentTime(bind(config.modules.dateTime.detailedFormat), undefined, self)) setupCustomTooltip(self, bindCurrentTime(bind(config.modules.dateTime.detailedFormat), undefined, self))
} }
> >
<box className="module date-time"> <box className={`module date-time ${spacerClassName(props)}`}>
<label className="icon" label="calendar_month" /> <label className="icon" label="calendar_month" />
<label <label
setup={self => setup={self =>
@ -521,14 +529,14 @@ const DateTime = () => (
</button> </button>
); );
const DateTimeVertical = () => ( const DateTimeVertical = (props: SpacerClassNameProps) => (
<button <button
onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("time")} onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("time")}
setup={self => setup={self =>
setupCustomTooltip(self, bindCurrentTime(bind(config.modules.dateTime.detailedFormat), undefined, self)) setupCustomTooltip(self, bindCurrentTime(bind(config.modules.dateTime.detailedFormat), undefined, self))
} }
> >
<box vertical className="module date-time"> <box vertical className={`module date-time ${spacerClassName(props)}`}>
<label className="icon" label="calendar_month" /> <label className="icon" label="calendar_month" />
<label label={bindCurrentTime("%H")} /> <label label={bindCurrentTime("%H")} />
<label label={bindCurrentTime("%M")} /> <label label={bindCurrentTime("%M")} />
@ -536,14 +544,24 @@ const DateTimeVertical = () => (
</button> </button>
); );
const Power = () => ( const Power = (props: SpacerClassNameProps) => (
<button <button
className="module power" className={`module power ${spacerClassName(props)}`}
label="power_settings_new" label="power_settings_new"
onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && App.toggle_window("session")} onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && App.toggle_window("session")}
/> />
); );
const Spacer = ({ onScroll }: { onScroll: (self: Widget.EventBox, event: Astal.ScrollEvent) => void }) => (
<eventbox onScroll={onScroll}>
<box vertical={bind(config.vertical)}>
<ScreenCorner place="topleft" />
<box expand />
<ScreenCorner place={bind(config.vertical).as(v => (v ? "bottomleft" : "topright"))} />
</box>
</eventbox>
);
const Dummy = () => <box visible={false} />; // Invisible box cause otherwise shows as text const Dummy = () => <box visible={false} />; // Invisible box cause otherwise shows as text
const bindWidget = (module: keyof typeof config.modules, Widget: () => JSX.Element) => const bindWidget = (module: keyof typeof config.modules, Widget: () => JSX.Element) =>
@ -561,20 +579,26 @@ const Bar = ({ monitor }: { monitor: Monitor }) => {
return ( return (
<centerbox vertical={bind(config.vertical)} className={bind(className)} onDestroy={() => className.drop()}> <centerbox vertical={bind(config.vertical)} className={bind(className)} onDestroy={() => className.drop()}>
<box vertical={bind(config.vertical)}> <box vertical={bind(config.vertical)}>
{bindWidget("osIcon", OSIcon)} {bindWidget("osIcon", () => (
{bindWidget("activeWindow", ActiveWindow)} <OSIcon />
{bindWidget("mediaPlaying", MediaPlaying)} ))}
<button {bindWidget("activeWindow", () => (
expand <ActiveWindow />
))}
{bindWidget("mediaPlaying", () => (
<MediaPlaying beforeSpacer />
))}
<Spacer
onScroll={(_, event) => onScroll={(_, event) =>
event.delta_y > 0 ? (monitor.brightness -= 0.1) : (monitor.brightness += 0.1) event.delta_y > 0 ? (monitor.brightness -= 0.1) : (monitor.brightness += 0.1)
} }
/> />
</box> </box>
{bindWidget("workspaces", Workspaces)} {bindWidget("workspaces", () => (
<Workspaces beforeSpacer afterSpacer />
))}
<box vertical={bind(config.vertical)}> <box vertical={bind(config.vertical)}>
<button <Spacer
expand
onScroll={(_, event) => { onScroll={(_, event) => {
const speaker = AstalWp.get_default()?.audio.defaultSpeaker; const speaker = AstalWp.get_default()?.audio.defaultSpeaker;
if (!speaker) return console.error("Unable to connect to WirePlumber."); if (!speaker) return console.error("Unable to connect to WirePlumber.");
@ -583,10 +607,18 @@ const Bar = ({ monitor }: { monitor: Monitor }) => {
else speaker.volume += 0.1; else speaker.volume += 0.1;
}} }}
/> />
{bindWidget("tray", Tray)} {bindWidget("tray", () => (
{bindWidget("statusIcons", StatusIcons)} <Tray afterSpacer />
{bindWidget("pkgUpdates", PkgUpdates)} ))}
{bindWidget("notifCount", NotifCount)} {bindWidget("statusIcons", () => (
<StatusIcons />
))}
{bindWidget("pkgUpdates", () => (
<PkgUpdates />
))}
{bindWidget("notifCount", () => (
<NotifCount />
))}
{bindCompositeWidget( {bindCompositeWidget(
"battery", "battery",
bind(AstalBattery.get_default(), "isBattery").as(b => (b ? <Battery /> : <Dummy />)) bind(AstalBattery.get_default(), "isBattery").as(b => (b ? <Battery /> : <Dummy />))
@ -595,7 +627,9 @@ const Bar = ({ monitor }: { monitor: Monitor }) => {
"dateTime", "dateTime",
bind(config.vertical).as(v => (v ? <DateTimeVertical /> : <DateTime />)) bind(config.vertical).as(v => (v ? <DateTimeVertical /> : <DateTime />))
)} )}
{bindWidget("power", Power)} {bindWidget("power", () => (
<Power />
))}
</box> </box>
</centerbox> </centerbox>
); );
@ -616,8 +650,9 @@ export default ({ monitor }: { monitor: Monitor }) => (
<overlay <overlay
passThrough passThrough
overlays={[ overlays={[
<ScreenCorner place="topleft" />, <ScreenCorner visible={bind(config.style).as(s => s !== "embedded")} place="topleft" />,
<ScreenCorner <ScreenCorner
visible={bind(config.style).as(s => s !== "embedded")}
halign={bind(config.vertical).as(v => (v ? undefined : Gtk.Align.END))} halign={bind(config.vertical).as(v => (v ? undefined : Gtk.Align.END))}
valign={bind(config.vertical).as(v => (v ? Gtk.Align.END : undefined))} valign={bind(config.vertical).as(v => (v ? Gtk.Align.END : undefined))}
place={bind(config.vertical).as(v => (v ? "bottomleft" : "topright"))} place={bind(config.vertical).as(v => (v ? "bottomleft" : "topright"))}

View file

@ -26,3 +26,26 @@ export default ({ monitor }: { monitor: Monitor }) => (
</box> </box>
</window> </window>
); );
export const BarScreenCorners = ({ monitor }: { monitor: Monitor }) => (
<window
namespace="caelestia-screencorners"
monitor={monitor.id}
anchor={bind(bar.vertical).as(
v =>
Astal.WindowAnchor.TOP |
Astal.WindowAnchor.LEFT |
(v ? Astal.WindowAnchor.BOTTOM : Astal.WindowAnchor.RIGHT)
)}
visible={bind(bar.style).as(s => s === "embedded")}
setup={self =>
self.connect("size-allocate", () => self.get_window()?.input_shape_combine_region(new Cairo.Region(), 0, 0))
}
>
<box vertical={bind(bar.vertical)}>
<ScreenCorner place="topleft" />
<box expand />
<ScreenCorner place={bind(bar.vertical).as(v => (v ? "bottomleft" : "topright"))} />
</box>
</window>
);