1 | #include <math.h> |
2 | #include "demo3widget.h" |
3 | |
4 | enum |
5 | { |
6 | PROP_PAINTABLE = 1, |
7 | PROP_SCALE |
8 | }; |
9 | |
10 | struct _Demo3Widget |
11 | { |
12 | GtkWidget parent_instance; |
13 | |
14 | GdkPaintable *paintable; |
15 | float scale; |
16 | |
17 | GtkWidget *; |
18 | }; |
19 | |
20 | struct _Demo3WidgetClass |
21 | { |
22 | GtkWidgetClass parent_class; |
23 | }; |
24 | |
25 | G_DEFINE_TYPE (Demo3Widget, demo3_widget, GTK_TYPE_WIDGET) |
26 | |
27 | static void |
28 | demo3_widget_init (Demo3Widget *self) |
29 | { |
30 | self->scale = 1.f; |
31 | gtk_widget_init_template (GTK_WIDGET (self)); |
32 | } |
33 | |
34 | static void |
35 | demo3_widget_dispose (GObject *object) |
36 | { |
37 | Demo3Widget *self = DEMO3_WIDGET (ptr: object); |
38 | |
39 | g_clear_object (&self->paintable); |
40 | g_clear_pointer (&self->menu, gtk_widget_unparent); |
41 | |
42 | G_OBJECT_CLASS (demo3_widget_parent_class)->dispose (object); |
43 | } |
44 | |
45 | static void |
46 | demo3_widget_snapshot (GtkWidget *widget, |
47 | GtkSnapshot *snapshot) |
48 | { |
49 | Demo3Widget *self = DEMO3_WIDGET (ptr: widget); |
50 | int x, y, width, height; |
51 | double w, h; |
52 | |
53 | width = gtk_widget_get_width (widget); |
54 | height = gtk_widget_get_height (widget); |
55 | |
56 | w = self->scale * gdk_paintable_get_intrinsic_width (paintable: self->paintable); |
57 | h = self->scale * gdk_paintable_get_intrinsic_height (paintable: self->paintable); |
58 | |
59 | x = MAX (0, (width - ceil (w)) / 2); |
60 | y = MAX (0, (height - ceil (h)) / 2); |
61 | |
62 | gtk_snapshot_push_clip (snapshot, bounds: &GRAPHENE_RECT_INIT (0, 0, width, height)); |
63 | gtk_snapshot_save (snapshot); |
64 | gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (x, y)); |
65 | gdk_paintable_snapshot (paintable: self->paintable, snapshot, width: w, height: h); |
66 | gtk_snapshot_restore (snapshot); |
67 | gtk_snapshot_pop (snapshot); |
68 | } |
69 | |
70 | static void |
71 | demo3_widget_measure (GtkWidget *widget, |
72 | GtkOrientation orientation, |
73 | int for_size, |
74 | int *minimum, |
75 | int *natural, |
76 | int *minimum_baseline, |
77 | int *natural_baseline) |
78 | { |
79 | Demo3Widget *self = DEMO3_WIDGET (ptr: widget); |
80 | int size; |
81 | |
82 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
83 | size = gdk_paintable_get_intrinsic_width (paintable: self->paintable); |
84 | else |
85 | size = gdk_paintable_get_intrinsic_height (paintable: self->paintable); |
86 | |
87 | *minimum = *natural = self->scale * size; |
88 | } |
89 | |
90 | static void |
91 | demo3_widget_size_allocate (GtkWidget *widget, |
92 | int width, |
93 | int height, |
94 | int baseline) |
95 | { |
96 | Demo3Widget *self = DEMO3_WIDGET (ptr: widget); |
97 | |
98 | /* Since we are not using a layout manager (who would do this |
99 | * for us), we need to allocate a size for our menu by calling |
100 | * gtk_popover_present(). |
101 | */ |
102 | gtk_popover_present (GTK_POPOVER (self->menu)); |
103 | } |
104 | |
105 | static void |
106 | demo3_widget_set_property (GObject *object, |
107 | guint prop_id, |
108 | const GValue *value, |
109 | GParamSpec *pspec) |
110 | { |
111 | Demo3Widget *self = DEMO3_WIDGET (ptr: object); |
112 | |
113 | switch (prop_id) |
114 | { |
115 | case PROP_PAINTABLE: |
116 | g_clear_object (&self->paintable); |
117 | self->paintable = g_value_dup_object (value); |
118 | gtk_widget_queue_resize (GTK_WIDGET (object)); |
119 | break; |
120 | |
121 | case PROP_SCALE: |
122 | self->scale = g_value_get_float (value); |
123 | gtk_widget_queue_resize (GTK_WIDGET (object)); |
124 | break; |
125 | |
126 | default: |
127 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
128 | break; |
129 | } |
130 | } |
131 | |
132 | static void |
133 | demo3_widget_get_property (GObject *object, |
134 | guint prop_id, |
135 | GValue *value, |
136 | GParamSpec *pspec) |
137 | { |
138 | Demo3Widget *self = DEMO3_WIDGET (ptr: object); |
139 | |
140 | switch (prop_id) |
141 | { |
142 | case PROP_PAINTABLE: |
143 | g_value_set_object (value, v_object: self->paintable); |
144 | break; |
145 | |
146 | case PROP_SCALE: |
147 | g_value_set_float (value, v_float: self->scale); |
148 | break; |
149 | |
150 | default: |
151 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
152 | break; |
153 | } |
154 | } |
155 | |
156 | static void |
157 | pressed_cb (GtkGestureClick *gesture, |
158 | guint n_press, |
159 | double x, |
160 | double y, |
161 | Demo3Widget *self) |
162 | { |
163 | /* We are placing our menu at the point where |
164 | * the click happened, before popping it up. |
165 | */ |
166 | gtk_popover_set_pointing_to (GTK_POPOVER (self->menu), |
167 | rect: &(const GdkRectangle){ x, y, 1, 1 }); |
168 | gtk_popover_popup (GTK_POPOVER (self->menu)); |
169 | } |
170 | |
171 | static void |
172 | zoom_cb (GtkWidget *widget, |
173 | const char *action_name, |
174 | GVariant *parameter) |
175 | { |
176 | Demo3Widget *self = DEMO3_WIDGET (ptr: widget); |
177 | float scale; |
178 | |
179 | if (g_str_equal (v1: action_name, v2: "zoom.in" )) |
180 | scale = MIN (10, self->scale * M_SQRT2); |
181 | else if (g_str_equal (v1: action_name, v2: "zoom.out" )) |
182 | scale = MAX (0.01, self->scale / M_SQRT2); |
183 | else |
184 | scale = 1.0; |
185 | |
186 | gtk_widget_action_set_enabled (widget, action_name: "zoom.in" , enabled: scale < 10); |
187 | gtk_widget_action_set_enabled (widget, action_name: "zoom.out" , enabled: scale > 0.01); |
188 | gtk_widget_action_set_enabled (widget, action_name: "zoom.reset" , enabled: scale != 1); |
189 | |
190 | g_object_set (object: widget, first_property_name: "scale" , scale, NULL); |
191 | } |
192 | |
193 | static void |
194 | demo3_widget_class_init (Demo3WidgetClass *class) |
195 | { |
196 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
197 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); |
198 | |
199 | object_class->dispose = demo3_widget_dispose; |
200 | object_class->set_property = demo3_widget_set_property; |
201 | object_class->get_property = demo3_widget_get_property; |
202 | |
203 | widget_class->snapshot = demo3_widget_snapshot; |
204 | widget_class->measure = demo3_widget_measure; |
205 | widget_class->size_allocate = demo3_widget_size_allocate; |
206 | |
207 | g_object_class_install_property (oclass: object_class, property_id: PROP_PAINTABLE, |
208 | pspec: g_param_spec_object (name: "paintable" , nick: "Paintable" , blurb: "Paintable" , |
209 | GDK_TYPE_PAINTABLE, |
210 | flags: G_PARAM_READWRITE)); |
211 | |
212 | g_object_class_install_property (oclass: object_class, property_id: PROP_SCALE, |
213 | pspec: g_param_spec_float (name: "scale" , nick: "Scale" , blurb: "Scale" , |
214 | minimum: 0.0, maximum: 10.0, default_value: 1.0, |
215 | flags: G_PARAM_READWRITE)); |
216 | |
217 | /* These are the actions that we are using in the menu */ |
218 | gtk_widget_class_install_action (widget_class, action_name: "zoom.in" , NULL, activate: zoom_cb); |
219 | gtk_widget_class_install_action (widget_class, action_name: "zoom.out" , NULL, activate: zoom_cb); |
220 | gtk_widget_class_install_action (widget_class, action_name: "zoom.reset" , NULL, activate: zoom_cb); |
221 | |
222 | gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/menu/demo3widget.ui" ); |
223 | gtk_widget_class_bind_template_child (widget_class, Demo3Widget, menu); |
224 | gtk_widget_class_bind_template_callback (widget_class, pressed_cb); |
225 | } |
226 | |
227 | GtkWidget * |
228 | demo3_widget_new (const char *resource) |
229 | { |
230 | Demo3Widget *self; |
231 | GdkPixbuf *pixbuf; |
232 | GdkPaintable *paintable; |
233 | |
234 | pixbuf = gdk_pixbuf_new_from_resource (resource_path: resource, NULL); |
235 | paintable = GDK_PAINTABLE (ptr: gdk_texture_new_for_pixbuf (pixbuf)); |
236 | |
237 | self = g_object_new (DEMO3_TYPE_WIDGET, first_property_name: "paintable" , paintable, NULL); |
238 | |
239 | g_object_unref (object: pixbuf); |
240 | g_object_unref (object: paintable); |
241 | |
242 | return GTK_WIDGET (self); |
243 | } |
244 | |