mirror of
https://github.com/elementary/gala.git
synced 2024-08-16 18:40:27 +03:00
Multitasking view 1:1 switch desktop animation with scroll events (#1051)
This commit is contained in:
parent
625d40cb1a
commit
3a0c856d7a
@ -31,5 +31,7 @@ namespace Gala {
|
||||
// Duration of the workspace switch animation
|
||||
WORKSPACE_SWITCH_MIN = 300,
|
||||
WORKSPACE_SWITCH = 400,
|
||||
// Duration of the nudge animation when trying to switch to at the end of the workspace list
|
||||
NUDGE = 360,
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ public class Gala.GestureAnimationDirector : Object {
|
||||
velocity = 0;
|
||||
}
|
||||
|
||||
public GestureAnimationDirector(int min_animation_duration, int max_animation_duration) {
|
||||
public GestureAnimationDirector (int min_animation_duration, int max_animation_duration) {
|
||||
Object (min_animation_duration: min_animation_duration, max_animation_duration: max_animation_duration);
|
||||
}
|
||||
|
||||
|
40
src/Gestures/Gesture.vala
Normal file
40
src/Gestures/Gesture.vala
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2021 elementary, Inc (https://elementary.io)
|
||||
* 2021 José Expósito <jose.exposito89@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Gala {
|
||||
public enum GestureDirection {
|
||||
UNKNOWN = 0,
|
||||
|
||||
// GestureType.SWIPE and GestureType.SCROLL
|
||||
UP = 1,
|
||||
DOWN = 2,
|
||||
LEFT = 3,
|
||||
RIGHT = 4,
|
||||
|
||||
// GestureType.PINCH
|
||||
IN = 5,
|
||||
OUT = 6,
|
||||
}
|
||||
|
||||
public class Gesture {
|
||||
public Gdk.EventType type;
|
||||
public GestureDirection direction;
|
||||
public int fingers;
|
||||
public Gdk.InputSource performed_on_device_type;
|
||||
}
|
||||
}
|
50
src/Gestures/GestureSettings.vala
Normal file
50
src/Gestures/GestureSettings.vala
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2021 elementary, Inc (https://elementary.io)
|
||||
* 2021 José Expósito <jose.exposito89@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Utility class to access the gesture settings. Easily accessible through GestureTracker.settings.
|
||||
*/
|
||||
public class Gala.GestureSettings : Object {
|
||||
private static GLib.Settings gala_settings;
|
||||
private static GLib.Settings touchpad_settings;
|
||||
|
||||
public const string MULTITASKING_ENABLED = "multitasking-gesture-enabled";
|
||||
public const string MULTITASKING_FINGERS = "multitasking-gesture-fingers";
|
||||
|
||||
public const string WORKSPACE_ENABLED = "workspaces-gesture-enabled";
|
||||
public const string WORKSPACE_FINGERS = "workspaces-gesture-fingers";
|
||||
|
||||
static construct {
|
||||
gala_settings = new GLib.Settings ("io.elementary.desktop.wm.gestures");
|
||||
touchpad_settings = new GLib.Settings ("org.gnome.desktop.peripherals.touchpad");
|
||||
}
|
||||
|
||||
public bool is_natural_scroll_enabled (Gdk.InputSource device_type) {
|
||||
return (device_type == Gdk.InputSource.TOUCHSCREEN)
|
||||
? true
|
||||
: touchpad_settings.get_boolean ("natural-scroll");
|
||||
}
|
||||
|
||||
public bool is_gesture_enabled (string setting_id) {
|
||||
return gala_settings.get_boolean (setting_id);
|
||||
}
|
||||
|
||||
public int gesture_fingers (string setting_id) {
|
||||
return gala_settings.get_int (setting_id);
|
||||
}
|
||||
}
|
268
src/Gestures/GestureTracker.vala
Normal file
268
src/Gestures/GestureTracker.vala
Normal file
@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright 2021 elementary, Inc (https://elementary.io)
|
||||
* 2021 José Expósito <jose.exposito89@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Allow to use multi-touch gestures from different sources (backends).
|
||||
* Usage:
|
||||
* - Create a new instance of the class
|
||||
* - Use the enable_* methods to enable different backends
|
||||
* - Connect the on_gesture_detected to your code
|
||||
* - When on_gesture_detected is emitted, if you want to handle the gesture, call connect_handlers
|
||||
* to start receiving events
|
||||
* - on_begin will be emitted once right after on_gesture_detected
|
||||
* - on_update will be emitted 0 or more times
|
||||
* - on_end will be emitted once when the gesture end
|
||||
* - When on_end is emitted, the handler connected with connect_handlers will be automatically
|
||||
* disconnected and you will only receive on_gesture_detected signals
|
||||
* - The enabled flag is usually disabled on_end and re-enabled once the end animation finish. In
|
||||
* this way, new gestures are not received while animating
|
||||
*/
|
||||
public class Gala.GestureTracker : Object {
|
||||
/**
|
||||
* Percentage of the animation to be completed to apply the action.
|
||||
*/
|
||||
private const double SUCCESS_PERCENTAGE_THRESHOLD = 0.2;
|
||||
|
||||
/**
|
||||
* When a gesture ends with a velocity greater than this constant, the action is not cancelled,
|
||||
* even if the animation threshold has not been reached.
|
||||
*/
|
||||
private const double SUCCESS_VELOCITY_THRESHOLD = 0.003;
|
||||
|
||||
/**
|
||||
* When a gesture ends with less velocity that this constant, this velocity is used instead.
|
||||
*/
|
||||
private const double ANIMATION_BASE_VELOCITY = 0.002;
|
||||
|
||||
/**
|
||||
* Maximum velocity allowed on gesture update.
|
||||
*/
|
||||
private const double MAX_VELOCITY = 0.01;
|
||||
|
||||
/**
|
||||
* Multiplier used to match libhandy's animation duration.
|
||||
*/
|
||||
private const int DURATION_MULTIPLIER = 3;
|
||||
|
||||
public GestureSettings settings { get; construct; }
|
||||
public int min_animation_duration { get; construct; }
|
||||
public int max_animation_duration { get; construct; }
|
||||
|
||||
/**
|
||||
* Property to control when event signals are emitted or not.
|
||||
*/
|
||||
public bool enabled { get; set; default = true; }
|
||||
|
||||
/**
|
||||
* Emitted when a new gesture is detected.
|
||||
* If the receiving code needs to handle this gesture, it should call to connect_handlers to
|
||||
* start receiving updates.
|
||||
* @param gesture Information about the gesture.
|
||||
*/
|
||||
public signal void on_gesture_detected (Gesture gesture);
|
||||
|
||||
/**
|
||||
* Emitted right after on_gesture_detected with the initial gesture information.
|
||||
* @param percentage Value between 0 and 1.
|
||||
*/
|
||||
public signal void on_begin (double percentage);
|
||||
|
||||
/**
|
||||
* Called every time the percentage changes.
|
||||
* @param percentage Value between 0 and 1.
|
||||
*/
|
||||
public signal void on_update (double percentage);
|
||||
|
||||
/**
|
||||
* @param percentage Value between 0 and 1.
|
||||
* @param cancel_action
|
||||
* @param calculated_duration
|
||||
*/
|
||||
public signal void on_end (double percentage, bool cancel_action, int calculated_duration);
|
||||
|
||||
public delegate void OnBegin (double percentage);
|
||||
public delegate void OnUpdate (double percentage);
|
||||
public delegate void OnEnd (double percentage, bool cancel_action, int calculated_duration);
|
||||
|
||||
/**
|
||||
* Scroll backend used if enable_scroll is called.
|
||||
*/
|
||||
private ScrollBackend scroll_backend;
|
||||
|
||||
private Gee.ArrayList<ulong> handlers;
|
||||
|
||||
private double previous_percentage;
|
||||
private uint64 previous_time;
|
||||
private double percentage_delta;
|
||||
private double velocity;
|
||||
|
||||
construct {
|
||||
settings = new GestureSettings ();
|
||||
|
||||
handlers = new Gee.ArrayList<ulong> ();
|
||||
previous_percentage = 0;
|
||||
previous_time = 0;
|
||||
percentage_delta = 0;
|
||||
velocity = 0;
|
||||
}
|
||||
|
||||
public GestureTracker (int min_animation_duration, int max_animation_duration) {
|
||||
Object (min_animation_duration: min_animation_duration, max_animation_duration: max_animation_duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to receive scroll gestures.
|
||||
* @param actor Clutter actor that will receive the scroll events.
|
||||
* @param orientation If we are interested in the horizontal or vertical axis.
|
||||
*/
|
||||
public void enable_scroll (Clutter.Actor actor, Clutter.Orientation orientation) {
|
||||
scroll_backend = new ScrollBackend (actor, orientation);
|
||||
scroll_backend.on_gesture_detected.connect (gesture_detected);
|
||||
scroll_backend.on_begin.connect (gesture_begin);
|
||||
scroll_backend.on_update.connect (gesture_update);
|
||||
scroll_backend.on_end.connect (gesture_end);
|
||||
}
|
||||
|
||||
public void connect_handlers (owned OnBegin? on_begin_handler, owned OnUpdate? on_update_handler, owned OnEnd? on_end_handler) {
|
||||
if (on_begin_handler != null) {
|
||||
ulong handler_id = on_begin.connect ((percentage) => on_begin_handler (percentage));
|
||||
handlers.add (handler_id);
|
||||
}
|
||||
|
||||
if (on_update_handler != null) {
|
||||
ulong handler_id = on_update.connect ((percentage) => on_update_handler (percentage));
|
||||
handlers.add (handler_id);
|
||||
}
|
||||
|
||||
if (on_end_handler != null) {
|
||||
ulong handler_id = on_end.connect ((percentage, cancel_action, duration) => on_end_handler (percentage, cancel_action, duration));
|
||||
handlers.add (handler_id);
|
||||
}
|
||||
}
|
||||
|
||||
private void disconnect_all_handlers () {
|
||||
foreach (var handler in handlers) {
|
||||
disconnect (handler);
|
||||
}
|
||||
|
||||
handlers.clear ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to calculate the current animation value based on the percentage of the
|
||||
* gesture performed.
|
||||
* Animations are always linear, as they are 1:1 to the user's movement.
|
||||
* @param initial_value Animation start value.
|
||||
* @param target_value Animation end value.
|
||||
* @param percentage Current animation percentage.
|
||||
* @param rounded If the returned value should be rounded to match physical pixels.
|
||||
* Default to false because some animations, like for example scaling an actor, use intermediate
|
||||
* values not divisible by physical pixels.
|
||||
* @returns The linear animation value at the specified percentage.
|
||||
*/
|
||||
public static float animation_value (float initial_value, float target_value, double percentage, bool rounded = false) {
|
||||
float value = ((target_value - initial_value) * (float) percentage) + initial_value;
|
||||
|
||||
if (rounded) {
|
||||
var scale_factor = InternalUtils.get_ui_scaling_factor ();
|
||||
value = (float) Math.round (value * scale_factor) / scale_factor;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private void gesture_detected (Gesture gesture) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
on_gesture_detected (gesture);
|
||||
}
|
||||
|
||||
private void gesture_begin (double percentage, uint64 elapsed_time) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
on_begin (percentage);
|
||||
|
||||
previous_percentage = percentage;
|
||||
previous_time = elapsed_time;
|
||||
}
|
||||
|
||||
private void gesture_update (double percentage, uint64 elapsed_time) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (elapsed_time != previous_time) {
|
||||
double distance = percentage - previous_percentage;
|
||||
double time = (double)(elapsed_time - previous_time);
|
||||
velocity = (distance / time);
|
||||
|
||||
if (velocity > MAX_VELOCITY) {
|
||||
velocity = MAX_VELOCITY;
|
||||
var used_percentage = MAX_VELOCITY * time + previous_percentage;
|
||||
percentage_delta += percentage - used_percentage;
|
||||
}
|
||||
}
|
||||
|
||||
on_update (applied_percentage (percentage, percentage_delta));
|
||||
|
||||
previous_percentage = percentage;
|
||||
previous_time = elapsed_time;
|
||||
}
|
||||
|
||||
private void gesture_end (double percentage, uint64 elapsed_time) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
double end_percentage = applied_percentage (percentage, percentage_delta);
|
||||
bool cancel_action = (end_percentage < SUCCESS_PERCENTAGE_THRESHOLD)
|
||||
&& ((end_percentage <= previous_percentage) && (velocity < SUCCESS_VELOCITY_THRESHOLD));
|
||||
int calculated_duration = calculate_end_animation_duration (end_percentage, cancel_action);
|
||||
|
||||
on_end (end_percentage, cancel_action, calculated_duration);
|
||||
disconnect_all_handlers ();
|
||||
|
||||
previous_percentage = 0;
|
||||
previous_time = 0;
|
||||
percentage_delta = 0;
|
||||
velocity = 0;
|
||||
}
|
||||
|
||||
private static double applied_percentage (double percentage, double percentage_delta) {
|
||||
return (percentage - percentage_delta).clamp (0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the end animation duration using the current gesture velocity.
|
||||
*/
|
||||
private int calculate_end_animation_duration (double end_percentage, bool cancel_action) {
|
||||
double animation_velocity = (velocity > ANIMATION_BASE_VELOCITY)
|
||||
? velocity
|
||||
: ANIMATION_BASE_VELOCITY;
|
||||
|
||||
double pending_percentage = cancel_action ? end_percentage : 1 - end_percentage;
|
||||
|
||||
int duration = ((int)(pending_percentage / animation_velocity).abs () * DURATION_MULTIPLIER)
|
||||
.clamp (min_animation_duration, max_animation_duration);
|
||||
return duration;
|
||||
}
|
||||
}
|
126
src/Gestures/ScrollBackend.vala
Normal file
126
src/Gestures/ScrollBackend.vala
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2021 elementary, Inc (https://elementary.io)
|
||||
* 2021 José Expósito <jose.exposito89@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This gesture backend transforms the touchpad scroll events received by an actor into gestures.
|
||||
*/
|
||||
public class Gala.ScrollBackend : Object {
|
||||
// Mutter does not expose the size of the touchpad, so we use the same values as GTK apps.
|
||||
// From GNOME Shell, TOUCHPAD_BASE_[WIDTH|HEIGHT] / SCROLL_MULTIPLIER
|
||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/master/js/ui/swipeTracker.js
|
||||
private const double FINISH_DELTA_HORIZONTAL = 40;
|
||||
private const double FINISH_DELTA_VERTICAL = 30;
|
||||
|
||||
public signal void on_gesture_detected (Gesture gesture);
|
||||
public signal void on_begin (double delta, uint64 time);
|
||||
public signal void on_update (double delta, uint64 time);
|
||||
public signal void on_end (double delta, uint64 time);
|
||||
|
||||
public Clutter.Actor actor { get; construct; }
|
||||
public Clutter.Orientation orientation { get; construct; }
|
||||
|
||||
private bool started;
|
||||
private double delta_x;
|
||||
private double delta_y;
|
||||
private GestureDirection direction;
|
||||
|
||||
construct {
|
||||
started = false;
|
||||
delta_x = 0;
|
||||
delta_y = 0;
|
||||
direction = GestureDirection.UNKNOWN;
|
||||
}
|
||||
|
||||
public ScrollBackend (Clutter.Actor actor, Clutter.Orientation orientation) {
|
||||
Object (actor: actor, orientation: orientation);
|
||||
|
||||
actor.scroll_event.connect (on_scroll_event);
|
||||
}
|
||||
|
||||
private bool on_scroll_event (Clutter.ScrollEvent event) {
|
||||
if (!can_handle_event (event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64 time = event.get_time ();
|
||||
double x, y;
|
||||
event.get_scroll_delta (out x, out y);
|
||||
delta_x += x;
|
||||
delta_y += y;
|
||||
|
||||
if (!started) {
|
||||
if (delta_x != 0 || delta_y != 0) {
|
||||
Gesture gesture = build_gesture (delta_x, delta_y, orientation);
|
||||
started = true;
|
||||
direction = gesture.direction;
|
||||
on_gesture_detected (gesture);
|
||||
|
||||
double delta = calculate_delta (delta_x, delta_y, direction);
|
||||
on_begin (delta, time);
|
||||
}
|
||||
} else {
|
||||
double delta = calculate_delta (delta_x, delta_y, direction);
|
||||
if (x == 0 && y == 0) {
|
||||
started = false;
|
||||
delta_x = 0;
|
||||
delta_y = 0;
|
||||
direction = GestureDirection.UNKNOWN;
|
||||
on_end (delta, time);
|
||||
} else {
|
||||
on_update (delta, time);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool can_handle_event (Clutter.ScrollEvent event) {
|
||||
return event.get_type () == Clutter.EventType.SCROLL
|
||||
&& event.get_source_device ().get_device_type () == Clutter.InputDeviceType.TOUCHPAD_DEVICE
|
||||
&& event.get_scroll_direction () == Clutter.ScrollDirection.SMOOTH;
|
||||
}
|
||||
|
||||
private static Gesture build_gesture (double delta_x, double delta_y, Clutter.Orientation orientation) {
|
||||
GestureDirection direction;
|
||||
if (orientation == Clutter.Orientation.HORIZONTAL) {
|
||||
direction = delta_x > 0 ? GestureDirection.RIGHT : GestureDirection.LEFT;
|
||||
} else {
|
||||
direction = delta_y > 0 ? GestureDirection.DOWN : GestureDirection.UP;
|
||||
}
|
||||
|
||||
return new Gesture () {
|
||||
type = Gdk.EventType.SCROLL,
|
||||
direction = direction,
|
||||
fingers = 2,
|
||||
performed_on_device_type = Gdk.InputSource.TOUCHPAD
|
||||
};
|
||||
}
|
||||
|
||||
private static double calculate_delta (double delta_x, double delta_y, GestureDirection direction) {
|
||||
bool is_horizontal = (direction == GestureDirection.LEFT || direction == GestureDirection.RIGHT);
|
||||
double used_delta = is_horizontal ? delta_x : delta_y;
|
||||
double finish_delta = is_horizontal ? FINISH_DELTA_HORIZONTAL : FINISH_DELTA_VERTICAL;
|
||||
|
||||
bool is_positive = (direction == GestureDirection.RIGHT || direction == GestureDirection.DOWN);
|
||||
double clamp_low = is_positive ? 0 : -1;
|
||||
double clamp_high = is_positive ? 1 : 0;
|
||||
|
||||
double normalized_delta = (used_delta / finish_delta).clamp (clamp_low, clamp_high).abs ();
|
||||
return normalized_delta;
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ namespace Gala {
|
||||
public const AnimationMode ANIMATION_MODE = AnimationMode.EASE_OUT_QUAD;
|
||||
|
||||
private GestureAnimationDirector gesture_animation_director;
|
||||
private GestureTracker gesture_tracker;
|
||||
|
||||
const int SMOOTH_SCROLL_DELAY = 500;
|
||||
|
||||
@ -39,8 +40,6 @@ namespace Gala {
|
||||
bool opened = false;
|
||||
bool animating = false;
|
||||
|
||||
bool is_smooth_scrolling = false;
|
||||
|
||||
List<MonitorClone> window_containers_monitors;
|
||||
|
||||
IconGroupContainer icon_groups;
|
||||
@ -61,6 +60,10 @@ namespace Gala {
|
||||
|
||||
gesture_animation_director = new GestureAnimationDirector (ANIMATION_DURATION, ANIMATION_DURATION);
|
||||
|
||||
gesture_tracker = new GestureTracker (AnimationDuration.WORKSPACE_SWITCH_MIN, AnimationDuration.WORKSPACE_SWITCH);
|
||||
gesture_tracker.enable_scroll (this, Clutter.Orientation.HORIZONTAL);
|
||||
gesture_tracker.on_gesture_detected.connect (on_gesture_detected);
|
||||
|
||||
workspaces = new Actor ();
|
||||
workspaces.set_easing_mode (AnimationMode.EASE_OUT_QUAD);
|
||||
|
||||
@ -164,14 +167,17 @@ namespace Gala {
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll through workspaces
|
||||
* Scroll through workspaces with the mouse wheel. Smooth scrolling is handled by
|
||||
* GestureTracker.
|
||||
*/
|
||||
public override bool scroll_event (ScrollEvent scroll_event) {
|
||||
if (!opened)
|
||||
if (!opened) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (scroll_event.direction != ScrollDirection.SMOOTH)
|
||||
if (scroll_event.direction != ScrollDirection.SMOOTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double dx, dy;
|
||||
#if VALA_0_32
|
||||
@ -181,43 +187,92 @@ namespace Gala {
|
||||
event->get_scroll_delta (out dx, out dy);
|
||||
#endif
|
||||
|
||||
var direction = MotionDirection.LEFT;
|
||||
|
||||
// concept from maya to detect mouse wheel and proper smooth scroll and prevent
|
||||
// too much repetition on the events
|
||||
// concept from maya to detect mouse wheel
|
||||
if (Math.fabs (dy) == 1.0) {
|
||||
// mouse wheel scroll
|
||||
direction = dy > 0 ? MotionDirection.RIGHT : MotionDirection.LEFT;
|
||||
} else if (!is_smooth_scrolling) {
|
||||
// actual smooth scroll
|
||||
var choice = Math.fabs (dx) > Math.fabs (dy) ? dx : dy;
|
||||
var direction = dy > 0 ? MotionDirection.RIGHT : MotionDirection.LEFT;
|
||||
|
||||
if (choice > 0.3)
|
||||
direction = MotionDirection.RIGHT;
|
||||
else if (choice < -0.3)
|
||||
direction = MotionDirection.LEFT;
|
||||
else
|
||||
return false;
|
||||
unowned Meta.WorkspaceManager manager = display.get_workspace_manager ();
|
||||
var active_workspace = manager.get_active_workspace ();
|
||||
var new_workspace = active_workspace.get_neighbor (direction);
|
||||
|
||||
is_smooth_scrolling = true;
|
||||
Timeout.add (SMOOTH_SCROLL_DELAY, () => {
|
||||
is_smooth_scrolling = false;
|
||||
return false;
|
||||
});
|
||||
} else
|
||||
// smooth scroll delay still active
|
||||
return false;
|
||||
|
||||
unowned Meta.WorkspaceManager manager = display.get_workspace_manager ();
|
||||
var active_workspace = manager.get_active_workspace ();
|
||||
var new_workspace = active_workspace.get_neighbor (direction);
|
||||
|
||||
if (active_workspace != new_workspace)
|
||||
new_workspace.activate (display.get_current_time ());
|
||||
if (active_workspace != new_workspace) {
|
||||
new_workspace.activate (display.get_current_time ());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void on_gesture_detected (Gesture gesture) {
|
||||
if (gesture.type == Gdk.EventType.SCROLL) {
|
||||
Meta.MotionDirection direction = (gesture.direction == GestureDirection.LEFT)
|
||||
? Meta.MotionDirection.LEFT
|
||||
: Meta.MotionDirection.RIGHT;
|
||||
switch_workspace_with_gesture (direction);
|
||||
}
|
||||
}
|
||||
|
||||
private void switch_workspace_with_gesture (Meta.MotionDirection direction) {
|
||||
unowned Meta.WorkspaceManager manager = display.get_workspace_manager ();
|
||||
var num_workspaces = manager.get_n_workspaces ();
|
||||
var active_workspace_index = manager.get_active_workspace ().index ();
|
||||
var target_workspace_index = (direction == Meta.MotionDirection.LEFT)
|
||||
? active_workspace_index - 1
|
||||
: active_workspace_index + 1;
|
||||
|
||||
float initial_x = workspaces.x;
|
||||
float target_x = 0;
|
||||
bool is_nudge_animation = (target_workspace_index < 0 || target_workspace_index >= num_workspaces);
|
||||
|
||||
if (is_nudge_animation) {
|
||||
var nudge_delta = (direction == Meta.MotionDirection.LEFT)
|
||||
? WindowManagerGala.NUDGE_GAP
|
||||
: -WindowManagerGala.NUDGE_GAP;
|
||||
target_x = initial_x + nudge_delta * InternalUtils.get_ui_scaling_factor ();
|
||||
} else {
|
||||
foreach (var child in workspaces.get_children ()) {
|
||||
unowned WorkspaceClone workspace_clone = (WorkspaceClone) child;
|
||||
var index = workspace_clone.workspace.index ();
|
||||
if (index == target_workspace_index) {
|
||||
target_x = -workspace_clone.multitasking_view_x ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug ("Starting MultitaskingView switch workspace animation:");
|
||||
debug ("Active workspace index: %d", active_workspace_index);
|
||||
debug ("Target workspace index: %d", target_workspace_index);
|
||||
debug ("Total number of workspaces: %d", num_workspaces);
|
||||
debug ("Is nudge animation: %s", is_nudge_animation ? "Yes" : "No");
|
||||
debug ("Initial X: %f", initial_x);
|
||||
debug ("Target X: %f", target_x);
|
||||
|
||||
GestureAnimationDirector.OnUpdate on_animation_update = (percentage) => {
|
||||
var x = GestureTracker.animation_value (initial_x, target_x, percentage, true);
|
||||
workspaces.x = x;
|
||||
};
|
||||
|
||||
GestureAnimationDirector.OnEnd on_animation_end = (percentage, cancel_action, calculated_duration) => {
|
||||
gesture_tracker.enabled = false;
|
||||
|
||||
var duration = is_nudge_animation ? (AnimationDuration.NUDGE / 2) : calculated_duration;
|
||||
workspaces.set_easing_duration (duration);
|
||||
workspaces.x = (is_nudge_animation || cancel_action) ? initial_x : target_x;
|
||||
|
||||
workspaces.get_transition ("x").completed.connect (() => {
|
||||
gesture_tracker.enabled = true;
|
||||
|
||||
if (!is_nudge_animation && !cancel_action) {
|
||||
manager.get_workspace_by_index (target_workspace_index).activate (display.get_current_time ());
|
||||
update_positions (false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
gesture_tracker.connect_handlers (null, (owned) on_animation_update, (owned) on_animation_end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Places the WorkspaceClones, moves the view so that the active one is shown
|
||||
* and does the same for the IconGroups.
|
||||
|
@ -100,6 +100,11 @@ namespace Gala {
|
||||
private bool animating_switch_workspace = false;
|
||||
private GestureAnimationDirector gesture_animation_director;
|
||||
|
||||
/**
|
||||
* Amount of pixels to move on the nudge animation.
|
||||
*/
|
||||
public const float NUDGE_GAP = 32;
|
||||
|
||||
/**
|
||||
* Gap to show between workspaces while switching between them.
|
||||
*/
|
||||
@ -495,8 +500,8 @@ namespace Gala {
|
||||
}
|
||||
|
||||
private void play_nudge_animation (Meta.MotionDirection direction) {
|
||||
int duration = 360;
|
||||
var dest = (direction == Meta.MotionDirection.LEFT ? 32.0f : -32.0f);
|
||||
var dest = (direction == Meta.MotionDirection.LEFT ? NUDGE_GAP : -NUDGE_GAP);
|
||||
dest *= InternalUtils.get_ui_scaling_factor ();
|
||||
|
||||
GestureAnimationDirector.OnUpdate on_animation_update = (percentage) => {
|
||||
var x = GestureAnimationDirector.animation_value (0.0f, dest, percentage, true);
|
||||
@ -505,7 +510,7 @@ namespace Gala {
|
||||
|
||||
GestureAnimationDirector.OnEnd on_animation_end = (percentage, cancel_action) => {
|
||||
var nudge_gesture = new Clutter.PropertyTransition ("x") {
|
||||
duration = (duration / 2),
|
||||
duration = (AnimationDuration.NUDGE / 2),
|
||||
remove_on_complete = true,
|
||||
progress_mode = Clutter.AnimationMode.LINEAR
|
||||
};
|
||||
@ -523,7 +528,7 @@ namespace Gala {
|
||||
GLib.Value[] x = { dest };
|
||||
|
||||
var nudge = new Clutter.KeyframeTransition ("translation-x") {
|
||||
duration = duration,
|
||||
duration = AnimationDuration.NUDGE,
|
||||
remove_on_complete = true,
|
||||
progress_mode = Clutter.AnimationMode.EASE_IN_QUAD
|
||||
};
|
||||
|
@ -23,6 +23,10 @@ gala_bin_sources = files(
|
||||
'Background/BackgroundManager.vala',
|
||||
'Background/BackgroundSource.vala',
|
||||
'Background/SystemBackground.vala',
|
||||
'Gestures/Gesture.vala',
|
||||
'Gestures/GestureSettings.vala',
|
||||
'Gestures/GestureTracker.vala',
|
||||
'Gestures/ScrollBackend.vala',
|
||||
'Widgets/IconGroup.vala',
|
||||
'Widgets/IconGroupContainer.vala',
|
||||
'Widgets/MonitorClone.vala',
|
||||
|
Loading…
Reference in New Issue
Block a user