1 | #include <gtk/gtk.h> |
2 | |
3 | typedef struct |
4 | { |
5 | GtkWidget parent_instance; |
6 | GtkWidget *child; |
7 | float scale; |
8 | float angle; |
9 | } GtkZoom; |
10 | |
11 | typedef struct |
12 | { |
13 | GtkWidgetClass parent_class; |
14 | } GtkZoomClass; |
15 | |
16 | enum { |
17 | PROP_0, |
18 | PROP_CHILD, |
19 | PROP_SCALE, |
20 | PROP_ANGLE, |
21 | NUM_PROPERTIES |
22 | }; |
23 | |
24 | static GParamSpec *props[NUM_PROPERTIES] = { NULL, }; |
25 | |
26 | GType gtk_zoom_get_type (void); |
27 | |
28 | G_DEFINE_TYPE (GtkZoom, gtk_zoom, GTK_TYPE_WIDGET) |
29 | |
30 | static void |
31 | gtk_zoom_init (GtkZoom *zoom) |
32 | { |
33 | zoom->child = NULL; |
34 | zoom->scale = 1.0; |
35 | zoom->angle = 0.0; |
36 | } |
37 | |
38 | static void |
39 | gtk_zoom_dispose (GObject *object) |
40 | { |
41 | GtkZoom *zoom = (GtkZoom *)object; |
42 | |
43 | g_clear_pointer (&zoom->child, gtk_widget_unparent); |
44 | |
45 | G_OBJECT_CLASS (gtk_zoom_parent_class)->dispose (object); |
46 | } |
47 | |
48 | static void |
49 | update_transform (GtkZoom *zoom) |
50 | { |
51 | GtkLayoutManager *manager; |
52 | GtkLayoutChild *child; |
53 | GskTransform *transform; |
54 | int w, h; |
55 | int x, y; |
56 | |
57 | manager = gtk_widget_get_layout_manager (GTK_WIDGET (zoom)); |
58 | child = gtk_layout_manager_get_layout_child (manager, child: zoom->child); |
59 | |
60 | w = gtk_widget_get_width (GTK_WIDGET (zoom)); |
61 | h = gtk_widget_get_height (GTK_WIDGET (zoom)); |
62 | |
63 | x = gtk_widget_get_allocated_width (GTK_WIDGET (zoom->child)); |
64 | y = gtk_widget_get_allocated_height (GTK_WIDGET (zoom->child)); |
65 | |
66 | transform = NULL; |
67 | transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (w/2, h/2)); |
68 | transform = gsk_transform_scale (next: transform, factor_x: zoom->scale, factor_y: zoom->scale); |
69 | transform = gsk_transform_rotate (next: transform, angle: zoom->angle); |
70 | transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (-x/2, -y/2)); |
71 | gtk_fixed_layout_child_set_transform (child: GTK_FIXED_LAYOUT_CHILD (ptr: child), transform); |
72 | gsk_transform_unref (self: transform); |
73 | } |
74 | |
75 | static void |
76 | gtk_zoom_set_scale (GtkZoom *zoom, |
77 | float scale) |
78 | { |
79 | |
80 | if (zoom->scale == scale) |
81 | return; |
82 | |
83 | zoom->scale = scale; |
84 | |
85 | update_transform (zoom); |
86 | |
87 | g_object_notify_by_pspec (G_OBJECT (zoom), pspec: props[PROP_SCALE]); |
88 | |
89 | gtk_widget_queue_resize (GTK_WIDGET (zoom)); |
90 | } |
91 | |
92 | static void |
93 | gtk_zoom_set_angle (GtkZoom *zoom, |
94 | float angle) |
95 | { |
96 | |
97 | if (zoom->angle == angle) |
98 | return; |
99 | |
100 | zoom->angle = angle; |
101 | |
102 | update_transform (zoom); |
103 | |
104 | g_object_notify_by_pspec (G_OBJECT (zoom), pspec: props[PROP_ANGLE]); |
105 | |
106 | gtk_widget_queue_resize (GTK_WIDGET (zoom)); |
107 | } |
108 | |
109 | static void |
110 | gtk_zoom_set_child (GtkZoom *zoom, |
111 | GtkWidget *child) |
112 | { |
113 | g_clear_pointer (&zoom->child, gtk_widget_unparent); |
114 | |
115 | zoom->child = child; |
116 | |
117 | if (zoom->child) |
118 | gtk_widget_set_parent (widget: zoom->child, GTK_WIDGET (zoom)); |
119 | |
120 | update_transform (zoom); |
121 | |
122 | g_object_notify_by_pspec (G_OBJECT (zoom), pspec: props[PROP_CHILD]); |
123 | } |
124 | |
125 | static void |
126 | gtk_zoom_set_property (GObject *object, |
127 | guint prop_id, |
128 | const GValue *value, |
129 | GParamSpec *pspec) |
130 | { |
131 | GtkZoom *zoom = (GtkZoom *)object; |
132 | |
133 | switch (prop_id) |
134 | { |
135 | case PROP_SCALE: |
136 | gtk_zoom_set_scale (zoom, scale: g_value_get_float (value)); |
137 | break; |
138 | |
139 | case PROP_ANGLE: |
140 | gtk_zoom_set_angle (zoom, angle: g_value_get_float (value)); |
141 | break; |
142 | |
143 | case PROP_CHILD: |
144 | gtk_zoom_set_child (zoom, child: g_value_get_object (value)); |
145 | break; |
146 | |
147 | default: |
148 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
149 | break; |
150 | } |
151 | } |
152 | |
153 | static void |
154 | gtk_zoom_get_property (GObject *object, |
155 | guint prop_id, |
156 | GValue *value, |
157 | GParamSpec *pspec) |
158 | { |
159 | GtkZoom *zoom = (GtkZoom *)object; |
160 | |
161 | switch (prop_id) |
162 | { |
163 | case PROP_SCALE: |
164 | g_value_set_float (value, v_float: zoom->scale); |
165 | break; |
166 | |
167 | case PROP_ANGLE: |
168 | g_value_set_float (value, v_float: zoom->angle); |
169 | break; |
170 | |
171 | case PROP_CHILD: |
172 | g_value_set_object (value, v_object: zoom->child); |
173 | break; |
174 | |
175 | default: |
176 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
177 | break; |
178 | } |
179 | } |
180 | |
181 | static void |
182 | gtk_zoom_class_init (GtkZoomClass *class) |
183 | { |
184 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
185 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); |
186 | |
187 | object_class->dispose = gtk_zoom_dispose; |
188 | object_class->set_property = gtk_zoom_set_property; |
189 | object_class->get_property = gtk_zoom_get_property; |
190 | |
191 | props[PROP_SCALE] = g_param_spec_float (name: "scale" , nick: "" , blurb: "" , |
192 | minimum: 0.0, maximum: 100.0, default_value: 1.0, |
193 | flags: G_PARAM_READWRITE); |
194 | |
195 | props[PROP_ANGLE] = g_param_spec_float (name: "angle" , nick: "" , blurb: "" , |
196 | minimum: 0.0, maximum: 360.0, default_value: 1.0, |
197 | flags: G_PARAM_READWRITE); |
198 | |
199 | props[PROP_CHILD] = g_param_spec_object (name: "child" , nick: "" , blurb: "" , |
200 | GTK_TYPE_WIDGET, |
201 | flags: G_PARAM_READWRITE); |
202 | |
203 | g_object_class_install_properties (oclass: object_class, n_pspecs: NUM_PROPERTIES, pspecs: props); |
204 | |
205 | gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_FIXED_LAYOUT); |
206 | } |
207 | |
208 | static GtkWidget * |
209 | gtk_zoom_new (void) |
210 | { |
211 | return g_object_new (object_type: gtk_zoom_get_type (), NULL); |
212 | } |
213 | |
214 | static gboolean |
215 | update_transform_once (GtkWidget *widget, |
216 | GdkFrameClock *frame_clock, |
217 | gpointer data) |
218 | { |
219 | static int count = 0; |
220 | |
221 | update_transform (zoom: (GtkZoom *)widget); |
222 | count++; |
223 | |
224 | if (count == 2) |
225 | return G_SOURCE_REMOVE; |
226 | |
227 | return G_SOURCE_CONTINUE; |
228 | } |
229 | |
230 | int |
231 | main (int argc, char *argv[]) |
232 | { |
233 | GtkWindow *window; |
234 | GtkWidget *zoom; |
235 | GtkWidget *box; |
236 | GtkWidget *grid; |
237 | GtkWidget *scale; |
238 | GtkWidget *angle; |
239 | GtkWidget *child; |
240 | GtkAdjustment *adjustment; |
241 | |
242 | gtk_init (); |
243 | |
244 | window = GTK_WINDOW (gtk_window_new ()); |
245 | gtk_window_set_default_size (GTK_WINDOW (window), width: 600, height: 400); |
246 | box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0); |
247 | gtk_window_set_child (window, child: box); |
248 | |
249 | grid = gtk_grid_new (); |
250 | gtk_grid_set_column_spacing (GTK_GRID (grid), spacing: 10); |
251 | |
252 | scale = gtk_scale_new_with_range (orientation: GTK_ORIENTATION_HORIZONTAL, min: 1.0, max: 10.0, step: 1.0); |
253 | gtk_widget_set_hexpand (widget: scale, TRUE); |
254 | gtk_grid_attach (GTK_GRID (grid), child: gtk_label_new (str: "Scale" ), column: 0, row: 0, width: 1, height: 1); |
255 | gtk_grid_attach (GTK_GRID (grid), child: scale, column: 1, row: 0, width: 1, height: 1); |
256 | |
257 | angle = gtk_scale_new_with_range (orientation: GTK_ORIENTATION_HORIZONTAL, min: 0.0, max: 360.0, step: 1.0); |
258 | gtk_scale_add_mark (GTK_SCALE (angle), value: 90.0, position: GTK_POS_BOTTOM, NULL); |
259 | gtk_scale_add_mark (GTK_SCALE (angle), value: 180.0, position: GTK_POS_BOTTOM, NULL); |
260 | gtk_scale_add_mark (GTK_SCALE (angle), value: 270.0, position: GTK_POS_BOTTOM, NULL); |
261 | gtk_widget_set_hexpand (widget: angle, TRUE); |
262 | gtk_grid_attach (GTK_GRID (grid), child: gtk_label_new (str: "Angle" ), column: 0, row: 1, width: 1, height: 1); |
263 | gtk_grid_attach (GTK_GRID (grid), child: angle, column: 1, row: 1, width: 1, height: 1); |
264 | gtk_box_append (GTK_BOX (box), child: grid); |
265 | |
266 | zoom = gtk_zoom_new (); |
267 | gtk_widget_set_hexpand (widget: zoom, TRUE); |
268 | gtk_widget_set_vexpand (widget: zoom, TRUE); |
269 | gtk_box_append (GTK_BOX (box), child: zoom); |
270 | |
271 | adjustment = gtk_range_get_adjustment (GTK_RANGE (scale)); |
272 | g_object_bind_property (source: adjustment, source_property: "value" , |
273 | target: zoom, target_property: "scale" , |
274 | flags: G_BINDING_DEFAULT); |
275 | |
276 | adjustment = gtk_range_get_adjustment (GTK_RANGE (angle)); |
277 | g_object_bind_property (source: adjustment, source_property: "value" , |
278 | target: zoom, target_property: "angle" , |
279 | flags: G_BINDING_DEFAULT); |
280 | |
281 | if (argc > 1) |
282 | { |
283 | GtkBuilder *builder = gtk_builder_new (); |
284 | gtk_builder_add_from_file (builder, filename: argv[1], NULL); |
285 | child = GTK_WIDGET (gtk_builder_get_object (builder, "child" )); |
286 | gtk_zoom_set_child (zoom: (GtkZoom *)zoom, child); |
287 | g_object_unref (object: builder); |
288 | } |
289 | else |
290 | gtk_zoom_set_child (zoom: (GtkZoom *)zoom, child: gtk_button_new_with_label (label: "Click me!" )); |
291 | |
292 | gtk_window_present (window); |
293 | |
294 | /* HACK to get the transform initally updated */ |
295 | gtk_widget_add_tick_callback (widget: zoom, callback: update_transform_once, NULL, NULL); |
296 | |
297 | while (g_list_model_get_n_items (list: gtk_window_get_toplevels ()) > 0) |
298 | g_main_context_iteration (NULL, TRUE); |
299 | |
300 | return 0; |
301 | } |
302 | |