From a44e26cb7da1c057ed2594397cb7b4d3726eaadd Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 25 Aug 2020 18:35:32 +0100 Subject: [PATCH] Locate pointer (#905) --- src/Widgets/PointerLocator.vala | 143 ++++++++++++++++++++++++++++++++ src/WindowManager.vala | 16 ++++ src/meson.build | 1 + 3 files changed, 160 insertions(+) create mode 100644 src/Widgets/PointerLocator.vala diff --git a/src/Widgets/PointerLocator.vala b/src/Widgets/PointerLocator.vala new file mode 100644 index 00000000..0d058511 --- /dev/null +++ b/src/Widgets/PointerLocator.vala @@ -0,0 +1,143 @@ +// +// Copyright 2020 elementary, Inc. (https://elementary.io) +// +// 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 . +// + +namespace Gala { + public class PointerLocator : Clutter.Actor, Clutter.Animatable { + private const int WIDTH_PX = 300; + private const int HEIGHT_PX = 300; + private const int ANIMATION_TIME_MS = 300; + + private const uint BORDER_WIDTH_PX = 3; + + private const string BACKGROUND_COLOR = "#64baff"; + private const double BACKGROUND_OPACITY = 0.7; + + public weak WindowManager wm { get; construct; } + + private int scaling_factor = 1; + + private GLib.Settings settings; + private Cogl.Pipeline pipeline; + private Cairo.Pattern stroke_color; + private Cairo.Pattern fill_color; + + private uint timeout_id; + + public PointerLocator (WindowManager wm) { + Object (wm: wm); + } + + construct { + visible = false; + reactive = false; + + settings = new GLib.Settings ("org.gnome.desktop.interface"); + pipeline = new Cogl.Pipeline (Clutter.get_default_backend ().get_cogl_context ()); + + scaling_factor = InternalUtils.get_ui_scaling_factor (); + set_size (WIDTH_PX * scaling_factor, HEIGHT_PX * scaling_factor); + + var rgba = Gdk.RGBA (); + rgba.parse (BACKGROUND_COLOR); + stroke_color = new Cairo.Pattern.rgb (rgba.red, rgba.green, rgba.blue); + fill_color = new Cairo.Pattern.rgba (rgba.red, rgba.green, rgba.blue, BACKGROUND_OPACITY); + } + + public override void paint (Clutter.PaintContext context) { + var width = WIDTH_PX * scaling_factor; + var height = HEIGHT_PX * scaling_factor; + + var radius = int.min (width / 2, height / 2); + + var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, width, height); + var cr = new Cairo.Context (surface); + cr.set_line_cap (Cairo.LineCap.ROUND); + cr.set_line_join (Cairo.LineJoin.ROUND); + cr.translate (width / 2, height / 2); + + cr.move_to (radius - BORDER_WIDTH_PX, 0); + cr.arc (0, 0, radius - BORDER_WIDTH_PX * scaling_factor, 0, 2 * Math.PI); + cr.close_path (); + + cr.set_line_width (0); + cr.set_source (fill_color); + cr.fill_preserve (); + + cr.set_line_width (BORDER_WIDTH_PX * scaling_factor); + cr.set_source (stroke_color); + cr.stroke (); + + var cogl_context = context.get_framebuffer ().get_context (); + + try { + var texture = new Cogl.Texture2D.from_data (cogl_context, width, height, Cogl.PixelFormat.BGRA_8888_PRE, + surface.get_stride (), surface.get_data ()); + + pipeline.set_layer_texture (0, texture); + + context.get_framebuffer ().draw_rectangle (pipeline, 0, 0, width, height); + } catch (Error e) {} + + base.paint (context); + } + + public void show_ripple () { + if (!settings.get_boolean ("locate-pointer")) { + return; + } + + if (timeout_id != 0) { + GLib.Source.remove (timeout_id); + timeout_id = 0; + visible = false; + restore_easing_state (); + } + + var tracker = wm.get_display ().get_cursor_tracker (); + int x, y; + tracker.get_pointer (out x, out y, null); + + this.x = x - (width / 2); + this.y = y - (width / 2); + + var pivot = Graphene.Point (); + pivot.x = 0.5f; + pivot.y = 0.5f; + pivot_point = pivot; + + scale_x = 1; + scale_y = 1; + + visible = true; + + save_easing_state (); + set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); + set_easing_duration (ANIMATION_TIME_MS); + + timeout_id = Timeout.add (ANIMATION_TIME_MS, () => { + timeout_id = 0; + + restore_easing_state (); + + return GLib.Source.REMOVE; + }); + + scale_x = 0; + scale_y = 0; + } + } +} diff --git a/src/WindowManager.vala b/src/WindowManager.vala index d56176bc..fdd92228 100644 --- a/src/WindowManager.vala +++ b/src/WindowManager.vala @@ -58,6 +58,10 @@ namespace Gala { public ScreenShield? screen_shield { get; private set; } +#if HAS_MUTTER336 + public PointerLocator pointer_locator { get; private set; } +#endif + Meta.PluginInfo info; WindowSwitcher? winswitcher = null; @@ -244,6 +248,12 @@ namespace Gala { #else top_window_group = screen.get_top_window_group (); #endif + + +#if HAS_MUTTER336 + pointer_locator = new PointerLocator (this); + ui_group.add_child (pointer_locator); +#endif ui_group.add_child (screen_shield); stage.remove_child (top_window_group); @@ -2176,6 +2186,12 @@ namespace Gala { end_switch_workspace (); } +#if HAS_MUTTER336 + public override void locate_pointer () { + pointer_locator.show_ripple (); + } +#endif + public override bool keybinding_filter (Meta.KeyBinding binding) { if (!is_modal ()) return false; diff --git a/src/meson.build b/src/meson.build index 3bdf187f..92c946a3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -28,6 +28,7 @@ gala_bin_sources = files( 'Widgets/MonitorClone.vala', 'Widgets/MultitaskingView.vala', 'Widgets/PixelPicker.vala', + 'Widgets/PointerLocator.vala', 'Widgets/SafeWindowClone.vala', 'Widgets/ScreenShield.vala', 'Widgets/SelectionArea.vala',