[gtk/wip/textview] text view: Smooth cursor blinking



commit 57fb29e572b4e4e756babaa72556b38d1fc8926c
Author: Matthias Clasen <mclasen redhat com>
Date:   Sun Jul 21 13:06:23 2019 -0700

    text view: Smooth cursor blinking
    
    Fade the text cursor in and out, instead
    of abruptly turning it on and off.

 gtk/gtktextlayout.c        |  14 +++-
 gtk/gtktextlayoutprivate.h |   3 +-
 gtk/gtktextutil.c          |   2 +-
 gtk/gtktextview.c          | 181 ++++++++++++++++++++++++++-------------------
 4 files changed, 119 insertions(+), 81 deletions(-)
---
diff --git a/gtk/gtktextlayout.c b/gtk/gtktextlayout.c
index 5564fd815f..5e2d42a07f 100644
--- a/gtk/gtktextlayout.c
+++ b/gtk/gtktextlayout.c
@@ -3795,7 +3795,8 @@ render_para (GskPangoRenderer   *crenderer,
              int                 offset_y,
              GtkTextLineDisplay *line_display,
              int                 selection_start_index,
-             int                 selection_end_index)
+             int                 selection_end_index,
+             float               cursor_alpha)
 {
   GtkStyleContext *context;
   PangoLayout *layout = line_display->layout;
@@ -3972,6 +3973,7 @@ render_para (GskPangoRenderer   *crenderer,
                * (normally white on black) */
               _gtk_style_context_get_cursor_color (context, &cursor_color, NULL);
 
+              gtk_snapshot_push_opacity (crenderer->snapshot, cursor_alpha);
               gtk_snapshot_append_color (crenderer->snapshot, &cursor_color, &bounds);
 
               /* draw text under the cursor if any */
@@ -3985,6 +3987,7 @@ render_para (GskPangoRenderer   *crenderer,
                                                    baseline);
                   gtk_snapshot_pop (crenderer->snapshot);
                 }
+              gtk_snapshot_pop (crenderer->snapshot);
             }
         }
 
@@ -4002,7 +4005,8 @@ void
 gtk_text_layout_snapshot (GtkTextLayout      *layout,
                           GtkWidget          *widget,
                           GtkSnapshot        *snapshot,
-                          const GdkRectangle *clip)
+                          const GdkRectangle *clip,
+                          float               cursor_alpha)
 {
   GskPangoRenderer *crenderer;
   GtkStyleContext *context;
@@ -4079,7 +4083,8 @@ gtk_text_layout_snapshot (GtkTextLayout      *layout,
             }
 
           render_para (crenderer, offset_y, line_display,
-                       selection_start_index, selection_end_index);
+                       selection_start_index, selection_end_index,
+                       cursor_alpha);
 
           /* We paint the cursors last, because they overlap another chunk
            * and need to appear on top.
@@ -4088,6 +4093,7 @@ gtk_text_layout_snapshot (GtkTextLayout      *layout,
             {
               int i;
 
+              gtk_snapshot_push_opacity (crenderer->snapshot, cursor_alpha);
               for (i = 0; i < line_display->cursors->len; i++)
                 {
                   int index;
@@ -4100,6 +4106,8 @@ gtk_text_layout_snapshot (GtkTextLayout      *layout,
                                                         line_display->x_offset, offset_y + 
line_display->top_margin,
                                                         line_display->layout, index, dir);
                 }
+
+              gtk_snapshot_pop (crenderer->snapshot);
             }
         } /* line_display->height > 0 */
 
diff --git a/gtk/gtktextlayoutprivate.h b/gtk/gtktextlayoutprivate.h
index 7ba6627b04..7aff2074ff 100644
--- a/gtk/gtktextlayoutprivate.h
+++ b/gtk/gtktextlayoutprivate.h
@@ -400,7 +400,8 @@ void gtk_text_layout_spew (GtkTextLayout *layout);
 void gtk_text_layout_snapshot (GtkTextLayout        *layout,
                                GtkWidget            *widget,
                                GtkSnapshot          *snapshot,
-                               const GdkRectangle   *clip);
+                               const GdkRectangle   *clip,
+                               float                 cursor_alpha);
 
 G_END_DECLS
 
diff --git a/gtk/gtktextutil.c b/gtk/gtktextutil.c
index e806c6a205..2c3c43d8bc 100644
--- a/gtk/gtktextutil.c
+++ b/gtk/gtktextutil.c
@@ -335,7 +335,7 @@ gtk_text_util_create_rich_drag_icon (GtkWidget     *widget,
 
   snapshot = gtk_snapshot_new ();
 
-  gtk_text_layout_snapshot (layout, widget, snapshot, &(GdkRectangle) { 0, 0, layout_width, layout_height });
+  gtk_text_layout_snapshot (layout, widget, snapshot, &(GdkRectangle) { 0, 0, layout_width, layout_height }, 
1.0);
 
   g_object_unref (layout);
   g_object_unref (new_buffer);
diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c
index 73c8521455..4b30b8d653 100644
--- a/gtk/gtktextview.c
+++ b/gtk/gtktextview.c
@@ -209,7 +209,10 @@ struct _GtkTextViewPrivate
   GtkTextMark *first_para_mark; /* Mark at the beginning of the first onscreen paragraph */
   gint first_para_pixels;       /* Offset of top of screen in the first onscreen paragraph */
 
-  guint blink_timeout;
+  guint64 blink_start_time;
+  guint blink_tick;
+  float cursor_alpha;
+
   guint scroll_timeout;
 
   guint first_validate_idle;        /* Idle to revalidate onscreen portion, runs before resize */
@@ -5346,12 +5349,6 @@ gtk_text_view_paint (GtkWidget   *widget,
       g_warning (G_STRLOC ": somehow some text lines were modified or scrolling occurred since the last 
validation of lines on the screen - may be a text widget bug.");
       g_assert_not_reached ();
     }
-  
-#if 0
-  printf ("painting %d,%d  %d x %d\n",
-          area->x, area->y,
-          area->width, area->height);
-#endif
 
   gtk_snapshot_save (snapshot);
   gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (-priv->xoffset, -priv->yoffset));
@@ -5364,7 +5361,8 @@ gtk_text_view_paint (GtkWidget   *widget,
                               priv->yoffset,
                               gtk_widget_get_width (widget),
                               gtk_widget_get_height (widget)
-                            });
+                            },
+                            priv->cursor_alpha);
 
   gtk_snapshot_restore (snapshot);
 }
@@ -5661,15 +5659,80 @@ get_cursor_blink_timeout (GtkTextView *text_view)
  * Blink!
  */
 
-static gint
-blink_cb (gpointer data)
+typedef struct {
+  guint64 start;
+  guint64 end;
+} BlinkData;
+
+static gboolean blink_cb (GtkWidget     *widget,
+                          GdkFrameClock *clock,
+                          gpointer       user_data);
+
+
+static void
+add_blink_timeout (GtkTextView *self)
+{
+  GtkTextViewPrivate *priv = self->priv;
+  BlinkData *data;
+  int blink_time;
+
+  priv->blink_start_time = g_get_monotonic_time ();
+  priv->cursor_alpha = 1.0;
+
+  blink_time = get_cursor_time (self);
+
+  data = g_new (BlinkData, 1);
+  data->start = priv->blink_start_time;
+  data->end = data->start + blink_time * 1000;
+
+  priv->blink_tick = gtk_widget_add_tick_callback (GTK_WIDGET (self),
+                                                   blink_cb,
+                                                   data,
+                                                   g_free);
+}
+
+static void
+remove_blink_timeout (GtkTextView *self)
+{
+  GtkTextViewPrivate *priv = self->priv;
+
+  if (priv->blink_tick)
+    {
+      gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->blink_tick);
+      priv->blink_tick = 0;
+    }
+}
+
+static float
+blink_alpha (float phase)
+{
+  /* keep it simple, and split the blink cycle evenly
+   * into visible, fading out, invisible, fading in
+   */
+  if (phase < 0.25)
+    return 1;
+  else if (phase < 0.5)
+    return 1 - 4 * (phase - 0.25);
+  else if (phase < 0.75)
+    return 0;
+  else
+    return 4 * (phase - 0.75);
+}
+
+static gboolean
+blink_cb (GtkWidget     *widget,
+          GdkFrameClock *clock,
+          gpointer       user_data)
 {
   GtkTextView *text_view;
   GtkTextViewPrivate *priv;
-  gboolean visible;
   gint blink_timeout;
+  gint blink_time;
+  guint64 now;
+  float phase;
+  BlinkData *data = user_data;
 
-  text_view = GTK_TEXT_VIEW (data);
+  text_view = GTK_TEXT_VIEW (widget);
   priv = text_view->priv;
 
   if (!gtk_widget_has_focus (GTK_WIDGET (text_view)))
@@ -5677,7 +5740,6 @@ blink_cb (gpointer data)
       g_warning ("GtkTextView - did not receive a focus-out.\n"
                  "If you handle this event, you must return\n"
                  "GDK_EVENT_PROPAGATE so the text view gets the event as well");
-
       gtk_text_view_check_cursor_blink (text_view);
 
       return FALSE;
@@ -5686,47 +5748,41 @@ blink_cb (gpointer data)
   g_assert (priv->layout);
   g_assert (cursor_visible (text_view));
 
-  visible = gtk_text_layout_get_cursor_visible (priv->layout);
-
   blink_timeout = get_cursor_blink_timeout (text_view);
-  if (priv->blink_time > 1000 * blink_timeout &&
-      blink_timeout < G_MAXINT/1000) 
+  blink_time = get_cursor_time (text_view);
+
+  now = g_get_monotonic_time ();
+
+  if (now > priv->blink_start_time + blink_timeout * 1000000)
     {
       /* we've blinked enough without the user doing anything, stop blinking */
-      visible = 0;
-      priv->blink_timeout = 0;
-    } 
-  else if (visible)
-    {
-      priv->blink_timeout = g_timeout_add (get_cursor_time (text_view) * CURSOR_OFF_MULTIPLIER / 
CURSOR_DIVIDER,
-                                           blink_cb,
-                                           text_view);
-      g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb");
+      priv->cursor_alpha = 1.0;
+      remove_blink_timeout (text_view);
+      gtk_widget_queue_draw (widget);
+
+      return G_SOURCE_REMOVE;
     }
-  else 
+
+  phase = (now - data->start) / (float) (data->end - data->start);
+
+  priv->cursor_alpha = blink_alpha (phase);
+
+  if (now >= data->end)
     {
-      priv->blink_timeout = g_timeout_add (get_cursor_time (text_view) * CURSOR_ON_MULTIPLIER / 
CURSOR_DIVIDER,
-                                           blink_cb,
-                                           text_view);
-      g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb");
-      priv->blink_time += get_cursor_time (text_view);
+      data->start = data->end;
+      data->end = data->start + blink_time * 1000;
     }
 
-  gtk_text_layout_set_cursor_visible (priv->layout, !visible);
+  gtk_widget_queue_draw (widget);
 
-  /* Remove ourselves */
-  return FALSE;
+  return G_SOURCE_CONTINUE;
 }
 
 
 static void
 gtk_text_view_stop_cursor_blink (GtkTextView *text_view)
 {
-  if (text_view->priv->blink_timeout)
-    { 
-      g_source_remove (text_view->priv->blink_timeout);
-      text_view->priv->blink_timeout = 0;
-    }
+  remove_blink_timeout (text_view);
 }
 
 static void
@@ -5734,52 +5790,25 @@ gtk_text_view_check_cursor_blink (GtkTextView *text_view)
 {
   GtkTextViewPrivate *priv = text_view->priv;
 
-  if (priv->layout != NULL &&
-      cursor_visible (text_view) &&
-      gtk_widget_has_focus (GTK_WIDGET (text_view)))
+  if (cursor_blinks (text_view))
     {
-      if (cursor_blinks (text_view))
-       {
-         if (priv->blink_timeout == 0)
-           {
-             gtk_text_layout_set_cursor_visible (priv->layout, TRUE);
-             
-             priv->blink_timeout = g_timeout_add (get_cursor_time (text_view) * CURSOR_OFF_MULTIPLIER / 
CURSOR_DIVIDER,
-                                                   blink_cb,
-                                                   text_view);
-             g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb");
-           }
-       }
-      else
-       {
-         gtk_text_view_stop_cursor_blink (text_view);
-         gtk_text_layout_set_cursor_visible (priv->layout, TRUE);
-       }
+      if (!priv->blink_tick)
+        add_blink_timeout (text_view);
     }
   else
     {
-      gtk_text_view_stop_cursor_blink (text_view);
-      gtk_text_layout_set_cursor_visible (priv->layout, FALSE);
+      if (priv->blink_tick)
+        remove_blink_timeout (text_view);
     }
 }
 
 static void
 gtk_text_view_pend_cursor_blink (GtkTextView *text_view)
 {
-  GtkTextViewPrivate *priv = text_view->priv;
-
-  if (priv->layout != NULL &&
-      cursor_visible (text_view) &&
-      gtk_widget_has_focus (GTK_WIDGET (text_view)) &&
-      cursor_blinks (text_view))
+  if (cursor_blinks (text_view))
     {
-      gtk_text_view_stop_cursor_blink (text_view);
-      gtk_text_layout_set_cursor_visible (priv->layout, TRUE);
-      
-      priv->blink_timeout = g_timeout_add (get_cursor_time (text_view) * CURSOR_PEND_MULTIPLIER / 
CURSOR_DIVIDER,
-                                           blink_cb,
-                                           text_view);
-      g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb");
+      remove_blink_timeout (text_view);
+      add_blink_timeout (text_view);
     }
 }
 
@@ -5788,7 +5817,7 @@ gtk_text_view_reset_blink_time (GtkTextView *text_view)
 {
   GtkTextViewPrivate *priv = text_view->priv;
 
-  priv->blink_time = 0;
+  priv->blink_start_time = g_get_monotonic_time ();
 }
 
 


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