[gtk/wip/otte/lottie: 2501/2503] Ottie: Add Fantastic, the Ottie Editor




commit ba347c85578cc6f2305f085e2d2a0459b2bf1080
Author: Benjamin Otte <otte redhat com>
Date:   Sat Dec 26 04:00:21 2020 +0100

    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(+)
---
diff --git a/meson.build b/meson.build
index 41328e13dd..0be8599233 100644
--- a/meson.build
+++ b/meson.build
@@ -743,6 +743,7 @@ subdir('ottie')
 subdir('gtk')
 subdir('tools')
 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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gtk/gtk4/fantastic">
+    <file preprocess="xml-stripblanks">fantasticwindow.ui</file>
+  </gresource>
+</gresources>
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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#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] = { "<Ctrl>Q", NULL };
+  const char *open_accels[2] = { "<Ctrl>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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __FANTASTIC_APPLICATION_H__
+#define __FANTASTIC_APPLICATION_H__
+
+#include <gtk/gtk.h>
+
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "fantasticobserver.h"
+
+#include "ottie/ottieobjectprivate.h"
+
+#include <glib/gi18n-lib.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __FANTASTIC_WINDOW_H__
+#define __FANTASTIC_WINDOW_H__
+
+#include <gtk/gtk.h>
+
+#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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <menu id="gear_menu">
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">_Help</attribute>
+        <attribute name="action">app.help</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">_Inspector</attribute>
+        <attribute name="action">app.inspector</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">_About Fantastic</attribute>
+        <attribute name="action">app.about</attribute>
+      </item>
+    </section>
+  </menu>
+
+  <object class="OttieCreation" id="creation">
+    <signal name="notify::prepared" handler="notify_prepared_cb"/>
+  </object>
+
+  <object class="OttiePaintable" id="paintable">
+    <property name="creation">creation</property>
+  </object>
+
+  <template class="FantasticWindow" parent="GtkApplicationWindow">
+    <property name="title" translatable="yes">Fantastic</property>
+    <property name="default-width">1024</property>
+    <property name="default-height">768</property>
+    <child type="titlebar">
+      <object class="GtkHeaderBar" id="header">
+        <child type="start">
+          <object class="GtkButton">
+            <property name="icon-name">document-open-symbolic</property>
+            <property name="tooltip-text">Open file</property>
+            <signal name="clicked" handler="open_cb"/>
+          </object>
+        </child>
+        <child type="start">
+          <object class="GtkButton">
+            <property name="icon-name">document-save-symbolic</property>
+            <property name="tooltip-text">Save</property>
+            <signal name="clicked" handler="save_cb"/>
+          </object>
+        </child>
+        <child type="start">
+          <object class="GtkButton">
+            <property name="icon-name">insert-image-symbolic</property>
+            <property name="tooltip-text">Export to image</property>
+            <signal name="clicked" handler="export_image_cb"/>
+          </object>
+        </child>
+        <child type="end">
+          <object class="GtkMenuButton" id="gear_menu_button">
+            <property name="valign">center</property>
+            <property name="menu-model">gear_menu</property>
+            <property name="icon-name">open-menu-symbolic</property>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkPaned">
+        <property name="shrink-end-child">false</property>
+        <property name="position">400</property>
+        <child>
+          <object class="GtkScrolledWindow">
+            <property name="hexpand">1</property>
+            <property name="vexpand">1</property>
+            <child>
+              <object class="GtkListView" id="listview">
+                <property name="factory">
+                  <object class="GtkBuilderListItemFactory">
+                    <property name="bytes"><![CDATA[
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GtkListItem">
+    <property name="child">
+      <object class="GtkTreeExpander" id="expander">
+        <binding name="list-row">
+          <lookup name="item">GtkListItem</lookup>
+        </binding>
+        <property name="child">
+          <object class="GtkLabel">
+            <property name="xalign">0</property>
+            <binding name="label">
+              <lookup name="name" type="OttieObject">
+                <lookup name="item">expander</lookup>
+              </lookup>
+            </binding>
+          </object>
+        </property>
+      </object>
+    </property>
+  </template>
+</interface>
+                    ]]></property>
+                  </object>
+                </property>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <child>
+              <object class="GtkScrolledWindow">
+                <property name="hexpand">1</property>
+                <property name="vexpand">1</property>
+                <property name="min-content-height">100</property>
+                <property name="min-content-width">100</property>
+                <child>
+                  <object class="GtkViewport">
+                    <child>
+                      <object class="GtkPicture" id="picture">
+                        <property name="can-shrink">0</property>
+                        <property name="paintable">paintable</property>
+                        <property name="halign">center</property>
+                        <property name="valign">center</property>
+                        <child>
+                          <object class="GtkGestureClick">
+                            <signal name="pressed" handler="pressed_cb" swapped="no"/>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#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


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]