feat: update system

This commit is contained in:
2024-02-07 14:25:34 +01:00
parent 3bfeb8e6fc
commit 09afd0bef6
67 changed files with 933 additions and 1347 deletions

View File

@@ -1,22 +1,2 @@
import { readFile } from "resource:///com/github/Aylur/ags/utils.js"; import { default as main } from "./js/main.js";
import App from "resource:///com/github/Aylur/ags/app.js"; export default main;
import { timeout } from "resource:///com/github/Aylur/ags/utils.js";
const pkgjson = JSON.parse(readFile(App.configDir + "/package.json"));
timeout(1000, () => JSON.stringify(App));
const v = {
ags: `v${pkg.version}`,
expected: `v${pkgjson.version}`,
};
function mismatch() {
print(`my config expects ${v.expected}, but your ags is ${v.ags}`);
App.connect("config-parsed", (app) => app.Quit());
return {};
}
/*export default v.ags === v.expected
? (await import("./js/main.js")).default
: mismatch();*/
export default (await import("./js/main.js")).default;

View File

@@ -1,6 +1,3 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import PopupWindow from "../misc/PopupWindow.js"; import PopupWindow from "../misc/PopupWindow.js";
import icons from "../icons.js"; import icons from "../icons.js";

View File

@@ -1,7 +1,4 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import options from "../options.js"; import options from "../options.js";
import { lookUpIcon } from "resource:///com/github/Aylur/ags/utils.js";
/** @param {import('resource:///com/github/Aylur/ags/service/applications.js').Application} app */ /** @param {import('resource:///com/github/Aylur/ags/service/applications.js').Application} app */
export default (app) => { export default (app) => {
@@ -23,7 +20,7 @@ export default (app) => {
}); });
const icon = Widget.Icon({ const icon = Widget.Icon({
icon: lookUpIcon(app.icon_name || "") ? app.icon_name || "" : "", icon: Utils.lookUpIcon(app.icon_name || "") ? app.icon_name || "" : "",
size: options.applauncher.icon_size.bind("value"), size: options.applauncher.icon_size.bind("value"),
}); });

View File

@@ -1,10 +1,9 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import Applications from "resource:///com/github/Aylur/ags/service/applications.js"; import Applications from "resource:///com/github/Aylur/ags/service/applications.js";
import PopupWindow from "../misc/PopupWindow.js"; import PopupWindow from "../misc/PopupWindow.js";
import AppItem from "./AppItem.js"; import AppItem from "./AppItem.js";
import icons from "../icons.js"; import icons from "../icons.js";
import { launchApp } from "../utils.js"; import { launchApp } from "../utils.js";
import options from "../options.js";
const WINDOW_NAME = "applauncher"; const WINDOW_NAME = "applauncher";
@@ -30,6 +29,7 @@ const Applauncher = () => {
let items = mkItems(); let items = mkItems();
const list = Widget.Box({ const list = Widget.Box({
class_name: "app-list",
vertical: true, vertical: true,
children: items, children: items,
}); });
@@ -86,4 +86,5 @@ export default () =>
name: WINDOW_NAME, name: WINDOW_NAME,
transition: "slide_down", transition: "slide_down",
child: Applauncher(), child: Applauncher(),
anchor: options.applauncher.anchor.bind("value"),
}); });

View File

@@ -1,27 +1,20 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
/** /**
* @typedef {Object} PanelButtonProps * @typedef {Object} PanelButtonProps
* @property {any} content * @property {import('types/widgets/button').ButtonProps['child']} content
* @property {string=} window * @property {string=} window
*/ */
/** /**
* @param {import('types/widgets/button').ButtonProps & PanelButtonProps} o * @param {import('types/widgets/button').ButtonProps & PanelButtonProps} o
*/ */
export default ({ export default ({ class_name, content, window = "", setup, ...rest }) =>
class_name, Widget.Button({
content, class_name: `panel-button ${class_name}`,
window = "", child: Widget.Box({ children: [content] }),
connections = [], setup: (self) => {
...rest
}) => {
let open = false; let open = false;
const connection = [ self.hook(App, (_, win, visible) => {
App,
(self, win, visible) => {
if (win !== window) return; if (win !== window) return;
if (open && !visible) { if (open && !visible) {
@@ -33,13 +26,9 @@ export default ({
open = true; open = true;
self.toggleClassName("active"); self.toggleClassName("active");
} }
}, });
];
return Widget.Button({ if (setup) setup(self);
class_name: `panel-button ${class_name}`, },
child: Widget.Box({ children: [content] }),
connections: connections.concat([connection]),
...rest, ...rest,
}); });
};

View File

@@ -1,6 +1,4 @@
import SystemTray from "resource:///com/github/Aylur/ags/service/systemtray.js"; import SystemTray from "resource:///com/github/Aylur/ags/service/systemtray.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Variable from "resource:///com/github/Aylur/ags/variable.js";
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js"; import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js";
import Battery from "resource:///com/github/Aylur/ags/service/battery.js"; import Battery from "resource:///com/github/Aylur/ags/service/battery.js";
@@ -28,25 +26,29 @@ SystemTray.connect("changed", () => {
}); });
/** /**
* @template T * @template {import('types/service').default} T
* @param {T=} service * @param {T=} service
* @param {(self: T) => boolean=} condition * @param {(service: T) => boolean=} condition
*/ */
const SeparatorDot = (service, condition) => { const SeparatorDot = (service, condition) =>
const visibility = (self) => { Widget.Separator({
vpack: "center",
setup: (self) => {
const visibility = () => {
if (!options.bar.separators.value) return (self.visible = false); if (!options.bar.separators.value) return (self.visible = false);
self.visible = self.visible =
condition && service ? condition(service) : options.bar.separators.value; condition && service
? condition(service)
: options.bar.separators.value;
}; };
const conn = service ? [[service, visibility]] : []; if (service && condition) self.hook(service, visibility);
return Widget.Separator({
connections: [["draw", visibility], ...conn], self.on("draw", visibility);
binds: [["visible", options.bar.separators]], self.bind("visible", options.bar.separators);
vpack: "center", },
}); });
};
const Start = () => const Start = () =>
Widget.Box({ Widget.Box({
@@ -99,14 +101,9 @@ export default (monitor) =>
class_name: "transparent", class_name: "transparent",
exclusivity: "exclusive", exclusivity: "exclusive",
monitor, monitor,
binds: [ anchor: options.bar.position
[ .bind("value")
"anchor", .transform((pos) => [pos, "left", "right"]),
options.bar.position,
"value",
(pos) => [pos, "left", "right"],
],
],
child: Widget.CenterBox({ child: Widget.CenterBox({
class_name: "panel", class_name: "panel",
start_widget: Start(), start_widget: Start(),

View File

@@ -7,43 +7,35 @@ import PanelButton from "../PanelButton.js";
const Indicator = () => const Indicator = () =>
Widget.Stack({ Widget.Stack({
items: [ children: {
["false", Widget.Icon({ binds: [["icon", Battery, "icon-name"]] })], false: Widget.Icon({ icon: Battery.bind("icon_name") }),
["true", FontIcon(icons.battery.charging)], true: FontIcon(icons.battery.charging),
],
binds: [["visible", options.battery.bar.show_icon]],
connections: [
[
Battery,
(stack) => {
stack.shown = `${Battery.charging || Battery.charged}`;
}, },
], visible: options.battery.bar.show_icon.bind("value"),
], setup: (self) =>
self.hook(Battery, () => {
self.shown = `${Battery.charging || Battery.charged}`;
}),
}); });
const PercentLabel = () => const PercentLabel = () =>
Widget.Revealer({ Widget.Revealer({
transition: "slide_right", transition: "slide_right",
binds: [["reveal-child", options.battery.show_percentage]], reveal_child: options.battery.show_percentage.bind("value"),
child: Widget.Label({ child: Widget.Label({
binds: [["label", Battery, "percent", (p) => `${p}%`]], label: Battery.bind("percent").transform((p) => `${p}%`),
}), }),
}); });
const LevelBar = () => const LevelBar = () =>
Widget.LevelBar({ Widget.LevelBar({
connections: [ value: Battery.bind("percent").transform((p) => p / 100),
[ setup: (self) =>
options.battery.bar.full, self.hook(options.battery.bar.full, () => {
(self) => {
const full = options.battery.bar.full.value; const full = options.battery.bar.full.value;
self.vpack = full ? "fill" : "center"; self.vpack = full ? "fill" : "center";
self.hpack = full ? "fill" : "center"; self.hpack = full ? "fill" : "center";
}, }),
],
],
binds: [["value", Battery, "percent", (p) => p / 100]],
}); });
const WholeButton = () => const WholeButton = () =>
@@ -57,7 +49,7 @@ const WholeButton = () =>
children: [ children: [
FontIcon({ FontIcon({
icon: icons.battery.charging, icon: icons.battery.charging,
binds: [["visible", Battery, "charging"]], visible: Battery.bind("charging"),
}), }),
Widget.Box({ Widget.Box({
hpack: "center", hpack: "center",
@@ -77,32 +69,21 @@ export default () =>
options.battery.show_percentage.value = !v; options.battery.show_percentage.value = !v;
}, },
content: Widget.Box({ content: Widget.Box({
connections: [ visible: Battery.bind("available"),
[ children: options.battery.bar.full
Battery, .bind("value")
(w) => { .transform((full) =>
full ? [WholeButton()] : [Indicator(), PercentLabel(), LevelBar()],
),
setup: (self) =>
self.hook(Battery, (w) => {
w.toggleClassName("charging", Battery.charging || Battery.charged); w.toggleClassName("charging", Battery.charging || Battery.charged);
w.toggleClassName( w.toggleClassName(
"medium", "medium",
Battery.percent < options.battery.medium.value, Battery.percent < options.battery.medium.value,
); );
w.toggleClassName( w.toggleClassName("low", Battery.percent < options.battery.low.value);
"low",
Battery.percent < options.battery.low.value,
);
w.toggleClassName("half", Battery.percent < 48); w.toggleClassName("half", Battery.percent < 48);
}, }),
],
],
binds: [
["visible", Battery, "available"],
[
"children",
options.battery.bar.full,
"value",
(full) =>
full ? [WholeButton()] : [Indicator(), PercentLabel(), LevelBar()],
],
],
}), }),
}); });

View File

@@ -7,9 +7,8 @@ export default () =>
PanelButton({ PanelButton({
class_name: "color-picker", class_name: "color-picker",
content: Widget.Icon("color-select-symbolic"), content: Widget.Icon("color-select-symbolic"),
binds: [["tooltip-text", Colors, "colors", (v) => `${v.length} colors`]], tooltip_text: Colors.bind("colors").transform((v) => `${v.length} colors`),
on_clicked: () => Colors.pick(), on_clicked: () => Colors.pick(),
on_secondary_click: (btn) => { on_secondary_click: (btn) => {
if (Colors.colors.length === 0) return; if (Colors.colors.length === 0) return;

View File

@@ -7,25 +7,16 @@ import { substitute } from "../../utils.js";
export const ClientLabel = () => export const ClientLabel = () =>
Widget.Label({ Widget.Label({
binds: [ label: Hyprland.active.client.bind("class").transform((c) => {
[
"label",
Hyprland.active.client,
"class",
(c) => {
const { titles } = options.substitutions; const { titles } = options.substitutions;
return substitute(titles, c); return substitute(titles, c);
}, }),
],
],
}); });
export const ClientIcon = () => export const ClientIcon = () =>
Widget.Icon({ Widget.Icon({
connections: [ setup: (self) =>
[ self.hook(Hyprland.active.client, () => {
Hyprland.active.client,
(self) => {
const { icons } = options.substitutions; const { icons } = options.substitutions;
const { client } = Hyprland.active; const { client } = Hyprland.active;
@@ -40,16 +31,14 @@ export const ClientIcon = () =>
if (hasTitleIcon) self.icon = titleIcon; if (hasTitleIcon) self.icon = titleIcon;
self.visible = !!(hasTitleIcon || hasClassIcon); self.visible = !!(hasTitleIcon || hasClassIcon);
}, }),
],
],
}); });
export default () => export default () =>
PanelButton({ PanelButton({
class_name: "focused-client", class_name: "focused-client",
content: Widget.Box({ content: Widget.Box({
tooltip_text: Hyprland.active.bind("client").transform((c) => c.title),
children: [ClientIcon(), ClientLabel()], children: [ClientIcon(), ClientLabel()],
binds: [["tooltip-text", Hyprland.active, "client", (c) => c.title]],
}), }),
}); });

View File

@@ -26,31 +26,24 @@ const Indicator = ({ player, direction = "right" }) =>
vexpand: true, vexpand: true,
truncate: "end", truncate: "end",
max_width_chars: 40, max_width_chars: 40,
connections: [ label: player
[ .bind("track_title")
player, .transform(
(label) => { () => `${player.track_artists.join(", ")} - ${player.track_title}`,
label.label = `${player.track_artists.join(", ")} - ${ ),
player.track_title
}`;
},
],
],
}), }),
connections: [ setupRevealer: (self) => {
[ let current = "";
player, self.hook(player, () => {
(revealer) => { if (current === player.track_title) return;
if (revealer._current === player.track_title) return;
revealer._current = player.track_title; current = player.track_title;
revealer.reveal_child = true; self.reveal_child = true;
Utils.timeout(3000, () => { Utils.timeout(3000, () => {
revealer.reveal_child = false; self.reveal_child = false;
});
}); });
}, },
],
],
}); });
/** /**
@@ -75,10 +68,7 @@ export default ({ direction = "right" } = {}) => {
box.children = [Indicator({ player, direction })]; box.children = [Indicator({ player, direction })];
}; };
return Widget.Box({ return Widget.Box()
connections: [ .hook(options.mpris.preferred, update)
[options.mpris.preferred, update], .hook(Mpris, update, "notify::players");
[Mpris, update, "notify::players"],
],
});
}; };

View File

@@ -12,52 +12,39 @@ import HoverRevealer from "../../misc/HoverRevealer.js";
export default ({ direction = "left" } = {}) => export default ({ direction = "left" } = {}) =>
HoverRevealer({ HoverRevealer({
class_name: "notifications panel-button", class_name: "notifications panel-button",
eventboxConnections: [ setupEventBox: (box) =>
["button-press-event", () => App.openWindow("dashboard")], box
[ .on("button-press-event", () => App.openWindow("dashboard"))
.hook(
Notifications, Notifications,
(box) => () =>
(box.visible = (box.visible =
Notifications.notifications.length > 0 || Notifications.dnd), Notifications.notifications.length > 0 || Notifications.dnd),
], ),
],
connections: [
[
Notifications,
(revealer) => {
const title = Notifications.notifications[0]?.summary;
if (revealer._title === title) return;
revealer._title = title; setupRevealer: (self) =>
revealer.reveal_child = true; self.hook(Notifications, () => {
let title = "";
const summary = Notifications.notifications[0]?.summary;
if (title === summary) return;
title = summary;
self.reveal_child = true;
Utils.timeout(3000, () => { Utils.timeout(3000, () => {
revealer.reveal_child = false; self.reveal_child = false;
}); });
}, }),
],
],
direction, direction,
indicator: Widget.Icon({ indicator: Widget.Icon({
binds: [ icon: Notifications.bind("dnd").transform(
[ (dnd) => icons.notifications[dnd ? "silent" : "noisy"],
"icon", ),
Notifications,
"dnd",
(dnd) =>
dnd ? icons.notifications.silent : icons.notifications.noisy,
],
],
}), }),
child: Widget.Label({ child: Widget.Label({
truncate: "end", truncate: "end",
max_width_chars: 40, max_width_chars: 40,
binds: [ label: Notifications.bind("notifications").transform(
[
"label",
Notifications,
"notifications",
(n) => n.reverse()[0]?.summary || "", (n) => n.reverse()[0]?.summary || "",
], ),
],
}), }),
}); });

View File

@@ -10,15 +10,8 @@ export default () =>
window: "overview", window: "overview",
on_clicked: () => App.toggleWindow("overview"), on_clicked: () => App.toggleWindow("overview"),
content: FontIcon({ content: FontIcon({
binds: [ label: options.bar.icon.bind("value").transform((v) => {
[
"icon",
options.bar.icon,
"value",
(v) => {
return v === "distro-icon" ? distroIcon : v; return v === "distro-icon" ? distroIcon : v;
}, }),
],
],
}), }),
}); });

View File

@@ -7,23 +7,16 @@ export default () =>
PanelButton({ PanelButton({
class_name: "recorder", class_name: "recorder",
on_clicked: () => Recorder.stop(), on_clicked: () => Recorder.stop(),
binds: [["visible", Recorder, "recording"]], visible: Recorder.bind("recording"),
content: Widget.Box({ content: Widget.Box({
children: [ children: [
Widget.Icon(icons.recorder.recording), Widget.Icon(icons.recorder.recording),
Widget.Label({ Widget.Label({
binds: [ label: Recorder.bind("timer").transform((time) => {
[
"label",
Recorder,
"timer",
(time) => {
const sec = time % 60; const sec = time % 60;
const min = Math.floor(time / 60); const min = Math.floor(time / 60);
return `${min}:${sec < 10 ? "0" + sec : sec}`; return `${min}:${sec < 10 ? "0" + sec : sec}`;
}, }),
],
],
}), }),
], ],
}), }),

View File

@@ -7,7 +7,7 @@ import options from "../../options.js";
/** /**
* @param {import('types/widgets/revealer').default} revealer * @param {import('types/widgets/revealer').default} revealer
* @param {'left' | 'right' | 'up' | 'down'} direction * @param {'left' | 'right' | 'up' | 'down'} direction
* @param {import('types/variable').Variable} items * @param {import('types/variable').Variable<number>} items
*/ */
const Arrow = (revealer, direction, items) => { const Arrow = (revealer, direction, items) => {
let deg = 0; let deg = 0;
@@ -29,14 +29,7 @@ const Arrow = (revealer, direction, items) => {
return Widget.Button({ return Widget.Button({
class_name: "panel-button sub-menu", class_name: "panel-button sub-menu",
connections: [ tooltip_text: items.bind().transform((v) => `${v} Items`),
[
items,
(btn) => {
btn.tooltip_text = `${items.value} Items`;
},
],
],
on_clicked: () => { on_clicked: () => {
animate(); animate();
revealer.reveal_child = !revealer.reveal_child; revealer.reveal_child = !revealer.reveal_child;

View File

@@ -13,23 +13,16 @@ const System = (type) => {
const progress = Widget.Box({ const progress = Widget.Box({
class_name: "progress", class_name: "progress",
child: Widget.CircularProgress({ child: Widget.CircularProgress({
binds: [["value", variables[type]]], value: variables[type].bind(),
}), }),
}); });
const revealer = Widget.Revealer({ const revealer = Widget.Revealer({
transition: "slide_right", transition: "slide_right",
child: Widget.Label({ child: Widget.Label({
binds: [ label: variables[type].bind("value").transform((v) => {
[
"label",
variables[type],
"value",
(v) => {
return ` ${type}: ${Math.round(v * 100)}%`; return ` ${type}: ${Math.round(v * 100)}%`;
}, }),
],
],
}), }),
}); });

View File

@@ -28,7 +28,6 @@ const MicrophoneIndicator = () =>
if (!Audio.microphone) return; if (!Audio.microphone) return;
const { muted, low, medium, high } = icons.audio.mic; const { muted, low, medium, high } = icons.audio.mic;
if (Audio.microphone.is_muted) return (icon.icon = muted);
/** @type {Array<[number, string]>} */ /** @type {Array<[number, string]>} */
const cons = [ const cons = [
@@ -42,7 +41,7 @@ const MicrophoneIndicator = () =>
icon.visible = Audio.recorders.length > 0 || Audio.microphone.is_muted; icon.visible = Audio.recorders.length > 0 || Audio.microphone.is_muted;
}, },
"speaker-changed", "microphone-changed",
); );
const DNDIndicator = () => const DNDIndicator = () =>

View File

@@ -26,9 +26,6 @@ const setChildren = (box) =>
})); }));
export default () => export default () =>
Widget.Box({ Widget.Box()
connections: [ .hook(Hyprland, setChildren, "notify::clients")
[Hyprland, setChildren, "notify::clients"], .hook(Hyprland, setChildren, "notify::active");
[Hyprland, setChildren, "notify::active"],
],
});

View File

@@ -12,40 +12,34 @@ const Workspaces = () => {
return Widget.Box({ return Widget.Box({
children: range(ws || 20).map((i) => children: range(ws || 20).map((i) =>
Widget.Button({ Widget.Button({
setup: (btn) => (btn.id = i), attribute: i,
on_clicked: () => dispatch(i), on_clicked: () => dispatch(i),
child: Widget.Label({ child: Widget.Label({
label: `${i}`, label: `${i}`,
class_name: "indicator", class_name: "indicator",
vpack: "center", vpack: "center",
}), }),
connections: [ setup: (self) =>
[ self.hook(Hyprland, () => {
Hyprland, self.toggleClassName("active", Hyprland.active.workspace.id === i);
(btn) => { self.toggleClassName(
btn.toggleClassName("active", Hyprland.active.workspace.id === i);
btn.toggleClassName(
"occupied", "occupied",
Hyprland.getWorkspace(i)?.windows > 0, (Hyprland.getWorkspace(i)?.windows || 0) > 0,
); );
}, }),
],
],
}), }),
), ),
connections: ws setup: (box) => {
? [] if (ws === 0) {
: [ box.hook(Hyprland.active.workspace, () =>
[
Hyprland.active.workspace,
(box) =>
box.children.map((btn) => { box.children.map((btn) => {
btn.visible = Hyprland.workspaces.some( btn.visible = Hyprland.workspaces.some(
(ws) => ws.id === btn.id, (ws) => ws.id === btn.attribute,
); );
}), }),
], );
], }
},
}); });
}; };
@@ -58,7 +52,7 @@ export default () =>
on_scroll_up: () => dispatch("m+1"), on_scroll_up: () => dispatch("m+1"),
on_scroll_down: () => dispatch("m-1"), on_scroll_down: () => dispatch("m-1"),
class_name: "eventbox", class_name: "eventbox",
binds: [["child", options.workspaces, "value", Workspaces]], child: options.workspaces.bind("value").transform(Workspaces),
}), }),
}), }),
}); });

View File

@@ -7,19 +7,15 @@ import options from "../options.js";
export default () => export default () =>
PopupWindow({ PopupWindow({
name: "dashboard", name: "dashboard",
connections: [ setup: (self) =>
[ self.hook(options.bar.position, () => {
options.bar.position,
(self) => {
self.anchor = [options.bar.position.value]; self.anchor = [options.bar.position.value];
if (options.bar.position.value === "top") if (options.bar.position.value === "top")
self.transition = "slide_down"; self.transition = "slide_down";
if (options.bar.position.value === "bottom") if (options.bar.position.value === "bottom")
self.transition = "slide_up"; self.transition = "slide_up";
}, }),
],
],
child: Widget.Box({ child: Widget.Box({
children: [ children: [
NotificationColumn(), NotificationColumn(),

View File

@@ -13,23 +13,16 @@ const SysProgress = (type, title, unit) =>
Widget.Box({ Widget.Box({
class_name: `circular-progress-box ${type}`, class_name: `circular-progress-box ${type}`,
hexpand: true, hexpand: true,
binds: [ tooltip_text: vars[type]
[ .bind("value")
"tooltipText", .transform((v) => `${title}: ${Math.floor(v * 100)}${unit}`),
vars[type],
"value",
(v) => `${title}: ${Math.floor(v * 100)}${unit}`,
],
],
child: Widget.CircularProgress({ child: Widget.CircularProgress({
hexpand: true, hexpand: true,
class_name: `circular-progress ${type}`, class_name: `circular-progress ${type}`,
child: Widget.Icon(icons.system[type]), child: Widget.Icon(icons.system[type]),
start_at: 0.75, start_at: 0.75,
binds: [ value: vars[type].bind(),
["value", vars[type]], rounded: options.radii.bind("value").transform((v) => v > 0),
["rounded", options.radii, "value", (v) => v > 0],
],
}), }),
}); });
@@ -45,7 +38,7 @@ export default () =>
Clock({ format: "%H:%M" }), Clock({ format: "%H:%M" }),
Widget.Label({ Widget.Label({
class_name: "uptime", class_name: "uptime",
binds: [["label", vars.uptime, "value", (t) => `uptime: ${t}`]], label: vars.uptime.bind("value").transform((t) => `uptime: ${t}`),
}), }),
], ],
}), }),

View File

@@ -11,19 +11,16 @@ const ClearButton = () =>
for (let i = 0; i < list.length; i++) for (let i = 0; i < list.length; i++)
timeout(50 * i, () => list[i]?.close()); timeout(50 * i, () => list[i]?.close());
}, },
binds: [["sensitive", Notifications, "notifications", (n) => n.length > 0]], sensitive: Notifications.bind("notifications").transform(
(n) => n.length > 0,
),
child: Widget.Box({ child: Widget.Box({
children: [ children: [
Widget.Label("Clear "), Widget.Label("Clear "),
Widget.Icon({ Widget.Icon({
binds: [ icon: Notifications.bind("notifications").transform(
[ (n) => icons.trash[n.length > 0 ? "full" : "empty"],
"icon", ),
Notifications,
"notifications",
(n) => (n.length > 0 ? icons.trash.full : icons.trash.empty),
],
],
}), }),
], ],
}), }),
@@ -42,18 +39,10 @@ const NotificationList = () =>
Widget.Box({ Widget.Box({
vertical: true, vertical: true,
vexpand: true, vexpand: true,
connections: [ children: Notifications.bind("notifications").transform((n) =>
[ n.reverse().map(Notification),
Notifications, ),
(box) => { visible: Notifications.bind("notifications").transform((n) => n.length > 0),
box.children = Notifications.notifications
.reverse()
.map(Notification);
box.visible = Notifications.notifications.length > 0;
},
],
],
}); });
const Placeholder = () => const Placeholder = () =>
@@ -64,11 +53,13 @@ const Placeholder = () =>
hpack: "center", hpack: "center",
vexpand: true, vexpand: true,
hexpand: true, hexpand: true,
visible: Notifications.bind("notifications").transform(
(n) => n.length === 0,
),
children: [ children: [
Widget.Icon(icons.notifications.silent), Widget.Icon(icons.notifications.silent),
Widget.Label("Your inbox is empty"), Widget.Label("Your inbox is empty"),
], ],
binds: [["visible", Notifications, "notifications", (n) => n.length === 0]],
}); });
export default () => export default () =>

View File

@@ -8,13 +8,12 @@ const DesktopClock = () =>
class_name: "clock-box-shadow", class_name: "clock-box-shadow",
child: Widget.CenterBox({ child: Widget.CenterBox({
class_name: "clock-box", class_name: "clock-box",
children: [ start_widget: Clock({
Clock({
class_name: "clock", class_name: "clock",
hpack: "center", hpack: "center",
format: "%H", format: "%H",
}), }),
Widget.Box({ center_widget: Widget.Box({
class_name: "separator-box", class_name: "separator-box",
vertical: true, vertical: true,
hexpand: true, hexpand: true,
@@ -24,12 +23,11 @@ const DesktopClock = () =>
Widget.Separator({ vpack: "center", vexpand: true }), Widget.Separator({ vpack: "center", vexpand: true }),
], ],
}), }),
Clock({ end_widget: Clock({
class_name: "clock", class_name: "clock",
hpack: "center", hpack: "center",
format: "%M", format: "%M",
}), }),
],
}), }),
}); });
@@ -40,21 +38,17 @@ const Desktop = () =>
vertical: true, vertical: true,
vexpand: true, vexpand: true,
hexpand: true, hexpand: true,
binds: [["visible", options.desktop.clock.enable]], visible: options.desktop.clock.enable.bind("value"),
connections: [ setup: (self) =>
[ self.hook(options.desktop.clock.position, () => {
options.desktop.clock.position,
(box) => {
const [hpack = "center", vpack = "center", offset = 64] = const [hpack = "center", vpack = "center", offset = 64] =
options.desktop.clock.position.value.split(" ") || []; options.desktop.clock.position.value.split(" ") || [];
// @ts-expect-error // @ts-expect-error
box.hpack = hpack; self.hpack = hpack;
box.vpack = vpack; self.vpack = vpack;
box.setCss(`margin: ${Number(offset)}px;`); self.setCss(`margin: ${Number(offset)}px;`);
}, }),
],
],
children: [ children: [
DesktopClock(), DesktopClock(),
Clock({ format: "%B %e. %A", class_name: "date" }), Clock({ format: "%B %e. %A", class_name: "date" }),
@@ -66,6 +60,7 @@ const Desktop = () =>
export default (monitor) => export default (monitor) =>
Widget.Window({ Widget.Window({
monitor, monitor,
keymode: "on-demand",
name: `desktop${monitor}`, name: `desktop${monitor}`,
layer: "background", layer: "background",
class_name: "desktop", class_name: "desktop",

View File

@@ -22,32 +22,26 @@ const AppButton = ({ icon, pinned = false, ...rest }) => {
), ),
}); });
const button = Widget.Button({ return Widget.Button({
...rest, ...rest,
attribute: indicators,
child: Widget.Box({ child: Widget.Box({
class_name: "box", class_name: "box",
child: Widget.Overlay({ child: Widget.Overlay({
child: Widget.Icon({ child: Widget.Icon({
icon, icon,
binds: [["size", options.desktop.dock.icon_size]], size: options.desktop.dock.icon_size.bind("value"),
}), }),
pass_through: true, pass_through: true,
overlays: pinned ? [indicators] : [], overlays: pinned ? [indicators] : [],
}), }),
}), }),
}); });
return Object.assign(button, { indicators });
}; };
const Taskbar = () => const Taskbar = () =>
Widget.Box({ Widget.Box({
binds: [ children: Hyprland.bind("clients").transform((c) =>
[
"children",
Hyprland,
"clients",
(c) =>
c.map((client) => { c.map((client) => {
for (const appName of options.desktop.dock.pinned_apps.value) { for (const appName of options.desktop.dock.pinned_apps.value) {
if (client.class.toLowerCase().includes(appName.toLowerCase())) if (client.class.toLowerCase().includes(appName.toLowerCase()))
@@ -67,24 +61,18 @@ const Taskbar = () =>
} }
} }
}), }),
], ),
],
}); });
const PinnedApps = () => const PinnedApps = () =>
Widget.Box({ Widget.Box({
class_name: "pins", class_name: "pins",
homogeneous: true, homogeneous: true,
binds: [ children: options.desktop.dock.pinned_apps.bind("value").transform((v) =>
[
"children",
options.desktop.dock.pinned_apps,
"value",
(v) =>
v v
.map((term) => ({ app: Applications.query(term)?.[0], term })) .map((term) => ({ app: Applications.query(term)?.[0], term }))
.filter(({ app }) => app) .filter(({ app }) => app)
.map(({ app, term = true }) => .map(({ app, term }) =>
AppButton({ AppButton({
pinned: true, pinned: true,
icon: app.icon_name || "", icon: app.icon_name || "",
@@ -98,23 +86,20 @@ const PinnedApps = () =>
}, },
on_middle_click: () => launchApp(app), on_middle_click: () => launchApp(app),
tooltip_text: app.name, tooltip_text: app.name,
connections: [ setup: (button) =>
[ button.hook(Hyprland, () => {
Hyprland,
(button) => {
const running = Hyprland.clients.filter((client) => const running = Hyprland.clients.filter((client) =>
client.class.toLowerCase().includes(term), client.class.toLowerCase().includes(term),
); );
const focused = running.find( const focused = running.find(
(client) => (client) => client.address === Hyprland.active.client.address,
client.address === Hyprland.active.client.address,
); );
const index = running.findIndex((c) => c === focused); const index = running.findIndex((c) => c === focused);
for (let i = 0; i < 5; ++i) { for (let i = 0; i < 5; ++i) {
const indicator = button.indicators.children[i]; const indicator = button.attribute.children[i];
indicator.visible = i < running.length; indicator.visible = i < running.length;
indicator.toggleClassName("focused", i === index); indicator.toggleClassName("focused", i === index);
} }
@@ -122,13 +107,10 @@ const PinnedApps = () =>
button.set_tooltip_text( button.set_tooltip_text(
running.length === 1 ? running[0].title : app.name, running.length === 1 ? running[0].title : app.name,
); );
}, }),
],
],
}), }),
), ),
], ),
],
}); });
export default () => { export default () => {
@@ -144,9 +126,14 @@ export default () => {
vpack: "center", vpack: "center",
hpack: "center", hpack: "center",
orientation: 1, orientation: 1,
connections: [ setup: (self) =>
[Hyprland, (box) => (box.visible = taskbar.children.length > 0)], self.hook(
], taskbar,
() => {
self.visible = taskbar.children.length > 0;
},
"notify::children",
),
}); });
return Widget.Box({ return Widget.Box({
class_name: "dock", class_name: "dock",

View File

@@ -35,20 +35,10 @@ export default (monitor) => {
}), }),
], ],
}), }),
connections: [ setup: (self) =>
[ self
"enter-notify-event", .on("enter-notify-event", () => (revealer.reveal_child = true))
() => { .on("leave-notify-event", () => (revealer.reveal_child = false))
revealer.reveal_child = true; .bind("visible", options.bar.position, "value", (v) => v !== "bottom"),
},
],
[
"leave-notify-event",
() => {
revealer.reveal_child = false;
},
],
],
binds: [["visible", options.bar.position, "value", (v) => v !== "bottom"]],
}); });
}; };

View File

@@ -87,6 +87,7 @@ export default {
next: "󰒭", next: "󰒭",
}, },
ui: { ui: {
colorpicker: "color-select-symbolic",
close: "window-close-symbolic", close: "window-close-symbolic",
info: "info-symbolic", info: "info-symbolic",
menu: "open-menu-symbolic", menu: "open-menu-symbolic",

View File

@@ -7,7 +7,7 @@ const PasswordEntry = () =>
Widget.Box({ Widget.Box({
children: [ children: [
Widget.Entry({ Widget.Entry({
connections: [[Lockscreen, (entry) => (entry.text = ""), "lock"]], setup: (self) => self.hook(Lockscreen, () => (self.text = ""), "lock"),
visibility: false, visibility: false,
placeholder_text: "Password", placeholder_text: "Password",
on_accept: ({ text }) => Lockscreen.auth(text || ""), on_accept: ({ text }) => Lockscreen.auth(text || ""),
@@ -17,9 +17,12 @@ const PasswordEntry = () =>
Widget.Spinner({ Widget.Spinner({
active: true, active: true,
vpack: "center", vpack: "center",
connections: [ setup: (self) =>
[Lockscreen, (w, auth) => (w.visible = auth), "authenticating"], self.hook(
], Lockscreen,
(_, auth) => (self.visible = auth),
"authenticating",
),
}), }),
], ],
}); });
@@ -32,7 +35,8 @@ export default (monitor) => {
monitor, monitor,
layer: "overlay", layer: "overlay",
visible: false, visible: false,
connections: [[Lockscreen, (w, lock) => (w.visible = lock), "lock"]], setup: (self) =>
self.hook(Lockscreen, (_, lock) => (self.visible = lock), "lock"),
child: Widget.Box({ child: Widget.Box({
css: "min-width: 3000px; min-height: 2000px;", css: "min-width: 3000px; min-height: 2000px;",
class_name: "shader", class_name: "shader",

View File

@@ -1,6 +1,6 @@
import Applauncher from "./applauncher/Applauncher.js";
import Dashboard from "./dashboard/Dashboard.js"; import Dashboard from "./dashboard/Dashboard.js";
import Desktop from "./desktop/Desktop.js"; import Desktop from "./desktop/Desktop.js";
import FloatingDock from "./dock/FloatingDock.js";
import Lockscreen from "./lockscreen/Lockscreen.js"; import Lockscreen from "./lockscreen/Lockscreen.js";
import Notifications from "./notifications/Notifications.js"; import Notifications from "./notifications/Notifications.js";
import OSD from "./osd/OSD.js"; import OSD from "./osd/OSD.js";
@@ -15,20 +15,19 @@ import { init } from "./settings/setup.js";
import { forMonitors } from "./utils.js"; import { forMonitors } from "./utils.js";
import { initWallpaper } from "./settings/wallpaper.js"; import { initWallpaper } from "./settings/wallpaper.js";
import options from "./options.js"; import options from "./options.js";
import Dock from "./dock/Dock.js";
initWallpaper(); initWallpaper();
const windows = () => [ const windows = () => [
forMonitors(Desktop), forMonitors(Desktop),
forMonitors(FloatingDock),
forMonitors(Lockscreen), forMonitors(Lockscreen),
forMonitors(Notifications), forMonitors(Notifications),
forMonitors(OSD), forMonitors(OSD),
forMonitors(ScreenCorners), forMonitors(ScreenCorners),
forMonitors(TopBar), forMonitors(TopBar),
Applauncher(),
Dashboard(), Dashboard(),
Dock(),
Overview(), Overview(),
PowerMenu(), PowerMenu(),
QuickSettings(), QuickSettings(),
@@ -39,8 +38,6 @@ const windows = () => [
export default { export default {
onConfigParsed: init, onConfigParsed: init,
windows: windows().flat(1), windows: windows().flat(1),
maxStreamVolume: 1.05,
cacheNotificationActions: false,
closeWindowDelay: { closeWindowDelay: {
quicksettings: options.transition.value, quicksettings: options.transition.value,
dashboard: options.transition.value, dashboard: options.transition.value,

View File

@@ -4,15 +4,11 @@ import Battery from "resource:///com/github/Aylur/ags/service/battery.js";
export default () => export default () =>
Widget.Icon({ Widget.Icon({
class_name: "battery", class_name: "battery",
binds: [["icon", Battery, "icon-name"]], icon: Battery.bind("icon_name"),
connections: [ setup: (icon) =>
[ icon.hook(Battery, () => {
Battery,
(icon) => {
icon.toggleClassName("charging", Battery.charging); icon.toggleClassName("charging", Battery.charging);
icon.toggleClassName("charged", Battery.charged); icon.toggleClassName("charged", Battery.charged);
icon.toggleClassName("low", Battery.percent < 30); icon.toggleClassName("low", Battery.percent < 30);
}, }),
],
],
}); });

View File

@@ -1,5 +1,5 @@
import { clock } from "../variables.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js"; import Widget from "resource:///com/github/Aylur/ags/widget.js";
import GLib from "gi://GLib";
/** /**
* @param {import('types/widgets/label').Props & { * @param {import('types/widgets/label').Props & {
@@ -7,20 +7,11 @@ import GLib from "gi://GLib";
* interval?: number, * interval?: number,
* }} o * }} o
*/ */
export default ({ export default ({ format = "%H:%M:%S %B %e. %A", ...rest } = {}) =>
format = "%H:%M:%S %B %e. %A",
interval = 1000,
...rest
} = {}) =>
Widget.Label({ Widget.Label({
class_name: "clock", class_name: "clock",
label: clock.bind("value").transform((time) => {
return time.format(format) || "wrong format";
}),
...rest, ...rest,
connections: [
[
interval,
(label) =>
(label.label =
GLib.DateTime.new_now_local().format(format) || "wrong format"),
],
],
}); });

View File

@@ -1,15 +1,13 @@
import Gtk from "gi://Gtk"; import Gtk from "gi://Gtk?version=3.0";
import { createCtor } from "resource:///com/github/Aylur/ags/widget.js"; import { subclass, register } from "resource:///com/github/Aylur/ags/widget.js";
import AgsLabel from "resource:///com/github/Aylur/ags/widgets/label.js"; import AgsLabel from "resource:///com/github/Aylur/ags/widgets/label.js";
import GObject from "gi://GObject";
export default createCtor( class FontIcon extends AgsLabel {
class FontIcon extends AgsLabel {
static { static {
GObject.registerClass(this); register(this);
} }
/** @param {string | import('types/widgets/label').Props & { icon?: string }} params */ /** @param {string | import('types/widgets/label').Props<any> & { icon?: string }} params */
constructor(params = "") { constructor(params = "") {
// @ts-expect-error // @ts-expect-error
const { icon = "", ...rest } = params; const { icon = "", ...rest } = params;
@@ -45,5 +43,6 @@ export default createCtor(
vfunc_get_preferred_width() { vfunc_get_preferred_width() {
return [this.size, this.size]; return [this.size, this.size];
} }
}, }
);
export default subclass(FontIcon);

View File

@@ -6,8 +6,8 @@ import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
* indicator?: import('types/widgets/box').BoxProps['child'] * indicator?: import('types/widgets/box').BoxProps['child']
* direction?: 'left' | 'right' | 'down' | 'up' * direction?: 'left' | 'right' | 'down' | 'up'
* duration?: number * duration?: number
* eventboxConnections?: import('types/widgets/box').BoxProps['connections'] * setupRevealer?: (rev: ReturnType<typeof Widget.Revealer>) => void
* connections?: import('types/widgets/revealer').RevealerProps['connections'] * setupEventBox?: (rev: ReturnType<typeof Widget.EventBox>) => void
* }} HoverRevealProps * }} HoverRevealProps
*/ */
@@ -19,9 +19,8 @@ export default ({
child, child,
direction = "left", direction = "left",
duration = 300, duration = 300,
connections = [], setupEventBox,
eventboxConnections = [], setupRevealer,
binds = [],
...rest ...rest
}) => { }) => {
let open = false; let open = false;
@@ -31,15 +30,14 @@ export default ({
const revealer = Widget.Revealer({ const revealer = Widget.Revealer({
transition: `slide_${direction}`, transition: `slide_${direction}`,
connections, setup: setupRevealer,
binds,
transition_duration: duration, transition_duration: duration,
child, child,
}); });
const eventbox = Widget.EventBox({ const eventbox = Widget.EventBox({
...rest, ...rest,
connections: eventboxConnections, setup: setupEventBox,
on_hover: () => { on_hover: () => {
if (open) return; if (open) return;

View File

@@ -4,23 +4,42 @@ import Widget from "resource:///com/github/Aylur/ags/widget.js";
import options from "../options.js"; import options from "../options.js";
import GObject from "gi://GObject"; import GObject from "gi://GObject";
class PopupWindow extends AgsWindow { const keyGrabber = Widget.Window({
name: "key-grabber",
popup: true,
anchor: ["top", "left", "right", "bottom"],
css: "background-color: transparent;",
visible: false,
exclusivity: "ignore",
keymode: "on-demand",
layer: "top",
attribute: { list: [] },
setup: (self) =>
self.on("notify::visible", ({ visible }) => {
if (!visible)
self.attribute?.list.forEach((name) => App.closeWindow(name));
}),
child: Widget.EventBox({ vexpand: true }).on("button-press-event", () => {
App.closeWindow("key-grabber");
keyGrabber.attribute?.list.forEach((name) => App.closeWindow(name));
}),
});
// add before any PopupWindow is instantiated
App.addWindow(keyGrabber);
export class PopupWindow extends AgsWindow {
static { static {
GObject.registerClass(this); GObject.registerClass(this);
} }
/** @param {import('types/widgets/window').WindowProps & {
* name: string
* child: import('types/widgets/box').default
* transition?: import('types/widgets/revealer').RevealerProps['transition']
* }} o
*/
constructor({ name, child, transition = "none", visible = false, ...rest }) { constructor({ name, child, transition = "none", visible = false, ...rest }) {
super({ super({
...rest, ...rest,
name, name,
popup: true, popup: true,
focusable: true, keymode: "exclusive",
layer: "overlay",
class_names: ["popup-window", name], class_names: ["popup-window", name],
}); });
@@ -28,15 +47,11 @@ class PopupWindow extends AgsWindow {
this.revealer = Widget.Revealer({ this.revealer = Widget.Revealer({
transition, transition,
child, child,
transitionDuration: options.transition.value, transition_duration: options.transition.value,
connections: [ setup: (self) =>
[ self.hook(App, (_, wname, visible) => {
App,
(_, wname, visible) => {
if (wname === name) this.revealer.reveal_child = visible; if (wname === name) this.revealer.reveal_child = visible;
}, }),
],
],
}); });
this.child = Widget.Box({ this.child = Widget.Box({
@@ -46,6 +61,9 @@ class PopupWindow extends AgsWindow {
this.show_all(); this.show_all();
this.visible = visible; this.visible = visible;
keyGrabber.bind("visible", this, "visible");
keyGrabber.attribute?.list.push(name);
} }
set transition(dir) { set transition(dir) {

View File

@@ -1,6 +1,10 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js"; import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
/** @param {import('types/widgets/box').BoxProps & {
* width: number
* height: number
* }} o */
export default ({ export default ({
height = 18, height = 18,
width = 180, width = 180,
@@ -27,8 +31,7 @@ export default ({
min-height: ${height}px; min-height: ${height}px;
`, `,
children: [fill], children: [fill],
setup: (progress) => attribute: (value) => {
(progress.setValue = (value) => {
if (value < 0) return; if (value < 0) return;
const axis = vertical ? "height" : "width"; const axis = vertical ? "height" : "width";
@@ -52,6 +55,6 @@ export default ({
fill.setCss(`min-${axis}: ${fill_size}px`); fill.setCss(`min-${axis}: ${fill_size}px`);
}); });
} }
}), },
}); });
}; };

View File

@@ -1,19 +1,4 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js"; import { subclass } from "resource:///com/github/Aylur/ags/widget.js";
import Gtk from "gi://Gtk"; import Gtk from "gi://Gtk";
import AgsWidget from "resource:///com/github/Aylur/ags/widgets/widget.js";
class RegularWindow extends AgsWidget(Gtk.Window, "RegularWindow") { export default subclass(Gtk.Window, "RegularWindow");
static {
AgsWidget.register(this);
}
/**
* @param {import('types/widgets/widget').BaseProps<
* RegularWindow, Gtk.Window.ConstructorProperties
* >} params */
constructor(params) {
// @ts-expect-error
super(params);
}
}
export default Widget.createCtor(RegularWindow);

View File

@@ -11,14 +11,9 @@ export const CoverArt = (player, props) =>
Widget.Box({ Widget.Box({
...props, ...props,
class_name: "cover", class_name: "cover",
binds: [ css: player
[ .bind("cover_path")
"css", .transform((p) => `background-image: url("${p}")`),
player,
"cover-path",
(path) => `background-image: url("${path}")`,
],
],
}); });
/** /**
@@ -29,16 +24,15 @@ export const BlurredCoverArt = (player, props) =>
Widget.Box({ Widget.Box({
...props, ...props,
class_name: "blurred-cover", class_name: "blurred-cover",
connections: [ setup: (self) =>
[ self.hook(
player, player,
(box) => (box) =>
blurImg(player.cover_path).then((img) => { blurImg(player.cover_path).then((img) => {
img && box.setCss(`background-image: url("${img}")`); img && box.setCss(`background-image: url("${img}")`);
}), }),
"notify::cover-path", "notify::cover-path",
], ),
],
}); });
/** /**
@@ -49,7 +43,7 @@ export const TitleLabel = (player, props) =>
Widget.Label({ Widget.Label({
...props, ...props,
class_name: "title", class_name: "title",
binds: [["label", player, "track-title"]], label: player.bind("track_title"),
}); });
/** /**
@@ -60,7 +54,7 @@ export const ArtistLabel = (player, props) =>
Widget.Label({ Widget.Label({
...props, ...props,
class_name: "artist", class_name: "artist",
binds: [["label", player, "track-artists", (a) => a.join(", ") || ""]], label: player.bind("track_artists").transform((a) => a.join(", ") || ""),
}); });
/** /**
@@ -72,17 +66,13 @@ export const PlayerIcon = (player, { symbolic = true, ...props } = {}) =>
...props, ...props,
class_name: "player-icon", class_name: "player-icon",
tooltip_text: player.identity || "", tooltip_text: player.identity || "",
connections: [ setup: (self) =>
[ self.hook(player, (icon) => {
player,
(icon) => {
const name = `${player.entry}${symbolic ? "-symbolic" : ""}`; const name = `${player.entry}${symbolic ? "-symbolic" : ""}`;
Utils.lookUpIcon(name) Utils.lookUpIcon(name)
? (icon.icon = name) ? (icon.icon = name)
: (icon.icon = icons.mpris.fallback); : (icon.icon = icons.mpris.fallback);
}, }),
],
],
}); });
/** /**
@@ -94,25 +84,18 @@ export const PositionSlider = (player, props) =>
...props, ...props,
class_name: "position-slider", class_name: "position-slider",
draw_value: false, draw_value: false,
on_change: ({ value }) => { on_change: ({ value }) => (player.position = player.length * value),
player.position = player.length * value; setup: (self) => {
}, const update = () => {
properties: [ if (self.dragging) return;
[
"update",
(slider) => {
if (slider.dragging) return;
slider.visible = player.length > 0; self.visible = player.length > 0;
if (player.length > 0) slider.value = player.position / player.length; if (player.length > 0) self.value = player.position / player.length;
};
self.hook(player, update);
self.hook(player, update, "position");
self.poll(1000, update);
}, },
],
],
connections: [
[player, (s) => s._update(s)],
[player, (s) => s._update(s), "position"],
[1000, (s) => s._update(s)],
],
}); });
/** @param {number} length */ /** @param {number} length */
@@ -126,90 +109,73 @@ function lengthStr(length) {
/** @param {import('types/service/mpris').MprisPlayer} player */ /** @param {import('types/service/mpris').MprisPlayer} player */
export const PositionLabel = (player) => export const PositionLabel = (player) =>
Widget.Label({ Widget.Label({
properties: [ setup: (self) => {
[ const update = (_, time) => {
"update",
(label, time) => {
player.length > 0 player.length > 0
? (label.label = lengthStr(time || player.position)) ? (self.label = lengthStr(time || player.position))
: (label.visible = !!player); : (self.visible = !!player);
};
self.hook(player, update, "position");
self.poll(1000, update);
}, },
],
],
connections: [
[player, (l, time) => l._update(l, time), "position"],
[1000, (l) => l._update(l)],
],
}); });
/** @param {import('types/service/mpris').MprisPlayer} player */ /** @param {import('types/service/mpris').MprisPlayer} player */
export const LengthLabel = (player) => export const LengthLabel = (player) =>
Widget.Label({ Widget.Label({
connections: [ label: player.bind("length").transform((l) => lengthStr(l)),
[ visible: player.bind("length").transform((l) => l > 0),
player,
(label) => {
player.length > 0
? (label.label = lengthStr(player.length))
: (label.visible = !!player);
},
],
],
}); });
/** @param {import('types/service/mpris').MprisPlayer} player */ /** @param {import('types/service/mpris').MprisPlayer} player */
export const Slash = (player) => export const Slash = (player) =>
Widget.Label({ Widget.Label({
label: "/", label: "/",
connections: [ visible: player.bind("length").transform((l) => l > 0),
[
player,
(label) => {
label.visible = player.length > 0;
},
],
],
}); });
/** /**
* @param {Object} o * @param {Object} o
* @param {import('types/service/mpris').MprisPlayer} o.player * @param {import('types/service/mpris').MprisPlayer} o.player
* @param {import('types/widgets/stack').StackProps['items']} o.items * @param {import('types/widgets/stack').StackProps['children']} o.children
* @param {'shuffle' | 'loop' | 'playPause' | 'previous' | 'next'} o.onClick * @param {'shuffle' | 'loop' | 'playPause' | 'previous' | 'next'} o.onClick
* @param {string} o.prop * @param {string} o.prop
* @param {string} o.canProp * @param {string} o.canProp
* @param {any} o.cantValue * @param {any} o.cantValue
*/ */
const PlayerButton = ({ player, items, onClick, prop, canProp, cantValue }) => const PlayerButton = ({
player,
children,
onClick,
prop,
canProp,
cantValue,
}) =>
Widget.Button({ Widget.Button({
child: Widget.Stack({ child: Widget.Stack({ children }).bind(
items, "shown",
binds: [["shown", player, prop, (p) => `${p}`]], player,
}), prop,
on_clicked: player[onClick].bind(player), (p) => `${p}`,
binds: [["visible", player, canProp, (c) => c !== cantValue]], ),
on_clicked: () => player[onClick](),
visible: player.bind(canProp).transform((c) => c !== cantValue),
}); });
/** @param {import('types/service/mpris').MprisPlayer} player */ /** @param {import('types/service/mpris').MprisPlayer} player */
export const ShuffleButton = (player) => export const ShuffleButton = (player) =>
PlayerButton({ PlayerButton({
player, player,
items: [ children: {
[ true: Widget.Label({
"true",
Widget.Label({
class_name: "shuffle enabled", class_name: "shuffle enabled",
label: icons.mpris.shuffle.enabled, label: icons.mpris.shuffle.enabled,
}), }),
], false: Widget.Label({
[
"false",
Widget.Label({
class_name: "shuffle disabled", class_name: "shuffle disabled",
label: icons.mpris.shuffle.disabled, label: icons.mpris.shuffle.disabled,
}), }),
], },
],
onClick: "shuffle", onClick: "shuffle",
prop: "shuffle-status", prop: "shuffle-status",
canProp: "shuffle-status", canProp: "shuffle-status",
@@ -220,29 +186,20 @@ export const ShuffleButton = (player) =>
export const LoopButton = (player) => export const LoopButton = (player) =>
PlayerButton({ PlayerButton({
player, player,
items: [ children: {
[ None: Widget.Label({
"None",
Widget.Label({
class_name: "loop none", class_name: "loop none",
label: icons.mpris.loop.none, label: icons.mpris.loop.none,
}), }),
], Track: Widget.Label({
[
"Track",
Widget.Label({
class_name: "loop track", class_name: "loop track",
label: icons.mpris.loop.track, label: icons.mpris.loop.track,
}), }),
], Playlist: Widget.Label({
[
"Playlist",
Widget.Label({
class_name: "loop playlist", class_name: "loop playlist",
label: icons.mpris.loop.playlist, label: icons.mpris.loop.playlist,
}), }),
], },
],
onClick: "loop", onClick: "loop",
prop: "loop-status", prop: "loop-status",
canProp: "loop-status", canProp: "loop-status",
@@ -253,29 +210,20 @@ export const LoopButton = (player) =>
export const PlayPauseButton = (player) => export const PlayPauseButton = (player) =>
PlayerButton({ PlayerButton({
player, player,
items: [ children: {
[ Playing: Widget.Label({
"Playing",
Widget.Label({
class_name: "playing", class_name: "playing",
label: icons.mpris.playing, label: icons.mpris.playing,
}), }),
], Paused: Widget.Label({
[
"Paused",
Widget.Label({
class_name: "paused", class_name: "paused",
label: icons.mpris.paused, label: icons.mpris.paused,
}), }),
], Stopped: Widget.Label({
[
"Stopped",
Widget.Label({
class_name: "stopped", class_name: "stopped",
label: icons.mpris.stopped, label: icons.mpris.stopped,
}), }),
], },
],
onClick: "playPause", onClick: "playPause",
prop: "play-back-status", prop: "play-back-status",
canProp: "can-play", canProp: "can-play",
@@ -286,15 +234,12 @@ export const PlayPauseButton = (player) =>
export const PreviousButton = (player) => export const PreviousButton = (player) =>
PlayerButton({ PlayerButton({
player, player,
items: [ children: {
[ true: Widget.Label({
"true",
Widget.Label({
class_name: "previous", class_name: "previous",
label: icons.mpris.prev, label: icons.mpris.prev,
}), }),
], },
],
onClick: "previous", onClick: "previous",
prop: "can-go-prev", prop: "can-go-prev",
canProp: "can-go-prev", canProp: "can-go-prev",
@@ -305,15 +250,12 @@ export const PreviousButton = (player) =>
export const NextButton = (player) => export const NextButton = (player) =>
PlayerButton({ PlayerButton({
player, player,
items: [ children: {
[ true: Widget.Label({
"true",
Widget.Label({
class_name: "next", class_name: "next",
label: icons.mpris.next, label: icons.mpris.next,
}), }),
], },
],
onClick: "next", onClick: "next",
prop: "can-go-next", prop: "can-go-next",
canProp: "can-go-next", canProp: "can-go-next",

View File

@@ -39,14 +39,10 @@ const Popups = (parent) => {
}); });
}; };
return Widget.Box({ return Widget.Box({ vertical: true })
vertical: true, .hook(Notifications, onNotified, "notified")
connections: [ .hook(Notifications, onDismissed, "dismissed")
[Notifications, onNotified, "notified"], .hook(Notifications, (box, id) => onDismissed(box, id, true), "closed");
[Notifications, onDismissed, "dismissed"],
[Notifications, (box, id) => onDismissed(box, id, true), "closed"],
],
});
}; };
/** @param {import('types/widgets/revealer').RevealerProps['transition']} transition */ /** @param {import('types/widgets/revealer').RevealerProps['transition']} transition */
@@ -67,6 +63,6 @@ export default (monitor) =>
monitor, monitor,
name: `notifications${monitor}`, name: `notifications${monitor}`,
class_name: "notifications", class_name: "notifications",
binds: [["anchor", options.notifications.position]], anchor: options.notifications.position.bind("value"),
child: PopupList(), child: PopupList(),
}); });

View File

@@ -10,56 +10,39 @@ export const OnScreenIndicator = ({ height = 300, width = 48 } = {}) =>
css: "padding: 1px;", css: "padding: 1px;",
child: Widget.Revealer({ child: Widget.Revealer({
transition: "slide_left", transition: "slide_left",
connections: [ setup: (self) =>
[ self.hook(Indicator, (_, value) => {
Indicator, self.reveal_child = value > -1;
(revealer, value) => { }),
revealer.reveal_child = value > -1;
},
],
],
child: Progress({ child: Progress({
width, width,
height, height,
vertical: true, vertical: true,
connections: [ setup: (self) =>
[Indicator, (progress, value) => progress.setValue(value)], self.hook(Indicator, (_, value) => self.attribute(value)),
],
child: Widget.Stack({ child: Widget.Stack({
vpack: "start", vpack: "start",
hpack: "center", hpack: "center",
hexpand: false, hexpand: false,
items: [ children: {
[ true: Widget.Icon({
"true",
Widget.Icon({
hpack: "center", hpack: "center",
size: width, size: width,
connections: [ setup: (w) =>
[Indicator, (icon, _v, name) => (icon.icon = name || "")], w.hook(Indicator, (_, _v, name) => (w.icon = name || "")),
],
}), }),
], false: FontIcon({
[
"false",
FontIcon({
hpack: "center", hpack: "center",
hexpand: true, hexpand: true,
css: `font-size: ${width}px;`, css: `font-size: ${width}px;`,
connections: [ setup: (w) =>
[Indicator, (icon, _v, name) => (icon.icon = name || "")], w.hook(Indicator, (_, _v, name) => (w.label = name || "")),
],
}), }),
],
],
connections: [
[
Indicator,
(stack, _v, name) => {
stack.shown = `${!!Utils.lookUpIcon(name)}`;
}, },
], setup: (self) =>
], self.hook(Indicator, (_, _v, name) => {
self.shown = `${!!Utils.lookUpIcon(name)}`;
}),
}), }),
}), }),
}), }),

View File

@@ -2,9 +2,10 @@ import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js"; import App from "resource:///com/github/Aylur/ags/app.js";
import { createSurfaceFromWidget, substitute } from "../utils.js"; import { createSurfaceFromWidget, substitute } from "../utils.js";
import Gdk from "gi://Gdk"; import Gdk from "gi://Gdk";
import Gtk from "gi://Gtk"; import Gtk from "gi://Gtk?version=3.0";
import options from "../options.js"; import options from "../options.js";
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import icons from "../icons.js";
const SCALE = 0.08; const SCALE = 0.08;
const TARGET = [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)]; const TARGET = [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)];
@@ -13,7 +14,13 @@ const TARGET = [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)];
const dispatch = (args) => Hyprland.sendMessage(`dispatch ${args}`); const dispatch = (args) => Hyprland.sendMessage(`dispatch ${args}`);
/** @param {string} str */ /** @param {string} str */
const icon = (str) => substitute(options.substitutions.icons, str); const icon = (str) => {
const icon = substitute(options.substitutions.icons, str);
if (Utils.lookUpIcon(icon)) return icon;
console.warn("no icon", icon);
return icons.fallback.executable;
};
export default ({ address, size: [w, h], class: c, title }) => export default ({ address, size: [w, h], class: c, title }) =>
Widget.Button({ Widget.Button({
@@ -32,19 +39,19 @@ export default ({ address, size: [w, h], class: c, title }) =>
App.closeWindow("overview"), App.closeWindow("overview"),
), ),
setup: (btn) => { setup: (btn) =>
btn.drag_source_set( btn
.on("drag-data-get", (_w, _c, data) =>
data.set_text(address, address.length),
)
.on("drag-begin", (_, context) => {
Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(btn));
btn.toggleClassName("hidden", true);
})
.on("drag-end", () => btn.toggleClassName("hidden", false))
.drag_source_set(
Gdk.ModifierType.BUTTON1_MASK, Gdk.ModifierType.BUTTON1_MASK,
TARGET, TARGET,
Gdk.DragAction.COPY, Gdk.DragAction.COPY,
); ),
btn.connect("drag-data-get", (_w, _c, data) =>
data.set_text(address, address.length),
);
btn.connect("drag-begin", (_, context) => {
Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(btn));
btn.toggleClassName("hidden", true);
});
btn.connect("drag-end", () => btn.toggleClassName("hidden", false));
},
}); });

View File

@@ -1,5 +1,3 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import PopupWindow from "../misc/PopupWindow.js"; import PopupWindow from "../misc/PopupWindow.js";
import Workspace from "./Workspace.js"; import Workspace from "./Workspace.js";
@@ -8,15 +6,30 @@ import { range } from "../utils.js";
const ws = options.workspaces; const ws = options.workspaces;
/** @param {import('types/widgets/box').default} box */ const Overview = () =>
Widget.Box({
children: [Workspace(0)], // for type infer
setup: (self) =>
Utils.idle(() => {
self.hook(ws, () => {
self.children = range(ws.value).map(Workspace);
update(self);
children(self);
});
self.hook(Hyprland, update);
self.hook(Hyprland, children, "notify::workspaces");
update(self);
children(self);
}),
});
/** @param {ReturnType<typeof Overview>} box */
const update = (box) => { const update = (box) => {
if (App.windows.has("overview") && !App.getWindow("overview")?.visible) if (!box.get_parent()?.visible) return;
return;
Hyprland.sendMessage("j/clients") Hyprland.sendMessage("j/clients")
.then((clients) => { .then((clients) => {
box.children.forEach((ws) => { box.children.forEach((ws) => {
// @ts-expect-error
ws.attribute(JSON.parse(clients)); ws.attribute(JSON.parse(clients));
}); });
}) })
@@ -35,19 +48,5 @@ const children = (box) => {
export default () => export default () =>
PopupWindow({ PopupWindow({
name: "overview", name: "overview",
child: Widget.Box({ child: Overview(),
setup: update,
connections: [
[
ws,
(box) => {
box.children = range(ws.value).map(Workspace);
update(box);
children(box);
},
],
[Hyprland, update],
[Hyprland, children, "notify::workspaces"],
],
}),
}); });

View File

@@ -2,7 +2,7 @@ import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js"; import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Gdk from "gi://Gdk"; import Gdk from "gi://Gdk";
import Gtk from "gi://Gtk"; import Gtk from "gi://Gtk?version=3.0";
import Client from "./Client.js"; import Client from "./Client.js";
const SCALE = 0.08; const SCALE = 0.08;
@@ -22,14 +22,10 @@ export default (index) => {
min-width: ${3840 * SCALE}px; min-width: ${3840 * SCALE}px;
min-height: ${2160 * SCALE}px; min-height: ${2160 * SCALE}px;
`, `,
connections: [ setup: (box) =>
[ box.hook(Hyprland, () => {
Hyprland,
(box) => {
box.toggleClassName("active", Hyprland.active.workspace.id === index); box.toggleClassName("active", Hyprland.active.workspace.id === index);
}, }),
],
],
child: Widget.EventBox({ child: Widget.EventBox({
hexpand: true, hexpand: true,
vexpand: true, vexpand: true,

View File

@@ -7,11 +7,13 @@ const Padding = (windowName) =>
class_name: "padding", class_name: "padding",
hexpand: true, hexpand: true,
vexpand: true, vexpand: true,
connections: [["button-press-event", () => App.toggleWindow(windowName)]], setup: (w) =>
w.on("button-press-event", () => App.toggleWindow(windowName)),
}); });
/** /**
* @param {import('types/widgets/window').WindowProps & { * @template {import('gi://Gtk?version=3.0').default.Widget} T
* @param {import('types/widgets/window').WindowProps<T> & {
* name: string * name: string
* child: import('types/widgets/box').default * child: import('types/widgets/box').default
* }} o * }} o
@@ -23,20 +25,20 @@ export default ({ name, child, ...rest }) =>
name, name,
visible: false, visible: false,
popup: true, popup: true,
focusable: true, keymode: "on-demand",
setup() { setup() {
child.toggleClassName("window-content"); child.toggleClassName("window-content");
}, },
child: Widget.CenterBox({ child: Widget.CenterBox({
class_name: "shader", class_name: "shader",
css: "min-width: 5000px; min-height: 3000px;", css: "min-width: 5000px; min-height: 3000px;",
children: [ start_widget: Padding(name),
Padding(name), end_widget: Padding(name),
Widget.CenterBox({ center_widget: Widget.CenterBox({
vertical: true, vertical: true,
children: [Padding(name), child, Padding(name)], start_widget: Padding(name),
end_widget: Padding(name),
center_widget: child,
}), }),
Padding(name),
],
}), }),
}); });

View File

@@ -17,7 +17,7 @@ export default () =>
children: [ children: [
Widget.Label({ Widget.Label({
class_name: "title", class_name: "title",
binds: [["label", PowerMenu, "title"]], label: PowerMenu.bind("title"),
}), }),
Widget.Label({ Widget.Label({
class_name: "desc", class_name: "desc",

View File

@@ -33,19 +33,15 @@ const Homogeneous = (toggles) =>
export default () => export default () =>
PopupWindow({ PopupWindow({
name: "quicksettings", name: "quicksettings",
connections: [ setup: (self) =>
[ self.hook(options.bar.position, () => {
options.bar.position,
(self) => {
self.anchor = ["right", options.bar.position.value]; self.anchor = ["right", options.bar.position.value];
if (options.bar.position.value === "top") if (options.bar.position.value === "top")
self.transition = "slide_down"; self.transition = "slide_down";
if (options.bar.position.value === "bottom") if (options.bar.position.value === "bottom")
self.transition = "slide_up"; self.transition = "slide_up";
}, }),
],
],
child: Widget.Box({ child: Widget.Box({
vertical: true, vertical: true,
children: [ children: [
@@ -60,12 +56,8 @@ export default () =>
], ],
}), }),
Row( Row(
[Homogeneous([/*NetworkToggle(),*/ BluetoothToggle()]), DND()], [Homogeneous([ThemeToggle(), BluetoothToggle()]), MicMute()],
[/*WifiSelection()*/ BluetoothDevices()], [ThemeSelector(), BluetoothDevices()],
),
Row(
[Homogeneous([/*ProfileToggle(),*/ ThemeToggle()]), MicMute()],
[/*ProfileSelector(),*/ ThemeSelector()],
), ),
Media(), Media(),
], ],

View File

@@ -18,13 +18,7 @@ App.connect("window-toggled", (_, name, visible) => {
export const Arrow = (name, activate) => { export const Arrow = (name, activate) => {
let deg = 0; let deg = 0;
let iconOpened = false; let iconOpened = false;
return Widget.Button({ const icon = Widget.Icon(icons.ui.arrow.right).hook(opened, () => {
child: Widget.Icon({
icon: icons.ui.arrow.right,
connections: [
[
opened,
(icon) => {
if ( if (
(opened.value === name && !iconOpened) || (opened.value === name && !iconOpened) ||
(opened.value !== name && iconOpened) (opened.value !== name && iconOpened)
@@ -38,10 +32,9 @@ export const Arrow = (name, activate) => {
}); });
} }
} }
}, });
], return Widget.Button({
], child: icon,
}),
on_clicked: () => { on_clicked: () => {
opened.value = opened.value === name ? "" : name; opened.value = opened.value === name ? "" : name;
if (typeof activate === "function") activate(); if (typeof activate === "function") activate();
@@ -70,14 +63,10 @@ export const ArrowToggleButton = ({
}) => }) =>
Widget.Box({ Widget.Box({
class_name: "toggle-button", class_name: "toggle-button",
connections: [ setup: (self) =>
[ self.hook(service, () => {
service, self.toggleClassName("active", condition());
(box) => { }),
box.toggleClassName("active", condition());
},
],
],
children: [ children: [
Widget.Button({ Widget.Button({
child: Widget.Box({ child: Widget.Box({
@@ -108,7 +97,7 @@ export const ArrowToggleButton = ({
export const Menu = ({ name, icon, title, content }) => export const Menu = ({ name, icon, title, content }) =>
Widget.Revealer({ Widget.Revealer({
transition: "slide_down", transition: "slide_down",
binds: [["reveal-child", opened, "value", (v) => v === name]], reveal_child: opened.bind().transform((v) => v === name),
child: Widget.Box({ child: Widget.Box({
class_names: ["menu", name], class_names: ["menu", name],
vertical: true, vertical: true,
@@ -136,14 +125,10 @@ export const SimpleToggleButton = ({
}) => }) =>
Widget.Button({ Widget.Button({
class_name: "simple-toggle", class_name: "simple-toggle",
connections: [ setup: (self) =>
[ self.hook(service, () => {
service, self.toggleClassName("active", condition());
(box) => { }),
box.toggleClassName("active", condition());
},
],
],
child: icon, child: icon,
on_clicked: toggle, on_clicked: toggle,
}); });

View File

@@ -8,10 +8,10 @@ export const ProfileToggle = () =>
ArrowToggleButton({ ArrowToggleButton({
name: "asusctl-profile", name: "asusctl-profile",
icon: Widget.Icon({ icon: Widget.Icon({
binds: [["icon", Asusctl, "profile", (p) => icons.asusctl.profile[p]]], icon: Asusctl.bind("profile").transform((p) => icons.asusctl.profile[p]),
}), }),
label: Widget.Label({ label: Widget.Label({
binds: [["label", Asusctl, "profile"]], label: Asusctl.bind("profile"),
}), }),
connection: [Asusctl, () => Asusctl.profile !== "Balanced"], connection: [Asusctl, () => Asusctl.profile !== "Balanced"],
activate: () => Asusctl.setProfile("Quiet"), activate: () => Asusctl.setProfile("Quiet"),
@@ -23,7 +23,7 @@ export const ProfileSelector = () =>
Menu({ Menu({
name: "asusctl-profile", name: "asusctl-profile",
icon: Widget.Icon({ icon: Widget.Icon({
binds: [["icon", Asusctl, "profile", (p) => icons.asusctl.profile[p]]], icon: Asusctl.bind("profile").transform((p) => icons.asusctl.profile[p]),
}), }),
title: Widget.Label("Profile Selector"), title: Widget.Label("Profile Selector"),
content: [ content: [

View File

@@ -7,41 +7,31 @@ export const BluetoothToggle = () =>
ArrowToggleButton({ ArrowToggleButton({
name: "bluetooth", name: "bluetooth",
icon: Widget.Icon({ icon: Widget.Icon({
connections: [ icon: Bluetooth.bind("enabled").transform(
[ (p) => icons.bluetooth[p ? "enabled" : "disabled"],
Bluetooth, ),
(icon) => {
icon.icon = Bluetooth.enabled
? icons.bluetooth.enabled
: icons.bluetooth.disabled;
},
],
],
}), }),
label: Widget.Label({ label: Widget.Label({
truncate: "end", truncate: "end",
connections: [ setup: (self) =>
[ self.hook(Bluetooth, () => {
Bluetooth, if (!Bluetooth.enabled) return (self.label = "Disabled");
(label) => {
if (!Bluetooth.enabled) return (label.label = "Disabled");
if (Bluetooth.connectedDevices.length === 0) if (Bluetooth.connected_devices.length === 0)
return (label.label = "Not Connected"); return (self.label = "Not Connected");
if (Bluetooth.connectedDevices.length === 1) if (Bluetooth.connected_devices.length === 1)
return (label.label = Bluetooth.connectedDevices[0].alias); return (self.label = Bluetooth.connected_devices[0].alias);
label.label = `${Bluetooth.connectedDevices.length} Connected`; self.label = `${Bluetooth.connected_devices.length} Connected`;
}, }),
],
],
}), }),
connection: [Bluetooth, () => Bluetooth.enabled], connection: [Bluetooth, () => Bluetooth.enabled],
deactivate: () => (Bluetooth.enabled = false), deactivate: () => (Bluetooth.enabled = false),
activate: () => (Bluetooth.enabled = true), activate: () => (Bluetooth.enabled = true),
}); });
/** @param {import('types/service/bluetooth').BluetoothDevice} device */
const DeviceItem = (device) => const DeviceItem = (device) =>
Widget.Box({ Widget.Box({
children: [ children: [
@@ -49,26 +39,20 @@ const DeviceItem = (device) =>
Widget.Label(device.name), Widget.Label(device.name),
Widget.Label({ Widget.Label({
label: `${device.battery_percentage}%`, label: `${device.battery_percentage}%`,
binds: [["visible", device, "battery-percentage", (p) => p > 0]], visible: device.bind("battery_percentage").transform((p) => p > 0),
}), }),
Widget.Box({ hexpand: true }), Widget.Box({ hexpand: true }),
Widget.Spinner({ Widget.Spinner({
binds: [ active: device.bind("connecting"),
["active", device, "connecting"], visible: device.bind("connecting"),
["visible", device, "connecting"],
],
}), }),
Widget.Switch({ Widget.Switch({
active: device.connected, active: device.connected,
binds: [["visible", device, "connecting", (c) => !c]], visible: device.bind("connecting").transform((p) => !p),
connections: [ setup: (self) =>
[ self.on("notify::active", () => {
"notify::active", device.setConnection(self.active);
({ active }) => { }),
device.setConnection(active);
},
],
],
}), }),
], ],
}); });
@@ -82,14 +66,9 @@ export const BluetoothDevices = () =>
Widget.Box({ Widget.Box({
hexpand: true, hexpand: true,
vertical: true, vertical: true,
binds: [ children: Bluetooth.bind("devices").transform((ds) =>
[ ds.filter((d) => d.name).map(DeviceItem),
"children", ),
Bluetooth,
"devices",
(ds) => ds.filter((d) => d.name).map(DeviceItem),
],
],
}), }),
], ],
}); });

View File

@@ -1,15 +1,12 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js"; import Widget from "resource:///com/github/Aylur/ags/widget.js";
import icons from "../../icons.js"; import icons from "../../icons.js";
import Brightness from "../../services/brightness.js"; import Brightness from "../../services/brightness.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
const CanSupportBrightness = () => Utils.exec("which gbmonctl");
const BrightnessSlider = () => const BrightnessSlider = () =>
Widget.Slider({ Widget.Slider({
draw_value: false, draw_value: false,
hexpand: true, hexpand: true,
binds: [["value", Brightness, "screen"]], value: Brightness.bind("screen"),
on_change: ({ value }) => (Brightness.screen = value), on_change: ({ value }) => (Brightness.screen = value),
}); });
@@ -18,14 +15,9 @@ export default () =>
children: [ children: [
Widget.Button({ Widget.Button({
child: Widget.Icon(icons.brightness.indicator), child: Widget.Icon(icons.brightness.indicator),
binds: [ tooltip_text: Brightness.bind("screen").transform(
[
"tooltip-text",
Brightness,
"screen",
(v) => `Screen Brightness: ${Math.floor(v * 100)}%`, (v) => `Screen Brightness: ${Math.floor(v * 100)}%`,
], ),
],
}), }),
BrightnessSlider(), BrightnessSlider(),
], ],

View File

@@ -6,17 +6,9 @@ import { SimpleToggleButton } from "../ToggleButton.js";
export default () => export default () =>
SimpleToggleButton({ SimpleToggleButton({
icon: Widget.Icon({ icon: Widget.Icon({
connections: [ icon: Notifications.bind("dnd").transform(
[ (dnd) => icons.notifications[dnd ? "silent" : "noisy"],
Notifications, ),
(icon) => {
icon.icon = Notifications.dnd
? icons.notifications.silent
: icons.notifications.noisy;
},
"notify::dnd",
],
],
}), }),
toggle: () => (Notifications.dnd = !Notifications.dnd), toggle: () => (Notifications.dnd = !Notifications.dnd),
connection: [Notifications, () => Notifications.dnd], connection: [Notifications, () => Notifications.dnd],

View File

@@ -6,6 +6,7 @@ import Avatar from "../../misc/Avatar.js";
import icons from "../../icons.js"; import icons from "../../icons.js";
import { openSettings } from "../../settings/theme.js"; import { openSettings } from "../../settings/theme.js";
import { uptime } from "../../variables.js"; import { uptime } from "../../variables.js";
import DND from "./DND.js";
export default () => export default () =>
Widget.Box({ Widget.Box({
@@ -20,16 +21,17 @@ export default () =>
/*Widget.Box({ /*Widget.Box({
class_name: "battery horizontal", class_name: "battery horizontal",
children: [ children: [
Widget.Icon({ binds: [["icon", Battery, "icon-name"]] }), Widget.Icon({ icon: Battery.bind("icon_name") }),
Widget.Label({ Widget.Label({
binds: [["label", Battery, "percent", (p) => `${p}%`]], label: Battery.bind("percent").transform((p) => `${p}%`),
}), }),
], ],
}), }),*/
DND(),
Widget.Label({ Widget.Label({
class_name: "uptime", class_name: "uptime",
binds: [["label", uptime, "value", (v) => `up: ${v}`]], label: uptime.bind().transform((v) => `up: ${v}`),
}),*/ }),
Widget.Button({ Widget.Button({
on_clicked: openSettings, on_clicked: openSettings,
child: Widget.Icon(icons.ui.settings), child: Widget.Icon(icons.ui.settings),

View File

@@ -7,8 +7,7 @@ import options from "../../options.js";
const Footer = (player) => const Footer = (player) =>
Widget.CenterBox({ Widget.CenterBox({
class_name: "footer-box", class_name: "footer-box",
children: [ start_widget: Widget.Box({
Widget.Box({
class_name: "position", class_name: "position",
children: [ children: [
mpris.PositionLabel(player), mpris.PositionLabel(player),
@@ -16,7 +15,7 @@ const Footer = (player) =>
mpris.LengthLabel(player), mpris.LengthLabel(player),
], ],
}), }),
Widget.Box({ center_widget: Widget.Box({
class_name: "controls", class_name: "controls",
children: [ children: [
mpris.ShuffleButton(player), mpris.ShuffleButton(player),
@@ -26,12 +25,11 @@ const Footer = (player) =>
mpris.LoopButton(player), mpris.LoopButton(player),
], ],
}), }),
mpris.PlayerIcon(player, { end_widget: mpris.PlayerIcon(player, {
symbolic: false, symbolic: false,
hexpand: true, hexpand: true,
hpack: "end", hpack: "end",
}), }),
],
}); });
/** @param {import('types/service/mpris').MprisPlayer} player */ /** @param {import('types/service/mpris').MprisPlayer} player */
@@ -84,23 +82,10 @@ export default () =>
Widget.Box({ Widget.Box({
vertical: true, vertical: true,
class_name: "media vertical", class_name: "media vertical",
connections: [ visible: Mpris.bind("players").transform((p) => p.length > 0),
[ children: Mpris.bind("players").transform((ps) =>
"draw",
(self) => {
self.visible = Mpris.players.length > 0;
},
],
],
binds: [
[
"children",
Mpris,
"players",
(ps) =>
ps ps
.filter((p) => !options.mpris.black_list.value.includes(p.identity)) .filter((p) => !options.mpris.black_list.value.includes(p.identity))
.map(PlayerBox), .map(PlayerBox),
], ),
],
}); });

View File

@@ -5,19 +5,15 @@ import { SimpleToggleButton } from "../ToggleButton.js";
export default () => export default () =>
SimpleToggleButton({ SimpleToggleButton({
icon: Widget.Icon({ icon: Widget.Icon().hook(
connections: [
[
Audio, Audio,
(icon) => { (self) => {
icon.icon = Audio.microphone?.is_muted self.icon = Audio.microphone?.is_muted
? icons.audio.mic.muted ? icons.audio.mic.muted
: icons.audio.mic.high; : icons.audio.mic.high;
}, },
"microphone-changed", "microphone-changed",
], ),
],
}),
toggle: () => (Audio.microphone.is_muted = !Audio.microphone.is_muted), toggle: () => (Audio.microphone.is_muted = !Audio.microphone.is_muted),
connection: [Audio, () => Audio.microphone?.is_muted], connection: [Audio, () => Audio.microphone?.is_muted || false],
}); });

View File

@@ -9,25 +9,13 @@ export const NetworkToggle = () =>
ArrowToggleButton({ ArrowToggleButton({
name: "network", name: "network",
icon: Widget.Icon({ icon: Widget.Icon({
connections: [ icon: Network.wifi.bind("icon_name"),
[
Network,
(icon) => {
icon.icon = Network.wifi.icon_name || "";
},
],
],
}), }),
label: Widget.Label({ label: Widget.Label({
truncate: "end", truncate: "end",
connections: [ label: Network.wifi
[ .bind("ssid")
Network, .transform((ssid) => ssid || "Not Connected"),
(label) => {
label.label = Network.wifi.ssid || "Not Connected";
},
],
],
}), }),
connection: [Network, () => Network.wifi.enabled], connection: [Network, () => Network.wifi.enabled],
deactivate: () => (Network.wifi.enabled = false), deactivate: () => (Network.wifi.enabled = false),
@@ -41,24 +29,17 @@ export const WifiSelection = () =>
Menu({ Menu({
name: "network", name: "network",
icon: Widget.Icon({ icon: Widget.Icon({
connections: [ icon: Network.wifi.bind("icon_name"),
[
Network,
(icon) => {
icon.icon = Network.wifi.icon_name;
},
],
],
}), }),
title: Widget.Label("Wifi Selection"), title: Widget.Label("Wifi Selection"),
content: [ content: [
Widget.Box({ Widget.Box({
vertical: true, vertical: true,
connections: [ setup: (self) =>
[ self.hook(
Network, Network,
(box) => () =>
(box.children = Network.wifi?.access_points.map((ap) => (self.children = Network.wifi?.access_points.map((ap) =>
Widget.Button({ Widget.Button({
on_clicked: () => on_clicked: () =>
Utils.execAsync(`nmcli device wifi connect ${ap.bssid}`), Utils.execAsync(`nmcli device wifi connect ${ap.bssid}`),
@@ -76,8 +57,7 @@ export const WifiSelection = () =>
}), }),
}), }),
)), )),
], ),
],
}), }),
Widget.Separator(), Widget.Separator(),
Widget.Button({ Widget.Button({

View File

@@ -8,8 +8,8 @@ import { setTheme, openSettings } from "../../settings/theme.js";
export const ThemeToggle = () => export const ThemeToggle = () =>
ArrowToggleButton({ ArrowToggleButton({
name: "theme", name: "theme",
icon: Widget.Label({ binds: [["label", options.theme.icon]] }), icon: Widget.Label().bind("label", options.theme.icon),
label: Widget.Label({ binds: [["label", options.theme.name]] }), label: Widget.Label().bind("label", options.theme.name),
connection: [opened, () => opened.value === "theme"], connection: [opened, () => opened.value === "theme"],
activate: () => opened.setValue("theme"), activate: () => opened.setValue("theme"),
activateOnArrow: false, activateOnArrow: false,
@@ -19,9 +19,7 @@ export const ThemeToggle = () =>
export const ThemeSelector = () => export const ThemeSelector = () =>
Menu({ Menu({
name: "theme", name: "theme",
icon: Widget.Label({ icon: Widget.Label().bind("label", options.theme.icon),
binds: [["label", options.theme.icon]],
}),
title: Widget.Label("Theme Selector"), title: Widget.Label("Theme Selector"),
content: [ content: [
...themes.map(({ name, icon }) => ...themes.map(({ name, icon }) =>
@@ -35,9 +33,9 @@ export const ThemeSelector = () =>
icon: icons.ui.tick, icon: icons.ui.tick,
hexpand: true, hexpand: true,
hpack: "end", hpack: "end",
binds: [ visible: options.theme.name
["visible", options.theme.name, "value", (v) => v === name], .bind("value")
], .transform((v) => v === name),
}), }),
], ],
}), }),

View File

@@ -11,25 +11,13 @@ import { Menu } from "../ToggleButton.js";
const VolumeIndicator = (type = "speaker") => const VolumeIndicator = (type = "speaker") =>
Widget.Button({ Widget.Button({
on_clicked: () => (Audio[type].is_muted = !Audio[type].is_muted), on_clicked: () => (Audio[type].is_muted = !Audio[type].is_muted),
child: Widget.Icon({ child: Widget.Icon().hook(Audio[type], (icon) => {
connections: [
[
Audio,
(icon) => {
if (!Audio[type]) return;
icon.icon = icon.icon =
type === "speaker" type === "speaker"
? getAudioTypeIcon(Audio[type].icon_name || "") ? getAudioTypeIcon(Audio[type].icon_name || "")
: icons.audio.mic.high; : icons.audio.mic.high;
icon.tooltip_text = `Volume ${Math.floor( icon.tooltip_text = `Volume ${Math.floor(Audio[type].volume * 100)}%`;
Audio[type].volume * 100,
)}%`;
},
`${type}-changed`,
],
],
}), }),
}); });
@@ -39,15 +27,10 @@ const VolumeSlider = (type = "speaker") =>
hexpand: true, hexpand: true,
draw_value: false, draw_value: false,
on_change: ({ value }) => (Audio[type].volume = value), on_change: ({ value }) => (Audio[type].volume = value),
connections: [ setup: (self) =>
[ self.hook(Audio[type], () => {
Audio, self.value = Audio[type].volume || 0;
(slider) => { }),
slider.value = Audio[type]?.volume;
},
`${type}-changed`,
],
],
}); });
export const Volume = () => export const Volume = () =>
@@ -62,14 +45,7 @@ export const Volume = () =>
Widget.Box({ Widget.Box({
vpack: "center", vpack: "center",
child: Arrow("app-mixer"), child: Arrow("app-mixer"),
connections: [ visible: Audio.bind("apps").transform((a) => a.length > 0),
[
Audio,
(box) => {
box.visible = Audio.apps.length > 0;
},
],
],
}), }),
], ],
}); });
@@ -77,7 +53,7 @@ export const Volume = () =>
export const Microhone = () => export const Microhone = () =>
Widget.Box({ Widget.Box({
class_name: "slider horizontal", class_name: "slider horizontal",
binds: [["visible", Audio, "recorders", (r) => r.length > 0]], visible: Audio.bind("recorders").transform((a) => a.length > 0),
children: [VolumeIndicator("microphone"), VolumeSlider("microphone")], children: [VolumeIndicator("microphone"), VolumeSlider("microphone")],
}); });
@@ -88,17 +64,10 @@ const MixerItem = (stream) =>
class_name: "mixer-item horizontal", class_name: "mixer-item horizontal",
children: [ children: [
Widget.Icon({ Widget.Icon({
binds: [["tooltipText", stream, "name"]], tooltip_text: stream.bind("name").transform((n) => n || ""),
connections: [ icon: stream.bind("name").transform((n) => {
[ return Utils.lookUpIcon(n || "") ? n || "" : icons.mpris.fallback;
stream, }),
(icon) => {
icon.icon = Utils.lookUpIcon(stream.name || "")
? stream.name || ""
: icons.mpris.fallback;
},
],
],
}), }),
Widget.Box({ Widget.Box({
vertical: true, vertical: true,
@@ -106,26 +75,19 @@ const MixerItem = (stream) =>
Widget.Label({ Widget.Label({
xalign: 0, xalign: 0,
truncate: "end", truncate: "end",
binds: [["label", stream, "description"]], label: stream.bind("description").transform((d) => d || ""),
}), }),
Widget.Slider({ Widget.Slider({
hexpand: true, hexpand: true,
draw_value: false, draw_value: false,
binds: [["value", stream, "volume"]], value: stream.bind("volume"),
on_change: ({ value }) => (stream.volume = value), on_change: ({ value }) => (stream.volume = value),
}), }),
], ],
}), }),
Widget.Label({ Widget.Label({
xalign: 1, xalign: 1,
connections: [ label: stream.bind("volume").transform((v) => `${Math.floor(v * 100)}`),
[
stream,
(l) => {
l.label = `${Math.floor(stream.volume * 100)}%`;
},
],
],
}), }),
], ],
}); });
@@ -148,7 +110,9 @@ const SinkItem = (stream) =>
icon: icons.ui.tick, icon: icons.ui.tick,
hexpand: true, hexpand: true,
hpack: "end", hpack: "end",
binds: [["visible", Audio, "speaker", (s) => s === stream]], visible: Audio.speaker
.bind("stream")
.transform((s) => s === stream.stream),
}), }),
], ],
}), }),
@@ -171,7 +135,7 @@ export const AppMixer = () =>
content: [ content: [
Widget.Box({ Widget.Box({
vertical: true, vertical: true,
binds: [["children", Audio, "apps", (a) => a.map(MixerItem)]], children: Audio.bind("apps").transform((a) => a.map(MixerItem)),
}), }),
Widget.Separator(), Widget.Separator(),
SettingsButton(), SettingsButton(),
@@ -186,7 +150,7 @@ export const SinkSelector = () =>
content: [ content: [
Widget.Box({ Widget.Box({
vertical: true, vertical: true,
binds: [["children", Audio, "speakers", (s) => s.map(SinkItem)]], children: Audio.bind("speakers").transform((a) => a.map(SinkItem)),
}), }),
Widget.Separator(), Widget.Separator(),
SettingsButton(), SettingsButton(),

View File

@@ -10,23 +10,22 @@ const Corner = (place) =>
vexpand: true, vexpand: true,
hpack: place.includes("left") ? "start" : "end", hpack: place.includes("left") ? "start" : "end",
vpack: place.includes("top") ? "start" : "end", vpack: place.includes("top") ? "start" : "end",
connections: [ setup: (self) =>
[ self
options.radii, .hook(options.radii, () => {
(self) => {
const r = options.radii.value * 2; const r = options.radii.value * 2;
self.set_size_request(r, r); self.set_size_request(r, r);
}, })
], .connect("draw", (self, cr) => {
],
setup: (self) =>
self.connect("draw", (self, cr) => {
const context = self.get_style_context(); const context = self.get_style_context();
const c = context.get_property( const c = context.get_property(
"background-color", "background-color",
Gtk.StateFlags.NORMAL, Gtk.StateFlags.NORMAL,
); );
const r = context.get_property("border-radius", Gtk.StateFlags.NORMAL); const r = context.get_property(
"border-radius",
Gtk.StateFlags.NORMAL,
);
switch (place) { switch (place) {
case "topleft": case "topleft":
@@ -70,7 +69,7 @@ export default (monitor) =>
place.includes("top") ? "top" : "bottom", place.includes("top") ? "top" : "bottom",
place.includes("right") ? "right" : "left", place.includes("right") ? "right" : "left",
], ],
binds: [["visible", options.desktop.screen_corners]], visible: options.desktop.screen_corners.bind("value"),
child: Corner(place), child: Corner(place),
}), }),
); );

View File

@@ -13,7 +13,7 @@ class Asusctl extends Service {
); );
} }
profiles = Object.freeze(["Performance", "Balanced", "Quiet"]); profiles = /** @type {const} */ (["Performance", "Balanced", "Quiet"]);
#profile = "Balanced"; #profile = "Balanced";
#mode = "Hyprid"; #mode = "Hyprid";

View File

@@ -1,8 +1,8 @@
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
import { Variable } from "resource:///com/github/Aylur/ags/variable.js"; import { Variable } from "resource:///com/github/Aylur/ags/variable.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Service from "resource:///com/github/Aylur/ags/service.js"; import Service from "resource:///com/github/Aylur/ags/service.js";
import { dependencies } from "../utils.js"; import { dependencies } from "../utils.js";
import icons from "../icons.js";
const COLORS_CACHE = Utils.CACHE_DIR + "/colorpicker.json"; const COLORS_CACHE = Utils.CACHE_DIR + "/colorpicker.json";
@@ -58,15 +58,15 @@ class Colors extends Service {
); );
} }
this.#notifID = Notifications.Notify( const n = await Utils.notify({
"Color Picker", id: this.#notifID,
this.#notifID, iconName: icons.ui.colorpicker,
"color-select-symbolic", summary: color,
color, actions: {
"", Copy: () => this.wlCopy(color),
[], },
{}, });
); this.#notifID = n.id;
} }
} }

View File

@@ -21,7 +21,7 @@ showSearch.connect("changed", ({ value }) => {
/** @param {import('./option.js').Opt<string>} opt */ /** @param {import('./option.js').Opt<string>} opt */
const EnumSetter = (opt) => { const EnumSetter = (opt) => {
const lbl = Widget.Label({ binds: [["label", opt]] }); const lbl = Widget.Label().bind("label", opt);
const step = (dir = 1) => { const step = (dir = 1) => {
const i = opt.enums.findIndex((i) => i === lbl.label); const i = opt.enums.findIndex((i) => i === lbl.label);
opt.setValue( opt.setValue(
@@ -57,51 +57,42 @@ const Setter = (opt) => {
setup(self) { setup(self) {
self.set_range(0, 1000); self.set_range(0, 1000);
self.set_increments(1, 5); self.set_increments(1, 5);
self.on("value-changed", () => opt.setValue(self.value, true));
self.hook(opt, () => (self.value = opt.value));
}, },
connections: [
["value-changed", (self) => opt.setValue(self.value, true)],
[opt, (self) => (self.value = opt.value)],
],
}); });
case "float": case "float":
case "object": case "object":
return Widget.Entry({ return Widget.Entry({
on_accept: (self) => opt.setValue(JSON.parse(self.text || ""), true), on_accept: (self) => opt.setValue(JSON.parse(self.text || ""), true),
connections: [[opt, (self) => (self.text = JSON.stringify(opt.value))]], setup: (self) =>
self.hook(opt, () => (self.text = JSON.stringify(opt.value))),
}); });
case "string": case "string":
return Widget.Entry({ return Widget.Entry({
on_accept: (self) => opt.setValue(self.text, true), on_accept: (self) => opt.setValue(self.text, true),
connections: [[opt, (self) => (self.text = opt.value)]], setup: (self) => self.hook(opt, () => (self.text = opt.value)),
}); });
case "enum": case "enum":
return EnumSetter(opt); return EnumSetter(opt);
case "boolean": case "boolean":
return Widget.Switch({ return Widget.Switch()
connections: [ .on("notify::active", (self) => opt.setValue(self.active, true))
["notify::active", (self) => opt.setValue(self.active, true)], .hook(opt, (self) => (self.active = opt.value));
[opt, (self) => (self.active = opt.value)],
],
});
case "img": case "img":
return Widget.FileChooserButton({ return Widget.FileChooserButton().on("selection-changed", (self) => {
connections: [
[
"selection-changed",
(self) => {
opt.setValue(self.get_uri()?.replace("file://", ""), true); opt.setValue(self.get_uri()?.replace("file://", ""), true);
},
],
],
}); });
case "font": case "font":
return Widget.FontButton({ return Widget.FontButton({
show_size: false, show_size: false,
use_size: false, use_size: false,
connections: [ setup: (self) =>
["notify::font", ({ font }) => opt.setValue(font, true)], self
[opt, (self) => (self.font = opt.value)], .on("notify::font", ({ font }) => opt.setValue(font, true))
], .hook(opt, () => (self.font = opt.value)),
}); });
default: default:
return Widget.Label({ return Widget.Label({
@@ -114,7 +105,7 @@ const Setter = (opt) => {
const Row = (opt) => const Row = (opt) =>
Widget.Box({ Widget.Box({
class_name: "row", class_name: "row",
setup: (self) => (self.opt = opt), attribute: opt,
children: [ children: [
Widget.Box({ Widget.Box({
vertical: true, vertical: true,
@@ -161,19 +152,15 @@ const Page = (category) =>
child: Widget.Box({ child: Widget.Box({
class_name: "page-content vertical", class_name: "page-content vertical",
vertical: true, vertical: true,
connections: [ setup: (self) =>
[ self.hook(search, () => {
search,
(self) => {
for (const child of self.children) { for (const child of self.children) {
child.visible = child.visible =
child.opt.id.includes(search.value) || child.attribute.id.includes(search.value) ||
child.opt.title.includes(search.value) || child.attribute.title.includes(search.value) ||
child.opt.note.includes(search.value); child.attribute.note.includes(search.value);
} }
}, }),
],
],
children: optionsList children: optionsList
.filter((opt) => opt.category.includes(category)) .filter((opt) => opt.category.includes(category))
.map(Row), .map(Row),
@@ -181,7 +168,7 @@ const Page = (category) =>
}); });
const sidebar = Widget.Revealer({ const sidebar = Widget.Revealer({
binds: [["reveal-child", search, "value", (v) => !v]], reveal_child: search.bind().transform((v) => !v),
transition: "slide_right", transition: "slide_right",
child: Widget.Box({ child: Widget.Box({
hexpand: false, hexpand: false,
@@ -213,14 +200,9 @@ const sidebar = Widget.Revealer({
Widget.Button({ Widget.Button({
label: (icons.dialog[name] || "") + " " + name, label: (icons.dialog[name] || "") + " " + name,
xalign: 0, xalign: 0,
binds: [ class_name: currentPage
[ .bind()
"class-name", .transform((v) => `${v === name ? "active" : ""}`),
currentPage,
"value",
(v) => (v === name ? "active" : ""),
],
],
on_clicked: () => currentPage.setValue(name), on_clicked: () => currentPage.setValue(name),
}), }),
), ),
@@ -254,21 +236,15 @@ const sidebar = Widget.Revealer({
const searchEntry = Widget.Revealer({ const searchEntry = Widget.Revealer({
transition: "slide_down", transition: "slide_down",
binds: [ reveal_child: showSearch.bind(),
["reveal-child", showSearch], transition_duration: options.transition.bind("value"),
["transition-duration", options.transition],
],
child: Widget.Entry({ child: Widget.Entry({
connections: [ setup: (self) =>
[ self.hook(showSearch, () => {
showSearch,
(self) => {
if (!showSearch.value) self.text = ""; if (!showSearch.value) self.text = "";
if (showSearch.value) self.grab_focus(); if (showSearch.value) self.grab_focus();
}, }),
],
],
hexpand: true, hexpand: true,
class_name: "search", class_name: "search",
placeholder_text: "Search Options", placeholder_text: "Search Options",
@@ -279,41 +255,36 @@ const searchEntry = Widget.Revealer({
const categoriesStack = Widget.Stack({ const categoriesStack = Widget.Stack({
transition: "slide_left_right", transition: "slide_left_right",
items: categories.map((name) => [name, Page(name)]), children: categories.reduce((obj, name) => {
binds: [ obj[name] = Page(name);
["shown", currentPage], return obj;
["visible", search, "value", (v) => !v], }, {}),
], shown: currentPage.bind(),
visible: search.bind().transform((v) => !v),
}); });
const searchPage = Widget.Box({ const searchPage = Widget.Box({
binds: [["visible", search, "value", (v) => !!v]], visible: search.bind().transform((v) => !!v),
child: Page(""), child: Page(""),
}); });
export default RegularWindow({ export default RegularWindow({
name: "settings-dialog", name: "settings-dialog",
title: "Settings", title: "Settings",
setup: (win) => win.set_default_size(800, 500), setup: (win) =>
connections: [ win
[ .on("delete-event", () => {
"delete-event",
(win) => {
win.hide(); win.hide();
return true; return true;
}, })
], .on("key-press-event", (_, event) => {
[
"key-press-event",
(self, event) => {
if (event.get_keyval()[1] === imports.gi.Gdk.KEY_Escape) { if (event.get_keyval()[1] === imports.gi.Gdk.KEY_Escape) {
self.text = "";
showSearch.setValue(false); showSearch.setValue(false);
search.setValue(""); search.setValue("");
} }
}, })
], .set_default_size(800, 500),
],
child: Widget.Box({ child: Widget.Box({
children: [ children: [
sidebar, sidebar,

View File

@@ -15,6 +15,9 @@ export async function globals() {
globalThis.indicator = ( globalThis.indicator = (
await import("../services/onScreenIndicator.js") await import("../services/onScreenIndicator.js")
).default; ).default;
globalThis.app = (
await import("resource:///com/github/Aylur/ags/app.js")
).default;
Mpris.players.forEach((player) => { Mpris.players.forEach((player) => {
player.connect("changed", (player) => { player.connect("changed", (player) => {

View File

@@ -5,7 +5,7 @@ import {
} from "resource:///com/github/Aylur/ags/utils.js"; } from "resource:///com/github/Aylur/ags/utils.js";
import { exec } from "resource:///com/github/Aylur/ags/utils.js"; import { exec } from "resource:///com/github/Aylur/ags/utils.js";
import options from "../options.js"; import options from "../options.js";
import Service, { Binding } from "resource:///com/github/Aylur/ags/service.js"; import Service from "resource:///com/github/Aylur/ags/service.js";
import { reloadScss } from "./scss.js"; import { reloadScss } from "./scss.js";
import { setupHyprland } from "./hyprland.js"; import { setupHyprland } from "./hyprland.js";
const CACHE_FILE = CACHE_DIR + "/options.json"; const CACHE_FILE = CACHE_DIR + "/options.json";

View File

@@ -1,7 +1,6 @@
import App from "resource:///com/github/Aylur/ags/app.js"; import App from "resource:///com/github/Aylur/ags/app.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import { getOptions } from "./option.js"; import { getOptions } from "./option.js";
import { dependencies } from "../utils.js";
export function scssWatcher() { export function scssWatcher() {
return Utils.subprocess( return Utils.subprocess(
@@ -26,8 +25,6 @@ export function scssWatcher() {
* options.bar.style.value => $bar-style * options.bar.style.value => $bar-style
*/ */
export async function reloadScss() { export async function reloadScss() {
if (!dependencies(["sassc"])) return;
const opts = getOptions(); const opts = getOptions();
const vars = opts.map((opt) => { const vars = opts.map((opt) => {
if (opt.scss === "exclude") return ""; if (opt.scss === "exclude") return "";

View File

@@ -8,7 +8,7 @@ import { wallpaper } from "./wallpaper.js";
import { hyprlandInit, setupHyprland } from "./hyprland.js"; import { hyprlandInit, setupHyprland } from "./hyprland.js";
import { globals } from "./globals.js"; import { globals } from "./globals.js";
import { showAbout } from "../about/about.js"; import { showAbout } from "../about/about.js";
import Gtk from "gi://Gtk"; import Gtk from "gi://Gtk?version=3.0";
export function init() { export function init() {
notificationBlacklist(); notificationBlacklist();

View File

@@ -32,7 +32,7 @@ export function forMonitors(widget) {
} }
/** /**
* @param {import('gi://Gtk').Gtk.Widget} widget * @param {import('gi://Gtk?version=3.0').default.Widget} widget
* @returns {any} - missing cairo type * @returns {any} - missing cairo type
*/ */
export function createSurfaceFromWidget(widget) { export function createSurfaceFromWidget(widget) {

View File

@@ -4,6 +4,10 @@ import options from "./options.js";
const intval = options.systemFetchInterval; const intval = options.systemFetchInterval;
export const clock = Variable(GLib.DateTime.new_now_local(), {
poll: [1000, () => GLib.DateTime.new_now_local()],
});
export const uptime = Variable("", { export const uptime = Variable("", {
poll: [ poll: [
60_000, 60_000,

View File

@@ -1,11 +1,13 @@
{ {
"name": "ags-dotfiles", "name": "ags-dotfiles",
"version": "1.6.4", "version": "1.7.5",
"description": "My config files for AGS", "description": "My config files for AGS",
"main": "config.js", "main": "config.js",
"scripts": { "scripts": {
"check": "tsc --noEmit",
"lint": "eslint . --fix", "lint": "eslint . --fix",
"stylelint": "stylelint ./scss --fix" "stylelint": "stylelint ./scss --fix",
"format": "prettier --write ."
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -18,10 +20,16 @@
"homepage": "https://github.com/Aylur/dotfiles#readme", "homepage": "https://github.com/Aylur/dotfiles#readme",
"kofi": "https://ko-fi.com/aylur", "kofi": "https://ko-fi.com/aylur",
"devDependencies": { "devDependencies": {
"@girs/dbusmenugtk3-0.4": "^0.4.0-3.2.7", "@girs/dbusmenugtk3-0.4": "^0.4.0-3.2.0",
"@girs/gobject-2.0": "^2.78.0-3.2.7", "@girs/gobject-2.0": "^2.76.1-3.2.3",
"@girs/gtk-3.0": "^3.24.38-3.2.7", "@girs/gtk-3.0": "^3.24.39-3.2.2",
"@girs/gvc-1.0": "^1.0.0-3.2.7", "@girs/gvc-1.0": "^1.0.0-3.1.0",
"@girs/nm-1.0": "^1.43.1-3.1.0" "@girs/nm-1.0": "^1.43.1-3.1.0",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"eslint": "^8.44.0",
"prettier": "^3.2.5",
"stylelint-config-standard-scss": "^10.0.0",
"typescript": "^5.3.3"
} }
} }

View File

@@ -8,7 +8,7 @@
"strict": true, "strict": true,
"noImplicitAny": false, "noImplicitAny": false,
"baseUrl": ".", "baseUrl": ".",
"typeRoots": ["./types/ags.d.ts", "./node_modules/@girs"], "typeRoots": ["./types"],
"skipLibCheck": true, "skipLibCheck": true,
"forceConsistentCasingInFileNames": true "forceConsistentCasingInFileNames": true
} }

View File

@@ -160,6 +160,7 @@
brightnessctl brightnessctl
ydotool ydotool
kitty kitty
hyprpicker
]}"; ]}";
}; };
Install = { Install = {