feat: polish base bar
Simplify the example design and popovers.
This commit is contained in:
parent
af86cc7beb
commit
1a23a45283
4 changed files with 169 additions and 53 deletions
50
style.scss
50
style.scss
|
@ -9,34 +9,25 @@
|
||||||
$bg: #{"@window_bg_color"};
|
$bg: #{"@window_bg_color"};
|
||||||
$fg: #{"@window_fg_color"};
|
$fg: #{"@window_fg_color"};
|
||||||
$accent: #{"@accent_color"};
|
$accent: #{"@accent_color"};
|
||||||
$radius: 7px;
|
|
||||||
|
|
||||||
window.Bar {
|
.Bar {
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
background-color: $bg;
|
background-color: $bg;
|
||||||
color: $fg;
|
color: $fg;
|
||||||
font-size: 1.2em;
|
font-size: 16px;
|
||||||
padding: 0.5em 0;
|
padding: 0.5rem;
|
||||||
|
|
||||||
centerbox {
|
|
||||||
min-height: 2.5em;
|
|
||||||
}
|
|
||||||
.Workspaces {
|
.Workspaces {
|
||||||
button {
|
button {
|
||||||
all: unset;
|
all: unset;
|
||||||
min-width: 1.1rem;
|
min-width: 0.9rem;
|
||||||
min-height: 1.1rem;
|
min-height: 0.9rem;
|
||||||
background-color: transparent;
|
|
||||||
border-radius: 50%;
|
|
||||||
|
|
||||||
border: 2px solid $fg;
|
border: 2px solid $fg;
|
||||||
margin: 0.25rem 0.3rem;
|
border-radius: 50%;
|
||||||
|
margin: 0em 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: gtkalpha($accent, 0.6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.focused {
|
.focused {
|
||||||
border-color: $accent;
|
border-color: $accent;
|
||||||
background-color: $accent;
|
background-color: $accent;
|
||||||
|
@ -44,13 +35,30 @@ window.Bar {
|
||||||
}
|
}
|
||||||
|
|
||||||
.Battery label {
|
.Battery label {
|
||||||
padding-left: 0;
|
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.NotificationCenter {
|
.QuickSettings {
|
||||||
&:hover {
|
image {
|
||||||
background-color: gtkalpha($fg, 0.2);
|
-gtk-icon-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menubutton box {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
menubutton button {
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 1em;
|
||||||
|
&:hover {
|
||||||
|
background-color: gtkalpha($fg, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
popover {
|
||||||
|
margin-top: 0.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { App, Astal, Gtk, Gdk } from "astal/gtk4";
|
import { Astal, Gtk, Gdk } from "astal/gtk4";
|
||||||
import { Variable, GLib, bind } from "astal";
|
import { Variable, GLib, bind } from "astal";
|
||||||
import Battery from "gi://AstalBattery";
|
import Battery from "gi://AstalBattery";
|
||||||
import Wp from "gi://AstalWp";
|
import Wp from "gi://AstalWp";
|
||||||
import Network from "gi://AstalNetwork";
|
import Network from "gi://AstalNetwork";
|
||||||
import { Workspaces } from "./Workspaces";
|
import Hyprland from "gi://AstalHyprland";
|
||||||
|
|
||||||
function Wifi() {
|
function Wifi() {
|
||||||
const network = Network.get_default();
|
const network = Network.get_default();
|
||||||
|
@ -25,7 +25,7 @@ function Audio() {
|
||||||
const speaker = Wp.get_default()?.audio.defaultSpeaker!;
|
const speaker = Wp.get_default()?.audio.defaultSpeaker!;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box cssClasses={["AudioSlider"]}>
|
<box cssClasses={["Audio"]}>
|
||||||
<image iconName={bind(speaker, "volumeIcon")} />
|
<image iconName={bind(speaker, "volumeIcon")} />
|
||||||
</box>
|
</box>
|
||||||
);
|
);
|
||||||
|
@ -51,10 +51,54 @@ function Time({ format = "%H:%M - %A %e." }) {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label cssClasses={["Time"]} onDestroy={() => time.drop()} label={time()} />
|
<box cssClasses={["Time"]}>
|
||||||
|
<menubutton>
|
||||||
|
<label label={time()} />
|
||||||
|
<popover hasArrow={false}>
|
||||||
|
<Gtk.Calendar />
|
||||||
|
</popover>
|
||||||
|
</menubutton>
|
||||||
|
</box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function QuickSettings() {
|
||||||
|
return (
|
||||||
|
<box cssClasses={["QuickSettings"]}>
|
||||||
|
<menubutton>
|
||||||
|
<box>
|
||||||
|
<Audio />
|
||||||
|
<Wifi />
|
||||||
|
<BatteryLevel />
|
||||||
|
</box>
|
||||||
|
<popover hasArrow={false}></popover>
|
||||||
|
</menubutton>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Workspaces() {
|
||||||
|
const hypr = Hyprland.get_default();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box cssClasses={["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
|
||||||
|
valign={Gtk.Align.CENTER}
|
||||||
|
cssClasses={bind(hypr, "focusedWorkspace").as((fw) =>
|
||||||
|
ws === fw ? ["focused"] : [""],
|
||||||
|
)}
|
||||||
|
onClicked={() => ws.focus()}
|
||||||
|
></button>
|
||||||
|
)),
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
export default function Bar(monitor: Gdk.Monitor) {
|
export default function Bar(monitor: Gdk.Monitor) {
|
||||||
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor;
|
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor;
|
||||||
|
|
||||||
|
@ -67,17 +111,15 @@ export default function Bar(monitor: Gdk.Monitor) {
|
||||||
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
||||||
anchor={TOP | LEFT | RIGHT}
|
anchor={TOP | LEFT | RIGHT}
|
||||||
>
|
>
|
||||||
<centerbox>
|
<centerbox shrinkCenterLast>
|
||||||
<box hexpand halign={Gtk.Align.START}>
|
<box hexpand halign={Gtk.Align.START}>
|
||||||
<Workspaces />
|
<Workspaces />
|
||||||
</box>
|
</box>
|
||||||
<box>
|
<box halign={Gtk.Align.CENTER}>
|
||||||
<Time />
|
<Time />
|
||||||
</box>
|
</box>
|
||||||
<box cssClasses={["NotificationCenter"]} hexpand halign={Gtk.Align.END}>
|
<box hexpand halign={Gtk.Align.END}>
|
||||||
<Audio />
|
<QuickSettings />
|
||||||
<Wifi />
|
|
||||||
<BatteryLevel />
|
|
||||||
</box>
|
</box>
|
||||||
</centerbox>
|
</centerbox>
|
||||||
</window>
|
</window>
|
||||||
|
|
88
widget/Popover.tsx
Normal file
88
widget/Popover.tsx
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import { Astal, Gdk, Gtk, Widget } from "astal/gtk4";
|
||||||
|
|
||||||
|
const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor;
|
||||||
|
|
||||||
|
type PopoverProps = Pick<
|
||||||
|
Widget.WindowProps,
|
||||||
|
| "name"
|
||||||
|
| "namespace"
|
||||||
|
| "className"
|
||||||
|
| "visible"
|
||||||
|
| "child"
|
||||||
|
| "marginBottom"
|
||||||
|
| "marginTop"
|
||||||
|
| "marginLeft"
|
||||||
|
| "marginRight"
|
||||||
|
| "halign"
|
||||||
|
| "valign"
|
||||||
|
> & {
|
||||||
|
onClose?(self): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Full screen window widget where you can space the child widget
|
||||||
|
* using margins and alignment properties.
|
||||||
|
*
|
||||||
|
* NOTE: Child widgets will assume they can span across the full window width
|
||||||
|
* this means that setting `wrap` or `ellipsize` on labels for example will not work
|
||||||
|
* without explicitly setting its `max_width_chars` property.
|
||||||
|
* For a workaround see Popover2.tsx
|
||||||
|
*/
|
||||||
|
export default function Popover({
|
||||||
|
child,
|
||||||
|
marginBottom,
|
||||||
|
marginTop,
|
||||||
|
marginLeft,
|
||||||
|
marginRight,
|
||||||
|
halign = Gtk.Align.CENTER,
|
||||||
|
valign = Gtk.Align.CENTER,
|
||||||
|
onClose,
|
||||||
|
...props
|
||||||
|
}: PopoverProps) {
|
||||||
|
return (
|
||||||
|
<window
|
||||||
|
{...props}
|
||||||
|
css="background-color: transparent"
|
||||||
|
keymode={Astal.Keymode.EXCLUSIVE}
|
||||||
|
anchor={TOP | BOTTOM | LEFT | RIGHT}
|
||||||
|
exclusivity={Astal.Exclusivity.IGNORE}
|
||||||
|
onNotifyVisible={(self) => {
|
||||||
|
if (!self.visible) onClose?.(self);
|
||||||
|
}}
|
||||||
|
// close when click occurs otside of child
|
||||||
|
onButtonPressEvent={(self, event) => {
|
||||||
|
const [, _x, _y] = event.get_coords();
|
||||||
|
const { x, y, width, height } = self.get_child()!.get_allocation();
|
||||||
|
|
||||||
|
const xOut = _x < x || _x > x + width;
|
||||||
|
const yOut = _y < y || _y > y + height;
|
||||||
|
|
||||||
|
// clicked outside
|
||||||
|
if (xOut || yOut) {
|
||||||
|
self.visible = false;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
// close when hitting Escape
|
||||||
|
onKeyPressEvent={(self, event: Gdk.Event) => {
|
||||||
|
if (event.get_keyval()[1] === Gdk.KEY_Escape) {
|
||||||
|
self.visible = false;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<box
|
||||||
|
// make sure click event does not bubble up
|
||||||
|
onButtonPressEvent={() => true}
|
||||||
|
// child can be positioned with `halign` `valign` and margins
|
||||||
|
expand
|
||||||
|
halign={halign}
|
||||||
|
valign={valign}
|
||||||
|
marginBottom={marginBottom}
|
||||||
|
marginTop={marginTop}
|
||||||
|
marginStart={marginLeft}
|
||||||
|
marginEnd={marginRight}
|
||||||
|
>
|
||||||
|
{child}
|
||||||
|
</box>
|
||||||
|
</window>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,24 +1,2 @@
|
||||||
import { bind } from "astal";
|
import { bind } from "astal";
|
||||||
import Hyprland from "gi://AstalHyprland";
|
import Hyprland from "gi://AstalHyprland";
|
||||||
|
|
||||||
export const Workspaces = () => {
|
|
||||||
const hypr = Hyprland.get_default();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<box cssClasses={["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
|
|
||||||
cssClasses={bind(hypr, "focusedWorkspace").as((fw) =>
|
|
||||||
ws === fw ? ["focused"] : [""],
|
|
||||||
)}
|
|
||||||
onClicked={() => ws.focus()}
|
|
||||||
></button>
|
|
||||||
)),
|
|
||||||
)}
|
|
||||||
</box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue