[gnome-builder/wip/plugins: 4/36] workspace: sketch out new workspace layout



commit d05d5be52e47716fc4fb0c6df52e78e86545623e
Author: Christian Hergert <christian hergert me>
Date:   Fri Jun 5 18:08:35 2015 -0700

    workspace: sketch out new workspace layout
    
    This adds a new custom layout manager, GbWorkspace and GbWorkspacePane to
    contain the children of the workspace.
    
    This is not a full docking library, on purpose. It only supports animated
    panes on left, right, and bottom.
    
    It does, however, allow us to avoid using GtkPaned, which has been a
    nightmare for keeping sizing information how we want.
    
    I expect GbViewGrid will get rewritten as part of this to be a custom
    multi-paned in the same way that GbWorkspace is.
    
    I think we need to dive into GtkTextView a bit, and do some performance
    work there with overzealous invalidations. For example, when shrinking
    or growing the textwindow, as long as we don't render an invalid area,
    we can keep using the pixel cached content. We should be more lazy about
    destroying/repainting the textview. (Should help make animations smoother).
    
    Anyway, is what it is. Very eary draft, tons to do, and will likely get
    rebased often.

 data/theme/Adwaita-shared.css              |    7 +
 data/theme/shared.css                      |    9 +
 data/ui/gb-workbench.ui                    |   91 ++-
 data/ui/gb-workspace-pane.ui               |   41 +
 data/ui/gb-workspace.ui                    |   30 +
 src/Makefile.am                            |   14 +-
 src/app/gb-application.c                   |    5 +-
 src/commands/gb-command-bar.c              |    7 +-
 src/commands/gb-command-gaction-provider.c |    3 +-
 src/editor/gb-editor-view-actions.c        |    6 +
 src/resources/gnome-builder.gresource.xml  |    3 +-
 src/search/gb-search-box.c                 |    6 +-
 src/workbench/gb-workbench-actions.c       |  107 +++
 src/workbench/gb-workbench-private.h       |    6 +-
 src/workbench/gb-workbench-types.h         |    1 -
 src/workbench/gb-workbench.c               |   87 +--
 src/workbench/gb-workbench.h               |    6 -
 src/workbench/gb-workspace.c               |  221 ------
 src/workbench/gb-workspace.h               |   53 --
 src/workspace/gb-workspace-pane.c          |  275 +++++++
 src/workspace/gb-workspace-pane.h          |   43 +
 src/workspace/gb-workspace.c               | 1137 ++++++++++++++++++++++++++++
 src/workspace/gb-workspace.h               |   40 +
 23 files changed, 1809 insertions(+), 389 deletions(-)
---
diff --git a/data/theme/Adwaita-shared.css b/data/theme/Adwaita-shared.css
index 283d7c1..96dc651 100644
--- a/data/theme/Adwaita-shared.css
+++ b/data/theme/Adwaita-shared.css
@@ -38,3 +38,10 @@ GbNewProjectDialog GtkFileChooserButton.linked-on-right .button {
   border-radius: 3px 0 0 3px;
 }
 
+
+/*
+ * Workspace pane header styling.
+ */
+GbWorkspacePane GtkBox.header {
+  border-bottom: 1px solid @borders;
+}
diff --git a/data/theme/shared.css b/data/theme/shared.css
index 96e876f..b0cafd6 100644
--- a/data/theme/shared.css
+++ b/data/theme/shared.css
@@ -110,3 +110,12 @@ GbGreeterWindow GtkFrame {
   border-right: 1px solid alpha(@borders, 0.4);
   border-bottom: none;
 }
+
+
+GbWorkspacePane {
+  -GbWorkspacePane-handle-size: 1;
+  -gtk-icon-source: none;
+}
+GbWorkspacePane .pane-separator {
+  background-color: @borders;
+}
diff --git a/data/ui/gb-workbench.ui b/data/ui/gb-workbench.ui
index f3f17f6..20b6cd3 100644
--- a/data/ui/gb-workbench.ui
+++ b/data/ui/gb-workbench.ui
@@ -32,6 +32,40 @@
             <property name="pack_type">end</property>
           </packing>
         </child>
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="visible">true</property>
+            <property name="margin-end">12</property>
+            <style>
+              <class name="linked"/>
+            </style>
+            <child>
+              <object class="GtkToggleButton">
+                <property name="action-name">workbench.show-left-pane</property>
+                <property name="label">L</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkToggleButton">
+                <property name="action-name">workbench.show-bottom-pane</property>
+                <property name="label">B</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkToggleButton">
+                <property name="action-name">workbench.show-right-pane</property>
+                <property name="label">R</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="pack_type">end</property>
+          </packing>
+        </child>
       </object>
     </child>
     <child>
@@ -39,15 +73,60 @@
         <property name="orientation">vertical</property>
         <property name="visible">true</property>
         <child>
-          <object class="GtkStack" id="stack">
+          <object class="GbWorkspace" id="workspace">
             <property name="expand">true</property>
-            <property name="transition_type">slide-up-down</property>
             <property name="visible">true</property>
-            <property name="homogeneous">false</property>
-            <child>
-              <object class="GbEditorWorkspace" id="editor_workspace">
-                <property name="visible">true</property>
+            <child internal-child="content_pane">
+              <object class="GbWorkspacePane">
+                <child internal-child="stack_switcher">
+                  <object class="GtkStackSwitcher">
+                    <property name="visible">false</property>
+                  </object>
+                </child>
+                <child internal-child="stack">
+                  <object class="GtkStack">
+                    <child>
+                      <object class="GbViewGrid" id="view_grid">
+                        <property name="visible">true</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child internal-child="left_pane">
+              <object class="GbWorkspacePane">
+                <child internal-child="stack">
+                  <object class="GtkStack">
+                    <child>
+                      <object class="GbProjectTree" id="project_tree">
+                        <property name="headers-visible">false</property>
+                        <property name="vexpand">true</property>
+                        <property name="visible">true</property>
+                      </object>
+                      <packing>
+                        <property name="icon-name">folder-symbolic</property>
+                        <property name="name">project_tree</property>
+                        <property name="title" translatable="yes">Project</property>
+                      </packing>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child internal-child="right_pane">
+              <object class="GbWorkspacePane">
+              </object>
+              <packing>
+                <property name="reveal">false</property>
+              </packing>
+            </child>
+            <child internal-child="bottom_pane">
+              <object class="GbWorkspacePane">
               </object>
+              <packing>
+                <property name="reveal">false</property>
+              </packing>
             </child>
           </object>
         </child>
diff --git a/data/ui/gb-workspace-pane.ui b/data/ui/gb-workspace-pane.ui
new file mode 100644
index 0000000..c3e7ffe
--- /dev/null
+++ b/data/ui/gb-workspace-pane.ui
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.16 -->
+  <template class="GbWorkspacePane" 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="dim-label"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkStack" id="stack">
+            <property name="expand">true</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/gb-workspace.ui b/data/ui/gb-workspace.ui
new file mode 100644
index 0000000..5dc5073
--- /dev/null
+++ b/data/ui/gb-workspace.ui
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.8 -->
+  <template class="GbWorkspace" 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/src/Makefile.am b/src/Makefile.am
index 52926a7..6b4b3e9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -65,11 +65,6 @@ libgnome_builder_la_SOURCES = \
        editor/gb-editor-view-private.h \
        editor/gb-editor-view.c \
        editor/gb-editor-view.h \
-       editor/gb-editor-workspace-actions.c \
-       editor/gb-editor-workspace-actions.h \
-       editor/gb-editor-workspace-private.h \
-       editor/gb-editor-workspace.c \
-       editor/gb-editor-workspace.h \
        gd/gd-tagged-entry.c \
        gd/gd-tagged-entry.h \
        greeter/gb-greeter-pill-box.c \
@@ -167,11 +162,13 @@ libgnome_builder_la_SOURCES = \
        workbench/gb-workbench-actions.c \
        workbench/gb-workbench-actions.h \
        workbench/gb-workbench-private.h \
-       workbench/gb-workbench-types.h \
        workbench/gb-workbench.c \
        workbench/gb-workbench.h \
-       workbench/gb-workspace.c \
-       workbench/gb-workspace.h \
+       workbench/gb-workbench-types.h \
+       workspace/gb-workspace.c \
+       workspace/gb-workspace.h \
+       workspace/gb-workspace-pane.c \
+       workspace/gb-workspace-pane.h \
        css/gb-css-provider.c \
        css/gb-css-provider.h \
        util/gb-settings.c \
@@ -223,6 +220,7 @@ libgnome_builder_la_CFLAGS = \
        -I$(srcdir)/views \
        -I$(srcdir)/vim \
        -I$(srcdir)/workbench \
+       -I$(srcdir)/workspace \
        -I$(top_builddir)/libide \
        -I$(top_srcdir)/libide \
        -I$(top_srcdir)/contrib/egg \
diff --git a/src/app/gb-application.c b/src/app/gb-application.c
index 8a51fd0..157255d 100644
--- a/src/app/gb-application.c
+++ b/src/app/gb-application.c
@@ -31,7 +31,6 @@
 #include "gb-application-private.h"
 #include "gb-css-provider.h"
 #include "gb-editor-document.h"
-#include "gb-editor-workspace.h"
 #include "gb-glib.h"
 #include "gb-greeter-window.h"
 #include "gb-projects-dialog.h"
@@ -136,7 +135,9 @@ gb_application_load_keybindings (GbApplication *self)
   g_autoptr(GSettings) settings = NULL;
   g_autofree gchar *name = NULL;
   static const struct { gchar *name; gchar *binding; } shared_bindings[] = {
-    { "workspace.toggle-sidebar", "F9" },
+    { "workbench.show-left-pane", "F9" },
+    { "workbench.show-right-pane", "<shift>F9" },
+    { "workbench.show-bottom-pane", "<ctrl>F9" },
     { "workspace.focus-sidebar", "<ctrl>0" },
     { "workspace.focus-stack(1)", "<ctrl>1" },
     { "workspace.focus-stack(2)", "<ctrl>2" },
diff --git a/src/commands/gb-command-bar.c b/src/commands/gb-command-bar.c
index b4735c2..ebca621 100644
--- a/src/commands/gb-command-bar.c
+++ b/src/commands/gb-command-bar.c
@@ -108,7 +108,6 @@ void
 gb_command_bar_hide (GbCommandBar *self)
 {
   GbWorkbench *workbench;
-  GbWorkspace *workspace;
   GtkWidget *focus;
 
   g_return_if_fail (GB_IS_COMMAND_BAR (self));
@@ -122,14 +121,10 @@ gb_command_bar_hide (GbCommandBar *self)
   if ((workbench == NULL) || gb_workbench_get_closing (workbench))
     return;
 
-  workspace = gb_workbench_get_active_workspace (workbench);
-  if (workspace == NULL)
-    return;
-
   if (self->last_focus)
     focus = find_alternate_focus (self->last_focus);
   else
-    focus = GTK_WIDGET (workspace);
+    focus = GTK_WIDGET (workbench);
 
   gtk_widget_grab_focus (focus);
 }
diff --git a/src/commands/gb-command-gaction-provider.c b/src/commands/gb-command-gaction-provider.c
index 5199335..37115b3 100644
--- a/src/commands/gb-command-gaction-provider.c
+++ b/src/commands/gb-command-gaction-provider.c
@@ -22,7 +22,6 @@
 #include <string.h>
 #include <glib.h>
 
-#include "gb-editor-workspace.h"
 #include "gb-editor-view.h"
 
 #include "gb-command-gaction-provider.h"
@@ -180,7 +179,7 @@ discover_groups (GbCommandGactionProvider *provider)
 
       /* We exclude these types, they're already in the widgets hierarchy */
       type = G_OBJECT_TYPE (widget);
-      if (type == GB_TYPE_EDITOR_WORKSPACE || type == GB_TYPE_EDITOR_VIEW)
+      if (type == GB_TYPE_EDITOR_VIEW)
         continue;
 
       prefixes = gtk_widget_list_action_prefixes (widget);
diff --git a/src/editor/gb-editor-view-actions.c b/src/editor/gb-editor-view-actions.c
index d935d76..1ca4563 100644
--- a/src/editor/gb-editor-view-actions.c
+++ b/src/editor/gb-editor-view-actions.c
@@ -31,6 +31,7 @@
 #include "gb-widget.h"
 #include "gb-workbench.h"
 
+#if 0
 static void
 gb_editor_view_actions_source_view_notify (IdeSourceView *source_view,
                                            GParamSpec    *pspec,
@@ -709,10 +710,12 @@ static GActionEntry GbEditorViewActions[] = {
   { "toggle-split", gb_editor_view_actions_toggle_split },
   { "use-spaces", NULL, "b", "false", gb_editor_view_actions_use_spaces },
 };
+#endif
 
 void
 gb_editor_view_actions_init (GbEditorView *self)
 {
+#if 0
   g_autoptr(GSimpleActionGroup) group = NULL;
 
   group = g_simple_action_group_new ();
@@ -739,11 +742,13 @@ gb_editor_view_actions_init (GbEditorView *self)
   WATCH_PROPERTY ("tab-width");
 
 #undef WATCH_PROPERTY
+#endif
 }
 
 void
 gb_editor_view_actions_update (GbEditorView *self)
 {
+#if 0
   GtkSourceLanguage *language;
   const gchar *lang_id = NULL;
   GActionGroup *group;
@@ -764,4 +769,5 @@ gb_editor_view_actions_update (GbEditorView *self)
              (g_strcmp0 (lang_id, "markdown") == 0));
   action = g_action_map_lookup_action (G_ACTION_MAP (group), "preview");
   g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
+#endif
 }
diff --git a/src/resources/gnome-builder.gresource.xml b/src/resources/gnome-builder.gresource.xml
index c9d42d9..d1c25e8 100644
--- a/src/resources/gnome-builder.gresource.xml
+++ b/src/resources/gnome-builder.gresource.xml
@@ -48,7 +48,6 @@
     <file alias="ui/gb-editor-settings-widget.ui">../../data/ui/gb-editor-settings-widget.ui</file>
     <file alias="ui/gb-editor-tweak-widget.ui">../../data/ui/gb-editor-tweak-widget.ui</file>
     <file alias="ui/gb-editor-view.ui">../../data/ui/gb-editor-view.ui</file>
-    <file alias="ui/gb-editor-workspace.ui">../../data/ui/gb-editor-workspace.ui</file>
     <file alias="ui/gb-greeter-pill-box.ui">../../data/ui/gb-greeter-pill-box.ui</file>
     <file alias="ui/gb-greeter-project-row.ui">../../data/ui/gb-greeter-project-row.ui</file>
     <file alias="ui/gb-greeter-window.ui">../../data/ui/gb-greeter-window.ui</file>
@@ -71,5 +70,7 @@
     <file alias="ui/gb-search-display-row.ui">../../data/ui/gb-search-display-row.ui</file>
     <file alias="ui/gb-view-stack.ui">../../data/ui/gb-view-stack.ui</file>
     <file alias="ui/gb-workbench.ui">../../data/ui/gb-workbench.ui</file>
+    <file alias="ui/gb-workspace.ui">../../data/ui/gb-workspace.ui</file>
+    <file alias="ui/gb-workspace-pane.ui">../../data/ui/gb-workspace-pane.ui</file>
   </gresource>
 </gresources>
diff --git a/src/search/gb-search-box.c b/src/search/gb-search-box.c
index 77b39b1..f4fe3d8 100644
--- a/src/search/gb-search-box.c
+++ b/src/search/gb-search-box.c
@@ -20,7 +20,6 @@
 
 #include <glib/gi18n.h>
 
-#include "gb-editor-workspace.h"
 #include "gb-glib.h"
 #include "gb-scrolled-window.h"
 #include "gb-search-box.h"
@@ -258,11 +257,10 @@ gb_search_box_display_result_activated (GbSearchBox     *self,
   else if (IDE_IS_DEVHELP_SEARCH_RESULT (result))
     {
       g_autofree gchar *uri = NULL;
-      GbEditorWorkspace *workspace;
 
       g_object_get (result, "uri", &uri, NULL);
-      workspace = gb_workbench_get_workspace_typed (workbench, GB_TYPE_EDITOR_WORKSPACE);
-      gb_editor_workspace_show_help (workspace, uri);
+      //workspace = gb_workbench_get_workspace_typed (workbench, GB_TYPE_EDITOR_WORKSPACE);
+      //gb_editor_workspace_show_help (workspace, uri);
     }
   else
     {
diff --git a/src/workbench/gb-workbench-actions.c b/src/workbench/gb-workbench-actions.c
index 8ebf0a8..fcfdca8 100644
--- a/src/workbench/gb-workbench-actions.c
+++ b/src/workbench/gb-workbench-actions.c
@@ -278,6 +278,7 @@ gb_workbench_actions_search_docs (GSimpleAction *action,
                                   GVariant      *parameter,
                                   gpointer       user_data)
 {
+#if 0
   GbWorkbench *self = user_data;
   const gchar *str;
 
@@ -285,6 +286,7 @@ gb_workbench_actions_search_docs (GSimpleAction *action,
 
   str = g_variant_get_string (parameter, NULL);
   gb_editor_workspace_search_help (self->editor_workspace, str);
+#endif
 }
 
 static void
@@ -299,6 +301,83 @@ gb_workbench_actions_show_gear_menu (GSimpleAction *action,
   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->gear_menu_button), TRUE);
 }
 
+static void
+gb_workbench_actions_show_left_pane (GSimpleAction *action,
+                                     GVariant      *parameter,
+                                     gpointer       user_data)
+{
+  GbWorkbench *self = user_data;
+  GtkWidget *left_pane;
+  gboolean reveal = FALSE;
+
+  g_assert (GB_IS_WORKBENCH (self));
+
+  left_pane = gb_workspace_get_left_pane (self->workspace);
+  gtk_container_child_get (GTK_CONTAINER (self->workspace), left_pane,
+                           "reveal", &reveal,
+                           NULL);
+  gtk_container_child_set (GTK_CONTAINER (self->workspace), left_pane,
+                           "reveal", !reveal,
+                           NULL);
+}
+
+static void
+gb_workbench_actions_show_right_pane (GSimpleAction *action,
+                                     GVariant      *parameter,
+                                     gpointer       user_data)
+{
+  GbWorkbench *self = user_data;
+  GtkWidget *right_pane;
+  gboolean reveal = FALSE;
+
+  g_assert (GB_IS_WORKBENCH (self));
+
+  right_pane = gb_workspace_get_right_pane (self->workspace);
+  gtk_container_child_get (GTK_CONTAINER (self->workspace), right_pane,
+                           "reveal", &reveal,
+                           NULL);
+  gtk_container_child_set (GTK_CONTAINER (self->workspace), right_pane,
+                           "reveal", !reveal,
+                           NULL);
+}
+
+static void
+gb_workbench_actions_show_bottom_pane (GSimpleAction *action,
+                                     GVariant      *parameter,
+                                     gpointer       user_data)
+{
+  GbWorkbench *self = user_data;
+  GtkWidget *bottom_pane;
+  gboolean reveal = FALSE;
+
+  g_assert (GB_IS_WORKBENCH (self));
+
+  bottom_pane = gb_workspace_get_bottom_pane (self->workspace);
+  gtk_container_child_get (GTK_CONTAINER (self->workspace), bottom_pane,
+                           "reveal", &reveal,
+                           NULL);
+  gtk_container_child_set (GTK_CONTAINER (self->workspace), bottom_pane,
+                           "reveal", !reveal,
+                           NULL);
+}
+
+static void
+sync_reveal_state (GtkWidget     *child,
+                   GParamSpec    *pspec,
+                   GSimpleAction *action)
+{
+  gboolean reveal = FALSE;
+
+  g_assert (GB_IS_WORKSPACE_PANE (child));
+  g_assert (pspec != NULL);
+  g_assert (G_IS_SIMPLE_ACTION (action));
+
+  gtk_container_child_get (GTK_CONTAINER (gtk_widget_get_parent (child)), child,
+                           "reveal", &reveal,
+                           NULL);
+  g_simple_action_set_state (action, g_variant_new_boolean (reveal));
+}
+
 static const GActionEntry GbWorkbenchActions[] = {
   { "build",            gb_workbench_actions_build },
   { "dayhack",          gb_workbench_actions_dayhack },
@@ -313,6 +392,9 @@ static const GActionEntry GbWorkbenchActions[] = {
   { "search-docs",      gb_workbench_actions_search_docs, "s" },
   { "show-command-bar", gb_workbench_actions_show_command_bar },
   { "show-gear-menu",   gb_workbench_actions_show_gear_menu },
+  { "show-left-pane",   gb_workbench_actions_show_left_pane, NULL, "true" },
+  { "show-right-pane",  gb_workbench_actions_show_right_pane, NULL, "false" },
+  { "show-bottom-pane", gb_workbench_actions_show_bottom_pane, NULL, "false" },
 };
 
 void
@@ -335,5 +417,30 @@ gb_workbench_actions_init (GbWorkbench *self)
   g_object_bind_property (self, "building", action, "enabled",
                           (G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN));
 
+  action = g_action_map_lookup_action (G_ACTION_MAP (actions), "rebuild");
+  g_object_bind_property (self, "building", action, "enabled",
+                          (G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN));
+
+  action = g_action_map_lookup_action (G_ACTION_MAP (actions), "show-left-pane");
+  g_signal_connect_object (gb_workspace_get_left_pane (self->workspace),
+                           "child-notify::reveal",
+                           G_CALLBACK (sync_reveal_state),
+                           action,
+                           0);
+
+  action = g_action_map_lookup_action (G_ACTION_MAP (actions), "show-right-pane");
+  g_signal_connect_object (gb_workspace_get_right_pane (self->workspace),
+                           "child-notify::reveal",
+                           G_CALLBACK (sync_reveal_state),
+                           action,
+                           0);
+
+  action = g_action_map_lookup_action (G_ACTION_MAP (actions), "show-bottom-pane");
+  g_signal_connect_object (gb_workspace_get_bottom_pane (self->workspace),
+                           "child-notify::reveal",
+                           G_CALLBACK (sync_reveal_state),
+                           action,
+                           0);
+
   gtk_widget_insert_action_group (GTK_WIDGET (self), "workbench", G_ACTION_GROUP (actions));
 }
diff --git a/src/workbench/gb-workbench-private.h b/src/workbench/gb-workbench-private.h
index 3d99018..d48c5a8 100644
--- a/src/workbench/gb-workbench-private.h
+++ b/src/workbench/gb-workbench-private.h
@@ -41,15 +41,11 @@ struct _GbWorkbench
   GCancellable           *unload_cancellable;
   gchar                  *current_folder_uri;
 
-  /* Weak reference */
-  GbWorkspace            *active_workspace;
-
   /* Template references */
   GbCommandBar           *command_bar;
-  GbEditorWorkspace      *editor_workspace;
   GeditMenuStackSwitcher *gear_menu_button;
   GbSearchBox            *search_box;
-  GtkStack               *stack;
+  GbWorkspace            *workspace;
 
   gulong                  project_notify_name_handler;
 
diff --git a/src/workbench/gb-workbench-types.h b/src/workbench/gb-workbench-types.h
index 9c5f6b3..8b95cf3 100644
--- a/src/workbench/gb-workbench-types.h
+++ b/src/workbench/gb-workbench-types.h
@@ -24,7 +24,6 @@
 G_BEGIN_DECLS
 
 typedef struct _GbWorkbench GbWorkbench;
-typedef struct _GbWorkspace GbWorkspace;
 
 G_END_DECLS
 
diff --git a/src/workbench/gb-workbench.c b/src/workbench/gb-workbench.c
index 337bd4c..7f322bc 100644
--- a/src/workbench/gb-workbench.c
+++ b/src/workbench/gb-workbench.c
@@ -30,6 +30,9 @@
 #include "gb-workbench-private.h"
 #include "gb-workbench.h"
 #include "gb-workspace.h"
+#include "gb-workspace-pane.h"
+#include "gb-project-tree.h"
+#include "gb-view-grid.h"
 
 G_DEFINE_TYPE (GbWorkbench, gb_workbench, GTK_TYPE_APPLICATION_WINDOW)
 
@@ -100,7 +103,7 @@ gb_workbench__context_restore_cb (GObject      *object,
   if ((ide_buffer_manager_get_n_buffers (buffer_manager) == 0) && (self->has_opened == FALSE))
     gb_workbench_add_temporary_buffer (self);
 
-  gtk_widget_grab_focus (GTK_WIDGET (self->editor_workspace));
+  gtk_widget_grab_focus (GTK_WIDGET (self->workspace));
 }
 
 static void
@@ -284,7 +287,7 @@ gb_workbench_grab_focus (GtkWidget *widget)
 
   g_assert (GB_IS_WORKBENCH (self));
 
-  gtk_widget_grab_focus (GTK_WIDGET (self->editor_workspace));
+  gtk_widget_grab_focus (GTK_WIDGET (self->workspace));
 }
 
 static void
@@ -295,7 +298,7 @@ gb_workbench_realize (GtkWidget *widget)
   if (GTK_WIDGET_CLASS (gb_workbench_parent_class)->realize)
     GTK_WIDGET_CLASS (gb_workbench_parent_class)->realize (widget);
 
-  gtk_widget_grab_focus (GTK_WIDGET (self->editor_workspace));
+  gtk_widget_grab_focus (GTK_WIDGET (self->workspace));
 
   ide_context_restore_async (self->context,
                              NULL,
@@ -314,18 +317,13 @@ gb_workbench_constructed (GObject *object)
 
   G_OBJECT_CLASS (gb_workbench_parent_class)->constructed (object);
 
-  gb_workbench_set_active_workspace (self, GB_WORKSPACE (self->editor_workspace));
-
   gb_workbench_actions_init (self);
 
   app = GTK_APPLICATION (g_application_get_default ());
   menu = gtk_application_get_menu_by_id (app, "gear-menu");
   gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (self->gear_menu_button), G_MENU_MODEL (menu));
 
-  if (self->active_workspace)
-    gtk_widget_grab_focus (GTK_WIDGET (self->active_workspace));
-  else
-    gtk_widget_grab_focus (GTK_WIDGET (self->editor_workspace));
+  gtk_widget_grab_focus (GTK_WIDGET (self->workspace));
 
   IDE_EXIT;
 }
@@ -356,7 +354,6 @@ gb_workbench_finalize (GObject *object)
 
   IDE_ENTRY;
 
-  ide_clear_weak_pointer (&self->active_workspace);
   g_clear_object (&self->context);
   g_clear_pointer (&self->current_folder_uri, g_free);
 
@@ -375,10 +372,6 @@ gb_workbench_get_property (GObject    *object,
 
   switch (prop_id)
     {
-    case PROP_ACTIVE_WORKSPACE:
-      g_value_set_object (value, gb_workbench_get_active_workspace (self));
-      break;
-
     case PROP_BUILDING:
       g_value_set_boolean (value, self->building);
       break;
@@ -406,10 +399,6 @@ gb_workbench_set_property (GObject      *object,
 
   switch (prop_id)
     {
-    case PROP_ACTIVE_WORKSPACE:
-      gb_workbench_set_active_workspace (self, g_value_get_object (value));
-      break;
-
     case PROP_CONTEXT:
       gb_workbench_set_context (self, g_value_get_object (value));
       break;
@@ -487,14 +476,16 @@ gb_workbench_class_init (GbWorkbenchClass *klass)
 
   GB_WIDGET_CLASS_TEMPLATE (klass, "gb-workbench.ui");
   GB_WIDGET_CLASS_BIND (klass, GbWorkbench, command_bar);
-  GB_WIDGET_CLASS_BIND (klass, GbWorkbench, editor_workspace);
   GB_WIDGET_CLASS_BIND (klass, GbWorkbench, gear_menu_button);
   GB_WIDGET_CLASS_BIND (klass, GbWorkbench, search_box);
-  GB_WIDGET_CLASS_BIND (klass, GbWorkbench, stack);
+  GB_WIDGET_CLASS_BIND (klass, GbWorkbench, workspace);
 
   g_type_ensure (GB_TYPE_COMMAND_BAR);
-  g_type_ensure (GB_TYPE_EDITOR_WORKSPACE);
+  g_type_ensure (GB_TYPE_PROJECT_TREE);
   g_type_ensure (GB_TYPE_SEARCH_BOX);
+  g_type_ensure (GB_TYPE_VIEW_GRID);
+  g_type_ensure (GB_TYPE_WORKSPACE);
+  g_type_ensure (GB_TYPE_WORKSPACE_PANE);
   g_type_ensure (GEDIT_TYPE_MENU_STACK_SWITCHER);
 }
 
@@ -545,38 +536,6 @@ gb_workbench_get_context (GbWorkbench *self)
   return self->context;
 }
 
-/**
- * gb_workbench_get_active_workspace:
- * @self: A #GbWorkbench.
- *
- * Gets the currently selected workspace.
- *
- * Returns: (transfer none): An #GbWorkspace.
- */
-GbWorkspace *
-gb_workbench_get_active_workspace (GbWorkbench *self)
-{
-  g_return_val_if_fail (GB_IS_WORKBENCH (self), NULL);
-
-  return self->active_workspace;
-}
-
-void
-gb_workbench_set_active_workspace (GbWorkbench *self,
-                                   GbWorkspace *workspace)
-{
-  GActionGroup *group;
-
-  g_return_if_fail (GB_IS_WORKBENCH (self));
-  g_return_if_fail (GB_IS_WORKSPACE (workspace));
-
-  if (ide_set_weak_pointer (&self->active_workspace, workspace))
-    gtk_stack_set_visible_child (self->stack, GTK_WIDGET (workspace));
-
-  group = gtk_widget_get_action_group (GTK_WIDGET (workspace), "workspace");
-  gtk_widget_insert_action_group (GTK_WIDGET (self), "workspace", group);
-}
-
 static gboolean
 supports_content_type (const gchar *filename,
                        const gchar *content_type)
@@ -732,26 +691,6 @@ gb_workbench_open_uri_list (GbWorkbench         *self,
     }
 }
 
-/**
- * gb_workbench_get_workspace_typed:
- * @self: A #GbWorkbench.
- *
- * Gets the workspace matching @workspace_type
- *
- * Returns: (transfer none): A #GbWorkspace.
- */
-gpointer
-gb_workbench_get_workspace_typed (GbWorkbench *self,
-                                  GType        workspace_type)
-{
-  g_return_val_if_fail (GB_IS_WORKBENCH (self), NULL);
-
-  if (workspace_type == GB_TYPE_EDITOR_WORKSPACE)
-    return self->editor_workspace;
-
-  return NULL;
-}
-
 static void
 gb_workbench__builder_build_cb (GObject      *object,
                                 GAsyncResult *result,
@@ -890,5 +829,5 @@ gb_workbench_views_foreach (GbWorkbench *self,
   g_return_if_fail (GB_IS_WORKBENCH (self));
   g_return_if_fail (callback != NULL);
 
-  gb_workspace_views_foreach (GB_WORKSPACE (self->editor_workspace), callback, callback_data);
+  //gb_workspace_views_foreach (GB_WORKSPACE (self->editor_workspace), callback, callback_data);
 }
diff --git a/src/workbench/gb-workbench.h b/src/workbench/gb-workbench.h
index 1675710..8bd1fac 100644
--- a/src/workbench/gb-workbench.h
+++ b/src/workbench/gb-workbench.h
@@ -23,7 +23,6 @@
 #include <ide.h>
 
 #include "gb-command-manager.h"
-#include "gb-workbench-types.h"
 
 G_BEGIN_DECLS
 
@@ -40,9 +39,6 @@ gboolean          gb_workbench_build_finish         (GbWorkbench         *self,
                                                      GAsyncResult        *result,
                                                      GError             **error);
 IdeContext       *gb_workbench_get_context          (GbWorkbench         *self);
-GbWorkspace      *gb_workbench_get_active_workspace (GbWorkbench         *self);
-void              gb_workbench_set_active_workspace (GbWorkbench         *self,
-                                                     GbWorkspace         *workspace);
 void              gb_workbench_add_temporary_buffer (GbWorkbench         *self);
 void              gb_workbench_open                 (GbWorkbench         *self,
                                                      GFile               *file);
@@ -51,8 +47,6 @@ void              gb_workbench_open_with_editor     (GbWorkbench         *self,
 void              gb_workbench_open_uri_list        (GbWorkbench         *self,
                                                      const gchar * const *uri_list);
 GbCommandManager *gb_workbench_get_command_manager  (GbWorkbench         *self);
-gpointer          gb_workbench_get_workspace_typed  (GbWorkbench         *self,
-                                                     GType                workspace_type);
 gboolean          gb_workbench_get_closing          (GbWorkbench         *self);
 void              gb_workbench_views_foreach        (GbWorkbench         *self,
                                                      GtkCallback          callback,
diff --git a/src/workspace/gb-workspace-pane.c b/src/workspace/gb-workspace-pane.c
new file mode 100644
index 0000000..02d86fc
--- /dev/null
+++ b/src/workspace/gb-workspace-pane.c
@@ -0,0 +1,275 @@
+/* gb-workspace-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 "gb-workspace-pane.h"
+
+struct _GbWorkspacePane
+{
+  GtkBin            parent_instance;
+
+  GtkBox           *box;
+  GtkStackSwitcher *stack_switcher;
+  GtkStack         *stack;
+
+  GdkRectangle      handle_pos;
+
+  GtkPositionType   position;
+};
+
+G_DEFINE_TYPE (GbWorkspacePane, gb_workspace_pane, GTK_TYPE_BIN)
+
+enum {
+  PROP_0,
+  PROP_POSITION,
+  LAST_PROP
+};
+
+enum {
+  STYLE_PROP_0,
+  STYLE_PROP_HANDLE_SIZE,
+  LAST_STYLE_PROP
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+static GParamSpec *gStyleParamSpecs [LAST_STYLE_PROP];
+
+static gboolean
+gb_workspace_pane_draw (GtkWidget *widget,
+                        cairo_t   *cr)
+{
+  GbWorkspacePane *self = (GbWorkspacePane *)widget;
+  GtkStyleContext *style_context;
+  gboolean ret;
+
+  g_assert (GB_IS_WORKSPACE_PANE (self));
+  g_assert (cr != NULL);
+
+  ret = GTK_WIDGET_CLASS (gb_workspace_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
+gb_workspace_pane_size_allocate (GtkWidget     *widget,
+                                 GtkAllocation *alloc)
+{
+  GbWorkspacePane *self = (GbWorkspacePane *)widget;
+  GtkWidget *child;
+  GtkAllocation child_alloc;
+  gint handle_size;
+
+  g_assert (GB_IS_WORKSPACE_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
+gb_workspace_pane_finalize (GObject *object)
+{
+  GbWorkspacePane *self = (GbWorkspacePane *)object;
+
+  self->stack = NULL;
+  self->stack_switcher = NULL;
+
+  G_OBJECT_CLASS (gb_workspace_pane_parent_class)->finalize (object);
+}
+
+static void
+gb_workspace_pane_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  GbWorkspacePane *self = GB_WORKSPACE_PANE (object);
+
+  switch (prop_id)
+    {
+    case PROP_POSITION:
+      g_value_set_enum (value, gb_workspace_pane_get_position (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gb_workspace_pane_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  GbWorkspacePane *self = GB_WORKSPACE_PANE (object);
+
+  switch (prop_id)
+    {
+    case PROP_POSITION:
+      gb_workspace_pane_set_position (self, g_value_get_enum (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gb_workspace_pane_class_init (GbWorkspacePaneClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = gb_workspace_pane_finalize;
+  object_class->get_property = gb_workspace_pane_get_property;
+  object_class->set_property = gb_workspace_pane_set_property;
+
+  widget_class->draw = gb_workspace_pane_draw;
+  widget_class->size_allocate = gb_workspace_pane_size_allocate;
+
+  /**
+   * GbWorkspacePane: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.
+   */
+  gParamSpecs [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, gParamSpecs);
+
+  gStyleParamSpecs [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,
+                                           gStyleParamSpecs [STYLE_PROP_HANDLE_SIZE]);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/gb-workspace-pane.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbWorkspacePane, box);
+  gtk_widget_class_bind_template_child_internal (widget_class, GbWorkspacePane, stack);
+  gtk_widget_class_bind_template_child_internal (widget_class, GbWorkspacePane, stack_switcher);
+}
+
+static void
+gb_workspace_pane_init (GbWorkspacePane *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+GtkWidget *
+gb_workspace_pane_new (void)
+{
+  return g_object_new (GB_TYPE_WORKSPACE_PANE, NULL);
+}
+
+GtkPositionType
+gb_workspace_pane_get_position (GbWorkspacePane *self)
+{
+  g_return_val_if_fail (GB_IS_WORKSPACE_PANE (self), GTK_POS_LEFT);
+
+  return self->position;
+}
+
+void
+gb_workspace_pane_set_position (GbWorkspacePane *self,
+                                GtkPositionType  position)
+{
+  g_return_if_fail (GB_IS_WORKSPACE_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), gParamSpecs [PROP_POSITION]);
+    }
+}
diff --git a/src/workspace/gb-workspace-pane.h b/src/workspace/gb-workspace-pane.h
new file mode 100644
index 0000000..66e8db8
--- /dev/null
+++ b/src/workspace/gb-workspace-pane.h
@@ -0,0 +1,43 @@
+/* gb-workspace-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 GB_WORKSPACE_PANE_H
+#define GB_WORKSPACE_PANE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_WORKSPACE_PANE (gb_workspace_pane_get_type())
+
+G_DECLARE_FINAL_TYPE (GbWorkspacePane, gb_workspace_pane, GB, WORKSPACE_PANE, GtkBin)
+
+gboolean         gb_workspace_pane_get_floating            (GbWorkspacePane *self);
+guint            gb_workspace_pane_get_transition_duration (GbWorkspacePane *self);
+GtkWidget       *gb_workspace_pane_new                     (void);
+void             gb_workspace_pane_set_floating            (GbWorkspacePane *self,
+                                                            gboolean         floating);
+void             gb_workspace_pane_set_transition_duration (GbWorkspacePane *self,
+                                                            guint            transition_duration);
+GtkPositionType  gb_workspace_pane_get_position            (GbWorkspacePane *self);
+void             gb_workspace_pane_set_position            (GbWorkspacePane *self,
+                                                            GtkPositionType  position);
+
+G_END_DECLS
+
+#endif /* GB_WORKSPACE_PANE_H */
diff --git a/src/workspace/gb-workspace.c b/src/workspace/gb-workspace.c
new file mode 100644
index 0000000..e4810d9
--- /dev/null
+++ b/src/workspace/gb-workspace.c
@@ -0,0 +1,1137 @@
+/* gb-workspace.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 <ide.h>
+#include <string.h>
+
+#include "gb-workspace.h"
+#include "gb-workspace-pane.h"
+
+#define ANIMATION_MODE     IDE_ANIMATION_EASE_IN_OUT_QUAD
+#define ANIMATION_DURATION 250
+#define HORIZ_GRIP_EXTRA   10
+#define VERT_GRIP_EXTRA    10
+#define MIN_POSITION       100
+
+typedef struct
+{
+  GtkWidget       *widget;
+  GtkAdjustment   *adjustment;
+  IdeAnimation    *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;
+} GbWorkspaceChild;
+
+struct _GbWorkspace
+{
+  GtkOverlay        parent_instance;
+
+  GbWorkspaceChild  children[4];
+
+  GtkGesture       *pan_gesture;
+
+  GbWorkspaceChild *drag_child;
+  gdouble           drag_position;
+};
+
+G_DEFINE_TYPE (GbWorkspace, gb_workspace, GTK_TYPE_OVERLAY)
+
+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 GParamSpec *gParamSpecs [LAST_PROP];
+static GParamSpec *gChildParamSpecs [LAST_CHILD_PROP];
+
+static void
+gb_workspace_move_resize_handle (GbWorkspace     *self,
+                                 GtkPositionType  type)
+{
+  GbWorkspaceChild *child;
+  GtkAllocation alloc;
+
+  g_assert (GB_IS_WORKSPACE (self));
+  g_assert ((type == GTK_POS_LEFT) ||
+            (type == GTK_POS_RIGHT) ||
+            (type == GTK_POS_BOTTOM));
+
+  child = &self->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
+gb_workspace_create_handle_window (GbWorkspace     *self,
+                                   GtkPositionType  type)
+{
+  GbWorkspaceChild *child;
+  GtkAllocation alloc;
+  GdkWindowAttr attributes = { 0 };
+  GdkWindow *parent;
+  GdkDisplay *display;
+
+  g_assert (GB_IS_WORKSPACE (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 = &self->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
+gb_workspace_destroy_handle_window (GbWorkspace     *self,
+                                    GtkPositionType  type)
+{
+  GbWorkspaceChild *child;
+
+  g_assert (GB_IS_WORKSPACE (self));
+
+  child = &self->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
+gb_workspace_relayout (GbWorkspace         *self,
+                       const GtkAllocation *alloc)
+{
+  GbWorkspaceChild *left;
+  GbWorkspaceChild *right;
+  GbWorkspaceChild *content;
+  GbWorkspaceChild *bottom;
+
+  g_assert (GB_IS_WORKSPACE (self));
+  g_assert (alloc != NULL);
+
+  left = &self->children [GTK_POS_LEFT];
+  right = &self->children [GTK_POS_RIGHT];
+  content = &self->children [GTK_POS_TOP];
+  bottom = &self->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);
+
+  gb_workspace_move_resize_handle (self, GTK_POS_LEFT);
+  gb_workspace_move_resize_handle (self, GTK_POS_RIGHT);
+  gb_workspace_move_resize_handle (self, GTK_POS_BOTTOM);
+}
+
+static void
+gb_workspace_size_allocate (GtkWidget     *widget,
+                            GtkAllocation *alloc)
+{
+  GbWorkspace *self = (GbWorkspace *)widget;
+  int i;
+
+  g_assert (GB_IS_WORKSPACE (self));
+  g_assert (alloc != NULL);
+
+  gb_workspace_relayout (self, alloc);
+
+  GTK_WIDGET_CLASS (gb_workspace_parent_class)->size_allocate (widget, alloc);
+
+  for (i = 0; i < G_N_ELEMENTS (self->children); i++)
+    {
+      GbWorkspaceChild *child = &self->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 GbWorkspaceChild *
+gb_workspace_child_find (GbWorkspace *self,
+                         GtkWidget   *child)
+{
+  int i;
+
+  g_assert (GB_IS_WORKSPACE (self));
+  g_assert (GTK_IS_WIDGET (child));
+
+  for (i = 0; i < G_N_ELEMENTS (self->children); i++)
+    {
+      GbWorkspaceChild *item = &self->children [i];
+
+      if (item->widget == child)
+        return item;
+    }
+
+  g_warning ("Child of type %s was not found in this GbWorkspace.",
+             g_type_name (G_OBJECT_TYPE (child)));
+
+  return NULL;
+}
+
+static void
+gb_workspace_animation_cb (gpointer data)
+{
+  g_autoptr(GtkWidget) child = data;
+  GtkWidget *parent;
+  GbWorkspace *self;
+  GbWorkspaceChild *item;
+
+  g_assert (GTK_IS_WIDGET (child));
+
+  parent = gtk_widget_get_parent (child);
+  if (!GB_IS_WORKSPACE (parent))
+    return;
+
+  self = GB_WORKSPACE (parent);
+
+  item = gb_workspace_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
+gb_workspace_get_child_position (GtkOverlay    *overlay,
+                                 GtkWidget     *child,
+                                 GtkAllocation *alloc)
+{
+  GbWorkspace *self = (GbWorkspace *)overlay;
+  GbWorkspaceChild *item;
+
+  g_assert (GB_IS_WORKSPACE (self));
+  g_assert (GTK_IS_WIDGET (child));
+  g_assert (alloc != NULL);
+
+  if (!(item = gb_workspace_child_find (self, child)))
+    return FALSE;
+
+  *alloc = item->alloc;
+
+  return TRUE;
+}
+
+static guint
+gb_workspace_child_get_position (GbWorkspace *self,
+                                 GtkWidget   *child)
+{
+  GbWorkspaceChild *item;
+
+  g_assert (GB_IS_WORKSPACE (self));
+  g_assert (GTK_IS_WIDGET (child));
+
+  if (!(item = gb_workspace_child_find (self, child)))
+    return FALSE;
+
+  return item->position;
+}
+
+static void
+gb_workspace_child_set_position (GbWorkspace *self,
+                                 GtkWidget   *child,
+                                 guint        position)
+{
+  GbWorkspaceChild *item;
+
+  g_assert (GB_IS_WORKSPACE (self));
+  g_assert (GTK_IS_WIDGET (child));
+
+  if (!(item = gb_workspace_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
+gb_workspace_child_get_reveal (GbWorkspace *self,
+                               GtkWidget   *child)
+{
+  GbWorkspaceChild *item;
+
+  g_assert (GB_IS_WORKSPACE (self));
+  g_assert (GTK_IS_WIDGET (child));
+
+  if (!(item = gb_workspace_child_find (self, child)))
+    return FALSE;
+
+  return item->reveal;
+}
+
+static void
+gb_workspace_child_set_reveal (GbWorkspace *self,
+                               GtkWidget   *child,
+                               gboolean     reveal)
+{
+  GbWorkspaceChild *item;
+  GdkFrameClock *frame_clock;
+
+  g_assert (GB_IS_WORKSPACE (self));
+  g_assert (GTK_IS_WIDGET (child));
+
+  reveal = !!reveal;
+
+  if (!(item = gb_workspace_child_find (self, child)) || (item->reveal == reveal))
+    return;
+
+  if (item->animation != NULL)
+    {
+      ide_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);
+
+  item->animation = ide_object_animate_full (item->adjustment,
+                                             ANIMATION_MODE,
+                                             ANIMATION_DURATION,
+                                             frame_clock,
+                                             gb_workspace_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);
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+gb_workspace_get_child_property (GtkContainer *container,
+                                 GtkWidget    *child,
+                                 guint         prop_id,
+                                 GValue       *value,
+                                 GParamSpec   *pspec)
+{
+  GbWorkspace *self = (GbWorkspace *)container;
+
+  switch (prop_id)
+    {
+    case CHILD_PROP_REVEAL:
+      g_value_set_boolean (value, gb_workspace_child_get_reveal (self, child));
+      break;
+
+    case CHILD_PROP_POSITION:
+      g_value_set_uint (value, gb_workspace_child_get_position (self, child));
+      break;
+
+    default:
+      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
+    }
+}
+
+static void
+gb_workspace_set_child_property (GtkContainer *container,
+                                 GtkWidget    *child,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  GbWorkspace *self = (GbWorkspace *)container;
+
+  switch (prop_id)
+    {
+    case CHILD_PROP_REVEAL:
+      gb_workspace_child_set_reveal (self, child, g_value_get_boolean (value));
+      break;
+
+    case CHILD_PROP_POSITION:
+      gb_workspace_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
+gb_workspace_get_preferred_width (GtkWidget *widget,
+                                  gint      *min_width,
+                                  gint      *nat_width)
+{
+  GbWorkspace *self = (GbWorkspace *)widget;
+  int i;
+
+  g_assert (GB_IS_WORKSPACE (self));
+
+  for (i = 0; i < G_N_ELEMENTS (self->children); i++)
+    {
+      GbWorkspaceChild *child = &self->children [i];
+
+      if (gtk_widget_get_visible (child->widget))
+        gtk_widget_get_preferred_width (child->widget, &child->min_width, &child->nat_width);
+    }
+
+  *min_width = self->children [GTK_POS_LEFT].min_width
+             + self->children [GTK_POS_RIGHT].min_width
+             + MAX (self->children [GTK_POS_TOP].min_width,
+                    self->children [GTK_POS_BOTTOM].min_width);
+  *nat_width = self->children [GTK_POS_LEFT].nat_width
+             + self->children [GTK_POS_RIGHT].nat_width
+             + MAX (self->children [GTK_POS_TOP].nat_width,
+                    self->children [GTK_POS_BOTTOM].nat_width);
+}
+
+static void
+gb_workspace_get_preferred_height (GtkWidget *widget,
+                                   gint      *min_height,
+                                   gint      *nat_height)
+{
+  GbWorkspace *self = (GbWorkspace *)widget;
+  int i;
+
+  g_assert (GB_IS_WORKSPACE (self));
+
+  for (i = 0; i < G_N_ELEMENTS (self->children); i++)
+    {
+      GbWorkspaceChild *child = &self->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 (self->children [GTK_POS_LEFT].min_height,
+                          self->children [GTK_POS_RIGHT].min_height),
+                     (self->children [GTK_POS_BOTTOM].position +
+                      self->children [GTK_POS_TOP].min_height));
+
+  *nat_height = MAX (MAX (self->children [GTK_POS_LEFT].nat_height,
+                          self->children [GTK_POS_RIGHT].nat_height),
+                     (self->children [GTK_POS_BOTTOM].position +
+                      self->children [GTK_POS_TOP].nat_height));
+}
+
+static GtkSizeRequestMode
+gb_workspace_get_request_mode (GtkWidget *widget)
+{
+  return GTK_SIZE_REQUEST_CONSTANT_SIZE;
+}
+
+static GtkAdjustment *
+gb_workspace_create_adjustment (GbWorkspace *self)
+{
+  GtkAdjustment *adj;
+
+  g_assert (GB_IS_WORKSPACE (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
+gb_workspace_drag_begin_cb (GbWorkspace   *self,
+                            gdouble        x,
+                            gdouble        y,
+                            GtkGesturePan *pan)
+{
+  GbWorkspaceChild *left;
+  GbWorkspaceChild *right;
+  GbWorkspaceChild *bottom;
+  GdkEventSequence *sequence;
+  const GdkEvent *event;
+
+  g_assert (GB_IS_WORKSPACE (self));
+  g_assert (GTK_IS_GESTURE_PAN (pan));
+
+  left = &self->children [GTK_POS_LEFT];
+  right = &self->children [GTK_POS_RIGHT];
+  bottom = &self->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);
+      self->drag_child = left;
+    }
+  else if (event->any.window == right->handle)
+    {
+      gtk_gesture_pan_set_orientation (pan, GTK_ORIENTATION_HORIZONTAL);
+      self->drag_child = right;
+    }
+  else if (event->any.window == bottom->handle)
+    {
+      gtk_gesture_pan_set_orientation (pan, GTK_ORIENTATION_VERTICAL);
+      self->drag_child = bottom;
+    }
+  else
+    {
+      gtk_gesture_set_state (GTK_GESTURE (pan), GTK_EVENT_SEQUENCE_DENIED);
+      self->drag_child = NULL;
+      return;
+    }
+
+  self->drag_position = MAX (self->drag_child->position, MIN_POSITION);
+  gtk_gesture_set_state (GTK_GESTURE (pan), GTK_EVENT_SEQUENCE_CLAIMED);
+  gtk_container_child_notify (GTK_CONTAINER (self), self->drag_child->widget, "position");
+}
+
+static void
+gb_workspace_drag_end_cb (GbWorkspace   *self,
+                          gdouble        x,
+                          gdouble        y,
+                          GtkGesturePan *pan)
+{
+  GdkEventSequence *sequence;
+  GtkEventSequenceState state;
+
+  g_assert (GB_IS_WORKSPACE (self));
+  g_assert (GTK_IS_GESTURE_PAN (pan));
+
+  if (self->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)
+    {
+      self->drag_child = NULL;
+      return;
+    }
+
+  if (self->drag_child->position < MIN_POSITION)
+    {
+      gtk_container_child_set (GTK_CONTAINER (self), self->drag_child->widget,
+                               "reveal", FALSE,
+                               NULL);
+      self->drag_child->restore_position = self->drag_position;
+    }
+
+  gtk_container_child_notify (GTK_CONTAINER (self), self->drag_child->widget, "position");
+
+  self->drag_child = NULL;
+  self->drag_position = 0;
+}
+
+static void
+gb_workspace_pan_cb (GbWorkspace     *self,
+                     GtkPanDirection  direction,
+                     gdouble          offset,
+                     GtkGesturePan   *pan)
+{
+  GtkAllocation alloc;
+  gint target_position = 0;
+  gint center_min_width;
+  gint left_max;
+  gint right_max;
+  gint bottom_max;
+
+  g_assert (GB_IS_WORKSPACE (self));
+  g_assert (GTK_IS_GESTURE_PAN (pan));
+  g_assert (self->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 (self->drag_child->type == GTK_POS_LEFT)
+        target_position = self->drag_position - offset;
+      else if (self->drag_child->type == GTK_POS_RIGHT)
+        target_position = self->drag_position + offset;
+      break;
+
+    case GTK_PAN_DIRECTION_RIGHT:
+      if (self->drag_child->type == GTK_POS_LEFT)
+        target_position = self->drag_position + offset;
+      else if (self->drag_child->type == GTK_POS_RIGHT)
+        target_position = self->drag_position - offset;
+      break;
+
+    case GTK_PAN_DIRECTION_UP:
+      if (self->drag_child->type == GTK_POS_BOTTOM)
+        target_position = self->drag_position + offset;
+      break;
+
+    case GTK_PAN_DIRECTION_DOWN:
+      if (self->drag_child->type == GTK_POS_BOTTOM)
+        target_position = self->drag_position - offset;
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+
+  center_min_width = MAX (self->children [GTK_POS_BOTTOM].min_width,
+                          self->children [GTK_POS_TOP].min_width);
+  left_max = alloc.width - self->children [GTK_POS_RIGHT].alloc.width - center_min_width;
+  right_max = alloc.width - self->children [GTK_POS_LEFT].position - center_min_width;
+  bottom_max = alloc.height - self->children [GTK_POS_TOP].min_height;
+
+  switch (self->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 ();
+    }
+
+  self->drag_child->position = MAX (0, target_position);
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static GtkGesture *
+gb_workspace_create_pan_gesture (GbWorkspace    *self,
+                                 GtkOrientation  orientation)
+{
+  GtkGesture *gesture;
+
+  g_assert (GB_IS_WORKSPACE (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 (gb_workspace_drag_begin_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (gesture,
+                           "drag-end",
+                           G_CALLBACK (gb_workspace_drag_end_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (gesture,
+                           "pan",
+                           G_CALLBACK (gb_workspace_pan_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  return gesture;
+}
+
+static void
+gb_workspace_realize (GtkWidget *widget)
+{
+  GbWorkspace *self = (GbWorkspace *)widget;
+
+  g_assert (GB_IS_WORKSPACE (self));
+
+  GTK_WIDGET_CLASS (gb_workspace_parent_class)->realize (widget);
+
+  gb_workspace_create_handle_window (self, GTK_POS_LEFT);
+  gb_workspace_create_handle_window (self, GTK_POS_RIGHT);
+  gb_workspace_create_handle_window (self, GTK_POS_BOTTOM);
+}
+
+static void
+gb_workspace_unrealize (GtkWidget *widget)
+{
+  GbWorkspace *self = (GbWorkspace *)widget;
+
+  g_assert (GB_IS_WORKSPACE (self));
+
+  gb_workspace_destroy_handle_window (self, GTK_POS_LEFT);
+  gb_workspace_destroy_handle_window (self, GTK_POS_RIGHT);
+  gb_workspace_destroy_handle_window (self, GTK_POS_BOTTOM);
+
+  GTK_WIDGET_CLASS (gb_workspace_parent_class)->unrealize (widget);
+}
+
+static void
+gb_workspace_map (GtkWidget *widget)
+{
+  GbWorkspace *self = (GbWorkspace *)widget;
+  int i;
+
+  g_assert (GB_IS_WORKSPACE (self));
+
+  GTK_WIDGET_CLASS (gb_workspace_parent_class)->map (widget);
+
+  for (i = 0; i < G_N_ELEMENTS (self->children); i++)
+    {
+      GbWorkspaceChild *child = &self->children [i];
+
+      if (child->handle != NULL)
+        gdk_window_show (child->handle);
+    }
+}
+
+static void
+gb_workspace_unmap (GtkWidget *widget)
+{
+  GbWorkspace *self = (GbWorkspace *)widget;
+  int i;
+
+  g_assert (GB_IS_WORKSPACE (self));
+
+  for (i = 0; i < G_N_ELEMENTS (self->children); i++)
+    {
+      GbWorkspaceChild *child = &self->children [i];
+
+      if (child->handle != NULL)
+        gdk_window_hide (child->handle);
+    }
+
+  GTK_WIDGET_CLASS (gb_workspace_parent_class)->unmap (widget);
+}
+
+static void
+gb_workspace_finalize (GObject *object)
+{
+  GbWorkspace *self = (GbWorkspace *)object;
+  gsize i;
+
+  for (i = 0; i < G_N_ELEMENTS (self->children); i++)
+    {
+      GbWorkspaceChild *child = &self->children [i];
+
+      ide_clear_weak_pointer (&child->animation);
+      g_clear_object (&child->adjustment);
+    }
+
+  g_clear_object (&self->pan_gesture);
+
+  G_OBJECT_CLASS (gb_workspace_parent_class)->finalize (object);
+}
+
+static void
+gb_workspace_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  GbWorkspace *self = GB_WORKSPACE (object);
+
+  switch (prop_id)
+    {
+    case PROP_LEFT_PANE:
+      g_value_set_object (value, gb_workspace_get_left_pane (self));
+      break;
+
+    case PROP_RIGHT_PANE:
+      g_value_set_object (value, gb_workspace_get_right_pane (self));
+      break;
+
+    case PROP_BOTTOM_PANE:
+      g_value_set_object (value, gb_workspace_get_bottom_pane (self));
+      break;
+
+    case PROP_CONTENT_PANE:
+      g_value_set_object (value, gb_workspace_get_content_pane (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gb_workspace_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
+gb_workspace_class_init (GbWorkspaceClass *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 = gb_workspace_finalize;
+  object_class->get_property = gb_workspace_get_property;
+  object_class->set_property = gb_workspace_set_property;
+
+  widget_class->get_preferred_height = gb_workspace_get_preferred_height;
+  widget_class->get_preferred_width = gb_workspace_get_preferred_width;
+  widget_class->get_request_mode = gb_workspace_get_request_mode;
+  widget_class->map = gb_workspace_map;
+  widget_class->unmap = gb_workspace_unmap;
+  widget_class->realize = gb_workspace_realize;
+  widget_class->unrealize = gb_workspace_unrealize;
+  widget_class->size_allocate = gb_workspace_size_allocate;
+
+  container_class->get_child_property = gb_workspace_get_child_property;
+  container_class->set_child_property = gb_workspace_set_child_property;
+
+  overlay_class->get_child_position = gb_workspace_get_child_position;
+
+  gParamSpecs [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));
+
+  gParamSpecs [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));
+
+  gParamSpecs [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));
+
+  gParamSpecs [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, gParamSpecs);
+
+  gChildParamSpecs [CHILD_PROP_POSITION] =
+    g_param_spec_uint ("position",
+                       _("Position"),
+                       _("The position of the pane relative to it's edge."),
+                       0, G_MAXUINT,
+                       0,
+                       (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  gtk_container_class_install_child_property (container_class, CHILD_PROP_POSITION,
+                                              gChildParamSpecs [CHILD_PROP_POSITION]);
+
+  gChildParamSpecs [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,
+                                              gChildParamSpecs [CHILD_PROP_REVEAL]);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/gb-workspace.ui");
+
+  gtk_widget_class_bind_template_child_full (widget_class, "bottom_pane", TRUE,
+                                             G_STRUCT_OFFSET (GbWorkspace, children[GTK_POS_BOTTOM].widget));
+  gtk_widget_class_bind_template_child_full (widget_class, "content_pane", TRUE,
+                                             G_STRUCT_OFFSET (GbWorkspace, children[GTK_POS_TOP].widget));
+  gtk_widget_class_bind_template_child_full (widget_class, "left_pane", TRUE,
+                                             G_STRUCT_OFFSET (GbWorkspace, children[GTK_POS_LEFT].widget));
+  gtk_widget_class_bind_template_child_full (widget_class, "right_pane", TRUE,
+                                             G_STRUCT_OFFSET (GbWorkspace, children[GTK_POS_RIGHT].widget));
+}
+
+static void
+gb_workspace_init (GbWorkspace *self)
+{
+  self->children [GTK_POS_LEFT].type = GTK_POS_LEFT;
+  self->children [GTK_POS_LEFT].reveal = TRUE;
+  self->children [GTK_POS_LEFT].position = 250;
+  self->children [GTK_POS_LEFT].adjustment = gb_workspace_create_adjustment (self);
+  self->children [GTK_POS_LEFT].cursor_type = GDK_SB_H_DOUBLE_ARROW;
+
+  self->children [GTK_POS_RIGHT].type = GTK_POS_RIGHT;
+  self->children [GTK_POS_RIGHT].reveal = TRUE;
+  self->children [GTK_POS_RIGHT].position = 250;
+  self->children [GTK_POS_RIGHT].adjustment = gb_workspace_create_adjustment (self);
+  self->children [GTK_POS_RIGHT].cursor_type = GDK_SB_H_DOUBLE_ARROW;
+
+  self->children [GTK_POS_BOTTOM].type = GTK_POS_BOTTOM;
+  self->children [GTK_POS_BOTTOM].reveal = TRUE;
+  self->children [GTK_POS_BOTTOM].position = 150;
+  self->children [GTK_POS_BOTTOM].adjustment = gb_workspace_create_adjustment (self);
+  self->children [GTK_POS_BOTTOM].cursor_type = GDK_SB_V_DOUBLE_ARROW;
+
+  self->children [GTK_POS_TOP].type = GTK_POS_TOP;
+  self->children [GTK_POS_TOP].reveal = TRUE;
+  self->children [GTK_POS_TOP].adjustment = gb_workspace_create_adjustment (self);
+
+  self->pan_gesture = gb_workspace_create_pan_gesture (self, GTK_ORIENTATION_HORIZONTAL);
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+GtkWidget *
+gb_workspace_new (void)
+{
+  return g_object_new (GB_TYPE_WORKSPACE, NULL);
+}
+
+GtkWidget *
+gb_workspace_get_left_pane (GbWorkspace *self)
+{
+  g_return_val_if_fail (GB_IS_WORKSPACE (self), NULL);
+
+  return self->children [GTK_POS_LEFT].widget;
+}
+
+GtkWidget *
+gb_workspace_get_right_pane (GbWorkspace *self)
+{
+  g_return_val_if_fail (GB_IS_WORKSPACE (self), NULL);
+
+  return self->children [GTK_POS_RIGHT].widget;
+}
+
+GtkWidget *
+gb_workspace_get_bottom_pane (GbWorkspace *self)
+{
+  g_return_val_if_fail (GB_IS_WORKSPACE (self), NULL);
+
+  return self->children [GTK_POS_BOTTOM].widget;
+}
+
+GtkWidget *
+gb_workspace_get_content_pane (GbWorkspace *self)
+{
+  g_return_val_if_fail (GB_IS_WORKSPACE (self), NULL);
+
+  return self->children [GTK_POS_TOP].widget;
+}
diff --git a/src/workspace/gb-workspace.h b/src/workspace/gb-workspace.h
new file mode 100644
index 0000000..fc7ad1a
--- /dev/null
+++ b/src/workspace/gb-workspace.h
@@ -0,0 +1,40 @@
+/* gb-workspace.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 GB_WORKSPACE_H
+#define GB_WORKSPACE_H
+
+#include <gtk/gtk.h>
+
+#include "gb-workspace-pane.h"
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_WORKSPACE (gb_workspace_get_type())
+
+G_DECLARE_FINAL_TYPE (GbWorkspace, gb_workspace, GB, WORKSPACE, GtkOverlay)
+
+GtkWidget *gb_workspace_new              (void);
+GtkWidget *gb_workspace_get_left_pane    (GbWorkspace *self);
+GtkWidget *gb_workspace_get_right_pane   (GbWorkspace *self);
+GtkWidget *gb_workspace_get_bottom_pane  (GbWorkspace *self);
+GtkWidget *gb_workspace_get_content_pane (GbWorkspace *self);
+
+G_END_DECLS
+
+#endif /* GB_WORKSPACE_H */


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