[gnome-builder/wip/panel] wip: panel-gtk



commit ef9de4d14bf3b9adfbe7ad93089ac883d5bf1027
Author: Christian Hergert <chergert redhat com>
Date:   Tue Mar 22 18:51:53 2016 -0700

    wip: panel-gtk

 Makefile.am                                        |   10 +-
 configure.ac                                       |    6 +
 contrib/Makefile.am                                |    1 +
 contrib/pnl/Makefile.am                            |  136 ++
 contrib/pnl/pnl-animation.c                        | 1164 ++++++++++++
 contrib/pnl/pnl-animation.h                        |   78 +
 contrib/pnl/pnl-dock-bin-edge-private.h            |   32 +
 contrib/pnl/pnl-dock-bin-edge.c                    |  259 +++
 contrib/pnl/pnl-dock-bin-edge.h                    |   48 +
 contrib/pnl/pnl-dock-bin.c                         | 1922 ++++++++++++++++++++
 contrib/pnl/pnl-dock-bin.h                         |   55 +
 contrib/pnl/pnl-dock-item.c                        |  456 +++++
 contrib/pnl/pnl-dock-item.h                        |   65 +
 contrib/pnl/pnl-dock-manager.c                     |  306 ++++
 contrib/pnl/pnl-dock-manager.h                     |   57 +
 contrib/pnl/pnl-dock-overlay-edge-private.h        |   39 +
 contrib/pnl/pnl-dock-overlay-edge.c                |  290 +++
 contrib/pnl/pnl-dock-overlay.c                     |  704 +++++++
 contrib/pnl/pnl-dock-overlay.h                     |   50 +
 contrib/pnl/pnl-dock-paned-private.h               |   31 +
 contrib/pnl/pnl-dock-paned.c                       |  134 ++
 contrib/pnl/pnl-dock-paned.h                       |   48 +
 contrib/pnl/pnl-dock-revealer.c                    |  779 ++++++++
 contrib/pnl/pnl-dock-revealer.h                    |   76 +
 contrib/pnl/pnl-dock-stack.c                       |  320 ++++
 contrib/pnl/pnl-dock-stack.h                       |   42 +
 contrib/pnl/pnl-dock-tab-strip.c                   |   41 +
 contrib/pnl/pnl-dock-tab-strip.h                   |   34 +
 contrib/pnl/pnl-dock-transient-grab.c              |  322 ++++
 contrib/pnl/pnl-dock-transient-grab.h              |   50 +
 contrib/pnl/pnl-dock-types.h                       |   56 +
 contrib/pnl/pnl-dock-widget.c                      |  184 ++
 contrib/pnl/pnl-dock-widget.h                      |   51 +
 contrib/pnl/pnl-dock-window.c                      |  113 ++
 contrib/pnl/pnl-dock-window.h                      |   39 +
 contrib/pnl/pnl-dock.c                             |   43 +
 contrib/pnl/pnl-dock.h                             |   37 +
 contrib/pnl/pnl-frame-source.c                     |  130 ++
 contrib/pnl/pnl-frame-source.h                     |   36 +
 contrib/pnl/pnl-multi-paned.c                      | 1863 +++++++++++++++++++
 contrib/pnl/pnl-multi-paned.h                      |   59 +
 contrib/pnl/pnl-tab-strip.c                        |  538 ++++++
 contrib/pnl/pnl-tab-strip.h                        |   53 +
 contrib/pnl/pnl-tab.c                              |  275 +++
 contrib/pnl/pnl-tab.h                              |   47 +
 contrib/pnl/pnl-util-private.h                     |   39 +
 contrib/pnl/pnl-util.c                             |  109 ++
 contrib/pnl/pnl-version.h.in                       |   97 +
 contrib/pnl/pnl.gresource.xml                      |    8 +
 contrib/pnl/pnl.h                                  |   52 +
 .../pnl/resources/panel-bottom-pane-symbolic.svg   |   26 +
 contrib/pnl/resources/panel-left-pane-symbolic.svg |   26 +
 .../pnl/resources/panel-right-pane-symbolic.svg    |   26 +
 data/libide-1.0.pc.in                              |    4 +-
 data/theme/Adwaita-dark.css                        |    6 +-
 data/theme/Adwaita-panels.css                      |   50 +
 data/theme/Adwaita-shared.css                      |   22 +-
 data/theme/Adwaita.css                             |    6 +-
 data/ui/ide-editor-perspective.ui                  |  104 +-
 data/ui/ide-layout-pane.ui                         |   27 +-
 data/ui/ide-layout.ui                              |   30 -
 doc/reference/libide/libide-sections.txt           |    4 -
 libide/Makefile.am                                 |    8 +
 libide/editor/ide-editor-perspective.c             |   52 +-
 libide/editor/ide-editor-workbench-addin.c         |    6 +
 libide/ide-application-actions.c                   |    6 +-
 libide/ide-layout-pane.c                           |  312 +---
 libide/ide-layout-pane.h                           |   15 +-
 libide/ide-layout-stack.c                          |    6 +
 libide/ide-layout.c                                | 1290 +-------------
 libide/ide-layout.h                                |   15 +-
 libide/ide-workbench.c                             |    6 +-
 libide/resources/libide.gresource.xml              |    2 +-
 plugins/build-tools/gbp-build-log-panel.c          |    6 +-
 plugins/build-tools/gbp-build-log-panel.h          |    2 +-
 plugins/build-tools/gbp-build-log-panel.ui         |    2 +-
 plugins/build-tools/gbp-build-panel.c              |    6 +-
 plugins/build-tools/gbp-build-panel.h              |    2 +-
 plugins/build-tools/gbp-build-panel.ui             |    2 +-
 plugins/build-tools/gbp-build-workbench-addin.c    |   16 +-
 plugins/devhelp/gbp-devhelp-editor-view-addin.c    |    2 +-
 plugins/devhelp/gbp-devhelp-panel.c                |   11 +-
 plugins/devhelp/gbp-devhelp-panel.h                |    4 +-
 plugins/devhelp/gbp-devhelp-search-provider.c      |    2 +-
 plugins/devhelp/gbp-devhelp-workbench-addin.c      |   11 +-
 plugins/devhelp/theme/shared.css                   |    7 +-
 plugins/project-tree/gb-project-tree-addin.c       |   37 +-
 plugins/symbol-tree/symbol-tree-panel.c            |    7 +-
 plugins/symbol-tree/symbol-tree-panel.h            |    4 +-
 plugins/symbol-tree/symbol-tree-panel.ui           |   31 +-
 plugins/symbol-tree/symbol-tree.c                  |   17 +-
 plugins/sysmon/gb-sysmon-addin.c                   |   16 +-
 plugins/sysmon/gb-sysmon-panel.c                   |    7 +-
 plugins/sysmon/gb-sysmon-panel.h                   |    4 +-
 plugins/sysmon/gb-sysmon-panel.ui                  |    3 +-
 plugins/terminal/gb-terminal-workbench-addin.c     |   27 +-
 plugins/todo/todo_plugin/__init__.py               |   10 +-
 src/Makefile.am                                    |    3 +
 tests/Makefile.am                                  |    2 +
 99 files changed, 11787 insertions(+), 1887 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 8730dad..d9d7bdb 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -67,12 +67,14 @@ dist-hook:
 
 .PHONY: AUTHORS
 
+RUNTIME_TYPELIB_PATH = libide:contrib/egg:contrib/pnl:contrib/tmpl:$(GI_TYPELIB_PATH)
+
 run:
        PEAS_DEBUG=1 \
        GB_IN_TREE_PLUGINS=1 \
        GB_IN_TREE_FONTS=1 \
        GB_IN_TREE_STYLE_SCHEMES=1 \
-       GI_TYPELIB_PATH="libide:contrib/egg:contrib/tmpl:$(GI_TYPELIB_PATH)" \
+       GI_TYPELIB_PATH="$(RUNTIME_TYPELIB_PATH)" \
        GOBJECT_DEBUG=instance-count \
        PYTHONDONTWRITEBYTECODE=yes \
        PATH=$(top_builddir)/src:${PATH} \
@@ -83,7 +85,7 @@ strace:
        GB_IN_TREE_PLUGINS=1 \
        GB_IN_TREE_FONTS=1 \
        GB_IN_TREE_STYLE_SCHEMES=1 \
-       GI_TYPELIB_PATH="libide:contrib/egg:contrib/tmpl:$(GI_TYPELIB_PATH)" \
+       GI_TYPELIB_PATH="$(RUNTIME_TYPELIB_PATH)" \
        GOBJECT_DEBUG=instance-count \
        PYTHONDONTWRITEBYTECODE=yes \
        PATH=$(top_builddir)/src:${PATH} \
@@ -94,7 +96,7 @@ debug:
        GB_IN_TREE_PLUGINS=1 \
        GB_IN_TREE_FONTS=1 \
        GB_IN_TREE_STYLE_SCHEMES=1 \
-       GI_TYPELIB_PATH="libide:contrib/egg:contrib/tmpl:$(GI_TYPELIB_PATH)" \
+       GI_TYPELIB_PATH="$(RUNTIME_TYPELIB_PATH)" \
        G_DEBUG=fatal-criticals \
        GOBJECT_DEBUG=instance-count \
        PYTHONDONTWRITEBYTECODE=yes \
@@ -106,7 +108,7 @@ valgrind:
        GB_IN_TREE_PLUGINS=1 \
        GB_IN_TREE_FONTS=1 \
        GB_IN_TREE_STYLE_SCHEMES=1 \
-       GI_TYPELIB_PATH="libide:contrib/egg:contrib/tmpl:$(GI_TYPELIB_PATH)" \
+       GI_TYPELIB_PATH="$(RUNTIME_TYPELIB_PATH)" \
        G_DEBUG=fatal-criticals \
        G_SLICE=always-malloc \
        PYTHONDONTWRITEBYTECODE=yes \
diff --git a/configure.ac b/configure.ac
index d1942c5..1b448f7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -197,6 +197,7 @@ PKG_CHECK_MODULES(LIBIDE,   [gio-2.0 >= glib_required_version
                              pangoft2 >= pangoft2_required_version])
 PKG_CHECK_MODULES(NAUTILUS, [glib-2.0 >= glib_required_version
                              gtk+-3.0 >= gtk_required_version])
+PKG_CHECK_MODULES(PANEL_GTK,[gtk+-3.0 >= gtk_required_version])
 PKG_CHECK_MODULES(PYGOBJECT,[pygobject-3.0 >= pygobject_required_version],
                             [have_pygobject=yes],
                             [have_pygobject=no])
@@ -395,10 +396,12 @@ dnl ***********************************************************************
 dnl Setup common cflags and ldflags for plugins
 dnl ***********************************************************************
 PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_builddir)/contrib/egg"
+PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_builddir)/contrib/pnl"
 PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_builddir)/libide"
 PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/egg"
 PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/gd"
 PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/nautilus"
+PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/pnl"
 PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/rg"
 PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/search"
 PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/xml"
@@ -423,6 +426,7 @@ PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --target-glib=2.44"
 PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --thread"
 PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --vapidir \$(top_builddir)/libide"
 PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --vapidir \$(top_builddir)/contrib/egg"
+PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --vapidir \$(top_builddir)/contrib/pnl"
 PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --vapidir \$(top_builddir)/contrib/tmpl"
 PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --pkg libide-1.0"
 PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --pkg libpeas-1.0"
@@ -443,6 +447,8 @@ AC_CONFIG_FILES([
        contrib/gd/Makefile
        contrib/libeditorconfig/Makefile
        contrib/nautilus/Makefile
+       contrib/pnl/Makefile
+       contrib/pnl/pnl-version.h
        contrib/rg/Makefile
        contrib/search/Makefile
        contrib/tmpl/Makefile
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
index 9ed79c4..516886b 100644
--- a/contrib/Makefile.am
+++ b/contrib/Makefile.am
@@ -3,6 +3,7 @@ SUBDIRS = \
        gd \
        libeditorconfig \
        nautilus \
+       pnl \
        rg \
        search \
        tmpl \
diff --git a/contrib/pnl/Makefile.am b/contrib/pnl/Makefile.am
new file mode 100644
index 0000000..bfc42e0
--- /dev/null
+++ b/contrib/pnl/Makefile.am
@@ -0,0 +1,136 @@
+CLEANFILES =
+DISTCLEANFILES =
+EXTRA_DIST =
+BUILT_SOURCES =
+
+pkglibdir = $(libdir)/gnome-builder
+pkglib_LTLIBRARIES = libpanel-gtk.la
+
+headersdir = $(includedir)/gnome-builder- VERSION@/pnl
+headers_DATA = \
+       pnl-animation.h \
+       pnl-dock-bin.h \
+       pnl-dock-bin-edge.h \
+       pnl-dock-item.h \
+       pnl-dock-manager.h \
+       pnl-dock-overlay.h \
+       pnl-dock-paned.h \
+       pnl-dock-revealer.h \
+       pnl-dock-stack.h \
+       pnl-dock-types.h \
+       pnl-dock-widget.h \
+       pnl-dock-window.h \
+       pnl-dock.h \
+       pnl-frame-source.h \
+       pnl-multi-paned.h \
+       pnl-tab-strip.h \
+       pnl-tab.h \
+       pnl-version.h \
+       pnl.h \
+       $(NULL)
+
+libpanel_gtk_la_SOURCES = \
+       $(headers_DATA) \
+       pnl-animation.c \
+       pnl-dock-bin-edge.c \
+       pnl-dock-bin-edge-private.h \
+       pnl-dock-bin.c \
+       pnl-dock-item.c \
+       pnl-dock-manager.c \
+       pnl-dock-overlay-edge-private.h \
+       pnl-dock-overlay-edge.c \
+       pnl-dock-overlay.c \
+       pnl-dock-paned-private.h \
+       pnl-dock-paned.c \
+       pnl-dock-revealer.c \
+       pnl-dock-stack.c \
+       pnl-dock-tab-strip.c \
+       pnl-dock-tab-strip.h \
+       pnl-dock-transient-grab.c \
+       pnl-dock-transient-grab.h \
+       pnl-dock-widget.c \
+       pnl-dock-window.c \
+       pnl-dock.c \
+       pnl-frame-source.c \
+       pnl-multi-paned.c \
+       pnl-tab-strip.c \
+       pnl-tab.c \
+       pnl-util-private.h \
+       pnl-util.c \
+       $(NULL)
+
+nodist_libpanel_gtk_la_SOURCES = pnl-resources.c pnl-resources.h
+
+libpanel_gtk_la_CFLAGS = \
+       -DPNL_COMPILATION \
+       $(PANEL_GTK_CFLAGS) \
+       $(NULL)
+
+libpanel_gtk_la_LIBADD = \
+       $(PANEL_GTK_LIBS) \
+       $(NULL)
+
+libpanel_gtk_la_LDFLAGS = \
+       --no-undefined \
+       $(NULL)
+
+
+glib_resources_h = pnl-resources.h
+glib_resources_c = pnl-resources.c
+glib_resources_xml = pnl.gresource.xml
+glib_resources_namespace = pnl
+include $(top_srcdir)/build/autotools/Makefile.am.gresources
+
+
+if HAVE_INTROSPECTION
+-include $(INTROSPECTION_MAKEFILE)
+
+INTROSPECTION_GIRS =
+INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) --warn-all
+INTROSPECTION_COMPILER_ARGS = --includedir=$(srcdir)
+
+Pnl-1.0.gir: libpanel-gtk.la
+Pnl_1_0_gir_INCLUDES = Gio-2.0 Gdk-3.0 Gtk-3.0
+Pnl_1_0_gir_CFLAGS = $(libpanel_gtk_la_CFLAGS) -DPNL_COMPILATION
+Pnl_1_0_gir_LIBS = libpanel-gtk.la
+Pnl_1_0_gir_FILES = $(libpanel_gtk_la_SOURCES)
+Pnl_1_0_gir_SCANNERFLAGS = \
+       --c-include="pnl.h" \
+       -n Pnl \
+       $(NULL)
+INTROSPECTION_GIRS += Pnl-1.0.gir
+
+girdir = $(datadir)/gnome-builder/gir-1.0
+dist_gir_DATA = $(INTROSPECTION_GIRS)
+
+typelibdir = $(pkglibdir)/girepository-1.0
+typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)
+
+CLEANFILES += $(dist_gir_DATA) $(typelib_DATA)
+endif
+
+
+if ENABLE_VAPIGEN
+-include $(VAPIGEN_MAKEFILE)
+
+panel-gtk.vapi: Pnl-1.0.gir
+
+VAPIGEN_VAPIS = panel-gtk.vapi
+
+panel_gtk_vapi_DEPS = gio-2.0 gtk+-3.0
+panel_gtk_vapi_METADATADIRS = $(srcdir)
+panel_gtk_vapi_FILES = Pnl-1.0.gir
+
+panel-gtk.deps: Makefile
+       $(AM_V_GEN) echo $(libpanel_gtk_vapi_DEPS) | tr ' ' '\n' > $@
+
+vapidir = $(datadir)/gnome-builder/vapi
+vapi_DATA = $(VAPIGEN_VAPIS) $(VAPIGEN_VAPIS:.vapi=.deps)
+
+EXTRA_DIST += panel-gtk.deps
+
+DISTCLEANFILES += $(vapi_DATA)
+endif
+
+
+-include $(top_srcdir)/git.mk
diff --git a/contrib/pnl/pnl-animation.c b/contrib/pnl/pnl-animation.c
new file mode 100644
index 0000000..e0a15d6
--- /dev/null
+++ b/contrib/pnl/pnl-animation.c
@@ -0,0 +1,1164 @@
+/* pnl-animation.c
+ *
+ * Copyright (C) 2010-2016 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+#include <gobject/gvaluecollector.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pnl-animation.h"
+#include "pnl-frame-source.h"
+
+#define FALLBACK_FRAME_RATE 60
+
+typedef gdouble (*AlphaFunc) (gdouble       offset);
+typedef void    (*TweenFunc) (const GValue *begin,
+                              const GValue *end,
+                              GValue       *value,
+                              gdouble       offset);
+
+typedef struct
+{
+  gboolean    is_child;  /* Does GParamSpec belong to parent widget */
+  GParamSpec *pspec;     /* GParamSpec of target property */
+  GValue      begin;     /* Begin value in animation */
+  GValue      end;       /* End value in animation */
+} Tween;
+
+
+struct _PnlAnimation
+{
+  GInitiallyUnowned  parent_instance;
+
+  gpointer           target;              /* Target object to animate */
+  guint64            begin_msec;          /* Time in which animation started */
+  guint              duration_msec;       /* Duration of animation */
+  guint              mode;                /* Tween mode */
+  gulong             tween_handler;       /* GSource or signal handler */
+  gdouble            last_offset;         /* Track our last offset */
+  GArray            *tweens;              /* Array of tweens to perform */
+  GdkFrameClock     *frame_clock;         /* An optional frame-clock for sync. */
+  GDestroyNotify     notify;              /* Notify callback */
+  gpointer           notify_data;         /* Data for notify */
+  guint              debug_ticks;         /* Number of tick updates */
+};
+
+G_DEFINE_TYPE (PnlAnimation, pnl_animation, G_TYPE_INITIALLY_UNOWNED)
+
+enum {
+  PROP_0,
+  PROP_DURATION,
+  PROP_FRAME_CLOCK,
+  PROP_MODE,
+  PROP_TARGET,
+  N_PROPS
+};
+
+
+enum {
+  TICK,
+  N_SIGNALS
+};
+
+
+/*
+ * Helper macros.
+ */
+#define LAST_FUNDAMENTAL 64
+#define TWEEN(type)                                       \
+  static void                                             \
+  tween_ ## type (const GValue * begin,                   \
+                  const GValue * end,                     \
+                  GValue * value,                         \
+                  gdouble offset)                         \
+  {                                                       \
+    g ## type x = g_value_get_ ## type (begin);           \
+    g ## type y = g_value_get_ ## type (end);             \
+    g_value_set_ ## type (value, x + ((y - x) * offset)); \
+  }
+
+
+/*
+ * Globals.
+ */
+static AlphaFunc   alpha_funcs[PNL_ANIMATION_LAST];
+static gboolean    debug;
+static GParamSpec *properties[N_PROPS];
+static guint       signals[N_SIGNALS];
+static TweenFunc   tween_funcs[LAST_FUNDAMENTAL];
+static guint       slow_down_factor = 1;
+
+
+/*
+ * Tweeners for basic types.
+ */
+TWEEN (int);
+TWEEN (uint);
+TWEEN (long);
+TWEEN (ulong);
+TWEEN (float);
+TWEEN (double);
+
+
+/**
+ * pnl_animation_alpha_ease_in_cubic:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @PNL_ANIMATION_CUBIC means the valu ewill be transformed into
+ * cubic acceleration (x * x * x).
+ */
+static gdouble
+pnl_animation_alpha_ease_in_cubic (gdouble offset)
+{
+  return offset * offset * offset;
+}
+
+
+static gdouble
+pnl_animation_alpha_ease_out_cubic (gdouble offset)
+{
+  gdouble p = offset - 1.0;
+
+  return p * p * p + 1.0;
+}
+
+static gdouble
+pnl_animation_alpha_ease_in_out_cubic (gdouble offset)
+{
+  if (offset < .5)
+    return pnl_animation_alpha_ease_in_cubic (offset * 2.0) / 2.0;
+  else
+    return .5 + pnl_animation_alpha_ease_out_cubic ((offset - .5) * 2.0) / 2.0;
+}
+
+
+/**
+ * pnl_animation_alpha_linear:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @PNL_ANIMATION_LINEAR means no tranformation will be made.
+ *
+ * Returns: @offset.
+ * Side effects: None.
+ */
+static gdouble
+pnl_animation_alpha_linear (gdouble offset)
+{
+  return offset;
+}
+
+
+/**
+ * pnl_animation_alpha_ease_in_quad:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @PNL_ANIMATION_EASE_IN_QUAD means that the value will be transformed
+ * into a quadratic acceleration.
+ *
+ * Returns: A tranformation of @offset.
+ * Side effects: None.
+ */
+static gdouble
+pnl_animation_alpha_ease_in_quad (gdouble offset)
+{
+  return offset * offset;
+}
+
+
+/**
+ * pnl_animation_alpha_ease_out_quad:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @PNL_ANIMATION_EASE_OUT_QUAD means that the value will be transformed
+ * into a quadratic deceleration.
+ *
+ * Returns: A tranformation of @offset.
+ * Side effects: None.
+ */
+static gdouble
+pnl_animation_alpha_ease_out_quad (gdouble offset)
+{
+  return -1.0 * offset * (offset - 2.0);
+}
+
+
+/**
+ * pnl_animation_alpha_ease_in_out_quad:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @PNL_ANIMATION_EASE_IN_OUT_QUAD means that the value will be transformed
+ * into a quadratic acceleration for the first half, and quadratic
+ * deceleration the second half.
+ *
+ * Returns: A tranformation of @offset.
+ * Side effects: None.
+ */
+static gdouble
+pnl_animation_alpha_ease_in_out_quad (gdouble offset)
+{
+  offset *= 2.0;
+  if (offset < 1.0)
+    return 0.5 * offset * offset;
+  offset -= 1.0;
+  return -0.5 * (offset * (offset - 2.0) - 1.0);
+}
+
+
+/**
+ * pnl_animation_load_begin_values:
+ * @animation: (in): A #PnlAnimation.
+ *
+ * Load the begin values for all the properties we are about to
+ * animate.
+ *
+ * Side effects: None.
+ */
+static void
+pnl_animation_load_begin_values (PnlAnimation *animation)
+{
+  GtkContainer *container;
+  Tween *tween;
+  guint i;
+
+  g_return_if_fail (PNL_IS_ANIMATION (animation));
+
+  for (i = 0; i < animation->tweens->len; i++)
+    {
+      tween = &g_array_index (animation->tweens, Tween, i);
+      g_value_reset (&tween->begin);
+      if (tween->is_child)
+        {
+          container = GTK_CONTAINER (gtk_widget_get_parent (animation->target));
+          gtk_container_child_get_property (container,
+                                            animation->target,
+                                            tween->pspec->name,
+                                            &tween->begin);
+        }
+      else
+        {
+          g_object_get_property (animation->target,
+                                 tween->pspec->name,
+                                 &tween->begin);
+        }
+    }
+}
+
+
+/**
+ * pnl_animation_unload_begin_values:
+ * @animation: (in): A #PnlAnimation.
+ *
+ * Unloads the begin values for the animation. This might be particularly
+ * useful once we support pointer types.
+ *
+ * Side effects: None.
+ */
+static void
+pnl_animation_unload_begin_values (PnlAnimation *animation)
+{
+  Tween *tween;
+  guint i;
+
+  g_return_if_fail (PNL_IS_ANIMATION (animation));
+
+  for (i = 0; i < animation->tweens->len; i++)
+    {
+      tween = &g_array_index (animation->tweens, Tween, i);
+      g_value_reset (&tween->begin);
+    }
+}
+
+
+/**
+ * pnl_animation_get_offset:
+ * @animation: A #PnlAnimation.
+ * @frame_time: the time to present the frame, or 0 for current timing.
+ *
+ * Retrieves the position within the animation from 0.0 to 1.0. This
+ * value is calculated using the msec of the beginning of the animation
+ * and the current time.
+ *
+ * Returns: The offset of the animation from 0.0 to 1.0.
+ */
+static gdouble
+pnl_animation_get_offset (PnlAnimation *animation,
+                          gint64        frame_time)
+{
+  gdouble offset;
+  gint64 frame_msec;
+
+  g_return_val_if_fail (PNL_IS_ANIMATION (animation), 0.0);
+
+  if (frame_time == 0)
+    {
+      if (animation->frame_clock != NULL)
+        frame_time = gdk_frame_clock_get_frame_time (animation->frame_clock);
+      else
+        frame_time = g_get_monotonic_time ();
+    }
+
+  frame_msec = frame_time / 1000L;
+
+  offset = (gdouble) (frame_msec - animation->begin_msec) /
+           (gdouble) MAX (animation->duration_msec, 1);
+
+  return CLAMP (offset, 0.0, 1.0);
+}
+
+
+/**
+ * pnl_animation_update_property:
+ * @animation: (in): A #PnlAnimation.
+ * @target: (in): A #GObject.
+ * @tween: (in): a #Tween containing the property.
+ * @value: (in): The new value for the property.
+ *
+ * Updates the value of a property on an object using @value.
+ *
+ * Side effects: The property of @target is updated.
+ */
+static void
+pnl_animation_update_property (PnlAnimation  *animation,
+                              gpointer      target,
+                              Tween        *tween,
+                              const GValue *value)
+{
+  g_assert (PNL_IS_ANIMATION (animation));
+  g_assert (G_IS_OBJECT (target));
+  g_assert (tween);
+  g_assert (value);
+
+  g_object_set_property (target, tween->pspec->name, value);
+}
+
+
+/**
+ * pnl_animation_update_child_property:
+ * @animation: (in): A #PnlAnimation.
+ * @target: (in): A #GObject.
+ * @tween: (in): A #Tween containing the property.
+ * @value: (in): The new value for the property.
+ *
+ * Updates the value of the parent widget of the target to @value.
+ *
+ * Side effects: The property of @target<!-- -->'s parent widget is updated.
+ */
+static void
+pnl_animation_update_child_property (PnlAnimation *animation,
+                                     gpointer      target,
+                                     Tween        *tween,
+                                     const GValue *value)
+{
+  GtkWidget *parent;
+
+  g_assert (PNL_IS_ANIMATION (animation));
+  g_assert (G_IS_OBJECT (target));
+  g_assert (tween);
+  g_assert (value);
+
+  parent = gtk_widget_get_parent (GTK_WIDGET (target));
+  gtk_container_child_set_property (GTK_CONTAINER (parent),
+                                    target,
+                                    tween->pspec->name,
+                                    value);
+}
+
+
+/**
+ * pnl_animation_get_value_at_offset:
+ * @animation: (in): A #PnlAnimation.
+ * @offset: (in): The offset in the animation from 0.0 to 1.0.
+ * @tween: (in): A #Tween containing the property.
+ * @value: (out): A #GValue in which to store the property.
+ *
+ * Retrieves a value for a particular position within the animation.
+ *
+ * Side effects: None.
+ */
+static void
+pnl_animation_get_value_at_offset (PnlAnimation *animation,
+                                   gdouble       offset,
+                                   Tween        *tween,
+                                   GValue       *value)
+{
+  g_return_if_fail (PNL_IS_ANIMATION (animation));
+  g_return_if_fail (tween != NULL);
+  g_return_if_fail (value != NULL);
+  g_return_if_fail (value->g_type == tween->pspec->value_type);
+
+  if (value->g_type < LAST_FUNDAMENTAL)
+    {
+      /*
+       * If you hit the following assertion, you need to add a function
+       * to create the new value at the given offset.
+       */
+      g_assert (tween_funcs[value->g_type]);
+      tween_funcs[value->g_type](&tween->begin, &tween->end, value, offset);
+    }
+  else
+    {
+      /*
+       * TODO: Support complex transitions.
+       */
+      if (offset >= 1.0)
+        g_value_copy (&tween->end, value);
+    }
+}
+
+static void
+pnl_animation_set_frame_clock (PnlAnimation  *animation,
+                               GdkFrameClock *frame_clock)
+{
+  if (animation->frame_clock != frame_clock)
+    {
+      g_clear_object (&animation->frame_clock);
+      animation->frame_clock = frame_clock ? g_object_ref (frame_clock) : NULL;
+    }
+}
+
+static void
+pnl_animation_set_target (PnlAnimation *animation,
+                          gpointer      target)
+{
+  g_assert (!animation->target);
+
+  animation->target = g_object_ref (target);
+
+  if (GTK_IS_WIDGET (animation->target))
+    pnl_animation_set_frame_clock (animation,
+                                  gtk_widget_get_frame_clock (animation->target));
+}
+
+
+/**
+ * pnl_animation_tick:
+ * @animation: (in): A #PnlAnimation.
+ *
+ * Moves the object properties to the next position in the animation.
+ *
+ * Returns: %TRUE if the animation has not completed; otherwise %FALSE.
+ * Side effects: None.
+ */
+static gboolean
+pnl_animation_tick (PnlAnimation *animation,
+                    gdouble       offset)
+{
+  gdouble alpha;
+  GValue value = { 0 };
+  Tween *tween;
+  guint i;
+
+  g_return_val_if_fail (PNL_IS_ANIMATION (animation), FALSE);
+
+  if (offset == animation->last_offset)
+    return offset < 1.0;
+
+  animation->debug_ticks++;
+
+  alpha = alpha_funcs[animation->mode](offset);
+
+  /*
+   * Update property values.
+   */
+  for (i = 0; i < animation->tweens->len; i++)
+    {
+      tween = &g_array_index (animation->tweens, Tween, i);
+      g_value_init (&value, tween->pspec->value_type);
+      pnl_animation_get_value_at_offset (animation, alpha, tween, &value);
+      if (!tween->is_child)
+        {
+          pnl_animation_update_property (animation,
+                                         animation->target,
+                                         tween,
+                                         &value);
+        }
+      else
+        {
+          pnl_animation_update_child_property (animation,
+                                               animation->target,
+                                               tween,
+                                               &value);
+        }
+      g_value_unset (&value);
+    }
+
+  /*
+   * Notify anyone interested in the tick signal.
+   */
+  g_signal_emit (animation, signals[TICK], 0);
+
+  /*
+   * Flush any outstanding events to the graphics server (in the case of X).
+   */
+#if !GTK_CHECK_VERSION (3, 13, 0)
+  if (GTK_IS_WIDGET (animation->target))
+    {
+      GdkWindow *window;
+
+      if ((window = gtk_widget_get_window (GTK_WIDGET (animation->target))))
+        gdk_window_flush (window);
+    }
+#endif
+
+  animation->last_offset = offset;
+
+  return offset < 1.0;
+}
+
+
+/**
+ * pnl_animation_timeout_cb:
+ * @user_data: (in): A #PnlAnimation.
+ *
+ * Timeout from the main loop to move to the next step of the animation.
+ *
+ * Returns: %TRUE until the animation has completed; otherwise %FALSE.
+ * Side effects: None.
+ */
+static gboolean
+pnl_animation_timeout_cb (gpointer user_data)
+{
+  PnlAnimation *animation = user_data;
+  gboolean ret;
+  gdouble offset;
+
+  offset = pnl_animation_get_offset (animation, 0);
+
+  if (!(ret = pnl_animation_tick (animation, offset)))
+    pnl_animation_stop (animation);
+
+  return ret;
+}
+
+
+static gboolean
+pnl_animation_widget_tick_cb (GdkFrameClock *frame_clock,
+                              PnlAnimation  *animation)
+{
+  gboolean ret = G_SOURCE_REMOVE;
+
+  g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
+  g_assert (PNL_IS_ANIMATION (animation));
+
+  if (animation->tween_handler)
+    {
+      gdouble offset;
+
+      offset = pnl_animation_get_offset (animation, 0);
+
+      if (!(ret = pnl_animation_tick (animation, offset)))
+        pnl_animation_stop (animation);
+    }
+
+  return ret;
+}
+
+
+/**
+ * pnl_animation_start:
+ * @animation: (in): A #PnlAnimation.
+ *
+ * Start the animation. When the animation stops, the internal reference will
+ * be dropped and the animation may be finalized.
+ *
+ * Side effects: None.
+ */
+void
+pnl_animation_start (PnlAnimation *animation)
+{
+  g_return_if_fail (PNL_IS_ANIMATION (animation));
+  g_return_if_fail (!animation->tween_handler);
+
+  g_object_ref_sink (animation);
+  pnl_animation_load_begin_values (animation);
+
+  if (animation->frame_clock)
+    {
+      animation->begin_msec = gdk_frame_clock_get_frame_time (animation->frame_clock) / 1000UL;
+      animation->tween_handler =
+        g_signal_connect (animation->frame_clock,
+                          "update",
+                          G_CALLBACK (pnl_animation_widget_tick_cb),
+                          animation);
+      gdk_frame_clock_begin_updating (animation->frame_clock);
+    }
+  else
+    {
+      animation->begin_msec = g_get_monotonic_time () / 1000UL;
+      animation->tween_handler = pnl_frame_source_add (FALLBACK_FRAME_RATE,
+                                                       pnl_animation_timeout_cb,
+                                                       animation);
+    }
+}
+
+
+static void
+pnl_animation_notify (PnlAnimation *self)
+{
+  g_assert (PNL_IS_ANIMATION (self));
+
+  if (self->notify != NULL)
+    {
+      GDestroyNotify notify = self->notify;
+      gpointer data = self->notify_data;
+
+      self->notify = NULL;
+      self->notify_data = NULL;
+
+      notify (data);
+    }
+}
+
+
+/**
+ * pnl_animation_stop:
+ * @animation: (in): A #PnlAnimation.
+ *
+ * Stops a running animation. The internal reference to the animation is
+ * dropped and therefore may cause the object to finalize.
+ *
+ * Side effects: None.
+ */
+void
+pnl_animation_stop (PnlAnimation *animation)
+{
+  g_return_if_fail (PNL_IS_ANIMATION (animation));
+
+  if (animation->tween_handler)
+    {
+      if (animation->frame_clock)
+        {
+          gdk_frame_clock_end_updating (animation->frame_clock);
+          g_signal_handler_disconnect (animation->frame_clock, animation->tween_handler);
+          animation->tween_handler = 0;
+        }
+      else
+        {
+          g_source_remove (animation->tween_handler);
+          animation->tween_handler = 0;
+        }
+      pnl_animation_unload_begin_values (animation);
+      pnl_animation_notify (animation);
+      g_object_unref (animation);
+    }
+}
+
+
+/**
+ * pnl_animation_add_property:
+ * @animation: (in): A #PnlAnimation.
+ * @pspec: (in): A #ParamSpec of @target or a #GtkWidget<!-- -->'s parent.
+ * @value: (in): The new value for the property at the end of the animation.
+ *
+ * Adds a new property to the set of properties to be animated during the
+ * lifetime of the animation.
+ *
+ * Side effects: None.
+ */
+void
+pnl_animation_add_property (PnlAnimation *animation,
+                            GParamSpec   *pspec,
+                            const GValue *value)
+{
+  Tween tween = { 0 };
+  GType type;
+
+  g_return_if_fail (PNL_IS_ANIMATION (animation));
+  g_return_if_fail (pspec != NULL);
+  g_return_if_fail (value != NULL);
+  g_return_if_fail (value->g_type);
+  g_return_if_fail (animation->target);
+  g_return_if_fail (!animation->tween_handler);
+
+  type = G_TYPE_FROM_INSTANCE (animation->target);
+  tween.is_child = !g_type_is_a (type, pspec->owner_type);
+  if (tween.is_child)
+    {
+      if (!GTK_IS_WIDGET (animation->target))
+        {
+          g_critical (_("Cannot locate property %s in class %s"),
+                      pspec->name, g_type_name (type));
+          return;
+        }
+    }
+
+  tween.pspec = g_param_spec_ref (pspec);
+  g_value_init (&tween.begin, pspec->value_type);
+  g_value_init (&tween.end, pspec->value_type);
+  g_value_copy (value, &tween.end);
+  g_array_append_val (animation->tweens, tween);
+}
+
+
+/**
+ * pnl_animation_dispose:
+ * @object: (in): A #PnlAnimation.
+ *
+ * Releases any object references the animation contains.
+ *
+ * Side effects: None.
+ */
+static void
+pnl_animation_dispose (GObject *object)
+{
+  PnlAnimation *self = PNL_ANIMATION (object);
+
+  g_clear_object (&self->target);
+  g_clear_object (&self->frame_clock);
+
+  G_OBJECT_CLASS (pnl_animation_parent_class)->dispose (object);
+}
+
+
+/**
+ * pnl_animation_finalize:
+ * @object: (in): A #PnlAnimation.
+ *
+ * Finalizes the object and releases any resources allocated.
+ *
+ * Side effects: None.
+ */
+static void
+pnl_animation_finalize (GObject *object)
+{
+  PnlAnimation *self = PNL_ANIMATION (object);
+  Tween *tween;
+  guint i;
+
+  for (i = 0; i < self->tweens->len; i++)
+    {
+      tween = &g_array_index (self->tweens, Tween, i);
+      g_value_unset (&tween->begin);
+      g_value_unset (&tween->end);
+      g_param_spec_unref (tween->pspec);
+    }
+
+  g_array_unref (self->tweens);
+
+  if (debug)
+    g_message ("%u tick updates, expected %d",
+               self->debug_ticks,
+               (int)(self->duration_msec / (1000.0 / FALLBACK_FRAME_RATE)));
+
+  G_OBJECT_CLASS (pnl_animation_parent_class)->finalize (object);
+}
+
+
+/**
+ * pnl_animation_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (in): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Set a given #GObject property.
+ */
+static void
+pnl_animation_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  PnlAnimation *animation = PNL_ANIMATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_DURATION:
+      animation->duration_msec = g_value_get_uint (value) * slow_down_factor;
+      break;
+
+    case PROP_FRAME_CLOCK:
+      pnl_animation_set_frame_clock (animation, g_value_get_object (value));
+      break;
+
+    case PROP_MODE:
+      animation->mode = g_value_get_enum (value);
+      break;
+
+    case PROP_TARGET:
+      pnl_animation_set_target (animation, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+
+/**
+ * pnl_animation_class_init:
+ * @klass: (in): A #PnlAnimationClass.
+ *
+ * Initializes the GObjectClass.
+ *
+ * Side effects: Properties, signals, and vtables are initialized.
+ */
+static void
+pnl_animation_class_init (PnlAnimationClass *klass)
+{
+  GObjectClass *object_class;
+  const gchar *slow_down_factor_env;
+
+  debug = !!g_getenv ("PNL_ANIMATION_DEBUG");
+  slow_down_factor_env = g_getenv ("PNL_ANIMATION_SLOW_DOWN_FACTOR");
+
+  if (slow_down_factor_env)
+    slow_down_factor = MAX (1, atoi (slow_down_factor_env));
+
+  object_class = G_OBJECT_CLASS (klass);
+  object_class->dispose = pnl_animation_dispose;
+  object_class->finalize = pnl_animation_finalize;
+  object_class->set_property = pnl_animation_set_property;
+
+  /**
+   * PnlAnimation:duration:
+   *
+   * The "duration" property is the total number of milliseconds that the
+   * animation should run before being completed.
+   */
+  properties[PROP_DURATION] =
+    g_param_spec_uint ("duration",
+                       "Duration",
+                       "The duration of the animation",
+                       0,
+                       G_MAXUINT,
+                       250,
+                       (G_PARAM_WRITABLE |
+                        G_PARAM_CONSTRUCT_ONLY |
+                        G_PARAM_STATIC_STRINGS));
+
+  properties[PROP_FRAME_CLOCK] =
+    g_param_spec_object ("frame-clock",
+                         "Frame Clock",
+                         "An optional frame-clock to synchronize with.",
+                         GDK_TYPE_FRAME_CLOCK,
+                         (G_PARAM_WRITABLE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+
+  /**
+   * PnlAnimation:mode:
+   *
+   * The "mode" property is the Alpha function that should be used to
+   * determine the offset within the animation based on the current
+   * offset in the animations duration.
+   */
+  properties[PROP_MODE] =
+    g_param_spec_enum ("mode",
+                       "Mode",
+                       "The animation mode",
+                       PNL_TYPE_ANIMATION_MODE,
+                       PNL_ANIMATION_LINEAR,
+                       (G_PARAM_WRITABLE |
+                        G_PARAM_CONSTRUCT_ONLY |
+                        G_PARAM_STATIC_STRINGS));
+
+  /**
+   * PnlAnimation:target:
+   *
+   * The "target" property is the #GObject that should have it's properties
+   * animated.
+   */
+  properties[PROP_TARGET] =
+    g_param_spec_object ("target",
+                         "Target",
+                         "The target of the animation",
+                         G_TYPE_OBJECT,
+                         (G_PARAM_WRITABLE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  /**
+   * PnlAnimation::tick:
+   *
+   * The "tick" signal is emitted on each frame in the animation.
+   */
+  signals[TICK] = g_signal_new ("tick",
+                                 PNL_TYPE_ANIMATION,
+                                 G_SIGNAL_RUN_FIRST,
+                                 0,
+                                 NULL, NULL, NULL,
+                                 G_TYPE_NONE,
+                                 0);
+
+#define SET_ALPHA(_T, _t) \
+  alpha_funcs[PNL_ANIMATION_ ## _T] = pnl_animation_alpha_ ## _t
+
+  SET_ALPHA (LINEAR, linear);
+  SET_ALPHA (EASE_IN_QUAD, ease_in_quad);
+  SET_ALPHA (EASE_OUT_QUAD, ease_out_quad);
+  SET_ALPHA (EASE_IN_OUT_QUAD, ease_in_out_quad);
+  SET_ALPHA (EASE_IN_CUBIC, ease_in_cubic);
+  SET_ALPHA (EASE_OUT_CUBIC, ease_out_cubic);
+  SET_ALPHA (EASE_IN_OUT_CUBIC, ease_in_out_cubic);
+
+#define SET_TWEEN(_T, _t) \
+  G_STMT_START { \
+    guint idx = G_TYPE_ ## _T; \
+    tween_funcs[idx] = tween_ ## _t; \
+  } G_STMT_END
+
+  SET_TWEEN (INT, int);
+  SET_TWEEN (UINT, uint);
+  SET_TWEEN (LONG, long);
+  SET_TWEEN (ULONG, ulong);
+  SET_TWEEN (FLOAT, float);
+  SET_TWEEN (DOUBLE, double);
+}
+
+
+/**
+ * pnl_animation_init:
+ * @animation: (in): A #PnlAnimation.
+ *
+ * Initializes the #PnlAnimation instance.
+ *
+ * Side effects: Everything.
+ */
+static void
+pnl_animation_init (PnlAnimation *animation)
+{
+  animation->duration_msec = 250;
+  animation->mode = PNL_ANIMATION_EASE_IN_OUT_QUAD;
+  animation->tweens = g_array_new (FALSE, FALSE, sizeof (Tween));
+  animation->last_offset = -G_MINDOUBLE;
+}
+
+
+/**
+ * pnl_animation_mode_get_type:
+ *
+ * Retrieves the GType for #PnlAnimationMode.
+ *
+ * Returns: A GType.
+ * Side effects: GType registered on first call.
+ */
+GType
+pnl_animation_mode_get_type (void)
+{
+  static GType type_id = 0;
+  static const GEnumValue values[] = {
+    { PNL_ANIMATION_LINEAR, "PNL_ANIMATION_LINEAR", "linear" },
+    { PNL_ANIMATION_EASE_IN_QUAD, "PNL_ANIMATION_EASE_IN_QUAD", "ease-in-quad" },
+    { PNL_ANIMATION_EASE_IN_OUT_QUAD, "PNL_ANIMATION_EASE_IN_OUT_QUAD", "ease-in-out-quad" },
+    { PNL_ANIMATION_EASE_OUT_QUAD, "PNL_ANIMATION_EASE_OUT_QUAD", "ease-out-quad" },
+    { PNL_ANIMATION_EASE_IN_CUBIC, "PNL_ANIMATION_EASE_IN_CUBIC", "ease-in-cubic" },
+    { PNL_ANIMATION_EASE_OUT_CUBIC, "PNL_ANIMATION_EASE_OUT_CUBIC", "ease-out-cubic" },
+    { PNL_ANIMATION_EASE_IN_OUT_CUBIC, "PNL_ANIMATION_EASE_IN_OUT_CUBIC", "ease-in-out-cubic" },
+    { 0 }
+  };
+
+  if (G_UNLIKELY (!type_id))
+    type_id = g_enum_register_static ("PnlAnimationMode", values);
+  return type_id;
+}
+
+/**
+ * pnl_object_animatev:
+ * @object: A #GObject.
+ * @mode: The animation mode.
+ * @duration_msec: The duration in milliseconds.
+ * @frame_clock: The target frame rate.
+ * @first_property: The first property to animate.
+ * @args: A variadac list of arguments
+ *
+ * Returns: (transfer none): A #PnlAnimation.
+ */
+PnlAnimation *
+pnl_object_animatev (gpointer          object,
+                     PnlAnimationMode  mode,
+                     guint             duration_msec,
+                     GdkFrameClock    *frame_clock,
+                     const gchar      *first_property,
+                     va_list           args)
+{
+  PnlAnimation *animation;
+  GObjectClass *klass;
+  GObjectClass *pklass;
+  const gchar *name;
+  GParamSpec *pspec;
+  GtkWidget *parent;
+  GValue value = { 0 };
+  gchar *error = NULL;
+  GType type;
+  GType ptype;
+  gboolean enable_animations;
+
+  g_return_val_if_fail (first_property != NULL, NULL);
+  g_return_val_if_fail (mode < PNL_ANIMATION_LAST, NULL);
+
+  if ((frame_clock == NULL) && GTK_IS_WIDGET (object))
+    frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (object));
+
+  /*
+   * If we have a frame clock, then we must be in the gtk thread and we
+   * should check GtkSettings for disabled animations. If we are disabled,
+   * we will just make the timeout immediate.
+   */
+  if (frame_clock != NULL)
+    {
+      g_object_get (gtk_settings_get_default (),
+                    "gtk-enable-animations", &enable_animations,
+                    NULL);
+
+      if (enable_animations == FALSE)
+        duration_msec = 0;
+    }
+
+  name = first_property;
+  type = G_TYPE_FROM_INSTANCE (object);
+  klass = G_OBJECT_GET_CLASS (object);
+  animation = g_object_new (PNL_TYPE_ANIMATION,
+                            "duration", duration_msec,
+                            "frame-clock", frame_clock,
+                            "mode", mode,
+                            "target", object,
+                            NULL);
+
+  do
+    {
+      /*
+       * First check for the property on the object. If that does not exist
+       * then check if the object has a parent and look at its child
+       * properties (if its a GtkWidget).
+       */
+      if (!(pspec = g_object_class_find_property (klass, name)))
+        {
+          if (!g_type_is_a (type, GTK_TYPE_WIDGET))
+            {
+              g_critical (_("Failed to find property %s in %s"),
+                          name, g_type_name (type));
+              goto failure;
+            }
+          if (!(parent = gtk_widget_get_parent (object)))
+            {
+              g_critical (_("Failed to find property %s in %s"),
+                          name, g_type_name (type));
+              goto failure;
+            }
+          pklass = G_OBJECT_GET_CLASS (parent);
+          ptype = G_TYPE_FROM_INSTANCE (parent);
+          if (!(pspec = gtk_container_class_find_child_property (pklass, name)))
+            {
+              g_critical (_("Failed to find property %s in %s or parent %s"),
+                          name, g_type_name (type), g_type_name (ptype));
+              goto failure;
+            }
+        }
+
+      g_value_init (&value, pspec->value_type);
+      G_VALUE_COLLECT (&value, args, 0, &error);
+      if (error != NULL)
+        {
+          g_critical (_("Failed to retrieve va_list value: %s"), error);
+          g_free (error);
+          goto failure;
+        }
+
+      pnl_animation_add_property (animation, pspec, &value);
+      g_value_unset (&value);
+    }
+  while ((name = va_arg (args, const gchar *)));
+
+  pnl_animation_start (animation);
+
+  return animation;
+
+failure:
+  g_object_ref_sink (animation);
+  g_object_unref (animation);
+  return NULL;
+}
+
+/**
+ * pnl_object_animate:
+ * @object: (in): A #GObject.
+ * @mode: (in): The animation mode.
+ * @duration_msec: (in): The duration in milliseconds.
+ * @first_property: (in): The first property to animate.
+ *
+ * Animates the properties of @object. The can be set in a similar manner to g_object_set(). They
+ * will be animated from their current value to the target value over the time period.
+ *
+ * Return value: (transfer none): A #PnlAnimation.
+ * Side effects: None.
+ */
+PnlAnimation*
+pnl_object_animate (gpointer        object,
+                    PnlAnimationMode mode,
+                    guint           duration_msec,
+                    GdkFrameClock  *frame_clock,
+                    const gchar    *first_property,
+                    ...)
+{
+  PnlAnimation *animation;
+  va_list args;
+
+  va_start (args, first_property);
+  animation = pnl_object_animatev (object,
+                                   mode,
+                                   duration_msec,
+                                   frame_clock,
+                                   first_property,
+                                   args);
+  va_end (args);
+
+  return animation;
+}
+
+/**
+ * pnl_object_animate_full:
+ *
+ * Return value: (transfer none): A #PnlAnimation.
+ */
+PnlAnimation*
+pnl_object_animate_full (gpointer        object,
+                         PnlAnimationMode mode,
+                         guint           duration_msec,
+                         GdkFrameClock  *frame_clock,
+                         GDestroyNotify  notify,
+                         gpointer        notify_data,
+                         const gchar    *first_property,
+                         ...)
+{
+  PnlAnimation *animation;
+  va_list args;
+
+  va_start (args, first_property);
+  animation = pnl_object_animatev (object,
+                                   mode,
+                                   duration_msec,
+                                   frame_clock,
+                                   first_property,
+                                   args);
+  va_end (args);
+
+  animation->notify = notify;
+  animation->notify_data = notify_data;
+
+  return animation;
+}
diff --git a/contrib/pnl/pnl-animation.h b/contrib/pnl/pnl-animation.h
new file mode 100644
index 0000000..73117b0
--- /dev/null
+++ b/contrib/pnl/pnl-animation.h
@@ -0,0 +1,78 @@
+/* pnl-animation.h
+ *
+ * Copyright (C) 2010-2016 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_ANIMATION_H
+#define PNL_ANIMATION_H
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_ANIMATION      (pnl_animation_get_type())
+#define PNL_TYPE_ANIMATION_MODE (pnl_animation_mode_get_type())
+
+G_DECLARE_FINAL_TYPE (PnlAnimation, pnl_animation, PNL, ANIMATION, GInitiallyUnowned)
+
+typedef enum
+{
+  PNL_ANIMATION_LINEAR,
+  PNL_ANIMATION_EASE_IN_QUAD,
+  PNL_ANIMATION_EASE_OUT_QUAD,
+  PNL_ANIMATION_EASE_IN_OUT_QUAD,
+  PNL_ANIMATION_EASE_IN_CUBIC,
+  PNL_ANIMATION_EASE_OUT_CUBIC,
+  PNL_ANIMATION_EASE_IN_OUT_CUBIC,
+
+  PNL_ANIMATION_LAST
+} PnlAnimationMode;
+
+GType         pnl_animation_mode_get_type (void);
+void          pnl_animation_start         (PnlAnimation     *animation);
+void          pnl_animation_stop          (PnlAnimation     *animation);
+void          pnl_animation_add_property  (PnlAnimation     *animation,
+                                           GParamSpec       *pspec,
+                                           const GValue     *value);
+
+PnlAnimation *pnl_object_animatev         (gpointer          object,
+                                           PnlAnimationMode  mode,
+                                           guint             duration_msec,
+                                           GdkFrameClock    *frame_clock,
+                                           const gchar      *first_property,
+                                           va_list           args);
+PnlAnimation* pnl_object_animate          (gpointer          object,
+                                           PnlAnimationMode  mode,
+                                           guint             duration_msec,
+                                           GdkFrameClock    *frame_clock,
+                                           const gchar      *first_property,
+                                           ...) G_GNUC_NULL_TERMINATED;
+PnlAnimation* pnl_object_animate_full     (gpointer          object,
+                                           PnlAnimationMode  mode,
+                                           guint             duration_msec,
+                                           GdkFrameClock    *frame_clock,
+                                           GDestroyNotify    notify,
+                                           gpointer          notify_data,
+                                           const gchar      *first_property,
+                                           ...) G_GNUC_NULL_TERMINATED;
+
+G_END_DECLS
+
+#endif /* PNL_ANIMATION_H */
diff --git a/contrib/pnl/pnl-dock-bin-edge-private.h b/contrib/pnl/pnl-dock-bin-edge-private.h
new file mode 100644
index 0000000..cf79307
--- /dev/null
+++ b/contrib/pnl/pnl-dock-bin-edge-private.h
@@ -0,0 +1,32 @@
+/* pnl-dock-bin-edge-private.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_DOCK_BIN_EDGE_PRIVATE_H
+#define PNL_DOCK_BIN_EDGE_PRIVATE_H
+
+#include "pnl-dock-bin-edge.h"
+
+G_BEGIN_DECLS
+
+GtkPositionType pnl_dock_bin_edge_get_edge (PnlDockBinEdge  *self);
+void            pnl_dock_bin_edge_set_edge (PnlDockBinEdge  *self,
+                                            GtkPositionType  bin_edge);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_BIN_EDGE_PRIVATE_H */
diff --git a/contrib/pnl/pnl-dock-bin-edge.c b/contrib/pnl/pnl-dock-bin-edge.c
new file mode 100644
index 0000000..ac9d19d
--- /dev/null
+++ b/contrib/pnl/pnl-dock-bin-edge.c
@@ -0,0 +1,259 @@
+/* pnl-dock-bin-edge.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-bin.h"
+#include "pnl-dock-bin-edge.h"
+#include "pnl-dock-bin-edge-private.h"
+#include "pnl-dock-revealer.h"
+
+typedef struct
+{
+  GtkPositionType edge : 3;
+} PnlDockBinEdgePrivate;
+
+G_DEFINE_TYPE_EXTENDED (PnlDockBinEdge, pnl_dock_bin_edge, PNL_TYPE_DOCK_REVEALER, 0,
+                        G_ADD_PRIVATE (PnlDockBinEdge)
+                        G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM, NULL))
+
+enum {
+  PROP_0,
+  PROP_EDGE,
+  N_PROPS
+};
+
+enum {
+  MOVE_TO_BIN_CHILD,
+  N_SIGNALS
+};
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
+static void
+pnl_dock_bin_edge_update_edge (PnlDockBinEdge *self)
+{
+  PnlDockBinEdgePrivate *priv = pnl_dock_bin_edge_get_instance_private (self);
+  GtkStyleContext *style_context;
+  PnlDockRevealerTransitionType transition_type;
+  const gchar *class_name = NULL;
+  GtkWidget *child;
+  GtkOrientation orientation;
+
+  g_assert (PNL_IS_DOCK_BIN_EDGE (self));
+
+  style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+  gtk_style_context_remove_class (style_context, "left");
+  gtk_style_context_remove_class (style_context, "right");
+  gtk_style_context_remove_class (style_context, "top");
+  gtk_style_context_remove_class (style_context, "bottom");
+
+  if (priv->edge == GTK_POS_LEFT)
+    {
+      class_name = "left";
+      transition_type = PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT;
+      orientation = GTK_ORIENTATION_VERTICAL;
+    }
+  else if (priv->edge == GTK_POS_RIGHT)
+    {
+      class_name = "right";
+      transition_type = PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT;
+      orientation = GTK_ORIENTATION_VERTICAL;
+    }
+  else if (priv->edge == GTK_POS_TOP)
+    {
+      class_name = "top";
+      transition_type = PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN;
+      orientation = GTK_ORIENTATION_HORIZONTAL;
+    }
+  else if (priv->edge == GTK_POS_BOTTOM)
+    {
+      class_name = "bottom";
+      transition_type = PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_UP;
+      orientation = GTK_ORIENTATION_HORIZONTAL;
+    }
+  else
+    {
+      g_assert_not_reached ();
+      return;
+    }
+
+  gtk_style_context_add_class (style_context, class_name);
+  pnl_dock_revealer_set_transition_type (PNL_DOCK_REVEALER (self), transition_type);
+
+  child = gtk_bin_get_child (GTK_BIN (self));
+
+  if (PNL_IS_DOCK_PANED (child))
+    gtk_orientable_set_orientation (GTK_ORIENTABLE (child), orientation);
+}
+
+GtkPositionType
+pnl_dock_bin_edge_get_edge (PnlDockBinEdge *self)
+{
+  PnlDockBinEdgePrivate *priv = pnl_dock_bin_edge_get_instance_private (self);
+
+  g_return_val_if_fail (PNL_IS_DOCK_BIN_EDGE (self), 0);
+
+  return priv->edge;
+}
+
+void
+pnl_dock_bin_edge_set_edge (PnlDockBinEdge  *self,
+                            GtkPositionType  edge)
+{
+  PnlDockBinEdgePrivate *priv = pnl_dock_bin_edge_get_instance_private (self);
+
+  g_return_if_fail (PNL_IS_DOCK_BIN_EDGE (self));
+
+  if (edge != priv->edge)
+    {
+      priv->edge = edge;
+      pnl_dock_bin_edge_update_edge (self);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EDGE]);
+    }
+}
+
+static void
+pnl_dock_bin_edge_add (GtkContainer *container,
+                       GtkWidget    *widget)
+{
+  GtkWidget *child;
+
+  g_assert (GTK_IS_CONTAINER (container));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  child = gtk_bin_get_child (GTK_BIN (container));
+
+  if (GTK_IS_CONTAINER (child))
+    gtk_container_add (GTK_CONTAINER (child), widget);
+  else
+    GTK_CONTAINER_CLASS (pnl_dock_bin_edge_parent_class)->add (container, widget);
+}
+
+static void
+pnl_dock_bin_edge_real_move_to_bin_child (PnlDockBinEdge *self)
+{
+  GtkWidget *parent;
+
+  g_assert (PNL_IS_DOCK_BIN_EDGE (self));
+
+  parent = gtk_widget_get_parent (GTK_WIDGET (self));
+
+  if (PNL_IS_DOCK_BIN (parent))
+    gtk_widget_grab_focus (parent);
+}
+
+static void
+pnl_dock_bin_edge_constructed (GObject *object)
+{
+  PnlDockBinEdge *self = (PnlDockBinEdge *)object;
+
+  G_OBJECT_CLASS (pnl_dock_bin_edge_parent_class)->constructed (object);
+
+  pnl_dock_bin_edge_update_edge (self);
+}
+
+static void
+pnl_dock_bin_edge_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  PnlDockBinEdge *self = PNL_DOCK_BIN_EDGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_EDGE:
+      g_value_set_enum (value, pnl_dock_bin_edge_get_edge (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_bin_edge_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  PnlDockBinEdge *self = PNL_DOCK_BIN_EDGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_EDGE:
+      pnl_dock_bin_edge_set_edge (self, g_value_get_enum (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_bin_edge_class_init (PnlDockBinEdgeClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+  GtkBindingSet *binding_set;
+
+  object_class->constructed = pnl_dock_bin_edge_constructed;
+  object_class->get_property = pnl_dock_bin_edge_get_property;
+  object_class->set_property = pnl_dock_bin_edge_set_property;
+
+  container_class->add = pnl_dock_bin_edge_add;
+
+  klass->move_to_bin_child = pnl_dock_bin_edge_real_move_to_bin_child;
+
+  properties [PROP_EDGE] =
+    g_param_spec_enum ("edge",
+                       "Edge",
+                       "The edge of the dock this widget is attached to",
+                       GTK_TYPE_POSITION_TYPE,
+                       GTK_POS_LEFT,
+                       (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  signals [MOVE_TO_BIN_CHILD] =
+    g_signal_new ("move-to-bin-child",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                  G_STRUCT_OFFSET (PnlDockBinEdgeClass, move_to_bin_child),
+                  NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+  binding_set = gtk_binding_set_by_class (klass);
+  gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "move-to-bin-child", 0);
+
+  gtk_widget_class_set_css_name (widget_class, "dockbinedge");
+}
+
+static void
+pnl_dock_bin_edge_init (PnlDockBinEdge *self)
+{
+  GtkWidget *child;
+
+  child = g_object_new (PNL_TYPE_DOCK_PANED,
+                        "visible", TRUE,
+                        NULL);
+  GTK_CONTAINER_CLASS (pnl_dock_bin_edge_parent_class)->add (GTK_CONTAINER (self), child);
+
+  pnl_dock_bin_edge_update_edge (self);
+}
diff --git a/contrib/pnl/pnl-dock-bin-edge.h b/contrib/pnl/pnl-dock-bin-edge.h
new file mode 100644
index 0000000..65dee0b
--- /dev/null
+++ b/contrib/pnl/pnl-dock-bin-edge.h
@@ -0,0 +1,48 @@
+/* pnl-dock-bin-edge.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_DOCK_BIN_EDGE_H
+#define PNL_DOCK_BIN_EDGE_H
+
+#include "pnl-dock-revealer.h"
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_DOCK_BIN_EDGE (pnl_dock_bin_edge_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (PnlDockBinEdge, pnl_dock_bin_edge, PNL, DOCK_BIN_EDGE, PnlDockRevealer)
+
+struct _PnlDockBinEdgeClass
+{
+  PnlDockRevealerClass parent;
+
+  void (*move_to_bin_child) (PnlDockBinEdge *self);
+
+  void (*padding1) (void);
+  void (*padding2) (void);
+  void (*padding3) (void);
+  void (*padding4) (void);
+  void (*padding5) (void);
+  void (*padding6) (void);
+  void (*padding7) (void);
+  void (*padding8) (void);
+};
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_BIN_EDGE_H */
diff --git a/contrib/pnl/pnl-dock-bin.c b/contrib/pnl/pnl-dock-bin.c
new file mode 100644
index 0000000..ff54493
--- /dev/null
+++ b/contrib/pnl/pnl-dock-bin.c
@@ -0,0 +1,1922 @@
+/* pnl-dock-bin.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+
+#include "pnl-dock-bin.h"
+#include "pnl-dock-bin-edge-private.h"
+#include "pnl-dock-item.h"
+
+#define HANDLE_WIDTH  10
+#define HANDLE_HEIGHT 10
+
+typedef enum
+{
+  PNL_DOCK_BIN_CHILD_LEFT   = GTK_POS_LEFT,
+  PNL_DOCK_BIN_CHILD_RIGHT  = GTK_POS_RIGHT,
+  PNL_DOCK_BIN_CHILD_TOP    = GTK_POS_TOP,
+  PNL_DOCK_BIN_CHILD_BOTTOM = GTK_POS_BOTTOM,
+  PNL_DOCK_BIN_CHILD_CENTER = 4,
+  LAST_PNL_DOCK_BIN_CHILD   = 5
+} PnlDockBinChildType;
+
+typedef struct
+{
+  /*
+   * The child widget in question.
+   * Typically this is a PnlDockBinEdge, but the
+   * center widget can be whatever.
+   */
+  GtkWidget *widget;
+
+  /*
+   * The GdkWindow for the handle to resize the edge.
+   * This is an input only window, the pane handle is drawn
+   * with CSS by whatever styling the application has chose.
+   */
+  GdkWindow *handle;
+
+  /*
+   * When dragging, we need to know our offset relative to the
+   * grab position to alter preferred size requests.
+   */
+  gint drag_offset;
+
+  /*
+   * This is the position of the child before the drag started.
+   * We use this, combined with @drag_offset to determine the
+   * size the child should be in the drag operation.
+   */
+  gint drag_begin_position;
+
+  /*
+   * Priority child property used to alter which child is
+   * dominant in each slice stage. See
+   * pnl_dock_bin_get_children_preferred_width() for more information
+   * on how the slicing is performed.
+   */
+  gint priority;
+
+  /*
+   * Cached size request used during size allocation.
+   */
+  GtkRequisition min_req;
+  GtkRequisition nat_req;
+
+  /*
+   * The type of child. The PNL_DOCK_BIN_CHILD_CENTER is always
+   * the last child, and our sort function ensures that.
+   */
+  PnlDockBinChildType type : 3;
+} PnlDockBinChild;
+
+typedef struct
+{
+  /*
+   * All of our dock children, including edges and center child.
+   */
+  PnlDockBinChild children[LAST_PNL_DOCK_BIN_CHILD];
+
+  /*
+   * Actions used to toggle edge visibility.
+   */
+  GSimpleActionGroup *actions;
+
+  /*
+   * The pan gesture is used to resize edges.
+   */
+  GtkGesturePan *pan_gesture;
+
+  /*
+   * While in a pan gesture, we need to drag the current edge
+   * being dragged. This is left, right, top, or bottom only.
+   */
+  PnlDockBinChild *drag_child;
+
+  /*
+   * We need to track the position during a DnD request. We can use this
+   * to highlight the area where the drop will occur.
+   */
+  gint dnd_drag_x;
+  gint dnd_drag_y;
+} PnlDockBinPrivate;
+
+static void pnl_dock_bin_init_buildable_iface (GtkBuildableIface    *iface);
+static void pnl_dock_bin_init_dock_iface      (PnlDockInterface     *iface);
+static void pnl_dock_bin_init_dock_item_iface (PnlDockItemInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (PnlDockBin, pnl_dock_bin, GTK_TYPE_CONTAINER, 0,
+                        G_ADD_PRIVATE (PnlDockBin)
+                        G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
+                                               pnl_dock_bin_init_buildable_iface)
+                        G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM,
+                                               pnl_dock_bin_init_dock_item_iface)
+                        G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK,
+                                               pnl_dock_bin_init_dock_iface))
+
+enum {
+  PROP_0,
+  PROP_MANAGER,
+  N_PROPS
+};
+
+enum {
+  CHILD_PROP_0,
+  CHILD_PROP_POSITION,
+  CHILD_PROP_PRIORITY,
+  N_CHILD_PROPS
+};
+
+enum {
+  STYLE_PROP_0,
+  STYLE_PROP_HANDLE_SIZE,
+  N_STYLE_PROPS
+};
+
+static GParamSpec *child_properties [N_CHILD_PROPS];
+static GParamSpec *style_properties [N_STYLE_PROPS];
+
+static gboolean
+map_boolean_to_variant (GBinding     *binding,
+                        const GValue *from_value,
+                        GValue       *to_value,
+                        gpointer      user_data)
+{
+  g_assert (G_IS_BINDING (binding));
+
+  if (g_value_get_boolean (from_value))
+    g_value_set_variant (to_value, g_variant_new_boolean (TRUE));
+  else
+    g_value_set_variant (to_value, g_variant_new_boolean (FALSE));
+
+  return TRUE;
+}
+
+static PnlDockBinChild *
+pnl_dock_bin_get_child (PnlDockBin *self,
+                        GtkWidget  *widget)
+{
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
+    {
+      PnlDockBinChild *child = &priv->children [i];
+
+      if ((GtkWidget *)child->widget == widget)
+        return child;
+    }
+
+  g_assert_not_reached ();
+
+  return NULL;
+}
+
+#if 0
+static PnlDockBinChild *
+pnl_dock_bin_get_child_at_coordinates (PnlDockBin *self,
+                                       gint        x,
+                                       gint        y)
+{
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  GtkAllocation our_alloc;
+  guint i;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+
+  gtk_widget_get_allocation (GTK_WIDGET (self), &our_alloc);
+
+  for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
+    {
+      PnlDockBinChild *child = &priv->children [i];
+      GtkAllocation alloc;
+
+      if (child->widget == NULL)
+        continue;
+
+      gtk_widget_get_allocation (child->widget, &alloc);
+
+      alloc.x -= our_alloc.x;
+      alloc.y -= our_alloc.y;
+
+      if (x >= alloc.x &&
+          x <= alloc.x + alloc.width &&
+          y >= alloc.y &&
+          y <= alloc.y + alloc.height)
+        return child;
+    }
+
+  return NULL;
+}
+#endif
+
+static PnlDockBinChild *
+pnl_dock_bin_get_child_typed (PnlDockBin          *self,
+                              PnlDockBinChildType  type)
+{
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (type >= PNL_DOCK_BIN_CHILD_LEFT);
+  g_assert (type < LAST_PNL_DOCK_BIN_CHILD);
+
+  for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
+    {
+      PnlDockBinChild *child = &priv->children [i];
+
+      if (child->type == type)
+        return child;
+    }
+
+  g_assert_not_reached ();
+
+  return NULL;
+}
+
+static void
+pnl_dock_bin_update_focus_chain (PnlDockBin *self)
+{
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  PnlDockBinChild *child;
+  GList *focus_chain = NULL;
+  guint i;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+
+  for (i = G_N_ELEMENTS (priv->children); i > 0; i--)
+    {
+      child = &priv->children [i - 1];
+
+      if ((child->widget != NULL) &&
+          (child->type != PNL_DOCK_BIN_CHILD_CENTER))
+        focus_chain = g_list_prepend (focus_chain, child->widget);
+    }
+
+  child = pnl_dock_bin_get_child_typed (self, PNL_DOCK_BIN_CHILD_CENTER);
+
+  if (child->widget != NULL)
+    focus_chain = g_list_prepend (focus_chain, child->widget);
+
+  if (focus_chain != NULL)
+    {
+      gtk_container_set_focus_chain (GTK_CONTAINER (self), focus_chain);
+      g_list_free (focus_chain);
+    }
+}
+
+static GAction *
+pnl_dock_bin_get_action_for_type (PnlDockBin          *self,
+                                  PnlDockBinChildType  type)
+{
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  const gchar *name = NULL;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+
+  if (type == PNL_DOCK_BIN_CHILD_LEFT)
+    name = "left-visible";
+  else if (type == PNL_DOCK_BIN_CHILD_RIGHT)
+    name = "right-visible";
+  else if (type == PNL_DOCK_BIN_CHILD_TOP)
+    name = "top-visible";
+  else if (type == PNL_DOCK_BIN_CHILD_BOTTOM)
+    name = "bottom-visible";
+  else
+    g_assert_not_reached ();
+
+  return g_action_map_lookup_action (G_ACTION_MAP (priv->actions), name);
+}
+
+static void
+pnl_dock_bin_add (GtkContainer *container,
+                  GtkWidget    *widget)
+{
+  PnlDockBin *self = (PnlDockBin *)container;
+  PnlDockBinChild *child;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  child = pnl_dock_bin_get_child_typed (self, PNL_DOCK_BIN_CHILD_CENTER);
+
+  if (child->widget != NULL)
+    {
+      g_warning ("Attempt to add a %s to a %s, but it already has a child of type %s",
+                 G_OBJECT_TYPE_NAME (widget),
+                 G_OBJECT_TYPE_NAME (self),
+                 G_OBJECT_TYPE_NAME (child->widget));
+      return;
+    }
+
+  if (PNL_IS_DOCK_ITEM (widget) &&
+      !pnl_dock_item_adopt (PNL_DOCK_ITEM (self), PNL_DOCK_ITEM (widget)))
+    {
+      g_warning ("Child of type %s has a different PnlDockManager than %s",
+                 G_OBJECT_TYPE_NAME (widget), G_OBJECT_TYPE_NAME (self));
+      return;
+    }
+
+  child->widget = g_object_ref_sink (widget);
+  gtk_widget_set_parent (widget, GTK_WIDGET (self));
+
+  pnl_dock_bin_update_focus_chain (self);
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+pnl_dock_bin_remove (GtkContainer *container,
+                     GtkWidget    *widget)
+{
+  PnlDockBin *self = (PnlDockBin *)container;
+  PnlDockBinChild *child;
+
+  g_return_if_fail (PNL_IS_DOCK_BIN (self));
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+
+  child = pnl_dock_bin_get_child (self, widget);
+  gtk_widget_unparent (child->widget);
+  g_clear_object (&child->widget);
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+pnl_dock_bin_forall (GtkContainer *container,
+                     gboolean      include_internal,
+                     GtkCallback   callback,
+                     gpointer      user_data)
+{
+  PnlDockBin *self = (PnlDockBin *)container;
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (callback != NULL);
+
+  for (i = G_N_ELEMENTS (priv->children); i > 0; i--)
+    {
+      PnlDockBinChild *child = &priv->children [i - 1];
+
+      if (child->widget != NULL)
+        callback (GTK_WIDGET (child->widget), user_data);
+    }
+}
+
+static void
+pnl_dock_bin_get_children_preferred_width (PnlDockBin      *self,
+                                           PnlDockBinChild *children,
+                                           gint             n_children,
+                                           gint            *min_width,
+                                           gint            *nat_width)
+{
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  PnlDockBinChild *child = children;
+  gint child_min_width = 0;
+  gint child_nat_width = 0;
+  gint neighbor_min_width = 0;
+  gint neighbor_nat_width = 0;
+  gint handle_size = 0;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (children != NULL);
+  g_assert (n_children > 0);
+  g_assert (min_width != NULL);
+  g_assert (nat_width != NULL);
+
+  *min_width = 0;
+  *nat_width = 0;
+
+  gtk_widget_style_get (GTK_WIDGET (self),
+                        "handle-size", &handle_size,
+                        NULL);
+
+  /*
+   * We have a fairly simple rule for deducing the size request of
+   * the children layout. Since children edges can have any priority,
+   * we need to know how to slice them into areas that allow us to
+   * combine (additive) or negotiate (maximum) widths with the
+   * neighboring widgets.
+   *
+   *          .
+   *          .
+   *     +----+---------------------------------+
+   *     |    |              2                  |
+   *     |    +=================================+.....
+   *     |    |                            |    |
+   *     |    |                            |    |
+   *     | 1  |              5             |    |
+   *     |    |                            | 3  |
+   *     |    +==.==.==.==.==.==.==.==.==.=+    |
+   *     |    |              4             |    |
+   *     +----+----------------------------+----+
+   *          .                            .
+   *          .                            .
+   *
+   * The children are sorted in their weighting order. Each child
+   * will dominate the leftover allocation, in the orientation that
+   * matters.
+   *
+   * 1 and 3 in the diagram above will always be additive with their
+   * neighbors horizontal neighbors. See the guide does for how this
+   * gets sliced. Even if 3 were dominant (instead of 2), it would still
+   * be additive to its neighbors. Same for 1.
+   *
+   * Both 2 and 4, will always negotiate their widths with the next
+   * child.
+   *
+   * This allows us to make a fairly simple recursive function to
+   * size ourselves and then call again with the next child, working our
+   * way down to 5 (the center widget).
+   *
+   * At this point, we walk back up the recursive-stack and do our
+   * adding or negotiating.
+   */
+
+  if (child->widget != NULL)
+    gtk_widget_get_preferred_width (child->widget, &child_min_width, &child_nat_width);
+
+  if (child == priv->drag_child)
+    child_nat_width = MAX (child_min_width,
+                           child->drag_begin_position + child->drag_offset);
+
+  if (n_children > 1)
+    pnl_dock_bin_get_children_preferred_width (self,
+                                               &children [1],
+                                               n_children - 1,
+                                               &neighbor_min_width,
+                                               &neighbor_nat_width);
+
+  switch (child->type)
+    {
+    case PNL_DOCK_BIN_CHILD_LEFT:
+    case PNL_DOCK_BIN_CHILD_RIGHT:
+      *min_width = (child_min_width + neighbor_min_width + handle_size);
+      *nat_width = (child_nat_width + neighbor_nat_width + handle_size);
+      break;
+
+    case PNL_DOCK_BIN_CHILD_TOP:
+    case PNL_DOCK_BIN_CHILD_BOTTOM:
+      *min_width = MAX (child_min_width, neighbor_min_width + handle_size);
+      *nat_width = MAX (child_nat_width, neighbor_nat_width + handle_size);
+      break;
+
+    case PNL_DOCK_BIN_CHILD_CENTER:
+      *min_width = child_min_width;
+      *nat_width = child_min_width;
+      break;
+
+    case LAST_PNL_DOCK_BIN_CHILD:
+    default:
+      g_assert_not_reached ();
+    }
+
+  child->min_req.width = *min_width;
+  child->nat_req.width = *nat_width;
+}
+
+static void
+pnl_dock_bin_get_preferred_width (GtkWidget *widget,
+                                  gint      *min_width,
+                                  gint      *nat_width)
+{
+  PnlDockBin *self = (PnlDockBin *)widget;
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (min_width != NULL);
+  g_assert (nat_width != NULL);
+
+  pnl_dock_bin_get_children_preferred_width (self,
+                                             priv->children,
+                                             G_N_ELEMENTS (priv->children),
+                                             min_width,
+                                             nat_width);
+}
+
+static void
+pnl_dock_bin_get_children_preferred_height (PnlDockBin      *self,
+                                            PnlDockBinChild *children,
+                                            gint             n_children,
+                                            gint            *min_height,
+                                            gint            *nat_height)
+{
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  PnlDockBinChild *child = children;
+  gint child_min_height = 0;
+  gint child_nat_height = 0;
+  gint neighbor_min_height = 0;
+  gint neighbor_nat_height = 0;
+  gint handle_size = 0;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (children != NULL);
+  g_assert (n_children > 0);
+  g_assert (min_height != NULL);
+  g_assert (nat_height != NULL);
+
+  *min_height = 0;
+  *nat_height = 0;
+
+  gtk_widget_style_get (GTK_WIDGET (self),
+                        "handle-size", &handle_size,
+                        NULL);
+
+  /*
+   * See pnl_dock_bin_get_children_preferred_width() for more information on
+   * how this works. This works just like that but the negotiated/additive
+   * operations are switched between the left/right and top/bottom.
+   */
+
+  if (child->widget != NULL)
+    gtk_widget_get_preferred_height (child->widget, &child_min_height, &child_nat_height);
+
+  if (child == priv->drag_child)
+    child_nat_height = MAX (child_min_height,
+                            child->drag_begin_position + child->drag_offset);
+
+  if (n_children > 1)
+    pnl_dock_bin_get_children_preferred_height (self,
+                                                &children [1],
+                                                n_children - 1,
+                                                &neighbor_min_height,
+                                                &neighbor_nat_height);
+
+  switch (child->type)
+    {
+    case PNL_DOCK_BIN_CHILD_LEFT:
+    case PNL_DOCK_BIN_CHILD_RIGHT:
+      *min_height = MAX (child_min_height, neighbor_min_height + handle_size);
+      *nat_height = MAX (child_nat_height, neighbor_nat_height + handle_size);
+      break;
+
+    case PNL_DOCK_BIN_CHILD_TOP:
+    case PNL_DOCK_BIN_CHILD_BOTTOM:
+      *min_height = (child_min_height + neighbor_min_height + handle_size);
+      *nat_height = (child_nat_height + neighbor_nat_height + handle_size);
+      break;
+
+    case PNL_DOCK_BIN_CHILD_CENTER:
+      *min_height = child_min_height;
+      *nat_height = child_min_height;
+      break;
+
+    case LAST_PNL_DOCK_BIN_CHILD:
+    default:
+      g_assert_not_reached ();
+    }
+
+  child->min_req.height = *min_height;
+  child->nat_req.height = *nat_height;
+}
+
+static void
+pnl_dock_bin_get_preferred_height (GtkWidget *widget,
+                                   gint      *min_height,
+                                   gint      *nat_height)
+{
+  PnlDockBin *self = (PnlDockBin *)widget;
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (min_height != NULL);
+  g_assert (nat_height != NULL);
+
+  pnl_dock_bin_get_children_preferred_height (self,
+                                              priv->children,
+                                              G_N_ELEMENTS (priv->children),
+                                              min_height,
+                                              nat_height);
+}
+
+static void
+pnl_dock_bin_negotiate_size (PnlDockBin           *self,
+                             const GtkAllocation  *allocation,
+                             const GtkRequisition *child_min,
+                             const GtkRequisition *child_nat,
+                             const GtkRequisition *neighbor_min,
+                             const GtkRequisition *neighbor_nat,
+                             GtkAllocation        *child_alloc)
+{
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (allocation != NULL);
+  g_assert (child_min != NULL);
+  g_assert (child_nat != NULL);
+  g_assert (neighbor_min != NULL);
+  g_assert (neighbor_nat != NULL);
+  g_assert (child_alloc != NULL);
+
+  if (allocation->width - child_nat->width < neighbor_min->width)
+    child_alloc->width = allocation->width - neighbor_min->width;
+  else
+    child_alloc->width = child_nat->width;
+
+  if (allocation->height - child_nat->height < neighbor_min->height)
+    child_alloc->height = allocation->height - neighbor_min->height;
+  else
+    child_alloc->height = child_nat->height;
+}
+
+static void
+pnl_dock_bin_child_size_allocate (PnlDockBin      *self,
+                                  PnlDockBinChild *children,
+                                  gint             n_children,
+                                  GtkAllocation   *allocation)
+{
+  PnlDockBinChild *child = children;
+  gint handle_size = 0;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (children != NULL);
+  g_assert (n_children >= 1);
+  g_assert (allocation != NULL);
+
+  if (n_children == 1)
+    {
+      g_assert (child->type == PNL_DOCK_BIN_CHILD_CENTER);
+
+      if (child->widget != NULL && gtk_widget_get_visible (child->widget))
+        gtk_widget_size_allocate (child->widget, allocation);
+
+      return;
+    }
+
+  gtk_widget_style_get (GTK_WIDGET (self),
+                        "handle-size", &handle_size,
+                        NULL);
+
+  if (child->widget != NULL && gtk_widget_get_visible (child->widget))
+    {
+      GtkAllocation child_alloc = { 0 };
+      GtkAllocation handle_alloc = { 0 };
+      GtkRequisition neighbor_min = { 0 };
+      GtkRequisition neighbor_nat = { 0 };
+
+      pnl_dock_bin_get_children_preferred_height (self, child, 1,
+                                                  &child->min_req.height,
+                                                  &child->nat_req.height);
+
+      pnl_dock_bin_get_children_preferred_width (self, child, 1,
+                                                 &child->min_req.width,
+                                                 &child->nat_req.width);
+
+      pnl_dock_bin_get_children_preferred_height (self,
+                                                  &children [1],
+                                                  n_children - 1,
+                                                  &neighbor_min.height,
+                                                  &neighbor_nat.height);
+
+      pnl_dock_bin_get_children_preferred_width (self,
+                                                 &children [1],
+                                                 n_children - 1,
+                                                 &neighbor_min.width,
+                                                 &neighbor_nat.width);
+
+      pnl_dock_bin_negotiate_size (self,
+                                   allocation,
+                                   &child->min_req,
+                                   &child->nat_req,
+                                   &neighbor_min,
+                                   &neighbor_nat,
+                                   &child_alloc);
+
+      switch (child->type)
+        {
+        case PNL_DOCK_BIN_CHILD_LEFT:
+          child_alloc.x = allocation->x;
+          child_alloc.y = allocation->y;
+          child_alloc.height = allocation->height;
+          child_alloc.width -= handle_size;
+          allocation->x += child_alloc.width + handle_size;
+          allocation->width -= child_alloc.width + handle_size;
+          break;
+
+        case PNL_DOCK_BIN_CHILD_RIGHT:
+          child_alloc.width -= handle_size;
+          child_alloc.x = allocation->x + allocation->width - child_alloc.width;
+          child_alloc.y = allocation->y;
+          child_alloc.height = allocation->height;
+          allocation->width -= child_alloc.width + handle_size;
+          break;
+
+        case PNL_DOCK_BIN_CHILD_TOP:
+          child_alloc.x = allocation->x;
+          child_alloc.y = allocation->y;
+          child_alloc.width = allocation->width;
+          child_alloc.height -= handle_size;
+          allocation->y += child_alloc.height + handle_size;
+          allocation->height -= child_alloc.height + handle_size;
+          break;
+
+        case PNL_DOCK_BIN_CHILD_BOTTOM:
+          child_alloc.height -= handle_size;
+          child_alloc.x = allocation->x;
+          child_alloc.y = allocation->y + allocation->height - child_alloc.height;
+          child_alloc.width = allocation->width;
+          allocation->height -= child_alloc.height + handle_size;
+          break;
+
+        case PNL_DOCK_BIN_CHILD_CENTER:
+        case LAST_PNL_DOCK_BIN_CHILD:
+        default:
+          g_assert_not_reached ();
+          break;
+        }
+
+      handle_alloc = child_alloc;
+
+      switch (child->type)
+        {
+        case PNL_DOCK_BIN_CHILD_LEFT:
+          handle_alloc.x += handle_alloc.width - HANDLE_WIDTH;
+          handle_alloc.width = HANDLE_WIDTH;
+
+        case PNL_DOCK_BIN_CHILD_RIGHT:
+          handle_alloc.width = HANDLE_WIDTH;
+          break;
+
+        case PNL_DOCK_BIN_CHILD_BOTTOM:
+          handle_alloc.height = HANDLE_HEIGHT;
+          break;
+
+        case PNL_DOCK_BIN_CHILD_TOP:
+          handle_alloc.y += handle_alloc.height - HANDLE_HEIGHT;
+          handle_alloc.height = HANDLE_HEIGHT;
+          break;
+
+        case PNL_DOCK_BIN_CHILD_CENTER:
+        case LAST_PNL_DOCK_BIN_CHILD:
+        default:
+          break;
+        }
+
+      if (child_alloc.width > 0 && child_alloc.height > 0 && child->handle)
+        gdk_window_move_resize (child->handle,
+                                handle_alloc.x, handle_alloc.y,
+                                handle_alloc.width, handle_alloc.height);
+
+      gtk_widget_size_allocate (child->widget, &child_alloc);
+    }
+
+  pnl_dock_bin_child_size_allocate (self, &children [1], n_children - 1, allocation);
+}
+
+static void
+pnl_dock_bin_size_allocate (GtkWidget     *widget,
+                            GtkAllocation *allocation)
+{
+  PnlDockBin *self = (PnlDockBin *)widget;
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  GtkAllocation child_allocation;
+  guint i;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (allocation != NULL);
+
+  gtk_widget_set_allocation (widget, allocation);
+
+  child_allocation.x = 0;
+  child_allocation.y = 0;
+  child_allocation.width = allocation->width;
+  child_allocation.height = allocation->height;
+
+  if (gtk_widget_get_realized (widget))
+    {
+      gdk_window_move_resize (gtk_widget_get_window (widget),
+                              allocation->x,
+                              allocation->y,
+                              child_allocation.width,
+                              child_allocation.height);
+    }
+
+  pnl_dock_bin_child_size_allocate (self,
+                                    priv->children,
+                                    G_N_ELEMENTS (priv->children),
+                                    &child_allocation);
+
+  /*
+   * Hide all of the handle input windows that should be hidden
+   * because the child has an empty allocation.
+   */
+
+  for (i = 0; i < PNL_DOCK_BIN_CHILD_CENTER; i++)
+    {
+      PnlDockBinChild *child = &priv->children [i];
+
+      if (child->handle != NULL)
+        {
+          if (PNL_IS_DOCK_BIN_EDGE (child->widget) &&
+              pnl_dock_revealer_get_reveal_child (PNL_DOCK_REVEALER (child->widget)))
+            gdk_window_show (child->handle);
+          else
+            gdk_window_hide (child->handle);
+        }
+    }
+}
+
+static void
+pnl_dock_bin_visible_action (GSimpleAction *action,
+                             GVariant      *state,
+                             gpointer       user_data)
+{
+  PnlDockBin *self = user_data;
+  PnlDockBinChild *child;
+  PnlDockBinChildType type;
+  const gchar *action_name;
+  gboolean reveal_child;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (state != NULL);
+  g_assert (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN));
+
+  action_name = g_action_get_name (G_ACTION (action));
+  reveal_child = g_variant_get_boolean (state);
+
+  if (g_str_has_prefix (action_name, "left"))
+    type = PNL_DOCK_BIN_CHILD_LEFT;
+  else if (g_str_has_prefix (action_name, "right"))
+    type = PNL_DOCK_BIN_CHILD_RIGHT;
+  else if (g_str_has_prefix (action_name, "top"))
+    type = PNL_DOCK_BIN_CHILD_TOP;
+  else if (g_str_has_prefix (action_name, "bottom"))
+    type = PNL_DOCK_BIN_CHILD_BOTTOM;
+  else
+    return;
+
+  child = pnl_dock_bin_get_child_typed (self, type);
+
+  pnl_dock_revealer_set_reveal_child (PNL_DOCK_REVEALER (child->widget), reveal_child);
+}
+
+static gint
+pnl_dock_bin_child_compare (gconstpointer a,
+                            gconstpointer b)
+{
+  const PnlDockBinChild *child_a = a;
+  const PnlDockBinChild *child_b = b;
+
+  if (child_a->type == PNL_DOCK_BIN_CHILD_CENTER)
+    return 1;
+  else if (child_b->type == PNL_DOCK_BIN_CHILD_CENTER)
+    return -1;
+
+  return child_a->priority - child_b->priority;
+}
+
+static void
+pnl_dock_bin_set_child_priority (PnlDockBin *self,
+                                 GtkWidget  *widget,
+                                 gint        priority)
+{
+  PnlDockBinChild *child;
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  child = pnl_dock_bin_get_child (self, widget);
+  child->priority = priority;
+
+  g_qsort_with_data (&priv->children[0],
+                     PNL_DOCK_BIN_CHILD_CENTER,
+                     sizeof (PnlDockBinChild),
+                     (GCompareDataFunc)pnl_dock_bin_child_compare,
+                     NULL);
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+pnl_dock_bin_create_child_handle (PnlDockBin      *self,
+                                  PnlDockBinChild *child)
+{
+  GdkWindowAttr attributes = { 0 };
+  GdkDisplay *display;
+  GdkWindow *parent;
+  GdkCursorType cursor_type;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (child != NULL);
+  g_assert (child->type < PNL_DOCK_BIN_CHILD_CENTER);
+  g_assert (child->handle == NULL);
+
+  display = gtk_widget_get_display (GTK_WIDGET (self));
+  parent = gtk_widget_get_window (GTK_WIDGET (self));
+
+  cursor_type = (child->type == PNL_DOCK_BIN_CHILD_LEFT || child->type == PNL_DOCK_BIN_CHILD_RIGHT)
+              ? GDK_SB_H_DOUBLE_ARROW
+              : GDK_SB_V_DOUBLE_ARROW;
+
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.wclass = GDK_INPUT_ONLY;
+  attributes.x = -1;
+  attributes.y = -1;
+  attributes.width = 1;
+  attributes.height = 1;
+  attributes.visual = gtk_widget_get_visual (GTK_WIDGET (self));
+  attributes.event_mask = (GDK_BUTTON_PRESS_MASK |
+                           GDK_BUTTON_RELEASE_MASK |
+                           GDK_ENTER_NOTIFY_MASK |
+                           GDK_LEAVE_NOTIFY_MASK |
+                           GDK_POINTER_MOTION_MASK);
+  attributes.cursor = gdk_cursor_new_for_display (display, cursor_type);
+
+  child->handle = gdk_window_new (parent, &attributes, GDK_WA_CURSOR);
+  gtk_widget_register_window (GTK_WIDGET (self), child->handle);
+
+  g_clear_object (&attributes.cursor);
+}
+
+static void
+pnl_dock_bin_destroy_child_handle (PnlDockBin      *self,
+                                   PnlDockBinChild *child)
+{
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (child != NULL);
+  g_assert (child->type < PNL_DOCK_BIN_CHILD_CENTER);
+
+  if (child->handle != NULL)
+    {
+      gdk_window_destroy (child->handle);
+      child->handle = NULL;
+    }
+}
+
+static void
+pnl_dock_bin_realize (GtkWidget *widget)
+{
+  PnlDockBin *self = (PnlDockBin *)widget;
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  GdkWindowAttr attributes = { 0 };
+  GdkWindow *parent;
+  GdkWindow *window;
+  GtkAllocation alloc;
+  gint attributes_mask = 0;
+  guint i;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+
+  gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+
+  gtk_widget_set_realized (GTK_WIDGET (self), TRUE);
+
+  parent = gtk_widget_get_parent_window (GTK_WIDGET (self));
+
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.wclass = GDK_INPUT_OUTPUT;
+  attributes.visual = gtk_widget_get_visual (GTK_WIDGET (self));
+  attributes.x = alloc.x;
+  attributes.y = alloc.y;
+  attributes.width = alloc.width;
+  attributes.height = alloc.height;
+  attributes.event_mask = 0;
+
+  attributes_mask = (GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL);
+
+  window = gdk_window_new (parent, &attributes, attributes_mask);
+  gtk_widget_set_window (GTK_WIDGET (self), window);
+  gtk_widget_register_window (GTK_WIDGET (self), window);
+
+  for (i = 0; i < PNL_DOCK_BIN_CHILD_CENTER; i++)
+    {
+      PnlDockBinChild *child = &priv->children [i];
+
+      pnl_dock_bin_create_child_handle (self, child);
+    }
+}
+
+static void
+pnl_dock_bin_unrealize (GtkWidget *widget)
+{
+  PnlDockBin *self = (PnlDockBin *)widget;
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+
+  for (i = 0; i < PNL_DOCK_BIN_CHILD_CENTER; i++)
+    {
+      PnlDockBinChild *child = &priv->children [i];
+
+      pnl_dock_bin_destroy_child_handle (self, child);
+    }
+
+  GTK_WIDGET_CLASS (pnl_dock_bin_parent_class)->unrealize (widget);
+}
+
+static void
+pnl_dock_bin_map (GtkWidget *widget)
+{
+  PnlDockBin *self = (PnlDockBin *)widget;
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+
+  GTK_WIDGET_CLASS (pnl_dock_bin_parent_class)->map (widget);
+
+  for (i = 0; i < PNL_DOCK_BIN_CHILD_CENTER; i++)
+    {
+      PnlDockBinChild *child = &priv->children [i];
+
+      if (child->handle != NULL)
+        gdk_window_show (child->handle);
+    }
+}
+
+static void
+pnl_dock_bin_unmap (GtkWidget *widget)
+{
+  PnlDockBin *self = (PnlDockBin *)widget;
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+
+  for (i = 0; i < PNL_DOCK_BIN_CHILD_CENTER; i++)
+    {
+      PnlDockBinChild *child = &priv->children [i];
+
+      if (child->handle != NULL)
+        gdk_window_hide (child->handle);
+    }
+
+  GTK_WIDGET_CLASS (pnl_dock_bin_parent_class)->unmap (widget);
+}
+
+static void
+pnl_dock_bin_pan_gesture_drag_begin (PnlDockBin    *self,
+                                     gdouble        x,
+                                     gdouble        y,
+                                     GtkGesturePan *gesture)
+{
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  GdkEventSequence *sequence;
+  PnlDockBinChild *child = NULL;
+  GtkAllocation child_alloc;
+  const GdkEvent *event;
+  guint i;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (GTK_IS_GESTURE_PAN (gesture));
+
+  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+  event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+
+  for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
+    {
+      if (priv->children [i].handle == event->any.window)
+        {
+          child = &priv->children [i];
+          break;
+        }
+    }
+
+  if (child == NULL || child->type >= PNL_DOCK_BIN_CHILD_CENTER)
+    {
+      gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+      return;
+    }
+
+  gtk_widget_get_allocation (child->widget, &child_alloc);
+
+  priv->drag_child = child;
+  priv->drag_child->drag_offset = 0;
+
+  if (child->type == PNL_DOCK_BIN_CHILD_LEFT || child->type == PNL_DOCK_BIN_CHILD_RIGHT)
+    {
+      gtk_gesture_pan_set_orientation (gesture, GTK_ORIENTATION_HORIZONTAL);
+      priv->drag_child->drag_begin_position = child_alloc.width;
+    }
+  else
+    {
+      gtk_gesture_pan_set_orientation (gesture, GTK_ORIENTATION_VERTICAL);
+      priv->drag_child->drag_begin_position = child_alloc.height;
+    }
+
+  gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+}
+
+static void
+pnl_dock_bin_pan_gesture_drag_end (PnlDockBin    *self,
+                                   gdouble        x,
+                                   gdouble        y,
+                                   GtkGesturePan *gesture)
+{
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  GdkEventSequence *sequence;
+  GtkEventSequenceState state;
+  GtkAllocation child_alloc;
+  gint position;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (GTK_IS_GESTURE_PAN (gesture));
+
+  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+  state = gtk_gesture_get_sequence_state (GTK_GESTURE (gesture), sequence);
+
+  if (state == GTK_EVENT_SEQUENCE_DENIED)
+    goto cleanup;
+
+  g_assert (priv->drag_child != NULL);
+  g_assert (PNL_IS_DOCK_BIN_EDGE (priv->drag_child->widget));
+
+  gtk_widget_get_allocation (priv->drag_child->widget, &child_alloc);
+
+  if ((priv->drag_child->type == PNL_DOCK_BIN_CHILD_LEFT) ||
+      (priv->drag_child->type == PNL_DOCK_BIN_CHILD_RIGHT))
+    position = child_alloc.width;
+  else
+    position = child_alloc.height;
+
+  pnl_dock_revealer_set_position (PNL_DOCK_REVEALER (priv->drag_child->widget), position);
+
+cleanup:
+  if (priv->drag_child != NULL)
+    {
+      priv->drag_child->drag_offset = 0;
+      priv->drag_child->drag_begin_position = 0;
+      priv->drag_child = NULL;
+    }
+}
+
+static void
+pnl_dock_bin_pan_gesture_pan (PnlDockBin      *self,
+                              GtkPanDirection  direction,
+                              gdouble          offset,
+                              GtkGesturePan   *gesture)
+{
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  gint position;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (GTK_IS_GESTURE_PAN (gesture));
+  g_assert (priv->drag_child != NULL);
+  g_assert (priv->drag_child->type < PNL_DOCK_BIN_CHILD_CENTER);
+
+  /*
+   * This callback is used to adjust the size allocation of the edge in
+   * question (denoted by priv->drag_child). It is always one of the
+   * left, right, top, or bottom children. Never the center child.
+   *
+   * Because of how GtkRevealer works, we are doing a bit of a workaround
+   * here. We need the revealer (the PnlDockBinEdge) child to have a size
+   * request that matches the visible area, otherwise animating out the
+   * revealer will not look right.
+   */
+
+  if (direction == GTK_PAN_DIRECTION_UP)
+    {
+      if (priv->drag_child->type == PNL_DOCK_BIN_CHILD_TOP)
+        offset = -offset;
+    }
+  else if (direction == GTK_PAN_DIRECTION_DOWN)
+    {
+      if (priv->drag_child->type == PNL_DOCK_BIN_CHILD_BOTTOM)
+        offset = -offset;
+    }
+  else if (direction == GTK_PAN_DIRECTION_LEFT)
+    {
+      if (priv->drag_child->type == PNL_DOCK_BIN_CHILD_LEFT)
+        offset = -offset;
+    }
+  else if (direction == GTK_PAN_DIRECTION_RIGHT)
+    {
+      if (priv->drag_child->type == PNL_DOCK_BIN_CHILD_RIGHT)
+        offset = -offset;
+    }
+
+  priv->drag_child->drag_offset = (gint)offset;
+
+  position = priv->drag_child->drag_offset + priv->drag_child->drag_begin_position;
+  if (position >= 0)
+    pnl_dock_revealer_set_position (PNL_DOCK_REVEALER (priv->drag_child->widget), position);
+}
+
+static void
+pnl_dock_bin_create_pan_gesture (PnlDockBin *self)
+{
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  GtkGesture *gesture;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (priv->pan_gesture == NULL);
+
+  gesture = gtk_gesture_pan_new (GTK_WIDGET (self), GTK_ORIENTATION_HORIZONTAL);
+  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE);
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), GTK_PHASE_CAPTURE);
+
+  g_signal_connect_object (gesture,
+                           "drag-begin",
+                           G_CALLBACK (pnl_dock_bin_pan_gesture_drag_begin),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (gesture,
+                           "drag-end",
+                           G_CALLBACK (pnl_dock_bin_pan_gesture_drag_end),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (gesture,
+                           "pan",
+                           G_CALLBACK (pnl_dock_bin_pan_gesture_pan),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  priv->pan_gesture = GTK_GESTURE_PAN (gesture);
+}
+
+static void
+pnl_dock_bin_drag_enter (PnlDockBin     *self,
+                         GdkDragContext *drag_context,
+                         gint            x,
+                         gint            y,
+                         guint           time_)
+{
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (GDK_IS_DRAG_CONTEXT (drag_context));
+
+}
+
+static gboolean
+pnl_dock_bin_drag_motion (GtkWidget      *widget,
+                          GdkDragContext *drag_context,
+                          gint            x,
+                          gint            y,
+                          guint           time_)
+{
+  PnlDockBin *self = (PnlDockBin *)widget;
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (GDK_IS_DRAG_CONTEXT (drag_context));
+
+  /*
+   * The purpose of this function is to determine of the location for which
+   * the drag is currently located, is a valid drop site. We first calculate
+   * the locations for the various zones, and then simply determine which
+   * zone we are in (or none).
+   */
+
+  if (priv->dnd_drag_x == -1 && priv->dnd_drag_y == -1)
+    pnl_dock_bin_drag_enter (self, drag_context, x, y, time_);
+
+  priv->dnd_drag_x = x;
+  priv->dnd_drag_y = y;
+
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+
+  return TRUE;
+}
+
+static void
+pnl_dock_bin_drag_leave (GtkWidget      *widget,
+                         GdkDragContext *context,
+                         guint           time_)
+{
+  PnlDockBin *self = (PnlDockBin *)widget;
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (GDK_IS_DRAG_CONTEXT (context));
+
+  priv->dnd_drag_x = -1;
+  priv->dnd_drag_y = -1;
+}
+
+static void
+pnl_dock_bin_grab_focus (GtkWidget *widget)
+{
+  PnlDockBin *self = (PnlDockBin *)widget;
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  PnlDockBinChild *child;
+  guint i;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+
+  child = pnl_dock_bin_get_child_typed (self, PNL_DOCK_BIN_CHILD_CENTER);
+
+  if (child->widget != NULL)
+    {
+      if (gtk_widget_child_focus (child->widget, GTK_DIR_TAB_FORWARD))
+        return;
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
+    {
+      child = &priv->children [i];
+
+      if (child->widget != NULL)
+        {
+          if (gtk_widget_child_focus (child->widget, GTK_DIR_TAB_FORWARD))
+            return;
+        }
+    }
+}
+
+static GtkWidget *
+pnl_dock_bin_real_create_edge (PnlDockBin *self)
+{
+  g_assert (PNL_IS_DOCK_BIN (self));
+
+  return g_object_new (PNL_TYPE_DOCK_BIN_EDGE,
+                       "visible", TRUE,
+                       "reveal-child", FALSE,
+                       NULL);
+}
+
+static void
+pnl_dock_bin_create_edge (PnlDockBin          *self,
+                          PnlDockBinChild     *child,
+                          PnlDockBinChildType  type)
+{
+  GAction *action;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (child != NULL);
+  g_assert (type >= PNL_DOCK_BIN_CHILD_LEFT);
+  g_assert (type < LAST_PNL_DOCK_BIN_CHILD);
+
+  child->widget = PNL_DOCK_BIN_GET_CLASS (self)->create_edge (self);
+
+  if (child->widget == NULL)
+    {
+      g_warning ("%s failed to create edge widget",
+                 G_OBJECT_TYPE_NAME (self));
+      return;
+    }
+  else if (!PNL_IS_DOCK_BIN_EDGE (child->widget))
+    {
+      g_warning ("%s child %s is not a PnlDockBinEdge",
+                 G_OBJECT_TYPE_NAME (self),
+                 G_OBJECT_TYPE_NAME (child));
+      return;
+    }
+
+  g_object_set (child->widget, "edge", (GtkPositionType)type, NULL);
+  gtk_widget_set_parent (g_object_ref_sink (child->widget), GTK_WIDGET (self));
+
+  action = pnl_dock_bin_get_action_for_type (self, type);
+  g_object_bind_property_full (child->widget, "reveal-child",
+                               action, "state",
+                               G_BINDING_SYNC_CREATE,
+                               map_boolean_to_variant,
+                               NULL, NULL, NULL);
+}
+
+static gboolean
+pnl_dock_bin_draw (GtkWidget *widget,
+                   cairo_t   *cr)
+{
+  PnlDockBin *self = (PnlDockBin *)widget;
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  GtkStyleContext *style_context;
+  gboolean ret;
+  guint i;
+  gint handle_size = 0;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (cr != NULL);
+
+  ret = GTK_WIDGET_CLASS (pnl_dock_bin_parent_class)->draw (widget, cr);
+
+  if (ret == GDK_EVENT_STOP)
+    return ret;
+
+  gtk_widget_style_get (widget,
+                        "handle-size", &handle_size,
+                        NULL);
+
+  if (handle_size == 0)
+    return ret;
+
+  style_context = gtk_widget_get_style_context (widget);
+
+  for (i = 0; i < PNL_DOCK_BIN_CHILD_CENTER; i++)
+    {
+      PnlDockBinChild *child = &priv->children [i];
+
+      if ((child->widget != NULL) &&
+          gtk_widget_get_visible (child->widget) &&
+          gtk_widget_get_child_visible (child->widget))
+        {
+          GtkAllocation handle;
+
+          gtk_widget_get_allocation (child->widget, &handle);
+
+          if (((child->type == PNL_DOCK_BIN_CHILD_LEFT) ||
+               (child->type == PNL_DOCK_BIN_CHILD_RIGHT)) &&
+              (handle.width <= handle_size))
+            continue;
+
+          if (((child->type == PNL_DOCK_BIN_CHILD_TOP) ||
+               (child->type == PNL_DOCK_BIN_CHILD_BOTTOM)) &&
+              (handle.height <= handle_size))
+            continue;
+
+          switch (child->type)
+            {
+            case PNL_DOCK_BIN_CHILD_LEFT:
+              handle.x += handle.width;
+              handle.width = handle_size;
+              break;
+
+            case PNL_DOCK_BIN_CHILD_RIGHT:
+              handle.x -= handle_size;
+              handle.width = handle_size;
+              break;
+
+            case PNL_DOCK_BIN_CHILD_TOP:
+              handle.y += handle.height;
+              handle.height = handle_size;
+              break;
+
+            case PNL_DOCK_BIN_CHILD_BOTTOM:
+              handle.y -= handle_size;
+              handle.height = handle_size;
+              break;
+
+            case PNL_DOCK_BIN_CHILD_CENTER:
+            case LAST_PNL_DOCK_BIN_CHILD:
+            default:
+              g_assert_not_reached ();
+              break;
+            }
+
+          gtk_render_handle (style_context, cr, handle.x, handle.y, handle.width, handle.height);
+        }
+    }
+
+  return ret;
+}
+
+static void
+pnl_dock_bin_init_child (PnlDockBin          *self,
+                         PnlDockBinChild     *child,
+                         PnlDockBinChildType  type)
+{
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (child != NULL);
+  g_assert (type >= PNL_DOCK_BIN_CHILD_LEFT);
+  g_assert (type < LAST_PNL_DOCK_BIN_CHILD);
+
+  child->type = type;
+  child->priority = (int)type * 100;
+}
+
+static void
+pnl_dock_bin_destroy (GtkWidget *widget)
+{
+  PnlDockBin *self = PNL_DOCK_BIN (widget);
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+
+  g_clear_object (&priv->actions);
+  g_clear_object (&priv->pan_gesture);
+
+  GTK_WIDGET_CLASS (pnl_dock_bin_parent_class)->destroy (widget);
+}
+
+static void
+pnl_dock_bin_get_child_property (GtkContainer *container,
+                                 GtkWidget    *widget,
+                                 guint         prop_id,
+                                 GValue       *value,
+                                 GParamSpec   *pspec)
+{
+  PnlDockBin *self = PNL_DOCK_BIN (container);
+  PnlDockBinChild *child = pnl_dock_bin_get_child (self, widget);
+
+  switch (prop_id)
+    {
+    case CHILD_PROP_PRIORITY:
+      g_value_set_int (value, child->priority);
+      break;
+
+    case CHILD_PROP_POSITION:
+      g_value_set_enum (value, (GtkPositionType)child->type);
+      break;
+
+    default:
+      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_bin_set_child_property (GtkContainer *container,
+                                 GtkWidget    *widget,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  PnlDockBin *self = PNL_DOCK_BIN (container);
+
+  switch (prop_id)
+    {
+    case CHILD_PROP_PRIORITY:
+      pnl_dock_bin_set_child_priority (self, widget, g_value_get_int (value));
+      break;
+
+    default:
+      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_bin_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  PnlDockBin *self = PNL_DOCK_BIN (object);
+
+  switch (prop_id)
+    {
+    case PROP_MANAGER:
+      g_value_set_object (value, pnl_dock_item_get_manager (PNL_DOCK_ITEM (self)));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_bin_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  PnlDockBin *self = PNL_DOCK_BIN (object);
+
+  switch (prop_id)
+    {
+    case PROP_MANAGER:
+      pnl_dock_item_set_manager (PNL_DOCK_ITEM (self), g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_bin_class_init (PnlDockBinClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+  object_class->get_property = pnl_dock_bin_get_property;
+  object_class->set_property = pnl_dock_bin_set_property;
+
+  widget_class->draw = pnl_dock_bin_draw;
+  widget_class->destroy = pnl_dock_bin_destroy;
+  widget_class->drag_leave = pnl_dock_bin_drag_leave;
+  widget_class->drag_motion = pnl_dock_bin_drag_motion;
+  widget_class->get_preferred_height = pnl_dock_bin_get_preferred_height;
+  widget_class->get_preferred_width = pnl_dock_bin_get_preferred_width;
+  widget_class->grab_focus = pnl_dock_bin_grab_focus;
+  widget_class->map = pnl_dock_bin_map;
+  widget_class->realize = pnl_dock_bin_realize;
+  widget_class->size_allocate = pnl_dock_bin_size_allocate;
+  widget_class->unmap = pnl_dock_bin_unmap;
+  widget_class->unrealize = pnl_dock_bin_unrealize;
+
+  container_class->add = pnl_dock_bin_add;
+  container_class->forall = pnl_dock_bin_forall;
+  container_class->get_child_property = pnl_dock_bin_get_child_property;
+  container_class->remove = pnl_dock_bin_remove;
+  container_class->set_child_property = pnl_dock_bin_set_child_property;
+
+  klass->create_edge = pnl_dock_bin_real_create_edge;
+
+  g_object_class_override_property (object_class, PROP_MANAGER, "manager");
+
+  child_properties [CHILD_PROP_POSITION] =
+    g_param_spec_enum ("position",
+                       "Position",
+                       "The position of the dock edge",
+                       GTK_TYPE_POSITION_TYPE,
+                       GTK_POS_LEFT,
+                       (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  child_properties [CHILD_PROP_PRIORITY] =
+    g_param_spec_int ("priority",
+                      "Priority",
+                      "The priority of the dock edge",
+                      G_MININT,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  gtk_container_class_install_child_properties (container_class, N_CHILD_PROPS, child_properties);
+
+  style_properties [STYLE_PROP_HANDLE_SIZE] =
+    g_param_spec_int ("handle-size",
+                      "Handle Size",
+                      "Width of the resize handle",
+                      0,
+                      G_MAXINT,
+                      1,
+                      (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+  gtk_widget_class_install_style_property (widget_class, style_properties [STYLE_PROP_HANDLE_SIZE]);
+
+  gtk_widget_class_set_css_name (widget_class, "dockbin");
+}
+
+static void
+pnl_dock_bin_init (PnlDockBin *self)
+{
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  static GtkTargetEntry drag_entries[] = {
+    { (gchar *)"PNL_DOCK_BIN_WIDGET", GTK_TARGET_SAME_APP, 0 },
+  };
+  static const GActionEntry entries[] = {
+    { "left-visible", NULL, NULL, "false", pnl_dock_bin_visible_action },
+    { "right-visible", NULL, NULL, "false", pnl_dock_bin_visible_action },
+    { "top-visible", NULL, NULL, "false", pnl_dock_bin_visible_action },
+    { "bottom-visible", NULL, NULL, "false", pnl_dock_bin_visible_action },
+  };
+
+  gtk_widget_set_has_window (GTK_WIDGET (self), TRUE);
+
+  priv->actions = g_simple_action_group_new ();
+  g_action_map_add_action_entries (G_ACTION_MAP (priv->actions),
+                                   entries, G_N_ELEMENTS (entries),
+                                   self);
+  gtk_widget_insert_action_group (GTK_WIDGET (self), "dockbin", G_ACTION_GROUP (priv->actions));
+
+  pnl_dock_bin_create_pan_gesture (self);
+
+  gtk_drag_dest_set (GTK_WIDGET (self),
+                     GTK_DEST_DEFAULT_ALL,
+                     drag_entries,
+                     G_N_ELEMENTS (drag_entries),
+                     GDK_ACTION_MOVE);
+
+  priv->dnd_drag_x = -1;
+  priv->dnd_drag_y = -1;
+
+  pnl_dock_bin_init_child (self, &priv->children [0], PNL_DOCK_BIN_CHILD_LEFT);
+  pnl_dock_bin_init_child (self, &priv->children [1], PNL_DOCK_BIN_CHILD_RIGHT);
+  pnl_dock_bin_init_child (self, &priv->children [2], PNL_DOCK_BIN_CHILD_BOTTOM);
+  pnl_dock_bin_init_child (self, &priv->children [3], PNL_DOCK_BIN_CHILD_TOP);
+  pnl_dock_bin_init_child (self, &priv->children [4], PNL_DOCK_BIN_CHILD_CENTER);
+}
+
+GtkWidget *
+pnl_dock_bin_new (void)
+{
+  return g_object_new (PNL_TYPE_DOCK_BIN, NULL);
+}
+
+/**
+ * pnl_dock_bin_get_center_widget:
+ * @self: A #PnlDockBin
+ *
+ * Gets the center widget for the dock.
+ *
+ * Returns: (transfer none) (nullable): A #GtkWidget or %NULL.
+ */
+GtkWidget *
+pnl_dock_bin_get_center_widget (PnlDockBin *self)
+{
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  PnlDockBinChild *child;
+
+  g_return_val_if_fail (PNL_IS_DOCK_BIN (self), NULL);
+
+  child = &priv->children [PNL_DOCK_BIN_CHILD_CENTER];
+
+  return child->widget;
+}
+
+/**
+ * pnl_dock_bin_get_top_edge:
+ * Returns: (transfer none): A #GtkWidget
+ */
+GtkWidget *
+pnl_dock_bin_get_top_edge (PnlDockBin *self)
+{
+  PnlDockBinChild *child;
+
+  g_return_val_if_fail (PNL_IS_DOCK_BIN (self), NULL);
+
+  child = pnl_dock_bin_get_child_typed (self, PNL_DOCK_BIN_CHILD_TOP);
+
+  if (child->widget == NULL)
+    pnl_dock_bin_create_edge (self, child, PNL_DOCK_BIN_CHILD_TOP);
+
+  return child->widget;
+}
+
+/**
+ * pnl_dock_bin_get_left_edge:
+ * Returns: (transfer none): A #GtkWidget
+ */
+GtkWidget *
+pnl_dock_bin_get_left_edge (PnlDockBin *self)
+{
+  PnlDockBinChild *child;
+
+  g_return_val_if_fail (PNL_IS_DOCK_BIN (self), NULL);
+
+  child = pnl_dock_bin_get_child_typed (self, PNL_DOCK_BIN_CHILD_LEFT);
+
+  if (child->widget == NULL)
+    pnl_dock_bin_create_edge (self, child, PNL_DOCK_BIN_CHILD_LEFT);
+
+  return child->widget;
+}
+
+/**
+ * pnl_dock_bin_get_bottom_edge:
+ * Returns: (transfer none): A #GtkWidget
+ */
+GtkWidget *
+pnl_dock_bin_get_bottom_edge (PnlDockBin *self)
+{
+  PnlDockBinChild *child;
+
+  g_return_val_if_fail (PNL_IS_DOCK_BIN (self), NULL);
+
+  child = pnl_dock_bin_get_child_typed (self, PNL_DOCK_BIN_CHILD_BOTTOM);
+
+  if (child->widget == NULL)
+    pnl_dock_bin_create_edge (self, child, PNL_DOCK_BIN_CHILD_BOTTOM);
+
+  return child->widget;
+}
+
+/**
+ * pnl_dock_bin_get_right_edge:
+ * Returns: (transfer none): A #GtkWidget
+ */
+GtkWidget *
+pnl_dock_bin_get_right_edge (PnlDockBin *self)
+{
+  PnlDockBinChild *child;
+
+  g_return_val_if_fail (PNL_IS_DOCK_BIN (self), NULL);
+
+  child = pnl_dock_bin_get_child_typed (self, PNL_DOCK_BIN_CHILD_RIGHT);
+
+  if (child->widget == NULL)
+    pnl_dock_bin_create_edge (self, child, PNL_DOCK_BIN_CHILD_RIGHT);
+
+  return child->widget;
+}
+
+static void
+pnl_dock_bin_init_dock_iface (PnlDockInterface *iface)
+{
+}
+
+static void
+pnl_dock_bin_add_child (GtkBuildable *buildable,
+                        GtkBuilder   *builder,
+                        GObject      *child,
+                        const gchar  *type)
+{
+  PnlDockBin *self = (PnlDockBin *)buildable;
+  GtkWidget *parent;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (GTK_IS_BUILDER (builder));
+  g_assert (G_IS_OBJECT (child));
+
+  if (!GTK_IS_WIDGET (child))
+    {
+      g_warning ("Attempt to add a child of type \"%s\" to a \"%s\"",
+                 G_OBJECT_TYPE_NAME (child), G_OBJECT_TYPE_NAME (self));
+      return;
+    }
+
+  if (PNL_IS_DOCK_ITEM (child) &&
+      !pnl_dock_item_adopt (PNL_DOCK_ITEM (self), PNL_DOCK_ITEM (child)))
+    {
+      g_warning ("Child of type %s has a different PnlDockManager than %s",
+                 G_OBJECT_TYPE_NAME (child), G_OBJECT_TYPE_NAME (self));
+      return;
+    }
+
+  if (!type || !*type || (g_strcmp0 ("center", type) == 0))
+    {
+      gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (child));
+      return;
+    }
+
+  if (g_strcmp0 ("top", type) == 0)
+    parent = pnl_dock_bin_get_top_edge (self);
+  else if (g_strcmp0 ("bottom", type) == 0)
+    parent = pnl_dock_bin_get_bottom_edge (self);
+  else if (g_strcmp0 ("right", type) == 0)
+    parent = pnl_dock_bin_get_right_edge (self);
+  else
+    parent = pnl_dock_bin_get_left_edge (self);
+
+  if (PNL_IS_DOCK_BIN_EDGE (parent))
+    gtk_container_add (GTK_CONTAINER (parent), GTK_WIDGET (child));
+}
+
+static void
+pnl_dock_bin_init_buildable_iface (GtkBuildableIface *iface)
+{
+  iface->add_child = pnl_dock_bin_add_child;
+}
+
+static void
+pnl_dock_bin_present_child (PnlDockItem *item,
+                            PnlDockItem *widget)
+{
+  PnlDockBin *self = (PnlDockBin *)item;
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (PNL_IS_DOCK_ITEM (widget));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
+    {
+      PnlDockBinChild *child = &priv->children [i];
+
+      if (PNL_IS_DOCK_BIN_EDGE (child->widget) &&
+          gtk_widget_is_ancestor (GTK_WIDGET (child->widget), child->widget))
+        {
+          pnl_dock_revealer_set_reveal_child (PNL_DOCK_REVEALER (child->widget), TRUE);
+          return;
+        }
+    }
+}
+
+static gboolean
+pnl_dock_bin_get_child_visible (PnlDockItem *item,
+                                PnlDockItem *child)
+{
+  PnlDockBin *self = (PnlDockBin *)item;
+  PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+  GtkWidget *ancestor;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (PNL_IS_DOCK_ITEM (item));
+
+  ancestor = gtk_widget_get_ancestor (GTK_WIDGET (child), PNL_TYPE_DOCK_BIN_EDGE);
+
+  if (ancestor == NULL)
+    return FALSE;
+
+  if ((ancestor == priv->children [0].widget) ||
+      (ancestor == priv->children [1].widget) ||
+      (ancestor == priv->children [2].widget) ||
+      (ancestor == priv->children [3].widget))
+    return pnl_dock_revealer_get_reveal_child (PNL_DOCK_REVEALER (ancestor));
+
+  return FALSE;
+}
+
+static void
+pnl_dock_bin_set_child_visible (PnlDockItem *item,
+                                PnlDockItem *child,
+                                gboolean     child_visible)
+{
+  PnlDockBin *self = (PnlDockBin *)item;
+  GtkWidget *ancestor;
+
+  g_assert (PNL_IS_DOCK_BIN (self));
+  g_assert (PNL_IS_DOCK_ITEM (item));
+
+  ancestor = gtk_widget_get_ancestor (GTK_WIDGET (child), PNL_TYPE_DOCK_BIN_EDGE);
+
+  if (ancestor != NULL)
+    pnl_dock_revealer_set_reveal_child (PNL_DOCK_REVEALER (ancestor), child_visible);
+}
+
+static void
+pnl_dock_bin_init_dock_item_iface (PnlDockItemInterface *iface)
+{
+  iface->present_child = pnl_dock_bin_present_child;
+  iface->get_child_visible = pnl_dock_bin_get_child_visible;
+  iface->set_child_visible = pnl_dock_bin_set_child_visible;
+}
diff --git a/contrib/pnl/pnl-dock-bin.h b/contrib/pnl/pnl-dock-bin.h
new file mode 100644
index 0000000..9ba8ec8
--- /dev/null
+++ b/contrib/pnl/pnl-dock-bin.h
@@ -0,0 +1,55 @@
+/* pnl-dock-bin-bin.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_BIN_BIN_H
+#define PNL_DOCK_BIN_BIN_H
+
+#include "pnl-dock-types.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockBinClass
+{
+  GtkContainerClass parent;
+
+  GtkWidget *(*create_edge) (PnlDockBin *self);
+
+  void (*padding1) (void);
+  void (*padding2) (void);
+  void (*padding3) (void);
+  void (*padding4) (void);
+  void (*padding5) (void);
+  void (*padding6) (void);
+  void (*padding7) (void);
+  void (*padding8) (void);
+};
+
+GtkWidget *pnl_dock_bin_new               (void);
+GtkWidget *pnl_dock_bin_get_center_widget (PnlDockBin   *self);
+GtkWidget *pnl_dock_bin_get_top_edge      (PnlDockBin   *self);
+GtkWidget *pnl_dock_bin_get_left_edge     (PnlDockBin   *self);
+GtkWidget *pnl_dock_bin_get_bottom_edge   (PnlDockBin   *self);
+GtkWidget *pnl_dock_bin_get_right_edge    (PnlDockBin   *self);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_BIN_BIN_H */
diff --git a/contrib/pnl/pnl-dock-item.c b/contrib/pnl/pnl-dock-item.c
new file mode 100644
index 0000000..60e335e
--- /dev/null
+++ b/contrib/pnl/pnl-dock-item.c
@@ -0,0 +1,456 @@
+/* pnl-dock-item.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-item.h"
+#include "pnl-dock-manager.h"
+#include "pnl-dock-widget.h"
+
+G_DEFINE_INTERFACE (PnlDockItem, pnl_dock_item, GTK_TYPE_WIDGET)
+
+/*
+ * The PnlDockItem is an interface that acts more like a mixin.
+ * This is mostly out of wanting to preserve widget inheritance
+ * without having to duplicate all sorts of plumbing.
+ */
+
+enum {
+  MANAGER_SET,
+  N_SIGNALS
+};
+
+static guint signals [N_SIGNALS];
+
+static void
+pnl_dock_item_real_set_manager (PnlDockItem    *self,
+                                PnlDockManager *manager)
+{
+  PnlDockManager *old_manager;
+
+  g_assert (PNL_IS_DOCK_ITEM (self));
+  g_assert (!manager || PNL_IS_DOCK_MANAGER (manager));
+
+  if (NULL != (old_manager = pnl_dock_item_get_manager (self)))
+    {
+      if (PNL_IS_DOCK (self))
+        pnl_dock_manager_unregister_dock (old_manager, PNL_DOCK (self));
+    }
+
+  if (manager != NULL)
+    {
+      g_object_set_data_full (G_OBJECT (self),
+                              "PNL_DOCK_MANAGER",
+                              g_object_ref (manager),
+                              g_object_unref);
+      if (PNL_IS_DOCK (self))
+        pnl_dock_manager_register_dock (manager, PNL_DOCK (self));
+    }
+  else
+    g_object_set_data (G_OBJECT (self), "PNL_DOCK_MANAGER", NULL);
+
+  g_signal_emit (self, signals [MANAGER_SET], 0, old_manager);
+}
+
+static PnlDockManager *
+pnl_dock_item_real_get_manager (PnlDockItem *self)
+{
+  g_assert (PNL_IS_DOCK_ITEM (self));
+
+  return g_object_get_data (G_OBJECT (self), "PNL_DOCK_MANAGER");
+}
+
+static void
+pnl_dock_item_real_update_visibility (PnlDockItem *self)
+{
+  GtkWidget *parent;
+
+  g_assert (PNL_IS_DOCK_ITEM (self));
+
+  for (parent = gtk_widget_get_parent (GTK_WIDGET (self));
+       parent != NULL;
+       parent = gtk_widget_get_parent (parent))
+    {
+      if (PNL_IS_DOCK_ITEM (parent))
+        {
+          pnl_dock_item_update_visibility (PNL_DOCK_ITEM (parent));
+          break;
+        }
+    }
+}
+
+static void
+pnl_dock_item_propagate_manager (PnlDockItem *self)
+{
+  PnlDockManager *manager;
+  GPtrArray *ar;
+  guint i;
+
+  g_return_if_fail (PNL_IS_DOCK_ITEM (self));
+
+  if (!GTK_IS_CONTAINER (self))
+    return;
+
+  if (NULL == (manager = pnl_dock_item_get_manager (self)))
+    return;
+
+  if (NULL == (ar = g_object_get_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS")))
+    return;
+
+  for (i = 0; i < ar->len; i++)
+    {
+      PnlDockItem *item = g_ptr_array_index (ar, i);
+
+      pnl_dock_item_set_manager (item, manager);
+    }
+}
+
+static void
+pnl_dock_item_real_manager_set (PnlDockItem    *self,
+                                PnlDockManager *manager)
+{
+  g_assert (PNL_IS_DOCK_ITEM (self));
+  g_assert (!manager || PNL_IS_DOCK_MANAGER (manager));
+
+  pnl_dock_item_propagate_manager (self);
+}
+
+static void
+pnl_dock_item_default_init (PnlDockItemInterface *iface)
+{
+  iface->get_manager = pnl_dock_item_real_get_manager;
+  iface->set_manager = pnl_dock_item_real_set_manager;
+  iface->manager_set = pnl_dock_item_real_manager_set;
+  iface->update_visibility = pnl_dock_item_real_update_visibility;
+
+  signals [MANAGER_SET] =
+    g_signal_new ("manager-set",
+                  G_TYPE_FROM_INTERFACE (iface),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (PnlDockItemInterface, manager_set),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, PNL_TYPE_DOCK_MANAGER);
+}
+
+/**
+ * pnl_dock_item_get_manager:
+ * @self: A #PnlDockItem
+ *
+ * Gets the dock manager for this dock item.
+ *
+ * Returns: (nullable) (transfer none): A #PnlDockmanager.
+ */
+PnlDockManager *
+pnl_dock_item_get_manager (PnlDockItem *self)
+{
+  g_return_val_if_fail (PNL_IS_DOCK_ITEM (self), NULL);
+
+  return PNL_DOCK_ITEM_GET_IFACE (self)->get_manager (self);
+}
+
+/**
+ * pnl_dock_item_set_manager:
+ * @self: A #PnlDockItem
+ * @manager: (nullable): A #PnlDockManager
+ *
+ * Sets the dock manager for this #PnlDockItem.
+ */
+void
+pnl_dock_item_set_manager (PnlDockItem    *self,
+                           PnlDockManager *manager)
+{
+  g_return_if_fail (PNL_IS_DOCK_ITEM (self));
+  g_return_if_fail (!manager || PNL_IS_DOCK_MANAGER (manager));
+
+  PNL_DOCK_ITEM_GET_IFACE (self)->set_manager (self, manager);
+}
+
+void
+pnl_dock_item_update_visibility (PnlDockItem *self)
+{
+  g_return_if_fail (PNL_IS_DOCK_ITEM (self));
+
+  PNL_DOCK_ITEM_GET_IFACE (self)->update_visibility (self);
+}
+
+static void
+pnl_dock_item_child_weak_notify (gpointer  data,
+                                 GObject  *where_object_was)
+{
+  PnlDockItem *self = data;
+  GPtrArray *descendants;
+
+  g_assert (PNL_IS_DOCK_ITEM (self));
+
+  descendants = g_object_get_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS");
+
+  if (descendants != NULL)
+    g_ptr_array_remove (descendants, where_object_was);
+
+  pnl_dock_item_update_visibility (self);
+}
+
+static void
+pnl_dock_item_destroy (PnlDockItem *self)
+{
+  GPtrArray *descendants;
+  guint i;
+
+  g_assert (PNL_IS_DOCK_ITEM (self));
+
+  descendants = g_object_get_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS");
+
+  if (descendants != NULL)
+    {
+      for (i = 0; i < descendants->len; i++)
+        {
+          PnlDockItem *child = g_ptr_array_index (descendants, i);
+
+          g_object_weak_unref (G_OBJECT (child),
+                               pnl_dock_item_child_weak_notify,
+                               self);
+        }
+
+      g_object_set_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS", NULL);
+      g_ptr_array_unref (descendants);
+    }
+}
+
+static void
+pnl_dock_item_track_child (PnlDockItem *self,
+                           PnlDockItem *child)
+{
+  GPtrArray *descendants;
+  guint i;
+
+  g_assert (PNL_IS_DOCK_ITEM (self));
+  g_assert (PNL_IS_DOCK_ITEM (child));
+
+  descendants = g_object_get_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS");
+
+  if (descendants == NULL)
+    {
+      descendants = g_ptr_array_new ();
+      g_object_set_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS", descendants);
+      g_signal_connect (self,
+                        "destroy",
+                        G_CALLBACK (pnl_dock_item_destroy),
+                        NULL);
+    }
+
+  for (i = 0; i < descendants->len; i++)
+    {
+      PnlDockItem *item = g_ptr_array_index (descendants, i);
+
+      if (item == child)
+        return;
+    }
+
+  g_object_weak_ref (G_OBJECT (child),
+                     pnl_dock_item_child_weak_notify,
+                     self);
+
+  g_ptr_array_add (descendants, child);
+
+  pnl_dock_item_update_visibility (child);
+}
+
+gboolean
+pnl_dock_item_adopt (PnlDockItem *self,
+                     PnlDockItem *child)
+{
+  PnlDockManager *manager;
+  PnlDockManager *child_manager;
+
+  g_return_val_if_fail (PNL_IS_DOCK_ITEM (self), FALSE);
+  g_return_val_if_fail (PNL_IS_DOCK_ITEM (child), FALSE);
+
+  manager = pnl_dock_item_get_manager (self);
+  child_manager = pnl_dock_item_get_manager (child);
+
+  if ((child_manager != NULL) && (manager != NULL) && (child_manager != manager))
+    return FALSE;
+
+  if (manager != NULL)
+    pnl_dock_item_set_manager (child, manager);
+
+  pnl_dock_item_track_child (self, child);
+
+  return TRUE;
+}
+
+void
+pnl_dock_item_present_child (PnlDockItem *self,
+                             PnlDockItem *child)
+{
+  g_assert (PNL_IS_DOCK_ITEM (self));
+  g_assert (PNL_IS_DOCK_ITEM (child));
+
+#if 0
+  g_print ("present_child (%s, %s)\n",
+           G_OBJECT_TYPE_NAME (self),
+           G_OBJECT_TYPE_NAME (child));
+#endif
+
+  if (PNL_DOCK_ITEM_GET_IFACE (self)->present_child)
+    PNL_DOCK_ITEM_GET_IFACE (self)->present_child (self, child);
+}
+
+/**
+ * pnl_dock_item_present:
+ * @self: A #PnlDockItem
+ *
+ * This widget will walk the widget hierarchy to ensure that the
+ * dock item is visible to the user.
+ */
+void
+pnl_dock_item_present (PnlDockItem *self)
+{
+  GtkWidget *parent;
+
+  g_return_if_fail (PNL_IS_DOCK_ITEM (self));
+
+  for (parent = gtk_widget_get_parent (GTK_WIDGET (self));
+       parent != NULL;
+       parent = gtk_widget_get_parent (parent))
+    {
+      if (PNL_IS_DOCK_ITEM (parent))
+        {
+          pnl_dock_item_present_child (PNL_DOCK_ITEM (parent), self);
+          pnl_dock_item_present (PNL_DOCK_ITEM (parent));
+          return;
+        }
+    }
+}
+
+gboolean
+pnl_dock_item_has_widgets (PnlDockItem *self)
+{
+  GPtrArray *ar;
+
+  g_return_val_if_fail (PNL_IS_DOCK_ITEM (self), FALSE);
+
+  if (PNL_IS_DOCK_WIDGET (self))
+    return TRUE;
+
+  ar = g_object_get_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS");
+
+  if (ar != NULL)
+    {
+      guint i;
+
+      for (i = 0; i < ar->len; i++)
+        {
+          PnlDockItem *child = g_ptr_array_index (ar, i);
+
+          if (pnl_dock_item_has_widgets (child))
+            return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
+static void
+pnl_dock_item_printf_internal (PnlDockItem *self,
+                               GString     *str,
+                               guint        depth)
+{
+  GPtrArray *ar;
+  guint i;
+
+  g_assert (PNL_IS_DOCK_ITEM (self));
+  g_assert (str != NULL);
+
+
+  for (i = 0; i < depth; i++)
+    g_string_append_c (str, ' ');
+
+  g_string_append_printf (str, "%s\n", G_OBJECT_TYPE_NAME (self));
+
+  ++depth;
+
+  ar = g_object_get_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS");
+
+  if (ar != NULL)
+    {
+      for (i = 0; i < ar->len; i++)
+        pnl_dock_item_printf_internal (g_ptr_array_index (ar, i), str, depth);
+    }
+}
+
+void
+_pnl_dock_item_printf (PnlDockItem *self)
+{
+  GString *str;
+
+  g_return_if_fail (PNL_IS_DOCK_ITEM (self));
+
+  str = g_string_new (NULL);
+  pnl_dock_item_printf_internal (self, str, 0);
+  g_printerr ("%s", str->str);
+  g_string_free (str, TRUE);
+}
+
+/**
+ * pnl_dock_item_get_parent:
+ *
+ * Gets the parent #PnlDockItem, or %NULL.
+ *
+ * Returns: (transfer none) (nullable): A #PnlDockItem or %NULL.
+ */
+PnlDockItem *
+pnl_dock_item_get_parent (PnlDockItem *self)
+{
+  GtkWidget *parent;
+
+  g_return_val_if_fail (PNL_IS_DOCK_ITEM (self), NULL);
+
+  for (parent = gtk_widget_get_parent (GTK_WIDGET (self));
+       parent != NULL;
+       parent = gtk_widget_get_parent (parent))
+    {
+      if (PNL_IS_DOCK_ITEM (parent))
+        return PNL_DOCK_ITEM (parent);
+    }
+
+  return NULL;
+}
+
+gboolean
+pnl_dock_item_get_child_visible (PnlDockItem *self,
+                                 PnlDockItem *child)
+{
+  g_return_val_if_fail (PNL_IS_DOCK_ITEM (self), FALSE);
+  g_return_val_if_fail (PNL_IS_DOCK_ITEM (child), FALSE);
+
+  if (PNL_DOCK_ITEM_GET_IFACE (self)->get_child_visible)
+    return PNL_DOCK_ITEM_GET_IFACE (self)->get_child_visible (self, child);
+
+  return TRUE;
+}
+
+void
+pnl_dock_item_set_child_visible (PnlDockItem *self,
+                                 PnlDockItem *child,
+                                 gboolean     child_visible)
+{
+  g_return_if_fail (PNL_IS_DOCK_ITEM (self));
+  g_return_if_fail (PNL_IS_DOCK_ITEM (child));
+
+  if (PNL_DOCK_ITEM_GET_IFACE (self)->set_child_visible)
+    PNL_DOCK_ITEM_GET_IFACE (self)->set_child_visible (self, child, child_visible);
+}
diff --git a/contrib/pnl/pnl-dock-item.h b/contrib/pnl/pnl-dock-item.h
new file mode 100644
index 0000000..2a067e1
--- /dev/null
+++ b/contrib/pnl/pnl-dock-item.h
@@ -0,0 +1,65 @@
+/* pnl-dock-item.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_DOCK_ITEM_H
+#define PNL_DOCK_ITEM_H
+
+#include "pnl-dock-manager.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockItemInterface
+{
+  GTypeInterface parent;
+
+  void            (*set_manager)       (PnlDockItem    *self,
+                                        PnlDockManager *manager);
+  PnlDockManager *(*get_manager)       (PnlDockItem    *self);
+  void            (*manager_set)       (PnlDockItem    *self,
+                                        PnlDockManager *old_manager);
+  void            (*present_child)     (PnlDockItem    *self,
+                                        PnlDockItem    *child);
+  void            (*update_visibility) (PnlDockItem    *self);
+  gboolean        (*get_child_visible) (PnlDockItem    *self,
+                                        PnlDockItem    *child);
+  void            (*set_child_visible) (PnlDockItem    *self,
+                                        PnlDockItem    *child,
+                                        gboolean        child_visible);
+};
+
+PnlDockManager *pnl_dock_item_get_manager       (PnlDockItem    *self);
+void            pnl_dock_item_set_manager       (PnlDockItem    *self,
+                                                 PnlDockManager *manager);
+gboolean        pnl_dock_item_adopt             (PnlDockItem    *self,
+                                                 PnlDockItem    *child);
+void            pnl_dock_item_present           (PnlDockItem    *self);
+void            pnl_dock_item_present_child     (PnlDockItem    *self,
+                                                 PnlDockItem    *child);
+void            pnl_dock_item_update_visibility (PnlDockItem    *self);
+gboolean        pnl_dock_item_has_widgets       (PnlDockItem    *self);
+gboolean        pnl_dock_item_get_child_visible (PnlDockItem    *self,
+                                                 PnlDockItem    *child);
+void            pnl_dock_item_set_child_visible (PnlDockItem    *self,
+                                                 PnlDockItem    *child,
+                                                 gboolean        child_visible);
+PnlDockItem    *pnl_dock_item_get_parent        (PnlDockItem    *self);
+void            _pnl_dock_item_printf           (PnlDockItem    *self);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_ITEM_H */
diff --git a/contrib/pnl/pnl-dock-manager.c b/contrib/pnl/pnl-dock-manager.c
new file mode 100644
index 0000000..a116da9
--- /dev/null
+++ b/contrib/pnl/pnl-dock-manager.c
@@ -0,0 +1,306 @@
+/* pnl-dock-manager.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-manager.h"
+#include "pnl-dock-transient-grab.h"
+
+typedef struct
+{
+  GPtrArray *docks;
+  PnlDockTransientGrab *grab;
+} PnlDockManagerPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (PnlDockManager, pnl_dock_manager, G_TYPE_OBJECT)
+
+enum {
+  REGISTER_DOCK,
+  UNREGISTER_DOCK,
+  N_SIGNALS
+};
+
+static guint signals [N_SIGNALS];
+
+static void
+pnl_dock_manager_set_focus (PnlDockManager *self,
+                            GtkWidget      *focus,
+                            GtkWidget      *toplevel)
+{
+  PnlDockManagerPrivate *priv = pnl_dock_manager_get_instance_private (self);
+  PnlDockTransientGrab *grab = NULL;
+  GtkWidget *parent;
+
+  g_assert (PNL_IS_DOCK_MANAGER (self));
+  g_assert (GTK_IS_WINDOW (toplevel));
+
+  /*
+   * Don't do anything if we get a NULL focus. Instead, wait for the focus
+   * to be updated with a widget.
+   */
+  if (focus == NULL)
+    return;
+
+#if 0
+  g_print ("Attempting to set focus on %s\n", G_OBJECT_TYPE_NAME (focus));
+#endif
+
+  if (priv->grab != NULL)
+    {
+      /*
+       * If the current transient grab contains the new focus widget,
+       * then there is nothing for us to do now.
+       */
+      if (pnl_dock_transient_grab_is_descendant (priv->grab, focus))
+        return;
+    }
+
+  /*
+   * If their is a PnlDockItem in the hierarchy, create a new transient grab.
+   */
+  for (parent = focus;
+       parent != NULL;
+       parent = gtk_widget_get_parent (parent))
+    {
+      if (PNL_IS_DOCK_ITEM (parent))
+        {
+          if (grab == NULL)
+            grab = pnl_dock_transient_grab_new ();
+          pnl_dock_transient_grab_add_item (grab, PNL_DOCK_ITEM (parent));
+        }
+    }
+
+  /*
+   * Steal common hierarchy so that we don't hide it when breaking grabs.
+   */
+  if (priv->grab != NULL && grab != NULL)
+    pnl_dock_transient_grab_steal_common_ancestors (grab, priv->grab);
+
+  /*
+   * Release the previous grab.
+   */
+  if (priv->grab != NULL)
+    {
+      pnl_dock_transient_grab_release (priv->grab);
+      g_clear_object (&priv->grab);
+    }
+
+  /* Start the grab process */
+  if (grab != NULL)
+    {
+      priv->grab = grab;
+      pnl_dock_transient_grab_acquire (priv->grab);
+    }
+}
+
+static void
+pnl_dock_manager_hierarchy_changed (PnlDockManager *self,
+                                    GtkWidget      *old_toplevel,
+                                    GtkWidget      *widget)
+{
+  GtkWidget *toplevel;
+
+  g_assert (PNL_IS_DOCK_MANAGER (self));
+  g_assert (!old_toplevel || GTK_IS_WIDGET (old_toplevel));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  if (GTK_IS_WINDOW (old_toplevel))
+    g_signal_handlers_disconnect_by_func (old_toplevel,
+                                          G_CALLBACK (pnl_dock_manager_set_focus),
+                                          self);
+
+  toplevel = gtk_widget_get_toplevel (widget);
+
+  if (GTK_IS_WINDOW (toplevel))
+    g_signal_connect_object (toplevel,
+                             "set-focus",
+                             G_CALLBACK (pnl_dock_manager_set_focus),
+                             self,
+                             G_CONNECT_SWAPPED);
+}
+
+static void
+pnl_dock_manager_watch_toplevel (PnlDockManager *self,
+                                 GtkWidget      *widget)
+{
+  g_assert (PNL_IS_DOCK_MANAGER (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  g_signal_connect_object (widget,
+                           "hierarchy-changed",
+                           G_CALLBACK (pnl_dock_manager_hierarchy_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  pnl_dock_manager_hierarchy_changed (self, NULL, widget);
+}
+
+static void
+pnl_dock_manager_weak_notify (gpointer  data,
+                              GObject  *where_the_object_was)
+{
+  PnlDockManager *self = data;
+  PnlDockManagerPrivate *priv = pnl_dock_manager_get_instance_private (self);
+
+  g_assert (PNL_IS_DOCK_MANAGER (self));
+
+  g_ptr_array_remove (priv->docks, where_the_object_was);
+}
+
+static void
+pnl_dock_manager_real_register_dock (PnlDockManager *self,
+                                     PnlDock        *dock)
+{
+  PnlDockManagerPrivate *priv = pnl_dock_manager_get_instance_private (self);
+
+  g_return_if_fail (PNL_IS_DOCK_MANAGER (self));
+  g_return_if_fail (PNL_IS_DOCK (dock));
+
+  g_object_weak_ref (G_OBJECT (dock), pnl_dock_manager_weak_notify, self);
+  g_ptr_array_add (priv->docks, dock);
+  pnl_dock_manager_watch_toplevel (self, GTK_WIDGET (dock));
+}
+
+static void
+pnl_dock_manager_real_unregister_dock (PnlDockManager *self,
+                                       PnlDock        *dock)
+{
+  PnlDockManagerPrivate *priv = pnl_dock_manager_get_instance_private (self);
+  guint i;
+
+  g_return_if_fail (PNL_IS_DOCK_MANAGER (self));
+  g_return_if_fail (PNL_IS_DOCK (dock));
+
+  for (i = 0; i < priv->docks->len; i++)
+    {
+      PnlDock *iter = g_ptr_array_index (priv->docks, i);
+
+      if (iter == dock)
+        {
+          g_object_weak_unref (G_OBJECT (dock), pnl_dock_manager_weak_notify, self);
+          g_ptr_array_remove_index (priv->docks, i);
+          break;
+        }
+    }
+}
+
+static void
+pnl_dock_manager_finalize (GObject *object)
+{
+  PnlDockManager *self = (PnlDockManager *)object;
+  PnlDockManagerPrivate *priv = pnl_dock_manager_get_instance_private (self);
+
+  while (priv->docks->len > 0)
+    {
+      PnlDock *dock = g_ptr_array_index (priv->docks, priv->docks->len - 1);
+
+      g_object_weak_unref (G_OBJECT (dock), pnl_dock_manager_weak_notify, self);
+      g_ptr_array_remove_index (priv->docks, priv->docks->len - 1);
+    }
+
+  g_clear_pointer (&priv->docks, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (pnl_dock_manager_parent_class)->finalize (object);
+}
+
+static void
+pnl_dock_manager_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_manager_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_manager_class_init (PnlDockManagerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = pnl_dock_manager_finalize;
+  object_class->get_property = pnl_dock_manager_get_property;
+  object_class->set_property = pnl_dock_manager_set_property;
+
+  klass->register_dock = pnl_dock_manager_real_register_dock;
+  klass->unregister_dock = pnl_dock_manager_real_unregister_dock;
+
+  signals [REGISTER_DOCK] =
+    g_signal_new ("register-dock",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (PnlDockManagerClass, register_dock),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, PNL_TYPE_DOCK);
+
+  signals [UNREGISTER_DOCK] =
+    g_signal_new ("unregister-dock",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (PnlDockManagerClass, unregister_dock),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, PNL_TYPE_DOCK);
+}
+
+static void
+pnl_dock_manager_init (PnlDockManager *self)
+{
+  PnlDockManagerPrivate *priv = pnl_dock_manager_get_instance_private (self);
+
+  priv->docks = g_ptr_array_new ();
+}
+
+PnlDockManager *
+pnl_dock_manager_new (void)
+{
+  return g_object_new (PNL_TYPE_DOCK_MANAGER, NULL);
+}
+
+void
+pnl_dock_manager_register_dock (PnlDockManager *self,
+                                PnlDock        *dock)
+{
+  g_return_if_fail (PNL_IS_DOCK_MANAGER (self));
+  g_return_if_fail (PNL_IS_DOCK (dock));
+
+  g_signal_emit (self, signals [REGISTER_DOCK], 0, dock);
+}
+
+void
+pnl_dock_manager_unregister_dock (PnlDockManager *self,
+                                  PnlDock        *dock)
+{
+  g_return_if_fail (PNL_IS_DOCK_MANAGER (self));
+  g_return_if_fail (PNL_IS_DOCK (dock));
+
+  g_signal_emit (self, signals [UNREGISTER_DOCK], 0, dock);
+}
diff --git a/contrib/pnl/pnl-dock-manager.h b/contrib/pnl/pnl-dock-manager.h
new file mode 100644
index 0000000..a6d022d
--- /dev/null
+++ b/contrib/pnl/pnl-dock-manager.h
@@ -0,0 +1,57 @@
+/* pnl-dock-manager.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_MANAGER_H
+#define PNL_DOCK_MANAGER_H
+
+#include "pnl-dock-types.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockManagerClass
+{
+  GObjectClass parent;
+
+  void (*register_dock)   (PnlDockManager *self,
+                           PnlDock        *dock);
+  void (*unregister_dock) (PnlDockManager *self,
+                           PnlDock        *dock);
+
+  void (*padding1) (void);
+  void (*padding2) (void);
+  void (*padding3) (void);
+  void (*padding4) (void);
+  void (*padding5) (void);
+  void (*padding6) (void);
+  void (*padding7) (void);
+  void (*padding8) (void);
+};
+
+PnlDockManager *pnl_dock_manager_new             (void);
+void            pnl_dock_manager_register_dock   (PnlDockManager *self,
+                                                  PnlDock        *dock);
+void            pnl_dock_manager_unregister_dock (PnlDockManager *self,
+                                                  PnlDock        *dock);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_MANAGER_H */
diff --git a/contrib/pnl/pnl-dock-overlay-edge-private.h b/contrib/pnl/pnl-dock-overlay-edge-private.h
new file mode 100644
index 0000000..1bcc1e5
--- /dev/null
+++ b/contrib/pnl/pnl-dock-overlay-edge-private.h
@@ -0,0 +1,39 @@
+/* pnl-dock-overlay-edge-private.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_DOCK_OVERLAY_EDGE_PRIVATE_H
+#define PNL_DOCK_OVERLAY_EDGE_PRIVATE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_DOCK_OVERLAY_EDGE (pnl_dock_overlay_edge_get_type())
+
+G_DECLARE_FINAL_TYPE (PnlDockOverlayEdge, pnl_dock_overlay_edge, PNL, DOCK_OVERLAY_EDGE, GtkBin)
+
+GtkPositionType pnl_dock_overlay_edge_get_edge     (PnlDockOverlayEdge *self);
+void            pnl_dock_overlay_edge_set_edge     (PnlDockOverlayEdge *self,
+                                                    GtkPositionType     edge);
+gint            pnl_dock_overlay_edge_get_position (PnlDockOverlayEdge *self);
+void            pnl_dock_overlay_edge_set_position (PnlDockOverlayEdge *self,
+                                                    gint                position);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_OVERLAY_EDGE_PRIVATE_H */
diff --git a/contrib/pnl/pnl-dock-overlay-edge.c b/contrib/pnl/pnl-dock-overlay-edge.c
new file mode 100644
index 0000000..e85f69e
--- /dev/null
+++ b/contrib/pnl/pnl-dock-overlay-edge.c
@@ -0,0 +1,290 @@
+/* pnl-dock-overlay-edge.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-item.h"
+#include "pnl-dock-overlay-edge-private.h"
+#include "pnl-dock-paned.h"
+#include "pnl-dock-paned-private.h"
+#include "pnl-dock-stack.h"
+#include "pnl-util-private.h"
+
+struct _PnlDockOverlayEdge
+{
+  GtkBin          parent;
+  GtkPositionType edge : 2;
+  gint            position;
+};
+
+G_DEFINE_TYPE_EXTENDED (PnlDockOverlayEdge, pnl_dock_overlay_edge, GTK_TYPE_BIN, 0,
+                        G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM, NULL))
+
+enum {
+  PROP_0,
+  PROP_EDGE,
+  PROP_POSITION,
+  N_PROPS
+};
+
+enum {
+  STYLE_PROP_0,
+  STYLE_PROP_OVERLAP_SIZE,
+  STYLE_PROP_MNEMONIC_OVERLAP_SIZE,
+  N_STYLE_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+static GParamSpec *style_properties [N_STYLE_PROPS];
+
+static void
+pnl_dock_overlay_edge_update_edge (PnlDockOverlayEdge *self)
+{
+  GtkWidget *child;
+  GtkPositionType edge;
+  GtkStyleContext *style_context;
+  GtkOrientation orientation;
+  const gchar *style_class;
+
+  g_assert (PNL_IS_DOCK_OVERLAY_EDGE (self));
+
+  style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+  gtk_style_context_remove_class (style_context, "left-edge");
+  gtk_style_context_remove_class (style_context, "right-edge");
+  gtk_style_context_remove_class (style_context, "top-edge");
+  gtk_style_context_remove_class (style_context, "bottom-edge");
+
+  switch (self->edge)
+    {
+    case GTK_POS_TOP:
+      edge = GTK_POS_BOTTOM;
+      orientation = GTK_ORIENTATION_HORIZONTAL;
+      style_class = "top-edge";
+      break;
+
+    case GTK_POS_BOTTOM:
+      edge = GTK_POS_TOP;
+      orientation = GTK_ORIENTATION_HORIZONTAL;
+      style_class = "bottom-edge";
+      break;
+
+    case GTK_POS_LEFT:
+      edge = GTK_POS_RIGHT;
+      orientation = GTK_ORIENTATION_VERTICAL;
+      style_class = "left-edge";
+      break;
+
+    case GTK_POS_RIGHT:
+      edge = GTK_POS_LEFT;
+      orientation = GTK_ORIENTATION_VERTICAL;
+      style_class = "right-edge";
+      break;
+
+    default:
+      g_assert_not_reached ();
+      return;
+    }
+
+  gtk_style_context_add_class (style_context, style_class);
+
+  child = gtk_bin_get_child (GTK_BIN (self));
+
+  if (PNL_IS_DOCK_PANED (child))
+    {
+      gtk_orientable_set_orientation (GTK_ORIENTABLE (child), orientation);
+      pnl_dock_paned_set_child_edge (PNL_DOCK_PANED (child), edge);
+    }
+  else if (PNL_IS_DOCK_STACK (child))
+    {
+      pnl_dock_stack_set_edge (PNL_DOCK_STACK (child), edge);
+    }
+}
+
+static void
+pnl_dock_overlay_edge_add (GtkContainer *container,
+                           GtkWidget    *child)
+{
+  PnlDockOverlayEdge *self = (PnlDockOverlayEdge *)container;
+
+  g_assert (PNL_IS_DOCK_OVERLAY_EDGE (self));
+  g_assert (GTK_IS_WIDGET (child));
+
+  GTK_CONTAINER_CLASS (pnl_dock_overlay_edge_parent_class)->add (container, child);
+
+  pnl_dock_overlay_edge_update_edge (self);
+
+  if (PNL_IS_DOCK_ITEM (child))
+    pnl_dock_item_adopt (PNL_DOCK_ITEM (self), PNL_DOCK_ITEM (child));
+}
+
+static void
+pnl_dock_overlay_edge_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  PnlDockOverlayEdge *self = PNL_DOCK_OVERLAY_EDGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_EDGE:
+      g_value_set_enum (value, pnl_dock_overlay_edge_get_edge (self));
+      break;
+
+    case PROP_POSITION:
+      g_value_set_int (value, pnl_dock_overlay_edge_get_position (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_overlay_edge_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  PnlDockOverlayEdge *self = PNL_DOCK_OVERLAY_EDGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_EDGE:
+      pnl_dock_overlay_edge_set_edge (self, g_value_get_enum (value));
+      break;
+
+    case PROP_POSITION:
+      pnl_dock_overlay_edge_set_position (self, g_value_get_int (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_overlay_edge_class_init (PnlDockOverlayEdgeClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->get_property = pnl_dock_overlay_edge_get_property;
+  object_class->set_property = pnl_dock_overlay_edge_set_property;
+
+  container_class->add = pnl_dock_overlay_edge_add;
+
+  widget_class->draw = pnl_gtk_bin_draw;
+  widget_class->size_allocate = pnl_gtk_bin_size_allocate;
+
+  properties [PROP_EDGE] =
+    g_param_spec_enum ("edge",
+                       "Edge",
+                       "Edge",
+                       GTK_TYPE_POSITION_TYPE,
+                       GTK_POS_LEFT,
+                       (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_POSITION] =
+    g_param_spec_int ("position",
+                      "Position",
+                      "The size of the edge",
+                      0,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  style_properties [STYLE_PROP_MNEMONIC_OVERLAP_SIZE] =
+    g_param_spec_int ("mnemonic-overlap-size",
+                      "Mnemonic Overlap Size",
+                      "The amount of pixels to overlap when mnemonics are visible",
+                      0,
+                      G_MAXINT,
+                      30,
+                      (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  gtk_widget_class_install_style_property (widget_class,
+                                           style_properties [STYLE_PROP_MNEMONIC_OVERLAP_SIZE]);
+
+  style_properties [STYLE_PROP_OVERLAP_SIZE] =
+    g_param_spec_int ("overlap-size",
+                      "Overlap Size",
+                      "The amount of pixels to overlap when hidden",
+                      0,
+                      G_MAXINT,
+                      5,
+                      (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  gtk_widget_class_install_style_property (widget_class,
+                                           style_properties [STYLE_PROP_OVERLAP_SIZE]);
+
+  gtk_widget_class_set_css_name (widget_class, "dockoverlayedge");
+}
+
+static void
+pnl_dock_overlay_edge_init (PnlDockOverlayEdge *self)
+{
+}
+
+gint
+pnl_dock_overlay_edge_get_position (PnlDockOverlayEdge *self)
+{
+  g_return_val_if_fail (PNL_IS_DOCK_OVERLAY_EDGE (self), 0);
+
+  return self->position;
+}
+
+void
+pnl_dock_overlay_edge_set_position (PnlDockOverlayEdge *self,
+                                    gint                position)
+{
+  g_return_if_fail (PNL_IS_DOCK_OVERLAY_EDGE (self));
+  g_return_if_fail (position >= 0);
+
+  if (position != self->position)
+    {
+      self->position = position;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_POSITION]);
+    }
+}
+
+GtkPositionType
+pnl_dock_overlay_edge_get_edge (PnlDockOverlayEdge *self)
+{
+  g_return_val_if_fail (PNL_IS_DOCK_OVERLAY_EDGE (self), 0);
+
+  return self->edge;
+}
+
+void
+pnl_dock_overlay_edge_set_edge (PnlDockOverlayEdge *self,
+                                GtkPositionType     edge)
+{
+  g_return_if_fail (PNL_IS_DOCK_OVERLAY_EDGE (self));
+  g_return_if_fail (edge >= 0);
+  g_return_if_fail (edge <= 3);
+
+  if (edge != self->edge)
+    {
+      self->edge = edge;
+      pnl_dock_overlay_edge_update_edge (self);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EDGE]);
+    }
+}
diff --git a/contrib/pnl/pnl-dock-overlay.c b/contrib/pnl/pnl-dock-overlay.c
new file mode 100644
index 0000000..167d1e7
--- /dev/null
+++ b/contrib/pnl/pnl-dock-overlay.c
@@ -0,0 +1,704 @@
+/* pnl-dock-overlay.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-animation.h"
+#include "pnl-dock-overlay-edge-private.h"
+#include "pnl-dock-item.h"
+#include "pnl-dock-overlay.h"
+#include "pnl-tab.h"
+#include "pnl-tab-strip.h"
+#include "pnl-util-private.h"
+
+#define REVEAL_DURATION 300
+#define MNEMONIC_REVEAL_DURATION 200
+
+typedef struct
+{
+  GtkOverlay         *overlay;
+  PnlDockOverlayEdge *edges [4];
+  GtkAdjustment      *edge_adj [4];
+  GtkAdjustment      *edge_handle_adj [4];
+  guint               child_reveal : 4;
+} PnlDockOverlayPrivate;
+
+static void pnl_dock_overlay_init_dock_iface      (PnlDockInterface     *iface);
+static void pnl_dock_overlay_init_dock_item_iface (PnlDockItemInterface *iface);
+static void pnl_dock_overlay_init_buildable_iface (GtkBuildableIface    *iface);
+static void pnl_dock_overlay_set_child_reveal     (PnlDockOverlay       *self,
+                                                   GtkWidget            *child,
+                                                   gboolean              reveal);
+
+G_DEFINE_TYPE_EXTENDED (PnlDockOverlay, pnl_dock_overlay, GTK_TYPE_EVENT_BOX, 0,
+                        G_ADD_PRIVATE (PnlDockOverlay)
+                        G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, pnl_dock_overlay_init_buildable_iface)
+                        G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM, pnl_dock_overlay_init_dock_item_iface)
+                        G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK, pnl_dock_overlay_init_dock_iface))
+
+enum {
+  PROP_0,
+  PROP_MANAGER,
+  N_PROPS
+};
+
+enum {
+  CHILD_PROP_0,
+  CHILD_PROP_REVEAL,
+  N_CHILD_PROPS
+};
+
+enum {
+  HIDE_EDGES,
+  N_SIGNALS
+};
+
+static GParamSpec *child_properties [N_CHILD_PROPS];
+static guint signals [N_SIGNALS];
+
+static void
+pnl_dock_overlay_update_focus_chain (PnlDockOverlay *self)
+{
+  PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+  GList *focus_chain = NULL;
+  GtkWidget *child;
+  guint i;
+
+  g_assert (PNL_IS_DOCK_OVERLAY (self));
+
+  for (i = G_N_ELEMENTS (priv->edges); i > 0; i--)
+    {
+      PnlDockOverlayEdge *edge = priv->edges [i - 1];
+
+      if (edge != NULL)
+        focus_chain = g_list_prepend (focus_chain, edge);
+    }
+
+  child = gtk_bin_get_child (GTK_BIN (self));
+
+  if (child != NULL)
+    focus_chain = g_list_prepend (focus_chain, child);
+
+  if (focus_chain != NULL)
+    {
+      gtk_container_set_focus_chain (GTK_CONTAINER (self), focus_chain);
+      g_list_free (focus_chain);
+    }
+}
+
+static void
+pnl_dock_overlay_get_edge_position (PnlDockOverlay     *self,
+                                    PnlDockOverlayEdge *edge,
+                                    GtkAllocation      *allocation)
+{
+  PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+  GtkPositionType type;
+  gdouble value;
+  gdouble handle_value;
+  gdouble flipped_value;
+  gint nat_width;
+  gint nat_height;
+
+  g_assert (PNL_IS_DOCK_OVERLAY (self));
+  g_assert (PNL_IS_DOCK_OVERLAY_EDGE (edge));
+  g_assert (allocation != NULL);
+
+  gtk_widget_get_allocation (GTK_WIDGET (self), allocation);
+
+  allocation->x = 0;
+  allocation->y = 0;
+
+  type = pnl_dock_overlay_edge_get_edge (edge);
+
+  if (type == GTK_POS_LEFT || type == GTK_POS_RIGHT)
+    {
+      nat_height = MAX (allocation->height, 1);
+      gtk_widget_get_preferred_width_for_height (GTK_WIDGET (edge), nat_height, NULL, &nat_width);
+    }
+  else if (type == GTK_POS_TOP || type == GTK_POS_BOTTOM)
+    {
+      nat_width = MAX (allocation->width, 1);
+      gtk_widget_get_preferred_height_for_width (GTK_WIDGET (edge), nat_width, NULL, &nat_height);
+    }
+  else
+    {
+      g_assert_not_reached ();
+      return;
+    }
+
+  value = gtk_adjustment_get_value (priv->edge_adj [type]);
+  flipped_value = 1.0 - value;
+
+  handle_value = gtk_adjustment_get_value (priv->edge_handle_adj [type]);
+
+  switch (type)
+    {
+    case GTK_POS_LEFT:
+      allocation->width = nat_width;
+      allocation->x -= nat_width * value;
+      if (flipped_value * nat_width <= handle_value)
+        allocation->x += (handle_value - (flipped_value * nat_width));
+      break;
+
+    case GTK_POS_RIGHT:
+      allocation->x = allocation->x + allocation->width - nat_width;
+      allocation->width = nat_width;
+      allocation->x += nat_width * value;
+      if (flipped_value * nat_width <= handle_value)
+        allocation->x -= (handle_value - (flipped_value * nat_width));
+      break;
+
+    case GTK_POS_BOTTOM:
+      allocation->y = allocation->y + allocation->height - nat_height;
+      allocation->height = nat_height;
+      allocation->y += nat_height * value;
+      if (flipped_value * nat_height <= handle_value)
+        allocation->y -= (handle_value - (flipped_value * nat_height));
+      break;
+
+    case GTK_POS_TOP:
+      allocation->height = nat_height;
+      allocation->y -= nat_height * value;
+      if (flipped_value * nat_height <= handle_value)
+        allocation->y += (handle_value - (flipped_value * nat_height));
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static gboolean
+pnl_dock_overlay_get_child_position (PnlDockOverlay *self,
+                                     GtkWidget      *widget,
+                                     GtkAllocation  *allocation)
+{
+  g_assert (PNL_IS_DOCK_OVERLAY (self));
+  g_assert (GTK_IS_WIDGET (widget));
+  g_assert (allocation != NULL);
+
+  if (PNL_IS_DOCK_OVERLAY_EDGE (widget))
+    {
+      pnl_dock_overlay_get_edge_position (self, PNL_DOCK_OVERLAY_EDGE (widget), allocation);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+pnl_dock_overlay_add (GtkContainer *container,
+                      GtkWidget    *widget)
+{
+  PnlDockOverlay *self = (PnlDockOverlay *)container;
+
+  g_assert (PNL_IS_DOCK_OVERLAY (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  GTK_CONTAINER_CLASS (pnl_dock_overlay_parent_class)->add (container, widget);
+
+  pnl_dock_overlay_update_focus_chain (self);
+
+  if (PNL_IS_DOCK_ITEM (widget))
+    {
+      pnl_dock_item_adopt (PNL_DOCK_ITEM (self), PNL_DOCK_ITEM (widget));
+      pnl_dock_item_update_visibility (PNL_DOCK_ITEM (widget));
+    }
+}
+
+static void
+pnl_dock_overlay_toplevel_mnemonics (PnlDockOverlay *self,
+                                     GParamSpec     *pspec,
+                                     GtkWindow      *toplevel)
+{
+  PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+  const gchar *style_prop;
+  gboolean mnemonics_visible;
+  guint i;
+
+  g_assert (PNL_IS_DOCK_OVERLAY (self));
+  g_assert (pspec != NULL);
+  g_assert (GTK_IS_WINDOW (toplevel));
+
+  mnemonics_visible = gtk_window_get_mnemonics_visible (toplevel);
+  style_prop = mnemonics_visible ? "mnemonic-overlap-size" : "overlap-size";
+
+  for (i = 0; i < G_N_ELEMENTS (priv->edges); i++)
+    {
+      PnlDockOverlayEdge *edge = priv->edges [i];
+      GtkAdjustment *handle_adj = priv->edge_handle_adj [i];
+      gint overlap = 0;
+
+      gtk_widget_style_get (GTK_WIDGET (edge), style_prop, &overlap, NULL);
+
+      pnl_object_animate (handle_adj,
+                          PNL_ANIMATION_EASE_IN_OUT_CUBIC,
+                          MNEMONIC_REVEAL_DURATION,
+                          gtk_widget_get_frame_clock (GTK_WIDGET (edge)),
+                          "value", (gdouble)overlap,
+                          NULL);
+    }
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+}
+
+static void
+pnl_dock_overlay_toplevel_set_focus (PnlDockOverlay *self,
+                                     GtkWidget      *widget,
+                                     GtkWindow      *toplevel)
+{
+  PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_DOCK_OVERLAY (self));
+  g_assert (!widget || GTK_IS_WIDGET (widget));
+  g_assert (GTK_IS_WINDOW (toplevel));
+
+  /*
+   * TODO: If the overlay obscurs the new focus widget,
+   *       hide immediately. Otherwise, use a short timeout.
+   */
+
+  for (i = 0; i < G_N_ELEMENTS (priv->edges); i++)
+    {
+      PnlDockOverlayEdge *edge = priv->edges [i];
+
+      if (!widget || !gtk_widget_is_ancestor (widget, GTK_WIDGET (edge)))
+        gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (edge),
+                                 "reveal", FALSE,
+                                 NULL);
+    }
+}
+
+static void
+pnl_dock_overlay_hide_edges (PnlDockOverlay *self)
+{
+  PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+  GtkWidget *child;
+  guint i;
+
+  g_assert (PNL_IS_DOCK_OVERLAY (self));
+
+  for (i = 0; i < G_N_ELEMENTS (priv->edges); i++)
+    {
+      PnlDockOverlayEdge *edge = priv->edges [i];
+
+      gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (edge),
+                               "reveal", FALSE,
+                               NULL);
+    }
+
+  child = gtk_bin_get_child (GTK_BIN (self));
+
+  if (child != NULL)
+    gtk_widget_grab_focus (child);
+}
+
+static void
+pnl_dock_overlay_destroy (GtkWidget *widget)
+{
+  PnlDockOverlay *self = (PnlDockOverlay *)widget;
+  PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+  guint i;
+
+  g_assert (GTK_IS_WIDGET (widget));
+
+  for (i = 0; i < G_N_ELEMENTS (priv->edge_adj); i++)
+    g_clear_object (&priv->edge_adj [i]);
+
+  GTK_WIDGET_CLASS (pnl_dock_overlay_parent_class)->destroy (widget);
+}
+
+static void
+pnl_dock_overlay_hierarchy_changed (GtkWidget *widget,
+                                    GtkWidget *old_toplevel)
+{
+  PnlDockOverlay *self = (PnlDockOverlay *)widget;
+  GtkWidget *toplevel;
+
+  g_assert (PNL_IS_DOCK_OVERLAY (self));
+  g_assert (!old_toplevel || GTK_IS_WIDGET (old_toplevel));
+
+  if (old_toplevel != NULL)
+    {
+      g_signal_handlers_disconnect_by_func (old_toplevel,
+                                            G_CALLBACK (pnl_dock_overlay_toplevel_mnemonics),
+                                            self);
+      g_signal_handlers_disconnect_by_func (old_toplevel,
+                                            G_CALLBACK (pnl_dock_overlay_toplevel_set_focus),
+                                            self);
+    }
+
+  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+
+  if (GTK_IS_WINDOW (toplevel))
+    {
+      g_signal_connect_object (toplevel,
+                               "notify::mnemonics-visible",
+                               G_CALLBACK (pnl_dock_overlay_toplevel_mnemonics),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      g_signal_connect_object (toplevel,
+                               "set-focus",
+                               G_CALLBACK (pnl_dock_overlay_toplevel_set_focus),
+                               self,
+                               G_CONNECT_SWAPPED);
+    }
+}
+
+static gboolean
+pnl_dock_overlay_get_child_reveal (PnlDockOverlay *self,
+                                   GtkWidget      *child)
+{
+  PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+
+  g_assert (PNL_IS_DOCK_OVERLAY (self));
+  g_assert (GTK_IS_WIDGET (child));
+
+  if (PNL_IS_DOCK_OVERLAY_EDGE (child))
+    {
+      GtkPositionType edge;
+
+      edge = pnl_dock_overlay_edge_get_edge (PNL_DOCK_OVERLAY_EDGE (child));
+
+      return !!(priv->child_reveal & (1 << edge));
+    }
+
+  return FALSE;
+}
+
+static void
+pnl_dock_overlay_set_child_reveal (PnlDockOverlay *self,
+                                   GtkWidget      *child,
+                                   gboolean        reveal)
+{
+  PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+  GtkPositionType edge;
+  guint child_reveal;
+
+  g_assert (PNL_IS_DOCK_OVERLAY (self));
+  g_assert (GTK_IS_WIDGET (child));
+
+  if (!PNL_IS_DOCK_OVERLAY_EDGE (child))
+    return;
+
+  edge = pnl_dock_overlay_edge_get_edge (PNL_DOCK_OVERLAY_EDGE (child));
+
+  if (reveal)
+    child_reveal = priv->child_reveal | (1 << edge);
+  else
+    child_reveal = priv->child_reveal & ~(1 << edge);
+
+  if (priv->child_reveal != child_reveal)
+    {
+      priv->child_reveal = child_reveal;
+
+      pnl_object_animate (priv->edge_adj [edge],
+                          PNL_ANIMATION_EASE_IN_OUT_CUBIC,
+                          REVEAL_DURATION,
+                          gtk_widget_get_frame_clock (child),
+                          "value", reveal ? 0.0 : 1.0,
+                          NULL);
+
+      gtk_container_child_notify_by_pspec (GTK_CONTAINER (self),
+                                           child,
+                                           child_properties [CHILD_PROP_REVEAL]);
+    }
+
+}
+
+static void
+pnl_dock_overlay_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  PnlDockOverlay *self = PNL_DOCK_OVERLAY (object);
+
+  switch (prop_id)
+    {
+    case PROP_MANAGER:
+      g_value_set_object (value, pnl_dock_item_get_manager (PNL_DOCK_ITEM (self)));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_overlay_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  PnlDockOverlay *self = PNL_DOCK_OVERLAY (object);
+
+  switch (prop_id)
+    {
+    case PROP_MANAGER:
+      pnl_dock_item_set_manager (PNL_DOCK_ITEM (self), g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_overlay_get_child_property (GtkContainer *container,
+                                     GtkWidget    *widget,
+                                     guint         prop_id,
+                                     GValue       *value,
+                                     GParamSpec   *pspec)
+{
+  PnlDockOverlay *self = PNL_DOCK_OVERLAY (container);
+
+  switch (prop_id)
+    {
+    case CHILD_PROP_REVEAL:
+      g_value_set_boolean (value, pnl_dock_overlay_get_child_reveal (self, widget));
+      break;
+
+    default:
+      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_overlay_set_child_property (GtkContainer *container,
+                                     GtkWidget    *widget,
+                                     guint         prop_id,
+                                     const GValue *value,
+                                     GParamSpec   *pspec)
+{
+  PnlDockOverlay *self = PNL_DOCK_OVERLAY (container);
+
+  switch (prop_id)
+    {
+    case CHILD_PROP_REVEAL:
+      pnl_dock_overlay_set_child_reveal (self, widget, g_value_get_boolean (value));
+      break;
+
+    default:
+      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_overlay_class_init (PnlDockOverlayClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+  GtkBindingSet *binding_set;
+
+  object_class->get_property = pnl_dock_overlay_get_property;
+  object_class->set_property = pnl_dock_overlay_set_property;
+
+  widget_class->destroy = pnl_dock_overlay_destroy;
+  widget_class->hierarchy_changed = pnl_dock_overlay_hierarchy_changed;
+
+  container_class->add = pnl_dock_overlay_add;
+  container_class->get_child_property = pnl_dock_overlay_get_child_property;
+  container_class->set_child_property = pnl_dock_overlay_set_child_property;
+
+  klass->hide_edges = pnl_dock_overlay_hide_edges;
+
+  g_object_class_override_property (object_class, PROP_MANAGER, "manager");
+
+  child_properties [CHILD_PROP_REVEAL] =
+    g_param_spec_boolean ("reveal",
+                          "Reveal",
+                          "If the panel edge should be revealed",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  gtk_container_class_install_child_properties (container_class, N_CHILD_PROPS, child_properties);
+
+  gtk_widget_class_set_css_name (widget_class, "dockoverlay");
+
+  signals [HIDE_EDGES] =
+    g_signal_new ("hide-edges",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                  G_STRUCT_OFFSET (PnlDockOverlayClass, hide_edges),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 0);
+
+  binding_set = gtk_binding_set_by_class (klass);
+
+  gtk_binding_entry_add_signal (binding_set,
+                                GDK_KEY_Escape,
+                                0,
+                                "hide-edges",
+                                0);
+}
+
+static void
+pnl_dock_overlay_init (PnlDockOverlay *self)
+{
+  PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+  guint i;
+
+  priv->overlay = g_object_new (GTK_TYPE_OVERLAY,
+                                "visible", TRUE,
+                                NULL);
+
+  GTK_CONTAINER_CLASS (pnl_dock_overlay_parent_class)->add (GTK_CONTAINER (self),
+                                                            GTK_WIDGET (priv->overlay));
+
+  g_signal_connect_object (priv->overlay,
+                           "get-child-position",
+                           G_CALLBACK (pnl_dock_overlay_get_child_position),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  for (i = 0; i <= GTK_POS_BOTTOM; i++)
+    {
+      PnlDockOverlayEdge *edge;
+
+      edge = g_object_new (PNL_TYPE_DOCK_OVERLAY_EDGE,
+                           "edge", (GtkPositionType)i,
+                           "visible", TRUE,
+                           NULL);
+
+      pnl_set_weak_pointer (&priv->edges[i], edge);
+
+      gtk_overlay_add_overlay (priv->overlay, GTK_WIDGET (priv->edges [i]));
+
+      priv->edge_adj [i] = gtk_adjustment_new (1, 0, 1, 0, 0, 0);
+
+      g_signal_connect_swapped (priv->edge_adj [i],
+                                "value-changed",
+                                G_CALLBACK (gtk_widget_queue_allocate),
+                                priv->overlay);
+
+      priv->edge_handle_adj [i] = gtk_adjustment_new (0, 0, 1000, 0, 0, 0);
+
+      g_signal_connect_swapped (priv->edge_handle_adj [i],
+                                "value-changed",
+                                G_CALLBACK (gtk_widget_queue_allocate),
+                                priv->overlay);
+    }
+}
+
+static void
+pnl_dock_overlay_init_dock_iface (PnlDockInterface *iface)
+{
+}
+
+GtkWidget *
+pnl_dock_overlay_new (void)
+{
+  return g_object_new (PNL_TYPE_DOCK_OVERLAY, NULL);
+}
+
+static void
+pnl_dock_overlay_add_child (GtkBuildable *buildable,
+                            GtkBuilder   *builder,
+                            GObject      *child,
+                            const gchar  *type)
+{
+  PnlDockOverlay *self = (PnlDockOverlay *)buildable;
+  PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+  PnlDockOverlayEdge *parent = NULL;
+
+  g_assert (PNL_IS_DOCK_OVERLAY (self));
+  g_assert (GTK_IS_BUILDER (builder));
+  g_assert (G_IS_OBJECT (child));
+
+  if (!GTK_IS_WIDGET (child))
+    {
+      g_warning ("Attempt to add a child of type \"%s\" to a \"%s\"",
+                 G_OBJECT_TYPE_NAME (child), G_OBJECT_TYPE_NAME (self));
+      return;
+    }
+
+  if ((type == NULL) || (g_strcmp0 ("center", type) == 0))
+    {
+      gtk_container_add (GTK_CONTAINER (priv->overlay), GTK_WIDGET (child));
+      goto adopt;
+    }
+
+  if (g_strcmp0 ("top", type) == 0)
+    parent = priv->edges [GTK_POS_TOP];
+  else if (g_strcmp0 ("bottom", type) == 0)
+    parent = priv->edges [GTK_POS_BOTTOM];
+  else if (g_strcmp0 ("right", type) == 0)
+    parent = priv->edges [GTK_POS_RIGHT];
+  else
+    parent = priv->edges [GTK_POS_LEFT];
+
+  gtk_container_add (GTK_CONTAINER (parent), GTK_WIDGET (child));
+
+adopt:
+  if (PNL_IS_DOCK_ITEM (child))
+    pnl_dock_item_adopt (PNL_DOCK_ITEM (self), PNL_DOCK_ITEM (child));
+}
+
+static void
+pnl_dock_overlay_init_buildable_iface (GtkBuildableIface *iface)
+{
+  iface->add_child = pnl_dock_overlay_add_child;
+}
+
+static void
+pnl_dock_overlay_present_child (PnlDockItem *item,
+                                PnlDockItem *child)
+{
+  PnlDockOverlay *self = (PnlDockOverlay *)item;
+
+  g_assert (PNL_IS_DOCK_OVERLAY (self));
+  g_assert (PNL_IS_DOCK_ITEM (child));
+
+  gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (child),
+                           "reveal", TRUE,
+                           NULL);
+}
+
+static void
+pnl_dock_overlay_update_visibility (PnlDockItem *item)
+{
+  PnlDockOverlay *self = (PnlDockOverlay *)item;
+  PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_DOCK_OVERLAY (self));
+
+  for (i = 0; i < G_N_ELEMENTS (priv->edges); i++)
+    {
+      PnlDockOverlayEdge *edge = priv->edges [i];
+      gboolean has_widgets;
+
+      if (edge == NULL)
+        continue;
+
+      has_widgets = pnl_dock_item_has_widgets (PNL_DOCK_ITEM (edge));
+
+      gtk_widget_set_child_visible (GTK_WIDGET (edge), has_widgets);
+    }
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+pnl_dock_overlay_init_dock_item_iface (PnlDockItemInterface *iface)
+{
+  iface->present_child = pnl_dock_overlay_present_child;
+  iface->update_visibility = pnl_dock_overlay_update_visibility;
+}
diff --git a/contrib/pnl/pnl-dock-overlay.h b/contrib/pnl/pnl-dock-overlay.h
new file mode 100644
index 0000000..5038456
--- /dev/null
+++ b/contrib/pnl/pnl-dock-overlay.h
@@ -0,0 +1,50 @@
+/* pnl-dock-overlay.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_OVERLAY_H
+#define PNL_DOCK_OVERLAY_H
+
+#include "pnl-dock.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockOverlayClass
+{
+  GtkEventBoxClass parent;
+
+  void (*hide_edges) (PnlDockOverlay *self);
+
+  void (*padding1) (void);
+  void (*padding2) (void);
+  void (*padding3) (void);
+  void (*padding4) (void);
+  void (*padding5) (void);
+  void (*padding6) (void);
+  void (*padding7) (void);
+  void (*padding8) (void);
+};
+
+GtkWidget *pnl_dock_overlay_new (void);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_OVERLAY_H */
diff --git a/contrib/pnl/pnl-dock-paned-private.h b/contrib/pnl/pnl-dock-paned-private.h
new file mode 100644
index 0000000..a120a0a
--- /dev/null
+++ b/contrib/pnl/pnl-dock-paned-private.h
@@ -0,0 +1,31 @@
+/* pnl-dock-paned-private.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_DOCK_PANED_PRIVATE_H
+#define PNL_DOCK_PANED_PRIVATE_H
+
+#include "pnl-dock-paned.h"
+
+G_BEGIN_DECLS
+
+void pnl_dock_paned_set_child_edge (PnlDockPaned    *self,
+                                    GtkPositionType  child_edge);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_PANED_PRIVATE_H */
diff --git a/contrib/pnl/pnl-dock-paned.c b/contrib/pnl/pnl-dock-paned.c
new file mode 100644
index 0000000..36cd6ba
--- /dev/null
+++ b/contrib/pnl/pnl-dock-paned.c
@@ -0,0 +1,134 @@
+/* pnl-dock-paned.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-item.h"
+#include "pnl-dock-paned.h"
+#include "pnl-dock-paned-private.h"
+#include "pnl-dock-stack.h"
+
+typedef struct
+{
+  GtkPositionType child_edge : 2;
+} PnlDockPanedPrivate;
+
+G_DEFINE_TYPE_EXTENDED (PnlDockPaned, pnl_dock_paned, PNL_TYPE_MULTI_PANED, 0,
+                        G_ADD_PRIVATE (PnlDockPaned)
+                        G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM, NULL))
+
+static void
+pnl_dock_paned_add (GtkContainer *container,
+                    GtkWidget    *widget)
+{
+  PnlDockPaned *self = (PnlDockPaned *)container;
+  PnlDockPanedPrivate *priv = pnl_dock_paned_get_instance_private (self);
+
+  g_assert (PNL_IS_DOCK_PANED (self));
+
+  if (PNL_IS_DOCK_STACK (widget))
+    pnl_dock_stack_set_edge (PNL_DOCK_STACK (widget), priv->child_edge);
+
+  GTK_CONTAINER_CLASS (pnl_dock_paned_parent_class)->add (container, widget);
+
+  if (PNL_IS_DOCK_ITEM (widget))
+    pnl_dock_item_adopt (PNL_DOCK_ITEM (self), PNL_DOCK_ITEM (widget));
+}
+
+static void
+pnl_dock_paned_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_paned_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_paned_class_init (PnlDockPanedClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+  object_class->get_property = pnl_dock_paned_get_property;
+  object_class->set_property = pnl_dock_paned_set_property;
+
+  container_class->add = pnl_dock_paned_add;
+
+  gtk_widget_class_set_css_name (widget_class, "dockpaned");
+}
+
+static void
+pnl_dock_paned_init (PnlDockPaned *self)
+{
+  PnlDockPanedPrivate *priv = pnl_dock_paned_get_instance_private (self);
+
+  priv->child_edge = GTK_POS_TOP;
+}
+
+GtkWidget *
+pnl_dock_paned_new (void)
+{
+  return g_object_new (PNL_TYPE_DOCK_PANED, NULL);
+}
+
+static void
+pnl_dock_paned_update_child_edge (GtkWidget *widget,
+                                  gpointer   user_data)
+{
+  GtkPositionType child_edge = GPOINTER_TO_INT (user_data);
+
+  g_assert (GTK_IS_WIDGET (widget));
+
+  if (PNL_IS_DOCK_STACK (widget))
+    pnl_dock_stack_set_edge (PNL_DOCK_STACK (widget), child_edge);
+}
+
+void
+pnl_dock_paned_set_child_edge (PnlDockPaned    *self,
+                               GtkPositionType  child_edge)
+{
+  PnlDockPanedPrivate *priv = pnl_dock_paned_get_instance_private (self);
+
+  g_return_if_fail (PNL_IS_DOCK_PANED (self));
+
+  if (priv->child_edge != child_edge)
+    {
+      priv->child_edge = child_edge;
+
+      gtk_container_foreach (GTK_CONTAINER (self),
+                             pnl_dock_paned_update_child_edge,
+                             GINT_TO_POINTER (child_edge));
+    }
+}
diff --git a/contrib/pnl/pnl-dock-paned.h b/contrib/pnl/pnl-dock-paned.h
new file mode 100644
index 0000000..4889e00
--- /dev/null
+++ b/contrib/pnl/pnl-dock-paned.h
@@ -0,0 +1,48 @@
+/* pnl-dock-paned.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_PANED_H
+#define PNL_DOCK_PANED_H
+
+#include "pnl-dock-types.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockPanedClass
+{
+  PnlMultiPanedClass parent;
+
+  void (*padding1) (void);
+  void (*padding2) (void);
+  void (*padding3) (void);
+  void (*padding4) (void);
+  void (*padding5) (void);
+  void (*padding6) (void);
+  void (*padding7) (void);
+  void (*padding8) (void);
+};
+
+GtkWidget *pnl_dock_paned_new (void);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_PANED_H */
diff --git a/contrib/pnl/pnl-dock-revealer.c b/contrib/pnl/pnl-dock-revealer.c
new file mode 100644
index 0000000..9200592
--- /dev/null
+++ b/contrib/pnl/pnl-dock-revealer.c
@@ -0,0 +1,779 @@
+/* pnl-dock-revealer.c
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-animation.h"
+#include "pnl-dock-revealer.h"
+#include "pnl-util-private.h"
+
+/**
+ * SECTION:pnl-dock-revealer
+ * @title: PnlDockRevealer
+ * @short_description: A panel revealer
+ *
+ * This widget is a bit like #GtkRevealer with a couple of important
+ * differences. First, it only supports a couple transition types
+ * (the direction to slide reveal). Additionally, the size of the
+ * child allocation will not change during the animation. This is not
+ * as generally useful as an upstream GTK+ widget, but is extremely
+ * important for the panel case to avoid things looking strange while
+ * animating into and out of view.
+ */
+
+#define IS_HORIZONTAL(type) \
+  (((type) == PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT) || \
+  ((type) == PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT))
+
+#define IS_VERTICAL(type) \
+  (((type) == PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_UP) || \
+  ((type) == PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN))
+
+typedef struct
+{
+  PnlAnimation                  *animation;
+  GtkAdjustment                 *adjustment;
+  GdkWindow                     *window;
+  gint                           position;
+  guint                          transition_duration;
+  PnlDockRevealerTransitionType  transition_type : 3;
+  guint                          position_set : 1;
+  guint                          reveal_child : 1;
+  guint                          child_revealed : 1;
+} PnlDockRevealerPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (PnlDockRevealer, pnl_dock_revealer, GTK_TYPE_BIN)
+
+enum {
+  PROP_0,
+  PROP_CHILD_REVEALED,
+  PROP_POSITION,
+  PROP_POSITION_SET,
+  PROP_REVEAL_CHILD,
+  PROP_TRANSITION_DURATION,
+  PROP_TRANSITION_TYPE,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+GtkWidget *
+pnl_dock_revealer_new (void)
+{
+  return g_object_new (PNL_TYPE_DOCK_REVEALER, NULL);
+}
+
+guint
+pnl_dock_revealer_get_transition_duration (PnlDockRevealer *self)
+{
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_return_val_if_fail (PNL_IS_DOCK_REVEALER (self), 0);
+
+  return priv->transition_duration;
+}
+
+void
+pnl_dock_revealer_set_transition_duration (PnlDockRevealer *self,
+                                           guint            transition_duration)
+{
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_return_if_fail (PNL_IS_DOCK_REVEALER (self));
+
+  if (priv->transition_duration != transition_duration)
+    {
+      priv->transition_duration = transition_duration;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TRANSITION_DURATION]);
+    }
+}
+
+PnlDockRevealerTransitionType
+pnl_dock_revealer_get_transition_type (PnlDockRevealer *self)
+{
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_return_val_if_fail (PNL_IS_DOCK_REVEALER (self), 0);
+
+  return priv->transition_type;
+}
+
+void
+pnl_dock_revealer_set_transition_type (PnlDockRevealer               *self,
+                                       PnlDockRevealerTransitionType  transition_type)
+{
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_return_if_fail (PNL_IS_DOCK_REVEALER (self));
+  g_return_if_fail (transition_type >= PNL_DOCK_REVEALER_TRANSITION_TYPE_NONE);
+  g_return_if_fail (transition_type <= PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
+
+  if (priv->transition_type != transition_type)
+    {
+      priv->transition_type = transition_type;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TRANSITION_TYPE]);
+    }
+}
+
+gboolean
+pnl_dock_revealer_get_child_revealed (PnlDockRevealer *self)
+{
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_return_val_if_fail (PNL_IS_DOCK_REVEALER (self), FALSE);
+
+  return priv->child_revealed;
+}
+
+gboolean
+pnl_dock_revealer_get_reveal_child (PnlDockRevealer *self)
+{
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_return_val_if_fail (PNL_IS_DOCK_REVEALER (self), FALSE);
+
+  return priv->reveal_child;
+}
+
+static void
+pnl_dock_revealer_animation_done (gpointer user_data)
+{
+  g_autoptr(PnlDockRevealer) self = user_data;
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_assert (PNL_DOCK_REVEALER (self));
+
+  if (priv->adjustment != NULL)
+    {
+      gboolean child_revealed;
+
+      child_revealed = (gtk_adjustment_get_value (priv->adjustment) == 1.0);
+
+      if (priv->child_revealed != child_revealed)
+        {
+          GtkWidget *child = gtk_bin_get_child (GTK_BIN (self));
+
+          priv->child_revealed = child_revealed;
+          gtk_widget_set_child_visible (GTK_WIDGET (child),
+                                        gtk_adjustment_get_value (priv->adjustment) != 0.0);
+          g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CHILD_REVEALED]);
+        }
+
+      gtk_widget_queue_resize (GTK_WIDGET (self));
+    }
+}
+
+static guint
+size_to_duration (gint size)
+{
+  return MAX (150, size * 1.2);
+}
+
+static guint
+pnl_dock_revealer_calculate_duration (PnlDockRevealer *self)
+{
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+  GtkWidget *child;
+  GtkRequisition min_size;
+  GtkRequisition nat_size;
+
+  g_assert (PNL_IS_DOCK_REVEALER (self));
+
+  child = gtk_bin_get_child (GTK_BIN (self));
+
+  if (child == NULL)
+    return 0;
+
+  if (priv->transition_type == PNL_DOCK_REVEALER_TRANSITION_TYPE_NONE)
+    return 0;
+
+  if (priv->transition_duration != 0)
+    return priv->transition_duration;
+
+  gtk_widget_get_preferred_size (child, &min_size, &nat_size);
+
+  if (IS_HORIZONTAL (priv->transition_type))
+    {
+      if (priv->position_set)
+        {
+          if (priv->position_set && priv->position > min_size.width)
+            return size_to_duration (priv->position);
+          return size_to_duration (min_size.width);
+        }
+
+      return size_to_duration (nat_size.width);
+    }
+  else
+    {
+      if (priv->position_set)
+        {
+          if (priv->position_set && priv->position > min_size.height)
+            return size_to_duration (priv->position);
+          return size_to_duration (min_size.height);
+        }
+
+      return size_to_duration (nat_size.height);
+    }
+}
+
+void
+pnl_dock_revealer_set_reveal_child (PnlDockRevealer *self,
+                                    gboolean         reveal_child)
+{
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_return_if_fail (PNL_IS_DOCK_REVEALER (self));
+
+  reveal_child = !!reveal_child;
+
+  if (reveal_child != priv->reveal_child)
+    {
+      PnlAnimation *animation;
+      GtkWidget *child;
+
+      priv->reveal_child = reveal_child;
+
+      child = gtk_bin_get_child (GTK_BIN (self));
+
+      if (child != NULL)
+        {
+          guint duration;
+
+          if (priv->animation != NULL)
+            {
+              pnl_animation_stop (priv->animation);
+              pnl_clear_weak_pointer (&priv->animation);
+            }
+
+          gtk_widget_set_child_visible (child, TRUE);
+
+          duration = pnl_dock_revealer_calculate_duration (self);
+
+          animation = pnl_object_animate_full (priv->adjustment,
+                                               PNL_ANIMATION_EASE_IN_OUT_CUBIC,
+                                               duration,
+                                               gtk_widget_get_frame_clock (GTK_WIDGET (self)),
+                                               pnl_dock_revealer_animation_done,
+                                               g_object_ref (self),
+                                               "value", reveal_child ? 1.0 : 0.0,
+                                               NULL);
+
+          pnl_set_weak_pointer (&priv->animation, animation);
+        }
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_REVEAL_CHILD]);
+    }
+}
+
+gint
+pnl_dock_revealer_get_position (PnlDockRevealer *self)
+{
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_return_val_if_fail (PNL_IS_DOCK_REVEALER (self), 0);
+
+  return priv->position;
+}
+
+void
+pnl_dock_revealer_set_position (PnlDockRevealer *self,
+                                gint             position)
+{
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_return_if_fail (PNL_IS_DOCK_REVEALER (self));
+  g_return_if_fail (position >= 0);
+
+  if (priv->position != position)
+    {
+      priv->position = position;
+
+      if (!priv->position_set)
+        {
+          priv->position_set = TRUE;
+          g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_POSITION_SET]);
+        }
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_POSITION]);
+      gtk_widget_queue_resize (GTK_WIDGET (self));
+    }
+}
+
+gboolean
+pnl_dock_revealer_get_position_set (PnlDockRevealer *self)
+{
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_return_val_if_fail (PNL_IS_DOCK_REVEALER (self), FALSE);
+
+  return priv->position_set;
+}
+
+void
+pnl_dock_revealer_set_position_set (PnlDockRevealer *self,
+                                    gboolean         position_set)
+{
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_return_if_fail (PNL_IS_DOCK_REVEALER (self));
+
+  position_set = !!position_set;
+
+  if (priv->position_set != position_set)
+    {
+      priv->position_set = position_set;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_POSITION_SET]);
+      gtk_widget_queue_resize (GTK_WIDGET (self));
+    }
+}
+
+static void
+pnl_dock_revealer_get_child_preferred_width (PnlDockRevealer *self,
+                                             gint            *min_width,
+                                             gint            *nat_width)
+{
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+  GtkWidget *child;
+
+  g_assert (PNL_IS_DOCK_REVEALER (self));
+  g_assert (min_width != NULL);
+  g_assert (nat_width != NULL);
+
+  *min_width = 0;
+  *nat_width = 0;
+
+  if (NULL == (child = gtk_bin_get_child (GTK_BIN (self))))
+    return;
+
+  if (!gtk_widget_get_child_visible (child) || !gtk_widget_get_visible (child))
+    return;
+
+  gtk_widget_get_preferred_width (child, min_width, nat_width);
+
+  if (IS_HORIZONTAL (priv->transition_type) && priv->position_set)
+    {
+      if (priv->position > *min_width)
+        *nat_width = priv->position;
+      else
+        *nat_width = *min_width;
+    }
+}
+
+static void
+pnl_dock_revealer_get_preferred_width (GtkWidget *widget,
+                                       gint      *min_width,
+                                       gint      *nat_width)
+{
+  PnlDockRevealer *self = (PnlDockRevealer *)widget;
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_assert (PNL_IS_DOCK_REVEALER (self));
+  g_assert (min_width != NULL);
+  g_assert (nat_width != NULL);
+
+  pnl_dock_revealer_get_child_preferred_width (self, min_width, nat_width);
+
+  if (IS_HORIZONTAL (priv->transition_type) && priv->animation != NULL)
+    {
+      /*
+       * We allow going smaller than the minimum size during animations
+       * and rely on clipping to hide the child.
+       */
+      *min_width = 0;
+
+      /*
+       * Our natural width is adjusted for the in-progress animation.
+       */
+      *nat_width *= gtk_adjustment_get_value (priv->adjustment);
+    }
+}
+
+static void
+pnl_dock_revealer_get_child_preferred_height (PnlDockRevealer *self,
+                                              gint            *min_height,
+                                              gint            *nat_height)
+{
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+  GtkWidget *child;
+
+  g_assert (PNL_IS_DOCK_REVEALER (self));
+  g_assert (min_height != NULL);
+  g_assert (nat_height != NULL);
+
+  *min_height = 0;
+  *nat_height = 0;
+
+  if (NULL == (child = gtk_bin_get_child (GTK_BIN (self))))
+    return;
+
+  if (!gtk_widget_get_child_visible (child) || !gtk_widget_get_visible (child))
+    return;
+
+  gtk_widget_get_preferred_height (child, min_height, nat_height);
+
+  if (IS_VERTICAL (priv->transition_type) && priv->position_set)
+    {
+      if (priv->position > *min_height)
+        *nat_height = priv->position;
+      else
+        *nat_height = *min_height;
+    }
+}
+
+static void
+pnl_dock_revealer_get_preferred_height (GtkWidget *widget,
+                                        gint      *min_height,
+                                        gint      *nat_height)
+{
+  PnlDockRevealer *self = (PnlDockRevealer *)widget;
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_assert (PNL_IS_DOCK_REVEALER (self));
+  g_assert (min_height != NULL);
+  g_assert (nat_height != NULL);
+
+  pnl_dock_revealer_get_child_preferred_height (self, min_height, nat_height);
+
+  if (IS_VERTICAL (priv->transition_type) && priv->animation != NULL)
+    {
+      /*
+       * We allow going smaller than the minimum size during animations
+       * and rely on clipping to hide the child.
+       */
+      *min_height = 0;
+
+      /*
+       * Our natural height is adjusted for the in-progress animation.
+       */
+      *nat_height *= gtk_adjustment_get_value (priv->adjustment);
+    }
+}
+
+static void
+pnl_dock_revealer_size_allocate (GtkWidget     *widget,
+                                 GtkAllocation *allocation)
+{
+  PnlDockRevealer *self = (PnlDockRevealer *)widget;
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+  GtkAllocation child_allocation;
+  GtkRequisition min_req;
+  GtkRequisition nat_req;
+  GtkWidget *child;
+
+  g_assert (PNL_IS_DOCK_REVEALER (self));
+
+  gtk_widget_set_allocation (widget, allocation);
+
+  if (gtk_widget_get_realized (GTK_WIDGET (self)))
+    gdk_window_move_resize (priv->window,
+                            allocation->x,
+                            allocation->y,
+                            allocation->width,
+                            allocation->height);
+
+  if (NULL == (child = gtk_bin_get_child (GTK_BIN (self))))
+    return;
+
+  if (!gtk_widget_get_child_visible (child))
+    return;
+
+  child_allocation.x = 0;
+  child_allocation.y = 0;
+  child_allocation.width = allocation->width;
+  child_allocation.height = allocation->height;
+
+  if (IS_HORIZONTAL (priv->transition_type))
+    {
+      pnl_dock_revealer_get_child_preferred_width (self, &min_req.width, &nat_req.width);
+      child_allocation.width = nat_req.width;
+
+      if (priv->transition_type == PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
+        child_allocation.x = allocation->width - child_allocation.width;
+    }
+  else if (IS_VERTICAL (priv->transition_type))
+    {
+      pnl_dock_revealer_get_child_preferred_height (self, &min_req.height, &nat_req.height);
+      child_allocation.height = nat_req.height;
+
+      if (priv->transition_type == PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN)
+        child_allocation.y = allocation->height - child_allocation.height;
+    }
+
+  gtk_widget_size_allocate (child, &child_allocation);
+}
+
+static void
+pnl_dock_revealer_add (GtkContainer *container,
+                       GtkWidget    *widget)
+{
+  PnlDockRevealer *self = (PnlDockRevealer *)container;
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_assert (PNL_IS_DOCK_REVEALER (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  GTK_CONTAINER_CLASS (pnl_dock_revealer_parent_class)->add (container, widget);
+
+  gtk_widget_set_child_visible (widget, priv->reveal_child);
+}
+
+static void
+pnl_dock_revealer_realize (GtkWidget *widget)
+{
+  PnlDockRevealer *self = (PnlDockRevealer *)widget;
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+  GdkWindowAttr attributes = { 0 };
+  GdkWindow *parent;
+  GtkAllocation alloc;
+  gint attributes_mask = 0;
+
+  g_assert (PNL_IS_DOCK_REVEALER (widget));
+
+  gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+
+  gtk_widget_set_realized (GTK_WIDGET (self), TRUE);
+
+  parent = gtk_widget_get_parent_window (GTK_WIDGET (self));
+
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.wclass = GDK_INPUT_OUTPUT;
+  attributes.visual = gtk_widget_get_visual (GTK_WIDGET (self));
+  attributes.x = alloc.x;
+  attributes.y = alloc.y;
+  attributes.width = alloc.width;
+  attributes.height = alloc.height;
+  attributes.event_mask = 0;
+
+  attributes_mask = (GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL);
+
+  priv->window = gdk_window_new (parent, &attributes, attributes_mask);
+  gtk_widget_set_window (GTK_WIDGET (self), priv->window);
+  gtk_widget_register_window (GTK_WIDGET (self), priv->window);
+}
+
+static void
+pnl_dock_revealer_destroy (GtkWidget *widget)
+{
+  PnlDockRevealer *self = (PnlDockRevealer *)widget;
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  g_clear_object (&priv->adjustment);
+  pnl_clear_weak_pointer (&priv->animation);
+
+  GTK_WIDGET_CLASS (pnl_dock_revealer_parent_class)->destroy (widget);
+}
+
+static void
+pnl_dock_revealer_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  PnlDockRevealer *self = PNL_DOCK_REVEALER (object);
+
+  switch (prop_id)
+    {
+    case PROP_CHILD_REVEALED:
+      g_value_set_boolean (value, pnl_dock_revealer_get_child_revealed (self));
+      break;
+
+    case PROP_POSITION:
+      g_value_set_int (value, pnl_dock_revealer_get_position (self));
+      break;
+
+    case PROP_POSITION_SET:
+      g_value_set_boolean (value, pnl_dock_revealer_get_position_set (self));
+      break;
+
+    case PROP_REVEAL_CHILD:
+      g_value_set_boolean (value, pnl_dock_revealer_get_reveal_child (self));
+      break;
+
+    case PROP_TRANSITION_DURATION:
+      g_value_set_uint (value, pnl_dock_revealer_get_transition_duration (self));
+      break;
+
+    case PROP_TRANSITION_TYPE:
+      g_value_set_enum (value, pnl_dock_revealer_get_transition_type (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_revealer_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  PnlDockRevealer *self = PNL_DOCK_REVEALER (object);
+
+  switch (prop_id)
+    {
+    case PROP_REVEAL_CHILD:
+      pnl_dock_revealer_set_reveal_child (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_POSITION:
+      pnl_dock_revealer_set_position (self, g_value_get_int (value));
+      break;
+
+    case PROP_POSITION_SET:
+      pnl_dock_revealer_set_position_set (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_TRANSITION_DURATION:
+      pnl_dock_revealer_set_transition_duration (self, g_value_get_uint (value));
+      break;
+
+    case PROP_TRANSITION_TYPE:
+      pnl_dock_revealer_set_transition_type (self, g_value_get_enum (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_revealer_class_init (PnlDockRevealerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+  object_class->get_property = pnl_dock_revealer_get_property;
+  object_class->set_property = pnl_dock_revealer_set_property;
+
+  widget_class->destroy = pnl_dock_revealer_destroy;
+  widget_class->get_preferred_width = pnl_dock_revealer_get_preferred_width;
+  widget_class->get_preferred_height = pnl_dock_revealer_get_preferred_height;
+  widget_class->realize = pnl_dock_revealer_realize;
+  widget_class->size_allocate = pnl_dock_revealer_size_allocate;
+
+  container_class->add = pnl_dock_revealer_add;
+
+  properties [PROP_CHILD_REVEALED] =
+    g_param_spec_boolean ("child-revealed",
+                          "Child Revealed",
+                          "If the child is fully revealed",
+                          TRUE,
+                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_POSITION] =
+    g_param_spec_int ("position",
+                      "Position",
+                      "Position",
+                      0,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_POSITION_SET] =
+    g_param_spec_boolean ("position-set",
+                          "Position Set",
+                          "If the position has been set",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_REVEAL_CHILD] =
+    g_param_spec_boolean ("reveal-child",
+                          "Reveal Child",
+                          "If the child should be revealed",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TRANSITION_DURATION] =
+    g_param_spec_uint ("transition-duration",
+                       "Transition Duration",
+                       "Length of duration in milliseconds",
+                       0,
+                       G_MAXUINT,
+                       0,
+                       (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TRANSITION_TYPE] =
+    g_param_spec_enum ("transition-type",
+                       "Transition Type",
+                       "Transition Type",
+                       PNL_TYPE_DOCK_REVEALER_TRANSITION_TYPE,
+                       PNL_DOCK_REVEALER_TRANSITION_TYPE_NONE,
+                       (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+pnl_dock_revealer_init (PnlDockRevealer *self)
+{
+  PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+  gtk_widget_set_has_window (GTK_WIDGET (self), TRUE);
+
+  priv->reveal_child = FALSE;
+  priv->child_revealed = FALSE;
+
+  priv->transition_duration = 0;
+
+  priv->adjustment = g_object_new (GTK_TYPE_ADJUSTMENT,
+                                   "lower", 0.0,
+                                   "upper", 1.0,
+                                   "value", 0.0,
+                                   NULL);
+
+  g_signal_connect_object (priv->adjustment,
+                           "value-changed",
+                           G_CALLBACK (gtk_widget_queue_resize),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+GType
+pnl_dock_revealer_transition_type_get_type (void)
+{
+  static GType type_id;
+
+  if (g_once_init_enter (&type_id))
+    {
+      GType _type_id;
+      static const GEnumValue values[] = {
+        { PNL_DOCK_REVEALER_TRANSITION_TYPE_NONE,
+          "PNL_DOCK_REVEALER_TRANSITION_TYPE_NONE",
+          "none" },
+        { PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT,
+          "PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT",
+          "slide-right" },
+        { PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT,
+          "PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT",
+          "slide-left" },
+        { PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_UP,
+          "PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_UP",
+          "slide-up" },
+        { PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN,
+          "PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN",
+          "slide-down" },
+        { 0 }
+      };
+
+      _type_id = g_enum_register_static ("PnlDockRevealerTransitionType", values);
+
+      g_once_init_leave (&type_id, _type_id);
+    }
+
+  return type_id;
+}
diff --git a/contrib/pnl/pnl-dock-revealer.h b/contrib/pnl/pnl-dock-revealer.h
new file mode 100644
index 0000000..0347d35
--- /dev/null
+++ b/contrib/pnl/pnl-dock-revealer.h
@@ -0,0 +1,76 @@
+/* pnl-dock-revealer.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_REVEALER_H
+#define PNL_DOCK_REVEALER_H
+
+#include "pnl-dock-types.h"
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_DOCK_REVEALER_TRANSITION_TYPE (pnl_dock_revealer_transition_type_get_type())
+
+typedef enum
+{
+  PNL_DOCK_REVEALER_TRANSITION_TYPE_NONE,
+  PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT,
+  PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT,
+  PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_UP,
+  PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN,
+} PnlDockRevealerTransitionType;
+
+struct _PnlDockRevealerClass
+{
+  GtkBinClass parent;
+
+  void (*padding1) (void);
+  void (*padding2) (void);
+  void (*padding3) (void);
+  void (*padding4) (void);
+  void (*padding5) (void);
+  void (*padding6) (void);
+  void (*padding7) (void);
+  void (*padding8) (void);
+};
+
+GType                          pnl_dock_revealer_transition_type_get_type (void);
+GtkWidget                     *pnl_dock_revealer_new                      (void);
+PnlDockRevealerTransitionType  pnl_dock_revealer_get_transition_type      (PnlDockRevealer *self);
+void                           pnl_dock_revealer_set_transition_type      (PnlDockRevealer               
*self,
+                                                                           PnlDockRevealerTransitionType  
transition_type);
+gboolean                       pnl_dock_revealer_get_child_revealed       (PnlDockRevealer               
*self);
+void                           pnl_dock_revealer_set_reveal_child         (PnlDockRevealer               
*self,
+                                                                           gboolean                       
reveal_child);
+gboolean                       pnl_dock_revealer_get_reveal_child         (PnlDockRevealer               
*self);
+gint                           pnl_dock_revealer_get_position             (PnlDockRevealer               
*self);
+void                           pnl_dock_revealer_set_position             (PnlDockRevealer               
*self,
+                                                                           gint                           
position);
+gboolean                       pnl_dock_revealer_get_position_set         (PnlDockRevealer               
*self);
+void                           pnl_dock_revealer_set_position_set         (PnlDockRevealer               
*self,
+                                                                           gboolean                       
position_set);
+guint                          pnl_dock_revealer_get_transition_duration  (PnlDockRevealer               
*self);
+void                           pnl_dock_revealer_set_transition_duration  (PnlDockRevealer               
*self,
+                                                                           guint                          
transition_duration);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_REVEALER_H */
diff --git a/contrib/pnl/pnl-dock-stack.c b/contrib/pnl/pnl-dock-stack.c
new file mode 100644
index 0000000..97cf410
--- /dev/null
+++ b/contrib/pnl/pnl-dock-stack.c
@@ -0,0 +1,320 @@
+/* pnl-dock-stack.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-item.h"
+#include "pnl-dock-stack.h"
+#include "pnl-dock-widget.h"
+#include "pnl-dock-tab-strip.h"
+
+typedef struct
+{
+  GtkStack         *stack;
+  PnlTabStrip      *tab_strip;
+  GtkPositionType   edge : 2;
+} PnlDockStackPrivate;
+
+static void pnl_dock_stack_init_dock_item_iface (PnlDockItemInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (PnlDockStack, pnl_dock_stack, GTK_TYPE_BOX, 0,
+                        G_ADD_PRIVATE (PnlDockStack)
+                        G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM,
+                                               pnl_dock_stack_init_dock_item_iface))
+
+enum {
+  PROP_0,
+  PROP_EDGE,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+pnl_dock_stack_add (GtkContainer *container,
+                    GtkWidget    *widget)
+{
+  PnlDockStack *self = (PnlDockStack *)container;
+  PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+  const gchar *title = NULL;
+
+  g_assert (PNL_IS_DOCK_STACK (self));
+
+  if (PNL_IS_DOCK_WIDGET (widget))
+    title = pnl_dock_widget_get_title (PNL_DOCK_WIDGET (widget));
+
+  gtk_container_add_with_properties (GTK_CONTAINER (priv->stack), widget,
+                                     "title", title,
+                                     NULL);
+
+  if (PNL_IS_DOCK_ITEM (widget))
+    pnl_dock_item_adopt (PNL_DOCK_ITEM (self), PNL_DOCK_ITEM (widget));
+}
+
+static void
+pnl_dock_stack_grab_focus (GtkWidget *widget)
+{
+  PnlDockStack *self = (PnlDockStack *)widget;
+  PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+  GtkWidget *child;
+
+  g_assert (PNL_IS_DOCK_STACK (self));
+
+  child = gtk_stack_get_visible_child (priv->stack);
+
+  if (child != NULL)
+    gtk_widget_grab_focus (GTK_WIDGET (priv->stack));
+  else
+    GTK_WIDGET_CLASS (pnl_dock_stack_parent_class)->grab_focus (widget);
+}
+
+static void
+pnl_dock_stack_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  PnlDockStack *self = PNL_DOCK_STACK (object);
+
+  switch (prop_id)
+    {
+    case PROP_EDGE:
+      g_value_set_enum (value, pnl_dock_stack_get_edge (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_stack_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  PnlDockStack *self = PNL_DOCK_STACK (object);
+
+  switch (prop_id)
+    {
+    case PROP_EDGE:
+      pnl_dock_stack_set_edge (self, g_value_get_enum (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_stack_class_init (PnlDockStackClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+  object_class->get_property = pnl_dock_stack_get_property;
+  object_class->set_property = pnl_dock_stack_set_property;
+
+  widget_class->grab_focus = pnl_dock_stack_grab_focus;
+
+  container_class->add = pnl_dock_stack_add;
+
+  properties [PROP_EDGE] =
+    g_param_spec_enum ("edge",
+                       "Edge",
+                       "The edge for the tab strip",
+                       GTK_TYPE_POSITION_TYPE,
+                       GTK_POS_TOP,
+                       (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_css_name (widget_class, "dockstack");
+}
+
+static void
+pnl_dock_stack_init (PnlDockStack *self)
+{
+  PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
+
+  priv->edge = GTK_POS_TOP;
+
+  priv->stack = g_object_new (GTK_TYPE_STACK,
+                              "homogeneous", FALSE,
+                              "visible", TRUE,
+                              NULL);
+
+  priv->tab_strip = g_object_new (PNL_TYPE_DOCK_TAB_STRIP,
+                                  "edge", GTK_POS_TOP,
+                                  "stack", priv->stack,
+                                  "visible", TRUE,
+                                  NULL);
+
+  GTK_CONTAINER_CLASS (pnl_dock_stack_parent_class)->add (GTK_CONTAINER (self),
+                                                          GTK_WIDGET (priv->tab_strip));
+  GTK_CONTAINER_CLASS (pnl_dock_stack_parent_class)->add (GTK_CONTAINER (self),
+                                                          GTK_WIDGET (priv->stack));
+}
+
+GtkWidget *
+pnl_dock_stack_new (void)
+{
+  return g_object_new (PNL_TYPE_DOCK_STACK, NULL);
+}
+
+GtkPositionType
+pnl_dock_stack_get_edge (PnlDockStack *self)
+{
+  PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+
+  g_return_val_if_fail (PNL_IS_DOCK_STACK (self), 0);
+
+  return priv->edge;
+}
+
+void
+pnl_dock_stack_set_edge (PnlDockStack    *self,
+                         GtkPositionType  edge)
+{
+  PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+
+  g_return_if_fail (PNL_IS_DOCK_STACK (self));
+  g_return_if_fail (edge >= 0);
+  g_return_if_fail (edge <= 3);
+
+  if (edge != priv->edge)
+    {
+      priv->edge = edge;
+
+      pnl_tab_strip_set_edge (priv->tab_strip, edge);
+
+      switch (edge)
+        {
+        case GTK_POS_TOP:
+          gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
+                                          GTK_ORIENTATION_VERTICAL);
+          gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->tab_strip),
+                                          GTK_ORIENTATION_HORIZONTAL);
+          gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (priv->tab_strip),
+                                   "position", 0,
+                                   NULL);
+          break;
+
+        case GTK_POS_BOTTOM:
+          gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
+                                          GTK_ORIENTATION_VERTICAL);
+          gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->tab_strip),
+                                          GTK_ORIENTATION_HORIZONTAL);
+          gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (priv->tab_strip),
+                                   "position", 1,
+                                   NULL);
+          break;
+
+        case GTK_POS_LEFT:
+          gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
+                                          GTK_ORIENTATION_HORIZONTAL);
+          gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->tab_strip),
+                                          GTK_ORIENTATION_VERTICAL);
+          gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (priv->tab_strip),
+                                   "position", 0,
+                                   NULL);
+          break;
+
+        case GTK_POS_RIGHT:
+          gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
+                                          GTK_ORIENTATION_HORIZONTAL);
+          gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->tab_strip),
+                                          GTK_ORIENTATION_VERTICAL);
+          gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (priv->tab_strip),
+                                   "position", 1,
+                                   NULL);
+          break;
+
+        default:
+          g_assert_not_reached ();
+        }
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EDGE]);
+    }
+}
+
+static void
+pnl_dock_stack_present_child (PnlDockItem *item,
+                              PnlDockItem *child)
+{
+  PnlDockStack *self = (PnlDockStack *)item;
+  PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+
+  g_assert (PNL_IS_DOCK_STACK (self));
+  g_assert (PNL_IS_DOCK_ITEM (child));
+
+  gtk_stack_set_visible_child (priv->stack, GTK_WIDGET (child));
+}
+
+static gboolean
+pnl_dock_stack_get_child_visible (PnlDockItem *item,
+                                  PnlDockItem *child)
+{
+  PnlDockStack *self = (PnlDockStack *)item;
+  PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+  GtkWidget *visible_child;
+
+  g_assert (PNL_IS_DOCK_STACK (self));
+  g_assert (PNL_IS_DOCK_ITEM (child));
+
+  visible_child = gtk_stack_get_visible_child (priv->stack);
+
+  if (visible_child != NULL)
+    return gtk_widget_is_ancestor (GTK_WIDGET (child), visible_child);
+
+  return FALSE;
+}
+
+static void
+pnl_dock_stack_set_child_visible (PnlDockItem *item,
+                                  PnlDockItem *child,
+                                  gboolean     child_visible)
+{
+  PnlDockStack *self = (PnlDockStack *)item;
+  PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+  GtkWidget *parent;
+  GtkWidget *last_parent = (GtkWidget *)child;
+
+  g_assert (PNL_IS_DOCK_STACK (self));
+  g_assert (PNL_IS_DOCK_ITEM (child));
+
+  for (parent = gtk_widget_get_parent (GTK_WIDGET (child));
+       parent != NULL;
+       last_parent = parent, parent = gtk_widget_get_parent (parent))
+    {
+      if (parent == (GtkWidget *)priv->stack)
+        {
+          gtk_stack_set_visible_child (priv->stack, last_parent);
+          return;
+        }
+    }
+}
+
+static void
+pnl_dock_stack_init_dock_item_iface (PnlDockItemInterface *iface)
+{
+  iface->present_child = pnl_dock_stack_present_child;
+  iface->get_child_visible = pnl_dock_stack_get_child_visible;
+  iface->set_child_visible = pnl_dock_stack_set_child_visible;
+}
diff --git a/contrib/pnl/pnl-dock-stack.h b/contrib/pnl/pnl-dock-stack.h
new file mode 100644
index 0000000..fb19286
--- /dev/null
+++ b/contrib/pnl/pnl-dock-stack.h
@@ -0,0 +1,42 @@
+/* pnl-dock-stack.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_STACK_H
+#define PNL_DOCK_STACK_H
+
+#include "pnl-dock-types.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockStackClass
+{
+  GtkBoxClass parent;
+};
+
+GtkWidget       *pnl_dock_stack_new      (void);
+GtkPositionType  pnl_dock_stack_get_edge (PnlDockStack    *self);
+void             pnl_dock_stack_set_edge (PnlDockStack    *self,
+                                          GtkPositionType  edge);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_STACK_H */
diff --git a/contrib/pnl/pnl-dock-tab-strip.c b/contrib/pnl/pnl-dock-tab-strip.c
new file mode 100644
index 0000000..3f0b9a6
--- /dev/null
+++ b/contrib/pnl/pnl-dock-tab-strip.c
@@ -0,0 +1,41 @@
+/* pnl-dock-tab-strip.c
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-tab-strip.h"
+
+struct _PnlDockTabStrip
+{
+  PnlTabStrip parent;
+};
+
+enum {
+  PROP_0,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (PnlDockTabStrip, pnl_dock_tab_strip, PNL_TYPE_TAB_STRIP)
+
+static void
+pnl_dock_tab_strip_class_init (PnlDockTabStripClass *klass)
+{
+}
+
+static void
+pnl_dock_tab_strip_init (PnlDockTabStrip *strip)
+{
+}
diff --git a/contrib/pnl/pnl-dock-tab-strip.h b/contrib/pnl/pnl-dock-tab-strip.h
new file mode 100644
index 0000000..6cf04d6
--- /dev/null
+++ b/contrib/pnl/pnl-dock-tab-strip.h
@@ -0,0 +1,34 @@
+/* pnl-dock-tab-strip.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_DOCK_TAB_STRIP_H
+#define PNL_DOCK_TAB_STRIP_H
+
+#include "pnl-tab-strip.h"
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_DOCK_TAB_STRIP (pnl_dock_tab_strip_get_type())
+
+G_DECLARE_FINAL_TYPE (PnlDockTabStrip, pnl_dock_tab_strip, PNL, DOCK_TAB_STRIP, PnlTabStrip)
+
+GtkWidget *pnl_dock_tab_strip_new (void);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_TAB_STRIP_H */
diff --git a/contrib/pnl/pnl-dock-transient-grab.c b/contrib/pnl/pnl-dock-transient-grab.c
new file mode 100644
index 0000000..9f85727
--- /dev/null
+++ b/contrib/pnl/pnl-dock-transient-grab.c
@@ -0,0 +1,322 @@
+/* pnl-dock-transient-grab.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-transient-grab.h"
+
+struct _PnlDockTransientGrab
+{
+  GObject parent_instance;
+
+  GPtrArray *items;
+  GHashTable *hidden;
+
+  guint timeout;
+
+  guint acquired : 1;
+};
+
+G_DEFINE_TYPE (PnlDockTransientGrab, pnl_dock_transient_grab, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_TIMEOUT,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+pnl_dock_transient_grab_weak_notify (gpointer  data,
+                                     GObject  *where_object_was)
+{
+  PnlDockTransientGrab *self = data;
+
+  g_assert (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+
+  g_ptr_array_remove (self->items, where_object_was);
+}
+
+static void
+pnl_dock_transient_grab_finalize (GObject *object)
+{
+  PnlDockTransientGrab *self = (PnlDockTransientGrab *)object;
+  guint i;
+
+  for (i = 0; i < self->items->len; i++)
+    g_object_weak_unref (g_ptr_array_index (self->items, i),
+                         pnl_dock_transient_grab_weak_notify,
+                         self);
+
+  g_clear_pointer (&self->items, g_ptr_array_unref);
+  g_clear_pointer (&self->hidden, g_hash_table_unref);
+
+  G_OBJECT_CLASS (pnl_dock_transient_grab_parent_class)->finalize (object);
+}
+
+static void
+pnl_dock_transient_grab_get_property (GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+  PnlDockTransientGrab *self = PNL_DOCK_TRANSIENT_GRAB (object);
+
+  switch (prop_id)
+    {
+    case PROP_TIMEOUT:
+      g_value_set_uint (value, pnl_dock_transient_grab_get_timeout (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_transient_grab_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+  PnlDockTransientGrab *self = PNL_DOCK_TRANSIENT_GRAB (object);
+
+  switch (prop_id)
+    {
+    case PROP_TIMEOUT:
+      pnl_dock_transient_grab_set_timeout (self, g_value_get_uint (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_transient_grab_class_init (PnlDockTransientGrabClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = pnl_dock_transient_grab_finalize;
+  object_class->get_property = pnl_dock_transient_grab_get_property;
+  object_class->set_property = pnl_dock_transient_grab_set_property;
+
+  properties [PROP_TIMEOUT] =
+    g_param_spec_uint ("timeout",
+                       "Timeout",
+                       "Timeout",
+                       0,
+                       G_MAXUINT,
+                       0,
+                       (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+pnl_dock_transient_grab_init (PnlDockTransientGrab *self)
+{
+  self->items = g_ptr_array_new ();
+  self->hidden = g_hash_table_new (NULL, NULL);
+}
+
+PnlDockTransientGrab *
+pnl_dock_transient_grab_new (void)
+{
+  return g_object_new (PNL_TYPE_DOCK_TRANSIENT_GRAB, NULL);
+}
+
+guint
+pnl_dock_transient_grab_get_timeout (PnlDockTransientGrab *self)
+{
+  g_return_val_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self), 0);
+
+  return self->timeout;
+}
+
+void
+pnl_dock_transient_grab_set_timeout (PnlDockTransientGrab *self,
+                                     guint                 timeout)
+{
+  g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+
+  if (timeout != self->timeout)
+    {
+      self->timeout = timeout;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TIMEOUT]);
+    }
+}
+
+gboolean
+pnl_dock_transient_grab_contains (PnlDockTransientGrab *self,
+                                  PnlDockItem          *item)
+{
+  guint i;
+
+  g_return_val_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self), FALSE);
+  g_return_val_if_fail (PNL_IS_DOCK_ITEM (item), FALSE);
+
+  for (i = 0; i < self->items->len; i++)
+    if (g_ptr_array_index (self->items, i) == item)
+      return TRUE;
+
+  return FALSE;
+}
+
+void
+pnl_dock_transient_grab_add_item (PnlDockTransientGrab *self,
+                                  PnlDockItem          *item)
+{
+  g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+  g_return_if_fail (PNL_IS_DOCK_ITEM (item));
+
+  g_ptr_array_add (self->items, item);
+
+  g_object_weak_ref (G_OBJECT (item),
+                     pnl_dock_transient_grab_weak_notify,
+                     self);
+}
+
+static void
+pnl_dock_transient_grab_remove_index (PnlDockTransientGrab *self,
+                                      guint                 index)
+{
+  PnlDockItem *item;
+
+  g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+  g_return_if_fail (index < self->items->len);
+
+  item = g_ptr_array_index (self->items, index);
+  g_object_weak_unref (G_OBJECT (item),
+                       pnl_dock_transient_grab_weak_notify,
+                       self);
+  g_ptr_array_remove_index (self->items, index);
+  g_hash_table_remove (self->hidden, item);
+}
+
+void
+pnl_dock_transient_grab_remove_item (PnlDockTransientGrab *self,
+                                     PnlDockItem          *item)
+{
+  guint i;
+
+  g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+  g_return_if_fail (PNL_IS_DOCK_ITEM (item));
+
+  for (i = 0; i < self->items->len; i++)
+    {
+      PnlDockItem *iter = g_ptr_array_index (self->items, i);
+
+      if (item == iter)
+        {
+          pnl_dock_transient_grab_remove_index (self, i);
+          return;
+        }
+    }
+}
+
+void
+pnl_dock_transient_grab_acquire (PnlDockTransientGrab *self)
+{
+  guint i;
+
+  g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+  g_return_if_fail (self->acquired == FALSE);
+
+  self->acquired = TRUE;
+
+  for (i = self->items->len; i > 1; i--)
+    {
+      PnlDockItem *parent = g_ptr_array_index (self->items, i - 1);
+      PnlDockItem *child = g_ptr_array_index (self->items, i - 2);
+
+      if (!pnl_dock_item_get_child_visible (parent, child))
+        {
+          pnl_dock_item_set_child_visible (parent, child, TRUE);
+          g_hash_table_insert (self->hidden, child, NULL);
+        }
+    }
+}
+
+void
+pnl_dock_transient_grab_release (PnlDockTransientGrab *self)
+{
+  guint i;
+
+  g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+  g_return_if_fail (self->acquired == TRUE);
+
+  for (i = 0; i < self->items->len; i++)
+    {
+      PnlDockItem *item = g_ptr_array_index (self->items, i);
+
+      if (g_hash_table_contains (self->hidden, item))
+        {
+          PnlDockItem *parent = pnl_dock_item_get_parent (item);
+
+          if (parent != NULL)
+            pnl_dock_item_set_child_visible (parent, item, FALSE);
+        }
+    }
+}
+
+gboolean
+pnl_dock_transient_grab_is_descendant (PnlDockTransientGrab *self,
+                                       GtkWidget            *widget)
+{
+  g_return_val_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self), FALSE);
+
+  if (self->items->len > 0)
+    {
+      GtkWidget *item = g_ptr_array_index (self->items, 0);
+      GtkWidget *ancestor;
+
+      ancestor = gtk_widget_get_ancestor (widget, PNL_TYPE_DOCK_ITEM);
+
+      return (item == ancestor);
+    }
+
+  return FALSE;
+}
+
+void
+pnl_dock_transient_grab_steal_common_ancestors (PnlDockTransientGrab *self,
+                                                PnlDockTransientGrab *other)
+{
+  guint i;
+
+  g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+  g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (other));
+
+  for (i = other->items->len; i > 0; i--)
+    {
+      PnlDockItem *item = g_ptr_array_index (other->items, i - 1);
+
+      if (pnl_dock_transient_grab_contains (self, item))
+        {
+          /*
+           * Since we are stealing the common ancestors, we don't want the
+           * previous grab to hide them when releasing, so clear the items
+           * from the hash of children it wants to hide.
+           */
+          g_hash_table_remove (other->hidden, item);
+
+          pnl_dock_transient_grab_add_item (self, item);
+          pnl_dock_transient_grab_remove_index (other, i - 1);
+        }
+    }
+}
diff --git a/contrib/pnl/pnl-dock-transient-grab.h b/contrib/pnl/pnl-dock-transient-grab.h
new file mode 100644
index 0000000..1c603d1
--- /dev/null
+++ b/contrib/pnl/pnl-dock-transient-grab.h
@@ -0,0 +1,50 @@
+/* pnl-dock-transient-grab.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_DOCK_TRANSIENT_GRAB_H
+#define PNL_DOCK_TRANSIENT_GRAB_H
+
+#include "pnl-dock-item.h"
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_DOCK_TRANSIENT_GRAB (pnl_dock_transient_grab_get_type())
+
+G_DECLARE_FINAL_TYPE (PnlDockTransientGrab, pnl_dock_transient_grab, PNL, DOCK_TRANSIENT_GRAB, GObject)
+
+PnlDockTransientGrab *pnl_dock_transient_grab_new                    (void);
+void                  pnl_dock_transient_grab_add_item               (PnlDockTransientGrab *self,
+                                                                      PnlDockItem          *item);
+void                  pnl_dock_transient_grab_remove_item            (PnlDockTransientGrab *self,
+                                                                      PnlDockItem          *item);
+void                  pnl_dock_transient_grab_acquire                (PnlDockTransientGrab *self);
+void                  pnl_dock_transient_grab_release                (PnlDockTransientGrab *self);
+guint                 pnl_dock_transient_grab_get_timeout            (PnlDockTransientGrab *self);
+void                  pnl_dock_transient_grab_set_timeout            (PnlDockTransientGrab *self,
+                                                                      guint                 timeout);
+gboolean              pnl_dock_transient_grab_contains               (PnlDockTransientGrab *self,
+                                                                      PnlDockItem          *item);
+gboolean              pnl_dock_transient_grab_is_descendant          (PnlDockTransientGrab *self,
+                                                                      GtkWidget            *widget);
+void                  pnl_dock_transient_grab_steal_common_ancestors (PnlDockTransientGrab *self,
+                                                                      PnlDockTransientGrab *other);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_TRANSIENT_GRAB_H */
+
diff --git a/contrib/pnl/pnl-dock-types.h b/contrib/pnl/pnl-dock-types.h
new file mode 100644
index 0000000..33f9c7b
--- /dev/null
+++ b/contrib/pnl/pnl-dock-types.h
@@ -0,0 +1,56 @@
+/* pnl-dock-types.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_TYPES_H
+#define PNL_TYPES_H
+
+#include <gtk/gtk.h>
+
+#include "pnl-multi-paned.h"
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_DOCK          (pnl_dock_get_type ())
+#define PNL_TYPE_DOCK_BIN      (pnl_dock_bin_get_type())
+#define PNL_TYPE_DOCK_ITEM     (pnl_dock_item_get_type())
+#define PNL_TYPE_DOCK_MANAGER  (pnl_dock_manager_get_type())
+#define PNL_TYPE_DOCK_OVERLAY  (pnl_dock_overlay_get_type())
+#define PNL_TYPE_DOCK_PANED    (pnl_dock_paned_get_type())
+#define PNL_TYPE_DOCK_REVEALER (pnl_dock_revealer_get_type())
+#define PNL_TYPE_DOCK_STACK    (pnl_dock_stack_get_type())
+#define PNL_TYPE_DOCK_WIDGET   (pnl_dock_widget_get_type())
+#define PNL_TYPE_DOCK_WINDOW   (pnl_dock_window_get_type())
+
+G_DECLARE_INTERFACE      (PnlDock,         pnl_dock,          PNL, DOCK,          GtkContainer)
+G_DECLARE_DERIVABLE_TYPE (PnlDockBin,      pnl_dock_bin,      PNL, DOCK_BIN,      GtkContainer)
+G_DECLARE_INTERFACE      (PnlDockItem,     pnl_dock_item,     PNL, DOCK_ITEM,     GtkWidget)
+G_DECLARE_DERIVABLE_TYPE (PnlDockManager,  pnl_dock_manager,  PNL, DOCK_MANAGER,  GObject)
+G_DECLARE_DERIVABLE_TYPE (PnlDockOverlay,  pnl_dock_overlay,  PNL, DOCK_OVERLAY,  GtkEventBox)
+G_DECLARE_DERIVABLE_TYPE (PnlDockPaned,    pnl_dock_paned,    PNL, DOCK_PANED,    PnlMultiPaned)
+G_DECLARE_DERIVABLE_TYPE (PnlDockRevealer, pnl_dock_revealer, PNL, DOCK_REVEALER, GtkBin)
+G_DECLARE_DERIVABLE_TYPE (PnlDockStack,    pnl_dock_stack,    PNL, DOCK_STACK,    GtkBox)
+G_DECLARE_DERIVABLE_TYPE (PnlDockWidget,   pnl_dock_widget,   PNL, DOCK_WIDGET,   GtkBin)
+G_DECLARE_DERIVABLE_TYPE (PnlDockWindow,   pnl_dock_window,   PNL, DOCK_WINDOW,   GtkWindow)
+
+G_END_DECLS
+
+#endif /* PNL_TYPES_H */
diff --git a/contrib/pnl/pnl-dock-widget.c b/contrib/pnl/pnl-dock-widget.c
new file mode 100644
index 0000000..738618b
--- /dev/null
+++ b/contrib/pnl/pnl-dock-widget.c
@@ -0,0 +1,184 @@
+/* pnl-dock-widget.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-item.h"
+#include "pnl-dock-widget.h"
+#include "pnl-util-private.h"
+
+typedef struct
+{
+  gchar *title;
+} PnlDockWidgetPrivate;
+
+G_DEFINE_TYPE_EXTENDED (PnlDockWidget, pnl_dock_widget, GTK_TYPE_BIN, 0,
+                        G_ADD_PRIVATE (PnlDockWidget)
+                        G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM, NULL))
+
+enum {
+  PROP_0,
+  PROP_MANAGER,
+  PROP_TITLE,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+pnl_dock_widget_grab_focus (GtkWidget *widget)
+{
+  PnlDockWidget *self = (PnlDockWidget *)widget;
+  GtkWidget *child;
+
+  g_assert (PNL_IS_DOCK_WIDGET (self));
+
+  pnl_dock_item_present (PNL_DOCK_ITEM (self));
+
+  child = gtk_bin_get_child (GTK_BIN (self));
+
+  if (child != NULL)
+    gtk_widget_child_focus (child, GTK_DIR_TAB_FORWARD);
+}
+
+static void
+pnl_dock_widget_finalize (GObject *object)
+{
+  PnlDockWidget *self = (PnlDockWidget *)object;
+  PnlDockWidgetPrivate *priv = pnl_dock_widget_get_instance_private (self);
+
+  g_clear_pointer (&priv->title, g_free);
+
+  G_OBJECT_CLASS (pnl_dock_widget_parent_class)->finalize (object);
+}
+
+static void
+pnl_dock_widget_get_property (GObject    *object,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  PnlDockWidget *self = PNL_DOCK_WIDGET (object);
+
+  switch (prop_id)
+    {
+    case PROP_MANAGER:
+      g_value_set_object (value, pnl_dock_item_get_manager (PNL_DOCK_ITEM (self)));
+      break;
+
+    case PROP_TITLE:
+      g_value_set_string (value, pnl_dock_widget_get_title (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_widget_set_property (GObject      *object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  PnlDockWidget *self = PNL_DOCK_WIDGET (object);
+
+  switch (prop_id)
+    {
+    case PROP_MANAGER:
+      pnl_dock_item_set_manager (PNL_DOCK_ITEM (self), g_value_get_object (value));
+      break;
+
+    case PROP_TITLE:
+      pnl_dock_widget_set_title (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_widget_class_init (PnlDockWidgetClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = pnl_dock_widget_finalize;
+  object_class->get_property = pnl_dock_widget_get_property;
+  object_class->set_property = pnl_dock_widget_set_property;
+
+  widget_class->draw = pnl_gtk_bin_draw;
+  widget_class->grab_focus = pnl_dock_widget_grab_focus;
+  widget_class->size_allocate = pnl_gtk_bin_size_allocate;
+
+  properties [PROP_MANAGER] =
+    g_param_spec_object ("manager",
+                         "Manager",
+                         "The panel manager",
+                         PNL_TYPE_DOCK_MANAGER,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TITLE] =
+    g_param_spec_string ("title",
+                         "Title",
+                         "Title",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_css_name (widget_class, "dockwidget");
+}
+
+static void
+pnl_dock_widget_init (PnlDockWidget *self)
+{
+  gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+  gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
+}
+
+GtkWidget *
+pnl_dock_widget_new (void)
+{
+  return g_object_new (PNL_TYPE_DOCK_WIDGET, NULL);
+}
+
+const gchar *
+pnl_dock_widget_get_title (PnlDockWidget *self)
+{
+  PnlDockWidgetPrivate *priv = pnl_dock_widget_get_instance_private (self);
+
+  g_return_val_if_fail (PNL_IS_DOCK_WIDGET (self), NULL);
+
+  return priv->title;
+}
+
+void
+pnl_dock_widget_set_title (PnlDockWidget *self,
+                           const gchar   *title)
+{
+  PnlDockWidgetPrivate *priv = pnl_dock_widget_get_instance_private (self);
+
+  g_return_if_fail (PNL_IS_DOCK_WIDGET (self));
+
+  if (g_strcmp0 (title, priv->title) != 0)
+    {
+      g_free (priv->title);
+      priv->title = g_strdup (title);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]);
+    }
+}
diff --git a/contrib/pnl/pnl-dock-widget.h b/contrib/pnl/pnl-dock-widget.h
new file mode 100644
index 0000000..7141ad4
--- /dev/null
+++ b/contrib/pnl/pnl-dock-widget.h
@@ -0,0 +1,51 @@
+/* pnl-dock-widget.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_WIDGET_H
+#define PNL_DOCK_WIDGET_H
+
+#include "pnl-dock-types.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockWidgetClass
+{
+  GtkBinClass parent;
+
+  void (*padding1) (void);
+  void (*padding2) (void);
+  void (*padding3) (void);
+  void (*padding4) (void);
+  void (*padding5) (void);
+  void (*padding6) (void);
+  void (*padding7) (void);
+  void (*padding8) (void);
+};
+
+GtkWidget   *pnl_dock_widget_new       (void);
+const gchar *pnl_dock_widget_get_title (PnlDockWidget *self);
+void         pnl_dock_widget_set_title (PnlDockWidget *self,
+                                        const gchar   *title);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_WIDGET_H */
diff --git a/contrib/pnl/pnl-dock-window.c b/contrib/pnl/pnl-dock-window.c
new file mode 100644
index 0000000..4aa7d85
--- /dev/null
+++ b/contrib/pnl/pnl-dock-window.c
@@ -0,0 +1,113 @@
+/* pnl-dock-window.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-item.h"
+#include "pnl-dock-window.h"
+
+typedef struct
+{
+  void *foo;
+} PnlDockWindowPrivate;
+
+static void pnl_dock_window_init_dock_iface (PnlDockInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (PnlDockWindow, pnl_dock_window, GTK_TYPE_WINDOW, 0,
+                        G_ADD_PRIVATE (PnlDockWindow)
+                        G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM, NULL)
+                        G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK, pnl_dock_window_init_dock_iface))
+
+enum {
+  PROP_0,
+  PROP_MANAGER,
+  N_PROPS
+};
+
+static void
+pnl_dock_window_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (pnl_dock_window_parent_class)->finalize (object);
+}
+
+static void
+pnl_dock_window_get_property (GObject    *object,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  PnlDockWindow *self = PNL_DOCK_WINDOW (object);
+
+  switch (prop_id)
+    {
+    case PROP_MANAGER:
+      g_value_set_object (value, pnl_dock_item_get_manager (PNL_DOCK_ITEM (self)));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_window_set_property (GObject      *object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  PnlDockWindow *self = PNL_DOCK_WINDOW (object);
+
+  switch (prop_id)
+    {
+    case PROP_MANAGER:
+      pnl_dock_item_set_manager (PNL_DOCK_ITEM (self), g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_dock_window_class_init (PnlDockWindowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = pnl_dock_window_finalize;
+  object_class->get_property = pnl_dock_window_get_property;
+  object_class->set_property = pnl_dock_window_set_property;
+
+  g_object_class_override_property (object_class, PROP_MANAGER, "manager");
+
+  gtk_widget_class_set_css_name (widget_class, "dockwindow");
+}
+
+static void
+pnl_dock_window_init (PnlDockWindow *self)
+{
+}
+
+GtkWidget *
+pnl_dock_window_new (void)
+{
+  return g_object_new (PNL_TYPE_DOCK_WINDOW, NULL);
+}
+
+static void
+pnl_dock_window_init_dock_iface (PnlDockInterface *iface)
+{
+}
diff --git a/contrib/pnl/pnl-dock-window.h b/contrib/pnl/pnl-dock-window.h
new file mode 100644
index 0000000..b45fb2d
--- /dev/null
+++ b/contrib/pnl/pnl-dock-window.h
@@ -0,0 +1,39 @@
+/* pnl-dock-window.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_WINDOW_H
+#define PNL_DOCK_WINDOW_H
+
+#include "pnl-dock.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockWindowClass
+{
+  GtkWindowClass parent;
+};
+
+GtkWidget *pnl_dock_window_new (void);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_WINDOW_H */
diff --git a/contrib/pnl/pnl-dock.c b/contrib/pnl/pnl-dock.c
new file mode 100644
index 0000000..ae47861
--- /dev/null
+++ b/contrib/pnl/pnl-dock.c
@@ -0,0 +1,43 @@
+/* pnl-dock.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock.h"
+#include "pnl-resources.h"
+
+G_DEFINE_INTERFACE (PnlDock, pnl_dock, GTK_TYPE_CONTAINER)
+
+static void
+pnl_dock_default_init (PnlDockInterface *iface)
+{
+  GdkScreen *screen;
+
+  g_resources_register (pnl_get_resource ());
+
+  screen = gdk_screen_get_default ();
+
+  if (screen != NULL)
+    gtk_icon_theme_add_resource_path (gtk_icon_theme_get_default (),
+                                      "/org/gnome/panel-gtk/icons");
+
+  g_object_interface_install_property (iface,
+                                       g_param_spec_object ("manager",
+                                                            "Manager",
+                                                            "Manager",
+                                                            PNL_TYPE_DOCK_MANAGER,
+                                                            (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+}
diff --git a/contrib/pnl/pnl-dock.h b/contrib/pnl/pnl-dock.h
new file mode 100644
index 0000000..f2b63c7
--- /dev/null
+++ b/contrib/pnl/pnl-dock.h
@@ -0,0 +1,37 @@
+/* pnl-dock.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_H
+#define PNL_DOCK_H
+
+#include "pnl-dock-types.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockInterface
+{
+  GTypeInterface parent;
+};
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_H */
diff --git a/contrib/pnl/pnl-frame-source.c b/contrib/pnl/pnl-frame-source.c
new file mode 100644
index 0000000..e70f5e6
--- /dev/null
+++ b/contrib/pnl/pnl-frame-source.c
@@ -0,0 +1,130 @@
+/* pnl-frame-source.c
+ *
+ * Copyright (C) 2010-2016 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-frame-source.h"
+
+typedef struct
+{
+   GSource parent;
+   guint   fps;
+   guint   frame_count;
+   gint64  start_time;
+} PnlFrameSource;
+
+static gboolean
+pnl_frame_source_prepare (GSource *source,
+                          gint    *timeout_)
+{
+   PnlFrameSource *fsource = (PnlFrameSource *)(gpointer)source;
+   gint64 current_time;
+   guint elapsed_time;
+   guint new_frame_num;
+   guint frame_time;
+
+   current_time = g_source_get_time(source) / 1000;
+   elapsed_time = current_time - fsource->start_time;
+   new_frame_num = elapsed_time * fsource->fps / 1000;
+
+   /* If time has gone backwards or the time since the last frame is
+    * greater than the two frames worth then reset the time and do a
+    * frame now */
+   if (new_frame_num < fsource->frame_count ||
+       new_frame_num - fsource->frame_count > 2) {
+      /* Get the frame time rounded up to the nearest ms */
+      frame_time = (1000 + fsource->fps - 1) / fsource->fps;
+
+      /* Reset the start time */
+      fsource->start_time = current_time;
+
+      /* Move the start time as if one whole frame has elapsed */
+      fsource->start_time -= frame_time;
+      fsource->frame_count = 0;
+      *timeout_ = 0;
+      return TRUE;
+   } else if (new_frame_num > fsource->frame_count) {
+      *timeout_ = 0;
+      return TRUE;
+   } else {
+      *timeout_ = (fsource->frame_count + 1) * 1000 / fsource->fps - elapsed_time;
+      return FALSE;
+   }
+}
+
+static gboolean
+pnl_frame_source_check (GSource *source)
+{
+   gint timeout_;
+   return pnl_frame_source_prepare(source, &timeout_);
+}
+
+static gboolean
+pnl_frame_source_dispatch (GSource     *source,
+                           GSourceFunc  source_func,
+                           gpointer     user_data)
+{
+   PnlFrameSource *fsource = (PnlFrameSource *)(gpointer)source;
+   gboolean ret;
+
+   if ((ret = source_func(user_data)))
+      fsource->frame_count++;
+   return ret;
+}
+
+static GSourceFuncs source_funcs = {
+   pnl_frame_source_prepare,
+   pnl_frame_source_check,
+   pnl_frame_source_dispatch,
+};
+
+/**
+ * pnl_frame_source_add:
+ * @frames_per_sec: (in): Target frames per second.
+ * @callback: (in) (scope notified): A #GSourceFunc to execute.
+ * @user_data: (in): User data for @callback.
+ *
+ * Creates a new frame source that will execute when the timeout interval
+ * for the source has elapsed. The timing will try to synchronize based
+ * on the end time of the animation.
+ *
+ * Returns: A source id that can be removed with g_source_remove().
+ */
+guint
+pnl_frame_source_add (guint       frames_per_sec,
+                      GSourceFunc callback,
+                      gpointer    user_data)
+{
+   PnlFrameSource *fsource;
+   GSource *source;
+   guint ret;
+
+   g_return_val_if_fail (frames_per_sec > 0, 0);
+   g_return_val_if_fail (frames_per_sec <= 120, 0);
+
+   source = g_source_new(&source_funcs, sizeof(PnlFrameSource));
+   fsource = (PnlFrameSource *)(gpointer)source;
+   fsource->fps = frames_per_sec;
+   fsource->frame_count = 0;
+   fsource->start_time = g_get_monotonic_time() / 1000;
+   g_source_set_callback(source, callback, user_data, NULL);
+   g_source_set_name(source, "PnlFrameSource");
+
+   ret = g_source_attach(source, NULL);
+   g_source_unref(source);
+
+   return ret;
+}
diff --git a/contrib/pnl/pnl-frame-source.h b/contrib/pnl/pnl-frame-source.h
new file mode 100644
index 0000000..043209d
--- /dev/null
+++ b/contrib/pnl/pnl-frame-source.h
@@ -0,0 +1,36 @@
+/* pnl-frame-source.h
+ *
+ * Copyright (C) 2010-2016 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_FRAME_SOURCE_H
+#define PNL_FRAME_SOURCE_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+guint pnl_frame_source_add (guint       frames_per_sec,
+                            GSourceFunc callback,
+                            gpointer    user_data);
+
+G_END_DECLS
+
+#endif /* PNL_FRAME_SOURCE_H */
diff --git a/contrib/pnl/pnl-multi-paned.c b/contrib/pnl/pnl-multi-paned.c
new file mode 100644
index 0000000..b18ae7f
--- /dev/null
+++ b/contrib/pnl/pnl-multi-paned.c
@@ -0,0 +1,1863 @@
+/* pnl-multi-paned.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "pnl-multi-paned.h"
+
+#define HANDLE_WIDTH  10
+#define HANDLE_HEIGHT 10
+
+#define IS_HORIZONTAL(o) (o == GTK_ORIENTATION_HORIZONTAL)
+
+/**
+ * SECTION:pnl-multi-paned
+ * @title: PnlMultiPaned
+ * @short_description: A widget with multiple adjustable panes
+ *
+ * This widget is similar to #GtkPaned except that it allows adding more than
+ * two children to the widget. For each additional child added to the
+ * #PnlMultiPaned, an additional resize grip is added.
+ */
+
+typedef struct
+{
+  /*
+   * The child widget in question.
+   */
+  GtkWidget *widget;
+
+  /*
+   * The input only window for resize grip.
+   * Has a cursor associated with it.
+   */
+  GdkWindow *handle;
+
+  /*
+   * The position the handle has been dragged to.
+   * This is used to adjust size requests.
+   */
+  gint position;
+
+  /*
+   * Cached size requests to avoid extra sizing calls during
+   * the layout procedure.
+   */
+  GtkRequisition min_req;
+  GtkRequisition nat_req;
+
+  /*
+   * A cached size allocation used during the size_allocate()
+   * cycle. This allows us to do a first pass to allocate
+   * natural sizes, and then followup when dealing with
+   * expanding children.
+   */
+  GtkAllocation alloc;
+
+  /*
+   * If the position field has been set.
+   */
+  guint position_set : 1;
+} PnlMultiPanedChild;
+
+typedef struct
+{
+  /*
+   * A GArray of PnlMultiPanedChild containing everything we need to
+   * do size requests, drag operations, resize handles, and temporary
+   * space needed in such operations.
+   */
+  GArray *children;
+
+  /*
+   * The gesture used for dragging resize handles.
+   *
+   * TODO: GtkPaned now uses two gestures, one for mouse and one for touch.
+   *       We should do the same as it improved things quite a bit.
+   */
+  GtkGesturePan *gesture;
+
+  /*
+   * For GtkOrientable:orientation.
+   */
+  GtkOrientation orientation;
+
+  /*
+   * This is the child that is currently being dragged. Keep in mind that
+   * the drag handle is immediately after the child. So the final visible
+   * child has the handle input-only window hidden.
+   */
+  PnlMultiPanedChild *drag_begin;
+
+  /*
+   * The position (width or height) of the child when the drag began.
+   * We use the pan delta offset to determine what the size should be
+   * by adding (or subtracting) to this value.
+   */
+  gint drag_begin_position;
+
+  /*
+   * If we are dragging a handle in a fashion that would shrink the
+   * previous widgets, we need to track how much to subtract from their
+   * target allocations. This is set during the drag operation and used
+   * in allocation_stage_drag_overflow() to adjust the neighbors.
+   */
+  gint drag_extra_offset;
+} PnlMultiPanedPrivate;
+
+typedef struct
+{
+  PnlMultiPanedChild **children;
+  guint                n_children;
+  GtkOrientation       orientation;
+  GtkAllocation        top_alloc;
+  gint                 avail_width;
+  gint                 avail_height;
+  gint                 handle_size;
+} AllocationState;
+
+typedef void (*AllocationStage) (PnlMultiPaned   *self,
+                                 AllocationState *state);
+
+static void allocation_stage_allocate      (PnlMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_borders       (PnlMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_cache_request (PnlMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_drag_overflow (PnlMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_expand        (PnlMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_handles       (PnlMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_minimums      (PnlMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_naturals      (PnlMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_positions     (PnlMultiPaned   *self,
+                                            AllocationState *state);
+
+G_DEFINE_TYPE_EXTENDED (PnlMultiPaned, pnl_multi_paned, GTK_TYPE_CONTAINER, 0,
+                        G_ADD_PRIVATE (PnlMultiPaned)
+                        G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
+
+enum {
+  PROP_0,
+  PROP_ORIENTATION,
+  N_PROPS
+};
+
+enum {
+  CHILD_PROP_0,
+  CHILD_PROP_POSITION,
+  N_CHILD_PROPS
+};
+
+enum {
+  STYLE_PROP_0,
+  STYLE_PROP_HANDLE_SIZE,
+  N_STYLE_PROPS
+};
+
+enum {
+  RESIZE_DRAG_BEGIN,
+  RESIZE_DRAG_END,
+  N_SIGNALS
+};
+
+/*
+ * TODO: An obvious optimization here would be to move the constant
+ *       branches outside the loops.
+ */
+
+static GParamSpec *properties [N_PROPS];
+static GParamSpec *child_properties [N_CHILD_PROPS];
+static GParamSpec *style_properties [N_STYLE_PROPS];
+static guint signals [N_SIGNALS];
+static AllocationStage allocation_stages[] = {
+  allocation_stage_borders,
+  allocation_stage_cache_request,
+  allocation_stage_minimums,
+  allocation_stage_handles,
+  allocation_stage_positions,
+  allocation_stage_drag_overflow,
+  allocation_stage_naturals,
+  allocation_stage_expand,
+  allocation_stage_allocate,
+};
+
+static void
+pnl_multi_paned_reset_positions (PnlMultiPaned *self)
+{
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+      child->position = -1;
+      child->position_set = FALSE;
+
+      gtk_container_child_notify_by_pspec (GTK_CONTAINER (self),
+                                           child->widget,
+                                           child_properties [CHILD_PROP_POSITION]);
+    }
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static PnlMultiPanedChild *
+pnl_multi_paned_get_next_visible_child (PnlMultiPaned      *self,
+                                        PnlMultiPanedChild *child)
+{
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (child != NULL);
+  g_assert (priv->children != NULL);
+  g_assert (priv->children->len > 0);
+
+  i = child - ((PnlMultiPanedChild *)(gpointer)priv->children->data);
+
+  for (++i; i < priv->children->len; i++)
+    {
+      PnlMultiPanedChild *next = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+      if (gtk_widget_get_visible (next->widget))
+        return next;
+    }
+
+  return NULL;
+}
+
+static gboolean
+pnl_multi_paned_is_last_visible_child (PnlMultiPaned      *self,
+                                       PnlMultiPanedChild *child)
+{
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (child != NULL);
+
+  return !pnl_multi_paned_get_next_visible_child (self, child);
+}
+
+static void
+pnl_multi_paned_get_handle_rect (PnlMultiPaned      *self,
+                                 PnlMultiPanedChild *child,
+                                 GdkRectangle       *handle_rect)
+{
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  GtkAllocation alloc;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (child != NULL);
+  g_assert (handle_rect != NULL);
+
+  handle_rect->x = -1;
+  handle_rect->y = -1;
+  handle_rect->width = 0;
+  handle_rect->height = 0;
+
+  if (!gtk_widget_get_visible (child->widget) ||
+      !gtk_widget_get_realized (child->widget))
+    return;
+
+  if (pnl_multi_paned_is_last_visible_child (self, child))
+    return;
+
+  gtk_widget_get_allocation (child->widget, &alloc);
+
+  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+      handle_rect->x = alloc.x + alloc.width - (HANDLE_WIDTH / 2);
+      handle_rect->width = HANDLE_WIDTH;
+      handle_rect->y = alloc.y;
+      handle_rect->height = alloc.height;
+    }
+  else
+    {
+      handle_rect->x = alloc.x;
+      handle_rect->width = alloc.width;
+      handle_rect->y = alloc.y + alloc.height - (HANDLE_HEIGHT / 2);
+      handle_rect->height = HANDLE_HEIGHT;
+    }
+}
+
+static void
+pnl_multi_paned_create_child_handle (PnlMultiPaned      *self,
+                                     PnlMultiPanedChild *child)
+{
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  GdkWindowAttr attributes = { 0 };
+  GdkDisplay *display;
+  GdkWindow *parent;
+  GdkCursorType cursor_type;
+  GdkRectangle handle_rect;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (child != NULL);
+  g_assert (child->handle == NULL);
+
+  display = gtk_widget_get_display (GTK_WIDGET (self));
+  parent = gtk_widget_get_window (GTK_WIDGET (self));
+
+  cursor_type = (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+              ? GDK_SB_H_DOUBLE_ARROW
+              : GDK_SB_V_DOUBLE_ARROW;
+
+  pnl_multi_paned_get_handle_rect (self, child, &handle_rect);
+
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.wclass = GDK_INPUT_ONLY;
+  attributes.x = handle_rect.x;
+  attributes.x = -handle_rect.y;
+  attributes.width = handle_rect.width;
+  attributes.height = handle_rect.height;
+  attributes.visual = gtk_widget_get_visual (GTK_WIDGET (self));
+  attributes.event_mask = (GDK_BUTTON_PRESS_MASK |
+                           GDK_BUTTON_RELEASE_MASK |
+                           GDK_ENTER_NOTIFY_MASK |
+                           GDK_LEAVE_NOTIFY_MASK |
+                           GDK_POINTER_MOTION_MASK);
+  attributes.cursor = gdk_cursor_new_for_display (display, cursor_type);
+
+  child->handle = gdk_window_new (parent, &attributes, GDK_WA_CURSOR);
+  gtk_widget_register_window (GTK_WIDGET (self), child->handle);
+
+  g_clear_object (&attributes.cursor);
+}
+
+static gint
+pnl_multi_paned_calc_handle_size (PnlMultiPaned *self)
+{
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  gint visible_children = 0;
+  gint handle_size = 1;
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+
+  gtk_widget_style_get (GTK_WIDGET (self), "handle-size", &handle_size, NULL);
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+      if (gtk_widget_get_visible (child->widget))
+        visible_children++;
+    }
+
+  return MAX (0, (visible_children - 1) * handle_size);
+}
+
+static void
+pnl_multi_paned_destroy_child_handle (PnlMultiPaned      *self,
+                                      PnlMultiPanedChild *child)
+{
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (child != NULL);
+
+  if (child->handle != NULL)
+    {
+      gdk_window_destroy (child->handle);
+      child->handle = NULL;
+    }
+}
+
+static PnlMultiPanedChild *
+pnl_multi_paned_get_child (PnlMultiPaned *self,
+                           GtkWidget     *widget)
+{
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+      if (child->widget == widget)
+        return child;
+    }
+
+  g_assert_not_reached ();
+
+  return NULL;
+}
+
+static gint
+pnl_multi_paned_get_child_position (PnlMultiPaned *self,
+                                    GtkWidget     *widget)
+{
+  PnlMultiPanedChild *child;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  child = pnl_multi_paned_get_child (self, widget);
+
+  return child->position;
+}
+
+static void
+pnl_multi_paned_set_child_position (PnlMultiPaned *self,
+                                    GtkWidget     *widget,
+                                    gint           position)
+{
+  PnlMultiPanedChild *child;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_WIDGET (widget));
+  g_assert (position >= -1);
+
+  child = pnl_multi_paned_get_child (self, widget);
+
+  if (child->position != position)
+    {
+      child->position = position;
+      child->position_set = (position != -1);
+      gtk_container_child_notify_by_pspec (GTK_CONTAINER (self), widget,
+                                           child_properties [CHILD_PROP_POSITION]);
+      gtk_widget_queue_resize (GTK_WIDGET (self));
+    }
+}
+
+static void
+pnl_multi_paned_add (GtkContainer *container,
+                     GtkWidget    *widget)
+{
+  PnlMultiPaned *self = (PnlMultiPaned *)container;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  PnlMultiPanedChild child = { 0 };
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  child.widget = g_object_ref_sink (widget);
+  child.position = -1;
+
+  if (gtk_widget_get_realized (GTK_WIDGET (self)))
+    pnl_multi_paned_create_child_handle (self, &child);
+
+  gtk_widget_set_parent (widget, GTK_WIDGET (self));
+
+  g_array_append_val (priv->children, child);
+
+  pnl_multi_paned_reset_positions (self);
+
+  gtk_gesture_set_state (GTK_GESTURE (priv->gesture), GTK_EVENT_SEQUENCE_DENIED);
+}
+
+static void
+pnl_multi_paned_remove (GtkContainer *container,
+                        GtkWidget    *widget)
+{
+  PnlMultiPaned *self = (PnlMultiPaned *)container;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+      if (child->widget == widget)
+        {
+          pnl_multi_paned_destroy_child_handle (self, child);
+
+          g_array_remove_index (priv->children, i);
+          child = NULL;
+
+          gtk_widget_unparent (widget);
+          g_object_unref (widget);
+
+          break;
+        }
+    }
+
+  pnl_multi_paned_reset_positions (self);
+
+  gtk_gesture_set_state (GTK_GESTURE (priv->gesture), GTK_EVENT_SEQUENCE_DENIED);
+}
+
+static void
+pnl_multi_paned_forall (GtkContainer *container,
+                        gboolean      include_internals,
+                        GtkCallback   callback,
+                        gpointer      user_data)
+{
+  PnlMultiPaned *self = (PnlMultiPaned *)container;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  gint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (callback != NULL);
+
+  for (i = priv->children->len; i > 0; i--)
+    {
+      PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i - 1);
+
+      callback (child->widget, user_data);
+    }
+}
+
+static GtkSizeRequestMode
+pnl_multi_paned_get_request_mode (GtkWidget *widget)
+{
+  PnlMultiPaned *self = (PnlMultiPaned *)widget;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+
+  return (priv->orientation == GTK_ORIENTATION_HORIZONTAL) ? GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT
+                                                           : GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+}
+
+static void
+pnl_multi_paned_get_preferred_height (GtkWidget *widget,
+                                      gint      *min_height,
+                                      gint      *nat_height)
+{
+  PnlMultiPaned *self = (PnlMultiPaned *)widget;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  guint i;
+  gint real_min_height = 0;
+  gint real_nat_height = 0;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (min_height != NULL);
+  g_assert (nat_height != NULL);
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+      gint child_min_height = 0;
+      gint child_nat_height = 0;
+
+      if (gtk_widget_get_visible (child->widget))
+        {
+          gtk_widget_get_preferred_height (child->widget, &child_min_height, &child_nat_height);
+
+          if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+            {
+              real_min_height += child_min_height;
+              real_nat_height += child_nat_height;
+            }
+          else
+            {
+              real_min_height = MAX (real_min_height, child_min_height);
+              real_nat_height = MAX (real_nat_height, child_nat_height);
+            }
+        }
+    }
+
+  if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+    {
+      gint handle_size = pnl_multi_paned_calc_handle_size (self);
+
+      real_min_height += handle_size;
+      real_nat_height += handle_size;
+    }
+
+  *min_height = real_min_height;
+  *nat_height = real_nat_height;
+}
+
+static void
+pnl_multi_paned_get_child_preferred_height_for_width (PnlMultiPaned      *self,
+                                                      PnlMultiPanedChild *children,
+                                                      gint                n_children,
+                                                      gint                width,
+                                                      gint               *min_height,
+                                                      gint               *nat_height)
+{
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  PnlMultiPanedChild *child = children;
+  gint child_min_height = 0;
+  gint child_nat_height = 0;
+  gint neighbor_min_height = 0;
+  gint neighbor_nat_height = 0;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (n_children == 0 || children != NULL);
+  g_assert (min_height != NULL);
+  g_assert (nat_height != NULL);
+
+  *min_height = 0;
+  *nat_height = 0;
+
+  if (n_children == 0)
+    return;
+
+  if (gtk_widget_get_visible (child->widget))
+    gtk_widget_get_preferred_height_for_width (child->widget,
+                                               width,
+                                               &child_min_height,
+                                               &child_nat_height);
+
+  pnl_multi_paned_get_child_preferred_height_for_width (self,
+                                                        children + 1,
+                                                        n_children - 1,
+                                                        width,
+                                                        &neighbor_min_height,
+                                                        &neighbor_nat_height);
+
+  if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+    {
+      *min_height = child_min_height + neighbor_min_height;
+      *nat_height = child_nat_height + neighbor_nat_height;
+    }
+  else
+    {
+      *min_height = MAX (child_min_height, neighbor_min_height);
+      *nat_height = MAX (child_nat_height, neighbor_nat_height);
+    }
+}
+
+static void
+pnl_multi_paned_get_preferred_height_for_width (GtkWidget *widget,
+                                                gint       width,
+                                                gint      *min_height,
+                                                gint      *nat_height)
+{
+  PnlMultiPaned *self = (PnlMultiPaned *)widget;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (min_height != NULL);
+  g_assert (nat_height != NULL);
+
+  *min_height = 0;
+  *nat_height = 0;
+
+  pnl_multi_paned_get_child_preferred_height_for_width (self,
+                                                        (PnlMultiPanedChild *)(gpointer)priv->children->data,
+                                                        priv->children->len,
+                                                        width,
+                                                        min_height,
+                                                        nat_height);
+
+  if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+    {
+      gint handle_size = pnl_multi_paned_calc_handle_size (self);
+
+      *min_height += handle_size;
+      *nat_height += handle_size;
+    }
+}
+
+static void
+pnl_multi_paned_get_preferred_width (GtkWidget *widget,
+                                     gint      *min_width,
+                                     gint      *nat_width)
+{
+  PnlMultiPaned *self = (PnlMultiPaned *)widget;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  guint i;
+  gint real_min_width = 0;
+  gint real_nat_width = 0;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (min_width != NULL);
+  g_assert (nat_width != NULL);
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+      gint child_min_width = 0;
+      gint child_nat_width = 0;
+
+      if (gtk_widget_get_visible (child->widget))
+        {
+          gtk_widget_get_preferred_width (child->widget, &child_min_width, &child_nat_width);
+
+          if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+            {
+              real_min_width = MAX (real_min_width, child_min_width);
+              real_nat_width = MAX (real_nat_width, child_nat_width);
+            }
+          else
+            {
+              real_min_width += child_min_width;
+              real_nat_width += child_nat_width;
+            }
+        }
+    }
+
+  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+      gint handle_size = pnl_multi_paned_calc_handle_size (self);
+
+      real_min_width += handle_size;
+      real_nat_width += handle_size;
+    }
+
+  *min_width = real_min_width;
+  *nat_width = real_nat_width;
+}
+
+static void
+pnl_multi_paned_get_child_preferred_width_for_height (PnlMultiPaned      *self,
+                                                      PnlMultiPanedChild *children,
+                                                      gint                n_children,
+                                                      gint                height,
+                                                      gint               *min_width,
+                                                      gint               *nat_width)
+{
+  PnlMultiPanedChild *child = children;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  gint child_min_width = 0;
+  gint child_nat_width = 0;
+  gint neighbor_min_width = 0;
+  gint neighbor_nat_width = 0;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (n_children == 0 || children != NULL);
+  g_assert (min_width != NULL);
+  g_assert (nat_width != NULL);
+
+  *min_width = 0;
+  *nat_width = 0;
+
+  if (n_children == 0)
+    return;
+
+  if (gtk_widget_get_visible (child->widget))
+    gtk_widget_get_preferred_width_for_height (child->widget,
+                                               height,
+                                               &child_min_width,
+                                               &child_nat_width);
+
+  pnl_multi_paned_get_child_preferred_width_for_height (self,
+                                                        children + 1,
+                                                        n_children - 1,
+                                                        height,
+                                                        &neighbor_min_width,
+                                                        &neighbor_nat_width);
+
+  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+      *min_width = child_min_width + neighbor_min_width;
+      *nat_width = child_nat_width + neighbor_nat_width;
+    }
+  else
+    {
+      *min_width = MAX (child_min_width, neighbor_min_width);
+      *nat_width = MAX (child_nat_width, neighbor_nat_width);
+    }
+}
+
+static void
+pnl_multi_paned_get_preferred_width_for_height (GtkWidget *widget,
+                                                gint       height,
+                                                gint      *min_width,
+                                                gint      *nat_width)
+{
+  PnlMultiPaned *self = (PnlMultiPaned *)widget;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (min_width != NULL);
+  g_assert (nat_width != NULL);
+
+  pnl_multi_paned_get_child_preferred_width_for_height (self,
+                                                        (PnlMultiPanedChild *)(gpointer)priv->children->data,
+                                                        priv->children->len,
+                                                        height,
+                                                        min_width,
+                                                        nat_width);
+
+  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+      gint handle_size = pnl_multi_paned_calc_handle_size (self);
+
+      *min_width += handle_size;
+      *nat_width += handle_size;
+    }
+}
+
+static void
+allocation_stage_handles (PnlMultiPaned   *self,
+                          AllocationState *state)
+{
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  /*
+   * Push each child allocation forward by the sum handle widths up to
+   * their position in the paned.
+   */
+
+  for (i = 1; i < state->n_children; i++)
+    {
+      PnlMultiPanedChild *child = state->children [i];
+
+      if (IS_HORIZONTAL (state->orientation))
+        child->alloc.x += (i * state->handle_size);
+      else
+        child->alloc.y += (i * state->handle_size);
+    }
+
+  if (IS_HORIZONTAL (state->orientation))
+    state->avail_width -= (state->n_children - 1) * state->handle_size;
+  else
+    state->avail_height -= (state->n_children - 1) * state->handle_size;
+}
+
+static void
+allocation_stage_minimums (PnlMultiPaned   *self,
+                           AllocationState *state)
+{
+  gint next_x;
+  gint next_y;
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  next_x = state->top_alloc.x;
+  next_y = state->top_alloc.y;
+
+  for (i = 0; i < state->n_children; i++)
+    {
+      PnlMultiPanedChild *child = state->children [i];
+
+      if (IS_HORIZONTAL (state->orientation))
+        {
+          child->alloc.x = next_x;
+          child->alloc.y = state->top_alloc.y;
+          child->alloc.width = child->min_req.width;
+          child->alloc.height = state->top_alloc.height;
+
+          next_x = child->alloc.x + child->alloc.width;
+
+          state->avail_width -= child->alloc.width;
+        }
+      else
+        {
+          child->alloc.x = state->top_alloc.x;
+          child->alloc.y = next_y;
+          child->alloc.width = state->top_alloc.width;
+          child->alloc.height = child->min_req.height;
+
+          next_y = child->alloc.y + child->alloc.height;
+
+          state->avail_height -= child->alloc.height;
+        }
+    }
+}
+
+static void
+allocation_stage_naturals (PnlMultiPaned   *self,
+                           AllocationState *state)
+{
+  gint x_adjust = 0;
+  gint y_adjust = 0;
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  for (i = 0; i < state->n_children; i++)
+    {
+      PnlMultiPanedChild *child = state->children [i];
+
+      child->alloc.x += x_adjust;
+      child->alloc.y += y_adjust;
+
+      if (!child->position_set)
+        {
+          if (IS_HORIZONTAL (state->orientation))
+            {
+              if (child->nat_req.width > child->alloc.width)
+                {
+                  gint adjust = MIN (state->avail_width, child->nat_req.width - child->alloc.width);
+
+                  child->alloc.width += adjust;
+                  state->avail_width -= adjust;
+                  x_adjust += adjust;
+                }
+            }
+          else
+            {
+              if (child->nat_req.height > child->alloc.height)
+                {
+                  gint adjust = MIN (state->avail_height, child->nat_req.height - child->alloc.height);
+
+                  child->alloc.height += adjust;
+                  state->avail_height -= adjust;
+                  y_adjust += adjust;
+                }
+            }
+        }
+    }
+}
+
+static void
+allocation_stage_borders (PnlMultiPaned   *self,
+                          AllocationState *state)
+{
+  gint border_width;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  border_width = gtk_container_get_border_width (GTK_CONTAINER (self));
+
+  state->top_alloc.x += border_width;
+  state->top_alloc.y += border_width;
+  state->top_alloc.width -= border_width * 2;
+  state->top_alloc.height -= border_width * 2;
+
+  if (state->top_alloc.width < 0)
+    state->top_alloc.width = 0;
+
+  if (state->top_alloc.height < 0)
+    state->top_alloc.height = 0;
+
+  state->avail_width = state->top_alloc.width;
+  state->avail_height = state->top_alloc.height;
+}
+
+static void
+allocation_stage_cache_request (PnlMultiPaned   *self,
+                                AllocationState *state)
+{
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  for (i = 0; i < state->n_children; i++)
+    {
+      PnlMultiPanedChild *child = state->children [i];
+
+      if (IS_HORIZONTAL (state->orientation))
+        gtk_widget_get_preferred_width_for_height (child->widget,
+                                                   state->avail_height,
+                                                   &child->min_req.width,
+                                                   &child->nat_req.width);
+      else
+        gtk_widget_get_preferred_height_for_width (child->widget,
+                                                   state->avail_width,
+                                                   &child->min_req.height,
+                                                   &child->nat_req.height);
+    }
+}
+
+static void
+allocation_stage_positions (PnlMultiPaned   *self,
+                            AllocationState *state)
+{
+  gint x_adjust = 0;
+  gint y_adjust = 0;
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  /*
+   * Child may have a position set, which happens when dragging the input
+   * window (handle) to resize the child. If so, we want to try to allocate
+   * extra space above the minimum size.
+   */
+
+  for (i = 0; i < state->n_children; i++)
+    {
+      PnlMultiPanedChild *child = state->children [i];
+
+      child->alloc.x += x_adjust;
+      child->alloc.y += y_adjust;
+
+      if (child->position_set)
+        {
+          if (IS_HORIZONTAL (state->orientation))
+            {
+              if (child->position > child->alloc.width)
+                {
+                  gint adjust = MIN (state->avail_width, child->position - child->alloc.width);
+
+                  child->alloc.width += adjust;
+                  state->avail_width -= adjust;
+                  x_adjust += adjust;
+                }
+            }
+          else
+            {
+              if (child->position > child->alloc.height)
+                {
+                  gint adjust = MIN (state->avail_height, child->position - child->alloc.height);
+
+                  child->alloc.height += adjust;
+                  state->avail_height -= adjust;
+                  y_adjust += adjust;
+                }
+            }
+        }
+    }
+}
+
+static void
+allocation_stage_drag_overflow (PnlMultiPaned   *self,
+                                AllocationState *state)
+{
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  guint drag_index;
+  gint j;
+  gint drag_overflow;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  if (priv->drag_begin == NULL)
+    return;
+
+  drag_overflow = ABS (priv->drag_extra_offset);
+
+  for (drag_index = 0; drag_index < state->n_children; drag_index++)
+    if (state->children [drag_index] == priv->drag_begin)
+      break;
+
+  if (drag_index == 0 ||
+      drag_index >= state->n_children ||
+      state->children [drag_index] != priv->drag_begin)
+    return;
+
+  /*
+   * If the user is dragging and we have run out of room in the drag
+   * child, then we need to start stealing space from the previous
+   * items.
+   *
+   * This works our way back to the beginning from the drag child
+   * stealing available space and giving it to the child *AFTER* the
+   * drag item. This is because the drag handle is after the drag
+   * child, so logically to the user, its drag_index+1.
+   */
+
+  for (j = (int)drag_index; j >= 0 && drag_overflow > 0; j--)
+    {
+      PnlMultiPanedChild *child = state->children [j];
+      guint k;
+      gint adjust = 0;
+
+      if (IS_HORIZONTAL (state->orientation))
+        {
+          if (child->alloc.width > child->min_req.width)
+            {
+              if (drag_overflow > (child->alloc.width - child->min_req.width))
+                adjust = child->alloc.width - child->min_req.width;
+              else
+                adjust = drag_overflow;
+              drag_overflow -= adjust;
+              child->alloc.width -= adjust;
+              state->children [drag_index + 1]->alloc.width += adjust;
+            }
+        }
+      else
+        {
+          if (child->alloc.height > child->min_req.height)
+            {
+              if (drag_overflow > (child->alloc.height - child->min_req.height))
+                adjust = child->alloc.height - child->min_req.height;
+              else
+                adjust = drag_overflow;
+              drag_overflow -= adjust;
+              child->alloc.height -= adjust;
+              state->children [drag_index + 1]->alloc.height += adjust;
+            }
+        }
+
+      /*
+       * Now walk back forward and adjust x/y offsets for all of the
+       * children that will have just shifted.
+       */
+
+      for (k = j + 1; k <= drag_index + 1; k++)
+        {
+          PnlMultiPanedChild *neighbor = state->children [k];
+
+          if (IS_HORIZONTAL (state->orientation))
+            neighbor->alloc.x -= adjust;
+          else
+            neighbor->alloc.y -= adjust;
+        }
+    }
+}
+
+static void
+allocation_stage_expand (PnlMultiPaned   *self,
+                         AllocationState *state)
+{
+  gint x_adjust = 0;
+  gint y_adjust = 0;
+  gint n_expand = 0;
+  gint adjust;
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  if (state->n_children == 1)
+    {
+      PnlMultiPanedChild *child = state->children [0];
+
+      /*
+       * Special case for single child, just expand to the
+       * available space. Ideally we would have much shorter
+       * allocation stages in this case.
+       */
+
+      if (IS_HORIZONTAL (state->orientation))
+        {
+          if (gtk_widget_get_hexpand (child->widget))
+            child->alloc.width = state->top_alloc.width;
+        }
+      else
+        {
+          if (gtk_widget_get_vexpand (child->widget))
+            child->alloc.height = state->top_alloc.height;
+        }
+
+      return;
+    }
+
+  for (i = 0; i < state->n_children; i++)
+    {
+      PnlMultiPanedChild *child = state->children [i];
+
+      if (!child->position_set)
+        {
+          if (IS_HORIZONTAL (state->orientation))
+            {
+              if (gtk_widget_get_hexpand (child->widget))
+                n_expand++;
+            }
+          else
+            {
+              if (gtk_widget_get_vexpand (child->widget))
+                n_expand++;
+            }
+        }
+    }
+
+  if (n_expand == 0)
+    return;
+
+  if (IS_HORIZONTAL (state->orientation))
+    adjust = state->avail_width / n_expand;
+  else
+    adjust = state->avail_height / n_expand;
+
+  for (i = 0; i < state->n_children; i++)
+    {
+      PnlMultiPanedChild *child = state->children [i];
+
+      child->alloc.x += x_adjust;
+      child->alloc.y += y_adjust;
+
+      if (!child->position_set)
+        {
+          if (IS_HORIZONTAL (state->orientation))
+            {
+              if (gtk_widget_get_hexpand (child->widget))
+                {
+                  child->alloc.width += adjust;
+                  state->avail_height -= adjust;
+                  x_adjust += adjust;
+                }
+            }
+          else
+            {
+              if (gtk_widget_get_vexpand (child->widget))
+                {
+                  child->alloc.height += adjust;
+                  state->avail_height -= adjust;
+                  y_adjust += adjust;
+                }
+            }
+        }
+    }
+
+  if (IS_HORIZONTAL (state->orientation))
+    {
+      if (state->avail_width > 0)
+        {
+          state->children [state->n_children - 1]->alloc.width += state->avail_width;
+          state->avail_width = 0;
+        }
+    }
+  else
+    {
+      if (state->avail_height > 0)
+        {
+          state->children [state->n_children - 1]->alloc.height += state->avail_height;
+          state->avail_height = 0;
+        }
+    }
+}
+
+static void
+allocation_stage_allocate (PnlMultiPaned   *self,
+                           AllocationState *state)
+{
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  for (i = 0; i < state->n_children; i++)
+    {
+      PnlMultiPanedChild *child = state->children [i];
+
+      gtk_widget_size_allocate (child->widget, &child->alloc);
+
+      if ((child->handle != NULL) && (state->n_children != (i + 1)))
+        {
+          if (state->orientation == GTK_ORIENTATION_HORIZONTAL)
+            {
+              gdk_window_move_resize (child->handle,
+                                      child->alloc.x + child->alloc.width - (HANDLE_WIDTH / 2),
+                                      child->alloc.y,
+                                      HANDLE_WIDTH,
+                                      child->alloc.height);
+            }
+          else
+            {
+              gdk_window_move_resize (child->handle,
+                                      child->alloc.x,
+                                      child->alloc.y + child->alloc.height - (HANDLE_HEIGHT / 2),
+                                      child->alloc.width,
+                                      HANDLE_HEIGHT);
+            }
+
+          gdk_window_show (child->handle);
+        }
+    }
+}
+
+static void
+pnl_multi_paned_size_allocate (GtkWidget     *widget,
+                               GtkAllocation *allocation)
+{
+  PnlMultiPaned *self = (PnlMultiPaned *)widget;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  AllocationState state = { 0 };
+  GPtrArray *children;
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (allocation != NULL);
+
+  GTK_WIDGET_CLASS (pnl_multi_paned_parent_class)->size_allocate (widget, allocation);
+
+  if (priv->children->len == 0)
+    return;
+
+  children = g_ptr_array_new ();
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+      child->alloc.x = 0;
+      child->alloc.y = 0;
+      child->alloc.width = 0;
+      child->alloc.height = 0;
+
+      if (child->widget != NULL &&
+          gtk_widget_get_child_visible (child->widget) &&
+          gtk_widget_get_visible (child->widget))
+        g_ptr_array_add (children, child);
+      else if (child->handle)
+        gdk_window_hide (child->handle);
+    }
+
+  state.children = (PnlMultiPanedChild **)children->pdata;
+  state.n_children = children->len;
+
+  if (state.n_children == 0)
+    {
+      g_ptr_array_free (children, TRUE);
+      return;
+    }
+
+  gtk_widget_style_get (GTK_WIDGET (self),
+                        "handle-size", &state.handle_size,
+                        NULL);
+
+  state.orientation = priv->orientation;
+  state.top_alloc = *allocation;
+  state.avail_width = allocation->width;
+  state.avail_height = allocation->height;
+
+  for (i = 0; i < G_N_ELEMENTS (allocation_stages); i++)
+    allocation_stages [i] (self, &state);
+
+  g_ptr_array_free (children, TRUE);
+}
+
+static void
+pnl_multi_paned_realize (GtkWidget *widget)
+{
+  PnlMultiPaned *self = (PnlMultiPaned *)widget;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+
+  GTK_WIDGET_CLASS (pnl_multi_paned_parent_class)->realize (widget);
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+      pnl_multi_paned_create_child_handle (self, child);
+    }
+}
+
+static void
+pnl_multi_paned_unrealize (GtkWidget *widget)
+{
+  PnlMultiPaned *self = (PnlMultiPaned *)widget;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+      pnl_multi_paned_destroy_child_handle (self, child);
+    }
+
+  GTK_WIDGET_CLASS (pnl_multi_paned_parent_class)->unrealize (widget);
+}
+
+static void
+pnl_multi_paned_map (GtkWidget *widget)
+{
+  PnlMultiPaned *self = (PnlMultiPaned *)widget;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+
+  GTK_WIDGET_CLASS (pnl_multi_paned_parent_class)->map (widget);
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+      gdk_window_show (child->handle);
+    }
+}
+
+static void
+pnl_multi_paned_unmap (GtkWidget *widget)
+{
+  PnlMultiPaned *self = (PnlMultiPaned *)widget;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+      gdk_window_hide (child->handle);
+    }
+
+  GTK_WIDGET_CLASS (pnl_multi_paned_parent_class)->unmap (widget);
+}
+
+static gboolean
+pnl_multi_paned_draw (GtkWidget *widget,
+                      cairo_t   *cr)
+{
+  PnlMultiPaned *self = (PnlMultiPaned *)widget;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  gboolean ret;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (cr != NULL);
+
+  ret = GTK_WIDGET_CLASS (pnl_multi_paned_parent_class)->draw (widget, cr);
+
+  if (ret != GDK_EVENT_STOP)
+    {
+      GtkStyleContext *style_context;
+      gint handle_size = 1;
+      guint i;
+
+      style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+      gtk_widget_style_get (widget, "handle-size", &handle_size, NULL);
+
+      for (i = 0; i < priv->children->len; i++)
+        {
+          PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+          GtkAllocation alloc;
+
+          if (!gtk_widget_get_realized (child->widget) ||
+              !gtk_widget_get_visible (child->widget))
+            continue;
+
+          gtk_widget_get_allocation (child->widget, &alloc);
+
+          if (!pnl_multi_paned_is_last_visible_child (self, child))
+            {
+              if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+                gtk_render_handle (style_context,
+                                   cr,
+                                   alloc.x + alloc.width,
+                                   0,
+                                   handle_size,
+                                   alloc.height);
+              else
+                gtk_render_handle (style_context,
+                                   cr,
+                                   0,
+                                   alloc.y + alloc.height,
+                                   alloc.width,
+                                   handle_size);
+            }
+        }
+    }
+
+  return ret;
+}
+
+static void
+pnl_multi_paned_pan_gesture_drag_begin (PnlMultiPaned *self,
+                                        gdouble        x,
+                                        gdouble        y,
+                                        GtkGesturePan *gesture)
+{
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  GdkEventSequence *sequence;
+  const GdkEvent *event;
+  guint i;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_GESTURE_PAN (gesture));
+  g_assert (gesture == priv->gesture);
+
+  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+  event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+
+  priv->drag_begin = NULL;
+  priv->drag_begin_position = 0;
+  priv->drag_extra_offset = 0;
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+      if (child->handle == event->any.window)
+        {
+          priv->drag_begin = child;
+          break;
+        }
+
+      /*
+       * We want to make any child before the drag child "sticky" so that it
+       * will no longer have expand adjustments while we perform the drag
+       * operation.
+       */
+      if (gtk_widget_get_child_visible (child->widget) &&
+          gtk_widget_get_visible (child->widget))
+        {
+          child->position_set = TRUE;
+          child->position = IS_HORIZONTAL (priv->orientation)
+            ? child->alloc.width
+            : child->alloc.height;
+        }
+    }
+
+  if (priv->drag_begin == NULL)
+    {
+      gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+      return;
+    }
+
+  if (IS_HORIZONTAL (priv->orientation))
+    priv->drag_begin_position = priv->drag_begin->alloc.width;
+  else
+    priv->drag_begin_position = priv->drag_begin->alloc.height;
+
+  gtk_gesture_pan_set_orientation (gesture, priv->orientation);
+  gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+
+  g_signal_emit (self, signals [RESIZE_DRAG_BEGIN], 0, priv->drag_begin->widget);
+}
+
+static void
+pnl_multi_paned_pan_gesture_drag_end (PnlMultiPaned *self,
+                                      gdouble        x,
+                                      gdouble        y,
+                                      GtkGesturePan *gesture)
+{
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  GdkEventSequence *sequence;
+  GtkEventSequenceState state;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_GESTURE_PAN (gesture));
+  g_assert (gesture == priv->gesture);
+
+  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+  state = gtk_gesture_get_sequence_state (GTK_GESTURE (gesture), sequence);
+
+  if (state != GTK_EVENT_SEQUENCE_CLAIMED)
+    goto cleanup;
+
+  g_assert (priv->drag_begin != NULL);
+
+  g_signal_emit (self, signals [RESIZE_DRAG_END], 0, priv->drag_begin->widget);
+
+cleanup:
+  priv->drag_begin = NULL;
+  priv->drag_begin_position = 0;
+  priv->drag_extra_offset = 0;
+}
+
+static void
+pnl_multi_paned_pan_gesture_pan (PnlMultiPaned   *self,
+                                 GtkPanDirection  direction,
+                                 gdouble          offset,
+                                 GtkGesturePan   *gesture)
+{
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  GtkAllocation alloc;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_GESTURE_PAN (gesture));
+  g_assert (gesture == priv->gesture);
+  g_assert (priv->drag_begin != NULL);
+
+  gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+
+  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+      if (direction == GTK_PAN_DIRECTION_LEFT)
+        offset = -offset;
+    }
+  else
+    {
+      g_assert (priv->orientation == GTK_ORIENTATION_VERTICAL);
+
+      if (direction == GTK_PAN_DIRECTION_UP)
+        offset = -offset;
+    }
+
+  if ((priv->drag_begin_position + offset) < 0)
+    priv->drag_extra_offset = (priv->drag_begin_position + offset);
+  else
+    priv->drag_extra_offset = 0;
+
+  priv->drag_begin->position = MAX (0, priv->drag_begin_position + offset);
+  priv->drag_begin->position_set = TRUE;
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+}
+
+static void
+pnl_multi_paned_create_pan_gesture (PnlMultiPaned *self)
+{
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+  GtkGesture *gesture;
+
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (priv->gesture == NULL);
+
+  gesture = gtk_gesture_pan_new (GTK_WIDGET (self), GTK_ORIENTATION_HORIZONTAL);
+  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE);
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), GTK_PHASE_CAPTURE);
+
+  g_signal_connect_object (gesture,
+                           "drag-begin",
+                           G_CALLBACK (pnl_multi_paned_pan_gesture_drag_begin),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (gesture,
+                           "drag-end",
+                           G_CALLBACK (pnl_multi_paned_pan_gesture_drag_end),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (gesture,
+                           "pan",
+                           G_CALLBACK (pnl_multi_paned_pan_gesture_pan),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  priv->gesture = GTK_GESTURE_PAN (gesture);
+}
+
+static void
+pnl_multi_paned_resize_drag_begin (PnlMultiPaned *self,
+                                   GtkWidget     *child)
+{
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_WIDGET (child));
+
+}
+
+static void
+pnl_multi_paned_resize_drag_end (PnlMultiPaned *self,
+                                 GtkWidget     *child)
+{
+  g_assert (PNL_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_WIDGET (child));
+
+}
+
+static void
+pnl_multi_paned_get_child_property (GtkContainer *container,
+                                    GtkWidget    *widget,
+                                    guint         prop_id,
+                                    GValue       *value,
+                                    GParamSpec   *pspec)
+{
+  PnlMultiPaned *self = PNL_MULTI_PANED (container);
+
+  switch (prop_id)
+    {
+    case CHILD_PROP_POSITION:
+      g_value_set_int (value, pnl_multi_paned_get_child_position (self, widget));
+      break;
+
+    default:
+      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
+    }
+}
+
+static void
+pnl_multi_paned_set_child_property (GtkContainer *container,
+                                    GtkWidget    *widget,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  PnlMultiPaned *self = PNL_MULTI_PANED (container);
+
+  switch (prop_id)
+    {
+    case CHILD_PROP_POSITION:
+      pnl_multi_paned_set_child_position (self, widget, g_value_get_int (value));
+      break;
+
+    default:
+      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
+    }
+}
+
+static void
+pnl_multi_paned_finalize (GObject *object)
+{
+  PnlMultiPaned *self = (PnlMultiPaned *)object;
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+
+  g_assert (priv->children->len == 0);
+
+  g_clear_pointer (&priv->children, g_array_unref);
+  g_clear_object (&priv->gesture);
+
+  G_OBJECT_CLASS (pnl_multi_paned_parent_class)->finalize (object);
+}
+
+static void
+pnl_multi_paned_get_property (GObject    *object,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  PnlMultiPaned *self = PNL_MULTI_PANED (object);
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_ORIENTATION:
+      g_value_set_enum (value, priv->orientation);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_multi_paned_set_property (GObject      *object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  PnlMultiPaned *self = PNL_MULTI_PANED (object);
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_ORIENTATION:
+      priv->orientation = g_value_get_enum (value);
+      gtk_widget_queue_resize (GTK_WIDGET (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_multi_paned_class_init (PnlMultiPanedClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+  object_class->get_property = pnl_multi_paned_get_property;
+  object_class->set_property = pnl_multi_paned_set_property;
+  object_class->finalize = pnl_multi_paned_finalize;
+
+  widget_class->get_request_mode = pnl_multi_paned_get_request_mode;
+  widget_class->get_preferred_width = pnl_multi_paned_get_preferred_width;
+  widget_class->get_preferred_height = pnl_multi_paned_get_preferred_height;
+  widget_class->get_preferred_width_for_height = pnl_multi_paned_get_preferred_width_for_height;
+  widget_class->get_preferred_height_for_width = pnl_multi_paned_get_preferred_height_for_width;
+  widget_class->size_allocate = pnl_multi_paned_size_allocate;
+  widget_class->realize = pnl_multi_paned_realize;
+  widget_class->unrealize = pnl_multi_paned_unrealize;
+  widget_class->map = pnl_multi_paned_map;
+  widget_class->unmap = pnl_multi_paned_unmap;
+  widget_class->draw = pnl_multi_paned_draw;
+
+  container_class->add = pnl_multi_paned_add;
+  container_class->remove = pnl_multi_paned_remove;
+  container_class->get_child_property = pnl_multi_paned_get_child_property;
+  container_class->set_child_property = pnl_multi_paned_set_child_property;
+  container_class->forall = pnl_multi_paned_forall;
+
+  klass->resize_drag_begin = pnl_multi_paned_resize_drag_begin;
+  klass->resize_drag_end = pnl_multi_paned_resize_drag_end;
+
+  gtk_widget_class_set_css_name (widget_class, "multipaned");
+
+  properties [PROP_ORIENTATION] =
+    g_param_spec_enum ("orientation",
+                       "Orientation",
+                       "Orientation",
+                       GTK_TYPE_ORIENTATION,
+                       GTK_ORIENTATION_VERTICAL,
+                       (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  child_properties [CHILD_PROP_POSITION] =
+    g_param_spec_int ("position",
+                      "Position",
+                      "Position",
+                      -1,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  gtk_container_class_install_child_properties (container_class, N_CHILD_PROPS, child_properties);
+
+  style_properties [STYLE_PROP_HANDLE_SIZE] =
+    g_param_spec_int ("handle-size",
+                      "Handle Size",
+                      "Width of the resize handle",
+                      0,
+                      G_MAXINT,
+                      1,
+                      (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+  gtk_widget_class_install_style_property (widget_class, style_properties [STYLE_PROP_HANDLE_SIZE]);
+
+  signals [RESIZE_DRAG_BEGIN] =
+    g_signal_new ("resize-drag-begin",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (PnlMultiPanedClass, resize_drag_begin),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, GTK_TYPE_WIDGET);
+
+  signals [RESIZE_DRAG_END] =
+    g_signal_new ("resize-drag-end",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (PnlMultiPanedClass, resize_drag_end),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, GTK_TYPE_WIDGET);
+}
+
+static void
+pnl_multi_paned_init (PnlMultiPaned *self)
+{
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+
+  gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+
+  priv->children = g_array_new (FALSE, TRUE, sizeof (PnlMultiPanedChild));
+
+  pnl_multi_paned_create_pan_gesture (self);
+}
+
+GtkWidget *
+pnl_multi_paned_new (void)
+{
+  return g_object_new (PNL_TYPE_MULTI_PANED, NULL);
+}
+
+guint
+pnl_multi_paned_get_n_children (PnlMultiPaned *self)
+{
+  PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+
+  g_return_val_if_fail (PNL_IS_MULTI_PANED (self), 0);
+
+  return priv->children ? priv->children->len : 0;
+}
diff --git a/contrib/pnl/pnl-multi-paned.h b/contrib/pnl/pnl-multi-paned.h
new file mode 100644
index 0000000..2dae9cd
--- /dev/null
+++ b/contrib/pnl/pnl-multi-paned.h
@@ -0,0 +1,59 @@
+/* pnl-multi-paned.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_MULTI_PANED_H
+#define PNL_MULTI_PANED_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_MULTI_PANED (pnl_multi_paned_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (PnlMultiPaned, pnl_multi_paned, PNL, MULTI_PANED, GtkContainer)
+
+struct _PnlMultiPanedClass
+{
+  GtkContainerClass parent;
+
+  void (*resize_drag_begin) (PnlMultiPaned *self,
+                             GtkWidget     *child);
+  void (*resize_drag_end)   (PnlMultiPaned *self,
+                             GtkWidget     *child);
+
+  void (*padding1) (void);
+  void (*padding2) (void);
+  void (*padding3) (void);
+  void (*padding4) (void);
+  void (*padding5) (void);
+  void (*padding6) (void);
+  void (*padding7) (void);
+  void (*padding8) (void);
+};
+
+GtkWidget *pnl_multi_paned_new            (void);
+guint      pnl_multi_paned_get_n_children (PnlMultiPaned *self);
+
+G_END_DECLS
+
+#endif /* PNL_MULTI_PANED_H */
diff --git a/contrib/pnl/pnl-tab-strip.c b/contrib/pnl/pnl-tab-strip.c
new file mode 100644
index 0000000..95d524e
--- /dev/null
+++ b/contrib/pnl/pnl-tab-strip.c
@@ -0,0 +1,538 @@
+/* pnl-tab-strip.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "pnl-tab.h"
+#include "pnl-tab-strip.h"
+
+typedef struct
+{
+  GAction         *action;
+  GtkStack        *stack;
+  GtkPositionType  edge : 2;
+} PnlTabStripPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (PnlTabStrip, pnl_tab_strip, GTK_TYPE_BOX)
+
+enum {
+  PROP_0,
+  PROP_EDGE,
+  PROP_STACK,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+set_tab_state (GSimpleAction *action,
+               GVariant      *state,
+               gpointer       user_data)
+{
+  PnlTabStrip *self = user_data;
+  PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+  PnlTab *tab = NULL;
+  const GList *iter;
+  GList *list;
+  gint stateval;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (PNL_IS_TAB_STRIP (self));
+  g_assert (state != NULL);
+  g_assert (g_variant_is_of_type (state, G_VARIANT_TYPE_INT32));
+
+  g_simple_action_set_state (action, state);
+
+  stateval = g_variant_get_int32 (state);
+
+  list = gtk_container_get_children (GTK_CONTAINER (priv->stack));
+
+  for (iter = list; iter != NULL; iter = iter->next)
+    {
+      GtkWidget *child = iter->data;
+      gint position = 0;
+
+      gtk_container_child_get (GTK_CONTAINER (priv->stack), GTK_WIDGET (child),
+                               "position", &position,
+                               NULL);
+
+      if (position == stateval)
+        {
+          tab = g_object_get_data (G_OBJECT (child), "PNL_TAB");
+          gtk_stack_set_visible_child (priv->stack, child);
+          break;
+        }
+    }
+
+  /*
+   * When clicking an active toggle button, we get the state callback but then
+   * the toggle button disables the checked state. So ensure it stays on by
+   * manually setting the state.
+   */
+  if (PNL_IS_TAB (tab))
+    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tab), TRUE);
+
+  g_list_free (list);
+}
+
+static void
+pnl_tab_strip_add (GtkContainer *container,
+                   GtkWidget    *widget)
+{
+  PnlTabStrip *self = (PnlTabStrip *)container;
+  PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+
+  g_assert (PNL_IS_TAB_STRIP (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  if (PNL_IS_TAB (widget))
+    pnl_tab_set_edge (PNL_TAB (widget), priv->edge);
+
+  GTK_CONTAINER_CLASS (pnl_tab_strip_parent_class)->add (container, widget);
+}
+
+static void
+pnl_tab_strip_destroy (GtkWidget *widget)
+{
+  PnlTabStrip *self = (PnlTabStrip *)widget;
+  PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+
+  g_assert (PNL_IS_TAB_STRIP (self));
+
+  pnl_tab_strip_set_stack (self, NULL);
+
+  g_clear_object (&priv->action);
+  g_clear_object (&priv->stack);
+
+  GTK_WIDGET_CLASS (pnl_tab_strip_parent_class)->destroy (widget);
+}
+
+static void
+pnl_tab_strip_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  PnlTabStrip *self = PNL_TAB_STRIP (object);
+
+  switch (prop_id)
+    {
+    case PROP_EDGE:
+      g_value_set_enum (value, pnl_tab_strip_get_edge (self));
+      break;
+
+    case PROP_STACK:
+      g_value_set_object (value, pnl_tab_strip_get_stack (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_tab_strip_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  PnlTabStrip *self = PNL_TAB_STRIP (object);
+
+  switch (prop_id)
+    {
+    case PROP_EDGE:
+      pnl_tab_strip_set_edge (self, g_value_get_enum (value));
+      break;
+
+    case PROP_STACK:
+      pnl_tab_strip_set_stack (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_tab_strip_class_init (PnlTabStripClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+  object_class->get_property = pnl_tab_strip_get_property;
+  object_class->set_property = pnl_tab_strip_set_property;
+
+  widget_class->destroy = pnl_tab_strip_destroy;
+
+  container_class->add = pnl_tab_strip_add;
+
+  properties [PROP_EDGE] =
+    g_param_spec_enum ("edge",
+                       "Edge",
+                       "The edge for the tab-strip",
+                       GTK_TYPE_POSITION_TYPE,
+                       GTK_POS_TOP,
+                       (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_STACK] =
+    g_param_spec_object ("stack",
+                         "Stack",
+                         "The stack of items to manage.",
+                         GTK_TYPE_STACK,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_css_name (widget_class, "tabstrip");
+}
+
+static void
+pnl_tab_strip_init (PnlTabStrip *self)
+{
+  PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+  GSimpleActionGroup *group;
+  static const GActionEntry entries[] = {
+    { "tab", NULL, "i", "0", set_tab_state },
+  };
+
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_HORIZONTAL);
+
+  group = g_simple_action_group_new ();
+  g_action_map_add_action_entries (G_ACTION_MAP (group), entries, G_N_ELEMENTS (entries), self);
+  priv->action = g_object_ref (g_action_map_lookup_action (G_ACTION_MAP (group), "tab"));
+  gtk_widget_insert_action_group (GTK_WIDGET (self), "tab-strip", G_ACTION_GROUP (group));
+  g_object_unref (group);
+
+  pnl_tab_strip_set_edge (self, GTK_POS_TOP);
+}
+
+static void
+pnl_tab_strip_child_position_changed (PnlTabStrip *self,
+                                      GParamSpec  *pspec,
+                                      GtkWidget   *child)
+{
+  GVariant *state;
+  GtkWidget *parent;
+  PnlTab *tab;
+  guint position;
+
+  g_assert (PNL_IS_TAB_STRIP (self));
+  g_assert (GTK_IS_WIDGET (child));
+
+  tab = g_object_get_data (G_OBJECT (child), "PNL_TAB");
+
+  if (!tab || !PNL_IS_TAB (tab))
+    return;
+
+  parent = gtk_widget_get_parent (child);
+
+  gtk_container_child_get (GTK_CONTAINER (parent), child,
+                           "position", &position,
+                           NULL);
+
+  gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (tab),
+                           "position", position,
+                           NULL);
+
+  state = g_variant_new_int32 (position);
+  gtk_actionable_set_action_target_value (GTK_ACTIONABLE (tab), state);
+}
+
+static void
+pnl_tab_strip_child_title_changed (PnlTabStrip *self,
+                                   GParamSpec  *pspec,
+                                   GtkWidget   *child)
+{
+  g_autofree gchar *title = NULL;
+  GtkWidget *parent;
+  PnlTab *tab;
+
+  g_assert (PNL_IS_TAB_STRIP (self));
+  g_assert (GTK_IS_WIDGET (child));
+
+  tab = g_object_get_data (G_OBJECT (child), "PNL_TAB");
+
+  if (!PNL_IS_TAB (tab))
+    return;
+
+  parent = gtk_widget_get_parent (child);
+
+  gtk_container_child_get (GTK_CONTAINER (parent), child,
+                           "title", &title,
+                           NULL);
+
+  pnl_tab_set_title (tab, title);
+}
+
+static void
+pnl_tab_strip_stack_notify_visible_child (PnlTabStrip *self,
+                                          GParamSpec  *pspec,
+                                          GtkStack    *stack)
+{
+  GtkWidget *visible;
+
+  g_assert (PNL_IS_TAB_STRIP (self));
+  g_assert (GTK_IS_STACK (stack));
+
+  visible = gtk_stack_get_visible_child (stack);
+
+  if (visible != NULL)
+    {
+      PnlTab *tab = g_object_get_data (G_OBJECT (visible), "PNL_TAB");
+
+      if (PNL_IS_TAB (tab))
+        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tab), TRUE);
+    }
+}
+
+static void
+pnl_tab_strip_stack_add (PnlTabStrip *self,
+                         GtkWidget   *widget,
+                         GtkStack    *stack)
+{
+  PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+  GVariant *target;
+  PnlTab *tab;
+  gint position = 0;
+
+  g_assert (PNL_IS_TAB_STRIP (self));
+  g_assert (GTK_IS_WIDGET (widget));
+  g_assert (GTK_IS_STACK (stack));
+
+  gtk_container_child_get (GTK_CONTAINER (stack), widget,
+                           "position", &position,
+                           NULL);
+
+  target = g_variant_new_int32 (position);
+
+  tab = g_object_new (PNL_TYPE_TAB,
+                      "action-name", "tab-strip.tab",
+                      "action-target", target,
+                      "edge", priv->edge,
+                      "widget", widget,
+                      NULL);
+
+  g_object_set_data (G_OBJECT (widget), "PNL_TAB", tab);
+
+  g_signal_connect_object (widget,
+                           "child-notify::position",
+                           G_CALLBACK (pnl_tab_strip_child_position_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (widget,
+                           "child-notify::title",
+                           G_CALLBACK (pnl_tab_strip_child_title_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (tab));
+
+  g_object_bind_property (widget, "visible", tab, "visible", G_BINDING_SYNC_CREATE);
+
+  pnl_tab_strip_child_title_changed (self, NULL, widget);
+  pnl_tab_strip_stack_notify_visible_child (self, NULL, stack);
+}
+
+static void
+pnl_tab_strip_stack_remove (PnlTabStrip *self,
+                            GtkWidget   *widget,
+                            GtkStack    *stack)
+{
+  PnlTab *tab;
+
+  g_assert (PNL_IS_TAB_STRIP (self));
+  g_assert (GTK_IS_WIDGET (widget));
+  g_assert (GTK_IS_STACK (stack));
+
+  tab = g_object_get_data (G_OBJECT (widget), "PNL_TAB");
+
+  if (PNL_IS_TAB (tab))
+    gtk_container_remove (GTK_CONTAINER (self), GTK_WIDGET (tab));
+}
+
+GtkWidget *
+pnl_tab_strip_new (void)
+{
+  return g_object_new (PNL_TYPE_TAB_STRIP, NULL);
+}
+
+/**
+ * pnl_tab_strip_get_stack:
+ *
+ * Returns: (transfer none) (nullable): A #GtkStack or %NULL.
+ */
+GtkStack *
+pnl_tab_strip_get_stack (PnlTabStrip *self)
+{
+  PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+
+  g_return_val_if_fail (PNL_IS_TAB_STRIP (self), NULL);
+
+  return priv->stack;
+}
+
+static void
+pnl_tab_strip_cold_plug (GtkWidget *widget,
+                         gpointer   user_data)
+{
+  PnlTabStrip *self = user_data;
+  PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+
+  g_assert (PNL_IS_TAB_STRIP (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  pnl_tab_strip_stack_add (self, widget, priv->stack);
+}
+
+void
+pnl_tab_strip_set_stack (PnlTabStrip *self,
+                         GtkStack    *stack)
+{
+  PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+
+  g_return_if_fail (PNL_IS_TAB_STRIP (self));
+  g_return_if_fail (!stack || GTK_IS_STACK (stack));
+
+  if (stack != priv->stack)
+    {
+      if (priv->stack != NULL)
+        {
+          g_signal_handlers_disconnect_by_func (priv->stack,
+                                                G_CALLBACK (pnl_tab_strip_stack_notify_visible_child),
+                                                self);
+
+          g_signal_handlers_disconnect_by_func (priv->stack,
+                                                G_CALLBACK (pnl_tab_strip_stack_add),
+                                                self);
+
+          g_signal_handlers_disconnect_by_func (priv->stack,
+                                                G_CALLBACK (pnl_tab_strip_stack_remove),
+                                                self);
+
+          gtk_container_foreach (GTK_CONTAINER (self), (GtkCallback)gtk_widget_destroy, NULL);
+
+          g_clear_object (&priv->stack);
+        }
+
+      if (stack != NULL)
+        {
+          priv->stack = g_object_ref (stack);
+
+          g_signal_connect_object (priv->stack,
+                                   "notify::visible-child",
+                                   G_CALLBACK (pnl_tab_strip_stack_notify_visible_child),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+          g_signal_connect_object (priv->stack,
+                                   "add",
+                                   G_CALLBACK (pnl_tab_strip_stack_add),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+          g_signal_connect_object (priv->stack,
+                                   "remove",
+                                   G_CALLBACK (pnl_tab_strip_stack_remove),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+          gtk_container_foreach (GTK_CONTAINER (priv->stack),
+                                 pnl_tab_strip_cold_plug,
+                                 self);
+        }
+    }
+}
+
+static void
+pnl_tab_strip_update_edge (GtkWidget *widget,
+                           gpointer   user_data)
+{
+  GtkPositionType edge = GPOINTER_TO_INT (user_data);
+
+  g_assert (GTK_IS_WIDGET (widget));
+
+  if (PNL_IS_TAB (widget))
+    pnl_tab_set_edge (PNL_TAB (widget), edge);
+}
+
+GtkPositionType
+pnl_tab_strip_get_edge (PnlTabStrip *self)
+{
+  PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+
+  g_return_val_if_fail (PNL_IS_TAB_STRIP (self), 0);
+
+  return priv->edge;
+}
+
+void
+pnl_tab_strip_set_edge (PnlTabStrip     *self,
+                        GtkPositionType  edge)
+{
+  PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+
+  g_return_if_fail (PNL_IS_TAB_STRIP (self));
+  g_return_if_fail (edge >= 0);
+  g_return_if_fail (edge <= 3);
+
+  if (priv->edge != edge)
+    {
+      GtkStyleContext *style_context;
+      const gchar *class_name = NULL;
+
+      priv->edge = edge;
+
+      gtk_container_foreach (GTK_CONTAINER (self),
+                             pnl_tab_strip_update_edge,
+                             GINT_TO_POINTER (edge));
+
+      style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+      gtk_style_context_remove_class (style_context, "left-edge");
+      gtk_style_context_remove_class (style_context, "top-edge");
+      gtk_style_context_remove_class (style_context, "right-edge");
+      gtk_style_context_remove_class (style_context, "bottom-edge");
+
+      switch (edge)
+        {
+        case GTK_POS_LEFT:
+          class_name = "left";
+          break;
+
+        case GTK_POS_RIGHT:
+          class_name = "right";
+          break;
+
+        case GTK_POS_TOP:
+          class_name = "top";
+          break;
+
+        case GTK_POS_BOTTOM:
+          class_name = "bottom";
+          break;
+
+        default:
+          g_assert_not_reached ();
+        }
+
+      gtk_style_context_add_class (style_context, class_name);
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EDGE]);
+    }
+}
diff --git a/contrib/pnl/pnl-tab-strip.h b/contrib/pnl/pnl-tab-strip.h
new file mode 100644
index 0000000..1aba919
--- /dev/null
+++ b/contrib/pnl/pnl-tab-strip.h
@@ -0,0 +1,53 @@
+/* pnl-tab-strip.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_TAB_STRIP_H
+#define PNL_TAB_STRIP_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_TAB_STRIP (pnl_tab_strip_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (PnlTabStrip, pnl_tab_strip, PNL, TAB_STRIP, GtkBox)
+
+struct _PnlTabStripClass
+{
+  GtkBoxClass parent;
+};
+
+GtkWidget       *pnl_tab_strip_new             (void);
+GtkStack        *pnl_tab_strip_get_stack       (PnlTabStrip     *self);
+void             pnl_tab_strip_set_stack       (PnlTabStrip     *self,
+                                                GtkStack        *stack);
+GtkPositionType  pnl_tab_strip_get_edge        (PnlTabStrip     *self);
+void             pnl_tab_strip_set_edge        (PnlTabStrip     *self,
+                                                GtkPositionType  edge);
+gboolean         pnl_tab_strip_get_show_labels (PnlTabStrip     *self);
+void             pnl_tab_strip_set_show_labels (PnlTabStrip     *self,
+                                                gboolean         show_labels);
+
+G_END_DECLS
+
+#endif /* PNL_TAB_STRIP_H */
diff --git a/contrib/pnl/pnl-tab.c b/contrib/pnl/pnl-tab.c
new file mode 100644
index 0000000..1340392
--- /dev/null
+++ b/contrib/pnl/pnl-tab.c
@@ -0,0 +1,275 @@
+/* pnl-tab.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "pnl-tab.h"
+
+struct _PnlTab
+{
+  GtkToggleButton  parent;
+  GtkPositionType  edge : 2;
+  GtkLabel        *title;
+  GtkWidget       *widget;
+};
+
+G_DEFINE_TYPE (PnlTab, pnl_tab, GTK_TYPE_TOGGLE_BUTTON)
+
+enum {
+  PROP_0,
+  PROP_EDGE,
+  PROP_TITLE,
+  PROP_WIDGET,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+pnl_tab_destroy (GtkWidget *widget)
+{
+  PnlTab *self = (PnlTab *)widget;
+
+  if (self->widget)
+    {
+      g_object_remove_weak_pointer (G_OBJECT (self->widget), (gpointer *)&self->widget);
+      self->widget = NULL;
+    }
+
+  GTK_WIDGET_CLASS (pnl_tab_parent_class)->destroy (widget);
+}
+
+static void
+pnl_tab_update_edge (PnlTab *self)
+{
+  g_assert (PNL_IS_TAB (self));
+
+  switch (self->edge)
+    {
+    case GTK_POS_TOP:
+      gtk_label_set_angle (self->title, 0.0);
+      gtk_widget_set_hexpand (GTK_WIDGET (self), TRUE);
+      gtk_widget_set_vexpand (GTK_WIDGET (self), FALSE);
+      break;
+
+    case GTK_POS_BOTTOM:
+      gtk_label_set_angle (self->title, 0.0);
+      gtk_widget_set_hexpand (GTK_WIDGET (self), TRUE);
+      gtk_widget_set_vexpand (GTK_WIDGET (self), FALSE);
+      break;
+
+    case GTK_POS_LEFT:
+      gtk_label_set_angle (self->title, -90.0);
+      gtk_widget_set_hexpand (GTK_WIDGET (self), FALSE);
+      gtk_widget_set_vexpand (GTK_WIDGET (self), TRUE);
+      break;
+
+    case GTK_POS_RIGHT:
+      gtk_label_set_angle (self->title, 90.0);
+      gtk_widget_set_hexpand (GTK_WIDGET (self), FALSE);
+      gtk_widget_set_vexpand (GTK_WIDGET (self), TRUE);
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+pnl_tab_get_property (GObject    *object,
+                      guint       prop_id,
+                      GValue     *value,
+                      GParamSpec *pspec)
+{
+  PnlTab *self = PNL_TAB (object);
+
+  switch (prop_id)
+    {
+    case PROP_EDGE:
+      g_value_set_enum (value, pnl_tab_get_edge (self));
+      break;
+
+    case PROP_TITLE:
+      g_value_set_string (value, pnl_tab_get_title (self));
+      break;
+
+    case PROP_WIDGET:
+      g_value_set_object (value, pnl_tab_get_widget (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_tab_set_property (GObject      *object,
+                      guint         prop_id,
+                      const GValue *value,
+                      GParamSpec   *pspec)
+{
+  PnlTab *self = PNL_TAB (object);
+
+  switch (prop_id)
+    {
+    case PROP_EDGE:
+      pnl_tab_set_edge (self, g_value_get_enum (value));
+      break;
+
+    case PROP_TITLE:
+      pnl_tab_set_title (self, g_value_get_string (value));
+      break;
+
+    case PROP_WIDGET:
+      pnl_tab_set_widget (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+pnl_tab_class_init (PnlTabClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->get_property = pnl_tab_get_property;
+  object_class->set_property = pnl_tab_set_property;
+
+  widget_class->destroy = pnl_tab_destroy;
+
+  gtk_widget_class_set_css_name (widget_class, "tab");
+
+  properties [PROP_EDGE] =
+    g_param_spec_enum ("edge",
+                       "Edge",
+                       "Edge",
+                       GTK_TYPE_POSITION_TYPE,
+                       GTK_POS_TOP,
+                       (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TITLE] =
+    g_param_spec_string ("title",
+                         "Title",
+                         "Title",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_WIDGET] =
+    g_param_spec_object ("widget",
+                         "Widget",
+                         "The widget the tab represents",
+                         GTK_TYPE_WIDGET,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+pnl_tab_init (PnlTab *self)
+{
+  self->edge = GTK_POS_TOP;
+
+  gtk_widget_set_hexpand (GTK_WIDGET (self), TRUE);
+  gtk_widget_set_vexpand (GTK_WIDGET (self), FALSE);
+
+  self->title = g_object_new (GTK_TYPE_LABEL,
+                              "ellipsize", PANGO_ELLIPSIZE_END,
+                              "use-underline", TRUE,
+                              "visible", TRUE,
+                              NULL);
+
+  gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (self->title));
+}
+
+const gchar *
+pnl_tab_get_title (PnlTab *self)
+{
+  g_return_val_if_fail (PNL_IS_TAB (self), NULL);
+
+  return gtk_label_get_label (self->title);
+}
+
+void
+pnl_tab_set_title (PnlTab      *self,
+                   const gchar *title)
+{
+  g_return_if_fail (PNL_IS_TAB (self));
+
+  gtk_label_set_label (self->title, title);
+}
+
+GtkPositionType
+pnl_tab_get_edge (PnlTab *self)
+{
+  g_return_val_if_fail (PNL_IS_TAB (self), 0);
+
+  return self->edge;
+}
+
+void
+pnl_tab_set_edge (PnlTab          *self,
+                  GtkPositionType  edge)
+{
+  g_return_if_fail (PNL_IS_TAB (self));
+  g_return_if_fail (edge >= 0);
+  g_return_if_fail (edge <= 3);
+
+  if (self->edge != edge)
+    {
+      self->edge = edge;
+      pnl_tab_update_edge (self);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EDGE]);
+    }
+}
+
+/**
+ * pnl_tab_get_widget:
+ *
+ * Returns: (transfer none) (nullable): A #GtkWidget or %NULL.
+ */
+GtkWidget *
+pnl_tab_get_widget (PnlTab *self)
+{
+  g_return_val_if_fail (PNL_IS_TAB (self), NULL);
+
+  return self->widget;
+}
+
+void
+pnl_tab_set_widget (PnlTab    *self,
+                    GtkWidget *widget)
+{
+  g_return_if_fail (PNL_IS_TAB (self));
+
+  if (self->widget != widget)
+    {
+      if (self->widget)
+        g_object_remove_weak_pointer (G_OBJECT (self->widget), (gpointer *)&self->widget);
+
+      self->widget = widget;
+
+      if (widget)
+        g_object_add_weak_pointer (G_OBJECT (self->widget), (gpointer *)&self->widget);
+
+      gtk_label_set_mnemonic_widget (self->title, widget);
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_WIDGET]);
+    }
+}
diff --git a/contrib/pnl/pnl-tab.h b/contrib/pnl/pnl-tab.h
new file mode 100644
index 0000000..a59bfd5
--- /dev/null
+++ b/contrib/pnl/pnl-tab.h
@@ -0,0 +1,47 @@
+/* pnl-tab.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_TAB_H
+#define PNL_TAB_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_TAB (pnl_tab_get_type())
+
+G_DECLARE_FINAL_TYPE (PnlTab, pnl_tab, PNL, TAB, GtkToggleButton)
+
+const gchar     *pnl_tab_get_title  (PnlTab          *self);
+void             pnl_tab_set_title  (PnlTab          *self,
+                                     const gchar     *title);
+GtkPositionType  pnl_tab_get_edge   (PnlTab          *self);
+void             pnl_tab_set_edge   (PnlTab          *self,
+                                     GtkPositionType  edge);
+GtkWidget       *pnl_tab_get_widget (PnlTab          *self);
+void             pnl_tab_set_widget (PnlTab          *self,
+                                     GtkWidget       *widget);
+
+G_END_DECLS
+
+#endif /* PNL_TAB_H */
diff --git a/contrib/pnl/pnl-util-private.h b/contrib/pnl/pnl-util-private.h
new file mode 100644
index 0000000..36e6845
--- /dev/null
+++ b/contrib/pnl/pnl-util-private.h
@@ -0,0 +1,39 @@
+/* pnl-util-private.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_UTIL_PRIVATE_H
+#define PNL_UTIL_PRIVATE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define pnl_clear_weak_pointer(ptr) \
+  (*(ptr) ? (g_object_remove_weak_pointer((GObject*)*(ptr), (gpointer*)ptr),*(ptr)=NULL,1) : 0)
+
+#define pnl_set_weak_pointer(ptr,obj) \
+  
((obj!=*(ptr))?(pnl_clear_weak_pointer(ptr),*(ptr)=obj,((obj)?g_object_add_weak_pointer((GObject*)obj,(gpointer*)ptr),NULL:NULL),1):0)
+
+gboolean pnl_gtk_bin_draw          (GtkWidget *widget,
+                                    cairo_t   *cr);
+void     pnl_gtk_bin_size_allocate (GtkWidget     *widget,
+                                    GtkAllocation *allocation);
+
+G_END_DECLS
+
+#endif /* PNL_UTIL_PRIVATE_H */
diff --git a/contrib/pnl/pnl-util.c b/contrib/pnl/pnl-util.c
new file mode 100644
index 0000000..5105be3
--- /dev/null
+++ b/contrib/pnl/pnl-util.c
@@ -0,0 +1,109 @@
+/* pnl-util.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-util-private.h"
+
+static void
+pnl_gtk_border_sum (GtkBorder       *one,
+                    const GtkBorder *two)
+{
+  one->top += two->top;
+  one->right += two->right;
+  one->bottom += two->bottom;
+  one->left += two->left;
+}
+
+gboolean
+pnl_gtk_bin_draw (GtkWidget *widget,
+                  cairo_t   *cr)
+{
+  GtkStyleContext *style_context;
+  GtkStateFlags state;
+  GtkAllocation alloc;
+  GtkBorder border;
+  GtkBorder padding;
+  GtkWidget *child;
+
+  g_assert (GTK_IS_WIDGET (widget));
+  g_assert (cr != NULL);
+
+  gtk_widget_get_allocation (widget, &alloc);
+
+  style_context = gtk_widget_get_style_context (widget);
+  state = gtk_style_context_get_state (style_context);
+  gtk_style_context_get_border (style_context, state, &border);
+  gtk_style_context_get_padding (style_context, state, &padding);
+
+  pnl_gtk_border_sum (&border, &padding);
+
+  gtk_render_background (gtk_widget_get_style_context (widget), cr,
+                         border.left,
+                         border.top,
+                         alloc.width - border.left - border.right,
+                         alloc.height - border.top - border.bottom);
+
+  child = gtk_bin_get_child (GTK_BIN (widget));
+
+  if (child != NULL)
+    gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr);
+
+  return GDK_EVENT_PROPAGATE;
+}
+
+void
+pnl_gtk_bin_size_allocate (GtkWidget     *widget,
+                           GtkAllocation *allocation)
+{
+  GtkStyleContext *style_context;
+  GtkStateFlags state;
+  GtkBorder border;
+  GtkBorder padding;
+  gint border_width;
+  GtkWidget *child;
+
+  g_return_if_fail (GTK_IS_BIN (widget));
+  g_return_if_fail (allocation != NULL);
+
+  gtk_widget_set_allocation (widget, allocation);
+
+  child = gtk_bin_get_child (GTK_BIN (widget));
+
+  if (child == NULL)
+    return;
+
+  style_context = gtk_widget_get_style_context (widget);
+  state = gtk_style_context_get_state (style_context);
+  gtk_style_context_get_border (style_context, state, &border);
+  gtk_style_context_get_padding (style_context, state, &padding);
+
+  pnl_gtk_border_sum (&border, &padding);
+
+  allocation->x += border.left;
+  allocation->y += border.top;
+  allocation->width -= border.left + border.right;
+  allocation->height -= border.top + border.bottom;
+
+  border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
+
+  allocation->x += border_width;
+  allocation->y += border_width;
+  allocation->width -= border_width * 2;
+  allocation->height -= border_width * 2;
+
+  gtk_widget_size_allocate (child, allocation);
+}
diff --git a/contrib/pnl/pnl-version.h.in b/contrib/pnl/pnl-version.h.in
new file mode 100644
index 0000000..a11acaa
--- /dev/null
+++ b/contrib/pnl/pnl-version.h.in
@@ -0,0 +1,97 @@
+/* pnl-version.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_VERSION_H
+#define PNL_VERSION_H
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+/**
+ * SECTION:pnl-version
+ * @short_description: pnl version checking
+ *
+ * pnl provides macros to check the version of the library
+ * at compile-time
+ */
+
+/**
+ * PNL_MAJOR_VERSION:
+ *
+ * pnl major version component (e.g. 1 if %PNL_VERSION is 1.2.3)
+ */
+#define PNL_MAJOR_VERSION (@MAJOR_VERSION@)
+
+/**
+ * PNL_MINOR_VERSION:
+ *
+ * pnl minor version component (e.g. 2 if %PNL_VERSION is 1.2.3)
+ */
+#define PNL_MINOR_VERSION (@MINOR_VERSION@)
+
+/**
+ * PNL_MICRO_VERSION:
+ *
+ * pnl micro version component (e.g. 3 if %PNL_VERSION is 1.2.3)
+ */
+#define PNL_MICRO_VERSION (@MICRO_VERSION@)
+
+/**
+ * PNL_VERSION
+ *
+ * pnl version.
+ */
+#define PNL_VERSION (@VERSION@)
+
+/**
+ * PNL_VERSION_S:
+ *
+ * pnl version, encoded as a string, useful for printing and
+ * concatenation.
+ */
+#define PNL_VERSION_S "@VERSION@"
+
+#define PNL_ENCODE_VERSION(major,minor,micro) \
+        ((major) << 24 | (minor) << 16 | (micro) << 8)
+
+/**
+ * PNL_VERSION_HEX:
+ *
+ * pnl version, encoded as an hexadecimal number, useful for
+ * integer comparisons.
+ */
+#define PNL_VERSION_HEX \
+        (PNL_ENCODE_VERSION (PNL_MAJOR_VERSION, PNL_MINOR_VERSION, PNL_MICRO_VERSION))
+
+/**
+ * PNL_CHECK_VERSION:
+ * @major: required major version
+ * @minor: required minor version
+ * @micro: required micro version
+ *
+ * Compile-time version checking. Evaluates to %TRUE if the version
+ * of pnl is greater than the required one.
+ */
+#define PNL_CHECK_VERSION(major,minor,micro)   \
+        (PNL_MAJOR_VERSION > (major) || \
+         (PNL_MAJOR_VERSION == (major) && PNL_MINOR_VERSION > (minor)) || \
+         (PNL_MAJOR_VERSION == (major) && PNL_MINOR_VERSION == (minor) && \
+          PNL_MICRO_VERSION >= (micro)))
+
+#endif /* PNL_VERSION_H */
diff --git a/contrib/pnl/pnl.gresource.xml b/contrib/pnl/pnl.gresource.xml
new file mode 100644
index 0000000..676d36f
--- /dev/null
+++ b/contrib/pnl/pnl.gresource.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/panel-gtk/icons/16x16/actions/">
+    <file compressed="true" 
alias="panel-bottom-pane-symbolic.svg">resources/panel-bottom-pane-symbolic.svg</file>
+    <file compressed="true" 
alias="panel-left-pane-symbolic.svg">resources/panel-left-pane-symbolic.svg</file>
+    <file compressed="true" 
alias="panel-right-pane-symbolic.svg">resources/panel-right-pane-symbolic.svg</file>
+  </gresource>
+</gresources>
diff --git a/contrib/pnl/pnl.h b/contrib/pnl/pnl.h
new file mode 100644
index 0000000..25ee5ef
--- /dev/null
+++ b/contrib/pnl/pnl.h
@@ -0,0 +1,52 @@
+/* pnl.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_H
+#define PNL_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PNL_INSIDE
+
+#include "pnl-dock.h"
+#include "pnl-dock-bin.h"
+#include "pnl-dock-bin-edge.h"
+#include "pnl-dock-item.h"
+#include "pnl-dock-manager.h"
+#include "pnl-dock-overlay.h"
+#include "pnl-dock-paned.h"
+#include "pnl-dock-revealer.h"
+#include "pnl-dock-stack.h"
+#include "pnl-dock-tab-strip.h"
+#include "pnl-dock-types.h"
+#include "pnl-dock-widget.h"
+#include "pnl-dock-window.h"
+
+#include "pnl-version.h"
+
+#include "pnl-tab.h"
+#include "pnl-tab-strip.h"
+#include "pnl-multi-paned.h"
+
+#undef PNL_INSIDE
+
+G_END_DECLS
+
+#endif /* PNL_H */
diff --git a/contrib/pnl/resources/panel-bottom-pane-symbolic.svg 
b/contrib/pnl/resources/panel-bottom-pane-symbolic.svg
new file mode 100644
index 0000000..a13bb63
--- /dev/null
+++ b/contrib/pnl/resources/panel-bottom-pane-symbolic.svg
@@ -0,0 +1,26 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no'?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/' 
sodipodi:docname='builder-view-bottom-pane-symbolic.svg' height='16' id='svg7384' 
xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' 
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' 
xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg' 
version='1.1' inkscape:version='0.91 r13725' width='16' xmlns='http://www.w3.org/2000/svg'>
+  <metadata id='metadata90'>
+    <rdf:RDF>
+      <cc:Work rdf:about=''>
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/>
+        <dc:title>Gnome Symbolic Icon Theme</dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview inkscape:bbox-nodes='true' inkscape:bbox-paths='true' bordercolor='#666666' 
borderopacity='1' inkscape:current-layer='layer2' inkscape:cx='-22.41638' inkscape:cy='18.171243' 
gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88' 
inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#555753' 
inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true' 
inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true' 
inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false' 
inkscape:snap-to-guides='true' inkscape:window-height='1376' inkscape:window-maximized='1' 
inkscape:window-width='2560' inkscape:window-x='0' inkscape:window-y='27' inkscape:zoom='1'>
+    <inkscape:grid empspacing='2' enabled='true' id='grid4866' originx='80.00005' originy='-72.98918' 
snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/>
+  </sodipodi:namedview>
+  <title id='title9167'>Gnome Symbolic Icon Theme</title>
+  <defs id='defs7386'/>
+  <g inkscape:groupmode='layer' id='layer2' inkscape:label='actions' style='display:inline' 
transform='translate(80.00005,72.98918)'>
+    
+    <path inkscape:connector-curvature='0' d='m -79,-72 0,0.5 0,13.5 14,0 0,-14 -14,0 z m 1,1 12,0 0,12 
-12,0 0,-12 z' id='rect8495' 
style='color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99999994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;str
 
oke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate'/>
+    <rect height='2.032932' id='rect8512' 
style='color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate'
 width='10.032078' x='-77.050407' y='-62.002731'/>
+  </g>
+  <g inkscape:groupmode='layer' id='layer9' inkscape:label='apps' style='display:inline' 
transform='translate(-161.00015,-144.01082)'/>
+  <g inkscape:groupmode='layer' id='layer1' inkscape:label='autocomplete' 
transform='translate(80.00005,72.98918)'/>
+</svg>
diff --git a/contrib/pnl/resources/panel-left-pane-symbolic.svg 
b/contrib/pnl/resources/panel-left-pane-symbolic.svg
new file mode 100644
index 0000000..116173d
--- /dev/null
+++ b/contrib/pnl/resources/panel-left-pane-symbolic.svg
@@ -0,0 +1,26 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no'?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/' 
sodipodi:docname='builder-view-left-pane-symbolic.svg' height='16' id='svg7384' 
xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' 
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' 
xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg' 
version='1.1' inkscape:version='0.91 r13725' width='16' xmlns='http://www.w3.org/2000/svg'>
+  <metadata id='metadata90'>
+    <rdf:RDF>
+      <cc:Work rdf:about=''>
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/>
+        <dc:title>Gnome Symbolic Icon Theme</dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview inkscape:bbox-nodes='true' inkscape:bbox-paths='true' bordercolor='#666666' 
borderopacity='1' inkscape:current-layer='layer2' inkscape:cx='-2.41639' inkscape:cy='18.171245' 
gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88' 
inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#555753' 
inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true' 
inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true' 
inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false' 
inkscape:snap-to-guides='true' inkscape:window-height='1376' inkscape:window-maximized='1' 
inkscape:window-width='2560' inkscape:window-x='0' inkscape:window-y='27' inkscape:zoom='1'>
+    <inkscape:grid empspacing='2' enabled='true' id='grid4866' originx='100.00004' originy='-72.989178' 
snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/>
+  </sodipodi:namedview>
+  <title id='title9167'>Gnome Symbolic Icon Theme</title>
+  <defs id='defs7386'/>
+  <g inkscape:groupmode='layer' id='layer2' inkscape:label='actions' style='display:inline' 
transform='translate(100.00004,72.989178)'>
+    
+    <path inkscape:connector-curvature='0' d='m -84.988281,-71.988281 -0.5,0 -13.5,0 0,14 14,0 0,-14 z m 
-1,1 0,12 -12,0 0,-12 12,0 z' id='rect8530' 
style='color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99999994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-da
 
sharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate'/>
+    <rect height='2.032932' id='rect8532' 
style='color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate'
 transform='matrix(0,1,-1,0,0,0)' width='10.032078' x='-70.039536' y='94.986488'/>
+  </g>
+  <g inkscape:groupmode='layer' id='layer9' inkscape:label='apps' style='display:inline' 
transform='translate(-141.00016,-144.01082)'/>
+  <g inkscape:groupmode='layer' id='layer1' inkscape:label='autocomplete' 
transform='translate(100.00004,72.989178)'/>
+</svg>
diff --git a/contrib/pnl/resources/panel-right-pane-symbolic.svg 
b/contrib/pnl/resources/panel-right-pane-symbolic.svg
new file mode 100644
index 0000000..1c099a7
--- /dev/null
+++ b/contrib/pnl/resources/panel-right-pane-symbolic.svg
@@ -0,0 +1,26 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no'?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/' 
sodipodi:docname='builder-view-right-pane-symbolic.svg' height='16' id='svg7384' 
xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' 
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' 
xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg' 
version='1.1' inkscape:version='0.91 r13725' width='16' xmlns='http://www.w3.org/2000/svg'>
+  <metadata id='metadata90'>
+    <rdf:RDF>
+      <cc:Work rdf:about=''>
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/>
+        <dc:title>Gnome Symbolic Icon Theme</dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview inkscape:bbox-nodes='true' inkscape:bbox-paths='true' bordercolor='#666666' 
borderopacity='1' inkscape:current-layer='layer2' inkscape:cx='-42.41638' inkscape:cy='18.171247' 
gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88' 
inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#555753' 
inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true' 
inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true' 
inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false' 
inkscape:snap-to-guides='true' inkscape:window-height='1376' inkscape:window-maximized='1' 
inkscape:window-width='2560' inkscape:window-x='0' inkscape:window-y='27' inkscape:zoom='1'>
+    <inkscape:grid empspacing='2' enabled='true' id='grid4866' originx='60.00005' originy='-72.989176' 
snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/>
+  </sodipodi:namedview>
+  <title id='title9167'>Gnome Symbolic Icon Theme</title>
+  <defs id='defs7386'/>
+  <g inkscape:groupmode='layer' id='layer2' inkscape:label='actions' style='display:inline' 
transform='translate(60.00005,72.989176)'>
+    
+    <path inkscape:connector-curvature='0' d='m -59.011719,-57.988281 0.5,0 13.5,0 0,-14 -14,0 0,14 z m 1,-1 
0,-12 12,0 0,12 -12,0 z' id='rect8520' 
style='color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99999994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-das
 
harray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate'/>
+    <rect height='2.032932' id='rect8522' 
style='color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate'
 transform='matrix(0,-1,1,0,0,0)' width='10.032078' x='59.93882' y='-49.013599'/>
+  </g>
+  <g inkscape:groupmode='layer' id='layer9' inkscape:label='apps' style='display:inline' 
transform='translate(-181.00015,-144.01082)'/>
+  <g inkscape:groupmode='layer' id='layer1' inkscape:label='autocomplete' 
transform='translate(60.00005,72.989176)'/>
+</svg>
diff --git a/data/libide-1.0.pc.in b/data/libide-1.0.pc.in
index 0917ffc..ec6bfae 100644
--- a/data/libide-1.0.pc.in
+++ b/data/libide-1.0.pc.in
@@ -6,6 +6,6 @@ includedir=${exec_prefix}/include
 Name: LibIDE
 Description: LibIDE contains the components used to build the GNOME Builder IDE.
 Version: @VERSION@
-Libs: -L${libdir}/gnome-builder -lide-1.0 -legg-private -Ltemplate-glib-1.0
-Cflags: -I${includedir}/gnome-builder- VERSION@/libide -I${includedir}/gnome-builder- VERSION@/egg 
-I${includedir}/gnome-builder- VERSION@/template-glib
+Libs: -L${libdir}/gnome-builder -lide-1.0 -legg-private -Ltemplate-glib-1.0 -lpanel-gtk
+Cflags: -I${includedir}/gnome-builder- VERSION@/libide -I${includedir}/gnome-builder- VERSION@/egg 
-I${includedir}/gnome-builder- VERSION@/template-glib -I${includedir}gnome-builder- VERSION@/pnl
 Requires: gio-2.0 gtk+-3.0 gtksourceview-3.0
diff --git a/data/theme/Adwaita-dark.css b/data/theme/Adwaita-dark.css
index a7b2ef6..f78d386 100644
--- a/data/theme/Adwaita-dark.css
+++ b/data/theme/Adwaita-dark.css
@@ -51,14 +51,14 @@ layouttab:backdrop {
 }
 
 
-layoutpane > box.vertical:first-child > box.horizontal {
+tabstrip {
   background-color: #303535;
   box-shadow: 0 4px 3px -5px #2c3030 inset,
               0 -1px 0 #1c1f1f inset;
 }
-layoutpane:backdrop > box.vertical:first-child > box.horizontal {
+tabstrip:backdrop {
   box-shadow: 0 -1px 0 #2c3030 inset;
 }
-layoutpane stackswitcher button:checked {
+tabstrip tab:checked {
   box-shadow: 0 -2px 0 #215d9c inset;
 }
diff --git a/data/theme/Adwaita-panels.css b/data/theme/Adwaita-panels.css
new file mode 100644
index 0000000..e45cd7a
--- /dev/null
+++ b/data/theme/Adwaita-panels.css
@@ -0,0 +1,50 @@
+dockbin {
+  border: 1px solid alpha(@borders, 0.75);
+  -PnlDockBin-handle-size: 1;
+}
+
+dockpaned {
+  border: 1px solid @borders;
+}
+
+tabstrip {
+  padding: 0 6px 0 6px;
+}
+
+tab {
+  background-image: none;
+  background-color: transparent;
+  border-color: transparent;
+  border-style: solid;
+  color: @theme_fg_color;
+  min-height: 39px;
+  outline-offset: -6px;
+  transition-duration: 200ms;
+  transition-timing-function: ease;
+}
+
+tab label {
+  font-size: 0.7em;
+}
+
+tab > * {
+  padding: 0 6px 0 6px;
+}
+
+dockoverlayedge {
+  background-color: @theme_bg_color;
+}
+
+dockoverlayedge tabstrip {
+  padding: 0;
+  border: none;
+}
+
+dockoverlayedge.left-edge tab:checked {
+  border-right-color: @theme_selected_bg_color;
+  border-bottom-color: transparent;
+}
+dockoverlayedge.right-edge tab:checked {
+  border-left-color: @theme_selected_bg_color;
+  border-bottom-color: transparent;
+}
diff --git a/data/theme/Adwaita-shared.css b/data/theme/Adwaita-shared.css
index d3a36fc..cbf6b7f 100644
--- a/data/theme/Adwaita-shared.css
+++ b/data/theme/Adwaita-shared.css
@@ -1,4 +1,5 @@
 @import url("resource:///org/gnome/builder/theme/shared.css");
+ import url("resource:///org/gnome/builder/theme/Adwaita-panels.css");
 
 
 /*
@@ -109,22 +110,7 @@ layouttab > box > button:last-child:backdrop image {
 }
 
 
-/*
- * The following tweaks the layout panel headers
- */
-layoutpane {
-  -IdeLayoutPane-handle-size: 1;
-  -gtk-icon-source: none;
-}
-layoutpane stackswitcher button {
-  min-width: 0;
-  border: none;
-  background: transparent;
-  box-shadow: none;
-  padding: 0 3px 0 3px;
-  border-radius: 0;
+layout {
+  border: 1px solid alpha(@borders, 0.75);
+  -PnlDockBin-handle-size: 1;
 }
-layoutpane .pane-separator {
-  background-color: @borders;
-}
-
diff --git a/data/theme/Adwaita.css b/data/theme/Adwaita.css
index 8563a30..25afe05 100644
--- a/data/theme/Adwaita.css
+++ b/data/theme/Adwaita.css
@@ -49,14 +49,14 @@ layouttab:backdrop {
 }
 
 
-layoutpane > box.vertical:first-child > box.horizontal {
+tabstrip {
   background-color: #d6d6d6;
   box-shadow: 0 4px 3px -5px #aaa inset,
               0 -1px 0 #a1a1a1 inset;
 }
-layoutpane:backdrop > box.vertical:first-child > box.horizontal {
+tabstrip:backdrop {
   box-shadow: 0 -1px 0 #a1a1a1 inset;
 }
-layoutpane stackswitcher button:checked {
+tabstrip tab:checked {
   box-shadow: 0 -2px 0 #4a90d9 inset;
 }
diff --git a/data/ui/ide-editor-perspective.ui b/data/ui/ide-editor-perspective.ui
index ceda61d..55bb06b 100644
--- a/data/ui/ide-editor-perspective.ui
+++ b/data/ui/ide-editor-perspective.ui
@@ -2,69 +2,61 @@
 <interface>
   <!-- interface-requires gtk+ 3.18 -->
   <template class="IdeEditorPerspective" parent="IdeLayout">
-    <child internal-child="content_pane">
-      <object class="IdeLayoutPane">
-        <child internal-child="header">
+    <child type="center">
+      <object class="GtkStack" id="content_stack">
+        <property name="visible">true</property>
+        <child>
           <object class="GtkBox">
-            <property name="visible">false</property>
-          </object>
-        </child>
-        <child internal-child="stack">
-          <object class="GtkStack" id="content_stack">
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">12</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkImage">
+                <property name="icon-name">text-editor-symbolic</property>
+                <property name="pixel-size">128</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+            </child>
             <child>
-              <object class="GtkBox">
-                <property name="halign">center</property>
-                <property name="valign">center</property>
-                <property name="orientation">vertical</property>
-                <property name="spacing">12</property>
+              <object class="GtkLabel">
+                <property name="label" translatable="yes">No open files</property>
                 <property name="visible">true</property>
-                <child>
-                  <object class="GtkImage">
-                    <property name="icon-name">text-editor-symbolic</property>
-                    <property name="pixel-size">128</property>
-                    <property name="visible">true</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="label" translatable="yes">No open files</property>
-                    <property name="visible">true</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
-                    <attributes>
-                      <attribute name="scale" value="2.0"/>
-                      <attribute name="weight" value="bold"/>
-                    </attributes>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="label" translatable="yes">Try opening a file by typing in the search box 
at the top</property>
-                    <property name="wrap">true</property>
-                    <property name="visible">true</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
-                  </object>
-                </child>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+                <attributes>
+                  <attribute name="scale" value="2.0"/>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
               </object>
-              <packing>
-                <property name="name">empty_state</property>
-              </packing>
             </child>
             <child>
-              <object class="IdeLayoutGrid" id="grid">
+              <object class="GtkLabel">
+                <property name="label" translatable="yes">Try opening a file by typing in the search box at 
the top</property>
+                <property name="wrap">true</property>
                 <property name="visible">true</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
               </object>
-              <packing>
-                <property name="name">grid</property>
-              </packing>
             </child>
           </object>
+          <packing>
+            <property name="name">empty_state</property>
+          </packing>
+        </child>
+        <child>
+          <object class="IdeLayoutGrid" id="grid">
+            <property name="visible">true</property>
+          </object>
+          <packing>
+            <property name="name">grid</property>
+          </packing>
         </child>
       </object>
     </child>
@@ -80,7 +72,7 @@
         </style>
         <child>
           <object class="GtkToggleButton">
-            <property name="action-name">panels.left</property>
+            <property name="action-name">dockbin.left-visible</property>
             <property name="tooltip-text" translatable="yes">Enable / Disable left panel. Shortcut: 
F9</property>
             <property name="focus-on-click">false</property>
             <property name="visible">true</property>
@@ -97,7 +89,7 @@
         </child>
         <child>
           <object class="GtkToggleButton">
-            <property name="action-name">panels.bottom</property>
+            <property name="action-name">dockbin.bottom-visible</property>
             <property name="tooltip-text" translatable="yes">Enable / Disable bottom panel. Shortcut: Ctrl + 
F9</property>
             <property name="focus-on-click">false</property>
             <property name="visible">true</property>
@@ -114,7 +106,7 @@
         </child>
         <child>
           <object class="GtkToggleButton">
-            <property name="action-name">panels.right</property>
+            <property name="action-name">dockbin.right-visible</property>
             <property name="tooltip-text" translatable="yes">Enable / Disable right panel. Shortcut: Shift + 
F9</property>
             <property name="focus-on-click">false</property>
             <property name="visible">true</property>
diff --git a/data/ui/ide-layout-pane.ui b/data/ui/ide-layout-pane.ui
index 7b7f3ea..ddf495e 100644
--- a/data/ui/ide-layout-pane.ui
+++ b/data/ui/ide-layout-pane.ui
@@ -1,32 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <!-- interface-requires gtk+ 3.16 -->
-  <template class="IdeLayoutPane" parent="GtkBin">
+  <template class="IdeLayoutPane" parent="PnlDockBinEdge">
     <child>
-      <object class="GtkBox" id="box">
-        <property name="orientation">vertical</property>
+      <object class="PnlDockStack" id="dock_stack">
+        <property name="expand">true</property>
         <property name="visible">true</property>
-        <child>
-          <object class="GtkBox" id="header">
-            <property name="visible">true</property>
-            <property name="vexpand">false</property>
-            <child type="center">
-              <object class="GtkStackSwitcher" id="stack_switcher">
-                <property name="stack">stack</property>
-                <property name="visible">true</property>
-              </object>
-            </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkStack" id="stack">
-            <property name="transition-duration">150</property>
-            <property name="transition-type">crossfade</property>
-            <property name="expand">true</property>
-            <property name="homogeneous">false</property>
-            <property name="visible">true</property>
-          </object>
-        </child>
       </object>
     </child>
   </template>
diff --git a/doc/reference/libide/libide-sections.txt b/doc/reference/libide/libide-sections.txt
index 49f5e6d..168dafc 100644
--- a/doc/reference/libide/libide-sections.txt
+++ b/doc/reference/libide/libide-sections.txt
@@ -976,10 +976,6 @@ ide_language_defaults_init_finish
 IDE_TYPE_LAYOUT
 IdeLayoutClass
 ide_layout_new
-ide_layout_get_left_pane
-ide_layout_get_right_pane
-ide_layout_get_bottom_pane
-ide_layout_get_content_pane
 ide_layout_get_active_view
 IdeLayout
 </SECTION>
diff --git a/libide/Makefile.am b/libide/Makefile.am
index bf6145b..4c204dc 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -446,9 +446,11 @@ libide_1_0_la_includes = \
        -I$(top_srcdir)/contrib/egg \
        -I$(top_srcdir)/contrib/gd \
        -I$(top_srcdir)/contrib/nautilus \
+       -I$(top_srcdir)/contrib/pnl \
        -I$(top_srcdir)/contrib/search \
        -I$(top_srcdir)/contrib/tmpl \
        -I$(top_srcdir)/contrib/xml \
+       -I$(top_builddir)/contrib/pnl \
        -I$(top_builddir)/contrib/tmpl \
        -I$(top_builddir)/data/icons/hicolor \
        -I$(srcdir) \
@@ -500,6 +502,7 @@ libide_1_0_la_LIBADD = \
        $(top_builddir)/contrib/egg/libegg-private.la \
        $(top_builddir)/contrib/gd/libgd.la \
        $(top_builddir)/contrib/nautilus/libnautilus.la \
+       $(top_builddir)/contrib/pnl/libpanel-gtk.la \
        $(top_builddir)/contrib/search/libsearch.la \
        $(top_builddir)/contrib/tmpl/libtemplate-glib-1.0.la \
        $(top_builddir)/contrib/xml/libxml.la \
@@ -577,6 +580,7 @@ INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) --warn-all
 INTROSPECTION_COMPILER_ARGS = \
        --includedir=$(srcdir) \
        --includedir=$(top_builddir)/contrib/egg \
+       --includedir=$(top_builddir)/contrib/pnl \
        --includedir=$(top_builddir)/contrib/tmpl
 
 introspection_sources = \
@@ -596,6 +600,7 @@ Ide_1_0_gir_LIBS = \
 Ide_1_0_gir_FILES = $(introspection_sources)
 Ide_1_0_gir_SCANNERFLAGS = \
        --include-uninstalled=$(top_builddir)/contrib/egg/Egg-1.0.gir \
+       --include-uninstalled=$(top_builddir)/contrib/pnl/Pnl-1.0.gir \
        --include-uninstalled=$(top_builddir)/contrib/tmpl/Template-1.0.gir \
        --c-include="ide.h" \
        -n Ide \
@@ -624,15 +629,18 @@ libide_1_0_vapi_DEPS = \
        gtk+-3.0 \
        gtksourceview-3.0 \
        libpeas-1.0 \
+       panel-gtk \
        template-glib-1.0 \
        egg-private
 libide_1_0_vapi_METADATADIRS = $(srcdir)
 libide_1_0_vapi_FILES = Ide-1.0.gir
 libide_1_0_vapi_VAPIDIRS = \
        $(top_builddir)/contrib/egg \
+       $(top_builddir)/contrib/pnl \
        $(top_builddir)/contrib/tmpl
 libide_1_0_vapi_GIRDIRS = \
        $(top_builddir)/contrib/egg \
+       $(top_builddir)/contrib/pnl \
        $(top_builddir)/contrib/tmpl
 
 libide-1.0.deps: Makefile
diff --git a/libide/editor/ide-editor-perspective.c b/libide/editor/ide-editor-perspective.c
index e81dedf..71ee5f6 100644
--- a/libide/editor/ide-editor-perspective.c
+++ b/libide/editor/ide-editor-perspective.c
@@ -76,29 +76,23 @@ ide_editor_perspective_restore_panel_state (IdeEditorPerspective *self)
 
   settings = g_settings_new ("org.gnome.builder.workbench");
 
-  pane = ide_layout_get_left_pane (IDE_LAYOUT (self));
+  pane = pnl_dock_bin_get_left_edge (PNL_DOCK_BIN (self));
   reveal = g_settings_get_boolean (settings, "left-visible");
   position = g_settings_get_int (settings, "left-position");
-  gtk_container_child_set (GTK_CONTAINER (self), pane,
-                           "position", position,
-                           "reveal", reveal,
-                           NULL);
+  pnl_dock_revealer_set_reveal_child (PNL_DOCK_REVEALER (pane), reveal);
+  pnl_dock_revealer_set_position (PNL_DOCK_REVEALER (pane), position);
 
-  pane = ide_layout_get_right_pane (IDE_LAYOUT (self));
+  pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (self));
   reveal = g_settings_get_boolean (settings, "right-visible");
   position = g_settings_get_int (settings, "right-position");
-  gtk_container_child_set (GTK_CONTAINER (self), pane,
-                           "position", position,
-                           "reveal", reveal,
-                           NULL);
+  pnl_dock_revealer_set_reveal_child (PNL_DOCK_REVEALER (pane), reveal);
+  pnl_dock_revealer_set_position (PNL_DOCK_REVEALER (pane), position);
 
-  pane = ide_layout_get_bottom_pane (IDE_LAYOUT (self));
+  pane = pnl_dock_bin_get_bottom_edge (PNL_DOCK_BIN (self));
   reveal = g_settings_get_boolean (settings, "bottom-visible");
   position = g_settings_get_int (settings, "bottom-position");
-  gtk_container_child_set (GTK_CONTAINER (self), pane,
-                           "position", position,
-                           "reveal", reveal,
-                           NULL);
+  pnl_dock_revealer_set_reveal_child (PNL_DOCK_REVEALER (pane), reveal);
+  pnl_dock_revealer_set_position (PNL_DOCK_REVEALER (pane), position);
 }
 
 static void
@@ -113,27 +107,21 @@ ide_editor_perspective_save_panel_state (IdeEditorPerspective *self)
 
   settings = g_settings_new ("org.gnome.builder.workbench");
 
-  pane = ide_layout_get_left_pane (IDE_LAYOUT (self));
-  gtk_container_child_get (GTK_CONTAINER (IDE_LAYOUT (self)), pane,
-                           "reveal", &reveal,
-                           "position", &position,
-                           NULL);
+  pane = pnl_dock_bin_get_left_edge (PNL_DOCK_BIN (self));
+  position = pnl_dock_revealer_get_position (PNL_DOCK_REVEALER (pane));
+  reveal = pnl_dock_revealer_get_reveal_child (PNL_DOCK_REVEALER (pane));
   g_settings_set_boolean (settings, "left-visible", reveal);
   g_settings_set_int (settings, "left-position", position);
 
-  pane = ide_layout_get_right_pane (IDE_LAYOUT (self));
-  gtk_container_child_get (GTK_CONTAINER (IDE_LAYOUT (self)), pane,
-                           "reveal", &reveal,
-                           "position", &position,
-                           NULL);
+  pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (self));
+  position = pnl_dock_revealer_get_position (PNL_DOCK_REVEALER (pane));
+  reveal = pnl_dock_revealer_get_reveal_child (PNL_DOCK_REVEALER (pane));
   g_settings_set_boolean (settings, "right-visible", reveal);
   g_settings_set_int (settings, "right-position", position);
 
-  pane = ide_layout_get_bottom_pane (IDE_LAYOUT (self));
-  gtk_container_child_get (GTK_CONTAINER (IDE_LAYOUT (self)), pane,
-                           "reveal", &reveal,
-                           "position", &position,
-                           NULL);
+  pane = pnl_dock_bin_get_bottom_edge (PNL_DOCK_BIN (self));
+  position = pnl_dock_revealer_get_position (PNL_DOCK_REVEALER (pane));
+  reveal = pnl_dock_revealer_get_reveal_child (PNL_DOCK_REVEALER (pane));
   g_settings_set_boolean (settings, "bottom-visible", reveal);
   g_settings_set_int (settings, "bottom-position", position);
 }
@@ -441,8 +429,8 @@ ide_editor_perspective_init (IdeEditorPerspective *self)
   g_action_map_add_action_entries (G_ACTION_MAP (self->actions), entries,
                                    G_N_ELEMENTS (entries), self);
 
-  actions = gtk_widget_get_action_group (GTK_WIDGET (self), "panels");
-  gtk_widget_insert_action_group (GTK_WIDGET (self->titlebar), "panels", actions);
+  actions = gtk_widget_get_action_group (GTK_WIDGET (self), "dockbin");
+  gtk_widget_insert_action_group (GTK_WIDGET (self->titlebar), "dockbin", actions);
 
   ide_editor_perspective_restore_panel_state (self);
 
diff --git a/libide/editor/ide-editor-workbench-addin.c b/libide/editor/ide-editor-workbench-addin.c
index e394322..fc1da1e 100644
--- a/libide/editor/ide-editor-workbench-addin.c
+++ b/libide/editor/ide-editor-workbench-addin.c
@@ -33,6 +33,7 @@
 struct _IdeEditorWorkbenchAddin
 {
   GObject               parent_instance;
+  PnlDockManager       *manager;
   IdeEditorPerspective *perspective;
   IdeWorkbench         *workbench;
 };
@@ -65,7 +66,10 @@ ide_editor_workbench_addin_load (IdeWorkbenchAddin *addin,
 
   self->workbench = workbench;
 
+  self->manager = pnl_dock_manager_new ();
+
   self->perspective = g_object_new (IDE_TYPE_EDITOR_PERSPECTIVE,
+                                    "manager", self->manager,
                                     "visible", TRUE,
                                     NULL);
   ide_workbench_add_perspective (workbench, IDE_PERSPECTIVE (self->perspective));
@@ -90,6 +94,8 @@ ide_editor_workbench_addin_unload (IdeWorkbenchAddin *addin,
   self->workbench = NULL;
 
   ide_workbench_remove_perspective (workbench, perspective);
+
+  g_clear_object (&self->manager);
 }
 
 static gboolean
diff --git a/libide/ide-application-actions.c b/libide/ide-application-actions.c
index 6f92ab6..e9cd7b0 100644
--- a/libide/ide-application-actions.c
+++ b/libide/ide-application-actions.c
@@ -309,9 +309,9 @@ ide_application_actions_init (IdeApplication *self)
   /*
    * FIXME: Once we get a new shortcuts engine, port these to that.
    */
-  gtk_application_set_accels_for_action (GTK_APPLICATION (self), "panels.left", left);
-  gtk_application_set_accels_for_action (GTK_APPLICATION (self), "panels.right", right);
-  gtk_application_set_accels_for_action (GTK_APPLICATION (self), "panels.bottom", bottom);
+  gtk_application_set_accels_for_action (GTK_APPLICATION (self), "dockbin.left-visible", left);
+  gtk_application_set_accels_for_action (GTK_APPLICATION (self), "dockbin.right-visible", right);
+  gtk_application_set_accels_for_action (GTK_APPLICATION (self), "dockbin.bottom-visible", bottom);
   gtk_application_set_accels_for_action (GTK_APPLICATION (self), "app.preferences", preferences);
   gtk_application_set_accels_for_action (GTK_APPLICATION (self), "perspective.global-search", global_search);
   gtk_application_set_accels_for_action (GTK_APPLICATION (self), "perspective.new-file", new_file);
diff --git a/libide/ide-layout-pane.c b/libide/ide-layout-pane.c
index fb54f07..0d51d78 100644
--- a/libide/ide-layout-pane.c
+++ b/libide/ide-layout-pane.c
@@ -27,154 +27,29 @@
 
 struct _IdeLayoutPane
 {
-  GtkBin            parent_instance;
-
-  GtkBox           *box;
-  GtkBox           *header;
-  GtkStackSwitcher *stack_switcher;
-  GtkStack         *stack;
+  PnlDockBinEdge    parent_instance;
 
   EggSignalGroup   *toplevel_signals;
 
-  gulong            notify_stack_signal_handler;
-
-  GdkRectangle      handle_pos;
-
-  GtkPositionType   position;
+  PnlDockStack     *dock_stack;
 };
 
-G_DEFINE_TYPE (IdeLayoutPane, ide_layout_pane, GTK_TYPE_BIN)
-
-enum {
-  PROP_0,
-  PROP_POSITION,
-  LAST_PROP
-};
-
-enum {
-  STYLE_PROP_0,
-  STYLE_PROP_HANDLE_SIZE,
-  LAST_STYLE_PROP
-};
-
-static GParamSpec *properties [LAST_PROP];
-static GParamSpec *styleParamSpecs [LAST_STYLE_PROP];
-
-static gboolean
-ide_layout_pane_draw (GtkWidget *widget,
-                      cairo_t   *cr)
-{
-  IdeLayoutPane *self = (IdeLayoutPane *)widget;
-  GtkStyleContext *style_context;
-  gboolean ret;
-
-  g_assert (IDE_IS_LAYOUT_PANE (self));
-  g_assert (cr != NULL);
-
-  ret = GTK_WIDGET_CLASS (ide_layout_pane_parent_class)->draw (widget, cr);
-
-  style_context = gtk_widget_get_style_context (widget);
-
-  gtk_style_context_save (style_context);
-  gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_PANE_SEPARATOR);
-  gtk_render_handle (style_context, cr,
-                     self->handle_pos.x,
-                     self->handle_pos.y,
-                     self->handle_pos.width,
-                     self->handle_pos.height);
-  gtk_style_context_restore (style_context);
-
-  return ret;
-}
+G_DEFINE_TYPE (IdeLayoutPane, ide_layout_pane, PNL_TYPE_DOCK_BIN_EDGE)
 
 static void
-ide_layout_pane_size_allocate (GtkWidget     *widget,
-                               GtkAllocation *alloc)
+ide_layout_pane_add (GtkContainer *container,
+                     GtkWidget    *widget)
 {
-  IdeLayoutPane *self = (IdeLayoutPane *)widget;
-  GtkWidget *child;
-  GtkAllocation child_alloc;
-  gint handle_size;
+  IdeLayoutPane *self = (IdeLayoutPane *)container;
 
   g_assert (IDE_IS_LAYOUT_PANE (self));
 
-  gtk_widget_set_allocation (widget, alloc);
-
-  child = gtk_bin_get_child (GTK_BIN (self));
-  if (child == NULL || !gtk_widget_get_visible (child))
-    return;
-
-  gtk_widget_style_get (widget, "handle-size", &handle_size, NULL);
-
-  child_alloc = *alloc;
-
-  switch (self->position)
-    {
-    case GTK_POS_LEFT:
-      child_alloc.width -= handle_size;
-      self->handle_pos.x = child_alloc.x + child_alloc.width;
-      self->handle_pos.width = handle_size;
-      self->handle_pos.height = child_alloc.height;
-      self->handle_pos.y = child_alloc.y;
-      break;
-
-    case GTK_POS_RIGHT:
-      child_alloc.x += handle_size;
-      child_alloc.width -= handle_size;
-      self->handle_pos.x = alloc->x;
-      self->handle_pos.width = handle_size;
-      self->handle_pos.height = child_alloc.height;
-      self->handle_pos.y = child_alloc.y;
-      break;
-
-    case GTK_POS_BOTTOM:
-      child_alloc.y += handle_size;
-      child_alloc.height -= handle_size;
-      self->handle_pos.x = alloc->x;
-      self->handle_pos.width = alloc->width;
-      self->handle_pos.height = handle_size;
-      self->handle_pos.y = alloc->y;
-      break;
-
-    case GTK_POS_TOP:
-      self->handle_pos.x = 0;
-      self->handle_pos.y = 0;
-      self->handle_pos.width = 0;
-      self->handle_pos.height = 0;
-      break;
-
-    default:
-      break;
-    }
-
-  gtk_widget_size_allocate (child, &child_alloc);
-}
-
-static void
-ide_layout_pane_grab_focus (GtkWidget *widget)
-{
-  IdeLayoutPane *self= (IdeLayoutPane *)widget;
-  GtkWidget *child;
-
-  child = gtk_stack_get_visible_child (self->stack);
-  if (child != NULL)
-    gtk_widget_grab_focus (child);
-}
-
-static void
-on_stack_changed (GtkWidget *widget)
-{
-  GtkWidget *child;
-
-  g_assert (GTK_IS_WIDGET (widget));
-
-  child = gtk_stack_get_visible_child (GTK_STACK (widget));
-
-  if (child != NULL)
-    gtk_widget_grab_focus (child);
+  if (PNL_IS_DOCK_WIDGET (widget))
+    gtk_container_add (GTK_CONTAINER (self->dock_stack), widget);
+  else
+    GTK_CONTAINER_CLASS (ide_layout_pane_parent_class)->add (container, widget);
 }
 
-
 static void
 workbench_focus_changed (GtkWidget     *toplevel,
                          GtkWidget     *focus,
@@ -219,10 +94,6 @@ ide_layout_pane_hierarchy_changed (GtkWidget *widget,
     toplevel = NULL;
 
   egg_signal_group_set_target (self->toplevel_signals, toplevel);
-
-  if (IDE_IS_WORKBENCH (toplevel) && gtk_widget_get_visible (GTK_WIDGET (self->stack_switcher)))
-    gtk_size_group_add_widget (IDE_WORKBENCH (toplevel)->header_size_group,
-                               GTK_WIDGET (self->stack_switcher));
 }
 
 static void
@@ -230,116 +101,28 @@ ide_layout_pane_dispose (GObject *object)
 {
   IdeLayoutPane *self = (IdeLayoutPane *)object;
 
-  if (self->notify_stack_signal_handler > 0 && GTK_IS_STACK (self->stack))
-    ide_clear_signal_handler (self->stack, &self->notify_stack_signal_handler);
-
   g_clear_object (&self->toplevel_signals);
 
   G_OBJECT_CLASS (ide_layout_pane_parent_class)->dispose (object);
 }
 
 static void
-ide_layout_pane_finalize (GObject *object)
-{
-  IdeLayoutPane *self = (IdeLayoutPane *)object;
-
-  self->stack = NULL;
-  self->stack_switcher = NULL;
-
-  G_OBJECT_CLASS (ide_layout_pane_parent_class)->finalize (object);
-}
-
-static void
-ide_layout_pane_get_property (GObject    *object,
-                              guint       prop_id,
-                              GValue     *value,
-                              GParamSpec *pspec)
-{
-  IdeLayoutPane *self = IDE_LAYOUT_PANE (object);
-
-  switch (prop_id)
-    {
-    case PROP_POSITION:
-      g_value_set_enum (value, ide_layout_pane_get_position (self));
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
-static void
-ide_layout_pane_set_property (GObject      *object,
-                              guint         prop_id,
-                              const GValue *value,
-                              GParamSpec   *pspec)
-{
-  IdeLayoutPane *self = IDE_LAYOUT_PANE (object);
-
-  switch (prop_id)
-    {
-    case PROP_POSITION:
-      ide_layout_pane_set_position (self, g_value_get_enum (value));
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
-static void
 ide_layout_pane_class_init (IdeLayoutPaneClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
 
   object_class->dispose = ide_layout_pane_dispose;
-  object_class->finalize = ide_layout_pane_finalize;
-  object_class->get_property = ide_layout_pane_get_property;
-  object_class->set_property = ide_layout_pane_set_property;
 
-  widget_class->draw = ide_layout_pane_draw;
-  widget_class->grab_focus = ide_layout_pane_grab_focus;
   widget_class->hierarchy_changed = ide_layout_pane_hierarchy_changed;
-  widget_class->size_allocate = ide_layout_pane_size_allocate;
-
-  gtk_widget_class_set_css_name (widget_class, "layoutpane");
-
-  /**
-   * IdeLayoutPane:position:
-   *
-   * The position at which to place the pane. This also dictates which
-   * direction that animations will occur.
-   *
-   * For example, setting to %GTK_POS_LEFT will result in the resize grip
-   * being placed on the right, and animations to and from the leftmost
-   * of the allocation.
-   */
-  properties [PROP_POSITION] =
-    g_param_spec_enum ("position",
-                       "Position",
-                       "The position of the pane.",
-                       GTK_TYPE_POSITION_TYPE,
-                       GTK_POS_LEFT,
-                       (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
-  g_object_class_install_properties (object_class, LAST_PROP, properties);
+  container_class->add = ide_layout_pane_add;
 
-  styleParamSpecs [STYLE_PROP_HANDLE_SIZE] =
-    g_param_spec_int ("handle-size",
-                      "Handle Size",
-                      "Width of handle.",
-                      0, G_MAXINT,
-                      1,
-                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-  gtk_widget_class_install_style_property (widget_class,
-                                           styleParamSpecs [STYLE_PROP_HANDLE_SIZE]);
+  gtk_widget_class_set_css_name (widget_class, "layoutpane");
 
   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/ide-layout-pane.ui");
-  gtk_widget_class_bind_template_child (widget_class, IdeLayoutPane, box);
-  gtk_widget_class_bind_template_child_internal (widget_class, IdeLayoutPane, stack);
-  gtk_widget_class_bind_template_child_internal (widget_class, IdeLayoutPane, header);
-  gtk_widget_class_bind_template_child_internal (widget_class, IdeLayoutPane, stack_switcher);
+  gtk_widget_class_bind_template_child (widget_class, IdeLayoutPane, dock_stack);
 }
 
 static void
@@ -353,71 +136,4 @@ ide_layout_pane_init (IdeLayoutPane *self)
                                    G_CONNECT_AFTER);
 
   gtk_widget_init_template (GTK_WIDGET (self));
-
-  self->notify_stack_signal_handler = g_signal_connect (self->stack,
-                                                        "notify::visible-child",
-                                                        G_CALLBACK (on_stack_changed),
-                                                        NULL);
-}
-
-GtkWidget *
-ide_layout_pane_new (void)
-{
-  return g_object_new (IDE_TYPE_LAYOUT_PANE, NULL);
-}
-
-GtkPositionType
-ide_layout_pane_get_position (IdeLayoutPane *self)
-{
-  g_return_val_if_fail (IDE_IS_LAYOUT_PANE (self), GTK_POS_LEFT);
-
-  return self->position;
-}
-
-void
-ide_layout_pane_set_position (IdeLayoutPane   *self,
-                              GtkPositionType  position)
-{
-  g_return_if_fail (IDE_IS_LAYOUT_PANE (self));
-  g_return_if_fail (position >= GTK_POS_LEFT);
-  g_return_if_fail (position <= GTK_POS_BOTTOM);
-
-  if (position != self->position)
-    {
-      self->position = position;
-      gtk_widget_queue_resize (GTK_WIDGET (self));
-      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_POSITION]);
-    }
-}
-
-/**
- * ide_layout_pane_add_page:
- * @self: An #IdeLayoutPane
- * @page: A #GtkWidget
- * @title: the title for the page
- * @icon_name: (nullable): the icon name
- *
- */
-void
-ide_layout_pane_add_page (IdeLayoutPane *self,
-                          GtkWidget     *page,
-                          const gchar   *title,
-                          const gchar   *icon_name)
-{
-  gtk_container_add_with_properties (GTK_CONTAINER (self->stack), page,
-#if 0
-                                     "icon-name", icon_name,
-#endif
-                                     "title", title,
-                                     NULL);
-}
-
-void
-ide_layout_pane_remove_page (IdeLayoutPane *self,
-                             GtkWidget     *page)
-{
-  g_return_if_fail (IDE_IS_LAYOUT_PANE (self));
-  g_return_if_fail (GTK_IS_WIDGET (page));
-
-  gtk_container_remove (GTK_CONTAINER (self->stack), page);
 }
diff --git a/libide/ide-layout-pane.h b/libide/ide-layout-pane.h
index ab944b9..8112e61 100644
--- a/libide/ide-layout-pane.h
+++ b/libide/ide-layout-pane.h
@@ -19,24 +19,13 @@
 #ifndef IDE_LAYOUT_PANE_H
 #define IDE_LAYOUT_PANE_H
 
-#include <gtk/gtk.h>
+#include <pnl.h>
 
 G_BEGIN_DECLS
 
 #define IDE_TYPE_LAYOUT_PANE (ide_layout_pane_get_type())
 
-G_DECLARE_FINAL_TYPE (IdeLayoutPane, ide_layout_pane, IDE, LAYOUT_PANE, GtkBin)
-
-GtkWidget       *ide_layout_pane_new          (void);
-GtkPositionType  ide_layout_pane_get_position (IdeLayoutPane   *self);
-void             ide_layout_pane_set_position (IdeLayoutPane   *self,
-                                               GtkPositionType  position);
-void             ide_layout_pane_add_page     (IdeLayoutPane   *self,
-                                               GtkWidget       *page,
-                                               const gchar     *title,
-                                               const gchar     *icon_name);
-void             ide_layout_pane_remove_page  (IdeLayoutPane   *self,
-                                               GtkWidget       *page);
+G_DECLARE_FINAL_TYPE (IdeLayoutPane, ide_layout_pane, IDE, LAYOUT_PANE, PnlDockBinEdge)
 
 G_END_DECLS
 
diff --git a/libide/ide-layout-stack.c b/libide/ide-layout-stack.c
index 3d25c61..8c46988 100644
--- a/libide/ide-layout-stack.c
+++ b/libide/ide-layout-stack.c
@@ -442,6 +442,7 @@ static void
 ide_layout_stack_init (IdeLayoutStack *self)
 {
   GtkStyleContext *context;
+  GList *focus_chain = NULL;
 
   gtk_widget_init_template (GTK_WIDGET (self));
 
@@ -463,6 +464,11 @@ ide_layout_stack_init (IdeLayoutStack *self)
                            G_CONNECT_SWAPPED);
 
   ide_widget_set_context_handler (self, ide_layout_stack_context_handler);
+
+  focus_chain = g_list_prepend (focus_chain, self->tab_bar);
+  focus_chain = g_list_prepend (focus_chain, self->stack);
+  gtk_container_set_focus_chain (GTK_CONTAINER (self), focus_chain);
+  g_list_free (focus_chain);
 }
 
 GtkWidget *
diff --git a/libide/ide-layout.c b/libide/ide-layout.c
index c530293..3fd16e8 100644
--- a/libide/ide-layout.c
+++ b/libide/ide-layout.c
@@ -16,1005 +16,26 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <egg-animation.h>
-#include <glib/gi18n.h>
-#include <ide.h>
-#include <string.h>
-
 #include "ide-layout.h"
 #include "ide-layout-pane.h"
-
-#define ANIMATION_MODE     EGG_ANIMATION_EASE_IN_OUT_QUAD
-#define ANIMATION_DURATION 250
-#define HORIZ_GRIP_EXTRA   5
-#define VERT_GRIP_EXTRA    5
-#define MIN_POSITION       100
-
-typedef struct
-{
-  GtkWidget       *widget;
-  GtkAdjustment   *adjustment;
-  EggAnimation    *animation;
-  GdkWindow       *handle;
-  GtkAllocation    handle_pos;
-  GtkAllocation    alloc;
-  gint             min_width;
-  gint             min_height;
-  gint             nat_width;
-  gint             nat_height;
-  gint             position;
-  gint             restore_position;
-  GdkCursorType    cursor_type;
-  GtkPositionType  type : 4;
-  guint            reveal : 1;
-  guint            hiding : 1;
-  guint            showing : 1;
-} IdeLayoutChild;
+#include "ide-layout-view.h"
 
 typedef struct
 {
-  IdeLayoutChild    children[4];
+  GtkWidget *active_view;
 
-  GActionMap       *actions;
-  GtkGesture       *pan_gesture;
-  IdeLayoutChild   *drag_child;
-  gdouble           drag_position;
-
-  GtkWidget        *active_view;
-  gulong            focus_handler;
+  gulong     focus_handler;
 } IdeLayoutPrivate;
 
-static void buildable_init_iface (GtkBuildableIface *iface);
-static const gchar *action_names[] = { "left", "right", NULL, "bottom" };
-
-G_DEFINE_TYPE_WITH_CODE (IdeLayout, ide_layout, GTK_TYPE_OVERLAY,
-                         G_ADD_PRIVATE (IdeLayout)
-                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_init_iface))
+G_DEFINE_TYPE_WITH_PRIVATE (IdeLayout, ide_layout, PNL_TYPE_DOCK_BIN)
 
 enum {
   PROP_0,
   PROP_ACTIVE_VIEW,
-  PROP_BOTTOM_PANE,
-  PROP_CONTENT_PANE,
-  PROP_LEFT_PANE,
-  PROP_RIGHT_PANE,
   LAST_PROP
 };
 
-enum {
-  CHILD_PROP_0,
-  CHILD_PROP_REVEAL,
-  CHILD_PROP_POSITION,
-  LAST_CHILD_PROP
-};
-
-static GtkBuildableIface *ide_layout_parent_buildable_iface;
-static GParamSpec        *properties [LAST_PROP];
-static GParamSpec        *child_properties [LAST_CHILD_PROP];
-
-static void
-ide_layout_move_resize_handle (IdeLayout       *self,
-                               GtkPositionType  type)
-{
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-  IdeLayoutChild *child;
-  GtkAllocation alloc;
-
-  g_assert (IDE_IS_LAYOUT (self));
-  g_assert ((type == GTK_POS_LEFT) ||
-            (type == GTK_POS_RIGHT) ||
-            (type == GTK_POS_BOTTOM));
-
-  child = &priv->children [type];
-
-  if (child->handle == NULL)
-    return;
-
-  gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
-
-  switch (type)
-    {
-    case GTK_POS_LEFT:
-      child->handle_pos.x = alloc.x + child->alloc.x + child->alloc.width - HORIZ_GRIP_EXTRA;
-      child->handle_pos.y = alloc.y + child->alloc.y;
-      child->handle_pos.width = 2 * HORIZ_GRIP_EXTRA;
-      child->handle_pos.height = child->alloc.height;
-      break;
-
-    case GTK_POS_RIGHT:
-      child->handle_pos.x = alloc.x + child->alloc.x - HORIZ_GRIP_EXTRA;
-      child->handle_pos.y = alloc.y + child->alloc.y;
-      child->handle_pos.width = 2 * HORIZ_GRIP_EXTRA;
-      child->handle_pos.height = child->alloc.height;
-      break;
-
-    case GTK_POS_BOTTOM:
-      child->handle_pos.x = alloc.x + child->alloc.x;
-      child->handle_pos.y = alloc.y + child->alloc.y - VERT_GRIP_EXTRA;
-      child->handle_pos.width = child->alloc.width;
-      child->handle_pos.height = 2 * VERT_GRIP_EXTRA;
-      break;
-
-    case GTK_POS_TOP:
-    default:
-      break;
-    }
-
-  if (!gtk_widget_get_child_visible (child->widget))
-    memset (&child->handle_pos, 0, sizeof child->handle_pos);
-
-  if (gtk_widget_get_mapped (GTK_WIDGET (self)))
-    gdk_window_move_resize (child->handle,
-                            child->handle_pos.x,
-                            child->handle_pos.y,
-                            child->handle_pos.width,
-                            child->handle_pos.height);
-}
-
-static void
-ide_layout_create_handle_window (IdeLayout       *self,
-                                 GtkPositionType  type)
-{
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-  IdeLayoutChild *child;
-  GtkAllocation alloc;
-  GdkWindowAttr attributes = { 0 };
-  GdkWindow *parent;
-  GdkDisplay *display;
-
-  g_assert (IDE_IS_LAYOUT (self));
-  g_assert ((type == GTK_POS_LEFT) ||
-            (type == GTK_POS_RIGHT) ||
-            (type == GTK_POS_BOTTOM));
-
-  display = gtk_widget_get_display (GTK_WIDGET (self));
-  parent = gtk_widget_get_window (GTK_WIDGET (self));
-
-  g_assert (GDK_IS_DISPLAY (display));
-  g_assert (GDK_IS_WINDOW (parent));
-
-  gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
-
-  child = &priv->children [type];
-
-  attributes.window_type = GDK_WINDOW_CHILD;
-  attributes.wclass = GDK_INPUT_ONLY;
-  attributes.x = child->handle_pos.x;
-  attributes.y = child->handle_pos.y;
-  attributes.width = child->handle_pos.width;
-  attributes.height = child->handle_pos.height;
-  attributes.visual = gtk_widget_get_visual (GTK_WIDGET (self));
-  attributes.event_mask = gtk_widget_get_events (GTK_WIDGET (self));
-  attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
-                            GDK_BUTTON_RELEASE_MASK |
-                            GDK_ENTER_NOTIFY_MASK |
-                            GDK_LEAVE_NOTIFY_MASK |
-                            GDK_POINTER_MOTION_MASK);
-  attributes.cursor = gdk_cursor_new_for_display (display, child->cursor_type);
-
-  child->handle = gdk_window_new (parent, &attributes, (GDK_WA_CURSOR | GDK_WA_X | GDK_WA_Y));
-  gtk_widget_register_window (GTK_WIDGET (self), child->handle);
-
-  g_clear_object (&attributes.cursor);
-}
-
-static void
-ide_layout_destroy_handle_window (IdeLayout       *self,
-                                  GtkPositionType  type)
-{
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-  IdeLayoutChild *child;
-
-  g_assert (IDE_IS_LAYOUT (self));
-
-  child = &priv->children [type];
-
-  if (child->handle)
-    {
-      gdk_window_hide (child->handle);
-      gtk_widget_unregister_window (GTK_WIDGET (self), child->handle);
-      gdk_window_destroy (child->handle);
-      child->handle = NULL;
-    }
-}
-
-static void
-ide_layout_child_get_preferred_size (IdeLayoutChild      *child,
-                                     const GtkAllocation *alloc)
-{
-  GtkRequisition min_req = { 0 };
-  GtkRequisition nat_req = { 0 };
-
-  g_assert (child != NULL);
-  g_assert (child->widget != NULL);
-  g_assert (alloc != NULL);
-
-  gtk_widget_get_preferred_size (child->widget, &min_req, &nat_req);
-
-  child->min_height = min_req.height;
-  child->nat_height = nat_req.height;
-  child->min_width = min_req.width;
-  child->nat_width = nat_req.width;
-}
-
-static void
-ide_layout_relayout (IdeLayout           *self,
-                     const GtkAllocation *alloc)
-{
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-  IdeLayoutChild *left;
-  IdeLayoutChild *right;
-  IdeLayoutChild *content;
-  IdeLayoutChild *bottom;
-
-  g_assert (IDE_IS_LAYOUT (self));
-  g_assert (alloc != NULL);
-
-  left = &priv->children [GTK_POS_LEFT];
-  right = &priv->children [GTK_POS_RIGHT];
-  content = &priv->children [GTK_POS_TOP];
-  bottom = &priv->children [GTK_POS_BOTTOM];
-
-  ide_layout_child_get_preferred_size (left, alloc);
-  ide_layout_child_get_preferred_size (right, alloc);
-  ide_layout_child_get_preferred_size (content, alloc);
-  ide_layout_child_get_preferred_size (bottom, alloc);
-
-  /*
-   * Determine everything as if we are animating in/out or the child is visible.
-   */
-
-  if (left->reveal)
-    {
-      left->alloc.x = 0;
-      left->alloc.y = 0;
-      left->alloc.width = left->position;
-      left->alloc.height = alloc->height;
-
-      left->alloc.x -= gtk_adjustment_get_value (left->adjustment) * left->position;
-    }
-  else
-    {
-      left->alloc.x = -left->position;
-      left->alloc.y = 0;
-      left->alloc.width = left->position;
-      left->alloc.height = alloc->height;
-    }
-
-  if (right->reveal)
-    {
-      right->alloc.x = alloc->width - right->position;
-      right->alloc.y = 0;
-      right->alloc.width = right->position;
-      right->alloc.height = alloc->height;
-
-      right->alloc.x += gtk_adjustment_get_value (right->adjustment) * right->position;
-    }
-  else
-    {
-      right->alloc.x = alloc->width;
-      right->alloc.y = 0;
-      right->alloc.width = right->position;
-      right->alloc.height = alloc->height;
-    }
-
-  if (bottom->reveal)
-    {
-      bottom->alloc.x = left->alloc.x + left->alloc.width;
-      bottom->alloc.y = alloc->height - bottom->position;
-      bottom->alloc.width = right->alloc.x - bottom->alloc.x;
-      bottom->alloc.height = bottom->position;
-
-      bottom->alloc.y += gtk_adjustment_get_value (bottom->adjustment) * bottom->position;
-    }
-  else
-    {
-      bottom->alloc.x = left->alloc.x + left->alloc.width;
-      bottom->alloc.y = alloc->height;
-      bottom->alloc.width = right->alloc.x - bottom->alloc.x;
-      bottom->alloc.height = bottom->position;
-    }
-
-  if (content->reveal)
-    {
-      content->alloc.x = left->alloc.x + left->alloc.width;
-      content->alloc.y = 0;
-      content->alloc.width = right->alloc.x - content->alloc.x;
-      content->alloc.height = bottom->alloc.y;
-
-      content->alloc.y -= gtk_adjustment_get_value (content->adjustment) * content->alloc.height;
-    }
-  else
-    {
-      content->alloc.x = left->alloc.x + left->alloc.width;
-      content->alloc.y = -bottom->alloc.y;
-      content->alloc.width = right->alloc.x - content->alloc.x;
-      content->alloc.height = bottom->alloc.y;
-    }
-
-  /*
-   * Now adjust for child visibility.
-   *
-   * We need to ensure we don't give the non-visible children an allocation
-   * as it will interfere with hit targets.
-   */
-  if (!gtk_widget_get_child_visible (content->widget))
-    memset (&content->alloc, 0, sizeof content->alloc);
-  if (!gtk_widget_get_child_visible (left->widget))
-    memset (&left->alloc, 0, sizeof left->alloc);
-  if (!gtk_widget_get_child_visible (right->widget))
-    memset (&right->alloc, 0, sizeof right->alloc);
-  if (!gtk_widget_get_child_visible (bottom->widget))
-    memset (&bottom->alloc, 0, sizeof bottom->alloc);
-
-  ide_layout_move_resize_handle (self, GTK_POS_LEFT);
-  ide_layout_move_resize_handle (self, GTK_POS_RIGHT);
-  ide_layout_move_resize_handle (self, GTK_POS_BOTTOM);
-}
-
-static void
-ide_layout_size_allocate (GtkWidget     *widget,
-                          GtkAllocation *alloc)
-{
-  IdeLayout *self = (IdeLayout *)widget;
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-  gint i;
-
-  g_assert (IDE_IS_LAYOUT (self));
-  g_assert (alloc != NULL);
-
-  ide_layout_relayout (self, alloc);
-
-  GTK_WIDGET_CLASS (ide_layout_parent_class)->size_allocate (widget, alloc);
-
-  for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
-    {
-      IdeLayoutChild *child = &priv->children [i];
-
-      if ((child->handle != NULL) &&
-          gtk_widget_get_visible (child->widget) &&
-          gtk_widget_get_child_visible (child->widget))
-        gdk_window_raise (child->handle);
-    }
-}
-
-static IdeLayoutChild *
-ide_layout_child_find (IdeLayout *self,
-                       GtkWidget *child)
-{
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-  gint i;
-
-  g_assert (IDE_IS_LAYOUT (self));
-  g_assert (GTK_IS_WIDGET (child));
-
-  for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
-    {
-      IdeLayoutChild *item = &priv->children [i];
-
-      if (item->widget == child)
-        return item;
-    }
-
-  g_warning ("Child of type %s was not found in this IdeLayout.",
-             g_type_name (G_OBJECT_TYPE (child)));
-
-  return NULL;
-}
-
-static void
-ide_layout_animation_cb (gpointer data)
-{
-  g_autoptr(GtkWidget) child = data;
-  GtkWidget *parent;
-  IdeLayout *self;
-  IdeLayoutChild *item;
-
-  g_assert (GTK_IS_WIDGET (child));
-
-  parent = gtk_widget_get_parent (child);
-  if (!IDE_IS_LAYOUT (parent))
-    return;
-
-  self = IDE_LAYOUT (parent);
-
-  item = ide_layout_child_find (self, child);
-  if (item == NULL)
-    return;
-
-  if (item->hiding)
-    {
-      gtk_widget_set_child_visible (item->widget, FALSE);
-      if (item->restore_position > item->position)
-        item->position = item->restore_position;
-    }
-
-  item->showing = FALSE;
-  item->hiding = FALSE;
-  item->reveal = gtk_adjustment_get_value (item->adjustment) == 0.0;
-
-  gtk_widget_queue_allocate (GTK_WIDGET (self));
-
-  gtk_container_child_notify (GTK_CONTAINER (self), child, "reveal");
-}
-
-static gboolean
-ide_layout_get_child_position (GtkOverlay    *overlay,
-                               GtkWidget     *child,
-                               GtkAllocation *alloc)
-{
-  IdeLayout *self = (IdeLayout *)overlay;
-  IdeLayoutChild *item;
-
-  g_assert (IDE_IS_LAYOUT (self));
-  g_assert (GTK_IS_WIDGET (child));
-  g_assert (alloc != NULL);
-
-  if (!(item = ide_layout_child_find (self, child)))
-    return FALSE;
-
-  *alloc = item->alloc;
-
-  return TRUE;
-}
-
-static guint
-ide_layout_child_get_position (IdeLayout *self,
-                               GtkWidget *child)
-{
-  IdeLayoutChild *item;
-
-  g_assert (IDE_IS_LAYOUT (self));
-  g_assert (GTK_IS_WIDGET (child));
-
-  if (!(item = ide_layout_child_find (self, child)))
-    return FALSE;
-
-  return item->position;
-}
-
-static void
-ide_layout_child_set_position (IdeLayout *self,
-                               GtkWidget *child,
-                               guint      position)
-{
-  IdeLayoutChild *item;
-
-  g_assert (IDE_IS_LAYOUT (self));
-  g_assert (GTK_IS_WIDGET (child));
-
-  if (!(item = ide_layout_child_find (self, child)))
-    return;
-
-  item->position = position;
-
-  gtk_widget_queue_allocate (GTK_WIDGET (self));
-
-  gtk_container_child_notify (GTK_CONTAINER (self), child, "position");
-}
-
-static gboolean
-ide_layout_child_get_reveal (IdeLayout *self,
-                             GtkWidget *child)
-{
-  IdeLayoutChild *item;
-
-  g_assert (IDE_IS_LAYOUT (self));
-  g_assert (GTK_IS_WIDGET (child));
-
-  if (!(item = ide_layout_child_find (self, child)))
-    return FALSE;
-
-  return item->reveal;
-}
-
-static void
-ide_layout_child_set_reveal (IdeLayout *self,
-                             GtkWidget *child,
-                             gboolean   reveal)
-{
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-  IdeLayoutChild *item;
-  GdkFrameClock *frame_clock;
-
-  g_assert (IDE_IS_LAYOUT (self));
-  g_assert (GTK_IS_WIDGET (child));
-
-  reveal = !!reveal;
-
-  if (!(item = ide_layout_child_find (self, child)) || (item->reveal == reveal))
-    return;
-
-  if (item->animation != NULL)
-    {
-      egg_animation_stop (item->animation);
-      ide_clear_weak_pointer (&item->animation);
-    }
-
-  item->reveal = TRUE;
-  item->showing = reveal;
-  item->hiding = !reveal;
-
-  if (item->position > MIN_POSITION)
-    {
-      item->restore_position = item->position;
-      gtk_container_child_notify (GTK_CONTAINER (self), item->widget, "position");
-    }
-
-  gtk_widget_set_child_visible (child, TRUE);
-
-  frame_clock = gtk_widget_get_frame_clock (child);
-
-  if (gtk_widget_get_realized (GTK_WIDGET (self)))
-    {
-      item->animation = egg_object_animate_full (item->adjustment,
-                                                 ANIMATION_MODE,
-                                                 ANIMATION_DURATION,
-                                                 frame_clock,
-                                                 ide_layout_animation_cb,
-                                                 g_object_ref (child),
-                                                 "value", reveal ? 0.0 : 1.0,
-                                                 NULL);
-      g_object_add_weak_pointer (G_OBJECT (item->animation), (gpointer *)&item->animation);
-    }
-  else
-    {
-      item->reveal = reveal;
-      gtk_adjustment_set_value (item->adjustment, reveal ? 0.0 : 1.0);
-      gtk_container_child_notify (GTK_CONTAINER (self), item->widget, "reveal");
-    }
-
-  if (action_names [item->type] != NULL)
-    {
-      GAction *action;
-
-      action = g_action_map_lookup_action (priv->actions, action_names [item->type]);
-
-      if (G_IS_SIMPLE_ACTION (action))
-        g_simple_action_set_state (G_SIMPLE_ACTION (action),
-                                   g_variant_new_boolean (reveal));
-    }
-
-  gtk_widget_queue_allocate (GTK_WIDGET (self));
-}
-
-static void
-ide_layout_get_child_property (GtkContainer *container,
-                               GtkWidget    *child,
-                               guint         prop_id,
-                               GValue       *value,
-                               GParamSpec   *pspec)
-{
-  IdeLayout *self = (IdeLayout *)container;
-
-  switch (prop_id)
-    {
-    case CHILD_PROP_REVEAL:
-      g_value_set_boolean (value, ide_layout_child_get_reveal (self, child));
-      break;
-
-    case CHILD_PROP_POSITION:
-      g_value_set_uint (value, ide_layout_child_get_position (self, child));
-      break;
-
-    default:
-      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
-    }
-}
-
-static void
-ide_layout_set_child_property (GtkContainer *container,
-                               GtkWidget    *child,
-                               guint         prop_id,
-                               const GValue *value,
-                               GParamSpec   *pspec)
-{
-  IdeLayout *self = (IdeLayout *)container;
-
-  switch (prop_id)
-    {
-    case CHILD_PROP_REVEAL:
-      ide_layout_child_set_reveal (self, child, g_value_get_boolean (value));
-      break;
-
-    case CHILD_PROP_POSITION:
-      ide_layout_child_set_position (self, child, g_value_get_uint (value));
-      break;
-
-    default:
-      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
-    }
-}
-
-static void
-ide_layout_get_preferred_width (GtkWidget *widget,
-                                gint      *min_width,
-                                gint      *nat_width)
-{
-  IdeLayout *self = (IdeLayout *)widget;
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-  gint i;
-
-  g_assert (IDE_IS_LAYOUT (self));
-
-  for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
-    {
-      IdeLayoutChild *child = &priv->children [i];
-
-      if (gtk_widget_get_visible (child->widget))
-        gtk_widget_get_preferred_width (child->widget, &child->min_width, &child->nat_width);
-    }
-
-  *min_width = priv->children [GTK_POS_LEFT].min_width
-             + priv->children [GTK_POS_RIGHT].min_width
-             + MAX (priv->children [GTK_POS_TOP].min_width,
-                    priv->children [GTK_POS_BOTTOM].min_width);
-  *nat_width = priv->children [GTK_POS_LEFT].nat_width
-             + priv->children [GTK_POS_RIGHT].nat_width
-             + MAX (priv->children [GTK_POS_TOP].nat_width,
-                    priv->children [GTK_POS_BOTTOM].nat_width);
-}
-
-static void
-ide_layout_get_preferred_height (GtkWidget *widget,
-                                 gint      *min_height,
-                                 gint      *nat_height)
-{
-  IdeLayout *self = (IdeLayout *)widget;
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-  gint i;
-
-  g_assert (IDE_IS_LAYOUT (self));
-
-  for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
-    {
-      IdeLayoutChild *child = &priv->children [i];
-
-      if (gtk_widget_get_visible (child->widget))
-        gtk_widget_get_preferred_height (child->widget, &child->min_height, &child->nat_height);
-    }
-
-  *min_height = MAX (MAX (priv->children [GTK_POS_LEFT].min_height,
-                          priv->children [GTK_POS_RIGHT].min_height),
-                     (priv->children [GTK_POS_BOTTOM].position +
-                      priv->children [GTK_POS_TOP].min_height));
-
-  *nat_height = MAX (MAX (priv->children [GTK_POS_LEFT].nat_height,
-                          priv->children [GTK_POS_RIGHT].nat_height),
-                     (priv->children [GTK_POS_BOTTOM].position +
-                      priv->children [GTK_POS_TOP].nat_height));
-}
-
-static GtkSizeRequestMode
-ide_layout_get_request_mode (GtkWidget *widget)
-{
-  return GTK_SIZE_REQUEST_CONSTANT_SIZE;
-}
-
-static GtkAdjustment *
-ide_layout_create_adjustment (IdeLayout *self)
-{
-  GtkAdjustment *adj;
-
-  g_assert (IDE_IS_LAYOUT (self));
-
-  adj = g_object_new (GTK_TYPE_ADJUSTMENT,
-                      "lower", 0.0,
-                      "upper", 1.0,
-                      "value", 0.0,
-                      NULL);
-
-  g_signal_connect_object (adj,
-                           "value-changed",
-                           G_CALLBACK (gtk_widget_queue_allocate),
-                           self,
-                           G_CONNECT_SWAPPED);
-
-  return adj;
-}
-
-static void
-ide_layout_drag_begin_cb (IdeLayout     *self,
-                          gdouble        x,
-                          gdouble        y,
-                          GtkGesturePan *pan)
-{
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-  IdeLayoutChild *left;
-  IdeLayoutChild *right;
-  IdeLayoutChild *bottom;
-  GdkEventSequence *sequence;
-  const GdkEvent *event;
-
-  g_assert (IDE_IS_LAYOUT (self));
-  g_assert (GTK_IS_GESTURE_PAN (pan));
-
-  left = &priv->children [GTK_POS_LEFT];
-  right = &priv->children [GTK_POS_RIGHT];
-  bottom = &priv->children [GTK_POS_BOTTOM];
-
-  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (pan));
-  event = gtk_gesture_get_last_event (GTK_GESTURE (pan), sequence);
-
-  if (event->any.window == left->handle)
-    {
-      gtk_gesture_pan_set_orientation (pan, GTK_ORIENTATION_HORIZONTAL);
-      priv->drag_child = left;
-    }
-  else if (event->any.window == right->handle)
-    {
-      gtk_gesture_pan_set_orientation (pan, GTK_ORIENTATION_HORIZONTAL);
-      priv->drag_child = right;
-    }
-  else if (event->any.window == bottom->handle)
-    {
-      gtk_gesture_pan_set_orientation (pan, GTK_ORIENTATION_VERTICAL);
-      priv->drag_child = bottom;
-    }
-  else
-    {
-      gtk_gesture_set_state (GTK_GESTURE (pan), GTK_EVENT_SEQUENCE_DENIED);
-      priv->drag_child = NULL;
-      return;
-    }
-
-  priv->drag_position = MAX (priv->drag_child->position, MIN_POSITION);
-  gtk_gesture_set_state (GTK_GESTURE (pan), GTK_EVENT_SEQUENCE_CLAIMED);
-  gtk_container_child_notify (GTK_CONTAINER (self), priv->drag_child->widget, "position");
-}
-
-static void
-ide_layout_drag_end_cb (IdeLayout     *self,
-                        gdouble        x,
-                        gdouble        y,
-                        GtkGesturePan *pan)
-{
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-  GdkEventSequence *sequence;
-  GtkEventSequenceState state;
-
-  g_assert (IDE_IS_LAYOUT (self));
-  g_assert (GTK_IS_GESTURE_PAN (pan));
-
-  if (priv->drag_child == NULL)
-    return;
-
-  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (pan));
-  state = gtk_gesture_get_sequence_state (GTK_GESTURE (pan), sequence);
-  if (state == GTK_EVENT_SEQUENCE_DENIED)
-    {
-      priv->drag_child = NULL;
-      return;
-    }
-
-  if (priv->drag_child->position < MIN_POSITION)
-    {
-      gtk_container_child_set (GTK_CONTAINER (self), priv->drag_child->widget,
-                               "reveal", FALSE,
-                               NULL);
-      priv->drag_child->restore_position = priv->drag_position;
-    }
-
-  gtk_container_child_notify (GTK_CONTAINER (self), priv->drag_child->widget, "position");
-
-  priv->drag_child = NULL;
-  priv->drag_position = 0;
-}
-
-static void
-ide_layout_pan_cb (IdeLayout       *self,
-                   GtkPanDirection  direction,
-                   gdouble          offset,
-                   GtkGesturePan   *pan)
-{
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-  GtkAllocation alloc;
-  gint target_position = 0;
-  gint center_min_width;
-  gint left_max;
-  gint right_max;
-  gint bottom_max;
-
-  g_assert (IDE_IS_LAYOUT (self));
-  g_assert (GTK_IS_GESTURE_PAN (pan));
-  g_assert (priv->drag_child != NULL);
-
-  /*
-   * NOTE: This is trickier than it looks, so I choose to be
-   *       very verbose. Feel free to clean it up.
-   */
-
-  gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
-
-  switch (direction)
-    {
-    case GTK_PAN_DIRECTION_LEFT:
-      if (priv->drag_child->type == GTK_POS_LEFT)
-        target_position = priv->drag_position - offset;
-      else if (priv->drag_child->type == GTK_POS_RIGHT)
-        target_position = priv->drag_position + offset;
-      break;
-
-    case GTK_PAN_DIRECTION_RIGHT:
-      if (priv->drag_child->type == GTK_POS_LEFT)
-        target_position = priv->drag_position + offset;
-      else if (priv->drag_child->type == GTK_POS_RIGHT)
-        target_position = priv->drag_position - offset;
-      break;
-
-    case GTK_PAN_DIRECTION_UP:
-      if (priv->drag_child->type == GTK_POS_BOTTOM)
-        target_position = priv->drag_position + offset;
-      break;
-
-    case GTK_PAN_DIRECTION_DOWN:
-      if (priv->drag_child->type == GTK_POS_BOTTOM)
-        target_position = priv->drag_position - offset;
-      break;
-
-    default:
-      g_assert_not_reached ();
-    }
-
-  center_min_width = MAX (priv->children [GTK_POS_BOTTOM].min_width,
-                          priv->children [GTK_POS_TOP].min_width);
-  left_max = alloc.width - priv->children [GTK_POS_RIGHT].alloc.width - center_min_width;
-  right_max = alloc.width - priv->children [GTK_POS_LEFT].position - center_min_width;
-  bottom_max = alloc.height - priv->children [GTK_POS_TOP].min_height;
-
-  switch (priv->drag_child->type)
-    {
-    case GTK_POS_LEFT:
-      target_position = MIN (left_max, target_position);
-      break;
-
-    case GTK_POS_RIGHT:
-      target_position = MIN (right_max, target_position);
-      break;
-
-    case GTK_POS_BOTTOM:
-      target_position = MIN (bottom_max, target_position);
-      break;
-
-    case GTK_POS_TOP:
-    default:
-      g_assert_not_reached ();
-    }
-
-  priv->drag_child->position = MAX (0, target_position);
-
-  gtk_widget_queue_allocate (GTK_WIDGET (self));
-}
-
-static GtkGesture *
-ide_layout_create_pan_gesture (IdeLayout      *self,
-                               GtkOrientation  orientation)
-{
-  GtkGesture *gesture;
-
-  g_assert (IDE_IS_LAYOUT (self));
-
-  gesture = gtk_gesture_pan_new (GTK_WIDGET (self), orientation);
-  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE);
-  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), GTK_PHASE_CAPTURE);
-
-  g_signal_connect_object (gesture,
-                           "drag-begin",
-                           G_CALLBACK (ide_layout_drag_begin_cb),
-                           self,
-                           G_CONNECT_SWAPPED);
-  g_signal_connect_object (gesture,
-                           "drag-end",
-                           G_CALLBACK (ide_layout_drag_end_cb),
-                           self,
-                           G_CONNECT_SWAPPED);
-  g_signal_connect_object (gesture,
-                           "pan",
-                           G_CALLBACK (ide_layout_pan_cb),
-                           self,
-                           G_CONNECT_SWAPPED);
-
-  return gesture;
-}
-
-static void
-ide_layout_realize (GtkWidget *widget)
-{
-  IdeLayout *self = (IdeLayout *)widget;
-
-  g_assert (IDE_IS_LAYOUT (self));
-
-  GTK_WIDGET_CLASS (ide_layout_parent_class)->realize (widget);
-
-  ide_layout_create_handle_window (self, GTK_POS_LEFT);
-  ide_layout_create_handle_window (self, GTK_POS_RIGHT);
-  ide_layout_create_handle_window (self, GTK_POS_BOTTOM);
-}
-
-static void
-ide_layout_unrealize (GtkWidget *widget)
-{
-  IdeLayout *self = (IdeLayout *)widget;
-
-  g_assert (IDE_IS_LAYOUT (self));
-
-  ide_layout_destroy_handle_window (self, GTK_POS_LEFT);
-  ide_layout_destroy_handle_window (self, GTK_POS_RIGHT);
-  ide_layout_destroy_handle_window (self, GTK_POS_BOTTOM);
-
-  GTK_WIDGET_CLASS (ide_layout_parent_class)->unrealize (widget);
-}
-
-static void
-ide_layout_map (GtkWidget *widget)
-{
-  IdeLayout *self = (IdeLayout *)widget;
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-  gint i;
-
-  g_assert (IDE_IS_LAYOUT (self));
-
-  GTK_WIDGET_CLASS (ide_layout_parent_class)->map (widget);
-
-  for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
-    {
-      IdeLayoutChild *child = &priv->children [i];
-
-      if (child->handle != NULL)
-        gdk_window_show (child->handle);
-    }
-}
-
-static void
-ide_layout_unmap (GtkWidget *widget)
-{
-  IdeLayout *self = (IdeLayout *)widget;
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-  int i;
-
-  g_assert (IDE_IS_LAYOUT (self));
-
-  for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
-    {
-      IdeLayoutChild *child = &priv->children [i];
-
-      if (child->handle != NULL)
-        gdk_window_hide (child->handle);
-    }
-
-  GTK_WIDGET_CLASS (ide_layout_parent_class)->unmap (widget);
-}
-
-static GObject *
-ide_layout_get_internal_child (GtkBuildable *buildable,
-                               GtkBuilder   *builder,
-                               const gchar  *childname)
-{
-  IdeLayout *self = (IdeLayout *)buildable;
-
-  g_assert (IDE_IS_LAYOUT (self));
-
-  /*
-   * Override default get_internal_child to handle RTL vs LTR.
-   */
-  if (ide_str_equal0 (childname, "left_pane"))
-    return G_OBJECT (ide_layout_get_left_pane (self));
-  else if (ide_str_equal0 (childname, "right_pane"))
-    return G_OBJECT (ide_layout_get_right_pane (self));
-
-  return ide_layout_parent_buildable_iface->get_internal_child (buildable, builder, childname);
-}
-
-static void
-ide_layout_grab_focus (GtkWidget *widget)
-{
-  IdeLayout *self = (IdeLayout *)widget;
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-
-  g_assert (IDE_IS_LAYOUT (self));
-
-  gtk_widget_grab_focus (priv->children [GTK_POS_TOP].widget);
-}
+static GParamSpec *properties [LAST_PROP];
 
 static void
 ide_layout_active_view_weak_cb (IdeLayout *self,
@@ -1085,6 +106,7 @@ ide_layout_hierarchy_changed (GtkWidget *widget,
   GtkWidget *toplevel;
 
   g_assert (IDE_IS_LAYOUT (self));
+  g_assert (!old_toplevel || GTK_IS_WIDGET (old_toplevel));
 
   if ((old_toplevel != NULL) && (priv->focus_handler != 0))
     {
@@ -1103,33 +125,15 @@ ide_layout_hierarchy_changed (GtkWidget *widget,
                                 self);
 }
 
-static void
-ide_layout_finalize (GObject *object)
+static GtkWidget *
+ide_layout_create_edge (PnlDockBin *dock)
 {
-  IdeLayout *self = (IdeLayout *)object;
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-  gsize i;
-
-  for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
-    {
-      IdeLayoutChild *child = &priv->children [i];
+  g_assert (IDE_IS_LAYOUT (dock));
 
-      ide_clear_weak_pointer (&child->animation);
-      g_clear_object (&child->adjustment);
-    }
-
-  if (priv->active_view)
-    {
-      g_object_weak_unref (G_OBJECT (priv->active_view),
-                           (GWeakNotify)ide_layout_active_view_weak_cb,
-                           self);
-      priv->active_view = NULL;
-    }
-
-  g_clear_object (&priv->pan_gesture);
-  g_clear_object (&priv->actions);
-
-  G_OBJECT_CLASS (ide_layout_parent_class)->finalize (object);
+  return g_object_new (IDE_TYPE_LAYOUT_PANE,
+                       "visible", TRUE,
+                       "reveal-child", FALSE,
+                       NULL);
 }
 
 static void
@@ -1146,91 +150,25 @@ ide_layout_get_property (GObject    *object,
       g_value_set_object (value, ide_layout_get_active_view (self));
       break;
 
-    case PROP_LEFT_PANE:
-      g_value_set_object (value, ide_layout_get_left_pane (self));
-      break;
-
-    case PROP_RIGHT_PANE:
-      g_value_set_object (value, ide_layout_get_right_pane (self));
-      break;
-
-    case PROP_BOTTOM_PANE:
-      g_value_set_object (value, ide_layout_get_bottom_pane (self));
-      break;
-
-    case PROP_CONTENT_PANE:
-      g_value_set_object (value, ide_layout_get_content_pane (self));
-      break;
-
     default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
     }
 }
 
 static void
-buildable_init_iface (GtkBuildableIface *iface)
-{
-  ide_layout_parent_buildable_iface = g_type_interface_peek_parent (iface);
-
-  iface->get_internal_child = ide_layout_get_internal_child;
-}
-
-static void
 ide_layout_class_init (IdeLayoutClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
-  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
-  GtkOverlayClass *overlay_class = GTK_OVERLAY_CLASS (klass);
+  PnlDockBinClass *dock_bin_class = PNL_DOCK_BIN_CLASS (klass);
 
-  object_class->finalize = ide_layout_finalize;
   object_class->get_property = ide_layout_get_property;
 
-  widget_class->get_preferred_height = ide_layout_get_preferred_height;
-  widget_class->get_preferred_width = ide_layout_get_preferred_width;
-  widget_class->get_request_mode = ide_layout_get_request_mode;
   widget_class->hierarchy_changed = ide_layout_hierarchy_changed;
-  widget_class->map = ide_layout_map;
-  widget_class->unmap = ide_layout_unmap;
-  widget_class->realize = ide_layout_realize;
-  widget_class->unrealize = ide_layout_unrealize;
-  widget_class->size_allocate = ide_layout_size_allocate;
-  widget_class->grab_focus = ide_layout_grab_focus;
 
-  gtk_widget_class_set_css_name (widget_class, "layout");
-
-  container_class->get_child_property = ide_layout_get_child_property;
-  container_class->set_child_property = ide_layout_set_child_property;
-
-  overlay_class->get_child_position = ide_layout_get_child_position;
-
-  properties [PROP_LEFT_PANE] =
-    g_param_spec_object ("left-pane",
-                         "Left Pane",
-                         "The left workspace pane.",
-                         GTK_TYPE_WIDGET,
-                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-  properties [PROP_RIGHT_PANE] =
-    g_param_spec_object ("right-pane",
-                         "Right Pane",
-                         "The right workspace pane.",
-                         GTK_TYPE_WIDGET,
-                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+  dock_bin_class->create_edge = ide_layout_create_edge;
 
-  properties [PROP_BOTTOM_PANE] =
-    g_param_spec_object ("bottom-pane",
-                         "Bottom Pane",
-                         "The bottom workspace pane.",
-                         GTK_TYPE_WIDGET,
-                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-  properties [PROP_CONTENT_PANE] =
-    g_param_spec_object ("content-pane",
-                         "Content Pane",
-                         "The content workspace pane.",
-                         GTK_TYPE_WIDGET,
-                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+  gtk_widget_class_set_css_name (widget_class, "layout");
 
   properties [PROP_ACTIVE_VIEW] =
     g_param_spec_object ("active-view",
@@ -1240,205 +178,11 @@ ide_layout_class_init (IdeLayoutClass *klass)
                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_properties (object_class, LAST_PROP, properties);
-
-  child_properties [CHILD_PROP_POSITION] =
-    g_param_spec_uint ("position",
-                       "Position",
-                       "The position of the pane relative to its edge.",
-                       0, G_MAXUINT,
-                       0,
-                       (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-  gtk_container_class_install_child_property (container_class, CHILD_PROP_POSITION,
-                                              child_properties [CHILD_PROP_POSITION]);
-
-  child_properties [CHILD_PROP_REVEAL] =
-    g_param_spec_boolean ("reveal",
-                          "Reveal",
-                          "If the pane should be revealed.",
-                          TRUE,
-                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-  gtk_container_class_install_child_property (container_class, CHILD_PROP_REVEAL,
-                                              child_properties [CHILD_PROP_REVEAL]);
-
-  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/ide-layout.ui");
-
-  gtk_widget_class_bind_template_child_full (widget_class, "bottom_pane", TRUE,
-                                             G_PRIVATE_OFFSET (IdeLayout, children[GTK_POS_BOTTOM].widget));
-  gtk_widget_class_bind_template_child_full (widget_class, "content_pane", TRUE,
-                                             G_PRIVATE_OFFSET (IdeLayout, children[GTK_POS_TOP].widget));
-  gtk_widget_class_bind_template_child_full (widget_class, "left_pane", TRUE,
-                                             G_PRIVATE_OFFSET (IdeLayout, children[GTK_POS_LEFT].widget));
-  gtk_widget_class_bind_template_child_full (widget_class, "right_pane", TRUE,
-                                             G_PRIVATE_OFFSET (IdeLayout, children[GTK_POS_RIGHT].widget));
-}
-
-static void
-ide_layout_activate_left (GSimpleAction *action,
-                          GVariant      *param,
-                          gpointer       user_data)
-{
-  IdeLayout *self = user_data;
-  GtkWidget *child;
-  gboolean reveal;
-
-  g_assert (G_IS_SIMPLE_ACTION (action));
-  g_assert (IDE_IS_LAYOUT (self));
-
-  child = ide_layout_get_left_pane (self);
-  reveal = g_variant_get_boolean (param);
-  gtk_container_child_set (GTK_CONTAINER (self), child, "reveal", reveal, NULL);
-}
-
-static void
-ide_layout_activate_right (GSimpleAction *action,
-                           GVariant      *param,
-                           gpointer       user_data)
-{
-  IdeLayout *self = user_data;
-  GtkWidget *child;
-  gboolean reveal;
-
-  g_assert (G_IS_SIMPLE_ACTION (action));
-  g_assert (IDE_IS_LAYOUT (self));
-
-  child = ide_layout_get_right_pane (self);
-  reveal = g_variant_get_boolean (param);
-  gtk_container_child_set (GTK_CONTAINER (self), child, "reveal", reveal, NULL);
 }
 
 static void
-ide_layout_activate_bottom (GSimpleAction *action,
-                            GVariant      *param,
-                            gpointer       user_data)
-{
-  IdeLayout *self = user_data;
-  GtkWidget *child;
-  gboolean reveal;
-
-  g_assert (G_IS_SIMPLE_ACTION (action));
-  g_assert (IDE_IS_LAYOUT (self));
-
-  child = ide_layout_get_bottom_pane (self);
-  reveal = g_variant_get_boolean (param);
-  gtk_container_child_set (GTK_CONTAINER (self), child, "reveal", reveal, NULL);
-}
-
-static const GActionEntry action_entries[] = {
-  { "left", NULL, NULL, "true", ide_layout_activate_left },
-  { "right", NULL, NULL, "true", ide_layout_activate_right },
-  { "bottom", NULL, NULL, "true", ide_layout_activate_bottom  },
-};
-
-static void
 ide_layout_init (IdeLayout *self)
 {
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-
-  priv->children [GTK_POS_LEFT].type = GTK_POS_LEFT;
-  priv->children [GTK_POS_LEFT].reveal = TRUE;
-  priv->children [GTK_POS_LEFT].position = 250;
-  priv->children [GTK_POS_LEFT].adjustment = ide_layout_create_adjustment (self);
-  priv->children [GTK_POS_LEFT].cursor_type = GDK_SB_H_DOUBLE_ARROW;
-
-  priv->children [GTK_POS_RIGHT].type = GTK_POS_RIGHT;
-  priv->children [GTK_POS_RIGHT].reveal = TRUE;
-  priv->children [GTK_POS_RIGHT].position = 250;
-  priv->children [GTK_POS_RIGHT].adjustment = ide_layout_create_adjustment (self);
-  priv->children [GTK_POS_RIGHT].cursor_type = GDK_SB_H_DOUBLE_ARROW;
-
-  priv->children [GTK_POS_BOTTOM].type = GTK_POS_BOTTOM;
-  priv->children [GTK_POS_BOTTOM].reveal = TRUE;
-  priv->children [GTK_POS_BOTTOM].position = 150;
-  priv->children [GTK_POS_BOTTOM].adjustment = ide_layout_create_adjustment (self);
-  priv->children [GTK_POS_BOTTOM].cursor_type = GDK_SB_V_DOUBLE_ARROW;
-
-  priv->children [GTK_POS_TOP].type = GTK_POS_TOP;
-  priv->children [GTK_POS_TOP].reveal = TRUE;
-  priv->children [GTK_POS_TOP].adjustment = ide_layout_create_adjustment (self);
-
-  priv->pan_gesture = ide_layout_create_pan_gesture (self, GTK_ORIENTATION_HORIZONTAL);
-
-  gtk_widget_init_template (GTK_WIDGET (self));
-
-  priv->actions = G_ACTION_MAP (g_simple_action_group_new ());
-  g_action_map_add_action_entries (G_ACTION_MAP (priv->actions),
-                                   action_entries,
-                                   G_N_ELEMENTS (action_entries),
-                                   self);
-  gtk_widget_insert_action_group (GTK_WIDGET (self), "panels",
-                                  G_ACTION_GROUP (priv->actions));
-}
-
-GtkWidget *
-ide_layout_new (void)
-{
-  return g_object_new (IDE_TYPE_LAYOUT, NULL);
-}
-
-/**
- * ide_layout_get_left_pane:
- *
- * Returns: (transfer none): A #GtkWidget.
- */
-GtkWidget *
-ide_layout_get_left_pane (IdeLayout *self)
-{
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-
-  g_return_val_if_fail (IDE_IS_LAYOUT (self), NULL);
-
-  if (gtk_widget_get_state_flags (GTK_WIDGET (self)) & GTK_STATE_FLAG_DIR_RTL)
-    return priv->children [GTK_POS_RIGHT].widget;
-  else
-    return priv->children [GTK_POS_LEFT].widget;
-}
-
-/**
- * ide_layout_get_right_pane:
- *
- * Returns: (transfer none): A #GtkWidget.
- */
-GtkWidget *
-ide_layout_get_right_pane (IdeLayout *self)
-{
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-
-  g_return_val_if_fail (IDE_IS_LAYOUT (self), NULL);
-
-  if (gtk_widget_get_state_flags (GTK_WIDGET (self)) & GTK_STATE_FLAG_DIR_RTL)
-    return priv->children [GTK_POS_LEFT].widget;
-  else
-    return priv->children [GTK_POS_RIGHT].widget;
-}
-
-/**
- * ide_layout_get_bottom_pane:
- *
- * Returns: (transfer none): A #GtkWidget.
- */
-GtkWidget *
-ide_layout_get_bottom_pane (IdeLayout *self)
-{
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-
-  g_return_val_if_fail (IDE_IS_LAYOUT (self), NULL);
-
-  return priv->children [GTK_POS_BOTTOM].widget;
-}
-
-/**
- * ide_layout_get_content_pane:
- *
- * Returns: (transfer none): A #GtkWidget.
- */
-GtkWidget *
-ide_layout_get_content_pane (IdeLayout *self)
-{
-  IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-
-  g_return_val_if_fail (IDE_IS_LAYOUT (self), NULL);
-
-  return priv->children [GTK_POS_TOP].widget;
 }
 
 /**
diff --git a/libide/ide-layout.h b/libide/ide-layout.h
index 98698d6..409427c 100644
--- a/libide/ide-layout.h
+++ b/libide/ide-layout.h
@@ -19,27 +19,20 @@
 #ifndef IDE_LAYOUT_H
 #define IDE_LAYOUT_H
 
-#include <gtk/gtk.h>
-
-#include "ide-layout-pane.h"
+#include <pnl.h>
 
 G_BEGIN_DECLS
 
 #define IDE_TYPE_LAYOUT (ide_layout_get_type())
 
-G_DECLARE_DERIVABLE_TYPE (IdeLayout, ide_layout, IDE, LAYOUT, GtkOverlay)
+G_DECLARE_DERIVABLE_TYPE (IdeLayout, ide_layout, IDE, LAYOUT, PnlDockBin)
 
 struct _IdeLayoutClass
 {
-  GtkOverlayClass parent_instance;
+  PnlDockBinClass parent_class;
 };
 
-GtkWidget *ide_layout_new              (void);
-GtkWidget *ide_layout_get_left_pane    (IdeLayout *self);
-GtkWidget *ide_layout_get_right_pane   (IdeLayout *self);
-GtkWidget *ide_layout_get_bottom_pane  (IdeLayout *self);
-GtkWidget *ide_layout_get_content_pane (IdeLayout *self);
-GtkWidget *ide_layout_get_active_view  (IdeLayout *self);
+GtkWidget *ide_layout_get_active_view (IdeLayout *self);
 
 G_END_DECLS
 
diff --git a/libide/ide-workbench.c b/libide/ide-workbench.c
index 9b7556c..decf659 100644
--- a/libide/ide-workbench.c
+++ b/libide/ide-workbench.c
@@ -780,11 +780,7 @@ ide_workbench_show_parents (GtkWidget *widget)
   parent = gtk_widget_get_parent (widget);
 
   if (IDE_IS_LAYOUT_PANE (widget))
-    {
-      gtk_container_child_set (GTK_CONTAINER (parent), widget,
-                               "reveal", TRUE,
-                               NULL);
-    }
+    pnl_dock_revealer_set_reveal_child (PNL_DOCK_REVEALER (widget), TRUE);
 
   if (IDE_IS_PERSPECTIVE (widget))
     ide_workbench_set_visible_perspective (ide_widget_get_workbench (widget),
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index be892d8..5117d97 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -20,6 +20,7 @@
   <gresource prefix="/org/gnome/builder/theme">
     <file compressed="true" alias="Adwaita.css">../../data/theme/Adwaita.css</file>
     <file compressed="true" alias="Adwaita-dark.css">../../data/theme/Adwaita-dark.css</file>
+    <file compressed="true" alias="Adwaita-panels.css">../../data/theme/Adwaita-panels.css</file>
     <file compressed="true" alias="Adwaita-shared.css">../../data/theme/Adwaita-shared.css</file>
 
     <file compressed="true" alias="Arc.css">../../data/theme/Arc.css</file>
@@ -57,7 +58,6 @@
     <file compressed="true" 
alias="ide-genesis-perspective.ui">../../data/ui/ide-genesis-perspective.ui</file>
     <file compressed="true" 
alias="ide-greeter-perspective.ui">../../data/ui/ide-greeter-perspective.ui</file>
     <file compressed="true" 
alias="ide-greeter-project-row.ui">../../data/ui/ide-greeter-project-row.ui</file>
-    <file compressed="true" alias="ide-layout.ui">../../data/ui/ide-layout.ui</file>
     <file compressed="true" alias="ide-layout-tab.ui">../../data/ui/ide-layout-tab.ui</file>
     <file compressed="true" alias="ide-layout-tab-bar.ui">../../data/ui/ide-layout-tab-bar.ui</file>
     <file compressed="true" alias="ide-layout-pane.ui">../../data/ui/ide-layout-pane.ui</file>
diff --git a/plugins/build-tools/gbp-build-log-panel.c b/plugins/build-tools/gbp-build-log-panel.c
index 8e13762..e677398 100644
--- a/plugins/build-tools/gbp-build-log-panel.c
+++ b/plugins/build-tools/gbp-build-log-panel.c
@@ -27,7 +27,7 @@
 
 struct _GbpBuildLogPanel
 {
-  GtkBin             parent_instance;
+  PnlDockWidget      parent_instance;
 
   IdeBuildResult    *result;
   EggSignalGroup    *signals;
@@ -46,7 +46,7 @@ enum {
   LAST_PROP
 };
 
-G_DEFINE_TYPE (GbpBuildLogPanel, gbp_build_log_panel, GTK_TYPE_BIN)
+G_DEFINE_TYPE (GbpBuildLogPanel, gbp_build_log_panel, PNL_TYPE_DOCK_WIDGET)
 
 static GParamSpec *properties [LAST_PROP];
 
@@ -246,6 +246,8 @@ gbp_build_log_panel_init (GbpBuildLogPanel *self)
 
   gtk_widget_init_template (GTK_WIDGET (self));
 
+  g_object_set (self, "title", _("Build Output"), NULL);
+
   gbp_build_log_panel_reset_view (self);
 
   self->signals = egg_signal_group_new (IDE_TYPE_BUILD_RESULT);
diff --git a/plugins/build-tools/gbp-build-log-panel.h b/plugins/build-tools/gbp-build-log-panel.h
index 9cc1a58..df407ba 100644
--- a/plugins/build-tools/gbp-build-log-panel.h
+++ b/plugins/build-tools/gbp-build-log-panel.h
@@ -26,7 +26,7 @@ G_BEGIN_DECLS
 
 #define GBP_TYPE_BUILD_LOG_PANEL (gbp_build_log_panel_get_type())
 
-G_DECLARE_FINAL_TYPE (GbpBuildLogPanel, gbp_build_log_panel, GBP, BUILD_LOG_PANEL, GtkBin)
+G_DECLARE_FINAL_TYPE (GbpBuildLogPanel, gbp_build_log_panel, GBP, BUILD_LOG_PANEL, PnlDockWidget)
 
 void gbp_build_log_panel_set_result (GbpBuildLogPanel *self,
                                      IdeBuildResult   *result);
diff --git a/plugins/build-tools/gbp-build-log-panel.ui b/plugins/build-tools/gbp-build-log-panel.ui
index 299fc55..1e054f4 100644
--- a/plugins/build-tools/gbp-build-log-panel.ui
+++ b/plugins/build-tools/gbp-build-log-panel.ui
@@ -1,5 +1,5 @@
 <interface>
-  <template class="GbpBuildLogPanel" parent="GtkBin">
+  <template class="GbpBuildLogPanel" parent="PnlDockWidget">
     <child>
       <object class="GtkScrolledWindow" id="scroller">
         <property name="visible">true</property>
diff --git a/plugins/build-tools/gbp-build-panel.c b/plugins/build-tools/gbp-build-panel.c
index 1b703bd..81e6c02 100644
--- a/plugins/build-tools/gbp-build-panel.c
+++ b/plugins/build-tools/gbp-build-panel.c
@@ -28,7 +28,7 @@
 
 struct _GbpBuildPanel
 {
-  GtkBin            parent_instance;
+  PnlDockWidget     parent_instance;
 
   IdeBuildResult   *result;
   EggSignalGroup   *signals;
@@ -50,7 +50,7 @@ struct _GbpBuildPanel
   guint             warning_count;
 };
 
-G_DEFINE_TYPE (GbpBuildPanel, gbp_build_panel, GTK_TYPE_BIN)
+G_DEFINE_TYPE (GbpBuildPanel, gbp_build_panel, PNL_TYPE_DOCK_WIDGET)
 
 enum {
   PROP_0,
@@ -423,6 +423,8 @@ gbp_build_panel_init (GbpBuildPanel *self)
 {
   gtk_widget_init_template (GTK_WIDGET (self));
 
+  g_object_set (self, "title", _("Build"), NULL);
+
   self->signals = egg_signal_group_new (IDE_TYPE_BUILD_RESULT);
 
   egg_signal_group_connect_object (self->signals,
diff --git a/plugins/build-tools/gbp-build-panel.h b/plugins/build-tools/gbp-build-panel.h
index 5d2f8bc..86f7527 100644
--- a/plugins/build-tools/gbp-build-panel.h
+++ b/plugins/build-tools/gbp-build-panel.h
@@ -26,7 +26,7 @@ G_BEGIN_DECLS
 
 #define GBP_TYPE_BUILD_PANEL (gbp_build_panel_get_type())
 
-G_DECLARE_FINAL_TYPE (GbpBuildPanel, gbp_build_panel, GBP, BUILD_PANEL, GtkBin)
+G_DECLARE_FINAL_TYPE (GbpBuildPanel, gbp_build_panel, GBP, BUILD_PANEL, PnlDockWidget)
 
 void gbp_build_panel_set_result (GbpBuildPanel  *self,
                                  IdeBuildResult *result);
diff --git a/plugins/build-tools/gbp-build-panel.ui b/plugins/build-tools/gbp-build-panel.ui
index 9b29a5c..77a9ebc 100644
--- a/plugins/build-tools/gbp-build-panel.ui
+++ b/plugins/build-tools/gbp-build-panel.ui
@@ -1,5 +1,5 @@
 <interface>
-  <template class="GbpBuildPanel" parent="GtkBin">
+  <template class="GbpBuildPanel" parent="PnlDockWidget">
     <child>
       <object class="GtkBox" id="toplevel">
         <property name="visible">true</property>
diff --git a/plugins/build-tools/gbp-build-workbench-addin.c b/plugins/build-tools/gbp-build-workbench-addin.c
index f074b71..8194a10 100644
--- a/plugins/build-tools/gbp-build-workbench-addin.c
+++ b/plugins/build-tools/gbp-build-workbench-addin.c
@@ -262,17 +262,16 @@ gbp_build_workbench_addin_load (IdeWorkbenchAddin *addin,
   configuration = ide_configuration_manager_get_current (configuration_manager);
 
   editor = ide_workbench_get_perspective_by_name (workbench, "editor");
-  pane = ide_layout_get_right_pane (IDE_LAYOUT (editor));
+  pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (editor));
   self->panel = g_object_new (GBP_TYPE_BUILD_PANEL,
                               "configuration-manager", configuration_manager,
                               "visible", TRUE,
                               NULL);
-  ide_layout_pane_add_page (IDE_LAYOUT_PANE (pane), GTK_WIDGET (self->panel), _("Build"), NULL);
+  gtk_container_add (GTK_CONTAINER (pane), GTK_WIDGET (self->panel));
 
-  pane = ide_layout_get_bottom_pane (IDE_LAYOUT (editor));
+  pane = pnl_dock_bin_get_bottom_edge (PNL_DOCK_BIN (editor));
   self->build_log_panel = g_object_new (GBP_TYPE_BUILD_LOG_PANEL, NULL);
-  ide_layout_pane_add_page (IDE_LAYOUT_PANE (pane), GTK_WIDGET (self->build_log_panel),
-                            _("Build Output"), NULL);
+  gtk_container_add (GTK_CONTAINER (pane), GTK_WIDGET (self->build_log_panel));
 
   gtk_widget_insert_action_group (GTK_WIDGET (workbench), "build-tools",
                                   G_ACTION_GROUP (self->actions));
@@ -292,8 +291,6 @@ gbp_build_workbench_addin_unload (IdeWorkbenchAddin *addin,
                                   IdeWorkbench      *workbench)
 {
   GbpBuildWorkbenchAddin *self = (GbpBuildWorkbenchAddin *)addin;
-  IdePerspective *editor;
-  GtkWidget *pane;
 
   g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
   g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
@@ -306,9 +303,8 @@ gbp_build_workbench_addin_unload (IdeWorkbenchAddin *addin,
 
   gtk_widget_insert_action_group (GTK_WIDGET (workbench), "build-tools", NULL);
 
-  editor = ide_workbench_get_perspective_by_name (workbench, "editor");
-  pane = ide_layout_get_right_pane (IDE_LAYOUT (editor));
-  ide_layout_pane_remove_page (IDE_LAYOUT_PANE (pane), GTK_WIDGET (self->panel));
+  gtk_widget_destroy (GTK_WIDGET (self->panel));
+  self->panel = NULL;
 }
 
 static void
diff --git a/plugins/devhelp/gbp-devhelp-editor-view-addin.c b/plugins/devhelp/gbp-devhelp-editor-view-addin.c
index 73fb07b..e67047e 100644
--- a/plugins/devhelp/gbp-devhelp-editor-view-addin.c
+++ b/plugins/devhelp/gbp-devhelp-editor-view-addin.c
@@ -45,7 +45,7 @@ request_documentation_cb (GbpDevhelpEditorViewAddin *self,
   if (layout == NULL)
     return;
 
-  pane = ide_layout_get_right_pane (IDE_LAYOUT (layout));
+  pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (layout));
   panel = ide_widget_find_child_typed (pane, GBP_TYPE_DEVHELP_PANEL);
   gbp_devhelp_panel_focus_search (GBP_DEVHELP_PANEL (panel), word);
 }
diff --git a/plugins/devhelp/gbp-devhelp-panel.c b/plugins/devhelp/gbp-devhelp-panel.c
index 757cd2d..903da6a 100644
--- a/plugins/devhelp/gbp-devhelp-panel.c
+++ b/plugins/devhelp/gbp-devhelp-panel.c
@@ -17,6 +17,7 @@
  */
 
 #include <devhelp/devhelp.h>
+#include <glib/gi18n.h>
 #include <ide.h>
 
 #include "gbp-devhelp-panel.h"
@@ -24,13 +25,13 @@
 
 struct _GbpDevhelpPanel
 {
-  GtkBin         parent_instance;
+  PnlDockWidget  parent_instance;
 
   DhBookManager *books;
   DhSidebar     *sidebar;
 };
 
-G_DEFINE_TYPE (GbpDevhelpPanel, gbp_devhelp_panel, GTK_TYPE_BIN)
+G_DEFINE_TYPE (GbpDevhelpPanel, gbp_devhelp_panel, PNL_TYPE_DOCK_WIDGET)
 
 enum {
   PROP_0,
@@ -183,19 +184,15 @@ gbp_devhelp_panel_class_init (GbpDevhelpPanelClass *klass)
 static void
 gbp_devhelp_panel_init (GbpDevhelpPanel *self)
 {
+  g_object_set (self, "title", _("Documentation"), NULL);
 }
 
 void
 gbp_devhelp_panel_focus_search (GbpDevhelpPanel *self,
                                 const gchar     *keyword)
 {
-  IdeWorkbench *workbench;
-
   g_return_if_fail (GBP_IS_DEVHELP_PANEL (self));
 
-  workbench = ide_widget_get_workbench (GTK_WIDGET (self));
-  ide_workbench_focus (workbench, GTK_WIDGET (self->sidebar));
-
   dh_sidebar_set_search_focus (self->sidebar);
 
   if (keyword)
diff --git a/plugins/devhelp/gbp-devhelp-panel.h b/plugins/devhelp/gbp-devhelp-panel.h
index 40f6c4d..494bc7a 100644
--- a/plugins/devhelp/gbp-devhelp-panel.h
+++ b/plugins/devhelp/gbp-devhelp-panel.h
@@ -19,13 +19,13 @@
 #ifndef GBP_DEVHELP_PANEL_H
 #define GBP_DEVHELP_PANEL_H
 
-#include <gtk/gtk.h>
+#include <pnl.h>
 
 G_BEGIN_DECLS
 
 #define GBP_TYPE_DEVHELP_PANEL (gbp_devhelp_panel_get_type())
 
-G_DECLARE_FINAL_TYPE (GbpDevhelpPanel, gbp_devhelp_panel, GBP, DEVHELP_PANEL, GtkBin)
+G_DECLARE_FINAL_TYPE (GbpDevhelpPanel, gbp_devhelp_panel, GBP, DEVHELP_PANEL, PnlDockWidget)
 
 void gbp_devhelp_panel_set_uri      (GbpDevhelpPanel *self,
                                      const gchar     *uri);
diff --git a/plugins/devhelp/gbp-devhelp-search-provider.c b/plugins/devhelp/gbp-devhelp-search-provider.c
index cc8dbf5..e1e18b5 100644
--- a/plugins/devhelp/gbp-devhelp-search-provider.c
+++ b/plugins/devhelp/gbp-devhelp-search-provider.c
@@ -174,7 +174,7 @@ gbp_devhelp_search_provider_activate (IdeSearchProvider *provider,
   editor = ide_workbench_get_perspective_by_name (IDE_WORKBENCH (toplevel), "editor");
   g_assert (editor != NULL);
 
-  pane = ide_layout_get_right_pane (IDE_LAYOUT (editor));
+  pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (editor));
   g_assert (pane != NULL);
 
   panel = ide_widget_find_child_typed (pane, GBP_TYPE_DEVHELP_PANEL);
diff --git a/plugins/devhelp/gbp-devhelp-workbench-addin.c b/plugins/devhelp/gbp-devhelp-workbench-addin.c
index 2844720..f21ffc8 100644
--- a/plugins/devhelp/gbp-devhelp-workbench-addin.c
+++ b/plugins/devhelp/gbp-devhelp-workbench-addin.c
@@ -79,15 +79,15 @@ gbp_devhelp_workbench_addin_load (IdeWorkbenchAddin *addin,
   perspective = ide_workbench_get_perspective_by_name (workbench, "editor");
   g_assert (IDE_IS_LAYOUT (perspective));
 
-  pane = ide_layout_get_right_pane (IDE_LAYOUT (perspective));
+  pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (perspective));
   g_assert (IDE_IS_LAYOUT_PANE (pane));
 
   self->panel = g_object_new (GBP_TYPE_DEVHELP_PANEL,
                               "book-manager", self->books,
+                              "expand", TRUE,
                               "visible", TRUE,
                               NULL);
-  ide_layout_pane_add_page (IDE_LAYOUT_PANE (pane), GTK_WIDGET (self->panel),
-                            _("Documentation"), "devhelp-symbolic");
+  gtk_container_add (GTK_CONTAINER (pane), GTK_WIDGET (self->panel));
 
   action = g_simple_action_new ("focus-devhelp-search", NULL);
   g_signal_connect_object (action, "activate", G_CALLBACK (focus_devhelp_search), self, 0);
@@ -114,10 +114,11 @@ gbp_devhelp_workbench_addin_unload (IdeWorkbenchAddin *addin,
   perspective = ide_workbench_get_perspective_by_name (workbench, "editor");
   g_assert (IDE_IS_LAYOUT (perspective));
 
-  pane = ide_layout_get_right_pane (IDE_LAYOUT (perspective));
+  pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (perspective));
   g_assert (IDE_IS_LAYOUT_PANE (pane));
 
-  ide_layout_pane_remove_page (IDE_LAYOUT_PANE (pane), GTK_WIDGET (self->panel));
+  gtk_widget_destroy (GTK_WIDGET (self->panel));
+  self->panel = NULL;
 
   g_action_map_remove_action (G_ACTION_MAP (workbench), "focus-devhelp-search");
 
diff --git a/plugins/devhelp/theme/shared.css b/plugins/devhelp/theme/shared.css
index e53dc97..70a5535 100644
--- a/plugins/devhelp/theme/shared.css
+++ b/plugins/devhelp/theme/shared.css
@@ -1,4 +1,9 @@
-devhelppanel entry {
+ binding-set devhelp-adjust-search-entry {
+  unbind "Escape";
+}
+
+devhelppanel entry.search {
   border-radius: 0;
   border-width: 0 0 1px 0;
+  -gtk-key-bindings: devhelp-adjust-search-entry;
 }
diff --git a/plugins/project-tree/gb-project-tree-addin.c b/plugins/project-tree/gb-project-tree-addin.c
index 359a6dd..4a7eede 100644
--- a/plugins/project-tree/gb-project-tree-addin.c
+++ b/plugins/project-tree/gb-project-tree-addin.c
@@ -27,8 +27,10 @@ static void workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface);
 
 struct _GbProjectTreeAddin
 {
-  GObject  jparent_instance;
-  IdeTree *tree;
+  GObject    parent_instance;
+
+  IdeTree   *tree;
+  GtkWidget *panel;
 };
 
 G_DEFINE_TYPE_EXTENDED (GbProjectTreeAddin, gb_project_tree_addin, G_TYPE_OBJECT, 0,
@@ -59,12 +61,10 @@ gb_project_tree_addin_grid_empty (GbProjectTreeAddin *self,
   layout = gtk_widget_get_ancestor (GTK_WIDGET (grid), IDE_TYPE_LAYOUT);
   g_assert (layout != NULL);
 
-  pane = ide_layout_get_left_pane (IDE_LAYOUT (layout));
+  pane = pnl_dock_bin_get_left_edge (PNL_DOCK_BIN (layout));
   g_assert (pane != NULL);
 
-  gtk_container_child_set (GTK_CONTAINER (layout), GTK_WIDGET (pane),
-                           "reveal", TRUE,
-                           NULL);
+  pnl_dock_revealer_set_reveal_child (PNL_DOCK_REVEALER (pane), TRUE);
 }
 
 static void
@@ -84,10 +84,10 @@ gb_project_tree_addin_load (IdeWorkbenchAddin *addin,
   editor = ide_workbench_get_perspective_by_name (workbench, "editor");
   g_assert (editor != NULL);
 
-  pane = ide_layout_get_left_pane (IDE_LAYOUT (editor));
+  pane = pnl_dock_bin_get_left_edge (PNL_DOCK_BIN (editor));
   g_assert (pane != NULL);
 
-  content = ide_layout_get_content_pane (IDE_LAYOUT (editor));
+  content = pnl_dock_bin_get_center_widget (PNL_DOCK_BIN (editor));
   g_assert (content != NULL);
 
   grid = ide_widget_find_child_typed (content, IDE_TYPE_LAYOUT_GRID);
@@ -108,8 +108,13 @@ gb_project_tree_addin_load (IdeWorkbenchAddin *addin,
                              NULL);
   gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (self->tree));
 
-  ide_layout_pane_add_page (IDE_LAYOUT_PANE (pane), scroller,
-                            _("Project"), "folder-symbolic");
+  self->panel = g_object_new (PNL_TYPE_DOCK_WIDGET,
+                              "expand", TRUE,
+                              "title", _("Project"),
+                              "visible", TRUE,
+                              NULL);
+  gtk_container_add (GTK_CONTAINER (self->panel), GTK_WIDGET (scroller));
+  gtk_container_add (GTK_CONTAINER (pane), GTK_WIDGET (self->panel));
 
   ide_widget_set_context_handler (self->tree, gb_project_tree_addin_context_set);
 
@@ -121,20 +126,12 @@ gb_project_tree_addin_unload (IdeWorkbenchAddin *addin,
                               IdeWorkbench      *workbench)
 {
   GbProjectTreeAddin *self = (GbProjectTreeAddin *)addin;
-  IdePerspective *editor;
-  GtkWidget *pane;
 
   g_assert (IDE_IS_WORKBENCH_ADDIN (self));
   g_assert (IDE_IS_WORKBENCH (workbench));
 
-  editor = ide_workbench_get_perspective_by_name (workbench, "editor");
-  g_assert (editor != NULL);
-
-  pane = ide_layout_get_left_pane (IDE_LAYOUT (editor));
-  g_assert (pane != NULL);
-
-  ide_layout_pane_remove_page (IDE_LAYOUT_PANE (pane),
-                               gtk_widget_get_parent (GTK_WIDGET (self->tree)));
+  gtk_widget_destroy (self->panel);
+  self->panel = NULL;
 }
 
 static void
diff --git a/plugins/symbol-tree/symbol-tree-panel.c b/plugins/symbol-tree/symbol-tree-panel.c
index ae1c093..8145fd4 100644
--- a/plugins/symbol-tree/symbol-tree-panel.c
+++ b/plugins/symbol-tree/symbol-tree-panel.c
@@ -34,7 +34,7 @@
 
 struct _SymbolTreePanel
 {
-  GtkBox parent_instance;
+  PnlDockWidget   parent_instance;
 
   GCancellable   *cancellable;
   EggTaskCache   *symbols_cache;
@@ -47,7 +47,7 @@ struct _SymbolTreePanel
   guint           refresh_tree_timeout;
 };
 
-G_DEFINE_TYPE (SymbolTreePanel, symbol_tree_panel, GTK_TYPE_BOX)
+G_DEFINE_TYPE (SymbolTreePanel, symbol_tree_panel, PNL_TYPE_DOCK_WIDGET)
 
 static void refresh_tree (SymbolTreePanel *self);
 
@@ -331,7 +331,8 @@ symbol_tree_panel_init (SymbolTreePanel *self)
                                             NULL);
 
   gtk_widget_init_template (GTK_WIDGET (self));
-  gtk_widget_show (GTK_WIDGET (self));
+
+  g_object_set (self, "title", _("Symbols"), NULL);
 
   root = ide_tree_node_new ();
   ide_tree_set_root (self->tree, root);
diff --git a/plugins/symbol-tree/symbol-tree-panel.h b/plugins/symbol-tree/symbol-tree-panel.h
index 0a6599c..128fb99 100644
--- a/plugins/symbol-tree/symbol-tree-panel.h
+++ b/plugins/symbol-tree/symbol-tree-panel.h
@@ -19,13 +19,13 @@
 #ifndef SYMBOL_TREE_PANEL_H
 #define SYMBOL_TREE_PANEL_H
 
-#include <gtk/gtk.h>
+#include <ide.h>
 
 G_BEGIN_DECLS
 
 #define SYMBOL_TYPE_TREE_PANEL (symbol_tree_panel_get_type())
 
-G_DECLARE_FINAL_TYPE (SymbolTreePanel, symbol_tree_panel, SYMBOL, TREE_PANEL, GtkBox)
+G_DECLARE_FINAL_TYPE (SymbolTreePanel, symbol_tree_panel, SYMBOL, TREE_PANEL, PnlDockWidget)
 
 void symbol_tree_panel_reset (SymbolTreePanel *self);
 
diff --git a/plugins/symbol-tree/symbol-tree-panel.ui b/plugins/symbol-tree/symbol-tree-panel.ui
index e7f0c3a..c6f3050 100644
--- a/plugins/symbol-tree/symbol-tree-panel.ui
+++ b/plugins/symbol-tree/symbol-tree-panel.ui
@@ -1,25 +1,30 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <!-- interface-requires gtk+ 3.16 -->
-  <template class="SymbolTreePanel" parent="GtkBox">
+  <template class="SymbolTreePanel" parent="PnlDockWidget">
     <property name="vexpand">true</property>
     <property name="visible">true</property>
-    <property name="orientation">vertical</property>
     <child>
-      <object class="GtkSearchEntry" id="search_entry">
-        <property name="visible">true</property>
-      </object>
-    </child>
-    <child>
-      <object class="GtkScrolledWindow">
-        <property name="expand">true</property>
+      <object class="GtkBox">
         <property name="visible">true</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkSearchEntry" id="search_entry">
+            <property name="visible">true</property>
+          </object>
+        </child>
         <child>
-          <object class="IdeTree" id="tree">
-            <property name="activate-on-single-click">true</property>
-            <property name="headers-visible">false</property>
-            <property name="show-icons">true</property>
+          <object class="GtkScrolledWindow">
+            <property name="expand">true</property>
             <property name="visible">true</property>
+            <child>
+              <object class="IdeTree" id="tree">
+                <property name="activate-on-single-click">true</property>
+                <property name="headers-visible">false</property>
+                <property name="show-icons">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
           </object>
         </child>
       </object>
diff --git a/plugins/symbol-tree/symbol-tree.c b/plugins/symbol-tree/symbol-tree.c
index 10c3ef2..785521a 100644
--- a/plugins/symbol-tree/symbol-tree.c
+++ b/plugins/symbol-tree/symbol-tree.c
@@ -74,15 +74,13 @@ symbol_tree_load (IdeWorkbenchAddin *addin,
                            self,
                            G_CONNECT_SWAPPED);
 
-  right_pane = ide_layout_get_right_pane (IDE_LAYOUT (perspective));
+  right_pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (perspective));
   g_assert (right_pane != NULL);
 
-  self->panel = g_object_new (SYMBOL_TYPE_TREE_PANEL, NULL);
-
-  ide_layout_pane_add_page (IDE_LAYOUT_PANE (right_pane),
-                            GTK_WIDGET (self->panel),
-                            _("Symbols"),
-                            "lang-function-symbolic");
+  self->panel = g_object_new (SYMBOL_TYPE_TREE_PANEL,
+                              "visible", TRUE,
+                              NULL);
+  gtk_container_add (GTK_CONTAINER (right_pane), GTK_WIDGET (self->panel));
 
   gtk_container_child_set (GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET (self->panel))),
                            GTK_WIDGET (self->panel),
@@ -106,10 +104,11 @@ symbol_tree_unload (IdeWorkbenchAddin *addin,
   perspective = ide_workbench_get_perspective_by_name (workbench, "editor");
   g_assert (IDE_IS_LAYOUT (perspective));
 
-  pane = ide_layout_get_right_pane (IDE_LAYOUT (perspective));
+  pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (perspective));
   g_assert (IDE_IS_LAYOUT_PANE (pane));
 
-  ide_layout_pane_remove_page (IDE_LAYOUT_PANE (pane), GTK_WIDGET (self->panel));
+  gtk_widget_destroy (GTK_WIDGET (self->panel));
+  self->panel = NULL;
 }
 
 
diff --git a/plugins/sysmon/gb-sysmon-addin.c b/plugins/sysmon/gb-sysmon-addin.c
index 5a93899..b41710e 100644
--- a/plugins/sysmon/gb-sysmon-addin.c
+++ b/plugins/sysmon/gb-sysmon-addin.c
@@ -53,23 +53,29 @@ gb_sysmon_addin_load (IdeWorkbenchAddin *addin,
   g_assert (editor != NULL);
   g_assert (IDE_IS_LAYOUT (editor));
 
-  pane = ide_layout_get_bottom_pane (IDE_LAYOUT (editor));
+  pane = pnl_dock_bin_get_bottom_edge (PNL_DOCK_BIN (editor));
   panel = g_object_new (GB_TYPE_SYSMON_PANEL,
+                        "expand", TRUE,
                         "visible", TRUE,
                         NULL);
   ide_set_weak_pointer (&self->panel, panel);
-  ide_layout_pane_add_page (IDE_LAYOUT_PANE (pane),
-                            GTK_WIDGET (panel),
-                            _("System Monitor"),
-                            "utilities-system-monitor-symbolic");
+  gtk_container_add (GTK_CONTAINER (pane), GTK_WIDGET (panel));
 }
 
 static void
 gb_sysmon_addin_unload (IdeWorkbenchAddin *addin,
                         IdeWorkbench      *workbench)
 {
+  GbSysmonAddin *self = (GbSysmonAddin *)addin;
+
   g_assert (GB_IS_SYSMON_ADDIN (addin));
   g_assert (IDE_IS_WORKBENCH (workbench));
+
+  if (self->panel != NULL)
+    {
+      gtk_widget_destroy (self->panel);
+      ide_clear_weak_pointer (&self->panel);
+    }
 }
 
 static void
diff --git a/plugins/sysmon/gb-sysmon-panel.c b/plugins/sysmon/gb-sysmon-panel.c
index dd25afe..d8b25df 100644
--- a/plugins/sysmon/gb-sysmon-panel.c
+++ b/plugins/sysmon/gb-sysmon-panel.c
@@ -16,18 +16,17 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <glib/gi18n.h>
 #include <realtime-graphs.h>
 
 #include "gb-sysmon-panel.h"
 
 struct _GbSysmonPanel
 {
-  GtkBox      parent_instance;
-  RgCpuGraph *cpu_graph;
+  PnlDockWidget  parent_instance;
+  RgCpuGraph    *cpu_graph;
 };
 
-G_DEFINE_TYPE (GbSysmonPanel, gb_sysmon_panel, GTK_TYPE_BOX)
+G_DEFINE_TYPE (GbSysmonPanel, gb_sysmon_panel, PNL_TYPE_DOCK_WIDGET)
 
 static void
 gb_sysmon_panel_finalize (GObject *object)
diff --git a/plugins/sysmon/gb-sysmon-panel.h b/plugins/sysmon/gb-sysmon-panel.h
index 195e77f..02d5950 100644
--- a/plugins/sysmon/gb-sysmon-panel.h
+++ b/plugins/sysmon/gb-sysmon-panel.h
@@ -19,13 +19,13 @@
 #ifndef GB_SYSMON_PANEL_H
 #define GB_SYSMON_PANEL_H
 
-#include <gtk/gtk.h>
+#include <ide.h>
 
 G_BEGIN_DECLS
 
 #define GB_TYPE_SYSMON_PANEL (gb_sysmon_panel_get_type())
 
-G_DECLARE_FINAL_TYPE (GbSysmonPanel, gb_sysmon_panel, GB, SYSMON_PANEL, GtkBox)
+G_DECLARE_FINAL_TYPE (GbSysmonPanel, gb_sysmon_panel, GB, SYSMON_PANEL, PnlDockWidget)
 
 G_END_DECLS
 
diff --git a/plugins/sysmon/gb-sysmon-panel.ui b/plugins/sysmon/gb-sysmon-panel.ui
index 196b564..eed139e 100644
--- a/plugins/sysmon/gb-sysmon-panel.ui
+++ b/plugins/sysmon/gb-sysmon-panel.ui
@@ -1,7 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <!-- interface-requires gtk+ 3.16 -->
-  <template class="GbSysmonPanel" parent="GtkBox">
+  <template class="GbSysmonPanel" parent="PnlDockWidget">
+    <property name="title" translatable="yes">System Monitor</property>
     <property name="visible">true</property>
     <child>
       <object class="RgCpuGraph" id="cpu_graph">
diff --git a/plugins/terminal/gb-terminal-workbench-addin.c b/plugins/terminal/gb-terminal-workbench-addin.c
index 2845e18..8b0a412 100644
--- a/plugins/terminal/gb-terminal-workbench-addin.c
+++ b/plugins/terminal/gb-terminal-workbench-addin.c
@@ -27,7 +27,9 @@ struct _GbTerminalWorkbenchAddin
   GObject         parent_instance;
 
   IdeWorkbench   *workbench;
+
   GbTerminalView *panel_terminal;
+  GtkWidget      *panel_dock_widget;
 };
 
 static void workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface);
@@ -83,21 +85,28 @@ gb_terminal_workbench_addin_load (IdeWorkbenchAddin *addin,
 
   if (self->panel_terminal == NULL)
     {
+      self->panel_dock_widget = g_object_new (PNL_TYPE_DOCK_WIDGET,
+                                              "expand", TRUE,
+                                              "title", _("Terminal"),
+                                              "visible", TRUE,
+                                              NULL);
       self->panel_terminal = g_object_new (GB_TYPE_TERMINAL_VIEW,
                                            "visible", TRUE,
                                            NULL);
+      gtk_container_add (GTK_CONTAINER (self->panel_dock_widget),
+                         GTK_WIDGET (self->panel_terminal));
+
       g_object_add_weak_pointer (G_OBJECT (self->panel_terminal),
                                  (gpointer *)&self->panel_terminal);
+      g_object_add_weak_pointer (G_OBJECT (self->panel_dock_widget),
+                                 (gpointer *)&self->panel_dock_widget);
     }
 
   perspective = ide_workbench_get_perspective_by_name (workbench, "editor");
   g_assert (IDE_IS_LAYOUT (perspective));
 
-  bottom_pane = ide_layout_get_bottom_pane (IDE_LAYOUT (perspective));
-  ide_layout_pane_add_page (IDE_LAYOUT_PANE (bottom_pane),
-                            GTK_WIDGET (self->panel_terminal),
-                            _("Terminal"),
-                            "utilities-terminal-symbolic");
+  bottom_pane = pnl_dock_bin_get_bottom_edge (PNL_DOCK_BIN (perspective));
+  gtk_container_add (GTK_CONTAINER (bottom_pane), GTK_WIDGET (self->panel_dock_widget));
 }
 
 static void
@@ -110,12 +119,10 @@ gb_terminal_workbench_addin_unload (IdeWorkbenchAddin *addin,
 
   g_action_map_remove_action (G_ACTION_MAP (self->workbench), "new-terminal");
 
-  if (self->panel_terminal != NULL)
+  if (self->panel_dock_widget != NULL)
     {
-      GtkWidget *parent;
-
-      parent = gtk_widget_get_parent (GTK_WIDGET (self->panel_terminal));
-      gtk_container_remove (GTK_CONTAINER (parent), GTK_WIDGET (self->panel_terminal));
+      gtk_widget_destroy (self->panel_dock_widget);
+      self->panel_dock_widget = NULL;
     }
 }
 
diff --git a/plugins/todo/todo_plugin/__init__.py b/plugins/todo/todo_plugin/__init__.py
index b95a9dd..18e461a 100644
--- a/plugins/todo/todo_plugin/__init__.py
+++ b/plugins/todo/todo_plugin/__init__.py
@@ -27,6 +27,7 @@ from gi.repository import Gio
 from gi.repository import GLib
 from gi.repository import GObject
 from gi.repository import Gtk
+from gi.repository import Pnl
 
 from gettext import gettext as _
 import re
@@ -56,8 +57,8 @@ class TodoWorkbenchAddin(GObject.Object, Ide.WorkbenchAddin):
         # Create our panel to display results
         self.panel = TodoPanel(workdir, visible=True)
         editor = workbench.get_perspective_by_name('editor')
-        pane = editor.get_bottom_pane()
-        pane.add_page(self.panel, _("Todo"), None)
+        pane = editor.get_bottom_edge()
+        pane.add(self.panel)
 
         # Mine the directory in a background thread
         self.mine(workdir)
@@ -171,10 +172,13 @@ class TodoItem(GObject.Object):
             return msg[:msg.index('\n')].strip()
         return msg.strip()
 
-class TodoPanel(Gtk.Bin):
+class TodoPanel(Pnl.DockWidget):
     def __init__(self, basedir, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
+        self.props.title = _("Todo")
+        self.props.expand = True
+
         self.basedir = basedir
         self.model = Gtk.ListStore(TodoItem)
 
diff --git a/src/Makefile.am b/src/Makefile.am
index b2480e2..1e4e25a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -12,11 +12,14 @@ AM_CPPFLAGS = \
        -I$(top_builddir)/data/icons/hicolor \
        -I$(top_builddir)/libide \
        -I$(top_srcdir)/libide \
+       -I$(top_srcdir)/contrib/pnl \
+       -I$(top_builddir)/contrib/pnl \
        $(NULL)
 
 gnome_builder_libs = \
        $(LIBIDE_LIBS) \
        $(top_builddir)/contrib/egg/libegg-private.la \
+       $(top_builddir)/contrib/pnl/libpanel-gtk.la \
        $(top_builddir)/data/icons/hicolor/libicons.la \
        $(top_builddir)/libide/libide-1.0.la \
        $(NULL)
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 19097ad..a09c4f7 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -44,6 +44,8 @@ tests_cflags = \
        $(LIBIDE_CFLAGS) \
        -I$(top_srcdir)/libide \
        -I$(top_builddir)/libide \
+       -I$(top_srcdir)/contrib/pnl \
+       -I$(top_builddir)/contrib/pnl \
        $(NULL)
 
 tests_libs = \


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