ags: finish center-window widget with big-media and calendar

This commit is contained in:
retrozinndev
2025-02-16 19:29:03 -03:00
parent 1e6b3bcbe3
commit 23d3b271b4
19 changed files with 394 additions and 181 deletions
+51 -44
View File
@@ -1,29 +1,30 @@
import AstalNotifd from "gi://AstalNotifd";
import { timeout } from "astal/time";
import { Connectable } from "astal/binding";
import { GObject, register, signal } from "astal";
import { Subscribable } from "astal/binding";
import { GObject, property, register, Variable } from "astal";
import { Windows } from "../windows";
import { FloatingNotifications } from "../window/FloatingNotifications";
@register({ GTypeName: "Notifications" })
class NotificationsClass extends GObject.Object implements Connectable {
class NotificationsClass extends GObject.Object implements Subscribable {
private static instance: NotificationsClass;
@property(AstalNotifd.Notifd)
private notifd: AstalNotifd.Notifd;
public notifications: Array<AstalNotifd.Notification> = [];
@property(Boolean)
private doNotDisturb: boolean = false;
@property()
public notificationHistory: Array<AstalNotifd.Notification> = [];
@signal(AstalNotifd.Notification)
declare notificationAdded: (added: AstalNotifd.Notification) => void;
@signal(Number)
declare notificationRemoved: (id: number) => void;
@property()
public notifications: Variable<Array<AstalNotifd.Notification>> = new Variable<Array<AstalNotifd.Notification>>([]);
public static getDefault(): NotificationsClass {
if(!NotificationsClass.instance) {
NotificationsClass.instance = new NotificationsClass();
this.instance._init();
}
return NotificationsClass.instance;
@@ -36,13 +37,28 @@ class NotificationsClass extends GObject.Object implements Connectable {
dontDisturb: false
} as AstalNotifd.Notifd.ConstructorProps);
this.getNotifd().connect("notified", (_source: AstalNotifd.Notifd, id: number, _replaced: boolean) => {
this.addNotification(this.getNotifd().get_notification(id));
this.getNotifd().connect("notified", (daemon: AstalNotifd.Notifd, id: number) => {
const notification: (AstalNotifd.Notification|null) = daemon.get_notification(id);
if(!notification) {
console.log("[LOG] Notification is null, ignoring");
return;
}
if(!this.doNotDisturb) {
this.handleNotification(notification);
return;
}
this.addHistory(notification);
});
}
public addNotification(notification: AstalNotifd.Notification) {
this.prependArray(this.notifications, this.getNotifd().get_notification(notification.id));
public handleNotification(notification: AstalNotifd.Notification): void {
Windows.open(FloatingNotifications);
let tmpArray = this.notifications.get().reverse();
tmpArray.push(notification);
this.notifications.set(tmpArray.reverse());
// default timeout if undefined
let notificationTimeout = 4000;
@@ -56,47 +72,38 @@ class NotificationsClass extends GObject.Object implements Connectable {
break;
}
notification.urgency !== AstalNotifd.Urgency.CRITICAL ?
notification.urgency !== AstalNotifd.Urgency.CRITICAL &&
timeout(notificationTimeout, () => {
this.notifications.map((item: AstalNotifd.Notification) =>
item.id === notification.id && (() => {
this.removeNotification(notification.id);
this.addToNotificationHistory(notification);
})())
})
: this.addToNotificationHistory(notification);
notification.dismiss();
this.notifications.set(this.notifications.get().filter((item) => item.id !== notification.id));
this.addHistory(notification);
});
this.emit("notification-added", notification);
}
public removeNotification(notificationId: number) {
if(this.notifications.length === 1)
Windows.close(Windows.getWindow("floating-notifications")!);
this.notifications = this.notifications.filter((notification: AstalNotifd.Notification) =>
notification.id !== notificationId);
this.emit("notification-removed", notificationId);
public addHistory(notification: AstalNotifd.Notification): void {
let tmpArray: Array<AstalNotifd.Notification> = this.notificationHistory.reverse()
.filter((item: AstalNotifd.Notification) => item.id !== notification.id);
tmpArray.push(notification);
this.notificationHistory = tmpArray.reverse();
}
public addToNotificationHistory(notification: AstalNotifd.Notification) {
this.prependArray(this.notificationHistory, notification);
}
public removeFromNotificationHistory(notificationId: number) {
public removeHistory(notification: AstalNotifd.Notification) {
this.notificationHistory = this.notificationHistory.filter((curNotification: AstalNotifd.Notification) =>
curNotification.id !== notificationId);
}
private prependArray(array: Array<any>, item: any): Array<any> {
let tmpArray = array.reverse();
tmpArray.push(item);
return tmpArray.reverse();
curNotification.id !== notification.id);
}
public getNotifd(): AstalNotifd.Notifd {
return this.notifd;
}
get() {
return this.notifications.get();
}
subscribe(callback: (list: Array<AstalNotifd.Notification>) => void) {
return this.notifications.subscribe(callback);
}
}
export const Notifications = new NotificationsClass();
View File
View File
+16
View File
@@ -2,11 +2,13 @@
@use "./style/wal";
@use "./style/mixins";
@use "./style/colors";
@use "./style/bar";
@use "./style/osd";
@use "./style/control-center";
@use "./style/center-window";
@use "./style/float-notifications";
* {
@@ -16,3 +18,17 @@
window * {
@include mixins.default-styles;
}
tooltip {
padding: 16px;
& > box {
padding: 7px 8px;
border-radius: 10px;
background: rgba(colors.$bg-primary, .98);
font-size: 13.1px;
font-weight: 500;
color: colors.$fg-primary;
box-shadow: 0 1px 4px 1px rgba(colors.$bg-primary, .6);
}
}
+17 -3
View File
@@ -91,6 +91,10 @@
}
}
.clock.open > button {
background-color: colors.$bg-primary;
}
.media-eventbox {
& > .media {
border-radius: 12px;
@@ -98,6 +102,10 @@
padding: 0 8px;
}
&:hover > .media {
box-shadow: inset 0 0 0 300px rgba(colors.$fg-primary, .2);
}
& .nf {
margin-right: 4px;
font-size: 14px;
@@ -105,10 +113,9 @@
& .media-controls {
transition: none;
padding-left: 6px;
padding: 0 6px;
border-top-right-radius: 12px;
border-bottom-right-radius: 12px;
background: linear-gradient(to left, colors.$bg-primary 45px, colors.$bg-primary);
& > button {
margin: 4px 1px;
@@ -124,6 +131,12 @@
& .media > box {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
padding: 0 6px;
}
& .media {
padding-left: 0;
padding-right: 0;
}
}
}
@@ -154,7 +167,8 @@
.audio {
@include mixins.reset-props;
&:hover > box {
&:hover > box,
&.open > box {
background: colors.$bg-primary;
}
+74 -26
View File
@@ -8,24 +8,8 @@
border-radius: 18px;
padding: 12px;
& .left {
& > .top {
padding-bottom: 10px;
& .time {
font-size: 28px;
font-weight: 800;
}
& .date {
font-size: 14px;
font-weight: 500;
color: colors.$fg-disabled;
}
}
& .big-media {
padding: 6px 16px;
padding: 6px;
& > box > .image {
background-size: cover;
@@ -51,27 +35,91 @@
}
}
& > .controls {
padding: 8px 0;
& slider {
background: transparent;
min-height: .6em;
}
& trough {
border-radius: 4px;
min-height: .6em;
}
& trough highlight {
border-radius: 4px;
min-height: .6em;
}
& .bottom {
& .controls {
margin-top: 5px;
& button {
padding: 7px;
& label {
font-size: 10px;
}
}
}
& .right {
& .elapsed,
& .length {
font-size: 12px;
color: colors.$fg-disabled;
}
}
}
& .left .top {
padding-bottom: 10px;
& .time {
font-size: 28px;
font-weight: 800;
}
& .date {
font-size: 14px;
font-weight: 500;
color: colors.$fg-disabled;
}
}
& .calendar-box {
padding: 5px;
& calendar {
border-radius: 6px;
padding: 2px;
$border-radius: 10px;
font-weight: 600;
padding-bottom: 2px;
&.view {
background: funs.toRGB(color.adjust($color: wal.$color1, $lightness: -35%));
background: colors.$bg-primary;
border-radius: $border-radius;
}
& header {
background: funs.toRGB(color.adjust($color: wal.$background, $lightness: -20%));
&.header {
background: colors.$bg-secondary;
border-top-left-radius: $border-radius;
border-top-right-radius: $border-radius;
padding: 4px;
}
&.button {
transition: 80ms linear;
border-radius: 6px;
&:hover {
background-color: colors.$bg-tertiary;
}
}
&:selected {
background: colors.$bg-secondary;
border-radius: 6px;
}
&.highlight {
background: transparent;
box-shadow: 0 2px 0 -1px rgba(colors.$bg-secondary, .5);
}
}
}
}
+41
View File
@@ -0,0 +1,41 @@
@use "./colors";
.floating-notifications-container {
padding: {
right: 6px;
top: 6px;
};
& > .notification {
background: colors.$bg-primary;
border-radius: 16px;
padding: 12px;
margin: 6px 0;
& > .top {
& .app-name {
font-size: 12px;
color: colors.$fg-disabled;
}
}
& .content {
& .image {
$size: 78px;
min-width: $size;
min-height: $size;
background-size: cover;
background-position: center 0;
margin: 6px;
}
}
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
}
-10
View File
@@ -69,16 +69,6 @@
}
}
& tooltip {
padding: 8px;
border-radius: 14px;
& label {
font-size: 14px;
color: colors.$fg-primary;
}
}
& trough {
background: funs.toRGB(color.adjust($color: wal.$color1, $lightness: -20%));
border-radius: 8px;
+20 -20
View File
@@ -1,26 +1,26 @@
// SCSS Variables
// Generated by 'wal'
$wallpaper: "/home/joaov/wallpapers/Miku, Rin and Luka Chibi.jpg";
$wallpaper: "/home/joaov/wallpapers/Miku Bush.jpg";
// Special
$background: #3d2217;
$foreground: #cec7c5;
$cursor: #cec7c5;
$background: #0f1b06;
$foreground: #c3c6c0;
$cursor: #c3c6c0;
// Colors
$color0: #3d2217;
$color1: #b38678;
$color2: #a4998a;
$color3: #b39e8a;
$color4: #a5a09b;
$color5: #aea299;
$color6: #b4aea2;
$color7: #a39c99;
$color8: #7f6f68;
$color9: #EFB3A1;
$color10: #DBCCB9;
$color11: #EFD3B9;
$color12: #DDD6CF;
$color13: #E8D8CD;
$color14: #F1E8D9;
$color15: #cec7c5;
$color0: #0f1b06;
$color1: #4e7278;
$color2: #5b7b94;
$color3: #71807c;
$color4: #7b9882;
$color5: #a3a881;
$color6: #778591;
$color7: #93988d;
$color8: #626d59;
$color9: #6898A1;
$color10: #7AA5C6;
$color11: #97ABA6;
$color12: #A5CBAE;
$color13: #DAE0AC;
$color14: #9FB2C2;
$color15: #c3c6c0;
+1
View File
@@ -0,0 +1 @@
//TODO
+5 -2
View File
@@ -1,3 +1,4 @@
import { Binding } from "astal";
import { Gtk, Widget } from "astal/gtk3";
export interface SeparatorProps {
@@ -6,21 +7,23 @@ export interface SeparatorProps {
cssColor?: string;
orientation?: Gtk.Orientation;
size?: number;
visible?: boolean | Binding<boolean | undefined>;
}
export function Separator(props: SeparatorProps) {
return new Widget.Box({
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 };
}
.separator-horizontal {
padding-right: ${props.size || 1 }px;
padding-bottom: ${props.size || 1 }px;
margin: 7px 4px;
}
.separator-vertical {
padding-bottom: ${props.size || 1 }px;
padding-right: ${props.size || 1 }px;
margin: 4px 7px;
}`,
} as Widget.BoxProps);
+3 -1
View File
@@ -2,12 +2,14 @@ import { bind, Process } from "astal";
import { Widget } from "astal/gtk3";
import AstalWp from "gi://AstalWp";
import { Wireplumber } from "../../scripts/volume";
import { ControlCenter } from "../../window/ControlCenter";
const wp = AstalWp.get_default();
export function Audio() {
return wp && new Widget.EventBox({
className: "audio",
className: bind(ControlCenter, "visible").as((visible: boolean) =>
visible ? "audio open" : "audio"),
onClick: () => Process.exec_async("astal toggle control-center", () => {}),
child: new Widget.Box({
children: [
+3 -2
View File
@@ -1,12 +1,13 @@
import { Widget } from "astal/gtk3";
import { getDateTime } from "../../scripts/time";
import { GLib } from "astal";
import { bind, GLib } from "astal";
import { Windows } from "../../windows";
import { CenterWindow } from "../../window/CenterWindow";
export function Clock(): JSX.Element {
return new Widget.Box({
className: "clock",
className: bind(CenterWindow, "visible").as((visible: boolean) =>
visible ? "clock open" : "clock"),
child: new Widget.Button({
onClick: () => Windows.toggle(CenterWindow),
label: getDateTime().as((dateTime: GLib.DateTime) => {
+7 -1
View File
@@ -2,6 +2,8 @@ import { bind, GLib, Process } from "astal";
import { Gtk, Widget } from "astal/gtk3";
import AstalMpris from "gi://AstalMpris";
import { Separator, SeparatorProps } from "../Separator";
import { CenterWindow } from "../../window/CenterWindow";
import { Windows } from "../../windows";
const mpris: AstalMpris.Mpris = AstalMpris.get_default();
@@ -66,7 +68,10 @@ export function Media(): Gtk.Widget {
const mediaWidget = new Widget.EventBox({
className: "media-eventbox",
visible: bind(mpris, "players").as((players: Array<AstalMpris.Player>) => players[0]).as(Boolean),
visible: bind(mpris, "players").as((players: Array<AstalMpris.Player>) => {
return players[0] && players[0].get_available() || CenterWindow.is_visible();
}),
onClick: () => Windows.toggle(CenterWindow),
child: new Widget.Box({
className: "media",
children: [
@@ -85,6 +90,7 @@ export function Media(): Gtk.Widget {
label: bind(players[0], "title").as((title: string) => title || "No Title")
} as Widget.LabelProps),
Separator({
orientation: Gtk.Orientation.VERTICAL,
size: 2,
cssColor: `rgb(180, 180, 180)`,
alpha: 1
+34 -6
View File
@@ -8,8 +8,11 @@ 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] ? [
players[0] && [
new Widget.Box({
halign: Gtk.Align.CENTER,
child: new Widget.Box({
@@ -72,16 +75,29 @@ export const BigMedia: Gtk.Widget = new Widget.Box({
})
]
}),
new Widget.Box({
className: "controls button-row",
new Widget.CenterBox({
className: "bottom",
homogeneous: false,
hexpand: true,
halign: Gtk.Align.CENTER,
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((_metadata: GLib.HashTable) =>
visible: bind(players[0], "metadata").as((_meta: GLib.HashTable) =>
players[0].get_meta("xesam:url") === null),
onClick: () => Process.exec(`wl-copy ${players[0].get_meta("xesam:url")?.get_string()[0]}`)
} as Widget.ButtonProps),
@@ -111,6 +127,18 @@ export const BigMedia: Gtk.Widget = new Widget.Box({
onClick: () => players[0].canGoNext && players[0].next()
} 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";
})
] : new Widget.Box({ className: "empty no-media" }))
} as Widget.LabelProps)
})
])
} as Widget.BoxProps);
+20 -10
View File
@@ -1,8 +1,9 @@
import { Astal, Gtk, Widget } from "astal/gtk3";
import { GLib } from "astal";
import { bind, GLib } from "astal";
import { getDateTime } from "../scripts/time";
import { BigMedia } from "../widget/center-window/BigMedia";
import { Separator, SeparatorProps } from "../widget/Separator";
export const CenterWindow: Widget.Window = new Widget.Window({
className: "center-window",
@@ -12,7 +13,6 @@ export const CenterWindow: Widget.Window = new Widget.Window({
layer: Astal.Layer.OVERLAY,
exclusivity: Astal.Exclusivity.NORMAL,
visible: false,
height_request: 400,
margin_top: 10,
anchor: Astal.WindowAnchor.TOP,
child: new Widget.Box({
@@ -21,7 +21,6 @@ export const CenterWindow: Widget.Window = new Widget.Window({
new Widget.Box({
className: "vertical left",
orientation: Gtk.Orientation.VERTICAL,
width_request: 300,
children: [
new Widget.Box({
className: "top",
@@ -36,18 +35,15 @@ export const CenterWindow: Widget.Window = new Widget.Window({
new Widget.Label({
className: "date",
label: getDateTime().as((dateTime: GLib.DateTime) =>
dateTime.format("%A, %B %d %Y"))
dateTime.format("%A, %B %d"))
} as Widget.LabelProps)
]
} as Widget.BoxProps),
BigMedia
]
} as Widget.BoxProps),
new Widget.Box({
className: "vertical right",
children: [
new Widget.Box({
className: "calendar-box",
vexpand: false,
hexpand: true,
valign: Gtk.Align.START,
child: new Gtk.Calendar({
visible: true,
show_heading: true,
@@ -56,6 +52,20 @@ export const CenterWindow: Widget.Window = new Widget.Window({
} as Gtk.Calendar.ConstructorProps)
} as Widget.BoxProps)
]
} as Widget.BoxProps),
Separator({
visible: bind(BigMedia, "visible"),
orientation: Gtk.Orientation.VERTICAL,
alpha: .5,
cssColor: "gray",
size: 1
} as SeparatorProps),
new Widget.Box({
className: "vertical right",
orientation: Gtk.Orientation.VERTICAL,
children: [
BigMedia
]
} as Widget.BoxProps)
]
} as Widget.BoxProps)
+13 -5
View File
@@ -16,14 +16,22 @@ function NotificationWidget(notification: AstalNotifd.Notification): Gtk.Widget
hexpand: true,
vexpand: false,
children: [
new Widget.Icon({
className: "icon",
visible: notification.appIcon !== "",
icon: notification.appIcon || "image-missing",
iconSize: Gtk.IconSize.DND,
css: ".icon { font-size: 24px; }"
}),
new Widget.Label({
className: "app-name",
halign: Gtk.Align.START,
label: notification.appName || "Unknown Application"
} as Widget.LabelProps),
new Widget.Button({
className: "close-button",
onClick: () => Notifications.removeNotification(notification.id)
className: "close nf",
onClick: () => notification.dismiss(),
label: "󰅖"
} as Widget.ButtonProps)
]
} as Widget.BoxProps),
@@ -71,8 +79,8 @@ export const FloatingNotifications: Widget.Window = new Widget.Window({
className: "floating-notifications-container",
orientation: Gtk.Orientation.VERTICAL,
homogeneous: false,
children: bind(Notifications, "notifications").as((notifications: Array<AstalNotifd.Notification>) =>
notifications.map((notification: AstalNotifd.Notification) =>
NotificationWidget(notification)))
children: Notifications.notifications().as((notifications: Array<AstalNotifd.Notification>) =>
notifications.map((item: AstalNotifd.Notification) =>
NotificationWidget(item)))
} as Widget.BoxProps)
} as Widget.WindowProps);
+37
View File
@@ -0,0 +1,37 @@
import { Variable } from "astal";
import { Astal, Gtk, Widget } from "astal/gtk3";
// TODO
export interface RunnerProps {
anchor?: Astal.WindowAnchor;
width?: number;
height?: number;
entryPlaceHolder?: string;
resultsPlaceholder?: Array<Gtk.Widget>;
}
export function Runner(props?: RunnerProps) {
const entryText: Variable<string> = new Variable<string>("");
const resultsBox: Widget.Box = new Widget.Box({
className: "results",
} as Widget.BoxProps);
return new Widget.Window({
namespace: "runner",
widthRequest: props?.width || 600,
heightRequest: props?.height || 500,
child: new Widget.Box({
className: "main",
children: [
new Widget.Entry({
className: "search",
onChanged: (entry) => entryText.set(entry.text),
} as Widget.EntryProps),
]
} as Widget.BoxProps)
} as Widget.WindowProps);
}
+2 -1
View File
@@ -1,4 +1,4 @@
import { Gtk, Widget } from "astal/gtk3";
import { Gtk } from "astal/gtk3";
import { Bar } from "./window/Bar";
import { OSD } from "./window/OSD";
@@ -22,6 +22,7 @@ export const Windows = GObject.registerClass({
WindowsClass.windowsMap.set("control-center", ControlCenter);
WindowsClass.windowsMap.set("center-window", CenterWindow);
WindowsClass.windowsMap.set("logout-menu", LogoutMenu);
WindowsClass.windowsMap.set("floating-notifications", FloatingNotifications);
}
public _init(...args: any[]) {