summaryrefslogtreecommitdiff
path: root/src/modules
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-01-30 21:31:19 +1100
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-01-30 21:31:19 +1100
commitad22dbdfebbb0def2ec2d8e2c91469e9a9e4fdf7 (patch)
tree826e715ae0aa7d4c5bcd29d76421a211b920dca1 /src/modules
parentsideright: fix assertion row != -1 (diff)
downloadcaelestia-shell-ad22dbdfebbb0def2ec2d8e2c91469e9a9e4fdf7.tar.gz
caelestia-shell-ad22dbdfebbb0def2ec2d8e2c91469e9a9e4fdf7.tar.bz2
caelestia-shell-ad22dbdfebbb0def2ec2d8e2c91469e9a9e4fdf7.zip
sideleft: create popdown window
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/bar.tsx32
-rw-r--r--src/modules/popdowns/index.tsx2
-rw-r--r--src/modules/popdowns/sideleft.tsx203
3 files changed, 226 insertions, 11 deletions
diff --git a/src/modules/bar.tsx b/src/modules/bar.tsx
index eb42d0b..16c505a 100644
--- a/src/modules/bar.tsx
+++ b/src/modules/bar.tsx
@@ -1,21 +1,21 @@
+import type { Monitor } from "@/services/monitors";
+import Players from "@/services/players";
+import Updates from "@/services/updates";
+import { getAppCategoryIcon } from "@/utils/icons";
+import { ellipsize } from "@/utils/strings";
+import { bindCurrentTime, osIcon } from "@/utils/system";
+import { setupCustomTooltip } from "@/utils/widgets";
+import type PopupWindow from "@/widgets/popupwindow";
import { execAsync, register, Variable } from "astal";
import { bind, kebabify } from "astal/binding";
import { App, Astal, astalify, Gdk, Gtk, type ConstructProps } from "astal/gtk3";
+import { bar as config } from "config";
import AstalBluetooth from "gi://AstalBluetooth";
import AstalHyprland from "gi://AstalHyprland";
import AstalNetwork from "gi://AstalNetwork";
import AstalNotifd from "gi://AstalNotifd";
import AstalTray from "gi://AstalTray";
import AstalWp01 from "gi://AstalWp";
-import { bar as config } from "../../config";
-import type { Monitor } from "../services/monitors";
-import Players from "../services/players";
-import Updates from "../services/updates";
-import { getAppCategoryIcon } from "../utils/icons";
-import { ellipsize } from "../utils/strings";
-import { bindCurrentTime, osIcon } from "../utils/system";
-import { setupCustomTooltip } from "../utils/widgets";
-import type PopupWindow from "../widgets/popupwindow";
const hyprland = AstalHyprland.get_default();
@@ -44,7 +44,13 @@ const togglePopup = (self: JSX.Element, event: Astal.ClickEvent, name: string) =
}
};
-const OSIcon = () => <label className="module os-icon" label={osIcon} />;
+const OSIcon = () => (
+ <button
+ className="module os-icon"
+ label={osIcon}
+ onClick={(self, event) => event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "sideleft")}
+ />
+);
const ActiveWindow = () => (
<box
@@ -453,7 +459,11 @@ const DateTime = () => (
);
const Power = () => (
- <button className="module power" label="power_settings_new" onClicked={() => App.toggle_window("session")} />
+ <button
+ className="module power"
+ label="power_settings_new"
+ onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && App.toggle_window("session")}
+ />
);
export default ({ monitor }: { monitor: Monitor }) => (
diff --git a/src/modules/popdowns/index.tsx b/src/modules/popdowns/index.tsx
index c4f4664..fb9abf7 100644
--- a/src/modules/popdowns/index.tsx
+++ b/src/modules/popdowns/index.tsx
@@ -2,6 +2,7 @@ import BluetoothDevices from "./bluetoothdevices";
import Media from "./media";
import Networks from "./networks";
import Notifications from "./notifications";
+import SideLeft from "./sideleft";
import SideRight from "./sideright";
import Updates from "./updates";
@@ -12,6 +13,7 @@ export default () => {
<Networks />;
<Media />;
<SideRight />;
+ <SideLeft />;
return null;
};
diff --git a/src/modules/popdowns/sideleft.tsx b/src/modules/popdowns/sideleft.tsx
new file mode 100644
index 0000000..fdf3e4f
--- /dev/null
+++ b/src/modules/popdowns/sideleft.tsx
@@ -0,0 +1,203 @@
+import Cpu from "@/services/cpu";
+import Gpu from "@/services/gpu";
+import Memory from "@/services/memory";
+import Storage from "@/services/storage";
+import { osId } from "@/utils/system";
+import PopupWindow from "@/widgets/popupwindow";
+import { bind, execAsync, GLib, type Binding } from "astal";
+import { App, Gtk, type Widget } from "astal/gtk3";
+import type cairo from "cairo";
+
+const fmt = (bytes: number, pow: number) => +(bytes / 1024 ** pow).toFixed(2);
+const format = ({ total, used }: { total: number; used: number }) => {
+ if (total >= 1024 ** 4) return `${fmt(used, 4)}/${fmt(total, 4)} TiB`;
+ if (total >= 1024 ** 3) return `${fmt(used, 3)}/${fmt(total, 3)} GiB`;
+ if (total >= 1024 ** 2) return `${fmt(used, 2)}/${fmt(total, 2)} MiB`;
+ if (total >= 1024) return `${fmt(used, 1)}/${fmt(total, 1)} KiB`;
+ return `${used}/${total} B`;
+};
+
+const User = () => (
+ <box className="user">
+ <box
+ valign={Gtk.Align.CENTER}
+ className="face"
+ css={`
+ background-image: url("${HOME}/.face");
+ `}
+ >
+ {!GLib.file_test(HOME + "/.face", GLib.FileTest.EXISTS) && (
+ <label
+ setup={self => {
+ const name = GLib.get_real_name();
+ if (name !== "Unknown")
+ self.label = name
+ .split(" ")
+ .map(s => s[0].toUpperCase())
+ .join("");
+ else self.label = "";
+ }}
+ />
+ )}
+ </box>
+ <box vertical hexpand valign={Gtk.Align.CENTER} className="details">
+ <label xalign={0} className="name" label={GLib.get_user_name()} />
+ <label xalign={0} label={(GLib.getenv("XDG_CURRENT_DESKTOP") ?? osId).toUpperCase()} />
+ </box>
+ <button
+ valign={Gtk.Align.CENTER}
+ className="power"
+ cursor="pointer"
+ onClicked={() => App.toggle_window("session")}
+ label="power_settings_new"
+ />
+ </box>
+);
+
+const QuickLaunch = () => (
+ <box className="quick-launch">
+ <box vertical>
+ <box>{/* <button */}</box> // TODO
+ </box>
+ </box>
+);
+
+const Location = ({ home, label, num }: { home?: boolean; label: string; num: number }) => (
+ <button
+ className={"loc" + num}
+ cursor="pointer"
+ onClicked={self => {
+ self.get_toplevel().hide();
+ execAsync(`xdg-open ${HOME}/${home ? "" : label.split(" ").at(-1) + "/"}`).catch(console.error);
+ }}
+ >
+ <label xalign={0} label={label} />
+ </button>
+);
+
+const Locations = () => (
+ <box className="locations">
+ <box vertical>
+ <Location label="󰉍 Downloads" num={1} />
+ <Location label="󱧶 Documents" num={2} />
+ <Location label="󱍙 Music" num={3} />
+ </box>
+ <box vertical>
+ <Location label="󰉏 Pictures" num={4} />
+ <Location label="󰉏 Videos" num={5} />
+ <Location label="󱂵 Home" num={6} home />
+ </box>
+ </box>
+);
+
+const Slider = ({ value }: { value: Binding<number> }) => (
+ <drawingarea
+ hexpand
+ valign={Gtk.Align.CENTER}
+ className="slider"
+ css={bind(value).as(v => `font-size: ${v}px;`)}
+ setup={self => {
+ const halfPi = Math.PI / 2;
+
+ const styleContext = self.get_style_context();
+ self.set_size_request(-1, styleContext.get_property("min-height", Gtk.StateFlags.NORMAL) as number);
+
+ self.connect("draw", (_, cr: cairo.Context) => {
+ const styleContext = self.get_style_context();
+
+ const width = self.get_allocated_width();
+ const height = styleContext.get_property("min-height", Gtk.StateFlags.NORMAL) as number;
+ self.set_size_request(-1, height);
+
+ const progressValue = styleContext.get_property("font-size", Gtk.StateFlags.NORMAL) as number;
+ let radius = styleContext.get_property("border-radius", Gtk.StateFlags.NORMAL) as number;
+
+ const bg = styleContext.get_background_color(Gtk.StateFlags.NORMAL);
+ cr.setSourceRGBA(bg.red, bg.green, bg.blue, bg.alpha);
+
+ // Background
+ cr.arc(radius, radius, radius, -Math.PI, -halfPi); // Top left
+ cr.arc(width - radius, radius, radius, -halfPi, 0); // Top right
+ cr.arc(width - radius, height - radius, radius, 0, halfPi); // Bottom right
+ cr.arc(radius, height - radius, radius, halfPi, Math.PI); // Bottom left
+ cr.fill();
+
+ // Flatten when near 0
+ radius = Math.min(radius, Math.min(width * progressValue, height) / 2);
+
+ const progressPosition = width * progressValue - radius;
+ const fg = styleContext.get_color(Gtk.StateFlags.NORMAL);
+ cr.setSourceRGBA(fg.red, fg.green, fg.blue, fg.alpha);
+
+ // Foreground
+ cr.arc(radius, radius, radius, -Math.PI, -halfPi); // Top left
+ cr.arc(progressPosition, radius, radius, -halfPi, 0); // Top right
+ cr.arc(progressPosition, height - radius, radius, 0, halfPi); // Bottom right
+ cr.arc(radius, height - radius, radius, halfPi, Math.PI); // Bottom left
+ cr.fill();
+ });
+ }}
+ />
+);
+
+const Resource = ({
+ icon,
+ name,
+ value,
+ labelSetup,
+}: {
+ icon: string;
+ name: string;
+ value: Binding<number>;
+ labelSetup?: (self: Widget.Label) => void;
+}) => (
+ <box vertical className={`resource ${name}`}>
+ <box className="inner">
+ <label label={icon} />
+ <Slider value={value.as(v => v / 100)} />
+ </box>
+ <label halign={Gtk.Align.END} label={labelSetup ? "" : value.as(v => `${+v.toFixed(2)}%`)} setup={labelSetup} />
+ </box>
+);
+
+const HwResources = () => (
+ <box vertical className="hw-resources">
+ {Gpu.get_default().available && <Resource icon="󰢮" name="gpu" value={bind(Gpu.get_default(), "usage")} />}
+ <Resource icon="" name="cpu" value={bind(Cpu.get_default(), "usage")} />
+ <Resource
+ icon=""
+ name="memory"
+ value={bind(Memory.get_default(), "usage")}
+ labelSetup={self => {
+ const mem = Memory.get_default();
+ const update = () => (self.label = format(mem));
+ self.hook(mem, "notify::used", update);
+ self.hook(mem, "notify::total", update);
+ update();
+ }}
+ />
+ <Resource
+ icon="󰋊"
+ name="storage"
+ value={bind(Storage.get_default(), "usage")}
+ labelSetup={self => {
+ const storage = Storage.get_default();
+ const update = () => (self.label = format(storage));
+ self.hook(storage, "notify::used", update);
+ self.hook(storage, "notify::total", update);
+ update();
+ }}
+ />
+ </box>
+);
+
+export default () => (
+ <PopupWindow name="sideleft">
+ <box vertical className="sideleft">
+ <User />
+ {/* <QuickLaunch /> */}
+ <Locations />
+ <HwResources />
+ </box>
+ </PopupWindow>
+);