diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml
index a71adcee26..ca8558dc29 100644
--- a/demos/gtk-demo/demo.gresource.xml
+++ b/demos/gtk-demo/demo.gresource.xml
@@ -37,6 +37,9 @@
theming.ui
+
+ themes.ui
+
css_pixbufs.css
cssview.css
@@ -212,6 +215,7 @@
textview.c
textscroll.c
theming_style_classes.c
+ themes.c
transparent.c
tree_store.c
textmask.c
diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build
index aba44c976c..87e5ae4f85 100644
--- a/demos/gtk-demo/meson.build
+++ b/demos/gtk-demo/meson.build
@@ -70,6 +70,7 @@ demos = files([
'textmask.c',
'textview.c',
'textscroll.c',
+ 'themes.c',
'theming_style_classes.c',
'transparent.c',
'tree_store.c',
diff --git a/demos/gtk-demo/themes.c b/demos/gtk-demo/themes.c
new file mode 100644
index 0000000000..bfffc3a69a
--- /dev/null
+++ b/demos/gtk-demo/themes.c
@@ -0,0 +1,203 @@
+/* Benchmark/Themes
+ *
+ * This demo switches themes like a maniac, like some of you.
+ */
+
+#include
+
+static guint tick_cb;
+
+static gint64
+guess_refresh_interval (GdkFrameClock *frame_clock)
+{
+ gint64 interval;
+ gint64 i;
+
+ interval = G_MAXINT64;
+
+ for (i = gdk_frame_clock_get_history_start (frame_clock);
+ i < gdk_frame_clock_get_frame_counter (frame_clock);
+ i++)
+ {
+ GdkFrameTimings *t, *before;
+ gint64 ts, before_ts;
+
+ t = gdk_frame_clock_get_timings (frame_clock, i);
+ before = gdk_frame_clock_get_timings (frame_clock, i - 1);
+ if (t == NULL || before == NULL)
+ continue;
+
+ ts = gdk_frame_timings_get_frame_time (t);
+ before_ts = gdk_frame_timings_get_frame_time (before);
+ if (ts == 0 || before_ts == 0)
+ continue;
+
+ interval = MIN (interval, ts - before_ts);
+ }
+
+ if (interval == G_MAXINT64)
+ return 0;
+
+ return interval;
+}
+
+static double
+frame_clock_get_fps (GdkFrameClock *frame_clock)
+{
+ GdkFrameTimings *start, *end;
+ gint64 start_counter, end_counter;
+ gint64 start_timestamp, end_timestamp;
+ gint64 interval;
+
+ start_counter = gdk_frame_clock_get_history_start (frame_clock);
+ end_counter = gdk_frame_clock_get_frame_counter (frame_clock);
+ start = gdk_frame_clock_get_timings (frame_clock, start_counter);
+ for (end = gdk_frame_clock_get_timings (frame_clock, end_counter);
+ end_counter > start_counter && end != NULL && !gdk_frame_timings_get_complete (end);
+ end = gdk_frame_clock_get_timings (frame_clock, end_counter))
+ end_counter--;
+ if (end_counter - start_counter < 4)
+ return 0.0;
+
+ start_timestamp = gdk_frame_timings_get_presentation_time (start);
+ end_timestamp = gdk_frame_timings_get_presentation_time (end);
+ if (start_timestamp == 0 || end_timestamp == 0)
+ {
+ start_timestamp = gdk_frame_timings_get_frame_time (start);
+ end_timestamp = gdk_frame_timings_get_frame_time (end);
+ }
+ interval = gdk_frame_timings_get_refresh_interval (end);
+ if (interval == 0)
+ {
+ interval = guess_refresh_interval (frame_clock);
+ if (interval == 0)
+ return 0.0;
+ }
+
+ return ((double) end_counter - start_counter) * G_USEC_PER_SEC / (end_timestamp - start_timestamp);
+}
+
+typedef struct {
+ const char *name;
+ gboolean dark;
+} Theme;
+
+static Theme themes[] = {
+ { "Adwaita", FALSE },
+ { "Adwaita", TRUE },
+ { "HighContrast", FALSE },
+ { "HighContrastInverse", FALSE }
+};
+
+static int theme;
+
+static gboolean
+change_theme (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ gpointer data)
+{
+ GtkBuilder *builder = data;
+ GtkWidget *header;
+ GtkWidget *label;
+ Theme next = themes[theme++ % G_N_ELEMENTS (themes)];
+ char *name;
+
+ g_object_set (gtk_settings_get_default (),
+ "gtk-theme-name", next.name,
+ "gtk-application-prefer-dark-theme", next.dark,
+ NULL);
+
+ header = GTK_WIDGET (gtk_builder_get_object (builder, "header"));
+ name = g_strconcat (next.name, next.dark ? " (dark)" : NULL, NULL);
+ gtk_header_bar_set_title (GTK_HEADER_BAR (header), name);
+ g_free (name);
+
+ label = GTK_WIDGET (gtk_builder_get_object (builder, "fps"));
+ if (frame_clock)
+ {
+ char *fps;
+
+ fps = g_strdup_printf ("%.2f fps", frame_clock_get_fps (frame_clock));
+ gtk_label_set_label (GTK_LABEL (label), fps);
+ g_free (fps);
+ }
+ else
+ gtk_label_set_label (GTK_LABEL (label), "");
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+clicked (GtkGestureClick *gesture,
+ int n_press,
+ double x,
+ double y,
+ gpointer data)
+{
+ GtkWidget *window;
+ const GdkEvent *event;
+ GdkModifierType state;
+
+ window = gtk_widget_get_ancestor (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)), GTK_TYPE_WINDOW);
+
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), NULL);
+ gdk_event_get_state (event, &state);
+
+ if (state & GDK_CONTROL_MASK)
+ {
+ if (tick_cb)
+ {
+ gtk_widget_remove_tick_callback (window, tick_cb);
+ tick_cb = 0;
+ }
+
+ change_theme (window, NULL, data);
+ }
+ else
+ {
+ if (tick_cb)
+ {
+ gtk_widget_remove_tick_callback (window, tick_cb);
+ tick_cb = 0;
+ }
+ else
+ {
+ tick_cb = gtk_widget_add_tick_callback (window, change_theme, data, NULL);
+ }
+ }
+}
+
+GtkWidget *
+do_themes (GtkWidget *do_widget)
+{
+ static GtkWidget *window = NULL;
+
+ if (!window)
+ {
+ GtkBuilder *builder;
+ GtkWidget *header;
+ GtkGesture *gesture;
+
+ builder = gtk_builder_new_from_resource ("/themes/themes.ui");
+ window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
+ g_signal_connect (window, "destroy",
+ G_CALLBACK (gtk_widget_destroyed), &window);
+ gtk_window_set_display (GTK_WINDOW (window),
+ gtk_widget_get_display (do_widget));
+ g_signal_connect (window, "destroy",
+ G_CALLBACK (gtk_widget_destroyed), &window);
+ header = GTK_WIDGET (gtk_builder_get_object (builder, "header"));
+ gesture = gtk_gesture_click_new ();
+ g_signal_connect (gesture, "pressed", G_CALLBACK (clicked), builder);
+ gtk_widget_add_controller (header, GTK_EVENT_CONTROLLER (gesture));
+
+ gtk_widget_realize (window);
+ }
+
+ if (!gtk_widget_get_visible (window))
+ gtk_widget_show (window);
+ else
+ gtk_widget_destroy (window);
+
+ return window;
+}
diff --git a/demos/gtk-demo/themes.ui b/demos/gtk-demo/themes.ui
new file mode 100644
index 0000000000..db6f1ebf98
--- /dev/null
+++ b/demos/gtk-demo/themes.ui
@@ -0,0 +1,241 @@
+
+
+
+