[gnome-builder] minimap: animate the minimap in/out of view



commit fd8aea6c9d0ffe9c948b09d977f37cc2eb68c346
Author: Christian Hergert <christian hergert me>
Date:   Tue Apr 28 16:34:30 2015 -0700

    minimap: animate the minimap in/out of view
    
    When the mouse is placed over an editor widget (such as the source map
    or the source view), animate the source map in. When it leaves, animate
    it out after a short delay.
    
    I assume we want to add to this the ability to hide the map once typing
    begins similar to the scrolled window.

 data/ui/gb-editor-frame.ui           |   21 +++--
 libide/ide-source-map.c              |  163 ++++++++++++++++++++++++++++++++--
 src/editor/gb-editor-frame-private.h |    7 ++-
 src/editor/gb-editor-frame.c         |  118 ++++++++++++++++++++++++-
 4 files changed, 291 insertions(+), 18 deletions(-)
---
diff --git a/data/ui/gb-editor-frame.ui b/data/ui/gb-editor-frame.ui
index 91a9a73..00f1912 100644
--- a/data/ui/gb-editor-frame.ui
+++ b/data/ui/gb-editor-frame.ui
@@ -3,7 +3,7 @@
   <!-- interface-requires gtk+ 3.15 -->
   <template class="GbEditorFrame" parent="GtkBin">
     <child>
-      <object class="GtkOverlay">
+      <object class="GtkOverlay" id="frame_overlay">
         <property name="expand">true</property>
         <property name="visible">true</property>
         <child type="overlay">
@@ -42,10 +42,16 @@
             <property name="orientation">horizontal</property>
             <property name="visible">true</property>
             <child>
-              <object class="GtkOverlay">
+              <object class="GtkOverlay" id="source_overlay">
                 <property name="expand">true</property>
                 <property name="visible">true</property>
                 <child type="overlay">
+                  <object class="GbEditorMapBin" id="source_map_container">
+                    <property name="floating-bar">floating_bar</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+                <child type="overlay">
                   <object class="GtkRevealer" id="search_revealer">
                     <property name="halign">end</property>
                     <property name="valign">start</property>
@@ -146,15 +152,14 @@
                 </child>
               </object>
             </child>
-            <child>
-              <object class="GbEditorMapBin" id="source_map_container">
-                <property name="floating-bar">floating_bar</property>
-                <property name="visible">true</property>
-              </object>
-            </child>
           </object>
         </child>
       </object>
     </child>
   </template>
+  <object class="GtkAdjustment" id="overlay_adj">
+    <property name="lower">0.0</property>
+    <property name="upper">1.0</property>
+    <property name="value">1.0</property>
+  </object>
 </interface>
diff --git a/libide/ide-source-map.c b/libide/ide-source-map.c
index 50b0187..b516b00 100644
--- a/libide/ide-source-map.c
+++ b/libide/ide-source-map.c
@@ -27,8 +27,9 @@
 #include "ide-source-map.h"
 #include "ide-source-view.h"
 
-#define DEFAULT_WIDTH 100
-#define DELAYED_DRAW_TIMEOUT_MSEC 34
+#define DEFAULT_WIDTH        100
+#define DELAYED_HIDE_TIMEOUT 1000
+#define DELAYED_SHOW_TIMEOUT 50
 
 struct _IdeSourceMap
 {
@@ -44,7 +45,11 @@ struct _IdeSourceMap
   GtkSourceView           *view;
   GtkSourceGutterRenderer *line_renderer;
 
+  guint                    delayed_reveal_timeout;
+
   guint                    in_press : 1;
+  guint                    is_hiding : 1;
+  guint                    show_map : 1;
 };
 
 struct _IdeSourceMapClass
@@ -61,7 +66,79 @@ enum {
   LAST_PROP
 };
 
+enum {
+  SHOW_MAP,
+  HIDE_MAP,
+  LAST_SIGNAL
+};
+
 static GParamSpec *gParamSpecs [LAST_PROP];
+static guint gSignals [LAST_SIGNAL];
+
+static gboolean
+ide_source_map_do_reveal (gpointer data)
+{
+  IdeSourceMap *self = data;
+
+  g_assert (IDE_IS_SOURCE_MAP (self));
+
+  self->delayed_reveal_timeout = 0;
+
+  /* ignore if we are already at this state */
+  if ((!self->is_hiding) == self->show_map)
+    return G_SOURCE_REMOVE;
+
+  self->show_map = !self->is_hiding;
+
+  if (self->is_hiding)
+    g_signal_emit (self, gSignals [HIDE_MAP], 0);
+  else
+    g_signal_emit (self, gSignals [SHOW_MAP], 0);
+
+  return G_SOURCE_REMOVE;
+}
+
+static gboolean
+ide_source_map__enter_notify_event (IdeSourceMap     *self,
+                                    GdkEventCrossing *event,
+                                    GtkWidget        *widget)
+{
+  g_assert (IDE_IS_SOURCE_MAP (self));
+  g_assert (event != NULL);
+  g_assert (GTK_IS_WIDGET (widget));
+
+  self->is_hiding = FALSE;
+
+  if (self->delayed_reveal_timeout != 0)
+    g_source_remove (self->delayed_reveal_timeout);
+
+  self->delayed_reveal_timeout = g_timeout_add (DELAYED_SHOW_TIMEOUT,
+                                                ide_source_map_do_reveal,
+                                                self);
+
+  return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+ide_source_map__leave_notify_event (IdeSourceMap     *self,
+                                    GdkEventCrossing *event,
+                                    GtkWidget        *widget)
+{
+  g_assert (IDE_IS_SOURCE_MAP (self));
+  g_assert (event != NULL);
+  g_assert (GTK_IS_WIDGET (widget));
+
+  self->is_hiding = TRUE;
+
+  if (self->delayed_reveal_timeout != 0)
+    g_source_remove (self->delayed_reveal_timeout);
+
+  self->delayed_reveal_timeout = g_timeout_add (DELAYED_HIDE_TIMEOUT,
+                                                ide_source_map_do_reveal,
+                                                self);
+
+  return GDK_EVENT_PROPAGATE;
+}
 
 static void
 ide_source_map_rebuild_css (IdeSourceMap *self)
@@ -334,6 +411,18 @@ ide_source_map_set_view (IdeSourceMap  *self,
                                    self,
                                    G_CONNECT_SWAPPED);
 
+          g_signal_connect_object (view,
+                                   "enter-notify-event",
+                                   G_CALLBACK (ide_source_map__enter_notify_event),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+          g_signal_connect_object (view,
+                                   "leave-notify-event",
+                                   G_CALLBACK (ide_source_map__leave_notify_event),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
           buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
           ide_source_map__buffer_notify_style_scheme (self, NULL, buffer);
 
@@ -361,6 +450,12 @@ ide_source_map_set_view (IdeSourceMap  *self,
                                    self,
                                    G_CONNECT_SWAPPED);
 
+          if ((gtk_widget_get_events (GTK_WIDGET (self->view)) & GDK_ENTER_NOTIFY_MASK) == 0)
+            gtk_widget_add_events (GTK_WIDGET (self->view), GDK_ENTER_NOTIFY_MASK);
+
+          if ((gtk_widget_get_events (GTK_WIDGET (self->view)) & GDK_LEAVE_NOTIFY_MASK) == 0)
+            gtk_widget_add_events (GTK_WIDGET (self->view), GDK_LEAVE_NOTIFY_MASK);
+
           ide_source_map_rebuild_css (self);
         }
 
@@ -688,16 +783,22 @@ ide_source_map_do_scroll_event (IdeSourceMap   *self,
 }
 
 static void
-ide_source_map_finalize (GObject *object)
+ide_source_map_destroy (GtkWidget *widget)
 {
-  IdeSourceMap *self = (IdeSourceMap *)object;
+  IdeSourceMap *self = (IdeSourceMap *)widget;
+
+  if (self->delayed_reveal_timeout)
+    {
+      g_source_remove (self->delayed_reveal_timeout);
+      self->delayed_reveal_timeout = 0;
+    }
 
   g_clear_object (&self->box_css_provider);
   g_clear_object (&self->view_css_provider);
   g_clear_pointer (&self->font_desc, pango_font_description_free);
   ide_clear_weak_pointer (&self->view);
 
-  G_OBJECT_CLASS (ide_source_map_parent_class)->finalize (object);
+  GTK_WIDGET_CLASS (ide_source_map_parent_class)->destroy (widget);
 }
 
 static void
@@ -749,10 +850,10 @@ ide_source_map_class_init (IdeSourceMapClass *klass)
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
   GtkOverlayClass *overlay_class = GTK_OVERLAY_CLASS (klass);
 
-  object_class->finalize = ide_source_map_finalize;
   object_class->get_property = ide_source_map_get_property;
   object_class->set_property = ide_source_map_set_property;
 
+  widget_class->destroy = ide_source_map_destroy;
   widget_class->get_preferred_height = ide_source_map_get_preferred_height;
   widget_class->get_preferred_width = ide_source_map_get_preferred_width;
   widget_class->size_allocate = ide_source_map_size_allocate;
@@ -774,6 +875,26 @@ ide_source_map_class_init (IdeSourceMapClass *klass)
                         PANGO_TYPE_FONT_DESCRIPTION,
                         (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
   g_object_class_install_property (object_class, PROP_FONT_DESC, gParamSpecs [PROP_FONT_DESC]);
+
+  gSignals [HIDE_MAP] =
+    g_signal_new ("hide-map",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                  0,
+                  NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  0);
+
+  gSignals [SHOW_MAP] =
+    g_signal_new ("show-map",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                  0,
+                  NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  0);
 }
 
 static void
@@ -834,7 +955,7 @@ ide_source_map_init (IdeSourceMap *self)
    */
   gutter = gtk_source_view_get_gutter (self->child_view, GTK_TEXT_WINDOW_LEFT);
   self->line_renderer = g_object_new (IDE_TYPE_LINE_CHANGE_GUTTER_RENDERER,
-                                      "size", 3,
+                                      "size", 2,
                                       "visible", TRUE,
                                       NULL);
   gtk_source_gutter_insert (gutter, self->line_renderer, 0);
@@ -850,7 +971,6 @@ ide_source_map_init (IdeSourceMap *self)
                            G_CALLBACK (ide_source_map__overlay_box_button_press_event),
                            self,
                            G_CONNECT_SWAPPED);
-  gtk_widget_add_events (GTK_WIDGET (self->overlay_box), GDK_SCROLL_MASK);
   g_signal_connect_object (self->overlay_box,
                            "scroll-event",
                            G_CALLBACK (ide_source_map_do_scroll_event),
@@ -878,4 +998,31 @@ ide_source_map_init (IdeSourceMap *self)
   gtk_source_completion_block_interactive (completion);
 
   ide_source_map_set_font_name (self, "Monospace 1");
+
+  gtk_widget_add_events (GTK_WIDGET (self->overlay_box),
+                         (GDK_SCROLL_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK));
+  gtk_widget_add_events (GTK_WIDGET (self->child_view),
+                         (GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK));
+
+  g_signal_connect_object (self->overlay_box,
+                           "enter-notify-event",
+                           G_CALLBACK (ide_source_map__enter_notify_event),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->overlay_box,
+                           "leave-notify-event",
+                           G_CALLBACK (ide_source_map__leave_notify_event),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->child_view,
+                           "enter-notify-event",
+                           G_CALLBACK (ide_source_map__enter_notify_event),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->child_view,
+                           "leave-notify-event",
+                           G_CALLBACK (ide_source_map__leave_notify_event),
+                           self,
+                           G_CONNECT_SWAPPED);
 }
diff --git a/src/editor/gb-editor-frame-private.h b/src/editor/gb-editor-frame-private.h
index 6bd1055..7044b58 100644
--- a/src/editor/gb-editor-frame-private.h
+++ b/src/editor/gb-editor-frame-private.h
@@ -22,6 +22,7 @@
 #include <gtk/gtk.h>
 #include <ide.h>
 
+#include "gb-editor-map-bin.h"
 #include "gd-tagged-entry.h"
 #include "nautilus-floating-bar.h"
 
@@ -33,14 +34,18 @@ struct _GbEditorFrame
 
   NautilusFloatingBar *floating_bar;
   GtkLabel            *mode_name_label;
+  GtkAdjustment       *overlay_adj;
   GtkLabel            *overwrite_label;
   GtkScrolledWindow   *scrolled_window;
   GtkRevealer         *search_revealer;
   GdTaggedEntry       *search_entry;
   GdTaggedEntryTag    *search_entry_tag;
   IdeSourceView       *source_view;
-  GtkBox              *source_map_container;
+  GbEditorMapBin      *source_map_container;
   IdeSourceMap        *source_map;
+  GtkOverlay          *source_overlay;
+
+  IdeAnimation        *map_animation;
 
   gulong               cursor_moved_handler;
 };
diff --git a/src/editor/gb-editor-frame.c b/src/editor/gb-editor-frame.c
index 79645a6..deea3e9 100644
--- a/src/editor/gb-editor-frame.c
+++ b/src/editor/gb-editor-frame.c
@@ -29,6 +29,9 @@
 #include "gb-view-stack.h"
 #include "gb-widget.h"
 
+#define MINIMAP_HIDE_DURATION 500
+#define MINIMAP_SHOW_DURATION 250
+
 G_DEFINE_TYPE (GbEditorFrame, gb_editor_frame, GTK_TYPE_BIN)
 
 enum {
@@ -46,6 +49,57 @@ enum {
 static GParamSpec *gParamSpecs [LAST_PROP];
 
 static void
+gb_editor_frame_animate_map (GbEditorFrame *self,
+                             gboolean       visible)
+{
+  IdeAnimation *animation;
+  GdkFrameClock *frame_clock;
+  gdouble value;
+  guint duration;
+
+  g_assert (GB_IS_EDITOR_FRAME (self));
+
+  if (self->map_animation)
+    {
+      animation = self->map_animation;
+      ide_clear_weak_pointer (&self->map_animation);
+      ide_animation_stop (animation);
+    }
+
+  frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (self->source_map_container));
+  duration = visible ? MINIMAP_SHOW_DURATION : MINIMAP_HIDE_DURATION;
+  value = visible ? 0.0 : 1.0;
+
+  animation = ide_object_animate (self->overlay_adj,
+                                  IDE_ANIMATION_EASE_IN_OUT_QUAD,
+                                  duration,
+                                  frame_clock,
+                                  "value", value,
+                                  NULL);
+  ide_set_weak_pointer (&self->map_animation, animation);
+}
+
+static void
+gb_editor_frame_show_map (GbEditorFrame *self,
+                          IdeSourceMap  *source_map)
+{
+  g_assert (GB_IS_EDITOR_FRAME (self));
+  g_assert (IDE_IS_SOURCE_MAP (source_map));
+
+  gb_editor_frame_animate_map (self, TRUE);
+}
+
+static void
+gb_editor_frame_hide_map (GbEditorFrame *self,
+                          IdeSourceMap  *source_map)
+{
+  g_assert (GB_IS_EDITOR_FRAME (self));
+  g_assert (IDE_IS_SOURCE_MAP (source_map));
+
+  gb_editor_frame_animate_map (self, FALSE);
+}
+
+static void
 gb_editor_frame_set_position_label (GbEditorFrame *self,
                                     const gchar   *text)
 {
@@ -485,6 +539,16 @@ gb_editor_frame_set_show_map (GbEditorFrame *self,
                                            "view", self->source_view,
                                            "visible", TRUE,
                                            NULL);
+          g_signal_connect_object (self->source_map,
+                                   "show-map",
+                                   G_CALLBACK (gb_editor_frame_show_map),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+          g_signal_connect_object (self->source_map,
+                                   "hide-map",
+                                   G_CALLBACK (gb_editor_frame_hide_map),
+                                   self,
+                                   G_CONNECT_SWAPPED);
           gtk_container_add (GTK_CONTAINER (self->source_map_container),
                              GTK_WIDGET (self->source_map));
         }
@@ -522,6 +586,42 @@ gb_editor_frame__source_view_populate_popup (GbEditorFrame *self,
     }
 }
 
+static gboolean
+gb_editor_frame__source_overlay_get_child_position (GbEditorFrame *self,
+                                                   GtkWidget     *widget,
+                                                   GtkAllocation *alloc,
+                                                   GtkOverlay    *overlay)
+{
+  GtkAllocation main_alloc;
+  GtkRequisition req;
+
+  g_assert (GTK_IS_OVERLAY (overlay));
+  g_assert (GB_IS_EDITOR_FRAME (self));
+  g_assert (GTK_IS_WIDGET (widget));
+  g_assert (alloc != NULL);
+
+  if (widget == (GtkWidget *)self->source_map_container)
+    {
+      gdouble value;
+
+      gtk_widget_get_allocation (GTK_WIDGET (self), &main_alloc);
+      gtk_widget_get_preferred_size (widget, &req, NULL);
+
+      alloc->x = main_alloc.x + main_alloc.width - req.width;
+      alloc->width = req.width;
+      alloc->y = main_alloc.y;
+      alloc->height = main_alloc.height;
+
+      /* adjust for animation */
+      value = gtk_adjustment_get_value (self->overlay_adj);
+      alloc->x += (value * alloc->width);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
 static void
 gb_editor_frame_constructed (GObject *object)
 {
@@ -571,6 +671,8 @@ gb_editor_frame_dispose (GObject *object)
 {
   GbEditorFrame *self = (GbEditorFrame *)object;
 
+  ide_clear_weak_pointer (&self->map_animation);
+
   if (self->source_view && self->cursor_moved_handler)
     {
       GtkTextBuffer *buffer;
@@ -677,11 +779,13 @@ gb_editor_frame_class_init (GbEditorFrameClass *klass)
 
   GB_WIDGET_CLASS_BIND (klass, GbEditorFrame, floating_bar);
   GB_WIDGET_CLASS_BIND (klass, GbEditorFrame, mode_name_label);
+  GB_WIDGET_CLASS_BIND (klass, GbEditorFrame, overlay_adj);
   GB_WIDGET_CLASS_BIND (klass, GbEditorFrame, overwrite_label);
   GB_WIDGET_CLASS_BIND (klass, GbEditorFrame, scrolled_window);
   GB_WIDGET_CLASS_BIND (klass, GbEditorFrame, search_entry);
-  GB_WIDGET_CLASS_BIND (klass, GbEditorFrame, source_map_container);
   GB_WIDGET_CLASS_BIND (klass, GbEditorFrame, search_revealer);
+  GB_WIDGET_CLASS_BIND (klass, GbEditorFrame, source_map_container);
+  GB_WIDGET_CLASS_BIND (klass, GbEditorFrame, source_overlay);
   GB_WIDGET_CLASS_BIND (klass, GbEditorFrame, source_view);
 
   g_type_ensure (NAUTILUS_TYPE_FLOATING_BAR);
@@ -715,6 +819,18 @@ gb_editor_frame_init (GbEditorFrame *self)
 
   g_object_bind_property (self->source_view, "overwrite", self->overwrite_label, "visible", 
G_BINDING_SYNC_CREATE);
 
+  g_signal_connect_object (self->source_overlay,
+                           "get-child-position",
+                           G_CALLBACK (gb_editor_frame__source_overlay_get_child_position),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->overlay_adj,
+                           "value-changed",
+                           G_CALLBACK (gtk_widget_queue_resize),
+                           self->source_map_container,
+                           G_CONNECT_SWAPPED);
+
   /*
    * we want to rubberbanding search until enter has been pressed or next/previous actions
    * have been activated.


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