From 85fbeb1031ece212e48b1926f0602273bb05422b Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 26 Dec 2020 04:00:21 +0100 Subject: [PATCH] Ottie: Add Fantastic, the Ottie Editor And yes, this naming has to do with elephants. --- meson.build | 1 + ottie/fantastic/fantastic.gresource.xml | 6 + ottie/fantastic/fantasticapplication.c | 184 +++++++++ ottie/fantastic/fantasticapplication.h | 39 ++ ottie/fantastic/fantasticobserver.c | 366 ++++++++++++++++++ ottie/fantastic/fantasticobserver.h | 50 +++ ottie/fantastic/fantasticwindow.c | 485 ++++++++++++++++++++++++ ottie/fantastic/fantasticwindow.h | 42 ++ ottie/fantastic/fantasticwindow.ui | 138 +++++++ ottie/fantastic/main.c | 28 ++ ottie/fantastic/meson.build | 32 ++ 11 files changed, 1371 insertions(+) create mode 100644 ottie/fantastic/fantastic.gresource.xml create mode 100644 ottie/fantastic/fantasticapplication.c create mode 100644 ottie/fantastic/fantasticapplication.h create mode 100644 ottie/fantastic/fantasticobserver.c create mode 100644 ottie/fantastic/fantasticobserver.h create mode 100644 ottie/fantastic/fantasticwindow.c create mode 100644 ottie/fantastic/fantasticwindow.h create mode 100644 ottie/fantastic/fantasticwindow.ui create mode 100644 ottie/fantastic/main.c create mode 100644 ottie/fantastic/meson.build diff --git a/meson.build b/meson.build index a502ecff8d..702c28c0cf 100644 --- a/meson.build +++ b/meson.build @@ -682,6 +682,7 @@ subdir('gsk') subdir('ottie') subdir('gtk') subdir('ottie/tools') +subdir('ottie/fantastic') subdir('modules') if get_option('demos') subdir('demos') diff --git a/ottie/fantastic/fantastic.gresource.xml b/ottie/fantastic/fantastic.gresource.xml new file mode 100644 index 0000000000..00774358ae --- /dev/null +++ b/ottie/fantastic/fantastic.gresource.xml @@ -0,0 +1,6 @@ + + + + fantasticwindow.ui + + diff --git a/ottie/fantastic/fantasticapplication.c b/ottie/fantastic/fantasticapplication.c new file mode 100644 index 0000000000..fad0aefc5a --- /dev/null +++ b/ottie/fantastic/fantasticapplication.c @@ -0,0 +1,184 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * 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.1 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 . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "fantasticapplication.h" + +#include "fantasticwindow.h" + +struct _FantasticApplication +{ + GtkApplication parent; +}; + +struct _FantasticApplicationClass +{ + GtkApplicationClass parent_class; +}; + +G_DEFINE_TYPE(FantasticApplication, fantastic_application, GTK_TYPE_APPLICATION); + +static void +fantastic_application_init (FantasticApplication *app) +{ +} + +static void +activate_about (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GtkApplication *app = user_data; + char *version; + GString *s; + char *os_name; + char *os_version; + GtkWidget *dialog; + + os_name = g_get_os_info (G_OS_INFO_KEY_NAME); + os_version = g_get_os_info (G_OS_INFO_KEY_VERSION_ID); + s = g_string_new (""); + if (os_name && os_version) + g_string_append_printf (s, "OS\t%s %s\n\n", os_name, os_version); + + g_string_append (s, "System libraries\n"); + g_string_append_printf (s, "\tGLib\t%d.%d.%d\n", + glib_major_version, + glib_minor_version, + glib_micro_version); + g_string_append_printf (s, "\tPango\t%s\n", + pango_version_string ()); + g_string_append_printf (s, "\tGTK\t%d.%d.%d\n", + gtk_get_major_version (), + gtk_get_minor_version (), + gtk_get_micro_version ()); + + version = g_strdup_printf ("%s\nRunning against GTK %d.%d.%d", + PACKAGE_VERSION, + gtk_get_major_version (), + gtk_get_minor_version (), + gtk_get_micro_version ()); + + dialog = g_object_new (GTK_TYPE_ABOUT_DIALOG, + "transient-for", gtk_application_get_active_window (app), + "program-name", "Fantastic", + "version", version, + "copyright", "© 2020 The GTK Team", + "license-type", GTK_LICENSE_LGPL_2_1, + "website", "http://www.gtk.org", + "comments", "Edit Lottie files", + "authors", (const char *[]){ "Benjamin Otte", NULL}, + "logo-icon-name", "org.gtk.gtk4.Fantastic.Devel", + "title", "About Fantastic", + "system-information", s->str, + NULL); + gtk_about_dialog_add_credit_section (GTK_ABOUT_DIALOG (dialog), + "Artwork by", (const char *[]) { "Jakub Steiner", NULL }); + + gtk_window_present (GTK_WINDOW (dialog)); + + g_string_free (s, TRUE); + g_free (version); + g_free (os_name); + g_free (os_version); +} + +static void +activate_quit (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + g_application_quit (G_APPLICATION (data)); +} + +static void +activate_inspector (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + gtk_window_set_interactive_debugging (TRUE); +} + +static GActionEntry app_entries[] = +{ + { "about", activate_about, NULL, NULL, NULL }, + { "quit", activate_quit, NULL, NULL, NULL }, + { "inspector", activate_inspector, NULL, NULL, NULL }, +}; + +static void +fantastic_application_startup (GApplication *app) +{ + const char *quit_accels[2] = { "Q", NULL }; + const char *open_accels[2] = { "O", NULL }; + + G_APPLICATION_CLASS (fantastic_application_parent_class)->startup (app); + + g_action_map_add_action_entries (G_ACTION_MAP (app), + app_entries, G_N_ELEMENTS (app_entries), + app); + gtk_application_set_accels_for_action (GTK_APPLICATION (app), "app.quit", quit_accels); + gtk_application_set_accels_for_action (GTK_APPLICATION (app), "win.open", open_accels); +} + +static void +fantastic_application_activate (GApplication *app) +{ + FantasticWindow *win; + + win = fantastic_window_new (FANTASTIC_APPLICATION (app)); + gtk_window_present (GTK_WINDOW (win)); +} + +static void +fantastic_application_open (GApplication *app, + GFile **files, + int n_files, + const char *hint) +{ + FantasticWindow *win; + int i; + + for (i = 0; i < n_files; i++) + { + win = fantastic_window_new (FANTASTIC_APPLICATION (app)); + fantastic_window_load (win, files[i]); + gtk_window_present (GTK_WINDOW (win)); + } +} + +static void +fantastic_application_class_init (FantasticApplicationClass *class) +{ + GApplicationClass *application_class = G_APPLICATION_CLASS (class); + + application_class->startup = fantastic_application_startup; + application_class->activate = fantastic_application_activate; + application_class->open = fantastic_application_open; +} + +FantasticApplication * +fantastic_application_new (void) +{ + return g_object_new (FANTASTIC_APPLICATION_TYPE, + "application-id", "org.gtk.gtk4.Fantastic", + "flags", G_APPLICATION_HANDLES_OPEN, + NULL); +} diff --git a/ottie/fantastic/fantasticapplication.h b/ottie/fantastic/fantasticapplication.h new file mode 100644 index 0000000000..4ecf537cf4 --- /dev/null +++ b/ottie/fantastic/fantasticapplication.h @@ -0,0 +1,39 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * 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.1 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 . + * + * Authors: Benjamin Otte + */ + +#ifndef __FANTASTIC_APPLICATION_H__ +#define __FANTASTIC_APPLICATION_H__ + +#include + + +#define FANTASTIC_APPLICATION_TYPE (fantastic_application_get_type ()) +#define FANTASTIC_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FANTASTIC_APPLICATION_TYPE, FantasticApplication)) + + +typedef struct _FantasticApplication FantasticApplication; +typedef struct _FantasticApplicationClass FantasticApplicationClass; + + +GType fantastic_application_get_type (void); + +FantasticApplication * fantastic_application_new (void); + + +#endif /* __FANTASTIC_APPLICATION_H__ */ diff --git a/ottie/fantastic/fantasticobserver.c b/ottie/fantastic/fantasticobserver.c new file mode 100644 index 0000000000..d44429611e --- /dev/null +++ b/ottie/fantastic/fantasticobserver.c @@ -0,0 +1,366 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * 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.1 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 . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "fantasticobserver.h" + +#include "ottie/ottieobjectprivate.h" + +#include + +struct _FantasticObserver +{ + OttieRenderObserver parent; + + GskRenderNode *node; + GHashTable *node_to_object; + GSList *objects; +}; + +struct _FantasticObserverClass +{ + OttieRenderObserverClass parent_class; +}; + +G_DEFINE_TYPE (FantasticObserver, fantastic_observer, OTTIE_TYPE_RENDER_OBSERVER) + +static void +fantastic_observer_start (OttieRenderObserver *observer, + OttieRender *render, + double timestamp) +{ + FantasticObserver *self = FANTASTIC_OBSERVER (observer); + + g_clear_pointer (&self->node, gsk_render_node_unref); + g_hash_table_remove_all (self->node_to_object); +} + +static void +fantastic_observer_end (OttieRenderObserver *observer, + OttieRender *render, + GskRenderNode *node) +{ + FantasticObserver *self = FANTASTIC_OBSERVER (observer); + + g_assert (self->objects == NULL); + + self->node = gsk_render_node_ref (node); +} + +static void +fantastic_observer_start_object (OttieRenderObserver *observer, + OttieRender *render, + OttieObject *object, + double timestamp) +{ + FantasticObserver *self = FANTASTIC_OBSERVER (observer); + + self->objects = g_slist_prepend (self->objects, object); +} + +static void +fantastic_observer_end_object (OttieRenderObserver *observer, + OttieRender *render, + OttieObject *object) +{ + FantasticObserver *self = FANTASTIC_OBSERVER (observer); + + self->objects = g_slist_remove (self->objects, object); +} + +static void +fantastic_observer_add_node (OttieRenderObserver *observer, + OttieRender *render, + GskRenderNode *node) +{ + FantasticObserver *self = FANTASTIC_OBSERVER (observer); + + g_hash_table_insert (self->node_to_object, + gsk_render_node_ref (node), + g_object_ref (self->objects->data)); +} + +static void +fantastic_observer_finalize (GObject *object) +{ + FantasticObserver *self = FANTASTIC_OBSERVER (object); + + g_clear_pointer (&self->node, gsk_render_node_unref); + g_hash_table_unref (self->node_to_object); + + G_OBJECT_CLASS (fantastic_observer_parent_class)->finalize (object); +} + +static void +fantastic_observer_class_init (FantasticObserverClass *klass) +{ + OttieRenderObserverClass *observer_class = OTTIE_RENDER_OBSERVER_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = fantastic_observer_finalize; + + observer_class->start = fantastic_observer_start; + observer_class->end = fantastic_observer_end; + observer_class->start_object = fantastic_observer_start_object; + observer_class->end_object = fantastic_observer_end_object; + observer_class->add_node = fantastic_observer_add_node; +} + +static void +fantastic_observer_init (FantasticObserver *self) +{ + self->node_to_object = g_hash_table_new_full (g_direct_hash, g_direct_equal, + (GDestroyNotify) gsk_render_node_unref, + g_object_unref); +} + +FantasticObserver * +fantastic_observer_new (void) +{ + return g_object_new (FANTASTIC_TYPE_OBSERVER, NULL); +} + +static gboolean +transform_point_inverse (GskTransform *self, + const graphene_point_t *point, + graphene_point_t *out_point) +{ + switch (gsk_transform_get_category (self)) + { + case GSK_TRANSFORM_CATEGORY_IDENTITY: + *out_point = *point; + return TRUE; + + case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE: + { + float dx, dy; + + gsk_transform_to_translate (self, &dx, &dy); + out_point->x = point->x - dx; + out_point->y = point->y - dy; + return TRUE; + } + + case GSK_TRANSFORM_CATEGORY_2D_AFFINE: + { + float dx, dy, scale_x, scale_y; + + gsk_transform_to_affine (self, &scale_x, &scale_y, &dx, &dy); + + if (scale_x == 0 || scale_y == 0) + return FALSE; + + out_point->x = (point->x - dx) / scale_x; + out_point->y = (point->y - dy) / scale_y; + return TRUE; + } + + case GSK_TRANSFORM_CATEGORY_2D: + { + float dx, dy, xx, xy, yx, yy; + float det, x, y; + + gsk_transform_to_2d (self, &xx, &yx, &xy, &yy, &dx, &dy); + det = xx * yy - yx * xy; + if (det == 0) + return FALSE; + + x = point->x - dx; + y = point->y - dy; + out_point->x = (x * xx - y * xy) / det; + out_point->y = (y * yy - x * yx) / det; + return TRUE; + } + + case GSK_TRANSFORM_CATEGORY_UNKNOWN: + case GSK_TRANSFORM_CATEGORY_ANY: + case GSK_TRANSFORM_CATEGORY_3D: + default: + g_warning ("FIXME: add 3D support"); + return FALSE; + } +} + +static GskRenderNode * +render_node_pick (FantasticObserver *self, + GskRenderNode *node, + const graphene_point_t *p, + OttieObject **out_object) +{ + GskRenderNode *result = NULL; + graphene_rect_t bounds; + + gsk_render_node_get_bounds (node, &bounds); + if (!graphene_rect_contains_point (&bounds, p)) + return NULL; + + switch (gsk_render_node_get_node_type (node)) + { + case GSK_CONTAINER_NODE: + for (gsize i = gsk_container_node_get_n_children (node); i-- > 0; ) + { + result = render_node_pick (self, gsk_container_node_get_child (node, i), p, out_object); + if (result) + break; + } + break; + + case GSK_CAIRO_NODE: + case GSK_COLOR_NODE: + case GSK_LINEAR_GRADIENT_NODE: + case GSK_REPEATING_LINEAR_GRADIENT_NODE: + case GSK_RADIAL_GRADIENT_NODE: + case GSK_REPEATING_RADIAL_GRADIENT_NODE: + case GSK_CONIC_GRADIENT_NODE: + case GSK_TEXTURE_NODE: + result = node; + break; + + case GSK_BORDER_NODE: + case GSK_INSET_SHADOW_NODE: + case GSK_OUTSET_SHADOW_NODE: + g_assert_not_reached (); + break; + + case GSK_TRANSFORM_NODE: + { + graphene_point_t tp; + if (transform_point_inverse (gsk_transform_node_get_transform (node), p, &tp)) + result = render_node_pick (self, gsk_transform_node_get_child (node), &tp, out_object); + } + break; + + case GSK_OPACITY_NODE: + result = render_node_pick (self, gsk_opacity_node_get_child (node), p, out_object); + break; + + case GSK_COLOR_MATRIX_NODE: + result = render_node_pick (self, gsk_color_matrix_node_get_child (node), p, out_object); + break; + + case GSK_REPEAT_NODE: + { + GskRenderNode *child = gsk_repeat_node_get_child (node); + graphene_point_t tp; + + gsk_render_node_get_bounds (child, &bounds); + tp.x = p->x - bounds.origin.x; + tp.y = p->y - bounds.origin.y; + tp.x = fmod (tp.x, bounds.size.width); + if (tp.x < 0) + tp.x += bounds.size.width; + tp.y = fmod (tp.y, bounds.size.height); + if (tp.y < 0) + tp.y += bounds.size.height; + + return render_node_pick (self, child, &tp, out_object); + } + + case GSK_CLIP_NODE: + result = render_node_pick (self, gsk_clip_node_get_child (node), p, out_object); + break; + + case GSK_ROUNDED_CLIP_NODE: + if (gsk_rounded_rect_contains_point (gsk_rounded_clip_node_get_clip (node), p)) + result = render_node_pick (self, gsk_rounded_clip_node_get_child (node), p, out_object); + break; + + case GSK_FILL_NODE: + { + GskPathMeasure *measure = gsk_path_measure_new (gsk_fill_node_get_path (node)); + gboolean in = gsk_path_measure_in_fill (measure, p, gsk_fill_node_get_fill_rule (node)); + gsk_path_measure_unref (measure); + if (in) + result = render_node_pick (self, gsk_fill_node_get_child (node), p, out_object); + } + break; + + case GSK_STROKE_NODE: + { + GskPathMeasure *measure = gsk_path_measure_new (gsk_stroke_node_get_path (node)); + float line_width = gsk_stroke_get_line_width (gsk_stroke_node_get_stroke (node)); + gboolean in = gsk_path_measure_get_closest_point_full (measure, p, line_width, NULL, NULL, NULL, NULL); + gsk_path_measure_unref (measure); + if (in) + result = render_node_pick (self, gsk_stroke_node_get_child (node), p, out_object); + } + break; + + case GSK_SHADOW_NODE: + case GSK_BLEND_NODE: + case GSK_CROSS_FADE_NODE: + case GSK_TEXT_NODE: + g_assert_not_reached (); + break; + + case GSK_BLUR_NODE: + result = render_node_pick (self, gsk_blur_node_get_child (node), p, out_object); + break; + + case GSK_DEBUG_NODE: + result = render_node_pick (self, gsk_debug_node_get_child (node), p, out_object); + break; + + case GSK_GL_SHADER_NODE: + g_assert_not_reached (); + break; + + case GSK_NOT_A_RENDER_NODE: + default: + g_assert_not_reached (); + break; + } + + if (result && out_object && *out_object == NULL) + *out_object = g_hash_table_lookup (self->node_to_object, node); + + return result; +} + +GskRenderNode * +fantastic_observer_pick_node (FantasticObserver *self, + double x, + double y) +{ + g_return_val_if_fail (FANTASTIC_IS_OBSERVER (self), NULL); + + if (self->node == NULL) + return NULL; + + return render_node_pick (self, self->node, &GRAPHENE_POINT_INIT (x, y), NULL); +} + +OttieObject * +fantastic_observer_pick (FantasticObserver *self, + double x, + double y) +{ + OttieObject *result = NULL; + + g_return_val_if_fail (FANTASTIC_IS_OBSERVER (self), NULL); + + if (self->node == NULL) + return NULL; + + if (!render_node_pick (self, self->node, &GRAPHENE_POINT_INIT (x, y), &result)) + return NULL; + + return result; +} diff --git a/ottie/fantastic/fantasticobserver.h b/ottie/fantastic/fantasticobserver.h new file mode 100644 index 0000000000..cef0c5746b --- /dev/null +++ b/ottie/fantastic/fantasticobserver.h @@ -0,0 +1,50 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * 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.1 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 . + * + * Authors: Benjamin Otte + */ + +#ifndef __FANTASTIC_OBSERVER_PRIVATE_H__ +#define __FANTASTIC_OBSERVER_PRIVATE_H__ + +#include "ottie/ottierenderobserverprivate.h" + +G_BEGIN_DECLS + +#define FANTASTIC_TYPE_OBSERVER (fantastic_observer_get_type ()) +#define FANTASTIC_OBSERVER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), FANTASTIC_TYPE_OBSERVER, FantasticObserver)) +#define FANTASTIC_OBSERVER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), FANTASTIC_TYPE_OBSERVER, FantasticObserverClass)) +#define FANTASTIC_IS_OBSERVER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), FANTASTIC_TYPE_OBSERVER)) +#define FANTASTIC_IS_OBSERVER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), FANTASTIC_TYPE_OBSERVER)) +#define FANTASTIC_OBSERVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), FANTASTIC_TYPE_OBSERVER, FantasticObserverClass)) + +typedef struct _FantasticObserver FantasticObserver; +typedef struct _FantasticObserverClass FantasticObserverClass; + +GType fantastic_observer_get_type (void) G_GNUC_CONST; + +FantasticObserver * fantastic_observer_new (void); + +GskRenderNode * fantastic_observer_pick_node (FantasticObserver *self, + double x, + double y); +OttieObject * fantastic_observer_pick (FantasticObserver *self, + double x, + double y); + +G_END_DECLS + +#endif /* __FANTASTIC_OBSERVER_PRIVATE_H__ */ diff --git a/ottie/fantastic/fantasticwindow.c b/ottie/fantastic/fantasticwindow.c new file mode 100644 index 0000000000..f6eff30e19 --- /dev/null +++ b/ottie/fantastic/fantasticwindow.c @@ -0,0 +1,485 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * 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.1 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 . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "fantasticwindow.h" + +#include "fantasticobserver.h" + +#include "ottie/ottiecreationprivate.h" +#include "ottie/ottiecompositionlayerprivate.h" +#include "ottie/ottiegroupshapeprivate.h" +#include "ottie/ottiepaintableprivate.h" +#include "ottie/ottieshapelayerprivate.h" + +struct _FantasticWindow +{ + GtkApplicationWindow parent; + + GFileMonitor *file_monitor; + + OttieCreation *creation; + OttiePaintable *paintable; + FantasticObserver *observer; + + GtkWidget *picture; + GtkWidget *listview; + GtkSingleSelection *selection; +}; + +struct _FantasticWindowClass +{ + GtkApplicationWindowClass parent_class; +}; + +G_DEFINE_TYPE(FantasticWindow, fantastic_window, GTK_TYPE_APPLICATION_WINDOW); + +static gboolean +load_file_contents (FantasticWindow *self, + GFile *file) +{ + GBytes *bytes; + + bytes = g_file_load_bytes (file, NULL, NULL, NULL); + if (bytes == NULL) + return FALSE; + + if (!g_utf8_validate (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes), NULL)) + { + g_bytes_unref (bytes); + return FALSE; + } + + ottie_creation_load_bytes (self->creation, bytes); +#if 0 + gtk_text_buffer_set_text (self->text_buffer, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes)); +#endif + + g_bytes_unref (bytes); + + return TRUE; +} + +static void +file_changed_cb (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + FantasticWindow *self = user_data; + + if (event_type == G_FILE_MONITOR_EVENT_CHANGED) + load_file_contents (self, file); +} + +void +fantastic_window_load (FantasticWindow *self, + GFile *file) +{ + GError *error = NULL; + + if (!load_file_contents (self, file)) + return; + + g_clear_object (&self->file_monitor); + self->file_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error); + + if (error) + { + g_warning ("couldn't monitor file: %s", error->message); + g_error_free (error); + } + else + { + g_signal_connect (self->file_monitor, "changed", G_CALLBACK (file_changed_cb), self); + } +} + +static void +open_response_cb (GtkWidget *dialog, + int response, + FantasticWindow *self) +{ + gtk_widget_hide (dialog); + + if (response == GTK_RESPONSE_ACCEPT) + { + GFile *file; + + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + fantastic_window_load (self, file); + g_object_unref (file); + } + + gtk_window_destroy (GTK_WINDOW (dialog)); +} + +static void +show_open_filechooser (FantasticWindow *self) +{ + GtkWidget *dialog; + + dialog = gtk_file_chooser_dialog_new ("Open lottie file", + GTK_WINDOW (self), + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Load", GTK_RESPONSE_ACCEPT, + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + + GFile *cwd = g_file_new_for_path ("."); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), cwd, NULL); + g_object_unref (cwd); + + g_signal_connect (dialog, "response", G_CALLBACK (open_response_cb), self); + gtk_widget_show (dialog); +} + +static void +open_cb (GtkWidget *button, + FantasticWindow *self) +{ + show_open_filechooser (self); +} + +static void +save_response_cb (GtkWidget *dialog, + int response, + FantasticWindow *self) +{ + gtk_widget_hide (dialog); + + if (response == GTK_RESPONSE_ACCEPT) + { +#if 0 + GFile *file; + char *text; +#endif + GError *error = NULL; + +#if 0 + text = get_current_text (self->text_buffer); + + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + g_file_replace_contents (file, text, strlen (text), + NULL, FALSE, + G_FILE_CREATE_NONE, + NULL, + NULL, + &error); +#endif + + if (error != NULL) + { + GtkWidget *message_dialog; + + message_dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self))), + GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_OK, + "Saving failed"); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message_dialog), + "%s", error->message); + g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL); + gtk_widget_show (message_dialog); + g_error_free (error); + } + +#if 0 + g_free (text); + g_object_unref (file); +#endif + } + + gtk_window_destroy (GTK_WINDOW (dialog)); +} + +static void +save_cb (GtkWidget *button, + FantasticWindow *self) +{ + GtkWidget *dialog; + + dialog = gtk_file_chooser_dialog_new ("Save file", + GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (button))), + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Save", GTK_RESPONSE_ACCEPT, + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + + GFile *cwd = g_file_new_for_path ("."); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), cwd, NULL); + g_object_unref (cwd); + + g_signal_connect (dialog, "response", G_CALLBACK (save_response_cb), self); + gtk_widget_show (dialog); +} + +static GdkTexture * +create_texture (FantasticWindow *self) +{ + GtkSnapshot *snapshot; + GskRenderer *renderer; + GskRenderNode *node; + GdkTexture *texture; + int width, height; + + width = gdk_paintable_get_intrinsic_width (GDK_PAINTABLE (self->paintable)); + height = gdk_paintable_get_intrinsic_height (GDK_PAINTABLE (self->paintable)); + + if (width <= 0 || height <= 0) + return NULL; + snapshot = gtk_snapshot_new (); + gdk_paintable_snapshot (GDK_PAINTABLE (self->paintable), snapshot, width, height); + node = gtk_snapshot_free_to_node (snapshot); + if (node == NULL) + return NULL; + + renderer = gtk_native_get_renderer (gtk_widget_get_native (GTK_WIDGET (self))); + texture = gsk_renderer_render_texture (renderer, node, NULL); + gsk_render_node_unref (node); + + return texture; +} + +static void +export_image_response_cb (GtkWidget *dialog, + int response, + GdkTexture *texture) +{ + gtk_widget_hide (dialog); + + if (response == GTK_RESPONSE_ACCEPT) + { + GFile *file; + + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + if (!gdk_texture_save_to_png (texture, g_file_peek_path (file))) + { + GtkWidget *message_dialog; + + message_dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_window_get_transient_for (GTK_WINDOW (dialog))), + GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_OK, + "Exporting to image failed"); + g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL); + gtk_widget_show (message_dialog); + } + + g_object_unref (file); + } + + gtk_window_destroy (GTK_WINDOW (dialog)); + g_object_unref (texture); +} + +static void +export_image_cb (GtkWidget *button, + FantasticWindow *self) +{ + GdkTexture *texture; + GtkWidget *dialog; + + texture = create_texture (self); + if (texture == NULL) + return; + + dialog = gtk_file_chooser_dialog_new ("", + GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (button))), + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Save", GTK_RESPONSE_ACCEPT, + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + g_signal_connect (dialog, "response", G_CALLBACK (export_image_response_cb), texture); + gtk_widget_show (dialog); +} + +static GListModel * +create_object_children (gpointer item, + gpointer user_data) +{ + if (OTTIE_IS_COMPOSITION_LAYER (item)) + { + return G_LIST_MODEL (g_object_ref (ottie_composition_layer_get_composition (item))); + } + else if (OTTIE_IS_SHAPE_LAYER (item)) + { + return G_LIST_MODEL (g_object_ref (ottie_shape_layer_get_shape (item))); + } + else if (OTTIE_IS_GROUP_SHAPE (item)) + { + return g_object_ref (item); + } + else + { + return NULL; + } +} + +static void +notify_prepared_cb (OttieCreation *creation, + GParamSpec *pspec, + FantasticWindow *self) +{ + GtkTreeListModel *treemodel; + + if (ottie_creation_is_prepared (creation)) + { + treemodel = gtk_tree_list_model_new (g_object_ref (G_LIST_MODEL (ottie_creation_get_composition (self->creation))), + FALSE, + TRUE, + create_object_children, + NULL, + NULL); + self->selection = gtk_single_selection_new (G_LIST_MODEL (treemodel)); + gtk_list_view_set_model (GTK_LIST_VIEW (self->listview), GTK_SELECTION_MODEL (self->selection)); + } + else + { + g_clear_object (&self->selection); + gtk_list_view_set_model (GTK_LIST_VIEW (self->listview), NULL); + } +} + +static void +fantastic_window_finalize (GObject *object) +{ + FantasticWindow *self = FANTASTIC_WINDOW (object); + + g_object_unref (self->observer); + g_clear_object (&self->selection); + + G_OBJECT_CLASS (fantastic_window_parent_class)->finalize (object); +} + +static void +fantastic_window_select_object (FantasticWindow *self, + OttieObject *object) +{ + for (guint i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->selection)); i++) + { + gpointer tree_item = g_list_model_get_item (G_LIST_MODEL (self->selection), i); + gpointer item = gtk_tree_list_row_get_item (tree_item); + gboolean match; + + match = item == object; + g_object_unref (item); + g_object_unref (tree_item); + + if (match) + { + gtk_single_selection_set_selected (self->selection, i); + break; + } + } +} + +static void +pressed_cb (GtkGestureClick *click, + int n_press, + double x, + double y, + FantasticWindow *self) +{ + GtkPicture *picture; + OttieObject *found; + graphene_rect_t bounds; + + picture = GTK_PICTURE (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (click))); + gtk_picture_get_paintable_bounds (picture, &bounds); + x -= bounds.origin.x; + y -= bounds.origin.y; + x *= ottie_creation_get_width (self->creation) / bounds.size.width; + y *= ottie_creation_get_height (self->creation) / bounds.size.height; + found = fantastic_observer_pick (self->observer, x, y); + if (found) + fantastic_window_select_object (self, found); +} + +static void +fantastic_window_class_init (FantasticWindowClass *class) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = fantastic_window_finalize; + + g_type_ensure (OTTIE_TYPE_CREATION); + g_type_ensure (OTTIE_TYPE_PAINTABLE); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gtk/gtk4/fantastic/fantasticwindow.ui"); + + gtk_widget_class_bind_template_child (widget_class, FantasticWindow, creation); + gtk_widget_class_bind_template_child (widget_class, FantasticWindow, paintable); + gtk_widget_class_bind_template_child (widget_class, FantasticWindow, picture); + gtk_widget_class_bind_template_child (widget_class, FantasticWindow, listview); + + gtk_widget_class_bind_template_callback (widget_class, open_cb); + gtk_widget_class_bind_template_callback (widget_class, save_cb); + gtk_widget_class_bind_template_callback (widget_class, export_image_cb); + gtk_widget_class_bind_template_callback (widget_class, notify_prepared_cb); + gtk_widget_class_bind_template_callback (widget_class, pressed_cb); +} + +static void +window_open (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + FantasticWindow *self = user_data; + + show_open_filechooser (self); +} + +static GActionEntry win_entries[] = { + { "open", window_open, NULL, NULL, NULL }, +}; + +static void +fantastic_window_init (FantasticWindow *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + g_action_map_add_action_entries (G_ACTION_MAP (self), win_entries, G_N_ELEMENTS (win_entries), self); + + self->observer = fantastic_observer_new (); + ottie_paintable_set_observer (self->paintable, OTTIE_RENDER_OBSERVER (self->observer)); +} + +FantasticWindow * +fantastic_window_new (FantasticApplication *application) +{ + return g_object_new (FANTASTIC_WINDOW_TYPE, + "application", application, + NULL); +} diff --git a/ottie/fantastic/fantasticwindow.h b/ottie/fantastic/fantasticwindow.h new file mode 100644 index 0000000000..1aa8338b59 --- /dev/null +++ b/ottie/fantastic/fantasticwindow.h @@ -0,0 +1,42 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * 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.1 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 . + * + * Authors: Benjamin Otte + */ + +#ifndef __FANTASTIC_WINDOW_H__ +#define __FANTASTIC_WINDOW_H__ + +#include + +#include "fantasticapplication.h" + +#define FANTASTIC_WINDOW_TYPE (fantastic_window_get_type ()) +#define FANTASTIC_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FANTASTIC_WINDOW_TYPE, FantasticWindow)) + + +typedef struct _FantasticWindow FantasticWindow; +typedef struct _FantasticWindowClass FantasticWindowClass; + + +GType fantastic_window_get_type (void); + +FantasticWindow * fantastic_window_new (FantasticApplication *application); + +void fantastic_window_load (FantasticWindow *self, + GFile *file); + +#endif /* __FANTASTIC_WINDOW_H__ */ diff --git a/ottie/fantastic/fantasticwindow.ui b/ottie/fantastic/fantasticwindow.ui new file mode 100644 index 0000000000..7315847f6c --- /dev/null +++ b/ottie/fantastic/fantasticwindow.ui @@ -0,0 +1,138 @@ + + + +
+ + _Help + app.help + + + _Inspector + app.inspector + + + _About Fantastic + app.about + +
+
+ + + + + + + creation + + + +
diff --git a/ottie/fantastic/main.c b/ottie/fantastic/main.c new file mode 100644 index 0000000000..86577e50f5 --- /dev/null +++ b/ottie/fantastic/main.c @@ -0,0 +1,28 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * 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.1 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 . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "fantasticapplication.h" + +int +main (int argc, char *argv[]) +{ + return g_application_run (G_APPLICATION (fantastic_application_new ()), argc, argv); +} diff --git a/ottie/fantastic/meson.build b/ottie/fantastic/meson.build new file mode 100644 index 0000000000..9259ead88e --- /dev/null +++ b/ottie/fantastic/meson.build @@ -0,0 +1,32 @@ +fantastic_sources = [ + 'main.c', + 'fantasticapplication.c', + 'fantasticobserver.c', + 'fantasticwindow.c', +] + +fantastic_resources = gnome.compile_resources('fantastic_resources', + 'fantastic.gresource.xml', + source_dir: '.', +) + +executable('fantastic', + sources: [fantastic_sources, fantastic_resources], + dependencies: [libgtk_css_dep, libgdk_dep, libgsk_dep, libgtk_static_dep, libottie_dep], + include_directories: confinc, + c_args: [ + '-DGTK_COMPILATION', + '-DG_LOG_DOMAIN="Fantastic"', + ] + common_cflags, + gui_app: true, + link_with: [libgtk_static, libgtk_css, libgdk, libgsk, libottie], + link_args: common_ldflags, + install: false, +) + +# icons, don't install them until we decide to install fantastic +#icontheme_dir = join_paths(gtk_datadir, 'icons/hicolor') + +#foreach size: ['scalable', 'symbolic'] +# install_subdir('data/' + size, install_dir: icontheme_dir) +#endforeach