Commit fd8aea6c authored by Christian Hergert's avatar Christian Hergert
Browse files

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.
parent ac80fef0
......@@ -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,9 +42,15 @@
<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>
......@@ -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>
......@@ -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);
}
......@@ -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;
};
......
......@@ -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 {
......@@ -45,6 +48,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.
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment