✨ ags: add ask popup, make notifications work(finally :3) and more improvements
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { Binding } from "astal";
|
||||
import { PopupWindow, PopupWindowProps } from "./PopupWindow";
|
||||
import { Astal, Gtk, Widget } from "astal/gtk3";
|
||||
import { Separator } from "./Separator";
|
||||
|
||||
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 certain question.
|
||||
* Runs onAccept() when user accepts or else onDecline() when
|
||||
* user doesn't accept or closes window.
|
||||
*/
|
||||
export function AskPopup(props: AskPopupProps) {
|
||||
const buttons = [
|
||||
new Widget.Button({
|
||||
className: "cancel",
|
||||
hexpand: true,
|
||||
label: props.cancelText || "Cancel",
|
||||
onClick: (_) => {
|
||||
window.destroy();
|
||||
props.onCancel && props.onCancel();
|
||||
}
|
||||
} as Widget.ButtonProps),
|
||||
new Widget.Button({
|
||||
className: "accept",
|
||||
hexpand: true,
|
||||
label: props.acceptText || "Ok",
|
||||
onClick: (_) => {
|
||||
window.destroy();
|
||||
props.onAccept && props.onAccept();
|
||||
}
|
||||
} as Widget.ButtonProps)
|
||||
];
|
||||
|
||||
const window = PopupWindow({
|
||||
namespace: "ask-popup",
|
||||
visible: true,
|
||||
className: "ask-popup",
|
||||
exclusivity: Astal.Exclusivity.IGNORE,
|
||||
widthRequest: 350,
|
||||
heightRequest: 200,
|
||||
onClose: (_) => {
|
||||
props.onCancel && props.onCancel();
|
||||
_.destroy();
|
||||
},
|
||||
child: new Widget.Box({
|
||||
className: "ask-popup-box",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
children: [
|
||||
new Widget.Label({
|
||||
className: "title",
|
||||
visible: Boolean(props.title),
|
||||
label: props.title || ""
|
||||
} 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),
|
||||
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);
|
||||
}
|
||||
+33
-10
@@ -1,5 +1,4 @@
|
||||
//TODO Needs more work
|
||||
|
||||
import { register, Variable } from "astal";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
|
||||
type CalendarProps = Pick<Widget.BoxProps,
|
||||
@@ -10,14 +9,38 @@ type CalendarProps = Pick<Widget.BoxProps,
|
||||
| "halign"
|
||||
| "valign"> & {
|
||||
|
||||
showWeekDays: boolean;
|
||||
showHeader: boolean;
|
||||
fillGrid: boolean; // I need a better name for this LMAOOO
|
||||
showWeekDays?: boolean;
|
||||
showHeader?: boolean;
|
||||
fillGrid?: boolean; // I need a better name for this LMAOOO
|
||||
};
|
||||
|
||||
export function Calendar(props?: Partial<CalendarProps>): Gtk.Widget {
|
||||
return new Widget.Box({
|
||||
...props,
|
||||
children: []
|
||||
} as Widget.BoxProps);
|
||||
@register({ GTypeName: "Calendar" })
|
||||
class Calendar extends Gtk.Box {
|
||||
#showWeekDays = new Variable<boolean>(true);
|
||||
#showHeader = new Variable<boolean>(true);
|
||||
#fillGrid = new Variable<boolean>(false);
|
||||
|
||||
set fillGrid(newValue: boolean) { this.#fillGrid.set(newValue); }
|
||||
get fillGrid() { return this.#fillGrid.get(); }
|
||||
set showHeader(newValue: boolean) { this.#showHeader.set(newValue); }
|
||||
get showHeader() { return this.#showHeader.get(); }
|
||||
set showWeekDays(newValue: boolean) { this.#showWeekDays.set(newValue); }
|
||||
get showWeekDays() { return this.#showWeekDays.get(); }
|
||||
|
||||
constructor(props?: CalendarProps) {
|
||||
super();
|
||||
this.add(new Widget.Box({
|
||||
...props,
|
||||
widthRequest: 128,
|
||||
heightRequest: 128,
|
||||
children: [
|
||||
new Widget.Box({
|
||||
className: "header",
|
||||
heightRequest: 24,
|
||||
hexpand: true,
|
||||
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { astalify, Gtk } from "astal/gtk3";
|
||||
|
||||
// TODO
|
||||
export class FlowBox extends astalify(Gtk.FlowBox) {}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { Astal, Gtk, Widget } from "astal/gtk3";
|
||||
import AstalNotifd from "gi://AstalNotifd";
|
||||
import { Separator } from "./Separator";
|
||||
|
||||
export function getUrgencyString(notif: AstalNotifd.Notification) {
|
||||
switch(notif.urgency) {
|
||||
case AstalNotifd.Urgency.LOW:
|
||||
return "low";
|
||||
case AstalNotifd.Urgency.CRITICAL:
|
||||
return "critical";
|
||||
}
|
||||
|
||||
return "normal";
|
||||
}
|
||||
|
||||
export function NotificationWidget(notification: AstalNotifd.Notification|number,
|
||||
onClose?: (notif: AstalNotifd.Notification) => void): Gtk.Widget {
|
||||
|
||||
notification = (notification instanceof AstalNotifd.Notification) ?
|
||||
notification
|
||||
: AstalNotifd.get_default().get_notification(notification);
|
||||
|
||||
return new Widget.Box({
|
||||
className: `notification ${getUrgencyString(notification)}`,
|
||||
homogeneous: false,
|
||||
expand: false,
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
children: [
|
||||
new Widget.Box({
|
||||
className: "top",
|
||||
orientation: Gtk.Orientation.HORIZONTAL,
|
||||
hexpand: true,
|
||||
vexpand: false,
|
||||
children: [
|
||||
new Widget.Icon({
|
||||
className: "icon app-icon",
|
||||
icon: Astal.Icon.lookup_icon(notification.appIcon) ?
|
||||
notification.appIcon
|
||||
: (Astal.Icon.lookup_icon(notification.appName.toLowerCase()) ?
|
||||
notification.appName.toLowerCase()
|
||||
: "image-missing"
|
||||
),
|
||||
setup: (_) => _.get_icon() === "image-missing" &&
|
||||
_.set_visible(false),
|
||||
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.Button({
|
||||
className: "close nf",
|
||||
halign: Gtk.Align.END,
|
||||
onClick: () => onClose && onClose(notification),
|
||||
image: new Widget.Icon({
|
||||
className: "close icon",
|
||||
icon: "window-close-symbolic"
|
||||
} as Widget.IconProps)
|
||||
} as Widget.ButtonProps)
|
||||
]
|
||||
} 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",
|
||||
visible: Boolean(notification.image),
|
||||
css: `box.image { background-image: url('${notification.image}'); }`
|
||||
} as Widget.BoxProps),
|
||||
new Widget.Box({
|
||||
className: "text",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
children: [
|
||||
new Widget.Label({
|
||||
className: "summary",
|
||||
useMarkup: true,
|
||||
xalign: 0,
|
||||
truncate: true,
|
||||
label: notification.summary
|
||||
}),
|
||||
new Widget.Label({
|
||||
className: "body",
|
||||
useMarkup: true,
|
||||
xalign: 0,
|
||||
expand: true,
|
||||
wrap: true,
|
||||
label: notification.body
|
||||
} as Widget.LabelProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
}
|
||||
+46
-29
@@ -4,33 +4,40 @@ import { Astal, Gdk, Gtk, Widget } from "astal/gtk3";
|
||||
|
||||
const { TOP, BOTTOM, LEFT, RIGHT }: typeof Astal.WindowAnchor = Astal.WindowAnchor;
|
||||
|
||||
export interface PopupWindowProps {
|
||||
className?: string | Binding<string | undefined>;
|
||||
namespace: string | Binding<string | undefined>;
|
||||
visible?: boolean | Binding<boolean | undefined>;
|
||||
halign?: Gtk.Align | Binding<Gtk.Align | undefined>;
|
||||
valign?: Gtk.Align | Binding<Gtk.Align | undefined>;
|
||||
hexpand?: boolean | Binding<boolean | undefined>;
|
||||
vexpand?: boolean | Binding<boolean | undefined>;
|
||||
expand?: boolean | Binding<boolean | undefined>;
|
||||
monitor?: number | Binding<number | undefined>;
|
||||
marginTop?: number | Binding<number | undefined>;
|
||||
marginBottom?: number | Binding<number | undefined>;
|
||||
marginLeft?: number | Binding<number | undefined>;
|
||||
marginRight?: number | Binding<number | undefined>;
|
||||
widthRequest?: number | Binding<number | undefined>;
|
||||
heightRequest?: number | Binding<number | undefined>;
|
||||
layer?: Astal.Layer | Binding<Astal.Layer | undefined>;
|
||||
onClose?: () => void;
|
||||
child: Gtk.Widget;
|
||||
}
|
||||
export type PopupWindowProps = Pick<Widget.WindowProps,
|
||||
"namespace"
|
||||
| "visible"
|
||||
| "className"
|
||||
| "hexpand"
|
||||
| "vexpand"
|
||||
| "halign"
|
||||
| "valign"
|
||||
| "expand"
|
||||
| "layer"
|
||||
| "widthRequest"
|
||||
| "heightRequest"
|
||||
| "child"
|
||||
| "monitor"
|
||||
| "exclusivity"> & {
|
||||
marginTop?: number;
|
||||
marginLeft?: number;
|
||||
marginBottom?: number;
|
||||
marginRight?: number;
|
||||
onKeyPressEvent?: (self: Widget.Window, event: Gdk.Event) => void;
|
||||
/** Do something else instead of hiding window on close action(clicking outside conent / pressing Escape)
|
||||
* Observation: onClose() function will still be ran after close action if defined.
|
||||
*/
|
||||
closeAction?: (self: Widget.Window) => void;
|
||||
onClose?: (self: Widget.Window) => void;
|
||||
};
|
||||
|
||||
export function PopupWindow(props: PopupWindowProps): Widget.Window {
|
||||
return new Widget.Window({
|
||||
namespace: props?.namespace || "popup-window",
|
||||
className: "popup-window",
|
||||
className: `popup-window ${(props.namespace instanceof Binding ?
|
||||
props.namespace.get() : props.namespace) || ""}`,
|
||||
anchor: TOP | BOTTOM | LEFT | RIGHT,
|
||||
exclusivity: Astal.Exclusivity.NORMAL,
|
||||
exclusivity: props.exclusivity || Astal.Exclusivity.NORMAL,
|
||||
keymode: Astal.Keymode.EXCLUSIVE,
|
||||
layer: props?.layer || Astal.Layer.OVERLAY,
|
||||
focusOnMap: true,
|
||||
@@ -44,11 +51,18 @@ export function PopupWindow(props: PopupWindowProps): Widget.Window {
|
||||
if((posX < childAllocation.x || posX > (childAllocation.x + childAllocation.width)) ||
|
||||
(posY < childAllocation.y || posY > (childAllocation.y + childAllocation.height))) {
|
||||
_.hide();
|
||||
props?.onClose && props.onClose();
|
||||
props?.onClose && props.onClose(_);
|
||||
}
|
||||
},
|
||||
onKeyPressEvent: (_, event: Gdk.Event) =>
|
||||
event.get_keyval()[1] === Gdk.KEY_Escape && _.hide(),
|
||||
onKeyPressEvent: (_, event: Gdk.Event) => {
|
||||
if(event.get_keyval()[1] === Gdk.KEY_Escape) {
|
||||
!props.closeAction ? _.hide() : props.closeAction(_);
|
||||
props.onClose && props.onClose(_);
|
||||
}
|
||||
|
||||
props.onKeyPressEvent &&
|
||||
props.onKeyPressEvent(_, event);
|
||||
},
|
||||
child: new Widget.Box({
|
||||
className: (props?.className instanceof Binding) ?
|
||||
props.className.as((clsName: string|undefined) =>
|
||||
@@ -62,10 +76,13 @@ export function PopupWindow(props: PopupWindowProps): Widget.Window {
|
||||
hexpand: props?.hexpand || false,
|
||||
visible: true,
|
||||
vexpand: props?.vexpand || false,
|
||||
marginTop: props?.marginTop || 0,
|
||||
marginBottom: props?.marginBottom || 0,
|
||||
marginLeft: props?.marginLeft || 0,
|
||||
marginRight: props?.marginRight || 0,
|
||||
css: `.popup {
|
||||
margin-top: ${props.marginTop || 0}px;
|
||||
margin-bottom: ${props.marginBottom || 0}px;
|
||||
margin-left: ${props.marginLeft || 0}px;
|
||||
margin-right: ${props.marginRight || 0}px;
|
||||
}`,
|
||||
onButtonPressEvent: () => true,
|
||||
child: props.child
|
||||
} as Widget.BoxProps)
|
||||
} as Widget.WindowProps);;
|
||||
|
||||
+11
-4
@@ -11,19 +11,26 @@ export interface SeparatorProps {
|
||||
}
|
||||
|
||||
export function Separator(props: SeparatorProps) {
|
||||
const alpha: number = props.alpha ?
|
||||
(props.alpha > 1) ?
|
||||
props.alpha / 100
|
||||
: props.alpha
|
||||
: 1;
|
||||
|
||||
return new Widget.Box({
|
||||
className: `separator separator-${ props.orientation == Gtk.Orientation.VERTICAL ? "vertical" : "horizontal" } ${ props.class && props.class }`,
|
||||
className: `separator separator-${ props.orientation == Gtk.Orientation.VERTICAL ?
|
||||
"vertical" : "horizontal" } ${ props.class && props.class }`,
|
||||
visible: props.visible,
|
||||
css: `.separator {
|
||||
background: ${ props.cssColor || "lightgray" };
|
||||
opacity: ${ props.alpha || 1 };
|
||||
opacity: ${alpha};
|
||||
}
|
||||
.separator-horizontal {
|
||||
padding-bottom: ${props.size || 1 }px;
|
||||
min-width: ${ props.size || 1 }px;
|
||||
margin: 4px 4px;
|
||||
}
|
||||
.separator-vertical {
|
||||
padding-right: ${props.size || 1 }px;
|
||||
min-height: ${ props.size || 1 }px;
|
||||
margin: 7px 7px;
|
||||
}`,
|
||||
} as Widget.BoxProps);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { bind, Process } from "astal";
|
||||
import { Widget } from "astal/gtk3";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
import { Wireplumber } from "../../scripts/volume";
|
||||
import { ControlCenter } from "../../window/ControlCenter";
|
||||
|
||||
export function Audio() {
|
||||
export function Audio(): Gtk.Widget {
|
||||
return new Widget.EventBox({
|
||||
className: bind(ControlCenter, "visible").as((visible: boolean) =>
|
||||
visible ? "audio open" : "audio"),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Widget } from "astal/gtk3";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
import { getDateTime } from "../../scripts/time";
|
||||
import { bind, GLib } from "astal";
|
||||
import { Windows } from "../../windows";
|
||||
import { CenterWindow } from "../../window/CenterWindow";
|
||||
|
||||
export function Clock(): JSX.Element {
|
||||
export function Clock(): Gtk.Widget {
|
||||
return new Widget.Box({
|
||||
className: bind(CenterWindow, "visible").as((visible: boolean) =>
|
||||
visible ? "clock open" : "clock"),
|
||||
|
||||
@@ -31,6 +31,7 @@ export function FocusedClient(): Gtk.Widget {
|
||||
new Widget.Label({
|
||||
className: "class",
|
||||
xalign: 0,
|
||||
visible: bind(focusedClient, "class").as(Boolean),
|
||||
maxWidthChars: 55,
|
||||
truncate: true,
|
||||
tooltipText: bind(focusedClient, "class").as((clientClass: string) =>
|
||||
@@ -41,6 +42,7 @@ export function FocusedClient(): Gtk.Widget {
|
||||
className: "title",
|
||||
xalign: 0,
|
||||
maxWidthChars: 50,
|
||||
visible: bind(focusedClient, "title").as(Boolean),
|
||||
truncate: true,
|
||||
tooltipText: bind(focusedClient, "title").as((clientTitle: string) =>
|
||||
clientTitle.length > 55 ? clientTitle : ""),
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Widget } from "astal/gtk3";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
import AstalHyprland from "gi://AstalHyprland";
|
||||
|
||||
export function Logo() {
|
||||
export function Logo(): Gtk.Widget {
|
||||
return new Widget.EventBox({
|
||||
onClickRelease: () => AstalHyprland.get_default().dispatch("exec", "anyrun"),
|
||||
className: "logo",
|
||||
child: new Widget.Box({
|
||||
child: new Widget.Label({
|
||||
className: "nf",
|
||||
label: "",
|
||||
label: ""
|
||||
} as Widget.LabelProps)
|
||||
} as Widget.BoxProps)
|
||||
} as Widget.EventBoxProps);
|
||||
|
||||
@@ -11,7 +11,7 @@ function menuFromModel(model: Gio.MenuModel, actionGroup: Gio.ActionGroup | null
|
||||
return menu;
|
||||
}
|
||||
|
||||
export function Tray() {
|
||||
export function Tray(): Gtk.Widget {
|
||||
return new Widget.Box({
|
||||
className: "tray",
|
||||
visible: bind(astalTray, "items").as((items: Array<AstalTray.TrayItem>) => items.length > 0),
|
||||
@@ -29,9 +29,10 @@ export function Tray() {
|
||||
tooltipMarkup: bind(item, "tooltipMarkup"),
|
||||
onClick: (_, event: Astal.ClickEvent) => {
|
||||
if(event.button === Astal.MouseButton.SECONDARY) {
|
||||
item.about_to_show();
|
||||
menu.popup_at_widget(_, Gdk.Gravity.NORTH, Gdk.Gravity.SOUTH_WEST, null);
|
||||
} else if(event.button === Astal.MouseButton.PRIMARY)
|
||||
item.secondary_activate(event.x, event.y);
|
||||
item.activate(event.x, event.y);
|
||||
},
|
||||
halign: Gtk.Align.CENTER,
|
||||
child: new Widget.Icon({
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { bind } from "astal";
|
||||
import { Gdk, Gtk, Widget } from "astal/gtk3";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
import AstalHyprland from "gi://AstalHyprland";
|
||||
|
||||
const hyprland = AstalHyprland.get_default();
|
||||
|
||||
export function Workspaces() {
|
||||
export function Workspaces(): Gtk.Widget {
|
||||
const workspacesEventBox = new Widget.EventBox({
|
||||
onScroll: (_, event) =>
|
||||
event.delta_y > 0 ? hyprland.dispatch("workspace", "e-1") : hyprland.dispatch("workspace", "e+1"),
|
||||
|
||||
@@ -4,163 +4,165 @@ import AstalMpris from "gi://AstalMpris";
|
||||
|
||||
let dragTimer: (AstalIO.Time|undefined);
|
||||
|
||||
export const BigMedia: Gtk.Widget = new Widget.Box({
|
||||
className: "big-media",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
homogeneous: false,
|
||||
width_request: 250,
|
||||
visible: bind(AstalMpris.get_default(), "players").as((players: Array<AstalMpris.Player>) =>
|
||||
players[0] ? true : false),
|
||||
children: bind(AstalMpris.get_default(), "players").as((players: Array<AstalMpris.Player>) =>
|
||||
players[0] && [
|
||||
new Widget.Box({
|
||||
halign: Gtk.Align.CENTER,
|
||||
child: new Widget.Box({
|
||||
className: "image",
|
||||
hexpand: false,
|
||||
export function BigMedia(): Gtk.Widget {
|
||||
return new Widget.Box({
|
||||
className: "big-media",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
homogeneous: false,
|
||||
width_request: 250,
|
||||
visible: bind(AstalMpris.get_default(), "players").as((players: Array<AstalMpris.Player>) =>
|
||||
players[0] ? true : false),
|
||||
children: bind(AstalMpris.get_default(), "players").as((players: Array<AstalMpris.Player>) =>
|
||||
players[0] && [
|
||||
new Widget.Box({
|
||||
halign: Gtk.Align.CENTER,
|
||||
child: new Widget.Box({
|
||||
className: "image",
|
||||
hexpand: false,
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
visible: getAlbumArt(players[0]).as(Boolean),
|
||||
css: getAlbumArt(players[0]).as((artUrl: string|undefined) =>
|
||||
artUrl ? `.image { background-image: url('${artUrl}'); }` : undefined),
|
||||
width_request: 132,
|
||||
height_request: 128
|
||||
} as Widget.BoxProps)
|
||||
} as Widget.BoxProps),
|
||||
new Widget.Box({
|
||||
className: "info",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
visible: getAlbumArt(players[0]).as(Boolean),
|
||||
css: getAlbumArt(players[0]).as((artUrl: string|undefined) =>
|
||||
artUrl ? `.image { background-image: url('${artUrl}'); }` : undefined),
|
||||
width_request: 132,
|
||||
height_request: 128
|
||||
} as Widget.BoxProps)
|
||||
} as Widget.BoxProps),
|
||||
new Widget.Box({
|
||||
className: "info",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
children: [
|
||||
new Widget.Label({
|
||||
className: "title",
|
||||
tooltipText: bind(players[0], "title").as((title: string) => !title ? "No Title" : title),
|
||||
label: bind(players[0], "title").as((title: string) => !title ? "No Title" : title),
|
||||
truncate: true,
|
||||
maxWidthChars: 25,
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Label({
|
||||
className: "artist",
|
||||
tooltipText: bind(players[0], "artist").as((artist: string) => !artist ? "No Artist" : artist),
|
||||
label: bind(players[0], "artist").as((artist: string) => !artist ? "No Artist" : artist),
|
||||
maxWidthChars: 28,
|
||||
truncate: true,
|
||||
} as Widget.LabelProps)
|
||||
]
|
||||
} as Widget.BoxProps),
|
||||
new Widget.Box({
|
||||
className: "progress",
|
||||
hexpand: true,
|
||||
visible: bind(players[0], "canSeek"),
|
||||
children: [
|
||||
new Widget.Slider({
|
||||
min: 0,
|
||||
hexpand: true,
|
||||
max: bind(players[0], "length").as((length: number) =>
|
||||
Math.floor(length)),
|
||||
value: bind(players[0], "position").as((position: number) =>
|
||||
Math.floor(position)),
|
||||
onDragged: (slider: Widget.Slider) => {
|
||||
if(dragTimer === undefined)
|
||||
dragTimer = timeout(600, () =>
|
||||
players[0].set_position(Math.round(slider.value)));
|
||||
else {
|
||||
dragTimer.cancel();
|
||||
dragTimer = timeout(600, () =>
|
||||
players[0].set_position(Math.round(slider.value)));
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
}),
|
||||
new Widget.CenterBox({
|
||||
className: "bottom",
|
||||
homogeneous: false,
|
||||
hexpand: true,
|
||||
startWidget: new Widget.Label({
|
||||
className: "elapsed",
|
||||
valign: Gtk.Align.START,
|
||||
halign: Gtk.Align.START,
|
||||
label: bind(players[0], "position").as((pos: number) => {
|
||||
const sec: number = Math.floor(pos % 60);
|
||||
return pos > 0 && players[0].length > 0 ?
|
||||
`${Math.floor(pos / 60)}:${sec < 10 ? "0" : ""}${sec}`
|
||||
: `0:00`;
|
||||
})
|
||||
} as Widget.LabelProps),
|
||||
centerWidget: new Widget.Box({
|
||||
className: "controls button-row",
|
||||
children: [
|
||||
new Widget.Button({
|
||||
className: "link nf",
|
||||
label: "",
|
||||
tooltipText: "Copy link to Clipboard",
|
||||
visible: bind(players[0], "metadata").as((_meta: GLib.HashTable) =>
|
||||
players[0].get_meta("xesam:url") === null),
|
||||
onClick: () => execAsync(`sh -c "wl-copy \\"$(playerctl metadata 'xesam:url')\\""`)
|
||||
} as Widget.ButtonProps),
|
||||
new Widget.Button({
|
||||
className: "shuffle nf",
|
||||
visible: bind(players[0], "shuffleStatus").as((shuffleStatus: AstalMpris.Shuffle) =>
|
||||
shuffleStatus !== AstalMpris.Shuffle.UNSUPPORTED),
|
||||
label: bind(players[0], "shuffleStatus").as((shuffleStatus: AstalMpris.Shuffle) =>
|
||||
shuffleStatus === AstalMpris.Shuffle.ON ? "" : ""),
|
||||
tooltipText: "Toggle Shuffle",
|
||||
onClick: () => players[0].shuffle()
|
||||
} as Widget.ButtonProps),
|
||||
new Widget.Button({
|
||||
className: "previous nf",
|
||||
label: "",
|
||||
tooltipText: "Previous",
|
||||
onClick: () => players[0].canGoPrevious && players[0].previous()
|
||||
} as Widget.ButtonProps),
|
||||
new Widget.Button({
|
||||
className: "pause nf",
|
||||
tooltipText: bind(players[0], "playback_status").as((status: AstalMpris.PlaybackStatus) =>
|
||||
status === AstalMpris.PlaybackStatus.PLAYING ? "Pause" : "Play"),
|
||||
label: bind(players[0], "playbackStatus").as((status: AstalMpris.PlaybackStatus) =>
|
||||
status === AstalMpris.PlaybackStatus.PLAYING ? "" : ""),
|
||||
onClick: () => {
|
||||
players[0].playbackStatus === AstalMpris.PlaybackStatus.PAUSED ?
|
||||
players[0].play()
|
||||
:
|
||||
players[0].pause()
|
||||
}
|
||||
} as Widget.ButtonProps),
|
||||
new Widget.Button({
|
||||
className: "next nf",
|
||||
label: "",
|
||||
tooltipText: "Next",
|
||||
onClick: () => players[0].canGoNext && players[0].next()
|
||||
} as Widget.ButtonProps),
|
||||
new Widget.Button({
|
||||
className: "repeat nf",
|
||||
visible: bind(players[0], "loopStatus").as((loopStatus: AstalMpris.Loop) =>
|
||||
loopStatus !== AstalMpris.Loop.UNSUPPORTED),
|
||||
label: bind(players[0], "loopStatus").as((loopStatus: AstalMpris.Loop) => {
|
||||
switch(loopStatus) {
|
||||
case AstalMpris.Loop.TRACK: return "";
|
||||
case AstalMpris.Loop.PLAYLIST: return "";
|
||||
default: return "";
|
||||
}
|
||||
}),
|
||||
tooltipText: "Toggle Loop",
|
||||
onClick: () => players[0].loop()
|
||||
} as Widget.ButtonProps)
|
||||
new Widget.Label({
|
||||
className: "title",
|
||||
tooltipText: bind(players[0], "title").as((title: string) => !title ? "No Title" : title),
|
||||
label: bind(players[0], "title").as((title: string) => !title ? "No Title" : title),
|
||||
truncate: true,
|
||||
maxWidthChars: 25,
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Label({
|
||||
className: "artist",
|
||||
tooltipText: bind(players[0], "artist").as((artist: string) => !artist ? "No Artist" : artist),
|
||||
label: bind(players[0], "artist").as((artist: string) => !artist ? "No Artist" : artist),
|
||||
maxWidthChars: 28,
|
||||
truncate: true,
|
||||
} as Widget.LabelProps)
|
||||
]
|
||||
} as Widget.BoxProps),
|
||||
endWidget: new Widget.Label({
|
||||
className: "length",
|
||||
valign: Gtk.Align.START,
|
||||
halign: Gtk.Align.END,
|
||||
label: bind(players[0], "length").as((len/* bananananananana */: number) => {
|
||||
const sec: number = Math.floor(len % 60);
|
||||
return len > 0 ?
|
||||
`${Math.floor(len / 60)}:${sec < 10 ? "0" : ""}${sec}`
|
||||
: "0:00";
|
||||
})
|
||||
} as Widget.LabelProps)
|
||||
})
|
||||
])
|
||||
} as Widget.BoxProps);
|
||||
new Widget.Box({
|
||||
className: "progress",
|
||||
hexpand: true,
|
||||
visible: bind(players[0], "canSeek"),
|
||||
children: [
|
||||
new Widget.Slider({
|
||||
min: 0,
|
||||
hexpand: true,
|
||||
max: bind(players[0], "length").as((length: number) =>
|
||||
Math.floor(length)),
|
||||
value: bind(players[0], "position").as((position: number) =>
|
||||
Math.floor(position)),
|
||||
onDragged: (slider: Widget.Slider) => {
|
||||
if(dragTimer === undefined)
|
||||
dragTimer = timeout(600, () =>
|
||||
players[0].set_position(Math.round(slider.value)));
|
||||
else {
|
||||
dragTimer.cancel();
|
||||
dragTimer = timeout(600, () =>
|
||||
players[0].set_position(Math.round(slider.value)));
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
}),
|
||||
new Widget.CenterBox({
|
||||
className: "bottom",
|
||||
homogeneous: false,
|
||||
hexpand: true,
|
||||
startWidget: new Widget.Label({
|
||||
className: "elapsed",
|
||||
valign: Gtk.Align.START,
|
||||
halign: Gtk.Align.START,
|
||||
label: bind(players[0], "position").as((pos: number) => {
|
||||
const sec: number = Math.floor(pos % 60);
|
||||
return pos > 0 && players[0].length > 0 ?
|
||||
`${Math.floor(pos / 60)}:${sec < 10 ? "0" : ""}${sec}`
|
||||
: `0:00`;
|
||||
})
|
||||
} as Widget.LabelProps),
|
||||
centerWidget: new Widget.Box({
|
||||
className: "controls button-row",
|
||||
children: [
|
||||
new Widget.Button({
|
||||
className: "link nf",
|
||||
label: "",
|
||||
tooltipText: "Copy link to Clipboard",
|
||||
visible: bind(players[0], "metadata").as((_meta: GLib.HashTable) =>
|
||||
players[0].get_meta("xesam:url") === null),
|
||||
onClick: () => execAsync(`sh -c "wl-copy \\"$(playerctl metadata 'xesam:url')\\""`)
|
||||
} as Widget.ButtonProps),
|
||||
new Widget.Button({
|
||||
className: "shuffle nf",
|
||||
visible: bind(players[0], "shuffleStatus").as((shuffleStatus: AstalMpris.Shuffle) =>
|
||||
shuffleStatus !== AstalMpris.Shuffle.UNSUPPORTED),
|
||||
label: bind(players[0], "shuffleStatus").as((shuffleStatus: AstalMpris.Shuffle) =>
|
||||
shuffleStatus === AstalMpris.Shuffle.ON ? "" : ""),
|
||||
tooltipText: "Toggle Shuffle",
|
||||
onClick: () => players[0].shuffle()
|
||||
} as Widget.ButtonProps),
|
||||
new Widget.Button({
|
||||
className: "previous nf",
|
||||
label: "",
|
||||
tooltipText: "Previous",
|
||||
onClick: () => players[0].canGoPrevious && players[0].previous()
|
||||
} as Widget.ButtonProps),
|
||||
new Widget.Button({
|
||||
className: "pause nf",
|
||||
tooltipText: bind(players[0], "playback_status").as((status: AstalMpris.PlaybackStatus) =>
|
||||
status === AstalMpris.PlaybackStatus.PLAYING ? "Pause" : "Play"),
|
||||
label: bind(players[0], "playbackStatus").as((status: AstalMpris.PlaybackStatus) =>
|
||||
status === AstalMpris.PlaybackStatus.PLAYING ? "" : ""),
|
||||
onClick: () => {
|
||||
players[0].playbackStatus === AstalMpris.PlaybackStatus.PAUSED ?
|
||||
players[0].play()
|
||||
:
|
||||
players[0].pause()
|
||||
}
|
||||
} as Widget.ButtonProps),
|
||||
new Widget.Button({
|
||||
className: "next nf",
|
||||
label: "",
|
||||
tooltipText: "Next",
|
||||
onClick: () => players[0].canGoNext && players[0].next()
|
||||
} as Widget.ButtonProps),
|
||||
new Widget.Button({
|
||||
className: "repeat nf",
|
||||
visible: bind(players[0], "loopStatus").as((loopStatus: AstalMpris.Loop) =>
|
||||
loopStatus !== AstalMpris.Loop.UNSUPPORTED),
|
||||
label: bind(players[0], "loopStatus").as((loopStatus: AstalMpris.Loop) => {
|
||||
switch(loopStatus) {
|
||||
case AstalMpris.Loop.TRACK: return "";
|
||||
case AstalMpris.Loop.PLAYLIST: return "";
|
||||
default: return "";
|
||||
}
|
||||
}),
|
||||
tooltipText: "Toggle Loop",
|
||||
onClick: () => players[0].loop()
|
||||
} as Widget.ButtonProps)
|
||||
]
|
||||
} as Widget.BoxProps),
|
||||
endWidget: new Widget.Label({
|
||||
className: "length",
|
||||
valign: Gtk.Align.START,
|
||||
halign: Gtk.Align.END,
|
||||
label: bind(players[0], "length").as((len/* bananananananana */: number) => {
|
||||
const sec: number = Math.floor(len % 60);
|
||||
return len > 0 ?
|
||||
`${Math.floor(len / 60)}:${sec < 10 ? "0" : ""}${sec}`
|
||||
: "0:00";
|
||||
})
|
||||
} as Widget.LabelProps)
|
||||
})
|
||||
])
|
||||
} as Widget.BoxProps);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
||||
+6
-5
@@ -1,15 +1,16 @@
|
||||
import { bind } from "astal";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
import AstalNotifd from "gi://AstalNotifd";
|
||||
import { Notifications } from "../../scripts/notification-handler";
|
||||
import { Notifications } from "../../scripts/notifications";
|
||||
|
||||
export const NotificationHistory: Gtk.Widget = new Widget.Scrollable({
|
||||
export const NotifHistory: Gtk.Widget = new Widget.Scrollable({
|
||||
hscroll: Gtk.PolicyType.NEVER,
|
||||
vscroll: Gtk.PolicyType.AUTOMATIC,
|
||||
expand: true,
|
||||
child: new Widget.Box({
|
||||
className: "notifications",
|
||||
children: bind(Notifications, "notificationHistory").as((history: Array<AstalNotifd.Notification>) =>
|
||||
history && history.length > 0 && history.map((notification: AstalNotifd.Notification) =>
|
||||
children: bind(Notifications.getDefault(), "history").as((history: Array<AstalNotifd.Notification>) =>
|
||||
history.map((notification: AstalNotifd.Notification) =>
|
||||
new Widget.Box({
|
||||
className: "notification",
|
||||
hexpand: true,
|
||||
@@ -35,7 +36,7 @@ export const NotificationHistory: Gtk.Widget = new Widget.Scrollable({
|
||||
new Widget.Button({
|
||||
className: "remove",
|
||||
label: "",
|
||||
onClick: () => Notifications.removeFromNotificationHistory(notification.id)
|
||||
onClick: () => Notifications.getDefault().removeHistory(notification.id)
|
||||
} as Widget.ButtonProps)
|
||||
]
|
||||
} as Widget.BoxProps),
|
||||
@@ -1,40 +1,45 @@
|
||||
import { timeout, Variable } from "astal";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
import { Page } from "./pages/Page";
|
||||
|
||||
const empty = new Widget.Box();
|
||||
const page = new Variable<Gtk.Widget>(empty);
|
||||
let connectionId: (number|undefined);
|
||||
const currentPage = new Variable<Page|undefined>(undefined);
|
||||
|
||||
export const PagesWidget: Widget.Revealer = new Widget.Revealer({
|
||||
revealChild: false,
|
||||
className: "pages",
|
||||
transitionType: Gtk.RevealerTransitionType.SLIDE_DOWN,
|
||||
transitionDuration: 250,
|
||||
child: page()
|
||||
transitionDuration: 360,
|
||||
child: currentPage((page: (Page|undefined)) =>
|
||||
!page ? new Widget.Box() : page.getPage())
|
||||
} as Widget.RevealerProps);
|
||||
|
||||
export function showPages(child: Gtk.Widget, onShow?: (self: Widget.Revealer) => void): void {
|
||||
page.set(child);
|
||||
export function showPages(page: Page): void {
|
||||
currentPage.set(page);
|
||||
PagesWidget.set_reveal_child(true);
|
||||
connectionId !== undefined && PagesWidget.disconnect(connectionId);
|
||||
connectionId = PagesWidget.connect("show", (_) =>
|
||||
onShow && onShow(_));
|
||||
page.props.onOpen && page.props.onOpen();
|
||||
}
|
||||
|
||||
export function getPage(): (Gtk.Widget|null) {
|
||||
return page.get();
|
||||
export function getPage(): (Page|undefined) {
|
||||
return currentPage.get();
|
||||
}
|
||||
|
||||
export function togglePage(page: Gtk.Widget): void {
|
||||
PagesWidget.revealChild ?
|
||||
hidePages()
|
||||
: showPages(page);
|
||||
export function togglePage(page: Page): void {
|
||||
if(!PagesWidget.revealChild) {
|
||||
showPages(page);
|
||||
return;
|
||||
}
|
||||
|
||||
hidePages();
|
||||
}
|
||||
|
||||
export function hidePages(onHide?: () => void) {
|
||||
export function hidePages() {
|
||||
PagesWidget.set_reveal_child(false);
|
||||
console.log("heyyyyy");
|
||||
timeout(300, () => {
|
||||
page.set(empty);
|
||||
onHide && onHide();
|
||||
if(!currentPage.get()) return;
|
||||
|
||||
timeout(500, () => {
|
||||
if(currentPage.get() && currentPage.get()?.props.onClose)
|
||||
currentPage.get()!.props.onClose!();
|
||||
|
||||
currentPage.set(undefined);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export const Sliders: Gtk.Widget = new Widget.Box({
|
||||
]
|
||||
} as Widget.BoxProps),
|
||||
/*new Widget.Box({
|
||||
className: "brightness screen",
|
||||
className: "brightness",
|
||||
children: [
|
||||
new Widget.Label({
|
||||
className: "icon nf",
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
import { TileInternet } from "./tiles/Internet";
|
||||
import { TileNetwork } from "./tiles/Network";
|
||||
import { TileBluetooth } from "./tiles/Bluetooth";
|
||||
import { TileRecording } from "./tiles/Recording";
|
||||
|
||||
export const tileList: Array<any> = [
|
||||
TileInternet,
|
||||
TileBluetooth
|
||||
TileNetwork,
|
||||
TileBluetooth,
|
||||
TileRecording
|
||||
];
|
||||
|
||||
export function TilesWidget(): Gtk.Widget {
|
||||
|
||||
@@ -1,98 +1,108 @@
|
||||
import { bind, timeout } from "astal";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
import AstalBluetooth from "gi://AstalBluetooth?version=0.1";
|
||||
import AstalBluetooth from "gi://AstalBluetooth";
|
||||
import { Page } from "./Page";
|
||||
import { Separator, SeparatorProps } from "../../Separator";
|
||||
|
||||
let watchingDevices: boolean = false;
|
||||
|
||||
export function BluetoothPage() {
|
||||
watchNewDevices();
|
||||
|
||||
return new Widget.Box({
|
||||
className: "page bluetooth container",
|
||||
export const BluetoothPage: Page = new Page({
|
||||
title: "Bluetooth Devices",
|
||||
description: "Manage your Bluetooth devices and add new ones.",
|
||||
className: "bluetooth",
|
||||
setup: () => {
|
||||
watchingDevices = true;
|
||||
watchNewDevices();
|
||||
},
|
||||
onClose: stopBluetoothDevicesWatch,
|
||||
pageChild: () => new Widget.Box({
|
||||
className: "connections",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
expand: true,
|
||||
hexpand: true,
|
||||
children: [
|
||||
new Widget.Box({
|
||||
className: "header",
|
||||
children: [
|
||||
new Widget.Label({
|
||||
hexpand: true,
|
||||
className: "title",
|
||||
label: "Bluetooth",
|
||||
halign: Gtk.Align.START
|
||||
} as Widget.LabelProps),
|
||||
]
|
||||
} as Widget.BoxProps),
|
||||
new Widget.Box({
|
||||
className: "connections",
|
||||
className: "paired",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
expand: true,
|
||||
children: bind(AstalBluetooth.get_default(), "devices").as((devices: Array<AstalBluetooth.Device>) =>
|
||||
devices.filter((device: AstalBluetooth.Device) => device.connected
|
||||
).map((dev: AstalBluetooth.Device) =>
|
||||
new Widget.Button({
|
||||
onClick: () => dev.connected ? dev.disconnect_device(null) : dev.connect_device(null),
|
||||
child: new Widget.Box({
|
||||
className: "device",
|
||||
orientation: Gtk.Orientation.HORIZONTAL,
|
||||
expand: true,
|
||||
children: [
|
||||
new Widget.Label({
|
||||
className: "alias",
|
||||
halign: Gtk.Align.START,
|
||||
label: bind(dev, "alias")
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Label({
|
||||
className: "battery",
|
||||
halign: Gtk.Align.END,
|
||||
label: bind(dev, "batteryPercentage").as(String)
|
||||
} as Widget.LabelProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
} as Widget.ButtonProps)).concat(
|
||||
devices.filter((device: AstalBluetooth.Device) => !device.connected
|
||||
).map((dev: AstalBluetooth.Device) =>
|
||||
new Widget.Button({
|
||||
onClick: () => dev.connect_device(() => {}),
|
||||
child: new Widget.Box({
|
||||
className: "device",
|
||||
orientation: Gtk.Orientation.HORIZONTAL,
|
||||
expand: true,
|
||||
children: [
|
||||
new Widget.Label({
|
||||
className: "alias",
|
||||
halign: Gtk.Align.START,
|
||||
label: bind(dev, "alias")
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Label({
|
||||
className: "battery",
|
||||
halign: Gtk.Align.END,
|
||||
label: bind(dev, "batteryPercentage").as(String)
|
||||
} as Widget.LabelProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
} as Widget.ButtonProps))
|
||||
devices.filter((device: AstalBluetooth.Device) => device.connected || device.paired)
|
||||
.map((dev: AstalBluetooth.Device) =>
|
||||
DeviceWidget(dev)
|
||||
)
|
||||
)
|
||||
} as Widget.BoxProps),
|
||||
Separator({
|
||||
size: .5,
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
alpha: .7
|
||||
} as SeparatorProps),
|
||||
new Widget.Box({
|
||||
className: "discovered",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
children: bind(AstalBluetooth.get_default(), "devices").as((devices: Array<AstalBluetooth.Device>) =>
|
||||
devices.filter((device: AstalBluetooth.Device) => !device.connected && !device.paired)
|
||||
.map((dev: AstalBluetooth.Device) =>
|
||||
DeviceWidget(dev)
|
||||
)
|
||||
)
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
})
|
||||
|
||||
function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget {
|
||||
return new Widget.Button({
|
||||
onClick: () => dev.connected ? dev.disconnect_device(null) : dev.connect_device(null),
|
||||
className: bind(dev, "connected").as((connected) => connected ? "connected" : ""),
|
||||
child: new Widget.Box({
|
||||
className: "device",
|
||||
orientation: Gtk.Orientation.HORIZONTAL,
|
||||
expand: true,
|
||||
children: [
|
||||
new Widget.Icon({
|
||||
className: "icon",
|
||||
icon: bind(dev, "icon").as((icon: string) =>
|
||||
icon ? icon : "bluetooth-active-symbolic"),
|
||||
css: "font-size: 20px; margin-right: 6px;"
|
||||
} as Widget.IconProps),
|
||||
new Widget.Label({
|
||||
className: "alias",
|
||||
halign: Gtk.Align.START,
|
||||
hexpand: true,
|
||||
label: bind(dev, "alias")
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Label({
|
||||
className: "battery",
|
||||
halign: Gtk.Align.END,
|
||||
visible: bind(dev, "batteryPercentage").as((bat: number) =>
|
||||
bat <= -1 ? false : true),
|
||||
label: bind(dev, "batteryPercentage").as((bat: number) =>
|
||||
` ${Math.floor(bat * 100)}%`)
|
||||
} as Widget.LabelProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
} as Widget.ButtonProps)
|
||||
}
|
||||
|
||||
function watchNewDevices(): void {
|
||||
if(watchingDevices) {
|
||||
timeout(10000, () => {
|
||||
reloadDevicesList();
|
||||
timeout(8000, () => {
|
||||
reloadBluetoothDevicesList();
|
||||
watchNewDevices();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
stopBluetoothDevicesWatch();
|
||||
}
|
||||
|
||||
function stopDeviceWatch(): void {
|
||||
export function stopBluetoothDevicesWatch(): void {
|
||||
watchingDevices = false;
|
||||
AstalBluetooth.get_default().adapter.discovering &&
|
||||
AstalBluetooth.get_default().adapter.stop_discovery();
|
||||
}
|
||||
|
||||
function reloadDevicesList(): void {
|
||||
export function reloadBluetoothDevicesList(): void {
|
||||
AstalBluetooth.get_default().adapter.start_discovery();
|
||||
timeout(5000, () => AstalBluetooth.get_default().adapter.stop_discovery());
|
||||
timeout(4000, () => AstalBluetooth.get_default().adapter.stop_discovery());
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { bind } from "astal";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
import AstalBluetooth from "gi://AstalBluetooth";
|
||||
|
||||
export function WifiPage() {
|
||||
return new Widget.Box({
|
||||
className: "page bluetooth container",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
hexpand: true,
|
||||
children: [
|
||||
new Widget.Box({
|
||||
className: "connections",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
expand: true,
|
||||
children: bind(AstalBluetooth.get_default(), "devices").as((devices: Array<AstalBluetooth.Device>) =>
|
||||
devices && devices.filter((device: AstalBluetooth.Device) => device.connected
|
||||
).map((dev: AstalBluetooth.Device) =>
|
||||
new Widget.Box({
|
||||
className: "device",
|
||||
orientation: Gtk.Orientation.HORIZONTAL,
|
||||
expand: true,
|
||||
children: [
|
||||
new Widget.Label({
|
||||
className: "alias",
|
||||
halign: Gtk.Align.START,
|
||||
label: bind(dev, "alias")
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Label({
|
||||
className: "battery",
|
||||
halign: Gtk.Align.END,
|
||||
} as Widget.LabelProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
))
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Widget } from "astal/gtk3";
|
||||
import { Page } from "./Page";
|
||||
import AstalNetwork from "gi://AstalNetwork";
|
||||
import { bind } from "astal";
|
||||
|
||||
export const PageNetwork = new Page({
|
||||
title: "Network",
|
||||
className: "network",
|
||||
headerButtons: () => [
|
||||
new Widget.Button({
|
||||
className: "reload nf",
|
||||
label: "",
|
||||
visible: bind(AstalNetwork.get_default(), "primary").as(
|
||||
(primary: AstalNetwork.Primary) => primary === AstalNetwork.Primary.WIFI
|
||||
),
|
||||
tooltipText: "Re-scan connections",
|
||||
onClick: () => AstalNetwork.get_default().wifi.scan()
|
||||
} as Widget.ButtonProps)
|
||||
],
|
||||
pageChild: () => new Widget.Box({
|
||||
} as Widget.BoxProps)
|
||||
});
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Binding, GObject, register } from "astal";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
|
||||
export type PageProps = {
|
||||
setup?: () => void;
|
||||
onClose?: () => void;
|
||||
onOpen?: () => void;
|
||||
className?: string | Binding<string | undefined>;
|
||||
title: string | Binding<string | undefined>;
|
||||
description?: string | Binding<string | undefined>;
|
||||
headerButtons?: () => Array<Gtk.Widget>;
|
||||
pageChild: () => Gtk.Widget;
|
||||
};
|
||||
|
||||
@register({ GTypeName: "Page" })
|
||||
class Page extends GObject.Object {
|
||||
readonly #props: PageProps;
|
||||
|
||||
get props() { return this.#props; }
|
||||
|
||||
constructor(props: PageProps) {
|
||||
super();
|
||||
this.#props = props;
|
||||
}
|
||||
|
||||
public getHeaderButtons(): (Array<Gtk.Widget>|null) {
|
||||
return this.props.headerButtons ?
|
||||
this.props.headerButtons()
|
||||
: null;
|
||||
}
|
||||
|
||||
public getPage(): Gtk.Widget {
|
||||
return new Widget.Box({
|
||||
className: (this.props.className instanceof Binding) ?
|
||||
this.props.className.as((clsName: (string|undefined)) => `page ${ clsName || "" }`) : `page ${this.#props.className || ""}`,
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
hexpand: true,
|
||||
setup: this.props.setup,
|
||||
children: [
|
||||
new Widget.Box({
|
||||
className: "header",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
hexpand: true,
|
||||
children: [
|
||||
new Widget.Box({
|
||||
className: "title",
|
||||
children: [
|
||||
new Widget.Label({
|
||||
hexpand: true,
|
||||
className: "title",
|
||||
truncate: true,
|
||||
visible: (this.props.title instanceof Binding) ?
|
||||
this.props.title.as(Boolean)
|
||||
: (this.props.title ? true : false),
|
||||
label: this.props.title,
|
||||
halign: Gtk.Align.START
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Box({
|
||||
className: "button-row",
|
||||
visible: Boolean(this.getHeaderButtons()),
|
||||
children: this.getHeaderButtons() || undefined
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps),
|
||||
new Widget.Label({
|
||||
className: "description",
|
||||
hexpand: true,
|
||||
truncate: true,
|
||||
xalign: 0,
|
||||
visible: (this.props.description instanceof Binding) ?
|
||||
this.props.description.as(Boolean)
|
||||
: this.props.description ? true : false,
|
||||
label: this.props.description
|
||||
} as Widget.LabelProps),
|
||||
]
|
||||
} as Widget.BoxProps),
|
||||
new Widget.Box({
|
||||
className: "content",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
expand: true,
|
||||
setup: (_) => _.add(this.props.pageChild())
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps);
|
||||
}
|
||||
}
|
||||
|
||||
export { Page };
|
||||
@@ -1,4 +1,4 @@
|
||||
import { bind } from "astal";
|
||||
import { bind, Variable } from "astal";
|
||||
import { Tile, TileProps } from "./Tile";
|
||||
import AstalBluetooth from "gi://AstalBluetooth";
|
||||
import { togglePage } from "../Pages";
|
||||
@@ -6,16 +6,18 @@ import { BluetoothPage } from "../pages/Bluetooth";
|
||||
|
||||
export const TileBluetooth = Tile({
|
||||
title: "Bluetooth",
|
||||
description: bind(AstalBluetooth.get_default(), "devices").as((devices: Array<AstalBluetooth.Device>) => {
|
||||
const connected: Array<AstalBluetooth.Device> = devices.filter(
|
||||
(dev: AstalBluetooth.Device) => dev.connected);
|
||||
|
||||
return connected[0] ? connected[0].get_alias() : undefined;
|
||||
}),
|
||||
description: bind(AstalBluetooth.get_default(), "devices").as((devices: Array<AstalBluetooth.Device>) =>
|
||||
devices.filter((dev: AstalBluetooth.Device) => dev.connected)[0]?.get_alias()),
|
||||
onToggledOn: () => AstalBluetooth.get_default().adapter.set_powered(true),
|
||||
onToggledOff: () => AstalBluetooth.get_default().adapter.set_powered(false),
|
||||
onClickMore: () => togglePage(BluetoothPage()),
|
||||
icon: "",
|
||||
onClickMore: () => togglePage(BluetoothPage),
|
||||
icon: Variable.derive([
|
||||
bind(AstalBluetooth.get_default().adapter, "powered"),
|
||||
bind(AstalBluetooth.get_default(), "isConnected")
|
||||
],
|
||||
(powered: boolean, isConnected: boolean) =>
|
||||
powered ? ( isConnected ? "" : "" ) : ""
|
||||
)(),
|
||||
iconSize: 16,
|
||||
toggleState: bind(AstalBluetooth.get_default().adapter, "powered")
|
||||
} as TileProps);
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { bind, execAsync } from "astal";
|
||||
import { Tile, TileProps } from "./Tile";
|
||||
import AstalNetwork from "gi://AstalNetwork";
|
||||
import { Widget } from "astal/gtk3";
|
||||
|
||||
export const TileInternet = new Widget.Box({
|
||||
child: bind(AstalNetwork.get_default(), "wired").as((wired: AstalNetwork.Wired) => Tile({
|
||||
title: "Wired",
|
||||
description: bind(wired, "internet").as((internet: AstalNetwork.Internet) => {
|
||||
switch(internet) {
|
||||
case AstalNetwork.Internet.CONNECTED:
|
||||
return "Connected";
|
||||
case AstalNetwork.Internet.DISCONNECTED:
|
||||
return "Disconnected";
|
||||
case AstalNetwork.Internet.CONNECTING:
|
||||
return "Connecting...";
|
||||
}
|
||||
}),
|
||||
onToggledOn: () => execAsync("nmcli n on"),
|
||||
onToggledOff: () => execAsync("nmcli n off"),
|
||||
icon: "",
|
||||
iconSize: 16,
|
||||
toggleState: bind(wired, "internet").as((internet: AstalNetwork.Internet) =>
|
||||
internet === AstalNetwork.Internet.CONNECTING || internet === AstalNetwork.Internet.CONNECTED)
|
||||
} as TileProps))
|
||||
} as Widget.BoxProps);
|
||||
@@ -0,0 +1,86 @@
|
||||
import { bind, execAsync, Variable } from "astal";
|
||||
import { Tile, TileProps } from "./Tile";
|
||||
import AstalNetwork from "gi://AstalNetwork";
|
||||
import { Widget } from "astal/gtk3";
|
||||
import { showPages, togglePage } from "../Pages";
|
||||
import { PageNetwork } from "../pages/Network";
|
||||
|
||||
export const TileNetwork = new Widget.Box({
|
||||
child: Variable.derive([
|
||||
bind(AstalNetwork.get_default(), "primary"),
|
||||
bind(AstalNetwork.get_default(), "wired"),
|
||||
bind(AstalNetwork.get_default(), "wifi")
|
||||
],
|
||||
(primary: AstalNetwork.Primary, wired: AstalNetwork.Wired, wifi: AstalNetwork.Wifi) => {
|
||||
if(primary === AstalNetwork.Primary.WIFI) {
|
||||
return Tile({
|
||||
title: "Wireless",
|
||||
description: Variable.derive(
|
||||
[ bind(wifi, "ssid"), bind(wifi, "internet") ],
|
||||
(ssid: string, internet: AstalNetwork.Internet) =>
|
||||
ssid ? ssid : (() => {
|
||||
switch(internet) {
|
||||
case AstalNetwork.Internet.CONNECTED:
|
||||
return "Connected";
|
||||
case AstalNetwork.Internet.DISCONNECTED:
|
||||
return "Disconnected";
|
||||
case AstalNetwork.Internet.CONNECTING:
|
||||
return "Connecting...";
|
||||
}
|
||||
})()
|
||||
)(),
|
||||
onToggledOn: () => wifi.set_enabled(true),
|
||||
onToggledOff: () => wifi.set_enabled(false),
|
||||
onClickMore: () => togglePage(PageNetwork),
|
||||
icon: "",
|
||||
iconSize: 16,
|
||||
toggleState: bind(wifi, "enabled")
|
||||
} as TileProps);
|
||||
|
||||
} else if(primary === AstalNetwork.Primary.WIRED) {
|
||||
return Tile({
|
||||
title: "Wired",
|
||||
description: bind(wired, "internet").as((internet: AstalNetwork.Internet) => {
|
||||
switch(internet) {
|
||||
case AstalNetwork.Internet.CONNECTED:
|
||||
return "Connected";
|
||||
case AstalNetwork.Internet.DISCONNECTED:
|
||||
return "Disconnected";
|
||||
case AstalNetwork.Internet.CONNECTING:
|
||||
return "Connecting...";
|
||||
}
|
||||
}),
|
||||
onToggledOn: () => execAsync("nmcli n on"),
|
||||
onToggledOff: () => execAsync("nmcli n off"),
|
||||
onClickMore: () => togglePage(PageNetwork),
|
||||
icon: bind(wired, "internet").as((internet: AstalNetwork.Internet) => {
|
||||
switch(internet) {
|
||||
case AstalNetwork.Internet.CONNECTED:
|
||||
return '';
|
||||
case AstalNetwork.Internet.DISCONNECTED:
|
||||
return '';
|
||||
}
|
||||
|
||||
return "";
|
||||
}),
|
||||
iconSize: 16,
|
||||
toggleState: bind(wired, "internet").as((internet: AstalNetwork.Internet) =>
|
||||
internet === AstalNetwork.Internet.CONNECTING
|
||||
|| internet === AstalNetwork.Internet.CONNECTED
|
||||
)
|
||||
} as TileProps);
|
||||
}
|
||||
|
||||
return Tile({
|
||||
title: "Network",
|
||||
description: "Disconnected",
|
||||
onToggledOn: () => execAsync("nmcli n on"),
|
||||
onToggledOff: () => execAsync("nmcli n off"),
|
||||
onClickMore: () => togglePage(PageNetwork),
|
||||
icon: "",
|
||||
iconSize: 16,
|
||||
toggleState: bind(wired, "internet").as((internet: AstalNetwork.Internet) =>
|
||||
internet === AstalNetwork.Internet.CONNECTING || internet === AstalNetwork.Internet.CONNECTED)
|
||||
} as TileProps);
|
||||
})()
|
||||
} as Widget.BoxProps);
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Tile, TileProps } from "./Tile";
|
||||
import { Recording } from "../../../scripts/recording";
|
||||
import { bind } from "astal";
|
||||
|
||||
export const TileRecording = Tile({
|
||||
title: "Screen Recording",
|
||||
description: bind(Recording.getDefault(), "recording").as(
|
||||
(isRecording: boolean) => isRecording ?
|
||||
"Recording {time}"
|
||||
: "Start a Screen Record"
|
||||
),
|
||||
icon: "",
|
||||
onToggledOff: () => Recording.getDefault().stopRecording(),
|
||||
onToggledOn: () => Recording.getDefault().startRecording(),
|
||||
iconSize: 16,
|
||||
toggleState: bind(Recording.getDefault(), "recording"),
|
||||
} as TileProps);
|
||||
@@ -55,23 +55,38 @@ export function Tile(props: TileProps): Widget.EventBox {
|
||||
new Widget.Label({
|
||||
className: "icon nf",
|
||||
label: props.icon || "icon",
|
||||
css: `.icon { font-size: ${props.iconSize || "12px"} }`
|
||||
css: `label { font-size: ${props.iconSize || "12"}px; }`
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Box({
|
||||
className: "text",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
vexpand: true,
|
||||
hexpand: true,
|
||||
valign: Gtk.Align.CENTER,
|
||||
children: [
|
||||
new Widget.Label({
|
||||
className: "title",
|
||||
xalign: 0,
|
||||
halign: Gtk.Align.START,
|
||||
truncate: true,
|
||||
label: props.title
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Label({
|
||||
className: "description",
|
||||
visible: props.description,
|
||||
visible: Boolean(props.description),
|
||||
setup: (label: Widget.Label) => {
|
||||
if(props.description instanceof Binding) {
|
||||
const sub = props.description.subscribe((value) => {
|
||||
label.set_visible(Boolean(value));
|
||||
});
|
||||
|
||||
const destroyId = label.connect("destroy-event", () => {
|
||||
label.disconnect(destroyId);
|
||||
sub();
|
||||
});
|
||||
}
|
||||
},
|
||||
halign: Gtk.Align.START,
|
||||
truncate: true,
|
||||
xalign: 0,
|
||||
label: props.description
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { register } from "astal";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
import { closeRunner } from "../../window/Runner";
|
||||
|
||||
export { ResultWidget, ResultWidgetProps };
|
||||
|
||||
type ResultWidgetProps = {
|
||||
icon?: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
closeOnClick?: boolean;
|
||||
setup?: () => void;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
@register({ GTypeName: "ResultWidget" })
|
||||
class ResultWidget extends Widget.EventBox {
|
||||
private readonly connections: Array<number>;
|
||||
public readonly onClick: ((() => void)|undefined);
|
||||
public readonly icon: (string|undefined);
|
||||
public readonly setup: ((() => void)|undefined);
|
||||
public readonly closeOnClick: boolean = true;
|
||||
|
||||
|
||||
constructor(props: ResultWidgetProps) {
|
||||
super();
|
||||
if(props.icon)
|
||||
this.icon = props.icon;
|
||||
if(props.onClick)
|
||||
this.onClick = props.onClick;
|
||||
if(props.setup)
|
||||
this.setup = props.setup;
|
||||
if(props.closeOnClick !== undefined)
|
||||
this.closeOnClick = props.closeOnClick;
|
||||
|
||||
this.connections = [
|
||||
this.connect("click", () => {
|
||||
this.onClick && this.onClick();
|
||||
this.closeOnClick && closeRunner();
|
||||
}),
|
||||
|
||||
this.connect("destroy-event", () => this.connections.map((id: number) =>
|
||||
this.disconnect(id)))
|
||||
];
|
||||
|
||||
this.add(new Widget.Box({
|
||||
className: "result",
|
||||
hexpand: true,
|
||||
children: [
|
||||
new Widget.Icon({
|
||||
visible: Boolean(props.icon),
|
||||
icon: props.icon || "image-missing"
|
||||
} as Widget.IconProps),
|
||||
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: Boolean(props.description),
|
||||
truncate: true,
|
||||
xalign: 0,
|
||||
label: props.description || ""
|
||||
} as Widget.LabelProps)
|
||||
]
|
||||
} as Widget.BoxProps),
|
||||
]
|
||||
} as Widget.BoxProps));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user