[gnome-games/wip/exalm/rebrand] Initial GTK4 and libadwaita port



commit 7100525dcec6e99dda85ffb02046735aee41fad5
Author: Alexander Mikhaylenko <alexm gnome org>
Date:   Sat Jun 19 20:16:45 2021 +0500

    Initial GTK4 and libadwaita port

 flatpak/org.gnome.Games.json                       |  37 ++
 meson.build                                        |   7 +-
 src/PROGRESS                                       |  16 +
 src/core/cover-loader.vala                         |  93 ++--
 src/gamepad/gamepad-mapper.ui                      |  11 +-
 src/gamepad/gamepad-mapper.vala                    |   2 +-
 src/gamepad/gamepad-tester.ui                      |  11 +-
 src/gamepad/gamepad-tester.vala                    |   2 +-
 src/gtk-style.css                                  |  24 +-
 .../actions}/screen-layout-left-right-symbolic.svg |   0
 .../screen-layout-quick-switch-symbolic.svg        |   0
 .../actions}/screen-layout-right-left-symbolic.svg |   0
 .../actions}/screen-layout-top-bottom-symbolic.svg |   0
 .../actions}/view-bottom-screen-symbolic.svg       |   0
 .../actions}/view-sidebar-symbolic-rtl.svg         |   0
 .../actions}/view-sidebar-symbolic.svg             |   0
 .../actions}/view-top-screen-symbolic.svg          |   0
 src/keyboard/keyboard-mapper.ui                    |  12 +-
 src/keyboard/keyboard-mapper.vala                  |  30 +-
 src/keyboard/keyboard-tester.ui                    |  12 +-
 src/keyboard/keyboard-tester.vala                  |  40 +-
 src/meson.build                                    |   6 +-
 src/org.gnome.Games.gresource.xml                  |  17 +-
 .../nintendo-3ds/nintendo-3ds-runner.vala          |   2 +-
 .../nintendo-64/nintendo-64-pak-controller.ui      |  42 --
 .../nintendo-64/nintendo-64-pak-controller.vala    |  44 --
 .../nintendo-64/nintendo-64-pak-switcher.ui        |  45 +-
 .../nintendo-64/nintendo-64-pak-switcher.vala      |  60 ++-
 src/platforms/nintendo-ds/nintendo-ds-runner.vala  |   2 +-
 src/preferences/preferences-page-controllers.ui    |   8 +-
 src/preferences/preferences-page-controllers.vala  |  29 +-
 src/preferences/preferences-page-platform-row.vala |  31 +-
 src/preferences/preferences-page-platforms.ui      |   6 +-
 src/preferences/preferences-page-platforms.vala    |   5 +-
 src/preferences/preferences-page-video.ui          |   5 +-
 src/preferences/preferences-page-video.vala        |  20 +-
 src/preferences/preferences-subpage-gamepad.ui     | 116 ++---
 src/preferences/preferences-subpage-gamepad.vala   |  20 +-
 src/preferences/preferences-subpage-keyboard.ui    | 119 ++---
 src/preferences/preferences-subpage-keyboard.vala  |  16 +-
 src/preferences/preferences-window.ui              |  14 +-
 src/preferences/preferences-window.vala            |   2 +-
 src/screen-layout/screen-layout-item.ui            |   6 -
 src/screen-layout/screen-layout-switcher.ui        |  63 +--
 src/screen-layout/screen-layout-switcher.vala      |  16 +-
 src/ui/application-window.ui                       |  39 +-
 src/ui/application-window.vala                     | 127 +----
 src/ui/application.vala                            |  68 ++-
 src/ui/checkmark-item.ui                           |   9 +-
 src/ui/checkmark-item.vala                         |   2 +-
 src/ui/collection-action-window.ui                 | 178 +++----
 src/ui/collection-action-window.vala               |  70 +--
 src/ui/collection-icon-view.ui                     |  94 ++--
 src/ui/collection-icon-view.vala                   |  23 +-
 src/ui/collection-list-item.ui                     |   4 +-
 src/ui/collection-list-item.vala                   |   2 +-
 src/ui/collection-thumbnail.ui                     |  54 +--
 src/ui/collection-thumbnail.vala                   |  30 +-
 src/ui/collection-view.ui                          | 528 +++++++--------------
 src/ui/collection-view.vala                        |  93 ++--
 src/ui/collections-main-page.ui                    |  45 +-
 src/ui/collections-main-page.vala                  |  45 +-
 src/ui/collections-page.ui                         |  17 +-
 src/ui/collections-page.vala                       |  32 +-
 src/ui/display-view.ui                             | 198 ++------
 src/ui/display-view.vala                           |  55 +--
 src/ui/error-info-bar.ui                           |  20 +-
 src/ui/error-info-bar.vala                         |   2 +-
 src/ui/flash-box.vala                              |  15 +-
 src/ui/fullscreen-box.ui                           |  15 +-
 src/ui/fullscreen-box.vala                         |  37 +-
 src/ui/game-icon-view.ui                           | 118 +++--
 src/ui/game-icon-view.vala                         |  20 +-
 src/ui/game-thumbnail.vala                         | 161 ++-----
 src/ui/gamepad-view.vala                           |  56 ++-
 src/ui/games-page.ui                               |  46 +-
 src/ui/games-page.vala                             |  49 +-
 src/ui/help-overlay.ui                             |  34 --
 src/ui/input-mode-switcher.ui                      |  35 +-
 src/ui/input-mode-switcher.vala                    |   4 +-
 src/ui/konami-code.vala                            |  11 +-
 src/ui/media-menu-button.ui                        |  58 +--
 src/ui/media-menu-button.vala                      |  17 +-
 src/ui/platform-list-item.ui                       |   2 -
 src/ui/platforms-page.ui                           |  40 +-
 src/ui/platforms-page.vala                         |  18 +-
 src/ui/popover-bin.vala                            |  79 +++
 src/ui/search-bar.ui                               |  25 +-
 src/ui/search-bar.vala                             |  11 +-
 src/ui/selection-action-bar.ui                     | 102 ++--
 src/ui/selection-action-bar.vala                   |   7 +-
 src/ui/snapshot-row.ui                             | 158 +++---
 src/ui/snapshot-row.vala                           | 113 ++++-
 src/ui/snapshot-thumbnail.vala                     | 119 ++---
 src/ui/snapshots-list.ui                           | 126 +----
 src/ui/snapshots-list.vala                         | 110 +----
 src/ui/ui-view.vala                                |   7 +-
 src/ui/undo-notification.ui                        |  23 +-
 src/ui/undo-notification.vala                      |   7 +-
 99 files changed, 1766 insertions(+), 2583 deletions(-)
---
diff --git a/flatpak/org.gnome.Games.json b/flatpak/org.gnome.Games.json
index a0f66819..bfc726ab 100644
--- a/flatpak/org.gnome.Games.json
+++ b/flatpak/org.gnome.Games.json
@@ -159,6 +159,43 @@
                 "/include"
             ]
         },
+        {
+            "name" : "libsass",
+            "buildsystem" : "meson",
+            "sources" : [
+                {
+                    "type" : "git",
+                    "url" : "https://github.com/lazka/libsass.git";,
+                    "branch" : "meson"
+                }
+            ]
+        },
+        {
+            "name" : "sassc",
+            "buildsystem" : "meson",
+            "sources" : [
+                {
+                    "type" : "git",
+                    "url" : "https://github.com/lazka/sassc.git";,
+                    "branch" : "meson"
+                }
+            ]
+        },
+        {
+            "name" : "libadwaita",
+            "buildsystem" : "meson",
+            "config-opts" : [
+                "-Dtests=false",
+                "-Dexamples=false"
+            ],
+            "sources" : [
+                {
+                    "type" : "git",
+                    "url" : "https://gitlab.gnome.org/GNOME/libadwaita.git";,
+                    "branch" : "main"
+                }
+            ]
+        },
         {
             "name" : "libevdev",
             "buildsystem" : "meson",
diff --git a/meson.build b/meson.build
index 7b4a1b08..ab43ce0f 100644
--- a/meson.build
+++ b/meson.build
@@ -29,7 +29,6 @@ cc = meson.get_compiler ('c')
 valac = meson.get_compiler ('vala')
 
 glib_min_version = '2.38'
-handy_min_version = '1.1.90'
 manette_min_version = '0.2.0'
 retro_gtk_min_version = '1.0.0'
 
@@ -37,11 +36,11 @@ archive_dep = dependency ('libarchive')
 gio_dep = dependency ('gio-2.0', version: '>=' + glib_min_version)
 glib_dep = dependency ('glib-2.0', version: '>=' + glib_min_version)
 grilo_dep = dependency ('grilo-0.3')
-gtk_dep = dependency ('gtk+-3.0')
-handy_dep = dependency ('libhandy-1', version: '>=' + handy_min_version)
+gtk_dep = dependency ('gtk4')
+libadwaita_dep = dependency ('libadwaita-1')
 m_dep = cc.find_library('m', required : false)
 manette_dep = dependency ('manette-0.2', version: '>=' + manette_min_version)
-retro_gtk_dep = dependency ('retro-gtk-1', version: '>=' + retro_gtk_min_version)
+retro_gtk_dep = dependency ('retro-gtk-2', version: '>=' + retro_gtk_min_version)
 rsvg_dep = dependency ('librsvg-2.0')
 soup_dep = dependency ('libsoup-2.4')
 sqlite_dep = dependency ('sqlite3')
diff --git a/src/PROGRESS b/src/PROGRESS
new file mode 100644
index 00000000..e8080fc7
--- /dev/null
+++ b/src/PROGRESS
@@ -0,0 +1,16 @@
+grep for "FIXME GTK4"
+
+platform rows spawn unref issues, are missing subtitles - looks like libadwaita bug
+gamepad view crashes
+pref window crashes if you close it and open again
+search bar entry doesn't expand if clamp isn't expanded, it should propagate expand correctly
+cover emblem only renders with default color
+selection checks aren't well visible on top of covers
+media button, layout switcher, n64 pak switcher - need an arrow
+(org.gnome.GamesDevel:2): Gtk-CRITICAL **: 17:26:31.473: gtk_button_set_icon_name: assertion 'icon_name != 
NULL' failed
+missing media button.active
+game shortcuts don't work, event alt+arrows and runner shortcuts
+need to restore the keymap thing everywhere, or move to shortcuts. And there's no way to get group?!
+wrong row selected after snapshot rename
+commented out action lookups there
+snapshot row - wrapping date messes things up, replaced with ellipsize for now
diff --git a/src/core/cover-loader.vala b/src/core/cover-loader.vala
index 068f3dcc..10a5df14 100644
--- a/src/core/cover-loader.vala
+++ b/src/core/cover-loader.vala
@@ -5,7 +5,7 @@ public class Games.CoverLoader : Object {
        const double SHADOW_FACTOR = 20.0 / 128;
        const uint TINY_ICON_SIZE = 32;
 
-       public delegate void CoverReadyCallback (int scale_factor, int cover_size, Gdk.Pixbuf? cover_pixbuf, 
int icon_size, Gdk.Pixbuf? icon_pixbuf);
+       public delegate void CoverReadyCallback (int scale_factor, int cover_size, Gdk.Paintable? 
cover_paintable, int icon_size, Gdk.Paintable? icon_paintable);
 
        private struct CoverRequest {
                Game game;
@@ -23,57 +23,64 @@ public class Games.CoverLoader : Object {
                thread = new Thread<void> (null, run_loader_thread);
        }
 
-       private void run_callback (CoverRequest request, int scale_factor, int cover_size, Gdk.Pixbuf? 
cover_pixbuf, int icon_size, Gdk.Pixbuf? icon_pixbuf) {
+       private void run_callback (CoverRequest request, int scale_factor, int cover_size, Gdk.Paintable? 
cover_paintable, int icon_size, Gdk.Paintable? icon_paintable) {
                Idle.add (() => {
-                       request.cb (scale_factor, cover_size, cover_pixbuf, icon_size, icon_pixbuf);
+                       request.cb (scale_factor, cover_size, cover_paintable, icon_size, icon_paintable);
                        return Source.REMOVE;
                });
        }
 
-       private Gdk.Pixbuf? try_load_cover (Game game, int size, int scale_factor) {
-               var pixbuf = load_cache_from_disk (game, size, scale_factor, "covers");
-               if (pixbuf != null)
-                       return pixbuf;
+       private Gdk.Paintable? try_load_cover (Game game, int size, int scale_factor) {
+               var paintable = load_cache_from_disk (game, size, scale_factor, "covers");
+               if (paintable != null)
+                       return paintable;
 
                var file = game.get_cover ().get_cover ();
 
-               if (file != null) {
-                       pixbuf = create_cover_thumbnail (file, size, scale_factor);
-                       save_cache_to_disk (game, pixbuf, size, scale_factor, "covers");
-               }
+               if (file == null)
+                       return null;
 
-               return pixbuf;
+               var pixbuf = create_cover_thumbnail (file, size, scale_factor);
+               save_cache_to_disk (game, pixbuf, size, scale_factor, "covers");
+
+               return Gdk.Texture.for_pixbuf (pixbuf);
        }
 
-       private Gdk.Pixbuf? try_load_icon (Game game, int size, int scale_factor) {
-               var pixbuf = load_cache_from_disk (game, size, scale_factor, "icons");
-               if (pixbuf != null)
-                       return pixbuf;
+       private Gdk.Paintable? try_load_icon (Game game, int size, int scale_factor) {
+               var paintable = load_cache_from_disk (game, size, scale_factor, "icons");
+               if (paintable != null)
+                       return paintable;
 
                var icon = game.get_icon ().get_icon ();
                if (icon == null)
                        return null;
 
-               var theme = Gtk.IconTheme.get_default ();
-               var lookup_flags = Gtk.IconLookupFlags.FORCE_SIZE | Gtk.IconLookupFlags.FORCE_REGULAR;
-               var icon_info = theme.lookup_by_gicon (icon, (int) size, lookup_flags);
+               var display = Gdk.Display.get_default ();
+               var theme = Gtk.IconTheme.get_for_display (display);
+               var lookup_flags = Gtk.IconLookupFlags.FORCE_REGULAR;
+               paintable = theme.lookup_by_gicon (icon, -1, -1, Gtk.TextDirection.LTR, lookup_flags);
 
-               if (icon_info == null)
+               if (paintable == null)
                        return null;
 
-               try {
-                       if (icon is Gdk.Pixbuf && ((Gdk.Pixbuf) icon).get_width () <= TINY_ICON_SIZE)
-                               pixbuf = ((Gdk.Pixbuf) icon).scale_simple (size * scale_factor, size * 
scale_factor, Gdk.InterpType.NEAREST);
-                       else
-                               pixbuf = icon_info.load_icon ();
-                       save_cache_to_disk (game, pixbuf, size, scale_factor, "icons");
-               }
-               catch (Error e) {
-                       critical ("Couldn’t load the icon: %s", e.message);
-                       return null;
-               }
+               var width = paintable.get_intrinsic_width ();
+               var height = paintable.get_intrinsic_height ();
+               var image_surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, width, height);
+               var cr = new Cairo.Context (image_surface);
+
+               var snapshot = new Gtk.Snapshot ();
+               paintable.snapshot (snapshot, width, height);
+               var node = snapshot.free_to_node ();
+               node.draw (cr);
+
+               var pixbuf = Gdk.pixbuf_get_from_surface (image_surface, 0, 0, width, height);
 
-               return pixbuf;
+               if (width <= TINY_ICON_SIZE)
+                       pixbuf = pixbuf.scale_simple (size, size, Gdk.InterpType.NEAREST);
+
+               save_cache_to_disk (game, pixbuf, size, scale_factor, "icons");
+
+               return Gdk.Texture.for_pixbuf (pixbuf);
        }
 
        private void run_loader_thread () {
@@ -84,15 +91,15 @@ public class Games.CoverLoader : Object {
                        var cover_size = request.cover_size;
                        var icon_size = request.icon_size;
 
-                       var cover_pixbuf = try_load_cover (game, cover_size, scale_factor);
-                       if (cover_pixbuf != null)
-                               run_callback (request, scale_factor, cover_size, cover_pixbuf, icon_size, 
null);
+                       var cover_paintable = try_load_cover (game, cover_size, scale_factor);
+                       if (cover_paintable != null)
+                               run_callback (request, scale_factor, cover_size, cover_paintable, icon_size, 
null);
 
-                       var icon_pixbuf = try_load_icon (game, icon_size, scale_factor);
+                       var icon_paintable = try_load_icon (game, icon_size, scale_factor);
 
                        run_callback (request, scale_factor,
-                                     cover_size, cover_pixbuf,
-                                     icon_size, icon_pixbuf);
+                                     cover_size, cover_paintable,
+                                     icon_size, icon_paintable);
                }
        }
 
@@ -104,11 +111,11 @@ public class Games.CoverLoader : Object {
                return @"$dir/$uid.png";
        }
 
-       private Gdk.Pixbuf? load_cache_from_disk (Game game, int size, int scale_factor, string dir) {
+       private Gdk.Paintable? load_cache_from_disk (Game game, int size, int scale_factor, string dir) {
                var cache_path = get_cache_path (game, size, scale_factor, dir);
 
                try {
-                       return new Gdk.Pixbuf.from_file (cache_path);
+                       return Gdk.Texture.from_file (File.new_for_path (cache_path));
                }
                catch (Error e) {
                        return null;
@@ -151,7 +158,11 @@ public class Games.CoverLoader : Object {
                cr.clip ();
 
                var subpixbuf = new Gdk.Pixbuf.subpixbuf (pixbuf, x, y, w, h);
-               var surface = Gdk.cairo_surface_create_from_pixbuf (subpixbuf, 0, null);
+               var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, w, h);
+               var surface_cr = new Cairo.Context (surface);
+               Gdk.cairo_set_source_pixbuf (surface_cr, subpixbuf, 0, 0);
+               surface_cr.paint ();
+
                CairoBlur.blur_surface (surface, radius);
                cr.set_source_surface (surface, 0, 0);
                cr.paint ();
diff --git a/src/gamepad/gamepad-mapper.ui b/src/gamepad/gamepad-mapper.ui
index 28c298ce..f0bcb1d8 100644
--- a/src/gamepad/gamepad-mapper.ui
+++ b/src/gamepad/gamepad-mapper.ui
@@ -1,13 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesGamepadMapper" parent="GtkBin">
-    <property name="visible">True</property>
+  <template class="GamesGamepadMapper" parent="AdwBin">
     <property name="vexpand">True</property>
-    <child>
-      <object class="GamesGamepadView" id="gamepad_view">
-        <property name="visible">True</property>
-      </object>
-    </child>
+    <property name="child">
+      <object class="GamesGamepadView" id="gamepad_view"/>
+    </property>
   </template>
 </interface>
diff --git a/src/gamepad/gamepad-mapper.vala b/src/gamepad/gamepad-mapper.vala
index 9538a18c..9754b389 100644
--- a/src/gamepad/gamepad-mapper.vala
+++ b/src/gamepad/gamepad-mapper.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/gamepad/gamepad-mapper.ui")]
-private class Games.GamepadMapper : Gtk.Bin {
+private class Games.GamepadMapper : Adw.Bin {
        private const double ANALOG_ANIMATION_SPEED = 166660.0;
 
        public signal void finished (string sdl_string);
diff --git a/src/gamepad/gamepad-tester.ui b/src/gamepad/gamepad-tester.ui
index c1fd209e..76b6da7d 100644
--- a/src/gamepad/gamepad-tester.ui
+++ b/src/gamepad/gamepad-tester.ui
@@ -1,13 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesGamepadTester" parent="GtkBin">
-    <property name="visible">True</property>
+  <template class="GamesGamepadTester" parent="AdwBin">
     <property name="vexpand">True</property>
-    <child>
-      <object class="GamesGamepadView" id="gamepad_view">
-        <property name="visible">True</property>
-      </object>
-    </child>
+    <property name="child">
+      <object class="GamesGamepadView" id="gamepad_view"/>
+    </property>
   </template>
 </interface>
diff --git a/src/gamepad/gamepad-tester.vala b/src/gamepad/gamepad-tester.vala
index 96bf7ae6..19c1219d 100644
--- a/src/gamepad/gamepad-tester.vala
+++ b/src/gamepad/gamepad-tester.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/gamepad/gamepad-tester.ui")]
-private class Games.GamepadTester : Gtk.Bin {
+private class Games.GamepadTester : Adw.Bin {
        [GtkChild]
        private unowned GamepadView gamepad_view;
 
diff --git a/src/gtk-style.css b/src/gtk-style.css
index a04dbe40..c473d450 100644
--- a/src/gtk-style.css
+++ b/src/gtk-style.css
@@ -37,7 +37,6 @@
 
 .pill-button {
   border-radius: 9999px;
-  -gtk-outline-radius: 9999px;
   padding: 6px 32px;
 }
 
@@ -46,7 +45,7 @@
 }
 
 .collection-icon-view:disabled {
-  opacity: 0.2;
+  filter: opacity(.2);
   background: none;
 }
 
@@ -69,27 +68,29 @@ gamescollectionsmainpage.large grid {
 gamescollectionsmainpage grid widget {
   background: alpha(black, 0.2);
   border-radius: 6px;
-  border: 1px solid @borders;
+  box-shadow: inset 0 0 0 1px @borders;
 }
 
 
 gamesgamethumbnail {
   background-color: mix(@theme_base_color, @theme_bg_color, 0.5);
   border-radius: 6px;
-  border: 1px solid @borders;
+  box-shadow: inset 0 0 0 1px @borders;
 }
 
 gamesgamethumbnail.cover {
   background-color: black;
-  border: none;
+  box-shadow: none;
 }
 
-gamesgamespage flowboxchild {
+gamesgamespage gamesgamethumbnail {
   min-width: 128px;
+  min-height: 128px;
 }
 
-gamesgamespage.large flowboxchild {
+gamesgamespage.large gamesgamethumbnail {
   min-width: 256px;
+  min-height: 256px;
 }
 
 gamesflashbox {
@@ -136,3 +137,12 @@ flap.titlebar-box shadow {
   min-width: 0;
   min-height: 0;
 }
+
+headerbar.selection-mode {
+  background: @blue_5;
+  color: white;
+}
+
+.numeric {
+  font-feature-settings: "tnum";
+}
diff --git a/src/icons/screen-layout-left-right-symbolic.svg 
b/src/icons/scalable/actions/screen-layout-left-right-symbolic.svg
similarity index 100%
rename from src/icons/screen-layout-left-right-symbolic.svg
rename to src/icons/scalable/actions/screen-layout-left-right-symbolic.svg
diff --git a/src/icons/screen-layout-quick-switch-symbolic.svg 
b/src/icons/scalable/actions/screen-layout-quick-switch-symbolic.svg
similarity index 100%
rename from src/icons/screen-layout-quick-switch-symbolic.svg
rename to src/icons/scalable/actions/screen-layout-quick-switch-symbolic.svg
diff --git a/src/icons/screen-layout-right-left-symbolic.svg 
b/src/icons/scalable/actions/screen-layout-right-left-symbolic.svg
similarity index 100%
rename from src/icons/screen-layout-right-left-symbolic.svg
rename to src/icons/scalable/actions/screen-layout-right-left-symbolic.svg
diff --git a/src/icons/screen-layout-top-bottom-symbolic.svg 
b/src/icons/scalable/actions/screen-layout-top-bottom-symbolic.svg
similarity index 100%
rename from src/icons/screen-layout-top-bottom-symbolic.svg
rename to src/icons/scalable/actions/screen-layout-top-bottom-symbolic.svg
diff --git a/src/icons/view-bottom-screen-symbolic.svg 
b/src/icons/scalable/actions/view-bottom-screen-symbolic.svg
similarity index 100%
rename from src/icons/view-bottom-screen-symbolic.svg
rename to src/icons/scalable/actions/view-bottom-screen-symbolic.svg
diff --git a/src/icons/view-sidebar-symbolic-rtl.svg 
b/src/icons/scalable/actions/view-sidebar-symbolic-rtl.svg
similarity index 100%
rename from src/icons/view-sidebar-symbolic-rtl.svg
rename to src/icons/scalable/actions/view-sidebar-symbolic-rtl.svg
diff --git a/src/icons/view-sidebar-symbolic.svg b/src/icons/scalable/actions/view-sidebar-symbolic.svg
similarity index 100%
rename from src/icons/view-sidebar-symbolic.svg
rename to src/icons/scalable/actions/view-sidebar-symbolic.svg
diff --git a/src/icons/view-top-screen-symbolic.svg b/src/icons/scalable/actions/view-top-screen-symbolic.svg
similarity index 100%
rename from src/icons/view-top-screen-symbolic.svg
rename to src/icons/scalable/actions/view-top-screen-symbolic.svg
diff --git a/src/keyboard/keyboard-mapper.ui b/src/keyboard/keyboard-mapper.ui
index 34d88b54..91f85574 100644
--- a/src/keyboard/keyboard-mapper.ui
+++ b/src/keyboard/keyboard-mapper.ui
@@ -1,13 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesKeyboardMapper" parent="GtkBin">
-    <property name="visible">True</property>
+  <template class="GamesKeyboardMapper" parent="AdwBin">
+    <property name="focusable">True</property>
     <property name="vexpand">True</property>
-    <child>
-      <object class="GamesGamepadView" id="gamepad_view">
-        <property name="visible">True</property>
-      </object>
-    </child>
+    <property name="child">
+      <object class="GamesGamepadView" id="gamepad_view"/>
+    </property>
   </template>
 </interface>
diff --git a/src/keyboard/keyboard-mapper.vala b/src/keyboard/keyboard-mapper.vala
index 75e052a0..12a1a0a1 100644
--- a/src/keyboard/keyboard-mapper.vala
+++ b/src/keyboard/keyboard-mapper.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/keyboard/keyboard-mapper.ui")]
-private class Games.KeyboardMapper : Gtk.Bin {
+private class Games.KeyboardMapper : Adw.Bin {
        public signal void finished (Retro.KeyJoypadMapping mapping);
 
        [GtkChild]
@@ -12,6 +12,8 @@ private class Games.KeyboardMapper : Gtk.Bin {
        private GamepadInput input;
        private uint current_input_index;
 
+       private Gtk.EventControllerKey? controller;
+
        public string info_message { get; private set; }
 
        private GamepadViewConfiguration _configuration;
@@ -53,18 +55,28 @@ private class Games.KeyboardMapper : Gtk.Bin {
        }
 
        private void connect_to_keyboard () {
-               get_toplevel ().key_release_event.connect (on_keyboard_event);
-       }
+               if (controller != null)
+                       return;
 
-       private void disconnect_from_keyboard () {
-               get_toplevel ().key_release_event.disconnect (on_keyboard_event);
+               controller = new Gtk.EventControllerKey ();
+
+               controller.key_pressed.connect (() => {
+                       return Gdk.EVENT_STOP;
+               });
+               controller.key_released.connect ((keyval, keycode, state) => {
+                       if (mapping_builder.set_input_mapping (input, (uint16) keycode))
+                               next_input ();
+               });
+
+               add_controller (controller);
        }
 
-       private bool on_keyboard_event (Gdk.EventKey key) {
-               if (mapping_builder.set_input_mapping (input, key.hardware_keycode))
-                       next_input ();
+       private void disconnect_from_keyboard () {
+               if (controller == null)
+                       return;
 
-               return true;
+               remove_controller (controller);
+               controller = null;
        }
 
        private void next_input () {
diff --git a/src/keyboard/keyboard-tester.ui b/src/keyboard/keyboard-tester.ui
index c9f51e7e..5987df91 100644
--- a/src/keyboard/keyboard-tester.ui
+++ b/src/keyboard/keyboard-tester.ui
@@ -1,13 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesKeyboardTester" parent="GtkBin">
-    <property name="visible">True</property>
+  <template class="GamesKeyboardTester" parent="AdwBin">
+    <property name="focusable">True</property>
     <property name="vexpand">True</property>
-    <child>
-      <object class="GamesGamepadView" id="gamepad_view">
-        <property name="visible">True</property>
-      </object>
-    </child>
+    <property name="child">
+      <object class="GamesGamepadView" id="gamepad_view"/>
+    </property>
   </template>
 </interface>
diff --git a/src/keyboard/keyboard-tester.vala b/src/keyboard/keyboard-tester.vala
index c4963997..690d6522 100644
--- a/src/keyboard/keyboard-tester.vala
+++ b/src/keyboard/keyboard-tester.vala
@@ -1,10 +1,12 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/keyboard/keyboard-tester.ui")]
-private class Games.KeyboardTester : Gtk.Bin {
+private class Games.KeyboardTester : Adw.Bin {
        [GtkChild]
        private unowned GamepadView gamepad_view;
 
+       private Gtk.EventControllerKey? controller;
+
        public Retro.KeyJoypadMapping mapping { get; set; }
 
        private GamepadViewConfiguration _configuration;
@@ -30,33 +32,35 @@ private class Games.KeyboardTester : Gtk.Bin {
        }
 
        private void connect_to_keyboard () {
-               var window = get_toplevel ();
-               window.key_press_event.connect (on_key_press_event);
-               window.key_release_event.connect (on_key_release_event);
-       }
+               if (controller != null)
+                       return;
 
-       private void disconnect_from_keyboard () {
-               var window = get_toplevel ();
-               window.key_press_event.disconnect (on_key_press_event);
-               window.key_release_event.disconnect (on_key_release_event);
-       }
+               controller = new Gtk.EventControllerKey ();
+
+               controller.key_pressed.connect ((keyval, keycode, state) => {
+                       update_gamepad_view (keycode, true);
 
-       private bool on_key_press_event (Gdk.EventKey key) {
-               update_gamepad_view (key, true);
+                       return Gdk.EVENT_STOP;
+               });
+               controller.key_released.connect ((keyval, keycode, state) => {
+                       update_gamepad_view (keycode, false);
+               });
 
-               return true;
+               add_controller (controller);
        }
 
-       private bool on_key_release_event (Gdk.EventKey key) {
-               update_gamepad_view (key, false);
+       private void disconnect_from_keyboard () {
+               if (controller == null)
+                       return;
 
-               return true;
+               remove_controller (controller);
+               controller = null;
        }
 
-       private void update_gamepad_view (Gdk.EventKey key, bool highlight) {
+       private void update_gamepad_view (uint keycode, bool highlight) {
                int count = Retro.ControllerType.JOYPAD.get_id_count ();
                for (Retro.JoypadId joypad_id = 0; joypad_id < count; joypad_id += 1) {
-                       if (mapping.get_button_key (joypad_id) == key.hardware_keycode) {
+                       if (mapping.get_button_key (joypad_id) == keycode) {
                                var code = joypad_id.to_button_code ();
                                gamepad_view.highlight ({ EventCode.EV_KEY, code }, highlight);
                        }
diff --git a/src/meson.build b/src/meson.build
index 6b2629d9..03aecf3c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -92,7 +92,6 @@ vala_sources = [
   'platforms/nintendo-3ds/nintendo-3ds-runner.vala',
 
   'platforms/nintendo-64/nintendo-64-pak.vala',
-  'platforms/nintendo-64/nintendo-64-pak-controller.vala',
   'platforms/nintendo-64/nintendo-64-pak-switcher.vala',
   'platforms/nintendo-64/nintendo-64-runner.vala',
   'platforms/nintendo-64/nintendo-64-snapshot.vala',
@@ -169,16 +168,17 @@ vala_sources = [
   'ui/fullscreen-box.vala',
   'ui/gamepad-browse.vala',
   'ui/gamepad-view.vala',
+  'ui/gamepad-view-configuration.vala',
   'ui/games-page.vala',
   'ui/game-icon-view.vala',
   'ui/game-thumbnail.vala',
-  'ui/gamepad-view-configuration.vala',
   'ui/header-bar-widget.vala',
   'ui/input-mode-switcher.vala',
   'ui/konami-code.vala',
   'ui/media-menu-button.vala',
   'ui/platform-list-item.vala',
   'ui/platforms-page.vala',
+  'ui/popover-bin.vala',
   'ui/search-bar.vala',
   'ui/selection-action-bar.vala',
   'ui/snapshot-row.vala',
@@ -230,7 +230,7 @@ dependencies = [
   glib_dep,
   grilo_dep,
   gtk_dep,
-  handy_dep,
+  libadwaita_dep,
   m_dep,
   manette_dep,
   retro_gtk_dep,
diff --git a/src/org.gnome.Games.gresource.xml b/src/org.gnome.Games.gresource.xml
index 876ef05a..954b59f2 100644
--- a/src/org.gnome.Games.gresource.xml
+++ b/src/org.gnome.Games.gresource.xml
@@ -7,14 +7,14 @@
     <file preprocess="xml-stripblanks">gamepad/gamepad-mapper.ui</file>
     <file preprocess="xml-stripblanks">gamepad/gamepad-tester.ui</file>
 
-    <file preprocess="xml-stripblanks">icons/screen-layout-left-right-symbolic.svg</file>
-    <file preprocess="xml-stripblanks">icons/screen-layout-quick-switch-symbolic.svg</file>
-    <file preprocess="xml-stripblanks">icons/screen-layout-right-left-symbolic.svg</file>
-    <file preprocess="xml-stripblanks">icons/screen-layout-top-bottom-symbolic.svg</file>
-    <file preprocess="xml-stripblanks">icons/view-bottom-screen-symbolic.svg</file>
-    <file preprocess="xml-stripblanks">icons/view-sidebar-symbolic.svg</file>
-    <file preprocess="xml-stripblanks">icons/view-sidebar-symbolic-rtl.svg</file>
-    <file preprocess="xml-stripblanks">icons/view-top-screen-symbolic.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/screen-layout-left-right-symbolic.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/screen-layout-quick-switch-symbolic.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/screen-layout-right-left-symbolic.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/screen-layout-top-bottom-symbolic.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/view-bottom-screen-symbolic.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/view-sidebar-symbolic.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/view-sidebar-symbolic-rtl.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/view-top-screen-symbolic.svg</file>
 
     <file preprocess="xml-stripblanks">gesture/button-east-symbolic.svg</file>
     <file preprocess="xml-stripblanks">gesture/button-home-symbolic.svg</file>
@@ -31,7 +31,6 @@
 
     <file>platforms/mame/supported-games</file>
 
-    <file preprocess="xml-stripblanks">platforms/nintendo-64/nintendo-64-pak-controller.ui</file>
     <file preprocess="xml-stripblanks">platforms/nintendo-64/nintendo-64-pak-switcher.ui</file>
 
     <file>platforms/nintendo-ds/layout-overrides</file>
diff --git a/src/platforms/nintendo-3ds/nintendo-3ds-runner.vala 
b/src/platforms/nintendo-3ds/nintendo-3ds-runner.vala
index da64c07e..8ffe20ee 100644
--- a/src/platforms/nintendo-3ds/nintendo-3ds-runner.vala
+++ b/src/platforms/nintendo-3ds/nintendo-3ds-runner.vala
@@ -86,7 +86,7 @@ private class Games.Nintendo3DsRunner : Runner {
        }
 
        public override bool key_press_event (uint keyval, Gdk.ModifierType state) {
-               if (state == Gdk.ModifierType.MOD1_MASK) {
+               if (state == Gdk.ModifierType.ALT_MASK) {
                        // Alt + 1|2|3|4
                        var shortcut_layout = layouts[keyval];
                        if (shortcut_layout != null) {
diff --git a/src/platforms/nintendo-64/nintendo-64-pak-switcher.ui 
b/src/platforms/nintendo-64/nintendo-64-pak-switcher.ui
index 6f98325f..f3189a28 100644
--- a/src/platforms/nintendo-64/nintendo-64-pak-switcher.ui
+++ b/src/platforms/nintendo-64/nintendo-64-pak-switcher.ui
@@ -1,52 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesNintendo64PakSwitcher" parent="GtkBin">
-    <property name="visible">True</property>
+  <template class="GamesNintendo64PakSwitcher" parent="AdwBin">
     <child>
       <object class="GtkMenuButton" id="menu_button">
-        <property name="visible">True</property>
-        <property name="can-focus">False</property>
-        <property name="popover">pak_popover</property>
+        <property name="icon-name">input-gaming-symbolic</property>
+        <property name="tooltip-text" translatable="yes">Controller Expansion</property>
         <signal name="notify::active" handler="on_menu_state_changed"/>
-        <child internal-child="accessible">
-          <object class="AtkObject" id="a11y-display-discs">
-            <property name="accessible-name" translatable="yes">Controller Expansion</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkBox">
-            <property name="visible">True</property>
-            <property name="spacing">6</property>
-            <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="icon-name">input-gaming-symbolic</property>
-              </object>
-            </child>
-            <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="icon-name">pan-down-symbolic</property>
-              </object>
-            </child>
-          </object>
-        </child>
       </object>
     </child>
   </template>
-  <object class="GtkPopover" id="pak_popover">
-    <property name="visible">False</property>
-    <child>
-      <object class="GtkBox" id="controllers_box">
-        <property name="visible">True</property>
-        <property name="orientation">vertical</property>
-        <property name="margin-top">12</property>
-        <property name="margin-bottom">12</property>
-        <property name="margin-start">12</property>
-        <property name="margin-end">12</property>
-        <property name="spacing">6</property>
-      </object>
-    </child>
-  </object>
 </interface>
diff --git a/src/platforms/nintendo-64/nintendo-64-pak-switcher.vala 
b/src/platforms/nintendo-64/nintendo-64-pak-switcher.vala
index 993e6b22..22ffff50 100644
--- a/src/platforms/nintendo-64/nintendo-64-pak-switcher.vala
+++ b/src/platforms/nintendo-64/nintendo-64-pak-switcher.vala
@@ -1,11 +1,9 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/platforms/nintendo-64/nintendo-64-pak-switcher.ui")]
-private class Games.Nintendo64PakSwitcher : Gtk.Bin, HeaderBarWidget {
+private class Games.Nintendo64PakSwitcher : Adw.Bin, HeaderBarWidget {
        [GtkChild]
        private unowned Gtk.MenuButton menu_button;
-       [GtkChild]
-       private unowned Gtk.Box controllers_box;
 
        public Nintendo64Runner runner { get; construct; }
 
@@ -19,7 +17,12 @@ private class Games.Nintendo64PakSwitcher : Gtk.Bin, HeaderBarWidget {
                get { return is_menu_open; }
        }
 
+       private Menu menu;
+
        public override void constructed () {
+               menu = new Menu ();
+               menu_button.menu_model = menu;
+
                update_ui ();
 
                runner.controllers_changed.connect (update_ui);
@@ -40,20 +43,25 @@ private class Games.Nintendo64PakSwitcher : Gtk.Bin, HeaderBarWidget {
                Object (runner: runner);
        }
 
+       static construct {
+               install_property_action ("n64.set_pak1", "pak1");
+               install_property_action ("n64.set_pak2", "pak2");
+               install_property_action ("n64.set_pak3", "pak3");
+               install_property_action ("n64.set_pak4", "pak4");
+       }
+
        [GtkCallback]
        private void on_menu_state_changed () {
-               is_menu_open = menu_button.active;
+// FIXME GTK4          is_menu_open = menu_button.active;
                notify_property ("block-autohide");
        }
 
        private void update_ui () {
-               foreach (var row in controllers_box.get_children ())
-                       controllers_box.remove (row);
+               menu.remove_all ();
 
                var core = runner.get_core ();
                var iterator = core.iterate_controllers ();
 
-               Nintendo64PakController first_widget = null;
                uint n_players = 0;
 
                uint port;
@@ -63,19 +71,39 @@ private class Games.Nintendo64PakSwitcher : Gtk.Bin, HeaderBarWidget {
                                break;
 
                        n_players++;
+               }
 
-                       var widget = new Nintendo64PakController (controller, port);
+               iterator = core.iterate_controllers ();
 
-                       if (first_widget == null)
-                               first_widget = widget;
+               uint i = 0;
 
-                       bind_property (@"pak$(port + 1)", widget, "pak",
-                                      BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
+               while (iterator.next (out port, out controller)) {
+                       if (i > n_players)
+                               break;
 
-                       controllers_box.add (widget);
-               }
+                       i++;
+
+                       var section = new Menu ();
+
+                       var item = new MenuItem (_("Controller Pak"), "n64.set_pak");
+                       item.set_action_and_target (@"n64.set_pak$i", "s", "memory");
+                       section.append_item (item);
+
+                       item = new MenuItem (_("Rumble Pak"), "n64.set_pak");
 
-               if (n_players == 1)
-                       first_widget.show_title = false;
+                       if (controller.get_supports_rumble ()) {
+                               item.set_action_and_target (@"n64.set_pak$i", "s", "rumble");
+                       } else {
+                               // We just want to disable the item
+                               item.set_detailed_action ("n64.empty");
+                       }
+
+                       section.append_item (item);
+
+                       if (n_players > 1)
+                               menu.append_section (_("Player %u").printf (i), section);
+                       else
+                               menu.append_section (null, section);
+               }
        }
 }
diff --git a/src/platforms/nintendo-ds/nintendo-ds-runner.vala 
b/src/platforms/nintendo-ds/nintendo-ds-runner.vala
index 05f7fe10..d26893f4 100644
--- a/src/platforms/nintendo-ds/nintendo-ds-runner.vala
+++ b/src/platforms/nintendo-ds/nintendo-ds-runner.vala
@@ -134,7 +134,7 @@ private class Games.NintendoDsRunner : Runner {
        }
 
        public override bool key_press_event (uint keyval, Gdk.ModifierType state) {
-               if (state == Gdk.ModifierType.MOD1_MASK) {
+               if (state == Gdk.ModifierType.ALT_MASK) {
                        // Alt + 1|2|3|4
                        var shortcut_layout = layouts[keyval];
                        if (shortcut_layout != null) {
diff --git a/src/preferences/preferences-page-controllers.ui b/src/preferences/preferences-page-controllers.ui
index d79b2be4..e460f908 100644
--- a/src/preferences/preferences-page-controllers.ui
+++ b/src/preferences/preferences-page-controllers.ui
@@ -1,18 +1,16 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesPreferencesPageControllers" parent="HdyPreferencesPage">
+  <template class="GamesPreferencesPageControllers" parent="AdwPreferencesPage">
     <property name="title" translatable="yes">Controllers</property>
     <property name="icon-name">applications-games-symbolic</property>
     <child>
-      <object class="HdyPreferencesGroup" id="gamepads_group">
-        <property name="visible">True</property>
+      <object class="AdwPreferencesGroup" id="gamepads_group">
         <property name="title" translatable="yes">Gamepads</property>
       </object>
     </child>
     <child>
-      <object class="HdyPreferencesGroup" id="keyboard_group">
-        <property name="visible">True</property>
+      <object class="AdwPreferencesGroup" id="keyboard_group">
         <property name="title" translatable="yes">Keyboard</property>
       </object>
     </child>
diff --git a/src/preferences/preferences-page-controllers.vala 
b/src/preferences/preferences-page-controllers.vala
index 8e92bcaa..edbfa45d 100644
--- a/src/preferences/preferences-page-controllers.vala
+++ b/src/preferences/preferences-page-controllers.vala
@@ -1,18 +1,21 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/preferences/preferences-page-controllers.ui")]
-private class Games.PreferencesPageControllers : Hdy.PreferencesPage {
+private class Games.PreferencesPageControllers : Adw.PreferencesPage {
        [GtkChild]
-       private unowned Hdy.PreferencesGroup gamepads_group;
+       private unowned Adw.PreferencesGroup gamepads_group;
        [GtkChild]
-       private unowned Hdy.PreferencesGroup keyboard_group;
+       private unowned Adw.PreferencesGroup keyboard_group;
 
        private Manette.Monitor monitor;
 
+       private Adw.ActionRow[] gamepad_rows;
+
        construct {
                monitor = new Manette.Monitor ();
                monitor.device_connected.connect (rebuild_gamepad_list);
                monitor.device_disconnected.connect (rebuild_gamepad_list);
+               gamepad_rows = {};
                build_gamepad_list ();
                build_keyboard_list ();
        }
@@ -28,9 +31,9 @@ private class Games.PreferencesPageControllers : Hdy.PreferencesPage {
                var iterator = monitor.iterate ();
 
                while (iterator.next (out device)) {
-                       var row = new Hdy.ActionRow ();
+                       var row = new Adw.ActionRow ();
                        row.title = device.get_name ();
-                       row.add (new Gtk.Image.from_icon_name ("go-next-symbolic", Gtk.IconSize.BUTTON));
+                       row.add_suffix (new Gtk.Image.from_icon_name ("go-next-symbolic"));
                        row.activatable = true;
 
                        // The original device variable will be overwritten on the
@@ -39,12 +42,12 @@ private class Games.PreferencesPageControllers : Hdy.PreferencesPage {
                        var this_device = device;
 
                        row.activated.connect (() => {
-                               var window = get_toplevel () as PreferencesWindow;
+                               var window = get_root () as PreferencesWindow;
                                window.open_subpage (new PreferencesSubpageGamepad (this_device));
                        });
 
-                       row.show_all ();
                        gamepads_group.add (row);
+                       gamepad_rows += row;
 
                        i += 1;
                }
@@ -53,21 +56,23 @@ private class Games.PreferencesPageControllers : Hdy.PreferencesPage {
        }
 
        private void clear_gamepad_list () {
-               gamepads_group.foreach ((child) => child.destroy ());
+               foreach (var row in gamepad_rows)
+                       gamepads_group.remove (row);
+
+               gamepad_rows = {};
        }
 
        private void build_keyboard_list () {
-               var row = new Hdy.ActionRow ();
+               var row = new Adw.ActionRow ();
                row.title = _("Keyboard");
-               row.add (new Gtk.Image.from_icon_name ("go-next-symbolic", Gtk.IconSize.BUTTON));
+               row.add_suffix (new Gtk.Image.from_icon_name ("go-next-symbolic"));
                row.activatable = true;
 
                row.activated.connect (() => {
-                       var window = get_toplevel () as PreferencesWindow;
+                       var window = get_root () as PreferencesWindow;
                        window.open_subpage (new PreferencesSubpageKeyboard ());
                });
 
-               row.show_all ();
                keyboard_group.add (row);
        }
 }
diff --git a/src/preferences/preferences-page-platform-row.vala 
b/src/preferences/preferences-page-platform-row.vala
index c923594e..812ea249 100644
--- a/src/preferences/preferences-page-platform-row.vala
+++ b/src/preferences/preferences-page-platform-row.vala
@@ -1,10 +1,8 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
-private class Games.PreferencesPagePlatformRow : Hdy.ComboRow {
+private class Games.PreferencesPagePlatformRow : Adw.ComboRow {
        public Platform platform { get; construct; }
 
-       private ListStore model;
-
        public PreferencesPagePlatformRow (Platform platform) {
                Object (platform: platform);
        }
@@ -18,24 +16,27 @@ private class Games.PreferencesPagePlatformRow : Hdy.ComboRow {
                subtitle = _("None");
                use_subtitle = true;
 
-               model = new ListStore (typeof (Retro.CoreDescriptor));
+               var store = new ListStore (typeof (Retro.CoreDescriptor));
 
                var core_manager = CoreManager.get_instance ();
                var cores = core_manager.get_cores_for_platform (platform);
 
-               foreach (var core in cores)
-                       model.append (core);
+               foreach (var core in cores) {
+                       store.append (core);
+                       store.append (core);
+               }
 
-               notify["selected-index"].connect (notify_selected_index_cb);
+               model = store;
 
-               bind_name_model (model, get_core_name);
-       }
+               expression = new Gtk.CClosureExpression (typeof (string),
+                                                        null, {},
+                                                        (Callback) get_core_name,
+                                                        null, null);
 
-       private string get_core_name (Object object) {
-               assert (object is Retro.CoreDescriptor);
-
-               var core = object as Retro.CoreDescriptor;
+               notify["selected"].connect (notify_selected_cb);
+       }
 
+       private static string get_core_name (Retro.CoreDescriptor core) {
                try {
                        return core.get_name ();
                }
@@ -44,8 +45,8 @@ private class Games.PreferencesPagePlatformRow : Hdy.ComboRow {
                }
        }
 
-       private void notify_selected_index_cb () {
-               var core = model.get_item (selected_index) as Retro.CoreDescriptor;
+       private void notify_selected_cb () {
+               var core = model.get_item (selected) as Retro.CoreDescriptor;
 
                if (core == null)
                        return;
diff --git a/src/preferences/preferences-page-platforms.ui b/src/preferences/preferences-page-platforms.ui
index 26f9a490..e9e5dd36 100644
--- a/src/preferences/preferences-page-platforms.ui
+++ b/src/preferences/preferences-page-platforms.ui
@@ -1,13 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesPreferencesPagePlatforms" parent="HdyPreferencesPage">
+  <template class="GamesPreferencesPagePlatforms" parent="AdwPreferencesPage">
     <property name="title" translatable="yes">Platforms</property>
     <property name="icon-name">view-list-bullet-symbolic</property>
     <child>
-      <object class="HdyPreferencesGroup" id="platforms_group">
-        <property name="visible">True</property>
-      </object>
+      <object class="AdwPreferencesGroup" id="platforms_group"/>
     </child>
   </template>
 </interface>
diff --git a/src/preferences/preferences-page-platforms.vala b/src/preferences/preferences-page-platforms.vala
index bd484af2..263b26d1 100644
--- a/src/preferences/preferences-page-platforms.vala
+++ b/src/preferences/preferences-page-platforms.vala
@@ -1,9 +1,9 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/preferences/preferences-page-platforms.ui")]
-private class Games.PreferencesPagePlatforms : Hdy.PreferencesPage {
+private class Games.PreferencesPagePlatforms : Adw.PreferencesPage {
        [GtkChild]
-       private unowned Hdy.PreferencesGroup platforms_group;
+       private unowned Adw.PreferencesGroup platforms_group;
 
        construct {
                var register = PlatformRegister.get_register ();
@@ -11,7 +11,6 @@ private class Games.PreferencesPagePlatforms : Hdy.PreferencesPage {
 
                foreach (var platform in platforms) {
                        var row = new PreferencesPagePlatformRow (platform);
-                       row.show ();
 
                        platforms_group.add (row);
                }
diff --git a/src/preferences/preferences-page-video.ui b/src/preferences/preferences-page-video.ui
index c97636bd..5bd79c45 100644
--- a/src/preferences/preferences-page-video.ui
+++ b/src/preferences/preferences-page-video.ui
@@ -1,12 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesPreferencesPageVideo" parent="HdyPreferencesPage">
+  <template class="GamesPreferencesPageVideo" parent="AdwPreferencesPage">
     <property name="title" translatable="yes">Video</property>
     <property name="icon-name">video-display-symbolic</property>
     <child>
-      <object class="HdyPreferencesGroup" id="filter_group">
-        <property name="visible">True</property>
+      <object class="AdwPreferencesGroup" id="filter_group">
         <property name="title" translatable="yes">Filter</property>
       </object>
     </child>
diff --git a/src/preferences/preferences-page-video.vala b/src/preferences/preferences-page-video.vala
index a28ed6fd..79ebca2f 100644
--- a/src/preferences/preferences-page-video.vala
+++ b/src/preferences/preferences-page-video.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/preferences/preferences-page-video.ui")]
-private class Games.PreferencesPageVideo : Hdy.PreferencesPage {
+private class Games.PreferencesPageVideo : Adw.PreferencesPage {
        private string _filter_active;
        public string filter_active {
                set {
@@ -17,7 +17,7 @@ private class Games.PreferencesPageVideo : Hdy.PreferencesPage {
        }
 
        [GtkChild]
-       private unowned Hdy.PreferencesGroup filter_group;
+       private unowned Adw.PreferencesGroup filter_group;
 
        // same as video-filters in gschema
        /* Translators: These values are video filters applied to the screen. Smooth
@@ -27,17 +27,20 @@ private class Games.PreferencesPageVideo : Hdy.PreferencesPage {
        private string[] filter_names = { "smooth", "sharp", "crt" };
 
        private Settings settings;
-       private Gtk.RadioButton filter_radios[3];
+       private Gtk.CheckButton filter_radios[3];
 
        construct {
                for (var i = 0; i < filter_display_names.length; i++) {
-                       var row = new Hdy.ActionRow ();
+                       var row = new Adw.ActionRow ();
                        row.title = filter_display_names [i];
 
-                       filter_radios[i] = new Gtk.RadioButton.from_widget (filter_radios[0]);
+                       filter_radios[i] = new Gtk.CheckButton ();
+
+                       if (i > 0)
+                               filter_radios[i].group = filter_radios[0];
+
                        filter_radios[i].name = filter_names[i];
                        filter_radios[i].valign = Gtk.Align.CENTER;
-                       filter_radios[i].can_focus = false;
                        filter_radios[i].toggled.connect ((radio) => {
                                if (!radio.active)
                                        return;
@@ -47,11 +50,6 @@ private class Games.PreferencesPageVideo : Hdy.PreferencesPage {
 
                        row.add_prefix (filter_radios[i]);
                        row.activatable_widget = filter_radios[i];
-                       row.show_all ();
-
-                       row.activated.connect (() => {
-                               filter_active = filter_names[i];
-                       });
 
                        filter_group.add (row);
                }
diff --git a/src/preferences/preferences-subpage-gamepad.ui b/src/preferences/preferences-subpage-gamepad.ui
index 53ff0ef1..8d42d030 100644
--- a/src/preferences/preferences-subpage-gamepad.ui
+++ b/src/preferences/preferences-subpage-gamepad.ui
@@ -1,82 +1,46 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesPreferencesSubpageGamepad" parent="GtkBin">
-    <property name="visible">True</property>
+  <template class="GamesPreferencesSubpageGamepad" parent="AdwBin">
     <child>
       <object class="GtkStack" id="stack">
-        <property name="visible">True</property>
         <property name="transition-type">crossfade</property>
         <child>
           <object class="GtkBox" id="tester_box">
-            <property name="visible">True</property>
             <property name="orientation">vertical</property>
             <child>
-              <object class="HdyWindowHandle">
-                <property name="visible">True</property>
-                <child>
-                  <object class="GtkHeaderBar" id="tester_header_bar">
-                    <property name="visible">True</property>
-                    <property name="show-close-button">True</property>
-                    <style>
-                      <class name="titlebar"/>
-                    </style>
-                    <child>
-                      <object class="GtkButton">
-                        <property name="visible">True</property>
-                        <signal name="clicked" handler="on_back_clicked"/>
-                        <style>
-                          <class name="image-button"/>
-                        </style>
-                        <child internal-child="accessible">
-                          <object class="AtkObject" id="a11y-back">
-                            <property name="accessible-name" translatable="yes">Back</property>
-                          </object>
-                        </child>
-                        <child>
-                          <object class="GtkImage">
-                            <property name="visible">True</property>
-                            <property name="icon-name">go-previous-symbolic</property>
-                            <property name="icon-size">1</property>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="pack-type">start</property>
-                      </packing>
-                    </child>
+              <object class="GtkHeaderBar" id="tester_header_bar">
+                <property name="title-widget">
+                  <object class="AdwWindowTitle" id="tester_window_title"/>
+                </property>
+                <child type="start">
+                  <object class="GtkButton">
+                    <property name="icon-name">go-previous-symbolic</property>
+                    <property name="tooltip-text" translatable="yes">Back</property>
+                    <signal name="clicked" handler="on_back_clicked"/>
                   </object>
                 </child>
               </object>
             </child>
             <child>
               <object class="GtkActionBar">
-                <property name="visible">True</property>
-                <child>
-                  <object class="GtkButton" id="reset_button">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes" context="Keyboard configuration factory 
reset">Reset</property>
-                    <signal name="clicked" handler="on_reset_clicked"/>
-                    <style>
-                      <class name="destructive-action"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="pack-type">end</property>
-                  </packing>
-                </child>
-                <child>
+                <child type="start">
                   <object class="GtkButton">
-                    <property name="visible">True</property>
                     <property name="label" translatable="yes">Configure</property>
                     <signal name="clicked" handler="on_configure_clicked"/>
                     <style>
                       <class name="suggested-action"/>
                     </style>
                   </object>
-                  <packing>
-                    <property name="pack-type">start</property>
-                  </packing>
+                </child>
+                <child type="end">
+                  <object class="GtkButton" id="reset_button">
+                    <property name="label" translatable="yes" context="Keyboard configuration factory 
reset">Reset</property>
+                    <signal name="clicked" handler="on_reset_clicked"/>
+                    <style>
+                      <class name="destructive-action"/>
+                    </style>
+                  </object>
                 </child>
               </object>
             </child>
@@ -84,48 +48,34 @@
         </child>
         <child>
           <object class="GtkBox" id="mapper_box">
-            <property name="visible">True</property>
             <property name="orientation">vertical</property>
             <child>
-              <object class="HdyWindowHandle">
-                <property name="visible">True</property>
-                <child>
-                  <object class="GtkHeaderBar" id="mapper_header_bar">
-                    <property name="visible">True</property>
-                    <style>
-                      <class name="titlebar"/>
-                      <class name="selection-mode"/>
-                    </style>
-                    <child>
-                      <object class="GtkButton">
-                        <property name="visible">True</property>
-                        <property name="label" translatable="yes">Cancel</property>
-                        <signal name="clicked" handler="on_cancel_clicked"/>
-                      </object>
-                      <packing>
-                        <property name="pack-type">end</property>
-                      </packing>
-                    </child>
+              <object class="GtkHeaderBar" id="mapper_header_bar">
+                <property name="show-title-buttons">False</property>
+                <style>
+                  <class name="selection-mode"/>
+                </style>
+                <property name="title-widget">
+                  <object class="AdwWindowTitle" id="mapper_window_title"/>
+                </property>
+                <child type="end">
+                  <object class="GtkButton">
+                    <property name="label" translatable="yes">Cancel</property>
+                    <signal name="clicked" handler="on_cancel_clicked"/>
                   </object>
                 </child>
               </object>
             </child>
             <child>
               <object class="GtkActionBar">
-                <property name="visible">True</property>
-                <child>
+                <child type="start">
                   <object class="GtkButton">
-                    <property name="visible">True</property>
                     <property name="label" translatable="yes">Skip</property>
                     <signal name="clicked" handler="on_skip_clicked"/>
                   </object>
-                  <packing>
-                    <property name="pack-type">start</property>
-                  </packing>
                 </child>
                 <child type="center">
                   <object class="GtkLabel">
-                    <property name="visible">True</property>
                     <property name="ellipsize">end</property>
                     <property name="label" bind-source="GamesPreferencesSubpageGamepad" 
bind-property="info-message" bind-flags="bidirectional"/>
                   </object>
diff --git a/src/preferences/preferences-subpage-gamepad.vala 
b/src/preferences/preferences-subpage-gamepad.vala
index e09a02da..6407fd8b 100644
--- a/src/preferences/preferences-subpage-gamepad.vala
+++ b/src/preferences/preferences-subpage-gamepad.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/preferences/preferences-subpage-gamepad.ui")]
-private class Games.PreferencesSubpageGamepad : Gtk.Bin, PreferencesSubpage {
+private class Games.PreferencesSubpageGamepad : Adw.Bin, PreferencesSubpage {
        private const GamepadInput[] STANDARD_GAMEPAD_INPUTS = {
                { EventCode.EV_KEY, EventCode.BTN_EAST },
                { EventCode.EV_KEY, EventCode.BTN_SOUTH },
@@ -75,6 +75,10 @@ private class Games.PreferencesSubpageGamepad : Gtk.Bin, PreferencesSubpage {
        [GtkChild]
        private unowned Gtk.HeaderBar mapper_header_bar;
        [GtkChild]
+       private unowned Adw.WindowTitle tester_window_title;
+       [GtkChild]
+       private unowned Adw.WindowTitle mapper_window_title;
+       [GtkChild]
        private unowned Gtk.Button reset_button;
 
        private GamepadMapper mapper;
@@ -88,17 +92,15 @@ private class Games.PreferencesSubpageGamepad : Gtk.Bin, PreferencesSubpage {
                        mapper = new GamepadMapper (value, GamepadViewConfiguration.get_default (), 
STANDARD_GAMEPAD_INPUTS);
                        tester = new GamepadTester (value, GamepadViewConfiguration.get_default ());
 
-                       tester_box.add (tester);
-                       tester_box.reorder_child (tester, 1);
-                       mapper_box.add (mapper);
-                       mapper_box.reorder_child (mapper, 1);
+                       tester_box.insert_child_after (tester, tester_header_bar);
+                       mapper_box.insert_child_after (mapper, mapper_header_bar);
 
                        mapper.bind_property ("info-message", this, "info-message", BindingFlags.SYNC_CREATE);
 
                        /* translators: testing a gamepad, %s is its name */
-                       tester_header_bar.title = _("Testing %s").printf (device.get_name ());
+                       tester_window_title.title = _("Testing %s").printf (device.get_name ());
                        /* translators: configuring a gamepad, %s is its name */
-                       mapper_header_bar.title = _("Configuring %s").printf (device.get_name ());
+                       mapper_window_title.title = _("Configuring %s").printf (device.get_name ());
                }
        }
 
@@ -137,7 +139,7 @@ private class Games.PreferencesSubpageGamepad : Gtk.Bin, PreferencesSubpage {
 
        private void reset_mapping () {
                var dialog = new Gtk.MessageDialog (
-                       get_toplevel () as Gtk.Window,
+                       get_root () as Gtk.Window,
                        Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
                        Gtk.MessageType.QUESTION,
                        Gtk.ButtonsType.CANCEL,
@@ -148,7 +150,7 @@ private class Games.PreferencesSubpageGamepad : Gtk.Bin, PreferencesSubpage {
                dialog.format_secondary_text ("%s", _("Your mapping will be lost"));
 
                var button = dialog.add_button (C_("Confirm controller configuration factory reset", 
"_Reset"), Gtk.ResponseType.ACCEPT);
-               button.get_style_context ().add_class ("destructive-action");
+               button.add_css_class ("destructive-action");
 
                dialog.response.connect ((response) => {
                        switch (response) {
diff --git a/src/preferences/preferences-subpage-keyboard.ui b/src/preferences/preferences-subpage-keyboard.ui
index cbe32268..bf2c911f 100644
--- a/src/preferences/preferences-subpage-keyboard.ui
+++ b/src/preferences/preferences-subpage-keyboard.ui
@@ -1,84 +1,48 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesPreferencesSubpageKeyboard" parent="GtkBin">
-    <property name="visible">True</property>
-    <property name="can-focus">True</property>
+  <template class="GamesPreferencesSubpageKeyboard" parent="AdwBin">
     <child>
       <object class="GtkStack" id="stack">
-        <property name="visible">True</property>
         <property name="transition-type">crossfade</property>
         <child>
           <object class="GtkBox" id="tester_box">
-            <property name="visible">True</property>
             <property name="orientation">vertical</property>
             <child>
-              <object class="HdyWindowHandle">
-                <property name="visible">True</property>
-                <child>
-                  <object class="GtkHeaderBar">
-                    <property name="visible">True</property>
-                    <property name="show-close-button">True</property>
+              <object class="GtkHeaderBar" id="tester_header_bar">
+                <property name="title-widget">
+                  <object class="AdwWindowTitle">
                     <property name="title" translatable="true">Testing Keyboard</property>
-                    <style>
-                      <class name="titlebar"/>
-                    </style>
-                    <child>
-                      <object class="GtkButton">
-                        <property name="visible">True</property>
-                        <signal name="clicked" handler="on_back_clicked"/>
-                        <style>
-                          <class name="image-button"/>
-                        </style>
-                        <child internal-child="accessible">
-                          <object class="AtkObject" id="a11y-back">
-                            <property name="accessible-name" translatable="yes">Back</property>
-                          </object>
-                        </child>
-                        <child>
-                          <object class="GtkImage">
-                            <property name="visible">True</property>
-                            <property name="icon-name">go-previous-symbolic</property>
-                            <property name="icon-size">1</property>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="pack-type">start</property>
-                      </packing>
-                    </child>
+                  </object>
+                </property>
+                <child type="start">
+                  <object class="GtkButton">
+                    <property name="icon-name">go-previous-symbolic</property>
+                    <property name="tooltip-text" translatable="yes">Back</property>
+                    <signal name="clicked" handler="on_back_clicked"/>
                   </object>
                 </child>
               </object>
             </child>
             <child>
               <object class="GtkActionBar">
-                <property name="visible">True</property>
-                <child>
-                  <object class="GtkButton" id="reset_button">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes" context="Keyboard configuration factory 
reset">Reset</property>
-                    <signal name="clicked" handler="on_reset_clicked"/>
-                    <style>
-                      <class name="destructive-action"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="pack-type">end</property>
-                  </packing>
-                </child>
-                <child>
+                <child type="start">
                   <object class="GtkButton">
-                    <property name="visible">True</property>
                     <property name="label" translatable="yes">Configure</property>
                     <signal name="clicked" handler="on_configure_clicked"/>
                     <style>
                       <class name="suggested-action"/>
                     </style>
                   </object>
-                  <packing>
-                    <property name="pack-type">start</property>
-                  </packing>
+                </child>
+                <child type="end">
+                  <object class="GtkButton" id="reset_button">
+                    <property name="label" translatable="yes" context="Keyboard configuration factory 
reset">Reset</property>
+                    <signal name="clicked" handler="on_reset_clicked"/>
+                    <style>
+                      <class name="destructive-action"/>
+                    </style>
+                  </object>
                 </child>
               </object>
             </child>
@@ -86,49 +50,36 @@
         </child>
         <child>
           <object class="GtkBox" id="mapper_box">
-            <property name="visible">True</property>
             <property name="orientation">vertical</property>
             <child>
-              <object class="HdyWindowHandle">
-                <property name="visible">True</property>
-                <child>
-                  <object class="GtkHeaderBar">
-                    <property name="visible">True</property>
+              <object class="GtkHeaderBar" id="mapper_header_bar">
+                <property name="show-title-buttons">False</property>
+                <property name="title-widget">
+                  <object class="AdwWindowTitle">
                     <property name="title" translatable="true">Configuring Keyboard</property>
-                    <style>
-                      <class name="titlebar"/>
-                      <class name="selection-mode"/>
-                    </style>
-                    <child>
-                      <object class="GtkButton">
-                        <property name="visible">True</property>
-                        <property name="label" translatable="yes">Cancel</property>
-                        <signal name="clicked" handler="on_cancel_clicked"/>
-                      </object>
-                      <packing>
-                        <property name="pack-type">end</property>
-                      </packing>
-                    </child>
+                  </object>
+                </property>
+                <style>
+                  <class name="selection-mode"/>
+                </style>
+                <child type="end">
+                  <object class="GtkButton">
+                    <property name="label" translatable="yes">Cancel</property>
+                    <signal name="clicked" handler="on_cancel_clicked"/>
                   </object>
                 </child>
               </object>
             </child>
             <child>
               <object class="GtkActionBar">
-                <property name="visible">True</property>
-                <child>
+                <child type="start">
                   <object class="GtkButton">
-                    <property name="visible">True</property>
                     <property name="label" translatable="yes">Skip</property>
                     <signal name="clicked" handler="on_skip_clicked"/>
                   </object>
-                  <packing>
-                    <property name="pack-type">start</property>
-                  </packing>
                 </child>
                 <child type="center">
                   <object class="GtkLabel">
-                    <property name="visible">True</property>
                     <property name="ellipsize">end</property>
                     <property name="label" bind-source="GamesPreferencesSubpageKeyboard" 
bind-property="info-message" bind-flags="bidirectional"/>
                   </object>
diff --git a/src/preferences/preferences-subpage-keyboard.vala 
b/src/preferences/preferences-subpage-keyboard.vala
index 9bc5df0a..4abd9eb5 100644
--- a/src/preferences/preferences-subpage-keyboard.vala
+++ b/src/preferences/preferences-subpage-keyboard.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/preferences/preferences-subpage-keyboard.ui")]
-private class Games.PreferencesSubpageKeyboard : Gtk.Bin, PreferencesSubpage {
+private class Games.PreferencesSubpageKeyboard : Adw.Bin, PreferencesSubpage {
        private const GamepadInput[] KEYBOARD_GAMEPAD_INPUTS = {
                { EventCode.EV_KEY, EventCode.BTN_EAST },
                { EventCode.EV_KEY, EventCode.BTN_SOUTH },
@@ -66,6 +66,10 @@ private class Games.PreferencesSubpageKeyboard : Gtk.Bin, PreferencesSubpage {
        [GtkChild]
        private unowned Gtk.Box mapper_box;
        [GtkChild]
+       private unowned Gtk.HeaderBar tester_header_bar;
+       [GtkChild]
+       private unowned Gtk.HeaderBar mapper_header_bar;
+       [GtkChild]
        private unowned Gtk.Button reset_button;
 
        private KeyboardMapper mapper;
@@ -77,10 +81,8 @@ private class Games.PreferencesSubpageKeyboard : Gtk.Bin, PreferencesSubpage {
                tester = new KeyboardTester (GamepadViewConfiguration.get_default ());
                mapping_manager = new KeyboardMappingManager ();
 
-               tester_box.add (tester);
-               tester_box.reorder_child (tester, 1);
-               mapper_box.add (mapper);
-               mapper_box.reorder_child (mapper, 1);
+               tester_box.insert_child_after (tester, tester_header_bar);
+               mapper_box.insert_child_after (mapper, mapper_header_bar);
 
                tester.mapping = mapping_manager.mapping;
                mapping_manager.changed.connect (() => {
@@ -119,7 +121,7 @@ private class Games.PreferencesSubpageKeyboard : Gtk.Bin, PreferencesSubpage {
 
        private void reset_mapping () {
                var dialog = new Gtk.MessageDialog (
-                       get_toplevel () as Gtk.Window,
+                       get_root () as Gtk.Window,
                        Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
                        Gtk.MessageType.QUESTION,
                        Gtk.ButtonsType.CANCEL,
@@ -130,7 +132,7 @@ private class Games.PreferencesSubpageKeyboard : Gtk.Bin, PreferencesSubpage {
                dialog.format_secondary_text ("%s", _("Your mapping will be lost"));
 
                var button = dialog.add_button (C_("Confirm controller configuration factory reset", 
"_Reset"), Gtk.ResponseType.ACCEPT);
-               button.get_style_context ().add_class ("destructive-action");
+               button.add_css_class ("destructive-action");
 
                dialog.response.connect ((response) => {
                        switch (response) {
diff --git a/src/preferences/preferences-window.ui b/src/preferences/preferences-window.ui
index b19e2deb..3020017a 100644
--- a/src/preferences/preferences-window.ui
+++ b/src/preferences/preferences-window.ui
@@ -1,24 +1,18 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesPreferencesWindow" parent="HdyPreferencesWindow">
+  <template class="GamesPreferencesWindow" parent="AdwPreferencesWindow">
     <property name="title" translatable="yes">Preferences</property>
     <property name="default-width">650</property>
     <property name="default-height">500</property>
     <child>
-      <object class="GamesPreferencesPageVideo">
-        <property name="visible">True</property>
-      </object>
+      <object class="GamesPreferencesPageVideo"/>
     </child>
     <child>
-      <object class="GamesPreferencesPageControllers">
-        <property name="visible">True</property>
-      </object>
+      <object class="GamesPreferencesPageControllers"/>
     </child>
     <child>
-      <object class="GamesPreferencesPagePlatforms">
-        <property name="visible">True</property>
-      </object>
+      <object class="GamesPreferencesPagePlatforms"/>
     </child>
   </template>
 </interface>
diff --git a/src/preferences/preferences-window.vala b/src/preferences/preferences-window.vala
index 1d6c1f70..4d0f2f87 100644
--- a/src/preferences/preferences-window.vala
+++ b/src/preferences/preferences-window.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/preferences/preferences-window.ui")]
-private class Games.PreferencesWindow : Hdy.PreferencesWindow {
+private class Games.PreferencesWindow : Adw.PreferencesWindow {
        private Binding swipe_back_binding;
 
        public void open_subpage (PreferencesSubpage subpage) {
diff --git a/src/screen-layout/screen-layout-item.ui b/src/screen-layout/screen-layout-item.ui
index 14f365a2..f2271266 100644
--- a/src/screen-layout/screen-layout-item.ui
+++ b/src/screen-layout/screen-layout-item.ui
@@ -2,22 +2,18 @@
 <interface>
   <requires lib="gtk+" version="3.24"/>
   <template class="GamesScreenLayoutItem" parent="GtkListBoxRow">
-    <property name="visible">True</property>
     <child>
       <object class="GtkBox">
         <property name="halign">start</property>
         <property name="spacing">6</property>
         <property name="valign">center</property>
-        <property name="visible">True</property>
         <child>
           <object class="GtkBox">
             <property name="halign">start</property>
             <property name="spacing">12</property>
             <property name="valign">center</property>
-            <property name="visible">True</property>
             <child>
               <object class="GtkImage" id="icon">
-                <property name="visible">True</property>
                 <style>
                   <class name="list-icon"/>
                 </style>
@@ -27,7 +23,6 @@
               <object class="GtkLabel" id="title">
                 <property name="halign">start</property>
                 <property name="valign">center</property>
-                <property name="visible">True</property>
                 <property name="xalign">0</property>
               </object>
             </child>
@@ -35,7 +30,6 @@
         </child>
         <child>
           <object class="GtkImage" id="checkmark">
-            <property name="visible">True</property>
             <property name="valign">center</property>
             <property name="halign">start</property>
             <property name="opacity">0</property>
diff --git a/src/screen-layout/screen-layout-switcher.ui b/src/screen-layout/screen-layout-switcher.ui
index c7fb6d39..81901a08 100644
--- a/src/screen-layout/screen-layout-switcher.ui
+++ b/src/screen-layout/screen-layout-switcher.ui
@@ -2,75 +2,40 @@
 <interface>
   <requires lib="gtk+" version="3.24"/>
   <template class="GamesScreenLayoutSwitcher" parent="GtkBox">
-    <property name="visible">True</property>
-
     <child>
       <object class="GtkRevealer" id="change_screen_revealer">
-        <property name="visible">True</property>
         <property name="transition-type">slide-left</property>
         <child>
-          <object class="GtkButton">
-            <property name="visible">True</property>
+          <object class="GtkButton" id="change_screen_button">
             <property name="can-focus">False</property>
             <property name="margin-end">6</property>
+            <property name="tooltip-text" translatable="yes">Change Screen</property>
             <signal name="clicked" handler="on_screen_changed"/>
-            <child internal-child="accessible">
-              <object class="AtkObject">
-                <property name="accessible-name" translatable="yes">Change Screen</property>
-              </object>
-            </child>
-            <child>
-              <object class="GtkImage" id="change_screen_image">
-                <property name="visible">True</property>
-              </object>
-            </child>
            </object>
         </child>
       </object>
     </child>
     <child>
       <object class="GtkMenuButton" id="layout_button">
-        <property name="visible">True</property>
+        <!-- FIXME GTK4 need an arrow -->
         <property name="can-focus">False</property>
-        <property name="popover">layout_popover</property>
+        <property name="tooltip-text" translatable="yes">Screen Layout</property>
         <signal name="notify::active" handler="on_menu_state_changed"/>
-        <child internal-child="accessible">
-          <object class="AtkObject" id="a11y-display-discs">
-            <property name="accessible-name" translatable="yes">Screen Layout</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkBox">
-            <property name="visible">True</property>
-            <property name="spacing">6</property>
+        <property name="popover">
+          <object class="GtkPopover" id="layout_popover">
+            <signal name="show" handler="update_ui"/>
             <child>
-              <object class="GtkImage" id="layout_image">
-                <property name="visible">True</property>
-              </object>
-            </child>
-            <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="icon-name">pan-down-symbolic</property>
+              <object class="GtkListBox" id="list_box">
+                <property name="selection-mode">none</property>
+                <signal name="row-activated" handler="on_row_activated"/>
               </object>
             </child>
+            <style>
+              <class name="combo"/>
+            </style>
           </object>
-        </child>
+        </property>
       </object>
     </child>
   </template>
-  <object class="GtkPopover" id="layout_popover">
-    <property name="visible">False</property>
-    <signal name="show" handler="update_ui"/>
-    <child>
-      <object class="GtkListBox" id="list_box">
-        <property name="visible">True</property>
-        <property name="selection-mode">none</property>
-        <signal name="row-activated" handler="on_row_activated"/>
-      </object>
-    </child>
-    <style>
-      <class name="combo"/>
-    </style>
-  </object>
 </interface>
diff --git a/src/screen-layout/screen-layout-switcher.vala b/src/screen-layout/screen-layout-switcher.vala
index 2c8427b1..94ac74ba 100644
--- a/src/screen-layout/screen-layout-switcher.vala
+++ b/src/screen-layout/screen-layout-switcher.vala
@@ -5,12 +5,10 @@ public class Games.ScreenLayoutSwitcher : Gtk.Box, HeaderBarWidget {
        [GtkChild]
        private unowned Gtk.Revealer change_screen_revealer;
        [GtkChild]
-       private unowned Gtk.Image change_screen_image;
+       private unowned Gtk.Button change_screen_button;
        [GtkChild]
        private unowned Gtk.MenuButton layout_button;
        [GtkChild]
-       private unowned Gtk.Image layout_image;
-       [GtkChild]
        private unowned Gtk.Popover layout_popover;
        [GtkChild]
        private unowned Gtk.ListBox list_box;
@@ -31,7 +29,7 @@ public class Games.ScreenLayoutSwitcher : Gtk.Box, HeaderBarWidget {
                        var item = new ScreenLayoutItem (layout);
 
                        items[layout] = item;
-                       list_box.add (item);
+                       list_box.append (item);
                }
 
                update_ui ();
@@ -44,13 +42,13 @@ public class Games.ScreenLayoutSwitcher : Gtk.Box, HeaderBarWidget {
 
        [GtkCallback]
        private void on_menu_state_changed () {
-               is_menu_open = layout_button.active;
+// FIXME GTK4          is_menu_open = layout_button.active;
                notify_property ("block-autohide");
        }
 
        [GtkCallback]
        private void update_ui () {
-               layout_image.icon_name = screen_layout.get_icon ();
+               layout_button.icon_name = screen_layout.get_icon ();
 
                foreach (var item in items.get_values ())
                        item.selected = item.layout == screen_layout;
@@ -59,9 +57,9 @@ public class Games.ScreenLayoutSwitcher : Gtk.Box, HeaderBarWidget {
                list_box.select_row (item);
 
                change_screen_revealer.reveal_child = (screen_layout == ScreenLayout.QUICK_SWITCH);
-               change_screen_image.icon_name = view_bottom_screen ?
-                                               "view-top-screen-symbolic" :
-                                               "view-bottom-screen-symbolic";
+               change_screen_button.icon_name = view_bottom_screen ?
+                                                "view-top-screen-symbolic" :
+                                                "view-bottom-screen-symbolic";
        }
 
        [GtkCallback]
diff --git a/src/ui/application-window.ui b/src/ui/application-window.ui
index 2a3e58aa..c93d7603 100644
--- a/src/ui/application-window.ui
+++ b/src/ui/application-window.ui
@@ -1,21 +1,17 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesApplicationWindow" parent="HdyApplicationWindow">
+  <template class="GamesApplicationWindow" parent="AdwApplicationWindow">
     <property name="default-width">768</property>
     <property name="default-height">600</property>
-    <property name="show-menubar">False</property>
-    <signal name="delete-event" after="yes" handler="on_delete_event"/>
-    <signal name="key-press-event" after="yes" handler="on_key_pressed"/>
-    <signal name="button-press-event" after="yes" handler="on_button_pressed"/>
-    <signal name="window-state-event" after="yes" handler="on_window_state_event"/>
     <signal name="notify::is-active" after="yes" handler="on_active_changed"/>
-    <child>
+    <signal name="notify::default-width" after="yes" handler="window_state_changed"/>
+    <signal name="notify::default-height" after="yes" handler="window_state_changed"/>
+    <signal name="notify::maximized" after="yes" handler="window_state_changed"/>
+    <property name="child">
       <object class="GtkStack" id="stack">
-        <property name="visible">True</property>
         <child>
           <object class="GamesCollectionView" id="collection_view">
-            <property name="visible">True</property>
             <property name="window">GamesApplicationWindow</property>
             <property name="loading-notification" bind-source="GamesApplicationWindow" 
bind-property="loading-notification"/>
             <signal name="game-activated" handler="on_game_activated"/>
@@ -23,13 +19,34 @@
         </child>
         <child>
           <object class="GamesDisplayView" id="display_view">
-            <property name="visible">True</property>
             <property name="window">GamesApplicationWindow</property>
-            <property name="is-fullscreen" bind-source="GamesApplicationWindow" 
bind-property="is-fullscreen" bind-flags="bidirectional"/>
+            <property name="is-fullscreen" bind-source="GamesApplicationWindow" bind-property="fullscreened" 
bind-flags="bidirectional"/>
             <signal name="back" handler="on_display_back"/>
           </object>
         </child>
       </object>
+    </property>
+    <child>
+      <object class="GtkEventControllerKey">
+        <signal name="key-pressed" handler="on_key_pressed"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkGestureClick">
+        <property name="button">0</property>
+        <signal name="pressed" handler="on_button_pressed"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkShortcutController">
+        <property name='scope'>managed</property>
+        <child>
+          <object class="GtkShortcut">
+            <property name="trigger">&lt;Control&gt;q</property>
+            <property name="action">action(app.quit)</property>
+          </object>
+        </child>
+      </object>
     </child>
   </template>
 </interface>
diff --git a/src/ui/application-window.vala b/src/ui/application-window.vala
index 04f34f95..1b28958b 100644
--- a/src/ui/application-window.vala
+++ b/src/ui/application-window.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/application-window.ui")]
-private class Games.ApplicationWindow : Hdy.ApplicationWindow {
+private class Games.ApplicationWindow : Adw.ApplicationWindow {
        private const uint WINDOW_SIZE_UPDATE_DELAY_MILLISECONDS = 500;
 
        [GtkChild]
@@ -35,19 +35,6 @@ private class Games.ApplicationWindow : Hdy.ApplicationWindow {
                }
        }
 
-       private bool _is_fullscreen;
-       public bool is_fullscreen {
-               get { return _is_fullscreen; }
-               set {
-                       _is_fullscreen = value && (current_view == display_view);
-
-                       if (_is_fullscreen)
-                               fullscreen ();
-                       else
-                               unfullscreen ();
-               }
-       }
-
        public bool loading_notification { get; set; }
 
        private Settings settings;
@@ -76,12 +63,8 @@ private class Games.ApplicationWindow : Hdy.ApplicationWindow {
 
                int width, height;
                settings.get ("window-size", "(ii)", out width, out height);
-               var geometry = get_geometry ();
-               if (geometry != null) {
-                       width = int.min (width, geometry.width);
-                       height = int.min (height, geometry.height);
-               }
-               resize (width, height);
+               default_width = width;
+               default_height = height;
 
                if (settings.get_boolean ("window-maximized"))
                        maximize ();
@@ -91,7 +74,7 @@ private class Games.ApplicationWindow : Hdy.ApplicationWindow {
                inhibit_flags = 0;
 
                if (Config.PROFILE == "Devel")
-                       get_style_context ().add_class ("devel");
+                       add_css_class ("devel");
 
                init_help_overlay ();
        }
@@ -143,15 +126,7 @@ private class Games.ApplicationWindow : Hdy.ApplicationWindow {
                return yield display_view.quit_game ();
        }
 
-       public override void size_allocate (Gtk.Allocation allocation) {
-               base.size_allocate (allocation);
-
-               if (window_size_update_timeout == -1 && !is_maximized)
-                       window_size_update_timeout = Timeout.add (WINDOW_SIZE_UPDATE_DELAY_MILLISECONDS, 
store_window_size);
-       }
-
-       [GtkCallback]
-       public bool on_delete_event () {
+       protected override bool close_request () {
                if (confirm_exit)
                        return true;
 
@@ -168,57 +143,15 @@ private class Games.ApplicationWindow : Hdy.ApplicationWindow {
        }
 
        [GtkCallback]
-       public bool on_key_pressed (Gdk.EventKey event) {
-               var default_modifiers = Gtk.accelerator_get_default_mod_mask ();
-
-               uint keyval;
-               var keymap = Gdk.Keymap.get_for_display (get_display ());
-               keymap.translate_keyboard_state (event.hardware_keycode, event.state,
-                                                event.group, out keyval, null, null, null);
-
-               if ((keyval == Gdk.Key.q || keyval == Gdk.Key.Q) &&
-                   (event.state & default_modifiers) == Gdk.ModifierType.CONTROL_MASK) {
-
-                       quit_game.begin ((obj, res) => {
-                               if (!quit_game.end (res))
-                                       return;
-
-                               destroy ();
-                       });
-
-                       return true;
-               }
-
-               return current_view.on_key_pressed (event);
-       }
-
-       [GtkCallback]
-       public bool on_button_pressed (Gdk.EventButton event) {
-               return current_view.on_button_pressed (event);
+       public bool on_key_pressed (uint keyval, uint keycode, Gdk.ModifierType state) {
+               return current_view.on_key_pressed (keycode, keyval, state);
        }
 
        [GtkCallback]
-       public bool on_window_state_event (Gdk.EventWindowState event) {
-               var is_maximized = (bool) (event.new_window_state & Gdk.WindowState.MAXIMIZED);
-               settings.set_boolean ("window-maximized", is_maximized);
+       public void on_button_pressed (Gtk.GestureClick gesture, int n_press, double x, double y) {
+               uint button = gesture.get_current_button ();
 
-               is_fullscreen = (bool) (event.new_window_state & Gdk.WindowState.FULLSCREEN);
-               if (current_view == display_view)
-                       display_view.update_pause ();
-
-               if (!(bool) (event.changed_mask & Gdk.WindowState.FOCUSED))
-                       return true;
-
-               var focused = (bool) (event.new_window_state & Gdk.WindowState.FOCUSED);
-               var playing = (current_view == display_view);
-
-               if (focused && playing)
-                       inhibit (Gtk.ApplicationInhibitFlags.IDLE);
-
-               if (!focused)
-                       uninhibit (Gtk.ApplicationInhibitFlags.IDLE);
-
-               return true;
+               current_view.on_button_pressed (button);
        }
 
        public bool gamepad_button_press_event (Manette.Event event) {
@@ -256,40 +189,28 @@ private class Games.ApplicationWindow : Hdy.ApplicationWindow {
 
        [GtkCallback]
        private void on_active_changed () {
-               if (current_view == display_view)
+               var playing = (current_view == display_view);
+
+               if (playing)
                        display_view.update_pause ();
-       }
 
-       private Gdk.Rectangle? get_geometry () {
-               var display = get_display ();
-               if (display == null)
-                       return null;
+               if (is_active && playing)
+                       inhibit (Gtk.ApplicationInhibitFlags.IDLE);
 
-               var window = get_window ();
-               if (window == null)
-                       return null;
+               if (!is_active)
+                       uninhibit (Gtk.ApplicationInhibitFlags.IDLE);
+       }
 
-               var monitor = display.get_monitor_at_window (window);
-               if (monitor == null)
-                       return null;
+       [GtkCallback]
+       private void window_state_changed () {
+               settings.set_boolean ("window-maximized", maximized);
 
-               return monitor.geometry;
+               if (window_size_update_timeout == -1 && !maximized)
+                       window_size_update_timeout = Timeout.add (WINDOW_SIZE_UPDATE_DELAY_MILLISECONDS, 
store_window_size);
        }
 
        private bool store_window_size () {
-               var geometry = get_geometry ();
-               if (geometry == null)
-                       return false;
-
-               int width = 0;
-               int height = 0;
-
-               get_size (out width, out height);
-
-               width = int.min (width, geometry.width);
-               height = int.min (height, geometry.height);
-
-               settings.set ("window-size", "(ii)", width, height);
+               settings.set ("window-size", "(ii)", default_width, default_height);
 
                Source.remove ((uint) window_size_update_timeout);
                window_size_update_timeout = -1;
diff --git a/src/ui/application.vala b/src/ui/application.vala
index 65986417..43561432 100644
--- a/src/ui/application.vala
+++ b/src/ui/application.vala
@@ -6,8 +6,8 @@ public class Games.Application : Gtk.Application {
 
        private Database database;
 
-       private PreferencesWindow preferences_window;
-       private ApplicationWindow window;
+       private weak PreferencesWindow preferences_window;
+       private weak ApplicationWindow window;
        private bool game_list_loaded;
 
        private GameCollection game_collection;
@@ -176,11 +176,15 @@ public class Games.Application : Gtk.Application {
 
                var response = yield DialogUtils.run_async (chooser);
 
-               if (response == Gtk.ResponseType.ACCEPT)
-                       foreach (unowned string uri_string in chooser.get_uris ()) {
-                               var uri = new Uri (uri_string);
+               if (response == Gtk.ResponseType.ACCEPT) {
+                       var files = chooser.get_files ();
+
+                       for (uint i = 0; i < files.get_n_items (); i++) {
+                               var file = files.get_item (i) as File;
+                               var uri = new Uri (file.get_uri ());
                                add_cached_uri (uri);
                        }
+               }
 
                chooser.destroy ();
        }
@@ -202,7 +206,7 @@ public class Games.Application : Gtk.Application {
                dialog.show ();
 
                var btn = dialog.add_button (_("_Import"), Gtk.ResponseType.ACCEPT);
-               btn.get_style_context ().add_class ("destructive-action");
+               btn.add_css_class ("destructive-action");
 
                var response = yield DialogUtils.run_async (dialog);
 
@@ -222,10 +226,10 @@ public class Games.Application : Gtk.Application {
                response = yield DialogUtils.run_native_async (chooser);
 
                if (response == Gtk.ResponseType.ACCEPT) {
-                       var archive_name = chooser.get_filename ();
+                       var archive_name = chooser.get_file ();
 
                        try {
-                               import_from (archive_name);
+                               import_from (archive_name.get_path ());
                        }
                        catch (ExtractionError e) {
                                var msg = _("Couldn’t import save data: %s").printf (e.message);
@@ -249,8 +253,6 @@ public class Games.Application : Gtk.Application {
                        _("_Cancel")
                );
 
-               chooser.do_overwrite_confirmation = true;
-
                var current_time = new DateTime.now_local ();
                var creation_time = current_time.format ("%c");
                var archive_filename = "gnome-games-save-data-%s.tar.gz".printf (creation_time);
@@ -260,10 +262,10 @@ public class Games.Application : Gtk.Application {
                var response = yield DialogUtils.run_native_async (chooser);
 
                if (response == Gtk.ResponseType.ACCEPT) {
-                       var filename = chooser.get_filename ();
+                       var file = chooser.get_file ();
 
                        try {
-                               export_to (filename);
+                               export_to (file.get_path ());
                        }
                        catch (CompressionError e) {
                                var msg = _("Couldn’t export save data: %s").printf (e.message);
@@ -311,15 +313,15 @@ public class Games.Application : Gtk.Application {
        protected override void startup () {
                base.startup ();
 
-               Hdy.init ();
+               Adw.init ();
 
-               var screen = Gdk.Screen.get_default ();
+               var display = Gdk.Display.get_default ();
                var provider = load_css ("gtk-style.css");
-               Gtk.StyleContext.add_provider_for_screen (screen, provider, 600);
+               Gtk.StyleContext.add_provider_for_display (display, provider, 600);
 
                Gtk.Settings.get_default ().gtk_application_prefer_dark_theme = true;
 
-               var icon_theme = Gtk.IconTheme.get_default ();
+               var icon_theme = Gtk.IconTheme.get_for_display (display);
                icon_theme.add_resource_path ("/org/gnome/Games/icons/");
                icon_theme.add_resource_path ("/org/gnome/Games/gesture");
        }
@@ -395,15 +397,14 @@ public class Games.Application : Gtk.Application {
                }
 
                if (window != null) {
-                       window.present_with_time (Gtk.get_current_event_time ());
+                       window.present ();
                        return;
                }
 
-               window = new ApplicationWindow (this, game_model, collection_model);
-               window.destroy.connect (() => {
-                       quit_application ();
-               });
-               window.show ();
+               var window = new ApplicationWindow (this, game_model, collection_model);
+               this.window = window;
+
+               window.present ();
 
                if (tracker_failed) {
                        string error_msg = _("Couldn't find Tracker, automatic game discovery may not work.");
@@ -512,26 +513,20 @@ public class Games.Application : Gtk.Application {
 
        private void preferences () {
                if (preferences_window == null) {
-                       preferences_window = new PreferencesWindow ();
+                       var preferences_window = new PreferencesWindow ();
 
                        preferences_window.transient_for = window;
                        preferences_window.modal = true;
+                       // FIXME GTK4 crashes if you try to open it again after closing
 
-                       preferences_window.destroy.connect (() => {
-                               preferences_window = null;
-                       });
+                       this.preferences_window = preferences_window;
                }
 
-               preferences_window.present_with_time (Gtk.get_current_event_time ());
+               preferences_window.present ();
        }
 
        private void help () {
-               try {
-                       Gtk.show_uri_on_window (active_window, HELP_URI, Gtk.get_current_event_time ());
-               }
-               catch (Error e) {
-                       critical (e.message);
-               }
+               Gtk.show_uri (active_window, HELP_URI, Gdk.CURRENT_TIME);
        }
 
        private void about () {
@@ -555,12 +550,7 @@ public class Games.Application : Gtk.Application {
                dialog.documenters = Credits.DOCUMENTERS;
                dialog.translator_credits = _("translator-credits");
 
-               dialog.response.connect ((response_id) => {
-                       if (response_id == Gtk.ResponseType.CANCEL || response_id == 
Gtk.ResponseType.DELETE_EVENT)
-                               dialog.hide_on_delete ();
-               });
-
-               dialog.present_with_time (Gtk.get_current_event_time ());
+               dialog.present ();
        }
 
        private void quit_application () {
diff --git a/src/ui/checkmark-item.ui b/src/ui/checkmark-item.ui
index f0fe2b87..bd1f5e42 100644
--- a/src/ui/checkmark-item.ui
+++ b/src/ui/checkmark-item.ui
@@ -2,18 +2,15 @@
 <interface>
   <requires lib="gtk+" version="3.24"/>
   <template class="GamesCheckmarkItem" parent="GtkListBoxRow">
-    <property name="visible">true</property>
-    <child>
+    <property name="child">
       <object class="GtkBox">
         <property name="margin-top">10</property>
         <property name="margin-bottom">10</property>
         <property name="margin-start">10</property>
         <property name="margin-end">10</property>
         <property name="spacing">12</property>
-        <property name="visible">true</property>
         <child>
           <object class="GtkLabel">
-            <property name="visible">true</property>
             <property name="vexpand">true</property>
             <property name="wrap">true</property>
             <property name="label" bind-source="GamesCheckmarkItem" bind-property="label" 
bind-flags="default"/>
@@ -21,12 +18,12 @@
         </child>
         <child>
           <object class="GtkImage">
-            <property name="visible" bind-source="GamesCheckmarkItem" bind-property="checkmark-visible" 
bind-flags="default">False</property>
+            <property name="visible" bind-source="GamesCheckmarkItem" bind-property="checkmark-visible" 
bind-flags="sync-create"/>
             <property name="icon-name">object-select-symbolic</property>
             <property name="valign">center</property>
           </object>
         </child>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
diff --git a/src/ui/checkmark-item.vala b/src/ui/checkmark-item.vala
index 59ef635d..9a40f005 100644
--- a/src/ui/checkmark-item.vala
+++ b/src/ui/checkmark-item.vala
@@ -2,7 +2,7 @@
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/checkmark-item.ui")]
 private class Games.CheckmarkItem : Gtk.ListBoxRow {
-       public bool checkmark_visible { get; set; }
+       public bool checkmark_visible { get; set; default = false; }
        public string label { get; construct; }
 
        public CheckmarkItem (string label) {
diff --git a/src/ui/collection-action-window.ui b/src/ui/collection-action-window.ui
index 21857a3a..fdb01aba 100644
--- a/src/ui/collection-action-window.ui
+++ b/src/ui/collection-action-window.ui
@@ -1,37 +1,35 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesCollectionActionWindow" parent="HdyWindow">
+  <template class="GamesCollectionActionWindow" parent="AdwWindow">
     <property name="title" translatable="yes">Add to Collection</property>
     <property name="default-height">550</property>
     <property name="default-width">400</property>
-    <property name="window-position">center-on-parent</property>
-    <signal name="key-press-event" after="yes" handler="on_key_pressed"/>
-    <child>
-      <object class="HdyDeck" id="deck">
-        <property name="visible">True</property>
+    <property name="child">
+      <object class="AdwLeaflet" id="leaflet">
+        <property name="can-unfold">False</property>
         <property name="can-swipe-back" bind-source="GamesCollectionActionWindow" 
bind-property="create-collection-page-only" bind-flags="invert-boolean|sync-create"/>
         <signal name="notify::visible-child" handler="on_visible_child_changed"/>
         <child>
           <object class="GtkBox" id="add_to_collection_page">
-            <property name="visible">True</property>
             <property name="orientation">vertical</property>
             <child>
-              <object class="HdyHeaderBar">
-                <property name="visible">True</property>
-                <property name="title" translatable="yes">Add to Collection</property>
-                <property name="show-close-button">False</property>
-                <child>
+              <object class="GtkHeaderBar">
+                <property name="show-title-buttons">False</property>
+                <property name="title-widget">
+                  <object class="AdwWindowTitle">
+                    <property name="title" translatable="yes">Add to Collection</property>
+                  </object>
+                </property>
+                <child type="start">
                   <object class="GtkButton" id="cancel_button">
-                    <property name="visible">True</property>
                     <property name="label" translatable="yes">_Cancel</property>
                     <property name="use_underline">True</property>
                     <property name="action-name">collection-action.go-back</property>
                   </object>
                 </child>
-                <child>
+                <child type="end">
                   <object class="GtkButton" id="add_button">
-                    <property name="visible">True</property>
                     <property name="label" translatable="yes">_Add</property>
                     <property name="use_underline">True</property>
                     <property name="sensitive" bind-source="GamesCollectionActionWindow" 
bind-property="is-user-collections-empty" bind-flags="invert-boolean|sync-create"/>
@@ -40,96 +38,75 @@
                       <class name="suggested-action"/>
                     </style>
                   </object>
-                  <packing>
-                    <property name="pack_type">end</property>
-                  </packing>
                 </child>
-                <child>
+                <child type="end">
                   <object class="GtkToggleButton">
-                    <property name="visible">True</property>
-                    <property name="valign">center</property>
+                    <property name="icon-name">edit-find-symbolic</property>
+                    <property name="tooltip-text" translatable="yes">Search</property>
                     <property name="active" bind-source="GamesCollectionActionWindow" 
bind-property="is-search-mode" bind-flags="bidirectional"/>
                     <property name="sensitive" bind-source="GamesCollectionActionWindow" 
bind-property="is-user-collections-empty" bind-flags="invert-boolean|sync-create"/>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="icon-name">edit-find-symbolic</property>
-                      </object>
-                    </child>
-                    <child internal-child="accessible">
-                      <object class="AtkObject">
-                        <property name="accessible-name" translatable="yes">Search</property>
-                      </object>
-                    </child>
-                    <style>
-                      <class name="image-button"/>
-                    </style>
                   </object>
-                  <packing>
-                    <property name="pack-type">end</property>
-                  </packing>
                 </child>
               </object>
             </child>
             <child>
-              <object class="HdySearchBar" id="search_bar">
-                <property name="visible">True</property>
+              <object class="GtkSearchBar" id="search_bar">
+                <property name="key-capture-widget">GamesCollectionActionWindow</property>
                 <property name="search-mode-enabled" bind-source="GamesCollectionActionWindow" 
bind-property="is-search-mode" bind-flags="sync-create|bidirectional"/>
-                <child>
-                  <object class="HdyClamp">
-                    <property name="visible">True</property>
+                <property name="child">
+                  <object class="AdwClamp">
                     <property name="maximum-size">400</property>
-                    <child>
+                    <property name="hexpand">True</property> <!-- FIXME GTK4 -->
+                    <property name="child">
                       <object class="GtkSearchEntry" id="search_entry">
-                        <property name="visible">True</property>
                         <signal name="search-changed" handler="on_search_text_notify"/>
                         <signal name="activate" handler="create_collection"/>
+                        <property name="hexpand">True</property>
                       </object>
-                    </child>
+                    </property>
                   </object>
-                </child>
+                </property>
               </object>
             </child>
             <child>
               <object class="GtkStack" id="user_collections_page_stack">
-                <property name="visible">True</property>
                 <property name="vexpand">True</property>
                 <child>
                   <object class="GtkScrolledWindow" id="list_page">
-                    <property name="visible">True</property>
                     <property name="hscrollbar-policy">never</property>
-                    <child>
-                      <object class="HdyClamp">
-                        <property name="visible">True</property>
-                        <property name="maximum-size">400</property>
-                        <property name="margin-top">18</property>
-                        <property name="margin-bottom">18</property>
-                        <property name="margin-start">18</property>
-                        <property name="margin-end">18</property>
-                        <child>
-                          <object class="GtkListBox" id="list_box">
-                            <property name="visible">True</property>
-                            <property name="activate-on-single-click">True</property>
-                            <property name="selection-mode">none</property>
-                            <signal name="row-activated" handler="on_listbox_row_activated"/>
-                            <style>
-                              <class name="content"/>
-                            </style>
+                    <property name="child">
+                      <object class="GtkViewport">
+                        <property name="scroll-to-focus">True</property>
+                        <property name="child">
+                          <object class="AdwClamp">
+                            <property name="maximum-size">400</property>
+                            <property name="margin-top">18</property>
+                            <property name="margin-bottom">18</property>
+                            <property name="margin-start">18</property>
+                            <property name="margin-end">18</property>
+                            <property name="child">
+                              <object class="GtkListBox" id="list_box">
+                                <property name="activate-on-single-click">True</property>
+                                <property name="selection-mode">none</property>
+                                <signal name="row-activated" handler="on_listbox_row_activated"/>
+                                <style>
+                                  <class name="content"/>
+                                </style>
+                              </object>
+                            </property>
                           </object>
-                        </child>
+                        </property>
                       </object>
-                    </child>
+                    </property>
                   </object>
                 </child>
                 <child>
-                  <object class="HdyStatusPage" id="empty_page">
-                    <property name="visible">True</property>
+                  <object class="AdwStatusPage" id="empty_page">
                     <property name="icon-name">folder-symbolic</property>
                     <property name="title" translatable="yes">No collections found</property>
                     <property name="description" translatable="yes">Add a new collection to add games to 
it.</property>
-                    <child>
+                    <property name="child">
                       <object class="GtkButton">
-                        <property name="visible">True</property>
                         <property name="label" translatable="yes">Create Collection</property>
                         <property name="tooltip-text" translatable="yes">Create a collection</property>
                         <property name="halign">center</property>
@@ -139,7 +116,7 @@
                           <class name="pill-button"/>
                         </style>
                       </object>
-                    </child>
+                    </property>
                   </object>
                 </child>
               </object>
@@ -148,52 +125,36 @@
         </child>
         <child>
           <object class="GtkBox" id="create_collection_page">
-            <property name="visible">True</property>
             <property name="orientation">vertical</property>
             <child>
-              <object class="HdyHeaderBar">
-                <property name="visible">True</property>
-                <property name="title" translatable="yes">Add a Collection</property>
-                <property name="show-close-button" bind-source="GamesCollectionActionWindow" 
bind-property="create-collection-page-only"/>
-                <child>
+              <object class="GtkHeaderBar">
+                <property name="show-title-buttons" bind-source="GamesCollectionActionWindow" 
bind-property="create-collection-page-only" bind-flags="sync-create"/>
+                <property name="title-widget">
+                  <object class="AdwWindowTitle">
+                    <property name="title" translatable="yes">Add a Collection</property>
+                  </object>
+                </property>
+                <child type="start">
                   <object class="GtkButton">
                     <property name="visible" bind-source="GamesCollectionActionWindow" 
bind-property="create-collection-page-only" bind-flags="invert-boolean|sync-create"/>
                     <property name="action-name">collection-action.go-back</property>
-                    <child internal-child="accessible">
-                      <object class="AtkObject">
-                        <property name="accessible-name" translatable="yes">Back</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="icon-name">go-previous-symbolic</property>
-                      </object>
-                    </child>
-                    <style>
-                      <class name="image-button"/>
-                    </style>
+                    <property name="tooltip-text" translatable="yes">Back</property>
+                    <property name="icon-name">go-previous-symbolic</property>
                   </object>
                 </child>
-                <style>
-                    <class name="titlebar"/>
-                </style>
               </object>
             </child>
             <child>
-              <object class="HdyStatusPage">
-                <property name="visible">True</property>
+              <object class="AdwStatusPage">
                 <property name="icon-name">folder-new-symbolic</property>
                 <property name="title" translatable="yes">Enter a collection name</property>
                 <property name="vexpand">True</property>
-                <child>
+                <property name="child">
                   <object class="GtkBox">
-                    <property name="visible">True</property>
                     <property name="orientation">vertical</property>
                     <property name="spacing">24</property>
                     <child>
                       <object class="GtkEntry" id="name_entry">
-                        <property name="visible">True</property>
                         <property name="width-request">300</property>
                         <property name="halign">center</property>
                         <signal name="notify::text" handler="on_collection_name_entry_changed"/>
@@ -202,7 +163,6 @@
                     </child>
                     <child>
                       <object class="GtkLabel" id="error_label">
-                        <property name="visible">True</property>
                         <property name="halign">center</property>
                         <style>
                           <class name="dim-label"/>
@@ -211,7 +171,6 @@
                     </child>
                     <child>
                       <object class="GtkButton">
-                        <property name="visible">True</property>
                         <property name="label" translatable="yes">C_reate</property>
                         <property name="tooltip-text" translatable="yes">Create a collection</property>
                         <property name="use-underline">True</property>
@@ -226,26 +185,29 @@
                       </object>
                     </child>
                   </object>
-                </child>
+                </property>
               </object>
             </child>
           </object>
         </child>
       </object>
+    </property>
+    <child>
+      <object class="GtkEventControllerKey">
+        <signal name="key-pressed" handler="key_pressed_cb"/>
+      </object>
     </child>
   </template>
   <object class="GtkListBoxRow" id="add_row">
-    <property name="visible">True</property>
     <property name="activatable">True</property>
-    <child>
+    <property name="child">
       <object class="GtkImage">
-        <property name="visible">True</property>
         <property name="icon-name">list-add-symbolic</property>
         <property name="margin-top">12</property>
         <property name="margin-bottom">12</property>
         <property name="margin-start">12</property>
         <property name="margin-end">12</property>
       </object>
-    </child>
+    </property>
   </object>
 </interface>
diff --git a/src/ui/collection-action-window.vala b/src/ui/collection-action-window.vala
index d791367e..3851a961 100644
--- a/src/ui/collection-action-window.vala
+++ b/src/ui/collection-action-window.vala
@@ -1,11 +1,11 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/collection-action-window.ui")]
-private class Games.CollectionActionWindow : Hdy.Window {
+private class Games.CollectionActionWindow : Adw.Window {
        public signal void confirmed (Collection[] collections);
 
        [GtkChild]
-       private unowned Hdy.Deck deck;
+       private unowned Adw.Leaflet leaflet;
        [GtkChild]
        private unowned Gtk.Box add_to_collection_page;
        [GtkChild]
@@ -15,7 +15,7 @@ private class Games.CollectionActionWindow : Hdy.Window {
        [GtkChild]
        private unowned Gtk.ScrolledWindow list_page;
        [GtkChild]
-       private unowned Hdy.StatusPage empty_page;
+       private unowned Adw.StatusPage empty_page;
        [GtkChild]
        private unowned Gtk.Entry name_entry;
        [GtkChild]
@@ -23,7 +23,7 @@ private class Games.CollectionActionWindow : Hdy.Window {
        [GtkChild]
        private unowned Gtk.ListBox list_box;
        [GtkChild]
-       private unowned Hdy.SearchBar search_bar;
+       private unowned Gtk.SearchBar search_bar;
        [GtkChild]
        private unowned Gtk.SearchEntry search_entry;
        [GtkChild]
@@ -82,9 +82,9 @@ private class Games.CollectionActionWindow : Hdy.Window {
 
        construct {
                if (create_collection_page_only)
-                       deck.visible_child = create_collection_page;
+                       leaflet.visible_child = create_collection_page;
                else
-                       deck.visible_child = add_to_collection_page;
+                       leaflet.visible_child = add_to_collection_page;
 
                collection_manager = Application.get_default ().get_collection_manager ();
 
@@ -138,14 +138,23 @@ private class Games.CollectionActionWindow : Hdy.Window {
        private void add_to_collection () {
                Collection[] collections = {};
 
-               foreach (var child in list_box.get_children ()) {
-                       var row = child as CollectionListItem;
-                       if (row == null || row.collection.get_collection_type () != CollectionType.USER)
+               int i = 0;
+               while (true) {
+                       var row = list_box.get_row_at_index (i);
+
+                       if (row == null || !(row is CollectionListItem))
+                               break;
+
+                       i++;
+
+                       var collection_row = row as CollectionListItem;
+
+                       if (collection_row.collection.get_collection_type () != CollectionType.USER)
                                continue;
 
-                       var check_button = row.activatable_widget as Gtk.CheckButton;
+                       var check_button = collection_row.activatable_widget as Gtk.CheckButton;
                        if (check_button.active)
-                               collections += row.collection;
+                               collections += collection_row.collection;
                }
 
                confirmed (collections);
@@ -153,11 +162,11 @@ private class Games.CollectionActionWindow : Hdy.Window {
        }
 
        private void new_collection () {
-               deck.navigate (Hdy.NavigationDirection.FORWARD);
+               leaflet.navigate (Adw.NavigationDirection.FORWARD);
        }
 
        private void go_back () {
-               if (create_collection_page_only || !deck.navigate (Hdy.NavigationDirection.BACK))
+               if (create_collection_page_only || !leaflet.navigate (Adw.NavigationDirection.BACK))
                        close ();
        }
 
@@ -177,16 +186,9 @@ private class Games.CollectionActionWindow : Hdy.Window {
        }
 
        [GtkCallback]
-       public bool on_key_pressed (Gdk.EventKey event) {
-               var default_modifiers = Gtk.accelerator_get_default_mod_mask ();
-
-               uint keyval;
-               var keymap = Gdk.Keymap.get_for_display (get_display ());
-               keymap.translate_keyboard_state (event.hardware_keycode, event.state,
-                                                event.group, out keyval, null, null, null);
-
+       private bool key_pressed_cb (uint keyval, uint keycode, Gdk.ModifierType state) {
                if (keyval == Gdk.Key.Escape) {
-                       if (deck.visible_child == create_collection_page) {
+                       if (leaflet.visible_child == create_collection_page) {
                                go_back ();
                                return Gdk.EVENT_STOP;
                        }
@@ -200,23 +202,23 @@ private class Games.CollectionActionWindow : Hdy.Window {
                        return Gdk.EVENT_STOP;
                }
 
-               if (((event.state & default_modifiers) == Gdk.ModifierType.MOD1_MASK) &&
+               if (state == Gdk.ModifierType.ALT_MASK &&
                   (((get_direction () == Gtk.TextDirection.LTR) && keyval == Gdk.Key.Left) ||
                    ((get_direction () == Gtk.TextDirection.RTL) && keyval == Gdk.Key.Right)) &&
-                   !create_collection_page_only && deck.navigate (Hdy.NavigationDirection.BACK))
+                   !create_collection_page_only && leaflet.navigate (Adw.NavigationDirection.BACK))
                        return Gdk.EVENT_STOP;
 
                if (is_user_collections_empty)
                        return Gdk.EVENT_PROPAGATE;
 
-               if (deck.visible_child == add_to_collection_page &&
+               if (leaflet.visible_child == add_to_collection_page &&
                    (keyval == Gdk.Key.f || keyval == Gdk.Key.F) &&
-                   (event.state & default_modifiers) == Gdk.ModifierType.CONTROL_MASK) {
+                   state == Gdk.ModifierType.CONTROL_MASK) {
                        is_search_mode = !is_search_mode;
                        return Gdk.EVENT_STOP;
                }
 
-               return search_bar.handle_event (event);
+               return Gdk.EVENT_PROPAGATE;
        }
 
        [GtkCallback]
@@ -224,36 +226,36 @@ private class Games.CollectionActionWindow : Hdy.Window {
                var name = name_entry.text.strip ();
                if (name == "") {
                        error_label.label = _("Collection name cannot be empty");
-                       name_entry.get_style_context ().add_class ("error");
+                       name_entry.add_css_class ("error");
                        is_collection_name_valid = false;
                        return;
                }
 
                if (collection_manager.does_collection_title_exist (name)) {
                        error_label.label = _("A collection with this name already exists");
-                       name_entry.get_style_context ().add_class ("error");
+                       name_entry.add_css_class ("error");
                        is_collection_name_valid = false;
                        return;
                }
 
-               name_entry.get_style_context ().remove_class ("error");
+               name_entry.remove_css_class ("error");
                error_label.label = null;
                is_collection_name_valid = true;
        }
 
        [GtkCallback]
        private void on_visible_child_changed () {
-               if (deck.visible_child == create_collection_page) {
+               if (leaflet.visible_child == create_collection_page) {
                        name_entry.text = "";
                        error_label.label = "";
-                       name_entry.get_style_context ().remove_class ("error");
-                       name_entry.grab_focus_without_selecting ();
+                       name_entry.remove_css_class ("error");
+                       name_entry.grab_focus ();
                }
        }
 
        [GtkCallback]
        private void on_search_text_notify () {
                filtering_text = search_entry.text;
-               search_entry.grab_focus_without_selecting ();
+               search_entry.grab_focus ();
        }
 }
diff --git a/src/ui/collection-icon-view.ui b/src/ui/collection-icon-view.ui
index 9be30335..68e17f86 100644
--- a/src/ui/collection-icon-view.ui
+++ b/src/ui/collection-icon-view.ui
@@ -2,57 +2,47 @@
 <interface>
   <requires lib="gtk+" version="3.24"/>
   <template class="GamesCollectionIconView" parent="GtkFlowBoxChild">
-    <property name="visible">True</property>
-      <child>
-        <object class="GtkEventBox">
-          <property name="visible">True</property>
-          <child>
-            <object class="GtkBox">
-              <property name="visible">True</property>
-              <property name="orientation">vertical</property>
-              <property name="spacing">6</property>
-              <child>
-                <object class="GtkOverlay">
-                  <property name="visible">True</property>
-                  <child>
-                    <object class="GamesCollectionThumbnail" id="thumbnail">
-                      <property name="visible">True</property>
-                    </object>
-                  </child>
-                  <child type="overlay">
-                    <object class="GtkRevealer">
-                      <property name="visible">True</property>
-                      <property name="transition-type">crossfade</property>
-                      <property name="reveal-child" bind-source="GamesCollectionIconView" 
bind-property="is_selection_mode"/>
-                      <child>
-                        <object class="GtkCheckButton" id="checkbox">
-                          <property name="visible">True</property>
-                          <property name="halign">end</property>
-                          <property name="valign">end</property>
-                          <property name="active" bind-source="GamesCollectionIconView" 
bind-property="checked" bind-flags="bidirectional"/>
-                          <style>
-                            <class name="check"/>
-                          </style>
-                        </object>
-                      </child>
-                    </object>
-                  </child>
-                </object>
-              </child>
-              <child>
-                <object class="GtkLabel" id="title">
-                  <property name="visible">True</property>
-                  <property name="ellipsize">middle</property>
-                  <property name="justify">center</property>
-                  <property name="lines">2</property>
-                  <property name="max-width-chars">0</property>
-                  <property name="wrap">True</property>
-                  <property name="wrap-mode">word-char</property>
-                </object>
-              </child>
-            </object>
-          </child>
-        </object>
-      </child>
+    <style>
+      <class name="tile"/>
+    </style>
+    <property name="child">
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkOverlay">
+            <child>
+              <object class="GamesCollectionThumbnail" id="thumbnail"/>
+            </child>
+            <child type="overlay">
+              <object class="GtkRevealer">
+                <property name="transition-type">crossfade</property>
+                <property name="reveal-child" bind-source="GamesCollectionIconView" 
bind-property="is_selection_mode"/>
+                <property name="child">
+                  <object class="GtkCheckButton" id="checkbox">
+                    <property name="halign">end</property>
+                    <property name="valign">end</property>
+                    <property name="active" bind-source="GamesCollectionIconView" bind-property="checked" 
bind-flags="bidirectional"/>
+                    <style>
+                      <class name="check"/>
+                    </style>
+                  </object>
+                </property>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="title">
+            <property name="ellipsize">middle</property>
+            <property name="justify">center</property>
+            <property name="lines">2</property>
+            <property name="max-width-chars">0</property>
+            <property name="wrap">True</property>
+            <property name="wrap-mode">word-char</property>
+          </object>
+        </child>
+      </object>
+    </property>
   </template>
 </interface>
diff --git a/src/ui/collection-icon-view.vala b/src/ui/collection-icon-view.vala
index 47b4cca9..c49b89a2 100644
--- a/src/ui/collection-icon-view.vala
+++ b/src/ui/collection-icon-view.vala
@@ -9,9 +9,6 @@ private class Games.CollectionIconView : Gtk.FlowBoxChild {
        [GtkChild]
        private unowned CollectionThumbnail thumbnail;
 
-       private Gtk.GestureMultiPress multi_press_gesture;
-       private Gtk.GestureLongPress long_press_gesture;
-
        public bool checked { get; set; }
        public bool is_selection_mode { get; set; }
 
@@ -27,28 +24,28 @@ private class Games.CollectionIconView : Gtk.FlowBoxChild {
        }
 
        construct {
-               get_style_context ().add_class ("collection-icon-view");
-
-               add_events (Gdk.EventMask.TOUCH_MASK);
+               add_css_class ("collection-icon-view");
 
-               multi_press_gesture = new Gtk.GestureMultiPress (this);
-               multi_press_gesture.button = 3;
-               multi_press_gesture.pressed.connect (() => {
-                       var event = Gtk.get_current_event ();
+               var click_gesture = new Gtk.GestureClick ();
+               click_gesture.button = 3;
+               click_gesture.pressed.connect (() => {
+                       var event = click_gesture.get_current_event ();
 
                        if (event.triggers_context_menu ()) {
                                secondary_click ();
-                               multi_press_gesture.set_state (Gtk.EventSequenceState.CLAIMED);
+                               click_gesture.set_state (Gtk.EventSequenceState.CLAIMED);
                        } else {
-                               multi_press_gesture.set_state (Gtk.EventSequenceState.DENIED);
+                               click_gesture.set_state (Gtk.EventSequenceState.DENIED);
                        }
                });
+               add_controller (click_gesture);
 
-               long_press_gesture = new Gtk.GestureLongPress (this);
+               var long_press_gesture = new Gtk.GestureLongPress ();
                long_press_gesture.pressed.connect (() => {
                        secondary_click ();
                        long_press_gesture.set_state (Gtk.EventSequenceState.CLAIMED);
                });
+               add_controller (long_press_gesture);
        }
 
        public CollectionIconView (Collection collection) {
diff --git a/src/ui/collection-list-item.ui b/src/ui/collection-list-item.ui
index 5abb8f37..128a39b0 100644
--- a/src/ui/collection-list-item.ui
+++ b/src/ui/collection-list-item.ui
@@ -1,12 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesCollectionListItem" parent="HdyActionRow">
-    <property name="visible">True</property>
+  <template class="GamesCollectionListItem" parent="AdwActionRow">
     <property name="activatable-widget">check_button</property>
     <child type="prefix">
       <object class="GtkCheckButton" id="check_button">
-        <property name="visible">True</property>
         <property name="valign">center</property>
       </object>
     </child>
diff --git a/src/ui/collection-list-item.vala b/src/ui/collection-list-item.vala
index 800cf668..75bf0725 100644
--- a/src/ui/collection-list-item.vala
+++ b/src/ui/collection-list-item.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/collection-list-item.ui")]
-private class Games.CollectionListItem : Hdy.ActionRow {
+private class Games.CollectionListItem : Adw.ActionRow {
        public Collection collection { get; construct; }
 
        construct {
diff --git a/src/ui/collection-thumbnail.ui b/src/ui/collection-thumbnail.ui
index 34d4afa6..13c72eea 100644
--- a/src/ui/collection-thumbnail.ui
+++ b/src/ui/collection-thumbnail.ui
@@ -1,60 +1,52 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesCollectionThumbnail" parent="GtkBin">
-    <property name="visible">True</property>
-    <child>
+  <template class="GamesCollectionThumbnail" parent="AdwBin">
+    <property name="child">
       <object class="GtkStack" id="thumbnail_stack">
-        <property name="visible">True</property>
         <property name="visible-child">grid</property>
         <child>
           <object class="GtkGrid" id="grid">
-            <property name="visible">True</property>
             <property name="row-spacing">6</property>
             <property name="column-spacing">6</property>
             <property name="row-homogeneous">True</property>
             <property name="column-homogeneous">True</property>
             <child>
-              <object class="GtkEventBox">
-                <property name="visible">True</property>
+              <object class="AdwBin" id="bin_1">
+                <layout>
+                  <property name="column">0</property>
+                  <property name="row">0</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left-attach">0</property>
-                <property name="top-attach">0</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkEventBox">
-                <property name="visible">True</property>
+              <object class="AdwBin" id="bin_2">
+                <layout>
+                  <property name="column">1</property>
+                  <property name="row">0</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left-attach">1</property>
-                <property name="top-attach">0</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkEventBox">
-                <property name="visible">True</property>
+              <object class="AdwBin" id="bin_3">
+                <layout>
+                  <property name="column">0</property>
+                  <property name="row">1</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left-attach">0</property>
-                <property name="top-attach">1</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkEventBox">
-                <property name="visible">True</property>
+              <object class="AdwBin" id="bin_4">
+                <layout>
+                  <property name="column">1</property>
+                  <property name="row">1</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left-attach">1</property>
-                <property name="top-attach">1</property>
-              </packing>
             </child>
           </object>
         </child>
         <child>
           <object class="GtkImage" id="new_collection_image">
-            <property name="visible">True</property>
             <property name="icon-name">list-add-symbolic</property>
             <property name="halign">center</property>
             <property name="valign">center</property>
@@ -65,6 +57,6 @@
           </object>
         </child>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
diff --git a/src/ui/collection-thumbnail.vala b/src/ui/collection-thumbnail.vala
index 0137154c..77fb125f 100644
--- a/src/ui/collection-thumbnail.vala
+++ b/src/ui/collection-thumbnail.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/collection-thumbnail.ui")]
-private class Games.CollectionThumbnail : Gtk.Bin {
+private class Games.CollectionThumbnail : Adw.Bin {
        // This should match the number of grid children in the template
        const uint N_ROWS = 2;
        const uint N_COLUMNS = 2;
@@ -9,7 +9,13 @@ private class Games.CollectionThumbnail : Gtk.Bin {
        [GtkChild]
        private unowned Gtk.Stack thumbnail_stack;
        [GtkChild]
-       private unowned Gtk.Grid grid;
+       private unowned Adw.Bin bin_1;
+       [GtkChild]
+       private unowned Adw.Bin bin_2;
+       [GtkChild]
+       private unowned Adw.Bin bin_3;
+       [GtkChild]
+       private unowned Adw.Bin bin_4;
        [GtkChild]
        private unowned Gtk.Image new_collection_image;
 
@@ -37,32 +43,22 @@ private class Games.CollectionThumbnail : Gtk.Bin {
        private void on_games_changed () {
                var max_subcovers = N_ROWS * N_COLUMNS;
 
-               var children = grid.get_children ();
-               children.reverse ();
+               Adw.Bin[] children = { bin_1, bin_2, bin_3, bin_4 };
 
                var pos = 0;
                var game_model = collection.get_game_model ();
                var n_games = game_model.get_n_items ();
-               foreach (var child in children) {
-                       var event_box = child as Gtk.Bin;
-
+               foreach (var bin in children) {
                        if (pos < n_games) {
                                var game = game_model.get_item (pos) as Game;
-                               var game_thumbnail = get_game_thumbnail (game);
-
-                               var current_thumbnail = event_box.get_child ();
-                               if (current_thumbnail != null)
-                                       event_box.remove (current_thumbnail);
-                               event_box.add (game_thumbnail);
-
+                               bin.child = get_game_thumbnail (game);
                                pos++;
+
                                continue;
                        }
 
                        if (pos < max_subcovers) {
-                               var game_thumbnail = event_box.get_child ();
-                               if (game_thumbnail != null)
-                                       event_box.remove (game_thumbnail);
+                               bin.child = null;
                                pos++;
                        }
                }
diff --git a/src/ui/collection-view.ui b/src/ui/collection-view.ui
index 3af80eeb..91cd83d3 100644
--- a/src/ui/collection-view.ui
+++ b/src/ui/collection-view.ui
@@ -14,315 +14,196 @@
     <signal name="notify::search-mode" handler="on_search_mode_changed"/>
     <child>
       <object class="GtkStack" id="header_bar_stack">
-        <property name="visible">True</property>
         <property name="transition-type">crossfade</property>
         <child>
           <object class="GtkStack" id="collections_stack">
-            <property name="visible">True</property>
             <property name="transition-type">crossfade</property>
             <child>
-              <object class="HdyHeaderBar" id="header_bar">
-                <property name="visible">True</property>
-                <property name="show-close-button">True</property>
+              <object class="AdwHeaderBar" id="header_bar">
                 <property name="centering-policy">strict</property>
-                <child>
+                <property name="title-widget">
+                  <object class="AdwViewSwitcherTitle" id="view_switcher_title">
+                    <property name="title" translatable="yes">Games</property>
+                    <property name="stack">viewstack</property>
+                    <property name="view-switcher-enabled" bind-source="GamesCollectionView" 
bind-property="is-empty-collection" bind-flags="bidirectional|sync-create|invert-boolean"/>
+                    <signal name="notify::title-visible" handler="update_adaptive_state"/>
+                  </object>
+                </property>
+                <child type="start">
                   <object class="GtkToggleButton" id="sidebar_button">
-                    <property name="visible" bind-source="GamesCollectionView" 
bind-property="is-sidebar-available"/>
-                    <property name="valign">center</property>
+                    <property name="visible" bind-source="GamesCollectionView" 
bind-property="is-sidebar-available" bind-flags="sync-create"/>
                     <property name="tooltip-text" translatable="yes">Sidebar</property>
+                    <property name="icon-name">view-sidebar-symbolic</property>
                     <property name="active" bind-source="platforms_page" bind-property="is-sidebar-visible" 
bind-flags="sync-create|bidirectional"/>
-                    <style>
-                      <class name="image-button"/>
-                    </style>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="icon-name">view-sidebar-symbolic</property>
-                      </object>
-                    </child>
                   </object>
-                  <packing>
-                    <property name="pack-type">start</property>
-                  </packing>
                 </child>
-                <child>
+                <child type="start">
                   <object class="GtkButton" id="add_game">
-                    <property name="visible" bind-source="GamesCollectionView" 
bind-property="is-add-available"/>
-                    <property name="valign">center</property>
+                    <property name="visible" bind-source="GamesCollectionView" 
bind-property="is-add-available" bind-flags="sync-create"/>
+                    <property name="tooltip-text" translatable="yes">Add game files…</property>
+                    <property name="icon-name">list-add-symbolic</property>
                     <property name="action-name">app.add-game-files</property>
-                    <style>
-                      <class name="image-button"/>
-                    </style>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="a11y-back">
-                        <property name="accessible-name" translatable="yes">Add game files…</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkImage" id="add_game_image">
-                        <property name="visible">True</property>
-                        <property name="icon-name">list-add-symbolic</property>
-                        <property name="icon-size">1</property>
-                      </object>
-                    </child>
                   </object>
-                  <packing>
-                    <property name="pack-type">start</property>
-                  </packing>
                 </child>
-                <child>
+                <child type="end">
                   <object class="GtkMenuButton" id="menu">
                     <property name="menu-model">primary_menu</property>
                     <!-- Translators: tooltip for the application menu button -->
                     <property name="tooltip-text" translatable="yes">Menu</property>
-                    <property name="valign">center</property>
-                    <property name="visible">True</property>
-                    <style>
-                      <class name="image-button"/>
-                    </style>
-                    <child>
-                      <object class="GtkImage" id="menu_image">
-                        <property name="visible">True</property>
-                        <property name="icon-name">open-menu-symbolic</property>
-                        <property name="icon-size">1</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="pack-type">end</property>
-                  </packing>
-                </child>
-                <child type="title">
-                  <object class="HdyViewSwitcherTitle" id="view_switcher_title">
-                    <property name="visible">True</property>
-                    <property name="title" translatable="yes">Games</property>
-                    <property name="stack">viewstack</property>
-                    <property name="view-switcher-enabled" bind-source="GamesCollectionView" 
bind-property="is-empty-collection" bind-flags="bidirectional|sync-create|invert-boolean"/>
-                    <signal name="notify::title-visible" handler="update_adaptive_state"/>
+                    <property name="icon-name">open-menu-symbolic</property>
                   </object>
                 </child>
-                <child>
+                <child type="end">
                   <object class="GtkButton" id="selection_button">
-                    <property name="visible">True</property>
-                    <property name="valign">center</property>
                     <property name="action-name">view.toggle-select</property>
                     <property name="sensitive" bind-source="GamesCollectionView" 
bind-property="is-empty-collection" bind-flags="bidirectional|invert-boolean"/>
-                    <child>
-                      <object class="GtkImage" id="select_image">
-                        <property name="visible">True</property>
-                        <property name="icon-name">object-select-symbolic</property>
-                      </object>
-                    </child>
-                    <child internal-child="accessible">
-                      <object class="AtkObject">
-                        <property name="accessible-name" translatable="yes">Select games</property>
-                      </object>
-                    </child>
+                    <property name="icon-name">object-select-symbolic</property>
+                    <property name="tooltip-text" translatable="yes">Select games</property>
                   </object>
-                  <packing>
-                    <property name="pack-type">end</property>
-                  </packing>
                 </child>
-                <child>
+                <child type="end">
                   <object class="GtkToggleButton" id="search">
-                    <property name="visible">True</property>
-                    <property name="valign">center</property>
                     <property name="active" bind-source="GamesCollectionView" bind-property="search-mode" 
bind-flags="bidirectional"/>
                     <property name="sensitive" bind-source="GamesCollectionView" 
bind-property="is-empty-collection" bind-flags="bidirectional|invert-boolean"/>
-                    <style>
-                      <class name="image-button"/>
-                    </style>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="a11y-search">
-                        <property name="accessible-name" translatable="yes">Search</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkImage" id="search_image">
-                        <property name="visible">True</property>
-                        <property name="icon-name">edit-find-symbolic</property>
-                        <property name="icon-size">1</property>
-                      </object>
-                    </child>
+                    <property name="tooltip-text" translatable="yes">Search</property>
+                    <property name="icon-name">edit-find-symbolic</property>
                   </object>
-                  <packing>
-                    <property name="pack-type">end</property>
-                  </packing>
                 </child>
               </object>
             </child>
             <child>
-              <object class="HdyHeaderBar" id="collection_subpage_header_bar">
-                <property name="visible">True</property>
-                <property name="show-close-button">True</property>
-                <property name="title" bind-source="collections_page" bind-property="collection-title"/>
-                <child>
-                  <object class="GtkButton">
-                    <property name="visible">True</property>
-                    <property name="valign">center</property>
-                    <signal name="clicked" handler="on_collection_subpage_back_clicked"/>
-                    <style>
-                      <class name="image-button"/>
-                    </style>
-                    <child internal-child="accessible">
-                      <object class="AtkObject">
-                        <property name="accessible-name" translatable="yes">Back</property>
+              <object class="GtkHeaderBar" id="collection_subpage_header_bar">
+                <property name="title-widget">
+                  <object class="GamesPopoverBin">
+                    <property name="child">
+                      <object class="AdwWindowTitle">
+                        <property name="title" bind-source="collections_page" 
bind-property="collection-title"/>
                       </object>
-                    </child>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="icon-name">go-previous-symbolic</property>
-                        <property name="icon-size">1</property>
+                    </property>
+                    <property name="popover">
+                      <object class="GtkPopover" id="rename_popover">
+                        <property name="width-request">360</property>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="orientation">vertical</property>
+                            <property name="margin-top">6</property>
+                            <property name="margin-bottom">6</property>
+                            <property name="margin-start">6</property>
+                            <property name="margin-end">6</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="label" translatable="yes">Rename Collection</property>
+                                <attributes>
+                                  <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="spacing">12</property>
+                                <child>
+                                  <object class="GtkEntry" id="collection_rename_entry">
+                                    <property name="hexpand">true</property>
+                                    <signal name="notify::text" handler="update_collection_name_validity"/>
+                                    <signal name="activate" handler="on_collection_rename_activated"/>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkButton">
+                                    <property name="label" translatable="yes">_Rename</property>
+                                    <property name="use-underline">True</property>
+                                    <property name="sensitive" bind-source="GamesCollectionView" 
bind-property="is-collection-rename-valid"/>
+                                    <signal name="clicked" handler="on_collection_rename_activated"/>
+                                    <style>
+                                      <class name="suggested-action"/>
+                                    </style>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkRevealer">
+                                <property name="reveal-child" bind-source="GamesCollectionView" 
bind-property="is-collection-rename-valid" bind-flags="invert-boolean|sync-create"/>
+                                <child>
+                                  <object class="GtkLabel" id="collection_rename_error_label">
+                                    <property name="margin-top">12</property>
+                                    <property name="halign">center</property>
+                                    <property name="wrap">true</property>
+                                    <property name="wrap-mode">word</property>
+                                    <style>
+                                      <class name="dim-label"/>
+                                    </style>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
                       </object>
-                    </child>
+                    </property>
+                  </object>
+                </property>
+                <child type="start">
+                  <object class="GtkButton">
+                    <property name="icon-name">go-previous-symbolic</property>
+                    <property name="tooltip-text" translatable="yes">Back</property>
+                    <signal name="clicked" handler="on_collection_subpage_back_clicked"/>
                   </object>
                 </child>
-                <child>
+                <child type="end">
                   <object class="GtkMenuButton">
+                    <property name="visible" bind-source="collections_page" 
bind-property="is-showing-user-collection" bind-flags="sync-create"/>
                     <property name="menu-model">collection_menu</property>
+                    <property name="icon-name">view-more-symbolic</property>
                     <property name="tooltip-text" translatable="yes">Collection menu</property>
-                    <property name="valign">center</property>
-                    <property name="visible" bind-source="collections_page" 
bind-property="is-showing-user-collection"/>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="icon-name">view-more-symbolic</property>
-                      </object>
-                    </child>
-                    <style>
-                      <class name="image-button"/>
-                    </style>
                   </object>
-                  <packing>
-                    <property name="pack-type">end</property>
-                  </packing>
                 </child>
-                <child>
+                <child type="end">
                   <object class="GtkButton">
-                    <property name="visible">True</property>
-                    <property name="valign">center</property>
                     <property name="action-name">view.toggle-select</property>
                     <property name="sensitive" bind-source="collections_page" 
bind-property="is-collection-empty" bind-flags="invert-boolean"/>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="icon-name">object-select-symbolic</property>
-                      </object>
-                    </child>
-                    <child internal-child="accessible">
-                      <object class="AtkObject">
-                        <property name="accessible-name" translatable="yes">Select games</property>
-                      </object>
-                    </child>
+                    <property name="icon-name">object-select-symbolic</property>
+                    <property name="tooltip-text" translatable="yes">Select games</property>
                   </object>
-                  <packing>
-                    <property name="pack-type">end</property>
-                  </packing>
                 </child>
-                <child>
+                <child type="end">
                   <object class="GtkToggleButton">
-                    <property name="visible">True</property>
-                    <property name="valign">center</property>
                     <property name="active" bind-source="GamesCollectionView" bind-property="search-mode" 
bind-flags="bidirectional"/>
                     <property name="sensitive" bind-source="collections_page" 
bind-property="is-collection-empty" bind-flags="invert-boolean"/>
-                    <style>
-                      <class name="image-button"/>
-                    </style>
-                    <child internal-child="accessible">
-                      <object class="AtkObject">
-                        <property name="accessible-name" translatable="yes">Search</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="icon-name">edit-find-symbolic</property>
-                        <property name="icon-size">1</property>
-                      </object>
-                    </child>
+                    <property name="icon-name">edit-find-symbolic</property>
+                    <property name="tooltip-text" translatable="yes">Search</property>
                   </object>
-                  <packing>
-                    <property name="pack-type">end</property>
-                  </packing>
                 </child>
               </object>
             </child>
           </object>
         </child>
         <child>
-          <object class="HdyHeaderBar" id="selection_mode_header_bar">
-            <property name="visible">True</property>
-            <child>
+          <object class="GtkHeaderBar" id="selection_mode_header_bar">
+            <property name="show-title-buttons">False</property>
+            <property name="title-widget">
+              <object class="GtkMenuButton" id="selection_mode_menu_button">
+                <property name="menu-model">selection-menu</property>
+                <property name="has-frame">False</property>
+                <property name="label">Click on items to select them</property>
+                <style>
+                  <class name="numeric"/>
+                </style>
+              </object>
+            </property>
+            <child type="end">
               <object class="GtkButton" id="cancel_selection_button">
-                <property name="visible">True</property>
-                <property name="valign">center</property>
                 <property name="label" translatable="yes">_Cancel</property>
                 <property name="action-name">view.toggle-select</property>
                 <property name="use-underline">True</property>
-                <child internal-child="accessible">
-                  <object class="AtkObject">
-                    <property name="accessible-name" translatable="yes">Cancel</property>
-                  </object>
-                </child>
               </object>
-              <packing>
-                <property name="pack-type">end</property>
-              </packing>
             </child>
-            <child>
+            <child type="end">
               <object class="GtkToggleButton" id="selection_mode_search">
-                <property name="visible">True</property>
-                <property name="valign">center</property>
                 <property name="active" bind-source="GamesCollectionView" bind-property="search-mode" 
bind-flags="bidirectional"/>
                 <property name="sensitive" bind-source="GamesCollectionView" 
bind-property="is-empty-collection" bind-flags="bidirectional|invert-boolean"/>
-                <style>
-                  <class name="image-button"/>
-                </style>
-                <child internal-child="accessible">
-                  <object class="AtkObject">
-                    <property name="accessible-name" translatable="yes">Search</property>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkImage">
-                    <property name="visible">True</property>
-                    <property name="icon-name">edit-find-symbolic</property>
-                    <property name="icon-size">1</property>
-                  </object>
-                </child>
-              </object>
-              <packing>
-                <property name="pack-type">end</property>
-              </packing>
-            </child>
-            <child type="title">
-              <object class="GtkMenuButton">
-                <property name="menu-model">selection-menu</property>
-                <property name="visible">True</property>
-                <property name="relief">none</property>
-                <child>
-                  <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="spacing">6</property>
-                    <child>
-                      <object class="GtkLabel" id="selection_mode_label">
-                        <property name="visible">True</property>
-                        <property name="label" translatable="yes">Click on items to select them</property>
-                        <property name="ellipsize">end</property>
-                        <attributes>
-                          <attribute name="font-features" value="tnum=1"/>
-                        </attributes>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="icon-name">pan-down-symbolic</property>
-                      </object>
-                    </child>
-                  </object>
-                </child>
+                <property name="tooltip-text" translatable="yes">Search</property>
+                <property name="icon-name">edit-find-symbolic</property>
               </object>
             </child>
             <style>
@@ -334,7 +215,7 @@
     </child>
     <child>
       <object class="GamesSearchBar" id="search_bar">
-        <property name="visible">True</property>
+        <property name="key_capture_widget">GamesCollectionView</property>
         <property name="search-mode-enabled" bind-source="GamesCollectionView" bind-property="search-mode" 
bind-flags="bidirectional"/>
         <signal name="notify::text" handler="on_search_text_notify"/>
       </object>
@@ -344,80 +225,75 @@
     </child>
     <child>
       <object class="GtkOverlay">
-        <property name="visible">True</property>
         <property name="vexpand">True</property>
         <child>
           <object class="GtkStack" id="empty_stack">
-            <property name="visible">True</property>
             <property name="visible-child">empty_collection</property>
             <property name="transition-type">crossfade</property>
             <style>
               <class name="background"/>
             </style>
             <child>
-              <object class="HdyStatusPage" id="empty_search">
-                <property name="visible">True</property>
+              <object class="AdwStatusPage" id="empty_search">
                 <property name="icon_name">edit-find-symbolic</property>
                 <property name="description" translatable="yes">Try a different search.</property>
               </object>
             </child>
             <child>
-              <object class="HdyStatusPage" id="empty_collection">
-                <property name="visible">True</property>
+              <object class="AdwStatusPage" id="empty_collection">
                 <property name="title" translatable="yes" comments="Translators: This is displayed when the 
games library is empty.">No games found</property>
                 <property name="description" translatable="yes" comments="Translators: This is displayed 
when the games library is empty.">Install games or add directories containing games to your search 
sources.</property>
               </object>
-              <packing>
-                <property name="name">empty</property>
-              </packing>
             </child>
             <child>
               <object class="GtkStack" id="viewstack">
-                <property name="visible">True</property>
                 <property name="visible-child">games_page</property>
                 <property name="transition-type">crossfade</property>
                 <signal name="notify::visible-child" handler="on_visible_child_changed"/>
                 <child>
-                  <object class="GamesGamesPage" id="games_page">
-                    <property name="visible">True</property>
-                    <property name="is-selection-mode" bind-source="GamesCollectionView" 
bind-property="is-selection-mode" bind-flags="bidirectional"/>
-                    <signal name="game-activated" handler="on_game_activated"/>
-                    <signal name="selected-items-changed" handler="on_selected_items_changed"/>
-                  </object>
-                  <packing>
+                  <object class="GtkStackPage" id="games_stack_page">
                     <property name="name">games</property>
                     <property name="title" translatable="yes">Games</property>
-                  </packing>
+                    <property name="child">
+                      <object class="GamesGamesPage" id="games_page">
+                        <property name="is-selection-mode" bind-source="GamesCollectionView" 
bind-property="is-selection-mode" bind-flags="bidirectional"/>
+                        <signal name="game-activated" handler="on_game_activated"/>
+                        <signal name="selected-items-changed" handler="on_selected_items_changed"/>
+                      </object>
+                    </property>
+                  </object>
                 </child>
                 <child>
-                  <object class="GamesPlatformsPage" id="platforms_page">
-                    <property name="visible">True</property>
-                    <property name="is-folded" bind-source="GamesCollectionView" bind-property="is-folded" 
bind-flags="bidirectional"/>
-                    <property name="is-selection-mode" bind-source="GamesCollectionView" 
bind-property="is-selection-mode" bind-flags="bidirectional"/>
-                    <signal name="game-activated" handler="on_game_activated"/>
-                    <signal name="selected-items-changed" handler="on_selected_items_changed"/>
-                  </object>
-                  <packing>
+                  <object class="GtkStackPage">
                     <property name="name">platform</property>
                     <!-- FIXME: the icon is meant to be used for text formatting -->
                     <property name="icon-name">view-list-bullet-symbolic</property>
                     <property name="title" translatable="yes">Platforms</property>
-                  </packing>
+                    <property name="child">
+                      <object class="GamesPlatformsPage" id="platforms_page">
+                        <property name="is-folded" bind-source="GamesCollectionView" 
bind-property="is-folded" bind-flags="bidirectional"/>
+                        <property name="is-selection-mode" bind-source="GamesCollectionView" 
bind-property="is-selection-mode" bind-flags="bidirectional"/>
+                        <signal name="game-activated" handler="on_game_activated"/>
+                        <signal name="selected-items-changed" handler="on_selected_items_changed"/>
+                      </object>
+                    </property>
+                  </object>
                 </child>
                 <child>
-                  <object class="GamesCollectionsPage" id="collections_page">
-                    <property name="visible">True</property>
-                    <property name="is-selection-mode" bind-source="GamesCollectionView" 
bind-property="is-selection-mode" bind-flags="bidirectional"/>
-                    <property name="is-search-mode" bind-source="GamesCollectionView" 
bind-property="search-mode" bind-flags="bidirectional"/>
-                    <signal name="game-activated" handler="on_game_activated"/>
-                    <signal name="selected-items-changed" handler="on_selected_items_changed"/>
-                    <signal name="notify::is-subpage-open" handler="on_collection_subpage_opened"/>
-                  </object>
-                  <packing>
+                  <object class="GtkStackPage">
                     <property name="name">collections</property>
                     <property name="icon-name">folder-symbolic</property>
                     <property name="title" translatable="yes">Collections</property>
-                  </packing>
+                    <property name="child">
+                      <object class="GamesCollectionsPage" id="collections_page">
+                        <property name="is-selection-mode" bind-source="GamesCollectionView" 
bind-property="is-selection-mode" bind-flags="bidirectional"/>
+                        <property name="is-search-mode" bind-source="GamesCollectionView" 
bind-property="search-mode" bind-flags="bidirectional"/>
+                        <signal name="game-activated" handler="on_game_activated"/>
+                        <signal name="selected-items-changed" handler="on_selected_items_changed"/>
+                        <signal name="notify::is-subpage-open" handler="on_collection_subpage_opened"/>
+                      </object>
+                    </property>
+                  </object>
                 </child>
               </object>
             </child>
@@ -425,41 +301,31 @@
         </child>
         <child type="overlay">
           <object class="GtkRevealer" id="loading_notification_revealer">
-            <property name="visible">True</property>
             <property name="halign">center</property>
             <property name="valign">start</property>
             <property name="reveal-child" bind-source="GamesCollectionView" 
bind-property="loading-notification" bind-flags="bidirectional"/>
             <child>
               <object class="GtkBox">
-                <property name="visible">True</property>
                 <property name="spacing">18</property>
                 <child>
                   <object class="GtkSpinner">
-                    <property name="visible">True</property>
-                    <property name="active">True</property>
+                    <property name="spinning">True</property>
+                    <property name="margin-start">12</property>
+                    <property name="margin-end">12</property>
                   </object>
                 </child>
                 <child>
                   <object class="GtkLabel">
-                    <property name="visible">True</property>
                     <property name="label" translatable="yes">Loading</property>
                   </object>
                 </child>
                 <child>
                   <object class="GtkButton">
-                    <property name="visible">True</property>
-                    <property name="can-focus">True</property>
-                    <property name="always-show-image">True</property>
+                    <property name="icon-name">window-close-symbolic</property>
                     <signal name="clicked" handler="on_loading_notification_closed"/>
                     <style>
                       <class name="flat"/>
                     </style>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="icon-name">window-close-symbolic</property>
-                      </object>
-                    </child>
                   </object>
                 </child>
                 <style>
@@ -471,26 +337,22 @@
         </child>
         <child type="overlay">
           <object class="GamesUndoNotification" id="undo_notification">
-            <property name="visible">True</property>
             <property name="label" bind-source="collections_page" 
bind-property="removed-notification-title"/>
           </object>
         </child>
       </object>
     </child>
     <child>
-      <object class="HdyViewSwitcherBar" id="view_switcher_bar">
-        <property name="visible">True</property>
+      <object class="AdwViewSwitcherBar" id="view_switcher_bar">
         <property name="stack">viewstack</property>
       </object>
     </child>
     <child>
       <object class="GtkRevealer">
-        <property name="visible">True</property>
         <property name="reveal-child" bind-source="GamesCollectionView" bind-property="is-selection-mode"/>
         <property name="transition-type">slide-up</property>
         <child>
           <object class="GamesSelectionActionBar" id="selection_action_bar">
-            <property name="visible">True</property>
             <property name="show-remove-button" bind-source="collections_page" 
bind-property="is-showing-user-collection"/>
             <property name="show-game-actions" bind-source="GamesCollectionView" 
bind-property="show-game-actions"/>
             <property name="show-remove-collection-button" bind-source="GamesCollectionView" 
bind-property="show-remove-action-button"/>
@@ -499,74 +361,6 @@
       </object>
     </child>
   </template>
-  <object class="GtkPopover" id="rename_popover">
-    <property name="position">top</property>
-    <property name="relative-to">collection_subpage_header_bar</property>
-    <property name="width-request">360</property>
-    <child>
-      <object class="GtkBox">
-        <property name="visible">true</property>
-        <property name="orientation">vertical</property>
-        <property name="margin-top">12</property>
-        <property name="margin-bottom">12</property>
-        <property name="margin-start">12</property>
-        <property name="margin-end">12</property>
-        <property name="spacing">12</property>
-        <child>
-          <object class="GtkLabel">
-            <property name="visible">true</property>
-            <property name="label" translatable="yes">Rename Collection</property>
-            <attributes>
-              <attribute name="weight" value="bold"/>
-            </attributes>
-          </object>
-        </child>
-        <child>
-          <object class="GtkBox">
-            <property name="visible">true</property>
-            <property name="spacing">12</property>
-            <child>
-              <object class="GtkEntry" id="collection_rename_entry">
-                <property name="visible">true</property>
-                <property name="hexpand">true</property>
-                <signal name="notify::text" handler="update_collection_name_validity"/>
-                <signal name="activate" handler="on_collection_rename_activated"/>
-              </object>
-            </child>
-            <child>
-              <object class="GtkButton">
-                <property name="visible">true</property>
-                <property name="label" translatable="yes">_Rename</property>
-                <property name="use-underline">True</property>
-                <property name="sensitive" bind-source="GamesCollectionView" 
bind-property="is-collection-rename-valid"/>
-                <signal name="clicked" handler="on_collection_rename_activated"/>
-                <style>
-                  <class name="suggested-action"/>
-                </style>
-              </object>
-            </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkRevealer">
-            <property name="visible">True</property>
-            <property name="reveal-child" bind-source="GamesCollectionView" 
bind-property="is-collection-rename-valid" bind-flags="invert-boolean|sync-create"/>
-            <child>
-              <object class="GtkLabel" id="collection_rename_error_label">
-                <property name="visible">true</property>
-                <property name="halign">center</property>
-                <property name="wrap">true</property>
-                <property name="wrap-mode">word</property>
-                <style>
-                  <class name="dim-label"/>
-                </style>
-              </object>
-            </child>
-          </object>
-        </child>
-      </object>
-    </child>
-  </object>
   <menu id="primary_menu">
     <section>
       <item>
diff --git a/src/ui/collection-view.vala b/src/ui/collection-view.vala
index 813a63a6..80461e00 100644
--- a/src/ui/collection-view.vala
+++ b/src/ui/collection-view.vala
@@ -11,19 +11,21 @@ private class Games.CollectionView : Gtk.Box, UiView {
        [GtkChild]
        private unowned Gtk.Stack header_bar_stack;
        [GtkChild]
-       private unowned Hdy.HeaderBar header_bar;
+       private unowned Adw.HeaderBar header_bar;
        [GtkChild]
-       private unowned Hdy.HeaderBar selection_mode_header_bar;
+       private unowned Gtk.HeaderBar selection_mode_header_bar;
        [GtkChild]
-       private unowned Hdy.ViewSwitcherTitle view_switcher_title;
+       private unowned Adw.ViewSwitcherTitle view_switcher_title;
        [GtkChild]
        private unowned ErrorInfoBar error_info_bar;
        [GtkChild]
        private unowned SearchBar search_bar;
        [GtkChild]
-       private unowned Hdy.StatusPage empty_collection;
+       private unowned Adw.StatusPage empty_collection;
        [GtkChild]
-       private unowned Hdy.StatusPage empty_search;
+       private unowned Adw.StatusPage empty_search;
+       [GtkChild]
+       private unowned Gtk.StackPage games_stack_page;
        [GtkChild]
        private unowned GamesPage games_page;
        [GtkChild]
@@ -31,7 +33,7 @@ private class Games.CollectionView : Gtk.Box, UiView {
        [GtkChild]
        private unowned CollectionsPage collections_page;
        [GtkChild]
-       private unowned Hdy.HeaderBar collection_subpage_header_bar;
+       private unowned Gtk.HeaderBar collection_subpage_header_bar;
        [GtkChild]
        private unowned SelectionActionBar selection_action_bar;
        [GtkChild]
@@ -39,7 +41,7 @@ private class Games.CollectionView : Gtk.Box, UiView {
        [GtkChild]
        private unowned Gtk.Stack viewstack;
        [GtkChild]
-       private unowned Hdy.ViewSwitcherBar view_switcher_bar;
+       private unowned Adw.ViewSwitcherBar view_switcher_bar;
        [GtkChild]
        private unowned UndoNotification undo_notification;
        [GtkChild]
@@ -49,7 +51,7 @@ private class Games.CollectionView : Gtk.Box, UiView {
        [GtkChild]
        private unowned Gtk.Label collection_rename_error_label;
        [GtkChild]
-       private unowned Gtk.Label selection_mode_label;
+       private unowned Gtk.MenuButton selection_mode_menu_button;
 
        private bool _is_view_active;
        public bool is_view_active {
@@ -72,6 +74,7 @@ private class Games.CollectionView : Gtk.Box, UiView {
                get { return _is_empty_collection; }
                set {
                        _is_empty_collection = value;
+
                        if (_is_empty_collection)
                                empty_stack.visible_child = empty_collection;
                        else
@@ -154,10 +157,9 @@ private class Games.CollectionView : Gtk.Box, UiView {
 
                undo_notification.undo.connect (collections_page.undo_remove_collection);
                undo_notification.closed.connect (collections_page.finalize_collection_removal);
-               window.destroy.connect (collections_page.finalize_collection_removal);
 
                var icon_name = Config.APPLICATION_ID + "-symbolic";
-               viewstack.child_set (games_page, "icon-name", icon_name);
+               games_stack_page.icon_name = icon_name;
                empty_collection.icon_name = icon_name;
 
                konami_code = new KonamiCode (window);
@@ -171,23 +173,26 @@ private class Games.CollectionView : Gtk.Box, UiView {
                update_available_selection_actions ();
        }
 
+       static construct {
+               typeof (PopoverBin).ensure ();
+       }
+
+       protected override void dispose () {
+               collections_page.finalize_collection_removal ();
+
+               base.dispose ();
+       }
+
        public void show_error (string error_message) {
                error_info_bar.show_error (error_message);
        }
 
-       public bool on_button_pressed (Gdk.EventButton event) {
+       public bool on_button_pressed (uint button) {
                return false;
        }
 
-       public bool on_key_pressed (Gdk.EventKey event) {
-               var default_modifiers = Gtk.accelerator_get_default_mod_mask ();
-
-               uint keyval;
-               var keymap = Gdk.Keymap.get_for_display (window.get_display ());
-               keymap.translate_keyboard_state (event.hardware_keycode, event.state,
-                                                event.group, out keyval, null, null, null);
-
-               if (((event.state & default_modifiers) == Gdk.ModifierType.MOD1_MASK) &&
+       public bool on_key_pressed (uint keyval, uint keycode, Gdk.ModifierType state) {
+               if ((state == Gdk.ModifierType.ALT_MASK) &&
                    (((window.get_direction () == Gtk.TextDirection.LTR) && keyval == Gdk.Key.Left) ||
                     ((window.get_direction () == Gtk.TextDirection.RTL) && keyval == Gdk.Key.Right)) &&
                    !is_selection_mode &&
@@ -196,7 +201,7 @@ private class Games.CollectionView : Gtk.Box, UiView {
                }
 
                if ((keyval == Gdk.Key.f || keyval == Gdk.Key.F) &&
-                   (event.state & default_modifiers) == Gdk.ModifierType.CONTROL_MASK &&
+                   state == Gdk.ModifierType.CONTROL_MASK &&
                     !collections_page.is_collection_empty &&
                     !is_empty_collection) {
                        if (!search_mode)
@@ -206,7 +211,7 @@ private class Games.CollectionView : Gtk.Box, UiView {
                }
 
                if ((keyval == Gdk.Key.question) &&
-                       (event.state & default_modifiers) == (Gdk.ModifierType.CONTROL_MASK | 
Gdk.ModifierType.SHIFT_MASK)) {
+                       state == (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK)) {
 
                        var win = window as Gtk.ApplicationWindow;
                        var action = win.lookup_action ("show-help-overlay");
@@ -217,7 +222,7 @@ private class Games.CollectionView : Gtk.Box, UiView {
                }
 
                if (((keyval == Gdk.Key.Menu) || ((keyval == Gdk.Key.F10) &&
-                   (event.state & default_modifiers) == Gdk.ModifierType.SHIFT_MASK)) &&
+                   state == Gdk.ModifierType.SHIFT_MASK)) &&
                    !collections_page.is_collection_empty && !is_empty_collection) {
                        toggle_select ();
 
@@ -229,6 +234,7 @@ private class Games.CollectionView : Gtk.Box, UiView {
 
                if (is_selection_mode && keyval == Gdk.Key.Escape) {
                        toggle_select ();
+
                        return true;
                }
 
@@ -236,7 +242,7 @@ private class Games.CollectionView : Gtk.Box, UiView {
                    collections_page.is_collection_empty))
                        return false;
 
-               return search_bar.handle_event (event);
+               return false;
        }
 
        public bool gamepad_button_press_event (Manette.Event event) {
@@ -258,26 +264,20 @@ private class Games.CollectionView : Gtk.Box, UiView {
                        if (is_selection_mode || collections_page.is_subpage_open)
                                return true;
 
-                       var views = viewstack.get_children ();
-                       unowned List<weak Gtk.Widget> current_view = views.find (viewstack.visible_child);
-
-                       assert (current_view != null);
+                       var prev_sibling = viewstack.visible_child.get_prev_sibling ();
 
-                       if (current_view.prev != null)
-                               viewstack.visible_child = current_view.prev.data;
+                       if (prev_sibling != null)
+                               viewstack.visible_child = prev_sibling;
 
                        return true;
                case EventCode.BTN_TR:
                        if (is_selection_mode || collections_page.is_subpage_open)
                                return true;
 
-                       var views = viewstack.get_children ();
-                       unowned List<weak Gtk.Widget> current_view = views.find (viewstack.visible_child);
+                       var next_sibling = viewstack.visible_child.get_next_sibling ();
 
-                       assert (current_view != null);
-
-                       if (current_view.next != null)
-                               viewstack.visible_child = current_view.next.data;
+                       if (next_sibling != null)
+                               viewstack.visible_child = next_sibling;
 
                        return true;
                default:
@@ -324,12 +324,7 @@ private class Games.CollectionView : Gtk.Box, UiView {
                if (!is_view_active)
                        return;
 
-               try {
-                       Gtk.show_uri_on_window (window, CONTRIBUTE_URI, Gtk.get_current_event_time ());
-               }
-               catch (Error e) {
-                       critical (e.message);
-               }
+               Gtk.show_uri (window, CONTRIBUTE_URI, Gdk.CURRENT_TIME);
        }
 
        public void run_search (string query) {
@@ -381,7 +376,7 @@ private class Games.CollectionView : Gtk.Box, UiView {
                                         collections_page.current_collection;
                var dialog = new CollectionActionWindow (false, current_collection);
                dialog.collection_model = collection_model;
-               dialog.transient_for = get_toplevel () as ApplicationWindow;
+               dialog.transient_for = get_root () as ApplicationWindow;
                dialog.modal = true;
                dialog.visible = true;
 
@@ -494,9 +489,9 @@ private class Games.CollectionView : Gtk.Box, UiView {
                }
 
                if (is_collection_rename_valid)
-                       collection_rename_entry.get_style_context ().remove_class ("error");
+                       collection_rename_entry.remove_css_class ("error");
                else
-                       collection_rename_entry.get_style_context ().add_class ("error");
+                       collection_rename_entry.add_css_class ("error");
        }
 
        [GtkCallback]
@@ -538,7 +533,7 @@ private class Games.CollectionView : Gtk.Box, UiView {
                else
                        label = _("Click on items to select them");
 
-               selection_mode_label.label = label;
+               selection_mode_menu_button.label = label;
        }
 
        [GtkCallback]
@@ -552,8 +547,7 @@ private class Games.CollectionView : Gtk.Box, UiView {
                        header_bar_stack.visible_child = selection_mode_header_bar;
 
                        selection_action_bar.favorite_state = SelectionActionBar.FavoriteState.NONE_FAVORITE;
-               }
-               else {
+               } else {
                        select_none ();
                        header_bar_stack.visible_child = collections_stack;
                        if (collections_page.is_subpage_open)
@@ -596,9 +590,8 @@ private class Games.CollectionView : Gtk.Box, UiView {
 
                filtering_text = null;
 
-               if (search_mode) {
+               if (search_mode)
                        on_search_text_notify ();
-               }
 
                update_add_game_availablity ();
                update_available_selection_actions ();
diff --git a/src/ui/collections-main-page.ui b/src/ui/collections-main-page.ui
index 5b638331..d41f9676 100644
--- a/src/ui/collections-main-page.ui
+++ b/src/ui/collections-main-page.ui
@@ -1,39 +1,38 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesCollectionsMainPage" parent="GtkBin">
-    <property name="visible">True</property>
+  <template class="GamesCollectionsMainPage" parent="AdwBin">
     <signal name="map" after="yes" handler="on_map"/>
     <signal name="unmap" after="no" handler="on_unmap"/>
-    <signal name="size-allocate" after="no" handler="on_size_allocate"/>
     <signal name="notify::is-search-mode" handler="on_search_mode_changed"/>
-    <child>
+    <property name="child">
       <object class="GtkScrolledWindow" id="scrolled_window">
-        <property name="visible">True</property>
-        <property name="can-focus">True</property>
         <property name="hscrollbar-policy">never</property>
         <style>
           <class name="content-view"/>
         </style>
-        <child>
-          <object class="GtkFlowBox" id="flow_box">
-            <property name="visible">True</property>
-            <property name="can-focus">True</property>
-            <property name="halign">center</property>
-            <property name="valign">start</property>
-            <property name="margin-start">28</property>
-            <property name="margin-end">28</property>
-            <property name="margin-top">21</property>
-            <property name="margin-bottom">21</property>
-            <property name="homogeneous">True</property>
-            <property name="column-spacing">14</property>
-            <property name="row-spacing">14</property>
-            <property name="selection-mode">single</property>
-            <signal name="child-activated" handler="on_child_activated"/>
+        <property name="child">
+          <object class="GtkViewport">
+            <property name="scroll-to-focus">True</property>
+            <property name="child">
+              <object class="GtkFlowBox" id="flow_box">
+                <property name="halign">center</property>
+                <property name="valign">start</property>
+                <property name="margin-start">28</property>
+                <property name="margin-end">28</property>
+                <property name="margin-top">21</property>
+                <property name="margin-bottom">21</property>
+                <property name="homogeneous">True</property>
+                <property name="column-spacing">14</property>
+                <property name="row-spacing">14</property>
+                <property name="selection-mode">single</property>
+                <signal name="child-activated" handler="on_child_activated"/>
+              </object>
+            </property>
           </object>
-        </child>
+        </property>
       </object>
-    </child>
+    </property>
   </template>
   <object class="GamesGamepadBrowse" id="gamepad_browse">
     <signal name="browse" handler="on_gamepad_browse"/>
diff --git a/src/ui/collections-main-page.vala b/src/ui/collections-main-page.vala
index 22d43d63..f5246882 100644
--- a/src/ui/collections-main-page.vala
+++ b/src/ui/collections-main-page.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/collections-main-page.ui")]
-private class Games.CollectionsMainPage : Gtk.Bin {
+private class Games.CollectionsMainPage : Adw.Bin {
        public signal void collection_activated (Collection collection);
        public signal void selected_items_changed ();
        public signal void gamepad_accepted ();
@@ -53,6 +53,18 @@ private class Games.CollectionsMainPage : Gtk.Bin {
                flow_box.max_children_per_line = uint.MAX;
 
                selected_collections = new GenericSet<CollectionIconView> (CollectionIconView.hash, 
CollectionIconView.equal);
+
+               var paintable = new Gtk.WidgetPaintable (this);
+               paintable.invalidate_size.connect (() => {
+                       // If the window's width is less than half the width of a 1920Ă—1080
+                       // screen, display the game thumbnails at half the size to see more of
+                       // them rather than a few huge thumbnails, making Games more usable on
+                       // small screens.
+                       if (paintable.get_intrinsic_width () < 960)
+                               remove_css_class ("large");
+                       else
+                               add_css_class ("large");
+               });
        }
 
        public bool has_collection_selected () {
@@ -117,13 +129,14 @@ private class Games.CollectionsMainPage : Gtk.Bin {
        }
 
        public void select_all () {
-               foreach (var child in flow_box.get_children ()) {
-                       var collection_icon_view = child as CollectionIconView;
+               for (int i = 0; i < collection_model.get_n_items (); i++) {
+                       var child = flow_box.get_child_at_index (i) as CollectionIconView;
+
                        if (is_search_mode)
-                               collection_icon_view.checked = filtering_terms == null ||
-                                                              filter_collection 
(collection_icon_view.collection);
+                               child.checked = filtering_terms == null ||
+                                               filter_collection (child.collection);
                        else
-                               collection_icon_view.checked = collection_icon_view.collection is 
UserCollection;
+                               child.checked = child.collection is UserCollection;
                }
 
                selected_items_changed ();
@@ -260,13 +273,13 @@ private class Games.CollectionsMainPage : Gtk.Bin {
 
                switch (direction) {
                case Gtk.DirectionType.UP:
-                       return flow_box.move_cursor (Gtk.MovementStep.DISPLAY_LINES, -1);
+                       return flow_box.move_cursor (Gtk.MovementStep.DISPLAY_LINES, -1, false, false);
                case Gtk.DirectionType.DOWN:
-                       return flow_box.move_cursor (Gtk.MovementStep.DISPLAY_LINES, 1);
+                       return flow_box.move_cursor (Gtk.MovementStep.DISPLAY_LINES, 1, false, false);
                case Gtk.DirectionType.LEFT:
-                       return flow_box.move_cursor (Gtk.MovementStep.VISUAL_POSITIONS, -1);
+                       return flow_box.move_cursor (Gtk.MovementStep.VISUAL_POSITIONS, -1, false, false);
                case Gtk.DirectionType.RIGHT:
-                       return flow_box.move_cursor (Gtk.MovementStep.VISUAL_POSITIONS, 1);
+                       return flow_box.move_cursor (Gtk.MovementStep.VISUAL_POSITIONS, 1, false, false);
                default:
                        return false;
                }
@@ -292,16 +305,4 @@ private class Games.CollectionsMainPage : Gtk.Bin {
                if (collection_icon_view != null)
                        collection_activated (collection_icon_view.collection);
        }
-
-       [GtkCallback]
-       private void on_size_allocate (Gtk.Allocation allocation) {
-               // If the window's width is less than half the width of a 1920Ă—1080
-               // screen, display the game thumbnails at half the size to see more of
-               // them rather than a few huge thumbnails, making Games more usable on
-               // small screens.
-               if (allocation.width < 960)
-                       get_style_context ().remove_class ("large");
-               else
-                       get_style_context ().add_class ("large");
-       }
 }
diff --git a/src/ui/collections-page.ui b/src/ui/collections-page.ui
index 44ae8c25..d149d505 100644
--- a/src/ui/collections-page.ui
+++ b/src/ui/collections-page.ui
@@ -1,19 +1,17 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesCollectionsPage" parent="GtkBin">
-    <property name="visible">True</property>
+  <template class="GamesCollectionsPage" parent="AdwBin">
     <signal name="notify::is-search-mode" handler="update_can_swipe_back"/>
     <signal name="notify::is-selection-mode" handler="update_can_swipe_back"/>
-    <child>
-      <object class="HdyDeck" id="collections_deck">
-        <property name="visible">True</property>
+    <property name="child">
+      <object class="AdwLeaflet" id="collections_leaflet">
         <property name="visible-child">collections_main_page</property>
+        <property name="can-unfold">False</property>
         <property name="can-swipe-back" bind-source="GamesCollectionsPage" bind-property="can-swipe-back"/>
         <signal name="notify::visible-child" handler="on_visible_child_changed"/>
         <child>
           <object class="GamesCollectionsMainPage" id="collections_main_page">
-            <property name="visible">True</property>
             <property name="is-selection-mode" bind-source="GamesCollectionsPage" 
bind-property="is-selection-mode" bind-flags="bidirectional"/>
             <property name="is-search-mode" bind-source="GamesCollectionsPage" 
bind-property="is-search-mode"/>
             <property name="is-search-empty" bind-source="GamesCollectionsPage" 
bind-property="is-search-empty" bind-flags="bidirectional"/>
@@ -22,11 +20,9 @@
         </child>
         <child>
           <object class="GtkStack" id="collections_subpage_stack">
-            <property name="visible">True</property>
             <property name="visible-child">collections_subpage</property>
             <child>
               <object class="GamesGamesPage" id="collections_subpage">
-                <property name="visible">True</property>
                 <property name="is-selection-mode" bind-source="GamesCollectionsPage" 
bind-property="is-selection-mode" bind-flags="bidirectional"/>
                 <signal name="game-activated" handler="on_game_activated"/>
                 <signal name="gamepad-cancel-clicked" handler="on_subpage_back_clicked"/>
@@ -36,8 +32,7 @@
               </object>
             </child>
             <child>
-              <object class="HdyStatusPage" id="collection_empty_subpage">
-                <property name="visible">True</property>
+              <object class="AdwStatusPage" id="collection_empty_subpage">
                 <property name="icon-name">folder-symbolic</property>
                 <property name="title" translatable="yes">This collection is empty</property>
                 <property name="description" translatable="yes">Add some games to this collection to see 
them here.</property>
@@ -46,6 +41,6 @@
           </object>
         </child>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
diff --git a/src/ui/collections-page.vala b/src/ui/collections-page.vala
index 4fe63f4c..247023fa 100644
--- a/src/ui/collections-page.vala
+++ b/src/ui/collections-page.vala
@@ -1,12 +1,12 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/collections-page.ui")]
-private class Games.CollectionsPage : Gtk.Bin {
+private class Games.CollectionsPage : Adw.Bin {
        public signal void game_activated (Game game);
        public signal void selected_items_changed ();
 
        [GtkChild]
-       private unowned Hdy.Deck collections_deck;
+       private unowned Adw.Leaflet collections_leaflet;
        [GtkChild]
        private unowned CollectionsMainPage collections_main_page;
        [GtkChild]
@@ -14,7 +14,7 @@ private class Games.CollectionsPage : Gtk.Bin {
        [GtkChild]
        private unowned GamesPage collections_subpage;
        [GtkChild]
-       private unowned Hdy.StatusPage collection_empty_subpage;
+       private unowned Adw.StatusPage collection_empty_subpage;
 
        private UserCollection[]? last_removed_collections;
        private CollectionManager collection_manager;
@@ -200,28 +200,28 @@ private class Games.CollectionsPage : Gtk.Bin {
                        return false;
 
                is_search_mode = false;
-               collections_deck.visible_child = collections_main_page;
+               collections_leaflet.visible_child = collections_main_page;
                current_collection = null;
                return true;
        }
 
        [GtkCallback]
        private void on_collection_activated (Collection collection) {
-               if (collection.get_collection_type () ==
-                   CollectionType.PLACEHOLDER) {
-                               // Finalize any pending removal of collection and dismiss undo notification 
if shown.
-                               finalize_collection_removal ();
-
-                               var dialog = new CollectionActionWindow ();
-                               dialog.transient_for = get_toplevel () as ApplicationWindow;
-                               dialog.modal = true;
-                               dialog.visible = true;
-                               return;
+               if (collection.get_collection_type () == CollectionType.PLACEHOLDER) {
+                       // Finalize any pending removal of collection and dismiss undo notification if shown.
+                       finalize_collection_removal ();
+
+                       var dialog = new CollectionActionWindow ();
+                       dialog.transient_for = get_root () as ApplicationWindow;
+                       dialog.modal = true;
+                       dialog.visible = true;
+
+                       return;
                }
 
                current_collection = collection;
                collection_title = collection.title;
-               collections_deck.visible_child = collections_subpage_stack;
+               collections_leaflet.visible_child = collections_subpage_stack;
 
                is_collection_empty = collection.get_game_model ().get_n_items () == 0;
                if (is_collection_empty)
@@ -239,7 +239,7 @@ private class Games.CollectionsPage : Gtk.Bin {
 
        [GtkCallback]
        private void on_visible_child_changed () {
-               is_subpage_open = collections_deck.visible_child == collections_subpage_stack;
+               is_subpage_open = collections_leaflet.visible_child == collections_subpage_stack;
        }
 
        [GtkCallback]
diff --git a/src/ui/display-view.ui b/src/ui/display-view.ui
index 8800db9b..576fcd6e 100644
--- a/src/ui/display-view.ui
+++ b/src/ui/display-view.ui
@@ -1,149 +1,86 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesDisplayView" parent="GtkBox">
-    <property name="orientation">vertical</property>
+  <template class="GamesDisplayView" parent="AdwBin">
     <signal name="notify::is-showing-snapshots" handler="on_showing_snapshots_changed"/>
     <signal name="notify::can-fullscreen" handler="on_fullscreen_changed"/>
     <signal name="notify::is-fullscreen" handler="on_fullscreen_changed"/>
     <child>
       <object class="GamesFullscreenBox" id="fullscreen_box">
-        <property name="visible">True</property>
         <property name="is-fullscreen" bind-source="GamesDisplayView" bind-property="is-fullscreen" 
bind-flags="bidirectional"/>
         <child type="titlebar">
           <object class="GtkStack" id="headerbar_stack">
-            <property name="visible">True</property>
             <property name="transition-type">crossfade</property>
             <property name="transition-duration">250</property>
             <child>
-              <object class="HdyHeaderBar" id="ingame_header_bar">
-                <property name="visible">True</property>
-                <property name="title" bind-source="GamesDisplayView" bind-property="game-title" 
bind-flags="bidirectional"/>
-                <property name="show-close-button" bind-source="GamesDisplayView" 
bind-property="is-fullscreen" bind-flags="bidirectional|sync-create|invert-boolean"/>
-                <child>
+              <object class="GtkHeaderBar" id="ingame_header_bar">
+                <property name="can-focus">False</property>
+                <property name="show-title-buttons" bind-source="GamesDisplayView" 
bind-property="is-fullscreen" bind-flags="bidirectional|sync-create|invert-boolean"/>
+                <property name="title-widget">
+                  <object class="AdwWindowTitle">
+                    <property name="title" bind-source="GamesDisplayView" bind-property="game-title" 
bind-flags="bidirectional"/>
+                  </object>
+                </property>
+                <child type="start">
                   <object class="GtkButton" id="back">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="valign">center</property>
+                    <property name="icon-name">go-previous-symbolic</property>
+                    <property name="tooltip-text" translatable="yes">Back</property>
                     <signal name="clicked" handler="on_back_clicked"/>
-                    <style>
-                      <class name="image-button"/>
-                    </style>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="a11y-back">
-                        <property name="accessible-name" translatable="yes">Back</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkImage" id="back_image">
-                        <property name="visible">True</property>
-                        <property name="icon-name">go-previous-symbolic</property>
-                        <property name="icon-size">1</property>
-                      </object>
-                    </child>
                   </object>
                 </child>
-                <child>
+                <child type="end">
                   <object class="GtkButton" id="restore">
                     <property name="visible">False</property>
-                    <property name="can_focus">False</property>
-                    <property name="valign">center</property>
+                    <property name="icon-name">view-restore-symbolic</property>
+                    <property name="tooltip-text" translatable="yes">Restore</property>
                     <signal name="clicked" handler="on_restore_clicked"/>
-                    <style>
-                      <class name="image-button"/>
-                    </style>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="a11y-restore">
-                        <property name="accessible-name" translatable="yes">Restore</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkImage" id="restore_image">
-                        <property name="visible">True</property>
-                        <property name="icon-name">view-restore-symbolic</property>
-                        <property name="icon-size">1</property>
-                      </object>
-                    </child>
                   </object>
-                  <packing>
-                    <property name="pack-type">end</property>
-                  </packing>
                 </child>
-                <child>
+                <child type="end">
                   <object class="GtkMenuButton" id="secondary_menu_button">
                     <property name="visible">False</property>
-                    <property name="valign">center</property>
-                    <property name="can-focus">False</property>
+                    <property name="icon-name">view-more-symbolic</property>
+                    <property name="tooltip-text" translatable="yes">Menu</property>
                     <property name="menu-model">secondary_menu</property>
                     <signal name="notify::active" handler="update_fullscreen_box"/>
-                    <style>
-                      <class name="image-button"/>
-                    </style>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="icon-name">view-more-symbolic</property>
-                        <property name="icon-size">1</property>
-                      </object>
-                    </child>
                   </object>
-                  <packing>
-                    <property name="pack-type">end</property>
-                  </packing>
                 </child>
-                <child>
+                <child type="end">
                   <object class="GtkButton" id="fullscreen">
                     <property name="visible">False</property>
-                    <property name="can_focus">False</property>
-                    <property name="valign">center</property>
+                    <property name="icon-name">view-fullscreen-symbolic</property>
+                    <property name="tooltip-text" translatable="yes">Fullscreen</property>
                     <signal name="clicked" handler="on_fullscreen_clicked"/>
-                    <style>
-                      <class name="image-button"/>
-                    </style>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="a11y-fullscreen">
-                        <property name="accessible-name" translatable="yes">Fullscreen</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkImage" id="fullscreen_image">
-                        <property name="visible">True</property>
-                        <property name="icon-name">view-fullscreen-symbolic</property>
-                        <property name="icon-size">1</property>
-                      </object>
-                    </child>
                   </object>
-                  <packing>
-                    <property name="pack-type">end</property>
-                  </packing>
                 </child>
-                <child>
+                <child type="end">
                   <object class="GamesMediaMenuButton" id="media_button">
                     <signal name="notify::active" handler="update_fullscreen_box"/>
                   </object>
-                  <packing>
-                    <property name="pack-type">end</property>
-                  </packing>
                 </child>
-                <child>
-                  <object class="GamesInputModeSwitcher" id="input_mode_switcher">
-                    <property name="visible">False</property>
-                  </object>
-                  <packing>
-                    <property name="pack-type">end</property>
-                  </packing>
+                <child type="end">
+                  <object class="GamesInputModeSwitcher" id="input_mode_switcher"/>
                 </child>
               </object>
             </child>
             <child>
-              <object class="HdyHeaderBar" id="snapshots_header_bar">
-                <property name="visible">True</property>
-                <property name="title" bind-source="GamesDisplayView" bind-property="game-title" 
bind-flags="bidirectional"/>
-                <child>
+              <object class="GtkHeaderBar" id="snapshots_header_bar">
+                <property name="show-title-buttons">False</property>
+                <property name="title-widget">
+                  <object class="AdwWindowTitle">
+                    <property name="title" bind-source="GamesDisplayView" bind-property="game-title" 
bind-flags="bidirectional"/>
+                  </object>
+                </property>
+                <child type="start">
+                  <object class="GtkButton">
+                    <property name="icon-name">go-previous-symbolic</property>
+                    <property name="tooltip-text" translatable="yes">Back</property>
+                    <signal name="clicked" handler="on_back_clicked"/>
+                  </object>
+                </child>
+                <child type="end">
                   <object class="GtkButton">
                     <property name="sensitive">False</property>
-                    <property name="visible">True</property>
-                    <property name="valign">center</property>
                     <property name="use-underline">True</property>
                     <property name="label" translatable="yes">_Load</property>
                     <property name="action-name">display.load-snapshot</property>
@@ -151,35 +88,6 @@
                       <class name="suggested-action"/>
                     </style>
                   </object>
-                  <packing>
-                    <property name="pack-type">end</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkButton">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="valign">center</property>
-                    <signal name="clicked" handler="on_back_clicked"/>
-                    <style>
-                      <class name="image-button"/>
-                    </style>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="a11y-snapshots-back">
-                        <property name="accessible-name" translatable="yes">Back</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="icon-name">go-previous-symbolic</property>
-                        <property name="icon-size">1</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="pack-type">start</property>
-                  </packing>
                 </child>
               </object>
             </child>
@@ -187,14 +95,11 @@
         </child>
         <child>
           <object class="GtkStack" id="stack">
-            <property name="visible">True</property>
             <child>
-              <object class="HdyStatusPage" id="error_display">
-                <property name="visible">True</property>
+              <object class="AdwStatusPage" id="error_display">
                 <property name="icon-name">face-uncertain-symbolic</property>
                 <child>
                   <object class="GtkButton" id="restart_btn">
-                    <property name="visible">True</property>
                     <property name="label">_Restart Game</property>
                     <property name="use-underline">True</property>
                     <property name="halign">center</property>
@@ -204,8 +109,7 @@
               </object>
             </child>
             <child>
-              <object class="HdyFlap" id="display_flap">
-                <property name="visible">True</property>
+              <object class="AdwFlap" id="display_flap">
                 <property name="flap-position">end</property>
                 <property name="reveal-flap" bind-source="GamesDisplayView" 
bind-property="is-showing-snapshots" bind-flags="bidirectional|sync-create"/>
                 <property name="locked">True</property>
@@ -213,30 +117,22 @@
                 <property name="modal">False</property>
                 <signal name="notify::reveal-progress" handler="maybe_resume_game"/>
                 <signal name="notify::reveal-flap" handler="maybe_resume_game"/>
-                <child type="content">
+                <property name="content">
                   <object class="GtkOverlay">
-                    <property name="visible">True</property>
                     <property name="width-request">360</property>
                     <child>
-                      <object class="GtkEventBox" id="display_bin">
-                        <property name="visible">True</property>
+                      <object class="AdwBin" id="display_bin">
                         <property name="hexpand">True</property>
                       </object>
                     </child>
                     <child type="overlay">
                       <object class="GamesFlashBox" id="flash_box"/>
-                      <packing>
-                        <property name="pass-through">True</property>
-                      </packing>
                     </child>
                   </object>
-                </child>
-                <child type="flap">
-                  <object class="GamesSnapshotsList" id="snapshots_list">
-                    <property name="visible">True</property>
-                    <property name="halign">end</property>
-                  </object>
-                </child>
+                </property>
+                <property name="flap">
+                  <object class="GamesSnapshotsList" id="snapshots_list"/>
+                </property>
               </object>
             </child>
           </object>
diff --git a/src/ui/display-view.vala b/src/ui/display-view.vala
index 78384ce9..6a502937 100644
--- a/src/ui/display-view.vala
+++ b/src/ui/display-view.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/display-view.ui")]
-private class Games.DisplayView : Gtk.Box, UiView {
+private class Games.DisplayView : Adw.Bin, UiView {
        private const uint FOCUS_OUT_DELAY_MILLISECONDS = 500;
 
        public signal void back ();
@@ -9,7 +9,7 @@ private class Games.DisplayView : Gtk.Box, UiView {
        [GtkChild]
        private unowned Gtk.Stack headerbar_stack;
        [GtkChild]
-       private unowned Hdy.HeaderBar ingame_header_bar;
+       private unowned Gtk.HeaderBar ingame_header_bar;
        [GtkChild]
        private unowned Gtk.Button fullscreen;
        [GtkChild]
@@ -17,7 +17,7 @@ private class Games.DisplayView : Gtk.Box, UiView {
        [GtkChild]
        private unowned Gtk.MenuButton secondary_menu_button;
        [GtkChild]
-       private unowned Hdy.HeaderBar snapshots_header_bar;
+       private unowned Gtk.HeaderBar snapshots_header_bar;
        [GtkChild]
        private unowned MediaMenuButton media_button;
        [GtkChild]
@@ -25,13 +25,13 @@ private class Games.DisplayView : Gtk.Box, UiView {
        [GtkChild]
        private unowned Gtk.Stack stack;
        [GtkChild]
-       private unowned Hdy.StatusPage error_display;
+       private unowned Adw.StatusPage error_display;
        [GtkChild]
        private unowned Gtk.Button restart_btn;
        [GtkChild]
-       private unowned Hdy.Flap display_flap;
+       private unowned Adw.Flap display_flap;
        [GtkChild]
-       private unowned Gtk.EventBox display_bin;
+       private unowned Adw.Bin display_bin;
        [GtkChild]
        private unowned FullscreenBox fullscreen_box;
        [GtkChild]
@@ -77,7 +77,7 @@ private class Games.DisplayView : Gtk.Box, UiView {
                                runner.snapshot_created.disconnect (flash_box.flash);
 
                        _runner = value;
-                       remove_display ();
+                       display_bin.child = null;
 
                        if (runner == null)
                                return;
@@ -152,9 +152,11 @@ private class Games.DisplayView : Gtk.Box, UiView {
                window.insert_action_group ("display", action_group);
        }
 
-       public bool on_button_pressed (Gdk.EventButton event) {
+       public bool on_button_pressed (uint button) {
+               // FIXME GTK4 we can add the controller right here instead of
+               // ApplicationWindow
                // Mouse button 8 is the navigation previous button
-               if (event.button == 8) {
+               if (button == 8) {
                        back ();
                        return true;
                }
@@ -162,26 +164,20 @@ private class Games.DisplayView : Gtk.Box, UiView {
                return false;
        }
 
-       public bool on_key_pressed (Gdk.EventKey event) {
+       public bool on_key_pressed (uint keyval, uint keycode, Gdk.ModifierType state) {
                if (!get_mapped ())
                        return false;
 
-               var default_modifiers = Gtk.accelerator_get_default_mod_mask ();
-
-               uint keyval;
-               var keymap = Gdk.Keymap.get_for_display (window.get_display ());
-               keymap.translate_keyboard_state (event.hardware_keycode, event.state,
-                                                event.group, out keyval, null, null, null);
-               var ctrl_pressed = (event.state & default_modifiers) == Gdk.ModifierType.CONTROL_MASK;
+               var ctrl_pressed = state == Gdk.ModifierType.CONTROL_MASK;
 
                if (runner == null)
                        return false;
 
                if (is_showing_snapshots &&
-                   snapshots_list.on_key_press_event (keyval, event.state & default_modifiers))
+                   snapshots_list.on_key_press_event (keyval, state))
                        return true;
 
-               if (runner.key_press_event (keyval, event.state & default_modifiers))
+               if (runner.key_press_event (keyval, state))
                        return true;
 
                if ((keyval == Gdk.Key.f || keyval == Gdk.Key.F) && ctrl_pressed &&
@@ -205,7 +201,7 @@ private class Games.DisplayView : Gtk.Box, UiView {
                        return true;
                }
 
-               if (((event.state & default_modifiers) == Gdk.ModifierType.MOD1_MASK) &&
+               if ((state == Gdk.ModifierType.ALT_MASK) &&
                    (((window.get_direction () == Gtk.TextDirection.LTR) && keyval == Gdk.Key.Left) ||
                     ((window.get_direction () == Gtk.TextDirection.RTL) && keyval == Gdk.Key.Right))) {
                        on_display_back ();
@@ -577,7 +573,7 @@ private class Games.DisplayView : Gtk.Box, UiView {
                quit_dialog.format_secondary_text ("%s", _("All unsaved progress will be lost."));
 
                var button = quit_dialog.add_button (_("Quit"), Gtk.ResponseType.ACCEPT);
-               button.get_style_context ().add_class ("destructive-action");
+               button.add_css_class ("destructive-action");
 
                cancellable.cancelled.connect (() => {
                        quit_dialog.destroy ();
@@ -706,7 +702,7 @@ private class Games.DisplayView : Gtk.Box, UiView {
                        restart_dialog.format_secondary_text ("%s", _("All unsaved progress will be lost."));
 
                        var button = restart_dialog.add_button (_("Restart"), Gtk.ResponseType.ACCEPT);
-                       button.get_style_context ().add_class ("destructive-action");
+                       button.add_css_class ("destructive-action");
 
                        var response = yield DialogUtils.run_async (restart_dialog);
 
@@ -735,22 +731,17 @@ private class Games.DisplayView : Gtk.Box, UiView {
        }
 
        private void set_display (Gtk.Widget display) {
-               remove_display ();
-               display_bin.add (display);
-               display.visible = true;
-       }
-
-       private void remove_display () {
-               var child = display_bin.get_child ();
-               if (child != null)
-                       display_bin.remove (child);
+               display_bin.child = display;
        }
 
        [GtkCallback]
        private void update_fullscreen_box () {
-               var is_menu_open = media_button.active ||
+               var is_menu_open = false;
+/* FIXME GTK4
+                                  media_button.active ||
                                   secondary_menu_button.active ||
                                   (extra_widget != null && extra_widget.block_autohide);
+*/
 
                fullscreen_box.autohide = !is_menu_open &&
                                          !is_showing_snapshots;
diff --git a/src/ui/error-info-bar.ui b/src/ui/error-info-bar.ui
index c8bec669..4c27b39d 100644
--- a/src/ui/error-info-bar.ui
+++ b/src/ui/error-info-bar.ui
@@ -1,26 +1,18 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesErrorInfoBar" parent="GtkBin">
-    <property name="visible">True</property>
+  <template class="GamesErrorInfoBar" parent="AdwBin">
     <child>
       <object class="GtkInfoBar" id="info_bar">
-        <property name="visible">True</property>
         <property name="revealed">False</property>
-        <property name="halign">fill</property>
-        <property name="valign">start</property>
         <property name="show-close-button">True</property>
         <property name="message-type">error</property>
         <signal name="response" handler="on_response"/>
-        <child internal-child="content_area">
-          <object class="GtkBox" id="content_area">
-            <child>
-              <object class="GtkLabel" id="label">
-                <property name="visible">True</property>
-                <property name="wrap">True</property>
-                <property name="wrap-mode">word-char</property>
-              </object>
-            </child>
+        <child>
+          <object class="GtkLabel" id="label">
+            <property name="wrap">True</property>
+            <property name="wrap-mode">word-char</property>
+            <property name="xalign">0</property>
           </object>
         </child>
       </object>
diff --git a/src/ui/error-info-bar.vala b/src/ui/error-info-bar.vala
index bd2efd76..596fc2cd 100644
--- a/src/ui/error-info-bar.vala
+++ b/src/ui/error-info-bar.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/error-info-bar.ui")]
-private class Games.ErrorInfoBar : Gtk.Bin {
+private class Games.ErrorInfoBar : Adw.Bin {
        [GtkChild]
        private unowned Gtk.InfoBar info_bar;
        [GtkChild]
diff --git a/src/ui/flash-box.vala b/src/ui/flash-box.vala
index f7d9f144..97c5d174 100644
--- a/src/ui/flash-box.vala
+++ b/src/ui/flash-box.vala
@@ -8,22 +8,13 @@ private class Games.FlashBox : Gtk.Widget {
        }
 
        construct {
-               set_has_window (false);
                opacity = 0;
+               can_target = false;
        }
 
        private int64 flash_start_time;
        private uint tick_callback_id;
 
-       public override bool draw (Cairo.Context cr) {
-               var width = get_allocated_width ();
-               var height = get_allocated_height ();
-
-               get_style_context ().render_background (cr, 0, 0, width, height);
-
-               return true;
-       }
-
        public void flash () {
                if (tick_callback_id == 0) {
                        tick_callback_id = add_tick_callback (on_tick);
@@ -55,8 +46,10 @@ private class Games.FlashBox : Gtk.Widget {
                return t * (2 - t);
        }
 
-       public override void destroy () {
+       public override void unmap () {
                if (tick_callback_id != 0)
                        remove_tick_callback (tick_callback_id);
+
+               base.unmap ();
        }
 }
diff --git a/src/ui/fullscreen-box.ui b/src/ui/fullscreen-box.ui
index 93d36b41..f260b9fc 100644
--- a/src/ui/fullscreen-box.ui
+++ b/src/ui/fullscreen-box.ui
@@ -1,13 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesFullscreenBox" parent="GtkEventBox">
-    <property name="visible">True</property>
-    <property name="events">pointer-motion-mask</property>
-    <signal name="motion-notify-event" handler="on_motion_event"/>
-    <child>
-      <object class="HdyFlap" id="flap">
-        <property name="visible">True</property>
+  <template class="GamesFullscreenBox" parent="AdwBin">
+    <property name="child">
+      <object class="AdwFlap" id="flap">
         <property name="orientation">vertical</property>
         <property name="locked">True</property>
         <property name="modal">False</property>
@@ -17,6 +13,11 @@
           <class name="titlebar-box"/>
         </style>
       </object>
+    </property>
+    <child>
+      <object class="GtkEventControllerMotion">
+        <signal name="motion" handler="motion_cb"/>
+      </object>
     </child>
   </template>
 </interface>
diff --git a/src/ui/fullscreen-box.vala b/src/ui/fullscreen-box.vala
index 12354ff5..9f9961d8 100644
--- a/src/ui/fullscreen-box.vala
+++ b/src/ui/fullscreen-box.vala
@@ -1,10 +1,13 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/fullscreen-box.ui")]
-private class Games.FullscreenBox : Gtk.EventBox, Gtk.Buildable {
+private class Games.FullscreenBox : Adw.Bin, Gtk.Buildable {
        private const uint INACTIVITY_TIME_MILLISECONDS = 3000;
        private const int SHOW_HEADERBAR_DISTANCE = 5;
 
+       [GtkChild]
+       private unowned Adw.Flap flap;
+
        private bool _is_fullscreen;
        public bool is_fullscreen {
                get { return _is_fullscreen; }
@@ -35,7 +38,7 @@ private class Games.FullscreenBox : Gtk.EventBox, Gtk.Buildable {
                        if (flap == null)
                                return;
 
-                       flap.fold_policy = overlay ? Hdy.FlapFoldPolicy.ALWAYS : Hdy.FlapFoldPolicy.NEVER;
+                       flap.fold_policy = overlay ? Adw.FlapFoldPolicy.ALWAYS : Adw.FlapFoldPolicy.NEVER;
                }
        }
 
@@ -70,9 +73,6 @@ private class Games.FullscreenBox : Gtk.EventBox, Gtk.Buildable {
                }
        }
 
-       [GtkChild]
-       private unowned Hdy.Flap flap;
-
        private uint ui_timeout_id;
        private uint cursor_timeout_id;
 
@@ -82,13 +82,14 @@ private class Games.FullscreenBox : Gtk.EventBox, Gtk.Buildable {
        }
 
        public void add_child (Gtk.Builder builder, Object child, string? type) {
-               var widget = child as Gtk.Widget;
+               if (!(child is Gtk.Widget) || flap == null) {
+                       base.add_child (builder, child, type);
 
-               if (flap == null) {
-                       add (widget);
                        return;
                }
 
+               var widget = child as Gtk.Widget;
+
                if (type == "titlebar")
                        flap.flap = widget;
                else
@@ -96,16 +97,14 @@ private class Games.FullscreenBox : Gtk.EventBox, Gtk.Buildable {
        }
 
        [GtkCallback]
-       private bool on_motion_event (Gdk.EventMotion event) {
+       private void motion_cb (double x, double y) {
                if (!autohide)
-                       return false;
+                       return;
 
-               if (event.y_root <= SHOW_HEADERBAR_DISTANCE)
+               if (y <= SHOW_HEADERBAR_DISTANCE)
                        show_ui ();
 
                on_cursor_moved ();
-
-               return false;
        }
 
        private void show_ui () {
@@ -170,19 +169,13 @@ private class Games.FullscreenBox : Gtk.EventBox, Gtk.Buildable {
        }
 
        private void show_cursor (bool show) {
-               var window = get_window ();
-               if (window == null)
-                       return;
-
-               if ((show && window.cursor == null) ||
-                   (!show && window.cursor != null))
+               if (show == (cursor == null))
                        return;
 
                if (!show) {
-                       var display = window.get_display ();
-                       window.cursor = new Gdk.Cursor.from_name (display, "none");
+                       cursor = new Gdk.Cursor.from_name ("none", null);
                }
                else
-                       window.cursor = null;
+                       cursor = null;
        }
 }
diff --git a/src/ui/game-icon-view.ui b/src/ui/game-icon-view.ui
index 0f163e22..e08d7399 100644
--- a/src/ui/game-icon-view.ui
+++ b/src/ui/game-icon-view.ui
@@ -2,69 +2,59 @@
 <interface>
   <requires lib="gtk+" version="3.24"/>
   <template class="GamesGameIconView" parent="GtkFlowBoxChild">
-    <property name="visible">True</property>
-      <child>
-        <object class="GtkEventBox">
-          <property name="visible">True</property>
-          <child>
-            <object class="GtkBox">
-              <property name="visible">True</property>
-              <property name="orientation">vertical</property>
-              <property name="spacing">6</property>
-              <child>
-                <object class="GtkOverlay">
-                  <property name="visible">True</property>
-                  <child>
-                    <object class="GamesGameThumbnail" id="thumbnail">
-                      <property name="visible">True</property>
-                    </object>
-                  </child>
-                  <child type="overlay">
-                    <object class="GtkRevealer">
-                      <property name="visible">True</property>
-                      <property name="transition-type">crossfade</property>
-                      <property name="reveal-child" bind-source="GamesGameIconView" 
bind-property="is_selection_mode"/>
-                      <child>
-                        <object class="GtkCheckButton" id="checkbox">
-                          <property name="visible">True</property>
-                          <property name="halign">end</property>
-                          <property name="valign">end</property>
-                          <property name="active" bind-source="GamesGameIconView" bind-property="checked" 
bind-flags="bidirectional"/>
-                          <style>
-                            <class name="check"/>
-                          </style>
-                        </object>
-                      </child>
-                    </object>
-                  </child>
-                  <child type="overlay">
-                      <object class="GtkImage">
-                        <property name="visible" bind-source="GamesGameIconView" 
bind-property="is-favorite"/>
-                        <property name="icon-name">starred-symbolic</property>
-                        <property name="halign">end</property>
-                        <property name="valign">start</property>
-                        <style>
-                          <class name="favorite-star"/>
-                        </style>
-                      </object>
-                  </child>
-                </object>
-              </child>
-              <child>
-                <object class="GtkLabel" id="title">
-                  <property name="visible">True</property>
-                  <property name="ellipsize">middle</property>
-                  <property name="justify">center</property>
-                  <property name="lines">2</property>
-                  <property name="max-width-chars">0</property>
-                  <property name="wrap">True</property>
-                  <property name="wrap-mode">word-char</property>
-                </object>
-              </child>
-            </object>
-          </child>
-        </object>
-      </child>
-
+    <style>
+      <class name="tile"/>
+    </style>
+    <property name="child">
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkOverlay">
+            <child>
+              <object class="GamesGameThumbnail" id="thumbnail">
+              </object>
+            </child>
+            <child type="overlay">
+              <object class="GtkRevealer">
+                <property name="transition-type">crossfade</property>
+                <property name="reveal-child" bind-source="GamesGameIconView" 
bind-property="is_selection_mode"/>
+                <property name="child">
+                  <object class="GtkCheckButton" id="checkbox">
+                    <property name="halign">end</property>
+                    <property name="valign">end</property>
+                    <property name="active" bind-source="GamesGameIconView" bind-property="checked" 
bind-flags="bidirectional"/>
+                    <style>
+                      <class name="check"/>
+                    </style>
+                  </object>
+                </property>
+              </object>
+            </child>
+            <child type="overlay">
+              <object class="GtkImage">
+                <property name="visible" bind-source="GamesGameIconView" bind-property="is-favorite" 
bind-flags="sync-create"/>
+                <property name="icon-name">starred-symbolic</property>
+                <property name="halign">end</property>
+                <property name="valign">start</property>
+                <style>
+                  <class name="favorite-star"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="title">
+            <property name="ellipsize">middle</property>
+            <property name="justify">center</property>
+            <property name="lines">2</property>
+            <property name="max-width-chars">0</property>
+            <property name="wrap">True</property>
+            <property name="wrap-mode">word-char</property>
+          </object>
+        </child>
+      </object>
+    </property>
   </template>
 </interface>
diff --git a/src/ui/game-icon-view.vala b/src/ui/game-icon-view.vala
index 26eb497d..7892875d 100644
--- a/src/ui/game-icon-view.vala
+++ b/src/ui/game-icon-view.vala
@@ -10,8 +10,6 @@ private class Games.GameIconView : Gtk.FlowBoxChild {
        private unowned Gtk.Label title;
 
        private ulong game_replaced_id;
-       private Gtk.GestureMultiPress multi_press_gesture;
-       private Gtk.GestureLongPress long_press_gesture;
 
        private Game _game;
        public Game game {
@@ -38,26 +36,26 @@ private class Games.GameIconView : Gtk.FlowBoxChild {
        public bool is_favorite { get; set; }
 
        construct {
-               add_events (Gdk.EventMask.TOUCH_MASK);
-
-               multi_press_gesture = new Gtk.GestureMultiPress (this);
-               multi_press_gesture.button = 0;
-               multi_press_gesture.pressed.connect (() => {
-                       var event = Gtk.get_current_event ();
+               var click_gesture = new Gtk.GestureClick ();
+               click_gesture.button = 0;
+               click_gesture.pressed.connect (() => {
+                       var event = click_gesture.get_current_event ();
 
                        if (event.triggers_context_menu ()) {
                                secondary_click ();
-                               multi_press_gesture.set_state (Gtk.EventSequenceState.CLAIMED);
+                               click_gesture.set_state (Gtk.EventSequenceState.CLAIMED);
                        } else {
-                               multi_press_gesture.set_state (Gtk.EventSequenceState.DENIED);
+                               click_gesture.set_state (Gtk.EventSequenceState.DENIED);
                        }
                });
+               add_controller (click_gesture);
 
-               long_press_gesture = new Gtk.GestureLongPress (this);
+               var long_press_gesture = new Gtk.GestureLongPress ();
                long_press_gesture.pressed.connect (() => {
                        secondary_click ();
                        long_press_gesture.set_state (Gtk.EventSequenceState.CLAIMED);
                });
+               add_controller (long_press_gesture);
        }
 
        public GameIconView (Game game) {
diff --git a/src/ui/game-thumbnail.vala b/src/ui/game-thumbnail.vala
index 9e528ec7..ca7e6131 100644
--- a/src/ui/game-thumbnail.vala
+++ b/src/ui/game-thumbnail.vala
@@ -1,6 +1,6 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
-private class Games.GameThumbnail : Gtk.DrawingArea {
+private class Games.GameThumbnail : Gtk.Widget {
        private const double EMBLEM_SCALE = 0.125;
        private const double ICON_SCALE = 0.75;
        private const double EMBLEM_MIN_SIZE = 16;
@@ -35,21 +35,21 @@ private class Games.GameThumbnail : Gtk.DrawingArea {
                }
        }
 
-       private Gdk.Pixbuf? cover_pixbuf;
-       private Gdk.Pixbuf? icon_pixbuf;
+       private Gdk.Paintable? cover_paintable;
+       private Gdk.Paintable? icon_paintable;
        private bool try_load_cover;
        private int last_scale_factor;
        private int last_cover_size;
 
        public struct DrawingContext {
-               Cairo.Context cr;
-               Gtk.StyleContext style;
-               Gtk.StateFlags state;
+               Gtk.Snapshot snapshot;
                int width;
                int height;
        }
 
        construct {
+               overflow = HIDDEN;
+
                try_load_cover = true;
        }
 
@@ -57,76 +57,44 @@ private class Games.GameThumbnail : Gtk.DrawingArea {
                set_css_name ("gamesgamethumbnail");
        }
 
-       public override Gtk.SizeRequestMode get_request_mode () {
-               return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
-       }
-
-       public override void get_preferred_height_for_width (int width, out int minimum_height, out int 
natural_height) {
-               minimum_height = natural_height = width;
-       }
-
-       public override void size_allocate (Gtk.Allocation alloc) {
-               var context = get_style_context ();
-               var clip = context.render_background_get_clip (
-                       alloc.x,
-                       alloc.y,
-                       alloc.width,
-                       alloc.height
-               );
-
-               base.size_allocate (alloc);
-
-               set_clip (clip);
-       }
-
-       public override bool draw (Cairo.Context cr) {
-               var style = get_style_context ();
-               var state = get_state_flags ();
-               var width = get_allocated_width ();
-               var height = get_allocated_height ();
-
-               DrawingContext context = {
-                       cr, style, state, width, height
-               };
-
+       protected override void snapshot (Gtk.Snapshot snapshot) {
                if (icon == null)
-                       return Gdk.EVENT_PROPAGATE;
+                       return;
 
                if (cover == null)
-                       return Gdk.EVENT_PROPAGATE;
+                       return;
 
-               context.style.render_background (cr, 0.0, 0.0, width, height);
-               context.style.render_frame (cr, 0.0, 0.0, width, height);
+               DrawingContext context = {
+                       snapshot, get_width (), get_height ()
+               };
 
                draw_image (context);
-
-               return Gdk.EVENT_PROPAGATE;
        }
 
        private void update_style_classes () {
-               if (cover_pixbuf != null)
-                       get_style_context ().add_class ("cover");
+               if (cover_paintable != null)
+                       add_css_class ("cover");
                else
-                       get_style_context ().remove_class ("cover");
+                       remove_css_class ("cover");
 
-               if (icon_pixbuf != null && cover_pixbuf == null)
-                       get_style_context ().add_class ("icon");
+               if (icon_paintable != null && cover_paintable == null)
+                       add_css_class ("icon");
                else
-                       get_style_context ().remove_class ("icon");
+                       remove_css_class ("icon");
        }
 
        public void draw_image (DrawingContext context) {
-               Gdk.Pixbuf cover, icon;
+               Gdk.Paintable cover, icon;
                get_icon_and_cover (context, out cover, out icon);
 
                if (cover != null) {
-                       draw_pixbuf (context, cover);
+                       draw_paintable (context, cover);
 
                        return;
                }
 
                if (icon != null) {
-                       draw_pixbuf (context, icon);
+                       draw_paintable (context, icon);
 
                        return;
                }
@@ -134,43 +102,34 @@ private class Games.GameThumbnail : Gtk.DrawingArea {
                var emblem = get_emblem (context);
 
                if (emblem != null)
-                       draw_pixbuf (context, emblem);
+                       draw_paintable (context, emblem);
        }
 
-       private Gdk.Pixbuf? get_emblem (DrawingContext context) {
+       private Gdk.Paintable? get_emblem (DrawingContext context) {
                string icon_name = @"$(Config.APPLICATION_ID)-symbolic";
-               Gdk.Pixbuf? emblem = null;
 
-               var color = context.style.get_color (context.state);
-
-               var theme = Gtk.IconTheme.get_default ();
+               var display = Gdk.Display.get_default ();
+               var theme = Gtk.IconTheme.get_for_display (display);
                var size = int.min (context.width, context.height) * EMBLEM_SCALE * scale_factor;
                size = double.max (size, EMBLEM_MIN_SIZE * scale_factor);
-               try {
-                       var icon_info = theme.lookup_icon (icon_name, (int) size, 
Gtk.IconLookupFlags.FORCE_SIZE);
-                       emblem = icon_info.load_symbolic (color);
-               }
-               catch (GLib.Error error) {
-                       warning (@"Unable to get icon “$icon_name”: $(error.message)");
-               }
 
-               return emblem;
+               return theme.lookup_icon (icon_name, null, (int) size, (int) size, get_direction (), 0);
        }
 
-       private void get_icon_and_cover (DrawingContext context, out Gdk.Pixbuf cover, out Gdk.Pixbuf icon) {
+       private void get_icon_and_cover (DrawingContext context, out Gdk.Paintable cover, out Gdk.Paintable 
icon) {
                var cover_size = int.min (context.width, context.height) * scale_factor;
                var icon_size = (int) (cover_size * ICON_SCALE);
 
                if (cover_size != last_cover_size || scale_factor != last_scale_factor) {
-                       cover_pixbuf = null;
-                       icon_pixbuf = null;
+                       cover_paintable = null;
+                       icon_paintable = null;
                        update_style_classes ();
                        try_load_cover = true;
                }
 
                if (!try_load_cover) {
-                       cover = cover_pixbuf;
-                       icon = icon_pixbuf;
+                       cover = cover_paintable;
+                       icon = icon_paintable;
                        return;
                }
 
@@ -180,19 +139,19 @@ private class Games.GameThumbnail : Gtk.DrawingArea {
                last_scale_factor = scale_factor;
 
                try_load_cover = false;
-               loader.fetch_cover (game, scale_factor, cover_size, icon_size, (scale_factor, cover_size, 
cover_pixbuf, icon_size, icon_pixbuf) => {
+               loader.fetch_cover (game, scale_factor, cover_size, icon_size, (scale_factor, cover_size, 
cover_paintable, icon_size, icon_paintable) => {
                        if (scale_factor != last_scale_factor || cover_size != last_cover_size) {
-                               this.cover_pixbuf = null;
-                               this.icon_pixbuf = null;
+                               this.cover_paintable = null;
+                               this.icon_paintable = null;
 
                                try_load_cover = true;
                        }
                        else {
-                               if (cover_pixbuf != null)
-                                       this.cover_pixbuf = cover_pixbuf;
+                               if (cover_paintable != null)
+                                       this.cover_paintable = cover_paintable;
 
-                               if (icon_pixbuf != null)
-                                       this.icon_pixbuf = icon_pixbuf;
+                               if (icon_paintable != null)
+                                       this.icon_paintable = icon_paintable;
                        }
 
                        update_style_classes ();
@@ -200,41 +159,23 @@ private class Games.GameThumbnail : Gtk.DrawingArea {
                        queue_draw ();
                });
 
-               cover = cover_pixbuf;
-               icon = icon_pixbuf;
+               cover = cover_paintable;
+               icon = icon_paintable;
        }
 
-       private void draw_pixbuf (DrawingContext context, Gdk.Pixbuf pixbuf) {
-               context.cr.save ();
+       private void draw_paintable (DrawingContext context, Gdk.Paintable paintable) {
+               context.snapshot.save ();
 
-               var border_radius = (int) context.style.get_property (Gtk.STYLE_PROPERTY_BORDER_RADIUS, 
context.state);
-               border_radius = border_radius.clamp (0, int.max (context.width / 2, context.height / 2));
+               float w = paintable.get_intrinsic_width ();
+               float h = paintable.get_intrinsic_height ();
 
-               rounded_rectangle (context.cr, 0, 0, context.width, context.height, border_radius);
-               context.cr.clip ();
-
-               var x_offset = (context.width * scale_factor - pixbuf.width) / 2;
-               var y_offset = (context.height * scale_factor - pixbuf.height) / 2;
-
-               context.cr.scale (1.0 / scale_factor, 1.0 / scale_factor);
-
-               Gdk.cairo_set_source_pixbuf (context.cr, pixbuf, x_offset, y_offset);
-               context.cr.paint ();
-
-               context.cr.restore ();
-       }
+               context.snapshot.scale (1.0f / scale_factor, 1.0f / scale_factor);
+               context.snapshot.translate ({
+                       (context.width * scale_factor - w) / 2.0f,
+                       (context.height * scale_factor - h) / 2.0f,
+               });
 
-       private void rounded_rectangle (Cairo.Context cr, double x, double y, double width, double height, 
double radius) {
-               const double ARC_0 = 0;
-               const double ARC_1 = Math.PI * 0.5;
-               const double ARC_2 = Math.PI;
-               const double ARC_3 = Math.PI * 1.5;
-
-               cr.new_sub_path ();
-               cr.arc (x + width - radius, y + radius,          radius, ARC_3, ARC_0);
-               cr.arc (x + width - radius, y + height - radius, radius, ARC_0, ARC_1);
-               cr.arc (x + radius,         y + height - radius, radius, ARC_1, ARC_2);
-               cr.arc (x + radius,         y + radius,          radius, ARC_2, ARC_3);
-               cr.close_path ();
+               paintable.snapshot (context.snapshot, w, h);
+               context.snapshot.restore ();
        }
 }
diff --git a/src/ui/gamepad-view.vala b/src/ui/gamepad-view.vala
index 8bbd886a..6f323bd0 100644
--- a/src/ui/gamepad-view.vala
+++ b/src/ui/gamepad-view.vala
@@ -61,6 +61,8 @@ private class Games.GamepadView : Gtk.DrawingArea {
                handle = new Rsvg.Handle ();
                configuration = { "", new GamepadButtonPath[0] };
                input_state = new HashTable<string, InputState?> (str_hash, str_equal);
+
+               set_draw_func (draw_cb);
        }
 
        private void get_dimensions (out double width, out double height) {
@@ -121,52 +123,56 @@ private class Games.GamepadView : Gtk.DrawingArea {
                return false;
        }
 
-       public override bool draw (Cairo.Context context) {
+       int ii;
+
+       private void draw_cb (Gtk.DrawingArea area, Cairo.Context cr, int width, int height) {
                double x, y, scale;
-               calculate_image_dimensions (out x, out y, out scale);
+               calculate_image_dimensions (width, height, out x, out y, out scale);
+
+               cr.translate (x, y);
+               cr.scale (scale, scale);
 
-               context.translate (x, y);
-               context.scale (scale, scale);
+               var context = get_style_context ();
+               ii = 0;
 
                foreach (var path in configuration.background_paths) {
-                       Gdk.RGBA color;
-                       get_style_context ().lookup_color ("theme_fg_color", out color);
+                       var color = context.get_color ();
 
-                       draw_path (context, path, color, 0, 0);
+                       draw_path (cr, path, color, 0, 0);
+                       ii++;
                }
 
                input_state.for_each ((path, state) => {
-                       var color_name = state.highlight ? "theme_selected_bg_color" : "theme_fg_color";
-
                        Gdk.RGBA color;
-                       get_style_context ().lookup_color (color_name, out color);
 
-                       draw_path (context, path, color, state.offset_x, state.offset_y);
-               });
+                       if (state.highlight)
+                               context.lookup_color ("accent_color", out color);
+                       else
+                               color = context.get_color ();
 
-               return false;
+                       draw_path (cr, path, color, state.offset_x, state.offset_y);
+                       ii++;
+               });
        }
 
-       private void draw_path (Cairo.Context context, string path, Gdk.RGBA color, double offset_x, double 
offset_y) {
-               context.push_group ();
+       private void draw_path (Cairo.Context cr, string path, Gdk.RGBA color, double offset_x, double 
offset_y) {
+               cr.push_group ();
 
-               context.translate (offset_x, offset_y);
+               cr.translate (offset_x, offset_y);
+               // FIXME GTK4 rsvg crashes here
+               handle.render_cairo_sub (cr, @"#$path");
 
-               handle.render_cairo_sub (context, @"#$path");
-               var group = context.pop_group ();
+               var group = cr.pop_group ();
 
-               context.set_source_rgba (color.red, color.green, color.blue, color.alpha);
-               context.mask (group);
+               Gdk.cairo_set_source_rgba (cr, color);
+               cr.mask (group);
        }
 
-       private void calculate_image_dimensions (out double x, out double y, out double scale) {
-               double width = get_allocated_width ();
-               double height = get_allocated_height ();
-
+       private void calculate_image_dimensions (int width, int height, out double x, out double y, out 
double scale) {
                double image_width, image_height;
                get_dimensions (out image_width, out image_height);
 
-               scale = double.min (height / image_height, width / image_width);
+               scale = double.min ((double) height / image_height, (double) width / image_width);
 
                x = (width - image_width * scale) / 2;
                y = (height - image_height * scale) / 2;
diff --git a/src/ui/games-page.ui b/src/ui/games-page.ui
index dfa2a74a..8a61d319 100644
--- a/src/ui/games-page.ui
+++ b/src/ui/games-page.ui
@@ -1,38 +1,38 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesGamesPage" parent="GtkBin">
-    <property name="visible">True</property>
+  <template class="GamesGamesPage" parent="AdwBin">
     <signal name="map" after="yes" handler="on_map"/>
     <signal name="unmap" after="no" handler="on_unmap"/>
-    <signal name="size-allocate" after="no" handler="on_size_allocate"/>
-    <child>
+    <property name="child">
       <object class="GtkScrolledWindow" id="scrolled_window">
-        <property name="visible">True</property>
-        <property name="can-focus">True</property>
         <property name="hscrollbar-policy">never</property>
         <style>
           <class name="content-view"/>
         </style>
-        <child>
-          <object class="GtkFlowBox" id="flow_box">
-            <property name="visible">True</property>
-            <property name="can-focus">True</property>
-            <property name="halign">center</property>
-            <property name="valign">start</property>
-            <property name="margin-start">28</property>
-            <property name="margin-end">28</property>
-            <property name="margin-top">21</property>
-            <property name="margin-bottom">21</property>
-            <property name="homogeneous">True</property>
-            <property name="column-spacing">14</property>
-            <property name="row-spacing">14</property>
-            <property name="selection-mode">single</property>
-            <signal name="child-activated" handler="on_child_activated"/>
+        <property name="child">
+          <object class="GtkViewport">
+            <property name="scroll-to-focus">True</property>
+            <property name="child">
+              <object class="GtkFlowBox" id="flow_box">
+                <property name="can-focus">True</property>
+                <property name="halign">center</property>
+                <property name="valign">start</property>
+                <property name="margin-start">28</property>
+                <property name="margin-end">28</property>
+                <property name="margin-top">21</property>
+                <property name="margin-bottom">21</property>
+                <property name="homogeneous">True</property>
+                <property name="column-spacing">14</property>
+                <property name="row-spacing">14</property>
+                <property name="selection-mode">single</property>
+                <signal name="child-activated" handler="on_child_activated"/>
+              </object>
+            </property>
           </object>
-        </child>
+        </property>
       </object>
-    </child>
+    </property>
   </template>
   <object class="GamesGamepadBrowse" id="gamepad_browse">
     <signal name="browse" handler="on_gamepad_browse"/>
diff --git a/src/ui/games-page.vala b/src/ui/games-page.vala
index 625af703..b8bfab39 100644
--- a/src/ui/games-page.vala
+++ b/src/ui/games-page.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/games-page.ui")]
-private class Games.GamesPage : Gtk.Bin {
+private class Games.GamesPage : Adw.Bin {
        public signal void game_activated (Game game);
        public signal void selected_items_changed ();
        public signal bool gamepad_cancel_clicked ();
@@ -31,9 +31,9 @@ private class Games.GamesPage : Gtk.Bin {
                        _hide_stars = value;
 
                        if (hide_stars)
-                               get_style_context ().add_class ("hide-stars");
+                               add_css_class ("hide-stars");
                        else
-                               get_style_context ().remove_class ("hide-stars");
+                               remove_css_class ("hide-stars");
                }
        }
 
@@ -72,6 +72,18 @@ private class Games.GamesPage : Gtk.Bin {
                flow_box.set_filter_func (filter_box);
 
                selected_games = new GenericSet<GameIconView> (GameIconView.hash, GameIconView.equal);
+
+               var paintable = new Gtk.WidgetPaintable (this);
+               paintable.invalidate_size.connect (() => {
+                       // If the window's width is less than half the width of a 1920Ă—1080
+                       // screen, display the game thumbnails at half the size to see more of
+                       // them rather than a few huge thumbnails, making Games more usable on
+                       // small screens.
+                       if (paintable.get_intrinsic_width () < 960)
+                               remove_css_class ("large");
+                       else
+                               add_css_class ("large");
+               });
        }
 
        [GtkCallback]
@@ -159,12 +171,13 @@ private class Games.GamesPage : Gtk.Bin {
        }
 
        public void select_all () {
-               foreach (var child in flow_box.get_children ()) {
-                       var game_icon_view = child as GameIconView;
+               for (int i = 0; i < game_model.get_n_items (); i++) {
+                       var child = flow_box.get_child_at_index (i) as GameIconView;
+
                        if (game_filter == null)
-                               game_icon_view.checked = filtering_terms == null || filter_game 
(game_icon_view.game);
-                       else if (filter_game (game_icon_view.game))
-                               game_icon_view.checked = true;
+                               child.checked = filtering_terms == null || filter_game (child.game);
+                       else if (filter_game (child.game))
+                               child.checked = true;
                }
        }
 
@@ -184,13 +197,13 @@ private class Games.GamesPage : Gtk.Bin {
 
                switch (direction) {
                case Gtk.DirectionType.UP:
-                       return flow_box.move_cursor (Gtk.MovementStep.DISPLAY_LINES, -1);
+                       return flow_box.move_cursor (Gtk.MovementStep.DISPLAY_LINES, -1, false, false);
                case Gtk.DirectionType.DOWN:
-                       return flow_box.move_cursor (Gtk.MovementStep.DISPLAY_LINES, 1);
+                       return flow_box.move_cursor (Gtk.MovementStep.DISPLAY_LINES, 1, false, false);
                case Gtk.DirectionType.LEFT:
-                       return flow_box.move_cursor (Gtk.MovementStep.VISUAL_POSITIONS, -1);
+                       return flow_box.move_cursor (Gtk.MovementStep.VISUAL_POSITIONS, -1, false, false);
                case Gtk.DirectionType.RIGHT:
-                       return flow_box.move_cursor (Gtk.MovementStep.VISUAL_POSITIONS, 1);
+                       return flow_box.move_cursor (Gtk.MovementStep.VISUAL_POSITIONS, 1, false, false);
                default:
                        return false;
                }
@@ -283,16 +296,4 @@ private class Games.GamesPage : Gtk.Bin {
 
                is_search_empty = !found_games ();
        }
-
-       [GtkCallback]
-       private void on_size_allocate (Gtk.Allocation allocation) {
-               // If the window's width is less than half the width of a 1920Ă—1080
-               // screen, display the game thumbnails at half the size to see more of
-               // them rather than a few huge thumbnails, making Games more usable on
-               // small screens.
-               if (allocation.width < 960)
-                       get_style_context ().remove_class ("large");
-               else
-                       get_style_context ().add_class ("large");
-       }
 }
diff --git a/src/ui/help-overlay.ui b/src/ui/help-overlay.ui
index 3ed652bb..d428b853 100644
--- a/src/ui/help-overlay.ui
+++ b/src/ui/help-overlay.ui
@@ -8,21 +8,17 @@
         <property name="max-height">12</property>
         <property name="section-name">shortcuts</property>
         <property name="title" translatable="yes">Shortcuts</property>
-        <property name="visible">True</property>
         <child>
           <object class="GtkShortcutsGroup">
             <property name="title" translatable="yes">General</property>
-            <property name="visible">True</property>
             <child>
               <object class="GtkShortcutsShortcut" id="general_shortcut_alt_left">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Go back</property>
                 <property name="accelerator">&lt;alt&gt;Left &lt;alt&gt;Right</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Quit</property>
                 <property name="accelerator">&lt;Primary&gt;Q</property>
               </object>
@@ -32,24 +28,20 @@
         <child>
           <object class="GtkShortcutsGroup">
             <property name="title" translatable="yes">Collection</property>
-            <property name="visible">True</property>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Search</property>
                 <property name="accelerator">&lt;Primary&gt;F</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Show Shortcuts</property>
                 <property name="accelerator">&lt;ctrl&gt;question</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="shortcut-type">gesture</property>
                 <property name="icon">stick_icon</property>
                 <property name="title" translatable="yes" context="shortcut window">Navigate</property>
@@ -58,7 +50,6 @@
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="shortcut-type">gesture</property>
                 <property name="icon">dpad_icon</property>
                 <property name="title" translatable="yes" context="shortcut window">Navigate</property>
@@ -67,7 +58,6 @@
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="shortcut-type">gesture</property>
                 <property name="icon">shoulders_front_icon</property>
                 <property name="title" translatable="yes" context="shortcut window">Select view</property>
@@ -76,7 +66,6 @@
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="shortcut-type">gesture</property>
                 <property name="icon">button_start_icon</property>
                 <property name="title" translatable="yes" context="shortcut window">Start game</property>
@@ -85,7 +74,6 @@
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="shortcut-type">gesture</property>
                 <property name="icon">button_south_icon</property>
                 <property name="title" translatable="yes" context="shortcut window">Start game</property>
@@ -97,45 +85,38 @@
         <child>
           <object class="GtkShortcutsGroup">
             <property name="title" translatable="yes">In Game</property>
-            <property name="visible">True</property>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Create new 
snapshot</property>
                 <property name="accelerator">&lt;ctrl&gt;S F2</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Load the latest 
snapshot</property>
                 <property name="accelerator">&lt;ctrl&gt;D F3</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Show snapshots</property>
                 <property name="accelerator">&lt;ctrl&gt;A F4</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Toggle 
fullscreen</property>
                 <property name="accelerator">&lt;Primary&gt;F F11</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Exit 
fullscreen</property>
                 <property name="accelerator">Escape</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="shortcut-type">gesture</property>
                 <property name="icon">button_home_icon</property>
                 <property name="title" translatable="yes" context="shortcut window">Go back to the 
collection</property>
@@ -144,7 +125,6 @@
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="shortcut-type">gesture</property>
                 <property name="icon">button_south_icon</property>
                 <property name="title" translatable="yes" context="shortcut window">Accept</property>
@@ -153,7 +133,6 @@
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="shortcut-type">gesture</property>
                 <property name="icon">button_east_icon</property>
                 <property name="title" translatable="yes" context="shortcut window">Cancel</property>
@@ -169,21 +148,17 @@
         <property name="max-height">12</property>
         <property name="section-name">nintendo_ds_3ds</property>
         <property name="title" translatable="yes">Nintendo DS and 3DS</property>
-        <property name="visible">True</property>
         <child>
           <object class="GtkShortcutsGroup">
             <property name="title" translatable="yes" comments="Translators: This is displayed in the 
shortcuts window. This corresponds to the screens layout for the Nintendo DS and 3DS.">Screen 
Layout</property>
-            <property name="visible">True</property>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window" comments="Translators: 
This describes the layout for the Nintendo DS and 3DS emulators. This setting means the two screens are 
stacked one on top of the other">Vertical</property>
                 <property name="accelerator">&lt;alt&gt;1</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window" comments="Translators: 
This describes the layout for the Nintendo DS and 3DS emulators. This setting means the two screens are 
displayed side by side">Side by side</property>
                 <property name="subtitle" translatable="yes" context="shortcut window" 
comments="Translators: This describes the layout for the Nintendo DS and 3DS emulators when the two screens 
are displayed side by side and not one on top of the other. The bottom screen is displayed to the right of 
the top screen.">Bottom to the right</property>
                 <property name="accelerator">&lt;alt&gt;2</property>
@@ -191,7 +166,6 @@
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window" comments="Translators: 
This describes the Nintendo DS and 3DS screens layout, side by side">Side by side</property>
                 <property name="subtitle" translatable="yes" context="shortcut window" 
comments="Translators: This describes the layout for the Nintendo DS and 3DS emulators when the two screens 
are displayed side by side and not one on top of the other. The bottom screen is displayed to the left of the 
top screen">Bottom to the left</property>
                 <property name="accelerator">&lt;alt&gt;3</property>
@@ -199,7 +173,6 @@
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window" comments="Translators: 
This describes the layout for the Nintendo DS and 3DS emulators. This setting means only one screen is 
displayed at once. The screen displayed can then be changed in-game">Single screen</property>
                 <property name="accelerator">&lt;alt&gt;4</property>
               </object>
@@ -209,24 +182,20 @@
         <child>
           <object class="GtkShortcutsGroup">
             <property name="title" translatable="yes">Screen Switching</property>
-            <property name="visible">True</property>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window" comments="Translators: 
This describes the shortcut for showing the top screen, when the Nintendo DS and 3DS emulators is single 
screen mode.">Show top screen</property>
                 <property name="accelerator">Page_Up</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window" comments="Translators: 
This describes the shortcut for showing the bottom screen, when the Nintendo DS and 3DS emulators is single 
screen mode.">Show bottom screen</property>
                 <property name="accelerator">Page_Down</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="shortcut-type">gesture</property>
                 <property name="icon">stick_icon</property>
                 <property name="title" translatable="yes" context="shortcut window" comments="Translators: 
This describes the shortcut for toggling whether the top or bottom screen is displayed, when the Nintendo DS 
and 3DS emulators is single screen mode.">Toggle screen</property>
@@ -242,14 +211,11 @@
         <property name="max-height">12</property>
         <property name="section-name">cheats</property>
         <property name="title" translatable="yes">Cheats</property>
-        <property name="visible">True</property>
         <child>
           <object class="GtkShortcutsGroup">
             <property name="title" translatable="yes">Collection</property>
-            <property name="visible">True</property>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Contribute</property>
                 <property name="accelerator">Up+Up+Down+Down+Left+Right+Left+Right+B+A</property>
               </object>
diff --git a/src/ui/input-mode-switcher.ui b/src/ui/input-mode-switcher.ui
index 00b5e145..f92bf7c0 100644
--- a/src/ui/input-mode-switcher.ui
+++ b/src/ui/input-mode-switcher.ui
@@ -6,42 +6,17 @@
       <class name="linked"/>
     </style>
     <child>
-      <object class="GtkRadioButton" id="gamepad_mode">
-        <property name="visible">True</property>
-        <property name="draw-indicator">False</property>
-        <property name="can-focus">False</property>
+      <object class="GtkToggleButton" id="gamepad_mode">
         <signal name="toggled" handler="on_gamepad_button_toggled"/>
-        <child internal-child="accessible">
-          <object class="AtkObject" id="a11y-gamepad-input">
-            <property name="accessible-name" translatable="yes">Gamepad Input</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkImage" id="gamepad_image">
-            <property name="visible">True</property>
-            <property name="icon-name">input-gaming-symbolic</property>
-          </object>
-        </child>
+        <property name="tooltip-text" translatable="yes">Gamepad Input</property>
       </object>
     </child>
     <child>
-      <object class="GtkRadioButton" id="keyboard_mode">
-        <property name="visible">True</property>
-        <property name="draw-indicator">False</property>
-        <property name="can-focus">False</property>
+      <object class="GtkToggleButton" id="keyboard_mode">
         <property name="group">gamepad_mode</property>
+        <property name="icon-name">input-keyboard-symbolic</property>
+        <property name="tooltip-text" translatable="yes">Keyboard Input</property>
         <signal name="toggled" handler="on_keyboard_button_toggled"/>
-        <child internal-child="accessible">
-          <object class="AtkObject" id="a11y-keyboard-input">
-            <property name="accessible-name" translatable="yes">Keyboard Input</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkImage" id="keyboard_image">
-            <property name="visible">True</property>
-            <property name="icon-name">input-keyboard-symbolic</property>
-          </object>
-        </child>
       </object>
     </child>
   </template>
diff --git a/src/ui/input-mode-switcher.vala b/src/ui/input-mode-switcher.vala
index c1bb05c6..754e47c2 100644
--- a/src/ui/input-mode-switcher.vala
+++ b/src/ui/input-mode-switcher.vala
@@ -23,9 +23,9 @@ private class Games.InputModeSwitcher : Gtk.Box {
        }
 
        [GtkChild]
-       private unowned Gtk.RadioButton keyboard_mode;
+       private unowned Gtk.ToggleButton keyboard_mode;
        [GtkChild]
-       private unowned Gtk.RadioButton gamepad_mode;
+       private unowned Gtk.ToggleButton gamepad_mode;
 
        [GtkCallback]
        private void on_keyboard_button_toggled () {
diff --git a/src/ui/konami-code.vala b/src/ui/konami-code.vala
index 306530c5..4be0d6f2 100644
--- a/src/ui/konami-code.vala
+++ b/src/ui/konami-code.vala
@@ -48,19 +48,16 @@ private class Games.KonamiCode : Object {
        }
 
        construct {
-               widget.key_press_event.connect (on_key_pressed);
+               var controller = new Gtk.EventControllerKey ();
+               controller.key_pressed.connect (on_key_pressed);
+               widget.add_controller (controller);
        }
 
        public void reset () {
                current_index = 0;
        }
 
-       private bool on_key_pressed (Gdk.EventKey event) {
-               uint keyval;
-               var keymap = Gdk.Keymap.get_for_display (widget.get_display ());
-               keymap.translate_keyboard_state (event.hardware_keycode, event.state,
-                                                event.group, out keyval, null, null, null);
-
+       private bool on_key_pressed (uint keyval, uint keycode, Gdk.ModifierType state) {
                if (keyval != CODE_LOWER_KEYS[current_index] &&
                    keyval != CODE_UPPER_KEYS[current_index]) {
                        current_index = 0;
diff --git a/src/ui/media-menu-button.ui b/src/ui/media-menu-button.ui
index aebb126a..6683f3aa 100644
--- a/src/ui/media-menu-button.ui
+++ b/src/ui/media-menu-button.ui
@@ -1,58 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesMediaMenuButton" parent="GtkBin">
+  <template class="GamesMediaMenuButton" parent="AdwBin">
     <child>
       <object class="GtkMenuButton" id="menu_button">
-        <property name="visible">True</property>
-        <property name="can-focus">False</property>
-        <property name="valign">center</property>
-        <property name="popover">popover</property>
-        <property name="active" bind-source="GamesMediaMenuButton" bind-property="active" 
bind-flags="bidirectional"/>
-        <child internal-child="accessible">
-          <object class="AtkObject" id="a11y-display-discs">
-            <property name="accessible-name" translatable="yes">Media</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkBox">
-            <property name="visible">True</property>
-            <property name="spacing">6</property>
-            <child>
-              <object class="GtkImage" id="media_image">
-                <property name="visible">True</property>
-              </object>
-            </child>
+        <!-- FIXME GTK4 Need a dropdown arrow -->
+        <property name="tooltip-text" translatable="yes">Media</property>
+<!-- FIXME GTK4        <property name="active" bind-source="GamesMediaMenuButton" bind-property="active" 
bind-flags="bidirectional"/>-->
+        <property name="popover">
+          <object class="GtkPopover" id="popover">
+            <style>
+              <class name="combo"/>
+            </style>
             <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="icon-name">pan-down-symbolic</property>
+              <object class="GtkListBox" id="list_box">
+                <property name="selection-mode">none</property>
+                <signal name="row-activated" after="yes" handler="on_row_activated"/>
               </object>
             </child>
           </object>
-        </child>
+        </property>
       </object>
     </child>
   </template>
-  <object class="GtkPopover" id="popover">
-    <property name="visible">False</property>
-    <property name="relative-to">menu_button</property>
-    <child>
-      <object class="GtkFrame">
-        <property name="visible">True</property>
-        <property name="margin-top">6</property>
-        <property name="margin-bottom">6</property>
-        <property name="margin-start">6</property>
-        <property name="margin-end">6</property>
-        <property name="shadow-type">in</property>
-        <child>
-          <object class="GtkListBox" id="list_box">
-            <property name="visible">True</property>
-            <property name="selection-mode">none</property>
-            <signal name="row-activated" after="yes" handler="on_row_activated"/>
-          </object>
-        </child>
-      </object>
-    </child>
-  </object>
 </interface>
diff --git a/src/ui/media-menu-button.vala b/src/ui/media-menu-button.vala
index 29546c1e..399d2881 100644
--- a/src/ui/media-menu-button.vala
+++ b/src/ui/media-menu-button.vala
@@ -1,9 +1,9 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/media-menu-button.ui")]
-private class Games.MediaMenuButton : Gtk.Bin {
+private class Games.MediaMenuButton : Adw.Bin {
        [GtkChild]
-       private unowned Gtk.Image media_image;
+       private unowned Gtk.MenuButton menu_button;
        [GtkChild]
        private unowned Gtk.Popover popover;
        [GtkChild]
@@ -24,7 +24,7 @@ private class Games.MediaMenuButton : Gtk.Bin {
 
                        if (_media_set != null) {
                                media_set_changed_id = _media_set.notify["selected-media-number"].connect 
(reset_media);
-                               media_image.set_from_icon_name (media_set.icon_name, Gtk.IconSize.BUTTON);
+                               menu_button.icon_name = media_set.icon_name;
                        }
 
                        reset_media ();
@@ -54,14 +54,21 @@ private class Games.MediaMenuButton : Gtk.Bin {
                        checkmark_item.sensitive = media_has_uris;
                        var is_current_media = (_media_set.selected_media_number == media_number);
                        checkmark_item.checkmark_visible = is_current_media;
-                       list_box.add (checkmark_item);
+                       list_box.append (checkmark_item);
 
                        media_number++;
                });
        }
 
        private void remove_media () {
-               list_box.foreach ((child) => child.destroy ());
+               while (true) {
+                       var row = list_box.get_row_at_index (0);
+
+                       if (row == null)
+                               break;
+
+                       list_box.remove (row);
+               }
        }
 
        [GtkCallback]
diff --git a/src/ui/platform-list-item.ui b/src/ui/platform-list-item.ui
index ecfb1d61..caae6f47 100644
--- a/src/ui/platform-list-item.ui
+++ b/src/ui/platform-list-item.ui
@@ -2,10 +2,8 @@
 <interface>
   <requires lib="gtk+" version="3.24"/>
   <template class="GamesPlatformListItem" parent="GtkListBoxRow">
-    <property name="visible">true</property>
     <child>
       <object class="GtkLabel" id="label">
-        <property name="visible">true</property>
         <property name="margin-top">10</property>
         <property name="margin-bottom">10</property>
         <property name="margin-start">10</property>
diff --git a/src/ui/platforms-page.ui b/src/ui/platforms-page.ui
index 3e1ceacc..dda8319a 100644
--- a/src/ui/platforms-page.ui
+++ b/src/ui/platforms-page.ui
@@ -1,15 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesPlatformsPage" parent="GtkBin">
-    <property name="visible">True</property>
+  <template class="GamesPlatformsPage" parent="AdwBin">
     <signal name="map" after="yes" handler="on_map"/>
     <signal name="unmap" after="no" handler="on_unmap"/>
     <signal name="notify::is-folded" handler="on_folded_changed"/>
     <signal name="notify::is-selection-mode" handler="on_selection_mode_changed"/>
     <child>
-      <object class="HdyFlap" id="flap">
-        <property name="visible">True</property>
+      <object class="AdwFlap" id="flap">
         <property name="swipe-to-close" bind-source="flap" bind-property="folded" bind-flags="sync-create"/>
         <property name="swipe-to-open" bind-source="GamesPlatformsPage" bind-property="is-selection-mode" 
bind-flags="invert-boolean"/>
         <property name="locked" bind-source="GamesPlatformsPage" bind-property="is-selection-mode"/>
@@ -17,32 +15,33 @@
         <signal name="notify::folded" handler="on_flap_folded_changed"/>
         <child type="flap">
           <object class="GtkScrolledWindow" id="scrolled_window">
-            <property name="visible">True</property>
             <property name="vexpand">True</property>
             <property name="width-request">250</property>
-            <child>
-              <object class="GtkBox">
-                <property name="visible">True</property>
-                <property name="orientation">vertical</property>
-                <style>
-                  <class name="sidebar"/>
-                </style>
-                <child>
-                  <object class="GtkListBox" id="list_box">
-                    <property name="visible">True</property>
-                    <signal name="row-activated" handler="on_list_box_row_activated"/>
+            <property name="child">
+              <object class="GtkViewport">
+                <property name="scroll-to-focus">True</property>
+                <property name="child">
+                  <object class="GtkBox">
+                    <property name="orientation">vertical</property>
                     <style>
-                      <class name="separators"/>
+                      <class name="sidebar"/>
                     </style>
+                    <child>
+                      <object class="GtkListBox" id="list_box">
+                        <signal name="row-activated" handler="on_list_box_row_activated"/>
+                        <style>
+                          <class name="separators"/>
+                        </style>
+                      </object>
+                    </child>
                   </object>
-                </child>
+                </property>
               </object>
-            </child>
+            </property>
           </object>
         </child>
         <child type="separator">
           <object class="GtkSeparator">
-            <property name="visible">True</property>
             <style>
               <class name="sidebar"/>
             </style>
@@ -50,7 +49,6 @@
         </child>
         <child type="content">
           <object class="GamesGamesPage" id="games_page">
-            <property name="visible">True</property>
             <property name="hexpand">True</property>
             <property name="is-selection-mode" bind-source="GamesPlatformsPage" 
bind-property="is-selection-mode" bind-flags="bidirectional"/>
             <property name="is-search-empty" bind-source="GamesPlatformsPage" 
bind-property="is-search-empty" bind-flags="bidirectional"/>
diff --git a/src/ui/platforms-page.vala b/src/ui/platforms-page.vala
index f5351a9b..a60fbf0a 100644
--- a/src/ui/platforms-page.vala
+++ b/src/ui/platforms-page.vala
@@ -1,12 +1,12 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/platforms-page.ui")]
-private class Games.PlatformsPage : Gtk.Bin {
+private class Games.PlatformsPage : Adw.Bin {
        public signal void game_activated (Game game);
        public signal void selected_items_changed ();
 
        [GtkChild]
-       private unowned Hdy.Flap flap;
+       private unowned Adw.Flap flap;
        [GtkChild]
        private unowned Gtk.ListBox list_box;
        [GtkChild]
@@ -182,12 +182,12 @@ private class Games.PlatformsPage : Gtk.Bin {
 
                switch (direction) {
                case Gtk.DirectionType.UP:
-                       list_box.move_cursor (Gtk.MovementStep.DISPLAY_LINES, -1);
+                       list_box.move_cursor (Gtk.MovementStep.DISPLAY_LINES, -1, false, false);
                        select_platform_for_row (list_box.get_selected_row ());
 
                        return true;
                case Gtk.DirectionType.DOWN:
-                       list_box.move_cursor (Gtk.MovementStep.DISPLAY_LINES, 1);
+                       list_box.move_cursor (Gtk.MovementStep.DISPLAY_LINES, 1, false, false);
                        select_platform_for_row (list_box.get_selected_row ());
 
                        return true;
@@ -243,8 +243,12 @@ private class Games.PlatformsPage : Gtk.Bin {
        }
 
        private void select_first_visible_row () {
-               foreach (var child in list_box.get_children ()) {
-                       var row = child as Gtk.ListBoxRow;
+               int i = 0;
+               while (true) {
+                       var row = list_box.get_row_at_index (i);
+
+                       if (row == null)
+                               break;
 
                        if (row.get_child_visible ()) {
                                list_box.select_row (row);
@@ -252,6 +256,8 @@ private class Games.PlatformsPage : Gtk.Bin {
                                select_platform_for_row (row);
                                break;
                        }
+
+                       i++;
                }
        }
 
diff --git a/src/ui/popover-bin.vala b/src/ui/popover-bin.vala
new file mode 100644
index 00000000..2dcc6f56
--- /dev/null
+++ b/src/ui/popover-bin.vala
@@ -0,0 +1,79 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+private class Games.PopoverBin : Gtk.Widget, Gtk.Buildable {
+       private Gtk.Widget? _child;
+       public Gtk.Widget? child {
+               get { return _child; }
+               set {
+                       if (child == value)
+                               return;
+
+                       if (child != null)
+                               child.unparent ();
+
+                       _child = value;
+
+                       if (child != null)
+                               child.set_parent (this);
+               }
+       }
+
+       private Gtk.Popover? _popover;
+       public Gtk.Popover? popover {
+               get { return _popover; }
+               set {
+                       if (popover == value)
+                               return;
+
+                       if (popover != null)
+                               popover.unparent ();
+
+                       _popover = value;
+
+                       if (popover != null)
+                               popover.set_parent (this);
+               }
+       }
+
+       public void add_child (Gtk.Builder builder, Object child, string? type) {
+               if (child is Gtk.Popover && type == "popover") {
+                       popover = child as Gtk.Popover;
+                       return;
+               }
+
+               if (child is Gtk.Widget) {
+                       this.child = child as Gtk.Widget;
+                       return;
+               }
+
+               base.add_child (builder, child, type);
+       }
+
+       protected override void measure (Gtk.Orientation orientation, int for_size, out int min, out int nat, 
out int min_baseline, out int nat_baseline) {
+               if (child == null) {
+                       min = 0;
+                       nat = 0;
+                       min_baseline = -1;
+                       nat_baseline = -1;
+
+                       return;
+               }
+
+               child.measure (orientation, for_size, out min, out nat, out min_baseline, out nat_baseline);
+       }
+
+       protected override void size_allocate (int width, int height, int baseline) {
+               if (child != null)
+                       child.allocate (width, height, baseline, null);
+
+               if (popover != null)
+                       popover.present ();
+       }
+
+       protected override void dispose () {
+               child = null;
+               popover = null;
+
+               base.dispose ();
+       }
+}
diff --git a/src/ui/search-bar.ui b/src/ui/search-bar.ui
index 498156b4..e7dec67d 100644
--- a/src/ui/search-bar.ui
+++ b/src/ui/search-bar.ui
@@ -1,25 +1,24 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesSearchBar" parent="GtkBin">
-    <property name="visible">True</property>
-    <child>
-      <object class="HdySearchBar" id="search_bar">
-        <property name="visible">True</property>
+  <template class="GamesSearchBar" parent="AdwBin">
+    <property name="child">
+      <object class="GtkSearchBar" id="search_bar">
+        <property name="key-capture-widget" bind-source="GamesSearchBar" bind-property="key-capture-widget"/>
         <property name="search-mode-enabled" bind-source="GamesSearchBar" 
bind-property="search-mode-enabled" bind-flags="sync-create|bidirectional"/>
-        <child>
-          <object class="HdyClamp">
-            <property name="visible">True</property>
-            <child>
+        <property name="child">
+          <object class="AdwClamp">
+            <property name="hexpand">True</property><!-- FIXME GTK4 we shouldn't need this, libadwaita bug 
-->
+            <property name="child">
               <object class="GtkSearchEntry" id="entry">
-                <property name="visible">True</property>
+                <property name="hexpand">True</property>
                 <signal name="search-changed" handler="on_search_changed"/>
                 <signal name="activate" handler="on_search_activated"/>
               </object>
-            </child>
+            </property>
           </object>
-        </child>
+        </property>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
diff --git a/src/ui/search-bar.vala b/src/ui/search-bar.vala
index 7c6a95f8..d3ec660d 100644
--- a/src/ui/search-bar.vala
+++ b/src/ui/search-bar.vala
@@ -1,12 +1,13 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/search-bar.ui")]
-private class Games.SearchBar : Gtk.Bin {
+private class Games.SearchBar : Adw.Bin {
        public string text { get; private set; }
        public bool search_mode_enabled { get; set; }
+       public unowned Gtk.Widget key_capture_widget { get; set; }
 
        [GtkChild]
-       private unowned Hdy.SearchBar search_bar;
+       private unowned Gtk.SearchBar search_bar;
        [GtkChild]
        private unowned Gtk.SearchEntry entry;
 
@@ -25,14 +26,10 @@ private class Games.SearchBar : Gtk.Bin {
        }
 
        public void focus_entry () {
-               entry.grab_focus_without_selecting ();
+               entry.grab_focus ();
        }
 
        public void run_search (string query) {
                entry.text = query;
        }
-
-       public bool handle_event (Gdk.Event event) {
-               return search_bar.handle_event (event);
-       }
 }
diff --git a/src/ui/selection-action-bar.ui b/src/ui/selection-action-bar.ui
index a71593c7..3b035514 100644
--- a/src/ui/selection-action-bar.ui
+++ b/src/ui/selection-action-bar.ui
@@ -1,66 +1,72 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesSelectionActionBar" parent="GtkActionBar">
-    <property name="visible">True</property>
+  <template class="GamesSelectionActionBar" parent="AdwBin">
     <property name="sensitive">False</property>
-    <child>
-      <object class="GtkBox">
-        <property name="visible" bind-source="GamesSelectionActionBar" bind-property="show-game-actions"/>
-        <property name="hexpand">True</property>
-        <property name="spacing">6</property>
-        <child>
+    <property name="child">
+      <object class="GtkActionBar">
+        <child type="start">
           <object class="GtkButton" id="favorite_button">
-            <property name="visible">True</property>
+            <property name="visible" bind-source="GamesSelectionActionBar" bind-property="show-game-actions" 
bind-flags="sync-create"/>
             <property name="action-name">view.favorite-action</property>
             <property name="tooltip-text" translatable="yes">Add/Remove selected games to favorite</property>
+            <style>
+              <class name="image-button"/>
+            </style>
             <child>
               <object class="GtkStack" id="icon_stack">
-                <property name="visible">True</property>
                 <property name="transition-type">crossfade</property>
                 <child>
-                  <object class="GtkImage">
-                    <property name="visible">True</property>
-                    <property name="icon-name">starred-symbolic</property>
-                  </object>
-                  <packing>
+                  <object class="GtkStackPage">
                     <property name="name">starred-icon</property>
-                  </packing>
+                    <property name="child">
+                      <object class="GtkImage">
+                        <property name="icon-name">starred-symbolic</property>
+                      </object>
+                    </property>
+                  </object>
                 </child>
                 <child>
-                  <object class="GtkImage">
-                    <property name="visible">True</property>
-                    <property name="icon-name">non-starred-symbolic</property>
-                  </object>
-                  <packing>
+                  <object class="GtkStackPage">
                     <property name="name">non-starred-icon</property>
-                  </packing>
+                    <property name="child">
+                      <object class="GtkImage">
+                        <property name="icon-name">non-starred-symbolic</property>
+                      </object>
+                    </property>
+                  </object>
                 </child>
                 <child>
-                  <object class="GtkImage">
-                    <property name="visible">True</property>
-                    <property name="icon-name">semi-starred-symbolic</property>
-                  </object>
-                  <packing>
+                  <object class="GtkStackPage">
                     <property name="name">semi-starred-icon</property>
-                  </packing>
+                    <property name="child">
+                      <object class="GtkImage">
+                        <property name="icon-name">semi-starred-symbolic</property>
+                      </object>
+                    </property>
+                  </object>
                 </child>
               </object>
             </child>
           </object>
         </child>
-        <child>
+        <child type="start">
           <object class="GtkButton" id="add_to_collection_button">
-            <property name="visible">True</property>
+            <property name="visible" bind-source="GamesSelectionActionBar" bind-property="show-game-actions" 
bind-flags="sync-create"/>
             <property name="label" translatable="yes">_Add to Collection</property>
             <property name="use-underline">True</property>
             <property name="action-name">view.add-to-collection</property>
             <property name="tooltip-text" translatable="yes">Add selected games to a collection</property>
           </object>
         </child>
-        <child>
+        <child type="end">
           <object class="GtkButton">
-            <property name="visible" bind-source="GamesSelectionActionBar" 
bind-property="show-remove-button"/>
+            <binding name="visible">
+              <closure  type="gboolean" function="and_2">
+                <lookup name="show-remove-button">GamesSelectionActionBar</lookup>
+                <lookup name="show-game-actions">GamesSelectionActionBar</lookup>
+              </closure>
+            </binding>
             <property name="label" translatable="yes">_Remove</property>
             <property name="use-underline">True</property>
             <property name="action-name">view.remove-from-collection</property>
@@ -69,26 +75,20 @@
               <class name="destructive-action"/>
             </style>
           </object>
-          <packing>
-            <property name="pack-type">end</property>
-          </packing>
+        </child>
+        <child type="end">
+          <object class="GtkButton">
+            <property name="visible" bind-source="GamesSelectionActionBar" 
bind-property="show-remove-collection-button" bind-flags="sync-create"/>
+            <property name="label" translatable="yes">_Remove</property>
+            <property name="use-underline">True</property>
+            <property name="action-name">view.remove-collection</property>
+            <property name="tooltip-text" translatable="yes">Remove selected collections</property>
+            <style>
+              <class name="destructive-action"/>
+            </style>
+          </object>
         </child>
       </object>
-    </child>
-    <child>
-      <object class="GtkButton">
-        <property name="visible" bind-source="GamesSelectionActionBar" 
bind-property="show-remove-collection-button"/>
-        <property name="label" translatable="yes">_Remove</property>
-        <property name="use-underline">True</property>
-        <property name="action-name">view.remove-collection</property>
-        <property name="tooltip-text" translatable="yes">Remove selected collections</property>
-        <style>
-          <class name="destructive-action"/>
-        </style>
-      </object>
-      <packing>
-        <property name="pack-type">end</property>
-      </packing>
-    </child>
+    </property>
   </template>
 </interface>
diff --git a/src/ui/selection-action-bar.vala b/src/ui/selection-action-bar.vala
index 21f65ac6..4c86c3be 100644
--- a/src/ui/selection-action-bar.vala
+++ b/src/ui/selection-action-bar.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/selection-action-bar.ui")]
-private class Games.SelectionActionBar : Gtk.ActionBar {
+private class Games.SelectionActionBar : Adw.Bin {
        [GtkChild]
        private unowned Gtk.Stack icon_stack;
        [GtkChild]
@@ -89,4 +89,9 @@ private class Games.SelectionActionBar : Gtk.ActionBar {
 
                return true;
        }
+
+       [GtkCallback]
+       private bool and_2 (bool a, bool b) {
+               return a && b;
+       }
 }
diff --git a/src/ui/snapshot-row.ui b/src/ui/snapshot-row.ui
index aab0e226..851e5f3b 100644
--- a/src/ui/snapshot-row.ui
+++ b/src/ui/snapshot-row.ui
@@ -2,76 +2,124 @@
 <interface>
   <requires lib="gtk+" version="3.24"/>
   <template class="GamesSnapshotRow" parent="GtkListBoxRow">
-    <property name="visible">true</property>
     <style>
       <class name="snapshot-row"/>
     </style>
     <child>
       <object class="GtkRevealer" id="revealer">
-        <property name="visible">true</property>
         <property name="reveal-child">False</property>
-        <child>
-          <object class="GtkBox">
-            <property name="visible">true</property>
-            <property name="margin-top">2</property>
-            <property name="margin-bottom">2</property>
-            <property name="margin-start">2</property>
-            <property name="margin-end">2</property>
-            <child>
-              <object class="GamesSnapshotThumbnail" id="thumbnail">
-                <property name="visible">true</property>
-                <property name="valign">start</property>
-                <property name="margin-top">7</property>
-                <property name="margin-bottom">7</property>
-                <property name="margin-start">7</property>
-                <property name="margin-end">7</property>
-              </object>
-            </child>
-            <child>
+        <property name="child">
+          <object class="GamesPopoverBin">
+            <property name="child">
               <object class="GtkBox">
-                <property name="visible">true</property>
-                <property name="orientation">vertical</property>
-                <property name="margin-start">6</property>
-                <property name="margin-top">3</property>
-                <property name="margin-bottom">6</property>
-                <property name="margin-end">3</property>
-                <property name="spacing">6</property>
-                <property name="vexpand">True</property>
+                <property name="margin-top">2</property>
+                <property name="margin-bottom">2</property>
+                <property name="margin-start">2</property>
+                <property name="margin-end">2</property>
                 <child>
-                  <object class="GtkLabel" id="name_label">
-                    <property name="visible">true</property>
-                    <property name="wrap">true</property>
-                    <property name="wrap-mode">word-char</property>
-                    <property name="xalign">0</property>
-                    <property name="yalign">0.75</property>
-                    <property name="vexpand">True</property>
-                    <attributes>
-                      <attribute name="font-features" value="tnum=1"/>
-                    </attributes>
-                    <style>
-                      <class name="snapshot-name"/>
-                    </style>
+                  <object class="GamesSnapshotThumbnail" id="thumbnail">
+                    <property name="valign">start</property>
+                    <property name="margin-top">7</property>
+                    <property name="margin-bottom">7</property>
+                    <property name="margin-start">7</property>
+                    <property name="margin-end">7</property>
                   </object>
                 </child>
                 <child>
-                  <object class="GtkLabel" id="date_label">
-                    <property name="visible">true</property>
-                    <property name="wrap">true</property>
-                    <property name="xalign">0</property>
-                    <property name="yalign">0.25</property>
-                    <property name="vexpand">True</property>
-                    <attributes>
-                      <attribute name="font-features" value="tnum=1"/>
-                    </attributes>
-                    <style>
-                      <class name="snapshot-date"/>
-                    </style>
+                  <object class="GtkBox">
+                    <property name="orientation">vertical</property>
+                    <property name="margin-start">6</property>
+                    <property name="margin-top">3</property>
+                    <property name="margin-bottom">6</property>
+                    <property name="margin-end">3</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkLabel" id="name_label">
+                        <property name="ellipsize">end</property>
+                        <property name="xalign">0</property>
+                        <property name="yalign">0.75</property>
+                        <property name="vexpand">True</property>
+                        <style>
+                          <class name="snapshot-name"/>
+                          <class name="numeric"/>
+                        </style>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="date_label">
+                        <property name="ellipsize">end</property>
+                        <property name="xalign">0</property>
+                        <property name="yalign">0.25</property>
+                        <property name="vexpand">True</property>
+                        <style>
+                          <class name="snapshot-date"/>
+                          <class name="numeric"/>
+                        </style>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </property>
+            <property name="popover">
+              <object class="GtkPopover" id="rename_popover">
+                <property name="position">left</property>
+                <property name="width-request">360</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">12</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">true</property>
+                        <property name="label" translatable="yes">Name</property>
+                        <property name="halign">start</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                        </attributes>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="visible">true</property>
+                        <property name="spacing">12</property>
+                        <child>
+                          <object class="GtkEntry" id="rename_entry">
+                            <property name="hexpand">true</property>
+                            <property name="width-chars">1</property>
+                            <signal name="notify::text" handler="on_rename_entry_text_changed"/>
+                            <signal name="activate" handler="on_rename_entry_activated"/>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="rename_popover_btn">
+                            <property name="use-underline">True</property>
+                            <property name="label" translatable="yes">_Rename</property>
+                            <signal name="clicked" handler="apply_rename"/>
+                            <style>
+                              <class name="suggested-action"/>
+                            </style>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="rename_error_label">
+                        <property name="halign">start</property>
+                        <property name="wrap">true</property>
+                        <property name="wrap-mode">word</property>
+                        <property name="max-width-chars">35</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                    </child>
                   </object>
                 </child>
               </object>
-            </child>
+            </property>
           </object>
-        </child>
+        </property>
       </object>
     </child>
   </template>
diff --git a/src/ui/snapshot-row.vala b/src/ui/snapshot-row.vala
index 05871c7b..5a2f8b60 100644
--- a/src/ui/snapshot-row.vala
+++ b/src/ui/snapshot-row.vala
@@ -10,40 +10,42 @@ private class Games.SnapshotRow : Gtk.ListBoxRow {
        private unowned Gtk.Label date_label;
        [GtkChild]
        private unowned Gtk.Revealer revealer;
+       [GtkChild]
+       private unowned Gtk.Popover rename_popover;
+       [GtkChild]
+       private unowned Gtk.Entry rename_entry;
+       [GtkChild]
+       private unowned Gtk.Button rename_popover_btn;
+       [GtkChild]
+       private unowned Gtk.Label rename_error_label;
+
+       public Runner runner { get; set; }
 
-       private Snapshot _snapshot;
-       public Snapshot snapshot {
-               get { return _snapshot; }
+       private Snapshot _game_snapshot;
+       public Snapshot game_snapshot {
+               get { return _game_snapshot; }
                set {
-                       _snapshot = value;
+                       _game_snapshot = value;
 
-                       if (snapshot.is_automatic)
+                       if (game_snapshot.is_automatic)
                                name_label.label = _("Autosave");
                        else
-                               name_label.label = snapshot.name;
+                               name_label.label = game_snapshot.name;
 
-                       var creation_date = snapshot.creation_date;
+                       var creation_date = game_snapshot.creation_date;
                        var date_format = get_date_format (creation_date);
                        date_label.label = creation_date.format (date_format);
 
-                       thumbnail.snapshot = snapshot;
+                       thumbnail.game_snapshot = game_snapshot;
                }
        }
 
-       public SnapshotRow (Snapshot snapshot) {
-               Object (snapshot: snapshot);
+       public SnapshotRow (Runner runner, Snapshot game_snapshot) {
+               Object (runner: runner, game_snapshot: game_snapshot);
        }
 
-       public void set_name (string name) {
-               name_label.label = name;
-               snapshot.name = name;
-
-               try {
-                       snapshot.write_metadata ();
-               }
-               catch (Error e) {
-                       critical ("Couldn't update snapshot name: %s", e.message);
-               }
+       static construct {
+               typeof (PopoverBin).ensure ();
        }
 
        public void reveal () {
@@ -53,7 +55,8 @@ private class Games.SnapshotRow : Gtk.ListBoxRow {
        public void remove_animated () {
                selectable = false;
                revealer.notify["child-revealed"].connect (() => {
-                       get_parent ().remove (this);
+                       var listbox = get_parent () as Gtk.ListBox;
+                       listbox.remove (this);
                });
                revealer.reveal_child = false;
        }
@@ -100,4 +103,72 @@ private class Games.SnapshotRow : Gtk.ListBoxRow {
                        return _("%-e %b %Y %X");
                }
        }
+
+       [GtkCallback]
+       private void on_rename_entry_activated () {
+               if (check_rename_is_valid ())
+                       apply_rename ();
+       }
+
+       [GtkCallback]
+       private void on_rename_entry_text_changed () {
+               check_rename_is_valid ();
+       }
+
+       private bool check_rename_is_valid () {
+               var entry_text = rename_entry.text.strip ();
+
+               if (entry_text == _("Autosave") || entry_text == "") {
+                       rename_entry.add_css_class ("error");
+                       rename_popover_btn.sensitive = false;
+
+                       /* Translators: This message is shown to the user if he tried to rename
+                        * his snapshot either with an empty string, or with the name of the
+                        * autosave */
+                       rename_error_label.label = _("Invalid name");
+
+                       return false;
+               }
+
+               var snapshots = runner.get_snapshots ();
+               foreach (var snapshot in snapshots) {
+                       if (snapshot.is_automatic)
+                               continue;
+
+                       if (snapshot.name == entry_text) {
+                               rename_entry.add_css_class ("error");
+                               rename_popover_btn.sensitive = false;
+                               rename_error_label.label =_("A snapshot with this name already exists");
+
+                               return false;
+                       }
+               }
+
+               // All checks passed, rename operation is valid
+               rename_entry.remove_css_class ("error");
+               rename_popover_btn.sensitive = true;
+               rename_error_label.label = "";
+
+               return true;
+       }
+
+       [GtkCallback]
+       private void apply_rename () {
+               game_snapshot.name = rename_entry.text.strip ();
+               name_label.label = game_snapshot.name;
+
+               try {
+                       game_snapshot.write_metadata ();
+               }
+               catch (Error e) {
+                       critical ("Couldn't update snapshot name: %s", e.message);
+               }
+
+               rename_popover.popdown ();
+       }
+
+       public void start_rename () {
+               rename_entry.text = game_snapshot.name;
+               rename_popover.popup ();
+       }
 }
diff --git a/src/ui/snapshot-thumbnail.vala b/src/ui/snapshot-thumbnail.vala
index 6894d4b5..2359a371 100644
--- a/src/ui/snapshot-thumbnail.vala
+++ b/src/ui/snapshot-thumbnail.vala
@@ -1,47 +1,58 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
-private class Games.SnapshotThumbnail : Gtk.DrawingArea {
+private class Games.SnapshotThumbnail : Gtk.Widget {
        public const int THUMBNAIL_SIZE = 64;
 
-       private Snapshot _snapshot;
-       public Snapshot snapshot {
-               get { return _snapshot; }
+       private Snapshot _game_snapshot;
+       public Snapshot game_snapshot {
+               get { return _game_snapshot; }
                set {
-                       _snapshot = value;
+                       _game_snapshot = value;
 
                        load_thumbnail ();
                }
        }
 
-       private Gdk.Pixbuf pixbuf;
+       private Gdk.Texture texture;
 
        construct {
                width_request = THUMBNAIL_SIZE;
                height_request = THUMBNAIL_SIZE;
 
-               get_style_context ().add_class ("snapshot-thumbnail");
+               add_css_class ("snapshot-thumbnail");
 
                notify["scale-factor"].connect (load_thumbnail);
        }
 
        private void load_thumbnail () {
-               if (snapshot == null)
+               if (game_snapshot == null)
                        return;
 
-               var screenshot_path = snapshot.get_screenshot_path ();
-               var screenshot_width = 0;
-               var screenshot_height = 0;
+               var screenshot_path = game_snapshot.get_screenshot_path ();
 
-               Gdk.Pixbuf.get_file_info (screenshot_path, out screenshot_width, out screenshot_height);
+               try {
+                       texture = Gdk.Texture.from_file (File.new_for_path (screenshot_path));
+               }
+               catch (Error e) {
+                       warning ("Failed to load snapshot thumbnail: %s", e.message);
+               }
+       }
 
-               var aspect_ratio = snapshot.screenshot_aspect_ratio;
+       public override void snapshot (Gtk.Snapshot snapshot) {
+               var width = get_width ();
+               var height = get_height ();
+
+               if (texture == null)
+                       return;
+
+               var aspect_ratio = game_snapshot.screenshot_aspect_ratio;
 
                // A fallback for migrated snapshots
                if (aspect_ratio == 0)
-                       aspect_ratio = (double) screenshot_width / screenshot_height;
+                       aspect_ratio = texture.get_intrinsic_aspect_ratio ();
 
-               var thumbnail_width = screenshot_width;
-               var thumbnail_height = (int) (screenshot_width / aspect_ratio);
+               var thumbnail_width = texture.get_intrinsic_width ();
+               var thumbnail_height = (int) (thumbnail_width / aspect_ratio);
 
                if (thumbnail_width > thumbnail_height) {
                        thumbnail_width = THUMBNAIL_SIZE;
@@ -55,76 +66,14 @@ private class Games.SnapshotThumbnail : Gtk.DrawingArea {
                thumbnail_width *= scale_factor;
                thumbnail_height *= scale_factor;
 
-               try {
-                       pixbuf = new Gdk.Pixbuf.from_file_at_scale (screenshot_path,
-                                                                   thumbnail_width,
-                                                                   thumbnail_height,
-                                                                   false);
-               }
-               catch (Error e) {
-                       warning ("Failed to load snapshot thumbnail: %s", e.message);
-               }
-       }
-
-       public override void size_allocate (Gtk.Allocation alloc) {
-               var context = get_style_context ();
-               var clip = context.render_background_get_clip (
-                       alloc.x,
-                       alloc.y,
-                       alloc.width,
-                       alloc.height
-               );
-
-               base.size_allocate (alloc);
-
-               set_clip (clip);
-       }
-
-       public override bool draw (Cairo.Context cr) {
-               var width = get_allocated_width ();
-               var height = get_allocated_height ();
-
-               var style = get_style_context ();
-               style.render_background (cr, 0.0, 0.0, width, height);
-               style.render_frame (cr, 0.0, 0.0, width, height);
-
-               if (pixbuf == null)
-                       return Gdk.EVENT_PROPAGATE;
-
-               cr.save ();
+               snapshot.scale (1.0f / scale_factor, 1.0f / scale_factor);
 
-               var flags = get_state_flags ();
-               var border_radius = (int) style.get_property (Gtk.STYLE_PROPERTY_BORDER_RADIUS, flags);
-               border_radius = border_radius.clamp (0, int.max (width / 2, height / 2));
-
-               rounded_rectangle (cr, 0, 0, width, height, border_radius);
-               cr.clip ();
-
-               cr.scale (1.0 / scale_factor, 1.0 / scale_factor);
-
-               var x_offset = (width * scale_factor - pixbuf.width) / 2;
-               var y_offset = (height * scale_factor - pixbuf.height) / 2;
-
-               Gdk.cairo_set_source_pixbuf (cr, pixbuf, x_offset, y_offset);
-               cr.paint ();
-
-               cr.restore ();
-
-               return Gdk.EVENT_PROPAGATE;
-       }
+               var x_offset = (width * scale_factor - thumbnail_width) / 2;
+               var y_offset = (height * scale_factor - thumbnail_height) / 2;
 
-       // TODO: Share this with GameThumbnail
-       private void rounded_rectangle (Cairo.Context cr, double x, double y, double width, double height, 
double radius) {
-               const double ARC_0 = 0;
-               const double ARC_1 = Math.PI * 0.5;
-               const double ARC_2 = Math.PI;
-               const double ARC_3 = Math.PI * 1.5;
-
-               cr.new_sub_path ();
-               cr.arc (x + width - radius, y + radius,          radius, ARC_3, ARC_0);
-               cr.arc (x + width - radius, y + height - radius, radius, ARC_0, ARC_1);
-               cr.arc (x + radius,         y + height - radius, radius, ARC_1, ARC_2);
-               cr.arc (x + radius,         y + radius,          radius, ARC_2, ARC_3);
-               cr.close_path ();
+               snapshot.append_texture (texture, {
+                       { x_offset, y_offset },
+                       { thumbnail_width, thumbnail_height }
+               });
        }
 }
diff --git a/src/ui/snapshots-list.ui b/src/ui/snapshots-list.ui
index 45689264..4dfa1f68 100644
--- a/src/ui/snapshots-list.ui
+++ b/src/ui/snapshots-list.ui
@@ -2,22 +2,20 @@
 <interface>
   <requires lib="gtk+" version="3.24"/>
   <template class="GamesSnapshotsList" parent="GtkBox">
+    <property name="orientation">vertical</property>
     <style>
       <class name="background"/>
     </style>
     <child>
-      <object class="GtkBox">
-        <property name="visible">True</property>
-        <property name="orientation">vertical</property>
-        <child>
-          <object class="GtkScrolledWindow" id="scrolled_window">
-            <property name="visible">True</property>
-            <property name="vexpand">True</property>
-            <property name="hscrollbar-policy">never</property>
-            <property name="width-request">360</property>
-            <child>
+      <object class="GtkScrolledWindow" id="scrolled_window">
+        <property name="vexpand">True</property>
+        <property name="hscrollbar-policy">never</property>
+        <property name="width-request">360</property>
+        <property name="child">
+          <object class="GtkViewport">
+            <property name="scroll-to-focus">True</property>
+            <property name="child">
               <object class="GtkListBox" id="list_box">
-                <property name="visible">True</property>
                 <signal name="move-cursor" after="yes" handler="on_move_cursor"/>
                 <signal name="row-activated" after="yes" handler="on_row_activated"/>
                 <style>
@@ -25,21 +23,18 @@
                 </style>
                 <child>
                   <object class="GtkListBoxRow" id="new_snapshot_row">
-                    <property name="visible">True</property>
                     <property name="selectable">False</property>
                     <style>
                       <class name="snapshot-row"/>
                     </style>
                     <child>
                       <object class="GtkBox">
-                        <property name="visible">True</property>
                         <property name="margin-top">2</property>
                         <property name="margin-bottom">2</property>
                         <property name="margin-start">2</property>
                         <property name="margin-end">2</property>
                         <child>
                           <object class="GtkImage">
-                            <property name="visible">True</property>
                             <property name="icon-name">list-add-symbolic</property>
                             <property name="pixel-size">32</property>
                             <property name="width-request">64</property>
@@ -55,7 +50,6 @@
                         </child>
                         <child>
                           <object class="GtkLabel">
-                            <property name="visible">True</property>
                             <property name="margin-start">6</property>
                             <property name="margin-top">3</property>
                             <property name="margin-bottom">3</property>
@@ -75,103 +69,31 @@
                   <class name="snapshot-list"/>
                 </style>
               </object>
-            </child>
+            </property>
           </object>
-        </child>
-        <child>
-          <object class="GtkActionBar">
-            <property name="visible">True</property>
-            <child>
-              <object class="GtkButton" id="delete_btn">
-                <property name="visible">True</property>
-                <property name="use-underline">True</property>
-                <property name="label" translatable="yes">_Delete</property>
-                <signal name="clicked" handler="on_delete_clicked"/>
-                <style>
-                  <class name="destructive-action"/>
-                </style>
-              </object>
-              <packing>
-                <property name="pack-type">end</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="rename_btn">
-                <property name="visible">True</property>
-                <property name="use-underline">True</property>
-                <property name="label" translatable="yes">_Rename</property>
-                <signal name="clicked" handler="on_rename_clicked"/>
-              </object>
-              <packing>
-                <property name="pack-type">start</property>
-              </packing>
-            </child>
-          </object>
-        </child>
+        </property>
       </object>
     </child>
-  </template>
-  <object class="GtkPopover" id="rename_popover">
-    <property name="position">left</property>
-    <property name="width-request">360</property>
     <child>
-      <object class="GtkBox">
-        <property name="visible">true</property>
-        <property name="orientation">vertical</property>
-        <property name="margin-top">12</property>
-        <property name="margin-bottom">12</property>
-        <property name="margin-start">12</property>
-        <property name="margin-end">12</property>
-        <property name="spacing">12</property>
-        <child>
-          <object class="GtkLabel">
-            <property name="visible">true</property>
-            <property name="label" translatable="yes">Name</property>
-            <property name="halign">start</property>
-            <attributes>
-              <attribute name="weight" value="bold"/>
-            </attributes>
+      <object class="GtkActionBar">
+        <child type="start">
+          <object class="GtkButton" id="rename_btn">
+            <property name="use-underline">True</property>
+            <property name="label" translatable="yes">_Rename</property>
+            <signal name="clicked" handler="on_rename_clicked"/>
           </object>
         </child>
-        <child>
-          <object class="GtkBox">
-            <property name="visible">true</property>
-            <property name="spacing">12</property>
-            <child>
-              <object class="GtkEntry" id="rename_entry">
-                <property name="visible">true</property>
-                <property name="hexpand">true</property>
-                <property name="width-chars">1</property>
-                <signal name="notify::text" handler="on_rename_entry_text_changed"/>
-                <signal name="activate" handler="on_rename_entry_activated"/>
-              </object>
-            </child>
-            <child>
-              <object class="GtkButton" id="rename_popover_btn">
-                <property name="visible">true</property>
-                <property name="use-underline">True</property>
-                <property name="label" translatable="yes">_Rename</property>
-                <signal name="clicked" handler="apply_rename"/>
-                <style>
-                  <class name="suggested-action"/>
-                </style>
-              </object>
-            </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkLabel" id="rename_error_label">
-            <property name="visible">true</property>
-            <property name="halign">start</property>
-            <property name="wrap">true</property>
-            <property name="wrap-mode">word</property>
-            <property name="max-width-chars">35</property>
+        <child type="end">
+          <object class="GtkButton" id="delete_btn">
+            <property name="use-underline">True</property>
+            <property name="label" translatable="yes">_Delete</property>
+            <signal name="clicked" handler="on_delete_clicked"/>
             <style>
-              <class name="dim-label"/>
+              <class name="destructive-action"/>
             </style>
           </object>
         </child>
       </object>
     </child>
-  </object>
+  </template>
 </interface>
diff --git a/src/ui/snapshots-list.vala b/src/ui/snapshots-list.vala
index b5f726f5..5d091d95 100644
--- a/src/ui/snapshots-list.vala
+++ b/src/ui/snapshots-list.vala
@@ -13,15 +13,6 @@ private class Games.SnapshotsList : Gtk.Box {
        [GtkChild]
        private unowned Gtk.Button rename_btn;
 
-       [GtkChild]
-       private unowned Gtk.Popover rename_popover;
-       [GtkChild]
-       private unowned Gtk.Entry rename_entry;
-       [GtkChild]
-       private unowned Gtk.Button rename_popover_btn;
-       [GtkChild]
-       private unowned Gtk.Label rename_error_label;
-
        private Snapshot selected_snapshot;
 
        public Runner runner { get; set; }
@@ -36,7 +27,7 @@ private class Games.SnapshotsList : Gtk.Box {
 
                if (row != null && row is SnapshotRow) {
                        var snapshot_row = row as SnapshotRow;
-                       var snapshot = snapshot_row.snapshot;
+                       var snapshot = snapshot_row.game_snapshot;
 
                        if (snapshot != selected_snapshot)
                                select_snapshot_row (row);
@@ -49,7 +40,7 @@ private class Games.SnapshotsList : Gtk.Box {
                        var snapshot = runner.try_create_snapshot (false);
 
                        if (snapshot != null) {
-                               var snapshot_row = new SnapshotRow (snapshot);
+                               var snapshot_row = new SnapshotRow (runner, snapshot);
 
                                list_box.insert (snapshot_row, 1);
                                select_snapshot_row (snapshot_row);
@@ -65,10 +56,13 @@ private class Games.SnapshotsList : Gtk.Box {
        }
 
        private void populate_list_box () {
-               var list_rows = list_box.get_children ();
-               foreach (var row in list_rows) {
-                       if (row != new_snapshot_row)
-                               list_box.remove (row);
+               while (true) {
+                       var row = list_box.get_row_at_index (0);
+
+                       if (row == null)
+                               break;
+
+                       list_box.remove (row);
                }
 
                if (runner == null)
@@ -76,11 +70,11 @@ private class Games.SnapshotsList : Gtk.Box {
 
                var snapshots = _runner.get_snapshots ();
                foreach (var snapshot in snapshots) {
-                       var list_row = new SnapshotRow (snapshot);
+                       var list_row = new SnapshotRow (runner, snapshot);
 
                        // Reveal it early so that it doesn't animate
                        list_row.reveal ();
-                       list_box.add (list_row);
+                       list_box.append (list_row);
                }
        }
 
@@ -95,7 +89,7 @@ private class Games.SnapshotsList : Gtk.Box {
                var selected_row = list_box.get_selected_row ();
                var selected_row_index = selected_row.get_index ();
                var snapshot_row = selected_row as SnapshotRow;
-               var snapshot = snapshot_row.snapshot;
+               var snapshot = snapshot_row.game_snapshot;
 
                ensure_row_is_visible (selected_row);
                runner.delete_snapshot (snapshot);
@@ -130,13 +124,11 @@ private class Games.SnapshotsList : Gtk.Box {
 
        [GtkCallback]
        private void on_rename_clicked () {
-               var selected_row = list_box.get_selected_row ();
+               var selected_row = list_box.get_selected_row () as SnapshotRow;
 
                ensure_row_is_visible (selected_row);
 
-               rename_entry.text = selected_snapshot.name;
-               rename_popover.relative_to = selected_row;
-               rename_popover.popup ();
+               selected_row.start_rename ();
        }
 
        // Adapted from gtklistbox.c, ensure_row_visible()
@@ -152,75 +144,15 @@ private class Games.SnapshotsList : Gtk.Box {
                scrolled_window.kinetic_scrolling = true;
        }
 
-       [GtkCallback]
-       private void on_rename_entry_activated () {
-               if (check_rename_is_valid ())
-                       apply_rename ();
-       }
-
-       [GtkCallback]
-       private void on_rename_entry_text_changed () {
-               check_rename_is_valid ();
-       }
-
-       private bool check_rename_is_valid () {
-               var entry_text = rename_entry.text.strip ();
-
-               if (entry_text == _("Autosave") || entry_text == "") {
-                       rename_entry.get_style_context ().add_class ("error");
-                       rename_popover_btn.sensitive = false;
-                       /* Translators: This message is shown to the user if he tried to rename
-                        * his snapshot either with an empty string, or with the name of the
-                        * autosave */
-                       rename_error_label.label = _("Invalid name");
-
-                       return false;
-               }
-
-               foreach (var list_child in list_box.get_children ()) {
-                       if (!(list_child is SnapshotRow))
-                               continue;
-
-                       var snapshot_row = list_child as SnapshotRow;
-                       var snapshot = snapshot_row.snapshot;
-
-                       if (snapshot.is_automatic)
-                               continue;
-
-                       if (snapshot.name == entry_text) {
-                               rename_entry.get_style_context ().add_class ("error");
-                               rename_popover_btn.sensitive = false;
-                               rename_error_label.label = _("A snapshot with this name already exists");
-
-                               return false;
-                       }
-               }
-
-               // All checks passed, rename operation is valid
-               rename_entry.get_style_context ().remove_class ("error");
-               rename_popover_btn.sensitive = true;
-               rename_error_label.label = "";
-
-               return true;
-       }
-
-       [GtkCallback]
-       private void apply_rename () {
-               var selected_row = list_box.get_selected_row ();
-               var snapshot_row = selected_row as SnapshotRow;
-
-               snapshot_row.set_name (rename_entry.text.strip ());
-               rename_popover.popdown ();
-       }
-
        private void update_header (Gtk.ListBoxRow row, Gtk.ListBoxRow? before) {
                if (before != null && row.get_header () == null) {
                        var separator = new Gtk.Separator (Gtk.Orientation.HORIZONTAL);
                        row.set_header (separator);
                }
        }
-
+/* FIXME GTK4
        private SimpleAction lookup_action (string name) {
+               // FIXME GTK4
                var group = get_action_group ("display") as ActionMap;
                assert (group != null);
 
@@ -229,14 +161,14 @@ private class Games.SnapshotsList : Gtk.Box {
 
                return action as SimpleAction;
        }
-
+*/
        private void select_snapshot_row (Gtk.ListBoxRow? row) {
                list_box.select_row (row);
 
                if (row == null) {
                        runner.preview_current_state ();
                        selected_snapshot = null;
-                       lookup_action ("load-snapshot").set_enabled (false);
+// FIXME GTK4                  lookup_action ("load-snapshot").set_enabled (false);
                }
                else {
                        row.grab_focus ();
@@ -245,16 +177,16 @@ private class Games.SnapshotsList : Gtk.Box {
                                return;
 
                        var snapshot_row = row as SnapshotRow;
-                       var snapshot = snapshot_row.snapshot;
+                       var snapshot = snapshot_row.game_snapshot;
 
                        if (snapshot == selected_snapshot) {
-                               lookup_action ("load-snapshot").activate (null);
+// FIXME GTK4                          lookup_action ("load-snapshot").activate (null);
                                return;
                        }
 
                        runner.preview_snapshot (snapshot);
                        selected_snapshot = snapshot;
-                       lookup_action ("load-snapshot").set_enabled (true);
+// FIXME GTK4                  lookup_action ("load-snapshot").set_enabled (true);
                }
 
                delete_btn.sensitive = (selected_snapshot != null);
diff --git a/src/ui/ui-view.vala b/src/ui/ui-view.vala
index 2f553848..b0fe120b 100644
--- a/src/ui/ui-view.vala
+++ b/src/ui/ui-view.vala
@@ -3,13 +3,10 @@
 private interface Games.UiView : Gtk.Widget {
        public abstract bool is_view_active { get; set; }
 
-       public abstract bool on_button_pressed (Gdk.EventButton event);
-
-       public abstract bool on_key_pressed (Gdk.EventKey event);
+       public abstract bool on_button_pressed (uint button);
+       public abstract bool on_key_pressed (uint keyval, uint keycode, Gdk.ModifierType state);
 
        public abstract bool gamepad_button_press_event (Manette.Event event);
-
        public abstract bool gamepad_button_release_event (Manette.Event event);
-
        public abstract bool gamepad_absolute_axis_event (Manette.Event event);
 }
diff --git a/src/ui/undo-notification.ui b/src/ui/undo-notification.ui
index 248242e9..66a42a52 100644
--- a/src/ui/undo-notification.ui
+++ b/src/ui/undo-notification.ui
@@ -1,21 +1,17 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesUndoNotification" parent="GtkEventBox">
-    <property name="visible">True</property>
+  <template class="GamesUndoNotification" parent="AdwBin">
     <property name="valign">start</property>"
     <property name="halign">center</property>
-    <child>
+    <property name="child">
       <object class="GtkRevealer">
-        <property name="visible">True</property>
         <property name="reveal-child" bind-source="GamesUndoNotification" bind-property="reveal"/>
-        <child>
+        <property name="child">
           <object class="GtkBox">
-            <property name="visible">True</property>
             <property name="spacing">6</property>
             <child>
               <object class="GtkLabel" id="notification_label">
-                <property name="visible">True</property>
                 <property name="ellipsize">end</property>
                 <property name="xalign">0</property>
                 <property name="label" bind-source="GamesUndoNotification" bind-property="label"/>
@@ -24,7 +20,6 @@
             </child>
             <child>
               <object class="GtkButton">
-                <property name="visible">True</property>
                 <property name="label" translatable="yes">_Undo</property>
                 <property name="use-underline">True</property>
                 <signal name="clicked" handler="on_undo_button_clicked"/>
@@ -32,14 +27,8 @@
             </child>
             <child>
               <object class="GtkButton">
-                <property name="visible">True</property>
+                <property name="icon-name">window-close-symbolic</property>
                 <signal name="clicked" handler="on_notification_closed"/>
-                <child>
-                  <object class="GtkImage">
-                    <property name="visible">True</property>
-                    <property name="icon-name">window-close-symbolic</property>
-                  </object>
-                </child>
                 <style>
                   <class name="flat"/>
                 </style>
@@ -49,8 +38,8 @@
               <class name="app-notification"/>
             </style>
           </object>
-        </child>
+        </property>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
diff --git a/src/ui/undo-notification.vala b/src/ui/undo-notification.vala
index a7d6a5eb..248b5cfc 100644
--- a/src/ui/undo-notification.vala
+++ b/src/ui/undo-notification.vala
@@ -1,27 +1,26 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/undo-notification.ui")]
-private class Games.UndoNotification : Gtk.EventBox {
+private class Games.UndoNotification : Adw.Bin {
        private const uint NOTIFICATION_TIMEOUT_SEC = 3;
 
        public signal void undo ();
        public signal void closed ();
 
        private uint timeout_id = 0;
-       private Gtk.EventControllerMotion motion_controller;
 
        public bool reveal { get; set; }
        public string label { get; set; }
 
        construct {
-               motion_controller = new Gtk.EventControllerMotion (this);
-               motion_controller.propagation_phase = Gtk.PropagationPhase.TARGET;
+               var motion_controller = new Gtk.EventControllerMotion ();
                motion_controller.enter.connect (() => {
                        remove_current_timeout_if_exists ();
                });
                motion_controller.leave.connect (() => {
                        set_new_timeout ();
                });
+               add_controller (motion_controller);
        }
 
        public void show_notification () {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]