chore: make workspaces fit in a single widget, add keyboard support for the logout menu

This commit is contained in:
retrozinndev
2025-06-28 13:29:33 -03:00
parent dece2776fe
commit 2bca31e601
9 changed files with 276 additions and 239 deletions
+10 -2
View File
@@ -69,9 +69,13 @@ entry {
right: 4px; right: 4px;
}; };
&:hover, &:focus { &:hover {
background: colors.$bg-secondary; background: colors.$bg-secondary;
} }
&:focus {
box-shadow: inset 0 0 0 2px colors.$fg-primary;
}
} }
} }
@@ -114,9 +118,13 @@ entry {
padding: 2px; padding: 2px;
border-radius: 8px; border-radius: 8px;
&:hover, &:focus { &:hover {
background: colors.$bg-secondary; background: colors.$bg-secondary;
} }
&:focus {
box-shadow: inset 0 0 0 1px colors.$fg-primary;
}
} }
& icon.close { & icon.close {
+6 -11
View File
@@ -5,8 +5,6 @@
@use "./functions"; @use "./functions";
.bar-container { .bar-container {
@include mixins.reset-props;
padding: 6px; padding: 6px;
padding-bottom: 0px; padding-bottom: 0px;
@@ -29,7 +27,7 @@
& > eventbox { & > eventbox {
&:hover { &:hover {
& > box:not(.workspaces):not(.special-workspaces) { & > box {
background: $color-hover; background: $color-hover;
} }
} }
@@ -38,7 +36,7 @@
margin: $padding 0; margin: $padding 0;
} }
& > box:not(.workspaces):not(.special-workspaces) { & > box {
padding: 0 8px; padding: 0 8px;
} }
} }
@@ -55,16 +53,15 @@
} }
} }
.workspaces, .special-workspaces { .workspaces-row {
@include mixins.reset-props; padding: 4px;
padding: 0 4px;
& > eventbox { & eventbox > box > eventbox {
& > box { & > box {
margin: 3px 0; margin: 3px 0;
border-radius: 16px; border-radius: 16px;
transition: 80ms linear; transition: 80ms linear;
min-width: 15px; min-width: 16px;
padding: 0 6px; padding: 0 6px;
background: colors.$bg-tertiary; background: colors.$bg-tertiary;
@@ -182,8 +179,6 @@
padding: 0 6px; padding: 0 6px;
& .item { & .item {
all: unset;
&:hover { &:hover {
background: none; background: none;
} }
+8
View File
@@ -23,6 +23,10 @@
} }
} }
/*& eventbox:focus, & button:focus {
box-shadow: inset 0 0 0 1px colors.$fg-primary;
}*/
& .quickactions { & .quickactions {
margin-bottom: .8em; margin-bottom: .8em;
@@ -188,6 +192,10 @@ box.history {
& button { & button {
padding: 6px; padding: 6px;
&:focus {
box-shadow: inset 0 0 0 1px colors.$fg-primary;
}
& icon { & icon {
font-size: 16px; font-size: 16px;
} }
+4
View File
@@ -24,6 +24,10 @@
font-size: 128px; font-size: 128px;
} }
&:focus {
box-shadow: inset 0 0 0 5px colors.$fg-primary;
}
margin: { margin: {
left: 4px; left: 4px;
right: 4px; right: 4px;
+20 -20
View File
@@ -1,26 +1,26 @@
// SCSS Variables // SCSS Variables
// Generated by 'wal' // Generated by 'wal'
$wallpaper: "/home/joaov/wallpapers/Gumi Forest Sunlight.jpg"; $wallpaper: "/home/joaov/wallpapers/Frieren Ring.jpeg";
// Special // Special
$background: #2a2825; $background: #523c42;
$foreground: #c9c9c8; $foreground: #d3cecf;
$cursor: #c9c9c8; $cursor: #d3cecf;
// Colors // Colors
$color0: #2a2825; $color0: #523c42;
$color1: #6a6a3b; $color1: #6c839d;
$color2: #7b7b48; $color2: #7a84a4;
$color3: #908a45; $color3: #9f8a9d;
$color4: #7e876d; $color4: #84a2b5;
$color5: #8a9680; $color5: #9f9cab;
$color6: #a5a679; $color6: #b7a1b2;
$color7: #a29f98; $color7: #b0a7a9;
$color8: #7d7667; $color8: #937b81;
$color9: #8E8E4F; $color9: #90AFD2;
$color10: #A5A560; $color10: #A3B0DB;
$color11: #C0B85C; $color11: #D4B9D2;
$color12: #A9B592; $color12: #B0D9F2;
$color13: #B9C8AB; $color13: #D5D0E5;
$color14: #DDDEA2; $color14: #F5D7EE;
$color15: #c9c9c8; $color15: #d3cecf;
-44
View File
@@ -1,44 +0,0 @@
import { bind, Variable } from "astal";
import { Gtk, Widget } from "astal/gtk3"
import AstalHyprland from "gi://AstalHyprland";
import { getAppIcon, getSymbolicIcon } from "../../scripts/apps";
export const SpecialWorkspaces: (() => Gtk.Widget) = () => new Widget.EventBox({
className: "special-ws-eventbox",
visible: bind(AstalHyprland.get_default(), "workspaces").as((workspaces) =>
workspaces.filter(ws => ws.id < 0).sort((a, b) => a.id - b.id).length > 0),
child: new Widget.Box({
className: "special-workspaces",
spacing: 4,
children: bind(AstalHyprland.get_default(), "workspaces").as((workspaces) =>
workspaces.filter(ws => ws.id < 0).sort((a, b) => a.id - b.id).map((workspace) =>
new Widget.EventBox({
className: bind(AstalHyprland.get_default(), "focusedWorkspace").as(focusWs =>
`${focusWs.id === workspace.id ? "focus" : ""}`),
tooltipText: bind(workspace, "name").as((name) => {
name = name.replace(/^special\:/, "");
return name.charAt(0).toUpperCase().concat(name.substring(1, name.length));
}),
child: new Widget.Box({
child: bind(workspace, "lastClient").as(lastClient =>
new Widget.Icon({
className: "last-app-icon",
visible: Variable.derive([
bind(workspace, "lastClient"),
bind(AstalHyprland.get_default(), "focusedWorkspace")
], (lastClient, focusedWorkspace) => focusedWorkspace?.id === workspace.id ?
false : Boolean(lastClient))(),
icon: bind(lastClient, "initialClass").as((initialClass) =>
getSymbolicIcon(initialClass) ?? getAppIcon(initialClass) ??
"application-x-executable-symbolic")
} as Widget.IconProps)
)
} as Widget.BoxProps),
onClickRelease: () => AstalHyprland.get_default().dispatch(
"togglespecialworkspace", workspace.name.replace(/^special\:/, "")
)
} as Widget.EventBoxProps)
)
)
} as Widget.BoxProps)
} as Widget.EventBoxProps);
+74 -8
View File
@@ -4,6 +4,7 @@ import AstalHyprland from "gi://AstalHyprland";
import { getAppIcon, getSymbolicIcon } from "../../scripts/apps"; import { getAppIcon, getSymbolicIcon } from "../../scripts/apps";
import { Windows } from "../../windows"; import { Windows } from "../../windows";
import { Config } from "../../scripts/config"; import { Config } from "../../scripts/config";
import { Separator, SeparatorProps } from "../Separator";
let showWsNum: (Variable<boolean>|undefined); let showWsNum: (Variable<boolean>|undefined);
export const showWorkspaceNumber = (show: boolean) => export const showWorkspaceNumber = (show: boolean) =>
@@ -13,7 +14,60 @@ export const showWorkspaceNumber = (show: boolean) =>
export function Workspaces(): Gtk.Widget { export function Workspaces(): Gtk.Widget {
showWsNum ??= new Variable<boolean>(false); showWsNum ??= new Variable<boolean>(false);
return new Widget.EventBox({ return new Widget.Box({
className: "workspaces-row",
orientation: Gtk.Orientation.HORIZONTAL,
children: [
new Widget.EventBox({
className: "special",
visible: bind(AstalHyprland.get_default(), "workspaces").as((workspaces) =>
workspaces.filter(ws => ws.id < 0).sort((a, b) => a.id - b.id).length > 0),
child: new Widget.Box({
className: "special-workspaces",
spacing: 4,
children: bind(AstalHyprland.get_default(), "workspaces").as((workspaces) =>
workspaces.filter(ws => ws.id < 0).sort((a, b) => a.id - b.id).map((workspace) =>
new Widget.EventBox({
className: bind(AstalHyprland.get_default(), "focusedWorkspace").as(focusWs =>
`${focusWs.id === workspace.id ? "focus" : ""}`),
tooltipText: bind(workspace, "name").as((name) => {
name = name.replace(/^special\:/, "");
return name.charAt(0).toUpperCase().concat(name.substring(1, name.length));
}),
child: new Widget.Box({
hexpand: true,
child: bind(workspace, "lastClient").as(lastClient =>
new Widget.Icon({
className: "last-app-icon",
halign: Gtk.Align.CENTER,
visible: Variable.derive([
bind(workspace, "lastClient"),
bind(AstalHyprland.get_default(), "focusedWorkspace")
], (lastClient, focusedWorkspace) => focusedWorkspace?.id === workspace.id ?
false : Boolean(lastClient))(),
icon: bind(lastClient, "initialClass").as((initialClass) =>
getSymbolicIcon(initialClass) ?? getAppIcon(initialClass) ??
"application-x-executable-symbolic")
} as Widget.IconProps)
)
} as Widget.BoxProps),
onClickRelease: () => AstalHyprland.get_default().dispatch(
"togglespecialworkspace", workspace.name.replace(/^special\:/, "")
)
} as Widget.EventBoxProps)
)
)
} as Widget.BoxProps)
} as Widget.EventBoxProps),
Separator({
alpha: .2,
orientation: Gtk.Orientation.HORIZONTAL,
margin: 12,
spacing: 8,
visible: bind(AstalHyprland.get_default(), "workspaces").as(wss =>
wss.filter(ws => ws.id < 0).length > 0)
} as SeparatorProps),
new Widget.EventBox({
onScroll: (_, event) => onScroll: (_, event) =>
event.delta_y > 0 ? event.delta_y > 0 ?
AstalHyprland.get_default().dispatch("workspace", "e-1") AstalHyprland.get_default().dispatch("workspace", "e-1")
@@ -83,33 +137,45 @@ export function Workspaces(): Gtk.Widget {
tooltipText.drop(); tooltipText.drop();
}, },
child: new Widget.Box({ child: new Widget.Box({
children: bind(workspace, "lastClient").as((lastClient) => [ hexpand: true,
children: bind(workspace, "lastClient").as((lastClient) => {
const widgets: Array<Gtk.Widget> = [
new Widget.Revealer({ new Widget.Revealer({
transitionDuration: 200, transitionDuration: 200,
transitionType: Gtk.RevealerTransitionType.SLIDE_LEFT, transitionType: Gtk.RevealerTransitionType.SLIDE_LEFT,
revealChild: showIds!(), revealChild: showIds!(),
hexpand: true,
child: new Widget.Label({ child: new Widget.Label({
label: bind(workspace, "id").as(String), label: bind(workspace, "id").as(String),
className: "id", className: "id",
hexpand: true
} as Widget.LabelProps) } as Widget.LabelProps)
} as Widget.RevealerProps), } as Widget.RevealerProps),
new Widget.Icon({ ];
if(lastClient) {
widgets.push(new Widget.Icon({
className: "last-app-icon", className: "last-app-icon",
halign: Gtk.Align.CENTER,
expand: true,
visible: bind(AstalHyprland.get_default(), "focusedWorkspace").as(focusedWorkspace => visible: bind(AstalHyprland.get_default(), "focusedWorkspace").as(focusedWorkspace =>
workspace.id === focusedWorkspace.id ? workspace.id === focusedWorkspace.id ?
false false
: Boolean(lastClient)), : Boolean(lastClient)),
icon: lastClient ? icon: lastClient ?
bind(lastClient, "class").as((clss) => bind(lastClient, "initialClass").as((clss) =>
getSymbolicIcon(clss) ?? getAppIcon(clss) ?? "application-x-executable-symbolic") getSymbolicIcon(clss) ?? getAppIcon(clss) ?? "application-x-executable-symbolic")
: undefined : undefined
} as Widget.IconProps) } as Widget.IconProps));
]) }
return widgets;
})
} as Widget.BoxProps) } as Widget.BoxProps)
} as Widget.EventBoxProps); } as Widget.EventBoxProps);
}) })
) )
} as Widget.BoxProps) } as Widget.BoxProps)
} as Widget.EventBoxProps); } as Widget.EventBoxProps)
]
} as Widget.BoxProps);
} }
-12
View File
@@ -7,10 +7,6 @@ import { Media } from "../widget/bar/Media";
import { Apps } from "../widget/bar/Apps"; import { Apps } from "../widget/bar/Apps";
import { Clock } from "../widget/bar/Clock"; import { Clock } from "../widget/bar/Clock";
import { Status } from "../widget/bar/Status"; import { Status } from "../widget/bar/Status";
import { SpecialWorkspaces } from "../widget/bar/SpecialWorkspaces";
import { Separator, SeparatorProps } from "../widget/Separator";
import AstalHyprland from "gi://AstalHyprland?version=0.1";
import { bind } from "astal";
export const Bar = (mon: number) => { export const Bar = (mon: number) => {
const widgetSpacing = 4; const widgetSpacing = 4;
@@ -36,14 +32,6 @@ export const Bar = (mon: number) => {
spacing: widgetSpacing, spacing: widgetSpacing,
children: [ children: [
Apps(), Apps(),
SpecialWorkspaces(),
Separator({
alpha: .2,
orientation: Gtk.Orientation.HORIZONTAL,
margin: 14,
visible: bind(AstalHyprland.get_default(), "workspaces").as(wss =>
wss.filter(ws => ws.id < 0).length > 0)
} as SeparatorProps),
Workspaces(), Workspaces(),
FocusedClient() FocusedClient()
] ]
+47 -35
View File
@@ -1,7 +1,7 @@
import { Astal, Gdk, Gtk, Widget } from "astal/gtk3"; import { Astal, Gdk, Gtk, Widget } from "astal/gtk3";
import { getDateTime } from "../scripts/time"; import { getDateTime } from "../scripts/time";
import { execAsync, Gio, GLib } from "astal"; import { execAsync, Gio, GLib } from "astal";
import { AskPopup } from "../widget/AskPopup"; import { AskPopup, AskPopupProps } from "../widget/AskPopup";
import { Windows } from "../windows"; import { Windows } from "../windows";
import { Notifications } from "../scripts/notifications"; import { Notifications } from "../scripts/notifications";
import AstalNotifd from "gi://AstalNotifd"; import AstalNotifd from "gi://AstalNotifd";
@@ -60,50 +60,41 @@ export const LogoutMenu = (mon: number) => new Widget.Window({
image: new Widget.Icon({ image: new Widget.Icon({
icon: "system-shutdown-symbolic" icon: "system-shutdown-symbolic"
} as Widget.IconProps), } as Widget.IconProps),
onClick: () => AskPopup({ onClick: () => AskPopup(poweroffAsk),
title: "Power Off", onActivate: () => AskPopup(poweroffAsk)
text: "Are you sure you want to power off? Unsaved work will be lost.",
onAccept: () => {
Config.getDefault().getProperty("night_light.save_on_shutdown", "boolean") &&
NightLight.getDefault().saveData();
execAsync("systemctl poweroff");
}
})
} as Widget.ButtonProps), } as Widget.ButtonProps),
new Widget.Button({ new Widget.Button({
className: "reboot", className: "reboot",
image: new Widget.Icon({ image: new Widget.Icon({
icon: "arrow-circular-top-right-symbolic" icon: "arrow-circular-top-right-symbolic"
} as Widget.IconProps), } as Widget.IconProps),
onClick: () => AskPopup({ onClick: () => AskPopup(rebootAsk),
title: "Reboot", onActivate: () => AskPopup(rebootAsk)
text: "Are you sure you want to Reboot? Unsaved work will be lost.",
onAccept: () => {
Config.getDefault().getProperty("night_light.save_on_shutdown", "boolean") &&
NightLight.getDefault().saveData();
execAsync("systemctl reboot");
}
})
} as Widget.ButtonProps), } as Widget.ButtonProps),
new Widget.Button({ new Widget.Button({
className: "suspend", className: "suspend",
image: new Widget.Icon({ image: new Widget.Icon({
icon: "weather-clear-night-symbolic" icon: "weather-clear-night-symbolic"
} as Widget.IconProps), } as Widget.IconProps),
onClick: () => AskPopup({ onClick: () => AskPopup(suspendAsk),
title: "Suspend", onActivate: () => AskPopup(suspendAsk)
text: "Are you sure you want to Suspend?",
onAccept: () => execAsync("systemctl suspend")
})
} as Widget.ButtonProps), } as Widget.ButtonProps),
new Widget.Button({ new Widget.Button({
className: "logout", className: "logout",
image: new Widget.Icon({ image: new Widget.Icon({
icon: "system-log-out-symbolic" icon: "system-log-out-symbolic"
} as Widget.IconProps), } as Widget.IconProps),
onClick: () => AskPopup({ onClick: () => AskPopup(logoutAsk),
onActivate: () => AskPopup(logoutAsk)
} as Widget.ButtonProps),
]
} as Widget.BoxProps)
]
})
} as Widget.EventBoxProps)
} as Widget.WindowProps);
const logoutAsk: AskPopupProps = {
title: "Log out", title: "Log out",
text: "Are you sure you want to log out? Your session will be ended.", text: "Are you sure you want to log out? Your session will be ended.",
onAccept: () => { onAccept: () => {
@@ -133,11 +124,32 @@ export const LogoutMenu = (mon: number) => new Widget.Window({
}) })
) )
} }
}) };
} as Widget.ButtonProps),
] const suspendAsk: AskPopupProps = {
} as Widget.BoxProps) title: "Suspend",
] text: "Are you sure you want to Suspend?",
}) onAccept: () => execAsync("systemctl suspend")
} as Widget.EventBoxProps) };
} as Widget.WindowProps);
const rebootAsk: AskPopupProps = {
title: "Reboot",
text: "Are you sure you want to Reboot? Unsaved work will be lost.",
onAccept: () => {
Config.getDefault().getProperty("night_light.save_on_shutdown", "boolean") &&
NightLight.getDefault().saveData();
execAsync("systemctl reboot");
}
};
const poweroffAsk: AskPopupProps = {
title: "Power Off",
text: "Are you sure you want to power off? Unsaved work will be lost.",
onAccept: () => {
Config.getDefault().getProperty("night_light.save_on_shutdown", "boolean") &&
NightLight.getDefault().saveData();
execAsync("systemctl poweroff");
}
};