1 #include "demo2widget.h"
2 #include "demo2layout.h"
3 
4 struct _Demo2Widget
5 {
6   GtkWidget parent_instance;
7 
8   gint64 start_time;
9   gint64 end_time;
10   float start_position;
11   float end_position;
12   float start_offset;
13   float end_offset;
14   gboolean animating;
15 };
16 
17 struct _Demo2WidgetClass
18 {
19   GtkWidgetClass parent_class;
20 };
21 
G_DEFINE_TYPE(Demo2Widget,demo2_widget,GTK_TYPE_WIDGET)22 G_DEFINE_TYPE (Demo2Widget, demo2_widget, GTK_TYPE_WIDGET)
23 
24 static void
25 demo2_widget_init (Demo2Widget *self)
26 {
27   gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
28 }
29 
30 static void
demo2_widget_dispose(GObject * object)31 demo2_widget_dispose (GObject *object)
32 {
33   GtkWidget *child;
34 
35   while ((child = gtk_widget_get_first_child (GTK_WIDGET (object))))
36     gtk_widget_unparent (child);
37 
38   G_OBJECT_CLASS (demo2_widget_parent_class)->dispose (object);
39 }
40 
41 /* From clutter-easing.c, based on Robert Penner's
42  * infamous easing equations, MIT license.
43  */
44 static double
ease_out_cubic(double t)45 ease_out_cubic (double t)
46 {
47   double p = t - 1;
48 
49   return p * p * p + 1;
50 }
51 
52 static gboolean
update_position(GtkWidget * widget,GdkFrameClock * clock,gpointer data)53 update_position (GtkWidget     *widget,
54                  GdkFrameClock *clock,
55                  gpointer       data)
56 {
57   Demo2Widget *self = DEMO2_WIDGET (widget);
58   Demo2Layout *layout = DEMO2_LAYOUT (gtk_widget_get_layout_manager (widget));
59   gint64 now;
60   double t;
61 
62   now = gdk_frame_clock_get_frame_time (clock);
63 
64   if (now >= self->end_time)
65     {
66       self->animating = FALSE;
67 
68       return G_SOURCE_REMOVE;
69     }
70 
71   t = (now - self->start_time) / (double) (self->end_time - self->start_time);
72 
73   t = ease_out_cubic (t);
74 
75   demo2_layout_set_position (layout, self->start_position + t * (self->end_position - self->start_position));
76   demo2_layout_set_offset (layout, self->start_offset + t * (self->end_offset - self->start_offset));
77   gtk_widget_queue_allocate (widget);
78 
79   return G_SOURCE_CONTINUE;
80 }
81 
82 static void
rotate_sphere(GtkWidget * widget,const char * action,GVariant * parameters)83 rotate_sphere (GtkWidget  *widget,
84                const char *action,
85                GVariant   *parameters)
86 {
87   Demo2Widget *self = DEMO2_WIDGET (widget);
88   Demo2Layout *layout = DEMO2_LAYOUT (gtk_widget_get_layout_manager (widget));
89   GtkOrientation orientation;
90   int direction;
91 
92   g_variant_get (parameters, "(ii)", &orientation, &direction);
93 
94   self->end_position = self->start_position = demo2_layout_get_position (layout);
95   self->end_offset = self->start_offset = demo2_layout_get_offset (layout);
96   if (orientation == GTK_ORIENTATION_HORIZONTAL)
97     self->end_position += 10 * direction;
98   else
99     self->end_offset += 10 * direction;
100   self->start_time = g_get_monotonic_time ();
101   self->end_time = self->start_time + 0.5 * G_TIME_SPAN_SECOND;
102 
103   if (!self->animating)
104     {
105       gtk_widget_add_tick_callback (widget, update_position, NULL, NULL);
106       self->animating = TRUE;
107     }
108 }
109 
110 static void
demo2_widget_snapshot(GtkWidget * widget,GtkSnapshot * snapshot)111 demo2_widget_snapshot (GtkWidget   *widget,
112                        GtkSnapshot *snapshot)
113 {
114   GtkWidget *child;
115 
116   for (child = gtk_widget_get_first_child (widget);
117        child != NULL;
118        child = gtk_widget_get_next_sibling (child))
119     {
120       /* our layout manager sets this for children that are out of view */
121       if (!gtk_widget_get_child_visible (child))
122         continue;
123 
124       gtk_widget_snapshot_child (widget, child, snapshot);
125     }
126 }
127 
128 static void
demo2_widget_class_init(Demo2WidgetClass * class)129 demo2_widget_class_init (Demo2WidgetClass *class)
130 {
131   GObjectClass *object_class = G_OBJECT_CLASS (class);
132   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
133 
134   object_class->dispose = demo2_widget_dispose;
135 
136   widget_class->snapshot = demo2_widget_snapshot;
137 
138   gtk_widget_class_install_action (widget_class, "rotate", "(ii)", rotate_sphere);
139 
140   gtk_widget_class_add_binding_action (widget_class,
141                                        GDK_KEY_Left, 0,
142                                        "rotate",
143                                        "(ii)", GTK_ORIENTATION_HORIZONTAL, -1);
144   gtk_widget_class_add_binding_action (widget_class,
145                                        GDK_KEY_Right, 0,
146                                        "rotate",
147                                        "(ii)", GTK_ORIENTATION_HORIZONTAL, 1);
148   gtk_widget_class_add_binding_action (widget_class,
149                                        GDK_KEY_Up, 0,
150                                        "rotate",
151                                        "(ii)", GTK_ORIENTATION_VERTICAL, 1);
152   gtk_widget_class_add_binding_action (widget_class,
153                                        GDK_KEY_Down, 0,
154                                        "rotate",
155                                        "(ii)", GTK_ORIENTATION_VERTICAL, -1);
156 
157   /* here is where we use our custom layout manager */
158   gtk_widget_class_set_layout_manager_type (widget_class, DEMO2_TYPE_LAYOUT);
159 }
160 
161 GtkWidget *
demo2_widget_new(void)162 demo2_widget_new (void)
163 {
164   return g_object_new (DEMO2_TYPE_WIDGET, NULL);
165 }
166 
167 void
demo2_widget_add_child(Demo2Widget * self,GtkWidget * child)168 demo2_widget_add_child (Demo2Widget *self,
169                         GtkWidget   *child)
170 {
171   gtk_widget_set_parent (child, GTK_WIDGET (self));
172 }
173