[gtk] text: Smooth cursor blinking
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk] text: Smooth cursor blinking
- Date: Sun, 21 Jul 2019 18:35:42 +0000 (UTC)
commit 51161fb0d6f7fd2036c8c31ec522d2c6e571e549
Author: Matthias Clasen <mclasen redhat com>
Date: Sat Jul 20 18:12:05 2019 -0700
text: Smooth cursor blinking
Fade the text cursor in and out, instead
of abruptly turning it on and off.
gtk/gtktext.c | 176 ++++++++++++++++++++++++++++++++--------------------------
1 file changed, 96 insertions(+), 80 deletions(-)
---
diff --git a/gtk/gtktext.c b/gtk/gtktext.c
index 832b1e2139..af9cf73e1d 100644
--- a/gtk/gtktext.c
+++ b/gtk/gtktext.c
@@ -195,8 +195,9 @@ struct _GtkTextPrivate
gunichar invisible_char;
- guint blink_time; /* time in msec the cursor has blinked since last user event */
- guint blink_timeout;
+ guint64 blink_start_time;
+ guint blink_tick;
+ float cursor_alpha;
guint16 preedit_length; /* length of preedit string, in bytes */
guint16 preedit_cursor; /* offset of cursor within preedit string, in chars */
@@ -212,7 +213,6 @@ struct _GtkTextPrivate
guint activates_default : 1;
guint cache_includes_preedit : 1;
guint change_count : 8;
- guint cursor_visible : 1;
guint editing_canceled : 1; /* Only used by GtkCellRendererText */
guint in_click : 1; /* Flag so we don't select all when clicking in entry to focus
in */
guint invisible_char_set : 1;
@@ -1765,10 +1765,10 @@ gtk_text_destroy (GtkWidget *widget)
gtk_text_reset_im_context (self);
gtk_text_reset_layout (self);
- if (priv->blink_timeout)
+ if (priv->blink_tick)
{
- g_source_remove (priv->blink_timeout);
- priv->blink_timeout = 0;
+ gtk_widget_remove_tick_callback (widget, priv->blink_tick);
+ priv->blink_tick = 0;
}
if (priv->magnifier)
@@ -1825,9 +1825,6 @@ gtk_text_finalize (GObject *object)
g_clear_pointer (&priv->placeholder, gtk_widget_unparent);
- if (priv->blink_timeout)
- g_source_remove (priv->blink_timeout);
-
if (priv->tabs)
pango_tab_array_free (priv->tabs);
@@ -2267,8 +2264,12 @@ gtk_text_snapshot (GtkWidget *widget,
/* When no text is being displayed at all, don't show the cursor */
if (gtk_text_get_display_mode (self) != DISPLAY_BLANK &&
gtk_widget_has_focus (widget) &&
- priv->selection_bound == priv->current_pos && priv->cursor_visible)
- gtk_text_draw_cursor (self, snapshot, CURSOR_STANDARD);
+ priv->selection_bound == priv->current_pos)
+ {
+ gtk_snapshot_push_opacity (snapshot, priv->cursor_alpha);
+ gtk_text_draw_cursor (self, snapshot, CURSOR_STANDARD);
+ gtk_snapshot_pop (snapshot);
+ }
gtk_text_draw_undershoot (self, snapshot);
}
@@ -6293,47 +6294,81 @@ get_cursor_blink_timeout (GtkText *self)
return timeout;
}
+typedef struct {
+ guint64 start;
+ guint64 end;
+} BlinkData;
+
+static gboolean blink_cb (GtkWidget *widget,
+ GdkFrameClock *clock,
+ gpointer user_data);
+
static void
-show_cursor (GtkText *self)
+add_blink_timeout (GtkText *self)
{
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
- GtkWidget *widget;
+ BlinkData *data;
+ int blink_time;
- if (!priv->cursor_visible)
- {
- priv->cursor_visible = TRUE;
+ priv->blink_start_time = g_get_monotonic_time ();
+ priv->cursor_alpha = 1.0;
- widget = GTK_WIDGET (self);
- if (gtk_widget_has_focus (widget) && priv->selection_bound == priv->current_pos)
- gtk_widget_queue_draw (widget);
- }
+ 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
-hide_cursor (GtkText *self)
+remove_blink_timeout (GtkText *self)
{
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
- GtkWidget *widget;
- if (priv->cursor_visible)
+ if (priv->blink_tick)
{
- priv->cursor_visible = FALSE;
-
- widget = GTK_WIDGET (self);
- if (gtk_widget_has_focus (widget) && priv->selection_bound == priv->current_pos)
- gtk_widget_queue_draw (widget);
+ gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->blink_tick);
+ priv->blink_tick = 0;
}
}
/*
* Blink!
*/
-static int
-blink_cb (gpointer data)
+
+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)
{
- GtkText *self = data;
+ GtkText *self = GTK_TEXT (widget);
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
+ BlinkData *data = user_data;
int blink_timeout;
+ int blink_time;
+ guint64 now;
+ float phase;
if (!gtk_widget_has_focus (GTK_WIDGET (self)))
{
@@ -6342,39 +6377,39 @@ blink_cb (gpointer data)
"GDK_EVENT_PROPAGATE so the self gets the event as well");
gtk_text_check_cursor_blink (self);
-
return G_SOURCE_REMOVE;
}
-
+
g_assert (priv->selection_bound == priv->current_pos);
-
+
blink_timeout = get_cursor_blink_timeout (self);
- if (priv->blink_time > 1000 * blink_timeout &&
- blink_timeout < G_MAXINT/1000)
+ blink_time = get_cursor_time (self);
+
+ 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 */
- show_cursor (self);
- priv->blink_timeout = 0;
- }
- else if (priv->cursor_visible)
- {
- hide_cursor (self);
- priv->blink_timeout = g_timeout_add (get_cursor_time (self) * CURSOR_OFF_MULTIPLIER / CURSOR_DIVIDER,
- blink_cb,
- self);
- g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb");
+ priv->cursor_alpha = 1.0;
+ remove_blink_timeout (self);
+ 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)
{
- show_cursor (self);
- priv->blink_time += get_cursor_time (self);
- priv->blink_timeout = g_timeout_add (get_cursor_time (self) * CURSOR_ON_MULTIPLIER / CURSOR_DIVIDER,
- blink_cb,
- self);
- g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb");
+ data->start = data->end;
+ data->end = data->start + blink_time * 1000;
}
- return G_SOURCE_REMOVE;
+ gtk_widget_queue_draw (widget);
+
+ return G_SOURCE_CONTINUE;
}
static void
@@ -6384,42 +6419,23 @@ gtk_text_check_cursor_blink (GtkText *self)
if (cursor_blinks (self))
{
- if (!priv->blink_timeout)
- {
- show_cursor (self);
- priv->blink_timeout = g_timeout_add (get_cursor_time (self) * CURSOR_ON_MULTIPLIER /
CURSOR_DIVIDER,
- blink_cb,
- self);
- g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb");
- }
+ if (!priv->blink_tick)
+ add_blink_timeout (self);
}
else
{
- if (priv->blink_timeout)
- {
- g_source_remove (priv->blink_timeout);
- priv->blink_timeout = 0;
- }
-
- priv->cursor_visible = TRUE;
+ if (priv->blink_tick)
+ remove_blink_timeout (self);
}
}
static void
gtk_text_pend_cursor_blink (GtkText *self)
{
- GtkTextPrivate *priv = gtk_text_get_instance_private (self);
-
if (cursor_blinks (self))
{
- if (priv->blink_timeout != 0)
- g_source_remove (priv->blink_timeout);
-
- priv->blink_timeout = g_timeout_add (get_cursor_time (self) * CURSOR_PEND_MULTIPLIER / CURSOR_DIVIDER,
- blink_cb,
- self);
- g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb");
- show_cursor (self);
+ remove_blink_timeout (self);
+ add_blink_timeout (self);
}
}
@@ -6428,7 +6444,7 @@ gtk_text_reset_blink_time (GtkText *self)
{
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
- 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]