diff options
| -rw-r--r-- | app.tsx | 9 | ||||
| -rw-r--r-- | modules/launcher.tsx | 52 | ||||
| -rw-r--r-- | scss/launcher.scss | 8 | ||||
| -rw-r--r-- | scss/widgets.scss | 9 | ||||
| -rw-r--r-- | utils/widgets.tsx | 139 |
5 files changed, 61 insertions, 156 deletions
@@ -5,7 +5,6 @@ import Bar from "./modules/bar"; import Launcher from "./modules/launcher"; import NotifPopups from "./modules/notifpopups"; import Players from "./services/players"; -import { PopupWindow } from "./utils/widgets"; const loadStyleAsync = async () => { if (!GLib.file_test(`${SRC}/scss/scheme/_index.scss`, GLib.FileTest.EXISTS)) @@ -34,13 +33,7 @@ App.start({ else if (request === "media next") Players.get_default().lastPlayer?.next(); else if (request === "media previous") Players.get_default().lastPlayer?.previous(); else if (request === "media stop") Players.get_default().lastPlayer?.stop(); - else if (request.startsWith("toggle")) { - const window = App.get_window(request.slice(7)); - if (window instanceof PopupWindow) window.toggle(); - else App.toggle_window(request.slice(7)); - - log = false; - } else return res("Unknown command: " + request); + else return res("Unknown command: " + request); if (log) console.log(`Request handled: ${request}`); res("OK"); diff --git a/modules/launcher.tsx b/modules/launcher.tsx index 83245ca..b212b85 100644 --- a/modules/launcher.tsx +++ b/modules/launcher.tsx @@ -8,7 +8,7 @@ import { Apps } from "../services/apps"; import Math, { type HistoryItem } from "../services/math"; import { getAppCategoryIcon } from "../utils/icons"; import { launch } from "../utils/system"; -import { PopupWindow, setupCustomTooltip, TransitionType } from "../utils/widgets"; +import { convertPopupWindowProps, setupCustomTooltip } from "../utils/widgets"; type Mode = "apps" | "files" | "math"; @@ -323,11 +323,7 @@ const LauncherContent = ({ showResults: Variable<boolean>; entry: Widget.Entry; }) => ( - <box - vertical - className={bind(mode).as(m => `launcher ${m}`)} - css={bind(AstalHyprland.get_default(), "focusedMonitor").as(m => `margin-top: ${m.height / 4}px;`)} - > + <box vertical className={bind(mode).as(m => `launcher ${m}`)}> <box className="search-bar"> <label className="icon" label="search" /> <SearchEntry entry={entry} /> @@ -351,7 +347,7 @@ const LauncherContent = ({ ); @register() -export default class Launcher extends PopupWindow { +export default class Launcher extends Widget.Window { readonly mode: Variable<Mode>; constructor() { @@ -359,29 +355,33 @@ export default class Launcher extends PopupWindow { const mode = Variable<Mode>("apps"); const showResults = Variable.derive([bind(entry, "textLength"), mode], (t, m) => t > 0 || m !== "apps"); - super({ - name: "launcher", - keymode: Astal.Keymode.EXCLUSIVE, - onKeyPressEvent(_, event) { - const keyval = event.get_keyval()[1]; - // Focus entry on typing - if (!entry.isFocus && keyval >= 32 && keyval <= 126) { - entry.text += String.fromCharCode(keyval); - entry.grab_focus(); - entry.set_position(-1); + super( + convertPopupWindowProps({ + name: "launcher", + anchor: Astal.WindowAnchor.TOP, + keymode: Astal.Keymode.EXCLUSIVE, + onKeyPressEvent(_, event) { + const keyval = event.get_keyval()[1]; + // Focus entry on typing + if (!entry.isFocus && keyval >= 32 && keyval <= 126) { + entry.text += String.fromCharCode(keyval); + entry.grab_focus(); + entry.set_position(-1); - // Consume event, if not consumed it will duplicate character in entry - return true; - } - }, - transitionType: TransitionType.SLIDE_DOWN, - halign: Gtk.Align.CENTER, - valign: Gtk.Align.START, - child: <LauncherContent mode={mode} showResults={showResults} entry={entry} />, - }); + // Consume event, if not consumed it will duplicate character in entry + return true; + } + }, + halign: Gtk.Align.CENTER, + valign: Gtk.Align.START, + child: <LauncherContent mode={mode} showResults={showResults} entry={entry} />, + }) + ); this.mode = mode; + this.connect("show", () => (this.marginTop = AstalHyprland.get_default().focusedMonitor.height / 4)); + // Clear search on hide if not in math mode this.connect("hide", () => mode.get() !== "math" && entry.set_text("")); } diff --git a/scss/launcher.scss b/scss/launcher.scss index 543d799..f0df755 100644 --- a/scss/launcher.scss +++ b/scss/launcher.scss @@ -15,14 +15,6 @@ } } -.launcher-wrapper { - @include lib.ease-in-out; - - &.visible { - @include lib.overshot; - } -} - .launcher { @include lib.rounded(10); @include lib.border(scheme.$overlay0, 0.2); diff --git a/scss/widgets.scss b/scss/widgets.scss index 39ab490..900dd69 100644 --- a/scss/widgets.scss +++ b/scss/widgets.scss @@ -17,6 +17,15 @@ label.icon { @include font.icon; } +window.popup { + transition: opacity 300ms linear; + opacity: 0; + + &.visible { + opacity: 1; + } +} + separator, .separator { @include lib.rounded(2); diff --git a/utils/widgets.tsx b/utils/widgets.tsx index 033a4e2..1065fae 100644 --- a/utils/widgets.tsx +++ b/utils/widgets.tsx @@ -1,5 +1,5 @@ -import { Binding, property, register, timeout } from "astal"; -import { App, Astal, Gdk, Gtk, Widget } from "astal/gtk3"; +import { Binding } from "astal"; +import { App, Astal, Gdk, Widget } from "astal/gtk3"; import AstalHyprland from "gi://AstalHyprland"; export const setupCustomTooltip = (self: any, text: string | Binding<string>) => { @@ -47,118 +47,29 @@ export const setupCustomTooltip = (self: any, text: string | Binding<string>) => export const setupChildClickthrough = (self: any) => self.connect("size-allocate", () => self.get_window()?.set_child_input_shapes()); -export enum TransitionType { - FADE = "", - SLIDE_DOWN = "margin-top: -${height}px; margin-bottom: ${height}px;", - SLIDE_UP = "margin-top: ${height}px; margin-bottom: -${height}px;", - SLIDE_RIGHT = "margin-left: -${width}px; margin-right: ${width}px;", - SLIDE_LEFT = "margin-left: ${width}px; margin-right: -${width}px;", -} +const overrideProp = <T,>( + prop: T | Binding<T | undefined> | undefined, + override: (prop: T | undefined) => T | undefined +) => prop && (prop instanceof Binding ? prop.as(override) : override(prop)); -@register() -export class PopupWindow extends Widget.Window { - readonly transitionType: TransitionType; - readonly transitionInDuration: number; - readonly transitionOutDuration: number; - readonly transitionAmount: number; +export const convertPopupWindowProps = (props: Widget.WindowProps): Widget.WindowProps => ({ + keymode: Astal.Keymode.ON_DEMAND, + exclusivity: Astal.Exclusivity.IGNORE, + ...props, + visible: false, + application: App, + namespace: overrideProp(props.name, n => `caelestia-${n}`), + className: overrideProp(props.className, c => `popup ${c}`), + onKeyPressEvent: (self, event) => { + // Close window on escape + if (event.get_keyval()[1] === Gdk.KEY_Escape) self.hide(); - readonly #content: Widget.Box; - #visible: boolean = false; + return props.onKeyPressEvent?.(self, event); + }, + setup: self => { + self.connect("notify::visible", () => self.toggleClassName("visible", self.visible)); + props.setup?.(self); + }, +}); - @property(Boolean) - get realVisible() { - return this.#visible; - } - - set realVisible(v: boolean) { - if (v) this.show(); - else this.hide(); - } - - constructor( - props: Widget.WindowProps & { - transitionType?: TransitionType; - transitionInDuration?: number; - transitionOutDuration?: number; - transitionAmount?: number; - } - ) { - const { - clickThrough, - child, - halign = Gtk.Align.START, - valign = Gtk.Align.START, - transitionType = TransitionType.FADE, - transitionInDuration = 300, - transitionOutDuration = 200, - transitionAmount = 0.2, - ...sProps - } = props; - - sProps.visible = false; - sProps.application = App; - sProps.namespace = `caelestia-${props.name}`; - sProps.anchor = - Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT | Astal.WindowAnchor.BOTTOM | Astal.WindowAnchor.RIGHT; - sProps.exclusivity = Astal.Exclusivity.IGNORE; - if (!sProps.keymode) sProps.keymode = Astal.Keymode.ON_DEMAND; - sProps.onKeyPressEvent = (self, event) => { - // Close window on escape - if (event.get_keyval()[1] === Gdk.KEY_Escape) self.hide(); - - return props.onKeyPressEvent?.(self, event); - }; - super(sProps); - - this.transitionType = transitionType; - this.transitionInDuration = transitionInDuration; - this.transitionOutDuration = transitionOutDuration; - this.transitionAmount = transitionAmount; - - // Wrapper box for animations - this.#content = ( - <box halign={halign} valign={valign} className={`${props.name}-wrapper`}> - {clickThrough ? <eventbox>{child}</eventbox> : child} - </box> - ) as Widget.Box; - this.#content.css = this.#getTransitionCss(false); - this.add(this.#content); - - if (clickThrough) setupChildClickthrough(this); - } - - #getTransitionCss(visible: boolean) { - return ( - `transition-duration: ${visible ? this.transitionInDuration : this.transitionOutDuration}ms;` + - (visible - ? "opacity: 1;" + this.transitionType.replaceAll("${width}", "0").replaceAll("${height}", "0") - : "opacity: 0;" + - this.transitionType - .replaceAll("${width}", String(this.#content.get_preferred_width()[1] * this.transitionAmount)) - .replaceAll("${height}", String(this.#content.get_preferred_height()[1] * this.transitionAmount))) - ); - } - - show() { - this.#visible = true; - this.notify("real-visible"); - - super.show(); - this.#content.toggleClassName("visible", true); - this.#content.css = this.#getTransitionCss(true); - } - - hide() { - this.#visible = false; - this.notify("real-visible"); - - this.#content.toggleClassName("visible", false); - this.#content.css = this.#getTransitionCss(false); - timeout(this.transitionOutDuration, () => !this.#visible && super.hide()); - } - - toggle() { - if (this.#visible) this.hide(); - else this.show(); - } -} +export const PopupWindow = (props: Widget.WindowProps) => <window {...convertPopupWindowProps(props)} />; |