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

Animate floating notifications + configurable max height (#561)

* Added initial AnimatedList and Item Widgets * Use the new list in the NotificationWindow class + massive list improvements This includes a more simplified way of calculating the item positions relative to the fade threshold * Added center animations * Fixed replacing notification not scrolling to the new position (top/bottom) * Simplified size_allocate y offset * Fixed very large notifications fading before reaching top with small list size * Disable card animation if GTK animations are disabled * Removed unneeded TODO about allocation order * Fixed input region including the fade padding
parent f55b433e
...@@ -11,12 +11,11 @@ template $SwayNotificationCenterNotificationWindow: Gtk.ApplicationWindow { ...@@ -11,12 +11,11 @@ template $SwayNotificationCenterNotificationWindow: Gtk.ApplicationWindow {
vscrollbar-policy: automatic; vscrollbar-policy: automatic;
has-frame: false; has-frame: false;
Gtk.Viewport viewport { $AnimatedList list {
vexpand: true; scroll_to_append: true;
transition_children: true;
$IterBox box { vexpand: true;
orientation: vertical;
}
} }
} }
} }
public class AnimatedListItem : Gtk.Widget {
public const int DEFAULT_ANIMATION_DURATION = 350;
public enum RevealAnimationType {
NONE, SLIDE, SLIDE_WITH
}
public enum ChildAnimationType {
NONE, SLIDE_FROM_LEFT, SLIDE_FROM_RIGHT
}
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 int animation_duration { get; construct set; }
public Adw.Easing animation_easing { get; construct set; }
public RevealAnimationType animation_reveal_type { get; construct set; }
public ChildAnimationType animation_child_type { get; construct set; }
public bool animation_child_fade { get; construct set; }
public bool destroying { get; private set; default = false; }
private Adw.CallbackAnimationTarget target;
private Adw.TimedAnimation animation;
private double animation_value = 0.0;
private ulong animation_done_cb_id = 0;
private unowned SourceFunc ? removed_cb = null;
private unowned SourceFunc ? added_cb = null;
public AnimatedListItem () {
Object (
css_name: "animatedlistitem",
accessible_role: Gtk.AccessibleRole.LIST_ITEM,
overflow: Gtk.Overflow.HIDDEN,
animation_duration: DEFAULT_ANIMATION_DURATION,
animation_easing: Adw.Easing.EASE_OUT_QUINT,
animation_reveal_type: RevealAnimationType.SLIDE,
animation_child_type: ChildAnimationType.SLIDE_FROM_RIGHT,
animation_child_fade: true
);
target = new Adw.CallbackAnimationTarget (animation_value_cb);
animation = new Adw.TimedAnimation (this, 0.0, 1.0,
animation_duration, target);
bind_property ("animation-easing",
animation, "easing",
BindingFlags.SYNC_CREATE, null, null);
}
public override Gtk.SizeRequestMode get_request_mode () {
return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
}
public override void size_allocate (int width,
int height,
int baseline) {
if (child == null || !child.should_layout ()) {
return;
}
if (animation_value >= 1) {
child.allocate (width, height, baseline, null);
} else if (animation_value < 0) {
return;
}
int child_width = width;
int child_height = height;
if (animation_value < 1.0) {
int min, nat;
child.measure (Gtk.Orientation.VERTICAL, width,
out min, out nat, null, null);
if (Math.ceil (nat * animation_value) == height) {
child_height = nat;
} else if (Math.ceil (min * animation_value) == height) {
child_height = min;
} else {
double d = Math.floor (height / animation_value);
child_height = int.min ((int) d, int.MAX);
}
}
Gsk.Transform transform = new Gsk.Transform ();
switch (animation_reveal_type) {
case RevealAnimationType.SLIDE_WITH:
transform = transform.translate_3d (
Graphene.Point3D ().init (0, height - child_height, 0)
);
break;
case RevealAnimationType.SLIDE:
case RevealAnimationType.NONE:
break;
}
switch (animation_child_type) {
case ChildAnimationType.SLIDE_FROM_RIGHT:
transform = transform.translate_3d (
Graphene.Point3D ()
.init (child_width * (float) (1 - animation_value), 0, 0)
);
break;
case ChildAnimationType.SLIDE_FROM_LEFT:
transform = transform.translate_3d (
Graphene.Point3D ()
.init (-child_width * (float) (1 - animation_value), 0, 0)
);
break;
case ChildAnimationType.NONE:
break;
}
child.allocate (child_width, child_height, -1, transform);
}
public 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;
if (child == null || !child.should_layout ()) {
return;
}
child.measure (orientation, for_size,
out minimum, out natural, null, null);
switch (orientation) {
case Gtk.Orientation.HORIZONTAL:
break;
case Gtk.Orientation.VERTICAL:;
minimum = (int) Math.ceil (minimum * animation_value);
natural = (int) Math.ceil (natural * animation_value);
break;
}
}
public override void snapshot (Gtk.Snapshot snapshot) {
if (!child.should_layout ()) {
return;
}
if (animation_child_fade) {
snapshot.push_opacity (animation_value);
}
snapshot_child (child, snapshot);
if (animation_child_fade) {
snapshot.pop ();
}
}
private void animation_value_cb (double value) {
this.animation_value = value;
queue_resize ();
}
private void animation_done_add () {
if (added_cb != null) {
added_cb ();
added_cb = null;
}
}
private void animation_done_remove () {
unparent ();
if (removed_cb != null) {
removed_cb ();
removed_cb = null;
}
}
private void animation_remove_done_cb () {
if (animation_done_cb_id != 0) {
animation.disconnect (animation_done_cb_id);
animation_done_cb_id = 0;
}
}
delegate void animation_done (Adw.Animation animation);
private void animation_add_done_cb (animation_done handler) {
animation_remove_done_cb ();
animation_done_cb_id = animation.done.connect ((a) => handler (a));
}
public async void added (bool transition) {
if (added_cb != null) {
// Already running animation
return;
}
animation_remove_done_cb ();
if (get_mapped () && transition) {
animation_add_done_cb (animation_done_add);
added_cb = added.callback;
animation.value_from
= animation.state == Adw.AnimationState.PLAYING
? animation_value : 0.0;
animation.value_from = animation.value;
animation.value_to = 1.0;
animation.play ();
yield;
} else {
animation_value = 1.0;
animation_done_add ();
}
}
public async bool removed (bool transition) {
if (removed_cb != null) {
// Already running animation
return false;
}
animation_remove_done_cb ();
set_can_focus (false);
set_can_target (false);
if (get_mapped () && transition) {
animation_add_done_cb (animation_done_remove);
removed_cb = removed.callback;
animation.value_from
= animation.state == Adw.AnimationState.PLAYING
? animation_value : 1.0;
animation.value_to = 0.0;
destroying = true;
animation.play ();
yield;
} else {
animation_value = 0.0;
animation_done_remove ();
}
return true;
}
}
...@@ -432,6 +432,8 @@ namespace SwayNotificationCenter { ...@@ -432,6 +432,8 @@ namespace SwayNotificationCenter {
* Notification window's width, in pixels. * Notification window's width, in pixels.
*/ */
public int notification_window_width { get; set; default = 500; } public int notification_window_width { get; set; default = 500; }
/** Max height of the notification in pixels */
public int notification_window_height { get; set; default = -1; }
/** Hides the control center after clearing all notifications */ /** Hides the control center after clearing all notifications */
public bool hide_on_clear { get; set; default = false; } public bool hide_on_clear { get; set; default = false; }
......
...@@ -133,6 +133,11 @@ ...@@ -133,6 +133,11 @@
"description": "Width of the notification in pixels", "description": "Width of the notification in pixels",
"default": 500 "default": 500
}, },
"notification-window-height": {
"type": "integer",
"description": "Max height of the notification in pixels. -1 to use the full amount of space given by the compositor.",
"default": -1
},
"fit-to-screen": { "fit-to-screen": {
"type": "boolean", "type": "boolean",
"description": "If the control center should expand to both edges of the screen", "description": "If the control center should expand to both edges of the screen",
......
...@@ -82,45 +82,27 @@ public class DismissibleWidget : Gtk.Widget, Adw.Swipeable { ...@@ -82,45 +82,27 @@ public class DismissibleWidget : Gtk.Widget, Adw.Swipeable {
minimum_baseline = -1; minimum_baseline = -1;
natural_baseline = -1; natural_baseline = -1;
int child_min, child_nat; if (child == null || !child.should_layout ()) {
if (!child.visible) {
return; return;
} }
child.measure (orientation, for_size, child.measure (orientation, for_size,
out child_min, out child_nat, null, null); out minimum, out natural, 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) { protected override void size_allocate (int width, int height, int baseline) {
if (!child.visible) { if (child == null || !child.should_layout ()) {
return; return;
} }
int child_width, child_height; int child_width = width;
int min = 0, nat = 0; int child_height = height;
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; double x = 0;
if (get_direction () == Gtk.TextDirection.RTL) { if (get_direction () == Gtk.TextDirection.RTL) {
x -= ((width * swipe_progress) - (width - child_width) / 2.0) x -= (width * swipe_progress);
+ (width * child_offset * 2);
} else { } else {
x -= - ((width * swipe_progress) - (width - child_width) / 2.0) x += (width * swipe_progress);
- (width * child_offset * 2);
} }
Gsk.Transform transform = new Gsk.Transform () Gsk.Transform transform = new Gsk.Transform ()
...@@ -130,15 +112,9 @@ public class DismissibleWidget : Gtk.Widget, Adw.Swipeable { ...@@ -130,15 +112,9 @@ public class DismissibleWidget : Gtk.Widget, Adw.Swipeable {
} }
protected override void snapshot (Gtk.Snapshot snapshot) { protected override void snapshot (Gtk.Snapshot snapshot) {
snapshot.save ();
snapshot.push_opacity (1 - swipe_progress.abs ()); snapshot.push_opacity (1 - swipe_progress.abs ());
snapshot_child (child, snapshot); snapshot_child (child, snapshot);
snapshot.pop (); snapshot.pop ();
snapshot.restore ();
} }
/* /*
...@@ -178,6 +154,9 @@ public class DismissibleWidget : Gtk.Widget, Adw.Swipeable { ...@@ -178,6 +154,9 @@ public class DismissibleWidget : Gtk.Widget, Adw.Swipeable {
animation.set_value_to (to); animation.set_value_to (to);
animation.set_initial_velocity (velocity); animation.set_initial_velocity (velocity);
// Disable user input if dismissed
set_can_target (to == 0);
animation.play (); animation.play ();
gesture_active = false; gesture_active = false;
......
...@@ -50,6 +50,8 @@ widget_sources = [ ...@@ -50,6 +50,8 @@ widget_sources = [
app_sources = [ app_sources = [
'main.vala', 'main.vala',
'animatedList/animatedList.vala',
'animatedList/animatedListItem.vala',
'orderedHashTable/orderedHashTable.vala', 'orderedHashTable/orderedHashTable.vala',
'iterHelpers/iterBox.vala', 'iterHelpers/iterBox.vala',
'iterHelpers/iterListBoxController.vala', 'iterHelpers/iterListBoxController.vala',
......
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