Compare commits

...
18 Commits
Author SHA1 Message Date
ssri_sunshine_sequence 9106ecc1ae ref: pin manifest to latest commit 2026-02-28 22:25:07 +03:00
ssri_sunshine_sequence 17678d789a ref: update branding colors 2026-02-28 22:23:57 +03:00
ssri_sunshine_sequence 7a9d850363 ref: formatting 2026-02-28 22:14:59 +03:00
ssri_sunshine_sequence 20f5b01327 ref: pin manifest sources to commit hash 2026-02-28 22:12:15 +03:00
ssri_sunshine_sequence 14cd08e28e fix: pin image commit hash instead of raw repo uri 2026-02-28 21:39:38 +03:00
ssri_sunshine_sequence 62eacf7e04 fix: flathub-specific preview without padding; aarch64 is unsupported 2026-02-28 21:37:11 +03:00
ssri_sunshine_sequence 2c1d656982 ref: change confusing app summary 2026-02-28 21:24:37 +03:00
ssri_sunshine_sequence 730c56d944 fix: install LICENSE file via meson as required by Flathub 2026-02-28 21:22:41 +03:00
ssri_sunshine_sequence da57f64816 ref: add image caption to suppress linter error 2026-02-28 19:31:01 +03:00
ssri_sunshine_sequence d82cd6f9d7 ref: add a homepage url and a screenshot 2026-02-28 19:20:08 +03:00
ssri_sunshine_sequence a7bb564c79 ref: remove unnecessary dbus permission 2026-02-28 19:12:09 +03:00
ssri_sunshine_sequence 20f9c44a6e ref: rename net.angeltech to jp.angeltech to match domain 2026-02-28 19:03:09 +03:00
ssri_sunshine_sequence 0013e75b59 Update README.md 2026-02-28 18:14:03 +03:00
ssri_sunshine_sequence e2347dd44b upd: preview 2026-02-28 17:02:43 +03:00
ssri_sunshine_sequence 62fc60f82c fix: flatpak test fails because no display 2026-02-28 15:48:59 +03:00
ssri_sunshine_sequence 6c112c41e7 ref: instead of filtering(slow) show all categories in the scroll box, scroll to category on type; change default window size 2026-02-28 15:45:45 +03:00
ssri_sunshine_sequence 6922724fa0 Update README.md 2026-02-28 05:15:17 +03:00
ssri_sunshine_sequence 95110dedae Update README.md 2026-02-28 04:50:30 +03:00
25 changed files with 380 additions and 112 deletions
+18 -3
View File
@@ -3,10 +3,16 @@ Kaomoji picker. Browse a library of Japanese emoticons, click to copy, add your
![preview](preview.png)
## Install
1. Download .flatpak file from Releases: https://gitea.angeltech.jp/Angel-Technologies/MoeMoji/releases/tag/konater
2. Install with `flatpak install ./moemoji.flatpak`
## Global shortcut
This app uses global shortcut (Meta+Shift+E) for easy access. Press once to show the window, press twice to hide the window.
If you use KDE like me, you will be requested with global shortcut access on app launch. You can remap this shortcut to anything you want and later access this shortcut from system settings.
App starts minimized by default and is hidden to the system tray; by design you are expected to press Meta+Shift+E to invoke the window, copy the emoticon and hide the window with the same shortcut, so it doesn't get in your way.
If you use KDE like me, you will be requested with global shortcut access on app launch. You can remap this shortcut to anything you want and later you can edit this shortcut from system settings.
![image](Screenshot_20260228_021628.png)
@@ -25,6 +31,15 @@ All kaomojis are stored in a simple, accessible format:
To add your own category/emoticon, simply create a folder and a corresponding .txt file in either `$XDG_DATA_HOME/moemoji/kaomoji/` or `/usr/local/share/moemoji`.
If you use flatpak, the correct data path is `~/.var/app/jp.angeltech.MoeMoji/data/`.
So adding your own emoticons would look like this:
1. `cd ~/.var/app/jp.angeltech.MoeMoji/data/`
2. `mkdir -p moemoji/kaomoji`
3. `cd moemoji/kaomoji`
4. `mkdir your-category`
5. `echo emoticon > your-category/001.txt`
6. relaunch app
You can add large ASCII text files or any text you want; the app handles multiline texts and displays a neat little preview.
# Build
@@ -36,5 +51,5 @@ Run dev build: `GSETTINGS_SCHEMA_DIR=./builddir/data ./builddir/src/moemoji`
# Flatpak packaging
`flatpak-builder --force-clean --user --install .flatpak-build net.angeltech.MoeMoji.json`
`flatpak run net.angeltech.MoeMoji`
`flatpak-builder --force-clean --user --install .flatpak-build jp.angeltech.MoeMoji.json`
`flatpak run jp.angeltech.MoeMoji`

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
application_id = 'net.angeltech.MoeMoji'
application_id = 'jp.angeltech.MoeMoji'
scalable_dir = join_paths('hicolor', 'scalable', 'apps')
install_data(
@@ -2,7 +2,7 @@
Type=Application
Name=MoeMoji
Comment=Japanese emoticon picker!
Icon=net.angeltech.MoeMoji
Icon=jp.angeltech.MoeMoji
Exec=moemoji
Terminal=false
Categories=Accessibility;GTK;Utility;
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist gettext-domain="moemoji">
<schema id='net.angeltech.MoeMoji' path='/net/angeltech/MoeMoji/'>
<schema id='jp.angeltech.MoeMoji' path='/jp/angeltech/MoeMoji/'>
</schema>
</schemalist>
+44
View File
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>jp.angeltech.MoeMoji</id>
<name>MoeMoji</name>
<summary>Kaomoji picker! Japanese emoticons library</summary>
<developer id="jp.angeltech">
<name>angeltech.jp</name>
</developer>
<description>
<p>Kaomoji picker! Browse a library of Japanese emoticons, click to copy, add your own!</p>
</description>
<metadata_license>CC0-1.0</metadata_license>
<url type="homepage">https://gitea.angeltech.jp/Angel-Technologies/MoeMoji</url>
<url type="vcs-browser">https://gitea.angeltech.jp/Angel-Technologies/MoeMoji</url>
<launchable type="desktop-id">jp.angeltech.MoeMoji.desktop</launchable>
<project_license>WTFPL</project_license>
<content_rating type="oars-1.1" />
<branding>
<color type="primary" scheme_preference="light">#a1bedd</color>
<color type="primary" scheme_preference="dark">#363e4f</color>
</branding>
<screenshots>
<screenshot type="default">
<image>https://gitea.angeltech.jp/Angel-Technologies/MoeMoji/raw/commit/62eacf7e0446c95c9e075145f3a6ce42ca39d55f/flathub-preview.png</image>
<caption>Kaomoji picker</caption>
</screenshot>
</screenshots>
<releases>
<release version="0.1.0" date="2026-02-28">
<description>
<p>Initial release.</p>
</description>
</release>
</releases>
</component>
+7 -7
View File
@@ -1,6 +1,6 @@
desktop_file = i18n.merge_file(
input: 'net.angeltech.MoeMoji.desktop.in',
output: 'net.angeltech.MoeMoji.desktop',
input: 'jp.angeltech.MoeMoji.desktop.in',
output: 'jp.angeltech.MoeMoji.desktop',
type: 'desktop',
po_dir: '../po',
install: true,
@@ -15,11 +15,11 @@ if desktop_utils.found()
endif
appstream_file = i18n.merge_file(
input: 'net.angeltech.MoeMoji.appdata.xml.in',
output: 'net.angeltech.MoeMoji.appdata.xml',
input: 'jp.angeltech.MoeMoji.metainfo.xml.in',
output: 'jp.angeltech.MoeMoji.metainfo.xml',
po_dir: '../po',
install: true,
install_dir: join_paths(get_option('datadir'), 'appdata')
install_dir: join_paths(get_option('datadir'), 'metainfo')
)
appstream_util = find_program('appstream-util', required: false)
@@ -29,12 +29,12 @@ if appstream_util.found()
)
endif
gnome.compile_schemas(build_by_default: true, depend_files: 'net.angeltech.MoeMoji.gschema.xml')
gnome.compile_schemas(build_by_default: true, depend_files: 'jp.angeltech.MoeMoji.gschema.xml')
devenv = environment()
devenv.set('GSETTINGS_SCHEMA_DIR', meson.current_build_dir() / 'data')
meson.add_devenv(devenv)
install_data('net.angeltech.MoeMoji.gschema.xml',
install_data('jp.angeltech.MoeMoji.gschema.xml',
install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas')
)
-17
View File
@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>net.angeltech.MoeMoji</id>
<name>MoeMoji</name>
<summary>Moe picker!</summary>
<description>
<p>Kaomoji picker. Browse a library of Japanese emoticons, click to copy, add your own!</p>
</description>
<metadata_license>CC0-1.0</metadata_license>
<launchable type="desktop-id">net.angeltech.MoeMoji.desktop</launchable>
<project_license>WTFPL</project_license>
<content_rating type="oars-1.1" />
</component>
Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

+3
View File
@@ -0,0 +1,3 @@
{
"only-arches": ["x86_64"]
}
@@ -1,5 +1,5 @@
{
"app-id": "net.angeltech.MoeMoji",
"app-id": "jp.angeltech.MoeMoji",
"runtime": "org.gnome.Platform",
"runtime-version": "49",
"sdk": "org.gnome.Sdk",
@@ -8,9 +8,7 @@
"--share=ipc",
"--socket=fallback-x11",
"--socket=wayland",
"--talk-name=org.kde.StatusNotifierWatcher",
"--talk-name=org.freedesktop.portal.Desktop",
"--own-name=org.kde.*"
"--talk-name=org.kde.StatusNotifierWatcher"
],
"modules": [
{
@@ -18,8 +16,10 @@
"buildsystem": "meson",
"sources": [
{
"type": "dir",
"path": "."
"type": "git",
"url": "https://gitea.angeltech.jp/Angel-Technologies/MoeMoji",
"tag": "v0.1.0",
"commit": "17678d789a3d1fd3f22449a1a06927ebee5d982e"
}
]
}
+3
View File
@@ -29,6 +29,9 @@ subdir('data')
subdir('src')
subdir('tests')
install_data('LICENSE',
install_dir: join_paths(get_option('datadir'), 'licenses', 'jp.angeltech.MoeMoji')
)
gnome.post_install(
glib_compile_schemas: true,
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 335 KiB

+2 -2
View File
@@ -21,7 +21,7 @@ static void register_with_portal (void) {
"org.freedesktop.host.portal.Registry",
"Register",
g_variant_new ("(s@a{sv})",
"net.angeltech.MoeMoji",
"jp.angeltech.MoeMoji",
g_variant_builder_end (&options)),
NULL,
G_DBUS_CALL_FLAGS_NONE,
@@ -41,6 +41,6 @@ int main (int argc, char *argv[]) {
textdomain(GETTEXT_PACKAGE);
register_with_portal ();
g_autoptr(MoeMojiApplication) app =
moemoji_application_new("net.angeltech.MoeMoji", G_APPLICATION_DEFAULT_FLAGS);
moemoji_application_new("jp.angeltech.MoeMoji", G_APPLICATION_DEFAULT_FLAGS);
return g_application_run(G_APPLICATION(app), argc, argv);
}
+12 -25
View File
@@ -246,7 +246,7 @@ GVariant *sni_get_property(G_GNUC_UNUSED GDBusConnection *connection,
MoeMojiApplication *self = MOEMOJI_APPLICATION(user_data);
return g_variant_new_string(self->tray_icon_name
? self->tray_icon_name
: "net.angeltech.MoeMoji-tray-dark");
: "jp.angeltech.MoeMoji-tray-dark");
}
if (g_strcmp0(property_name, "ItemIsMenu") == 0)
return g_variant_new_boolean(FALSE);
@@ -266,21 +266,11 @@ static const GDBusInterfaceVTable sni_vtable = {
.set_property = NULL,
};
static void on_sni_bus_name_acquired(GDBusConnection *connection,
const gchar *name,
G_GNUC_UNUSED gpointer user_data) {
g_dbus_connection_call(
connection, "org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher",
"org.kde.StatusNotifierWatcher", "RegisterStatusNotifierItem",
g_variant_new("(s)", name), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL,
NULL);
}
static void update_tray_icon(MoeMojiApplication *self) {
AdwStyleManager *sm = adw_style_manager_get_default();
gboolean dark = adw_style_manager_get_dark(sm);
self->tray_icon_name = dark ? "net.angeltech.MoeMoji-tray-dark"
: "net.angeltech.MoeMoji-tray-light";
self->tray_icon_name = dark ? "jp.angeltech.MoeMoji-tray-dark"
: "jp.angeltech.MoeMoji-tray-light";
if (self->dbus_conn && self->sni_registration_id > 0) {
g_dbus_connection_emit_signal(self->dbus_conn, NULL, "/StatusNotifierItem",
@@ -334,11 +324,12 @@ static void setup_sni(MoeMojiApplication *self) {
g_warning("dbusmenu register: %s", error->message);
g_error_free(error);
}
g_autofree gchar *bus_name =
g_strdup_printf("org.kde.StatusNotifierItem-%d-1", getpid());
self->sni_bus_name_id = g_bus_own_name_on_connection(
self->dbus_conn, bus_name, G_BUS_NAME_OWNER_FLAGS_NONE,
on_sni_bus_name_acquired, NULL, NULL, NULL);
const gchar *unique_name = g_dbus_connection_get_unique_name(self->dbus_conn);
g_dbus_connection_call(
self->dbus_conn, "org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher",
"org.kde.StatusNotifierWatcher", "RegisterStatusNotifierItem",
g_variant_new("(s)", unique_name), NULL, G_DBUS_CALL_FLAGS_NONE, -1,
NULL, NULL, NULL);
}
static void on_shortcuts_activated(GDBusProxy *proxy, const gchar *sender_name,
@@ -507,8 +498,8 @@ static void moemoji_application_startup(GApplication *app) {
MoeMojiApplication *self = MOEMOJI_APPLICATION(app);
gtk_icon_theme_add_resource_path(
gtk_icon_theme_get_for_display(gdk_display_get_default()),
"/net/angeltech/MoeMoji/icons");
gtk_window_set_default_icon_name("net.angeltech.MoeMoji-symbolic");
"/jp/angeltech/MoeMoji/icons");
gtk_window_set_default_icon_name("jp.angeltech.MoeMoji-symbolic");
gboolean in_flatpak = g_file_test("/.flatpak-info", G_FILE_TEST_EXISTS);
if (in_flatpak) {
self->icon_theme_path = NULL;
@@ -561,10 +552,6 @@ static void moemoji_application_dispose(GObject *object) {
self->menu_registration_id);
self->menu_registration_id = 0;
}
if (self->sni_bus_name_id > 0) {
g_bus_unown_name(self->sni_bus_name_id);
self->sni_bus_name_id = 0;
}
g_clear_object(&self->shortcuts_proxy);
g_clear_pointer(&self->shortcuts_session_path, g_free);
g_clear_pointer(&self->icon_theme_path, g_free);
@@ -592,7 +579,7 @@ static void moemoji_application_class_init(MoeMojiApplicationClass *klass) {
}
static void moemoji_application_init(MoeMojiApplication *self) {
self->settings = g_settings_new("net.angeltech.MoeMoji");
self->settings = g_settings_new("jp.angeltech.MoeMoji");
self->window_created = FALSE;
self->shortcuts_bound = FALSE;
g_autoptr(GSimpleAction) quit_action = g_simple_action_new("quit", NULL);
-1
View File
@@ -11,7 +11,6 @@ struct _MoeMojiApplication {
gboolean shortcuts_bound;
GDBusConnection *dbus_conn;
guint sni_registration_id;
guint sni_bus_name_id;
guint menu_registration_id;
gchar *icon_theme_path;
const gchar *tray_icon_name;
+121 -19
View File
@@ -62,6 +62,13 @@ static void on_popover_leave(G_GNUC_UNUSED GtkEventControllerMotion *ctrl,
gtk_popover_popdown(GTK_POPOVER(user_data));
}
static void on_button_destroy(GtkWidget *button,
G_GNUC_UNUSED gpointer user_data) {
GtkWidget *popover = g_object_get_data(G_OBJECT(button), "popover");
if (popover)
gtk_widget_unparent(popover);
}
static void add_kaomoji_button(GtkFlowBox *flow, const char *text) {
gboolean multiline = (strchr(text, '\n') != NULL);
const char *label_text = text;
@@ -88,6 +95,8 @@ static void add_kaomoji_button(GtkFlowBox *flow, const char *text) {
gtk_widget_add_css_class(label, "kaomoji-preview");
gtk_popover_set_child(GTK_POPOVER(popover), label);
gtk_widget_set_parent(popover, button);
g_object_set_data(G_OBJECT(button), "popover", popover);
g_signal_connect(button, "destroy", G_CALLBACK(on_button_destroy), NULL);
GtkEventController *motion = gtk_event_controller_motion_new();
g_signal_connect(motion, "enter", G_CALLBACK(on_popover_enter), popover);
@@ -139,47 +148,127 @@ static void load_category(MoeMojiWindow *self, const char *kaomoji_dir,
g_free(cat_path);
}
static void set_active_chip(MoeMojiWindow *self, int index) {
if (index == self->active_chip_index)
return;
if (self->active_chip_index >= 0 &&
(guint)self->active_chip_index < self->category_widgets->len) {
CategoryWidgets *old =
g_ptr_array_index(self->category_widgets, self->active_chip_index);
if (old->chip)
gtk_widget_remove_css_class(old->chip, "chip-active");
}
self->active_chip_index = index;
if (index >= 0 && (guint)index < self->category_widgets->len) {
CategoryWidgets *cw = g_ptr_array_index(self->category_widgets, index);
if (cw->chip)
gtk_widget_add_css_class(cw->chip, "chip-active");
}
}
static void scroll_to_category(MoeMojiWindow *self, int index) {
CategoryWidgets *cw = g_ptr_array_index(self->category_widgets, index);
graphene_point_t p;
if (gtk_widget_compute_point(cw->header, GTK_WIDGET(self->content_box),
&GRAPHENE_POINT_INIT(0, 0), &p)) {
GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(
GTK_SCROLLED_WINDOW(self->kaomoji_scroll));
gtk_adjustment_set_value(vadj, p.y);
}
}
static void on_chip_clicked(GtkButton *button, gpointer user_data) {
MoeMojiWindow *self = MOEMOJI_WINDOW(user_data);
const char *current = gtk_editable_get_text(GTK_EDITABLE(self->search_entry));
const char *name = gtk_button_get_label(button);
if (g_strcmp0(current, name) == 0)
gtk_editable_set_text(GTK_EDITABLE(self->search_entry), "");
else
gtk_editable_set_text(GTK_EDITABLE(self->search_entry), name);
for (guint i = 0; i < self->category_widgets->len; i++) {
CategoryWidgets *cw = g_ptr_array_index(self->category_widgets, i);
if (cw->chip == GTK_WIDGET(button)) {
scroll_to_category(self, (int)i);
return;
}
}
}
static void update_chip_from_scroll(MoeMojiWindow *self) {
double scroll_pos = gtk_adjustment_get_value(
gtk_scrolled_window_get_vadjustment(
GTK_SCROLLED_WINDOW(self->kaomoji_scroll)));
int best = 0;
for (guint i = 0; i < self->category_widgets->len; i++) {
CategoryWidgets *cw = g_ptr_array_index(self->category_widgets, i);
graphene_point_t p;
if (gtk_widget_compute_point(cw->header, GTK_WIDGET(self->content_box),
&GRAPHENE_POINT_INIT(0, 0), &p)) {
if (p.y <= scroll_pos)
best = (int)i;
}
}
set_active_chip(self, best);
}
static void update_spacer_height(MoeMojiWindow *self) {
if (!self->bottom_spacer || self->category_widgets->len == 0)
return;
GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(
GTK_SCROLLED_WINDOW(self->kaomoji_scroll));
int page = (int)gtk_adjustment_get_page_size(vadj);
CategoryWidgets *last = g_ptr_array_index(self->category_widgets,
self->category_widgets->len - 1);
int spacing = gtk_box_get_spacing(self->content_box);
int last_h = gtk_widget_get_height(last->header) + spacing +
gtk_widget_get_height(last->flow);
int spacer_h = page - last_h;
if (spacer_h < 0)
spacer_h = 0;
gtk_widget_set_size_request(self->bottom_spacer, -1, spacer_h);
}
static void on_scroll_changed(GtkAdjustment *adj, gpointer user_data) {
(void)adj;
MoeMojiWindow *self = MOEMOJI_WINDOW(user_data);
update_chip_from_scroll(self);
}
static void on_page_size_changed(G_GNUC_UNUSED GtkAdjustment *adj,
G_GNUC_UNUSED GParamSpec *pspec,
gpointer user_data) {
MoeMojiWindow *self = MOEMOJI_WINDOW(user_data);
update_spacer_height(self);
}
static void on_kaomoji_scroll_map(G_GNUC_UNUSED GtkWidget *widget,
gpointer user_data) {
MoeMojiWindow *self = MOEMOJI_WINDOW(user_data);
update_chip_from_scroll(self);
}
static void on_search_changed(GtkSearchEntry *entry, gpointer user_data) {
MoeMojiWindow *self = MOEMOJI_WINDOW(user_data);
const char *query = gtk_editable_get_text(GTK_EDITABLE(entry));
if (query == NULL || query[0] == '\0')
return;
g_autofree char *query_lower = g_utf8_strdown(query, -1);
for (guint i = 0; i < self->category_widgets->len; i++) {
CategoryWidgets *cw = g_ptr_array_index(self->category_widgets, i);
gboolean visible;
if (query == NULL || query[0] == '\0') {
visible = TRUE;
} else {
char *name_lower = g_utf8_strdown(cw->name, -1);
char *query_lower = g_utf8_strdown(query, -1);
visible = (strstr(name_lower, query_lower) != NULL);
g_free(name_lower);
g_free(query_lower);
g_autofree char *name_lower = g_utf8_strdown(cw->name, -1);
if (strstr(name_lower, query_lower) != NULL) {
scroll_to_category(self, (int)i);
return;
}
gtk_widget_set_visible(cw->header, visible);
gtk_widget_set_visible(cw->flow, visible);
}
}
static void moemoji_window_class_init(MoeMojiWindowClass *klass) {
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
gtk_widget_class_set_template_from_resource(
widget_class, "/net/angeltech/MoeMoji/moemoji-window.ui");
widget_class, "/jp/angeltech/MoeMoji/moemoji-window.ui");
gtk_widget_class_bind_template_child(widget_class, MoeMojiWindow, outer_box);
gtk_widget_class_bind_template_child(widget_class, MoeMojiWindow,
content_box);
gtk_widget_class_bind_template_child(widget_class, MoeMojiWindow,
search_entry);
gtk_widget_class_bind_template_child(widget_class, MoeMojiWindow, header_bar);
gtk_widget_class_bind_template_child(widget_class, MoeMojiWindow,
kaomoji_scroll);
}
static void collect_categories(const char *base_dir, GHashTable *seen,
@@ -218,6 +307,7 @@ static void moemoji_window_init(MoeMojiWindow *self) {
gtk_widget_add_css_class(GTK_WIDGET(self->content_box), "content-area");
self->category_widgets =
g_ptr_array_new_with_free_func(category_widgets_free);
self->active_chip_index = -1;
char *kaomoji_dir = find_kaomoji_dir();
g_autofree char *user_dir =
g_build_filename(g_get_user_data_dir(), "moemoji", "kaomoji", NULL);
@@ -246,6 +336,8 @@ static void moemoji_window_init(MoeMojiWindow *self) {
}
g_ptr_array_free(entries, TRUE);
g_free(kaomoji_dir);
self->bottom_spacer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_append(self->content_box, self->bottom_spacer);
g_signal_connect(self->search_entry, "search-changed",
G_CALLBACK(on_search_changed), self);
if (self->category_widgets->len > 0) {
@@ -260,6 +352,7 @@ static void moemoji_window_init(MoeMojiWindow *self) {
gtk_widget_add_css_class(btn, "category-chip");
gtk_widget_add_css_class(btn, "flat");
g_signal_connect(btn, "clicked", G_CALLBACK(on_chip_clicked), self);
cw->chip = btn;
gtk_flow_box_insert(GTK_FLOW_BOX(flow), btn, -1);
}
self->category_bar = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
@@ -275,5 +368,14 @@ static void moemoji_window_init(MoeMojiWindow *self) {
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(cat_scroll), flow);
gtk_box_append(self->category_bar, cat_scroll);
gtk_box_append(self->outer_box, GTK_WIDGET(self->category_bar));
GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(
GTK_SCROLLED_WINDOW(self->kaomoji_scroll));
g_signal_connect(vadj, "value-changed", G_CALLBACK(on_scroll_changed),
self);
g_signal_connect(vadj, "notify::page-size",
G_CALLBACK(on_page_size_changed), self);
g_signal_connect(self->kaomoji_scroll, "map",
G_CALLBACK(on_kaomoji_scroll_map), self);
}
}
+4
View File
@@ -5,6 +5,7 @@
typedef struct {
GtkWidget *header;
GtkWidget *flow;
GtkWidget *chip;
char *name;
} CategoryWidgets;
@@ -15,7 +16,10 @@ struct _MoeMojiWindow {
GtkSearchEntry *search_entry;
GtkWidget *header_bar;
GtkBox *category_bar;
GtkWidget *kaomoji_scroll;
GPtrArray *category_widgets;
GtkWidget *bottom_spacer;
int active_chip_index;
};
G_BEGIN_DECLS
+4 -4
View File
@@ -2,8 +2,8 @@
<interface>
<requires lib="gtk" version="4.0" />
<template class="MoeMojiWindow" parent="GtkApplicationWindow">
<property name="default-width">280</property>
<property name="default-height">600</property>
<property name="default-width">680</property>
<property name="default-height">400</property>
<property name="resizable">True</property>
<property name="title">MoeMoji</property>
@@ -18,14 +18,14 @@
<property name="orientation">vertical</property>
<child>
<object class="GtkSearchEntry" id="search_entry">
<property name="placeholder-text">Filter by category...</property>
<property name="placeholder-text">Jump to category...</property>
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="margin-top">6</property>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<object class="GtkScrolledWindow" id="kaomoji_scroll">
<property name="hscrollbar-policy">never</property>
<property name="vexpand">True</property>
<child>
+4 -4
View File
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/net/angeltech/MoeMoji">
<gresource prefix="/jp/angeltech/MoeMoji">
<file>moemoji-window.ui</file>
<file>style.css</file>
<file>wp.png</file>
<file alias="icons/scalable/apps/net.angeltech.MoeMoji.svg">../data/icons/hicolor/scalable/apps/net.angeltech.MoeMoji.svg</file>
<file alias="icons/scalable/apps/net.angeltech.MoeMoji-symbolic.svg">../data/icons/hicolor/symbolic/apps/net.angeltech.MoeMoji-symbolic.svg</file>
<file alias="icons/scalable/apps/jp.angeltech.MoeMoji.svg">../data/icons/hicolor/scalable/apps/jp.angeltech.MoeMoji.svg</file>
<file alias="icons/scalable/apps/jp.angeltech.MoeMoji-symbolic.svg">../data/icons/hicolor/symbolic/apps/jp.angeltech.MoeMoji-symbolic.svg</file>
</gresource>
</gresources>
</gresources>
+6 -1
View File
@@ -31,7 +31,7 @@
}
.wallpaper-bg {
background-image: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/net/angeltech/MoeMoji/wp.png");
background-image: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/jp/angeltech/MoeMoji/wp.png");
background-size: cover;
background-color: transparent;
}
@@ -53,3 +53,8 @@ wallpaper-bg > * {
border-radius: 14px;
padding: 8px;
}
.chip-active {
background-color: rgba(255, 255, 255, 0.25);
border-radius: 8px;
}
+143 -20
View File
@@ -1,6 +1,8 @@
#include <glib.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include "moemoji-internal.h"
#include "moemoji-window.h"
static void
test_display_name_underscores (void)
@@ -45,16 +47,16 @@ test_display_name_empty (void)
static void
test_find_kaomoji_with_env (void)
{
g_setenv ("MESON_SOURCE_ROOT", SRCDIR, TRUE);
char *dir = find_kaomoji_dir ();
g_assert_nonnull (dir);
g_assert_true (g_file_test (dir, G_FILE_TEST_IS_DIR));
g_free (dir);
g_unsetenv ("MESON_SOURCE_ROOT");
}
static gboolean find_kaomoji_bogus_passed = FALSE;
static void
test_find_kaomoji_bogus (void)
run_find_kaomoji_bogus (void)
{
g_setenv ("MESON_SOURCE_ROOT", "/nonexistent", TRUE);
char *saved = g_get_current_dir ();
@@ -65,7 +67,14 @@ test_find_kaomoji_bogus (void)
g_assert_true (g_chdir (saved) == 0);
g_free (saved);
g_unsetenv ("MESON_SOURCE_ROOT");
g_setenv ("MESON_SOURCE_ROOT", SRCDIR, TRUE);
find_kaomoji_bogus_passed = TRUE;
}
static void
test_find_kaomoji_bogus (void)
{
g_assert_true (find_kaomoji_bogus_passed);
}
static void
@@ -145,27 +154,141 @@ test_dbusmenu_unknown (void)
g_assert_null (v);
}
static void
test_category_widgets_chip_init (void)
{
CategoryWidgets *cw = g_new0 (CategoryWidgets, 1);
g_assert_null (cw->chip);
g_assert_null (cw->header);
g_assert_null (cw->flow);
g_assert_null (cw->name);
g_free (cw);
}
G_GNUC_INTERNAL GResource *moemoji_get_resource (void);
static GResource *test_res = NULL;
static GtkWidget *test_win = NULL;
static MoeMojiWindow *test_self = NULL;
static void
window_test_setup (void)
{
if (!test_res) {
test_res = moemoji_get_resource ();
g_resources_register (test_res);
}
test_win = g_object_new (MOEMOJI_TYPE_WINDOW, NULL);
test_self = MOEMOJI_WINDOW (test_win);
}
static void
window_test_teardown (void)
{
gtk_window_destroy (GTK_WINDOW (test_win));
test_win = NULL;
test_self = NULL;
}
static void
test_window_chip_setup (void)
{
window_test_setup ();
g_assert_cmpuint (test_self->category_widgets->len, >, 0);
for (guint i = 0; i < test_self->category_widgets->len; i++) {
CategoryWidgets *cw = g_ptr_array_index (test_self->category_widgets, i);
g_assert_nonnull (cw->chip);
}
window_test_teardown ();
}
static void
test_window_chip_labels_match (void)
{
window_test_setup ();
for (guint i = 0; i < test_self->category_widgets->len; i++) {
CategoryWidgets *cw = g_ptr_array_index (test_self->category_widgets, i);
const char *chip_label = gtk_button_get_label (GTK_BUTTON (cw->chip));
g_assert_cmpstr (chip_label, ==, cw->name);
}
window_test_teardown ();
}
static void
test_window_initial_active_chip (void)
{
window_test_setup ();
g_assert_cmpint (test_self->active_chip_index, ==, -1);
window_test_teardown ();
}
static void
test_window_all_categories_visible (void)
{
window_test_setup ();
for (guint i = 0; i < test_self->category_widgets->len; i++) {
CategoryWidgets *cw = g_ptr_array_index (test_self->category_widgets, i);
g_assert_true (gtk_widget_get_visible (cw->header));
g_assert_true (gtk_widget_get_visible (cw->flow));
}
window_test_teardown ();
}
static void
test_window_bottom_spacer (void)
{
window_test_setup ();
g_assert_nonnull (test_self->bottom_spacer);
GtkWidget *last = gtk_widget_get_last_child (GTK_WIDGET (test_self->content_box));
g_assert_true (last == test_self->bottom_spacer);
window_test_teardown ();
}
int
main (int argc, char *argv[])
{
g_setenv ("MESON_SOURCE_ROOT", SRCDIR, TRUE);
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/display-name/underscores", test_display_name_underscores);
g_test_add_func ("/display-name/no-underscores", test_display_name_no_underscores);
g_test_add_func ("/display-name/already-upper", test_display_name_already_upper);
g_test_add_func ("/display-name/single-char", test_display_name_single_char);
g_test_add_func ("/display-name/empty", test_display_name_empty);
g_test_add_func ("/find-kaomoji/with-env", test_find_kaomoji_with_env);
g_test_add_func ("/find-kaomoji/bogus", test_find_kaomoji_bogus);
g_test_add_func ("/sni/category", test_sni_category);
g_test_add_func ("/sni/id", test_sni_id);
g_test_add_func ("/sni/item-is-menu", test_sni_item_is_menu);
g_test_add_func ("/sni/menu", test_sni_menu);
g_test_add_func ("/sni/unknown", test_sni_unknown);
g_test_add_func ("/dbusmenu/version", test_dbusmenu_version);
g_test_add_func ("/dbusmenu/status", test_dbusmenu_status);
g_test_add_func ("/dbusmenu/text-direction", test_dbusmenu_text_direction);
g_test_add_func ("/dbusmenu/unknown", test_dbusmenu_unknown);
run_find_kaomoji_bogus ();
gboolean have_display = gtk_init_check ();
g_test_add_func ("/display-name/underscores", test_display_name_underscores);
g_test_add_func ("/display-name/no-underscores", test_display_name_no_underscores);
g_test_add_func ("/display-name/already-upper", test_display_name_already_upper);
g_test_add_func ("/display-name/single-char", test_display_name_single_char);
g_test_add_func ("/display-name/empty", test_display_name_empty);
g_test_add_func ("/find-kaomoji/with-env", test_find_kaomoji_with_env);
g_test_add_func ("/find-kaomoji/bogus", test_find_kaomoji_bogus);
g_test_add_func ("/sni/category", test_sni_category);
g_test_add_func ("/sni/id", test_sni_id);
g_test_add_func ("/sni/item-is-menu", test_sni_item_is_menu);
g_test_add_func ("/sni/menu", test_sni_menu);
g_test_add_func ("/sni/unknown", test_sni_unknown);
g_test_add_func ("/dbusmenu/version", test_dbusmenu_version);
g_test_add_func ("/dbusmenu/status", test_dbusmenu_status);
g_test_add_func ("/dbusmenu/text-direction", test_dbusmenu_text_direction);
g_test_add_func ("/dbusmenu/unknown", test_dbusmenu_unknown);
g_test_add_func ("/category-widgets/chip-init", test_category_widgets_chip_init);
if (have_display) {
g_test_add_func ("/window/chip-setup", test_window_chip_setup);
g_test_add_func ("/window/chip-labels-match", test_window_chip_labels_match);
g_test_add_func ("/window/initial-active-chip", test_window_initial_active_chip);
g_test_add_func ("/window/all-categories-visible", test_window_all_categories_visible);
g_test_add_func ("/window/bottom-spacer", test_window_bottom_spacer);
}
return g_test_run ();
}