From 7aad0896a73e6957c8d5ef65d59b2d0891df1b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Fri, 18 Jan 2019 10:47:16 +0100 Subject: [PATCH] entry: Use a label as placeholder This gives us a better way of choosing the color of the placeholder text (and enabled general css styling on it of course). Closes #378 (If you want to keep the placeholder on focused and empty entries, just don't set the placeholder opacity to 0 in entry:focus>placeholder. This is the default behavior but this commit includes a rule in Adwaita to hide it. --- gtk/gtkentry.c | 175 +++++++++++++++------------------ gtk/theme/Adwaita/_common.scss | 11 ++- 2 files changed, 90 insertions(+), 96 deletions(-) diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c index 3efa22992e..4096daa2cd 100644 --- a/gtk/gtkentry.c +++ b/gtk/gtkentry.c @@ -128,6 +128,7 @@ * * |[ * entry[.read-only][.flat][.warning][.error] + * ├── placeholder * ├── image.left * ├── image.right * ├── undershoot.left @@ -202,8 +203,6 @@ struct _GtkEntryPrivate gchar *im_module; - gchar *placeholder_text; - GtkTextHandle *text_handle; GtkWidget *selection_bubble; guint selection_bubble_timeout_id; @@ -211,6 +210,8 @@ struct _GtkEntryPrivate GtkWidget *magnifier_popover; GtkWidget *magnifier; + GtkWidget *placeholder; + GtkGesture *drag_gesture; GtkEventController *key_controller; @@ -2761,9 +2762,10 @@ gtk_entry_finalize (GObject *object) g_clear_pointer (&priv->magnifier_popover, gtk_widget_destroy); g_clear_pointer (&priv->progress_widget, gtk_widget_unparent); g_clear_object (&priv->text_handle); - g_free (priv->placeholder_text); g_free (priv->im_module); + g_clear_pointer (&priv->placeholder, gtk_widget_unparent); + if (priv->blink_timeout) g_source_remove (priv->blink_timeout); @@ -3037,7 +3039,7 @@ construct_icon_info (GtkWidget *widget, icon_info->widget = gtk_image_new (); gtk_widget_set_cursor_from_name (icon_info->widget, "default"); - gtk_widget_set_parent (icon_info->widget, widget); + gtk_widget_insert_after (icon_info->widget, widget, priv->placeholder); update_icon_style (widget, icon_pos); update_node_ordering (entry); @@ -3170,6 +3172,16 @@ gtk_entry_measure (GtkWidget *widget, min = MAX (min, icon_width); nat = MAX (min, nat); + if (priv->placeholder) + { + int pmin, pnat; + + gtk_widget_measure (priv->placeholder, GTK_ORIENTATION_HORIZONTAL, -1, + &pmin, &pnat, NULL, NULL); + min = MAX (min, pmin); + nat = MAX (nat, pnat); + } + *minimum = min; *natural = nat; } @@ -3213,6 +3225,16 @@ gtk_entry_measure (GtkWidget *widget, if (icon_height > height) baseline += (icon_height - height) / 2; + if (priv->placeholder) + { + int min, nat; + + gtk_widget_measure (priv->placeholder, GTK_ORIENTATION_VERTICAL, -1, + &min, &nat, NULL, NULL); + *minimum = MAX (*minimum, min); + *natural = MAX (*natural, nat); + } + if (minimum_baseline) *minimum_baseline = baseline; if (natural_baseline) @@ -3301,6 +3323,15 @@ gtk_entry_size_allocate (GtkWidget *widget, gtk_widget_size_allocate (priv->progress_widget, &progress_alloc, -1); } + if (priv->placeholder) + { + gtk_widget_size_allocate (priv->placeholder, + &(GtkAllocation) { + priv->text_x, 0, + priv->text_width, height + }, -1); + } + /* Do this here instead of gtk_entry_size_allocate() so it works * inside spinbuttons, which don't chain up. */ @@ -3394,6 +3425,9 @@ gtk_entry_snapshot (GtkWidget *widget, if (priv->dnd_position != -1) gtk_entry_draw_cursor (GTK_ENTRY (widget), snapshot, CURSOR_DND); + if (priv->placeholder) + gtk_widget_snapshot_child (widget, priv->placeholder, snapshot); + gtk_entry_draw_text (GTK_ENTRY (widget), snapshot); /* When no text is being displayed at all, don't show the cursor */ @@ -4083,16 +4117,8 @@ gtk_entry_focus_in (GtkWidget *widget) g_signal_connect (keymap, "direction-changed", G_CALLBACK (keymap_direction_changed), entry); - if (gtk_entry_buffer_get_bytes (get_buffer (entry)) == 0 && - priv->placeholder_text != NULL) - { - gtk_entry_recompute (entry); - } - else - { - gtk_entry_reset_blink_time (entry); - gtk_entry_check_cursor_blink (entry); - } + gtk_entry_reset_blink_time (entry); + gtk_entry_check_cursor_blink (entry); } static void @@ -4120,15 +4146,7 @@ gtk_entry_focus_out (GtkWidget *widget) remove_capslock_feedback (entry); } - if (gtk_entry_buffer_get_bytes (get_buffer (entry)) == 0 && - priv->placeholder_text != NULL) - { - gtk_entry_recompute (entry); - } - else - { - gtk_entry_check_cursor_blink (entry); - } + gtk_entry_check_cursor_blink (entry); g_signal_handlers_disconnect_by_func (keymap, keymap_state_changed, entry); g_signal_handlers_disconnect_by_func (keymap, keymap_direction_changed, entry); @@ -4449,6 +4467,16 @@ gtk_entry_remove_password_hint (gpointer data) return G_SOURCE_REMOVE; } +static void +update_placeholder_visibility (GtkEntry *entry) +{ + GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); + + if (priv->placeholder) + gtk_widget_set_child_visible (priv->placeholder, + gtk_entry_buffer_get_length (priv->buffer) == 0); +} + /* Default signal handlers */ static void @@ -4477,6 +4505,8 @@ gtk_entry_real_insert_text (GtkEditable *editable, gtk_widget_error_bell (GTK_WIDGET (editable)); *position += n_inserted; + + update_placeholder_visibility (GTK_ENTRY (editable)); } static void @@ -4495,6 +4525,7 @@ gtk_entry_real_delete_text (GtkEditable *editable, gtk_entry_buffer_delete_text (get_buffer (GTK_ENTRY (editable)), start_pos, end_pos - start_pos); end_change (GTK_ENTRY (editable)); + update_placeholder_visibility (GTK_ENTRY (editable)); } /* GtkEntryBuffer signal handlers @@ -5396,35 +5427,6 @@ gtk_entry_recompute (GtkEntry *entry) gtk_widget_queue_draw (GTK_WIDGET (entry)); } -static void -gtk_entry_get_placeholder_text_color (GtkEntry *entry, - PangoColor *color) -{ - GtkWidget *widget = GTK_WIDGET (entry); - GtkStyleContext *context; - GdkRGBA fg = { 0.5, 0.5, 0.5 }; - - context = gtk_widget_get_style_context (widget); - gtk_style_context_lookup_color (context, "placeholder_text_color", &fg); - - color->red = CLAMP (fg.red * 65535. + 0.5, 0, 65535); - color->green = CLAMP (fg.green * 65535. + 0.5, 0, 65535); - color->blue = CLAMP (fg.blue * 65535. + 0.5, 0, 65535); -} - -static inline gboolean -show_placeholder_text (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (!gtk_widget_has_focus (GTK_WIDGET (entry)) && - gtk_entry_buffer_get_bytes (get_buffer (entry)) == 0 && - priv->placeholder_text != NULL) - return TRUE; - - return FALSE; -} - static PangoLayout * gtk_entry_create_layout (GtkEntry *entry, gboolean include_preedit) @@ -5434,7 +5436,6 @@ gtk_entry_create_layout (GtkEntry *entry, GtkStyleContext *context; PangoLayout *layout; PangoAttrList *tmp_attrs; - gboolean placeholder_layout; gchar *preedit_string = NULL; gint preedit_length = 0; @@ -5453,32 +5454,16 @@ gtk_entry_create_layout (GtkEntry *entry, if (!tmp_attrs) tmp_attrs = pango_attr_list_new (); - placeholder_layout = show_placeholder_text (entry); - if (placeholder_layout) - display_text = g_strdup (priv->placeholder_text); - else - display_text = _gtk_entry_get_display_text (entry, 0, -1); + display_text = _gtk_entry_get_display_text (entry, 0, -1); n_bytes = strlen (display_text); - if (!placeholder_layout && include_preedit) + if (include_preedit) { gtk_im_context_get_preedit_string (priv->im_context, - &preedit_string, &preedit_attrs, NULL); + &preedit_string, &preedit_attrs, NULL); preedit_length = priv->preedit_length; } - else if (placeholder_layout) - { - PangoColor color; - PangoAttribute *attr; - - gtk_entry_get_placeholder_text_color (entry, &color); - attr = pango_attr_foreground_new (color.red, color.green, color.blue); - attr->start_index = 0; - attr->end_index = G_MAXINT; - pango_attr_list_insert (tmp_attrs, attr); - pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END); - } if (preedit_length) { @@ -5632,9 +5617,6 @@ gtk_entry_draw_text (GtkEntry *entry, gtk_entry_get_layout_offsets (entry, &x, &y); - if (show_placeholder_text (entry)) - pango_layout_set_width (layout, PANGO_SCALE * priv->text_width); - gtk_snapshot_render_layout (snapshot, context, x, y, layout); if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start_pos, &end_pos)) @@ -8584,9 +8566,6 @@ gtk_entry_drag_motion (GtkWidget *widget, priv->dnd_position = -1; } - if (show_placeholder_text (entry)) - priv->dnd_position = -1; - gdk_drop_status (drop, suggested_action); if (suggested_action == 0) gtk_drag_unhighlight (widget); @@ -9147,15 +9126,9 @@ gtk_entry_progress_pulse (GtkEntry *entry) * @entry: a #GtkEntry * @text: (nullable): a string to be displayed when @entry is empty and unfocused, or %NULL * - * Sets text to be displayed in @entry when it is empty and unfocused. + * Sets text to be displayed in @entry when it is empty. * This can be used to give a visual hint of the expected contents of * the #GtkEntry. - * - * Note that since the placeholder text gets removed when the entry - * received focus, using this feature is a bit problematic if the entry - * is given the initial focus in a window. Sometimes this can be - * worked around by delaying the initial focus setting until the - * first key event arrives. **/ void gtk_entry_set_placeholder_text (GtkEntry *entry, @@ -9165,13 +9138,20 @@ gtk_entry_set_placeholder_text (GtkEntry *entry, g_return_if_fail (GTK_IS_ENTRY (entry)); - if (g_strcmp0 (priv->placeholder_text, text) == 0) - return; - - g_free (priv->placeholder_text); - priv->placeholder_text = g_strdup (text); - - gtk_entry_recompute (entry); + if (priv->placeholder == NULL) + { + priv->placeholder = g_object_new (GTK_TYPE_LABEL, + "label", text, + "css-name", "placeholder", + "xalign", 0.0f, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + gtk_widget_insert_after (priv->placeholder, GTK_WIDGET (entry), NULL); + } + else + { + gtk_label_set_text (GTK_LABEL (priv->placeholder), text); + } g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_PLACEHOLDER_TEXT]); } @@ -9182,8 +9162,10 @@ gtk_entry_set_placeholder_text (GtkEntry *entry, * * Retrieves the text that will be displayed when @entry is empty and unfocused * - * Returns: a pointer to the placeholder text as a string. This string points to internally allocated - * storage in the widget and must not be freed, modified or stored. + * Returns: (nullable) (transfer none):a pointer to the placeholder text as a string. + * This string points to internally allocated storage in the widget and must + * not be freed, modified or stored. If no placeholder text has been set, + * %NULL will be returned. **/ const gchar * gtk_entry_get_placeholder_text (GtkEntry *entry) @@ -9192,7 +9174,10 @@ gtk_entry_get_placeholder_text (GtkEntry *entry) g_return_val_if_fail (GTK_IS_ENTRY (entry), NULL); - return priv->placeholder_text; + if (!priv->placeholder) + return NULL; + + return gtk_label_get_text (GTK_LABEL (priv->placeholder)); } /* Caps Lock warning for password entries */ diff --git a/gtk/theme/Adwaita/_common.scss b/gtk/theme/Adwaita/_common.scss index d766a9e817..0ec41d5a10 100644 --- a/gtk/theme/Adwaita/_common.scss +++ b/gtk/theme/Adwaita/_common.scss @@ -279,6 +279,10 @@ entry { &.right { margin-left: 6px; } } + placeholder { + @extend .dim-label; + } + undershoot { &.left { @include undershoot(left); } &.right { @include undershoot(right); } @@ -294,7 +298,12 @@ entry { } } - &:focus { @include entry(focus); } + &:focus { + @include entry(focus); + > placeholder { + opacity: 0; /* We hide placeholders on focus */ + } + } &:disabled { @include entry(insensitive); }