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..fdd8f9be4 --- /dev/null +++ b/demo/Views/SwipeView.vala @@ -0,0 +1,59 @@ +/* + * 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; + + private Gtk.ProgressBar progressbar; + private uint spring_timeout = -1; + + construct { + title = "Multitouch"; + + progressbar = new Gtk.ProgressBar () { + fraction = 0.5 + }; + + child = progressbar; + + swipe_tracker = new Granite.SwipeTracker (this); + 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/demo/meson.build b/demo/meson.build index ef8a75d02..9c64c6596 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', diff --git a/lib/SwipeTracker.vala b/lib/SwipeTracker.vala new file mode 100644 index 000000000..86250b597 --- /dev/null +++ b/lib/SwipeTracker.vala @@ -0,0 +1,67 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +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; + + // The widget that accepts the swipe event + public unowned Gtk.Widget swipeable { get; construct; } + + // How completed the swipe is + private double progress; + private double prev_offset = 0; + + public Gtk.GestureDrag drag_gesture; + + public SwipeTracker (Gtk.Widget swipeable) { + Object (swipeable: swipeable); + } + + 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); + + swipeable.add_controller (drag_gesture); + } + + ~SwipeTracker () { + swipeable.remove_controller (drag_gesture); + } + + private void on_drag_begin () { + prev_offset = 0; + progress = 0; + begin_swipe (); + } + + 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); + + update_swipe (progress); + } + + private void on_drag_end () { + end_swipe (); + } +} 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',