From d8607d218dd0e7058a332844b1157a4070294d57 Mon Sep 17 00:00:00 2001 From: lenemter Date: Sun, 23 Jun 2024 22:13:07 +0900 Subject: [PATCH] Implement zoom --- compositor/WindowManager.vala | 27 +++++++ compositor/Zoom.vala | 141 ++++++++++++++++++++++++++++++++++ compositor/meson.build | 3 +- data/compositor.gschema.xml | 14 ++++ data/meson.build | 6 ++ meson.build | 2 + 6 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 compositor/Zoom.vala create mode 100644 data/compositor.gschema.xml diff --git a/compositor/WindowManager.vala b/compositor/WindowManager.vala index 54f80b169..6071ba419 100644 --- a/compositor/WindowManager.vala +++ b/compositor/WindowManager.vala @@ -63,6 +63,8 @@ namespace GreeterCompositor { //ActivatableComponent? workspace_view = null; //ActivatableComponent? window_overview = null; + private Zoom zoom; + //ScreenSaver? screensaver; //Gee.LinkedList modal_stack = new Gee.LinkedList (); @@ -135,6 +137,7 @@ namespace GreeterCompositor { ui_group = new Clutter.Actor (); ui_group.reactive = true; + update_ui_group_size (); stage.add_child (ui_group); int width, height; @@ -161,6 +164,9 @@ namespace GreeterCompositor { pointer_locator = new PointerLocator (this); ui_group.add_child (pointer_locator); + unowned var monitor_manager = display.get_context ().get_backend ().get_monitor_manager (); + monitor_manager.monitors_changed.connect (update_ui_group_size); + /*keybindings*/ KeyBinding.set_custom_handler ("switch-to-workspace-first", () => {}); @@ -188,6 +194,8 @@ namespace GreeterCompositor { KeyBinding.set_custom_handler ("show-desktop", () => {}); + zoom = new Zoom (this); + /* orca (screenreader) doesn't listen to it's org.gnome.desktop.a11y.applications screen-reader-enabled key so we handle it ourselves @@ -208,6 +216,25 @@ namespace GreeterCompositor { }); } + private void update_ui_group_size () { + unowned var display = get_display (); + + int max_width = 0; + int max_height = 0; + + var num_monitors = display.get_n_monitors (); + for (int i = 0; i < num_monitors; i++) { + var geom = display.get_monitor_geometry (i); + var total_width = geom.x + geom.width; + var total_height = geom.y + geom.height; + + max_width = (max_width > total_width) ? max_width : total_width; + max_height = (max_height > total_height) ? max_height : total_height; + } + + ui_group.set_size (max_width, max_height); + } + private async void start_command (string[] command) { if (Meta.Util.is_wayland_compositor ()) { yield start_wayland (command); diff --git a/compositor/Zoom.vala b/compositor/Zoom.vala new file mode 100644 index 000000000..f6288e806 --- /dev/null +++ b/compositor/Zoom.vala @@ -0,0 +1,141 @@ +/* + * 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 GreeterCompositor.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; + + public Zoom (WindowManager wm) { + Object (wm: wm); + + unowned var display = wm.get_display (); + var schema = new GLib.Settings ("io.elementary.greeter-compositor.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); + } + + ~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, true); + } + + [CCode (instance_pos = -1)] + private void zoom_out (Meta.Display display, Meta.Window? window, + Clutter.KeyEvent event, Meta.KeyBinding binding) { + zoom (-SHORTCUT_DELTA, true, true); + } + + 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 (); + } +} diff --git a/compositor/meson.build b/compositor/meson.build index 5347ef071..db72ec7f0 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -96,7 +96,8 @@ compositor_files = files( 'MediaFeedback.vala', 'PointerLocator.vala', 'Utils.vala', - 'WindowManager.vala' + 'WindowManager.vala', + 'Zoom.vala' ) executable( diff --git a/data/compositor.gschema.xml b/data/compositor.gschema.xml new file mode 100644 index 000000000..3dc4abdb5 --- /dev/null +++ b/data/compositor.gschema.xml @@ -0,0 +1,14 @@ + + + + + plus', 'KP_Add']]]> + Zoom in + + + + minus', 'KP_Subtract']]]> + Zoom out + + + diff --git a/data/meson.build b/data/meson.build index 4343e7479..4e407c890 100644 --- a/data/meson.build +++ b/data/meson.build @@ -2,6 +2,12 @@ conf_data = configuration_data() conf_data.set('GETTEXT_PACKAGE', meson.project_name()) conf_data.set('PROJECT_NAME', meson.project_name()) +install_data( + 'compositor.gschema.xml', + install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0', 'schemas'), + rename: 'io.elementary.greeter-compositor.gschema.xml' +) + desktop_in = configure_file ( input: meson.project_name() + '.desktop.in.in', output: meson.project_name() + '.desktop.in', diff --git a/meson.build b/meson.build index a7cea8d5c..647178008 100644 --- a/meson.build +++ b/meson.build @@ -55,3 +55,5 @@ vapigen = find_program('vapigen', required: false) if vapigen.found() subdir('vapi') endif + +gnome.post_install(glib_compile_schemas: true)