[gnome-builder/wip/chergert/perspective] libide: add IdeLayout



commit 2ee8ff94ba981d62ca9715eeb7f13b1923651e6f
Author: Christian Hergert <chergert redhat com>
Date:   Wed Nov 11 00:29:28 2015 -0800

    libide: add IdeLayout
    
    This is a rename from GbWorkspace. There is certainly more I'd like to
    add to this (such as pane splits), but this will keep us moving on the
    refactor so we can merge things sooner.

 data/ui/ide-layout-pane.ui            |   42 ++
 data/ui/ide-layout.ui                 |   30 +
 libide/Makefile.am                    |    6 +-
 libide/ide-layout-manager.c           |   72 --
 libide/ide-layout-manager.h           |   60 --
 libide/ide-layout-pane.c              |  378 ++++++++++
 libide/ide-layout-pane.h              |   43 ++
 libide/ide-layout.c                   | 1218 +++++++++++++++++++++++++++++++++
 libide/ide-layout.h                   |   45 ++
 libide/ide.h                          |    3 +-
 libide/resources/libide.gresource.xml |    2 +
 11 files changed, 1764 insertions(+), 135 deletions(-)
---
diff --git a/data/ui/ide-layout-pane.ui b/data/ui/ide-layout-pane.ui
new file mode 100644
index 0000000..52a6f4f
--- /dev/null
+++ b/data/ui/ide-layout-pane.ui
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.16 -->
+  <template class="IdeLayoutPane" parent="GtkBin">
+    <child>
+      <object class="GtkBox" id="box">
+        <property name="orientation">vertical</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">true</property>
+            <property name="vexpand">false</property>
+            <style>
+              <class name="header"/>
+              <class name="notebook"/>
+            </style>
+            <child type="center">
+              <object class="GtkStackSwitcher" id="stack_switcher">
+                <property name="margin-top">3</property>
+                <property name="margin-bottom">3</property>
+                <property name="margin-start">6</property>
+                <property name="margin-end">6</property>
+                <property name="stack">stack</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="flat"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkStack" id="stack">
+            <property name="expand">true</property>
+            <property name="homogeneous">false</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/ide-layout.ui b/data/ui/ide-layout.ui
new file mode 100644
index 0000000..09f2813
--- /dev/null
+++ b/data/ui/ide-layout.ui
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.8 -->
+  <template class="IdeLayout" parent="GtkOverlay">
+    <child type="overlay">
+      <object class="GbWorkspacePane" id="left_pane">
+        <property name="position">left</property>
+        <property name="visible">true</property>
+      </object>
+    </child>
+    <child type="overlay">
+      <object class="GbWorkspacePane" id="right_pane">
+        <property name="position">right</property>
+        <property name="visible">true</property>
+      </object>
+    </child>
+    <child type="overlay">
+      <object class="GbWorkspacePane" id="bottom_pane">
+        <property name="position">bottom</property>
+        <property name="visible">true</property>
+      </object>
+    </child>
+    <child type="overlay">
+      <object class="GbWorkspacePane" id="content_pane">
+        <property name="position">top</property>
+        <property name="visible">true</property>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 19fb883..ee834c3 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -92,8 +92,10 @@ libide_1_0_la_public_sources = \
        ide-indent-style.h \
        ide-indenter.c \
        ide-indenter.h \
-       ide-layout-manager.c \
-       ide-layout-manager.h \
+       ide-layout.c \
+       ide-layout.h \
+       ide-layout-pane.c \
+       ide-layout-pane.h \
        ide-log.c \
        ide-log.h \
        ide-macros.h \
diff --git a/libide/ide-layout-pane.c b/libide/ide-layout-pane.c
new file mode 100644
index 0000000..2ab0fcd
--- /dev/null
+++ b/libide/ide-layout-pane.c
@@ -0,0 +1,378 @@
+/* ide-layout-pane.c
+ *
+ * Copyright (C) 2015 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 <glib/gi18n.h>
+
+#include "egg-signal-group.h"
+
+#include "ide-layout-pane.h"
+
+struct _IdeLayoutPane
+{
+  GtkBin            parent_instance;
+
+  GtkBox           *box;
+  GtkStackSwitcher *stack_switcher;
+  GtkStack         *stack;
+
+  EggSignalGroup   *toplevel_signals;
+
+  GdkRectangle      handle_pos;
+
+  GtkPositionType   position;
+};
+
+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;
+}
+
+static void
+ide_layout_pane_size_allocate (GtkWidget     *widget,
+                               GtkAllocation *alloc)
+{
+  IdeLayoutPane *self = (IdeLayoutPane *)widget;
+  GtkWidget *child;
+  GtkAllocation child_alloc;
+  gint handle_size;
+
+  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
+workbench_focus_changed (GtkWidget     *toplevel,
+                         GtkWidget     *focus,
+                         IdeLayoutPane *self)
+{
+  GtkStyleContext *style_context;
+  GtkWidget *parent;
+
+  g_assert (GTK_IS_WIDGET (toplevel));
+  g_assert (!focus || GTK_IS_WIDGET (focus));
+  g_assert (IDE_IS_LAYOUT_PANE (self));
+
+  style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+  parent = focus;
+
+  while (parent && (parent != (GtkWidget *)self))
+    {
+      if (GTK_IS_POPOVER (parent))
+        parent = gtk_popover_get_relative_to (GTK_POPOVER (parent));
+      else
+        parent = gtk_widget_get_parent (parent);
+    }
+
+  if (parent == NULL)
+    gtk_style_context_remove_class (style_context, "focused");
+  else
+    gtk_style_context_add_class (style_context, "focused");
+}
+
+static void
+ide_layout_pane_hierarchy_changed (GtkWidget *widget,
+                                   GtkWidget *old_parent)
+{
+  IdeLayoutPane *self = (IdeLayoutPane *)widget;
+  GtkWidget *toplevel;
+
+  g_assert (IDE_IS_LAYOUT_PANE (self));
+
+  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+  if (!GTK_IS_WINDOW (toplevel))
+    toplevel = NULL;
+
+  egg_signal_group_set_target (self->toplevel_signals, toplevel);
+}
+
+static void
+ide_layout_pane_dispose (GObject *object)
+{
+  IdeLayoutPane *self = (IdeLayoutPane *)object;
+
+  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);
+
+  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;
+
+  /**
+   * 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);
+
+  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_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, stack_switcher);
+}
+
+static void
+ide_layout_pane_init (IdeLayoutPane *self)
+{
+  self->toplevel_signals = egg_signal_group_new (GTK_TYPE_WINDOW);
+  egg_signal_group_connect_object (self->toplevel_signals,
+                                   "set-focus",
+                                   G_CALLBACK (workbench_focus_changed),
+                                   self,
+                                   G_CONNECT_AFTER);
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+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]);
+    }
+}
+
+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,
+                                     "icon-name", icon_name,
+                                     "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
new file mode 100644
index 0000000..ab944b9
--- /dev/null
+++ b/libide/ide-layout-pane.h
@@ -0,0 +1,43 @@
+/* ide-layout-pane.h
+ *
+ * Copyright (C) 2015 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 IDE_LAYOUT_PANE_H
+#define IDE_LAYOUT_PANE_H
+
+#include <gtk/gtk.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_END_DECLS
+
+#endif /* IDE_LAYOUT_PANE_H */
diff --git a/libide/ide-layout.c b/libide/ide-layout.c
new file mode 100644
index 0000000..40e3230
--- /dev/null
+++ b/libide/ide-layout.c
@@ -0,0 +1,1218 @@
+/* ide-layout.c
+ *
+ * Copyright (C) 2015 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 <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;
+
+typedef struct
+{
+  IdeLayoutChild    children[4];
+  GtkGesture       *pan_gesture;
+  IdeLayoutChild   *drag_child;
+  gdouble           drag_position;
+} IdeLayoutPrivate;
+
+static void buildable_init_iface (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (IdeLayout, ide_layout, GTK_TYPE_OVERLAY,
+                         G_ADD_PRIVATE (IdeLayout)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_init_iface))
+
+enum {
+  PROP_0,
+  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_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];
+
+  /*
+   * 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_resize (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_resize (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)
+{
+  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");
+    }
+
+  gtk_widget_queue_resize (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_resize),
+                           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_resize (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 void
+ide_layout_finalize (GObject *object)
+{
+  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];
+
+      ide_clear_weak_pointer (&child->animation);
+      g_clear_object (&child->adjustment);
+    }
+
+  g_clear_object (&priv->pan_gesture);
+
+  G_OBJECT_CLASS (ide_layout_parent_class)->finalize (object);
+}
+
+static void
+ide_layout_get_property (GObject    *object,
+                         guint       prop_id,
+                         GValue     *value,
+                         GParamSpec *pspec)
+{
+  IdeLayout *self = IDE_LAYOUT (object);
+
+  switch (prop_id)
+    {
+    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);
+    }
+}
+
+static void
+ide_layout_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
+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);
+
+  object_class->finalize = ide_layout_finalize;
+  object_class->get_property = ide_layout_get_property;
+  object_class->set_property = ide_layout_set_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->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;
+
+  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));
+
+  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));
+
+  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_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));
+}
+
+GtkWidget *
+ide_layout_new (void)
+{
+  return g_object_new (IDE_TYPE_LAYOUT, NULL);
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+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
new file mode 100644
index 0000000..f293025
--- /dev/null
+++ b/libide/ide-layout.h
@@ -0,0 +1,45 @@
+/* ide-layout.h
+ *
+ * Copyright (C) 2015 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 IDE_LAYOUT_H
+#define IDE_LAYOUT_H
+
+#include <gtk/gtk.h>
+
+#include "ide-layout-pane.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_LAYOUT (ide_layout_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeLayout, ide_layout, IDE, LAYOUT, GtkOverlay)
+
+struct _IdeLayoutClass
+{
+  GtkOverlayClass parent_instance;
+};
+
+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);
+
+G_END_DECLS
+
+#endif /* IDE_LAYOUT_H */
diff --git a/libide/ide.h b/libide/ide.h
index 7168b3b..8d86215 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -57,7 +57,8 @@ G_BEGIN_DECLS
 #include "ide-highlight-engine.h"
 #include "ide-highlighter.h"
 #include "ide-indenter.h"
-#include "ide-layout-manager.h"
+#include "ide-layout.h"
+#include "ide-layout-pane.h"
 #include "ide-log.h"
 #include "ide-macros.h"
 #include "ide-object.h"
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index 35d6560..957ad37 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -25,6 +25,8 @@
     <file alias="ui/ide-editor-perspective.ui">../../data/ui/ide-editor-perspective.ui</file>
     <file alias="ui/ide-greeter-perspective.ui">../../data/ui/ide-greeter-perspective.ui</file>
     <file alias="ui/ide-greeter-project-row.ui">../../data/ui/ide-greeter-project-row.ui</file>
+    <file alias="ui/ide-layout.ui">../../data/ui/ide-layout.ui</file>
+    <file alias="ui/ide-layout-pane.ui">../../data/ui/ide-layout-pane.ui</file>
     <file alias="ui/ide-preferences-entry.ui">../../data/ui/ide-preferences-entry.ui</file>
     <file alias="ui/ide-preferences-font-button.ui">../../data/ui/ide-preferences-font-button.ui</file>
     <file alias="ui/ide-preferences-group.ui">../../data/ui/ide-preferences-group.ui</file>


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