From 05dba63eab5a034d7735cf6baa6247b49bf71eef Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Fri, 18 Apr 2025 21:56:14 -0300 Subject: [PATCH] :sparkles: ags(widgets): add entry-popup This new Widget offers a new popup window, with an entry. When user confirms the input, you have a callback with the provided user input. It's pretty useful if you think about it :speaking_head: :boom: --- ags/widget/EntryPopup.ts | 107 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 ags/widget/EntryPopup.ts diff --git a/ags/widget/EntryPopup.ts b/ags/widget/EntryPopup.ts new file mode 100644 index 0000000..8357b35 --- /dev/null +++ b/ags/widget/EntryPopup.ts @@ -0,0 +1,107 @@ +import { Binding } from "astal"; +import { Astal, Gtk, Widget } from "astal/gtk3"; +import { tr } from "../i18n/intl"; +import { Windows } from "../windows"; +import { PopupWindow, PopupWindowProps } from "./PopupWindow"; +import { Separator } from "./Separator"; + +export type EntryPopupProps = { + title?: string | Binding; + text: string | Binding; + cancelText?: string | Binding; + acceptText?: string | Binding; + closeOnAccept?: boolean; + entryPlaceholder?: string | Binding; + onAccept: (userInput: string) => void; + onCancel?: () => void; + onFinish?: () => void; + isPassword?: boolean | Binding; +}; + +export function EntryPopup(props: EntryPopupProps): Widget.Window { + props.closeOnAccept = props.closeOnAccept ?? true; + + const entry = new Widget.Entry({ + className: props.isPassword && "password", + visibility: (props.isPassword instanceof Binding) ? + props.isPassword.as(isPasswd => !isPasswd) + : !props.isPassword, + invisibleChar: 0x00B7, // set 'ยท' as the invisible char + xalign: .5, + placeholderText: props.entryPlaceholder, + onActivate: (self) => { + props.closeOnAccept && window.close(); + entered = true; + props.onAccept(self.text); + self.text = ""; + }, + } as Widget.EntryProps); + + let entered: boolean = false; + const buttons = [ + new Widget.Button({ + className: "cancel", + hexpand: true, + label: props.cancelText ?? tr("cancel"), + onClick: () => props.closeOnAccept && window.close(), + } as Widget.ButtonProps), + new Widget.Button({ + className: "accept", + hexpand: true, + label: props.acceptText ?? tr("accept"), + onClick: () => { + props.closeOnAccept && window.close(); + entered = true; + props.onAccept(entry.text); + entry.text = ""; + } + } as Widget.ButtonProps) + ]; + + const window = Windows.createWindowForFocusedMonitor((mon: number) => PopupWindow({ + namespace: "entry-popup", + className: "entry-popup", + monitor: mon, + cssBackgroundWindow: "background: rgba(0, 0, 0, .3);", + exclusivity: Astal.Exclusivity.IGNORE, + layer: Astal.Layer.OVERLAY, + widthRequest: 400, + heightRequest: 220, + onDestroy: () => { + props.onFinish?.(); + !entered && props.onCancel?.() + }, + child: new Widget.Box({ + className: "entry-popup-box", + orientation: Gtk.Orientation.VERTICAL, + children: [ + new Widget.Label({ + className: "title", + visible: Boolean(props.title), + label: props.title || tr("ask_popup.title") || "Question" + } as Widget.LabelProps), + Separator({ + alpha: .2, + orientation: Gtk.Orientation.VERTICAL + }), + new Widget.Label({ + className: "text", + label: props.text, + yalign: 0, + expand: true + } as Widget.LabelProps), + entry, + new Widget.Box({ + className: "buttons", + orientation: Gtk.Orientation.HORIZONTAL, + hexpand: true, + heightRequest: 38, + homogeneous: true, + children: buttons + } as Widget.BoxProps) + ] + } as Widget.BoxProps) + } as PopupWindowProps))(); + + return window; +}