summaryrefslogtreecommitdiff
path: root/modules/osds.tsx
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-01-16 16:35:37 +1100
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-01-16 16:35:37 +1100
commit02fd2e97f2c8a53bf2344e6fa8b14769cb15ee38 (patch)
tree5e2a56becf6ba6961995e541ce9688224f704773 /modules/osds.tsx
parentpopupwindow: switch to class (diff)
downloadcaelestia-shell-02fd2e97f2c8a53bf2344e6fa8b14769cb15ee38.tar.gz
caelestia-shell-02fd2e97f2c8a53bf2344e6fa8b14769cb15ee38.tar.bz2
caelestia-shell-02fd2e97f2c8a53bf2344e6fa8b14769cb15ee38.zip
refactor: move ts to src
Also move popupwindow to own file
Diffstat (limited to 'modules/osds.tsx')
-rw-r--r--modules/osds.tsx326
1 files changed, 0 insertions, 326 deletions
diff --git a/modules/osds.tsx b/modules/osds.tsx
deleted file mode 100644
index b6e1333..0000000
--- a/modules/osds.tsx
+++ /dev/null
@@ -1,326 +0,0 @@
-import { execAsync, register, timeout, Variable, type Time } from "astal";
-import { App, Astal, Gtk, Widget } from "astal/gtk3";
-import cairo from "cairo";
-import AstalWp from "gi://AstalWp";
-import Cairo from "gi://cairo";
-import Pango from "gi://Pango";
-import PangoCairo from "gi://PangoCairo";
-import { osds as config } from "../config";
-import Monitors, { type Monitor } from "../services/monitors";
-import { PopupWindow } from "../utils/widgets";
-
-const getStyle = (context: Gtk.StyleContext, prop: string) => context.get_property(prop, Gtk.StateFlags.NORMAL);
-const getNumStyle = (context: Gtk.StyleContext, prop: string) => getStyle(context, prop) as number;
-
-const mix = (a: number, b: number, r: number) => a * r + b * (1 - r);
-
-const pangoWeightToStr = (weight: Pango.Weight) => {
- switch (weight) {
- case Pango.Weight.ULTRALIGHT:
- return "UltraLight";
- case Pango.Weight.LIGHT:
- return "Light";
- case Pango.Weight.BOLD:
- return "Bold";
- case Pango.Weight.ULTRABOLD:
- return "UltraBold";
- case Pango.Weight.HEAVY:
- return "Heavy";
- default:
- return "Normal";
- }
-};
-
-const SliderOsd = ({
- fillIcons,
- monitor,
- type,
- windowSetup,
- className = "",
- initValue,
- drawAreaSetup,
-}: {
- fillIcons?: boolean;
- monitor?: Monitor;
- type: "volume" | "brightness";
- windowSetup: (self: Widget.Window, show: () => void) => void;
- className?: string;
- initValue: number;
- drawAreaSetup: (self: Widget.DrawingArea, icon: Variable<string>) => void;
-}) => (
- <PopupWindow
- name={type}
- monitor={monitor?.id}
- keymode={Astal.Keymode.NONE}
- anchor={config[type].position}
- margin={config[type].margin}
- setup={self => {
- let time: Time | null = null;
- const hideAfterTimeout = () => {
- time?.cancel();
- time = timeout(config[type].hideDelay, () => self.hide());
- };
- self.connect("show", hideAfterTimeout);
- windowSetup(self, () => {
- self.show();
- hideAfterTimeout();
- });
- }}
- >
- <box className={type}>
- <drawingarea
- className={`inner ${className}`}
- css={"font-size: " + initValue + "px;"}
- setup={self => {
- const halfPi = Math.PI / 2;
- const vertical =
- config[type].position === Astal.WindowAnchor.LEFT ||
- config[type].position === Astal.WindowAnchor.RIGHT;
-
- const icon = Variable("");
- drawAreaSetup(self, icon);
- self.hook(icon, () => self.queue_draw());
-
- // Init size
- const styleContext = self.get_style_context();
- const width = getNumStyle(styleContext, "min-width");
- const height = getNumStyle(styleContext, "min-height");
- if (vertical) self.set_size_request(height, width);
- else self.set_size_request(width, height);
-
- let fontDesc: Pango.FontDescription | null = null;
-
- self.connect("draw", (_, cr: cairo.Context) => {
- const parent = self.get_parent();
- if (!parent) return;
-
- const styleContext = self.get_style_context();
- const pContext = parent.get_style_context();
-
- let width = getNumStyle(styleContext, "min-width");
- let height = getNumStyle(styleContext, "min-height");
-
- const progressValue = getNumStyle(styleContext, "font-size");
- let radius = getNumStyle(pContext, "border-radius");
- // Flatten when near 0, do before swap cause its simpler
- radius = Math.min(radius, Math.min(width * progressValue, height) / 2);
-
- if (vertical) [width, height] = [height, width]; // Swap if vertical
- self.set_size_request(width, height);
-
- const progressPosition = vertical
- ? height * (1 - progressValue) + radius // Top is 0, but we want it to start from the bottom
- : width * progressValue - radius;
-
- const bg = styleContext.get_background_color(Gtk.StateFlags.NORMAL);
- cr.setSourceRGBA(bg.red, bg.green, bg.blue, bg.alpha);
-
- // Background
- if (vertical) {
- cr.arc(radius, progressPosition, radius, -Math.PI, -halfPi); // Top left
- cr.arc(width - radius, progressPosition, radius, -halfPi, 0); // Top right
- cr.arc(width - radius, height - radius, radius, 0, halfPi); // Bottom right
- } else {
- 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 fg = pContext.get_background_color(Gtk.StateFlags.NORMAL);
- cr.setAntialias(cairo.Antialias.BEST);
-
- // Progress number, at top/right
- let nw = 0;
- let nh = 0;
- if (config[type].showValue) {
- const numLayout = parent.create_pango_layout(String(Math.round(progressValue * 100)));
- [nw, nh] = numLayout.get_pixel_size();
- let diff;
- if (vertical) {
- diff = ((1 - progressValue) * height) / nh;
- cr.moveTo((width - nw) / 2, radius / 2);
- } else {
- diff = ((1 - progressValue) * width) / nw;
- cr.moveTo(width - nw - radius, (height - nh) / 2);
- }
- diff = Math.max(0, Math.min(1, diff));
-
- cr.setSourceRGBA(
- mix(bg.red, fg.red, diff),
- mix(bg.green, fg.green, diff),
- mix(bg.blue, fg.blue, diff),
- mix(bg.alpha, fg.alpha, diff)
- );
-
- PangoCairo.show_layout(cr, numLayout);
- }
-
- // Progress icon, follows progress
- if (fontDesc === null) {
- const weight = pangoWeightToStr(getStyle(pContext, "font-weight") as Pango.Weight);
- const size = getNumStyle(pContext, "font-size") * 1.5;
- fontDesc = Pango.font_description_from_string(
- `Material Symbols Rounded ${weight} ${size}px`
- );
- // Ugh GTK CSS doesn't support font-variations, so you need to manually create the layout and font desc instead of using Gtk.Widget#create_pango_layout
- if (fillIcons) fontDesc.set_variations("FILL=1");
- }
-
- const iconLayout = PangoCairo.create_layout(cr);
- iconLayout.set_font_description(fontDesc);
- iconLayout.set_text(icon.get(), -1);
-
- const [iw, ih] = iconLayout.get_pixel_size();
- let diff;
- if (vertical) {
- diff = (progressValue * height) / ih;
- cr.moveTo(
- (width - iw) / 2,
- Math.max(nh, Math.min(height - ih, progressPosition - ih / 2 + radius))
- );
- } else {
- diff = (progressValue * width) / iw;
- cr.moveTo(
- Math.min(
- width - nw * 1.1 - iw - radius,
- Math.max(0, progressPosition - iw / 2 - radius)
- ),
- (height - ih) / 2
- );
- }
- diff = Math.max(0, Math.min(1, diff));
-
- cr.setSourceRGBA(
- mix(fg.red, bg.red, diff),
- mix(fg.green, bg.green, diff),
- mix(fg.blue, bg.blue, diff),
- mix(fg.alpha, bg.alpha, diff)
- );
-
- PangoCairo.show_layout(cr, iconLayout);
- });
- }}
- />
- </box>
- </PopupWindow>
-);
-
-const Volume = ({ audio }: { audio: AstalWp.Audio }) => (
- <SliderOsd
- fillIcons
- type="volume"
- windowSetup={(self, show) => {
- self.hook(audio.defaultSpeaker, "notify::volume", show);
- self.hook(audio.defaultSpeaker, "notify::mute", show);
- }}
- className={audio.defaultSpeaker.mute ? "mute" : ""}
- initValue={audio.defaultSpeaker.volume}
- drawAreaSetup={(self, icon) => {
- const updateIcon = () => {
- if (/head(phone|set)/i.test(audio.defaultSpeaker.icon)) icon.set("headphones");
- else if (audio.defaultSpeaker.mute) icon.set("no_sound");
- else if (audio.defaultSpeaker.volume === 0) icon.set("volume_mute");
- else if (audio.defaultSpeaker.volume <= 0.5) icon.set("volume_down");
- else icon.set("volume_up");
- };
- updateIcon();
- self.hook(audio.defaultSpeaker, "notify::icon", updateIcon);
- self.hook(audio.defaultSpeaker, "notify::mute", () => {
- updateIcon();
- self.toggleClassName("mute", audio.defaultSpeaker.mute);
- });
- self.hook(audio.defaultSpeaker, "notify::volume", () => {
- updateIcon();
- self.css = `font-size: ${audio.defaultSpeaker.volume}px`;
- });
- }}
- />
-);
-
-const Brightness = ({ monitor }: { monitor: Monitor }) => (
- <SliderOsd
- monitor={monitor}
- type="brightness"
- windowSetup={(self, show) => self.hook(monitor, "notify::brightness", show)}
- initValue={monitor.brightness}
- drawAreaSetup={(self, icon) => {
- const update = () => {
- if (monitor.brightness > 0.66) icon.set("brightness_high");
- else if (monitor.brightness > 0.33) icon.set("brightness_medium");
- else if (monitor.brightness > 0) icon.set("brightness_low");
- else icon.set("brightness_empty");
- self.css = `font-size: ${monitor.brightness}px`;
- };
- self.hook(monitor, "notify::brightness", update);
- update();
- }}
- />
-);
-
-@register()
-class LockOsd extends Widget.Window {
- readonly lockType: "caps" | "num";
-
- #timeout: Time | null = null;
-
- constructor({ type, icon, right }: { type: "caps" | "num"; icon: string; right?: boolean }) {
- super({
- visible: false,
- name: `lock-${type}`,
- application: App,
- namespace: `caelestia-lock-${type}`,
- anchor:
- Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT | Astal.WindowAnchor.BOTTOM | Astal.WindowAnchor.RIGHT,
- exclusivity: Astal.Exclusivity.IGNORE,
- });
-
- this.lockType = type;
- this.#update();
-
- this.add(
- <box vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} className={`lock ${type}`}>
- <label vexpand className="icon" label={icon} />
- <label vexpand className="text" label={type.slice(0, 1).toUpperCase() + type.slice(1) + "lock"} />
- </box>
- );
-
- // Clickthrough
- this.connect("size-allocate", () => this.input_shape_combine_region(new Cairo.Region()));
-
- // Move over when other indicator opens/closes
- this.hook(App, "window-toggled", (_, window) => {
- if (window !== this && window instanceof LockOsd) {
- const child = this.get_child();
- if (!child) return;
- this[right ? "marginLeft" : "marginRight"] = window.visible
- ? child.get_preferred_width()[1] + config.lock.spacing
- : 0;
- }
- });
- }
-
- #update() {
- execAsync(`fish -c 'cat /sys/class/leds/input*::${this.lockType}lock/brightness'`)
- .then(out => (this.get_child() as Widget.Box | null)?.toggleClassName("enabled", out.includes("1")))
- .catch(console.error);
- }
-
- show() {
- super.show();
- this.#update();
- this.#timeout?.cancel();
- this.#timeout = timeout(config.lock[this.lockType].hideDelay, () => this.hide());
- }
-}
-
-export default () => {
- if (AstalWp.get_default()) <Volume audio={AstalWp.get_default()!.audio} />;
- Monitors.get_default().forEach(monitor => <Brightness monitor={monitor} />);
-
- <LockOsd type="caps" icon="keyboard_capslock" />;
- <LockOsd right type="num" icon="filter_1" />;
-
- return null;
-};