Skip to content

Commit

Permalink
feat(Composer): custom emoji picker (#308)
Browse files Browse the repository at this point in the history
* feat(Composer): custom emoji picker

* feat(Widgets.Emoji): request paintable on idle

* feat: increase default image cache to 5m

* feat: move cechooser to its own widget

* feat: do not use linked

* feat: change title if only other category exists

* fix: margins

* chore: remove unused code
  • Loading branch information
GeopJr authored Jun 18, 2023
1 parent 80ae54c commit 97d1145
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 20 deletions.
1 change: 1 addition & 0 deletions data/gresource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
<file preprocess="xml-stripblanks">icons/scalable/actions/tuba-newspaper-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/tuba-explore2-large-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/tuba-text-justify-left-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/tuba-cat-symbolic.svg</file>
<!-- <file preprocess="xml-stripblanks">icons/scalable/actions/tuba-language-symbolic.svg</file> -->

<file>gtk/dropdown/icon.ui</file>
Expand Down
2 changes: 2 additions & 0 deletions data/icons/scalable/actions/tuba-cat-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions src/API/Emoji.vala
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
public class Tuba.API.Emoji : Entity {
public string shortcode { get; set; }
public string url { get; set; }
public string category { get; set; default=_("Other"); }
public bool visible_in_picker { get; set; default=true; }
public bool is_other {
get {
return category == _("Other");
}
}
}
4 changes: 3 additions & 1 deletion src/Application.vala
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ namespace Tuba {
streams = new Streams ();
network = new Network ();
entity_cache = new EntityCache ();
image_cache = new ImageCache ();
image_cache = new ImageCache () {
maintenance_secs = 60 * 5
};
accounts = new SecretAccountStore();
accounts.init ();

Expand Down
7 changes: 0 additions & 7 deletions src/Dialogs/Composer/Completion/CompletionProvider.vala
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,6 @@ public abstract class Tuba.CompletionProvider: Object, GtkSource.CompletionProvi
public string? trigger_char { get; construct; }
protected bool is_capturing_input { get; set; default = false; }
protected int empty_triggers = 0;
protected ImageCache image_cache;

construct {
image_cache = new ImageCache () {
maintenance_secs = 60
};
}

public virtual bool is_trigger (Gtk.TextIter iter, unichar ch) {
if (this.trigger_char == null) {
Expand Down
10 changes: 10 additions & 0 deletions src/Dialogs/Composer/EditorPage.vala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class Tuba.EditorPage : ComposerPage {
install_languages (status.language);
add_button (new Gtk.Separator (Orientation.VERTICAL));
install_cw (status.spoiler_text);
add_button (new Gtk.Separator (Orientation.VERTICAL));
install_emoji_picker();

validate ();
Expand Down Expand Up @@ -199,9 +200,18 @@ public class Tuba.EditorPage : ComposerPage {
tooltip_text = _("Emoji Picker")
};

var custom_emoji_picker = new Widgets.CustomEmojiChooser ();
var custom_emoji_button = new MenuButton () {
icon_name = "tuba-cat-symbolic",
popover = custom_emoji_picker,
tooltip_text = _("Custom Emoji Picker")
};

add_button(emoji_button);
add_button(custom_emoji_button);

emoji_picker.emoji_picked.connect(on_emoji_picked);
custom_emoji_picker.emoji_picked.connect(on_emoji_picked);
}

protected void on_emoji_picked(string emoji_unicode) {
Expand Down
5 changes: 0 additions & 5 deletions src/Views/MediaViewer.vala
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,6 @@ public class Tuba.Views.MediaViewer : Gtk.Box {
private Gee.ArrayList<Item> items = new Gee.ArrayList<Item> ();
protected Gtk.Button fullscreen_btn;
protected Adw.HeaderBar headerbar;
protected ImageCache image_cache;
private Adw.Carousel carousel;
private Adw.CarouselIndicatorDots carousel_dots;

Expand All @@ -224,10 +223,6 @@ public class Tuba.Views.MediaViewer : Gtk.Box {
overlay.add_overlay (generate_media_buttons ());
overlay.child = carousel;

image_cache = new ImageCache () {
maintenance_secs = 60 * 5
};

var drag = new Gtk.GestureDrag ();
drag.drag_begin.connect(on_drag_begin);
drag.drag_update.connect(on_drag_update);
Expand Down
197 changes: 197 additions & 0 deletions src/Widgets/CustomEmojiChooser.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
public class Tuba.Widgets.CustomEmojiChooser : Gtk.Popover {
public string query { get; set; default = ""; }
public signal void emoji_picked (string shortcode);
public bool is_populated { get; protected set; default=false; }

private Gee.HashMap<string, Gee.ArrayList<API.Emoji>> gen_emojis_cat_map () {
var res = new Gee.HashMap<string, Gee.ArrayList<API.Emoji>>();
var emojis = accounts.active.instance_emojis;

if (emojis != null && emojis.size > 0) {
emojis.@foreach (e => {
if (!e.visible_in_picker) return true;

if (res.has_key(e.category)) {
var array = res.get (e.category);
array.add (e);
} else {
var array = new Gee.ArrayList<API.Emoji> ();
array.add (e);
res.set(e.category, array);
}

return true;
});
}

return res;
}

private Gtk.Box custom_emojis_box;
private Gtk.SearchEntry entry;
private Gtk.FlowBox results;
private Gtk.Label results_label;
private Gtk.ScrolledWindow custom_emojis_scrolled;
private GLib.ListStore list_store = new GLib.ListStore (typeof (API.Emoji));
construct {
this.add_css_class ("emoji-picker");
custom_emojis_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 6) {
margin_end = 6,
margin_bottom = 6,
margin_start = 6
};
custom_emojis_scrolled = new Gtk.ScrolledWindow () {
hscrollbar_policy = Gtk.PolicyType.NEVER,
height_request = 360
};
var content_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
content_box.append (custom_emojis_scrolled);

this.child = content_box;
custom_emojis_scrolled.child = custom_emojis_box;

results_label = create_category_label (_("Results"));
results_label.visible = false;
custom_emojis_box.append (results_label);

results = create_emoji_box ();
custom_emojis_box.append (results);

results.bind_model (list_store, model => {
var emoji = model as API.Emoji;
if (emoji == null) Process.exit (0);
return create_emoji_button (emoji);
});

entry = new Gtk.SearchEntry () {
text = query,
hexpand = true
};

var entry_bin = new Adw.Bin () {
css_classes = { "emoji-searchbar" },
child = entry
};
content_box.prepend (entry_bin);

entry.activate.connect (search);
entry.search_changed.connect (search);
entry.stop_search.connect (search);
}

protected void search () {
query = entry.text.chug ().chomp ().down ().replace (":", "");
list_store.remove_all ();

if (query == "") {
results_label.visible = false;
return;
}

var emojis = accounts.active.instance_emojis;

if (emojis != null && emojis.size > 0) {
var at_least_one = false;
emojis.@foreach (e => {
if (!e.visible_in_picker) return true;
if (query in e.shortcode) {
at_least_one = true;
list_store.append (e);
};

return true;
});

if (at_least_one) {
results_label.label = _("Results");
custom_emojis_scrolled.scroll_child (Gtk.ScrollType.START, false);
} else {
results_label.label = _("No Results");
}

results_label.visible = true;
}
}

protected void on_custom_emoji_picked (Gtk.Button emoji_btn) {
var emoji = emoji_btn.child as Emoji;
if (emoji != null) {
on_close ();
emoji_picked (@":$(emoji.shortcode):");
}
}

protected void on_close () {
this.popdown ();
}

public override void show () {
base.show ();

GLib.Idle.add (() => {
if (!is_populated) populate_chooser ();
entry.grab_focus ();
return GLib.Source.REMOVE;
});
}

protected void populate_chooser () {
var categorized_custom_emojis = gen_emojis_cat_map ();

categorized_custom_emojis.@foreach (e => {
if (e.key == _("Other")) return true;
create_category (e.key, e.value);

return true;
});

if (categorized_custom_emojis.has_key (_("Other")))
create_category (categorized_custom_emojis.size > 1 ? _("Other") : _("Custom Emojis"), categorized_custom_emojis.get(_("Other")));

is_populated = true;
}

protected Gtk.Button create_emoji_button (API.Emoji emoji) {
var emoji_btn = new Gtk.Button () {
css_classes = { "flat" },
child = new Widgets.Emoji (emoji.url, emoji.shortcode)
};
emoji_btn.set_css_name ("emoji");

emoji_btn.clicked.connect (on_custom_emoji_picked);
return emoji_btn;
}

protected void create_category (string key, Gee.ArrayList<API.Emoji> value) {
custom_emojis_box.append (create_category_label (key));

var emojis_flowbox = create_emoji_box ();
value.@foreach (emoji => {
emojis_flowbox.append (create_emoji_button (emoji));

return true;
});

custom_emojis_box.append (emojis_flowbox);
}

protected Gtk.FlowBox create_emoji_box () {
return new Gtk.FlowBox () {
homogeneous = true,
column_spacing = 6,
row_spacing = 6,
max_children_per_line = 6,
min_children_per_line = 6,
selection_mode = Gtk.SelectionMode.NONE
};
}

protected Gtk.Label create_category_label (string label) {
return new Gtk.Label (label) {
wrap = true,
wrap_mode = Pango.WrapMode.WORD_CHAR,
halign = Gtk.Align.START,
margin_top = 3
};
}
}
20 changes: 13 additions & 7 deletions src/Widgets/Emoji.vala
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,27 @@ using Gdk;
public class Tuba.Widgets.Emoji : Adw.Bin {

protected Image image;
public string? shortcode { get; set; }

construct {
image = new Gtk.Image ();
child = image;
}

public Emoji (string emoji_url, string? shortcode = null) {
if (shortcode != null)
image.tooltip_text = shortcode;
image_cache.request_paintable (emoji_url, on_cache_response);
public Emoji (string emoji_url, string? t_shortcode = null) {
if (t_shortcode != null) {
image.tooltip_text = t_shortcode;
shortcode = t_shortcode;
}

GLib.Idle.add (() => {
image_cache.request_paintable (emoji_url, on_cache_response);
return GLib.Source.REMOVE;
});
}

void on_cache_response (bool is_loaded, owned Paintable? data) {
var image_widget = (child as Image);
if (child != null && image_widget != null)
image_widget.paintable = data;
if (image != null)
image.paintable = data;
}
}
1 change: 1 addition & 0 deletions src/Widgets/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ sources += files(
'Background.vala',
'BookWyrmPage.vala',
'Conversation.vala',
'CustomEmojiChooser.vala',
'Emoji.vala',
'EmojiLabel.vala',
'LabelWithWidgets.vala',
Expand Down

0 comments on commit 97d1145

Please sign in to comment.