Unverified Commit 3d719a82 authored by Erik Reider's avatar Erik Reider Committed by GitHub

Ported to GTK 4 (#539)

* Ported to GTK 4 * Updated README dependencies * Updated GitHub actions * Added blueprint-compiler to README dependencies * Fixed expanded_group not being set to null when group only contains a single notification * Added a overlay close button for grouped notifications, just like regular notifications * Fixed control center kb shortcuts not always working * Fixed Notification sizing in Control Center When adding a large notification with a body image and actions, and add a seperate non-grouped notification, the first notification would take up more space than needed * Simplified Notification Group offset calculation * Limit the control center height to the monitors height * Fixed vala-lint issues * Fixed MPRIS background not using album art gicon * Show --skip-system-css in --help print * Fixed CC adaptive height being short with fit-to-screen enabled * Fixed grouped notifications not notifying CC that it's not the expanded one * Fixed notification groups focus being buggy and not visually visible * Fixed notification focus resetting to the top when removing the last notification
parent 5a6ae71d
...@@ -21,7 +21,7 @@ jobs: ...@@ -21,7 +21,7 @@ jobs:
- name: Install packages - name: Install packages
run: | run: |
pacman-key --init pacman-key --init
pacman -Syu --noconfirm pacman -Sy --noconfirm
- name: Add builduser - name: Add builduser
run: | run: |
...@@ -34,13 +34,23 @@ jobs: ...@@ -34,13 +34,23 @@ jobs:
- name: Check out sources - name: Check out sources
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
fetch-depth: 0
path: swaync
- name: Test PKGBUILD - name: Test PKGBUILD
run: | run: |
build_dir="/tmp/PKGBUILD-git/" cd swaync
build_dir="/tmp/PKGBUILD-git"
cd build-scripts cd build-scripts
mkdir $build_dir mkdir $build_dir
cp ./PKGBUILD-git $build_dir/PKGBUILD cp ./PKGBUILD-git $build_dir/PKGBUILD
mkdir $build_dir/src
cp -r ../ $build_dir/src/swaync
cd $build_dir cd $build_dir
sudo chown builduser $build_dir sudo chown -R builduser $build_dir
sudo -H -u builduser bash -c 'makepkg -s --noconfirm' # Use the locally checked-out branch instead of downloading the source
sudo -H -u builduser bash -c "makepkg -s --noconfirm --noextract SRCDEST=$build_dir"
...@@ -15,11 +15,11 @@ on: ...@@ -15,11 +15,11 @@ on:
jobs: jobs:
ubuntu-build: ubuntu-build:
container: ubuntu:24.10 container: ubuntu:25.04
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
PACKAGES: meson libwayland-dev libgtk-3-dev gobject-introspection libgirepository1.0-dev valac libjson-glib-dev libhandy-1-dev libgtk-layer-shell-dev scdoc libgee-0.8-dev libpulse-dev sassc libgranite-dev PACKAGES: meson libwayland-dev libgtk-4-dev gobject-introspection libgirepository1.0-dev valac libjson-glib-dev libgtk4-layer-shell-dev scdoc libgee-0.8-dev libpulse-dev sassc libgranite-7-dev blueprint-compiler libadwaita-1-dev
steps: steps:
- name: Install packages - name: Install packages
run: | run: |
......
SwayNotificationCenter # SwayNotificationCenter
======================
[![Check PKGBUILD builds for Arch.](https://github.com/ErikReider/SwayNotificationCenter/actions/workflows/PKGBUILD-buildd.yml/badge.svg)](https://github.com/ErikReider/SwayNotificationCenter/actions/workflows/PKGBUILD-buildd.yml) [![Check PKGBUILD builds for Arch.](https://github.com/ErikReider/SwayNotificationCenter/actions/workflows/PKGBUILD-buildd.yml/badge.svg)](https://github.com/ErikReider/SwayNotificationCenter/actions/workflows/PKGBUILD-buildd.yml)
[![Check build for Fedora.](https://github.com/ErikReider/SwayNotificationCenter/actions/workflows/fedora-build.yml/badge.svg)](https://github.com/ErikReider/SwayNotificationCenter/actions/workflows/fedora-build.yml) [![Check build for Fedora.](https://github.com/ErikReider/SwayNotificationCenter/actions/workflows/fedora-build.yml/badge.svg)](https://github.com/ErikReider/SwayNotificationCenter/actions/workflows/fedora-build.yml)
...@@ -21,8 +20,7 @@ https://github.com/ErikReider/SwayNotificationCenter/assets/35975961/93ff072f-e6 ...@@ -21,8 +20,7 @@ https://github.com/ErikReider/SwayNotificationCenter/assets/35975961/93ff072f-e6
![Screenshot of panel](./assets/panel.png) ![Screenshot of panel](./assets/panel.png)
Table of Contents ## Table of Contents
=================
* [Want to show off your sick config?](#want-to-show-off-your-sick-config) * [Want to show off your sick config?](#want-to-show-off-your-sick-config)
* [Features](#features) * [Features](#features)
...@@ -49,6 +47,7 @@ Table of Contents ...@@ -49,6 +47,7 @@ Table of Contents
* [Disable scripting](#disable-scripting) * [Disable scripting](#disable-scripting)
* [i3status-rs Example](#i3status-rs-example) * [i3status-rs Example](#i3status-rs-example)
* [Waybar Example](#waybar-example) * [Waybar Example](#waybar-example)
* [Debugging Environment Variables](#debugging-environment-variables)
## Want to show off your sick config? ## Want to show off your sick config?
...@@ -154,7 +153,6 @@ Lunar and later: ...@@ -154,7 +153,6 @@ Lunar and later:
sudo apt install sway-notification-center sudo apt install sway-notification-center
``` ```
### Debian ### Debian
Bookworm and later: Bookworm and later:
...@@ -166,6 +164,7 @@ sudo apt install sway-notification-center ...@@ -166,6 +164,7 @@ sudo apt install sway-notification-center
### Guix ### Guix
The simplest way is to install it to user's profile: The simplest way is to install it to user's profile:
```zsh ```zsh
guix install swaynotificationcenter guix install swaynotificationcenter
``` ```
...@@ -173,7 +172,8 @@ guix install swaynotificationcenter ...@@ -173,7 +172,8 @@ guix install swaynotificationcenter
But we recommend to use [Guix Home](https://guix.gnu.org/manual/devel/en/html_node/Home-Configuration.html) to manage packages and their configurations declaratively. But we recommend to use [Guix Home](https://guix.gnu.org/manual/devel/en/html_node/Home-Configuration.html) to manage packages and their configurations declaratively.
### rde ### rde
```
```scm
(use-modules (rde features wm)) (use-modules (rde features wm))
;; Include the following code into the list of your rde features: ;; Include the following code into the list of your rde features:
...@@ -186,19 +186,20 @@ But we recommend to use [Guix Home](https://guix.gnu.org/manual/devel/en/html_no ...@@ -186,19 +186,20 @@ But we recommend to use [Guix Home](https://guix.gnu.org/manual/devel/en/html_no
- `vala >= 0.56` - `vala >= 0.56`
- `meson` - `meson`
- `blueprint-compiler`
- `git` - `git`
- `scdoc` - `scdoc`
- `sassc` - `sassc`
- `gtk3` - `gtk4`
- `gtk-layer-shell` - `gtk4-layer-shell`
- `dbus` - `dbus`
- `glib2` - `glib2`
- `gobject-introspection` - `gobject-introspection`
- `libgee` - `libgee`
- `json-glib` - `json-glib`
- `libhandy` - `libadwaita`
- `gvfs` - `gvfs`
- `granite` - `granite7`
##### Optional Dependencies ##### Optional Dependencies
...@@ -446,3 +447,15 @@ Alternatively, the number of notifications can be shown by adding `{}` anywhere ...@@ -446,3 +447,15 @@ Alternatively, the number of notifications can be shown by adding `{}` anywhere
// ... // ...
}, },
``` ```
## Debugging Environment Variables
- `G_MESSAGES_DEBUG=all`: Displays all of the debug messages.
- `GTK_DEBUG=interactive`: Opens the GTK Inspector.
- `G_ENABLE_DIAGNOSTIC=1`: If set to a non-zero value, this environment variable
enables diagnostic messages, like deprecation messages for GObject properties
and signals.
- `G_DEBUG=fatal_criticals` or `G_DEBUG=fatal_warnings`: Causes GLib to abort
the program at the first call to g_warning() or g_critical().
More can be read [here](https://www.manpagez.com/html/glib/glib-2.56.0/glib-running.php)
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# Maintainer: Erik Reider <erik.reider@protonmail.com> # Maintainer: Erik Reider <erik.reider@protonmail.com>
pkgname=swaync-git pkgname=swaync-git
_pkgname=swaync _pkgname=swaync
pkgver=r558.8d1b813 pkgver=r560.a935000
pkgrel=1 pkgrel=1
pkgdesc="A simple notification daemon with a GTK panel for checking previous notifications like other DEs" pkgdesc="A simple notification daemon with a GTK panel for checking previous notifications like other DEs"
url="https://github.com/ErikReider/SwayNotificationCenter" url="https://github.com/ErikReider/SwayNotificationCenter"
...@@ -12,7 +12,7 @@ arch=( ...@@ -12,7 +12,7 @@ arch=(
'armv7h' # ARM v7 hardfloat 'armv7h' # ARM v7 hardfloat
) )
license=('GPL3') license=('GPL3')
depends=("gtk3" "gtk-layer-shell" "dbus" "glib2" "gobject-introspection" "libgee" "json-glib" "libhandy" "libpulse" "gvfs" "libnotify" "granite") depends=("gtk3" "gtk4-layer-shell" "dbus" "glib2" "gobject-introspection" "libgee" "json-glib" "libpulse" "gvfs" "libnotify" "granite7" "blueprint-compiler" "libadwaita")
conflicts=("swaync" "swaync-client") conflicts=("swaync" "swaync-client")
provides=("swaync" "swaync-client" "notification-daemon") provides=("swaync" "swaync-client" "notification-daemon")
makedepends=("vala>=0.56" meson git scdoc sassc) makedepends=("vala>=0.56" meson git scdoc sassc)
...@@ -30,12 +30,15 @@ prepare() { ...@@ -30,12 +30,15 @@ prepare() {
} }
build() { build() {
arch-meson $_pkgname build -Dscripting=true # Very important to be in the directory! Otherwise somehow breaks linking to the .ui files
cd $_pkgname
arch-meson build -Dscripting=true
ninja -C build ninja -C build
} }
package() { package() {
cd $_pkgname
DESTDIR="$pkgdir/" ninja -C build install DESTDIR="$pkgdir/" ninja -C build install
install -Dm644 "$_pkgname/COPYING" -t "$pkgdir/usr/share/licenses/$pkgname" install -Dm644 "COPYING" -t "$pkgdir/usr/share/licenses/$pkgname"
install -Dm644 "$_pkgname/README.md" -t "$pkgdir/usr/share/doc/$pkgname" install -Dm644 "README.md" -t "$pkgdir/usr/share/doc/$pkgname"
} }
...@@ -14,23 +14,24 @@ URL: https://github.com/ErikReider/SwayNotificationCenter ...@@ -14,23 +14,24 @@ URL: https://github.com/ErikReider/SwayNotificationCenter
VCS: {{{ git_repo_vcs }}} VCS: {{{ git_repo_vcs }}}
Source: {{{ git_repo_pack }}} Source: {{{ git_repo_pack }}}
BuildRequires: meson >= 0.51.0 BuildRequires: meson >= 1.5.1
BuildRequires: vala >= 0.56 BuildRequires: vala >= 0.56
BuildRequires: scdoc BuildRequires: scdoc
BuildRequires: pkgconfig(gtk+-3.0) >= 3.22 BuildRequires: pkgconfig(gtk4) >= 4.16
BuildRequires: pkgconfig(gtk-layer-shell-0) >= 0.1 BuildRequires: pkgconfig(gtk4-layer-shell-0) >= 1.0.3
BuildRequires: pkgconfig(json-glib-1.0) >= 1.0 BuildRequires: pkgconfig(json-glib-1.0) >= 1.0
BuildRequires: pkgconfig(libhandy-1) >= 1.4.0 BuildRequires: pkgconfig(libadwaita-1) >= 1.6.5
BuildRequires: pkgconfig(glib-2.0) >= 2.50 BuildRequires: pkgconfig(glib-2.0) >= 2.50
BuildRequires: pkgconfig(gobject-introspection-1.0) >= 1.68 BuildRequires: pkgconfig(gobject-introspection-1.0) >= 1.68
BuildRequires: pkgconfig(gee-0.8) >= 0.20 BuildRequires: pkgconfig(gee-0.8) >= 0.20
BuildRequires: pkgconfig(bash-completion) BuildRequires: pkgconfig(bash-completion)
BuildRequires: pkgconfig(fish) BuildRequires: pkgconfig(fish)
BuildRequires: pkgconfig(libpulse) BuildRequires: pkgconfig(libpulse)
BuildRequires: pkgconfig(granite) BuildRequires: pkgconfig(granite-7)
BuildRequires: systemd-devel BuildRequires: systemd-devel
BuildRequires: systemd BuildRequires: systemd
BuildRequires: sassc BuildRequires: sassc
BuildRequires: blueprint-compiler >= 0.16
BuildRequires: granite-devel BuildRequires: granite-devel
Requires: gvfs Requires: gvfs
......
...@@ -13,23 +13,24 @@ URL: https://github.com/ErikReider/SwayNotificationCenter ...@@ -13,23 +13,24 @@ URL: https://github.com/ErikReider/SwayNotificationCenter
VCS: {{{ git_repo_vcs }}} VCS: {{{ git_repo_vcs }}}
Source: {{{ git_repo_pack }}} Source: {{{ git_repo_pack }}}
BuildRequires: meson >= 0.51.0 BuildRequires: meson >= 1.5.1
BuildRequires: vala >= 0.56 BuildRequires: vala >= 0.56
BuildRequires: scdoc BuildRequires: scdoc
BuildRequires: pkgconfig(gtk+-3.0) >= 3.22 BuildRequires: pkgconfig(gtk4) >= 4.16
BuildRequires: pkgconfig(gtk-layer-shell-0) >= 0.1 BuildRequires: pkgconfig(gtk4-layer-shell-0) >= 1.0.3
BuildRequires: pkgconfig(json-glib-1.0) >= 1.0 BuildRequires: pkgconfig(json-glib-1.0) >= 1.0
BuildRequires: pkgconfig(libhandy-1) >= 1.4.0 BuildRequires: pkgconfig(libadwaita-1) >= 1.6.5
BuildRequires: pkgconfig(glib-2.0) >= 2.50 BuildRequires: pkgconfig(glib-2.0) >= 2.50
BuildRequires: pkgconfig(gobject-introspection-1.0) >= 1.68 BuildRequires: pkgconfig(gobject-introspection-1.0) >= 1.68
BuildRequires: pkgconfig(gee-0.8) >= 0.20 BuildRequires: pkgconfig(gee-0.8) >= 0.20
BuildRequires: pkgconfig(bash-completion) BuildRequires: pkgconfig(bash-completion)
BuildRequires: pkgconfig(fish) BuildRequires: pkgconfig(fish)
BuildRequires: pkgconfig(libpulse) BuildRequires: pkgconfig(libpulse)
BuildRequires: pkgconfig(granite) BuildRequires: pkgconfig(granite-7)
BuildRequires: systemd-devel BuildRequires: systemd-devel
BuildRequires: systemd BuildRequires: systemd
BuildRequires: sassc BuildRequires: sassc
BuildRequires: blueprint-compiler >= 0.16
BuildRequires: granite-devel BuildRequires: granite-devel
Requires: gvfs Requires: gvfs
......
app_resources += gnome.compile_resources('icon-resources',
'swaync_icons.gresource.xml',
c_name: 'sway_notification_center_icons'
)
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 3.292969 6.707031 l 4 4 c 0.390625 0.390625 1.023437 0.390625 1.414062 0 l 4 -4 c 0.390625 -0.390625 0.390625 -1.023437 0 -1.414062 s -1.023437 -0.390625 -1.414062 0 l -3.292969 3.292969 l -3.292969 -3.292969 c -0.390625 -0.390625 -1.023437 -0.390625 -1.414062 0 s -0.390625 1.023437 0 1.414062 z m 0 0" fill="#222222" fill-rule="evenodd"/></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 7.292969 5.292969 l -4 4 c -0.390625 0.390625 -0.390625 1.023437 0 1.414062 s 1.023437 0.390625 1.414062 0 l 3.292969 -3.292969 l 3.292969 3.292969 c 0.390625 0.390625 1.023437 0.390625 1.414062 0 s 0.390625 -1.023437 0 -1.414062 l -4 -4 c -0.390625 -0.390625 -1.023437 -0.390625 -1.414062 0 z m 0 0" fill="#222222" fill-rule="evenodd"/></svg>
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/erikreider/swaync/icons/scalable/actions">
<file preprocess="xml-stripblanks">swaync-collapse-symbolic.svg</file>
<file preprocess="xml-stripblanks">swaync-close-symbolic.svg</file>
</gresource>
</gresources>
...@@ -2,8 +2,6 @@ install_data('org.erikreider.swaync.gschema.xml', ...@@ -2,8 +2,6 @@ install_data('org.erikreider.swaync.gschema.xml',
install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas') install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas')
) )
subdir('icons')
compile_schemas = find_program('glib-compile-schemas', required: false) compile_schemas = find_program('glib-compile-schemas', required: false)
if compile_schemas.found() if compile_schemas.found()
test('Validate schema file', compile_schemas, test('Validate schema file', compile_schemas,
...@@ -34,3 +32,21 @@ style_css = custom_target( ...@@ -34,3 +32,21 @@ style_css = custom_target(
) )
message(style_css.full_path()) message(style_css.full_path())
blueprints = custom_target('blueprints',
input: files(run_command('find', '.', '-name', '*.blp').stdout().strip().split('\n')),
output: '.',
command: [
find_program('blueprint-compiler', required: true),
'batch-compile',
'@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'
],
build_always_stale: true,
build_by_default: true,
)
app_resources += gnome.compile_resources('sway_notification_center-resources',
'sway_notification_center.gresource.xml',
dependencies: [ blueprints ],
c_name: 'sway_notification_center'
)
@define-color cc-bg rgba(46, 46, 46, 0.7); :root {
--cc-bg: rgba(46, 46, 46, 0.7);
@define-color noti-border-color rgba(255, 255, 255, 0.15);
@define-color noti-bg rgba(48, 48, 48, 0.8); --noti-border-color: rgba(255, 255, 255, 0.15);
@define-color noti-bg-opaque rgb(48, 48, 48); --noti-bg: 48, 48, 48;
@define-color noti-bg-darker rgb(38, 38, 38); --noti-bg-alpha: 0.8;
@define-color noti-bg-hover rgb(56, 56, 56); --noti-bg-darker: rgb(38, 38, 38);
@define-color noti-bg-hover-opaque rgb(56, 56, 56); --noti-bg-hover: rgb(56, 56, 56);
@define-color noti-bg-focus rgba(68, 68, 68, 0.6); --noti-bg-focus: rgba(68, 68, 68, 0.6);
@define-color noti-close-bg rgba(255, 255, 255, 0.1); --noti-close-bg: rgb(78, 78, 78);
@define-color noti-close-bg-hover rgba(255, 255, 255, 0.15); --noti-close-bg-hover: rgb(94, 94, 94);
@define-color text-color rgb(255, 255, 255); --text-color: rgb(255, 255, 255);
@define-color text-color-disabled rgb(150, 150, 150); --text-color-disabled: rgb(150, 150, 150);
@define-color bg-selected rgb(0, 128, 255); --bg-selected: rgb(0, 128, 255);
}
$border: 1px solid #{"@noti-border-color"}; $border: 1px solid var(--noti-border-color);
$border-radius: 12px; $border-radius: 12px;
$margin: 8px;
$font-size-body: 15px; $font-size-body: 15px;
$font-size-summary: 16px; $font-size-summary: 16px;
...@@ -27,37 +29,39 @@ $group-collapse-tranistion: opacity 400ms ease-in-out; ...@@ -27,37 +29,39 @@ $group-collapse-tranistion: opacity 400ms ease-in-out;
$notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
0 1px 3px 1px rgba(0, 0, 0, 0.7), 0 2px 6px 2px rgba(0, 0, 0, 0.3); 0 1px 3px 1px rgba(0, 0, 0, 0.7), 0 2px 6px 2px rgba(0, 0, 0, 0.3);
.close-button {
/* The notification Close Button */
background: var(--noti-close-bg);
color: var(--text-color);
text-shadow: none;
padding: 0;
border-radius: 100%;
margin-top: $margin;
margin-right: $margin;
box-shadow: none;
border: none;
min-width: 24px;
min-height: 24px;
&:hover {
box-shadow: none;
background: var(--noti-close-bg-hover);
transition: $hover-tranistion;
border: none;
}
}
.notification-row { .notification-row {
background: none;
outline: none; outline: none;
&:focus, &:focus {
&:hover { background: var(--noti-bg-focus);
background: #{"@noti-bg-focus"};
} }
.notification-background { .notification-background {
padding: 6px 12px; padding: 6px 12px;
.close-button { .close-button {
/* The notification Close Button */
background: #{"@noti-close-bg"};
color: #{"@text-color"};
text-shadow: none;
padding: 0;
border-radius: 100%;
$margin: 5px;
margin-top: $margin;
margin-right: $margin;
box-shadow: none;
border: none;
min-width: 24px;
min-height: 24px;
&:hover {
box-shadow: none;
background: #{"@noti-close-bg-hover"};
transition: $hover-tranistion;
border: none;
}
} }
.notification { .notification {
...@@ -66,7 +70,7 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), ...@@ -66,7 +70,7 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
border: $border; border: $border;
padding: 0; padding: 0;
transition: $hover-tranistion; transition: $hover-tranistion;
background: #{"@noti-bg"}; background: rgba(var(--noti-bg), var(--noti-bg-alpha));
&.low { &.low {
/* Low Priority Notification */ /* Low Priority Notification */
...@@ -78,27 +82,20 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), ...@@ -78,27 +82,20 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
/* Critical Priority Notification */ /* Critical Priority Notification */
} }
%action { .notification-default-action {
padding: 4px; /* The large action that also displays the notification summary and body */
padding: $margin / 2;
margin: 0; margin: 0;
box-shadow: none; box-shadow: none;
background: transparent; background: transparent;
border: none; border: none;
color: #{"@text-color"}; color: var(--text-color);
transition: $hover-tranistion; transition: $hover-tranistion;
}
%action-hover {
-gtk-icon-effect: none;
background: #{"@noti-bg-hover"};
}
.notification-default-action {
/* The large action that also displays the notification summary and body */
@extend %action;
border-radius: $border-radius; border-radius: $border-radius;
&:hover { &:hover {
@extend %action-hover; -gtk-icon-filter: none;
background: var(--noti-bg-hover);
} }
&:not(:only-child) { &:not(:only-child) {
...@@ -111,28 +108,33 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), ...@@ -111,28 +108,33 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
background: transparent; background: transparent;
border-radius: $border-radius; border-radius: $border-radius;
$margin: 4px; $margin: 4px;
padding: $margin; padding: 0;
.image { .image {
/* Notification Primary Image */ /* Notification Primary Image */
-gtk-icon-effect: none; -gtk-icon-filter: none;
border-radius: 100px; /* Size in px */ border-radius: 100px; /* Size in px */
margin: $margin; margin: $margin;
} }
.app-icon { .app-icon {
/* Notification app icon (only visible when the primary image is set) */ /* Notification app icon (only visible when the primary image is set) */
-gtk-icon-effect: none; -gtk-icon-filter: none;
-gtk-icon-shadow: 0 1px 4px black; -gtk-icon-shadow: 0 1px 4px black;
margin: 6px; margin: 6px;
} }
.text-box { .text-box {
label {
/* Fixes base GTK 4 CSS setting a filter of opacity 50% for some odd reason */
filter: none;
}
.summary { .summary {
/* Notification summary/title */ /* Notification summary/title */
font-size: $font-size-summary; font-size: $font-size-summary;
font-weight: bold; font-weight: bold;
background: transparent; background: transparent;
color: #{"@text-color"}; color: var(--text-color);
text-shadow: none; text-shadow: none;
} }
.time { .time {
...@@ -140,7 +142,7 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), ...@@ -140,7 +142,7 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
font-size: $font-size-summary; font-size: $font-size-summary;
font-weight: bold; font-weight: bold;
background: transparent; background: transparent;
color: #{"@text-color"}; color: var(--text-color);
text-shadow: none; text-shadow: none;
margin-right: 30px; margin-right: 30px;
} }
...@@ -149,7 +151,7 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), ...@@ -149,7 +151,7 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
font-size: $font-size-body; font-size: $font-size-body;
font-weight: normal; font-weight: normal;
background: transparent; background: transparent;
color: #{"@text-color"}; color: var(--text-color);
text-shadow: none; text-shadow: none;
} }
} }
...@@ -163,8 +165,7 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), ...@@ -163,8 +165,7 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
/* The "extra" optional bottom notification image */ /* The "extra" optional bottom notification image */
margin-top: $margin; margin-top: $margin;
background-color: white; background-color: white;
border-radius: $border-radius; -gtk-icon-filter: none;
-gtk-icon-effect: none;
} }
.inline-reply { .inline-reply {
...@@ -172,51 +173,47 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), ...@@ -172,51 +173,47 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
margin-top: $margin; margin-top: $margin;
.inline-reply-entry { .inline-reply-entry {
background: #{"@noti-bg-darker"}; background: var(--noti-bg-darker);
color: #{"@text-color"}; color: var(--text-color);
caret-color: #{"@text-color"}; caret-color: var(--text-color);
border: $border; border: $border;
border-radius: $border-radius; border-radius: $border-radius;
} }
.inline-reply-button { .inline-reply-button {
margin-left: 4px; margin-left: 4px;
background: #{"@noti-bg"}; background: rgba(var(--noti-bg), var(--noti-bg-alpha));
border: $border; border: $border;
border-radius: $border-radius; border-radius: $border-radius;
color: #{"@text-color"}; color: var(--text-color);
&:disabled { &:disabled {
background: initial; background: initial;
color: #{"@text-color-disabled"}; color: var(--text-color-disabled);
border: $border; border: $border;
border-color: transparent; border-color: transparent;
} }
&:hover { &:hover {
background: #{"@noti-bg-hover"}; background: var(--noti-bg-hover);
} }
} }
} }
} }
} }
.notification-alt-actions {
background: none;
border-bottom-left-radius: $border-radius;
border-bottom-right-radius: $border-radius;
padding: $margin / 2;
}
.notification-action { .notification-action {
/* The alternative actions below the default action */ /* The alternative actions below the default action */
@extend %action; margin: $margin / 2;
border-top: $border; padding: 0;
border-radius: 0px;
border-right: $border;
&:first-child {
/* add bottom border radius to eliminate clipping */
border-bottom-left-radius: $border-radius;
}
&:last-child {
border-bottom-right-radius: $border-radius;
border-right: none;
}
&:hover { & > button {
@extend %action-hover; border-radius: $border-radius;
} }
} }
} }
...@@ -225,6 +222,11 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), ...@@ -225,6 +222,11 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
.notification-group { .notification-group {
/* Styling only for Grouped Notifications */ /* Styling only for Grouped Notifications */
transition: opacity 200ms ease-in-out;
&:focus {
background: var(--noti-bg-focus);
}
&.low { &.low {
/* Low Priority Group */ /* Low Priority Group */
...@@ -236,9 +238,14 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), ...@@ -236,9 +238,14 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
/* Low Priority Group */ /* Low Priority Group */
} }
/* The groups close button */
.notification-group-close-button .close-button {
margin: $margin * 1.5 $margin * 2.5;
}
%header { %header {
margin: 0 16px; margin: 0 $margin * 2;
color: #{"@text-color"}; color: var(--text-color);
} }
.notification-group-headers { .notification-group-headers {
...@@ -246,10 +253,10 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), ...@@ -246,10 +253,10 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
@extend %header; @extend %header;
.notification-group-icon { .notification-group-icon {
color: #{"@text-color"}; color: var(--text-color);
} }
.notification-group-header { .notification-group-header {
color: #{"@text-color"}; color: var(--text-color);
} }
} }
.notification-group-buttons { .notification-group-buttons {
...@@ -258,9 +265,14 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), ...@@ -258,9 +265,14 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
} }
&.collapsed { &.collapsed {
/* When another group is expanded, lower the opacity of the collapsed ones */
&.not-expanded {
opacity: 0.4;
}
.notification-row { .notification-row {
.notification { .notification {
background-color: #{"@noti-bg-opaque"}; background-color: rgba(var(--noti-bg), 1);
} }
&:not(:last-child) { &:not(:last-child) {
...@@ -276,7 +288,7 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), ...@@ -276,7 +288,7 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
&:hover { &:hover {
.notification-row:not(:only-child) { .notification-row:not(:only-child) {
.notification { .notification {
background-color: #{"@noti-bg-hover-opaque"}; background-color: var(--noti-bg-hover);
} }
} }
} }
...@@ -285,8 +297,8 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), ...@@ -285,8 +297,8 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
.control-center { .control-center {
/* The Control Center which contains the old notifications + widgets */ /* The Control Center which contains the old notifications + widgets */
background: #{"@cc-bg"}; background: var(--cc-bg);
color: #{"@text-color"}; color: var(--text-color);
border-radius: $border-radius; border-radius: $border-radius;
.control-center-list-placeholder { .control-center-list-placeholder {
...@@ -306,7 +318,7 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), ...@@ -306,7 +318,7 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
transition: $group-collapse-tranistion, $hover-tranistion; transition: $group-collapse-tranistion, $hover-tranistion;
&:hover { &:hover {
background-color: #{"@noti-bg-hover"}; background-color: var(--noti-bg-hover);
} }
} }
} }
...@@ -322,7 +334,7 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), ...@@ -322,7 +334,7 @@ $notification-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3),
background: transparent; background: transparent;
.notification { .notification {
box-shadow: none; box-shadow: none;
} }
} }
......
.widget-backlight { .widget-backlight {
background-color: #{"@noti-bg"}; padding: $margin;
padding: 8px; margin: $margin;
margin: 8px; border-radius: $border-radius;
border-radius: 12px;
} }
.widget-buttons-grid { .widget-buttons-grid {
padding: 8px; padding: $margin;
margin: 8px; margin: $margin;
border-radius: 12px; border-radius: $border-radius;
background-color: #{"@noti-bg"};
}
.widget-buttons-grid > flowbox > flowboxchild > button {
background: #{"@noti-bg"};
border-radius: 12px;
}
.widget-buttons-grid > flowbox > flowboxchild > button.toggle:checked { & flowboxchild > button {
/* style given to the active toggle button */ border-radius: $border-radius;
} &.toggle:checked {
/* style given to the active toggle button */
}
.widget-buttons-grid > flowbox > flowboxchild > button:hover { &:hover {
}
}
} }
.widget-dnd { .widget-dnd {
color: #{"@text-color"}; label {
margin: 8px; color: var(--text-color);
font-size: 1.1rem; margin: $margin;
} font-size: 1.1rem;
.widget-dnd > switch { }
font-size: initial;
border-radius: 12px; switch {
background: #{"@noti-bg"}; border-radius: $border-radius;
border: 1px solid #{"@noti-border-color"}; margin: $margin;
box-shadow: none;
} &:checked {
.widget-dnd > switch:checked { }
background: #{"@bg-selected"};
} slider {
.widget-dnd > switch slider { border-radius: $border-radius;
background: #{"@noti-bg-hover"}; }
border-radius: 12px; }
} }
.widget-inhibitors { .widget-inhibitors {
margin: 8px; & > label {
font-size: 1.5rem; margin: $margin;
} font-size: 1.5rem;
.widget-inhibitors > button { }
font-size: initial;
color: #{"@text-color"}; & > button {
text-shadow: none; margin: $margin;
background: #{"@noti-bg"}; border-radius: $border-radius;
border: 1px solid #{"@noti-border-color"}; }
box-shadow: none;
border-radius: 12px;
}
.widget-inhibitors > button:hover {
background: #{"@noti-bg-hover"};
} }
.widget-label { .widget-label {
margin: 8px; margin: $margin;
}
.widget-label > label { & > label {
font-size: 1.1rem; font-size: 1.1rem;
}
} }
.widget-menubar > box > .menu-button-bar > button { .widget-menubar {
border: none; & > .menu-button-bar {
background: transparent; /* The left button container */
} & > .start {
margin-left: $margin;
}
/* .AnyName { Name defined in config after # /* The right button container */
background-color: @noti-bg; & > .end {
padding: 8px; margin-right: $margin;
margin: 8px; }
border-radius: 12px;
}
.AnyName>button { /* The left and right button container */
background: transparent; & > .widget-menubar-container {
border: none; button {
} border-radius: $border-radius;
margin: 0 $margin / 2;
}
}
}
/* The revealer buttons */
& > revealer {
margin-top: $margin;
button {
border-radius: $border-radius;
margin: $margin;
margin-top: 0;
}
}
/* .AnyName { Name defined in config after #
background-color: rgba(var(--noti-bg), 1.0);
padding: 8px;
margin: 8px;
border-radius: 12px;
}
.AnyName>button:hover { .AnyName>button {
background-color: @noti-bg-hover; background: transparent;
} */ border: none;
}
.topbar-buttons > button { .AnyName>button:hover {
/* Name defined in config after # */ background-color: var(--noti-bg-hover);
border: none; } */
background: transparent;
} }
@define-color mpris-album-art-overlay rgba(0, 0, 0, 0.55); :root {
@define-color mpris-button-hover rgba(0, 0, 0, 0.50); --mpris-album-art-overlay: rgba(0, 0, 0, 0.55);
--mpris-button-hover: rgba(0, 0, 0, 0.5);
}
$mpris-shadow: 0px 0px 10px rgba(0, 0, 0, 0.75); $mpris-shadow: 0px 0px 10px rgba(0, 0, 0, 0.75);
.widget-mpris { .widget-mpris {
margin: $margin;
/* The parent to all players */ /* The parent to all players */
.widget-mpris-player { .widget-mpris-player {
padding: 8px;
padding: 16px;
margin: 16px 20px; margin: 16px 20px;
background-color: #{"@mpris-album-art-overlay"};
border-radius: $border-radius; border-radius: $border-radius;
box-shadow: $mpris-shadow; box-shadow: $mpris-shadow;
button:hover { .mpris-background {
/* The media player buttons (play, pause, next, etc...) */ filter: blur(10px);
background: #{"@noti-bg-hover"};
} }
.widget-mpris-album-art { .mpris-overlay {
border-radius: $border-radius; padding: 16px;
box-shadow: $mpris-shadow; background-color: var(--mpris-album-art-overlay);
}
.widget-mpris-title { button:hover {
font-weight: bold; /* The media player buttons (play, pause, next, etc...) */
font-size: 1.25rem; background: var(--noti-bg-hover);
} }
.widget-mpris-subtitle {
font-size: 1.1rem; .widget-mpris-album-art {
} border-radius: $border-radius;
box-shadow: $mpris-shadow;
-gtk-icon-size: 96px;
}
.widget-mpris-title {
font-weight: bold;
font-size: 1.25rem;
}
.widget-mpris-subtitle {
font-size: 1.1rem;
}
& > box > button { & > box > button {
/* Change player control buttons */ /* Change player control buttons */
&:hover { &:hover {
background-color: #{"@mpris-button-hover"}; background-color: var(--mpris-button-hover);
}
} }
} }
} }
......
.widget-title { .widget-title {
color: #{"@text-color"}; & > label {
margin: 8px; margin: $margin;
font-size: 1.5rem; font-size: 1.5rem;
} }
.widget-title > button {
font-size: initial; & > button {
color: #{"@text-color"}; margin: $margin;
text-shadow: none; border-radius: $border-radius;
background: #{"@noti-bg"}; }
border: 1px solid #{"@noti-border-color"};
box-shadow: none;
border-radius: 12px;
}
.widget-title > button:hover {
background: #{"@noti-bg-hover"};
} }
.widget-volume { .widget-volume {
background-color: #{"@noti-bg"}; padding: $margin;
padding: 8px; margin: $margin;
margin: 8px; border-radius: $border-radius;
border-radius: 12px;
} }
.widget-volume > box > button { .widget-volume > box > button {
background: transparent;
border: none;
} }
.per-app-volume { .per-app-volume {
background-color: #{"@noti-bg-alt"}; background-color: var(--noti-bg-alt);
padding: 4px 8px 8px 8px; padding: $margin/2 $margin $margin $margin;
margin: 0px 8px 8px 8px; margin: 0px $margin $margin $margin;
border-radius: 12px; border-radius: $border-radius;
} }
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/erikreider/swaync">
<file preprocess="xml-stripblanks">icons/scalable/actions/swaync-collapse-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/swaync-close-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/swaync-up-small-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/swaync-down-small-symbolic.svg</file>
<file preprocess="xml-stripblanks">ui/notification_window.ui</file>
<file preprocess="xml-stripblanks">ui/notification.ui</file>
<file preprocess="xml-stripblanks">ui/control_center.ui</file>
<file preprocess="xml-stripblanks">ui/mpris_player.ui</file>
</gresource>
</gresources>
using Gtk 4.0;
template $SwayNotificationCenterControlCenter: ApplicationWindow {
name: "control-center-window";
styles [
"blank-window",
]
ScrolledWindow window {
hscrollbar-policy: never;
styles [
"control-center",
]
child: Viewport {
child: $IterBox box {
orientation: vertical;
vexpand: true;
Box notifications_box {
orientation: vertical;
Stack stack {
hhomogeneous: true;
vhomogeneous: true;
transition-type: crossfade;
transition-duration: 200;
vexpand: true;
StackPage {
name: "notifications-list";
child: ScrolledWindow scrolled_window {
hscrollbar-policy: never;
has-frame: false;
child: Viewport viewport {
vexpand: true;
child: ListBox list_box {
valign: fill;
styles [
"control-center-list",
]
};
};
};
}
StackPage {
name: "notifications-placeholder";
child: Box {
halign: center;
valign: center;
hexpand: true;
vexpand: true;
orientation: vertical;
spacing: 12;
styles [
"control-center-list-placeholder",
]
Image {
pixel-size: 96;
icon-name: "preferences-system-notifications-symbolic";
use-fallback: true;
}
Label text_empty_label {
label: "No Notifications";
}
};
}
}
}
};
};
}
}
using Gtk 4.0;
template $SwayNotificationCenterWidgetsMprisMprisPlayer: $Underlay {
overflow: hidden;
valign: start;
hexpand: true;
underlay_child: Picture background_picture {
content-fit: cover;
styles [
"mpris-background",
]
};
child: Box {
orientation: vertical;
styles [
"mpris-overlay",
]
Box {
spacing: 12;
Image album_art {
icon-name: "audio-x-generic-symbolic";
use-fallback: true;
pixel-size: 96;
overflow: hidden;
styles [
"widget-mpris-album-art",
]
}
Box {
orientation: vertical;
spacing: 4;
hexpand: true;
valign: center;
Label title {
justify: left;
ellipsize: end;
wrap-mode: word_char;
xalign: 0;
yalign: 0;
styles [
"widget-mpris-title",
]
}
Label sub_title {
justify: left;
ellipsize: end;
wrap-mode: word_char;
xalign: 0;
yalign: 0;
styles [
"widget-mpris-subtitle",
]
}
}
}
Box {
spacing: 6;
halign: center;
Button button_shuffle {
has-frame: false;
icon-name: "media-playlist-shuffle-symbolic";
styles [
"circular",
"image-button",
"flat",
]
}
Button button_prev {
has-frame: false;
icon-name: "media-seek-backward-symbolic";
styles [
"circular",
"image-button",
"flat",
]
}
Button button_play_pause {
has-frame: false;
icon-name: "media-playback-pause-symbolic";
styles [
"circular",
"image-button",
"flat",
]
}
Button button_next {
has-frame: false;
icon-name: "media-seek-forward-symbolic";
styles [
"circular",
"image-button",
"flat",
]
}
Button button_repeat {
has-frame: false;
icon-name: "media-playlist-repeat-symbolic";
styles [
"circular",
"image-button",
"flat",
]
}
}
};
}
using Gtk 4.0;
template $SwayNotificationCenterNotification: Box {
hexpand: true;
styles [
"notification-row",
]
Revealer revealer {
transition-type: crossfade;
reveal-child: false;
hexpand: true;
child: $DismissibleWidget dismissible_widget {
halign: fill;
valign: fill;
child: Overlay base_widget {
hexpand: true;
styles [
"notification-background",
]
$IterBox base_box {
orientation: vertical;
overflow: hidden;
styles [
"notification",
]
Box default_action {
orientation: vertical;
styles [
"notification-default-action",
]
Box {
orientation: vertical;
spacing: 8;
styles [
"notification-content",
]
Box {
// Box with images/icons
Overlay {
halign: center;
valign: center;
child: Image img {
// pixel-size: 48;
valign: center;
overflow: hidden;
styles [
"image",
]
};
[overlay]
Image img_app_icon {
// pixel-size: 48;
halign: end;
valign: end;
overflow: hidden;
styles [
"app-icon",
]
}
}
// Body, summary, and time
Box {
hexpand: true;
vexpand: true;
valign: center;
orientation: vertical;
styles [
"text-box",
]
Box {
orientation: horizontal;
valign: center;
Label summary {
justify: left;
ellipsize: end;
wrap: false;
xalign: 0;
hexpand: true;
styles [
"summary",
]
}
Label time {
justify: left;
ellipsize: none;
valign: start;
styles [
"time",
]
}
}
Label body {
ellipsize: end;
justify: left;
wrap-mode: word_char;
natural-wrap-mode: word;
lines: 1;
xalign: 0;
yalign: 0;
hexpand: true;
styles [
"body",
]
}
}
}
ProgressBar progress_bar {
orientation: horizontal;
}
Picture body_image {
halign: center;
overflow: hidden;
}
Box inline_reply_box {
styles [
"inline-reply",
]
Entry inline_reply_entry {
input-purpose: free_form;
input-hints: spellcheck | emoji;
hexpand: true;
styles [
"inline-reply-entry",
]
}
Button inline_reply_button {
styles [
"inline-reply-button",
]
}
}
}
}
FlowBox alt_actions_box {
orientation: horizontal;
homogeneous: true;
overflow: hidden;
selection-mode: none;
activate-on-single-click: true;
styles [
"notification-alt-actions",
]
}
}
[overlay]
$SwayNotificationCenterNotificationCloseButton close_button {}
};
};
}
}
using Gtk 4.0;
template $SwayNotificationCenterNotificationWindow: Gtk.ApplicationWindow {
styles [
"floating-notifications",
]
Gtk.ScrolledWindow scrolled_window {
propagate-natural-height: true;
hscrollbar-policy: never;
vscrollbar-policy: automatic;
has-frame: false;
Gtk.Viewport viewport {
vexpand: true;
$IterBox box {
orientation: vertical;
}
}
}
}
...@@ -19,6 +19,9 @@ swaync - A simple notification daemon with a GTK gui for notifications and the c ...@@ -19,6 +19,9 @@ swaync - A simple notification daemon with a GTK gui for notifications and the c
*-s, --style* <CSS file> *-s, --style* <CSS file>
Use a custom Stylesheet file Use a custom Stylesheet file
*--skip-system-css*
Skip trying to parse the packaged Stylesheet file. Useful for CSS debugging
*-c, --config* <Config file> *-c, --config* <Config file>
Use a custom config file Use a custom config file
......
...@@ -330,11 +330,6 @@ config file to be able to detect config errors ...@@ -330,11 +330,6 @@ config file to be able to detect config errors
optional: true ++ optional: true ++
default: 96 ++ default: 96 ++
description: The size of the album art. ++ description: The size of the album art. ++
image-radius: ++
type: integer ++
optional: true ++
default: 12 ++
description: The border radius of the album art. ++
blacklist: ++ blacklist: ++
type: array ++ type: array ++
optional: true ++ optional: true ++
...@@ -615,7 +610,6 @@ config file to be able to detect config errors ...@@ -615,7 +610,6 @@ config file to be able to detect config errors
}, },
"mpris": { "mpris": {
"image-size": 96, "image-size": 96,
"image-radius": 12,
"blacklist": ["playerctld"], "blacklist": ["playerctld"],
"autohide": true "autohide": true
}, },
......
project('sway-notificaton-center', ['c', 'vala'], project('sway-notificaton-center', ['c', 'vala'],
version: '0.11.0', version: '0.11.0',
meson_version: '>= 0.59.0', meson_version: '>= 0.60.0',
default_options: [ 'warning_level=2' ], default_options: [ 'warning_level=2' ],
) )
......
#include <gdk/gdk.h>
#include <gdk/gdkwayland.h>
#include <glib.h>
#include <stdio.h>
#include <wayland-client.h>
/** GDK doesn't provide a vapi file for GDK Wayland... */
#define PRINT_ERROR \
g_error("Gdk Display isn't a Wayland display! Only Wayland is supported")
struct wl_display *get_wl_display() {
GdkDisplay *display = gdk_display_get_default();
if (GDK_IS_WAYLAND_DISPLAY(display)) {
return gdk_wayland_display_get_wl_display(display);
}
PRINT_ERROR;
return NULL;
}
struct wl_surface *get_wl_surface(GdkWindow *window) {
if (GDK_IS_WAYLAND_WINDOW(window)) {
return gdk_wayland_window_get_wl_surface(window);
}
PRINT_ERROR;
return NULL;
}
extern Wl.Display * get_wl_display ();
extern Wl.Surface * get_wl_surface (Gdk.Window window);
namespace SwayNotificationCenter {
public class Animation : Object {
private unowned Gtk.Widget widget;
double value;
double value_from;
double value_to;
int64 duration;
int64 start_time;
uint tick_cb_id;
ulong unmap_cb_id;
unowned AnimationEasingFunc easing_func;
unowned AnimationValueCallback value_cb;
unowned AnimationDoneCallback done_cb;
bool is_done;
public delegate void AnimationValueCallback (double value);
public delegate void AnimationDoneCallback ();
public delegate double AnimationEasingFunc (double t);
public Animation (Gtk.Widget widget, int64 duration,
AnimationEasingFunc easing_func,
AnimationValueCallback value_cb,
AnimationDoneCallback done_cb) {
this.widget = widget;
this.duration = duration;
this.easing_func = easing_func;
this.value_cb = value_cb;
this.done_cb = done_cb;
this.is_done = false;
}
~Animation () {
stop ();
}
void set_value (double value) {
this.value = value;
this.value_cb (value);
}
void done () {
if (is_done) return;
is_done = true;
done_cb ();
}
bool tick_cb (Gtk.Widget widget, Gdk.FrameClock frame_clock) {
int64 frame_time = frame_clock.get_frame_time () / 1000; /* ms */
double t = (double) (frame_time - start_time) / duration;
if (t >= 1) {
tick_cb_id = 0;
set_value (value_to);
if (unmap_cb_id != 0) {
SignalHandler.disconnect (widget, unmap_cb_id);
unmap_cb_id = 0;
}
done ();
return Source.REMOVE;
}
set_value (lerp (value_from, value_to, easing_func (t)));
return Source.CONTINUE;
}
public void start (double from, double to) {
this.value_from = from;
this.value_to = to;
this.value = from;
this.is_done = false;
unowned Gtk.Settings ? gsettings = Gtk.Settings.get_default ();
bool animations_enabled =
gsettings != null ? gsettings.gtk_enable_animations : true;
if (animations_enabled != true ||
!widget.get_mapped () || duration <= 0) {
set_value (value_to);
done ();
return;
}
start_time = widget.get_frame_clock ().get_frame_time () / 1000;
if (tick_cb_id != 0) return;
unmap_cb_id =
Signal.connect_swapped (widget, "unmap", (Callback) stop, this);
tick_cb_id = widget.add_tick_callback (tick_cb);
}
public void stop () {
if (tick_cb_id != 0) {
widget.remove_tick_callback (tick_cb_id);
tick_cb_id = 0;
}
if (unmap_cb_id != 0) {
SignalHandler.disconnect (widget, unmap_cb_id);
unmap_cb_id = 0;
}
done ();
}
public double get_value () {
return value;
}
public static double lerp (double a, double b, double t) {
return a * (1.0 - t) + b * t;
}
public static double ease_out_cubic (double t) {
double p = t - 1;
return p * p * p + 1;
}
public static double ease_in_cubic (double t) {
return t * t * t;
}
public static double ease_in_out_cubic (double t) {
double p = t * 2;
if (p < 1) return 0.5 * p * p * p;
p -= 2;
return 0.5 * (p * p * p + 2);
}
}
}
namespace SwayNotificationCenter { namespace SwayNotificationCenter {
public class BlankWindow : Gtk.Window { public class BlankWindow : Gtk.ApplicationWindow {
public unowned Gdk.Display display { get; private set; }
public unowned Gdk.Monitor monitor { get; private set; } public unowned Gdk.Monitor monitor { get; private set; }
unowned SwayncDaemon daemon;
Gtk.Button button; private Gtk.GestureClick blank_window_gesture;
private bool blank_window_down = false;
private bool blank_window_in = false;
public BlankWindow (Gdk.Display disp, public BlankWindow (Gdk.Monitor monitor) {
Gdk.Monitor mon, this.monitor = monitor;
SwayncDaemon dae) {
display = disp;
monitor = mon;
daemon = dae;
// Use button click event instead of Window button_press_event due blank_window_gesture = new Gtk.GestureClick ();
// to Gtk layer shell bug. This would grab focus instead of ControlCenter ((Gtk.Widget) this).add_controller (blank_window_gesture);
button = new Gtk.Button () { blank_window_gesture.touch_only = false;
expand = true, blank_window_gesture.exclusive = true;
opacity = 0, blank_window_gesture.button = Gdk.BUTTON_PRIMARY;
relief = Gtk.ReliefStyle.NONE, blank_window_gesture.propagation_phase = Gtk.PropagationPhase.BUBBLE;
visible = true, blank_window_gesture.pressed.connect ((n_press, x, y) => {
}; blank_window_in = true;
button.clicked.connect (() => { blank_window_down = true;
try { });
daemon.set_visibility (false); blank_window_gesture.released.connect ((n_press, x, y) => {
} catch (Error e) { // Emit released
stderr.printf ("BlankWindow Click Error: %s\n", e.message); if (!blank_window_down) return;
blank_window_down = false;
if (blank_window_in) {
try {
swaync_daemon.set_visibility (false);
} catch (Error e) {
stderr.printf ("ControlCenter BlankWindow Click Error: %s\n",
e.message);
}
}
if (blank_window_gesture.get_current_sequence () == null) {
blank_window_in = false;
}
});
blank_window_gesture.update.connect ((gesture, sequence) => {
Gtk.GestureSingle gesture_single = (Gtk.GestureSingle) gesture;
if (sequence != gesture_single.get_current_sequence ()) return;
// Calculate if the clicked coords intersect other monitors
double x, y;
gesture.get_point (sequence, out x, out y);
Graphene.Point click_point = Graphene.Point ()
.init ((float) x, (float) y);
Graphene.Rect ? bounds = null;
this.compute_bounds (this, out bounds);
if (bounds != null && bounds.contains_point (click_point)) {
blank_window_in = false;
} }
}); });
add (button); blank_window_gesture.cancel.connect (() => {
blank_window_down = false;
});
if (!GtkLayerShell.is_supported ()) { if (!GtkLayerShell.is_supported ()) {
stderr.printf ("GTKLAYERSHELL IS NOT SUPPORTED!\n"); stderr.printf ("GTKLAYERSHELL IS NOT SUPPORTED!\n");
...@@ -48,25 +72,22 @@ namespace SwayNotificationCenter { ...@@ -48,25 +72,22 @@ namespace SwayNotificationCenter {
GtkLayerShell.set_exclusive_zone (this, -1); GtkLayerShell.set_exclusive_zone (this, -1);
GtkLayerShell.Layer layer; GtkLayerShell.set_layer (
switch (ConfigModel.instance.control_center_layer) { this, ConfigModel.instance.control_center_layer.to_layer ());
case Layer.BACKGROUND:
layer = GtkLayerShell.Layer.BACKGROUND;
break;
case Layer.BOTTOM:
layer = GtkLayerShell.Layer.BOTTOM;
break;
case Layer.TOP:
layer = GtkLayerShell.Layer.TOP;
break;
default:
case Layer.OVERLAY:
layer = GtkLayerShell.Layer.OVERLAY;
break;
}
GtkLayerShell.set_layer (this, layer);
get_style_context ().add_class ("blank-window"); add_css_class ("blank-window");
}
protected override void snapshot (Gtk.Snapshot snapshot) {
// HACK: Fixes fully transparent windows not being mapped
Gdk.RGBA color = Gdk.RGBA () {
red = 0,
green = 0,
blue = 0,
alpha = 0,
};
snapshot.append_color (color, Graphene.Rect.zero ());
base.snapshot (snapshot);
} }
} }
} }
...@@ -75,7 +75,6 @@ ...@@ -75,7 +75,6 @@
}, },
"mpris": { "mpris": {
"image-size": 96, "image-size": 96,
"image-radius": 12,
"blacklist": [], "blacklist": [],
"autohide": false "autohide": false
}, },
......
...@@ -38,6 +38,20 @@ namespace SwayNotificationCenter { ...@@ -38,6 +38,20 @@ namespace SwayNotificationCenter {
return "overlay"; return "overlay";
} }
} }
public GtkLayerShell.Layer to_layer () {
switch (this) {
case BACKGROUND:
return GtkLayerShell.Layer.BACKGROUND;
case BOTTOM:
return GtkLayerShell.Layer.BOTTOM;
case TOP:
return GtkLayerShell.Layer.TOP;
default:
case OVERLAY:
return GtkLayerShell.Layer.OVERLAY;
}
}
} }
public enum CssPriority { public enum CssPriority {
...@@ -75,8 +89,7 @@ namespace SwayNotificationCenter { ...@@ -75,8 +89,7 @@ namespace SwayNotificationCenter {
public string ? category { get; set; default = null; } public string ? category { get; set; default = null; }
private const RegexCompileFlags REGEX_COMPILE_OPTIONS = private const RegexCompileFlags REGEX_COMPILE_OPTIONS =
RegexCompileFlags.MULTILINE RegexCompileFlags.MULTILINE;
| RegexCompileFlags.JAVASCRIPT_COMPAT;
private const RegexMatchFlags REGEX_MATCH_FLAGS = RegexMatchFlags.NOTEMPTY; private const RegexMatchFlags REGEX_MATCH_FLAGS = RegexMatchFlags.NOTEMPTY;
......
...@@ -145,7 +145,7 @@ ...@@ -145,7 +145,7 @@
}, },
"control-center-height": { "control-center-height": {
"type": "integer", "type": "integer",
"description": "Height of the control center in pixels. Ignored when 'fit-to-screen' is set to 'true'", "description": "Height of the control center in pixels. Ignored when 'fit-to-screen' is set to 'true'. Also limited to the height of the monitor, unless 'layer-shell-cover-screen' is set to false.",
"default": 600, "default": 600,
"minimum": 300 "minimum": 300
}, },
...@@ -409,17 +409,6 @@ ...@@ -409,17 +409,6 @@
"type": "integer", "type": "integer",
"description": "The size of the album art", "description": "The size of the album art",
"default": 96 "default": 96
},
"image-radius": {
"type": "integer",
"description": "The border radius of the album art. Will be overriden by setting the border-radius in the style.css for the \".widget-mpris-album-art\" class",
"default": 12
},
"blur": {
"type": "boolean",
"description": "Appy the artwork as the MPRIS background and blur it",
"default": true
} }
} }
}, },
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.40.0 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<template class="SwayNotificationCenterControlCenter" parent="GtkApplicationWindow">
<property name="name">control-center-window</property>
<property name="can-focus">False</property>
<property name="resizable">False</property>
<property name="skip-taskbar-hint">True</property>
<property name="skip-pager-hint">True</property>
<property name="urgency-hint">True</property>
<property name="decorated">False</property>
<property name="deletable">False</property>
<property name="gravity">north-east</property>
<property name="show-menubar">False</property>
<child>
<object class="GtkBox" id="box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="notifications_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="transition-type">crossfade</property>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hscrollbar-policy">never</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="name">notifications-list</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="pixel-size">96</property>
<property name="icon-name">preferences-system-notifications-symbolic</property>
<property name="use-fallback">True</property>
<property name="icon_size">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="text_empty_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">No Notifications</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<style>
<class name="control-center-list-placeholder"/>
</style>
</object>
<packing>
<property name="name">notifications-placeholder</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<style>
<class name="control-center"/>
</style>
</object>
</child>
<style>
<class name="blank-window"/>
</style>
</template>
</interface>
namespace SwayNotificationCenter { namespace SwayNotificationCenter {
[GtkTemplate (ui = "/org/erikreider/sway-notification-center/controlCenter/controlCenter.ui")] /* Only to be used by the Viewport in the Control Center */
private class FixedViewportLayout : Gtk.LayoutManager {
private unowned Gtk.ScrolledWindow parent;
public FixedViewportLayout (Gtk.ScrolledWindow parent) {
this.parent = parent;
}
public override void measure (Gtk.Widget widget,
Gtk.Orientation orientation, int for_size,
out int minimum, out int natural,
out int minimum_baseline, out int natural_baseline) {
minimum = 0;
natural = 0;
minimum_baseline = 0;
natural_baseline = 0;
if (widget == null || !widget.should_layout ()) {
return;
}
unowned Gtk.Widget child = ((Gtk.Viewport) widget).child;
if (!child.should_layout ()) {
return;
}
child.measure (orientation, for_size,
out minimum, out natural,
out minimum_baseline, out natural_baseline);
}
public override void allocate (Gtk.Widget widget,
int width, int height, int baseline) {
if (widget == null || !widget.should_layout ()) {
return;
}
unowned Gtk.Widget child = ((Gtk.Viewport) widget).child;
if (!child.should_layout ()) {
return;
}
if (ConfigModel.instance.fit_to_screen) {
child.allocate (width, height, baseline, null);
return;
}
int m_height, n_height;
child.measure (Gtk.Orientation.VERTICAL, width,
out m_height, out n_height,
null, null);
int m_width, n_width;
child.measure (Gtk.Orientation.HORIZONTAL, height,
out m_width, out n_width,
null, null);
int parent_width = parent.get_width ();
int parent_height = parent.get_height ();
// Limit the size to the ScrolledWindows size
child.allocate (
n_width.clamp ((int) Math.fmin (m_width, parent_width), parent_width),
n_height.clamp ((int) Math.fmin (m_height, parent_height), parent_height),
baseline, null);
}
}
[GtkTemplate (ui = "/org/erikreider/swaync/ui/control_center.ui")]
public class ControlCenter : Gtk.ApplicationWindow { public class ControlCenter : Gtk.ApplicationWindow {
[GtkChild] [GtkChild]
unowned Gtk.ScrolledWindow window;
[GtkChild]
unowned Gtk.Box notifications_box; unowned Gtk.Box notifications_box;
[GtkChild] [GtkChild]
unowned Gtk.Label text_empty_label; unowned Gtk.Label text_empty_label;
...@@ -9,17 +78,18 @@ namespace SwayNotificationCenter { ...@@ -9,17 +78,18 @@ namespace SwayNotificationCenter {
unowned Gtk.Stack stack; unowned Gtk.Stack stack;
[GtkChild] [GtkChild]
unowned Gtk.ScrolledWindow scrolled_window; unowned Gtk.ScrolledWindow scrolled_window;
FadedViewport viewport = new FadedViewport (20); [GtkChild]
Gtk.ListBox list_box = new Gtk.ListBox (); unowned Gtk.Viewport viewport;
[GtkChild]
unowned Gtk.ListBox list_box;
IterListBoxController list_box_controller;
[GtkChild] [GtkChild]
unowned Gtk.Box box; unowned IterBox box;
unowned NotificationGroup ? expanded_group = null; unowned NotificationGroup ? expanded_group = null;
private double fade_animation_progress = 1.0; uint scroll_timer_id = 0;
private Animation ? notification_fade_animation;
private double scroll_animation_progress = 1.0;
private Animation ? scroll_animation;
HashTable<uint32, unowned NotificationGroup> noti_groups_id = HashTable<uint32, unowned NotificationGroup> noti_groups_id =
new HashTable<uint32, unowned NotificationGroup> (direct_hash, direct_equal); new HashTable<uint32, unowned NotificationGroup> (direct_hash, direct_equal);
...@@ -30,10 +100,12 @@ namespace SwayNotificationCenter { ...@@ -30,10 +100,12 @@ namespace SwayNotificationCenter {
const string STACK_NOTIFICATIONS_PAGE = "notifications-list"; const string STACK_NOTIFICATIONS_PAGE = "notifications-list";
const string STACK_PLACEHOLDER_PAGE = "notifications-placeholder"; const string STACK_PLACEHOLDER_PAGE = "notifications-placeholder";
private Gtk.GestureMultiPress blank_window_gesture; private Gtk.GestureClick blank_window_gesture;
private bool blank_window_down = false; private bool blank_window_down = false;
private bool blank_window_in = false; private bool blank_window_in = false;
private Gtk.EventControllerKey key_controller;
private SwayncDaemon swaync_daemon; private SwayncDaemon swaync_daemon;
private NotiDaemon noti_daemon; private NotiDaemon noti_daemon;
...@@ -49,19 +121,7 @@ namespace SwayNotificationCenter { ...@@ -49,19 +121,7 @@ namespace SwayNotificationCenter {
this.swaync_daemon = swaync_daemon; this.swaync_daemon = swaync_daemon;
this.noti_daemon = noti_daemon; this.noti_daemon = noti_daemon;
this.swaync_daemon.reloading_css.connect (reload_notifications_style); list_box_controller = new IterListBoxController (list_box);
viewport.set_visible (true);
viewport.set_vexpand (true);
viewport.set_shadow_type (Gtk.ShadowType.NONE);
scrolled_window.add (viewport);
list_box.set_visible (true);
list_box.set_valign (Gtk.Align.END);
list_box.set_selection_mode (Gtk.SelectionMode.NONE);
list_box.set_activate_on_single_click (false);
list_box.get_style_context ().add_class ("control-center-list");
viewport.add (list_box);
text_empty_label.set_text (ConfigModel.instance.text_empty); text_empty_label.set_text (ConfigModel.instance.text_empty);
...@@ -75,23 +135,20 @@ namespace SwayNotificationCenter { ...@@ -75,23 +135,20 @@ namespace SwayNotificationCenter {
} }
GtkLayerShell.init_for_window (this); GtkLayerShell.init_for_window (this);
GtkLayerShell.set_namespace (this, "swaync-control-center"); GtkLayerShell.set_namespace (this, "swaync-control-center");
GtkLayerShell.set_anchor (this, GtkLayerShell.Edge.TOP, true); set_anchor ();
GtkLayerShell.set_anchor (this, GtkLayerShell.Edge.LEFT, true);
GtkLayerShell.set_anchor (this, GtkLayerShell.Edge.RIGHT, true);
GtkLayerShell.set_anchor (this, GtkLayerShell.Edge.BOTTOM, true);
} }
this.map.connect (() => { this.map.connect (() => {
set_anchor (); set_anchor ();
// Wait until the layer has attached
unowned Gdk.Surface surface = get_surface ();
if (!(surface is Gdk.Surface)) {
return;
}
ulong id = 0; ulong id = 0;
id = notify["has-toplevel-focus"].connect (() => { id = surface.enter_monitor.connect ((monitor) => {
disconnect (id); surface.disconnect (id);
unowned Gdk.Monitor monitor = null;
unowned Gdk.Window ? win = get_window ();
if (win != null) {
monitor = get_display ().get_monitor_at_window (win);
}
swaync_daemon.show_blank_windows (monitor); swaync_daemon.show_blank_windows (monitor);
}); });
}); });
...@@ -100,23 +157,22 @@ namespace SwayNotificationCenter { ...@@ -100,23 +157,22 @@ namespace SwayNotificationCenter {
/* /*
* Handling of bank window presses (pressing outside of ControlCenter) * Handling of bank window presses (pressing outside of ControlCenter)
*/ */
blank_window_gesture = new Gtk.GestureMultiPress (this); blank_window_gesture = new Gtk.GestureClick ();
blank_window_gesture.set_touch_only (false); ((Gtk.Widget) this).add_controller (blank_window_gesture);
blank_window_gesture.set_exclusive (true); blank_window_gesture.touch_only = false;
blank_window_gesture.set_button (Gdk.BUTTON_PRIMARY); blank_window_gesture.exclusive = true;
blank_window_gesture.set_propagation_phase (Gtk.PropagationPhase.BUBBLE); blank_window_gesture.button = Gdk.BUTTON_PRIMARY;
blank_window_gesture.pressed.connect ((_gesture, _n_press, x, y) => { blank_window_gesture.propagation_phase = Gtk.PropagationPhase.BUBBLE;
blank_window_gesture.pressed.connect ((n_press, x, y) => {
// Calculate if the clicked coords intersect the ControlCenter // Calculate if the clicked coords intersect the ControlCenter
Gdk.Rectangle click_rectangle = Gdk.Rectangle () { Graphene.Point click_point = Graphene.Point ()
width = 1, .init ((float) x, (float) y);
height = 1, Graphene.Rect ? bounds = null;
x = (int) x, window.compute_bounds (this, out bounds);
y = (int) y, blank_window_in = !(bounds != null && bounds.contains_point (click_point));
};
blank_window_in = !box.intersect (click_rectangle, null);
blank_window_down = true; blank_window_down = true;
}); });
blank_window_gesture.released.connect ((gesture, _n_press, _x, _y) => { blank_window_gesture.released.connect ((n_press, x, y) => {
// Emit released // Emit released
if (!blank_window_down) return; if (!blank_window_down) return;
blank_window_down = false; blank_window_down = false;
...@@ -129,8 +185,7 @@ namespace SwayNotificationCenter { ...@@ -129,8 +185,7 @@ namespace SwayNotificationCenter {
} }
} }
Gdk.EventSequence ? sequence = gesture.get_current_sequence (); if (blank_window_gesture.get_current_sequence () == null) {
if (sequence == null) {
blank_window_in = false; blank_window_in = false;
} }
}); });
...@@ -140,220 +195,151 @@ namespace SwayNotificationCenter { ...@@ -140,220 +195,151 @@ namespace SwayNotificationCenter {
// Calculate if the clicked coords intersect the ControlCenter // Calculate if the clicked coords intersect the ControlCenter
double x, y; double x, y;
gesture.get_point (sequence, out x, out y); gesture.get_point (sequence, out x, out y);
Gdk.Rectangle click_rectangle = Gdk.Rectangle () { Graphene.Point click_point = Graphene.Point ()
width = 1, .init ((float) x, (float) y);
height = 1, Graphene.Rect ? bounds = null;
x = (int) x, window.compute_bounds (this, out bounds);
y = (int) y, if (bounds != null && bounds.contains_point (click_point)) {
};
if (box.intersect (click_rectangle, null)) {
blank_window_in = false; blank_window_in = false;
} }
}); });
blank_window_gesture.cancel.connect ((gesture, sequence) => { blank_window_gesture.cancel.connect (() => {
blank_window_down = false; blank_window_down = false;
}); });
// Only use release for closing notifications due to Escape key // Only use release for closing notifications due to Escape key
// sometimes being passed through to unfucused application // sometimes being passed through to unfucused application
// Ex: Firefox in a fullscreen YouTube video // Ex: Firefox in a fullscreen YouTube video
this.key_release_event.connect ((w, event_key) => { key_controller = new Gtk.EventControllerKey ();
if (this.get_focus () is Gtk.Entry) { key_controller.set_propagation_phase (Gtk.PropagationPhase.CAPTURE);
switch (Gdk.keyval_name (event_key.keyval)) { ((Gtk.Widget) this).add_controller (key_controller);
case "Escape": key_controller.key_released.connect (key_released_event_cb);
this.set_focus (null); key_controller.key_pressed.connect (key_press_event_cb);
return true;
}
return false;
}
if (event_key.type == Gdk.EventType.KEY_RELEASE) {
switch (Gdk.keyval_name (event_key.keyval)) {
case "Escape":
case "Caps_Lock":
this.set_visibility (false);
return true;
}
}
return true;
});
key_press_event.connect (key_press_event_cb);
stack.set_visible_child_name (STACK_PLACEHOLDER_PAGE); stack.set_visible_child_name (STACK_PLACEHOLDER_PAGE);
// Switches the stack page depending on the amount of notifications
list_box.add.connect (() => {
stack.set_visible_child_name (STACK_NOTIFICATIONS_PAGE);
});
list_box.remove.connect ((container, _widget) => {
if (container.get_children ().length () > 0) return;
stack.set_visible_child_name (STACK_PLACEHOLDER_PAGE);
});
add_widgets (); add_widgets ();
notification_fade_animation = new Animation (this, Constants.ANIMATION_DURATION,
Animation.ease_in_out_cubic,
fade_animation_value_cb,
fade_animation_done_cb);
scroll_animation = new Animation (this, Constants.ANIMATION_DURATION,
Animation.ease_in_out_cubic,
scroll_animation_value_cb,
scroll_animation_done_cb);
list_box.draw.connect (list_box_draw_cb);
}
void fade_animation_value_cb (double progress) {
this.fade_animation_progress = progress;
this.queue_draw ();
}
void fade_animation_done_cb () {}
void fade_animate (double to) {
notification_fade_animation.stop ();
notification_fade_animation.start (fade_animation_progress, to);
}
void scroll_animation_value_cb (double progress) {
this.scroll_animation_progress = progress;
// Scroll to the top of the group
if (scroll_animation_progress > 0) {
scrolled_window.vadjustment.set_value (scroll_animation_progress);
}
}
void scroll_animation_done_cb () {
int y = expanded_group.get_relative_y (list_box);
if (y > 0) {
scrolled_window.vadjustment.set_value (y);
}
} }
// Scroll to the expanded group once said group has fully expanded
void scroll_animate (double to) { void scroll_animate (double to) {
scroll_animation.stop (); if (scroll_timer_id > 0) {
scroll_animation.start (scroll_animation_progress, to); Source.remove (scroll_timer_id);
scroll_timer_id = 0;
}
scroll_timer_id = Timeout.add_once (Constants.ANIMATION_DURATION, () => {
scroll_timer_id = 0;
if (expanded_group == null) {
return;
}
float y = expanded_group.get_relative_y (list_box);
if (y > 0) {
viewport.scroll_to (expanded_group, null);
}
});
} }
/// Fade non-expanded groups when one group is expanded private void key_released_event_cb (uint keyval, uint keycode, Gdk.ModifierType state) {
private bool list_box_draw_cb (Cairo.Context cr) { if (this.get_focus () is Gtk.Entry) {
Cairo.Pattern fade_gradient = new Cairo.Pattern.linear (0, 0, 0, 1); switch (Gdk.keyval_name (keyval)) {
fade_gradient.add_color_stop_rgba (0, 1, 1, 1, 1 - fade_animation_progress - 0.5); case "Escape":
this.set_focus (null);
foreach (unowned Gtk.Widget widget in list_box.get_children ()) { return;
Gtk.Allocation alloc;
widget.get_allocated_size (out alloc, null);
cr.save ();
cr.translate (0, alloc.y);
cr.push_group ();
widget.draw (cr);
cr.scale (alloc.width, alloc.height);
if (widget != expanded_group) {
cr.set_source (fade_gradient);
cr.rectangle (0, 0, alloc.width, alloc.height);
cr.set_operator (Cairo.Operator.DEST_OUT);
cr.fill ();
} }
return;
cr.pop_group_to_source (); }
cr.paint (); switch (Gdk.keyval_name (keyval)) {
case "Escape":
cr.restore (); case "Caps_Lock":
this.set_visibility (false);
return;
} }
return true;
} }
private bool key_press_event_cb (Gdk.EventKey event_key) { private bool key_press_event_cb (uint keyval, uint keycode, Gdk.ModifierType state) {
if (this.get_focus () is Gtk.Entry) return false; if (this.get_focus () is Gtk.Entry) return false;
if (event_key.type == Gdk.EventType.KEY_PRESS) { var children = list_box_controller.get_children ();
var children = list_box.get_children (); var group = (NotificationGroup) list_box.get_focus_child ();
var group = (NotificationGroup) list_box.get_focus_child (); switch (Gdk.keyval_name (keyval)) {
switch (Gdk.keyval_name (event_key.keyval)) { case "Return":
case "Return": if (group != null) {
if (group != null) { var noti = group.get_latest_notification ();
var noti = group.get_latest_notification (); if (group.only_single_notification () && noti != null) {
if (group.only_single_notification () && noti != null) { noti.click_default_action ();
noti.click_default_action (); break;
break;
}
group.on_expand_change (group.toggle_expanded ());
} }
break; group.on_expand_change (group.toggle_expanded ());
case "Delete": }
case "BackSpace": break;
if (group != null) { case "Delete":
int len = (int) children.length (); case "BackSpace":
if (len == 0) break; if (group != null) {
// Add a delta so that we select the next notification int len = (int) children.length ();
// due to it not being gone from the list yet due to if (len == 0) break;
// the fade transition // Add a delta so that we select the next notification
int delta = 2; // due to it not being gone from the list yet due to
if (list_reverse) { // the fade transition
if (children.first ().data != group) { int delta = 2;
delta = 0; if (list_reverse) {
} if (children.first ().data != group) {
list_position--; delta = 0;
} else {
if (list_position > 0) list_position--;
if (children.last ().data == group) {
delta = 0;
}
} }
var noti = group.get_latest_notification (); list_position--;
if (group.only_single_notification () && noti != null) { } else {
close_notification (noti.param.applied_id, true); if (list_position > 0) list_position--;
break; if (children.last ().data == group) {
delta = 0;
} }
group.close_all_notifications ();
navigate_list (list_position + delta);
return true;
} }
break; var noti = group.get_latest_notification ();
case "C": if (group.only_single_notification () && noti != null) {
close_all_notifications (); close_notification (noti.param.applied_id, true);
break; break;
case "D":
try {
swaync_daemon.toggle_dnd ();
} catch (Error e) {
error ("Error: %s\n", e.message);
}
break;
case "Down":
if (list_position + 1 < children.length ()) {
++list_position;
} }
break; group.close_all_notifications ();
case "Up": navigate_list (list_position + delta);
if (list_position > 0) --list_position; return true;
break; }
case "Home": break;
list_position = 0; case "C":
break; close_all_notifications ();
case "End": break;
list_position = ((int) children.length ()) - 1; case "D":
if (list_position == uint.MAX) list_position = 0; try {
break; swaync_daemon.toggle_dnd ();
default: } catch (Error e) {
// Pressing 1-9 to activate a notification action error ("Error: %s\n", e.message);
for (int i = 0; i < 9; i++) { }
uint keyval = Gdk.keyval_from_name ( break;
(i + 1).to_string ()); case "Down":
if (event_key.keyval == keyval && group != null) { if (list_position + 1 < children.length ()) {
var noti = group.get_latest_notification (); ++list_position;
noti.click_alt_action (i); }
break; break;
} case "Up":
if (list_position > 0) --list_position;
break;
case "Home":
list_position = 0;
break;
case "End":
list_position = ((int) children.length ()) - 1;
if (list_position == uint.MAX) list_position = 0;
break;
default:
// Pressing 1-9 to activate a notification action
for (int i = 0; i < 9; i++) {
uint num_keyval = Gdk.keyval_from_name (
(i + 1).to_string ());
if (keyval == num_keyval && group != null) {
var noti = group.get_latest_notification ();
noti.click_alt_action (i);
break;
} }
break; }
} break;
navigate_list (list_position);
} }
navigate_list (list_position);
// Override the builtin list navigation // Override the builtin list navigation
return true; return true;
} }
...@@ -371,10 +357,13 @@ namespace SwayNotificationCenter { ...@@ -371,10 +357,13 @@ namespace SwayNotificationCenter {
bool has_notification = false; bool has_notification = false;
foreach (string key in w) { foreach (string key in w) {
// Reposition the notifications_box // Reposition the notifications_box
// TODO: Move notifications into its own widget
if (key == "notifications") { if (key == "notifications") {
has_notification = true; has_notification = true;
uint pos = box.get_children ().length (); unowned Gtk.Widget ? sibling = box.get_last_child ();
box.reorder_child (notifications_box, (int) (pos > 0 ? --pos : 0)); if (sibling != notifications_box) {
box.reorder_child_after (notifications_box, sibling);
}
continue; continue;
} }
// Add the widget if it is valid // Add the widget if it is valid
...@@ -382,13 +371,14 @@ namespace SwayNotificationCenter { ...@@ -382,13 +371,14 @@ namespace SwayNotificationCenter {
key, swaync_daemon, noti_daemon); key, swaync_daemon, noti_daemon);
if (widget == null) continue; if (widget == null) continue;
widgets.append_val (widget); widgets.append_val (widget);
box.pack_start (widgets.index (widgets.length - 1), box.append (widgets.index (widgets.length - 1));
false, true, 0);
} }
if (!has_notification) { if (!has_notification) {
warning ("Notification widget not included in \"widgets\" config. Using default bottom position"); warning ("Notification widget not included in \"widgets\" config. Using default bottom position");
uint pos = box.get_children ().length (); unowned Gtk.Widget ? sibling = box.get_last_child ();
box.reorder_child (notifications_box, (int) (pos > 0 ? --pos : 0)); if (sibling != notifications_box) {
box.reorder_child_after (notifications_box, sibling);
}
} }
} }
...@@ -411,23 +401,8 @@ namespace SwayNotificationCenter { ...@@ -411,23 +401,8 @@ namespace SwayNotificationCenter {
GtkLayerShell.set_keyboard_mode (this, mode); GtkLayerShell.set_keyboard_mode (this, mode);
// Set layer // Set layer
GtkLayerShell.Layer layer; GtkLayerShell.set_layer (
switch (ConfigModel.instance.control_center_layer) { this, ConfigModel.instance.control_center_layer.to_layer ());
case Layer.BACKGROUND:
layer = GtkLayerShell.Layer.BACKGROUND;
break;
case Layer.BOTTOM:
layer = GtkLayerShell.Layer.BOTTOM;
break;
case Layer.TOP:
layer = GtkLayerShell.Layer.TOP;
break;
default:
case Layer.OVERLAY:
layer = GtkLayerShell.Layer.OVERLAY;
break;
}
GtkLayerShell.set_layer (this, layer);
// Set whether the control center should cover the whole screen or not // Set whether the control center should cover the whole screen or not
bool cover_screen = ConfigModel.instance.layer_shell_cover_screen; bool cover_screen = ConfigModel.instance.layer_shell_cover_screen;
...@@ -470,13 +445,13 @@ namespace SwayNotificationCenter { ...@@ -470,13 +445,13 @@ namespace SwayNotificationCenter {
} }
} }
// Set the box margins // Set the window margins
box.set_margin_top (ConfigModel.instance.control_center_margin_top); window.set_margin_top (ConfigModel.instance.control_center_margin_top);
box.set_margin_start (ConfigModel.instance.control_center_margin_left); window.set_margin_start (ConfigModel.instance.control_center_margin_left);
box.set_margin_end (ConfigModel.instance.control_center_margin_right); window.set_margin_end (ConfigModel.instance.control_center_margin_right);
box.set_margin_bottom (ConfigModel.instance.control_center_margin_bottom); window.set_margin_bottom (ConfigModel.instance.control_center_margin_bottom);
// Anchor box to north/south edges as needed // Anchor window to north/south edges as needed
Gtk.Align align_x = Gtk.Align.END; Gtk.Align align_x = Gtk.Align.END;
switch (pos_x) { switch (pos_x) {
case PositionX.LEFT: case PositionX.LEFT:
...@@ -515,15 +490,22 @@ namespace SwayNotificationCenter { ...@@ -515,15 +490,22 @@ namespace SwayNotificationCenter {
// Fit the ControlCenter to the monitor height // Fit the ControlCenter to the monitor height
if (ConfigModel.instance.fit_to_screen) align_y = Gtk.Align.FILL; if (ConfigModel.instance.fit_to_screen) align_y = Gtk.Align.FILL;
// Set the ControlCenter alignment // Set the ControlCenter alignment
box.set_halign (align_x); window.set_halign (align_x);
box.set_valign (align_y); window.set_valign (align_y);
list_box.set_valign (list_align); list_box.set_valign (list_align);
list_box.set_sort_func (list_box_sort_func); list_box.set_sort_func (list_box_sort_func);
list_box.set_selection_mode (Gtk.SelectionMode.NONE);
list_box.set_activate_on_single_click (false);
// Always set the size request in all events. window.set_propagate_natural_height (true);
// Re-set the minimum size
box.set_size_request (ConfigModel.instance.control_center_width, box.set_size_request (ConfigModel.instance.control_center_width,
ConfigModel.instance.control_center_height); ConfigModel.instance.control_center_height);
// Use a custom layout to limit the minimum size above to the size
// of the window so that it doesn't exceed the monitors edge
window.child.set_layout_manager (new FixedViewportLayout (window));
} }
/** /**
...@@ -562,7 +544,7 @@ namespace SwayNotificationCenter { ...@@ -562,7 +544,7 @@ namespace SwayNotificationCenter {
public uint notification_count () { public uint notification_count () {
uint count = 0; uint count = 0;
foreach (unowned Gtk.Widget widget in list_box.get_children ()) { foreach (unowned Gtk.Widget widget in list_box_controller.get_children ()) {
if (widget is NotificationGroup) { if (widget is NotificationGroup) {
count += ((NotificationGroup) widget).get_num_notifications (); count += ((NotificationGroup) widget).get_num_notifications ();
} }
...@@ -571,7 +553,7 @@ namespace SwayNotificationCenter { ...@@ -571,7 +553,7 @@ namespace SwayNotificationCenter {
} }
public void close_all_notifications () { public void close_all_notifications () {
foreach (var w in list_box.get_children ()) { foreach (unowned Gtk.Widget w in list_box_controller.get_children ()) {
NotificationGroup group = (NotificationGroup) w; NotificationGroup group = (NotificationGroup) w;
if (group != null) group.close_all_notifications (); if (group != null) group.close_all_notifications ();
} }
...@@ -591,14 +573,14 @@ namespace SwayNotificationCenter { ...@@ -591,14 +573,14 @@ namespace SwayNotificationCenter {
} }
private void navigate_list (int i) { private void navigate_list (int i) {
unowned Gtk.Widget ? widget = list_box.get_children ().nth_data (i); unowned Gtk.ListBoxRow ? widget = list_box.get_row_at_index (i);
if (widget == null) { if (widget == null) {
// Try getting the last widget // Try getting the last widget
if (list_reverse) { if (list_reverse) {
widget = list_box.get_children ().nth_data (0); widget = list_box.get_row_at_index (0);
} else { } else {
int len = ((int) list_box.get_children ().length ()) - 1; int len = ((int) list_box_controller.length) - 1;
widget = list_box.get_children ().nth_data (len); widget = list_box.get_row_at_index (len);
} }
} }
if (widget != null) { if (widget != null) {
...@@ -615,20 +597,18 @@ namespace SwayNotificationCenter { ...@@ -615,20 +597,18 @@ namespace SwayNotificationCenter {
if (this.visible) { if (this.visible) {
// Focus the first notification // Focus the first notification
list_position = list_reverse ? list_position = (list_reverse ? (((int) list_box_controller.length) - 1) : 0)
(((int) list_box.get_children ().length ()) - 1) : 0; .clamp (0, (int) list_box_controller.length);
if (list_position == uint.MAX) list_position = 0;
list_box.grab_focus (); list_box.grab_focus ();
navigate_list (list_position); navigate_list (list_position);
foreach (var w in list_box.get_children ()) { foreach (unowned Gtk.Widget w in list_box_controller.get_children ()) {
var group = (NotificationGroup) w; var group = (NotificationGroup) w;
if (group != null) group.update (); if (group != null) group.update ();
} }
this.get_style_context ().add_class ("open"); add_css_class ("open");
} } else {
else { remove_css_class ("open");
this.get_style_context ().remove_class ("open");
} }
swaync_daemon.subscribe_v2 (notification_count (), swaync_daemon.subscribe_v2 (notification_count (),
noti_daemon.dnd, noti_daemon.dnd,
...@@ -638,45 +618,56 @@ namespace SwayNotificationCenter { ...@@ -638,45 +618,56 @@ namespace SwayNotificationCenter {
public bool toggle_visibility () { public bool toggle_visibility () {
var cc_visibility = !this.visible; var cc_visibility = !this.visible;
if (this.visible != cc_visibility) { set_visibility (cc_visibility);
this.set_visible (cc_visibility);
on_visibility_change ();
}
return cc_visibility; return cc_visibility;
} }
public void set_visibility (bool visibility) { public void set_visibility (bool visibility) {
if (this.visible == visibility) return; if (this.visible == visibility) return;
if (visibility) {
// Destroy the wl_surface to get a new "enter-monitor" signal
((Gtk.Widget) this).unrealize ();
}
this.set_visible (visibility); this.set_visible (visibility);
on_visibility_change (); on_visibility_change ();
} }
public void close_notification (uint32 id, bool dismiss) { public void close_notification (uint32 id, bool dismiss) {
unowned NotificationGroup group = null; unowned NotificationGroup group = null;
if (!noti_groups_id.lookup_extended (id, null, out group))return; if (!noti_groups_id.lookup_extended (id, null, out group)) {
return;
}
foreach (var w in group.get_notifications ()) { foreach (var w in group.get_notifications ()) {
var noti = (Notification) w; var noti = (Notification) w;
if (noti != null && noti.param.applied_id == id) { if (noti != null && noti.param.applied_id == id) {
if (!dismiss) { if (dismiss) {
noti.remove_noti_timeout ();
noti.destroy ();
} else {
noti.close_notification (false); noti.close_notification (false);
group.remove_notification (noti);
} }
group.remove_notification (noti);
noti_groups_id.remove (id); noti_groups_id.remove (id);
break; break;
} }
} }
if (group.is_empty ()) {
if (group.only_single_notification ()) {
if (expanded_group == group) {
expanded_group = null;
}
} else if (group.is_empty ()) {
if (group.name_id.length > 0) { if (group.name_id.length > 0) {
noti_groups_name.remove (group.name_id); noti_groups_name.remove (group.name_id);
} }
if (expanded_group == group) { if (expanded_group == group) {
expanded_group = null; expanded_group = null;
fade_animate (1);
} }
group.destroy ();
list_box_controller.remove (group);
navigate_list (--list_position);
// Switches the stack page depending on the amount of notifications
if (list_box_controller.length < 1) {
stack.set_visible_child_name (STACK_PLACEHOLDER_PAGE);
}
} }
} }
...@@ -704,12 +695,6 @@ namespace SwayNotificationCenter { ...@@ -704,12 +695,6 @@ namespace SwayNotificationCenter {
var noti = new Notification.regular (param, var noti = new Notification.regular (param,
noti_daemon, noti_daemon,
NotificationType.CONTROL_CENTER); NotificationType.CONTROL_CENTER);
noti.grab_focus.connect ((w) => {
int i = list_box.get_children ().index (w);
if (list_position != uint.MAX && list_position != i) {
list_position = i;
}
});
noti.set_time (); noti.set_time ();
NotificationGroup ? group = null; NotificationGroup ? group = null;
...@@ -721,34 +706,53 @@ namespace SwayNotificationCenter { ...@@ -721,34 +706,53 @@ namespace SwayNotificationCenter {
// Collapse other groups on expand // Collapse other groups on expand
group.on_expand_change.connect ((expanded) => { group.on_expand_change.connect ((expanded) => {
if (!expanded) { if (!expanded) {
fade_animate (1); foreach (unowned Gtk.Widget child in list_box_controller.get_children ()) {
foreach (unowned Gtk.Widget child in list_box.get_children ()) { if (child is NotificationGroup) {
child.set_sensitive (true); child.remove_css_class ("not-expanded");
}
} }
expanded_group = null;
return; return;
} }
expanded_group = group; expanded_group = group;
expanded_group.set_sensitive (true); float y = expanded_group.get_relative_y (list_box);
fade_animate (0);
int y = expanded_group.get_relative_y (list_box);
if (y > 0) { if (y > 0) {
scroll_animate (y); scroll_animate (y);
} }
foreach (unowned Gtk.Widget child in list_box.get_children ()) { foreach (unowned Gtk.Widget child in list_box_controller.get_children ()) {
NotificationGroup g = (NotificationGroup) child; NotificationGroup g = (NotificationGroup) child;
if (g != null && g != group) { if (g != null && g != group) {
g.set_expanded (false); g.set_expanded (false);
if (g.only_single_notification ()) { child.add_css_class ("not-expanded");
g.set_sensitive (false);
}
} }
} }
}); });
if (param.name_id.length > 0) { if (param.name_id.length > 0) {
noti_groups_name.set (param.name_id, group); noti_groups_name.set (param.name_id, group);
} }
list_box.add (group);
// Set the new list position when the group receives keyboard focus
Gtk.EventControllerFocus focus_controller = new Gtk.EventControllerFocus ();
group.add_controller (focus_controller);
focus_controller.enter.connect (() => {
int i = list_box_controller.get_children ().index (group);
if (list_position != int.MAX && list_position != i) {
list_position = i;
}
});
// Switches the stack page depending on the amount of notifications
stack.set_visible_child_name (STACK_NOTIFICATIONS_PAGE);
list_box_controller.append (group);
} }
// Set the group as not-expanded (reduce opacity) if there's
// already a group that's expanded.
if (expanded_group != null) {
group.add_css_class ("not-expanded");
}
noti_groups_id.set (param.applied_id, group); noti_groups_id.set (param.applied_id, group);
group.add_notification (noti); group.add_notification (noti);
...@@ -771,18 +775,5 @@ namespace SwayNotificationCenter { ...@@ -771,18 +775,5 @@ namespace SwayNotificationCenter {
public bool get_visibility () { public bool get_visibility () {
return this.visible; return this.visible;
} }
/** Forces each notification EventBox to reload its style_context #27 */
private void reload_notifications_style () {
foreach (var c in list_box.get_children ()) {
NotificationGroup group = (NotificationGroup) c;
if (group != null) {
foreach (unowned var widget in group.get_notifications ()) {
Notification noti = (Notification) widget;
noti.reload_style_context ();
}
}
}
}
} }
} }
...@@ -51,15 +51,14 @@ namespace SwayNotificationCenter.Widgets { ...@@ -51,15 +51,14 @@ namespace SwayNotificationCenter.Widgets {
slider.set_draw_value (false); slider.set_draw_value (false);
slider.set_round_digits (0); slider.set_round_digits (0);
slider.set_hexpand (true);
slider.value_changed.connect (() => { slider.value_changed.connect (() => {
this.client.set_brightness.begin ((float) slider.get_value ()); this.client.set_brightness.begin ((float) slider.get_value ());
slider.tooltip_text = ((int) slider.get_value ()).to_string (); slider.tooltip_text = ((int) slider.get_value ()).to_string ();
}); });
add (label_widget); append (label_widget);
pack_start (slider, true, true, 0); append (slider);
show_all ();
} }
public override void on_cc_visibility_change (bool val) { public override void on_cc_visibility_change (bool val) {
......
...@@ -79,15 +79,11 @@ namespace SwayNotificationCenter.Widgets { ...@@ -79,15 +79,11 @@ namespace SwayNotificationCenter.Widgets {
public async void set_brightness (float percent) { public async void set_brightness (float percent) {
this.close (); this.close ();
try { if (subsystem == "backlight") {
if (subsystem == "backlight") { int actual = calc_actual (percent);
int actual = calc_actual (percent); login1.set_brightness.begin (subsystem, device, actual);
login1.set_brightness.begin (subsystem, device, actual); } else {
} else { login1.set_brightness.begin (subsystem, device, (uint32) percent);
login1.set_brightness.begin (subsystem, device, (uint32) percent);
}
} catch (Error e) {
error ("Error %s\n", e.message);
} }
connect_monitor (); connect_monitor ();
} }
......
...@@ -34,8 +34,8 @@ namespace SwayNotificationCenter.Widgets { ...@@ -34,8 +34,8 @@ namespace SwayNotificationCenter.Widgets {
this.swaync_daemon = swaync_daemon; this.swaync_daemon = swaync_daemon;
this.noti_daemon = noti_daemon; this.noti_daemon = noti_daemon;
get_style_context ().add_class (css_class_name); add_css_class (css_class_name);
if (suffix.length > 0) get_style_context ().add_class (suffix); if (suffix.length > 0) add_css_class (suffix);
} }
protected Json.Object ? get_config (Gtk.Widget widget) { protected Json.Object ? get_config (Gtk.Widget widget) {
...@@ -108,7 +108,7 @@ namespace SwayNotificationCenter.Widgets { ...@@ -108,7 +108,7 @@ namespace SwayNotificationCenter.Widgets {
string t = actions.get_object_element (i).get_string_member_with_default ("type", "normal"); string t = actions.get_object_element (i).get_string_member_with_default ("type", "normal");
ButtonType type = ButtonType.parse (t); ButtonType type = ButtonType.parse (t);
string update_command = string update_command =
actions.get_object_element (i).get_string_member_with_default ("update-command", ""); actions.get_object_element (i).get_string_member_with_default ("update-command", "");
bool active = actions.get_object_element (i).get_boolean_member_with_default ("active", false); bool active = actions.get_object_element (i).get_boolean_member_with_default ("active", false);
res[i] = Action () { res[i] = Action () {
label = label, label = label,
......
...@@ -23,7 +23,8 @@ namespace SwayNotificationCenter.Widgets { ...@@ -23,7 +23,8 @@ namespace SwayNotificationCenter.Widgets {
Gtk.FlowBox container = new Gtk.FlowBox (); Gtk.FlowBox container = new Gtk.FlowBox ();
container.set_selection_mode (Gtk.SelectionMode.NONE); container.set_selection_mode (Gtk.SelectionMode.NONE);
pack_start (container, true, true, 0); container.set_hexpand (true);
append (container);
// add action to container // add action to container
foreach (var act in actions) { foreach (var act in actions) {
...@@ -40,8 +41,6 @@ namespace SwayNotificationCenter.Widgets { ...@@ -40,8 +41,6 @@ namespace SwayNotificationCenter.Widgets {
break; break;
} }
} }
show_all ();
} }
public override void on_cc_visibility_change (bool value) { public override void on_cc_visibility_change (bool value) {
......
...@@ -24,7 +24,9 @@ namespace SwayNotificationCenter.Widgets { ...@@ -24,7 +24,9 @@ namespace SwayNotificationCenter.Widgets {
// Title // Title
title_widget = new Gtk.Label (title); title_widget = new Gtk.Label (title);
add (title_widget); title_widget.set_hexpand (true);
title_widget.set_halign (Gtk.Align.START);
append (title_widget);
// Dnd button // Dnd button
dnd_button = new Gtk.Switch () { dnd_button = new Gtk.Switch () {
...@@ -40,10 +42,8 @@ namespace SwayNotificationCenter.Widgets { ...@@ -40,10 +42,8 @@ namespace SwayNotificationCenter.Widgets {
dnd_button.set_can_focus (false); dnd_button.set_can_focus (false);
dnd_button.valign = Gtk.Align.CENTER; dnd_button.valign = Gtk.Align.CENTER;
// Backwards compatible towards older CSS stylesheets // Backwards compatible towards older CSS stylesheets
dnd_button.get_style_context ().add_class ("control-center-dnd"); dnd_button.add_css_class ("control-center-dnd");
pack_end (dnd_button, false); append (dnd_button);
show_all ();
} }
private bool state_set (Gtk.Widget widget, bool state) { private bool state_set (Gtk.Widget widget, bool state) {
......
...@@ -42,8 +42,10 @@ namespace SwayNotificationCenter.Widgets { ...@@ -42,8 +42,10 @@ namespace SwayNotificationCenter.Widgets {
} }
title_widget = new Gtk.Label (title); title_widget = new Gtk.Label (title);
title_widget.set_halign (Gtk.Align.START);
title_widget.set_hexpand (true);
title_widget.show (); title_widget.show ();
add (title_widget); append (title_widget);
if (has_clear_all_button) { if (has_clear_all_button) {
clear_all_button = new Gtk.Button.with_label (button_text); clear_all_button = new Gtk.Button.with_label (button_text);
...@@ -57,7 +59,7 @@ namespace SwayNotificationCenter.Widgets { ...@@ -57,7 +59,7 @@ namespace SwayNotificationCenter.Widgets {
clear_all_button.set_can_focus (false); clear_all_button.set_can_focus (false);
clear_all_button.valign = Gtk.Align.CENTER; clear_all_button.valign = Gtk.Align.CENTER;
clear_all_button.show (); clear_all_button.show ();
pack_end (clear_all_button, false); append (clear_all_button);
} }
hide (); hide ();
......
...@@ -29,18 +29,17 @@ namespace SwayNotificationCenter.Widgets { ...@@ -29,18 +29,17 @@ namespace SwayNotificationCenter.Widgets {
label_widget.set_text (text); label_widget.set_text (text);
label_widget.set_ellipsize (Pango.EllipsizeMode.END); label_widget.set_ellipsize (Pango.EllipsizeMode.END);
label_widget.set_line_wrap (true); label_widget.set_wrap (true);
label_widget.set_lines (max_lines); label_widget.set_lines (max_lines);
// Without this and pack_start fill, the label would expand to // Without this and pack_start fill, the label would expand to
// the monitors full width... GTK bug!... // the monitors full width... GTK bug!...
label_widget.set_max_width_chars (0); label_widget.set_max_width_chars (0);
label_widget.set_line_wrap_mode (Pango.WrapMode.WORD_CHAR); label_widget.set_wrap_mode (Pango.WrapMode.WORD_CHAR);
label_widget.set_justify (Gtk.Justification.LEFT); label_widget.set_justify (Gtk.Justification.LEFT);
label_widget.set_alignment (0, 0); label_widget.set_xalign (0.0f);
label_widget.set_yalign (0.0f);
pack_start (label_widget, true, true, 0); append (label_widget);
show_all ();
} }
} }
} }
...@@ -37,35 +37,48 @@ namespace SwayNotificationCenter.Widgets { ...@@ -37,35 +37,48 @@ namespace SwayNotificationCenter.Widgets {
} }
} }
Gtk.Box menus_container; Gtk.Box left_container;
Gtk.Box topbar_container; Gtk.Box right_container;
List<ConfigObject ?> menu_objects; List<ConfigObject ?> menu_objects;
List<ToggleButton> toggle_buttons; List<ToggleButton> toggle_buttons;
public Menubar (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) { public Menubar (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
base (suffix, swaync_daemon, noti_daemon); base (suffix, swaync_daemon, noti_daemon);
set_orientation (Gtk.Orientation.VERTICAL);
set_hexpand (true);
Json.Object ? config = get_config (this); Json.Object ? config = get_config (this);
if (config != null) { if (config != null) {
parse_config_objects (config); parse_config_objects (config);
} }
menus_container = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); Gtk.Box topbar_container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
topbar_container.add_css_class ("menu-button-bar");
topbar_container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); append (topbar_container);
topbar_container.get_style_context ().add_class ("menu-button-bar");
left_container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) {
menus_container.add (topbar_container); overflow = Gtk.Overflow.HIDDEN,
hexpand = true,
halign = Gtk.Align.START,
};
left_container.add_css_class ("widget-menubar-container");
left_container.add_css_class ("start");
topbar_container.append (left_container);
right_container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) {
overflow = Gtk.Overflow.HIDDEN,
hexpand = true,
halign = Gtk.Align.END,
};
right_container.add_css_class ("widget-menubar-container");
right_container.add_css_class ("end");
topbar_container.append (right_container);
for (int i = 0; i < menu_objects.length (); i++) { for (int i = 0; i < menu_objects.length (); i++) {
unowned ConfigObject ? obj = menu_objects.nth_data (i); unowned ConfigObject ? obj = menu_objects.nth_data (i);
add_menu (ref obj); add_menu (ref obj);
} }
pack_start (menus_container, true, true, 0);
show_all ();
foreach (var obj in menu_objects) { foreach (var obj in menu_objects) {
obj.revealer ?.set_reveal_child (false); obj.revealer ?.set_reveal_child (false);
} }
...@@ -75,43 +88,48 @@ namespace SwayNotificationCenter.Widgets { ...@@ -75,43 +88,48 @@ namespace SwayNotificationCenter.Widgets {
switch (obj.type) { switch (obj.type) {
case MenuType.BUTTONS: case MenuType.BUTTONS:
Gtk.Box container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); Gtk.Box container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
if (obj.name != null) container.get_style_context ().add_class (obj.name); if (obj.name != null) container.add_css_class (obj.name);
foreach (Action a in obj.actions) { foreach (Action a in obj.actions) {
switch (a.type) { switch (a.type) {
case ButtonType.TOGGLE: case ButtonType.TOGGLE:
ToggleButton tb = new ToggleButton (a.label, a.command, a.update_command, a.active); ToggleButton tb = new ToggleButton (a.label, a.command, a.update_command, a.active);
container.add (tb); container.append (tb);
toggle_buttons.append (tb); toggle_buttons.append (tb);
break; break;
default: default:
Gtk.Button b = new Gtk.Button.with_label (a.label); Gtk.Button b = new Gtk.Button.with_label (a.label);
b.clicked.connect (() => execute_command.begin (a.command)); b.clicked.connect (() => execute_command.begin (a.command));
container.add (b); container.append (b);
break; break;
} }
} }
switch (obj.position) { switch (obj.position) {
case Position.LEFT: case Position.LEFT:
topbar_container.pack_start (container, false, false, 0); left_container.append (container);
break; break;
case Position.RIGHT: case Position.RIGHT:
topbar_container.pack_end (container, false, false, 0); right_container.append (container);
break; break;
} }
break; break;
case MenuType.MENU: case MenuType.MENU:
Gtk.Button show_button = new Gtk.Button.with_label (obj.label); Gtk.ToggleButton show_button = new Gtk.ToggleButton.with_label (obj.label);
Gtk.Box menu = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); Gtk.Box menu = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
if (obj.name != null) menu.get_style_context ().add_class (obj.name); if (obj.name != null) menu.add_css_class (obj.name);
Gtk.Revealer r = new Gtk.Revealer (); Gtk.Revealer r = new Gtk.Revealer ();
r.add (menu); r.set_child (menu);
r.set_transition_duration (obj.animation_duration); r.set_transition_duration (obj.animation_duration);
r.set_transition_type (obj.animation_type); r.set_transition_type (obj.animation_type);
obj.revealer = r; obj.revealer = r;
// Make sure that the toggle buttons state is always synced
// with the revealers visibility.
r.bind_property ("child-revealed",
show_button, "active", BindingFlags.SYNC_CREATE, null, null);
show_button.clicked.connect (() => { show_button.clicked.connect (() => {
bool visible = !r.get_reveal_child (); bool visible = !r.get_reveal_child ();
foreach (var o in menu_objects) { foreach (var o in menu_objects) {
...@@ -124,27 +142,29 @@ namespace SwayNotificationCenter.Widgets { ...@@ -124,27 +142,29 @@ namespace SwayNotificationCenter.Widgets {
switch (a.type) { switch (a.type) {
case ButtonType.TOGGLE: case ButtonType.TOGGLE:
ToggleButton tb = new ToggleButton (a.label, a.command, a.update_command, a.active); ToggleButton tb = new ToggleButton (a.label, a.command, a.update_command, a.active);
menu.pack_start (tb, true, true, 0); tb.set_hexpand (true);
menu.append (tb);
toggle_buttons.append (tb); toggle_buttons.append (tb);
break; break;
default: default:
Gtk.Button b = new Gtk.Button.with_label (a.label); Gtk.Button b = new Gtk.Button.with_label (a.label);
b.set_hexpand (true);
b.clicked.connect (() => execute_command.begin (a.command)); b.clicked.connect (() => execute_command.begin (a.command));
menu.pack_start (b, true, true, 0); menu.append (b);
break; break;
} }
} }
switch (obj.position) { switch (obj.position) {
case Position.RIGHT: case Position.RIGHT:
topbar_container.pack_end (show_button, false, false, 0); right_container.append (show_button);
break; break;
case Position.LEFT: case Position.LEFT:
topbar_container.pack_start (show_button, false, false, 0); left_container.append (show_button);
break; break;
} }
menus_container.add (r); append (r);
break; break;
} }
} }
......
namespace SwayNotificationCenter.Widgets.Mpris { namespace SwayNotificationCenter.Widgets.Mpris {
public struct Config { public struct Config {
int image_size; int image_size;
int image_radius;
bool blur;
bool autohide; bool autohide;
string[] blacklist; string[] blacklist;
} }
...@@ -24,15 +22,13 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -24,15 +22,13 @@ namespace SwayNotificationCenter.Widgets.Mpris {
Gtk.Button button_prev; Gtk.Button button_prev;
Gtk.Button button_next; Gtk.Button button_next;
Gtk.Box carousel_box; Gtk.Box carousel_box;
Hdy.Carousel carousel; Adw.Carousel carousel;
Hdy.CarouselIndicatorDots carousel_dots; Adw.CarouselIndicatorDots carousel_dots;
// Default config values // Default config values
Config mpris_config = Config () { Config mpris_config = Config () {
image_size = 96, image_size = 96,
image_radius = 12, autohide = false,
blur = true,
autohide = false
}; };
public Mpris (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) { public Mpris (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
...@@ -45,43 +41,41 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -45,43 +41,41 @@ namespace SwayNotificationCenter.Widgets.Mpris {
visible = true, visible = true,
}; };
button_prev = new Gtk.Button.from_icon_name ("go-previous", Gtk.IconSize.BUTTON) { button_prev = new Gtk.Button.from_icon_name ("go-previous") {
relief = Gtk.ReliefStyle.NONE, has_frame = false,
visible = false, visible = false,
}; };
button_prev.clicked.connect (() => change_carousel_position (-1)); button_prev.clicked.connect (() => change_carousel_position (-1));
button_next = new Gtk.Button.from_icon_name ("go-next", Gtk.IconSize.BUTTON) { button_next = new Gtk.Button.from_icon_name ("go-next") {
relief = Gtk.ReliefStyle.NONE, has_frame = false,
visible = false, visible = false,
}; };
button_next.clicked.connect (() => change_carousel_position (1)); button_next.clicked.connect (() => change_carousel_position (1));
carousel = new Hdy.Carousel () { carousel = new Adw.Carousel () {
visible = true, visible = true,
}; };
carousel.allow_scroll_wheel = true; carousel.allow_scroll_wheel = true;
carousel.page_changed.connect ((index) => { carousel.page_changed.connect ((index) => {
GLib.List<weak Gtk.Widget> children = carousel.get_children (); if (carousel.n_pages <= 1) {
int children_length = (int) children.length ();
if (children_length <= 1) {
button_prev.sensitive = false; button_prev.sensitive = false;
button_next.sensitive = false; button_next.sensitive = false;
return; return;
} }
button_prev.sensitive = index > 0; button_prev.sensitive = index > 0;
button_next.sensitive = index < children_length - 1; button_next.sensitive = index < carousel.n_pages - 1;
}); });
carousel_box.add (button_prev); carousel_box.append (button_prev);
carousel_box.add (carousel); carousel_box.append (carousel);
carousel_box.add (button_next); carousel_box.append (button_next);
add (carousel_box); append (carousel_box);
carousel_dots = new Hdy.CarouselIndicatorDots (); carousel_dots = new Adw.CarouselIndicatorDots ();
carousel_dots.set_carousel (carousel); carousel_dots.set_carousel (carousel);
carousel_dots.show (); carousel_dots.show ();
add (carousel_dots); append (carousel_dots);
// Config // Config
Json.Object ? config = get_config (this); Json.Object ? config = get_config (this);
...@@ -90,18 +84,6 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -90,18 +84,6 @@ namespace SwayNotificationCenter.Widgets.Mpris {
int? image_size = get_prop<int> (config, "image-size"); int? image_size = get_prop<int> (config, "image-size");
if (image_size != null) mpris_config.image_size = image_size; if (image_size != null) mpris_config.image_size = image_size;
// Get image-border-radius
int? image_radius = get_prop<int> (config, "image-radius");
if (image_radius != null) mpris_config.image_radius = image_radius;
// Clamp the radius
mpris_config.image_radius = mpris_config.image_radius.clamp (
0, (int) (mpris_config.image_size * 0.5));
// Get blur
bool blur_found;
bool? blur = get_prop<bool> (config, "blur", out blur_found);
if (blur_found) mpris_config.blur = blur;
Json.Array ? blacklist = get_prop_array (config, "blacklist"); Json.Array ? blacklist = get_prop_array (config, "blacklist");
if (blacklist != null) { if (blacklist != null) {
mpris_config.blacklist = new string[blacklist.get_length ()]; mpris_config.blacklist = new string[blacklist.get_length ()];
...@@ -128,19 +110,6 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -128,19 +110,6 @@ namespace SwayNotificationCenter.Widgets.Mpris {
} }
} }
/**
* Forces the carousel to reload its style_context.
* Fixes carousel items not redrawing when window isn't visible.
* Probably related to: https://gitlab.gnome.org/GNOME/libhandy/-/issues/363
*/
public override void on_cc_visibility_change (bool value) {
if (!value) return;
carousel.get_style_context ().changed ();
foreach (var child in carousel.get_children ()) {
child.get_style_context ().changed ();
}
}
private void setup_mpris () throws Error { private void setup_mpris () throws Error {
dbus_iface = Bus.get_proxy_sync (BusType.SESSION, dbus_iface = Bus.get_proxy_sync (BusType.SESSION,
"org.freedesktop.DBus", "org.freedesktop.DBus",
...@@ -175,7 +144,6 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -175,7 +144,6 @@ namespace SwayNotificationCenter.Widgets.Mpris {
return false; return false;
} }
private bool check_player_metadata_empty (string name) { private bool check_player_metadata_empty (string name) {
MprisPlayer ? player = players.lookup (name); MprisPlayer ? player = players.lookup (name);
if (player == null) return true; if (player == null) return true;
...@@ -188,19 +156,21 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -188,19 +156,21 @@ namespace SwayNotificationCenter.Widgets.Mpris {
} }
private bool check_carousel_has_player (MprisPlayer player) { private bool check_carousel_has_player (MprisPlayer player) {
return carousel.get_children ().find (player) != null; return player != null && player.parent == carousel;
} }
private void add_player_to_carousel (string name) { private void add_player_to_carousel (string name) {
MprisPlayer ? player = players.lookup (name); MprisPlayer ? player = players.lookup (name);
if (player == null || check_carousel_has_player (player)) return; if (player == null || check_carousel_has_player (player)) return;
carousel.prepend (player); // HACK: The carousel doesn't focus the prepended player when not mapped.
uint children_length = carousel.get_children ().length (); carousel.append (player);
if (children_length > 1) { carousel.reorder (player, 0);
if (carousel.n_pages > 1) {
button_prev.show (); button_prev.show ();
button_next.show (); button_next.show ();
// Scroll to the new player // Scroll to the new player
carousel.scroll_to (player); carousel.scroll_to (player, false);
} }
if (!visible) show (); if (!visible) show ();
...@@ -208,7 +178,7 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -208,7 +178,7 @@ namespace SwayNotificationCenter.Widgets.Mpris {
private void add_player (string name, MprisSource source) { private void add_player (string name, MprisSource source) {
MprisPlayer player = new MprisPlayer (source, mpris_config); MprisPlayer player = new MprisPlayer (source, mpris_config);
player.get_style_context ().add_class ("%s-player".printf (css_class_name)); player.add_css_class ("%s-player".printf (css_class_name));
players.set (name, player); players.set (name, player);
if (mpris_config.autohide) { if (mpris_config.autohide) {
...@@ -229,10 +199,11 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -229,10 +199,11 @@ namespace SwayNotificationCenter.Widgets.Mpris {
MprisPlayer ? player = players.lookup (name); MprisPlayer ? player = players.lookup (name);
if (player == null || !check_carousel_has_player (player)) return; if (player == null || !check_carousel_has_player (player)) return;
carousel.remove (player); carousel.remove (player);
uint children_length = carousel.get_children ().length ();
if (children_length == 0) hide ();
uint children_length = carousel.n_pages;
if (children_length == 0) {
hide ();
}
if (children_length <= 1) { if (children_length <= 1) {
button_prev.hide (); button_prev.hide ();
button_next.hide (); button_next.hide ();
...@@ -248,17 +219,15 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -248,17 +219,15 @@ namespace SwayNotificationCenter.Widgets.Mpris {
remove_player_from_carousel (name); remove_player_from_carousel (name);
player.before_destroy (); player.before_destroy ();
player.destroy ();
players.remove (name); players.remove (name);
} }
private void change_carousel_position (int delta) { private void change_carousel_position (int delta) {
GLib.List<weak Gtk.Widget> children = carousel.get_children (); uint children_length = carousel.n_pages;
int children_length = (int) children.length ();
if (children_length == 0) return; if (children_length == 0) return;
int position = ((int) carousel.position + delta).clamp ( uint position = ((uint) carousel.position + delta)
0, children_length - 1); .clamp (0, (children_length - 1));
carousel.scroll_to (children.nth_data (position)); carousel.scroll_to (carousel.get_nth_page (position), true);
} }
private bool is_blacklisted (string name) { private bool is_blacklisted (string name) {
...@@ -266,7 +235,7 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -266,7 +235,7 @@ namespace SwayNotificationCenter.Widgets.Mpris {
if (blacklistedPattern == null || blacklistedPattern.length == 0) { if (blacklistedPattern == null || blacklistedPattern.length == 0) {
continue; continue;
} }
if (GLib.Regex.match_simple (blacklistedPattern, name, GLib.RegexCompileFlags.JAVASCRIPT_COMPAT, 0)) { if (GLib.Regex.match_simple (blacklistedPattern, name, RegexCompileFlags.DEFAULT, 0)) {
message ("\"%s\" is blacklisted", name); message ("\"%s\" is blacklisted", name);
return true; return true;
} }
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.40.0 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<template class="SwayNotificationCenterWidgetsMprisMprisPlayer" parent="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkImage" id="album_art">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="pixel-size">96</property>
<property name="icon-name">audio-x-generic-symbolic</property>
<property name="use-fallback">True</property>
<property name="icon_size">0</property>
<style>
<class name="widget-mpris-album-art"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">center</property>
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<child>
<object class="GtkLabel" id="title">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="ellipsize">end</property>
<property name="width-chars">0</property>
<property name="max-width-chars">0</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<style>
<class name="widget-mpris-title"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="sub_title">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="ellipsize">end</property>
<property name="width-chars">0</property>
<property name="max-width-chars">0</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<style>
<class name="widget-mpris-subtitle"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="spacing">6</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkButton" id="button_shuffle">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="receives-default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="button_shuffle_img">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="xpad">4</property>
<property name="ypad">4</property>
<property name="pixel-size">20</property>
<property name="icon-name">media-playlist-shuffle-symbolic</property>
<property name="icon_size">0</property>
</object>
</child>
<style>
<class name="circular"/>
<class name="image-button"/>
<class name="flat"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_prev">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="receives-default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="button_prev_img">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="xpad">4</property>
<property name="ypad">4</property>
<property name="pixel-size">20</property>
<property name="icon-name">media-seek-backward-symbolic</property>
<property name="icon_size">0</property>
</object>
</child>
<style>
<class name="circular"/>
<class name="image-button"/>
<class name="flat"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_play_pause">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="receives-default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="button_play_pause_img">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="xpad">4</property>
<property name="ypad">4</property>
<property name="pixel-size">20</property>
<property name="icon-name">media-playback-pause-symbolic</property>
<property name="icon_size">0</property>
</object>
</child>
<style>
<class name="circular"/>
<class name="image-button"/>
<class name="flat"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_next">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="receives-default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="button_next_img">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="xpad">4</property>
<property name="ypad">4</property>
<property name="pixel-size">20</property>
<property name="icon-name">media-seek-forward-symbolic</property>
<property name="icon_size">0</property>
</object>
</child>
<style>
<class name="circular"/>
<class name="image-button"/>
<class name="flat"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_repeat">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="receives-default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="button_repeat_img">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="xpad">4</property>
<property name="ypad">4</property>
<property name="pixel-size">20</property>
<property name="icon-name">media-playlist-repeat-symbolic</property>
<property name="icon_size">0</property>
</object>
</child>
<style>
<class name="circular"/>
<class name="image-button"/>
<class name="flat"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</template>
</interface>
namespace SwayNotificationCenter.Widgets.Mpris { namespace SwayNotificationCenter.Widgets.Mpris {
[GtkTemplate (ui = "/org/erikreider/sway-notification-center/controlCenter/widgets/mpris/mpris_player.ui")] [GtkTemplate (ui = "/org/erikreider/swaync/ui/mpris_player.ui")]
public class MprisPlayer : Gtk.Box { public class MprisPlayer : Underlay {
[GtkChild] [GtkChild]
unowned Gtk.Label title; public unowned Gtk.Label title;
[GtkChild] [GtkChild]
unowned Gtk.Label sub_title; unowned Gtk.Label sub_title;
...@@ -10,19 +10,18 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -10,19 +10,18 @@ namespace SwayNotificationCenter.Widgets.Mpris {
unowned Gtk.Image album_art; unowned Gtk.Image album_art;
[GtkChild] [GtkChild]
unowned Gtk.Picture background_picture;
[GtkChild]
unowned Gtk.Button button_shuffle; unowned Gtk.Button button_shuffle;
[GtkChild] [GtkChild]
unowned Gtk.Button button_prev; unowned Gtk.Button button_prev;
[GtkChild] [GtkChild]
unowned Gtk.Button button_play_pause; unowned Gtk.Button button_play_pause;
[GtkChild] [GtkChild]
unowned Gtk.Image button_play_pause_img;
[GtkChild]
unowned Gtk.Button button_next; unowned Gtk.Button button_next;
[GtkChild] [GtkChild]
unowned Gtk.Button button_repeat; unowned Gtk.Button button_repeat;
[GtkChild]
unowned Gtk.Image button_repeat_img;
public MprisSource source { construct; get; } public MprisSource source { construct; get; }
...@@ -38,9 +37,6 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -38,9 +37,6 @@ namespace SwayNotificationCenter.Widgets.Mpris {
private string prev_art_url; private string prev_art_url;
private DesktopAppInfo ? desktop_entry = null; private DesktopAppInfo ? desktop_entry = null;
private Gdk.Pixbuf ? album_art_pixbuf = null;
private Granite.Drawing.BufferSurface ? blurred_cover_surface = null;
private unowned Config mpris_config; private unowned Config mpris_config;
public signal void content_updated (); public signal void content_updated ();
...@@ -105,77 +101,6 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -105,77 +101,6 @@ namespace SwayNotificationCenter.Widgets.Mpris {
source.properties_changed.disconnect (properties_changed); source.properties_changed.disconnect (properties_changed);
} }
public override bool draw (Cairo.Context cr) {
unowned Gdk.Window ? window = get_window ();
if (!mpris_config.blur || window == null ||
!(album_art_pixbuf is Gdk.Pixbuf)) {
return base.draw (cr);
}
const double DEGREES = Math.PI / 180.0;
unowned Gtk.StyleContext style_ctx = this.get_style_context ();
unowned Gtk.StateFlags state = style_ctx.get_state ();
Gtk.Border border = style_ctx.get_border (state);
Gtk.Border margin = style_ctx.get_margin (state);
Value radius_value = style_ctx.get_property (
Gtk.STYLE_PROPERTY_BORDER_RADIUS, state);
int radius = 0;
if (!radius_value.holds (Type.INT) ||
(radius = radius_value.get_int ()) == 0) {
radius = mpris_config.image_radius;
}
int scale = style_ctx.get_scale ();
int width = get_allocated_width ()
- margin.left - margin.right
- border.left - border.right;
int height = get_allocated_height ()
- margin.top - margin.bottom
- border.top - border.bottom;
cr.save ();
cr.new_sub_path ();
// Top Right
cr.arc (width - radius + margin.right,
radius + margin.top,
radius, -90 * DEGREES, 0 * DEGREES);
// Bottom Right
cr.arc (width - radius + margin.right,
height - radius + margin.bottom,
radius, 0 * DEGREES, 90 * DEGREES);
// Bottom Left
cr.arc (radius + margin.left,
height - radius + margin.bottom,
radius, 90 * DEGREES, 180 * DEGREES);
// Top Left
cr.arc (radius + margin.left,
radius + margin.top,
radius, 180 * DEGREES, 270 * DEGREES);
cr.close_path ();
cr.set_source_rgba (0, 0, 0, 0);
cr.clip ();
// Draw blurred player background
if (blurred_cover_surface == null
|| blurred_cover_surface.width != width
|| blurred_cover_surface.height != height) {
var buffer = new Granite.Drawing.BufferSurface (width, height);
Cairo.Surface surface = Functions.scale_pixbuf (
album_art_pixbuf, width, height, scale);
buffer.context.set_source_surface (surface, 0, 0);
buffer.context.paint ();
buffer.fast_blur (8, 2);
blurred_cover_surface = buffer;
}
cr.set_source_surface (blurred_cover_surface.surface, margin.left, margin.top);
cr.paint ();
cr.restore ();
return base.draw (cr);
}
private void properties_changed (string iface, private void properties_changed (string iface,
HashTable<string, Variant> changed, HashTable<string, Variant> changed,
string[] invalid) { string[] invalid) {
...@@ -325,89 +250,61 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -325,89 +250,61 @@ namespace SwayNotificationCenter.Widgets.Mpris {
if (url == prev_art_url) return; if (url == prev_art_url) return;
prev_art_url = url; prev_art_url = url;
int scale = get_style_context ().get_scale ();
// Cancel previous download, reset the state and download again // Cancel previous download, reset the state and download again
album_art_cancellable.cancel (); album_art_cancellable.cancel ();
album_art_cancellable.reset (); album_art_cancellable.reset ();
Gdk.Texture ? album_art_texture = null;
try { try {
File file = File.new_for_uri (url); File file = File.new_for_uri (url);
InputStream stream = yield file.read_async (Priority.DEFAULT, InputStream stream = yield file.read_async (Priority.DEFAULT,
album_art_cancellable); album_art_cancellable);
this.album_art_pixbuf = yield new Gdk.Pixbuf.from_stream_async ( Gdk.Pixbuf pixbuf = yield new Gdk.Pixbuf.from_stream_async (
stream, album_art_cancellable); stream, album_art_cancellable);
album_art_texture = Gdk.Texture.for_pixbuf (pixbuf);
} catch (Error e) { } catch (Error e) {
debug ("Could not download album art for %s. Using fallback...", debug ("Could not download album art for %s. Using fallback...",
source.media_player.identity); source.media_player.identity);
this.album_art_pixbuf = null;
} }
if (this.album_art_pixbuf != null) { if (album_art_texture != null) {
unowned Gtk.StyleContext style_ctx = album_art.get_style_context (); // Set album art
Value br_value = Gtk.Snapshot snapshot = new Gtk.Snapshot ();
style_ctx.get_property (Gtk.STYLE_PROPERTY_BORDER_RADIUS, Functions.scale_texture (album_art_texture,
style_ctx.get_state ()); mpris_config.image_size, mpris_config.image_size,
int radius = 0; get_scale_factor (), snapshot);
if (!br_value.holds (Type.INT) || Graphene.Size size = Graphene.Size ().init (mpris_config.image_size, mpris_config.image_size);
(radius = br_value.get_int ()) == 0) { album_art.set_from_paintable (snapshot.free_to_paintable (size));
radius = mpris_config.image_radius;
} // Set background album art
var surface = Functions.scale_pixbuf (this.album_art_pixbuf, background_picture.set_paintable (album_art_texture);
mpris_config.image_size,
mpris_config.image_size,
scale);
this.album_art_pixbuf = Gdk.pixbuf_get_from_surface (surface,
0, 0,
mpris_config.image_size,
mpris_config.image_size);
this.blurred_cover_surface = null;
surface = Functions.round_surface (surface,
mpris_config.image_size,
mpris_config.image_size,
scale,
radius);
var pix_buf = Gdk.pixbuf_get_from_surface (surface,
0, 0,
mpris_config.image_size,
mpris_config.image_size);
surface.finish ();
album_art.set_from_pixbuf (pix_buf);
album_art.get_style_context ().set_scale (1);
this.queue_draw ();
this.queue_draw ();
return; return;
} }
} }
this.album_art_pixbuf = null;
this.blurred_cover_surface = null;
// Get the app icon // Get the app icon
Icon ? icon = null; Icon ? icon = null;
if (desktop_entry is DesktopAppInfo) { if (desktop_entry is DesktopAppInfo) {
icon = desktop_entry.get_icon (); icon = desktop_entry.get_icon ();
} }
Gtk.IconInfo ? icon_info = null; unowned Gtk.IconTheme icon_theme = Gtk.IconTheme.get_for_display (get_display ());
if (icon != null) { if (icon != null) {
album_art.set_from_gicon (icon, Gtk.IconSize.INVALID); album_art.set_from_gicon (icon);
icon_info = Gtk.IconTheme.get_default ().lookup_by_gicon (icon,
mpris_config.image_size, Gtk.IconPaintable ? icon_info = icon_theme.lookup_by_gicon (
Gtk.IconLookupFlags.USE_BUILTIN); icon, mpris_config.image_size, get_scale_factor (), Gtk.TextDirection.NONE, 0);
background_picture.set_paintable (icon_info);
} else { } else {
// Default icon // Default icon
string icon_name = "audio-x-generic-symbolic"; string icon_name = "audio-x-generic-symbolic";
album_art.set_from_icon_name (icon_name, album_art.set_from_icon_name (icon_name);
Gtk.IconSize.INVALID);
icon_info = Gtk.IconTheme.get_default ().lookup_icon (icon_name,
mpris_config.image_size,
Gtk.IconLookupFlags.USE_BUILTIN);
}
if (icon_info != null) { Gtk.IconPaintable ? icon_info = icon_theme.lookup_icon (
try { icon_name, null, mpris_config.image_size, get_scale_factor (),
this.album_art_pixbuf = icon_info.load_icon (); Gtk.TextDirection.NONE, 0);
} catch (Error e) { background_picture.set_paintable (icon_info);
warning ("Could not load icon: %s", e.message);
}
} }
} }
...@@ -455,7 +352,7 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -455,7 +352,7 @@ namespace SwayNotificationCenter.Widgets.Mpris {
check = source.media_player.can_play; check = source.media_player.can_play;
break; break;
} }
button_play_pause_img.icon_name = icon_name; button_play_pause.set_icon_name (icon_name);
button_play_pause.sensitive = check && source.media_player.can_control; button_play_pause.sensitive = check && source.media_player.can_control;
} }
...@@ -493,12 +390,14 @@ namespace SwayNotificationCenter.Widgets.Mpris { ...@@ -493,12 +390,14 @@ namespace SwayNotificationCenter.Widgets.Mpris {
icon_name = ICON_REPEAT_SONG; icon_name = ICON_REPEAT_SONG;
break; break;
} }
unowned Gtk.StyleContext ctx = button_repeat.get_style_context (); if (remove_flat_css_class) {
if (remove_flat_css_class) ctx.remove_class ("flat"); button_repeat.remove_css_class ("flat");
else ctx.add_class ("flat"); } else {
button_repeat.add_css_class ("flat");
}
button_repeat.get_child ().opacity = opacity; button_repeat.get_child ().opacity = opacity;
button_repeat.sensitive = true; button_repeat.sensitive = true;
button_repeat_img.icon_name = icon_name; button_repeat.set_icon_name (icon_name);
button_repeat.show (); button_repeat.show ();
} }
} else { } else {
......
...@@ -10,6 +10,8 @@ namespace SwayNotificationCenter.Widgets { ...@@ -10,6 +10,8 @@ namespace SwayNotificationCenter.Widgets {
this.update_command = update_command; this.update_command = update_command;
this.label = label; this.label = label;
this.set_has_frame (true);
if (active) { if (active) {
this.active = true; this.active = true;
} }
......
...@@ -33,7 +33,9 @@ namespace SwayNotificationCenter.Widgets { ...@@ -33,7 +33,9 @@ namespace SwayNotificationCenter.Widgets {
} }
title_widget = new Gtk.Label (title); title_widget = new Gtk.Label (title);
add (title_widget); title_widget.set_hexpand (true);
title_widget.set_halign (Gtk.Align.START);
append (title_widget);
if (has_clear_all_button) { if (has_clear_all_button) {
clear_all_button = new Gtk.Button.with_label (button_text); clear_all_button = new Gtk.Button.with_label (button_text);
...@@ -44,14 +46,19 @@ namespace SwayNotificationCenter.Widgets { ...@@ -44,14 +46,19 @@ namespace SwayNotificationCenter.Widgets {
error ("Error: %s\n", e.message); error ("Error: %s\n", e.message);
} }
}); });
if (noti_daemon.control_center != null) {
clear_all_button.set_sensitive (
noti_daemon.control_center.notification_count () > 0);
}
swaync_daemon.subscribe_v2.connect ((count) => {
clear_all_button.set_sensitive (count > 0);
});
clear_all_button.set_can_focus (false); clear_all_button.set_can_focus (false);
clear_all_button.valign = Gtk.Align.CENTER; clear_all_button.valign = Gtk.Align.CENTER;
// Backwards compatible towards older CSS stylesheets // Backwards compatible towards older CSS stylesheets
clear_all_button.get_style_context ().add_class ("control-center-clear-all"); clear_all_button.add_css_class ("control-center-clear-all");
pack_end (clear_all_button, false); append (clear_all_button);
} }
show_all ();
} }
} }
} }
...@@ -19,24 +19,27 @@ namespace SwayNotificationCenter.Widgets { ...@@ -19,24 +19,27 @@ namespace SwayNotificationCenter.Widgets {
this.show_per_app_icon = show_per_app_icon; this.show_per_app_icon = show_per_app_icon;
this.show_per_app_label = show_per_app_label; this.show_per_app_label = show_per_app_label;
set_activatable (false);
update (sink_input); update (sink_input);
scale.draw_value = false; scale.draw_value = false;
scale.set_hexpand (true);
icon.pixel_size = icon_size; icon.pixel_size = icon_size;
container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
if (show_per_app_icon) { if (show_per_app_icon) {
container.add (icon); container.append (icon);
} }
if (show_per_app_label) { if (show_per_app_label) {
container.add (label); container.append (label);
} }
container.pack_start (scale); container.append (scale);
add (container); set_child (container);
scale.value_changed.connect (() => { scale.value_changed.connect (() => {
client.set_sink_input_volume (sink_input, (float) scale.get_value ()); client.set_sink_input_volume (sink_input, (float) scale.get_value ());
...@@ -49,9 +52,7 @@ namespace SwayNotificationCenter.Widgets { ...@@ -49,9 +52,7 @@ namespace SwayNotificationCenter.Widgets {
if (show_per_app_icon) { if (show_per_app_icon) {
icon.set_from_icon_name ( icon.set_from_icon_name (
sink_input.application_icon_name ?? "application-x-executable", sink_input.application_icon_name ?? "application-x-executable");
Gtk.IconSize.DIALOG
);
} }
if (show_per_app_label) { if (show_per_app_label) {
...@@ -60,8 +61,6 @@ namespace SwayNotificationCenter.Widgets { ...@@ -60,8 +61,6 @@ namespace SwayNotificationCenter.Widgets {
scale.set_value (sink_input.volume); scale.set_value (sink_input.volume);
scale.tooltip_text = ((int) scale.get_value ()).to_string (); scale.tooltip_text = ((int) scale.get_value ()).to_string ();
this.show_all ();
} }
} }
} }
...@@ -10,15 +10,16 @@ namespace SwayNotificationCenter.Widgets { ...@@ -10,15 +10,16 @@ namespace SwayNotificationCenter.Widgets {
Gtk.Label label_widget = new Gtk.Label (null); Gtk.Label label_widget = new Gtk.Label (null);
Gtk.Scale slider = new Gtk.Scale.with_range (Gtk.Orientation.HORIZONTAL, 0, 100, 1); Gtk.Scale slider = new Gtk.Scale.with_range (Gtk.Orientation.HORIZONTAL, 0, 100, 1);
// Per app volume controll // Per app volume control
Gtk.ListBox levels_listbox; Gtk.ListBox levels_listbox;
Gtk.Button reveal_button; IterListBoxController list_box_controller;
Gtk.ToggleButton reveal_button;
Gtk.Revealer revealer; Gtk.Revealer revealer;
Gtk.Label no_sink_inputs_label; Gtk.Label no_sink_inputs_label;
string empty_label = "No active sink input"; string empty_label = "No active sink input";
string expand_label = "⇧"; string ? expand_label = null;
string collapse_label = "⇩"; string ? collapse_label = null;
int icon_size = 24; int icon_size = 24;
Gtk.RevealerTransitionType revealer_type = Gtk.RevealerTransitionType.SLIDE_DOWN; Gtk.RevealerTransitionType revealer_type = Gtk.RevealerTransitionType.SLIDE_DOWN;
...@@ -98,49 +99,65 @@ namespace SwayNotificationCenter.Widgets { ...@@ -98,49 +99,65 @@ namespace SwayNotificationCenter.Widgets {
this.orientation = Gtk.Orientation.VERTICAL; this.orientation = Gtk.Orientation.VERTICAL;
slider.draw_value = false; slider.draw_value = false;
slider.set_hexpand (true);
main_volume_slider_container.add (label_widget); main_volume_slider_container.append (label_widget);
main_volume_slider_container.pack_start (slider, true, true, 0); main_volume_slider_container.append (slider);
add (main_volume_slider_container); append (main_volume_slider_container);
if (show_per_app) { if (show_per_app) {
reveal_button = new Gtk.Button.with_label (expand_label);
revealer = new Gtk.Revealer (); revealer = new Gtk.Revealer ();
revealer.transition_type = revealer_type; revealer.transition_type = revealer_type;
revealer.transition_duration = revealer_duration; revealer.transition_duration = revealer_duration;
levels_listbox = new Gtk.ListBox (); levels_listbox = new Gtk.ListBox ();
levels_listbox.get_style_context ().add_class ("per-app-volume"); levels_listbox.add_css_class ("per-app-volume");
revealer.add (levels_listbox); levels_listbox.set_activate_on_single_click (true);
levels_listbox.set_selection_mode (Gtk.SelectionMode.NONE);
revealer.set_child (levels_listbox);
list_box_controller = new IterListBoxController (levels_listbox);
if (this.client.active_sinks.size == 0) { if (this.client.active_sinks.size == 0) {
no_sink_inputs_label = new Gtk.Label (empty_label); no_sink_inputs_label = new Gtk.Label (empty_label);
levels_listbox.add (no_sink_inputs_label); list_box_controller.append (no_sink_inputs_label);
} }
foreach (var item in this.client.active_sinks.values) { foreach (var item in this.client.active_sinks.values) {
levels_listbox.add (new SinkInputRow (item, client, SinkInputRow row = new SinkInputRow (item, client,
icon_size, show_per_app_icon, show_per_app_label)); icon_size, show_per_app_icon,
show_per_app_label);
list_box_controller.append (row);
} }
this.client.change_active_sink.connect (active_sink_change); this.client.change_active_sink.connect (active_sink_change);
this.client.new_active_sink.connect (active_sink_added); this.client.new_active_sink.connect (active_sink_added);
this.client.remove_active_sink.connect (active_sink_removed); this.client.remove_active_sink.connect (active_sink_removed);
reveal_button.clicked.connect (() => { reveal_button = new Gtk.ToggleButton ();
bool show = revealer.reveal_child; set_button_icon ();
revealer.set_reveal_child (!show); reveal_button.toggled.connect (() => {
if (show) { set_button_icon ();
reveal_button.label = expand_label; revealer.set_reveal_child (reveal_button.active);
} else {
reveal_button.label = collapse_label;
}
}); });
main_volume_slider_container.append (reveal_button);
main_volume_slider_container.pack_end (reveal_button, false, false, 0); append (revealer);
add (revealer);
} }
}
show_all (); private void set_button_icon () {
if (!reveal_button.active) {
if (expand_label == null) {
reveal_button.set_icon_name ("swaync-up-small-symbolic");
} else {
reveal_button.set_label (expand_label);
}
} else {
if (collapse_label == null) {
reveal_button.set_icon_name ("swaync-down-small-symbolic");
} else {
reveal_button.set_label (collapse_label);
}
}
} }
public override void on_cc_visibility_change (bool val) { public override void on_cc_visibility_change (bool val) {
...@@ -160,8 +177,10 @@ namespace SwayNotificationCenter.Widgets { ...@@ -160,8 +177,10 @@ namespace SwayNotificationCenter.Widgets {
} }
private void active_sink_change (PulseSinkInput sink) { private void active_sink_change (PulseSinkInput sink) {
foreach (var row in levels_listbox.get_children ()) { foreach (unowned Gtk.Widget row in list_box_controller.get_children ()) {
if (row == null) continue; if (!(row is SinkInputRow)) {
continue;
}
var s = (SinkInputRow) row; var s = (SinkInputRow) row;
if (s.sink_input.cmp (sink)) { if (s.sink_input.cmp (sink)) {
s.update (sink); s.update (sink);
...@@ -173,25 +192,28 @@ namespace SwayNotificationCenter.Widgets { ...@@ -173,25 +192,28 @@ namespace SwayNotificationCenter.Widgets {
private void active_sink_added (PulseSinkInput sink) { private void active_sink_added (PulseSinkInput sink) {
// one element added -> remove the empty label // one element added -> remove the empty label
if (this.client.active_sinks.size == 1) { if (this.client.active_sinks.size == 1) {
var label = levels_listbox.get_children ().first ().data; var label = levels_listbox.get_first_child ();
levels_listbox.remove ((Gtk.Widget) label); list_box_controller.remove ((Gtk.Widget) label);
} }
levels_listbox.add (new SinkInputRow (sink, client, icon_size, show_per_app_icon, show_per_app_label)); SinkInputRow row = new SinkInputRow (sink, client,
show_all (); icon_size, show_per_app_icon,
show_per_app_label);
list_box_controller.append (row);
} }
private void active_sink_removed (PulseSinkInput sink) { private void active_sink_removed (PulseSinkInput sink) {
foreach (var row in levels_listbox.get_children ()) { foreach (unowned Gtk.Widget row in list_box_controller.get_children ()) {
if (row == null) continue; if (!(row is SinkInputRow)) {
continue;
}
var s = (SinkInputRow) row; var s = (SinkInputRow) row;
if (s.sink_input.cmp (sink)) { if (s.sink_input.cmp (sink)) {
levels_listbox.remove (row); list_box_controller.remove (row);
break; break;
} }
} }
if (levels_listbox.get_children ().length () == 0) { if (levels_listbox.get_first_child () == null) {
levels_listbox.add (no_sink_inputs_label); list_box_controller.append (no_sink_inputs_label);
show_all ();
} }
} }
} }
......
public enum SwipeDirection {
SWIPE_LEFT, SWIPE_RIGHT;
}
public class DismissibleWidget : Gtk.Widget, Adw.Swipeable {
protected unowned Gtk.Widget _child = null;
public unowned Gtk.Widget child {
get {
return _child;
}
set {
_child = value;
if (_child != null) {
_child.unparent ();
_child.set_parent (this);
}
}
}
public Gtk.Orientation orientation {
get;
private set;
default = Gtk.Orientation.HORIZONTAL;
}
// Animation
Adw.SpringAnimation animation;
Adw.AnimationTarget target;
// Swipe Gesture
Adw.SwipeTracker swipe_tracker;
bool transition_running = false;
bool gesture_active = false;
double child_offset = 0;
double swipe_progress = 0.0;
SwipeDirection swipe_direction = SwipeDirection.SWIPE_RIGHT;
construct {
swipe_tracker = new Adw.SwipeTracker (this);
swipe_tracker.set_orientation (orientation);
swipe_tracker.set_reversed (true);
swipe_tracker.set_upper_overshoot (true);
swipe_tracker.set_lower_overshoot (true);
swipe_tracker.set_allow_long_swipes (true);
swipe_tracker.set_enabled (true);
swipe_tracker.set_allow_mouse_drag (true);
swipe_tracker.prepare.connect (swipe_prepare_cb);
swipe_tracker.update_swipe.connect (swipe_update_swipe_cb);
swipe_tracker.end_swipe.connect (swipe_end_swipe_cb);
double[] snap_dir = get_snap_points ();
target = new Adw.CallbackAnimationTarget (animate_value_cb);
animation = new Adw.SpringAnimation (this, snap_dir[0], snap_dir[1],
new Adw.SpringParams (1, 0.5, 500),
target);
animation.set_clamp (true);
animation.done.connect (animation_done_cb);
}
public signal void dismissed ();
public void set_can_dismiss (bool state) {
swipe_tracker.set_enabled (state);
}
/*
* Overrides
*/
protected override Gtk.SizeRequestMode get_request_mode () {
return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
}
protected override void measure (Gtk.Orientation orientation, int for_size,
out int minimum, out int natural,
out int minimum_baseline, out int natural_baseline) {
minimum = 0;
natural = 0;
minimum_baseline = -1;
natural_baseline = -1;
int child_min, child_nat;
if (!child.visible) {
return;
}
child.measure (orientation, for_size,
out child_min, out child_nat, null, null);
minimum = int.max (minimum, child_min);
natural = int.max (natural, child_nat);
}
protected override void size_allocate (int width, int height, int baseline) {
if (!child.visible) {
return;
}
int child_width, child_height;
int min = 0, nat = 0;
if (orientation == Gtk.Orientation.HORIZONTAL) {
child.measure (orientation,
height, out min, out nat, null, null);
} else {
child.measure (orientation,
width, out min, out nat, null, null);
}
child_width = width;
child_height = height;
double x = 0;
if (get_direction () == Gtk.TextDirection.RTL) {
x -= ((width * swipe_progress) - (width - child_width) / 2.0)
+ (width * child_offset * 2);
} else {
x -= - ((width * swipe_progress) - (width - child_width) / 2.0)
- (width * child_offset * 2);
}
Gsk.Transform transform = new Gsk.Transform ()
.translate (Graphene.Point ().init ((float) x, 0));
child.allocate (child_width, child_height, baseline, transform);
}
protected override void snapshot (Gtk.Snapshot snapshot) {
snapshot.save ();
snapshot.push_opacity (1 - swipe_progress.abs ());
snapshot_child (child, snapshot);
snapshot.pop ();
snapshot.restore ();
}
/*
* Callbacks
*/
private void animate_value_cb (double value) {
set_position (value);
}
private void animation_done_cb () {
transition_running = false;
if (swipe_progress != 0) {
dismissed ();
}
}
private void swipe_prepare_cb (Adw.NavigationDirection direction) {
gesture_active = true;
if (transition_running) {
animation.pause ();
} else {
transition_running = true;
}
}
private void swipe_update_swipe_cb (double distance) {
set_position (distance);
}
private void swipe_end_swipe_cb (double velocity, double to) {
if (!gesture_active) {
return;
}
animation.set_value_from (swipe_progress);
animation.set_value_to (to);
animation.set_initial_velocity (velocity);
animation.play ();
gesture_active = false;
}
/*
* Methods
*/
private void set_position (double value) {
this.swipe_progress = value;
queue_allocate ();
}
public void set_gesture_direction (SwipeDirection swipe_direction) {
this.swipe_direction = swipe_direction;
// Reset the position
set_position (0.0);
}
/*
* Swipe gesture
*/
/** Gets the progress this will snap back to after the gesture is canceled. */
public double get_cancel_progress () {
return 0;
}
/** Gets the swipe distance of this. */
public double get_distance () {
return get_width ();
}
/** Gets the current progress of this. */
public double get_progress () {
if (!transition_running) {
return 0;
}
return this.swipe_progress;
}
/** Gets the snap points of this. */
public double[] get_snap_points () {
switch (swipe_direction) {
case SwipeDirection.SWIPE_LEFT:
return new double[] { -1, 0 };
default:
case SwipeDirection.SWIPE_RIGHT:
return new double[] { 0, 1 };
}
}
/**
* Gets the area this can start a swipe from for the given direction
* and gesture type.
*/
public Gdk.Rectangle get_swipe_area (Adw.NavigationDirection direction,
bool is_drag) {
Gtk.Allocation alloc = Gtk.Allocation ();
Graphene.Rect bounds;
this.compute_bounds (this, out bounds);
alloc.width = (int) bounds.size.width;
alloc.height = (int) bounds.size.height;
alloc.x = (int) bounds.origin.x;
alloc.y = (int) bounds.origin.y;
return alloc;
}
}
namespace SwayNotificationCenter {
public class FadedViewport : Gtk.Viewport {
private int fade_height = 30;
private FadedViewportChild container;
public FadedViewport (int fade_height) {
if (fade_height > 0) this.fade_height = fade_height;
this.container = new FadedViewportChild (this.fade_height);
base.add (container);
}
public override void add (Gtk.Widget widget) {
container.add (widget);
}
public override void remove (Gtk.Widget widget) {
container.remove (widget);
}
public override bool draw (Cairo.Context cr) {
Gtk.Allocation alloc;
get_allocated_size (out alloc, null);
Cairo.Pattern top_fade_gradient = new Cairo.Pattern.linear (0, 0, 0, 1);
top_fade_gradient.add_color_stop_rgba (0, 1, 1, 1, 1);
top_fade_gradient.add_color_stop_rgba (1, 1, 1, 1, 0);
Cairo.Pattern bottom_fade_gradient = new Cairo.Pattern.linear (0, 0, 0, 1);
bottom_fade_gradient.add_color_stop_rgba (0, 1, 1, 1, 0);
bottom_fade_gradient.add_color_stop_rgba (1, 1, 1, 1, 1);
cr.save ();
cr.push_group ();
// Draw widgets
base.draw (cr);
/// Draw vertical fade
// Top fade
cr.save ();
cr.scale (alloc.width, fade_height);
cr.rectangle (0, 0, alloc.width, fade_height);
cr.set_source (top_fade_gradient);
cr.set_operator (Cairo.Operator.DEST_OUT);
cr.fill ();
cr.restore ();
// Bottom fade
cr.save ();
cr.translate (0, alloc.height - fade_height);
cr.scale (alloc.width, fade_height);
cr.rectangle (0, 0, alloc.width, fade_height);
cr.set_source (bottom_fade_gradient);
cr.set_operator (Cairo.Operator.DEST_OUT);
cr.fill ();
cr.restore ();
cr.pop_group_to_source ();
cr.paint ();
cr.restore ();
return true;
}
}
}
private class FadedViewportChild : Gtk.Container {
private int y_padding;
private unowned Gtk.Widget _child;
public FadedViewportChild (int y_padding) {
base.set_has_window (false);
base.set_can_focus (true);
base.set_redraw_on_allocate (false);
// Half due to the fade basically stopping at 50% of the height
this.y_padding = y_padding / 2;
this._child = null;
this.show ();
}
public override void add (Gtk.Widget widget) {
if (this._child == null) {
widget.set_parent (this);
this._child = widget;
}
}
public override void remove (Gtk.Widget widget) {
if (this._child == widget) {
widget.unparent ();
this._child = null;
if (this.get_visible () && widget.get_visible ()) {
this.queue_resize_no_redraw ();
}
}
}
public override void forall_internal (bool include_internals, Gtk.Callback callback) {
if (this._child != null) {
callback (this._child);
}
}
public override Gtk.SizeRequestMode get_request_mode () {
if (this._child != null) {
return this._child.get_request_mode ();
} else {
return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
}
}
public override void size_allocate (Gtk.Allocation allocation) {
Gtk.Allocation child_allocation = Gtk.Allocation ();
uint border_width = this.get_border_width ();
if (this._child != null && this._child.get_visible ()) {
child_allocation.x = allocation.x + (int) border_width;
child_allocation.y = allocation.y + (int) border_width;
Gtk.Align align_y = _child.get_valign ();
if (align_y == Gtk.Align.END) {
child_allocation.y -= y_padding;
} else {
child_allocation.y += y_padding;
}
child_allocation.width = allocation.width - 2 * (int) border_width;
child_allocation.height = allocation.height - 2 * (int) border_width;
this._child.size_allocate (child_allocation);
if (this.get_realized ()) {
this._child.show ();
}
}
if (this.get_realized ()) {
if (this._child != null) {
this._child.set_child_visible (true);
}
}
base.size_allocate (allocation);
}
public override void get_preferred_height_for_width (int width,
out int minimum_height,
out int natural_height) {
minimum_height = 0;
natural_height = 0;
if (_child != null && _child.get_visible ()) {
_child.get_preferred_height_for_width (width,
out minimum_height,
out natural_height);
minimum_height += y_padding * 2;
natural_height += y_padding * 2;
}
}
public override bool draw (Cairo.Context cr) {
base.draw (cr);
return false;
}
}
namespace SwayNotificationCenter { namespace SwayNotificationCenter {
public class Functions { public class Functions {
const Gsk.ScalingFilter SCALING_FILTER = Gsk.ScalingFilter.NEAREST;
private static Gtk.CssProvider system_css_provider; private static Gtk.CssProvider system_css_provider;
private static Gtk.CssProvider user_css_provider; private static Gtk.CssProvider user_css_provider;
...@@ -10,7 +12,7 @@ namespace SwayNotificationCenter { ...@@ -10,7 +12,7 @@ namespace SwayNotificationCenter {
user_css_provider = new Gtk.CssProvider (); user_css_provider = new Gtk.CssProvider ();
// Init resources // Init resources
var theme = Gtk.IconTheme.get_default (); var theme = Gtk.IconTheme.get_for_display (Gdk.Display.get_default ());
theme.add_resource_path ("/org/erikreider/swaync/icons"); theme.add_resource_path ("/org/erikreider/swaync/icons");
} }
...@@ -29,7 +31,6 @@ namespace SwayNotificationCenter { ...@@ -29,7 +31,6 @@ namespace SwayNotificationCenter {
public static void set_image_uri (owned string uri, public static void set_image_uri (owned string uri,
Gtk.Image img, Gtk.Image img,
int icon_size, int icon_size,
int radius,
bool file_exists, bool file_exists,
bool is_theme_icon = false) { bool is_theme_icon = false) {
const string URI_PREFIX = "file://"; const string URI_PREFIX = "file://";
...@@ -40,18 +41,8 @@ namespace SwayNotificationCenter { ...@@ -40,18 +41,8 @@ namespace SwayNotificationCenter {
try { try {
if (is_uri) uri = uri.slice (URI_PREFIX.length, uri.length); if (is_uri) uri = uri.slice (URI_PREFIX.length, uri.length);
var pixbuf = new Gdk.Pixbuf.from_file_at_scale ( Gdk.Texture texture = Gdk.Texture.from_filename (Uri.unescape_string (uri));
Uri.unescape_string (uri), img.set_from_paintable (texture);
icon_size * img.scale_factor,
icon_size * img.scale_factor,
true);
// Scale and round the image. Scales to fit the size
var surface = scale_round_pixbuf (pixbuf,
icon_size,
icon_size,
img.scale_factor,
radius);
img.set_from_surface (surface);
} catch (Error e) { } catch (Error e) {
stderr.printf (e.message + "\n"); stderr.printf (e.message + "\n");
} }
...@@ -59,30 +50,26 @@ namespace SwayNotificationCenter { ...@@ -59,30 +50,26 @@ namespace SwayNotificationCenter {
// Try as icon name // Try as icon name
if (img.storage_type == Gtk.ImageType.EMPTY) { if (img.storage_type == Gtk.ImageType.EMPTY) {
img.set_from_icon_name (uri, Gtk.IconSize.INVALID); unowned Gdk.Display display = Gdk.Display.get_default ();
unowned Gtk.IconTheme icon_theme = Gtk.IconTheme.get_for_display (display);
if (icon_theme.has_icon (uri)) {
img.set_from_icon_name (uri);
}
} }
} }
public static void set_image_data (ImageData data, public static void set_image_data (ImageData data,
Gtk.Image img, Gtk.Image img,
int icon_size, int icon_size) {
int radius) { Gdk.MemoryFormat format = Gdk.MemoryFormat.R8G8B8;
// Rebuild and scale the image if (data.has_alpha) {
var pixbuf = new Gdk.Pixbuf.with_unowned_data (data.data, format = Gdk.MemoryFormat.R8G8B8A8;
Gdk.Colorspace.RGB, }
data.has_alpha, var texture = new Gdk.MemoryTexture (data.width, data.height,
data.bits_per_sample, format,
data.width, new Bytes.static (data.data),
data.height, data.rowstride);
data.rowstride, img.set_from_paintable (texture);
null);
var surface = scale_round_pixbuf (pixbuf,
icon_size,
icon_size,
img.scale_factor,
radius);
img.set_from_surface (surface);
} }
/** Load the package provided CSS file as a base. /** Load the package provided CSS file as a base.
...@@ -95,31 +82,26 @@ namespace SwayNotificationCenter { ...@@ -95,31 +82,26 @@ namespace SwayNotificationCenter {
// Load packaged CSS as backup // Load packaged CSS as backup
string system_css = get_style_path (null, true); string system_css = get_style_path (null, true);
system_css = File.new_for_path (system_css).get_path () ?? system_css; system_css = File.new_for_path (system_css).get_path () ?? system_css;
message ("Loading CSS: \"%s\"", system_css); if (!skip_packaged_css) {
try { message ("Loading CSS: \"%s\"", system_css);
system_css_provider.load_from_path (system_css); system_css_provider.load_from_path (system_css);
Gtk.StyleContext.add_provider_for_screen ( Gtk.StyleContext.add_provider_for_display (
Gdk.Screen.get_default (), Gdk.Display.get_default (),
system_css_provider, system_css_provider,
css_priority); css_priority);
} catch (Error e) { } else {
critical ("Load packaged CSS Error (\"%s\"):\n\t%s\n", system_css, e.message); message ("Skipping system CSS: \"%s\"", system_css);
} }
// Load user CSS // Load user CSS
string user_css = get_style_path (style_path); string user_css = get_style_path (style_path);
user_css = File.new_for_path (user_css).get_path () ?? user_css; user_css = File.new_for_path (user_css).get_path () ?? user_css;
message ("Loading CSS: \"%s\"", user_css); message ("Loading CSS: \"%s\"", user_css);
try { user_css_provider.load_from_path (user_css);
user_css_provider.load_from_path (user_css); Gtk.StyleContext.add_provider_for_display (
Gtk.StyleContext.add_provider_for_screen ( Gdk.Display.get_default (),
Gdk.Screen.get_default (), user_css_provider,
user_css_provider, css_priority);
css_priority);
} catch (Error e) {
critical ("Load user CSS Error (\"%s\"):\n\t%s\n", user_css, e.message);
return false;
}
return true; return true;
} }
...@@ -217,122 +199,64 @@ namespace SwayNotificationCenter { ...@@ -217,122 +199,64 @@ namespace SwayNotificationCenter {
return type; return type;
} }
/** Roundes the Cairo Surface to the given radii */ /** Scales the Texture to fit the given dimensions */
public static Cairo.Surface round_surface (Cairo.Surface base_surf, public static void scale_texture (Gdk.Texture texture,
int buffer_width, int buffer_width,
int buffer_height, int buffer_height,
int img_scale, int img_scale,
int radius) { Gtk.Snapshot snapshot) {
// Limit radii size int width = texture.width / img_scale;
radius = int.min (radius, int.min (buffer_width / 2, buffer_height / 2)); int height = texture.height / img_scale;
Cairo.Surface surface = new Cairo.ImageSurface (Cairo.Format.ARGB32,
buffer_width,
buffer_height);
var cr = new Cairo.Context (surface);
// Border radius
const double DEGREES = Math.PI / 180.0;
cr.new_sub_path ();
cr.arc (buffer_width - radius, radius, radius, -90 * DEGREES, 0 * DEGREES);
cr.arc (buffer_width - radius, buffer_height - radius, radius, 0 * DEGREES, 90 * DEGREES);
cr.arc (radius, buffer_height - radius, radius, 90 * DEGREES, 180 * DEGREES);
cr.arc (radius, radius, radius, 180 * DEGREES, 270 * DEGREES);
cr.close_path ();
cr.set_source_rgba (0, 0, 0, 0);
cr.clip ();
cr.paint ();
cr.save ();
cr.set_source_surface (base_surf, 0, 0);
cr.paint ();
cr.restore ();
return surface;
}
/** Scales the pixbuf to fit the given dimensions */
public static Cairo.Surface scale_pixbuf (Gdk.Pixbuf pixbuf,
int buffer_width,
int buffer_height,
int img_scale) {
Cairo.Surface surface = new Cairo.ImageSurface (Cairo.Format.ARGB32,
buffer_width,
buffer_height);
var cr = new Cairo.Context (surface);
cr.save ();
Cairo.Surface base_surf = Gdk.cairo_surface_create_from_pixbuf (pixbuf,
img_scale,
null);
int width = pixbuf.width / img_scale;
int height = pixbuf.height / img_scale;
double window_ratio = (double) buffer_width / buffer_height; double window_ratio = (double) buffer_width / buffer_height;
double bg_ratio = width / height; double bg_ratio = width / height;
snapshot.save ();
if (window_ratio > bg_ratio) { // Taller wallpaper than monitor if (window_ratio > bg_ratio) { // Taller wallpaper than monitor
double scale = (double) buffer_width / width; double scale = (double) buffer_width / width;
if (scale * height < buffer_height) { if (scale * height < buffer_height) {
draw_scale_wide (buffer_width, width, buffer_height, height, cr, base_surf); draw_scale_wide (buffer_width, width, buffer_height, height, snapshot, texture);
} else { } else {
draw_scale_tall (buffer_width, width, buffer_height, height, cr, base_surf); draw_scale_tall (buffer_width, width, buffer_height, height, snapshot, texture);
} }
} else { // Wider wallpaper than monitor } else { // Wider wallpaper than monitor
double scale = (double) buffer_height / height; double scale = (double) buffer_height / height;
if (scale * width < buffer_width) { if (scale * width < buffer_width) {
draw_scale_tall (buffer_width, width, buffer_height, height, cr, base_surf); draw_scale_tall (buffer_width, width, buffer_height, height, snapshot, texture);
} else { } else {
draw_scale_wide (buffer_width, width, buffer_height, height, cr, base_surf); draw_scale_wide (buffer_width, width, buffer_height, height, snapshot, texture);
} }
} }
cr.paint ();
cr.restore ();
base_surf.finish ();
return surface;
}
/** Scales the pixbuf to fit the given dimensions */ snapshot.restore ();
public static Cairo.Surface scale_round_pixbuf (Gdk.Pixbuf pixbuf,
int buffer_width,
int buffer_height,
int img_scale,
int radius) {
var surface = Functions.scale_pixbuf (pixbuf,
buffer_width,
buffer_height,
img_scale);
surface = Functions.round_surface (surface,
buffer_width,
buffer_height,
img_scale,
radius);
return surface;
} }
private static void draw_scale_tall (int buffer_width, private static void draw_scale_tall (int buffer_width,
int width, int width,
int buffer_height, int buffer_height,
int height, int height,
Cairo.Context cr, Gtk.Snapshot snapshot,
Cairo.Surface surface) { Gdk.Texture texture) {
double scale = (double) buffer_width / width; float scale = (float) buffer_width / width;
cr.scale (scale, scale); snapshot.scale (scale, scale);
cr.set_source_surface (surface, float x = 0;
0, (double) buffer_height / 2 / scale - height / 2); float y = (float) (buffer_height / 2 / scale - height / 2);
snapshot.append_scaled_texture (texture,
SCALING_FILTER,
{ { x, y }, { width, height } });
} }
private static void draw_scale_wide (int buffer_width, private static void draw_scale_wide (int buffer_width,
int width, int width,
int buffer_height, int buffer_height,
int height, int height,
Cairo.Context cr, Gtk.Snapshot snapshot,
Cairo.Surface surface) { Gdk.Texture texture) {
double scale = (double) buffer_height / height; float scale = (float) buffer_height / height;
cr.scale (scale, scale); snapshot.scale (scale, scale);
cr.set_source_surface ( float x = (float) (buffer_width / 2 / scale - width / 2);
surface, float y = 0;
(double) buffer_width / 2 / scale - width / 2, 0); snapshot.append_scaled_texture (texture,
SCALING_FILTER,
{ { x, y }, { width, height } });
} }
public delegate bool FilterFunc (char character); public delegate bool FilterFunc (char character);
...@@ -412,5 +336,24 @@ namespace SwayNotificationCenter { ...@@ -412,5 +336,24 @@ namespace SwayNotificationCenter {
return false; return false;
} }
} }
public static unowned Wl.Display get_wl_display () {
unowned var display = Gdk.Display.get_default ();
if (display is Gdk.Wayland.Display) {
return ((Gdk.Wayland.Display) display).get_wl_display ();
}
GLib.error ("Only supports Wayland!");
}
public static Wl.Surface * get_wl_surface (Gdk.Surface surface) {
if (surface is Gdk.Wayland.Surface) {
return ((Gdk.Wayland.Surface) surface).get_wl_surface ();
}
GLib.error ("Only supports Wayland!");
}
public static double lerp (double a, double b, double t) {
return a * (1.0 - t) + b * t;
}
} }
} }
public class IterBox : Gtk.Box {
public uint length { get; private set; default = 0; }
private List<Gtk.Widget> children = new List<Gtk.Widget> ();
public IterBox (Gtk.Orientation orientation, int spacing) {
Object (orientation: orientation, spacing: spacing);
set_name ("iterbox");
}
private void on_add (Gtk.Widget child) {
length++;
child.destroy.connect (() => {
children.remove (child);
});
}
public List<weak Gtk.Widget> get_children () {
return children.copy ();
}
public new void append (Gtk.Widget child) {
children.append (child);
base.append (children.last ().data);
on_add (child);
}
public new void prepend (Gtk.Widget child) {
children.prepend (child);
base.prepend (children.first ().data);
on_add (child);
}
public new void remove (Gtk.Widget child) {
children.remove (child);
base.remove (child);
length--;
}
}
public class IterListBoxController : Object {
public uint length { get; private set; default = 0; }
private List<Gtk.Widget> children = new List<Gtk.Widget> ();
public unowned Gtk.ListBox list_box {
get;
private set;
}
public IterListBoxController (Gtk.ListBox list_box) {
this.list_box = list_box;
}
private void on_add (Gtk.Widget child) {
length++;
child.destroy.connect (() => {
children.remove (child);
});
}
public List<weak Gtk.Widget> get_children () {
return children.copy ();
}
public void append (Gtk.Widget child) {
children.append (child);
list_box.append (children.last ().data);
on_add (child);
}
public void prepend (Gtk.Widget child) {
children.prepend (child);
list_box.prepend (children.first ().data);
on_add (child);
}
public void insert (Gtk.Widget child, int position) {
children.insert (child, position);
list_box.insert (children.nth (position).data, position);
on_add (child);
}
public void remove (Gtk.Widget child) {
children.remove (child);
list_box.remove (child);
length--;
}
}
namespace SwayNotificationCenter { namespace SwayNotificationCenter {
static SwayncDaemon swaync_daemon; static SwayncDaemon swaync_daemon;
// Args
static string ? style_path; static string ? style_path;
static string ? config_path; static string ? config_path;
// Dev args
static uint layer_shell_protocol_version = 3; static bool skip_packaged_css = false;
static Settings self_settings; static Settings self_settings;
public void main (string[] args) { public void main (string[] args) {
Gtk.init (ref args); Gtk.init ();
Hdy.init (); Adw.init ();
Functions.init (); Functions.init ();
self_settings = new Settings ("org.erikreider.swaync"); self_settings = new Settings ("org.erikreider.swaync");
...@@ -22,6 +23,9 @@ namespace SwayNotificationCenter { ...@@ -22,6 +23,9 @@ namespace SwayNotificationCenter {
case "--style": case "--style":
style_path = args[++i]; style_path = args[++i];
break; break;
case "--skip-system-css":
skip_packaged_css = true;
break;
case "-c": case "-c":
case "--config": case "--config":
config_path = args[++i]; config_path = args[++i];
...@@ -39,25 +43,28 @@ namespace SwayNotificationCenter { ...@@ -39,25 +43,28 @@ namespace SwayNotificationCenter {
} }
} }
ConfigModel.init (config_path); var app = new Gtk.Application ("org.erikreider.swaync",
Functions.load_css (style_path); ApplicationFlags.DEFAULT_FLAGS);
app.hold ();
app.activate.connect (() => {
ConfigModel.init (config_path);
Functions.load_css (style_path);
if (ConfigModel.instance.layer_shell) { swaync_daemon = new SwayncDaemon ();
layer_shell_protocol_version = GtkLayerShell.get_protocol_version (); Bus.own_name (BusType.SESSION, "org.erikreider.swaync.cc",
} BusNameOwnerFlags.NONE,
on_cc_bus_aquired,
() => {},
() => {
stderr.printf (
"Could not acquire swaync name!...\n");
Process.exit (1);
});
swaync_daemon = new SwayncDaemon (); app.add_window (swaync_daemon.noti_daemon.control_center);
Bus.own_name (BusType.SESSION, "org.erikreider.swaync.cc",
BusNameOwnerFlags.NONE,
on_cc_bus_aquired,
() => {},
() => {
stderr.printf (
"Could not acquire swaync name!...\n");
Process.exit (1);
}); });
Gtk.main (); app.run ();
} }
void on_cc_bus_aquired (DBusConnection conn) { void on_cc_bus_aquired (DBusConnection conn) {
...@@ -78,5 +85,6 @@ namespace SwayNotificationCenter { ...@@ -78,5 +85,6 @@ namespace SwayNotificationCenter {
print ("Options:\n"); print ("Options:\n");
print ("\t -s, --style \t\t Use a custom Stylesheet file\n"); print ("\t -s, --style \t\t Use a custom Stylesheet file\n");
print ("\t -c, --config \t\t Use a custom config file\n"); print ("\t -c, --config \t\t Use a custom config file\n");
print ("\t --skip-system-css \t Skip trying to parse the packaged Stylesheet file. Useful for CSS debugging\n");
} }
} }
...@@ -50,22 +50,22 @@ widget_sources = [ ...@@ -50,22 +50,22 @@ widget_sources = [
app_sources = [ app_sources = [
'main.vala', 'main.vala',
'animation/animation.vala',
'orderedHashTable/orderedHashTable.vala', 'orderedHashTable/orderedHashTable.vala',
'iterHelpers/iterBox.vala',
'iterHelpers/iterListBoxController.vala',
'underlay/underlay.vala',
'configModel/configModel.vala', 'configModel/configModel.vala',
'swayncDaemon/swayncDaemon.vala', 'swayncDaemon/swayncDaemon.vala',
'notiDaemon/notiDaemon.vala', 'notiDaemon/notiDaemon.vala',
'notiModel/notiModel.vala', 'notiModel/notiModel.vala',
'fadedViewport/fadedViewport.vala',
'notificationWindow/notificationWindow.vala', 'notificationWindow/notificationWindow.vala',
'dismissableWidget/dismissableWidget.vala',
'notification/notification.vala', 'notification/notification.vala',
'notificationGroup/notificationGroup.vala', 'notificationGroup/notificationGroup.vala',
'controlCenter/controlCenter.vala', 'controlCenter/controlCenter.vala',
widget_sources, widget_sources,
'blankWindow/blankWindow.vala', 'blankWindow/blankWindow.vala',
'functions.vala', 'functions.vala',
'WaylandUtils/WaylandUtils.c',
'WaylandUtils/WaylandUtils.vala',
'xdg_activation.vala', 'xdg_activation.vala',
constants, constants,
] ]
...@@ -73,20 +73,18 @@ app_sources = [ ...@@ -73,20 +73,18 @@ app_sources = [
assert(vala.version() >= '0.56') assert(vala.version() >= '0.56')
app_deps = [ app_deps = [
dependency('gtk4-layer-shell-0', version: '>= 1.0.3'),
dependency('gio-2.0', version: '>= 2.50'), dependency('gio-2.0', version: '>= 2.50'),
dependency('gio-unix-2.0', version: '>= 2.50'), dependency('gio-unix-2.0', version: '>= 2.50'),
dependency('gtk+-3.0', version: '>= 3.22'), dependency('gtk4', version: '>= 4.16.13'),
dependency('gtk4-wayland', version: '>= 4.16.5'),
dependency('json-glib-1.0', version: '>= 1.0'), dependency('json-glib-1.0', version: '>= 1.0'),
dependency('libhandy-1', version: '>= 1.8.0'), dependency('libadwaita-1', version: '>= 1.6.1'),
dependency('granite', version: '>= 6.2.0'), dependency('granite-7', version: '>= 7.5.0'),
dependency('gtk-layer-shell-0',
fallback: ['gtk-layer-shell-0', 'gtk-layer-shell'],
version: '>= 0.8.0'
),
cc.find_library('m', required : true), cc.find_library('m', required : true),
vala.find_library('posix'), vala.find_library('posix'),
dependency('gee-0.8'), dependency('gee-0.8', version: '>=0.20.6'),
dependency('wayland-client'), dependency('wayland-client', version: '>=1.23.0'),
protocol_dep, protocol_dep,
] ]
...@@ -113,15 +111,9 @@ if get_option('pulse-audio') ...@@ -113,15 +111,9 @@ if get_option('pulse-audio')
endif endif
args = [ args = [
'--target-glib=2.50', '--target-glib=2.82',
'--pkg=GtkLayerShell-0.1',
] ]
app_resources += gnome.compile_resources('sway_notification_center-resources',
'sway_notification_center.gresource.xml',
c_name: 'sway_notification_center'
)
executable('swaync', executable('swaync',
[ app_sources, app_resources ], [ app_sources, app_resources ],
vala_args: args, vala_args: args,
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.40.0 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<requires lib="libhandy" version="1.2"/>
<template class="SwayNotificationCenterNotification" parent="GtkListBoxRow">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
<object class="GtkRevealer" id="revealer">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="transition-type">crossfade</property>
<child>
<object class="HdyCarousel" id="carousel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<child>
<placeholder/>
</child>
</object>
</child>
<child>
<object class="GtkEventBox" id="event_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkOverlay" id="overlay">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkBox" id="base_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkEventBox" id="default_action">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="events">GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_STRUCTURE_MASK</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkOverlay">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<child>
<object class="GtkImage" id="img">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">center</property>
<property name="icon_size">6</property>
<style>
<class name="image"/>
</style>
</object>
<packing>
<property name="index">-1</property>
</packing>
</child>
<child type="overlay">
<object class="GtkImage" id="img_app_icon">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="valign">end</property>
<property name="icon_size">6</property>
<style>
<class name="app-icon"/>
</style>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">center</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">center</property>
<child>
<object class="GtkLabel" id="summary">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="ellipsize">end</property>
<property name="max-width-chars">0</property>
<property name="xalign">0</property>
<style>
<class name="summary"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="time">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="margin-start">6</property>
<property name="track-visited-links">False</property>
<style>
<class name="time"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="body">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="ellipsize">end</property>
<property name="lines">1</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<style>
<class name="body"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<style>
<class name="text-box"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkProgressBar" id="progress_bar">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkImage" id="body_image">
<property name="can-focus">False</property>
<property name="halign">center</property>
<style>
<class name="body-image"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox" id="inline_reply_box">
<property name="can-focus">False</property>
<child>
<object class="GtkEntry" id="inline_reply_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="input-hints">GTK_INPUT_HINT_SPELLCHECK | GTK_INPUT_HINT_EMOJI | GTK_INPUT_HINT_NONE</property>
<style>
<class name="inline-reply-entry"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="inline_reply_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<style>
<class name="inline-reply-button"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<style>
<class name="inline-reply"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<style>
<class name="notification-content"/>
</style>
</object>
</child>
<style>
<class name="notification-default-action"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<style>
<class name="notification"/>
</style>
</object>
<packing>
<property name="index">-1</property>
</packing>
</child>
<child type="overlay">
<object class="GtkRevealer" id="close_revealer">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="valign">start</property>
<property name="transition-type">crossfade</property>
<child>
<object class="GtkButton" id="close_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="margin-top">2</property>
<property name="margin-bottom">2</property>
<property name="relief">none</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">swaync-close-symbolic</property>
</object>
</child>
<style>
<class name="close-button"/>
<class name="circular"/>
</style>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<style>
<class name="notification-background"/>
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<style>
<class name="notification-row"/>
</style>
</template>
</interface>
namespace SwayNotificationCenter { namespace SwayNotificationCenter {
public class NotificationCloseButton : Adw.Bin {
Gtk.Revealer revealer;
Gtk.Button button;
construct {
valign = Gtk.Align.START;
// TODO: Configurable
halign = Gtk.Align.END;
revealer = new Gtk.Revealer () {
transition_type = Gtk.RevealerTransitionType.CROSSFADE,
reveal_child = false,
};
revealer.notify["child-revealed"].connect (() => {
set_visible (revealer.reveal_child);
});
set_child (revealer);
button = new Gtk.Button.from_icon_name ("swaync-close-symbolic") {
has_frame = false,
halign = Gtk.Align.CENTER,
valign = Gtk.Align.CENTER,
};
button.add_css_class ("close-button");
button.add_css_class ("circular");
button.clicked.connect (() => this.clicked ());
revealer.set_child (button);
}
public signal void clicked ();
public void set_reveal (bool state) {
if (state == revealer.reveal_child) {
return;
}
if (state) {
set_visible (true);
}
revealer.set_reveal_child (state);
}
public void set_transition_duration (uint duration) {
revealer.set_transition_duration (duration);
}
}
public enum NotificationType { CONTROL_CENTER, POPUP } public enum NotificationType { CONTROL_CENTER, POPUP }
[GtkTemplate (ui = "/org/erikreider/sway-notification-center/notification/notification.ui")] [GtkTemplate (ui = "/org/erikreider/swaync/ui/notification.ui")]
public class Notification : Gtk.ListBoxRow { public class Notification : Gtk.Box {
[GtkChild] [GtkChild]
unowned Gtk.Revealer revealer; unowned Gtk.Revealer revealer;
[GtkChild] [GtkChild]
unowned Hdy.Carousel carousel; unowned DismissibleWidget dismissible_widget;
[GtkChild] [GtkChild]
unowned Gtk.EventBox event_box; unowned Gtk.Overlay base_widget;
[GtkChild] [GtkChild]
unowned Gtk.EventBox default_action; unowned Gtk.Box default_action;
[GtkChild]
unowned Gtk.FlowBox alt_actions_box;
/** The default_action gesture. Allows clicks while not in swipe gesture. */ /** The default_action gesture. Allows clicks while not in swipe gesture. */
private Gtk.GestureMultiPress gesture; private Gtk.GestureClick gesture;
/** Detects when hovering over the widget */
private Gtk.EventControllerMotion motion_controller;
[GtkChild] [GtkChild]
unowned Gtk.ProgressBar progress_bar; unowned Gtk.ProgressBar progress_bar;
[GtkChild] [GtkChild]
unowned Gtk.Box base_box; unowned IterBox base_box;
[GtkChild] [GtkChild]
unowned Gtk.Revealer close_revealer; unowned NotificationCloseButton close_button;
[GtkChild]
unowned Gtk.Button close_button;
private Gtk.ButtonBox ? alt_actions_box = null;
[GtkChild] [GtkChild]
unowned Gtk.Label summary; unowned Gtk.Label summary;
...@@ -43,7 +90,7 @@ namespace SwayNotificationCenter { ...@@ -43,7 +90,7 @@ namespace SwayNotificationCenter {
[GtkChild] [GtkChild]
unowned Gtk.Image img_app_icon; unowned Gtk.Image img_app_icon;
[GtkChild] [GtkChild]
unowned Gtk.Image body_image; unowned Gtk.Picture body_image;
// Inline Reply // Inline Reply
[GtkChild] [GtkChild]
...@@ -56,7 +103,6 @@ namespace SwayNotificationCenter { ...@@ -56,7 +103,6 @@ namespace SwayNotificationCenter {
private bool default_action_down = false; private bool default_action_down = false;
private bool default_action_in = false; private bool default_action_in = false;
public static Gtk.IconSize icon_size = Gtk.IconSize.INVALID;
private int notification_icon_size { get; default = ConfigModel.instance.notification_icon_size; } private int notification_icon_size { get; default = ConfigModel.instance.notification_icon_size; }
private int notification_body_image_height { private int notification_body_image_height {
...@@ -91,8 +137,6 @@ namespace SwayNotificationCenter { ...@@ -91,8 +137,6 @@ namespace SwayNotificationCenter {
public bool has_inline_reply { get; private set; default = false; } public bool has_inline_reply { get; private set; default = false; }
private int carousel_empty_widget_index = 0;
private static Regex code_regex; private static Regex code_regex;
private static Regex tag_regex; private static Regex tag_regex;
...@@ -151,51 +195,57 @@ namespace SwayNotificationCenter { ...@@ -151,51 +195,57 @@ namespace SwayNotificationCenter {
stderr.printf ("Invalid regex: %s", e.message); stderr.printf ("Invalid regex: %s", e.message);
} }
// Build the default_action gesture. Makes clickes compatible with // Build the default_action gesture
// the Hdy Swipe gesture unlike a regular ::button_release_event gesture = new Gtk.GestureClick ();
gesture = new Gtk.GestureMultiPress (default_action); default_action.add_controller (gesture);
gesture.set_touch_only (false); gesture.set_touch_only (false);
gesture.set_exclusive (true); gesture.set_exclusive (true);
gesture.set_button (Gdk.BUTTON_PRIMARY); gesture.set_button (0);
gesture.set_propagation_phase (Gtk.PropagationPhase.BUBBLE); gesture.set_propagation_phase (Gtk.PropagationPhase.BUBBLE);
gesture.pressed.connect ((_gesture, _n_press, _x, _y) => { gesture.pressed.connect ((_gesture, _n_press, _x, _y) => {
default_action_in = true; default_action_in = true;
default_action_down = true; default_action_down = true;
default_action_update_state ();
}); });
gesture.released.connect ((gesture, _n_press, _x, _y) => { gesture.released.connect ((gesture, _n_press, _x, _y) => {
// Emit released // Emit released
if (!default_action_down) return; if (!default_action_down) return;
default_action_down = false; default_action_down = false;
if (default_action_in) { if (default_action_in) {
click_default_action (); // Close notification on middle and right button click
switch (gesture.get_current_button ()) {
default:
case Gdk.BUTTON_PRIMARY:
click_default_action ();
break;
case Gdk.BUTTON_MIDDLE:
case Gdk.BUTTON_SECONDARY:
this.close_notification ();
break;
}
} }
Gdk.EventSequence ? sequence = gesture.get_current_sequence (); Gdk.EventSequence ? sequence = gesture.get_current_sequence ();
if (sequence == null) { if (sequence == null) {
default_action_in = false; default_action_in = false;
default_action_update_state ();
} }
}); });
gesture.update.connect ((gesture, sequence) => { gesture.update.connect ((gesture, sequence) => {
Gtk.GestureSingle gesture_single = (Gtk.GestureSingle) gesture; Gtk.GestureSingle gesture_single = (Gtk.GestureSingle) gesture;
if (sequence != gesture_single.get_current_sequence ()) return; if (sequence != gesture_single.get_current_sequence ()) return;
Gtk.Allocation allocation; int width = default_action.get_width ();
int height = default_action.get_height ();
double x, y; double x, y;
default_action.get_allocation (out allocation);
gesture.get_point (sequence, out x, out y); gesture.get_point (sequence, out x, out y);
bool in_button = (x >= 0 && y >= 0 && x < allocation.width && y < allocation.height); bool in_button = (x >= 0 && y >= 0 && x < width && y < height);
if (default_action_in != in_button) { if (default_action_in != in_button) {
default_action_in = in_button; default_action_in = in_button;
default_action_update_state ();
} }
}); });
gesture.cancel.connect ((_gesture, _sequence) => { gesture.cancel.connect ((_gesture, _sequence) => {
if (default_action_down) { if (default_action_down) {
default_action_down = false; default_action_down = false;
default_action_update_state ();
} }
}); });
...@@ -205,72 +255,41 @@ namespace SwayNotificationCenter { ...@@ -205,72 +255,41 @@ namespace SwayNotificationCenter {
/// Signals /// Signals
/// ///
this.button_press_event.connect ((event) => {
// Close notification on middle and right button click
switch (event.button) {
case Gdk.BUTTON_MIDDLE:
case Gdk.BUTTON_SECONDARY:
this.close_notification ();
return true;
default:
return false;
}
});
// Adds CSS :hover selector to EventBox
default_action.enter_notify_event.connect ((event) => {
if (event.detail != Gdk.NotifyType.INFERIOR
&& event.window == default_action.get_window ()) {
default_action_in = true;
default_action_update_state ();
}
return true;
});
default_action.leave_notify_event.connect ((event) => {
if (event.detail != Gdk.NotifyType.INFERIOR
&& event.window == default_action.get_window ()) {
default_action_in = false;
default_action_update_state ();
}
return true;
});
default_action.unmap.connect (() => default_action_in = false); default_action.unmap.connect (() => default_action_in = false);
close_button.clicked.connect (() => close_notification ()); close_button.clicked.connect (() => close_notification ());
this.event_box.enter_notify_event.connect ((event) => { motion_controller = new Gtk.EventControllerMotion ();
close_revealer.set_reveal_child (true); base_widget.add_controller (motion_controller);
motion_controller.enter.connect ((event) => {
close_button.set_reveal (true);
remove_noti_timeout (); remove_noti_timeout ();
return false;
}); });
this.event_box.leave_notify_event.connect ((event) => { motion_controller.leave.connect ((controller) => {
if (event.detail == Gdk.NotifyType.INFERIOR) return true; close_button.set_reveal (false);
close_revealer.set_reveal_child (false);
add_notification_timeout (); add_notification_timeout ();
return false;
}); });
this.carousel.page_changed.connect ((_, i) => {
if (i != this.carousel_empty_widget_index) return; // Remove notification when it has been swiped
dismissible_widget.dismissed.connect (() => {
remove_noti_timeout (); remove_noti_timeout ();
try { try {
noti_daemon.manually_close_notification ( noti_daemon.manually_close_notification (
param.applied_id, false); param.applied_id, false);
} catch (Error e) { } catch (Error e) {
print ("Error: %s\n", e.message); printerr ("Error: %s\n", e.message);
this.destroy (); this.destroy ();
} }
}); });
inline_reply_entry.key_release_event.connect ((w, event_key) => { Gtk.EventControllerKey reply_key_controller = new Gtk.EventControllerKey ();
switch (Gdk.keyval_name (event_key.keyval)) { reply_key_controller.key_released.connect ((keyval, keycode, state) => {
case "Return": if (Gdk.keyval_name (keyval) == "Return") {
inline_reply_button.clicked (); inline_reply_button.clicked ();
return true;
default:
return false;
} }
}); });
inline_reply_entry.add_controller (reply_key_controller);
inline_reply_button.clicked.connect (() => { inline_reply_button.clicked.connect (() => {
string text = inline_reply_entry.get_text ().strip (); string text = inline_reply_entry.get_text ().strip ();
if (text.length == 0) return; if (text.length == 0) return;
...@@ -281,58 +300,31 @@ namespace SwayNotificationCenter { ...@@ -281,58 +300,31 @@ namespace SwayNotificationCenter {
} }
private void default_action_update_state () {
bool pressed = default_action_in && default_action_down;
Gtk.StateFlags flags = default_action.get_state_flags () &
~(Gtk.StateFlags.PRELIGHT | Gtk.StateFlags.ACTIVE);
if (default_action_in) flags |= Gtk.StateFlags.PRELIGHT;
if (pressed) flags |= Gtk.StateFlags.ACTIVE;
default_action.set_state_flags (flags, true);
}
private void on_size_allocation (Gtk.Allocation _ignored) {
// Force recomputing the allocated size of the wrapped GTK label in the body.
// `queue_resize` alone DOES NOT WORK because it does not properly invalidate
// the cache, this is a GTK bug!
// See https://gitlab.gnome.org/GNOME/gtk/-/issues/2556
if (body != null) {
body.set_size_request (-1, body.get_allocated_height ());
}
}
private void build_noti () { private void build_noti () {
this.body.set_line_wrap (true); this.body.set_wrap (true);
this.body.set_line_wrap_mode (Pango.WrapMode.WORD_CHAR); this.body.set_wrap_mode (Pango.WrapMode.WORD_CHAR);
this.body.set_natural_wrap_mode (Gtk.NaturalWrapMode.WORD);
this.body.set_ellipsize (Pango.EllipsizeMode.END); this.body.set_ellipsize (Pango.EllipsizeMode.END);
this.summary.set_line_wrap (false); this.summary.set_wrap (false);
this.summary.set_text (param.summary ?? param.app_name); this.summary.set_text (param.summary ?? param.app_name);
this.summary.set_ellipsize (Pango.EllipsizeMode.END); this.summary.set_ellipsize (Pango.EllipsizeMode.END);
close_revealer.set_transition_duration (this.transition_time); close_button.set_transition_duration (this.transition_time);
this.revealer.set_transition_duration (this.transition_time); this.revealer.set_transition_duration (this.transition_time);
this.carousel.set_animation_duration (this.transition_time);
// Changes the swipe direction depending on the notifications X position // Changes the swipe direction depending on the notifications X position
switch (ConfigModel.instance.positionX) { switch (ConfigModel.instance.positionX) {
case PositionX.LEFT: case PositionX.LEFT:
this.carousel.reorder (event_box, 0); dismissible_widget.set_gesture_direction (SwipeDirection.SWIPE_LEFT);
this.carousel_empty_widget_index = 1;
break; break;
default: default:
case PositionX.RIGHT: case PositionX.RIGHT:
case PositionX.CENTER: case PositionX.CENTER:
this.carousel.scroll_to (event_box); dismissible_widget.set_gesture_direction (SwipeDirection.SWIPE_RIGHT);
this.carousel_empty_widget_index = 0;
break; break;
} }
// Reset state
this.carousel.scroll_to (event_box);
this.carousel.allow_scroll_wheel = false;
if (this.progress_bar.visible = param.has_synch) { if (this.progress_bar.visible = param.has_synch) {
this.progress_bar.set_fraction (param.value * 0.01); this.progress_bar.set_fraction (param.value * 0.01);
...@@ -344,18 +336,14 @@ namespace SwayNotificationCenter { ...@@ -344,18 +336,14 @@ namespace SwayNotificationCenter {
set_actions (); set_actions ();
set_style_urgency (); set_style_urgency ();
this.show ();
Timeout.add (0, () => { Timeout.add (0, () => {
this.revealer.set_reveal_child (true); revealer.set_reveal_child (true);
return Source.REMOVE; return Source.REMOVE;
}); });
remove_noti_timeout (); remove_noti_timeout ();
this.size_allocate.disconnect (on_size_allocation);
if (is_timed) { if (is_timed) {
add_notification_timeout (); add_notification_timeout ();
this.size_allocate.connect (on_size_allocation);
} }
} }
...@@ -365,7 +353,7 @@ namespace SwayNotificationCenter { ...@@ -365,7 +353,7 @@ namespace SwayNotificationCenter {
this.body.set_lines (this.number_of_body_lines); this.body.set_lines (this.number_of_body_lines);
// Reset state // Reset state
this.body_image.hide (); body_image.hide ();
// Removes all image tags and adds them to an array // Removes all image tags and adds them to an array
if (text.length > 0) { if (text.length > 0) {
...@@ -396,16 +384,20 @@ namespace SwayNotificationCenter { ...@@ -396,16 +384,20 @@ namespace SwayNotificationCenter {
// Set the image if exists and is valid // Set the image if exists and is valid
if (img_paths.length > 0) { if (img_paths.length > 0) {
var img = Functions.uri_to_path (img_paths[0]); string img = Functions.uri_to_path (img_paths[0]);
var file = File.new_for_path (img); File file = File.new_for_path (img);
if (img.length > 0 && file.query_exists ()) { if (img.length > 0 && file.query_exists ()) {
var buf = new Gdk.Pixbuf.from_file_at_scale ( Gdk.Texture texture = Gdk.Texture.from_file (file);
file.get_path (), body_image.set_paintable (texture);
notification_body_image_width, body_image.set_can_shrink (true);
notification_body_image_height, body_image.set_content_fit (Gtk.ContentFit.SCALE_DOWN);
true); body_image.width_request = notification_body_image_width;
this.body_image.set_from_pixbuf (buf); body_image.height_request = notification_body_image_height;
this.body_image.show (); // Fixes the Picture taking up too much space:
// https://gitlab.gnome.org/GNOME/gtk/-/issues/7092
Gtk.LayoutManager layout = new Gtk.CenterLayout ();
body_image.set_layout_manager (layout);
body_image.show ();
} }
} }
} catch (Error e) { } catch (Error e) {
...@@ -471,13 +463,20 @@ namespace SwayNotificationCenter { ...@@ -471,13 +463,20 @@ namespace SwayNotificationCenter {
} }
public void click_alt_action (uint index) { public void click_alt_action (uint index) {
if (alt_actions_box == null) return; if (!alt_actions_box.visible) return;
List<weak Gtk.Widget> ? children = alt_actions_box.get_children ();
uint length = children.length ();
if (length == 0 || index >= length) return;
unowned Gtk.Widget button = children.nth_data (index); unowned Gtk.Widget? button = null;
if (button is Gtk.Button) { int i = 0;
foreach (unowned Gtk.Widget child in base_box.get_children ()) {
if (i == index) {
button = child;
}
i++;
}
if (button == null) {
return;
} else if (button is Gtk.Button) {
((Gtk.Button) button).clicked (); ((Gtk.Button) button).clicked ();
return; return;
} }
...@@ -511,20 +510,20 @@ namespace SwayNotificationCenter { ...@@ -511,20 +510,20 @@ namespace SwayNotificationCenter {
private void set_style_urgency () { private void set_style_urgency () {
// Reset state // Reset state
base_box.get_style_context ().remove_class ("low"); base_box.remove_css_class ("low");
base_box.get_style_context ().remove_class ("normal"); base_box.remove_css_class ("normal");
base_box.get_style_context ().remove_class ("critical"); base_box.remove_css_class ("critical");
switch (param.urgency) { switch (param.urgency) {
case UrgencyLevels.LOW: case UrgencyLevels.LOW:
base_box.get_style_context ().add_class ("low"); base_box.add_css_class ("low");
break; break;
case UrgencyLevels.NORMAL: case UrgencyLevels.NORMAL:
default: default:
base_box.get_style_context ().add_class ("normal"); base_box.add_css_class ("normal");
break; break;
case UrgencyLevels.CRITICAL: case UrgencyLevels.CRITICAL:
base_box.get_style_context ().add_class ("critical"); base_box.add_css_class ("critical");
break; break;
} }
} }
...@@ -536,7 +535,7 @@ namespace SwayNotificationCenter { ...@@ -536,7 +535,7 @@ namespace SwayNotificationCenter {
// supports ON_DEMAND layer shell keyboard interactivity // supports ON_DEMAND layer shell keyboard interactivity
if (!ConfigModel.instance.notification_inline_replies if (!ConfigModel.instance.notification_inline_replies
|| (ConfigModel.instance.layer_shell || (ConfigModel.instance.layer_shell
&& layer_shell_protocol_version < 4 && swaync_daemon.has_layer_on_demand
&& notification_type == NotificationType.POPUP)) { && notification_type == NotificationType.POPUP)) {
return; return;
} }
...@@ -563,51 +562,51 @@ namespace SwayNotificationCenter { ...@@ -563,51 +562,51 @@ namespace SwayNotificationCenter {
} }
private void set_actions () { private void set_actions () {
// Reset state alt_actions_box.set_visible (false);
foreach (Gtk.Widget child in base_box.get_children ()) {
if (child is Gtk.ScrolledWindow) { // Remove all of the old alt actions
child.destroy (); for (unowned Gtk.Widget child = alt_actions_box.get_first_child ();
} child != null;
child = alt_actions_box.get_first_child ()) {
alt_actions_box.remove (child);
} }
// Check for security codes // Check for security codes
string ? code = parse_body_codes (); string ? code = parse_body_codes ();
// Display all of the actions
if (param.actions.length > 0 || code != null) { if (param.actions.length > 0 || code != null) {
var viewport = new Gtk.Viewport (null, null); alt_actions_box.set_visible (true);
var scroll = new Gtk.ScrolledWindow (null, null);
alt_actions_box = new Gtk.ButtonBox (Gtk.Orientation.HORIZONTAL);
alt_actions_box.set_homogeneous (true);
alt_actions_box.set_layout (Gtk.ButtonBoxStyle.EXPAND);
// Add "Copy code" Action if available and copy it to clipboard when clicked // Add "Copy code" Action if available and copy it to clipboard when clicked
if (code != null && code.length > 0) { if (code != null && code.length > 0) {
string action_name = "COPY \"%s\"".printf (code); Gtk.FlowBoxChild flowbox_child = new Gtk.FlowBoxChild ();
var action_button = new Gtk.Button.with_label (action_name); flowbox_child.add_css_class ("notification-action");
alt_actions_box.append (flowbox_child);
Gtk.Button action_button = new Gtk.Button.with_label (
"COPY \"%s\"".printf (code));
action_button.clicked.connect (() => { action_button.clicked.connect (() => {
// Copy to clipboard // Copy to clipboard
get_clipboard (Gdk.SELECTION_CLIPBOARD).set_text (code, -1); get_clipboard ().set_text (code);
// Dismiss notification // Dismiss notification
action_clicked (null); action_clicked (null);
}); });
action_button
.get_style_context ().add_class ("notification-action");
action_button.set_can_focus (false); action_button.set_can_focus (false);
alt_actions_box.add (action_button); flowbox_child.set_child (action_button);
} }
// Add notification specified actions // Add notification specified actions
foreach (var action in param.actions.data) { foreach (var action in param.actions.data) {
var action_button = new Gtk.Button.with_label (action.name); Gtk.FlowBoxChild flowbox_child = new Gtk.FlowBoxChild ();
flowbox_child.add_css_class ("notification-action");
alt_actions_box.append (flowbox_child);
Gtk.Button action_button = new Gtk.Button.with_label (action.name);
action_button.clicked.connect (() => action_clicked (action)); action_button.clicked.connect (() => action_clicked (action));
action_button
.get_style_context ().add_class ("notification-action");
action_button.set_can_focus (false); action_button.set_can_focus (false);
alt_actions_box.add (action_button); flowbox_child.set_child (action_button);
} }
viewport.add (alt_actions_box);
scroll.add (viewport);
base_box.add (scroll);
scroll.show_all ();
} }
} }
...@@ -712,11 +711,8 @@ namespace SwayNotificationCenter { ...@@ -712,11 +711,8 @@ namespace SwayNotificationCenter {
// Check if it's a freedesktop.org-compliant icon // Check if it's a freedesktop.org-compliant icon
if (!img_path_exists) { if (!img_path_exists) {
unowned Gtk.IconTheme icon_theme = Gtk.IconTheme.get_default (); unowned Gtk.IconTheme icon_theme = Gtk.IconTheme.get_for_display (get_display ());
Gtk.IconInfo? info = icon_theme.lookup_icon (param.image_path, img_path_exists = icon_theme.has_icon (param.image_path);
notification_icon_size,
Gtk.IconLookupFlags.USE_BUILTIN);
img_path_exists = info != null;
img_path_is_theme_icon = img_path_exists; img_path_is_theme_icon = img_path_exists;
} }
} }
...@@ -726,30 +722,20 @@ namespace SwayNotificationCenter { ...@@ -726,30 +722,20 @@ namespace SwayNotificationCenter {
app_icon_exists = File.new_for_path (app_icon_uri ?? "").query_exists (); app_icon_exists = File.new_for_path (app_icon_uri ?? "").query_exists ();
} }
// Get the image CSS corner radius in pixels
int radius = 0;
unowned var ctx = img.get_style_context ();
var value = ctx.get_property (Gtk.STYLE_PROPERTY_BORDER_RADIUS,
ctx.get_state ());
if (value.type () == Type.INT) {
radius = value.get_int ();
}
// Set the main image to the provided image // Set the main image to the provided image
if (param.image_data.is_initialized) { if (param.image_data.is_initialized) {
Functions.set_image_data (param.image_data, img, Functions.set_image_data (param.image_data, img,
notification_icon_size, radius); notification_icon_size);
} else if (param.image_path != null && } else if (param.image_path != null &&
param.image_path != "" && param.image_path != "" &&
img_path_exists) { img_path_exists) {
Functions.set_image_uri (param.image_path, img, Functions.set_image_uri (param.image_path, img,
notification_icon_size, notification_icon_size,
radius,
img_path_exists, img_path_exists,
img_path_is_theme_icon); img_path_is_theme_icon);
} else if (param.icon_data.is_initialized) { } else if (param.icon_data.is_initialized) {
Functions.set_image_data (param.icon_data, img, Functions.set_image_data (param.icon_data, img,
notification_icon_size, radius); notification_icon_size);
} }
if (img.storage_type == Gtk.ImageType.EMPTY) { if (img.storage_type == Gtk.ImageType.EMPTY) {
...@@ -757,13 +743,12 @@ namespace SwayNotificationCenter { ...@@ -757,13 +743,12 @@ namespace SwayNotificationCenter {
if (app_icon_uri != null) { if (app_icon_uri != null) {
Functions.set_image_uri (app_icon_uri, img, Functions.set_image_uri (app_icon_uri, img,
notification_icon_size, notification_icon_size,
radius,
app_icon_exists); app_icon_exists);
} else if (app_icon_name != null) { } else if (app_icon_name != null) {
img.set_from_gicon (app_icon_name, icon_size); img.set_from_gicon (app_icon_name);
} else if (image_visibility == ImageVisibility.ALWAYS) { } else if (image_visibility == ImageVisibility.ALWAYS) {
// Default icon // Default icon
img.set_from_icon_name ("image-missing", icon_size); img.set_from_icon_name ("image-missing");
} else { } else {
img.set_visible (false); img.set_visible (false);
} }
...@@ -772,10 +757,9 @@ namespace SwayNotificationCenter { ...@@ -772,10 +757,9 @@ namespace SwayNotificationCenter {
if (app_icon_uri != null) { if (app_icon_uri != null) {
Functions.set_image_uri (app_icon_uri, img_app_icon, Functions.set_image_uri (app_icon_uri, img_app_icon,
app_icon_size, app_icon_size,
0,
app_icon_exists); app_icon_exists);
} else if (app_icon_name != null) { } else if (app_icon_name != null) {
img_app_icon.set_from_gicon (app_icon_name, Gtk.IconSize.INVALID); img_app_icon.set_from_gicon (app_icon_name);
} }
} }
} }
...@@ -816,11 +800,5 @@ namespace SwayNotificationCenter { ...@@ -816,11 +800,5 @@ namespace SwayNotificationCenter {
timeout_id = 0; timeout_id = 0;
} }
} }
/** Forces the EventBox to reload its style_context #27 */
public void reload_style_context () {
event_box.get_style_context ().changed ();
default_action.get_style_context ().changed ();
}
} }
} }
...@@ -5,12 +5,15 @@ namespace SwayNotificationCenter { ...@@ -5,12 +5,15 @@ namespace SwayNotificationCenter {
public string name_id; public string name_id;
private NotificationCloseButton close_button;
private DismissibleWidget dismissible;
private ExpandableGroup group; private ExpandableGroup group;
private Gtk.Revealer revealer = new Gtk.Revealer (); private Gtk.Revealer revealer = new Gtk.Revealer ();
private Gtk.Image app_icon; private Gtk.Image app_icon;
private Gtk.Label app_label; private Gtk.Label app_label;
private Gtk.GestureMultiPress gesture; private Gtk.EventControllerMotion motion_controller;
private Gtk.GestureClick gesture;
private bool gesture_down = false; private bool gesture_down = false;
private bool gesture_in = false; private bool gesture_in = false;
...@@ -21,9 +24,23 @@ namespace SwayNotificationCenter { ...@@ -21,9 +24,23 @@ namespace SwayNotificationCenter {
public NotificationGroup (string name_id, string display_name) { public NotificationGroup (string name_id, string display_name) {
this.name_id = name_id; this.name_id = name_id;
get_style_context ().add_class ("notification-group"); add_css_class ("notification-group");
dismissible = new DismissibleWidget ();
dismissible.dismissed.connect (close_all_notifications);
set_child (dismissible);
Gtk.Overlay overlay = new Gtk.Overlay ();
dismissible.child = overlay;
Gtk.Box box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); Gtk.Box box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
box.set_hexpand (true);
overlay.set_child (box);
close_button = new NotificationCloseButton ();
close_button.clicked.connect (close_all_notifications);
close_button.add_css_class ("notification-group-close-button");
overlay.add_overlay (close_button);
revealer.set_transition_type (Gtk.RevealerTransitionType.SLIDE_UP); revealer.set_transition_type (Gtk.RevealerTransitionType.SLIDE_UP);
revealer.set_reveal_child (false); revealer.set_reveal_child (false);
...@@ -33,59 +50,56 @@ namespace SwayNotificationCenter { ...@@ -33,59 +50,56 @@ namespace SwayNotificationCenter {
Gtk.Box controls_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 4); Gtk.Box controls_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 4);
Gtk.Box end_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 4); Gtk.Box end_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 4);
end_box.set_halign (Gtk.Align.END); end_box.set_halign (Gtk.Align.END);
end_box.get_style_context ().add_class ("notification-group-buttons"); end_box.add_css_class ("notification-group-buttons");
// Collapse button // Collapse button
Gtk.Button collapse_button = new Gtk.Button.from_icon_name ( Gtk.Button collapse_button = new Gtk.Button.from_icon_name (
"swaync-collapse-symbolic", Gtk.IconSize.BUTTON); "swaync-collapse-symbolic");
collapse_button.get_style_context ().add_class ("flat"); collapse_button.add_css_class ("circular");
collapse_button.get_style_context ().add_class ("circular"); collapse_button.add_css_class ("notification-group-collapse-button");
collapse_button.get_style_context ().add_class ("notification-group-collapse-button");
collapse_button.set_relief (Gtk.ReliefStyle.NORMAL);
collapse_button.set_halign (Gtk.Align.END); collapse_button.set_halign (Gtk.Align.END);
collapse_button.set_valign (Gtk.Align.CENTER); collapse_button.set_valign (Gtk.Align.CENTER);
collapse_button.clicked.connect (() => { collapse_button.clicked.connect (() => {
set_expanded (false); set_expanded (false);
on_expand_change (false); on_expand_change (false);
}); });
end_box.add (collapse_button); end_box.append (collapse_button);
// Close all button // Close all button
Gtk.Button close_all_button = new Gtk.Button.from_icon_name ( Gtk.Button close_all_button = new Gtk.Button.from_icon_name (
"swaync-close-symbolic", Gtk.IconSize.BUTTON); "swaync-close-symbolic");
close_all_button.get_style_context ().add_class ("flat"); close_all_button.add_css_class ("circular");
close_all_button.get_style_context ().add_class ("circular"); close_all_button.add_css_class ("notification-group-close-all-button");
close_all_button.get_style_context ().add_class ("notification-group-close-all-button");
close_all_button.set_relief (Gtk.ReliefStyle.NORMAL);
close_all_button.set_halign (Gtk.Align.END); close_all_button.set_halign (Gtk.Align.END);
close_all_button.set_valign (Gtk.Align.CENTER); close_all_button.set_valign (Gtk.Align.CENTER);
close_all_button.clicked.connect (() => { close_all_button.clicked.connect (() => {
close_all_notifications (); close_all_notifications ();
on_expand_change (false); on_expand_change (false);
}); });
end_box.add (close_all_button); end_box.append (close_all_button);
// Group name label // Group name label
Gtk.Box start_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 4); Gtk.Box start_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 4);
start_box.set_halign (Gtk.Align.START); start_box.set_halign (Gtk.Align.START);
start_box.get_style_context ().add_class ("notification-group-headers"); start_box.set_hexpand (true);
start_box.add_css_class ("notification-group-headers");
// App Icon // App Icon
app_icon = new Gtk.Image (); app_icon = new Gtk.Image ();
app_icon.set_pixel_size (32); app_icon.set_pixel_size (32);
app_icon.set_valign (Gtk.Align.CENTER); app_icon.set_valign (Gtk.Align.CENTER);
app_icon.get_style_context ().add_class ("notification-group-icon"); app_icon.add_css_class ("notification-group-icon");
start_box.add (app_icon); start_box.append (app_icon);
// App Label // App Label
app_label = new Gtk.Label (display_name); app_label = new Gtk.Label (display_name);
app_label.xalign = 0; app_label.xalign = 0;
app_label.get_style_context ().add_class ("title-1"); app_label.add_css_class ("title-1");
app_label.get_style_context ().add_class ("notification-group-header"); app_label.add_css_class ("notification-group-header");
start_box.add (app_label); start_box.append (app_label);
controls_box.pack_start (start_box); controls_box.prepend (start_box);
controls_box.pack_end (end_box); controls_box.append (end_box);
revealer.add (controls_box); revealer.set_child (controls_box);
box.add (revealer); box.append (revealer);
set_activatable (false); set_activatable (false);
...@@ -98,15 +112,13 @@ namespace SwayNotificationCenter { ...@@ -98,15 +112,13 @@ namespace SwayNotificationCenter {
} }
}); });
set_classes (); set_classes ();
box.add (group); box.append (group);
add (box);
show_all ();
/* /*
* Handling of group presses * Handling of group presses
*/ */
gesture = new Gtk.GestureMultiPress (this); gesture = new Gtk.GestureClick ();
box.add_controller (gesture);
gesture.set_touch_only (false); gesture.set_touch_only (false);
gesture.set_exclusive (true); gesture.set_exclusive (true);
gesture.set_button (Gdk.BUTTON_PRIMARY); gesture.set_button (Gdk.BUTTON_PRIMARY);
...@@ -122,7 +134,7 @@ namespace SwayNotificationCenter { ...@@ -122,7 +134,7 @@ namespace SwayNotificationCenter {
if (gesture_in) { if (gesture_in) {
bool single_noti = only_single_notification (); bool single_noti = only_single_notification ();
if (!group.is_expanded && !single_noti) { if (!group.is_expanded && !single_noti) {
group.set_expanded (true); set_expanded (true);
on_expand_change (true); on_expand_change (true);
} }
group.set_sensitive (single_noti || group.is_expanded); group.set_sensitive (single_noti || group.is_expanded);
...@@ -137,12 +149,12 @@ namespace SwayNotificationCenter { ...@@ -137,12 +149,12 @@ namespace SwayNotificationCenter {
Gtk.GestureSingle gesture_single = (Gtk.GestureSingle) gesture; Gtk.GestureSingle gesture_single = (Gtk.GestureSingle) gesture;
if (sequence != gesture_single.get_current_sequence ()) return; if (sequence != gesture_single.get_current_sequence ()) return;
Gtk.Allocation allocation; int width = get_width ();
int height = get_height ();
double x, y; double x, y;
get_allocation (out allocation);
gesture.get_point (sequence, out x, out y); gesture.get_point (sequence, out x, out y);
bool intersects = (x >= 0 && y >= 0 && x < allocation.width && y < allocation.height); bool intersects = (x >= 0 && y >= 0 && x < width && y < height);
if (gesture_in != intersects) { if (gesture_in != intersects) {
gesture_in = intersects; gesture_in = intersects;
} }
...@@ -152,14 +164,25 @@ namespace SwayNotificationCenter { ...@@ -152,14 +164,25 @@ namespace SwayNotificationCenter {
gesture_down = false; gesture_down = false;
} }
}); });
/*
* Handling of group hover
*/
motion_controller = new Gtk.EventControllerMotion ();
this.add_controller (motion_controller);
motion_controller.motion.connect ((event) => {
close_button.set_reveal (!group.is_expanded && !only_single_notification ());
});
motion_controller.leave.connect ((controller) => {
close_button.set_reveal (false);
});
} }
private void set_classes () { private void set_classes () {
unowned Gtk.StyleContext ctx = get_style_context (); remove_css_class (STYLE_CLASS_COLLAPSED);
ctx.remove_class (STYLE_CLASS_COLLAPSED);
if (!group.is_expanded) { if (!group.is_expanded) {
if (!ctx.has_class (STYLE_CLASS_COLLAPSED)) { if (!has_css_class (STYLE_CLASS_COLLAPSED)) {
ctx.add_class (STYLE_CLASS_COLLAPSED); add_css_class (STYLE_CLASS_COLLAPSED);
} }
} }
} }
...@@ -173,23 +196,22 @@ namespace SwayNotificationCenter { ...@@ -173,23 +196,22 @@ namespace SwayNotificationCenter {
Icon ? icon = null; Icon ? icon = null;
if (param.desktop_app_info != null if (param.desktop_app_info != null
&& (icon = param.desktop_app_info.get_icon ()) != null) { && (icon = param.desktop_app_info.get_icon ()) != null) {
app_icon.set_from_gicon (icon, Gtk.IconSize.INVALID); app_icon.set_from_gicon (icon);
app_icon.show (); app_icon.show ();
} else { } else {
app_icon.set_from_icon_name ("application-x-executable-symbolic", app_icon.set_from_icon_name ("application-x-executable-symbolic");
Gtk.IconSize.INVALID);
} }
} }
/// Returns if there's more than one notification /// Returns if there's more than one notification
public bool only_single_notification () { public bool only_single_notification () {
unowned Gtk.Widget ? widget = group.widgets.nth_data (1); return group.widgets.nth_data (0) != null && group.widgets.nth_data (1) == null;
return widget == null;
} }
public void set_expanded (bool state) { public void set_expanded (bool state) {
group.set_expanded (state); group.set_expanded (state);
group.set_sensitive (only_single_notification () || group.is_expanded); group.set_sensitive (only_single_notification () || group.is_expanded);
dismissible.set_can_dismiss (!state);
} }
public bool toggle_expanded () { public bool toggle_expanded () {
...@@ -201,9 +223,8 @@ namespace SwayNotificationCenter { ...@@ -201,9 +223,8 @@ namespace SwayNotificationCenter {
public void add_notification (Notification noti) { public void add_notification (Notification noti) {
if (noti.param.urgency == UrgencyLevels.CRITICAL) { if (noti.param.urgency == UrgencyLevels.CRITICAL) {
urgent_notifications.insert (noti.param.applied_id, true); urgent_notifications.insert (noti.param.applied_id, true);
unowned Gtk.StyleContext ctx = get_style_context (); if (!has_css_class (STYLE_CLASS_URGENT)) {
if (!ctx.has_class (STYLE_CLASS_URGENT)) { add_css_class (STYLE_CLASS_URGENT);
ctx.add_class (STYLE_CLASS_URGENT);
} }
} }
group.add (noti); group.add (noti);
...@@ -219,7 +240,7 @@ namespace SwayNotificationCenter { ...@@ -219,7 +240,7 @@ namespace SwayNotificationCenter {
public void remove_notification (Notification noti) { public void remove_notification (Notification noti) {
urgent_notifications.remove (noti.param.applied_id); urgent_notifications.remove (noti.param.applied_id);
if (urgent_notifications.length == 0) { if (urgent_notifications.length == 0) {
get_style_context ().remove_class (STYLE_CLASS_URGENT); remove_css_class (STYLE_CLASS_URGENT);
} }
group.remove (noti); group.remove (noti);
if (only_single_notification ()) { if (only_single_notification ()) {
...@@ -254,10 +275,13 @@ namespace SwayNotificationCenter { ...@@ -254,10 +275,13 @@ namespace SwayNotificationCenter {
} }
public void close_all_notifications () { public void close_all_notifications () {
close_button.set_reveal (false);
urgent_notifications.remove_all (); urgent_notifications.remove_all ();
foreach (unowned Gtk.Widget widget in group.widgets) { foreach (unowned Gtk.Widget widget in group.widgets) {
var noti = (Notification) widget; var noti = (Notification) widget;
if (noti != null) noti.close_notification (false); if (noti != null) {
noti.close_notification (false);
}
} }
} }
...@@ -269,14 +293,15 @@ namespace SwayNotificationCenter { ...@@ -269,14 +293,15 @@ namespace SwayNotificationCenter {
} }
} }
public int get_relative_y (Gtk.Widget parent) { public float get_relative_y (Gtk.Widget parent) {
int dest_y; Graphene.Point point = Graphene.Point.zero ();
translate_coordinates (parent, 0, 0, null, out dest_y); Graphene.Point dest_point;
return dest_y; compute_point (parent, point, out dest_point);
return dest_point.y;
} }
} }
private class ExpandableGroup : Gtk.Container { private class ExpandableGroup : Gtk.Widget {
const int NUM_STACKED_NOTIFICATIONS = 3; const int NUM_STACKED_NOTIFICATIONS = 3;
const int COLLAPSED_NOTIFICATION_OFFSET = 8; const int COLLAPSED_NOTIFICATION_OFFSET = 8;
...@@ -288,7 +313,8 @@ namespace SwayNotificationCenter { ...@@ -288,7 +313,8 @@ namespace SwayNotificationCenter {
return (1 - animation_progress); return (1 - animation_progress);
} }
} }
private Animation ? animation; private Adw.TimedAnimation animation;
private Adw.CallbackAnimationTarget animation_target;
private unowned on_expand_change change_cb; private unowned on_expand_change change_cb;
...@@ -297,15 +323,16 @@ namespace SwayNotificationCenter { ...@@ -297,15 +323,16 @@ namespace SwayNotificationCenter {
public delegate void on_expand_change (bool state); public delegate void on_expand_change (bool state);
public ExpandableGroup (uint animation_duration, on_expand_change change_cb) { public ExpandableGroup (uint animation_duration, on_expand_change change_cb) {
base.set_has_window (false);
base.set_can_focus (true); base.set_can_focus (true);
base.set_redraw_on_allocate (false);
this.change_cb = change_cb; this.change_cb = change_cb;
animation = new Animation (this, animation_duration,
Animation.ease_in_out_cubic, animation_target = new Adw.CallbackAnimationTarget (animation_value_cb);
animation_value_cb, animation = new Adw.TimedAnimation (this, 1.0, 0.0,
animation_done_cb); animation_duration,
animation_target);
animation.set_easing (Adw.Easing.EASE_IN_OUT_CUBIC);
animation.done.connect (animation_done_cb);
this.show (); this.show ();
...@@ -323,36 +350,84 @@ namespace SwayNotificationCenter { ...@@ -323,36 +350,84 @@ namespace SwayNotificationCenter {
change_cb (is_expanded); change_cb (is_expanded);
} }
public override void add (Gtk.Widget widget) { public void add (Gtk.Widget widget) {
widget.set_parent (this); widget.set_parent (this);
widgets.append (widget); widgets.append (widget);
} }
public override void remove (Gtk.Widget widget) { public void remove (Gtk.Widget widget) {
widget.unparent (); widget.unparent ();
widgets.remove (widget); widgets.remove (widget);
if (this.get_visible () && widget.get_visible ()) { if (this.get_visible () && widget.get_visible ()) {
this.queue_resize_no_redraw (); queue_resize ();
} }
} }
public override void forall_internal (bool include_internals, Gtk.Callback callback) { private Gtk.Allocation get_alloc (Gtk.Widget w) {
foreach (unowned Gtk.Widget widget in widgets) { Gtk.Allocation alloc = Gtk.Allocation ();
callback (widget); Graphene.Rect bounds;
} w.compute_bounds (this, out bounds);
alloc.width = w.get_width ();
alloc.height = w.get_height ();
alloc.x = (int) bounds.origin.x;
alloc.y = (int) bounds.origin.y;
return alloc;
} }
public override Gtk.SizeRequestMode get_request_mode () { public override Gtk.SizeRequestMode get_request_mode () {
return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH; return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
} }
public override void size_allocate (Gtk.Allocation allocation) { protected override void measure (Gtk.Orientation orientation, int for_size,
base.size_allocate (allocation); out int minimum, out int natural,
out int minimum_baseline, out int natural_baseline) {
minimum = 0;
natural = 0;
minimum_baseline = -1;
natural_baseline = -1;
if (orientation == Gtk.Orientation.HORIZONTAL) {
return;
}
foreach (unowned Gtk.Widget widget in widgets) {
if (widget != null && widget.get_visible ()) {
int widget_minimum_height = 0;
int widget_natural_height = 0;
widget.measure (orientation, for_size,
out widget_minimum_height,
out widget_natural_height,
null, null);
minimum += widget_minimum_height;
natural += widget_natural_height;
}
}
int target_natural_height;
int target_minimum_height;
get_height_for_latest_notifications (for_size, out target_minimum_height,
out target_natural_height);
// TODO: Always use natural as minimum?
// Fixes large (tall) Notification body Pictures
minimum = (int) Functions.lerp (minimum,
target_natural_height,
animation_progress_inv);
natural = (int) Functions.lerp (natural,
target_natural_height,
animation_progress_inv);
}
protected override void size_allocate (int alloc_width, int alloc_height, int baseline) {
base.size_allocate (alloc_width, alloc_height, baseline);
int length = (int) widgets.length (); int length = (int) widgets.length ();
if (length == 0) return; if (length == 0) return;
uint border_width = get_border_width (); Gtk.Allocation allocation = get_alloc (this);
allocation.width = alloc_width;
allocation.height = alloc_height;
Gtk.Allocation prev_allocation = Gtk.Allocation (); Gtk.Allocation prev_allocation = Gtk.Allocation ();
prev_allocation.y = allocation.y; prev_allocation.y = allocation.y;
...@@ -360,29 +435,28 @@ namespace SwayNotificationCenter { ...@@ -360,29 +435,28 @@ namespace SwayNotificationCenter {
// The height of the most recent notification // The height of the most recent notification
unowned Gtk.Widget last = widgets.last ().data; unowned Gtk.Widget last = widgets.last ().data;
int target_height = 0; int target_height = 0;
last.get_preferred_height_for_width (allocation.width,
out target_height, null); last.measure (Gtk.Orientation.VERTICAL, allocation.width, null, out target_height, null, null);
for (int i = length - 1; i >= 0; i--) { for (int i = length - 1; i >= 0; i--) {
unowned Gtk.Widget widget = widgets.nth_data (i); unowned Gtk.Widget widget = widgets.nth_data (i);
if (widget != null && widget.get_visible ()) { if (widget != null && widget.get_visible ()) {
int height; int height;
widget.get_preferred_height_for_width (allocation.width, widget.measure (Gtk.Orientation.VERTICAL, allocation.width,
out height, null); null, out height,
null, null);
Gtk.Allocation alloc = Gtk.Allocation (); Gtk.Allocation alloc = Gtk.Allocation ();
alloc.x = allocation.x + (int) border_width; alloc.x = allocation.x;
alloc.y = (int) (prev_allocation.y + alloc.y = (int) (prev_allocation.y +
animation_progress * prev_allocation.height + animation_progress * prev_allocation.height);
border_width); alloc.width = allocation.width;
alloc.width = allocation.width - 2 * (int) border_width;
alloc.height = height; alloc.height = height;
// Expand smaller stacked notifications to the expected height // Expand smaller stacked notifications to the expected height
// But only when the animation has finished // But only when the animation has finished
if (target_height > height && !is_expanded && animation_progress == 0) { if (target_height > height && !is_expanded && animation_progress == 0) {
alloc.height = target_height; alloc.height = target_height;
} }
alloc.height -= 2 * (int) border_width;
// Add the collapsed offset to only stacked notifications. // Add the collapsed offset to only stacked notifications.
// Excludes notifications index > NUM_STACKED_NOTIFICATIONS // Excludes notifications index > NUM_STACKED_NOTIFICATIONS
...@@ -391,7 +465,9 @@ namespace SwayNotificationCenter { ...@@ -391,7 +465,9 @@ namespace SwayNotificationCenter {
} }
prev_allocation = alloc; prev_allocation = alloc;
widget.size_allocate (alloc); Gsk.Transform transform = new Gsk.Transform ();
transform = transform.translate (Graphene.Point ().init (alloc.x, alloc.y));
widget.allocate (alloc.width, alloc.height, baseline, transform);
if (get_realized ()) { if (get_realized ()) {
widget.show (); widget.show ();
...@@ -403,62 +479,22 @@ namespace SwayNotificationCenter { ...@@ -403,62 +479,22 @@ namespace SwayNotificationCenter {
} }
} }
public override void get_preferred_height_for_width (int width, // Draw the widget
out int minimum_height, protected override void snapshot (Gtk.Snapshot snapshot) {
out int natural_height) {
minimum_height = 0;
natural_height = 0;
foreach (unowned Gtk.Widget widget in widgets) {
if (widget != null && widget.get_visible ()) {
int widget_minimum_height = 0;
int widget_natural_height = 0;
widget.get_preferred_height_for_width (width,
out widget_minimum_height,
out widget_natural_height);
minimum_height += widget_minimum_height;
natural_height += widget_natural_height;
}
}
int target_minimum_height;
int target_natural_height;
get_height_for_latest_notifications (width,
out target_minimum_height,
out target_natural_height);
minimum_height = (int) Animation.lerp (minimum_height,
target_minimum_height,
animation_progress_inv);
natural_height = (int) Animation.lerp (natural_height,
target_natural_height,
animation_progress_inv);
}
public override bool draw (Cairo.Context cr) {
int length = (int) widgets.length (); int length = (int) widgets.length ();
if (length == 0) return true; if (length == 0) {
return;
}
Gtk.Allocation alloc; Graphene.Rect bounds = Graphene.Rect ();
get_allocated_size (out alloc, null); if (!compute_bounds (this, out bounds)) {
return;
}
Gtk.Allocation alloc = get_alloc (this);
int width = alloc.width;
unowned Gtk.Widget latest = widgets.nth_data (length - 1); unowned Gtk.Widget latest = widgets.nth_data (length - 1);
Gtk.Allocation latest_alloc; Gtk.Allocation latest_alloc = get_alloc (latest);
latest.get_allocated_size (out latest_alloc, null);
Cairo.Pattern hover_gradient = new Cairo.Pattern.linear (0, 0, 0, 1);
hover_gradient.add_color_stop_rgba (0, 1, 1, 1, 1);
hover_gradient.add_color_stop_rgba (1, 1, 1, 1, 1);
// Fades from the bottom at 0.5 -> top at 0.0 opacity
Cairo.Pattern fade_gradient = new Cairo.Pattern.linear (0, 0, 0, 1);
fade_gradient.add_color_stop_rgba (0, 1, 1, 1, animation_progress_inv);
fade_gradient.add_color_stop_rgba (1, 1, 1, 1, 0);
// Cross-fades in the non visible stacked notifications when expanded
Cairo.Pattern cross_fade_pattern =
new Cairo.Pattern.rgba (1, 1, 1, 1.5 * animation_progress_inv);
int width = alloc.width;
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
// Skip drawing excess notifications // Skip drawing excess notifications
...@@ -469,101 +505,100 @@ namespace SwayNotificationCenter { ...@@ -469,101 +505,100 @@ namespace SwayNotificationCenter {
} }
unowned Gtk.Widget widget = widgets.nth_data (i); unowned Gtk.Widget widget = widgets.nth_data (i);
int preferred_height; Gtk.Allocation widget_alloc = get_alloc (widget);
widget.get_preferred_height_for_width (width,
out preferred_height, null);
Gtk.Allocation widget_alloc;
widget.get_allocated_size (out widget_alloc, null);
int height_diff = latest_alloc.height - widget_alloc.height; int height_diff = latest_alloc.height - widget_alloc.height;
cr.save (); snapshot.save ();
// Translate to the widgets allocated y
double translate_y = widget_alloc.y - alloc.y;
// Move down even more if the height is larger than the latest // Move down even more if the height is larger than the latest
// in the stack (helps with only rendering the bottom portion) // in the stack (helps with only rendering the bottom portion)
translate_y += height_diff * animation_progress_inv; double translate_y = height_diff * animation_progress_inv;
cr.translate (0, translate_y); snapshot.translate (Graphene.Point ().init (0, (float) translate_y));
// Scale down lower notifications in the stack // Scale down lower notifications in the stack
if (i + 1 != length) { if (i + 1 != length) {
double scale = double.min ( double scale = double.min (
animation_progress + Math.pow (0.95, length - 1 - i), 1); animation_progress + Math.pow (0.95, length - 1 - i), 1);
// Moves the scaled notification to the center of X and bottom y // Moves the scaled notification to the center of X and bottom y
cr.translate ((widget_alloc.width - width * scale) * 0.5, snapshot.translate (Graphene.Point ().init (
widget_alloc.height * (1 - scale)); (float) ((widget_alloc.width - width * scale) * 0.5),
cr.scale (scale, scale); (float) (widget_alloc.height * (1 - scale))));
snapshot.scale ((float) scale, (float) scale);
} }
int lerped_y = (int) Animation.lerp (-height_diff, 0, animation_progress); int lerped_y = (int) Functions.lerp (-height_diff, 0, animation_progress);
int lerped_height = (int) Animation.lerp (latest_alloc.height, lerped_y += (int) widget_alloc.y - alloc.y;
int lerped_height = (int) Functions.lerp (latest_alloc.height,
widget_alloc.height, widget_alloc.height,
animation_progress); animation_progress);
// Clip to the size of the latest notification // Clip to the size of the latest notification
// (fixes issue where a larger bottom notification would // (fixes issue where a larger bottom notification would
// be visible above) // be visible above)
cr.rectangle (0, lerped_y, width, lerped_height); Graphene.Rect clip_bounds = Graphene.Rect ().init (0f,
cr.clip (); (float) lerped_y,
(float) width,
(float) lerped_height);
snapshot.push_clip (clip_bounds);
// TODO: Fades from the bottom at 0.5 -> top at 0.0 opacity
// Draw patterns on the notification // Draw patterns on the notification
cr.push_group (); // cr.push_group ();
widget.draw (cr); // widget.draw (cr);
if (i + 1 != length) { // if (i + 1 != length) {
// Draw Fade Gradient // // Draw Fade Gradient
cr.save (); // cr.save ();
cr.translate (0, lerped_y); // cr.translate (0, lerped_y);
cr.scale (1, lerped_height * 0.5); // cr.scale (1, lerped_height * 0.5);
cr.set_source (fade_gradient); // cr.set_source (fade_gradient);
cr.rectangle (0, 0, width, lerped_height * 0.5); // cr.rectangle (0, 0, width, lerped_height * 0.5);
cr.set_operator (Cairo.Operator.DEST_OUT); // cr.set_operator (Cairo.Operator.DEST_OUT);
cr.fill (); // cr.fill ();
cr.restore (); // cr.restore ();
// }
// cr.pop_group_to_source ();
// cr.paint ();
// Cross-fades in the non visible stacked notifications when expanded
if (i < length - NUM_STACKED_NOTIFICATIONS) {
snapshot.push_opacity (1.5 * animation_progress);
} }
// Draw notification cross-fade snapshot_child (widget, snapshot);
if (i < length - NUM_STACKED_NOTIFICATIONS) { if (i < length - NUM_STACKED_NOTIFICATIONS) {
cr.save (); snapshot.pop (); // Cross-fade
cr.translate (0, lerped_y);
cr.scale (1, lerped_height);
cr.set_source (cross_fade_pattern);
cr.rectangle (0, 0, width, lerped_height);
cr.set_operator (Cairo.Operator.DEST_OUT);
cr.fill ();
cr.restore ();
} }
cr.pop_group_to_source (); snapshot.pop (); // Clip
cr.paint ();
cr.restore (); snapshot.restore ();
} }
return true;
} }
/** Gets the collapsed height (first notification + stacked) */ /** Gets the collapsed height (first notification + stacked) */
private void get_height_for_latest_notifications (int width, private void get_height_for_latest_notifications (int for_size,
out int minimum_height, out int minimum,
out int natural_height) { out int natural) {
minimum_height = 0; minimum = 0;
natural_height = 0; natural = 0;
uint length = widgets.length (); int length = (int) widgets.length ();
if (length == 0) {
return;
}
if (length == 0) return; unowned GLib.List<weak Gtk.Widget> last = widgets.last ();
if (last != null) {
unowned Gtk.Widget last_widget = widgets.last ().data;
int offset = 0; last_widget.measure (Gtk.Orientation.VERTICAL, for_size,
for (uint i = 1; out minimum, out natural,
i < length && i < NUM_STACKED_NOTIFICATIONS; null, null);
i++) {
offset += COLLAPSED_NOTIFICATION_OFFSET;
} }
unowned Gtk.Widget last = widgets.last ().data; int offset = (length - 1).clamp (0, NUM_STACKED_NOTIFICATIONS - 1)
last.get_preferred_height_for_width (width, * COLLAPSED_NOTIFICATION_OFFSET;
out minimum_height,
out natural_height);
minimum_height += offset; natural += offset;
natural_height += offset;
} }
void animation_value_cb (double progress) { void animation_value_cb (double progress) {
...@@ -573,14 +608,14 @@ namespace SwayNotificationCenter { ...@@ -573,14 +608,14 @@ namespace SwayNotificationCenter {
} }
void animation_done_cb () { void animation_done_cb () {
animation.dispose ();
this.queue_allocate (); this.queue_allocate ();
} }
void animate (double to) { void animate (double to) {
animation.stop (); animation.set_value_from (animation_progress);
animation.start (animation_progress, to); animation.set_value_to (to);
animation.reset ();
animation.play ();
} }
} }
} }
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<template class="SwayNotificationCenterNotificationWindow" parent="GtkApplicationWindow">
<property name="can-focus">False</property>
<property name="role">NotificationWindow</property>
<property name="resizable">False</property>
<property name="type-hint">notification</property>
<property name="skip-taskbar-hint">True</property>
<property name="skip-pager-hint">True</property>
<property name="urgency-hint">True</property>
<property name="decorated">False</property>
<property name="deletable">False</property>
<property name="gravity">north-east</property>
<property name="show-menubar">False</property>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hscrollbar-policy">never</property>
<property name="window-placement">top-right</property>
<property name="propagate-natural-height">True</property>
<child>
<object class="GtkViewport" id="viewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="shadow-type">none</property>
<child>
<object class="GtkBox" id="box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<style>
<class name="floating-notifications"/>
</style>
</template>
</interface>
namespace SwayNotificationCenter { namespace SwayNotificationCenter {
[GtkTemplate (ui = "/org/erikreider/sway-notification-center/notificationWindow/notificationWindow.ui")] [GtkTemplate (ui = "/org/erikreider/swaync/ui/notification_window.ui")]
public class NotificationWindow : Gtk.ApplicationWindow { public class NotificationWindow : Gtk.ApplicationWindow {
private static NotificationWindow ? window = null; private static NotificationWindow ? window = null;
/** /**
...@@ -34,11 +34,10 @@ namespace SwayNotificationCenter { ...@@ -34,11 +34,10 @@ namespace SwayNotificationCenter {
[GtkChild] [GtkChild]
unowned Gtk.Viewport viewport; unowned Gtk.Viewport viewport;
[GtkChild] [GtkChild]
unowned Gtk.Box box; unowned IterBox box;
private bool list_reverse = false; private bool list_reverse = false;
private uint scroll_to_source_id = 0;
private double last_upper = 0;
Gee.HashSet<uint32> inline_reply_notifications = new Gee.HashSet<uint32> (); Gee.HashSet<uint32> inline_reply_notifications = new Gee.HashSet<uint32> ();
...@@ -63,30 +62,25 @@ namespace SwayNotificationCenter { ...@@ -63,30 +62,25 @@ namespace SwayNotificationCenter {
scrolled_window.set_max_content_height (MAX_HEIGHT); scrolled_window.set_max_content_height (MAX_HEIGHT);
scrolled_window.set_propagate_natural_height (true); scrolled_window.set_propagate_natural_height (true);
viewport.size_allocate.connect (size_alloc); set_resizable (false);
default_width = ConfigModel.instance.notification_window_width;
}
this.default_width = ConfigModel.instance.notification_window_width; protected override void snapshot (Gtk.Snapshot snapshot) {
// HACK: Fixes fully transparent windows not being mapped
Gdk.RGBA color = Gdk.RGBA () {
red = 0,
green = 0,
blue = 0,
alpha = 0,
};
snapshot.append_color (color, Graphene.Rect.zero ());
base.snapshot (snapshot);
} }
private void set_anchor () { private void set_anchor () {
if (swaync_daemon.use_layer_shell) { if (swaync_daemon.use_layer_shell) {
GtkLayerShell.Layer layer; GtkLayerShell.set_layer (this, ConfigModel.instance.layer.to_layer ());
switch (ConfigModel.instance.layer) {
case Layer.BACKGROUND:
layer = GtkLayerShell.Layer.BACKGROUND;
break;
case Layer.BOTTOM:
layer = GtkLayerShell.Layer.BOTTOM;
break;
case Layer.TOP:
layer = GtkLayerShell.Layer.TOP;
break;
default:
case Layer.OVERLAY:
layer = GtkLayerShell.Layer.OVERLAY;
break;
}
GtkLayerShell.set_layer (this, layer);
switch (ConfigModel.instance.positionX) { switch (ConfigModel.instance.positionX) {
case PositionX.LEFT: case PositionX.LEFT:
...@@ -133,21 +127,6 @@ namespace SwayNotificationCenter { ...@@ -133,21 +127,6 @@ namespace SwayNotificationCenter {
list_reverse = ConfigModel.instance.positionY == PositionY.BOTTOM; list_reverse = ConfigModel.instance.positionY == PositionY.BOTTOM;
} }
private void size_alloc () {
var adj = viewport.vadjustment;
double upper = adj.get_upper ();
if (last_upper < upper) {
scroll_to_start (list_reverse);
}
last_upper = upper;
}
private void scroll_to_start (bool reverse) {
var adj = viewport.vadjustment;
var val = (reverse ? adj.get_upper () : adj.get_lower ());
adj.set_value (val);
}
public void change_visibility (bool value) { public void change_visibility (bool value) {
if (!value) { if (!value) {
close_all_notifications (); close_all_notifications ();
...@@ -162,8 +141,8 @@ namespace SwayNotificationCenter { ...@@ -162,8 +141,8 @@ namespace SwayNotificationCenter {
public void close_all_notifications (remove_iter_func ? func = null) { public void close_all_notifications (remove_iter_func ? func = null) {
inline_reply_notifications.clear (); inline_reply_notifications.clear ();
if (!this.get_realized ()) return; if (!this.get_realized ()) return;
foreach (var w in box.get_children ()) { foreach (unowned Gtk.Widget child in box.get_children ()) {
Notification notification = (Notification) w; Notification notification = (Notification) child;
if (func == null || func (notification)) { if (func == null || func (notification)) {
remove_notification (notification, false); remove_notification (notification, false);
} }
...@@ -178,6 +157,7 @@ namespace SwayNotificationCenter { ...@@ -178,6 +157,7 @@ namespace SwayNotificationCenter {
if (noti.has_inline_reply) { if (noti.has_inline_reply) {
inline_reply_notifications.remove (noti.param.applied_id); inline_reply_notifications.remove (noti.param.applied_id);
if (inline_reply_notifications.size == 0 if (inline_reply_notifications.size == 0
&& swaync_daemon.use_layer_shell
&& GtkLayerShell.get_keyboard_mode (this) && GtkLayerShell.get_keyboard_mode (this)
!= GtkLayerShell.KeyboardMode.NONE) { != GtkLayerShell.KeyboardMode.NONE) {
GtkLayerShell.set_keyboard_mode ( GtkLayerShell.set_keyboard_mode (
...@@ -185,19 +165,34 @@ namespace SwayNotificationCenter { ...@@ -185,19 +165,34 @@ namespace SwayNotificationCenter {
} }
} }
noti.remove_noti_timeout (); noti.remove_noti_timeout ();
noti.destroy (); box.remove (noti);
} }
if (dismiss if (dismiss
&& (!get_realized () && (!get_realized ()
|| !get_mapped () || !get_mapped ()
|| !(get_child () is Gtk.Widget) || !(get_child () is Gtk.Widget)
|| box.get_children ().length () == 0)) { || box.length == 0)) {
close (); close ();
return; return;
} }
} }
/** Scroll to the latest notification */
private void scroll_to_latest_notification () {
if (scroll_to_source_id > 0) {
Source.remove (scroll_to_source_id);
}
scroll_to_source_id = Idle.add_once (() => {
scroll_to_source_id = 0;
if (list_reverse) {
viewport.scroll_to (box.get_last_child (), null);
} else {
viewport.scroll_to (box.get_first_child (), null);
}
});
}
public void add_notification (NotifyParams param) { public void add_notification (NotifyParams param) {
var noti = new Notification.timed (param, var noti = new Notification.timed (param,
swaync_daemon.noti_daemon, swaync_daemon.noti_daemon,
...@@ -208,32 +203,31 @@ namespace SwayNotificationCenter { ...@@ -208,32 +203,31 @@ namespace SwayNotificationCenter {
if (noti.has_inline_reply) { if (noti.has_inline_reply) {
inline_reply_notifications.add (param.applied_id); inline_reply_notifications.add (param.applied_id);
if (GtkLayerShell.get_keyboard_mode (this) if (swaync_daemon.use_layer_shell &&
!= GtkLayerShell.KeyboardMode.ON_DEMAND) { GtkLayerShell.get_keyboard_mode (this)
!= GtkLayerShell.KeyboardMode.ON_DEMAND
&& swaync_daemon.has_layer_on_demand) {
GtkLayerShell.set_keyboard_mode ( GtkLayerShell.set_keyboard_mode (
this, GtkLayerShell.KeyboardMode.ON_DEMAND); this, GtkLayerShell.KeyboardMode.ON_DEMAND);
} }
} }
if (list_reverse) { if (list_reverse) {
box.pack_start (noti); box.append (noti);
} else { } else {
box.pack_end (noti); box.prepend (noti);
} }
this.grab_focus ();
if (!this.get_mapped () || !this.get_realized ()) { if (!this.get_mapped () || !this.get_realized ()) {
this.set_anchor (); this.set_anchor ();
this.show (); this.show ();
} }
// IMPORTANT: queue a resize event to force the layout to be recomputed scroll_to_latest_notification ();
noti.queue_resize ();
scroll_to_start (list_reverse);
} }
public void close_notification (uint32 id, bool dismiss) { public void close_notification (uint32 id, bool dismiss) {
foreach (var w in box.get_children ()) { foreach (unowned Gtk.Widget child in box.get_children ()) {
var noti = (Notification) w; var noti = (Notification) child;
if (noti != null && noti.param.applied_id == id) { if (noti != null && noti.param.applied_id == id) {
remove_notification (noti, dismiss); remove_notification (noti, dismiss);
break; break;
...@@ -242,12 +236,17 @@ namespace SwayNotificationCenter { ...@@ -242,12 +236,17 @@ namespace SwayNotificationCenter {
} }
public void replace_notification (uint32 id, NotifyParams new_params) { public void replace_notification (uint32 id, NotifyParams new_params) {
foreach (var w in box.get_children ()) { foreach (unowned Gtk.Widget child in box.get_children ()) {
var noti = (Notification) w; var noti = (Notification) child;
if (noti != null && noti.param.applied_id == id) { if (noti != null && noti.param.applied_id == id) {
noti.replace_notification (new_params); noti.replace_notification (new_params);
// Position the notification in the beginning of the list // Position the notification in the beginning/end of the list
box.reorder_child (noti, (int) box.get_children ().length ()); if (list_reverse) {
box.reorder_child_after (noti, box.get_last_child ());
} else {
box.reorder_child_after (noti, null);
}
scroll_to_latest_notification ();
return; return;
} }
} }
...@@ -257,14 +256,15 @@ namespace SwayNotificationCenter { ...@@ -257,14 +256,15 @@ namespace SwayNotificationCenter {
} }
public uint32 ? get_latest_notification () { public uint32 ? get_latest_notification () {
List<weak Gtk.Widget> children = box.get_children (); if (box.length == 0) {
if (children.is_empty ()) return null; return null;
}
Gtk.Widget ? child = null; unowned Gtk.Widget ? child = null;
if (list_reverse) { if (list_reverse) {
child = children.last ().data; child = box.get_last_child ();
} else { } else {
child = children.first ().data; child = box.get_first_child ();
} }
if (child == null || !(child is Notification)) return null; if (child == null || !(child is Notification)) return null;
...@@ -273,14 +273,15 @@ namespace SwayNotificationCenter { ...@@ -273,14 +273,15 @@ namespace SwayNotificationCenter {
} }
public void latest_notification_action (uint32 action) { public void latest_notification_action (uint32 action) {
List<weak Gtk.Widget> children = box.get_children (); if (box.length == 0) {
if (children.is_empty ()) return; return;
}
Gtk.Widget ? child = null; Gtk.Widget ? child = null;
if (list_reverse) { if (list_reverse) {
child = children.last ().data; child = box.get_last_child ();
} else { } else {
child = children.first ().data; child = box.get_first_child ();
} }
if (child == null || !(child is Notification)) return; if (child == null || !(child is Notification)) return;
......
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/erikreider/sway-notification-center">
<file>notificationWindow/notificationWindow.ui</file>
<file>notification/notification.ui</file>
<file>controlCenter/controlCenter.ui</file>
<file>controlCenter/widgets/mpris/mpris_player.ui</file>
</gresource>
</gresources>
...@@ -18,17 +18,18 @@ namespace SwayNotificationCenter { ...@@ -18,17 +18,18 @@ namespace SwayNotificationCenter {
private Array<BlankWindow> blank_windows = new Array<BlankWindow> (); private Array<BlankWindow> blank_windows = new Array<BlankWindow> ();
private unowned Gdk.Display ? display = Gdk.Display.get_default (); private unowned Gdk.Display ? display = Gdk.Display.get_default ();
private unowned GLib.ListModel ? monitors = null;
[DBus (visible = false)]
public signal void reloading_css ();
// Only set on swaync start due to some limitations of GtkLayerShell // Only set on swaync start due to some limitations of GtkLayerShell
[DBus (visible = false)] [DBus (visible = false)]
public bool use_layer_shell { get; private set; } public bool use_layer_shell { get; private set; }
[DBus (visible = false)]
public bool has_layer_on_demand { get; private set; }
public SwayncDaemon () { public SwayncDaemon () {
// Init noti_daemon // Init noti_daemon
this.use_layer_shell = ConfigModel.instance.layer_shell; this.use_layer_shell = ConfigModel.instance.layer_shell;
this.has_layer_on_demand = use_layer_shell && GtkLayerShell.get_protocol_version () >= 4;
this.noti_daemon = new NotiDaemon (this); this.noti_daemon = new NotiDaemon (this);
this.xdg_activation = new XdgActivationHelper (); this.xdg_activation = new XdgActivationHelper ();
Bus.own_name (BusType.SESSION, "org.freedesktop.Notifications", Bus.own_name (BusType.SESSION, "org.freedesktop.Notifications",
...@@ -67,27 +68,13 @@ namespace SwayNotificationCenter { ...@@ -67,27 +68,13 @@ namespace SwayNotificationCenter {
/// Blank windows /// Blank windows
if (display == null) return; if (display == null) return;
init_blank_windows (false); monitors = display.get_monitors ();
monitors.items_changed.connect (() => {
display.closed.connect ((is_error) => { close_blank_windows ();
clear_blank_windows ();
if (is_error) stderr.printf ("Display closed due to error!\n");
});
display.opened.connect ((d) => {
bool visibility = noti_daemon.control_center.get_visibility ();
init_blank_windows (visibility);
});
display.monitor_added.connect ((d, m) => {
bool visibility = noti_daemon.control_center.get_visibility ();
add_blank_window (d, m, visibility);
});
display.monitor_removed.connect ((monitor) => {
bool visibility = noti_daemon.control_center.get_visibility (); bool visibility = noti_daemon.control_center.get_visibility ();
init_blank_windows (visibility); init_blank_windows (visibility);
}); });
init_blank_windows (false);
} }
private void on_noti_bus_aquired (DBusConnection conn) { private void on_noti_bus_aquired (DBusConnection conn) {
...@@ -100,25 +87,20 @@ namespace SwayNotificationCenter { ...@@ -100,25 +87,20 @@ namespace SwayNotificationCenter {
} }
} }
private void add_blank_window (Gdk.Display display,
Gdk.Monitor monitor,
bool visible) {
var win = new BlankWindow (display, monitor, this);
win.set_visible (visible);
blank_windows.append_val (win);
}
private void init_blank_windows (bool visible) { private void init_blank_windows (bool visible) {
clear_blank_windows (); close_blank_windows ();
// Add a window to all monitors // Add a window to all monitors
for (int i = 0; i < display.get_n_monitors (); i++) { for (int i = 0; i < monitors.get_n_items (); i++) {
unowned Gdk.Monitor ? monitor = display.get_monitor (i); Object ? obj = monitors.get_item (i);
if (monitor == null) continue; if (obj == null || !(obj is Gdk.Monitor)) continue;
add_blank_window (display, monitor, visible); BlankWindow win = new BlankWindow ((Gdk.Monitor) obj);
win.set_visible (visible);
blank_windows.append_val (win);
} }
} }
private void clear_blank_windows () { private void close_blank_windows () {
while (blank_windows.length > 0) { while (blank_windows.length > 0) {
uint i = blank_windows.length - 1; uint i = blank_windows.length - 1;
unowned BlankWindow ? win = blank_windows.index (i); unowned BlankWindow ? win = blank_windows.index (i);
...@@ -128,12 +110,14 @@ namespace SwayNotificationCenter { ...@@ -128,12 +110,14 @@ namespace SwayNotificationCenter {
} }
[DBus (visible = false)] [DBus (visible = false)]
public void show_blank_windows (Gdk.Monitor ? monitor) { public void show_blank_windows (Gdk.Monitor ? ref_monitor) {
if (!use_layer_shell || !ConfigModel.instance.layer_shell_cover_screen) { if (!use_layer_shell || !ConfigModel.instance.layer_shell_cover_screen) {
return; return;
} }
foreach (unowned BlankWindow win in blank_windows.data) { foreach (unowned BlankWindow win in blank_windows.data) {
if (win.monitor != monitor) win.show (); if (win.monitor != ref_monitor) {
win.show ();
}
} }
} }
...@@ -174,7 +158,6 @@ namespace SwayNotificationCenter { ...@@ -174,7 +158,6 @@ namespace SwayNotificationCenter {
/** Reloads the CSS file */ /** Reloads the CSS file */
public bool reload_css () throws Error { public bool reload_css () throws Error {
bool result = Functions.load_css (style_path); bool result = Functions.load_css (style_path);
if (result) reloading_css ();
return result; return result;
} }
......
public class Underlay : Gtk.Widget {
protected unowned Gtk.Widget _child = null;
public unowned Gtk.Widget child {
get {
return _child;
}
set {
_child = value;
set_children ();
}
}
protected unowned Gtk.Widget _underlay_child = null;
public unowned Gtk.Widget underlay_child {
get {
return _underlay_child;
}
set {
_underlay_child = value;
set_children ();
}
}
private void set_children () {
if (_underlay_child != null) {
_underlay_child.unparent ();
_underlay_child.insert_after (this, null);
}
if (_child != null) {
_child.unparent ();
_child.insert_after (this, _underlay_child);
}
}
/*
* Overrides
*/
protected override Gtk.SizeRequestMode get_request_mode () {
return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
}
protected override void measure (Gtk.Orientation orientation, int for_size,
out int minimum, out int natural,
out int minimum_baseline, out int natural_baseline) {
minimum = 0;
natural = 0;
minimum_baseline = -1;
natural_baseline = -1;
int child_min, child_nat;
if (!child.visible) {
return;
}
child.measure (orientation, for_size,
out child_min, out child_nat, null, null);
minimum = int.max (minimum, child_min);
natural = int.max (natural, child_nat);
}
protected override void size_allocate (int width, int height, int baseline) {
if (child != null && child.should_layout ()) {
child.allocate (width, height, baseline, null);
}
if (underlay_child != null && underlay_child.should_layout ()) {
underlay_child.allocate (width, height, baseline, null);
}
}
}
...@@ -8,7 +8,7 @@ public class XdgActivationHelper : Object { ...@@ -8,7 +8,7 @@ public class XdgActivationHelper : Object {
private Activation * xdg_activation = null; private Activation * xdg_activation = null;
public XdgActivationHelper () { public XdgActivationHelper () {
unowned Wl.Display wl_display = get_wl_display (); unowned Wl.Display wl_display = Functions.get_wl_display ();
var wl_registry = wl_display.get_registry (); var wl_registry = wl_display.get_registry ();
wl_registry.add_listener (registry_listener, this); wl_registry.add_listener (registry_listener, this);
...@@ -48,13 +48,13 @@ public class XdgActivationHelper : Object { ...@@ -48,13 +48,13 @@ public class XdgActivationHelper : Object {
return null; return null;
} }
unowned Wl.Display wl_display = get_wl_display (); unowned Wl.Display wl_display = Functions.get_wl_display ();
unowned Gdk.Window ? window = widget.get_window (); unowned Gtk.Root ? root = widget.get_root ();
if (window == null) { if (root == null) {
warning ("GDK Window is null"); warning ("GDK Window is null");
return null; return null;
} }
unowned Wl.Surface wl_surface = get_wl_surface (window); unowned Wl.Surface wl_surface = Functions.get_wl_surface (root.get_surface ());
Value token_value = Value (typeof (string)); Value token_value = Value (typeof (string));
token_value.set_string (null); token_value.set_string (null);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment