Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 123 additions & 137 deletions src/Widgets/PointerLocator.vala
Original file line number Diff line number Diff line change
@@ -1,165 +1,151 @@
//
// 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 <http://www.gnu.org/licenses/>.
//

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 int BORDER_WIDTH_PX = 1;

private const double BACKGROUND_OPACITY = 0.7;

public Meta.Display display { get; construct; }

private float scaling_factor = 1.0f;
private int surface_width = WIDTH_PX;
private int surface_height = HEIGHT_PX;

private GLib.Settings settings;
private Cogl.Pipeline pipeline;
private Cairo.ImageSurface surface;
private Cairo.Pattern stroke_color;
private Cairo.Pattern fill_color;

public PointerLocator (Meta.Display display) {
Object (display: display);
}
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020-2025 elementary, Inc. (https://elementary.io)
*/

public class Gala.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 int BORDER_WIDTH_PX = 1;

private const double BACKGROUND_OPACITY = 0.7;

construct {
visible = false;
reactive = false;
public Meta.Display display { get; construct; }

private float scaling_factor = 1.0f;
private int surface_width = WIDTH_PX;
private int surface_height = HEIGHT_PX;

private GLib.Settings settings;
private Cogl.Pipeline pipeline;
private Cairo.ImageSurface surface;
private Cairo.Pattern stroke_color;
private Cairo.Pattern fill_color;

public PointerLocator (Meta.Display display) {
Object (display: display);
}

settings = new GLib.Settings ("org.gnome.desktop.interface");
construct {
visible = false;
reactive = false;

settings = new GLib.Settings ("org.gnome.desktop.interface");

#if HAS_MUTTER47
unowned var ctx = context.get_backend ().get_cogl_context ();
unowned var ctx = context.get_backend ().get_cogl_context ();
#else
unowned var ctx = Clutter.get_default_backend ().get_cogl_context ();
unowned var ctx = Clutter.get_default_backend ().get_cogl_context ();
#endif
pipeline = new Cogl.Pipeline (ctx);
pipeline = new Cogl.Pipeline (ctx);

var pivot = Graphene.Point ();
pivot.init (0.5f, 0.5f);
pivot_point = pivot;
}
var pivot = Graphene.Point ();
pivot.init (0.5f, 0.5f);
pivot_point = pivot;
}

private void update_surface (float cur_scale) {
if (surface == null || cur_scale != scaling_factor) {
scaling_factor = cur_scale;
surface_width = InternalUtils.scale_to_int (WIDTH_PX, scaling_factor);
surface_height = InternalUtils.scale_to_int (HEIGHT_PX, scaling_factor);
private void update_surface (float cur_scale) {
if (surface == null || cur_scale != scaling_factor) {
scaling_factor = cur_scale;
surface_width = InternalUtils.scale_to_int (WIDTH_PX, scaling_factor);
surface_height = InternalUtils.scale_to_int (HEIGHT_PX, scaling_factor);

surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, surface_width, surface_height);
surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, surface_width, surface_height);

set_size (surface_width, surface_height);
}
set_size (surface_width, surface_height);
}
}

public override void paint (Clutter.PaintContext context) {
var radius = int.min (surface_width / 2, surface_height / 2);
public override void paint (Clutter.PaintContext context) {
var radius = int.min (surface_width / 2, surface_height / 2);

var cr = new Cairo.Context (surface);
var border_width = InternalUtils.scale_to_int (BORDER_WIDTH_PX, scaling_factor);
var cr = new Cairo.Context (surface);
var border_width = InternalUtils.scale_to_int (BORDER_WIDTH_PX, scaling_factor);

// Clear the surface
cr.save ();
cr.set_source_rgba (0, 0, 0, 0);
cr.set_operator (Cairo.Operator.SOURCE);
cr.paint ();
cr.restore ();
// Clear the surface
cr.save ();
cr.set_source_rgba (0, 0, 0, 0);
cr.set_operator (Cairo.Operator.SOURCE);
cr.paint ();
cr.restore ();

cr.set_line_cap (Cairo.LineCap.ROUND);
cr.set_line_join (Cairo.LineJoin.ROUND);
cr.translate (surface_width / 2, surface_height / 2);
cr.set_line_cap (Cairo.LineCap.ROUND);
cr.set_line_join (Cairo.LineJoin.ROUND);
cr.translate (surface_width / 2, surface_height / 2);

cr.move_to (radius - BORDER_WIDTH_PX, 0);
cr.arc (0, 0, radius - border_width, 0, 2 * Math.PI);
cr.close_path ();
cr.move_to (radius - BORDER_WIDTH_PX, 0);
cr.arc (0, 0, radius - border_width, 0, 2 * Math.PI);
cr.close_path ();

cr.set_line_width (0);
cr.set_source (fill_color);
cr.fill_preserve ();
cr.set_line_width (0);
cr.set_source (fill_color);
cr.fill_preserve ();

cr.set_line_width (border_width);
cr.set_source (stroke_color);
cr.stroke ();
cr.set_line_width (border_width);
cr.set_source (stroke_color);
cr.stroke ();

var cogl_context = context.get_framebuffer ().get_context ();
var cogl_context = context.get_framebuffer ().get_context ();

try {
var texture = new Cogl.Texture2D.from_data (cogl_context, surface_width, surface_height,
Cogl.PixelFormat.BGRA_8888_PRE, surface.get_stride (), surface.get_data ());
try {
var texture = new Cogl.Texture2D.from_data (cogl_context, surface_width, surface_height,
Cogl.PixelFormat.BGRA_8888_PRE, surface.get_stride (), surface.get_data ());

pipeline.set_layer_texture (0, texture);
pipeline.set_layer_texture (0, texture);

context.get_framebuffer ().draw_rectangle (pipeline, 0, 0, surface_width, surface_height);
} catch (Error e) {}
context.get_framebuffer ().draw_rectangle (pipeline, 0, 0, surface_width, surface_height);
} catch (Error e) {}

base.paint (context);
base.paint (context);
}

public void show_ripple () {
if (!settings.get_boolean ("locate-pointer")) {
return;
}

public void show_ripple () {
if (!settings.get_boolean ("locate-pointer")) {
return;
}

unowned var old_transition = get_transition ("circle");
if (old_transition != null) {
old_transition.stop ();
}

var transition = new Clutter.TransitionGroup ();
transition.remove_on_complete = true;
var transition_x = new Clutter.PropertyTransition ("scale-x");
var transition_y = new Clutter.PropertyTransition ("scale-y");
var start_val = Value (typeof (double));
start_val.set_double (1);
var stop_val = Value (typeof (double));
stop_val.set_double (0);
transition_x.set_from_value (start_val);
transition_y.set_from_value (start_val);
transition_x.set_to_value (stop_val);
transition_y.set_to_value (stop_val);
transition.progress_mode = Clutter.AnimationMode.EASE_OUT_QUAD;
transition.duration = ANIMATION_TIME_MS;
transition.add_transition (transition_x);
transition.add_transition (transition_y);
transition.stopped.connect (() => { visible = false; });
transition.started.connect (() => { visible = true; });
add_transition ("circle", transition);

var rgba = Drawing.StyleManager.get_instance ().theme_accent_color;

/* Don't use alpha from the stylesheet to ensure contrast */
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);

unowned var tracker = display.get_cursor_tracker ();
Graphene.Point coords = {};
tracker.get_pointer (out coords, null);

update_surface (display.get_monitor_scale (display.get_current_monitor ()));

x = coords.x - (width / 2);
y = coords.y - (width / 2);

transition.start ();
unowned var old_transition = get_transition ("circle");
if (old_transition != null) {
old_transition.stop ();
}

var transition = new Clutter.TransitionGroup ();
transition.remove_on_complete = true;
var transition_x = new Clutter.PropertyTransition ("scale-x");
var transition_y = new Clutter.PropertyTransition ("scale-y");
var start_val = Value (typeof (double));
start_val.set_double (1);
var stop_val = Value (typeof (double));
stop_val.set_double (0);
transition_x.set_from_value (start_val);
transition_y.set_from_value (start_val);
transition_x.set_to_value (stop_val);
transition_y.set_to_value (stop_val);
transition.progress_mode = Clutter.AnimationMode.EASE_OUT_QUAD;
transition.duration = ANIMATION_TIME_MS;
transition.add_transition (transition_x);
transition.add_transition (transition_y);
transition.stopped.connect (() => { visible = false; });
transition.started.connect (() => { visible = true; });
add_transition ("circle", transition);

var rgba = Drawing.StyleManager.get_instance ().theme_accent_color;

/* Don't use alpha from the stylesheet to ensure contrast */
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);

unowned var tracker = display.get_cursor_tracker ();
Graphene.Point coords = {};
tracker.get_pointer (out coords, null);

update_surface (display.get_monitor_scale (display.get_current_monitor ()));

x = coords.x - (width / 2);
y = coords.y - (width / 2);

transition.start ();
}
}
Loading