diff --git a/src/Widgets/CircularProgressbar.vala b/src/Widgets/CircularProgressbar.vala new file mode 100644 index 000000000..ae026833f --- /dev/null +++ b/src/Widgets/CircularProgressbar.vala @@ -0,0 +1,116 @@ +// TODO: Copyright + +public class Gala.CircularProgressbar : Clutter.Actor { + public uint radius { get; set; default = 0; } + public uint duration { get; set; default = 0; } + public float monitor_scaling_factor { get; set; default = 1.0f; } + public double angle { get; set; default = START_ANGLE; } + + private const double BACKGROUND_OPACITY = 0.7; + private const int BORDER_WIDTH_PX = 1; + private const double START_ANGLE = 3 * Math.PI_2; + + private Cogl.Pipeline pipeline; + private Clutter.PropertyTransition transition; + private Cairo.ImageSurface? surface; + private uint diameter { get { return radius * 2; }} + + construct { + visible = false; + reactive = false; + +#if HAS_MUTTER47 + unowned var backend = context.get_backend (); +#else + unowned var backend = Clutter.get_default_backend (); +#endif + pipeline = new Cogl.Pipeline (backend.get_cogl_context ()); + + transition = new Clutter.PropertyTransition ("angle"); + transition.set_progress_mode (Clutter.AnimationMode.LINEAR); + transition.set_animatable (this); + transition.set_from_value (START_ANGLE); + transition.set_to_value (START_ANGLE + 2 * Math.PI); + + transition.new_frame.connect (() => { + queue_redraw (); + }); + } + + public void start () { + visible = true; + width = diameter; + height = diameter; + + if (surface == null || surface.get_width () != diameter || surface.get_height () != diameter) { + surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, (int) diameter, (int) diameter); + } + + transition.duration = duration; + transition.start (); + } + + public void reset () { + visible = false; + transition.stop (); + } + + public override void paint (Clutter.PaintContext context) { + if (angle == START_ANGLE) { + return; + } + + var rgba = Drawing.StyleManager.get_instance ().theme_accent_color; + + /* Don't use alpha from the stylesheet to ensure contrast */ + var stroke_color = new Cairo.Pattern.rgb (rgba.red, rgba.green, rgba.blue); + var fill_color = new Cairo.Pattern.rgba (rgba.red, rgba.green, rgba.blue, BACKGROUND_OPACITY); + + var border_width = InternalUtils.scale_to_int (BORDER_WIDTH_PX, monitor_scaling_factor); + + var cr = new Cairo.Context (surface); + + // 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 (radius, radius); + + cr.move_to (0, 0); + cr.arc (0, 0, radius - border_width, START_ANGLE, angle); + cr.line_to (0, 0); + cr.close_path (); + + 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 (); + + var cogl_context = context.get_framebuffer ().get_context (); + + try { + var texture = new Cogl.Texture2D.from_data ( + cogl_context, + (int) diameter, (int) diameter, + 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, diameter, diameter); + } catch (Error e) { + warning ("CircularProgressbar: Couldn't create new texture: %s", e.message); + } + + base.paint (context); + } +} diff --git a/src/Widgets/DwellClickTimer.vala b/src/Widgets/DwellClickTimer.vala index a2e9ac4a0..4b9c88aeb 100644 --- a/src/Widgets/DwellClickTimer.vala +++ b/src/Widgets/DwellClickTimer.vala @@ -3,33 +3,15 @@ * SPDX-FileCopyrightText: 2020, 2025 elementary, Inc. (https://elementary.io) */ -public class Gala.DwellClickTimer : Clutter.Actor, Clutter.Animatable { - private const double BACKGROUND_OPACITY = 0.7; - private const int BORDER_WIDTH_PX = 1; - - private const double START_ANGLE = 3 * Math.PI / 2; - - /** - * Delay, in milliseconds, before showing the animation. - * libinput uses a timeout of 180ms when tapping is enabled. Use that value plus a safety - * margin so the animation is never displayed when tapping. - */ - private const double DELAY_TIMEOUT = 185; - +public class Gala.DwellClickTimer : Clutter.Actor { private float scaling_factor = 1.0f; - private int cursor_size = 24; + private uint cursor_size = 24; - private Cogl.Pipeline pipeline; - private Clutter.PropertyTransition transition; - private Cairo.Pattern stroke_color; - private Cairo.Pattern fill_color; + private CircularProgressbar progressbar; private GLib.Settings interface_settings; - private Cairo.ImageSurface surface; public Meta.Display display { get; construct; } - public double angle { get; set; } - public DwellClickTimer (Meta.Display display) { Object (display: display); } @@ -38,32 +20,21 @@ public class Gala.DwellClickTimer : Clutter.Actor, Clutter.Animatable { visible = false; reactive = false; + interface_settings = new GLib.Settings ("org.gnome.desktop.interface"); + + progressbar = new CircularProgressbar (); + add_child (progressbar); + #if HAS_MUTTER47 unowned var backend = context.get_backend (); #else unowned var backend = Clutter.get_default_backend (); #endif - - pipeline = new Cogl.Pipeline (backend.get_cogl_context ()); - - transition = new Clutter.PropertyTransition ("angle"); - transition.set_progress_mode (Clutter.AnimationMode.EASE_OUT_QUAD); - transition.set_animatable (this); - transition.set_from_value (START_ANGLE); - transition.set_to_value (START_ANGLE + (2 * Math.PI)); - - transition.new_frame.connect (() => { - queue_redraw (); - }); - - interface_settings = new GLib.Settings ("org.gnome.desktop.interface"); - var seat = backend.get_default_seat (); seat.set_pointer_a11y_dwell_click_type (Clutter.PointerA11yDwellClickType.PRIMARY); seat.ptr_a11y_timeout_started.connect ((device, type, timeout) => { - var scale = display.get_monitor_scale (display.get_current_monitor ()); - update_cursor_size (scale); + update_cursor_size (); unowned var tracker = display.get_cursor_tracker (); Graphene.Point coords = {}; @@ -72,97 +43,24 @@ public class Gala.DwellClickTimer : Clutter.Actor, Clutter.Animatable { x = coords.x - (width / 2); y = coords.y - (width / 2); - transition.set_duration (timeout); visible = true; - transition.start (); + progressbar.duration = timeout; + progressbar.start (); }); seat.ptr_a11y_timeout_stopped.connect ((device, type, clicked) => { - transition.stop (); visible = false; + progressbar.reset (); }); } - private void update_cursor_size (float scale) { - scaling_factor = scale; + private void update_cursor_size () { + scaling_factor = display.get_monitor_scale (display.get_current_monitor ()); - cursor_size = (int) (interface_settings.get_int ("cursor-size") * scaling_factor * 1.25); - - if (surface == null || surface.get_width () != cursor_size || surface.get_height () != cursor_size) { - surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, cursor_size, cursor_size); - } + cursor_size = (uint) (interface_settings.get_int ("cursor-size") * scaling_factor * 1.25); + var radius = cursor_size / 2; + progressbar.radius = radius; set_size (cursor_size, cursor_size); } - - public override void paint (Clutter.PaintContext context) { - if (angle == 0) { - return; - } - - 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); - - var radius = int.min (cursor_size / 2, cursor_size / 2); - var end_angle = START_ANGLE + angle; - var border_width = InternalUtils.scale_to_int (BORDER_WIDTH_PX, scaling_factor); - - var cr = new Cairo.Context (surface); - - // 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 (cursor_size / 2, cursor_size / 2); - - cr.move_to (0, 0); - cr.arc (0, 0, radius - border_width, START_ANGLE, end_angle); - cr.line_to (0, 0); - cr.close_path (); - - 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 (); - - var cogl_context = context.get_framebuffer ().get_context (); - - try { - var texture = new Cogl.Texture2D.from_data (cogl_context, cursor_size, cursor_size, 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, cursor_size, cursor_size); - } catch (Error e) {} - - base.paint (context); - } - - public bool interpolate_value (string property_name, Clutter.Interval interval, double progress, out Value @value) { - if (property_name == "angle") { - @value = 0; - - var elapsed_time = transition.get_elapsed_time (); - if (elapsed_time > DELAY_TIMEOUT) { - double delayed_progress = (elapsed_time - DELAY_TIMEOUT) / (transition.duration - DELAY_TIMEOUT); - @value = (delayed_progress * 2 * Math.PI); - } - - return true; - } - - return base.interpolate_value (property_name, interval, progress, out @value); - } } diff --git a/src/meson.build b/src/meson.build index a039dd096..0b0fb1222 100644 --- a/src/meson.build +++ b/src/meson.build @@ -52,6 +52,7 @@ gala_bin_sources = files( 'ShellClients/PositionedWindow.vala', 'ShellClients/ShellClientsManager.vala', 'ShellClients/ShellWindow.vala', + 'Widgets/CircularProgressbar.vala', 'Widgets/DwellClickTimer.vala', 'Widgets/IconGroup.vala', 'Widgets/IconGroupContainer.vala',