✨ chore: migrate widgets to ags v3 and gtk4
This commit is contained in:
@@ -1,46 +0,0 @@
|
||||
import { Binding } from "astal";
|
||||
import { Widget } from "astal/gtk3";
|
||||
import { tr } from "../i18n/intl";
|
||||
import { CustomDialog, CustomDialogProps } from "./CustomDialog";
|
||||
|
||||
|
||||
export type AskPopupProps = {
|
||||
title?: string | Binding<string | undefined>;
|
||||
text: string | Binding<string | undefined>;
|
||||
cancelText?: string;
|
||||
acceptText?: string;
|
||||
onAccept?: () => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* A Popup Widget that asks yes or no to a defined promt.
|
||||
* Runs onAccept() when user accepts, or else onDecline() when
|
||||
* user doesn't accept / closes window.
|
||||
* This window isn't usually registered in this shell windowing
|
||||
* system.
|
||||
*/
|
||||
export function AskPopup(props: AskPopupProps): Widget.Window {
|
||||
let accepted: boolean = false;
|
||||
|
||||
const window = CustomDialog({
|
||||
namespace: "ask-popup",
|
||||
widthRequest: 400,
|
||||
heightRequest: 250,
|
||||
title: props.title ?? tr("ask_popup.title"),
|
||||
text: props.text,
|
||||
onFinish: () => !accepted && props.onCancel?.(),
|
||||
options: [
|
||||
{ text: props.cancelText ?? tr("cancel") },
|
||||
{
|
||||
text: props.acceptText ?? tr("accept"),
|
||||
onClick: () => {
|
||||
accepted = true;
|
||||
props.onAccept?.();
|
||||
}
|
||||
}
|
||||
]
|
||||
} as CustomDialogProps);
|
||||
|
||||
return window;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Accessor } from "ags";
|
||||
import { tr } from "../i18n/intl";
|
||||
import { CustomDialog } from "./CustomDialog";
|
||||
import { Astal } from "ags/gtk4";
|
||||
|
||||
|
||||
export type AskPopupProps = {
|
||||
title?: string | Accessor<string>;
|
||||
text: string | Accessor<string>;
|
||||
cancelText?: string;
|
||||
acceptText?: string;
|
||||
onAccept?: () => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* A Popup Widget that asks yes or no to a defined promt.
|
||||
* Runs onAccept() when user accepts, or else onDecline() when
|
||||
* user doesn't accept / closes window.
|
||||
* This window isn't usually registered in this shell windowing
|
||||
* system.
|
||||
*/
|
||||
export function AskPopup(props: AskPopupProps): Astal.Window {
|
||||
let accepted: boolean = false;
|
||||
|
||||
return <CustomDialog
|
||||
namespace={"ask-popup"}
|
||||
widthRequest={400}
|
||||
heightRequest={250}
|
||||
title={props.title ?? tr("ask_popup.title")}
|
||||
text={props.text}
|
||||
onFinish={() => !accepted && props.onCancel?.()}
|
||||
options={[
|
||||
{ text: props.cancelText ?? tr("cancel") },
|
||||
{
|
||||
text: props.acceptText ?? tr("accept"),
|
||||
onClick: () => {
|
||||
accepted = true;
|
||||
props.onAccept?.();
|
||||
}
|
||||
}
|
||||
]} /> as Astal.Window;
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { Binding } from "astal";
|
||||
import { Astal, Gdk, Widget } from "astal/gtk3";
|
||||
|
||||
|
||||
const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor;
|
||||
|
||||
export type BackgroundWindowProps = {
|
||||
/** GtkWindow Layer */
|
||||
layer?: Astal.Layer | Binding<Astal.Layer | undefined>;
|
||||
/** Monitor number where the window should open */
|
||||
monitor: number | Binding<number | undefined>;
|
||||
/** Custom stylesheet used in the window. default: `background: rgba(0, 0, 0, .2)` */
|
||||
css?: string | Binding<string | undefined>;
|
||||
/** Function that is called when the user triggers a mouse-click or escape action on the window */
|
||||
onAction?: (window: Widget.Window) => void;
|
||||
/** Function that is called when the user clicks on the window with primary mouse button */
|
||||
onClickPrimary?: (window: Widget.Window) => void;
|
||||
/** Function that is called when the user clicks on the window with secodary mouse button */
|
||||
onClickSecondary?: (window: Widget.Window) => void;
|
||||
keymode?: Astal.Keymode;
|
||||
exclusivity?: Astal.Exclusivity;
|
||||
};
|
||||
|
||||
/** Creates a fullscreen GtkWindow that is used for making
|
||||
* the user focus on the content after this window(e.g.: AskPopup,
|
||||
* Authentication Window(futurely) or any PopupWindow)
|
||||
*
|
||||
* @param props Properties for background-window
|
||||
*
|
||||
* @returns The generated background window
|
||||
*/
|
||||
export function BackgroundWindow(props: BackgroundWindowProps) {
|
||||
return new Widget.Window({
|
||||
namespace: "background-window",
|
||||
css: props.css ?? "background: rgba(0, 0, 0, .2);",
|
||||
monitor: props.monitor,
|
||||
layer: props.layer ?? Astal.Layer.OVERLAY,
|
||||
anchor: TOP | LEFT | BOTTOM | RIGHT,
|
||||
keymode: props.keymode,
|
||||
exclusivity: props.exclusivity ?? Astal.Exclusivity.IGNORE,
|
||||
onKeyPressEvent: (self, event: Gdk.Event) => {
|
||||
event.get_keyval()[1] === Gdk.KEY_Escape &&
|
||||
props.onAction?.(self);
|
||||
},
|
||||
onButtonPressEvent: (self, event: Gdk.Event) => {
|
||||
if(event.get_button()[1]) {
|
||||
props.onAction?.(self);
|
||||
return;
|
||||
}
|
||||
|
||||
if(event.get_button()[1] === Gdk.BUTTON_PRIMARY) {
|
||||
props.onClickPrimary?.(self);
|
||||
return;
|
||||
}
|
||||
|
||||
if(event.get_button()[1] === Gdk.BUTTON_SECONDARY)
|
||||
props.onClickSecondary?.(self);
|
||||
}
|
||||
} as Widget.WindowProps);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Accessor } from "ags";
|
||||
import { Astal, Gdk, Gtk } from "ags/gtk4";
|
||||
import GObject from "gi://GObject?version=2.0";
|
||||
|
||||
|
||||
const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor;
|
||||
|
||||
export type BackgroundWindowProps = {
|
||||
/** GtkWindow Layer */
|
||||
layer?: Astal.Layer | Accessor<Astal.Layer>;
|
||||
/** Monitor number where the window should open */
|
||||
monitor: number | Accessor<number>;
|
||||
/** Custom stylesheet used in the window. default: `background: rgba(0, 0, 0, .2)` */
|
||||
css?: string | Accessor<string>;
|
||||
/* Function that is called when the user releases a key in the keyboard on the window
|
||||
* The `Escape` key is not passed to this function */
|
||||
actionKeyPressed?: (window: Astal.Window, keyval: number, keycode: number) => void;
|
||||
/** Function that is called when the user triggers a mouse-click or escape action on the window */
|
||||
actionFired?: (window: Astal.Window) => void;
|
||||
/** Function that is called when the user clicks on the window with primary mouse button */
|
||||
actionClickPrimary?: (window: Astal.Window) => void;
|
||||
/** Function that is called when the user clicks on the window with secodary mouse button */
|
||||
actionClickSecondary?: (window: Astal.Window) => void;
|
||||
keymode?: Astal.Keymode;
|
||||
exclusivity?: Astal.Exclusivity;
|
||||
};
|
||||
|
||||
/** Creates a fullscreen GtkWindow that is used for making
|
||||
* the user focus on the content after this window(e.g.: AskPopup,
|
||||
* Authentication Window(futurely) or any PopupWindow)
|
||||
*
|
||||
* @param props Properties for background-window
|
||||
*
|
||||
* @returns The generated background window
|
||||
*/
|
||||
export function BackgroundWindow(props: BackgroundWindowProps): Astal.Window {
|
||||
const conns: Map<GObject.Object, number> = new Map();
|
||||
|
||||
return <Astal.Window namespace={"background-window"}
|
||||
css={props.css ?? "background: rgba(0, 0, 0, .2);"}
|
||||
monitor={props.monitor}
|
||||
layer={props.layer ?? Astal.Layer.OVERLAY}
|
||||
anchor={TOP | LEFT | BOTTOM | RIGHT}
|
||||
keymode={props.keymode}
|
||||
exclusivity={props.exclusivity ?? Astal.Exclusivity.IGNORE}
|
||||
onDestroy={(_) => conns.forEach((id, obj) => obj.disconnect(id))}
|
||||
$={(self) => {
|
||||
const gestureClick = Gtk.GestureClick.new(),
|
||||
eventControllerKey = Gtk.EventControllerKey.new();
|
||||
|
||||
self.add_controller(gestureClick);
|
||||
self.add_controller(eventControllerKey);
|
||||
|
||||
conns.set(eventControllerKey, eventControllerKey.connect("key-released",
|
||||
(_, keyval, keycode) => {
|
||||
if(keyval === Gdk.KEY_Escape) {
|
||||
props.actionFired?.(self);
|
||||
return;
|
||||
}
|
||||
|
||||
props.actionKeyPressed?.(self, keyval, keycode);
|
||||
}
|
||||
));
|
||||
|
||||
conns.set(gestureClick, gestureClick.connect("released", (gesture) => {
|
||||
if(gesture.get_current_button() === Gdk.BUTTON_PRIMARY) {
|
||||
props.actionClickPrimary?.(self);
|
||||
return;
|
||||
}
|
||||
|
||||
if(gesture.get_current_button() === Gdk.BUTTON_SECONDARY) {
|
||||
props.actionClickSecondary?.(self);
|
||||
return;
|
||||
}
|
||||
|
||||
props.actionFired?.(self);
|
||||
}));
|
||||
}}
|
||||
/> as Astal.Window;
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
import { Binding } from "astal";
|
||||
import { Astal, Gtk, Widget } from "astal/gtk3";
|
||||
import { Windows } from "../windows";
|
||||
import { PopupWindow, PopupWindowProps } from "./PopupWindow";
|
||||
import { Separator } from "./Separator";
|
||||
import { tr } from "../i18n/intl";
|
||||
|
||||
export type CustomDialogProps = {
|
||||
namespace?: string | Binding<string>;
|
||||
className?: string | Binding<string>;
|
||||
cssBackground?: string;
|
||||
title?: string | Binding<string>;
|
||||
text?: string | Binding<string>;
|
||||
heightRequest?: number | Binding<number>;
|
||||
widthRequest?: number | Binding<number>;
|
||||
childOrientation?: Gtk.Orientation | Binding<Gtk.Orientation>;
|
||||
children?: Array<Gtk.Widget> | Binding<Array<Gtk.Widget>>;
|
||||
child?: Gtk.Widget | Binding<Gtk.Widget>;
|
||||
onFinish?: () => void;
|
||||
options?: Array<CustomDialogOption>;
|
||||
optionsOrientation?: Gtk.Orientation | Binding<Gtk.Orientation>;
|
||||
};
|
||||
|
||||
export interface CustomDialogOption {
|
||||
onClick?: () => void;
|
||||
text: string | Binding<string>;
|
||||
closeOnClick?: boolean | Binding<boolean>;
|
||||
}
|
||||
|
||||
export function CustomDialog(props: CustomDialogProps = {
|
||||
options: [{ text: tr("accept") }]
|
||||
}): Widget.Window {
|
||||
const window = Windows.createWindowForFocusedMonitor((mon: number) => PopupWindow({
|
||||
namespace: props.namespace ?? "custom-dialog",
|
||||
monitor: mon,
|
||||
cssBackgroundWindow: props.cssBackground ?? "background: rgba(0, 0, 0, .3);",
|
||||
exclusivity: Astal.Exclusivity.IGNORE,
|
||||
layer: Astal.Layer.OVERLAY,
|
||||
halign: Gtk.Align.CENTER,
|
||||
valign: Gtk.Align.CENTER,
|
||||
widthRequest: props.widthRequest ?? 400,
|
||||
heightRequest: props.heightRequest ?? 220,
|
||||
onDestroy: props.onFinish,
|
||||
child: new Widget.Box({
|
||||
className: props.className ?? "custom-dialog-container",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
children: [
|
||||
new Widget.Label({
|
||||
className: "title",
|
||||
visible: props.title,
|
||||
label: props.title
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Label({
|
||||
className: "text",
|
||||
visible: props.text,
|
||||
label: props.text,
|
||||
yalign: 0,
|
||||
expand: true
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Box({
|
||||
className: "custom-children custom-child",
|
||||
visible: props.children || props.child,
|
||||
orientation: props.childOrientation ?? Gtk.Orientation.VERTICAL,
|
||||
children: props.children,
|
||||
child: props.child
|
||||
} as Widget.BoxProps),
|
||||
Separator({
|
||||
alpha: .2,
|
||||
visible: props.options && props.options.length > 0,
|
||||
spacing: 8,
|
||||
orientation: Gtk.Orientation.VERTICAL
|
||||
}),
|
||||
new Widget.Box({
|
||||
className: "options",
|
||||
orientation: props.optionsOrientation ?? Gtk.Orientation.HORIZONTAL,
|
||||
hexpand: true,
|
||||
heightRequest: 38,
|
||||
homogeneous: true,
|
||||
children: props.options && props.options.map(option => {
|
||||
const onClick = () => {
|
||||
option.onClick?.();
|
||||
(option.closeOnClick ?? true) &&
|
||||
window.close();
|
||||
};
|
||||
const connections: Array<number> = [];
|
||||
const btn = new Widget.Button({
|
||||
className: "option",
|
||||
label: option.text,
|
||||
hexpand: true,
|
||||
setup: (self) => {
|
||||
connections.push(
|
||||
self.connect("click-release", (_, event: Astal.ClickEvent) =>
|
||||
event.button === Astal.MouseButton.PRIMARY &&
|
||||
onClick()),
|
||||
self.connect("activate", (_) => onClick())
|
||||
);
|
||||
},
|
||||
onDestroy: (self) => connections.map(id => self.disconnect(id))
|
||||
} as Widget.ButtonProps);
|
||||
|
||||
return btn;
|
||||
})
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
} as PopupWindowProps))();
|
||||
|
||||
return window;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Astal, Gtk } from "ags/gtk4";
|
||||
import { Windows } from "../windows";
|
||||
import { PopupWindow } from "./PopupWindow";
|
||||
import { Separator } from "./Separator";
|
||||
import { tr } from "../i18n/intl";
|
||||
import { Accessor, For, With } from "ags";
|
||||
import { variableToBoolean, WidgetNodeType } from "../scripts/utils";
|
||||
|
||||
|
||||
export type CustomDialogProps = {
|
||||
namespace?: string | Accessor<string>;
|
||||
className?: string | Accessor<string>;
|
||||
cssBackground?: string;
|
||||
title?: string | Accessor<string>;
|
||||
text?: string | Accessor<string>;
|
||||
heightRequest?: number | Accessor<number>;
|
||||
widthRequest?: number | Accessor<number>;
|
||||
childOrientation?: Gtk.Orientation | Accessor<Gtk.Orientation>;
|
||||
children?: WidgetNodeType;
|
||||
onFinish?: () => void;
|
||||
options?: Array<CustomDialogOption> | Accessor<Array<CustomDialogOption>>;
|
||||
optionsOrientation?: Gtk.Orientation | Accessor<Gtk.Orientation>;
|
||||
};
|
||||
|
||||
export interface CustomDialogOption {
|
||||
onClick?: () => void;
|
||||
text: string | Accessor<string>;
|
||||
closeOnClick?: boolean | Accessor<boolean>;
|
||||
}
|
||||
|
||||
function CustomDialogOption(props: CustomDialogOption & { dialog: Astal.Window }) {
|
||||
function onClicked() {
|
||||
props.onClick?.();
|
||||
props.closeOnClick && props.dialog.close();
|
||||
}
|
||||
|
||||
return <Gtk.Button class="option" hexpand={true} label={props.text}
|
||||
onClicked={onClicked} onActivate={onClicked}/>
|
||||
}
|
||||
|
||||
export function CustomDialog({ options = [{ text: tr("accept") }], ...props}: CustomDialogProps) {
|
||||
let dialog: Astal.Window;
|
||||
return Windows.getDefault().createWindowForFocusedMonitor((mon: number) =>
|
||||
<PopupWindow namespace={props.namespace ?? "custom-dialog"} monitor={mon}
|
||||
cssBackgroundWindow={props.cssBackground ?? "background: rgba(0, 0, 0, .3);"}
|
||||
exclusivity={Astal.Exclusivity.IGNORE} layer={Astal.Layer.OVERLAY}
|
||||
halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}
|
||||
widthRequest={props.widthRequest ?? 400} heightRequest={props.heightRequest ?? 220}
|
||||
onDestroy={props.onFinish} $={(self) => dialog = self}>
|
||||
|
||||
<Gtk.Box class={props.className ?? "custom-dialog-container"}
|
||||
orientation={Gtk.Orientation.VERTICAL}>
|
||||
<Gtk.Label class={"title"} visible={variableToBoolean(props.title)} label={props.title} />
|
||||
<Gtk.Label class={"text"} visible={variableToBoolean(props.text)} label={props.text} />
|
||||
<Gtk.Box class={"custom-children custom-child"} visible={variableToBoolean(props.children)}
|
||||
orientation={props.childOrientation ?? Gtk.Orientation.VERTICAL}>
|
||||
|
||||
{
|
||||
(props.children instanceof Accessor) ?
|
||||
(Array.isArray(props.children) ?
|
||||
<For each={props.children! as Accessor<Array<JSX.Element>>}>
|
||||
{(widget) => widget && widget}
|
||||
</For>
|
||||
: <With value={props.children as Accessor<JSX.Element>}>
|
||||
{(widget) => widget && widget}
|
||||
</With>)
|
||||
: (Array.isArray(props.children) ?
|
||||
props.children.map(widget => widget && widget).filter(w => w)
|
||||
: props.children)
|
||||
}
|
||||
</Gtk.Box>
|
||||
<Separator alpha={.2} visible={options && options.length > 0}
|
||||
spacing={8} orientation={Gtk.Orientation.VERTICAL} />
|
||||
|
||||
{(<Gtk.Box class={"options"} orientation={props.optionsOrientation ?? Gtk.Orientation.HORIZONTAL}
|
||||
hexpand={true} heightRequest={38} homogeneous={true}>
|
||||
{
|
||||
(options instanceof Accessor) ?
|
||||
<For each={options}>
|
||||
{(option) => <CustomDialogOption {...option} dialog={dialog} />}
|
||||
</For>
|
||||
: options.map(option =>
|
||||
<CustomDialogOption {...option} dialog={dialog} />)
|
||||
}
|
||||
</Gtk.Box>)}
|
||||
</Gtk.Box>
|
||||
</PopupWindow>)();
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import { Binding } from "astal";
|
||||
import { Widget } from "astal/gtk3";
|
||||
import { tr } from "../i18n/intl";
|
||||
import { CustomDialog, CustomDialogProps } from "./CustomDialog";
|
||||
|
||||
export type EntryPopupProps = {
|
||||
title: string | Binding<string>;
|
||||
text?: string | Binding<string>;
|
||||
cancelText?: string | Binding<string>;
|
||||
acceptText?: string | Binding<string>;
|
||||
closeOnAccept?: boolean;
|
||||
entryPlaceholder?: string | Binding<string>;
|
||||
onAccept: (userInput: string) => void;
|
||||
onCancel?: () => void;
|
||||
onFinish?: () => void;
|
||||
isPassword?: boolean | Binding<string>;
|
||||
};
|
||||
|
||||
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 window = CustomDialog({
|
||||
namespace: "entry-popup",
|
||||
widthRequest: 420,
|
||||
heightRequest: 220,
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
child: entry,
|
||||
options: [
|
||||
{
|
||||
text: props.cancelText ?? tr("cancel"),
|
||||
onClick: props.onCancel
|
||||
},
|
||||
{
|
||||
text: props.acceptText ?? tr("accept"),
|
||||
closeOnClick: props.closeOnAccept,
|
||||
onClick: () => {
|
||||
entered = true;
|
||||
props.onAccept(entry.text);
|
||||
entry.text = "";
|
||||
}
|
||||
}
|
||||
],
|
||||
onFinish: () => {
|
||||
!entered && props.onCancel?.()
|
||||
props.onFinish?.();
|
||||
}
|
||||
} as CustomDialogProps);
|
||||
|
||||
return window;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Accessor } from "ags";
|
||||
import { tr } from "../i18n/intl";
|
||||
import { CustomDialog } from "./CustomDialog";
|
||||
import { Astal, Gtk } from "ags/gtk4";
|
||||
|
||||
export type EntryPopupProps = {
|
||||
title: string | Accessor<string>;
|
||||
text?: string | Accessor<string>;
|
||||
cancelText?: string | Accessor<string>;
|
||||
acceptText?: string | Accessor<string>;
|
||||
closeOnAccept?: boolean;
|
||||
entryPlaceholder?: string | Accessor<string>;
|
||||
onAccept: (userInput: string) => void;
|
||||
onCancel?: () => void;
|
||||
onFinish?: () => void;
|
||||
isPassword?: boolean | Accessor<string>;
|
||||
};
|
||||
|
||||
export function EntryPopup(props: EntryPopupProps): Astal.Window {
|
||||
props.closeOnAccept = props.closeOnAccept ?? true;
|
||||
let entered: boolean = false;
|
||||
|
||||
function onActivate(entry: Gtk.Entry|Gtk.PasswordEntry) {
|
||||
props.closeOnAccept && window.close();
|
||||
entered = true;
|
||||
props.onAccept(entry.text);
|
||||
entry.text = "";
|
||||
}
|
||||
|
||||
const entry = props.isPassword ?
|
||||
<Gtk.PasswordEntry class={"password"} xalign={.5}
|
||||
placeholderText={props.entryPlaceholder}
|
||||
onActivate={onActivate}
|
||||
/> as Gtk.PasswordEntry
|
||||
: <Gtk.Entry xalign={.5} placeholderText={props.entryPlaceholder}
|
||||
onActivate={onActivate} /> as Gtk.Entry;
|
||||
|
||||
const window = <CustomDialog namespace={"entry-popup"} widthRequest={420}
|
||||
heightRequest={220} title={props.title} text={props.text}
|
||||
options={[
|
||||
{
|
||||
text: props.cancelText ?? tr("cancel"),
|
||||
onClick: props.onCancel
|
||||
},
|
||||
{
|
||||
text: props.acceptText ?? tr("accept"),
|
||||
closeOnClick: props.closeOnAccept,
|
||||
onClick: () => {
|
||||
entered = true;
|
||||
props.onAccept(entry.text);
|
||||
entry.text = "";
|
||||
}
|
||||
}
|
||||
]} onFinish={() => {
|
||||
!entered && props.onCancel?.()
|
||||
props.onFinish?.();
|
||||
}}
|
||||
/> as Astal.Window;
|
||||
|
||||
return window;
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
import { Astal, Gtk, Widget } from "astal/gtk3";
|
||||
import AstalNotifd from "gi://AstalNotifd";
|
||||
import { Separator } from "./Separator";
|
||||
import { HistoryNotification, Notifications } from "../scripts/notifications";
|
||||
import { GLib } from "astal";
|
||||
import { getAppIcon } from "../scripts/apps";
|
||||
import Pango from "gi://Pango";
|
||||
|
||||
|
||||
function getNotificationImage(notif: AstalNotifd.Notification|HistoryNotification): (string|undefined) {
|
||||
const img = notif.image || notif.appIcon;
|
||||
|
||||
if(!img || !img.includes('/'))
|
||||
return undefined;
|
||||
|
||||
switch(true) {
|
||||
case /^[/]/.test(img):
|
||||
return `file://${img}`;
|
||||
|
||||
case /^[~]/.test(img):
|
||||
case /^file:\/\/[~]/i.test(img):
|
||||
return `file://${GLib.get_home_dir()}/${img.replace(/^(file\:\/\/|[~]|file\:\/\[~])/i, "")}`;
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
export function NotificationWidget(notification: AstalNotifd.Notification|number|HistoryNotification,
|
||||
onClose?: (notif: AstalNotifd.Notification|HistoryNotification) => void,
|
||||
showTime?: boolean /* It's showTime :speaking_head: :boom: :bangbang: */,
|
||||
holdOnHover?: boolean): Gtk.Widget {
|
||||
|
||||
notification = (typeof notification === "number") ?
|
||||
AstalNotifd.get_default().get_notification(notification)
|
||||
: notification;
|
||||
|
||||
return new Widget.EventBox({
|
||||
onClick: () => {
|
||||
if(notification instanceof AstalNotifd.Notification) {
|
||||
const viewAction = notification.actions.filter(action =>
|
||||
action.label.toLowerCase() === "view")?.[0];
|
||||
|
||||
viewAction && notification.invoke(viewAction.id);
|
||||
}
|
||||
|
||||
onClose?.(notification);
|
||||
},
|
||||
onHover: () => holdOnHover && Notifications.getDefault().holdNotification(notification.id),
|
||||
onHoverLost: () => holdOnHover && onClose?.(notification),
|
||||
hexpand: true,
|
||||
vexpand: false,
|
||||
child: new Widget.Box({
|
||||
className: `notification ${ (notification instanceof AstalNotifd.Notification) ?
|
||||
Notifications.getDefault().getUrgencyString(notification.urgency) : "" }`,
|
||||
homogeneous: false,
|
||||
expand: true,
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
spacing: 5,
|
||||
children: [
|
||||
new Widget.Box({
|
||||
className: "top",
|
||||
orientation: Gtk.Orientation.HORIZONTAL,
|
||||
hexpand: true,
|
||||
vexpand: false,
|
||||
children: [
|
||||
new Widget.Icon({
|
||||
className: "icon app-icon",
|
||||
icon: notification.appIcon && Astal.Icon.lookup_icon(notification.appIcon) ?
|
||||
notification.appIcon
|
||||
: getAppIcon(notification.appName),
|
||||
setup: (self) => self.set_visible(Boolean(self.get_icon())),
|
||||
halign: Gtk.Align.START,
|
||||
css: "font-size: 16px;"
|
||||
}),
|
||||
new Widget.Label({
|
||||
className: "app-name",
|
||||
halign: Gtk.Align.START,
|
||||
hexpand: true,
|
||||
label: notification.appName || "Unknown Application"
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Box({
|
||||
halign: Gtk.Align.END,
|
||||
children: [
|
||||
new Widget.Label({
|
||||
xalign: 1,
|
||||
visible: !showTime ? false : true,
|
||||
className: "time",
|
||||
label: GLib.DateTime.new_from_unix_local(notification.time).format("%H:%M"),
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Button({
|
||||
className: "close",
|
||||
onClick: () => onClose && onClose(notification),
|
||||
image: new Widget.Icon({
|
||||
className: "close icon",
|
||||
icon: "window-close-symbolic"
|
||||
} as Widget.IconProps)
|
||||
} as Widget.ButtonProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps),
|
||||
Separator({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
alpha: 10
|
||||
}),
|
||||
new Widget.Box({
|
||||
className: "content",
|
||||
orientation: Gtk.Orientation.HORIZONTAL,
|
||||
children: [
|
||||
new Widget.Box({
|
||||
className: "image",
|
||||
setup: (box) => {
|
||||
const img = getNotificationImage(notification);
|
||||
|
||||
box.set_visible(Boolean(img));
|
||||
img && box.set_css(`background-image: image(url("${img}"))`);
|
||||
}
|
||||
} as Widget.BoxProps),
|
||||
new Widget.Box({
|
||||
className: "text",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
expand: true,
|
||||
children: [
|
||||
new Widget.Label({
|
||||
className: "summary",
|
||||
useMarkup: true,
|
||||
xalign: 0,
|
||||
truncate: true,
|
||||
label: notification.summary.replace(/\&/g, "&")
|
||||
}),
|
||||
new Widget.Label({
|
||||
className: "body",
|
||||
useMarkup: true,
|
||||
halign: Gtk.Align.START,
|
||||
xalign: 0,
|
||||
truncate: false,
|
||||
wrap: true,
|
||||
singleLineMode: false,
|
||||
wrapMode: Pango.WrapMode.WORD_CHAR,
|
||||
label: notification.body.replace(/&/g, "&")
|
||||
} as Widget.LabelProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps),
|
||||
new Widget.Box({
|
||||
className: "actions button-row",
|
||||
hexpand: true,
|
||||
visible: (notification instanceof AstalNotifd.Notification) ?
|
||||
(notification.actions.filter(action => action.label.toLowerCase() !== "view").length > 0)
|
||||
: false,
|
||||
children: (notification instanceof AstalNotifd.Notification) ?
|
||||
notification.actions.filter(action => action.label.toLowerCase() !== "view")
|
||||
.map((action: AstalNotifd.Action) =>
|
||||
new Widget.Button({
|
||||
className: "action",
|
||||
label: action.label,
|
||||
hexpand: true,
|
||||
onClicked: () => {
|
||||
notification.invoke(action.id);
|
||||
onClose && onClose(notification);
|
||||
}
|
||||
} as Widget.ButtonProps)
|
||||
)
|
||||
: []
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps),
|
||||
} as Widget.EventBoxProps);
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import { Gdk, Gtk } from "ags/gtk4";
|
||||
import { Separator } from "./Separator";
|
||||
import { HistoryNotification, Notifications } from "../scripts/notifications";
|
||||
import { getAppIcon, getSymbolicIcon } from "../scripts/apps";
|
||||
|
||||
import AstalNotifd from "gi://AstalNotifd";
|
||||
import Pango from "gi://Pango?version=1.0";
|
||||
import GLib from "gi://GLib?version=2.0";
|
||||
import GObject from "gi://GObject?version=2.0";
|
||||
|
||||
function getNotificationImage(notif: AstalNotifd.Notification|HistoryNotification): (string|undefined) {
|
||||
const img = notif.image || notif.appIcon;
|
||||
|
||||
if(!img || !img.includes('/'))
|
||||
return undefined;
|
||||
|
||||
switch(true) {
|
||||
case /^[/]/.test(img):
|
||||
return `file://${img}`;
|
||||
|
||||
case /^[~]/.test(img):
|
||||
case /^file:\/\/[~]/i.test(img):
|
||||
return `file://${GLib.get_home_dir()}/${img.replace(/^(file\:\/\/|[~]|file\:\/\[~])/i, "")}`;
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
export function NotificationWidget({ notification, actionClicked, holdOnHover, showTime, actionClose }: {
|
||||
notification: AstalNotifd.Notification|number|HistoryNotification;
|
||||
actionClicked?: (notif: AstalNotifd.Notification|HistoryNotification) => void;
|
||||
actionClose?: (notif: AstalNotifd.Notification|HistoryNotification) => void;
|
||||
holdOnHover?: boolean;
|
||||
showTime?: boolean; // It's showTime :speaking_head: :boom: :bangbang:
|
||||
}): Gtk.Widget {
|
||||
|
||||
notification = (typeof notification === "number") ?
|
||||
AstalNotifd.get_default().get_notification(notification)
|
||||
: notification;
|
||||
|
||||
const conns: Map<GObject.Object, Array<number>> = new Map();
|
||||
|
||||
return <Gtk.Box hexpand={true} vexpand={true} class={`notification ${
|
||||
Notifications.getDefault().getUrgencyString(notification.urgency)
|
||||
}`} orientation={Gtk.Orientation.VERTICAL} spacing={5}
|
||||
$={(self) => {
|
||||
const eventControllerMotion = Gtk.EventControllerMotion.new(),
|
||||
gestureClick = Gtk.GestureClick.new();
|
||||
|
||||
self.add_controller(eventControllerMotion);
|
||||
self.add_controller(gestureClick);
|
||||
|
||||
conns.set(eventControllerMotion, [
|
||||
eventControllerMotion.connect("enter", () =>
|
||||
holdOnHover && Notifications.getDefault().holdNotification(notification.id)),
|
||||
eventControllerMotion.connect("leave", () =>
|
||||
holdOnHover && Notifications.getDefault().removeNotification(notification.id))
|
||||
]);
|
||||
|
||||
conns.set(gestureClick, [
|
||||
gestureClick.connect("released", (gesture) => {
|
||||
gesture.get_current_button() === Gdk.BUTTON_PRIMARY &&
|
||||
actionClicked?.(notification);
|
||||
})
|
||||
]);
|
||||
}} onDestroy={(_) => {
|
||||
conns.forEach((ids, obj) => ids.forEach(id => obj.disconnect(id)));
|
||||
}}>
|
||||
|
||||
<Gtk.Box class={"top"} hexpand={true}>
|
||||
<Gtk.Image css={"font-size: 16px;"} $={(self) => {
|
||||
const icon = getSymbolicIcon(notification.appIcon ?? notification.appName) ??
|
||||
getSymbolicIcon(notification.appName) ?? getAppIcon(notification.appName);
|
||||
|
||||
if(icon) {
|
||||
self.set_from_icon_name(icon);
|
||||
return;
|
||||
}
|
||||
|
||||
self.set_visible(false);
|
||||
}} />
|
||||
<Gtk.Label class={"app-name"} halign={Gtk.Align.START} hexpand={true}
|
||||
label={notification.appName || "Application"} />
|
||||
|
||||
<Gtk.Label class={"time"} visible={showTime} xalign={1}
|
||||
label={GLib.DateTime.new_from_unix_local(notification.time).format("%H:%M") ?? ""} />
|
||||
|
||||
<Gtk.Button halign={Gtk.Align.END} iconName={"window-close-symbolic"}
|
||||
class={"close icon"}/>
|
||||
</Gtk.Box>
|
||||
<Separator alpha={.1} orientation={Gtk.Orientation.VERTICAL} />
|
||||
<Gtk.Box class={"content"} $={(self) => {
|
||||
const image = getNotificationImage(notification);
|
||||
|
||||
image &&
|
||||
self.prepend(Gtk.Picture.new_for_filename(image));
|
||||
}}>
|
||||
|
||||
<Gtk.Box class={"text"} orientation={Gtk.Orientation.VERTICAL}
|
||||
vexpand={true}>
|
||||
|
||||
<Gtk.Label class={"summary"} useMarkup={true} xalign={0}
|
||||
ellipsize={Pango.EllipsizeMode.END} label={
|
||||
notification.summary.replace(/[&]/g, "&")
|
||||
} />
|
||||
|
||||
<Gtk.Label class={"body"} useMarkup={true} xalign={0} wrap={true}
|
||||
wrapMode={Pango.WrapMode.WORD_CHAR} singleLineMode={false}
|
||||
label={notification.body.replace(/[&]/g, "&")} />
|
||||
</Gtk.Box>
|
||||
</Gtk.Box>
|
||||
|
||||
<Gtk.Box class={"action button-row"} hexpand={true} visible={
|
||||
(notification instanceof AstalNotifd.Notification) &&
|
||||
(notification.actions.filter(action => action.label.toLowerCase() !== "view").length > 0)
|
||||
}>
|
||||
{
|
||||
(notification instanceof AstalNotifd.Notification) &&
|
||||
notification.actions.filter(a => a.label.toLowerCase() !== "view").map(action =>
|
||||
<Gtk.Button class={"action"} label={action.label}
|
||||
hexpand={true} onClicked={(_) => {
|
||||
notification.invoke(action.id);
|
||||
actionClose?.(notification);
|
||||
}}
|
||||
/>)
|
||||
}
|
||||
</Gtk.Box>
|
||||
</Gtk.Box> as Gtk.Widget;
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
import { Binding } from "astal";
|
||||
import { Astal, Gdk, Gtk, Widget } from "astal/gtk3";
|
||||
import { BackgroundWindow } from "./BackgroundWindow";
|
||||
|
||||
type PopupWindowSpecificProps = {
|
||||
onDestroy?: (self: Widget.Window) => void;
|
||||
onKeyPressEvent?: (self: Widget.Window, event: Gdk.Event) => void;
|
||||
onButtonPressEvent?: (self: Gtk.Widget, event: Gdk.Event) => void;
|
||||
/** Stylesheet for the background of the popup-window */
|
||||
cssBackgroundWindow?: string;
|
||||
onClickedOutside?: (self: Widget.Window) => void;
|
||||
};
|
||||
|
||||
export type PopupWindowProps = Pick<Widget.WindowProps,
|
||||
"child"
|
||||
| "monitor"
|
||||
| "css"
|
||||
| "layer"
|
||||
| "exclusivity"
|
||||
| "marginLeft"
|
||||
| "marginTop"
|
||||
| "marginRight"
|
||||
| "marginBottom"
|
||||
| "expand"
|
||||
| "cursor"
|
||||
| "canFocus"
|
||||
| "hasFocus"
|
||||
| "tooltipMarkup"
|
||||
| "namespace"
|
||||
| "widthRequest"
|
||||
| "heightRequest"
|
||||
| "halign"
|
||||
| "valign"
|
||||
| "vexpand"
|
||||
| "hexpand"> & PopupWindowSpecificProps;
|
||||
|
||||
const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor;
|
||||
|
||||
export function PopupWindow(props: PopupWindowProps): Widget.Window {
|
||||
props.layer = props.layer ?? Astal.Layer.OVERLAY;
|
||||
|
||||
const bgWindow = props.cssBackgroundWindow ? BackgroundWindow({
|
||||
monitor: props.monitor ?? 0,
|
||||
layer: props.layer,
|
||||
css: props.cssBackgroundWindow,
|
||||
}) : undefined;
|
||||
|
||||
const winProps: Widget.WindowProps = {};
|
||||
for(const key of Object.keys(props).filter(k => k !== "onClickedOutside")) {
|
||||
// @ts-ignore ignore the `onClickedOutside()` method because astal thinks it's a signal
|
||||
winProps[key as keyof typeof winProps] = props[key as keyof typeof props];
|
||||
}
|
||||
|
||||
return new Widget.Window({
|
||||
...winProps,
|
||||
namespace: props?.namespace ?? "popup-window",
|
||||
className: `popup-window ${(props.namespace instanceof Binding ?
|
||||
props.namespace.get() : props.namespace) || ""}`,
|
||||
keymode: Astal.Keymode.EXCLUSIVE,
|
||||
anchor: TOP | LEFT | RIGHT | BOTTOM,
|
||||
exclusivity: props.exclusivity ?? Astal.Exclusivity.NORMAL,
|
||||
halign: undefined,
|
||||
valign: undefined,
|
||||
focusOnMap: true,
|
||||
widthRequest: undefined,
|
||||
heightRequest: undefined,
|
||||
marginTop: undefined,
|
||||
marginBottom: undefined,
|
||||
marginLeft: undefined,
|
||||
marginRight: undefined,
|
||||
onDestroy: (self) => {
|
||||
bgWindow?.close();
|
||||
props.onDestroy?.(self);
|
||||
},
|
||||
onButtonPressEvent: (self, event) => {
|
||||
if((event.get_button()[1] === Gdk.BUTTON_PRIMARY ||
|
||||
event.get_button()[1] === Gdk.BUTTON_SECONDARY)) {
|
||||
|
||||
const [ , x, y ] = event.get_coords();
|
||||
const allocation = (self.get_child()! as Widget.Box).get_child()!.get_allocation();
|
||||
|
||||
if((x < allocation.x || x > (allocation.x + allocation.width)) ||
|
||||
(y < allocation.y || y > (allocation.y + allocation.height))) {
|
||||
|
||||
if(!props.onClickedOutside) {
|
||||
self.close();
|
||||
return;
|
||||
}
|
||||
|
||||
props.onClickedOutside?.(self);
|
||||
}
|
||||
}
|
||||
},
|
||||
onKeyPressEvent: (self, event: Gdk.Event) => {
|
||||
if(event.get_keyval()[1] === Gdk.KEY_Escape) {
|
||||
self.close();
|
||||
return;
|
||||
}
|
||||
|
||||
props.onKeyPressEvent?.(self, event);
|
||||
},
|
||||
child: new Widget.Box({
|
||||
expand: props.expand ?? false,
|
||||
halign: props.halign,
|
||||
valign: props.valign,
|
||||
hexpand: true,
|
||||
css: `box {
|
||||
margin-left: ${props.marginLeft ?? 0}px;
|
||||
margin-right: ${props.marginRight ?? 0}px;
|
||||
margin-top: ${props.marginTop ?? 0}px;
|
||||
margin-bottom: ${props.marginBottom ?? 0}px;
|
||||
}`,
|
||||
|
||||
child: new Widget.Box({
|
||||
onButtonPressEvent: props.onButtonPressEvent ?? (() => true),
|
||||
widthRequest: props.widthRequest,
|
||||
heightRequest: props.heightRequest,
|
||||
child: props.child
|
||||
} as Widget.BoxProps)
|
||||
} as Widget.BoxProps)
|
||||
} as Widget.WindowProps);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import { Astal, Gdk, Gtk } from "ags/gtk4";
|
||||
import { BackgroundWindow } from "./BackgroundWindow";
|
||||
import GObject from "gi://GObject?version=2.0";
|
||||
import { Accessor, CCProps, createComputed } from "ags";
|
||||
import { omitObjectKeys, WidgetNodeType } from "../scripts/utils";
|
||||
|
||||
|
||||
type PopupWindowSpecificProps = {
|
||||
children?: WidgetNodeType;
|
||||
onDestroy?: (self: Astal.Window) => void;
|
||||
/** Stylesheet for the background of the popup-window */
|
||||
cssBackgroundWindow?: string;
|
||||
class?: string | Accessor<string>;
|
||||
actionClickedOutside?: (self: Astal.Window) => void;
|
||||
actionKeyPressed?: (self: Astal.Window, keyval: number, keycode: number) => void;
|
||||
};
|
||||
|
||||
export type PopupWindowProps = Pick<Partial<CCProps<Astal.Window, Astal.Window.ConstructorProps>>,
|
||||
"monitor"
|
||||
| "layer"
|
||||
| "exclusivity"
|
||||
| "marginLeft"
|
||||
| "marginTop"
|
||||
| "marginRight"
|
||||
| "marginBottom"
|
||||
| "cursor"
|
||||
| "canFocus"
|
||||
| "hasFocus"
|
||||
| "tooltipMarkup"
|
||||
| "namespace"
|
||||
| "widthRequest"
|
||||
| "heightRequest"
|
||||
| "halign"
|
||||
| "valign"
|
||||
| "vexpand"
|
||||
| "hexpand"> & PopupWindowSpecificProps;
|
||||
|
||||
const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor;
|
||||
|
||||
export function PopupWindow(props: PopupWindowProps): GObject.Object {
|
||||
props.layer ??= Astal.Layer.OVERLAY;
|
||||
|
||||
const bgWindow = props.cssBackgroundWindow ? (<BackgroundWindow
|
||||
monitor={props.monitor ?? 0}
|
||||
layer={props.layer}
|
||||
css={props.cssBackgroundWindow} /> as Astal.Window)
|
||||
: undefined;
|
||||
|
||||
const omittedProps = omitObjectKeys(props, [
|
||||
"children",
|
||||
"actionKeyPressed",
|
||||
"actionClickedOutside",
|
||||
"cssBackgroundWindow",
|
||||
"marginTop",
|
||||
"marginLeft",
|
||||
"marginRight",
|
||||
"marginBottom"
|
||||
]);
|
||||
|
||||
return <Astal.Window {...omittedProps}
|
||||
namespace={props.namespace ?? "popup-window"} class={
|
||||
(props.class instanceof Accessor) ?
|
||||
((props.namespace instanceof Accessor) ?
|
||||
createComputed([props.class, props.namespace], (clss, namespace) =>
|
||||
`popup-window ${clss} ${namespace}`)
|
||||
: props.class.as(clss => `popup-window ${clss} ${props.namespace ?? ""}`))
|
||||
: `popup-window ${props.class ?? ""} ${props.namespace ?? ""}`
|
||||
} keymode={Astal.Keymode.EXCLUSIVE} anchor={TOP | LEFT | RIGHT | BOTTOM}
|
||||
exclusivity={props.exclusivity ?? Astal.Exclusivity.NORMAL}
|
||||
onDestroy={(self) => {
|
||||
bgWindow?.close();
|
||||
props.onDestroy?.(self);
|
||||
}}
|
||||
$={(self) => {
|
||||
props.actionClickedOutside ??= self.close;
|
||||
|
||||
const conns: Map<GObject.Object, number> = new Map();
|
||||
const gestureClick = Gtk.GestureClick.new();
|
||||
const keyController = Gtk.EventControllerKey.new();
|
||||
const allocation = (self.get_child()! as Gtk.Box).get_first_child()!.get_allocation();
|
||||
|
||||
self.add_controller(gestureClick);
|
||||
self.add_controller(keyController);
|
||||
|
||||
conns.set(gestureClick, gestureClick.connect("released", (_, __, x, y) => {
|
||||
if((x < allocation.x || x > (allocation.x + allocation.width)) ||
|
||||
(y < allocation.y || y > (allocation.y + allocation.height))) {
|
||||
|
||||
// Disconnect signals because window is being closed
|
||||
conns.forEach((id, obj) => {
|
||||
obj.disconnect(id);
|
||||
});
|
||||
|
||||
props.actionClickedOutside!(self);
|
||||
}
|
||||
}));
|
||||
|
||||
conns.set(keyController, keyController.connect("key-pressed", (_, keyval, keycode) => {
|
||||
if(keyval === Gdk.KEY_Escape) {
|
||||
conns.forEach((id, obj) => {
|
||||
obj.disconnect(id);
|
||||
});
|
||||
|
||||
props.actionClickedOutside!(self);
|
||||
return;
|
||||
}
|
||||
|
||||
props.actionKeyPressed?.(self, keyval, keycode);
|
||||
}));
|
||||
|
||||
conns.set(self, self.connect("destroy", () => conns.forEach((id, obj) =>
|
||||
obj.disconnect(id))));
|
||||
}}>
|
||||
<Gtk.Box
|
||||
halign={props.halign}
|
||||
valign={props.valign}
|
||||
hexpand vexpand
|
||||
css={`box {
|
||||
margin-left: ${props.marginLeft ?? 0}px;
|
||||
margin-right: ${props.marginRight ?? 0}px;
|
||||
margin-top: ${props.marginTop ?? 0}px;
|
||||
margin-bottom: ${props.marginBottom ?? 0}px;
|
||||
}`}>
|
||||
|
||||
<Gtk.Box widthRequest={props.widthRequest} heightRequest={props.heightRequest}
|
||||
$={(self) => {
|
||||
const gestureClick = Gtk.GestureClick.new();
|
||||
self.add_controller(gestureClick);
|
||||
}}>
|
||||
{props.children}
|
||||
</Gtk.Box>
|
||||
</Gtk.Box>
|
||||
</Astal.Window>;
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import { Binding } from "astal";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
|
||||
export interface SeparatorProps {
|
||||
class?: string;
|
||||
alpha?: number;
|
||||
cssColor?: string;
|
||||
orientation?: Gtk.Orientation;
|
||||
size?: number;
|
||||
spacing?: number;
|
||||
margin?: number;
|
||||
visible?: boolean | Binding<boolean>;
|
||||
}
|
||||
|
||||
export function Separator(props: SeparatorProps = {
|
||||
orientation: Gtk.Orientation.HORIZONTAL
|
||||
}) {
|
||||
props.alpha = props.alpha ?
|
||||
(props.alpha > 1 ?
|
||||
props.alpha / 100
|
||||
: props.alpha)
|
||||
: 1;
|
||||
|
||||
props.orientation = props.orientation ?? Gtk.Orientation.HORIZONTAL;
|
||||
|
||||
return new Widget.Box({
|
||||
name: "separator",
|
||||
...(props.orientation === Gtk.Orientation.HORIZONTAL ?
|
||||
{ vexpand: true } : { hexpand: true }),
|
||||
className: `separator ${ props.orientation === Gtk.Orientation.VERTICAL ?
|
||||
"vertical" : "horizontal" }`,
|
||||
visible: props.visible,
|
||||
css: `.vertical {
|
||||
padding: ${props.spacing ?? 0}px ${props.margin ?? 7}px;
|
||||
}
|
||||
.horizontal {
|
||||
padding: ${props.margin ?? 4}px ${props.spacing ?? 0}px;
|
||||
}`,
|
||||
child: new Widget.Box({
|
||||
className: `${ props.orientation === Gtk.Orientation.VERTICAL ?
|
||||
"vertical" : "horizontal" } ${ props.class ? props.class : "" }`,
|
||||
...(props.orientation === Gtk.Orientation.HORIZONTAL ?
|
||||
{ vexpand: true } : { hexpand: true }),
|
||||
css: `* {
|
||||
background: ${ props.cssColor ?? "lightgray" };
|
||||
opacity: ${props.alpha};
|
||||
}
|
||||
.horizontal {
|
||||
min-width: ${ props.size ?? 1 }px;
|
||||
}
|
||||
|
||||
.vertical {
|
||||
min-height: ${ props.size ?? 1 }px;
|
||||
}`
|
||||
} as Widget.BoxProps)
|
||||
} as Widget.BoxProps);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Accessor } from "ags";
|
||||
import { Gtk } from "ags/gtk4";
|
||||
|
||||
|
||||
export interface SeparatorProps {
|
||||
class?: string;
|
||||
alpha?: number;
|
||||
cssColor?: string;
|
||||
orientation?: Gtk.Orientation;
|
||||
size?: number;
|
||||
spacing?: number;
|
||||
margin?: number;
|
||||
visible?: boolean | Accessor<boolean>;
|
||||
}
|
||||
|
||||
export function Separator(props: SeparatorProps = {
|
||||
orientation: Gtk.Orientation.HORIZONTAL
|
||||
}) {
|
||||
props.alpha = props.alpha ?
|
||||
(props.alpha > 1 ?
|
||||
props.alpha / 100
|
||||
: props.alpha)
|
||||
: 1;
|
||||
|
||||
props.orientation = props.orientation ?? Gtk.Orientation.HORIZONTAL;
|
||||
|
||||
return <Gtk.Box name={"separator"} vexpand={props.orientation === Gtk.Orientation.HORIZONTAL}
|
||||
hexpand={props.orientation === Gtk.Orientation.VERTICAL}
|
||||
class={`separator ${ props.orientation === Gtk.Orientation.VERTICAL ?
|
||||
"vertical" : "horizontal" }`} visible={props.visible}
|
||||
css={`.vertical { padding: {${props.spacing ?? 0}px ${props.margin ?? 7}px; }
|
||||
.horizontal { padding: {${props.margin ?? 4}px ${props.spacing ?? 0}px; }`}>
|
||||
|
||||
<Gtk.Box class={`${props.orientation === Gtk.Orientation.VERTICAL ?
|
||||
"vertical"
|
||||
: "horizontal"} ${props.class ?? ""}`}
|
||||
vexpand={props.orientation === Gtk.Orientation.HORIZONTAL}
|
||||
hexpand={props.orientation === Gtk.Orientation.VERTICAL}
|
||||
|
||||
css={`* {
|
||||
background: ${ props.cssColor ?? "lightgray" };
|
||||
opacity: ${props.alpha};
|
||||
}
|
||||
.horizontal { min-width: ${ props.size ?? 1 }px; }
|
||||
.vertical { min-height: ${ props.size ?? 1 }px; }`}
|
||||
/>
|
||||
</Gtk.Box>
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import { Binding, register } from "astal";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
|
||||
export { ResultWidget, ResultWidgetProps };
|
||||
|
||||
type ResultWidgetProps = {
|
||||
icon?: string | Binding<string> | Gtk.Widget | Binding<Gtk.Widget>;
|
||||
title: string | Binding<string | undefined>;
|
||||
description?: string | Binding<string | undefined>;
|
||||
closeOnClick?: boolean;
|
||||
setup?: () => void;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
@register({ GTypeName: "ResultWidget" })
|
||||
class ResultWidget extends Widget.Box {
|
||||
|
||||
public readonly onClick: (() => void);
|
||||
public readonly setup: ((() => void)|undefined);
|
||||
public icon: (string | Binding<string> | Gtk.Widget | Binding<Gtk.Widget> | undefined);
|
||||
public closeOnClick: boolean = true;
|
||||
|
||||
|
||||
constructor(props: ResultWidgetProps) {
|
||||
super({
|
||||
className: "result",
|
||||
hexpand: true
|
||||
});
|
||||
|
||||
this.icon = props.icon;
|
||||
this.setup = props.setup;
|
||||
this.closeOnClick = props.closeOnClick ?? true;
|
||||
this.onClick = () => props.onClick?.();
|
||||
|
||||
if(this.icon !== undefined) {
|
||||
if(this.icon instanceof Binding) {
|
||||
if(typeof this.icon.get() === "string") {
|
||||
this.add(new Widget.Icon({
|
||||
icon: this.icon as Binding<string>
|
||||
} as Widget.IconProps));
|
||||
} else {
|
||||
this.add(new Widget.Box({
|
||||
child: this.icon as Binding<Gtk.Widget>
|
||||
} as Widget.BoxProps));
|
||||
}
|
||||
} else {
|
||||
if(typeof this.icon === "string") {
|
||||
this.add(new Widget.Icon({
|
||||
icon: this.icon as string
|
||||
} as Widget.IconProps));
|
||||
} else
|
||||
this.add(this.icon as Gtk.Widget);
|
||||
}
|
||||
}
|
||||
|
||||
this.add(new Widget.Box({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
valign: Gtk.Align.CENTER,
|
||||
children: [
|
||||
new Widget.Label({
|
||||
className: "title",
|
||||
xalign: 0,
|
||||
truncate: true,
|
||||
label: props.title
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Label({
|
||||
className: "description",
|
||||
visible: (props.description instanceof Binding) ?
|
||||
props.description.as(Boolean)
|
||||
: Boolean(props.description),
|
||||
truncate: true,
|
||||
xalign: 0,
|
||||
label: props.description || ""
|
||||
} as Widget.LabelProps)
|
||||
]
|
||||
} as Widget.BoxProps));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Accessor, With } from "ags";
|
||||
import { register } from "ags/gobject";
|
||||
import { Gtk } from "ags/gtk4";
|
||||
import Pango from "gi://Pango?version=1.0";
|
||||
import { variableToBoolean } from "../../scripts/utils";
|
||||
|
||||
export { ResultWidget, ResultWidgetProps };
|
||||
|
||||
type ResultWidgetProps = {
|
||||
icon?: string | Accessor<string> | JSX.Element | Accessor<JSX.Element>;
|
||||
title: string | Accessor<string>;
|
||||
description?: string | Accessor<string>;
|
||||
closeOnClick?: boolean;
|
||||
setup?: () => void;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
@register({ GTypeName: "ResultWidget" })
|
||||
class ResultWidget extends Gtk.Box {
|
||||
|
||||
public readonly onClick: () => void;
|
||||
public readonly setup?: () => void;
|
||||
public icon?: (string | Accessor<string> | JSX.Element | Accessor<JSX.Element>);
|
||||
public closeOnClick: boolean = true;
|
||||
|
||||
|
||||
constructor(props: ResultWidgetProps) {
|
||||
super({
|
||||
cssClasses: ["result"],
|
||||
hexpand: true
|
||||
});
|
||||
|
||||
this.icon = props.icon;
|
||||
this.setup = props.setup;
|
||||
this.closeOnClick = props.closeOnClick ?? true;
|
||||
this.onClick = () => props.onClick?.();
|
||||
|
||||
if(this.icon !== undefined) {
|
||||
if(this.icon instanceof Accessor) {
|
||||
if(typeof this.icon.get() === "string") {
|
||||
this.prepend(<Gtk.Image iconName={
|
||||
this.icon as Accessor<string>
|
||||
} /> as Gtk.Image);
|
||||
} else {
|
||||
this.prepend(<Gtk.Box>
|
||||
<With value={this.icon as Accessor<Gtk.Widget>}>
|
||||
{(widget) => widget}
|
||||
</With>
|
||||
</Gtk.Box> as Gtk.Box);
|
||||
}
|
||||
} else {
|
||||
if(typeof this.icon === "string")
|
||||
this.prepend(<Gtk.Image iconName={this.icon as string} /> as Gtk.Image);
|
||||
else
|
||||
this.prepend(this.icon as Gtk.Widget);
|
||||
}
|
||||
}
|
||||
|
||||
this.append(<Gtk.Box orientation={Gtk.Orientation.VERTICAL} valign={Gtk.Align.CENTER}>
|
||||
<Gtk.Label class={"title"} xalign={0} ellipsize={Pango.EllipsizeMode.END}
|
||||
label={props.title} />
|
||||
|
||||
<Gtk.Label class={"description"} visible={variableToBoolean(props.description)}
|
||||
ellipsize={Pango.EllipsizeMode.END} xalign={0} label={props.description ?? ""} />
|
||||
</Gtk.Box> as Gtk.Box);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user