diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 15b5a573f6ae6f07e8fde5e23a2594e3a317ff43..b17cbb0405f38ae06eeda12257478c415272b09b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -14,9 +14,9 @@ flatpak:
 
   script:
   - dnf install -y flatpak-builder
-  - flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
-  - flatpak install -y flathub org.gnome.Platform 3.38
-  - flatpak install -y flathub org.gnome.Sdk 3.38
+  - flatpak remote-add --if-not-exists gnome-nightly https://nightly.gnome.org/gnome-nightly.flatpakrepo
+  - flatpak install -y gnome-nightly org.gnome.Platform
+  - flatpak install -y gnome-nightly org.gnome.Sdk
   - flatpak-builder --stop-at=${FLATPAK_MODULE} app ${MANIFEST_PATH}
   - flatpak build app meson --prefix=/app ${MESON_ARGS} _build
   - flatpak build app ninja -C _build install
diff --git a/build-aux/uk.me.bcowan.Rugby.yml b/build-aux/uk.me.bcowan.Rugby.yml
index 943fcebebb86f8a7b9aacdd61ffdb022dac539c8..a24edb3e74808c0038aee095dc9cadc6155dcaf6 100644
--- a/build-aux/uk.me.bcowan.Rugby.yml
+++ b/build-aux/uk.me.bcowan.Rugby.yml
@@ -1,10 +1,10 @@
-# SPDX-FileCopyrightText: 2018-2020 Bruce Cowan <bruce@bcowan.me.uk>
+# SPDX-FileCopyrightText: 2018-2021 Bruce Cowan <bruce@bcowan.me.uk>
 #
 # SPDX-License-Identifier: CC0-1.0
 
 app-id: uk.me.bcowan.Rugby
 runtime: org.gnome.Platform
-runtime-version: "3.38"
+runtime-version: "master"
 sdk: org.gnome.Sdk
 command: rugby
 
diff --git a/data/interface.ui b/data/interface.ui
index 4f2d437e25139d35e450401abedbd0cf08ab12d9..0930ce96f2d18283a9914066ce7a9a56fdb544a5 100644
--- a/data/interface.ui
+++ b/data/interface.ui
@@ -1,125 +1,71 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.0 -->
 <!--
   SPDX-FileCopyrightText: 2012-2020 Bruce Cowan <bruce@bcowan.me.uk>
   SPDX-License-Identifier: GPL-3.0-or-later
 -->
 <interface>
-  <requires lib="gtk+" version="3.16"/>
+  <requires lib="gtk" version="4.0"/>
   <object class="GtkAdjustment" id="adjustment1">
     <property name="upper">200</property>
     <property name="step_increment">1</property>
     <property name="page_increment">10</property>
   </object>
-  <object class="GtkPopoverMenu" id="popovermenu1">
-    <property name="can_focus">False</property>
-    <child>
-      <object class="GtkGrid">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="margin_start">6</property>
-        <property name="margin_end">6</property>
-        <property name="margin_top">6</property>
-        <property name="margin_bottom">6</property>
-        <child>
-          <object class="GtkModelButton">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="action_name">app.about</property>
-            <property name="text" translatable="yes">_About Rugby</property>
-          </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">1</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkModelButton">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="action_name">app.prefs</property>
-            <property name="text" translatable="yes">_Preferences</property>
-          </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">0</property>
-          </packing>
-        </child>
-      </object>
-      <packing>
-        <property name="submenu">main</property>
-      </packing>
-    </child>
-  </object>
+  <menu id="menu">
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">_Preferences</attribute>
+        <attribute name="action">app.prefs</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">_About Rugby</attribute>
+        <attribute name="action">app.about</attribute>
+      </item>
+    </section>
+  </menu>
   <template class="RugbyAppWindow" parent="GtkApplicationWindow">
-    <property name="can_focus">False</property>
+    <property name="can_focus">0</property>
+    <property name="title" translatable="yes">Rugby</property>
     <child>
       <object class="GtkScrolledWindow">
         <property name="height_request">400</property>
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="shadow_type">in</property>
-        <child>
+        <property name="child">
           <object class="GtkViewport">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <child>
+            <property name="can_focus">0</property>
+            <property name="child">
               <object class="GtkListBox" id="listbox">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
+                <property name="can_focus">0</property>
                 <property name="selection_mode">none</property>
               </object>
-            </child>
+            </property>
           </object>
-        </child>
+        </property>
       </object>
     </child>
     <child type="titlebar">
       <object class="GtkHeaderBar">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="title" translatable="yes">Rugby</property>
-        <property name="show_close_button">True</property>
+        <property name="can_focus">0</property>
         <child>
           <object class="GtkLabel">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="can_focus">0</property>
             <property name="label" translatable="yes">Score:</property>
           </object>
         </child>
         <child>
           <object class="GtkSpinButton" id="scorespin">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
             <property name="text" translatable="yes">0</property>
             <property name="adjustment">adjustment1</property>
           </object>
-          <packing>
-            <property name="position">1</property>
-          </packing>
         </child>
-        <child>
-          <object class="GtkMenuButton">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="popover">popovermenu1</property>
-            <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="icon_name">open-menu</property>
-              </object>
-            </child>
+        <child type="end">
+          <object class="GtkMenuButton" id="menu_button">
+            <property name="direction">none</property>
+            <property name="menu_model">menu</property>
           </object>
-          <packing>
-            <property name="pack_type">end</property>
-            <property name="position">2</property>
-          </packing>
         </child>
       </object>
     </child>
   </template>
 </interface>
+
diff --git a/data/prefs.ui b/data/prefs.ui
index 1120fe5cb0ea545d333051e441b4be2c67d46290..65e4dfb512ed2b03dadf56f8319adfd14dc73716 100644
--- a/data/prefs.ui
+++ b/data/prefs.ui
@@ -1,12 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.0 -->
 <!--
   SPDX-FileCopyrightText: 2020 Bruce Cowan <bruce@bcowan.me.uk>
 
   SPDX-License-Identifier: GPL-3.0-or-later
 -->
 <interface>
-  <requires lib="gtk+" version="3.16"/>
+  <requires lib="gtk" version="4.0"/>
   <object class="GtkAdjustment" id="adjustment1">
     <property name="lower">1</property>
     <property name="upper">7</property>
@@ -29,13 +28,11 @@
     <property name="page_increment">1</property>
   </object>
   <template class="RugbyPrefWindow" parent="GtkWindow">
-    <property name="can_focus">False</property>
+    <property name="can_focus">0</property>
     <property name="title">Preferences</property>
-    <property name="type_hint">dialog</property>
-    <child>
+    <property name="child">
       <object class="GtkGrid">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
+        <property name="can_focus">0</property>
         <property name="margin_start">18</property>
         <property name="margin_end">18</property>
         <property name="margin_top">18</property>
@@ -44,81 +41,72 @@
         <property name="column_spacing">12</property>
         <child>
           <object class="GtkLabel">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="can_focus">0</property>
             <property name="label" translatable="yes">Points for a _converted try</property>
-            <property name="use_underline">True</property>
+            <property name="use_underline">1</property>
             <property name="mnemonic_widget">try_spin</property>
             <property name="xalign">1</property>
+            <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="GtkLabel">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="can_focus">0</property>
             <property name="label" translatable="yes">Points for an _unconverted try</property>
-            <property name="use_underline">True</property>
+            <property name="use_underline">1</property>
             <property name="mnemonic_widget">utry_spin</property>
             <property name="xalign">1</property>
+            <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="GtkLabel">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="can_focus">0</property>
             <property name="label" translatable="yes">Points for a _kick</property>
-            <property name="use_underline">True</property>
+            <property name="use_underline">1</property>
             <property name="mnemonic_widget">kick_spin</property>
             <property name="xalign">1</property>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">2</property>
+            </layout>
           </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">2</property>
-          </packing>
         </child>
         <child>
           <object class="GtkSpinButton" id="try_spin">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
             <property name="adjustment">adjustment1</property>
+            <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="GtkSpinButton" id="utry_spin">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
             <property name="adjustment">adjustment2</property>
+            <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>
         <child>
           <object class="GtkSpinButton" id="kick_spin">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
             <property name="adjustment">adjustment3</property>
+            <layout>
+              <property name="column">1</property>
+              <property name="row">2</property>
+            </layout>
           </object>
-          <packing>
-            <property name="left_attach">1</property>
-            <property name="top_attach">2</property>
-          </packing>
         </child>
       </object>
-    </child>
+    </property>
     <child type="titlebar">
       <placeholder/>
     </child>
diff --git a/meson.build b/meson.build
index 08fd6fd439d7ad9c54a417eaf0ff36509e6134e8..806c45aac32454d767599fec64f3f6bc635ad34f 100644
--- a/meson.build
+++ b/meson.build
@@ -1,4 +1,4 @@
-# SPDX-FileCopyrightText: 2016-2020 Bruce Cowan <bruce@bcowan.me.uk>
+# SPDX-FileCopyrightText: 2016-2021 Bruce Cowan <bruce@bcowan.me.uk>
 #
 # SPDX-License-Identifier: CC0-1.0
 
@@ -12,7 +12,7 @@ datadir = get_option('datadir')
 gnome = import('gnome')
 
 gio_dep = dependency('gio-2.0', version: '>= 2.44')
-gtk_dep = dependency('gtk+-3.0', version: '>= 3.16')
+gtk_dep = dependency('gtk4', version: '>= 4.0')
 
 conf = configuration_data()
 conf.set_quoted('VERSION','@VCS_TAG@')
@@ -30,8 +30,9 @@ config_h = declare_dependency(
 cflags = []
 cflags += ['-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_44',
            '-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_44']
-cflags += ['-DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_16',
-           '-DGDK_VERSION_MAX_ALLOWED=GDK_VERSION_3_16']
+cflags += ['-DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_4_0',
+           '-DGDK_VERSION_MAX_ALLOWED=GDK_VERSION_4_0']
+
 cflags += '-Wno-overlength-strings'
 
 cc = meson.get_compiler('c')
diff --git a/src/main.c b/src/main.c
index 566bdb652a71f3a4810f0d16a6e19a0e42befd67..f53e325110c8145db560d93707b887ee69b33317 100644
--- a/src/main.c
+++ b/src/main.c
@@ -72,9 +72,9 @@ on_startup (GApplication          *app,
     g_autoptr (GtkCssProvider) provider = gtk_css_provider_new ();
     gtk_css_provider_load_from_resource (provider, "/uk/me/bcowan/rugby/rugby.css");
 
-    gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
-                                               GTK_STYLE_PROVIDER (provider),
-                                               GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+    gtk_style_context_add_provider_for_display (gdk_display_get_default (),
+                                                GTK_STYLE_PROVIDER (provider),
+                                                GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
 }
 
 int
diff --git a/src/rugby-possibility-widget.c b/src/rugby-possibility-widget.c
index 8ab7a978652de5f2bd7dd47d12265f4500a7f80e..57f0203a1f18ec059df405b43d973ca853a7e708 100644
--- a/src/rugby-possibility-widget.c
+++ b/src/rugby-possibility-widget.c
@@ -1,5 +1,6 @@
 /*
- * SPDX-FileCopyrightText: 2018, 2020 Bruce Cowan <bruce@bcowan.me.uk>
+ * SPDX-FileCopyrightText: 2018-2021 Bruce Cowan <bruce@bcowan.me.uk>
+ *
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
@@ -10,6 +11,7 @@ struct _RugbyPossibilityWidget
 {
     GtkDrawingArea parent_instance;
 
+    GSettings *settings;
     RugbyPossibility *possibility;
 };
 
@@ -31,6 +33,7 @@ rugby_possibility_widget_dispose (GObject *object)
 {
     RugbyPossibilityWidget *self = RUGBY_POSSIBILITY_WIDGET (object);
 
+    g_clear_object (&self->settings);
     g_clear_object (&self->possibility);
 
     G_OBJECT_CLASS (rugby_possibility_widget_parent_class)->dispose (object);
@@ -73,14 +76,20 @@ rugby_possibility_widget_set_property (GObject      *object,
 }
 
 static void
-rugby_possibility_widget_get_preferred_height (G_GNUC_UNUSED GtkWidget *widget,
-                                               int                     *minimum_height,
-                                               int                     *natural_height)
+rugby_possibility_widget_measure (G_GNUC_UNUSED GtkWidget *widget,
+                                  GtkOrientation           orientation,
+                                  G_GNUC_UNUSED int        for_size,
+                                  int                     *minimum,
+                                  int                     *natural,
+                                  G_GNUC_UNUSED int       *minimum_baseline,
+                                  G_GNUC_UNUSED int       *natural_baseline)
+
 {
-    if (minimum_height)
-        *minimum_height = FIXED_HEIGHT;
-    if (natural_height)
-        *natural_height = FIXED_HEIGHT;
+    if (orientation == GTK_ORIENTATION_VERTICAL)
+    {
+        *minimum = FIXED_HEIGHT;
+        *natural = FIXED_HEIGHT;
+    }
 }
 
 static void
@@ -98,21 +107,23 @@ render_bar (cairo_t         *cr,
     gtk_style_context_restore (context);
 }
 
-static gboolean
-rugby_possibility_widget_draw (GtkWidget *widget,
-                               cairo_t   *cr)
+static void
+draw_func (GtkDrawingArea        *drawing_area,
+           cairo_t               *cr,
+           int                    width,
+           int                    height,
+           G_GNUC_UNUSED gpointer user_data)
 {
-    RugbyPossibilityWidget *self = RUGBY_POSSIBILITY_WIDGET (widget);
-
-    double width = gtk_widget_get_allocated_width (widget);
-    double height = gtk_widget_get_allocated_height (widget);
-    double x = 0.0, y = 0.0;
+    RugbyPossibilityWidget *self = RUGBY_POSSIBILITY_WIDGET (drawing_area);
 
-    GtkStyleContext *context = gtk_widget_get_style_context (widget);
-    gtk_style_context_save (context);
-    gtk_style_context_add_class (context, "possibility");
+    GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (drawing_area));
+    gtk_widget_add_css_class (GTK_WIDGET (drawing_area), "possibility");
 
-    gtk_render_background (context, cr, x, y, width, height);
+    double x = 0.0;
+    gtk_render_background (context, cr, x, 0.0, width, height);
+    int try_points = g_settings_get_int (self->settings, "try-points");
+    int utry_points = g_settings_get_int (self->settings, "utry-points");
+    int kick_points = g_settings_get_int (self->settings, "kick-points");
 
     int tries, utries, kicks;
 
@@ -121,37 +132,33 @@ rugby_possibility_widget_draw (GtkWidget *widget,
                   "utries", &utries,
                   "kicks", &kicks,
                   NULL);
-    int score = tries * 7 + utries * 5 + kicks * 3;
+    int score = tries * try_points + utries * utry_points + kicks * kick_points;
 
-    gtk_style_context_add_class (context, "score-block");
+    gtk_widget_add_css_class (GTK_WIDGET (drawing_area), "score-block");
 
     // Tries
-    double w = width / (score / 7.0);
+    double w = width / (score / (double) try_points);
     for (int i = 0; i < tries; i++)
     {
-        render_bar (cr, context, x, y, w, height, "try");
+        render_bar (cr, context, x, 0.0, w, height, "try");
         x += w;
     }
 
     // Unconverted tries
-    w = width / (score / 5.0);
+    w = width / (score / (double) utry_points);
     for (int i = 0; i < utries; i++)
     {
-        render_bar (cr, context, x, y, w, height, "utry");
+        render_bar (cr, context, x, 0.0, w, height, "utry");
         x += w;
     }
 
     // Unconverted kicks
-    w = width / (score / 3.0);
+    w = width / (score / (double) kick_points);
     for (int i = 0; i < kicks; i++)
     {
-        render_bar (cr, context, x, y, w, height, "kick");
+        render_bar (cr, context, x, 0.0, w, height, "kick");
         x += w;
     }
-
-    gtk_style_context_restore (context);
-
-    return TRUE;
 }
 
 static void
@@ -195,8 +202,7 @@ rugby_possibility_widget_class_init (RugbyPossibilityWidgetClass *klass)
     object_class->get_property = rugby_possibility_widget_get_property;
     object_class->set_property = rugby_possibility_widget_set_property;
 
-    widget_class->draw = rugby_possibility_widget_draw;
-    widget_class->get_preferred_height = rugby_possibility_widget_get_preferred_height;
+    widget_class->measure = rugby_possibility_widget_measure;
 
     properties[PROP_POSSIBILITY] = g_param_spec_object ("possibility",
                                                         "Possibility",
@@ -210,8 +216,12 @@ rugby_possibility_widget_class_init (RugbyPossibilityWidgetClass *klass)
 }
 
 static void
-rugby_possibility_widget_init (G_GNUC_UNUSED RugbyPossibilityWidget *self)
+rugby_possibility_widget_init (RugbyPossibilityWidget *self)
 {
+    gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (self),
+                                    draw_func, NULL, NULL);
+
+    self->settings = g_settings_new ("uk.me.bcowan.Rugby");
 }
 
 GtkWidget *