From 23cee127c6b425938061b9c382c2d90b317b53c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Wed, 25 Jun 2025 17:09:59 -0700 Subject: [PATCH 1/7] Prototyping swipe tracker --- demo/GraniteDemo.vala | 2 ++ demo/Views/SwipeView.vala | 65 +++++++++++++++++++++++++++++++++++++++ demo/meson.build | 4 ++- 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 demo/Views/SwipeView.vala diff --git a/demo/GraniteDemo.vala b/demo/GraniteDemo.vala index 9b1204abd..a33271036 100644 --- a/demo/GraniteDemo.vala +++ b/demo/GraniteDemo.vala @@ -30,6 +30,7 @@ public class Granite.Demo : Gtk.Application { var toast_view = new ToastView (); var settings_uris_view = new SettingsUrisView (); var style_manager_view = new StyleManagerView (); + var multitouch_view = new SwipeView (); var utils_view = new UtilsView (); var video_view = new VideoView (); var placeholder = new WelcomeView (); @@ -55,6 +56,7 @@ public class Granite.Demo : Gtk.Application { main_stack.add_titled (settings_uris_view, "settings_uris", "Settings URIs"); main_stack.add_titled (toast_view, "toasts", "Toast"); main_stack.add_titled (utils_view, "utils", "Utils"); + main_stack.add_titled (multitouch_view, "multitouch", multitouch_view.title); main_stack.add_titled (dialogs_view, "dialogs", "Dialogs"); main_stack.add_titled (application_view, "application", "Application"); diff --git a/demo/Views/SwipeView.vala b/demo/Views/SwipeView.vala new file mode 100644 index 000000000..bd8805a92 --- /dev/null +++ b/demo/Views/SwipeView.vala @@ -0,0 +1,65 @@ +/* + * Copyright 2025 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +public class SwipeView : DemoPage { + // prevent Vala unrefing + private Granite.SwipeTracker swipe_tracker; + + construct { + title = "Multitouch"; + + var progressbar = new Gtk.ProgressBar () { + fraction = 0.5 + }; + + child = progressbar; + + swipe_tracker = new Granite.SwipeTracker (this); + swipe_tracker.notify["progress"].connect (() => { + progressbar.fraction = swipe_tracker.progress / 2 + 0.5; + }); + } +} + +public class Granite.SwipeTracker : Object { + // private const int TOUCHPAD_BASE_DISTANCE_H = 400; + // private const int TOUCHPAD_BASE_DISTANCE_V = 300; + + // The widget that accepts the swipe event + public unowned Gtk.Widget widget { get; construct; } + + // How completed the swipe is + public double progress { get; set; } + + private double prev_offset = 0; + + public Gtk.GestureDrag drag_gesture; + + public SwipeTracker (Gtk.Widget widget) { + Object (widget: widget); + } + + construct { + drag_gesture = new Gtk.GestureDrag (); + drag_gesture.drag_update.connect (on_drag_update); + + widget.add_controller (drag_gesture); + } + + ~SwipeTracker () { + widget.remove_controller (drag_gesture); + } + + public void on_drag_update (Gtk.Gesture gesture, double offset_x, double offset_y) { + double delta, offset; + + offset = offset_x; + delta = offset - prev_offset; + prev_offset = offset; + + progress += delta / 100; + progress = progress.clamp (-1, 1); + } +} diff --git a/demo/meson.build b/demo/meson.build index ef8a75d02..ce71daae3 100644 --- a/demo/meson.build +++ b/demo/meson.build @@ -18,6 +18,7 @@ executable( 'Views/OverlayBarView.vala', 'Views/SettingsUrisView.vala', 'Views/StyleManagerView.vala', + 'Views/SwipeView.vala', 'Views/ToastView.vala', 'Views/UtilsView.vala', 'Views/VideoView.vala', @@ -25,7 +26,8 @@ executable( dependencies: [ libgranite_dep, - dependency('shumate-1.0') + dependency('shumate-1.0'), + meson.get_compiler('c').find_library('m') ], install: true, From e8fba9621a8141f90eea8b0dea98ab47bd4a29dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Wed, 25 Jun 2025 17:29:27 -0700 Subject: [PATCH 2/7] Basic spring --- demo/Views/SwipeView.vala | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/demo/Views/SwipeView.vala b/demo/Views/SwipeView.vala index bd8805a92..7bf8c2939 100644 --- a/demo/Views/SwipeView.vala +++ b/demo/Views/SwipeView.vala @@ -35,6 +35,8 @@ public class Granite.SwipeTracker : Object { private double prev_offset = 0; + private uint spring_timeout = -1; + public Gtk.GestureDrag drag_gesture; public SwipeTracker (Gtk.Widget widget) { @@ -43,7 +45,9 @@ public class Granite.SwipeTracker : Object { construct { drag_gesture = new Gtk.GestureDrag (); + drag_gesture.drag_begin.connect (on_drag_begin); drag_gesture.drag_update.connect (on_drag_update); + drag_gesture.drag_end.connect (on_drag_end); widget.add_controller (drag_gesture); } @@ -52,7 +56,14 @@ public class Granite.SwipeTracker : Object { widget.remove_controller (drag_gesture); } - public void on_drag_update (Gtk.Gesture gesture, double offset_x, double offset_y) { + private void on_drag_begin () { + if (spring_timeout != -1) { + Source.remove (spring_timeout); + spring_timeout = -1; + } + } + + private void on_drag_update (Gtk.Gesture gesture, double offset_x, double offset_y) { double delta, offset; offset = offset_x; @@ -62,4 +73,20 @@ public class Granite.SwipeTracker : Object { progress += delta / 100; progress = progress.clamp (-1, 1); } + + private void on_drag_end () { + prev_offset = 0; + + spring_timeout = Timeout.add (10, () => { + if (progress < 0.01 && progress > -0.01) { + progress = 0; + spring_timeout = -1; + return Source.REMOVE; + } + + progress *= 0.8; + + return Source.CONTINUE; + }); + } } From 91d57dcb1901184aeab88f6bf4866055770ce48c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Wed, 25 Jun 2025 17:31:12 -0700 Subject: [PATCH 3/7] Adjust framerate --- demo/Views/SwipeView.vala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/Views/SwipeView.vala b/demo/Views/SwipeView.vala index 7bf8c2939..809207fa3 100644 --- a/demo/Views/SwipeView.vala +++ b/demo/Views/SwipeView.vala @@ -77,7 +77,8 @@ public class Granite.SwipeTracker : Object { private void on_drag_end () { prev_offset = 0; - spring_timeout = Timeout.add (10, () => { + // 60 FPS → 16.67 ms per frame + spring_timeout = Timeout.add (16, () => { if (progress < 0.01 && progress > -0.01) { progress = 0; spring_timeout = -1; From 26a6d791ad4ffbbb99c7706d2055d5d13a8b99e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Thu, 26 Jun 2025 10:56:40 -0700 Subject: [PATCH 4/7] More 1:1-like movement --- demo/Views/SwipeView.vala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/Views/SwipeView.vala b/demo/Views/SwipeView.vala index 809207fa3..ecfe84fbb 100644 --- a/demo/Views/SwipeView.vala +++ b/demo/Views/SwipeView.vala @@ -24,7 +24,7 @@ public class SwipeView : DemoPage { } public class Granite.SwipeTracker : Object { - // private const int TOUCHPAD_BASE_DISTANCE_H = 400; + private const int TOUCHPAD_BASE_DISTANCE_H = 400; // private const int TOUCHPAD_BASE_DISTANCE_V = 300; // The widget that accepts the swipe event @@ -70,7 +70,7 @@ public class Granite.SwipeTracker : Object { delta = offset - prev_offset; prev_offset = offset; - progress += delta / 100; + progress += delta / TOUCHPAD_BASE_DISTANCE_H; progress = progress.clamp (-1, 1); } From 508d8e594f8336ce09cf0a27db3e235234e8dcd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Thu, 26 Jun 2025 10:59:08 -0700 Subject: [PATCH 5/7] Move SwipeTracker to the library --- demo/Views/SwipeView.vala | 69 ------------------------------------ demo/meson.build | 3 +- lib/SwipeTracker.vala | 73 +++++++++++++++++++++++++++++++++++++++ lib/meson.build | 1 + 4 files changed, 75 insertions(+), 71 deletions(-) create mode 100644 lib/SwipeTracker.vala diff --git a/demo/Views/SwipeView.vala b/demo/Views/SwipeView.vala index ecfe84fbb..280aa9c87 100644 --- a/demo/Views/SwipeView.vala +++ b/demo/Views/SwipeView.vala @@ -22,72 +22,3 @@ public class SwipeView : DemoPage { }); } } - -public class Granite.SwipeTracker : Object { - private const int TOUCHPAD_BASE_DISTANCE_H = 400; - // private const int TOUCHPAD_BASE_DISTANCE_V = 300; - - // The widget that accepts the swipe event - public unowned Gtk.Widget widget { get; construct; } - - // How completed the swipe is - public double progress { get; set; } - - private double prev_offset = 0; - - private uint spring_timeout = -1; - - public Gtk.GestureDrag drag_gesture; - - public SwipeTracker (Gtk.Widget widget) { - Object (widget: widget); - } - - construct { - drag_gesture = new Gtk.GestureDrag (); - drag_gesture.drag_begin.connect (on_drag_begin); - drag_gesture.drag_update.connect (on_drag_update); - drag_gesture.drag_end.connect (on_drag_end); - - widget.add_controller (drag_gesture); - } - - ~SwipeTracker () { - widget.remove_controller (drag_gesture); - } - - private void on_drag_begin () { - if (spring_timeout != -1) { - Source.remove (spring_timeout); - spring_timeout = -1; - } - } - - private void on_drag_update (Gtk.Gesture gesture, double offset_x, double offset_y) { - double delta, offset; - - offset = offset_x; - delta = offset - prev_offset; - prev_offset = offset; - - progress += delta / TOUCHPAD_BASE_DISTANCE_H; - progress = progress.clamp (-1, 1); - } - - private void on_drag_end () { - prev_offset = 0; - - // 60 FPS → 16.67 ms per frame - spring_timeout = Timeout.add (16, () => { - if (progress < 0.01 && progress > -0.01) { - progress = 0; - spring_timeout = -1; - return Source.REMOVE; - } - - progress *= 0.8; - - return Source.CONTINUE; - }); - } -} diff --git a/demo/meson.build b/demo/meson.build index ce71daae3..9c64c6596 100644 --- a/demo/meson.build +++ b/demo/meson.build @@ -26,8 +26,7 @@ executable( dependencies: [ libgranite_dep, - dependency('shumate-1.0'), - meson.get_compiler('c').find_library('m') + dependency('shumate-1.0') ], install: true, diff --git a/lib/SwipeTracker.vala b/lib/SwipeTracker.vala new file mode 100644 index 000000000..c4742935f --- /dev/null +++ b/lib/SwipeTracker.vala @@ -0,0 +1,73 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +public class Granite.SwipeTracker : Object { + private const int TOUCHPAD_BASE_DISTANCE_H = 400; + // private const int TOUCHPAD_BASE_DISTANCE_V = 300; + + // The widget that accepts the swipe event + public unowned Gtk.Widget widget { get; construct; } + + // How completed the swipe is + public double progress { get; set; } + + private double prev_offset = 0; + + private uint spring_timeout = -1; + + public Gtk.GestureDrag drag_gesture; + + public SwipeTracker (Gtk.Widget widget) { + Object (widget: widget); + } + + construct { + drag_gesture = new Gtk.GestureDrag (); + drag_gesture.drag_begin.connect (on_drag_begin); + drag_gesture.drag_update.connect (on_drag_update); + drag_gesture.drag_end.connect (on_drag_end); + + widget.add_controller (drag_gesture); + } + + ~SwipeTracker () { + widget.remove_controller (drag_gesture); + } + + private void on_drag_begin () { + if (spring_timeout != -1) { + Source.remove (spring_timeout); + spring_timeout = -1; + } + } + + private void on_drag_update (Gtk.Gesture gesture, double offset_x, double offset_y) { + double delta, offset; + + offset = offset_x; + delta = offset - prev_offset; + prev_offset = offset; + + progress += delta / TOUCHPAD_BASE_DISTANCE_H; + progress = progress.clamp (-1, 1); + } + + private void on_drag_end () { + prev_offset = 0; + + // 60 FPS → 16.67 ms per frame + spring_timeout = Timeout.add (16, () => { + if (progress < 0.01 && progress > -0.01) { + progress = 0; + spring_timeout = -1; + return Source.REMOVE; + } + + progress *= 0.8; + + return Source.CONTINUE; + }); + } +} diff --git a/lib/meson.build b/lib/meson.build index 5d73c9fdb..ed613c838 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -6,6 +6,7 @@ libgranite_sources = files( 'Constants.vala', 'Init.vala', 'StyleManager.vala', + 'SwipeTracker.vala', 'Services/Application.vala', 'Services/AsyncMutex.vala', From 1048b3d85fdd3b61c6613d5e4eaf6988e239326b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Thu, 26 Jun 2025 11:03:06 -0700 Subject: [PATCH 6/7] Rename widget to swipeable --- lib/SwipeTracker.vala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/SwipeTracker.vala b/lib/SwipeTracker.vala index c4742935f..2d24ed68e 100644 --- a/lib/SwipeTracker.vala +++ b/lib/SwipeTracker.vala @@ -8,7 +8,7 @@ public class Granite.SwipeTracker : Object { // private const int TOUCHPAD_BASE_DISTANCE_V = 300; // The widget that accepts the swipe event - public unowned Gtk.Widget widget { get; construct; } + public unowned Gtk.Widget swipeable { get; construct; } // How completed the swipe is public double progress { get; set; } @@ -19,8 +19,8 @@ public class Granite.SwipeTracker : Object { public Gtk.GestureDrag drag_gesture; - public SwipeTracker (Gtk.Widget widget) { - Object (widget: widget); + public SwipeTracker (Gtk.Widget swipeable) { + Object (swipeable: swipeable); } construct { @@ -29,11 +29,11 @@ public class Granite.SwipeTracker : Object { drag_gesture.drag_update.connect (on_drag_update); drag_gesture.drag_end.connect (on_drag_end); - widget.add_controller (drag_gesture); + swipeable.add_controller (drag_gesture); } ~SwipeTracker () { - widget.remove_controller (drag_gesture); + swipeable.remove_controller (drag_gesture); } private void on_drag_begin () { From a7657aaee580bec1dcdc81d21440abd0b21bb9cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Thu, 26 Jun 2025 11:28:57 -0700 Subject: [PATCH 7/7] Move spring out of swipe tracker --- demo/Views/SwipeView.vala | 41 ++++++++++++++++++++++++++++++++++++--- lib/SwipeTracker.vala | 38 +++++++++++++++--------------------- 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/demo/Views/SwipeView.vala b/demo/Views/SwipeView.vala index 280aa9c87..fdd8f9be4 100644 --- a/demo/Views/SwipeView.vala +++ b/demo/Views/SwipeView.vala @@ -7,18 +7,53 @@ public class SwipeView : DemoPage { // prevent Vala unrefing private Granite.SwipeTracker swipe_tracker; + private Gtk.ProgressBar progressbar; + private uint spring_timeout = -1; + construct { title = "Multitouch"; - var progressbar = new Gtk.ProgressBar () { + progressbar = new Gtk.ProgressBar () { fraction = 0.5 }; child = progressbar; swipe_tracker = new Granite.SwipeTracker (this); - swipe_tracker.notify["progress"].connect (() => { - progressbar.fraction = swipe_tracker.progress / 2 + 0.5; + swipe_tracker.begin_swipe.connect (on_swipe_begin); + swipe_tracker.update_swipe.connect (on_swipe_update); + swipe_tracker.end_swipe.connect (on_swipe_end); + } + + private void on_swipe_begin () { + if (spring_timeout != -1) { + Source.remove (spring_timeout); + spring_timeout = -1; + } + } + + private void on_swipe_update (double progress) { + progressbar.fraction = progress / 2 + 0.5; + } + + private void on_swipe_end () { + double epsilon = 0.05; + + // 60 FPS → 16.67 ms per frame + spring_timeout = Timeout.add (16, () => { + if (progressbar.fraction < 0.5 + epsilon && progressbar.fraction > 0.5 - epsilon) { + progressbar.fraction = 0.5; + spring_timeout = -1; + return Source.REMOVE; + } + + if (progressbar.fraction < 0.5) { + progressbar.fraction *= 1.1; + } else { + progressbar.fraction *= 0.9; + } + + return Source.CONTINUE; }); } } diff --git a/lib/SwipeTracker.vala b/lib/SwipeTracker.vala index 2d24ed68e..86250b597 100644 --- a/lib/SwipeTracker.vala +++ b/lib/SwipeTracker.vala @@ -4,6 +4,15 @@ */ public class Granite.SwipeTracker : Object { + // This signal is emitted right before a swipe will be started + public signal void begin_swipe (); + + // This signal is emitted every time the progress value changes. + public signal void update_swipe (double progress); + + // This signal is emitted as soon as the gesture has stopped. + public signal void end_swipe (); + private const int TOUCHPAD_BASE_DISTANCE_H = 400; // private const int TOUCHPAD_BASE_DISTANCE_V = 300; @@ -11,12 +20,9 @@ public class Granite.SwipeTracker : Object { public unowned Gtk.Widget swipeable { get; construct; } // How completed the swipe is - public double progress { get; set; } - + private double progress; private double prev_offset = 0; - private uint spring_timeout = -1; - public Gtk.GestureDrag drag_gesture; public SwipeTracker (Gtk.Widget swipeable) { @@ -37,10 +43,9 @@ public class Granite.SwipeTracker : Object { } private void on_drag_begin () { - if (spring_timeout != -1) { - Source.remove (spring_timeout); - spring_timeout = -1; - } + prev_offset = 0; + progress = 0; + begin_swipe (); } private void on_drag_update (Gtk.Gesture gesture, double offset_x, double offset_y) { @@ -52,22 +57,11 @@ public class Granite.SwipeTracker : Object { progress += delta / TOUCHPAD_BASE_DISTANCE_H; progress = progress.clamp (-1, 1); + + update_swipe (progress); } private void on_drag_end () { - prev_offset = 0; - - // 60 FPS → 16.67 ms per frame - spring_timeout = Timeout.add (16, () => { - if (progress < 0.01 && progress > -0.01) { - progress = 0; - spring_timeout = -1; - return Source.REMOVE; - } - - progress *= 0.8; - - return Source.CONTINUE; - }); + end_swipe (); } }