mirror of
https://github.com/zephrynis/nix-flake.git
synced 2026-02-18 20:21:53 +00:00
feat: make AGS colorshell configuration fully declarative
- Add complete colorshell v2.0.3 configuration to home/ags-config/ - Disable runner plugin and NightLight tile (incompatible with NixOS) - Customize SCSS with full opacity (no transparency) - Add dark pale blue color scheme in home/pywal-colors/ - Configure Papirus-Dark icon theme via home-manager - Make ~/.config/ags/ immutable and managed by Nix store - Auto-deploy pywal colors to ~/.cache/wal/colors.json All AGS configuration is now reproducible and version controlled.
This commit is contained in:
13
home/ags-config/window/bar/widgets/Apps.tsx
Normal file
13
home/ags-config/window/bar/widgets/Apps.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Gtk } from "ags/gtk4";
|
||||
import { Windows } from "../../../windows";
|
||||
import { createBinding } from "ags";
|
||||
import { tr } from "../../../i18n/intl";
|
||||
|
||||
|
||||
export const Apps = () =>
|
||||
<Gtk.Button class={createBinding(Windows.getDefault(), "openWindows").as((openWindows) =>
|
||||
`apps ${Object.hasOwn(openWindows, "apps-window") ? "open" : ""}`
|
||||
)} iconName={"applications-other-symbolic"} halign={Gtk.Align.CENTER}
|
||||
hexpand tooltipText={tr("apps")} onClicked={() =>
|
||||
Windows.getDefault().open("apps-window")}
|
||||
/>;
|
||||
16
home/ags-config/window/bar/widgets/Clock.tsx
Normal file
16
home/ags-config/window/bar/widgets/Clock.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Gtk } from "ags/gtk4";
|
||||
import { Windows } from "../../../windows";
|
||||
import { createBinding } from "ags";
|
||||
import { time } from "../../../modules/utils";
|
||||
import { generalConfig } from "../../../config";
|
||||
|
||||
|
||||
export const Clock = () =>
|
||||
<Gtk.Button class={createBinding(Windows.getDefault(), "openWindows").as((wins) =>
|
||||
`clock ${wins.includes("center-window") ? "open" : ""}`)}
|
||||
onClicked={() => Windows.getDefault().toggle("center-window")}
|
||||
label={time((dt) => dt.format(
|
||||
generalConfig.getProperty("clock.date_format", "string"))
|
||||
?? "An error occurred"
|
||||
)}
|
||||
/>;
|
||||
42
home/ags-config/window/bar/widgets/FocusedClient.tsx
Normal file
42
home/ags-config/window/bar/widgets/FocusedClient.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { CompositorHyprland } from "../../../modules/compositors/hyprland";
|
||||
import { Gtk } from "ags/gtk4";
|
||||
import { createBinding, With } from "ags";
|
||||
import { variableToBoolean } from "../../../modules/utils";
|
||||
import { getAppIcon, getSymbolicIcon } from "../../../modules/apps";
|
||||
|
||||
import Pango from "gi://Pango?version=1.0";
|
||||
|
||||
const hyprland = new CompositorHyprland;
|
||||
|
||||
export const FocusedClient = () => {
|
||||
const focusedClient = createBinding(hyprland, "focusedClient");
|
||||
|
||||
return <Gtk.Box class={"focused-client"} visible={variableToBoolean(focusedClient)}>
|
||||
<With value={focusedClient}>
|
||||
{(focusedClient) => focusedClient?.class && <Gtk.Box>
|
||||
<Gtk.Image iconName={createBinding(focusedClient, "class").as((clss) =>
|
||||
getSymbolicIcon(clss) ?? getAppIcon(clss) ??
|
||||
getAppIcon(focusedClient.initialClass) ??
|
||||
"application-x-executable-symbolic"
|
||||
)} vexpand
|
||||
/>
|
||||
|
||||
<Gtk.Box valign={Gtk.Align.CENTER} class={"text-content"}
|
||||
orientation={Gtk.Orientation.VERTICAL}>
|
||||
|
||||
<Gtk.Label class={"class"} xalign={0} maxWidthChars={55}
|
||||
ellipsize={Pango.EllipsizeMode.END}
|
||||
label={createBinding(focusedClient, "class")}
|
||||
tooltipText={createBinding(focusedClient, "class")}
|
||||
/>
|
||||
|
||||
<Gtk.Label class={"title"} xalign={0} maxWidthChars={50}
|
||||
ellipsize={Pango.EllipsizeMode.END}
|
||||
label={createBinding(focusedClient, "title")}
|
||||
tooltipText={createBinding(focusedClient, "title")}
|
||||
/>
|
||||
</Gtk.Box>
|
||||
</Gtk.Box>}
|
||||
</With>
|
||||
</Gtk.Box>;
|
||||
}
|
||||
130
home/ags-config/window/bar/widgets/Media.tsx
Normal file
130
home/ags-config/window/bar/widgets/Media.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import { createBinding, With } from "ags";
|
||||
import { Gtk } from "ags/gtk4";
|
||||
import { Separator } from "../../../widget/Separator";
|
||||
import { Windows } from "../../../windows";
|
||||
import { Clipboard } from "../../../modules/clipboard";
|
||||
import { getPlayerIconFromBusName, secureBaseBinding, variableToBoolean } from "../../../modules/utils";
|
||||
import { tr } from "../../../i18n/intl";
|
||||
import { default as Player } from "../../../modules/media";
|
||||
|
||||
import AstalMpris from "gi://AstalMpris";
|
||||
import Pango from "gi://Pango?version=1.0";
|
||||
|
||||
|
||||
export const Media = () =>
|
||||
<Gtk.Box class={"media"} visible={createBinding(Player.getDefault(), "player").as(p => p.available)}>
|
||||
<Gtk.EventControllerScroll $={(self) => {
|
||||
self.set_flags(Gtk.EventControllerScrollFlags.VERTICAL)
|
||||
}} onScroll={(_, __, dy) => {
|
||||
if(AstalMpris.get_default().players.length === 1 &&
|
||||
Player.getDefault().player.busName === AstalMpris.get_default().players[0].busName)
|
||||
return true;
|
||||
|
||||
const players = AstalMpris.get_default().players;
|
||||
|
||||
for(let i = 0; i < players.length; i++) {
|
||||
const pl = players[i];
|
||||
|
||||
if(pl.busName !== Player.getDefault().player.busName)
|
||||
continue;
|
||||
|
||||
if(dy > 0 && players[i-1]) {
|
||||
Player.getDefault().player = players[i-1];
|
||||
break;
|
||||
}
|
||||
|
||||
if(dy < 0 && players[i+1]) {
|
||||
Player.getDefault().player = players[i+1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}}
|
||||
/>
|
||||
<Gtk.GestureClick onReleased={() => Windows.getDefault().toggle("center-window")} />
|
||||
<Gtk.EventControllerMotion onEnter={(self) => {
|
||||
const revealer = self.get_widget()!.get_last_child() as Gtk.Revealer;
|
||||
revealer.set_reveal_child(true);
|
||||
}} onLeave={(self) => {
|
||||
const revealer = self.get_widget()!.get_last_child() as Gtk.Revealer;
|
||||
revealer.set_reveal_child(false);
|
||||
}}
|
||||
/>
|
||||
<Gtk.Box spacing={4} visible={createBinding(Player.getDefault(), "player")
|
||||
.as(p => p.available)}>
|
||||
|
||||
<With value={createBinding(Player.getDefault(), "player")
|
||||
.as(p => p.available)}>
|
||||
|
||||
{(available: boolean) => available && <Gtk.Box>
|
||||
<Gtk.Image class={"player-icon"} iconName={
|
||||
secureBaseBinding<AstalMpris.Player>(createBinding(
|
||||
Player.getDefault(), "player"
|
||||
), "busName", "org.MediaPlayer2.folder-music-symbolic").as(
|
||||
getPlayerIconFromBusName
|
||||
)}
|
||||
/>
|
||||
<Gtk.Label class={"title"} label={secureBaseBinding<AstalMpris.Player>(createBinding(
|
||||
Player.getDefault(), "player"
|
||||
), "title", "").as(title => title ?? tr("media.no_title"))}
|
||||
maxWidthChars={20} ellipsize={Pango.EllipsizeMode.END}
|
||||
/>
|
||||
<Separator orientation={Gtk.Orientation.HORIZONTAL} size={1} margin={5}
|
||||
alpha={.3} spacing={6} />
|
||||
<Gtk.Label class={"artist"} label={secureBaseBinding<AstalMpris.Player>(createBinding(
|
||||
Player.getDefault(), "player"
|
||||
), "artist", "").as(artist => artist ?? tr("media.no_artist"))}
|
||||
maxWidthChars={18} ellipsize={Pango.EllipsizeMode.END}
|
||||
/>
|
||||
</Gtk.Box>}
|
||||
</With>
|
||||
</Gtk.Box>
|
||||
<Gtk.Revealer transitionType={Gtk.RevealerTransitionType.SLIDE_RIGHT} transitionDuration={260}
|
||||
revealChild={false}>
|
||||
|
||||
<With value={createBinding(Player.getDefault(), "player")
|
||||
.as(p => p.available)}>
|
||||
|
||||
{(available: boolean) => available && <Gtk.Box class={"buttons"} spacing={4}>
|
||||
<Gtk.Box class={"extra button-row"}>
|
||||
<Gtk.Button class={"link"} iconName={"edit-paste-symbolic"}
|
||||
visible={variableToBoolean(Player.accessMediaUrl(Player.getDefault().player))}
|
||||
tooltipText={tr("copy_to_clipboard")} onClicked={() => {
|
||||
const url = Player.getMediaUrl(Player.getDefault().player);
|
||||
url && Clipboard.getDefault().copyAsync(url);
|
||||
}}
|
||||
/>
|
||||
</Gtk.Box>
|
||||
<Gtk.Box class={"media-controls button-row"}>
|
||||
<Gtk.Button class={"previous"} iconName={"media-skip-backward-symbolic"}
|
||||
tooltipText={tr("media.previous")} onClicked={() =>
|
||||
Player.getDefault().player.canGoPrevious &&
|
||||
Player.getDefault().player.previous()
|
||||
}
|
||||
/>
|
||||
<Gtk.Button class={"play-pause"} iconName={secureBaseBinding<AstalMpris.Player>(
|
||||
createBinding(Player.getDefault(), "player"),
|
||||
"playbackStatus",
|
||||
AstalMpris.PlaybackStatus.PAUSED
|
||||
).as(status => status === AstalMpris.PlaybackStatus.PAUSED ?
|
||||
"media-playback-start-symbolic"
|
||||
: "media-playback-pause-symbolic"
|
||||
)}
|
||||
tooltipText={secureBaseBinding<AstalMpris.Player>(
|
||||
createBinding(Player.getDefault(), "player"),
|
||||
"playbackStatus",
|
||||
AstalMpris.PlaybackStatus.PAUSED
|
||||
).as(status => status === AstalMpris.PlaybackStatus.PAUSED ?
|
||||
tr("media.play") : tr("media.pause")
|
||||
)} onClicked={() => Player.getDefault().player.play_pause()}
|
||||
/>
|
||||
<Gtk.Button class={"next"} iconName={"media-skip-forward-symbolic"}
|
||||
tooltipText={tr("media.next")} onClicked={() => Player.getDefault().player.canGoNext &&
|
||||
Player.getDefault().player.next()}
|
||||
/>
|
||||
</Gtk.Box>
|
||||
</Gtk.Box>}
|
||||
</With>
|
||||
</Gtk.Revealer>
|
||||
</Gtk.Box> as Gtk.Box;
|
||||
218
home/ags-config/window/bar/widgets/Status.tsx
Normal file
218
home/ags-config/window/bar/widgets/Status.tsx
Normal file
@@ -0,0 +1,218 @@
|
||||
import { Gtk } from "ags/gtk4";
|
||||
import { Wireplumber } from "../../../modules/volume";
|
||||
import { Battery } from "../../../modules/battery";
|
||||
import { Notifications } from "../../../modules/notifications";
|
||||
import { Windows } from "../../../windows";
|
||||
import { Recording } from "../../../modules/recording";
|
||||
import { Accessor, createBinding, createComputed, With } from "ags";
|
||||
import { variableToBoolean } from "../../../modules/utils";
|
||||
import { Bluetooth } from "../../../modules/bluetooth";
|
||||
|
||||
import GObject from "ags/gobject";
|
||||
import AstalBluetooth from "gi://AstalBluetooth";
|
||||
import AstalNetwork from "gi://AstalNetwork";
|
||||
import AstalWp from "gi://AstalWp";
|
||||
|
||||
export const Status = () =>
|
||||
(
|
||||
<Gtk.Button
|
||||
class={createBinding(Windows.getDefault(), "openWindows").as((openWins) =>
|
||||
openWins.includes("control-center") ? "open status" : "status"
|
||||
)}
|
||||
onClicked={() => { console.log("Status clicked!"); Windows.getDefault().toggle("control-center"); }}
|
||||
>
|
||||
<Gtk.Box>
|
||||
<Gtk.Box class={"volume-indicators"} spacing={5}>
|
||||
<BatteryStatus
|
||||
visible={Battery.getDefault().bindHasBattery()}
|
||||
class="battery"
|
||||
icon={Battery.getDefault().bindIcon()}
|
||||
percentage={Battery.getDefault().bindPercentage()}
|
||||
></BatteryStatus>
|
||||
<VolumeStatus
|
||||
class="sink"
|
||||
endpoint={Wireplumber.getDefault().getDefaultSink()}
|
||||
icon={createBinding(
|
||||
Wireplumber.getDefault().getDefaultSink(),
|
||||
"volumeIcon"
|
||||
).as((icon) =>
|
||||
!Wireplumber.getDefault().isMutedSink() &&
|
||||
Wireplumber.getDefault().getSinkVolume() > 0
|
||||
? icon
|
||||
: "audio-volume-muted-symbolic"
|
||||
)}
|
||||
/>
|
||||
|
||||
<VolumeStatus
|
||||
class="source"
|
||||
endpoint={Wireplumber.getDefault().getDefaultSource()}
|
||||
icon={createBinding(
|
||||
Wireplumber.getDefault().getDefaultSource(),
|
||||
"volumeIcon"
|
||||
).as((icon) =>
|
||||
!Wireplumber.getDefault().isMutedSource() &&
|
||||
Wireplumber.getDefault().getSourceVolume() > 0
|
||||
? icon
|
||||
: "microphone-sensitivity-muted-symbolic"
|
||||
)}
|
||||
/>
|
||||
</Gtk.Box>
|
||||
<Gtk.Revealer
|
||||
revealChild={createBinding(Recording.getDefault(), "recording")}
|
||||
transitionDuration={500}
|
||||
transitionType={Gtk.RevealerTransitionType.SLIDE_LEFT}
|
||||
>
|
||||
<Gtk.Box>
|
||||
<Gtk.Image
|
||||
class={"recording state"}
|
||||
iconName={"media-record-symbolic"}
|
||||
css={"margin-right: 6px;"}
|
||||
/>
|
||||
|
||||
<Gtk.Label
|
||||
class={"rec-time"}
|
||||
label={createBinding(Recording.getDefault(), "recordingTime")}
|
||||
/>
|
||||
</Gtk.Box>
|
||||
</Gtk.Revealer>
|
||||
<StatusIcons />
|
||||
</Gtk.Box>
|
||||
</Gtk.Button>
|
||||
) as Gtk.Button;
|
||||
|
||||
function VolumeStatus(props: {
|
||||
class?: string;
|
||||
endpoint: AstalWp.Endpoint;
|
||||
icon?: string | Accessor<string>;
|
||||
}) {
|
||||
return (
|
||||
<Gtk.Box
|
||||
spacing={2}
|
||||
class={props.class}
|
||||
$={(self) => {
|
||||
const conns: Map<GObject.Object, number> = new Map();
|
||||
const controllerScroll = Gtk.EventControllerScroll.new(
|
||||
Gtk.EventControllerScrollFlags.VERTICAL |
|
||||
Gtk.EventControllerScrollFlags.KINETIC
|
||||
);
|
||||
|
||||
conns.set(
|
||||
controllerScroll,
|
||||
controllerScroll.connect("scroll", (_, _dx, dy) => {
|
||||
console.log`Scrolled! dx: ${_dx}; dy: ${dy}`;
|
||||
dy > 0
|
||||
? Wireplumber.getDefault().decreaseEndpointVolume(
|
||||
props.endpoint,
|
||||
5
|
||||
)
|
||||
: Wireplumber.getDefault().increaseEndpointVolume(
|
||||
props.endpoint,
|
||||
5
|
||||
);
|
||||
|
||||
return true;
|
||||
})
|
||||
);
|
||||
|
||||
conns.set(
|
||||
self,
|
||||
self.connect("destroy", () =>
|
||||
conns.forEach((id, obj) => obj.disconnect(id))
|
||||
)
|
||||
);
|
||||
}}
|
||||
>
|
||||
{props.icon && <Gtk.Image iconName={props.icon} />}
|
||||
<Gtk.Label
|
||||
class={"volume"}
|
||||
label={createBinding(props.endpoint, "volume").as(
|
||||
(vol) => `${Math.floor(vol * 100)}%`
|
||||
)}
|
||||
/>
|
||||
</Gtk.Box>
|
||||
) as Gtk.Box;
|
||||
}
|
||||
|
||||
function BatteryStatus(props: {
|
||||
visible?: Accessor<boolean>;
|
||||
class?: string;
|
||||
percentage?: Accessor<string>;
|
||||
icon?: string | Accessor<string>;
|
||||
}) {
|
||||
return (
|
||||
<Gtk.Box visible={props.visible} spacing={2} class={props.class}>
|
||||
{props.icon && <Gtk.Image iconName={props.icon} />}
|
||||
<Gtk.Label class={"level"} label={props.percentage} />
|
||||
</Gtk.Box>
|
||||
) as Gtk.Box;
|
||||
}
|
||||
|
||||
function StatusIcons() {
|
||||
return (
|
||||
<Gtk.Box class={"status-icons"} spacing={8}>
|
||||
<Gtk.Image
|
||||
iconName={createComputed(
|
||||
[
|
||||
createBinding(AstalBluetooth.get_default(), "isPowered"),
|
||||
createBinding(AstalBluetooth.get_default(), "isConnected"),
|
||||
],
|
||||
(powered, connected) => {
|
||||
return powered
|
||||
? connected
|
||||
? "bluetooth-active-symbolic"
|
||||
: "bluetooth-symbolic"
|
||||
: "bluetooth-disabled-symbolic";
|
||||
}
|
||||
)}
|
||||
class={"bluetooth state"}
|
||||
visible={createBinding(Bluetooth.getDefault(), "adapter").as(Boolean)}
|
||||
/>
|
||||
|
||||
<Gtk.Box
|
||||
visible={createBinding(AstalNetwork.get_default(), "primary").as(
|
||||
(primary) => primary !== AstalNetwork.Primary.UNKNOWN
|
||||
)}
|
||||
>
|
||||
<With value={createBinding(AstalNetwork.get_default(), "primary")}>
|
||||
{(primary: AstalNetwork.Primary) => {
|
||||
let device: AstalNetwork.Wifi | AstalNetwork.Wired;
|
||||
switch (primary) {
|
||||
case AstalNetwork.Primary.WIRED:
|
||||
device = AstalNetwork.get_default().wired;
|
||||
break;
|
||||
case AstalNetwork.Primary.WIFI:
|
||||
device = AstalNetwork.get_default().wifi;
|
||||
break;
|
||||
|
||||
default:
|
||||
return <Gtk.Image iconName={"network-no-route-symbolic"} />;
|
||||
}
|
||||
|
||||
return <Gtk.Image iconName={createBinding(device, "iconName")} />;
|
||||
}}
|
||||
</With>
|
||||
</Gtk.Box>
|
||||
|
||||
<Gtk.Box>
|
||||
<Gtk.Image
|
||||
class={"bell state"}
|
||||
iconName={createBinding(
|
||||
Notifications.getDefault().getNotifd(),
|
||||
"dontDisturb"
|
||||
).as((dnd) =>
|
||||
dnd
|
||||
? "minus-circle-filled-symbolic"
|
||||
: "preferences-system-notifications-symbolic"
|
||||
)}
|
||||
/>
|
||||
<Gtk.Image
|
||||
iconName={"circle-filled-symbolic"}
|
||||
class={"notification-count"}
|
||||
visible={variableToBoolean(
|
||||
createBinding(Notifications.getDefault(), "history")
|
||||
)}
|
||||
/>
|
||||
</Gtk.Box>
|
||||
</Gtk.Box>
|
||||
);
|
||||
}
|
||||
60
home/ags-config/window/bar/widgets/Tray.tsx
Normal file
60
home/ags-config/window/bar/widgets/Tray.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { createBinding, createComputed, For, With } from "ags";
|
||||
import { Gdk, Gtk } from "ags/gtk4";
|
||||
import { variableToBoolean } from "../../../modules/utils";
|
||||
|
||||
import GObject from "gi://GObject?version=2.0";
|
||||
import AstalTray from "gi://AstalTray"
|
||||
import Gio from "gi://Gio?version=2.0";
|
||||
|
||||
|
||||
const astalTray = AstalTray.get_default();
|
||||
|
||||
export const Tray = () => {
|
||||
const items = createBinding(astalTray, "items").as(items => items.filter(item => item?.gicon));
|
||||
|
||||
return <Gtk.Box class={"tray"} visible={variableToBoolean(items)} spacing={10}>
|
||||
<For each={items}>
|
||||
{(item: AstalTray.TrayItem) => <Gtk.Box class={"item"}>
|
||||
<With value={createComputed([
|
||||
createBinding(item, "actionGroup"),
|
||||
createBinding(item, "menuModel")
|
||||
])}>
|
||||
{([actionGroup, menuModel]: [Gio.ActionGroup, Gio.MenuModel]) => {
|
||||
const popover = Gtk.PopoverMenu.new_from_model(menuModel);
|
||||
popover.insert_action_group("dbusmenu", actionGroup);
|
||||
popover.hasArrow = false;
|
||||
|
||||
return <Gtk.Box class={"item"} tooltipMarkup={
|
||||
createBinding(item, "tooltipMarkup")
|
||||
} tooltipText={
|
||||
createBinding(item, "tooltipText")
|
||||
} $={(self) => {
|
||||
const conns: Map<GObject.Object, number> = new Map();
|
||||
const gestureClick = Gtk.GestureClick.new();
|
||||
gestureClick.set_button(0);
|
||||
|
||||
self.add_controller(gestureClick);
|
||||
|
||||
// Set popover parent to this box
|
||||
popover.set_parent(self);
|
||||
|
||||
conns.set(gestureClick, gestureClick.connect("released", (gesture, _, x, y) => {
|
||||
if(gesture.get_current_button() === Gdk.BUTTON_PRIMARY) {
|
||||
item.activate(x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
if(gesture.get_current_button() === Gdk.BUTTON_SECONDARY) {
|
||||
item.about_to_show();
|
||||
popover.popup();
|
||||
}
|
||||
}))
|
||||
}}>
|
||||
<Gtk.Image gicon={createBinding(item, "gicon")} pixelSize={16} />
|
||||
</Gtk.Box>;
|
||||
}}
|
||||
</With>
|
||||
</Gtk.Box>}
|
||||
</For>
|
||||
</Gtk.Box>
|
||||
}
|
||||
144
home/ags-config/window/bar/widgets/Workspaces.tsx
Normal file
144
home/ags-config/window/bar/widgets/Workspaces.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
import { Gtk } from "ags/gtk4";
|
||||
import { getAppIcon, getSymbolicIcon } from "../../../modules/apps";
|
||||
import { Separator } from "../../../widget/Separator";
|
||||
import { generalConfig } from "../../../config";
|
||||
import { createBinding, createComputed, createState, For, With } from "ags";
|
||||
import { variableToBoolean } from "../../../modules/utils";
|
||||
|
||||
import AstalHyprland from "gi://AstalHyprland";
|
||||
|
||||
|
||||
const [showNumbers, setShowNumbers] = createState(false);
|
||||
export const showWorkspaceNumber = (show: boolean) =>
|
||||
setShowNumbers(show);
|
||||
|
||||
|
||||
export const Workspaces = () => {
|
||||
const workspaces = createBinding(AstalHyprland.get_default(), "workspaces"),
|
||||
defaultWorkspaces = workspaces.as(wss =>
|
||||
wss.filter(ws => ws.id > 0).sort((a, b) => a.id - b.id)),
|
||||
specialWorkspaces = workspaces.as(wss =>
|
||||
wss.filter(ws => ws.id < 0).sort((a, b) => a.id - b.id)),
|
||||
focusedWorkspace = createBinding(AstalHyprland.get_default(), "focusedWorkspace");
|
||||
|
||||
|
||||
return <Gtk.Box class={"workspaces-row"} visible={createComputed([
|
||||
workspaces.as(wss => wss.length <= 1),
|
||||
generalConfig.bindProperty("workspaces.hide_if_single", "boolean")
|
||||
], (hideable, enabled) => enabled && hideable ? false : true
|
||||
)}>
|
||||
<Gtk.Box class={"special-workspaces"} spacing={4}>
|
||||
<For each={specialWorkspaces}>
|
||||
{(ws: AstalHyprland.Workspace) =>
|
||||
<Gtk.Button class={"workspace"}
|
||||
tooltipText={createBinding(ws, "name").as(name => {
|
||||
name = name.replace(/^special\:/, "");
|
||||
return name.charAt(0).toUpperCase().concat(name.substring(1, name.length));
|
||||
})} onClicked={() => AstalHyprland.get_default().dispatch(
|
||||
"togglespecialworkspace", ws.name.replace(/^special[:]/, "")
|
||||
)}>
|
||||
|
||||
<With value={createBinding(ws, "lastClient")}>
|
||||
{(lastClient: AstalHyprland.Client|null) => lastClient &&
|
||||
<Gtk.Image class="last-client" iconName={
|
||||
createBinding(lastClient, "initialClass").as(initialClass =>
|
||||
getSymbolicIcon(initialClass) ?? getAppIcon(initialClass) ??
|
||||
"application-x-executable-symbolic")}
|
||||
/>
|
||||
}
|
||||
</With>
|
||||
</Gtk.Button>
|
||||
}
|
||||
</For>
|
||||
</Gtk.Box>
|
||||
<Gtk.Revealer transitionType={Gtk.RevealerTransitionType.SLIDE_RIGHT}
|
||||
transitionDuration={220} revealChild={variableToBoolean(specialWorkspaces)}>
|
||||
|
||||
<Separator alpha={.2} orientation={Gtk.Orientation.HORIZONTAL}
|
||||
margin={12} spacing={8} visible={variableToBoolean(specialWorkspaces)}
|
||||
/>
|
||||
</Gtk.Revealer>
|
||||
<Gtk.Box class={"default-workspaces"} spacing={4}>
|
||||
<Gtk.EventControllerScroll $={(self) => self.set_flags(Gtk.EventControllerScrollFlags.VERTICAL)}
|
||||
onScroll={(_, __, dy) => {
|
||||
dy > 0 ?
|
||||
AstalHyprland.get_default().dispatch("workspace", "e-1")
|
||||
: AstalHyprland.get_default().dispatch("workspace", "e+1");
|
||||
|
||||
return true;
|
||||
}}
|
||||
/>
|
||||
<Gtk.EventControllerMotion onEnter={() => setShowNumbers(true)}
|
||||
onLeave={() => setShowNumbers(false)}
|
||||
/>
|
||||
<For each={defaultWorkspaces}>
|
||||
{(ws: AstalHyprland.Workspace, i) => {
|
||||
const showId = createComputed([
|
||||
generalConfig.bindProperty("workspaces.always_show_id", "boolean").as(Boolean),
|
||||
generalConfig.bindProperty("workspaces.enable_helper", "boolean").as(Boolean),
|
||||
showNumbers,
|
||||
i
|
||||
], (alwaysShowIds, enableHelper, showIds, i) => {
|
||||
if(enableHelper && !alwaysShowIds) {
|
||||
const previousWorkspace = defaultWorkspaces.get()[i-1];
|
||||
const nextWorkspace = defaultWorkspaces.get()[i+1];
|
||||
|
||||
if((defaultWorkspaces.get().filter((_, ii) => ii < i).length > 0 &&
|
||||
previousWorkspace?.id < (ws.id-1)) ||
|
||||
(defaultWorkspaces.get().filter((_, ii) => ii > i).length > 0 &&
|
||||
nextWorkspace?.id > (ws.id+1))
|
||||
|| (i === 0 && ws.id > 1)) {
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return alwaysShowIds || showIds;
|
||||
});
|
||||
|
||||
return <Gtk.Button class={createComputed([
|
||||
createBinding(AstalHyprland.get_default(), "focusedWorkspace"),
|
||||
showId
|
||||
], (focusedWs, showWsNumbers) =>
|
||||
`workspace ${focusedWs.id === ws.id ? "focus" : ""} ${
|
||||
showWsNumbers ? "show" : ""}`
|
||||
)} tooltipText={createComputed([
|
||||
createBinding(ws, "lastClient"),
|
||||
createBinding(AstalHyprland.get_default(), "focusedWorkspace")
|
||||
], (lastClient, focusWs) => focusWs.id === ws.id ? "" :
|
||||
`workspace ${ws.id}${ lastClient ? ` - ${
|
||||
!lastClient.title.toLowerCase().includes(lastClient.class) ?
|
||||
`${lastClient.get_class()}: `
|
||||
: ""
|
||||
} ${lastClient.title}` : "" }`
|
||||
)} onClicked={() => focusedWorkspace.get()?.id !== ws.id && ws.focus()}>
|
||||
|
||||
<With value={createBinding(ws, "lastClient")}>
|
||||
{(lastClient: AstalHyprland.Client) =>
|
||||
<Gtk.Box class={"last-client"} hexpand>
|
||||
<Gtk.Revealer transitionDuration={280} revealChild={showId}
|
||||
transitionType={focusedWorkspace.as(
|
||||
fws => fws.id !== ws.id ?
|
||||
Gtk.RevealerTransitionType.SLIDE_LEFT
|
||||
: Gtk.RevealerTransitionType.SLIDE_RIGHT
|
||||
)}>
|
||||
<Gtk.Label label={createBinding(ws, "id").as(String)}
|
||||
class={"id"} hexpand
|
||||
/>
|
||||
</Gtk.Revealer>
|
||||
{lastClient && <Gtk.Image class={"last-client-icon"} iconName={
|
||||
createBinding(lastClient, "initialClass").as(initialClass =>
|
||||
getSymbolicIcon(initialClass) ?? getAppIcon(initialClass) ??
|
||||
"application-x-executable-symbolic")}
|
||||
hexpand vexpand visible={createBinding(AstalHyprland.get_default(), "focusedWorkspace")
|
||||
.as(fws => fws.id !== ws.id)}
|
||||
/>}
|
||||
</Gtk.Box>
|
||||
}
|
||||
</With>
|
||||
</Gtk.Button>
|
||||
}}
|
||||
</For>
|
||||
</Gtk.Box>
|
||||
</Gtk.Box>
|
||||
}
|
||||
Reference in New Issue
Block a user