diff options
author | Matthias Clasen <mclasen@redhat.com> | 2017-08-19 14:06:47 -0400 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2018-06-03 09:43:28 -0400 |
commit | 65bb238a3f0075d88f6dd82e76de1e86946db001 (patch) | |
tree | ef58e768708f80a7457f29652e1b2cbe22969054 | |
parent | 7e9ae85dd4f2ce9b3402b6aac60e424c70194dbc (diff) | |
download | gtk+-65bb238a3f0075d88f6dd82e76de1e86946db001.tar.gz |
Add an emoji completion popup
This widget provides entry completion-like functionality
for Emoji codes like :grin: or :kiss:.
-rw-r--r-- | gtk/Makefile.am | 2 | ||||
-rw-r--r-- | gtk/gtkemojicompletion.c | 699 | ||||
-rw-r--r-- | gtk/gtkemojicompletion.h | 41 | ||||
-rw-r--r-- | gtk/theme/Adwaita/_common.scss | 16 | ||||
-rw-r--r-- | gtk/ui/gtkemojicompletion.ui | 17 | ||||
-rw-r--r-- | po/POTFILES.in | 2 |
6 files changed, 776 insertions, 1 deletions
diff --git a/gtk/Makefile.am b/gtk/Makefile.am index a903078efa..54a2ca4908 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -469,6 +469,7 @@ gtk_private_h_sources = \ gtkdialogprivate.h \ gtkdndprivate.h \ gtkemojichooser.h \ + gtkemojicompletion.h \ gtkentryprivate.h \ gtkeventcontrollerprivate.h \ gtkfilechooserembed.h \ @@ -747,6 +748,7 @@ gtk_base_c_sources = \ gtkdrawingarea.c \ gtkeditable.c \ gtkemojichooser.c \ + gtkemojicompletion.c \ gtkentry.c \ gtkentrybuffer.c \ gtkentrycompletion.c \ diff --git a/gtk/gtkemojicompletion.c b/gtk/gtkemojicompletion.c new file mode 100644 index 0000000000..664629be49 --- /dev/null +++ b/gtk/gtkemojicompletion.c @@ -0,0 +1,699 @@ +/* gtkemojicompletion.c: An Emoji picker widget + * Copyright 2017, Red Hat, Inc. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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/>. + */ + +#include "config.h" + +#include "gtkemojicompletion.h" + +#include "gtkentryprivate.h" +#include "gtkbox.h" +#include "gtkcssprovider.h" +#include "gtklistbox.h" +#include "gtklabel.h" +#include "gtkpopover.h" +#include "gtkintl.h" +#include "gtkprivate.h" +#include "gtkgesturelongpress.h" +#include "gtkflowbox.h" +#include "gtkstack.h" + +struct _GtkEmojiCompletion +{ + GtkPopover parent_instance; + + GtkEntry *entry; + char *text; + guint length; + guint offset; + gulong changed_id; + guint n_matches; + + GtkWidget *list; + GtkWidget *active; + GtkWidget *active_variation; + + GVariant *data; + + GtkGesture *long_press; +}; + +struct _GtkEmojiCompletionClass { + GtkPopoverClass parent_class; +}; + +static void connect_signals (GtkEmojiCompletion *completion, + GtkEntry *entry); +static void disconnect_signals (GtkEmojiCompletion *completion); +static int populate_completion (GtkEmojiCompletion *completion, + const char *text, + guint offset); + +#define MAX_ROWS 5 + +G_DEFINE_TYPE (GtkEmojiCompletion, gtk_emoji_completion, GTK_TYPE_POPOVER) + +static void +gtk_emoji_completion_finalize (GObject *object) +{ + GtkEmojiCompletion *completion = GTK_EMOJI_COMPLETION (object); + + disconnect_signals (completion); + + g_free (completion->text); + g_variant_unref (completion->data); + + g_clear_object (&completion->long_press); + + G_OBJECT_CLASS (gtk_emoji_completion_parent_class)->finalize (object); +} + +static void +update_completion (GtkEmojiCompletion *completion) +{ + const char *text; + guint length; + guint n_matches; + + n_matches = 0; + + text = gtk_entry_get_text (GTK_ENTRY (completion->entry)); + length = strlen (text); + + if (length > 0) + { + gboolean found_candidate = FALSE; + const char *p; + + p = text + length; + do + { +next: + p = g_utf8_prev_char (p); + if (*p == ':') + { + if (p + 1 == text + length) + goto next; + + if (p == text || !g_unichar_isalnum (g_utf8_get_char (p - 1))) + { + found_candidate = TRUE; + } + + break; + } + } + while (g_unichar_isalnum (g_utf8_get_char (p)) || *p == '_'); + + if (found_candidate) + n_matches = populate_completion (completion, p, 0); + } + + if (n_matches > 0) + gtk_popover_popup (GTK_POPOVER (completion)); + else + gtk_popover_popdown (GTK_POPOVER (completion)); +} + +static void +entry_changed (GtkEntry *entry, GtkEmojiCompletion *completion) +{ + update_completion (completion); +} + +static void +emoji_activated (GtkWidget *row, + GtkEmojiCompletion *completion) +{ + const char *emoji; + guint length; + + gtk_popover_popdown (GTK_POPOVER (completion)); + + emoji = (const char *)g_object_get_data (G_OBJECT (row), "text"); + + g_signal_handler_block (completion->entry, completion->changed_id); + + length = g_utf8_strlen (gtk_entry_get_text (completion->entry), -1); + gtk_entry_set_positions (completion->entry, length - completion->length, length); + gtk_entry_enter_text (completion->entry, emoji); + + g_signal_handler_unblock (completion->entry, completion->changed_id); +} + +static void +row_activated (GtkListBox *list, + GtkListBoxRow *row, + gpointer data) +{ + GtkEmojiCompletion *completion = data; + + emoji_activated (GTK_WIDGET (row), completion); +} + +static void +child_activated (GtkFlowBox *box, + GtkFlowBoxChild *child, + gpointer data) +{ + GtkEmojiCompletion *completion = data; + + emoji_activated (GTK_WIDGET (child), completion); +} + +static void +move_active_row (GtkEmojiCompletion *completion, + int direction) +{ + GtkWidget *child; + GtkWidget *base; + GList *children, *l, *active, *last; + + active = NULL; + last = NULL; + children = gtk_container_get_children (GTK_CONTAINER (completion->list)); + for (l = children; l; l = l->next) + { + child = l->data; + + if (completion->active == child) + active = l; + + if (l->next == NULL) + last = l; + + gtk_widget_unset_state_flags (child, GTK_STATE_FLAG_PRELIGHT); + base = GTK_WIDGET (g_object_get_data (G_OBJECT (child), "base")); + gtk_widget_unset_state_flags (base, GTK_STATE_FLAG_PRELIGHT); + } + + if (completion->active != NULL) + { + if (direction == 1) + completion->active = (active && active->next) ? active->next->data : NULL; + else + completion->active = (active && active->prev) ? active->prev->data : NULL; + } + + if (completion->active == NULL) + { + if (direction == 1) + completion->active = children->data; + else + completion->active = last->data; + } + + if (completion->active != NULL) + gtk_widget_set_state_flags (completion->active, GTK_STATE_FLAG_PRELIGHT, FALSE); + + if (completion->active_variation) + { + gtk_widget_unset_state_flags (completion->active_variation, GTK_STATE_FLAG_PRELIGHT); + completion->active_variation = NULL; + } + + g_list_free (children); +} + +static void +activate_active_row (GtkEmojiCompletion *completion) +{ + if (GTK_IS_FLOW_BOX_CHILD (completion->active_variation)) + emoji_activated (completion->active_variation, completion); + else if (completion->active != NULL) + emoji_activated (completion->active, completion); +} + +static void +show_variations (GtkEmojiCompletion *completion, + GtkWidget *row, + gboolean visible) +{ + GtkWidget *stack; + GtkWidget *box; + gboolean is_visible; + + if (!row) + return; + + stack = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "stack")); + box = gtk_stack_get_child_by_name (GTK_STACK (stack), "variations"); + if (!box) + return; + + is_visible = gtk_stack_get_visible_child (GTK_STACK (stack)) == box; + if (is_visible == visible) + return; + + if (visible) + gtk_widget_unset_state_flags (row, GTK_STATE_FLAG_PRELIGHT); + else + gtk_widget_set_state_flags (row, GTK_STATE_FLAG_PRELIGHT, FALSE); + + gtk_stack_set_visible_child_name (GTK_STACK (stack), visible ? "variations" : "text"); + if (completion->active_variation) + { + gtk_widget_unset_state_flags (completion->active_variation, GTK_STATE_FLAG_PRELIGHT); + completion->active_variation = NULL; + } +} + +static gboolean +move_active_variation (GtkEmojiCompletion *completion, + int direction) +{ + GtkWidget *base; + GtkWidget *stack; + GtkWidget *box; + GtkWidget *next; + GList *children, *l, *active; + + if (!completion->active) + return FALSE; + + base = GTK_WIDGET (g_object_get_data (G_OBJECT (completion->active), "base")); + stack = GTK_WIDGET (g_object_get_data (G_OBJECT (completion->active), "stack")); + box = gtk_stack_get_child_by_name (GTK_STACK (stack), "variations"); + + if (gtk_stack_get_visible_child (GTK_STACK (stack)) != box) + return FALSE; + + next = NULL; + + active = NULL; + children = gtk_container_get_children (GTK_CONTAINER (box)); + for (l = children; l; l = l->next) + { + if (l->data == completion->active_variation) + active = l; + } + + if (!completion->active_variation) + next = base; + else if (completion->active_variation == base && direction == 1) + next = children->data; + else if (completion->active_variation == children->data && direction == -1) + next = base; + else if (direction == 1) + next = (active && active->next) ? active->next->data : NULL; + else if (direction == -1) + next = (active && active->prev) ? active->prev->data : NULL; + + if (next) + { + if (completion->active_variation) + gtk_widget_unset_state_flags (completion->active_variation, GTK_STATE_FLAG_PRELIGHT); + completion->active_variation = next; + gtk_widget_set_state_flags (completion->active_variation, GTK_STATE_FLAG_PRELIGHT, FALSE); + } + + g_list_free (children); + + return next != NULL; +} + +static gboolean +entry_key_press (GtkEntry *entry, + GdkEventKey *event, + GtkEmojiCompletion *completion) +{ + guint keyval; + + if (!gtk_widget_get_visible (GTK_WIDGET (completion))) + return FALSE; + + gdk_event_get_keyval ((GdkEvent*)event, &keyval); + + if (keyval == GDK_KEY_Escape) + { + gtk_popover_popdown (GTK_POPOVER (completion)); + return TRUE; + } + + if (keyval == GDK_KEY_Tab) + { + show_variations (completion, completion->active, FALSE); + + guint offset = completion->offset + MAX_ROWS; + if (offset >= completion->n_matches) + offset = 0; + populate_completion (completion, completion->text, offset); + return TRUE; + } + + if (keyval == GDK_KEY_Up) + { + show_variations (completion, completion->active, FALSE); + + move_active_row (completion, -1); + return TRUE; + } + + if (keyval == GDK_KEY_Down) + { + show_variations (completion, completion->active, FALSE); + + move_active_row (completion, 1); + return TRUE; + } + + if (keyval == GDK_KEY_Return || + keyval == GDK_KEY_KP_Enter || + keyval == GDK_KEY_ISO_Enter) + { + activate_active_row (completion); + return TRUE; + } + + if (keyval == GDK_KEY_Right) + { + show_variations (completion, completion->active, TRUE); + move_active_variation (completion, 1); + return TRUE; + } + + if (keyval == GDK_KEY_Left) + { + if (!move_active_variation (completion, -1)) + show_variations (completion, completion->active, FALSE); + return TRUE; + } + + return FALSE; +} + +static gboolean +entry_focus_out (GtkWidget *entry, + GParamSpec *pspec, + GtkEmojiCompletion *completion) +{ + if (!gtk_widget_has_focus (entry)) + gtk_popover_popdown (GTK_POPOVER (completion)); + return FALSE; +} + +static void +connect_signals (GtkEmojiCompletion *completion, + GtkEntry *entry) +{ + completion->entry = entry; + + completion->changed_id = g_signal_connect (entry, "changed", G_CALLBACK (entry_changed), completion); + g_signal_connect (entry, "key-press-event", G_CALLBACK (entry_key_press), completion); + g_signal_connect (entry, "notify::has-focus", G_CALLBACK (entry_focus_out), completion); +} + +static void +disconnect_signals (GtkEmojiCompletion *completion) +{ + g_signal_handlers_disconnect_by_func (completion->entry, entry_changed, completion); + g_signal_handlers_disconnect_by_func (completion->entry, entry_key_press, completion); + g_signal_handlers_disconnect_by_func (completion->entry, entry_focus_out, completion); + + completion->entry = NULL; +} + +static gboolean +has_variations (GVariant *emoji_data) +{ + GVariant *codes; + int i; + gboolean has_variations; + + has_variations = FALSE; + codes = g_variant_get_child_value (emoji_data, 0); + for (i = 0; i < g_variant_n_children (codes); i++) + { + gunichar code; + g_variant_get_child (codes, i, "u", &code); + if (code == 0) + { + has_variations = TRUE; + break; + } + } + g_variant_unref (codes); + + return has_variations; +} + +static void +get_text (GVariant *emoji_data, + gunichar modifier, + char *text, + gsize length) +{ + GVariant *codes; + int i; + char *p; + + p = text; + codes = g_variant_get_child_value (emoji_data, 0); + for (i = 0; i < g_variant_n_children (codes); i++) + { + gunichar code; + + g_variant_get_child (codes, i, "u", &code); + if (code == 0) + code = modifier; + if (code != 0) + p += g_unichar_to_utf8 (code, p); + } + g_variant_unref (codes); + p[0] = 0; +} + +static void +add_emoji_variation (GtkWidget *box, + GVariant *emoji_data, + gunichar modifier) +{ + GtkWidget *child; + GtkWidget *label; + PangoAttrList *attrs; + char text[64]; + + get_text (emoji_data, modifier, text, 64); + + label = gtk_label_new (text); + gtk_widget_show (label); + attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE)); + gtk_label_set_attributes (GTK_LABEL (label), attrs); + pango_attr_list_unref (attrs); + + child = gtk_flow_box_child_new (); + gtk_widget_show (child); + gtk_style_context_add_class (gtk_widget_get_style_context (child), "emoji"); + g_object_set_data_full (G_OBJECT (child), "text", g_strdup (text), g_free); + g_object_set_data_full (G_OBJECT (child), "emoji-data", + g_variant_ref (emoji_data), + (GDestroyNotify)g_variant_unref); + if (modifier != 0) + g_object_set_data (G_OBJECT (child), "modifier", GUINT_TO_POINTER (modifier)); + + gtk_container_add (GTK_CONTAINER (child), label); + gtk_flow_box_insert (GTK_FLOW_BOX (box), child, -1); +} + +static void +add_emoji (GtkWidget *list, + GVariant *emoji_data, + GtkEmojiCompletion *completion) +{ + GtkWidget *child; + GtkWidget *label; + GtkWidget *box; + PangoAttrList *attrs; + char text[64]; + const char *shortname; + GtkWidget *stack; + gunichar modifier; + + get_text (emoji_data, 0, text, 64); + + label = gtk_label_new (text); + gtk_widget_show (label); + attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE)); + gtk_label_set_attributes (GTK_LABEL (label), attrs); + pango_attr_list_unref (attrs); + gtk_style_context_add_class (gtk_widget_get_style_context (label), "emoji"); + + child = gtk_list_box_row_new (); + gtk_widget_show (child); + gtk_widget_set_focus_on_click (child, FALSE); + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_show (box); + gtk_container_add (GTK_CONTAINER (child), box); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + g_object_set_data (G_OBJECT (child), "base", label); + + stack = gtk_stack_new (); + gtk_widget_show (stack); + gtk_stack_set_homogeneous (GTK_STACK (stack), TRUE); + gtk_stack_set_transition_type (GTK_STACK (stack), GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT); + gtk_box_pack_start (GTK_BOX (box), stack, FALSE, FALSE, 0); + g_object_set_data (G_OBJECT (child), "stack", stack); + + g_variant_get_child (emoji_data, 2, "&s", &shortname); + label = gtk_label_new (shortname); + gtk_widget_show (label); + gtk_label_set_xalign (GTK_LABEL (label), 0); + + gtk_stack_add_named (GTK_STACK (stack), label, "text"); + + if (has_variations (emoji_data)) + { + box = gtk_flow_box_new (); + gtk_widget_show (box); + gtk_flow_box_set_homogeneous (GTK_FLOW_BOX (box), TRUE); + gtk_flow_box_set_min_children_per_line (GTK_FLOW_BOX (box), 5); + gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (box), 5); + gtk_flow_box_set_activate_on_single_click (GTK_FLOW_BOX (box), TRUE); + gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (box), GTK_SELECTION_NONE); + g_signal_connect (box, "child-activated", G_CALLBACK (child_activated), completion); + for (modifier = 0x1f3fb; modifier <= 0x1f3ff; modifier++) + add_emoji_variation (box, emoji_data, modifier); + + gtk_stack_add_named (GTK_STACK (stack), box, "variations"); + } + + g_object_set_data_full (G_OBJECT (child), "text", g_strdup (text), g_free); + g_object_set_data_full (G_OBJECT (child), "emoji-data", + g_variant_ref (emoji_data), (GDestroyNotify)g_variant_unref); + gtk_style_context_add_class (gtk_widget_get_style_context (child), "emoji-completion-row"); + + gtk_list_box_insert (GTK_LIST_BOX (list), child, -1); +} + +static int +populate_completion (GtkEmojiCompletion *completion, + const char *text, + guint offset) +{ + GList *children, *l; + int n_matches; + int n_added; + GVariantIter iter; + GVariant *item; + + text = g_strdup (text); + g_free (completion->text); + completion->text = g_strdup (text); + completion->length = g_utf8_strlen (text, -1); + completion->offset = offset; + + children = gtk_container_get_children (GTK_CONTAINER (completion->list)); + for (l = children; l; l = l->next) + gtk_widget_destroy (GTK_WIDGET (l->data)); + g_list_free (children); + + completion->active = NULL; + + n_matches = 0; + n_added = 0; + g_variant_iter_init (&iter, completion->data); + while ((item = g_variant_iter_next_value (&iter))) + { + const char *shortname; + + g_variant_get_child (item, 2, "&s", &shortname); + if (g_str_has_prefix (shortname, text)) + { + n_matches++; + + if (n_matches > offset && n_added < MAX_ROWS) + { + add_emoji (completion->list, item, completion); + n_added++; + } + } + } + + completion->n_matches = n_matches; + + if (n_added > 0) + { + GList *children; + + children = gtk_container_get_children (GTK_CONTAINER (completion->list)); + completion->active = children->data; + g_list_free (children); + gtk_widget_set_state_flags (completion->active, GTK_STATE_FLAG_PRELIGHT, FALSE); + } + + return n_added; +} + +static void +long_pressed_cb (GtkGesture *gesture, + double x, + double y, + gpointer data) +{ + GtkEmojiCompletion *completion = data; + GtkWidget *row; + + row = GTK_WIDGET (gtk_list_box_get_row_at_y (GTK_LIST_BOX (completion->list), y)); + if (!row) + return; + + show_variations (completion, row, TRUE); +} + +static void +gtk_emoji_completion_init (GtkEmojiCompletion *completion) +{ + g_autoptr(GBytes) bytes = NULL; + + gtk_widget_init_template (GTK_WIDGET (completion)); + + bytes = g_resources_lookup_data ("/org/gtk/libgtk/emoji/emoji.data", 0, NULL); + completion->data = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE ("a(auss)"), bytes, TRUE)); + + completion->long_press = gtk_gesture_long_press_new (completion->list); + g_signal_connect (completion->long_press, "pressed", G_CALLBACK (long_pressed_cb), completion); +} + +static void +gtk_emoji_completion_class_init (GtkEmojiCompletionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gtk_emoji_completion_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkemojicompletion.ui"); + + gtk_widget_class_bind_template_child (widget_class, GtkEmojiCompletion, list); + + gtk_widget_class_bind_template_callback (widget_class, row_activated); +} + +GtkWidget * +gtk_emoji_completion_new (GtkEntry *entry) +{ + GtkEmojiCompletion *completion; + + completion = GTK_EMOJI_COMPLETION (g_object_new (GTK_TYPE_EMOJI_COMPLETION, + "relative-to", entry, + NULL)); + + connect_signals (completion, entry); + + return GTK_WIDGET (completion); +} diff --git a/gtk/gtkemojicompletion.h b/gtk/gtkemojicompletion.h new file mode 100644 index 0000000000..ff7cb1fa63 --- /dev/null +++ b/gtk/gtkemojicompletion.h @@ -0,0 +1,41 @@ +/* gtkemojicompletion.h: An Emoji picker widget + * Copyright 2017, Red Hat, Inc. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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/>. + */ + +#pragma once + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only <gtk/gtk.h> can be included directly." +#endif + +#include <gtk/gtkentry.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_EMOJI_COMPLETION (gtk_emoji_completion_get_type ()) +#define GTK_EMOJI_COMPLETION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_EMOJI_COMPLETION, GtkEmojiCompletion)) +#define GTK_EMOJI_COMPLETION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_EMOJI_COMPLETION, GtkEmojiCompletionClass)) +#define GTK_IS_EMOJI_COMPLETION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_EMOJI_COMPLETION)) +#define GTK_IS_EMOJI_COMPLETION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_EMOJI_COMPLETION)) +#define GTK_EMOJI_COMPLETION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_EMOJI_COMPLETION, GtkEmojiCompletionClass)) + +typedef struct _GtkEmojiCompletion GtkEmojiCompletion; +typedef struct _GtkEmojiCompletionClass GtkEmojiCompletionClass; + +GType gtk_emoji_completion_get_type (void) G_GNUC_CONST; +GtkWidget *gtk_emoji_completion_new (GtkEntry *entry); + +G_END_DECLS diff --git a/gtk/theme/Adwaita/_common.scss b/gtk/theme/Adwaita/_common.scss index 85c60c41de..f095083b8f 100644 --- a/gtk/theme/Adwaita/_common.scss +++ b/gtk/theme/Adwaita/_common.scss @@ -4557,7 +4557,7 @@ button.emoji-section { &:checked label { opacity: 1; } } -.emoji { +popover.emoji-picker .emoji { font-size: x-large; padding: 6px; border-radius: 6px; @@ -4566,3 +4566,17 @@ button.emoji-section { background: $selected_bg_color; } } + +popover.emoji-completion arrow { + border: none; + background: none; +} + +popover.emoji-completion contents row box { + border-spacing: 10px; + padding: 2px 10px; +} + +popover.emoji-completion .emoji:hover { + background: $popover_hover_color; +} diff --git a/gtk/ui/gtkemojicompletion.ui b/gtk/ui/gtkemojicompletion.ui new file mode 100644 index 0000000000..7f5f3ef49a --- /dev/null +++ b/gtk/ui/gtkemojicompletion.ui @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface domain="gtk40"> + <template class="GtkEmojiCompletion" parent="GtkPopover"> + <property name="modal">0</property> + <style> + <class name="emoji-completion"/> + </style> + <child> + <object class="GtkListBox" id="list"> + <property name="visible">1</property> + <property name="selection-mode">none</property> + <property name="activate-on-single-click">1</property> + <signal name="row-activated" handler="row_activated"/> + </object> + </child> + </template> +</interface> diff --git a/po/POTFILES.in b/po/POTFILES.in index b72b837f29..a2cd6f5c86 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -136,6 +136,7 @@ gtk/gtkdragsource.c gtk/gtkdrawingarea.c gtk/gtkeditable.c gtk/gtkemojichooser.c +gtk/gtkemojicompletion.c gtk/gtkentrybuffer.c gtk/gtkentry.c gtk/gtkentrycompletion.c @@ -357,6 +358,7 @@ gtk/ui/gtkcoloreditor.ui gtk/ui/gtkdialog.ui gtk/ui/gtkemojichooser.ui gtk/ui/gtkfilechooserbutton.ui +gtk/ui/gtkemojicompletion.ui gtk/ui/gtkfilechooserdialog.ui gtk/ui/gtkfilechooserwidget.ui gtk/ui/gtkfontbutton.ui |