[gtk/pango2] fontexplorer: Some refactoring



commit 2a3cd0364866560ea99292004a8d5f3102bc3747
Author: Matthias Clasen <mclasen redhat com>
Date:   Sun Jul 10 09:08:59 2022 -0400

    fontexplorer: Some refactoring
    
    Break the FontView class into individual views.

 demos/font-explorer/fontexplorer.css           |  18 +-
 demos/font-explorer/fontexplorer.gresource.xml |  12 +-
 demos/font-explorer/fontexplorerwin.c          | 188 ++++--
 demos/font-explorer/fontexplorerwin.h          |   2 +-
 demos/font-explorer/fontexplorerwin.ui         | 146 ++++-
 demos/font-explorer/fontview.c                 | 774 -------------------------
 demos/font-explorer/fontview.h                 |  17 -
 demos/font-explorer/fontview.ui                | 145 -----
 demos/font-explorer/glyphsview.c               | 238 ++++++++
 demos/font-explorer/glyphsview.h               |  14 +
 demos/font-explorer/glyphsview.ui              |  26 +
 demos/font-explorer/infoview.c                 | 416 +++++++++++++
 demos/font-explorer/infoview.h                 |  14 +
 demos/font-explorer/infoview.ui                |  23 +
 demos/font-explorer/meson.build                |  18 +-
 demos/font-explorer/plainview.c                | 342 +++++++++++
 demos/font-explorer/plainview.h                |  14 +
 demos/font-explorer/plainview.ui               |  32 +
 demos/font-explorer/sampleeditor.c             | 161 +++++
 demos/font-explorer/sampleeditor.h             |  14 +
 demos/font-explorer/sampleeditor.ui            |  21 +
 demos/font-explorer/waterfallview.c            | 362 ++++++++++++
 demos/font-explorer/waterfallview.h            |  14 +
 demos/font-explorer/waterfallview.ui           |  31 +
 24 files changed, 2038 insertions(+), 1004 deletions(-)
---
diff --git a/demos/font-explorer/fontexplorer.css b/demos/font-explorer/fontexplorer.css
index fa8da5f49c..9e6ae9d9f8 100644
--- a/demos/font-explorer/fontexplorer.css
+++ b/demos/font-explorer/fontexplorer.css
@@ -14,7 +14,23 @@ fontcolors > grid {
 samplechooser {
   border-spacing: 10px;
 }
-fontview {
+plainview {
+  padding: 10px;
+  border-spacing: 10px;
+}
+waterfallview {
+  padding: 10px;
+  border-spacing: 10px;
+}
+infoview {
+  padding: 10px;
+  border-spacing: 10px;
+}
+glyphsview {
+  padding: 10px;
+  border-spacing: 10px;
+}
+sampleeditor {
   padding: 10px;
   border-spacing: 10px;
 }
diff --git a/demos/font-explorer/fontexplorer.gresource.xml b/demos/font-explorer/fontexplorer.gresource.xml
index 8715abd461..052a0475e2 100644
--- a/demos/font-explorer/fontexplorer.gresource.xml
+++ b/demos/font-explorer/fontexplorer.gresource.xml
@@ -1,14 +1,18 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <gresources>
   <gresource prefix="/org/gtk/fontexplorer">
-    <file preprocess="xml-stripblanks">fontexplorerwin.ui</file>
-    <file preprocess="xml-stripblanks">fontview.ui</file>
-    <file preprocess="xml-stripblanks">fontcontrols.ui</file>
-    <file preprocess="xml-stripblanks">samplechooser.ui</file>
     <file preprocess="xml-stripblanks">fontcolors.ui</file>
+    <file preprocess="xml-stripblanks">fontcontrols.ui</file>
+    <file preprocess="xml-stripblanks">fontexplorerwin.ui</file>
     <file preprocess="xml-stripblanks">fontfeatures.ui</file>
     <file preprocess="xml-stripblanks">fontvariations.ui</file>
+    <file preprocess="xml-stripblanks">glyphsview.ui</file>
+    <file preprocess="xml-stripblanks">infoview.ui</file>
+    <file preprocess="xml-stripblanks">plainview.ui</file>
     <file preprocess="xml-stripblanks">rangeedit.ui</file>
+    <file preprocess="xml-stripblanks">samplechooser.ui</file>
+    <file preprocess="xml-stripblanks">sampleeditor.ui</file>
+    <file preprocess="xml-stripblanks">waterfallview.ui</file>
     <file>fontexplorer.css</file>
   </gresource>
 </gresources>
diff --git a/demos/font-explorer/fontexplorerwin.c b/demos/font-explorer/fontexplorerwin.c
index a0d8eecebb..e6432e1b33 100644
--- a/demos/font-explorer/fontexplorerwin.c
+++ b/demos/font-explorer/fontexplorerwin.c
@@ -1,11 +1,16 @@
-#include "fontexplorerapp.h"
 #include "fontexplorerwin.h"
-#include "fontview.h"
-#include "fontcontrols.h"
-#include "samplechooser.h"
+
 #include "fontcolors.h"
+#include "fontcontrols.h"
+#include "fontexplorerapp.h"
 #include "fontfeatures.h"
 #include "fontvariations.h"
+#include "glyphsview.h"
+#include "infoview.h"
+#include "plainview.h"
+#include "samplechooser.h"
+#include "sampleeditor.h"
+#include "waterfallview.h"
 
 #include <gtk/gtk.h>
 #include <string.h>
@@ -15,12 +20,19 @@ struct _FontExplorerWindow
 {
   GtkApplicationWindow parent;
 
+  Pango2FontMap *font_map;
+
   GtkFontButton *fontbutton;
   FontControls *controls;
   FontFeatures *features;
   FontVariations *variations;
   FontColors *colors;
-  FontView *view;
+  GtkStack *stack;
+  GtkToggleButton *plain_toggle;
+  GtkToggleButton *waterfall_toggle;
+  GtkToggleButton *glyphs_toggle;
+  GtkToggleButton *info_toggle;
+  GtkToggleButton *edit_toggle;
 };
 
 struct _FontExplorerWindowClass
@@ -28,57 +40,120 @@ struct _FontExplorerWindowClass
   GtkApplicationWindowClass parent_class;
 };
 
+enum {
+  PROP_FONT_MAP = 1,
+  NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
 G_DEFINE_TYPE(FontExplorerWindow, font_explorer_window, GTK_TYPE_APPLICATION_WINDOW);
 
 static void
 reset (GSimpleAction      *action,
        GVariant           *parameter,
-       FontExplorerWindow *win)
+       FontExplorerWindow *self)
 {
-  g_action_activate (font_controls_get_reset_action (win->controls), NULL);
-  g_action_activate (font_features_get_reset_action (win->features), NULL);
-  g_action_activate (font_variations_get_reset_action (win->variations), NULL);
-  g_action_activate (font_colors_get_reset_action (win->colors), NULL);
+  g_action_activate (font_controls_get_reset_action (self->controls), NULL);
+  g_action_activate (font_features_get_reset_action (self->features), NULL);
+  g_action_activate (font_variations_get_reset_action (self->variations), NULL);
+  g_action_activate (font_colors_get_reset_action (self->colors), NULL);
 }
 
 static void
 update_reset (GSimpleAction      *action,
               GParamSpec         *pspec,
-              FontExplorerWindow *win)
+              FontExplorerWindow *self)
 {
   gboolean enabled;
   GAction *reset_action;
 
-  enabled = g_action_get_enabled (font_controls_get_reset_action (win->controls)) ||
-            g_action_get_enabled (font_features_get_reset_action (win->features)) ||
-            g_action_get_enabled (font_variations_get_reset_action (win->variations)) ||
-            g_action_get_enabled (font_colors_get_reset_action (win->colors));
+  enabled = g_action_get_enabled (font_controls_get_reset_action (self->controls)) ||
+            g_action_get_enabled (font_features_get_reset_action (self->features)) ||
+            g_action_get_enabled (font_variations_get_reset_action (self->variations)) ||
+            g_action_get_enabled (font_colors_get_reset_action (self->colors));
 
-  reset_action = g_action_map_lookup_action (G_ACTION_MAP (win), "reset");
+  reset_action = g_action_map_lookup_action (G_ACTION_MAP (self), "reset");
 
   g_simple_action_set_enabled (G_SIMPLE_ACTION (reset_action), enabled);
 }
 
 static void
-font_explorer_window_init (FontExplorerWindow *win)
+font_explorer_window_init (FontExplorerWindow *self)
 {
   GSimpleAction *reset_action;
 
-  gtk_widget_init_template (GTK_WIDGET (win));
+  self->font_map = g_object_ref (pango2_font_map_get_default ());
+
+  gtk_widget_init_template (GTK_WIDGET (self));
 
   reset_action = g_simple_action_new ("reset", NULL);
-  g_signal_connect (reset_action, "activate", G_CALLBACK (reset), win);
-  g_signal_connect (font_controls_get_reset_action (win->controls),
-                    "notify::enabled", G_CALLBACK (update_reset), win);
-  g_signal_connect (font_variations_get_reset_action (win->variations),
-                    "notify::enabled", G_CALLBACK (update_reset), win);
-  g_signal_connect (font_colors_get_reset_action (win->colors),
-                    "notify::enabled", G_CALLBACK (update_reset), win);
-  g_signal_connect (font_features_get_reset_action (win->features),
-                    "notify::enabled", G_CALLBACK (update_reset), win);
-
-  g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (reset_action));
-  update_reset (NULL, NULL, win);
+  g_signal_connect (reset_action, "activate", G_CALLBACK (reset), self);
+  g_signal_connect (font_controls_get_reset_action (self->controls),
+                    "notify::enabled", G_CALLBACK (update_reset), self);
+  g_signal_connect (font_variations_get_reset_action (self->variations),
+                    "notify::enabled", G_CALLBACK (update_reset), self);
+  g_signal_connect (font_colors_get_reset_action (self->colors),
+                    "notify::enabled", G_CALLBACK (update_reset), self);
+  g_signal_connect (font_features_get_reset_action (self->features),
+                    "notify::enabled", G_CALLBACK (update_reset), self);
+
+  g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (reset_action));
+  update_reset (NULL, NULL, self);
+}
+
+static void
+update_view (GtkToggleButton    *button,
+             FontExplorerWindow *self)
+{
+  if (gtk_toggle_button_get_active (self->edit_toggle))
+    gtk_stack_set_visible_child_name (self->stack, "edit");
+  else if (gtk_toggle_button_get_active (self->plain_toggle))
+    gtk_stack_set_visible_child_name (self->stack, "plain");
+  else if (gtk_toggle_button_get_active (self->waterfall_toggle))
+    gtk_stack_set_visible_child_name (self->stack, "waterfall");
+  else if (gtk_toggle_button_get_active (self->glyphs_toggle))
+    gtk_stack_set_visible_child_name (self->stack, "glyphs");
+  else if (gtk_toggle_button_get_active (self->info_toggle))
+    gtk_stack_set_visible_child_name (self->stack, "info");
+}
+
+static void
+font_explorer_window_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  FontExplorerWindow *self = FONT_EXPLORER_WINDOW (object);
+
+  switch (prop_id)
+    {
+    case PROP_FONT_MAP:
+      g_set_object (&self->font_map, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+font_explorer_window_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  FontExplorerWindow *self = FONT_EXPLORER_WINDOW (object);
+
+  switch (prop_id)
+    {
+    case PROP_FONT_MAP:
+      g_value_set_object (value, self->font_map);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
 }
 
 static void
@@ -92,7 +167,9 @@ font_explorer_window_dispose (GObject *object)
 static void
 font_explorer_window_finalize (GObject *object)
 {
-//  FontExplorerWindow *win = FONT_EXPLORER_WINDOW (object);
+  FontExplorerWindow *self = FONT_EXPLORER_WINDOW (object);
+
+  g_clear_object (&self->font_map);
 
   G_OBJECT_CLASS (font_explorer_window_parent_class)->finalize (object);
 }
@@ -102,16 +179,29 @@ font_explorer_window_class_init (FontExplorerWindowClass *class)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (class);
 
-  g_type_ensure (FONT_VIEW_TYPE);
-  g_type_ensure (FONT_CONTROLS_TYPE);
-  g_type_ensure (SAMPLE_CHOOSER_TYPE);
-  g_type_ensure (FONT_VARIATIONS_TYPE);
   g_type_ensure (FONT_COLORS_TYPE);
+  g_type_ensure (FONT_CONTROLS_TYPE);
   g_type_ensure (FONT_FEATURES_TYPE);
+  g_type_ensure (FONT_VARIATIONS_TYPE);
+  g_type_ensure (GLYPHS_VIEW_TYPE);
+  g_type_ensure (INFO_VIEW_TYPE);
+  g_type_ensure (PLAIN_VIEW_TYPE);
+  g_type_ensure (SAMPLE_CHOOSER_TYPE);
+  g_type_ensure (SAMPLE_EDITOR_TYPE);
+  g_type_ensure (WATERFALL_VIEW_TYPE);
 
+  object_class->set_property = font_explorer_window_set_property;
+  object_class->get_property = font_explorer_window_get_property;
   object_class->dispose = font_explorer_window_dispose;
   object_class->finalize = font_explorer_window_finalize;
 
+  properties[PROP_FONT_MAP] =
+      g_param_spec_object ("font-map", "", "",
+                           PANGO2_TYPE_FONT_MAP,
+                           G_PARAM_READWRITE);
+
+  g_object_class_install_properties (G_OBJECT_CLASS (class), NUM_PROPERTIES, properties);
+
   gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
                                                "/org/gtk/fontexplorer/fontexplorerwin.ui");
 
@@ -120,8 +210,16 @@ font_explorer_window_class_init (FontExplorerWindowClass *class)
   gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), FontExplorerWindow, variations);
   gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), FontExplorerWindow, colors);
   gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), FontExplorerWindow, features);
-  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), FontExplorerWindow, view);
+  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), FontExplorerWindow, stack);
+  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), FontExplorerWindow, plain_toggle);
+  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), FontExplorerWindow, waterfall_toggle);
+  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), FontExplorerWindow, glyphs_toggle);
+  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), FontExplorerWindow, info_toggle);
+  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), FontExplorerWindow, edit_toggle);
 
+  gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), update_view);
+
+  gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (class), "fontexplorer");
 }
 
 FontExplorerWindow *
@@ -131,7 +229,7 @@ font_explorer_window_new (FontExplorerApp *app)
 }
 
 void
-font_explorer_window_load (FontExplorerWindow *win,
+font_explorer_window_load (FontExplorerWindow *self,
                            GFile              *file)
 {
   const char *path;
@@ -149,21 +247,23 @@ font_explorer_window_load (FontExplorerWindow *win,
   pango2_font_map_add_face (map, PANGO2_FONT_FACE (face));
   pango2_font_map_set_fallback (map, pango2_font_map_get_default ());
 
-  font_features_set_font_map (win->features, map);
-  font_variations_set_font_map (win->variations, map);
-  font_colors_set_font_map (win->colors, map);
-  font_view_set_font_map (win->view, map);
+  font_features_set_font_map (self->features, map);
+  font_variations_set_font_map (self->variations, map);
+  font_colors_set_font_map (self->colors, map);
+
+  g_set_object (&self->font_map, map);
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FONT_MAP]);
 
   g_object_unref (map);
 
-  gtk_font_chooser_set_font_desc (GTK_FONT_CHOOSER (win->fontbutton), desc);
+  gtk_font_chooser_set_font_desc (GTK_FONT_CHOOSER (self->fontbutton), desc);
 
-  gtk_widget_hide (GTK_WIDGET (win->fontbutton));
+  gtk_widget_hide (GTK_WIDGET (self->fontbutton));
 
   title = g_strdup_printf ("%s — %s",
                            pango2_font_description_get_family (desc),
                            path);
-  gtk_window_set_title (GTK_WINDOW (win), title);
+  gtk_window_set_title (GTK_WINDOW (self), title);
   g_free (title);
 
   pango2_font_description_free (desc);
diff --git a/demos/font-explorer/fontexplorerwin.h b/demos/font-explorer/fontexplorerwin.h
index 130b216710..6a86c11bb0 100644
--- a/demos/font-explorer/fontexplorerwin.h
+++ b/demos/font-explorer/fontexplorerwin.h
@@ -14,5 +14,5 @@ typedef struct _FontExplorerWindowClass    FontExplorerWindowClass;
 
 GType                   font_explorer_window_get_type     (void);
 FontExplorerWindow *    font_explorer_window_new          (FontExplorerApp    *app);
-void                    font_explorer_window_load         (FontExplorerWindow *win,
+void                    font_explorer_window_load         (FontExplorerWindow *self,
                                                            GFile              *file);
diff --git a/demos/font-explorer/fontexplorerwin.ui b/demos/font-explorer/fontexplorerwin.ui
index 68c6ad0b26..67b946634c 100644
--- a/demos/font-explorer/fontexplorerwin.ui
+++ b/demos/font-explorer/fontexplorerwin.ui
@@ -80,17 +80,141 @@
           </object>
         </child>
         <child>
-          <object class="FontView" id="view">
-            <property name="font-desc" bind-source="fontbutton" bind-flags="sync-create"/>
-            <property name="size" bind-source="controls" bind-flags="sync-create"/>
-            <property name="letterspacing" bind-source="controls" bind-flags="sync-create"/>
-            <property name="line-height" bind-source="controls" bind-flags="sync-create"/>
-            <property name="foreground" bind-source="controls" bind-flags="sync-create"/>
-            <property name="background" bind-source="controls" bind-flags="sync-create"/>
-            <property name="sample-text" bind-source="samplechooser" bind-flags="sync-create"/>
-            <property name="features" bind-source="features" bind-flags="sync-create"/>
-            <property name="variations" bind-source="variations" bind-flags="sync-create"/>
-            <property name="palette" bind-source="colors" bind-flags="sync-create"/>
+          <object class="GtkBox">
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkStack" id="stack">
+                <property name="hexpand">1</property>
+                <child>
+                  <object class="GtkStackPage">
+                    <property name="name">plain</property>
+                    <property name="child">
+                      <object class="PlainView">
+                        <property name="font-map" bind-source="FontExplorerWindow" bind-flags="sync-create"/>
+                        <property name="font-desc" bind-source="fontbutton" bind-flags="sync-create"/>
+                        <property name="font-desc" bind-source="fontbutton" bind-flags="sync-create"/>
+                        <property name="size" bind-source="controls" bind-flags="sync-create"/>
+                        <property name="letterspacing" bind-source="controls" bind-flags="sync-create"/>
+                        <property name="line-height" bind-source="controls" bind-flags="sync-create"/>
+                        <property name="foreground" bind-source="controls" bind-flags="sync-create"/>
+                        <property name="background" bind-source="controls" bind-flags="sync-create"/>
+                        <property name="sample-text" bind-source="sampleeditor" bind-flags="sync-create"/>
+                        <property name="features" bind-source="features" bind-flags="sync-create"/>
+                        <property name="variations" bind-source="variations" bind-flags="sync-create"/>
+                        <property name="palette" bind-source="colors" bind-flags="sync-create"/>
+                      </object>
+                    </property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkStackPage">
+                    <property name="name">waterfall</property>
+                    <property name="child">
+                      <object class="WaterfallView">
+                        <property name="font-map" bind-source="FontExplorerWindow" bind-flags="sync-create"/>
+                        <property name="font-desc" bind-source="fontbutton" bind-flags="sync-create"/>
+                        <property name="size" bind-source="controls" bind-flags="sync-create"/>
+                        <property name="letterspacing" bind-source="controls" bind-flags="sync-create"/>
+                        <property name="line-height" bind-source="controls" bind-flags="sync-create"/>
+                        <property name="foreground" bind-source="controls" bind-flags="sync-create"/>
+                        <property name="background" bind-source="controls" bind-flags="sync-create"/>
+                        <property name="sample-text" bind-source="sampleeditor" bind-flags="sync-create"/>
+                        <property name="features" bind-source="features" bind-flags="sync-create"/>
+                        <property name="variations" bind-source="variations" bind-flags="sync-create"/>
+                        <property name="palette" bind-source="colors" bind-flags="sync-create"/>
+                      </object>
+                    </property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkStackPage">
+                    <property name="name">glyphs</property>
+                    <property name="child">
+                      <object class="GlyphsView">
+                        <property name="font-map" bind-source="FontExplorerWindow" bind-flags="sync-create"/>
+                        <property name="font-desc" bind-source="fontbutton" bind-flags="sync-create"/>
+                        <property name="variations" bind-source="variations" bind-flags="sync-create"/>
+                        <property name="palette" bind-source="colors" bind-flags="sync-create"/>
+                      </object>
+                    </property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkStackPage">
+                    <property name="name">info</property>
+                    <property name="child">
+                      <object class="InfoView">
+                        <property name="font-map" bind-source="FontExplorerWindow" bind-flags="sync-create"/>
+                        <property name="font-desc" bind-source="fontbutton" bind-flags="sync-create"/>
+                        <property name="size" bind-source="controls" bind-flags="sync-create"/>
+                        <property name="variations" bind-source="variations" bind-flags="sync-create"/>
+                      </object>
+                    </property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkStackPage">
+                    <property name="name">edit</property>
+                    <property name="child">
+                      <object class="SampleEditor" id="sampleeditor">
+                        <property name="sample-text" bind-source="samplechooser" bind-flags="sync-create"/>
+                        <property name="editing" bind-source="edit_toggle" bind-property="active" 
bind-flags="sync-create"/>
+                      </object>
+                    </property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="orientation">horizontal</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="orientation">horizontal</property>
+                    <style>
+                      <class name="linked"/>
+                    </style>
+                    <child>
+                      <object class="GtkToggleButton" id="plain_toggle">
+                        <property name="label">Plain</property>
+                        <property name="active">1</property>
+                        <signal name="toggled" handler="update_view"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkToggleButton" id="waterfall_toggle">
+                        <property name="label">Waterfall</property>
+                        <property name="group">plain_toggle</property>
+                        <signal name="toggled" handler="update_view"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkToggleButton" id="glyphs_toggle">
+                        <property name="label">Glyphs</property>
+                        <property name="group">plain_toggle</property>
+                        <signal name="toggled" handler="update_view"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkToggleButton" id="info_toggle">
+                        <property name="label">Info</property>
+                        <property name="group">plain_toggle</property>
+                        <signal name="toggled" handler="update_view"/>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkToggleButton" id="edit_toggle">
+                    <property name="hexpand">1</property>
+                    <property name="halign">end</property>
+                    <property name="icon-name">document-edit-symbolic</property>
+                    <property name="tooltip-text" translatable="yes">Edit the sample</property>
+                    <signal name="toggled" handler="update_view"/>
+                  </object>
+                </child>
+              </object>
+            </child>
           </object>
         </child>
       </object>
diff --git a/demos/font-explorer/glyphsview.c b/demos/font-explorer/glyphsview.c
new file mode 100644
index 0000000000..4ef0b44d47
--- /dev/null
+++ b/demos/font-explorer/glyphsview.c
@@ -0,0 +1,238 @@
+#include "glyphsview.h"
+#include "glyphitem.h"
+#include "glyphmodel.h"
+#include "glyphview.h"
+#include <gtk/gtk.h>
+
+#include <hb-ot.h>
+
+enum {
+  PROP_FONT_MAP = 1,
+  PROP_FONT_DESC,
+  PROP_VARIATIONS,
+  PROP_PALETTE,
+  NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+struct _GlyphsView
+{
+  GtkWidget parent;
+
+  Pango2FontMap *font_map;
+  GtkGridView *glyphs;
+
+  Pango2FontDescription *font_desc;
+  char *variations;
+  char *palette;
+  GQuark palette_quark;
+};
+
+struct _GlyphsViewClass
+{
+  GtkWidgetClass parent_class;
+};
+
+G_DEFINE_TYPE(GlyphsView, glyphs_view, GTK_TYPE_WIDGET);
+
+static void
+glyphs_view_init (GlyphsView *self)
+{
+  self->font_map = g_object_ref (pango2_font_map_get_default ());
+  self->font_desc = pango2_font_description_from_string ("sans 12");
+  self->variations = g_strdup ("");
+  self->palette = g_strdup (PANGO2_COLOR_PALETTE_DEFAULT);
+  self->palette_quark = g_quark_from_string (self->palette);
+
+  gtk_widget_set_layout_manager (GTK_WIDGET (self),
+                                 gtk_box_layout_new (GTK_ORIENTATION_VERTICAL));
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+glyphs_view_dispose (GObject *object)
+{
+  gtk_widget_clear_template (GTK_WIDGET (object), GLYPHS_VIEW_TYPE);
+
+  G_OBJECT_CLASS (glyphs_view_parent_class)->dispose (object);
+}
+
+static void
+glyphs_view_finalize (GObject *object)
+{
+  GlyphsView *self = GLYPHS_VIEW (object);
+
+  g_clear_object (&self->font_map);
+  pango2_font_description_free (self->font_desc);
+  g_free (self->variations);
+  g_free (self->palette);
+
+  G_OBJECT_CLASS (glyphs_view_parent_class)->finalize (object);
+}
+
+static Pango2Font *
+get_font (GlyphsView *self,
+          int       size)
+{
+  Pango2Context *context;
+  Pango2FontDescription *desc;
+  Pango2Font *font;
+
+  context = pango2_context_new_with_font_map (self->font_map);
+  desc = pango2_font_description_copy_static (self->font_desc);
+  pango2_font_description_set_variations (desc, self->variations);
+  pango2_font_description_set_size (desc, size);
+  font = pango2_context_load_font (context, desc);
+  pango2_font_description_free (desc);
+  g_object_unref (context);
+
+  return font;
+}
+
+static void
+update_glyph_model (GlyphsView *self)
+{
+  Pango2Font *font = get_font (self, 60 * PANGO2_SCALE);
+  GlyphModel *gm;
+  GtkSelectionModel *model;
+
+  gm = glyph_model_new (font);
+  model = GTK_SELECTION_MODEL (gtk_no_selection_new (G_LIST_MODEL (gm)));
+  gtk_grid_view_set_model (self->glyphs, model);
+  g_object_unref (model);
+  g_object_unref (font);
+}
+
+static void
+setup_glyph (GtkSignalListItemFactory *factory,
+             GObject                  *listitem)
+{
+  gtk_list_item_set_child (GTK_LIST_ITEM (listitem), GTK_WIDGET (glyph_view_new ()));
+}
+
+static void
+bind_glyph (GtkSignalListItemFactory *factory,
+            GObject                  *listitem,
+            GlyphsView                 *self)
+{
+  GlyphView *view;
+  GObject *item;
+
+  view = GLYPH_VIEW (gtk_list_item_get_child (GTK_LIST_ITEM (listitem)));
+  item = gtk_list_item_get_item (GTK_LIST_ITEM (listitem));
+  glyph_view_set_font (view, glyph_item_get_font (GLYPH_ITEM (item)));
+  glyph_view_set_glyph (view, glyph_item_get_glyph (GLYPH_ITEM (item)));
+  glyph_view_set_palette (view, self->palette_quark);
+}
+
+static void
+glyphs_view_set_property (GObject      *object,
+                          guint         prop_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
+{
+  GlyphsView *self = GLYPHS_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_FONT_MAP:
+      g_set_object (&self->font_map, g_value_get_object (value));
+      break;
+
+    case PROP_FONT_DESC:
+      pango2_font_description_free (self->font_desc);
+      self->font_desc = pango2_font_description_copy (g_value_get_boxed (value));
+      break;
+
+    case PROP_VARIATIONS:
+      g_free (self->variations);
+      self->variations = g_strdup (g_value_get_string (value));
+      break;
+
+    case PROP_PALETTE:
+      g_free (self->palette);
+      self->palette = g_strdup (g_value_get_string (value));
+      self->palette_quark = g_quark_from_string (self->palette);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+
+  update_glyph_model (self);
+}
+
+static void
+glyphs_view_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  GlyphsView *self = GLYPHS_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_FONT_MAP:
+      g_value_set_object (value, self->font_map);
+      break;
+
+    case PROP_FONT_DESC:
+      g_value_set_boxed (value, self->font_desc);
+      break;
+
+    case PROP_VARIATIONS:
+      g_value_set_string (value, self->variations);
+      break;
+
+    case PROP_PALETTE:
+      g_value_set_string (value, self->palette);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+glyphs_view_class_init (GlyphsViewClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->dispose = glyphs_view_dispose;
+  object_class->finalize = glyphs_view_finalize;
+  object_class->get_property = glyphs_view_get_property;
+  object_class->set_property = glyphs_view_set_property;
+
+  properties[PROP_FONT_MAP] =
+      g_param_spec_object ("font-map", "", "",
+                           PANGO2_TYPE_FONT_MAP,
+                           G_PARAM_READWRITE);
+
+  properties[PROP_FONT_DESC] =
+      g_param_spec_boxed ("font-desc", "", "",
+                          PANGO2_TYPE_FONT_DESCRIPTION,
+                          G_PARAM_READWRITE);
+
+  properties[PROP_VARIATIONS] =
+      g_param_spec_string ("variations", "", "",
+                           "",
+                           G_PARAM_READWRITE);
+
+  properties[PROP_PALETTE] =
+      g_param_spec_string ("palette", "", "",
+                           PANGO2_COLOR_PALETTE_DEFAULT,
+                           G_PARAM_READWRITE);
+
+  g_object_class_install_properties (G_OBJECT_CLASS (class), NUM_PROPERTIES, properties);
+
+  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
+                                               "/org/gtk/fontexplorer/glyphsview.ui");
+
+  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), GlyphsView, glyphs);
+  gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), setup_glyph);
+  gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), bind_glyph);
+
+  gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (class), "glyphsview");
+}
diff --git a/demos/font-explorer/glyphsview.h b/demos/font-explorer/glyphsview.h
new file mode 100644
index 0000000000..d6935ffe1f
--- /dev/null
+++ b/demos/font-explorer/glyphsview.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <gtk/gtk.h>
+
+
+#define GLYPHS_VIEW_TYPE (glyphs_view_get_type ())
+#define GLYPHS_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GLYPHS_VIEW_TYPE, GlyphsView))
+
+
+typedef struct _GlyphsView         GlyphsView;
+typedef struct _GlyphsViewClass    GlyphsViewClass;
+
+
+GType           glyphs_view_get_type     (void);
diff --git a/demos/font-explorer/glyphsview.ui b/demos/font-explorer/glyphsview.ui
new file mode 100644
index 0000000000..344d8d36d9
--- /dev/null
+++ b/demos/font-explorer/glyphsview.ui
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GlyphsView" parent="GtkWidget">
+    <style>
+      <class name="view"/>
+    </style>
+    <child>
+      <object class="GtkScrolledWindow">
+        <property name="hexpand">1</property>
+        <property name="vexpand">1</property>
+        <property name="hscrollbar-policy">never</property>
+        <property name="vscrollbar-policy">automatic</property>
+        <child>
+          <object class="GtkGridView" id="glyphs">
+            <property name="factory">
+              <object class="GtkSignalListItemFactory">
+                <signal name="setup" handler="setup_glyph"/>
+                <signal name="bind" handler="bind_glyph"/>
+              </object>
+            </property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/demos/font-explorer/infoview.c b/demos/font-explorer/infoview.c
new file mode 100644
index 0000000000..40464bf397
--- /dev/null
+++ b/demos/font-explorer/infoview.c
@@ -0,0 +1,416 @@
+#include "infoview.h"
+#include <gtk/gtk.h>
+
+#include <hb-ot.h>
+
+enum {
+  PROP_FONT_MAP = 1,
+  PROP_FONT_DESC,
+  PROP_SIZE,
+  PROP_VARIATIONS,
+  NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+struct _InfoView
+{
+  GtkWidget parent;
+
+  GtkGrid *info;
+
+  Pango2FontMap *font_map;
+  Pango2FontDescription *font_desc;
+  float size;
+  char *variations;
+};
+
+struct _InfoViewClass
+{
+  GtkWidgetClass parent_class;
+};
+
+G_DEFINE_TYPE(InfoView, info_view, GTK_TYPE_WIDGET);
+
+static void
+info_view_init (InfoView *self)
+{
+  self->font_map = g_object_ref (pango2_font_map_get_default ());
+  self->font_desc = pango2_font_description_from_string ("sans 12");
+  self->size = 12.;
+  self->variations = g_strdup ("");
+
+  gtk_widget_set_layout_manager (GTK_WIDGET (self),
+                                 gtk_box_layout_new (GTK_ORIENTATION_VERTICAL));
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+info_view_dispose (GObject *object)
+{
+  gtk_widget_clear_template (GTK_WIDGET (object), INFO_VIEW_TYPE);
+
+  G_OBJECT_CLASS (info_view_parent_class)->dispose (object);
+}
+
+static void
+info_view_finalize (GObject *object)
+{
+  InfoView *self = INFO_VIEW (object);
+
+  g_clear_object (&self->font_map);
+  pango2_font_description_free (self->font_desc);
+  g_free (self->variations);
+
+  G_OBJECT_CLASS (info_view_parent_class)->finalize (object);
+}
+
+static Pango2Font *
+get_font (InfoView *self,
+          int       size)
+{
+  Pango2Context *context;
+  Pango2FontDescription *desc;
+  Pango2Font *font;
+
+  context = pango2_context_new_with_font_map (self->font_map);
+  desc = pango2_font_description_copy_static (self->font_desc);
+  pango2_font_description_set_variations (desc, self->variations);
+  pango2_font_description_set_size (desc, size);
+  font = pango2_context_load_font (context, desc);
+  pango2_font_description_free (desc);
+  g_object_unref (context);
+
+  return font;
+}
+
+static GtkWidget *
+make_title_label (const char *title)
+{
+  GtkWidget *label;
+
+  label = gtk_label_new (title);
+  gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+  gtk_widget_set_halign (label, GTK_ALIGN_START);
+  g_object_set (label, "margin-top", 10, "margin-bottom", 10, NULL);
+  gtk_widget_add_css_class (label, "heading");
+
+  return label;
+}
+
+static void
+add_misc_line (InfoView   *self,
+               const char *title,
+               const char *value,
+               int         row)
+{
+  GtkWidget *label;
+
+  label = gtk_label_new (title);
+  gtk_widget_set_halign (label, GTK_ALIGN_START);
+  gtk_widget_set_valign (label, GTK_ALIGN_START);
+  gtk_label_set_xalign (GTK_LABEL (label), 0);
+  gtk_widget_set_hexpand (label, TRUE);
+  gtk_grid_attach (self->info, label, 0, row, 1, 1);
+
+  label = gtk_label_new (value);
+  gtk_widget_set_halign (label, GTK_ALIGN_END);
+  gtk_widget_set_valign (label, GTK_ALIGN_START);
+  gtk_label_set_xalign (GTK_LABEL (label), 1);
+  gtk_label_set_wrap (GTK_LABEL (label), TRUE);
+  gtk_label_set_width_chars (GTK_LABEL (label), 40);
+  gtk_label_set_max_width_chars (GTK_LABEL (label), 40);
+  gtk_grid_attach (self->info, label, 1, row, 1, 1);
+}
+
+static void
+add_info_line (InfoView        *self,
+               hb_face_t       *face,
+               hb_ot_name_id_t  name_id,
+               const char      *title,
+               int              row)
+{
+  char info[256];
+  unsigned int len = sizeof (info);
+
+  if (hb_ot_name_get_utf8 (face, name_id, HB_LANGUAGE_INVALID, &len, info) > 0)
+    add_misc_line (self, title, info, row);
+}
+
+static void
+add_metrics_line (InfoView            *self,
+                  hb_font_t           *font,
+                  hb_ot_metrics_tag_t  metrics_tag,
+                  const char          *title,
+                  int                  row)
+{
+  hb_position_t pos;
+
+  if (hb_ot_metrics_get_position (font, metrics_tag, &pos))
+    {
+      char buf[128];
+
+      g_snprintf (buf, sizeof (buf), "%d", pos);
+      add_misc_line (self, title, buf, row);
+    }
+}
+
+static void
+add_style_line (InfoView       *self,
+                hb_font_t      *font,
+                hb_style_tag_t  style_tag,
+                const char     *title,
+                int             row)
+{
+  float value;
+  char buf[16];
+
+  value = hb_style_get_value (font, style_tag);
+  g_snprintf (buf, sizeof (buf), "%.2f", value);
+  add_misc_line (self, title, buf, row);
+}
+
+static void
+update_info (InfoView *self)
+{
+  GtkWidget *child;
+  int size = pango2_font_description_get_size (self->font_desc);
+  Pango2Font *pango_font = get_font (self, MAX (size, 10 * PANGO2_SCALE));
+  hb_font_t *font1 = pango2_font_get_hb_font (pango_font);
+  hb_face_t *face = hb_font_get_face (font1);
+  hb_font_t *font = hb_font_create_sub_font (font1);
+  int row = 0;
+  char buf[128];
+  unsigned int count;
+  GString *s;
+  hb_tag_t *tables;
+
+  hb_font_set_scale (font, hb_face_get_upem (face), hb_face_get_upem (face));
+
+  while ((child = gtk_widget_get_first_child (GTK_WIDGET (self->info))) != NULL)
+    gtk_widget_unparent (child);
+
+  gtk_grid_attach (self->info, make_title_label ("General Info"), 0, row++, 2, 1);
+  add_info_line (self, face, HB_OT_NAME_ID_FONT_FAMILY, "Font Family Name", row++);
+  add_info_line (self, face, HB_OT_NAME_ID_FONT_SUBFAMILY, "Font Subfamily Name", row++);
+  add_info_line (self, face, HB_OT_NAME_ID_UNIQUE_ID, "Unique Font Identifier", row++);
+  add_info_line (self, face, HB_OT_NAME_ID_FULL_NAME, "Full Name", row++);
+  add_info_line (self, face, HB_OT_NAME_ID_VERSION_STRING, "Version", row++);
+  add_info_line (self, face, HB_OT_NAME_ID_POSTSCRIPT_NAME, "Postscript Name", row++);
+  add_info_line (self, face, HB_OT_NAME_ID_TYPOGRAPHIC_FAMILY, "Typographic Family Name", row++);
+  add_info_line (self, face, HB_OT_NAME_ID_TYPOGRAPHIC_SUBFAMILY, "Typographic Subfamily Name", row++);
+  add_info_line (self, face, HB_OT_NAME_ID_MANUFACTURER, "Vendor ID", row++);
+  add_info_line (self, face, HB_OT_NAME_ID_DESIGNER, "Designer", row++);
+  add_info_line (self, face, HB_OT_NAME_ID_DESCRIPTION, "Description", row++);
+  add_info_line (self, face, HB_OT_NAME_ID_COPYRIGHT, "Copyright", row++);
+
+  gtk_grid_attach (self->info, make_title_label ("Metrics"), 0, row++, 2, 1);
+  g_snprintf (buf, sizeof (buf), "%d", hb_face_get_upem (face));
+  add_misc_line (self, "Units per Em", buf, row++);
+  add_metrics_line (self, font, HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER, "Ascender", row++);
+  add_metrics_line (self, font, HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER, "Descender", row++);
+  add_metrics_line (self, font, HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP, "Line Gap", row++);
+  add_metrics_line (self, font, HB_OT_METRICS_TAG_HORIZONTAL_CARET_RISE, "Caret Rise", row++);
+  add_metrics_line (self, font, HB_OT_METRICS_TAG_HORIZONTAL_CARET_RUN, "Caret Run", row++);
+  add_metrics_line (self, font, HB_OT_METRICS_TAG_HORIZONTAL_CARET_OFFSET, "Caret Offset", row++);
+  add_metrics_line (self, font, HB_OT_METRICS_TAG_X_HEIGHT, "x Height", row++);
+  add_metrics_line (self, font, HB_OT_METRICS_TAG_CAP_HEIGHT, "Cap Height", row++);
+
+  add_metrics_line (self, font, HB_OT_METRICS_TAG_STRIKEOUT_SIZE, "Strikeout Size", row++);
+  add_metrics_line (self, font, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET, "Strikeout Offset", row++);
+  add_metrics_line (self, font, HB_OT_METRICS_TAG_STRIKEOUT_SIZE, "Underline Size", row++);
+  add_metrics_line (self, font, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET, "Underline Offset", row++);
+
+  gtk_grid_attach (self->info, make_title_label ("Style"), 0, row++, 2, 1);
+
+  add_style_line (self, font, HB_STYLE_TAG_ITALIC, "Italic", row++);
+  add_style_line (self, font, HB_STYLE_TAG_OPTICAL_SIZE, "Optical Size", row++);
+  add_style_line (self, font, HB_STYLE_TAG_SLANT_ANGLE, "Slant Angle", row++);
+  add_style_line (self, font, HB_STYLE_TAG_WIDTH, "Width", row++);
+  add_style_line (self, font, HB_STYLE_TAG_WEIGHT, "Weight", row++);
+
+  gtk_grid_attach (self->info, make_title_label ("Miscellaneous"), 0, row++, 2, 1);
+
+  count = hb_face_get_glyph_count (face);
+  g_snprintf (buf, sizeof (buf), "%d", count);
+  add_misc_line (self, "Glyph Count", buf, row++);
+
+  if (hb_ot_var_get_axis_count (face) > 0)
+    {
+      s = g_string_new ("");
+      hb_ot_var_axis_info_t *axes;
+
+      axes = g_newa (hb_ot_var_axis_info_t, hb_ot_var_get_axis_count (face));
+      count = hb_ot_var_get_axis_count (face);
+      hb_ot_var_get_axis_infos (face, 0, &count, axes);
+      for (int i = 0; i < count; i++)
+        {
+          char name[256];
+          unsigned int len;
+
+          len = sizeof (buf);
+          hb_ot_name_get_utf8 (face, axes[i].name_id, HB_LANGUAGE_INVALID, &len, name);
+          if (s->len > 0)
+            g_string_append (s, ", ");
+          g_string_append (s, name);
+        }
+      add_misc_line (self, "Axes", s->str, row++);
+      g_string_free (s, TRUE);
+    }
+
+  if (hb_ot_var_get_named_instance_count (face) > 0)
+    {
+      s = g_string_new ("");
+      for (int i = 0; i < hb_ot_var_get_named_instance_count (face); i++)
+        {
+          hb_ot_name_id_t name_id;
+          char name[256];
+          unsigned int len;
+
+          name_id = hb_ot_var_named_instance_get_subfamily_name_id (face, i);
+          len = sizeof (buf);
+          hb_ot_name_get_utf8 (face, name_id, HB_LANGUAGE_INVALID, &len, name);
+          if (s->len > 0)
+            g_string_append (s, ", ");
+          g_string_append (s, name);
+        }
+      add_misc_line (self, "Named Instances", s->str, row++);
+      g_string_free (s, TRUE);
+    }
+
+  s = g_string_new ("");
+  count = hb_face_get_table_tags (face, 0, NULL, NULL);
+  tables = g_newa (hb_tag_t, count);
+  hb_face_get_table_tags (face, 0, &count, tables);
+  for (int i = 0; i < count; i++)
+    {
+      memset (buf, 0, sizeof (buf));
+      hb_tag_to_string (tables[i], buf);
+      if (s->len > 0)
+        g_string_append (s, ", ");
+      g_string_append (s, buf);
+    }
+  add_misc_line (self, "Tables", s->str, row++);
+  g_string_free (s, TRUE);
+
+  s = g_string_new ("");
+  if (hb_ot_color_has_palettes (face))
+    g_string_append_printf (s, "%s", "Palettes");
+  if (hb_ot_color_has_layers (face))
+    g_string_append_printf (s, "%s%s", s->len > 0 ? ", " : "", "Layers");
+  if (hb_ot_color_has_svg (face))
+    g_string_append_printf (s, "%s%s", s->len > 0 ? ", " : "", "SVG");
+  if (hb_ot_color_has_png (face))
+    g_string_append_printf (s, "%s%s", s->len > 0 ? ", " : "", "PNG");
+  if (s->len > 0)
+    add_misc_line (self, "Color", s->str, row++);
+  g_string_free (s, TRUE);
+
+  hb_font_destroy (font);
+}
+
+static void
+info_view_set_property (GObject      *object,
+                        guint         prop_id,
+                        const GValue *value,
+                        GParamSpec   *pspec)
+{
+  InfoView *self = INFO_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_FONT_MAP:
+      g_set_object (&self->font_map, g_value_get_object (value));
+      break;
+
+    case PROP_FONT_DESC:
+      pango2_font_description_free (self->font_desc);
+      self->font_desc = pango2_font_description_copy (g_value_get_boxed (value));
+      break;
+
+    case PROP_SIZE:
+      self->size = g_value_get_float (value);
+      break;
+
+    case PROP_VARIATIONS:
+      g_free (self->variations);
+      self->variations = g_strdup (g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+
+  update_info (self);
+}
+
+static void
+info_view_get_property (GObject    *object,
+                        guint       prop_id,
+                        GValue     *value,
+                        GParamSpec *pspec)
+{
+  InfoView *self = INFO_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_FONT_MAP:
+      g_value_set_object (value, self->font_map);
+      break;
+
+    case PROP_FONT_DESC:
+      g_value_set_boxed (value, self->font_desc);
+      break;
+
+    case PROP_SIZE:
+      g_value_set_float (value, self->size);
+      break;
+
+    case PROP_VARIATIONS:
+      g_value_set_string (value, self->variations);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+info_view_class_init (InfoViewClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->dispose = info_view_dispose;
+  object_class->finalize = info_view_finalize;
+  object_class->get_property = info_view_get_property;
+  object_class->set_property = info_view_set_property;
+
+  properties[PROP_FONT_MAP] =
+      g_param_spec_object ("font-map", "", "",
+                           PANGO2_TYPE_FONT_MAP,
+                           G_PARAM_READWRITE);
+
+  properties[PROP_FONT_DESC] =
+      g_param_spec_boxed ("font-desc", "", "",
+                          PANGO2_TYPE_FONT_DESCRIPTION,
+                          G_PARAM_READWRITE);
+
+  properties[PROP_SIZE] =
+      g_param_spec_float ("size", "", "",
+                          0., 100., 12.,
+                          G_PARAM_READWRITE);
+
+  properties[PROP_VARIATIONS] =
+      g_param_spec_string ("variations", "", "",
+                           "",
+                           G_PARAM_READWRITE);
+
+  g_object_class_install_properties (G_OBJECT_CLASS (class), NUM_PROPERTIES, properties);
+
+  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
+                                               "/org/gtk/fontexplorer/infoview.ui");
+
+  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), InfoView, info);
+
+  gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (class), "infoview");
+}
diff --git a/demos/font-explorer/infoview.h b/demos/font-explorer/infoview.h
new file mode 100644
index 0000000000..f21142ef9f
--- /dev/null
+++ b/demos/font-explorer/infoview.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <gtk/gtk.h>
+
+
+#define INFO_VIEW_TYPE (info_view_get_type ())
+#define INFO_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INFO_VIEW_TYPE, InfoView))
+
+
+typedef struct _InfoView         InfoView;
+typedef struct _InfoViewClass    InfoViewClass;
+
+
+GType           info_view_get_type     (void);
diff --git a/demos/font-explorer/infoview.ui b/demos/font-explorer/infoview.ui
new file mode 100644
index 0000000000..ad34072dbe
--- /dev/null
+++ b/demos/font-explorer/infoview.ui
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="InfoView" parent="GtkWidget">
+    <style>
+      <class name="view"/>
+    </style>
+    <child>
+      <object class="GtkScrolledWindow">
+        <property name="hscrollbar-policy">never</property>
+        <property name="vscrollbar-policy">automatic</property>
+        <property name="hexpand">1</property>
+        <property name="vexpand">1</property>
+        <child>
+          <object class="GtkGrid" id="info">
+            <style>
+              <class name="fontinfo"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/demos/font-explorer/meson.build b/demos/font-explorer/meson.build
index d552853990..d08eebac1e 100644
--- a/demos/font-explorer/meson.build
+++ b/demos/font-explorer/meson.build
@@ -1,18 +1,22 @@
 fontexplorer_sources = [
-  'main.c',
+  'fontcolors.c',
+  'fontcontrols.c',
   'fontexplorerapp.c',
   'fontexplorerwin.c',
-  'fontcontrols.c',
-  'samplechooser.c',
-  'fontcolors.c',
   'fontfeatures.c',
   'fontvariations.c',
-  'fontview.c',
-  'rangeedit.c',
-  'language-names.c',
   'glyphitem.c',
   'glyphmodel.c',
+  'glyphsview.c',
   'glyphview.c',
+  'infoview.c',
+  'language-names.c',
+  'main.c',
+  'plainview.c',
+  'rangeedit.c',
+  'samplechooser.c',
+  'sampleeditor.c',
+  'waterfallview.c',
 ]
 
 fontexplorer_resources = gnome.compile_resources('fontexplorer_resources',
diff --git a/demos/font-explorer/plainview.c b/demos/font-explorer/plainview.c
new file mode 100644
index 0000000000..1c5fe12b50
--- /dev/null
+++ b/demos/font-explorer/plainview.c
@@ -0,0 +1,342 @@
+#include "plainview.h"
+#include "glyphitem.h"
+#include "glyphmodel.h"
+#include "glyphview.h"
+#include <gtk/gtk.h>
+
+#include <hb-ot.h>
+
+enum {
+  PROP_FONT_MAP = 1,
+  PROP_FONT_DESC,
+  PROP_SIZE,
+  PROP_LETTERSPACING,
+  PROP_LINE_HEIGHT,
+  PROP_FOREGROUND,
+  PROP_BACKGROUND,
+  PROP_VARIATIONS,
+  PROP_FEATURES,
+  PROP_PALETTE,
+  PROP_SAMPLE_TEXT,
+  NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+struct _PlainView
+{
+  GtkWidget parent;
+
+  GtkLabel *content;
+  GtkScrolledWindow *swin;
+
+  Pango2FontMap *font_map;
+  Pango2FontDescription *font_desc;
+  float size;
+  char *variations;
+  char *features;
+  char *palette;
+  GQuark palette_quark;
+  int letterspacing;
+  float line_height;
+  GdkRGBA foreground;
+  GdkRGBA background;
+  GtkCssProvider *bg_provider;
+  char *sample_text;
+};
+
+struct _PlainViewClass
+{
+  GtkWidgetClass parent_class;
+};
+
+G_DEFINE_TYPE(PlainView, plain_view, GTK_TYPE_WIDGET);
+
+static void
+plain_view_init (PlainView *self)
+{
+  self->font_map = g_object_ref (pango2_font_map_get_default ());
+  self->font_desc = pango2_font_description_from_string ("sans 12");
+  self->size = 12.;
+  self->letterspacing = 0;
+  self->line_height = 1.;
+  self->variations = g_strdup ("");
+  self->features = g_strdup ("");
+  self->palette = g_strdup (PANGO2_COLOR_PALETTE_DEFAULT);
+  self->palette_quark = g_quark_from_string (self->palette);
+  self->foreground = (GdkRGBA){0., 0., 0., 1. };
+  self->background = (GdkRGBA){1., 1., 1., 1. };
+  self->sample_text = g_strdup ("Some sample text is better than other sample text");
+
+  gtk_widget_set_layout_manager (GTK_WIDGET (self),
+                                 gtk_box_layout_new (GTK_ORIENTATION_VERTICAL));
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  self->bg_provider = gtk_css_provider_new ();
+  gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (self->content)),
+                                  GTK_STYLE_PROVIDER (self->bg_provider), 800);
+}
+
+static void
+plain_view_dispose (GObject *object)
+{
+  gtk_widget_clear_template (GTK_WIDGET (object), PLAIN_VIEW_TYPE);
+
+  G_OBJECT_CLASS (plain_view_parent_class)->dispose (object);
+}
+
+static void
+plain_view_finalize (GObject *object)
+{
+  PlainView *self = PLAIN_VIEW (object);
+
+  g_clear_object (&self->font_map);
+  pango2_font_description_free (self->font_desc);
+  g_free (self->variations);
+  g_free (self->features);
+  g_free (self->palette);
+
+  G_OBJECT_CLASS (plain_view_parent_class)->finalize (object);
+}
+
+static void
+update_view (PlainView *self)
+{
+  Pango2FontDescription *desc;
+  Pango2AttrList *attrs;
+  char *fg, *bg, *css;
+
+  desc = pango2_font_description_copy_static (self->font_desc);
+  pango2_font_description_set_size (desc, 12 * PANGO2_SCALE);
+  pango2_font_description_set_variations (desc, self->variations);
+
+  attrs = pango2_attr_list_new ();
+  pango2_attr_list_insert (attrs, pango2_attr_font_desc_new (desc));
+  pango2_attr_list_insert (attrs, pango2_attr_size_new (self->size * PANGO2_SCALE));
+  pango2_attr_list_insert (attrs, pango2_attr_letter_spacing_new (self->letterspacing));
+  pango2_attr_list_insert (attrs, pango2_attr_line_height_new (self->line_height));
+  pango2_attr_list_insert (attrs, pango2_attr_foreground_new (&(Pango2Color){65535 * self->foreground.red,
+                                                                             65535 * self->foreground.green,
+                                                                             65535 * self->foreground.blue,
+                                                                             65535 * 
self->foreground.alpha}));
+  pango2_attr_list_insert (attrs, pango2_attr_font_features_new (self->features));
+  pango2_attr_list_insert (attrs, pango2_attr_palette_new (self->palette));
+
+  pango2_font_description_free (desc);
+
+  gtk_label_set_label (self->content, self->sample_text);
+  gtk_label_set_attributes (self->content, attrs);
+
+  pango2_attr_list_unref (attrs);
+
+  fg = gdk_rgba_to_string (&self->foreground);
+  bg = gdk_rgba_to_string (&self->background);
+  css = g_strdup_printf (".view_background { caret-color: %s; background-color: %s; }", fg, bg);
+  gtk_css_provider_load_from_data (self->bg_provider, css, strlen (css));
+  g_free (css);
+  g_free (fg);
+  g_free (bg);
+}
+
+static void
+plain_view_set_property (GObject      *object,
+                        guint         prop_id,
+                        const GValue *value,
+                        GParamSpec   *pspec)
+{
+  PlainView *self = PLAIN_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_FONT_MAP:
+      g_set_object (&self->font_map, g_value_get_object (value));
+      gtk_widget_set_font_map (GTK_WIDGET (self->content), self->font_map);
+      break;
+
+    case PROP_FONT_DESC:
+      pango2_font_description_free (self->font_desc);
+      self->font_desc = pango2_font_description_copy (g_value_get_boxed (value));
+      break;
+
+    case PROP_SIZE:
+      self->size = g_value_get_float (value);
+      break;
+
+    case PROP_LETTERSPACING:
+      self->letterspacing = g_value_get_int (value);
+      break;
+
+    case PROP_LINE_HEIGHT:
+      self->line_height = g_value_get_float (value);
+      break;
+
+    case PROP_FOREGROUND:
+      self->foreground = *(GdkRGBA *)g_value_get_boxed (value);
+      break;
+
+    case PROP_BACKGROUND:
+      self->background = *(GdkRGBA *)g_value_get_boxed (value);
+      break;
+
+    case PROP_VARIATIONS:
+      g_free (self->variations);
+      self->variations = g_strdup (g_value_get_string (value));
+      break;
+
+    case PROP_FEATURES:
+      g_free (self->features);
+      self->features = g_strdup (g_value_get_string (value));
+      break;
+
+    case PROP_PALETTE:
+      g_free (self->palette);
+      self->palette = g_strdup (g_value_get_string (value));
+      self->palette_quark = g_quark_from_string (self->palette);
+      break;
+
+    case PROP_SAMPLE_TEXT:
+      g_free (self->sample_text);
+      self->sample_text = g_strdup (g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+
+  update_view (self);
+}
+
+static void
+plain_view_get_property (GObject    *object,
+                        guint       prop_id,
+                        GValue     *value,
+                        GParamSpec *pspec)
+{
+  PlainView *self = PLAIN_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_FONT_MAP:
+      g_value_set_object (value, self->font_map);
+      break;
+
+    case PROP_FONT_DESC:
+      g_value_set_boxed (value, self->font_desc);
+      break;
+
+    case PROP_SIZE:
+      g_value_set_float (value, self->size);
+      break;
+
+    case PROP_LETTERSPACING:
+      g_value_set_int (value, self->letterspacing);
+      break;
+
+    case PROP_LINE_HEIGHT:
+      g_value_set_float (value, self->line_height);
+      break;
+
+    case PROP_FOREGROUND:
+      g_value_set_boxed (value, &self->foreground);
+      break;
+
+    case PROP_BACKGROUND:
+      g_value_set_boxed (value, &self->background);
+      break;
+
+    case PROP_VARIATIONS:
+      g_value_set_string (value, self->variations);
+      break;
+
+    case PROP_FEATURES:
+      g_value_set_string (value, self->features);
+      break;
+
+    case PROP_PALETTE:
+      g_value_set_string (value, self->palette);
+      break;
+
+    case PROP_SAMPLE_TEXT:
+      g_value_set_string (value, self->sample_text);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+plain_view_class_init (PlainViewClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->dispose = plain_view_dispose;
+  object_class->finalize = plain_view_finalize;
+  object_class->get_property = plain_view_get_property;
+  object_class->set_property = plain_view_set_property;
+
+  properties[PROP_FONT_MAP] =
+      g_param_spec_object ("font-map", "", "",
+                           PANGO2_TYPE_FONT_MAP,
+                           G_PARAM_READWRITE);
+
+  properties[PROP_FONT_DESC] =
+      g_param_spec_boxed ("font-desc", "", "",
+                          PANGO2_TYPE_FONT_DESCRIPTION,
+                          G_PARAM_READWRITE);
+
+  properties[PROP_SIZE] =
+      g_param_spec_float ("size", "", "",
+                          0., 100., 12.,
+                          G_PARAM_READWRITE);
+
+  properties[PROP_LETTERSPACING] =
+      g_param_spec_int ("letterspacing", "", "",
+                        -G_MAXINT, G_MAXINT, 0,
+                        G_PARAM_READWRITE);
+
+  properties[PROP_LINE_HEIGHT] =
+      g_param_spec_float ("line-height", "", "",
+                          0., 100., 1.,
+                          G_PARAM_READWRITE);
+
+  properties[PROP_FOREGROUND] =
+      g_param_spec_boxed ("foreground", "", "",
+                          GDK_TYPE_RGBA,
+                          G_PARAM_READWRITE);
+
+  properties[PROP_BACKGROUND] =
+      g_param_spec_boxed ("background", "", "",
+                          GDK_TYPE_RGBA,
+                          G_PARAM_READWRITE);
+
+  properties[PROP_VARIATIONS] =
+      g_param_spec_string ("variations", "", "",
+                           "",
+                           G_PARAM_READWRITE);
+
+  properties[PROP_FEATURES] =
+      g_param_spec_string ("features", "", "",
+                           "",
+                           G_PARAM_READWRITE);
+
+  properties[PROP_PALETTE] =
+      g_param_spec_string ("palette", "", "",
+                           PANGO2_COLOR_PALETTE_DEFAULT,
+                           G_PARAM_READWRITE);
+
+  properties[PROP_SAMPLE_TEXT] =
+      g_param_spec_string ("sample-text", "", "",
+                           "",
+                           G_PARAM_READWRITE);
+
+  g_object_class_install_properties (G_OBJECT_CLASS (class), NUM_PROPERTIES, properties);
+
+  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
+                                               "/org/gtk/fontexplorer/plainview.ui");
+
+  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), PlainView, swin);
+  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), PlainView, content);
+
+  gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (class), "plainview");
+}
diff --git a/demos/font-explorer/plainview.h b/demos/font-explorer/plainview.h
new file mode 100644
index 0000000000..8996efe0e9
--- /dev/null
+++ b/demos/font-explorer/plainview.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <gtk/gtk.h>
+
+
+#define PLAIN_VIEW_TYPE (plain_view_get_type ())
+#define PLAIN_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLAIN_VIEW_TYPE, PlainView))
+
+
+typedef struct _PlainView         PlainView;
+typedef struct _PlainViewClass    PlainViewClass;
+
+
+GType           plain_view_get_type     (void);
diff --git a/demos/font-explorer/plainview.ui b/demos/font-explorer/plainview.ui
new file mode 100644
index 0000000000..462ffe3882
--- /dev/null
+++ b/demos/font-explorer/plainview.ui
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="PlainView" parent="GtkWidget">
+    <property name="hexpand">1</property>
+    <property name="vexpand">1</property>
+    <style>
+      <class name="view"/>
+    </style>
+    <child>
+      <object class="GtkScrolledWindow" id="swin">
+        <property name="hscrollbar-policy">never</property>
+        <property name="vscrollbar-policy">automatic</property>
+        <child>
+          <object class="GtkLabel" id="content">
+            <property name="label">Content</property>
+            <property name="wrap">1</property>
+            <property name="wrap-mode">word-char</property>
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
+            <property name="hexpand">1</property>
+            <property name="vexpand">1</property>
+            <property name="halign">fill</property>
+            <property name="valign">fill</property>
+            <style>
+              <class name="view_background"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/demos/font-explorer/sampleeditor.c b/demos/font-explorer/sampleeditor.c
new file mode 100644
index 0000000000..8ec68e0a38
--- /dev/null
+++ b/demos/font-explorer/sampleeditor.c
@@ -0,0 +1,161 @@
+#include "sampleeditor.h"
+#include <gtk/gtk.h>
+
+enum {
+  PROP_SAMPLE_TEXT = 1,
+  PROP_EDITING,
+  NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+struct _SampleEditor
+{
+  GtkWidget parent;
+
+  GtkTextView *edit;
+  char *sample_text;
+  gboolean editing;
+};
+
+struct _SampleEditorClass
+{
+  GtkWidgetClass parent_class;
+};
+
+G_DEFINE_TYPE(SampleEditor, sample_editor, GTK_TYPE_WIDGET);
+
+static void
+sample_editor_init (SampleEditor *self)
+{
+  self->sample_text = g_strdup ("Some sample text is better than other sample text");
+  self->editing = FALSE;
+
+  gtk_widget_set_layout_manager (GTK_WIDGET (self),
+                                 gtk_box_layout_new (GTK_ORIENTATION_VERTICAL));
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+sample_editor_dispose (GObject *object)
+{
+  gtk_widget_clear_template (GTK_WIDGET (object), SAMPLE_EDITOR_TYPE);
+
+  G_OBJECT_CLASS (sample_editor_parent_class)->dispose (object);
+}
+
+static void
+sample_editor_finalize (GObject *object)
+{
+  SampleEditor *self = SAMPLE_EDITOR (object);
+
+  g_free (self->sample_text);
+
+  G_OBJECT_CLASS (sample_editor_parent_class)->finalize (object);
+}
+
+static void
+update_editing (SampleEditor *self,
+                gboolean      editing)
+{
+  GtkTextBuffer *buffer;
+
+  if (self->editing == editing)
+    return;
+
+  self->editing = editing;
+
+  buffer = gtk_text_view_get_buffer (self->edit);
+
+  if (self->editing)
+    {
+      gtk_text_buffer_set_text (buffer, self->sample_text, -1);
+      gtk_widget_grab_focus (GTK_WIDGET (self->edit));
+    }
+  else
+    {
+      GtkTextIter start, end;
+
+      g_free (self->sample_text);
+      gtk_text_buffer_get_bounds (buffer, &start, &end);
+      self->sample_text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SAMPLE_TEXT]);
+    }
+}
+
+static void
+sample_editor_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  SampleEditor *self = SAMPLE_EDITOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_SAMPLE_TEXT:
+      g_free (self->sample_text);
+      self->sample_text = g_strdup (g_value_get_string (value));
+      break;
+
+    case PROP_EDITING:
+      update_editing (self, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+sample_editor_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  SampleEditor *self = SAMPLE_EDITOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_SAMPLE_TEXT:
+      g_value_set_string (value, self->sample_text);
+      break;
+
+    case PROP_EDITING:
+      g_value_set_boolean (value, self->editing);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+sample_editor_class_init (SampleEditorClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->dispose = sample_editor_dispose;
+  object_class->finalize = sample_editor_finalize;
+  object_class->get_property = sample_editor_get_property;
+  object_class->set_property = sample_editor_set_property;
+
+  properties[PROP_SAMPLE_TEXT] =
+      g_param_spec_string ("sample-text", "", "",
+                           "",
+                           G_PARAM_READWRITE);
+
+  properties[PROP_EDITING] =
+      g_param_spec_boolean ("editing", "", "",
+                            FALSE,
+                            G_PARAM_READWRITE);
+
+  g_object_class_install_properties (G_OBJECT_CLASS (class), NUM_PROPERTIES, properties);
+
+  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
+                                               "/org/gtk/fontexplorer/sampleeditor.ui");
+
+  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), SampleEditor, edit);
+
+  gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (class), "sampleeditor");
+}
diff --git a/demos/font-explorer/sampleeditor.h b/demos/font-explorer/sampleeditor.h
new file mode 100644
index 0000000000..3779b78cff
--- /dev/null
+++ b/demos/font-explorer/sampleeditor.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <gtk/gtk.h>
+
+
+#define SAMPLE_EDITOR_TYPE (sample_editor_get_type ())
+#define SAMPLE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SAMPLE_EDITOR_TYPE, SampleEditor))
+
+
+typedef struct _SampleEditor         SampleEditor;
+typedef struct _SampleEditorClass    SampleEditorClass;
+
+
+GType           sample_editor_get_type     (void);
diff --git a/demos/font-explorer/sampleeditor.ui b/demos/font-explorer/sampleeditor.ui
new file mode 100644
index 0000000000..77d1f71f91
--- /dev/null
+++ b/demos/font-explorer/sampleeditor.ui
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="SampleEditor" parent="GtkWidget">
+    <style>
+      <class name="view"/>
+    </style>
+    <child>
+      <object class="GtkScrolledWindow">
+        <property name="hscrollbar-policy">never</property>
+        <property name="vscrollbar-policy">automatic</property>
+        <property name="hexpand">1</property>
+        <property name="vexpand">1</property>
+        <child>
+          <object class="GtkTextView" id="edit">
+            <property name="wrap-mode">word-char</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/demos/font-explorer/waterfallview.c b/demos/font-explorer/waterfallview.c
new file mode 100644
index 0000000000..0689a63cdc
--- /dev/null
+++ b/demos/font-explorer/waterfallview.c
@@ -0,0 +1,362 @@
+#include "waterfallview.h"
+#include "glyphitem.h"
+#include "glyphmodel.h"
+#include "glyphview.h"
+#include <gtk/gtk.h>
+
+#include <hb-ot.h>
+
+enum {
+  PROP_FONT_MAP = 1,
+  PROP_FONT_DESC,
+  PROP_SIZE,
+  PROP_LETTERSPACING,
+  PROP_LINE_HEIGHT,
+  PROP_FOREGROUND,
+  PROP_BACKGROUND,
+  PROP_VARIATIONS,
+  PROP_FEATURES,
+  PROP_PALETTE,
+  PROP_SAMPLE_TEXT,
+  NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+struct _WaterfallView
+{
+  GtkWidget parent;
+
+  GtkLabel *content;
+  GtkScrolledWindow *swin;
+
+  Pango2FontMap *font_map;
+  Pango2FontDescription *font_desc;
+  float size;
+  char *variations;
+  char *features;
+  char *palette;
+  GQuark palette_quark;
+  int letterspacing;
+  float line_height;
+  GdkRGBA foreground;
+  GdkRGBA background;
+  GtkCssProvider *bg_provider;
+  char *sample_text;
+};
+
+struct _WaterfallViewClass
+{
+  GtkWidgetClass parent_class;
+};
+
+G_DEFINE_TYPE(WaterfallView, waterfall_view, GTK_TYPE_WIDGET);
+
+static void
+waterfall_view_init (WaterfallView *self)
+{
+  self->font_map = g_object_ref (pango2_font_map_get_default ());
+  self->font_desc = pango2_font_description_from_string ("sans 12");
+  self->size = 12.;
+  self->letterspacing = 0;
+  self->line_height = 1.;
+  self->variations = g_strdup ("");
+  self->features = g_strdup ("");
+  self->palette = g_strdup (PANGO2_COLOR_PALETTE_DEFAULT);
+  self->palette_quark = g_quark_from_string (self->palette);
+  self->foreground = (GdkRGBA){0., 0., 0., 1. };
+  self->background = (GdkRGBA){1., 1., 1., 1. };
+  self->sample_text = g_strdup ("Some sample text is better than other sample text");
+
+  gtk_widget_set_layout_manager (GTK_WIDGET (self),
+                                 gtk_box_layout_new (GTK_ORIENTATION_VERTICAL));
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  self->bg_provider = gtk_css_provider_new ();
+  gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (self->content)),
+                                  GTK_STYLE_PROVIDER (self->bg_provider), 800);
+}
+
+static void
+waterfall_view_dispose (GObject *object)
+{
+  gtk_widget_clear_template (GTK_WIDGET (object), WATERFALL_VIEW_TYPE);
+
+  G_OBJECT_CLASS (waterfall_view_parent_class)->dispose (object);
+}
+
+static void
+waterfall_view_finalize (GObject *object)
+{
+  WaterfallView *self = WATERFALL_VIEW (object);
+
+  g_clear_object (&self->font_map);
+  pango2_font_description_free (self->font_desc);
+  g_free (self->variations);
+  g_free (self->features);
+  g_free (self->palette);
+
+  G_OBJECT_CLASS (waterfall_view_parent_class)->finalize (object);
+}
+
+static void
+update_view (WaterfallView *self)
+{
+  Pango2FontDescription *desc;
+  Pango2AttrList *attrs;
+  char *fg, *bg, *css;
+  GString *str;
+  int sizes[] = { 7, 8, 9, 10, 12, 14, 16, 20, 24, 30, 40, 50, 60, 70, 90 };
+  int start, end, text_len;
+
+  desc = pango2_font_description_copy_static (self->font_desc);
+  pango2_font_description_set_size (desc, 12 * PANGO2_SCALE);
+  pango2_font_description_set_variations (desc, self->variations);
+
+  attrs = pango2_attr_list_new ();
+  pango2_attr_list_insert (attrs, pango2_attr_font_desc_new (desc));
+  pango2_attr_list_insert (attrs, pango2_attr_size_new (self->size * PANGO2_SCALE));
+  pango2_attr_list_insert (attrs, pango2_attr_letter_spacing_new (self->letterspacing));
+  pango2_attr_list_insert (attrs, pango2_attr_line_height_new (self->line_height));
+  pango2_attr_list_insert (attrs, pango2_attr_foreground_new (&(Pango2Color){65535 * self->foreground.red,
+                                                                             65535 * self->foreground.green,
+                                                                             65535 * self->foreground.blue,
+                                                                             65535 * 
self->foreground.alpha}));
+  pango2_attr_list_insert (attrs, pango2_attr_font_features_new (self->features));
+  pango2_attr_list_insert (attrs, pango2_attr_palette_new (self->palette));
+
+  pango2_font_description_free (desc);
+
+  str = g_string_new ("");
+  start = 0;
+  text_len = strlen (self->sample_text);
+  for (int i = 0; i < G_N_ELEMENTS (sizes); i++)
+    {
+      Pango2Attribute *attr;
+
+      g_string_append (str, self->sample_text);
+      g_string_append (str, "
"); /* Unicode line separator */
+      end = start + text_len + strlen ("
");
+
+      attr = pango2_attr_size_new (sizes[i] * PANGO2_SCALE);
+      pango2_attribute_set_range (attr, start, end);
+      pango2_attr_list_insert (attrs, attr);
+      start = end;
+    }
+  gtk_label_set_text (self->content, str->str);
+  gtk_label_set_attributes (self->content, attrs);
+  g_string_free (str, TRUE);
+
+  pango2_attr_list_unref (attrs);
+
+  fg = gdk_rgba_to_string (&self->foreground);
+  bg = gdk_rgba_to_string (&self->background);
+  css = g_strdup_printf (".view_background { caret-color: %s; background-color: %s; }", fg, bg);
+  gtk_css_provider_load_from_data (self->bg_provider, css, strlen (css));
+  g_free (css);
+  g_free (fg);
+  g_free (bg);
+}
+
+static void
+waterfall_view_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  WaterfallView *self = WATERFALL_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_FONT_MAP:
+      g_set_object (&self->font_map, g_value_get_object (value));
+      gtk_widget_set_font_map (GTK_WIDGET (self->content), self->font_map);
+      break;
+
+    case PROP_FONT_DESC:
+      pango2_font_description_free (self->font_desc);
+      self->font_desc = pango2_font_description_copy (g_value_get_boxed (value));
+      break;
+
+    case PROP_SIZE:
+      self->size = g_value_get_float (value);
+      break;
+
+    case PROP_LETTERSPACING:
+      self->letterspacing = g_value_get_int (value);
+      break;
+
+    case PROP_LINE_HEIGHT:
+      self->line_height = g_value_get_float (value);
+      break;
+
+    case PROP_FOREGROUND:
+      self->foreground = *(GdkRGBA *)g_value_get_boxed (value);
+      break;
+
+    case PROP_BACKGROUND:
+      self->background = *(GdkRGBA *)g_value_get_boxed (value);
+      break;
+
+    case PROP_VARIATIONS:
+      g_free (self->variations);
+      self->variations = g_strdup (g_value_get_string (value));
+      break;
+
+    case PROP_FEATURES:
+      g_free (self->features);
+      self->features = g_strdup (g_value_get_string (value));
+      break;
+
+    case PROP_PALETTE:
+      g_free (self->palette);
+      self->palette = g_strdup (g_value_get_string (value));
+      self->palette_quark = g_quark_from_string (self->palette);
+      break;
+
+    case PROP_SAMPLE_TEXT:
+      g_free (self->sample_text);
+      self->sample_text = g_strdup (g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+
+  update_view (self);
+}
+
+static void
+waterfall_view_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  WaterfallView *self = WATERFALL_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_FONT_MAP:
+      g_value_set_object (value, self->font_map);
+      break;
+
+    case PROP_FONT_DESC:
+      g_value_set_boxed (value, self->font_desc);
+      break;
+
+    case PROP_SIZE:
+      g_value_set_float (value, self->size);
+      break;
+
+    case PROP_LETTERSPACING:
+      g_value_set_int (value, self->letterspacing);
+      break;
+
+    case PROP_LINE_HEIGHT:
+      g_value_set_float (value, self->line_height);
+      break;
+
+    case PROP_FOREGROUND:
+      g_value_set_boxed (value, &self->foreground);
+      break;
+
+    case PROP_BACKGROUND:
+      g_value_set_boxed (value, &self->background);
+      break;
+
+    case PROP_VARIATIONS:
+      g_value_set_string (value, self->variations);
+      break;
+
+    case PROP_FEATURES:
+      g_value_set_string (value, self->features);
+      break;
+
+    case PROP_PALETTE:
+      g_value_set_string (value, self->palette);
+      break;
+
+    case PROP_SAMPLE_TEXT:
+      g_value_set_string (value, self->sample_text);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+waterfall_view_class_init (WaterfallViewClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->dispose = waterfall_view_dispose;
+  object_class->finalize = waterfall_view_finalize;
+  object_class->get_property = waterfall_view_get_property;
+  object_class->set_property = waterfall_view_set_property;
+
+  properties[PROP_FONT_MAP] =
+      g_param_spec_object ("font-map", "", "",
+                           PANGO2_TYPE_FONT_MAP,
+                           G_PARAM_READWRITE);
+
+  properties[PROP_FONT_DESC] =
+      g_param_spec_boxed ("font-desc", "", "",
+                          PANGO2_TYPE_FONT_DESCRIPTION,
+                          G_PARAM_READWRITE);
+
+  properties[PROP_SIZE] =
+      g_param_spec_float ("size", "", "",
+                          0., 100., 12.,
+                          G_PARAM_READWRITE);
+
+  properties[PROP_LETTERSPACING] =
+      g_param_spec_int ("letterspacing", "", "",
+                        -G_MAXINT, G_MAXINT, 0,
+                        G_PARAM_READWRITE);
+
+  properties[PROP_LINE_HEIGHT] =
+      g_param_spec_float ("line-height", "", "",
+                          0., 100., 1.,
+                          G_PARAM_READWRITE);
+
+  properties[PROP_FOREGROUND] =
+      g_param_spec_boxed ("foreground", "", "",
+                          GDK_TYPE_RGBA,
+                          G_PARAM_READWRITE);
+
+  properties[PROP_BACKGROUND] =
+      g_param_spec_boxed ("background", "", "",
+                          GDK_TYPE_RGBA,
+                          G_PARAM_READWRITE);
+
+  properties[PROP_VARIATIONS] =
+      g_param_spec_string ("variations", "", "",
+                           "",
+                           G_PARAM_READWRITE);
+
+  properties[PROP_FEATURES] =
+      g_param_spec_string ("features", "", "",
+                           "",
+                           G_PARAM_READWRITE);
+
+  properties[PROP_PALETTE] =
+      g_param_spec_string ("palette", "", "",
+                           PANGO2_COLOR_PALETTE_DEFAULT,
+                           G_PARAM_READWRITE);
+
+  properties[PROP_SAMPLE_TEXT] =
+      g_param_spec_string ("sample-text", "", "",
+                           "",
+                           G_PARAM_READWRITE);
+
+  g_object_class_install_properties (G_OBJECT_CLASS (class), NUM_PROPERTIES, properties);
+
+  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
+                                               "/org/gtk/fontexplorer/waterfallview.ui");
+
+  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), WaterfallView, swin);
+  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), WaterfallView, content);
+
+  gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (class), "waterfallview");
+}
diff --git a/demos/font-explorer/waterfallview.h b/demos/font-explorer/waterfallview.h
new file mode 100644
index 0000000000..98208178ae
--- /dev/null
+++ b/demos/font-explorer/waterfallview.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <gtk/gtk.h>
+
+
+#define WATERFALL_VIEW_TYPE (waterfall_view_get_type ())
+#define WATERFALL_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WATERFALL_VIEW_TYPE, WaterfallView))
+
+
+typedef struct _WaterfallView         WaterfallView;
+typedef struct _WaterfallViewClass    WaterfallViewClass;
+
+
+GType           waterfall_view_get_type     (void);
diff --git a/demos/font-explorer/waterfallview.ui b/demos/font-explorer/waterfallview.ui
new file mode 100644
index 0000000000..d70c97dddf
--- /dev/null
+++ b/demos/font-explorer/waterfallview.ui
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="WaterfallView" parent="GtkWidget">
+    <property name="hexpand">1</property>
+    <property name="vexpand">1</property>
+    <style>
+      <class name="view"/>
+    </style>
+    <child>
+      <object class="GtkScrolledWindow" id="swin">
+        <property name="hscrollbar-policy">automatic</property>
+        <property name="vscrollbar-policy">automatic</property>
+        <child>
+          <object class="GtkLabel" id="content">
+            <property name="label">Content</property>
+            <property name="wrap">0</property>
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
+            <property name="hexpand">1</property>
+            <property name="vexpand">1</property>
+            <property name="halign">fill</property>
+            <property name="valign">fill</property>
+            <style>
+              <class name="view_background"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>


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