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 | |
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 |
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 (widget: 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 |
45 | ease_out_cubic (double t) |
46 | { |
47 | double p = t - 1; |
48 | |
49 | return p * p * p + 1; |
50 | } |
51 | |
52 | static gboolean |
53 | update_position (GtkWidget *widget, |
54 | GdkFrameClock *clock, |
55 | gpointer data) |
56 | { |
57 | Demo2Widget *self = DEMO2_WIDGET (ptr: widget); |
58 | Demo2Layout *layout = DEMO2_LAYOUT (ptr: gtk_widget_get_layout_manager (widget)); |
59 | gint64 now; |
60 | double t; |
61 | |
62 | now = gdk_frame_clock_get_frame_time (frame_clock: 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, position: self->start_position + t * (self->end_position - self->start_position)); |
76 | demo2_layout_set_offset (layout, offset: 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 |
83 | rotate_sphere (GtkWidget *widget, |
84 | const char *action, |
85 | GVariant *parameters) |
86 | { |
87 | Demo2Widget *self = DEMO2_WIDGET (ptr: widget); |
88 | Demo2Layout *layout = DEMO2_LAYOUT (ptr: gtk_widget_get_layout_manager (widget)); |
89 | GtkOrientation orientation; |
90 | int direction; |
91 | |
92 | g_variant_get (value: parameters, format_string: "(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, callback: update_position, NULL, NULL); |
106 | self->animating = TRUE; |
107 | } |
108 | } |
109 | |
110 | static void |
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 (widget: child)) |
119 | { |
120 | /* our layout manager sets this for children that are out of view */ |
121 | if (!gtk_widget_get_child_visible (widget: child)) |
122 | continue; |
123 | |
124 | gtk_widget_snapshot_child (widget, child, snapshot); |
125 | } |
126 | } |
127 | |
128 | static void |
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, action_name: "rotate" , parameter_type: "(ii)" , activate: rotate_sphere); |
139 | |
140 | gtk_widget_class_add_binding_action (widget_class, |
141 | GDK_KEY_Left, mods: 0, |
142 | action_name: "rotate" , |
143 | format_string: "(ii)" , GTK_ORIENTATION_HORIZONTAL, -1); |
144 | gtk_widget_class_add_binding_action (widget_class, |
145 | GDK_KEY_Right, mods: 0, |
146 | action_name: "rotate" , |
147 | format_string: "(ii)" , GTK_ORIENTATION_HORIZONTAL, 1); |
148 | gtk_widget_class_add_binding_action (widget_class, |
149 | GDK_KEY_Up, mods: 0, |
150 | action_name: "rotate" , |
151 | format_string: "(ii)" , GTK_ORIENTATION_VERTICAL, 1); |
152 | gtk_widget_class_add_binding_action (widget_class, |
153 | GDK_KEY_Down, mods: 0, |
154 | action_name: "rotate" , |
155 | format_string: "(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 * |
162 | demo2_widget_new (void) |
163 | { |
164 | return g_object_new (DEMO2_TYPE_WIDGET, NULL); |
165 | } |
166 | |
167 | void |
168 | demo2_widget_add_child (Demo2Widget *self, |
169 | GtkWidget *child) |
170 | { |
171 | gtk_widget_set_parent (widget: child, GTK_WIDGET (self)); |
172 | } |
173 | |