forked from AuroraMiddleware/gtk
text: Smooth cursor blinking
Fade the text cursor in and out, instead of abruptly turning it on and off.
This commit is contained in:
parent
63fd97749c
commit
51161fb0d6
176
gtk/gtktext.c
176
gtk/gtktext.c
@ -195,8 +195,9 @@ struct _GtkTextPrivate
|
|||||||
|
|
||||||
gunichar invisible_char;
|
gunichar invisible_char;
|
||||||
|
|
||||||
guint blink_time; /* time in msec the cursor has blinked since last user event */
|
guint64 blink_start_time;
|
||||||
guint blink_timeout;
|
guint blink_tick;
|
||||||
|
float cursor_alpha;
|
||||||
|
|
||||||
guint16 preedit_length; /* length of preedit string, in bytes */
|
guint16 preedit_length; /* length of preedit string, in bytes */
|
||||||
guint16 preedit_cursor; /* offset of cursor within preedit string, in chars */
|
guint16 preedit_cursor; /* offset of cursor within preedit string, in chars */
|
||||||
@ -212,7 +213,6 @@ struct _GtkTextPrivate
|
|||||||
guint activates_default : 1;
|
guint activates_default : 1;
|
||||||
guint cache_includes_preedit : 1;
|
guint cache_includes_preedit : 1;
|
||||||
guint change_count : 8;
|
guint change_count : 8;
|
||||||
guint cursor_visible : 1;
|
|
||||||
guint editing_canceled : 1; /* Only used by GtkCellRendererText */
|
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 in_click : 1; /* Flag so we don't select all when clicking in entry to focus in */
|
||||||
guint invisible_char_set : 1;
|
guint invisible_char_set : 1;
|
||||||
@ -1765,10 +1765,10 @@ gtk_text_destroy (GtkWidget *widget)
|
|||||||
gtk_text_reset_im_context (self);
|
gtk_text_reset_im_context (self);
|
||||||
gtk_text_reset_layout (self);
|
gtk_text_reset_layout (self);
|
||||||
|
|
||||||
if (priv->blink_timeout)
|
if (priv->blink_tick)
|
||||||
{
|
{
|
||||||
g_source_remove (priv->blink_timeout);
|
gtk_widget_remove_tick_callback (widget, priv->blink_tick);
|
||||||
priv->blink_timeout = 0;
|
priv->blink_tick = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (priv->magnifier)
|
if (priv->magnifier)
|
||||||
@ -1825,9 +1825,6 @@ gtk_text_finalize (GObject *object)
|
|||||||
|
|
||||||
g_clear_pointer (&priv->placeholder, gtk_widget_unparent);
|
g_clear_pointer (&priv->placeholder, gtk_widget_unparent);
|
||||||
|
|
||||||
if (priv->blink_timeout)
|
|
||||||
g_source_remove (priv->blink_timeout);
|
|
||||||
|
|
||||||
if (priv->tabs)
|
if (priv->tabs)
|
||||||
pango_tab_array_free (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 */
|
/* When no text is being displayed at all, don't show the cursor */
|
||||||
if (gtk_text_get_display_mode (self) != DISPLAY_BLANK &&
|
if (gtk_text_get_display_mode (self) != DISPLAY_BLANK &&
|
||||||
gtk_widget_has_focus (widget) &&
|
gtk_widget_has_focus (widget) &&
|
||||||
priv->selection_bound == priv->current_pos && priv->cursor_visible)
|
priv->selection_bound == priv->current_pos)
|
||||||
gtk_text_draw_cursor (self, snapshot, CURSOR_STANDARD);
|
{
|
||||||
|
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);
|
gtk_text_draw_undershoot (self, snapshot);
|
||||||
}
|
}
|
||||||
@ -6293,47 +6294,81 @@ get_cursor_blink_timeout (GtkText *self)
|
|||||||
return timeout;
|
return timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
guint64 start;
|
||||||
|
guint64 end;
|
||||||
|
} BlinkData;
|
||||||
|
|
||||||
|
static gboolean blink_cb (GtkWidget *widget,
|
||||||
|
GdkFrameClock *clock,
|
||||||
|
gpointer user_data);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
show_cursor (GtkText *self)
|
add_blink_timeout (GtkText *self)
|
||||||
{
|
{
|
||||||
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
|
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
|
||||||
GtkWidget *widget;
|
BlinkData *data;
|
||||||
|
int blink_time;
|
||||||
|
|
||||||
if (!priv->cursor_visible)
|
priv->blink_start_time = g_get_monotonic_time ();
|
||||||
{
|
priv->cursor_alpha = 1.0;
|
||||||
priv->cursor_visible = TRUE;
|
|
||||||
|
|
||||||
widget = GTK_WIDGET (self);
|
blink_time = get_cursor_time (self);
|
||||||
if (gtk_widget_has_focus (widget) && priv->selection_bound == priv->current_pos)
|
|
||||||
gtk_widget_queue_draw (widget);
|
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
|
static void
|
||||||
hide_cursor (GtkText *self)
|
remove_blink_timeout (GtkText *self)
|
||||||
{
|
{
|
||||||
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
|
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
|
||||||
GtkWidget *widget;
|
|
||||||
|
|
||||||
if (priv->cursor_visible)
|
if (priv->blink_tick)
|
||||||
{
|
{
|
||||||
priv->cursor_visible = FALSE;
|
gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->blink_tick);
|
||||||
|
priv->blink_tick = 0;
|
||||||
widget = GTK_WIDGET (self);
|
|
||||||
if (gtk_widget_has_focus (widget) && priv->selection_bound == priv->current_pos)
|
|
||||||
gtk_widget_queue_draw (widget);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Blink!
|
* Blink!
|
||||||
*/
|
*/
|
||||||
static int
|
|
||||||
blink_cb (gpointer data)
|
static float
|
||||||
|
blink_alpha (float phase)
|
||||||
{
|
{
|
||||||
GtkText *self = data;
|
/* 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 = GTK_TEXT (widget);
|
||||||
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
|
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
|
||||||
|
BlinkData *data = user_data;
|
||||||
int blink_timeout;
|
int blink_timeout;
|
||||||
|
int blink_time;
|
||||||
|
guint64 now;
|
||||||
|
float phase;
|
||||||
|
|
||||||
if (!gtk_widget_has_focus (GTK_WIDGET (self)))
|
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");
|
"GDK_EVENT_PROPAGATE so the self gets the event as well");
|
||||||
|
|
||||||
gtk_text_check_cursor_blink (self);
|
gtk_text_check_cursor_blink (self);
|
||||||
|
|
||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_assert (priv->selection_bound == priv->current_pos);
|
g_assert (priv->selection_bound == priv->current_pos);
|
||||||
|
|
||||||
blink_timeout = get_cursor_blink_timeout (self);
|
blink_timeout = get_cursor_blink_timeout (self);
|
||||||
if (priv->blink_time > 1000 * blink_timeout &&
|
blink_time = get_cursor_time (self);
|
||||||
blink_timeout < G_MAXINT/1000)
|
|
||||||
|
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 */
|
/* we've blinked enough without the user doing anything, stop blinking */
|
||||||
show_cursor (self);
|
priv->cursor_alpha = 1.0;
|
||||||
priv->blink_timeout = 0;
|
remove_blink_timeout (self);
|
||||||
}
|
gtk_widget_queue_draw (widget);
|
||||||
else if (priv->cursor_visible)
|
|
||||||
{
|
return G_SOURCE_REMOVE;
|
||||||
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");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return G_SOURCE_REMOVE;
|
phase = (now - data->start) / (float) (data->end - data->start);
|
||||||
|
|
||||||
|
priv->cursor_alpha = blink_alpha (phase);
|
||||||
|
|
||||||
|
if (now >= data->end)
|
||||||
|
{
|
||||||
|
data->start = data->end;
|
||||||
|
data->end = data->start + blink_time * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_widget_queue_draw (widget);
|
||||||
|
|
||||||
|
return G_SOURCE_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -6384,42 +6419,23 @@ gtk_text_check_cursor_blink (GtkText *self)
|
|||||||
|
|
||||||
if (cursor_blinks (self))
|
if (cursor_blinks (self))
|
||||||
{
|
{
|
||||||
if (!priv->blink_timeout)
|
if (!priv->blink_tick)
|
||||||
{
|
add_blink_timeout (self);
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (priv->blink_timeout)
|
if (priv->blink_tick)
|
||||||
{
|
remove_blink_timeout (self);
|
||||||
g_source_remove (priv->blink_timeout);
|
|
||||||
priv->blink_timeout = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
priv->cursor_visible = TRUE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
gtk_text_pend_cursor_blink (GtkText *self)
|
gtk_text_pend_cursor_blink (GtkText *self)
|
||||||
{
|
{
|
||||||
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
|
|
||||||
|
|
||||||
if (cursor_blinks (self))
|
if (cursor_blinks (self))
|
||||||
{
|
{
|
||||||
if (priv->blink_timeout != 0)
|
remove_blink_timeout (self);
|
||||||
g_source_remove (priv->blink_timeout);
|
add_blink_timeout (self);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6428,7 +6444,7 @@ gtk_text_reset_blink_time (GtkText *self)
|
|||||||
{
|
{
|
||||||
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
|
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
|
||||||
|
|
||||||
priv->blink_time = 0;
|
priv->blink_start_time = g_get_monotonic_time ();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user