[gtksourceview/wip/chergert/hoverers] more work on hover machinery



commit b3f95b33daf79467710ac3b9b75f0fb08ee64fe2
Author: Christian Hergert <chergert redhat com>
Date:   Tue Mar 9 14:03:33 2021 -0800

    more work on hover machinery

 gtksourceview/gtksourcehover.c                  | 333 +++++++++++++++++++++++-
 gtksourceview/gtksourcehoverassistant-private.h |  39 +++
 gtksourceview/gtksourcehoverassistant.c         |  81 ++++++
 gtksourceview/meson.build                       |   1 +
 4 files changed, 441 insertions(+), 13 deletions(-)
---
diff --git a/gtksourceview/gtksourcehover.c b/gtksourceview/gtksourcehover.c
index 9b8b6e79..a44f5946 100644
--- a/gtksourceview/gtksourcehover.c
+++ b/gtksourceview/gtksourcehover.c
@@ -23,26 +23,83 @@
 
 #include "gtksourceassistant-private.h"
 #include "gtksourcehover-private.h"
+#include "gtksourcehoverassistant-private.h"
 #include "gtksourcehovercontext.h"
 #include "gtksourcehoverprovider.h"
+#include "gtksourceiter-private.h"
 #include "gtksourcesignalgroup-private.h"
 #include "gtksourceview.h"
 
+#define DISMISS_DELAY_MSEC 10
+#define GRACE_X 20
+#define GRACE_Y 20
+#define MOTION_SETTLE_TIMEOUT_MSEC 250
+
 struct _GtkSourceHover
 {
-  GObject             parent_instance;
+       GObject             parent_instance;
+
+       GtkSourceView      *view;
+       GtkSourceAssistant *assistant;
+
+       GPtrArray          *providers;
+
+       gdouble             motion_x;
+       gdouble             motion_y;
 
-  GtkSourceView      *view;
-  GtkSourceAssistant *assistant;
+       guint               state;
 
-  GPtrArray          *providers;
+       guint               delay_display_source;
+       guint               dismiss_source;
+};
 
-  guint               delay_display_source;
-  guint               dismiss_source;
+enum {
+       HOVER_STATE_INITIAL,
+       HOVER_STATE_DISPLAY,
+       HOVER_STATE_IN_POPOVER,
 };
 
 G_DEFINE_TYPE (GtkSourceHover, gtk_source_hover, G_TYPE_OBJECT)
 
+static gboolean
+gtk_source_hover_dismiss_cb (gpointer data)
+{
+       GtkSourceHover *self = data;
+
+       g_assert (GTK_SOURCE_IS_HOVER (self));
+
+       self->dismiss_source = 0;
+
+       switch (self->state) {
+       case HOVER_STATE_DISPLAY:
+               g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self->assistant));
+
+               gtk_widget_hide (GTK_WIDGET (self->assistant));
+
+               g_assert (self->state == HOVER_STATE_INITIAL);
+               g_assert (self->assistant == NULL);
+
+       break;
+
+       case HOVER_STATE_INITIAL:
+       case HOVER_STATE_IN_POPOVER:
+       default:
+               g_clear_handle_id (&self->delay_display_source, g_source_remove);
+       break;
+       }
+
+       return G_SOURCE_REMOVE;
+}
+
+static void
+gtk_source_hover_queue_dismiss (GtkSourceHover *self)
+{
+       g_assert (GTK_SOURCE_IS_HOVER (self));
+
+       g_clear_handle_id (&self->dismiss_source, g_source_remove);
+       self->dismiss_source = g_timeout_add (DISMISS_DELAY_MSEC, gtk_source_hover_dismiss_cb, self);
+}
+
 static gboolean
 on_key_pressed_cb (GtkSourceHover        *hover,
                    guint                  keyval,
@@ -66,34 +123,285 @@ on_focus_enter_cb (GtkSourceHover          *hover,
 }
 
 static void
-on_focus_leave_cb (GtkSourceHover          *hover,
+on_focus_leave_cb (GtkSourceHover          *self,
                    GtkEventControllerFocus *controller)
 {
-       g_assert (GTK_SOURCE_IS_HOVER (hover));
+       g_assert (GTK_SOURCE_IS_HOVER (self));
        g_assert (GTK_IS_EVENT_CONTROLLER_FOCUS (controller));
 
+       gtk_source_hover_queue_dismiss (self);
+}
+
+static gboolean
+gtk_source_hover_get_bounds (GtkSourceHover *self,
+                             GtkTextIter    *begin,
+                             GtkTextIter    *end,
+                             GtkTextIter    *location)
+{
+       GtkTextIter iter;
+       int x, y;
+
+       g_assert (GTK_SOURCE_IS_HOVER (self));
+       g_assert (GTK_SOURCE_IS_VIEW (self->view));
+       g_assert (begin != NULL);
+       g_assert (end != NULL);
+       g_assert (location != NULL);
+
+       memset (begin, 0, sizeof *begin);
+       memset (end, 0, sizeof *end);
+       memset (location, 0, sizeof *location);
+
+       if (self->view == NULL)
+       {
+               return FALSE;
+       }
+
+       g_assert (GTK_SOURCE_IS_VIEW (self->view));
+
+       gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (self->view),
+                                              GTK_TEXT_WINDOW_WIDGET,
+                                              self->motion_x,
+                                              self->motion_y,
+                                              &x, &y);
+
+       if (!gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (self->view), &iter, x, y))
+       {
+               return FALSE;
+       }
+
+       *location = iter;
+
+       if (!_gtk_source_iter_inside_word (&iter))
+       {
+               *begin = iter;
+               gtk_text_iter_set_line_offset (begin, 0);
+
+               *end = iter;
+               gtk_text_iter_forward_to_line_end (end);
+
+               return TRUE;
+       }
+
+       if (!_gtk_source_iter_starts_full_word (&iter))
+       {
+               _gtk_source_iter_backward_full_word_start (&iter);
+       }
+
+       *begin = iter;
+       *end = iter;
+
+       _gtk_source_iter_forward_full_word_end (end);
+
+       return TRUE;
 }
 
 static void
-on_motion_cb (GtkSourceHover           *hover,
+gtk_source_hover_assistant_closed_cb (GtkSourceHover          *self,
+                                      GtkSourceHoverAssistant *assistant)
+{
+       g_assert (GTK_SOURCE_IS_HOVER (self));
+       g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (assistant));
+
+       self->state = HOVER_STATE_INITIAL;
+
+       g_clear_pointer (&self->assistant, _gtk_source_assistant_destroy);
+
+       g_clear_handle_id (&self->dismiss_source, g_source_remove);
+       g_clear_handle_id (&self->delay_display_source, g_source_remove);
+
+       g_assert (self->assistant == NULL);
+       g_assert (self->state == HOVER_STATE_INITIAL);
+       g_assert (self->dismiss_source == 0);
+       g_assert (self->delay_display_source == 0);
+}
+
+static gboolean
+gtk_source_hover_assistant_motion_enter_cb (GtkSourceHover           *self,
+                                            double                    x,
+                                            double                    y,
+                                            GtkEventControllerMotion *motion)
+{
+       g_assert (GTK_SOURCE_IS_HOVER (self));
+       g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion));
+
+       /* Possible with DnD dragging? */
+       if (self->state != HOVER_STATE_DISPLAY)
+       {
+               return GDK_EVENT_PROPAGATE;
+       }
+
+       self->state = HOVER_STATE_IN_POPOVER;
+       g_clear_handle_id (&self->dismiss_source, g_source_remove);
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+gtk_source_hover_assistant_motion_leave_cb (GtkSourceHover           *self,
+                                            GtkEventControllerMotion *motion)
+{
+       g_assert (GTK_SOURCE_IS_HOVER (self));
+       g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion));
+
+       if (self->state == HOVER_STATE_IN_POPOVER)
+       {
+               self->state = HOVER_STATE_DISPLAY;
+       }
+
+       if (gtk_event_controller_motion_contains_pointer (motion))
+       {
+               gtk_source_hover_queue_dismiss (self);
+       }
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+gtk_source_hover_motion_timeout_cb (gpointer data)
+{
+       GtkSourceHover *self = data;
+       GdkRectangle rect;
+       GdkRectangle begin_rect;
+       GdkRectangle end_rect;
+       GdkRectangle location_rect;
+       GtkTextIter begin;
+       GtkTextIter end;
+       GtkTextIter location;
+
+       g_assert (GTK_SOURCE_IS_HOVER (self));
+
+       self->delay_display_source = 0;
+
+       if (self->view == NULL ||
+           self->state != HOVER_STATE_INITIAL ||
+           gtk_source_hover_get_bounds (self, &begin, &end, &location))
+       {
+               return G_SOURCE_REMOVE;
+       }
+
+       if (self->assistant == NULL)
+       {
+               GtkEventController *motion;
+
+               self->assistant = _gtk_source_assistant_new ();
+               gtk_popover_set_position (GTK_POPOVER (self->assistant), GTK_POS_TOP);
+
+               g_signal_connect_object (self->assistant,
+                                        "closed",
+                                        G_CALLBACK (gtk_source_hover_assistant_closed_cb),
+                                        self,
+                                        G_CONNECT_SWAPPED);
+
+               motion = gtk_event_controller_motion_new ();
+               g_signal_connect_object (motion,
+                                        "enter",
+                                        G_CALLBACK (gtk_source_hover_assistant_motion_enter_cb),
+                                        self,
+                                        G_CONNECT_SWAPPED);
+               g_signal_connect_object (motion,
+                                        "leave",
+                                        G_CALLBACK (gtk_source_hover_assistant_motion_leave_cb),
+                                        self,
+                                        G_CONNECT_SWAPPED);
+               gtk_widget_add_controller (GTK_WIDGET (self->assistant), motion);
+       }
+
+       self->state = HOVER_STATE_DISPLAY;
+       gtk_text_view_get_iter_location (GTK_TEXT_VIEW (self->view), &begin, &begin_rect);
+       gtk_text_view_get_iter_location (GTK_TEXT_VIEW (self->view), &end, &end_rect);
+       gtk_text_view_get_iter_location (GTK_TEXT_VIEW (self->view), &location, &location_rect);
+       gdk_rectangle_union (&begin_rect, &end_rect, &rect);
+
+       gtk_text_view_buffer_to_window_coords (GTK_TEXT_VIEW (self->view),
+                                              GTK_TEXT_WINDOW_WIDGET,
+                                              rect.x, rect.y, &rect.x, &rect.y);
+
+       _gtk_source_hover_assistant_set_hovered_at (GTK_SOURCE_HOVER_ASSISTANT (self->assistant),
+                                                   &location_rect);
+
+       if (gtk_text_iter_equal (&begin, &end) &&
+           gtk_text_iter_starts_line (&begin))
+       {
+               rect.width = 1;
+               gtk_popover_set_position (GTK_POPOVER (self->assistant), GTK_POS_RIGHT);
+       }
+       else
+       {
+               gtk_popover_set_position (GTK_POPOVER (self->assistant), GTK_POS_TOP);
+       }
+
+       gtk_widget_show (GTK_WIDGET (self->assistant));
+
+       return G_SOURCE_REMOVE;
+}
+
+static void
+on_motion_cb (GtkSourceHover           *self,
               double                    x,
               double                    y,
               GtkEventControllerMotion *controller)
 {
-       g_assert (GTK_SOURCE_IS_HOVER (hover));
+       g_assert (GTK_SOURCE_IS_HOVER (self));
        g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (controller));
 
+       /*
+        * If we have a popover displayed, get it's allocation so that
+        * we can detect if our x/y coordinate is outside the threshold
+        * of the rectangle + grace area. If so, we'll dismiss the popover
+        * immediately.
+        */
+
+       if (self->assistant != NULL)
+       {
+               GtkAllocation alloc;
+               GdkRectangle pointing_to;
+               double dx, dy;
+
+               gtk_widget_get_allocation (GTK_WIDGET (self->assistant), &alloc);
+               gtk_widget_translate_coordinates (GTK_WIDGET (self->assistant),
+                                                 GTK_WIDGET (self->view),
+                                                 alloc.x, alloc.y,
+                                                 &dx, &dy);
+               gtk_popover_get_pointing_to (GTK_POPOVER (self->assistant), &pointing_to);
+
+               alloc.x = dx - GRACE_X;
+               alloc.width += GRACE_X * 2;
+               alloc.y = dy - GRACE_Y;
+               alloc.height += GRACE_Y * 2;
+
+               gdk_rectangle_union (&alloc, &pointing_to, &alloc);
+
+               if (x < alloc.x ||
+                   x > (alloc.x + alloc.width) ||
+                   y < alloc.y ||
+                   y > (alloc.y + alloc.height))
+               {
+                       gtk_widget_hide (GTK_WIDGET (self->assistant));
+
+                       g_assert (self->assistant == NULL);
+                       g_assert (self->state == HOVER_STATE_INITIAL);
+               }
+       }
+
+       g_clear_handle_id (&self->dismiss_source, g_source_remove);
+       g_clear_handle_id (&self->delay_display_source, g_source_remove);
+
+       self->delay_display_source = g_timeout_add (MOTION_SETTLE_TIMEOUT_MSEC,
+                                                   gtk_source_hover_motion_timeout_cb,
+                                                   self);
 }
 
 static gboolean
-on_scroll_cb (GtkSourceHover           *hover,
+on_scroll_cb (GtkSourceHover           *self,
               double                    dx,
               double                    dy,
               GtkEventControllerScroll *controller)
 {
-       g_assert (GTK_SOURCE_IS_HOVER (hover));
+       g_assert (GTK_SOURCE_IS_HOVER (self));
        g_assert (GTK_IS_EVENT_CONTROLLER_SCROLL (controller));
 
+       g_clear_pointer (&self->assistant, _gtk_source_assistant_destroy);
+
        return GDK_EVENT_PROPAGATE;
 }
 
@@ -157,7 +465,6 @@ _gtk_source_hover_new (GtkSourceView *view)
        g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), NULL);
 
        self = g_object_new (GTK_SOURCE_TYPE_HOVER, NULL);
-
        g_set_weak_pointer (&self->view, view);
 
        key = gtk_event_controller_key_new ();
diff --git a/gtksourceview/gtksourcehoverassistant-private.h b/gtksourceview/gtksourcehoverassistant-private.h
new file mode 100644
index 00000000..00885fe6
--- /dev/null
+++ b/gtksourceview/gtksourcehoverassistant-private.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#include "gtksourcetypes.h"
+
+#include "gtksourceassistant-private.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_HOVER_ASSISTANT (gtk_source_hover_assistant_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkSourceHoverAssistant, gtk_source_hover_assistant, GTK_SOURCE, HOVER_ASSISTANT, 
GtkSourceAssistant)
+
+GtkSourceHoverAssistant *_gtk_source_hover_assistant_new            (void);
+GtkSourceHoverDisplay   *_gtk_source_hover_assistant_get_display    (GtkSourceHoverAssistant *self);
+void                     _gtk_source_hover_assistant_set_hovered_at (GtkSourceHoverAssistant *self,
+                                                                     const GdkRectangle      *rect);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcehoverassistant.c b/gtksourceview/gtksourcehoverassistant.c
new file mode 100644
index 00000000..924bee39
--- /dev/null
+++ b/gtksourceview/gtksourcehoverassistant.c
@@ -0,0 +1,81 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gtksourceassistant-private.h"
+#include "gtksourcehoverassistant-private.h"
+#include "gtksourcehoverdisplay.h"
+
+struct _GtkSourceHoverAssistant
+{
+       GtkSourceAssistant parent_instance;
+  GtkSourceHoverDisplay *display;
+       GdkRectangle hovered_at;
+};
+
+G_DEFINE_TYPE (GtkSourceHoverAssistant, gtk_source_hover_assistant, GTK_SOURCE_TYPE_ASSISTANT)
+
+static void
+gtk_source_hover_assistant_get_target_location (GtkSourceAssistant *assistant,
+                                                GdkRectangle       *rect)
+{
+       *rect = GTK_SOURCE_HOVER_ASSISTANT (assistant)->hovered_at;
+}
+
+static void
+gtk_source_hover_assistant_class_init (GtkSourceHoverAssistantClass *klass)
+{
+       GtkSourceAssistantClass *assistant_class = GTK_SOURCE_ASSISTANT_CLASS (klass);
+
+       assistant_class->get_target_location = gtk_source_hover_assistant_get_target_location;
+}
+
+static void
+gtk_source_hover_assistant_init (GtkSourceHoverAssistant *self)
+{
+       self->display = g_object_new (GTK_SOURCE_TYPE_HOVER_DISPLAY, NULL);
+       _gtk_source_assistant_set_child (GTK_SOURCE_ASSISTANT (self), GTK_WIDGET (self->display));
+}
+
+GtkSourceHoverAssistant *
+_gtk_source_hover_assistant_new (void)
+{
+       return g_object_new (GTK_SOURCE_TYPE_HOVER_ASSISTANT, NULL);
+}
+
+void
+_gtk_source_hover_assistant_set_hovered_at (GtkSourceHoverAssistant *self,
+                                            const GdkRectangle      *hovered_at)
+{
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_ASSISTANT (self));
+       g_return_if_fail (hovered_at != NULL);
+
+       self->hovered_at = *hovered_at;
+}
+
+GtkSourceHoverDisplay *
+_gtk_source_hover_assistant_get_display (GtkSourceHoverAssistant *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_HOVER_ASSISTANT (self), NULL);
+
+       return self->display;
+}
diff --git a/gtksourceview/meson.build b/gtksourceview/meson.build
index b3982261..6f6013ae 100644
--- a/gtksourceview/meson.build
+++ b/gtksourceview/meson.build
@@ -115,6 +115,7 @@ core_private_c = files([
   'gtksourceengine.c',
   'gtksourcegutterrendererlines.c',
   'gtksourcegutterrenderermarks.c',
+  'gtksourcehoverassistant.c',
   'gtksourceinformative.c',
   'gtksourceiter.c',
   'gtksourcelanguage-parser-2.c',


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