diff --git a/data/gesture-demo-background.svg b/data/gesture-demo-background.svg new file mode 100644 index 00000000..b4c22a79 --- /dev/null +++ b/data/gesture-demo-background.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/gesture-demo-dock.png b/data/gesture-demo-dock.png new file mode 100644 index 00000000..2767ad8b Binary files /dev/null and b/data/gesture-demo-dock.png differ diff --git a/data/gesture-demo-fingers.png b/data/gesture-demo-fingers.png new file mode 100644 index 00000000..192fb01b Binary files /dev/null and b/data/gesture-demo-fingers.png differ diff --git a/data/gesture-demo-multitasking.svg b/data/gesture-demo-multitasking.svg new file mode 100644 index 00000000..8bde8c50 --- /dev/null +++ b/data/gesture-demo-multitasking.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/gesture-demo-touchpad.svg b/data/gesture-demo-touchpad.svg new file mode 100644 index 00000000..29c1f5e8 --- /dev/null +++ b/data/gesture-demo-touchpad.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/gesture-demo-workspace-two.svg b/data/gesture-demo-workspace-two.svg new file mode 100644 index 00000000..96a56d52 --- /dev/null +++ b/data/gesture-demo-workspace-two.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/data/gesture-demo-workspace.svg b/data/gesture-demo-workspace.svg new file mode 100644 index 00000000..27ca2144 --- /dev/null +++ b/data/gesture-demo-workspace.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/data/io.elementary.onboarding.gresource.xml b/data/io.elementary.onboarding.gresource.xml index 9d0bb11d..218e72f9 100644 --- a/data/io.elementary.onboarding.gresource.xml +++ b/data/io.elementary.onboarding.gresource.xml @@ -5,5 +5,12 @@ appearance-default.svg appearance-dark.svg appearance-scheduled.svg + gesture-demo-background.svg + gesture-demo-workspace.svg + gesture-demo-workspace-two.svg + gesture-demo-touchpad.svg + gesture-demo-multitasking.svg + gesture-demo-fingers.png + gesture-demo-dock.png diff --git a/src/GestureDemo/DemoWindow.vala b/src/GestureDemo/DemoWindow.vala new file mode 100644 index 00000000..11dd57ee --- /dev/null +++ b/src/GestureDemo/DemoWindow.vala @@ -0,0 +1,122 @@ +/* + * Copyright 2020-2023 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 . + * + * Authored by: Owen David Malicsi + */ + +public class Onboarding.DemoWindow : Gtk.Window { + public Adw.Carousel carousel { get; set; } + public DemoWindow (Onboarding.MultitouchView view) { + Object ( + transient_for: (Onboarding.MainWindow) view.get_root () + ); + } + + construct { + default_width = 960; + default_height = 640; + resizable = false; + + var front_page = new Granite.Placeholder ("Try Multitouch Gestures!") { + description = "This is a demo of multitouch gestures used throughout the system", + icon = new ThemedIcon ("preferences-desktop-workspaces"), + hexpand = true, + vexpand = true, + halign = Gtk.Align.CENTER, + valign = Gtk.Align.CENTER + }; + + var multitasking_demo = new Onboarding.MultitaskingDemo () { + hexpand = true, + vexpand = true + }; + + var switch_workspace_demo = new Onboarding.SwitchWorkspaceDemo () { + hexpand = true, + vexpand = true + }; + + carousel = new Adw.Carousel () { + hexpand = true, + vexpand = true + }; + carousel.append (front_page); + carousel.append (multitasking_demo); + carousel.append (switch_workspace_demo); + + var skip_button = new Gtk.Button.with_label (_("Skip All")); + + var next_button = new Gtk.Button.with_label (_("Next")); + next_button.add_css_class (Granite.STYLE_CLASS_SUGGESTED_ACTION); + + var buttons_group = new Gtk.SizeGroup (Gtk.SizeGroupMode.BOTH); + buttons_group.add_widget (skip_button); + buttons_group.add_widget (next_button); + + var action_area = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6) { + valign = Gtk.Align.END + }; + action_area.add_css_class ("dialog-action-area"); + action_area.append (skip_button); + action_area.append (new Adw.Bin () { hexpand = true }); + action_area.append (next_button); + + var grid = new Gtk.Grid () { + row_spacing = 12 + }; + grid.attach (carousel, 0, 0); + grid.attach (action_area, 0, 1); + + child = grid; + titlebar = new Adw.Bin () { visible = false }; + + add_css_class ("dialog"); + + carousel.page_changed.connect ((page) => { + if (page == 1) { + multitasking_demo.animated_child.play_animation (); + } else if (page == 2) { + switch_workspace_demo.animated_child.play_animation (); + } + }); + + skip_button.clicked.connect (() => { + if (carousel.position == 0) { + this.hide (); + } else { + if (carousel.position == 1) { + skip_button.label = "Skip All"; + } + + next_button.label = "Next"; + carousel.scroll_to (carousel.get_nth_page ((uint) carousel.position - 1), true); + } + }); + + next_button.clicked.connect (() => { + if (carousel.position < carousel.n_pages - 1) { + if (carousel.position == 1) { + next_button.label = "Done"; + } + + skip_button.label = "Previous"; + carousel.scroll_to (carousel.get_nth_page ((uint) carousel.position + 1), true); + } else { + this.hide (); + } + }); + } +} diff --git a/src/GestureDemo/MultitaskingDemo.vala b/src/GestureDemo/MultitaskingDemo.vala new file mode 100644 index 00000000..38c007ea --- /dev/null +++ b/src/GestureDemo/MultitaskingDemo.vala @@ -0,0 +1,166 @@ +/* + * Copyright 2020-2023 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 . + * + * Authored by: Owen David Malicsi + */ + +public class Onboarding.MultitaskingDemo : Gtk.Box { + public MultitaskingAnimatedChild animated_child { get; set; } + construct { + orientation = Gtk.Orientation.VERTICAL; + + var label = new Granite.Placeholder ("Multitasking View") { + description = "Swipe vertically with your 3 fingers to show or hide multitasking view", + icon = new ThemedIcon ("preferences-desktop-workspaces"), + halign = Gtk.Align.START, + valign = Gtk.Align.START, + }; + + animated_child = new MultitaskingAnimatedChild () { + hexpand = true, + vexpand = true, + margin_start = 10, + margin_bottom = 10, + margin_end = 10 + }; + + append (label); + append (animated_child); + vexpand = true; + } + + public class MultitaskingAnimatedChild : Gtk.Widget { + private Gdk.Texture background; + private Gdk.Texture touchpad; + private Gdk.Texture fingers; + private Gdk.Texture multitasking; + private Gdk.Texture workspace; + private Gdk.Texture dock; + + private float finger_y = 135; + private float scale = 1; + private float dock_height = 25; + + private const float LOWEST_FINGER_POSITION = 140; + private const float HIGHEST_FINGER_POSITION = 70; + private const float DOCK_MAX_HEIGHT = 25; + private const float BACKGROUND_WIDTH = 440; + private const float BACKGROUND_HEIGHT = 500; + private const float MULTITASKING_WIDTH = 410; + private const float MULTITASKING_HEIGHT = 300; + private const float DOCK_STARTING_POSX = 560; + + private Adw.TimedAnimation animation; + + static construct { + set_layout_manager_type (typeof (Gtk.BinLayout)); + } + + construct { + background = Gdk.Texture.from_resource ("/io/elementary/onboarding/gesture-demo-background.svg"); + touchpad = Gdk.Texture.from_resource ("/io/elementary/onboarding/gesture-demo-touchpad.svg"); + fingers = Gdk.Texture.from_resource ("/io/elementary/onboarding/gesture-demo-fingers.png"); + multitasking = Gdk.Texture.from_resource ("/io/elementary/onboarding/gesture-demo-multitasking.svg"); + workspace = Gdk.Texture.from_resource ("/io/elementary/onboarding/gesture-demo-workspace.svg"); + dock = Gdk.Texture.from_resource ("/io/elementary/onboarding/gesture-demo-dock.png"); + + animation = new Adw.TimedAnimation (this, 0, 1000, 4000, new Adw.CallbackAnimationTarget ((value) => { + set_progress ((float) value); + })) { + easing = Adw.Easing.LINEAR + }; + + animation.done.connect (play_animation); + this.unmap.connect (() => { + animation.done.disconnect (play_animation); + }); + } + + public void play_animation () { + animation.play (); + } + + private void set_progress (float val) { + if (val <= 250) { // We show workspace for 1 second + finger_y = LOWEST_FINGER_POSITION - ((val / 1000) * (HIGHEST_FINGER_POSITION * 4)); + scale = 1.0f - (val / 1000); + + if (val < 64.5) { // we hide the dock faster + dock_height = DOCK_MAX_HEIGHT - ((val / 1000) * (DOCK_MAX_HEIGHT * 16)); + } else { + dock_height = 0; + } + } else if (val <= 500 && val > 250) { // We stop for another second + finger_y = HIGHEST_FINGER_POSITION; + } else if (val <= 750 && val > 500) { // We hide workspace for 1 second + finger_y = LOWEST_FINGER_POSITION - (((750 - val) / 1000) * (HIGHEST_FINGER_POSITION * 4)); + scale = 1.0f - ((750 - val) / 1000); + + if (val < 564.5) { + dock_height = 0; + } else { + dock_height = DOCK_MAX_HEIGHT - (((750 - val) / 1000) * (DOCK_MAX_HEIGHT * 16)); + } + } else { // We pause + finger_y = LOWEST_FINGER_POSITION; + } + + queue_draw (); + } + + protected override void snapshot (Gtk.Snapshot snapshot) { + snapshot.append_texture (background, { + { 15, 0}, + { BACKGROUND_WIDTH, BACKGROUND_HEIGHT } + }); + + snapshot.append_texture (background, { + { 460, 0}, + { BACKGROUND_WIDTH, BACKGROUND_HEIGHT } + }); + + // 30 because 15 margin from background with posx 15 + snapshot.append_texture (touchpad, { + {30, 30}, + { MULTITASKING_WIDTH, MULTITASKING_HEIGHT } + }); + snapshot.append_texture (multitasking, { + {475, 100}, + { MULTITASKING_WIDTH, MULTITASKING_HEIGHT } + }); + // 475 because 15 margin, 10 bg separator and 15 margin from col2 bg. + + snapshot.save (); // Finger Animation + snapshot.translate ({110, finger_y}); // finger posy from 70 to 140 to simulate swipe up + fingers.snapshot (snapshot, 250, 300); + snapshot.restore (); + + snapshot.save (); // workspace zooming out animation + snapshot.translate ({ // Position coordinates are increased by a fraction of the size per interval. + 475 + ((410 - (scale * 410)) / 2), + 100 + ((300 - (scale * 300)) / 2) + }); // divide by 2 so we will be in the center. + snapshot.scale (scale, scale); + workspace.snapshot (snapshot, 410, 300); + snapshot.restore (); + + snapshot.save (); // hide dock animation + snapshot.translate ({ DOCK_STARTING_POSX, 340 + (25 - dock_height) }); + dock.snapshot (snapshot, 250, dock_height); + snapshot.restore (); + } + } +} diff --git a/src/GestureDemo/SwitchWorkspaceDemo.vala b/src/GestureDemo/SwitchWorkspaceDemo.vala new file mode 100644 index 00000000..64bd4f32 --- /dev/null +++ b/src/GestureDemo/SwitchWorkspaceDemo.vala @@ -0,0 +1,160 @@ +/* + * Copyright 2020-2023 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 . + * + * Authored by: Owen David Malicsi + */ + +public class Onboarding.SwitchWorkspaceDemo : Gtk.Box { + public SwitchingAnimatedChild animated_child { get; set; } + construct { + orientation = Gtk.Orientation.VERTICAL; + + var label = new Granite.Placeholder ("Switch to Workspace") { + description = "Swipe horizontally with your 3 fingers to switch between workspaces", + icon = new ThemedIcon ("preferences-desktop-wallpaper"), + halign = Gtk.Align.START, + valign = Gtk.Align.START + }; + + animated_child = new SwitchingAnimatedChild () { + hexpand = true, + vexpand = true, + margin_start = 10, + margin_bottom = 10, + margin_end = 10 + }; + + append (label); + append (animated_child); + vexpand = true; + } + + public class SwitchingAnimatedChild : Gtk.Widget { + private Gdk.Texture background; + private Gdk.Texture touchpad; + private Gdk.Texture fingers; + private Gdk.Texture multitasking; + private Gdk.Texture workspace; + private Gdk.Texture workspace_two; + private Gdk.Texture dock; + + private float finger_x = 140; + private float scale = 1; + + private Adw.TimedAnimation animation; + + private const float LEFTMOST_FINGER_POSITION = 75; + private const float RIGHTMOST_FINGER_POSITION = 150; + private const float DOCK_MAX_HEIGHT = 25; + private const float BACKGROUND_WIDTH = 440; + private const float BACKGROUND_HEIGHT = 500; + private const float MULTITASKING_WIDTH = 410; + private const float MULTITASKING_HEIGHT = 300; + + static construct { + set_layout_manager_type (typeof (Gtk.BinLayout)); + } + + construct { + background = Gdk.Texture.from_resource ("/io/elementary/onboarding/gesture-demo-background.svg"); + touchpad = Gdk.Texture.from_resource ("/io/elementary/onboarding/gesture-demo-touchpad.svg"); + fingers = Gdk.Texture.from_resource ("/io/elementary/onboarding/gesture-demo-fingers.png"); + multitasking = Gdk.Texture.from_resource ("/io/elementary/onboarding/gesture-demo-multitasking.svg"); + workspace = Gdk.Texture.from_resource ("/io/elementary/onboarding/gesture-demo-workspace.svg"); + workspace_two = Gdk.Texture.from_resource ("/io/elementary/onboarding/gesture-demo-workspace-two.svg"); + dock = Gdk.Texture.from_resource ("/io/elementary/onboarding/gesture-demo-dock.png"); + + animation = new Adw.TimedAnimation (this, 0, 1000, 4000, new Adw.CallbackAnimationTarget ((value) => { + set_progress ((float) value); + })) { + easing = Adw.Easing.LINEAR + }; + + animation.done.connect (play_animation); + this.unmap.connect (() => { + animation.done.disconnect (play_animation); + }); + } + + public void play_animation () { + animation.play (); + } + + private void set_progress (float val) { + if (val <= 250) { // We switch for 1 second + finger_x = LEFTMOST_FINGER_POSITION + ((val / 1000) * (LEFTMOST_FINGER_POSITION * 4)); + scale = 1.0f - (val / 250); + } else if (val <= 500 && val > 250) { // We stop for another second + finger_x = RIGHTMOST_FINGER_POSITION; + } else if (val <= 750 && val > 500) { // We hide workspace for 1 second + finger_x = LEFTMOST_FINGER_POSITION + (((750 - val) / 1000) * (LEFTMOST_FINGER_POSITION * 4)); + scale = 1.0f - ((750 - val) / 250); + } else { // We pause + finger_x = LEFTMOST_FINGER_POSITION; + } + + queue_draw (); + } + + protected override void snapshot (Gtk.Snapshot snapshot) { + snapshot.append_texture (background, { + { 15, 0}, + { BACKGROUND_WIDTH, BACKGROUND_HEIGHT } + }); + + snapshot.append_texture (background, { + { 460, 0}, + { BACKGROUND_WIDTH, BACKGROUND_HEIGHT } + }); + + // 30 because 15 margin from background with posx 15 + snapshot.append_texture (touchpad, { + {30, 100}, + { MULTITASKING_WIDTH, MULTITASKING_HEIGHT } + }); + snapshot.append_texture (multitasking, { + {475, 100}, + { MULTITASKING_WIDTH, MULTITASKING_HEIGHT } + }); + // 475 because 15 margin, 10 bg separator and 15 margin from col2 bg. + + snapshot.save (); // Finger Animation + snapshot.translate ({finger_x, 145 }); // finger posy from 70 to 160 to simulate swipe up + fingers.snapshot (snapshot, 250, 300); + snapshot.restore (); + + snapshot.save (); // Switching FROM workspace animation + snapshot.translate ({ 475, 100}); + workspace.snapshot (snapshot, 410 - (scale * 410), 300); // we subtract width until workspace1 disappears + snapshot.restore (); + + snapshot.save (); // Switching TO workspace animation + snapshot.translate ({ 475 + (410 - (scale * 410)), 100}); // we add desired xpos to w1 width + workspace_two.snapshot (snapshot, 410 * scale, 300); // which diminishes slowly so w2 will replace it. + snapshot.restore (); + + snapshot.save (); // Switching FROM dock animation + snapshot.translate ({ 560 - ((560 - 475) * scale), 340 }); // we subtract width until dock1 disappers + dock.snapshot (snapshot, 250 - (scale * 250), DOCK_MAX_HEIGHT); // at the origin of w1. + snapshot.restore (); + + snapshot.save (); // Switching TO dock animation + snapshot.translate ({ 885 - ((885 - 560) * scale), 340 }); // we subtract from the highest x of w2 + dock.snapshot (snapshot, 250 * scale, DOCK_MAX_HEIGHT); // to get origin and end at 560 where dock starts. + snapshot.restore (); + } + } +} diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 49767590..f94fd0a3 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -79,6 +79,14 @@ public class Onboarding.MainWindow : Gtk.ApplicationWindow { } } + var seat = Gdk.Display.get_default ().get_default_seat (); // untested + foreach (var device in seat.get_devices (seat.get_capabilities ())) { + if (device.source == Gdk.InputSource.TOUCHPAD && !("multitouch" in viewed)) { + var multitouch_view = new MultitouchView (); + carousel.append (multitouch_view); + } + } + if (!("night-light" in viewed)) { var night_light_view = new NightLightView (); carousel.append (night_light_view); diff --git a/src/Views/MultitouchView.vala b/src/Views/MultitouchView.vala new file mode 100644 index 00000000..fe4a7a8b --- /dev/null +++ b/src/Views/MultitouchView.vala @@ -0,0 +1,57 @@ +/* + * Copyright 2020-2023 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 . + * + * Authored by: Owen David Malicsi + */ + +public class Onboarding.MultitouchView : AbstractOnboardingView { + public MultitouchView () { + Object ( + view_name: "multitouch", + description: _("Navigate efficiently through your windows and workspaces with modern multitouch gestures."), + icon_name: "input-touchpad", // Change to better one + title: _("Multitouch Gestures") + ); + } + + construct { + var demo_button = new Gtk.LinkButton.with_label ("", _("Show multitouch gestures demo…")) { + hexpand = false, + halign = Gtk.Align.CENTER, + has_tooltip = false + }; + + var settings_label = new Gtk.Label (_("You can change these gestures later in System Settings")) { + justify = Gtk.Justification.CENTER, + max_width_chars = 45, + use_markup = true, + valign = Gtk.Align.END, + vexpand = true, + wrap = true + }; + settings_label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL); + settings_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); + + custom_bin.attach (demo_button, 0, 0); + custom_bin.attach (settings_label, 0, 1); + + var demo_window = new Onboarding.DemoWindow (this); + demo_button.activate_link.connect (() => { + demo_window.carousel.scroll_to (demo_window.carousel.get_nth_page (0), false); + demo_window.present (); + }); + } +} diff --git a/src/meson.build b/src/meson.build index 90a8d010..f322fb0c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -6,6 +6,9 @@ vala_files = [ 'PantheonAccountsServicePlugin.vala', 'Switcher.vala', 'Utils.vala', + 'GestureDemo/DemoWindow.vala', + 'GestureDemo/MultitaskingDemo.vala', + 'GestureDemo/SwitchWorkspaceDemo.vala', 'Views/AbstractOnboardingView.vala', 'Views/AppCenterView.vala', 'Views/EarlyAccessView.vala', @@ -17,6 +20,7 @@ vala_files = [ 'Views/UpdatesView.vala', 'Views/WelcomeView.vala', 'Views/WhatsNewView.vala', + 'Views/MultitouchView.vala', ] config_data = configuration_data()