mirror of
https://github.com/elementary/gala.git
synced 2024-09-11 15:15:39 +03:00
186 lines
6.2 KiB
Vala
186 lines
6.2 KiB
Vala
/*
|
|
* Copyright 2022 elementary, Inc. (https://elementary.io)
|
|
* Copyright 2013 Tom Beckmann
|
|
* Copyright 2013 Rico Tzschichholz
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
public class Gala.Zoom : Object {
|
|
private const float MIN_ZOOM = 1.0f;
|
|
private const float MAX_ZOOM = 10.0f;
|
|
private const float SHORTCUT_DELTA = 0.5f;
|
|
private const int ANIMATION_DURATION = 300;
|
|
private const uint MOUSE_POLL_TIME = 50;
|
|
|
|
public WindowManager wm { get; construct; }
|
|
|
|
private uint mouse_poll_timer = 0;
|
|
private float current_zoom = MIN_ZOOM;
|
|
private ulong wins_handler_id = 0UL;
|
|
|
|
private GestureTracker gesture_tracker;
|
|
|
|
public Zoom (WindowManager wm) {
|
|
Object (wm: wm);
|
|
|
|
unowned var display = wm.get_display ();
|
|
var schema = new GLib.Settings (Config.SCHEMA + ".keybindings");
|
|
|
|
display.add_keybinding ("zoom-in", schema, Meta.KeyBindingFlags.NONE, (Meta.KeyHandlerFunc) zoom_in);
|
|
display.add_keybinding ("zoom-out", schema, Meta.KeyBindingFlags.NONE, (Meta.KeyHandlerFunc) zoom_out);
|
|
|
|
gesture_tracker = new GestureTracker (ANIMATION_DURATION, ANIMATION_DURATION);
|
|
gesture_tracker.enable_touchpad ();
|
|
gesture_tracker.on_gesture_detected.connect (on_gesture_detected);
|
|
}
|
|
|
|
~Zoom () {
|
|
if (wm == null) {
|
|
return;
|
|
}
|
|
|
|
unowned var display = wm.get_display ();
|
|
display.remove_keybinding ("zoom-in");
|
|
display.remove_keybinding ("zoom-out");
|
|
|
|
if (mouse_poll_timer > 0) {
|
|
Source.remove (mouse_poll_timer);
|
|
mouse_poll_timer = 0;
|
|
}
|
|
}
|
|
|
|
[CCode (instance_pos = -1)]
|
|
private void zoom_in (Meta.Display display, Meta.Window? window,
|
|
Clutter.KeyEvent event, Meta.KeyBinding binding) {
|
|
zoom (SHORTCUT_DELTA, true, wm.enable_animations);
|
|
}
|
|
|
|
[CCode (instance_pos = -1)]
|
|
private void zoom_out (Meta.Display display, Meta.Window? window,
|
|
Clutter.KeyEvent event, Meta.KeyBinding binding) {
|
|
zoom (-SHORTCUT_DELTA, true, wm.enable_animations);
|
|
}
|
|
|
|
private void on_gesture_detected (Gesture gesture) {
|
|
if (gesture.type != Clutter.EventType.TOUCHPAD_PINCH ||
|
|
(gesture.direction != GestureDirection.IN && gesture.direction != GestureDirection.OUT)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if ((gesture.fingers == 3 && GestureSettings.get_string ("three-finger-pinch") == "zoom") ||
|
|
(gesture.fingers == 4 && GestureSettings.get_string ("four-finger-pinch") == "zoom")
|
|
) {
|
|
zoom_with_gesture (gesture.direction);
|
|
}
|
|
}
|
|
|
|
private void zoom_with_gesture (GestureDirection direction) {
|
|
var initial_zoom = current_zoom;
|
|
var target_zoom = (direction == GestureDirection.IN)
|
|
? initial_zoom - MAX_ZOOM
|
|
: initial_zoom + MAX_ZOOM;
|
|
|
|
GestureTracker.OnUpdate on_animation_update = (percentage) => {
|
|
var zoom_level = GestureTracker.animation_value (initial_zoom, target_zoom, percentage);
|
|
var delta = zoom_level - current_zoom;
|
|
|
|
if (!wm.enable_animations) {
|
|
if (delta.abs () >= SHORTCUT_DELTA) {
|
|
delta = (delta > 0) ? SHORTCUT_DELTA : -SHORTCUT_DELTA;
|
|
} else {
|
|
delta = 0;
|
|
}
|
|
}
|
|
|
|
zoom (delta, false, false);
|
|
};
|
|
|
|
gesture_tracker.connect_handlers (null, (owned) on_animation_update, null);
|
|
}
|
|
|
|
private inline Graphene.Point compute_new_pivot_point () {
|
|
unowned var wins = wm.ui_group;
|
|
Graphene.Point coords;
|
|
wm.get_display ().get_cursor_tracker ().get_pointer (out coords, null);
|
|
var new_pivot = Graphene.Point () {
|
|
x = coords.x / wins.width,
|
|
y = coords.y / wins.height
|
|
};
|
|
|
|
return new_pivot;
|
|
}
|
|
|
|
private void zoom (float delta, bool play_sound, bool animate) {
|
|
// Nothing to do if zooming out of our bounds is requested
|
|
if ((current_zoom <= MIN_ZOOM && delta < 0) || (current_zoom >= MAX_ZOOM && delta >= 0)) {
|
|
if (play_sound) {
|
|
Gdk.beep ();
|
|
}
|
|
return;
|
|
}
|
|
|
|
unowned var wins = wm.ui_group;
|
|
// Add timer to poll current mouse position to reposition window-group
|
|
// to show requested zoomed area
|
|
if (mouse_poll_timer == 0) {
|
|
wins.pivot_point = compute_new_pivot_point ();
|
|
|
|
mouse_poll_timer = Timeout.add (MOUSE_POLL_TIME, () => {
|
|
var new_pivot = compute_new_pivot_point ();
|
|
if (wins.pivot_point.equal (new_pivot)) {
|
|
return true;
|
|
}
|
|
|
|
wins.save_easing_state ();
|
|
wins.set_easing_mode (Clutter.AnimationMode.LINEAR);
|
|
wins.set_easing_duration (MOUSE_POLL_TIME);
|
|
wins.pivot_point = new_pivot;
|
|
wins.restore_easing_state ();
|
|
return true;
|
|
});
|
|
}
|
|
|
|
current_zoom += delta;
|
|
var animation_duration = animate ? ANIMATION_DURATION : 0;
|
|
|
|
if (wins_handler_id > 0) {
|
|
wins.disconnect (wins_handler_id);
|
|
wins_handler_id = 0;
|
|
}
|
|
|
|
if (current_zoom <= MIN_ZOOM) {
|
|
current_zoom = MIN_ZOOM;
|
|
|
|
if (mouse_poll_timer > 0) {
|
|
Source.remove (mouse_poll_timer);
|
|
mouse_poll_timer = 0;
|
|
}
|
|
|
|
wins.save_easing_state ();
|
|
wins.set_easing_mode (Clutter.AnimationMode.EASE_OUT_CUBIC);
|
|
wins.set_easing_duration (animation_duration);
|
|
wins.set_scale (MIN_ZOOM, MIN_ZOOM);
|
|
wins.restore_easing_state ();
|
|
|
|
if (animate) {
|
|
wins_handler_id = wins.transitions_completed.connect (() => {
|
|
wins.disconnect (wins_handler_id);
|
|
wins_handler_id = 0;
|
|
wins.set_pivot_point (0.0f, 0.0f);
|
|
});
|
|
} else {
|
|
wins.set_pivot_point (0.0f, 0.0f);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
wins.save_easing_state ();
|
|
wins.set_easing_mode (Clutter.AnimationMode.EASE_OUT_CUBIC);
|
|
wins.set_easing_duration (animation_duration);
|
|
wins.set_scale (current_zoom, current_zoom);
|
|
wins.restore_easing_state ();
|
|
}
|
|
}
|