[gnome-builder] workbench: Introduce focus mode



commit e36c0fa4130618341c634bf8461ad0b52c9ff826
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Fri Jun 30 00:27:26 2017 -0300

    workbench: Introduce focus mode
    
    When using a long-running application like GNOME Builder,
    it is common that the user needs to put all efforts and
    attention into it.
    
    For these cases, having even less window chrome is desired,
    and this can be achieved by making the window fullscreen
    and automatically showing or hiding the header bar based
    on the mouse position. This is the behavior of various
    GNOME apps, like Gedit and Totem.
    
    This commit, then, introduces this focus mode where the
    window turns fullscreen and the header bar, autohidden.
    This mode can be toggled by pressing F11, or by typing
    'toggle-focus-mode' at the command bar.

 data/keybindings/shared.css                |    1 +
 libide/keybindings/ide-shortcuts-window.ui |    7 ++
 libide/workbench/ide-workbench-actions.c   |   15 +++
 libide/workbench/ide-workbench-private.h   |    4 +
 libide/workbench/ide-workbench.c           |  133 ++++++++++++++++++++++++++++
 libide/workbench/ide-workbench.h           |    3 +
 libide/workbench/ide-workbench.ui          |   75 ++++++++++------
 7 files changed, 210 insertions(+), 28 deletions(-)
---
diff --git a/data/keybindings/shared.css b/data/keybindings/shared.css
index f54bd89..260d3ca 100644
--- a/data/keybindings/shared.css
+++ b/data/keybindings/shared.css
@@ -19,6 +19,7 @@
 @binding-set builder-workbench-bindings
 {
   bind "<ctrl>comma" { "set-perspective" ("preferences") };
+  bind "F11" { "action" ("win", "toggle-focus-mode", "") };
 }
 
 entry.gb-command-bar-entry {
diff --git a/libide/keybindings/ide-shortcuts-window.ui b/libide/keybindings/ide-shortcuts-window.ui
index 8196f9e..e20fde8 100644
--- a/libide/keybindings/ide-shortcuts-window.ui
+++ b/libide/keybindings/ide-shortcuts-window.ui
@@ -54,6 +54,13 @@
                 <property name="accelerator">&lt;ctrl&gt;&lt;shift&gt;question</property>
               </object>
             </child>
+            <child>
+              <object class="GtkShortcutsShortcut">
+                <property name="visible">true</property>
+                <property name="title" translatable="yes" context="shortcut window">Toggle Focus 
Mode</property>
+                <property name="accelerator">F11</property>
+              </object>
+            </child>
           </object>
         </child>
         <child>
diff --git a/libide/workbench/ide-workbench-actions.c b/libide/workbench/ide-workbench-actions.c
index b10e3cb..02e1f2b 100644
--- a/libide/workbench/ide-workbench-actions.c
+++ b/libide/workbench/ide-workbench-actions.c
@@ -196,6 +196,9 @@ ide_workbench_actions_global_search (GSimpleAction *action,
   g_assert (IDE_IS_WORKBENCH (self));
 
   ide_workbench_header_bar_focus_search (self->header_bar);
+
+  if (self->focus_mode)
+    gtk_revealer_set_reveal_child (self->header_revealer, TRUE);
 }
 
 static void
@@ -224,6 +227,17 @@ ide_workbench_actions_counters (GSimpleAction *action,
   dzl_counter_arena_unref (arena);
 }
 
+static void
+ide_workbench_action_toggle_focus_mode (GSimpleAction *action,
+                                        GVariant      *state,
+                                        gpointer       user_data)
+{
+  IdeWorkbench *self = user_data;
+
+  ide_workbench_set_focus_mode (self, g_variant_get_boolean (state));
+  g_simple_action_set_state (action, state);
+}
+
 void
 ide_workbench_actions_init (IdeWorkbench *self)
 {
@@ -235,6 +249,7 @@ ide_workbench_actions_init (IdeWorkbench *self)
     { "save-all", ide_workbench_actions_save_all },
     { "save-all-quit", ide_workbench_actions_save_all_quit },
     { "counters", ide_workbench_actions_counters },
+    { "toggle-focus-mode", NULL, NULL, "false", ide_workbench_action_toggle_focus_mode },
   };
 
   g_action_map_add_action_entries (G_ACTION_MAP (self), actions, G_N_ELEMENTS (actions), self);
diff --git a/libide/workbench/ide-workbench-private.h b/libide/workbench/ide-workbench-private.h
index f761168..36d38ee 100644
--- a/libide/workbench/ide-workbench-private.h
+++ b/libide/workbench/ide-workbench-private.h
@@ -33,6 +33,7 @@ struct _IdeWorkbench
   GtkApplicationWindow       parent;
 
   guint                      unloading : 1;
+  guint                      focus_mode : 1;
   guint                      disable_greeter : 1;
   guint                      early_perspectives_removed : 1;
   guint                      did_initial_editor_transition : 1;
@@ -46,12 +47,15 @@ struct _IdeWorkbench
    */
   GListStore                *perspectives;
 
+  GtkContainer              *header_container;
+  GtkRevealer               *header_revealer;
   GtkStack                  *header_stack;
   IdeWorkbenchHeaderBar     *header_bar;
   IdePerspectiveMenuButton  *perspective_menu_button;
   GtkStack                  *perspectives_stack;
   GtkSizeGroup              *header_size_group;
   GtkBox                    *message_box;
+  GtkEventBox               *fullscreen_eventbox;
 
   GObject                   *selection_owner;
 };
diff --git a/libide/workbench/ide-workbench.c b/libide/workbench/ide-workbench.c
index 8d4063a..60ce1f1 100644
--- a/libide/workbench/ide-workbench.c
+++ b/libide/workbench/ide-workbench.c
@@ -40,6 +40,7 @@
 #include "workbench/ide-workbench.h"
 
 #define STABLIZE_DELAY_MSEC 50
+#define SHOW_HEADER_OFFSET  5
 
 G_DEFINE_TYPE (IdeWorkbench, ide_workbench, GTK_TYPE_APPLICATION_WINDOW)
 
@@ -47,6 +48,7 @@ enum {
   PROP_0,
   PROP_CONTEXT,
   PROP_DISABLE_GREETER,
+  PROP_FOCUS_MODE,
   PROP_VISIBLE_PERSPECTIVE,
   PROP_VISIBLE_PERSPECTIVE_NAME,
   LAST_PROP
@@ -63,6 +65,61 @@ static GParamSpec *properties [LAST_PROP];
 static guint signals [LAST_SIGNAL];
 
 static void
+ide_workbench_event_box_notify (GtkWidget    *revealer,
+                                GParamSpec   *pspec,
+                                IdeWorkbench *self)
+{
+  gboolean visible;
+
+  /* Hack to show and hide the revealer when its child is not visible */
+  visible = gtk_revealer_get_reveal_child (GTK_REVEALER (revealer)) ||
+            gtk_revealer_get_child_revealed (GTK_REVEALER (revealer));
+
+  gtk_widget_set_visible (revealer, visible);
+}
+
+static gboolean
+ide_workbench_event_box_motion_notify_event (GtkEventBox    *event_box,
+                                             GdkEventMotion *event,
+                                             IdeWorkbench   *self)
+{
+  GtkWidget *focus_widget;
+  GdkWindow *window;
+  gdouble x, y;
+
+  x = event->x;
+  y = event->y;
+
+  /* Find the (x, y) values relative to the workbench */
+  window = event->window;
+  while (window != NULL && window != gtk_widget_get_window (GTK_WIDGET (event_box)))
+    {
+      gdk_window_coords_to_parent (window, x, y, &x, &y);
+      window = gdk_window_get_parent (window);
+    }
+
+  if (window == NULL)
+    return GDK_EVENT_PROPAGATE;
+
+  /* If any widget in the header is focused, don't hide it */
+  focus_widget = gtk_window_get_focus (GTK_WINDOW (self));
+
+  if (focus_widget &&
+      gtk_widget_get_ancestor (focus_widget, IDE_TYPE_WORKBENCH_HEADER_BAR) != NULL)
+    {
+      return GDK_EVENT_PROPAGATE;
+    }
+
+  /* Show the header with a small offset below */
+  if (y < SHOW_HEADER_OFFSET)
+    gtk_widget_show (GTK_WIDGET (self->header_revealer));
+
+  gtk_revealer_set_reveal_child (self->header_revealer, y < SHOW_HEADER_OFFSET);
+
+  return GDK_EVENT_PROPAGATE;
+}
+
+static void
 ide_workbench_notify_visible_child (IdeWorkbench *self,
                                     GParamSpec   *pspec,
                                     GtkStack     *stack)
@@ -261,6 +318,10 @@ ide_workbench_get_property (GObject    *object,
       g_value_set_boolean (value, self->disable_greeter);
       break;
 
+    case PROP_FOCUS_MODE:
+      g_value_set_boolean (value, self->focus_mode);
+      break;
+
     case PROP_VISIBLE_PERSPECTIVE:
       g_value_set_object (value, ide_workbench_get_visible_perspective (self));
       break;
@@ -288,6 +349,10 @@ ide_workbench_set_property (GObject      *object,
       self->disable_greeter = g_value_get_boolean (value);
       break;
 
+    case PROP_FOCUS_MODE:
+      ide_workbench_set_focus_mode (self, g_value_get_boolean (value));
+      break;
+
     case PROP_VISIBLE_PERSPECTIVE:
       ide_workbench_set_visible_perspective (self, g_value_get_object (value));
       break;
@@ -368,6 +433,19 @@ ide_workbench_class_init (IdeWorkbenchClass *klass)
                           (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
 
   /**
+   * IdeWorkbench:focus-mode:
+   *
+   * Whether this workbench is in focus mode. Focus mode turns the workbench
+   * into a fullscreen window with a floating headerbar.
+   */
+  properties [PROP_FOCUS_MODE] =
+    g_param_spec_boolean ("focus-mode",
+                          "Focus mode",
+                          "If the workbench is in focus mode",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  /**
    * IdeWorkbench:visible-perspective-name:
    *
    * This property is just like #IdeWorkbench:visible-perspective except that
@@ -420,12 +498,17 @@ ide_workbench_class_init (IdeWorkbenchClass *klass)
                   1, IDE_TYPE_CONTEXT);
 
   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/ide-workbench.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeWorkbench, fullscreen_eventbox);
   gtk_widget_class_bind_template_child (widget_class, IdeWorkbench, header_bar);
+  gtk_widget_class_bind_template_child (widget_class, IdeWorkbench, header_container);
+  gtk_widget_class_bind_template_child (widget_class, IdeWorkbench, header_revealer);
   gtk_widget_class_bind_template_child (widget_class, IdeWorkbench, header_size_group);
   gtk_widget_class_bind_template_child (widget_class, IdeWorkbench, header_stack);
   gtk_widget_class_bind_template_child (widget_class, IdeWorkbench, message_box);
   gtk_widget_class_bind_template_child (widget_class, IdeWorkbench, perspective_menu_button);
   gtk_widget_class_bind_template_child (widget_class, IdeWorkbench, perspectives_stack);
+
+  gtk_widget_class_bind_template_callback (widget_class, ide_workbench_event_box_notify);
 }
 
 static void
@@ -1083,3 +1166,53 @@ ide_workbench_pop_message (IdeWorkbench *self,
 
   return FALSE;
 }
+
+gboolean
+ide_workbench_get_focus_mode (IdeWorkbench *self)
+{
+  g_return_val_if_fail (IDE_IS_WORKBENCH (self), FALSE);
+
+  return self->focus_mode;
+}
+
+void
+ide_workbench_set_focus_mode (IdeWorkbench *self,
+                              gboolean      focus_mode)
+{
+  focus_mode = !!focus_mode;
+
+  g_return_if_fail (IDE_IS_WORKBENCH (self));
+
+  if (focus_mode == self->focus_mode)
+    return;
+
+  self->focus_mode = focus_mode;
+
+  g_object_ref (self->header_stack);
+
+  if (focus_mode)
+    {
+      gtk_container_remove (self->header_container, GTK_WIDGET (self->header_stack));
+      gtk_container_add (GTK_CONTAINER (self->header_revealer), GTK_WIDGET (self->header_stack));
+      gtk_window_fullscreen (GTK_WINDOW (self));
+
+      g_signal_connect (self->fullscreen_eventbox,
+                        "motion-notify-event",
+                        G_CALLBACK (ide_workbench_event_box_motion_notify_event),
+                        self);
+    }
+  else
+    {
+      g_signal_handlers_disconnect_by_func (self->fullscreen_eventbox,
+                                            ide_workbench_event_box_motion_notify_event,
+                                            self);
+      gtk_container_remove (GTK_CONTAINER (self->header_revealer), GTK_WIDGET (self->header_stack));
+      gtk_container_add (self->header_container, GTK_WIDGET (self->header_stack));
+      gtk_revealer_set_reveal_child (self->header_revealer, FALSE);
+      gtk_window_unfullscreen (GTK_WINDOW (self));
+    }
+
+  g_object_unref (self->header_stack);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FOCUS_MODE]);
+}
diff --git a/libide/workbench/ide-workbench.h b/libide/workbench/ide-workbench.h
index dbf5515..5643147 100644
--- a/libide/workbench/ide-workbench.h
+++ b/libide/workbench/ide-workbench.h
@@ -103,6 +103,9 @@ void                   ide_workbench_push_message                 (IdeWorkbench
                                                                    IdeWorkbenchMessage    *message);
 gboolean               ide_workbench_pop_message                  (IdeWorkbench           *self,
                                                                    const gchar            *message_id);
+gboolean               ide_workbench_get_focus_mode               (IdeWorkbench           *self);
+void                   ide_workbench_set_focus_mode               (IdeWorkbench           *self,
+                                                                   gboolean                focus_mode);
 
 G_END_DECLS
 
diff --git a/libide/workbench/ide-workbench.ui b/libide/workbench/ide-workbench.ui
index 4fcbd49..df19635 100644
--- a/libide/workbench/ide-workbench.ui
+++ b/libide/workbench/ide-workbench.ui
@@ -6,22 +6,27 @@
       <class name="workbench"/>
     </style>
     <child type="titlebar">
-      <object class="GtkStack" id="header_stack">
+      <object class="GtkBox" id="header_container">
         <property name="visible">true</property>
         <child>
-          <object class="IdeWorkbenchHeaderBar" id="header_bar">
+          <object class="GtkStack" id="header_stack">
             <property name="visible">true</property>
-            <child internal-child="left">
-              <object class="DzlPriorityBox">
-                <child>
-                  <object class="IdePerspectiveMenuButton" id="perspective_menu_button">
-                    <property name="focus-on-click">false</property>
-                    <property name="stack">perspectives_stack</property>
-                    <property name="visible">false</property>
+            <child>
+              <object class="IdeWorkbenchHeaderBar" id="header_bar">
+                <property name="visible">true</property>
+                <child internal-child="left">
+                  <object class="DzlPriorityBox">
+                    <child>
+                      <object class="IdePerspectiveMenuButton" id="perspective_menu_button">
+                        <property name="focus-on-click">false</property>
+                        <property name="stack">perspectives_stack</property>
+                        <property name="visible">false</property>
+                      </object>
+                      <packing>
+                        <property name="priority">-100000</property>
+                      </packing>
+                    </child>
                   </object>
-                  <packing>
-                    <property name="priority">-100000</property>
-                  </packing>
                 </child>
               </object>
             </child>
@@ -32,27 +37,41 @@
     <child>
       <object class="GtkOverlay">
         <property name="visible">true</property>
+        <child type="overlay">
+          <object class="GtkRevealer" id="header_revealer">
+            <property name="valign">start</property>
+            <property name="reveal-child">false</property>
+            <property name="transition-type">slide-down</property>
+            <signal name="notify::reveal-child" handler="ide_workbench_event_box_notify" 
object="IdeWorkbench" swapped="no" />
+            <signal name="notify::child-revealed" handler="ide_workbench_event_box_notify" 
object="IdeWorkbench" swapped="no" />
+          </object>
+        </child>
         <child>
-          <object class="GtkBox">
-            <property name="orientation">vertical</property>
+          <object class="GtkEventBox" id="fullscreen_eventbox">
             <property name="visible">true</property>
             <child>
-              <object class="GtkBox" id="message_box">
+              <object class="GtkBox">
                 <property name="orientation">vertical</property>
-                <property name="hexpand">false</property>
-                <property name="visible">true</property>
-                <style>
-                  <class name="message-box"/>
-                </style>
-              </object>
-            </child>
-            <child>
-              <object class="GtkStack" id="perspectives_stack">
-                <property name="hexpand">true</property>
-                <property name="homogeneous">false</property>
-                <property name="transition-type">crossfade</property>
-                <property name="transition-duration">333</property>
                 <property name="visible">true</property>
+                <child>
+                  <object class="GtkBox" id="message_box">
+                    <property name="orientation">vertical</property>
+                    <property name="hexpand">false</property>
+                    <property name="visible">true</property>
+                    <style>
+                      <class name="message-box"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkStack" id="perspectives_stack">
+                    <property name="hexpand">true</property>
+                    <property name="homogeneous">false</property>
+                    <property name="transition-type">crossfade</property>
+                    <property name="transition-duration">333</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
               </object>
             </child>
           </object>


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