1 /*
2 * Copyright (C) 2019 Alexandros Theodotou <alex at zrythm dot org>
3 *
4 * This file is part of ZPlugins
5 *
6 * ZPlugins is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version.
10 *
11 * ZPlugins is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Affero Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23
24 #include "ztoolkit/ztk.h"
25
26 #include "pugl/pugl.h"
27 #include "pugl/pugl_cairo.h"
28
29 static void
on_close(PuglView * view)30 on_close (PuglView* view)
31 {
32 (void) view;
33 }
34
35 static void
on_expose(PuglView * view,const PuglEventExpose * expose)36 on_expose (
37 PuglView* view,
38 const PuglEventExpose * expose)
39 {
40 ZtkApp * self = puglGetHandle (view);
41
42 /** update each widget */
43 ZtkWidget * w = NULL;
44 for (int i = 0; i < self->num_widgets; i++)
45 {
46 w = self->widgets[i];
47 w->update_cb (w, w->user_data);
48 }
49
50 /* reset offsets */
51 self->prev_press_x = self->offset_press_x;
52 self->prev_press_y = self->offset_press_y;
53
54 cairo_t* cr = (cairo_t*)puglGetContext(view);
55
56 #if 0
57 const PuglRect frame = puglGetFrame(view);
58 const double width = frame.width;
59 const double height = frame.height;
60
61 // Scale to view size
62 const double scaleX =
63 (width - (self->width / width)) / self->width;
64 const double scaleY =
65 (height - (self->height / height)) /
66 self->height;
67 cairo_scale(cr, scaleX, scaleY);
68 cairo_stroke (cr);
69 #endif
70
71 ZtkRect rect = {
72 expose->x, expose->y, expose->width,
73 expose->height };
74 ztk_app_draw (
75 self, cr, &rect);
76 }
77
78 static int
is_first_widget_hit(ZtkApp * self,ZtkWidget * widget,double x,double y)79 is_first_widget_hit (
80 ZtkApp * self,
81 ZtkWidget * widget,
82 double x,
83 double y)
84 {
85 for (int i = self->num_widgets - 1; i >= 0; i--)
86 {
87 ZtkWidget * w = self->widgets[i];
88 if (w->visible && ztk_widget_is_hit (w, x, y))
89 {
90 if (widget == w)
91 return 1;
92 else
93 return 0;
94 }
95 }
96
97 return 0;
98 }
99
100 static void
post_event_to_widgets(ZtkApp * self,const PuglEvent * event)101 post_event_to_widgets (
102 ZtkApp * self,
103 const PuglEvent * event)
104 {
105 ZtkWidget * w = NULL;
106
107 /* if any combo box is active:
108 * - if hit, set the flag to ignore other presses
109 * - if not hit, remove it */
110 int combo_box_hit = 0;
111 if (event->type == PUGL_BUTTON_PRESS ||
112 event->type == PUGL_BUTTON_RELEASE)
113 {
114 const PuglEventButton * ev =
115 (const PuglEventButton *) event;
116 for (int i = self->num_widgets - 1;
117 i >= 0; i--)
118 {
119 w = self->widgets[i];
120 if (w->type ==
121 ZTK_WIDGET_TYPE_COMBO_BOX)
122 {
123 if (w->visible &&
124 ztk_widget_is_hit (
125 w, ev->x, ev->y))
126 {
127 combo_box_hit = 1;
128 }
129 else
130 {
131 ztk_app_remove_widget (self, w);
132 }
133 }
134 }
135 }
136
137 for (int i = self->num_widgets - 1; i >= 0; i--)
138 {
139 w = self->widgets[i];
140 switch (event->type)
141 {
142 case PUGL_KEY_PRESS:
143 case PUGL_KEY_RELEASE:
144 {
145 const PuglEventKey * ev =
146 (const PuglEventKey *) event;
147 if (w->visible && w->key_event_cb)
148 {
149 w->key_event_cb (w, ev);
150 w->mod = ev->state;
151 }
152 }
153 break;
154 case PUGL_BUTTON_PRESS:
155 {
156 const PuglEventButton * ev =
157 (const PuglEventButton *) event;
158 if (w->visible &&
159 ztk_widget_is_hit (
160 w, ev->x, ev->y) &&
161 is_first_widget_hit (
162 self, w, ev->x, ev->y))
163 {
164 w->before_last_btn_press = w->last_btn_press;
165 w->last_btn_press = ev->time;
166 w->state |=
167 ZTK_WIDGET_STATE_PRESSED;
168 w->mod = ev->state;
169 if (ev->button == 3)
170 {
171 w->state |=
172 ZTK_WIDGET_STATE_RIGHT_PRESSED;
173 }
174 w->state |=
175 ZTK_WIDGET_STATE_SELECTED;
176 if (w->button_event_cb &&
177 (w->type ==
178 ZTK_WIDGET_TYPE_COMBO_BOX ||
179 !combo_box_hit))
180 {
181 w->button_event_cb (
182 w, ev, w->user_data);
183 }
184 }
185 else
186 {
187 w->state &=
188 (unsigned int)
189 ~ZTK_WIDGET_STATE_SELECTED;
190 }
191 }
192 break;
193 case PUGL_BUTTON_RELEASE:
194 {
195 const PuglEventButton * ev =
196 (const PuglEventButton *) event;
197 w->state &=
198 (unsigned int)
199 ~ZTK_WIDGET_STATE_PRESSED;
200 w->state &=
201 (unsigned int)
202 ~ZTK_WIDGET_STATE_RIGHT_PRESSED;
203 w->mod = ev->state;
204 w->before_last_btn_release =
205 w->last_btn_release;
206 w->last_btn_release = ev->time;
207 if (w->visible &&
208 w->button_event_cb &&
209 (w->type ==
210 ZTK_WIDGET_TYPE_COMBO_BOX ||
211 !combo_box_hit))
212 {
213 w->button_event_cb (
214 w, ev, w->user_data);
215 }
216 }
217 break;
218 case PUGL_MOTION_NOTIFY:
219 {
220 const PuglEventMotion * ev =
221 (const PuglEventMotion *) event;
222 w->mod = ev->state;
223 if (w->visible &&
224 ztk_widget_is_hit (
225 w, ev->x, ev->y) &&
226 is_first_widget_hit (
227 self, w, ev->x, ev->y))
228 {
229 w->state |=
230 ZTK_WIDGET_STATE_HOVERED;
231 if (w->motion_event_cb)
232 {
233 w->motion_event_cb (
234 w, ev, w->user_data);
235 }
236 }
237 else
238 {
239 w->state &=
240 (unsigned int)
241 ~ZTK_WIDGET_STATE_HOVERED;
242 }
243 }
244 break;
245 case PUGL_LEAVE_NOTIFY:
246 {
247 const PuglEventMotion * ev =
248 (const PuglEventMotion *) event;
249 w->mod = ev->state;
250 if (w->state & ZTK_WIDGET_STATE_HOVERED)
251 {
252 w->state &=
253 (unsigned int)
254 ~ZTK_WIDGET_STATE_HOVERED;
255 if (w->visible &&
256 w->motion_event_cb)
257 {
258 w->motion_event_cb (
259 w, ev, w->user_data);
260 }
261 }
262 }
263 break;
264 case PUGL_SCROLL:
265 {
266 const PuglEventScroll * ev =
267 (const PuglEventScroll *) event;
268 w->mod = ev->state;
269 if (w->visible &&
270 ztk_widget_is_hit (
271 w, ev->x, ev->y) &&
272 w->scroll_event_cb)
273 {
274 w->scroll_event_cb (w, ev);
275 }
276 }
277 break;
278 default:
279 break;
280 }
281 }
282 }
283
284 #undef POST_EVENT_FUNC
285
286 static PuglStatus
on_event(PuglView * view,const PuglEvent * event)287 on_event (
288 PuglView * view,
289 const PuglEvent * event)
290 {
291 ZtkApp * self = puglGetHandle (view);
292
293 post_event_to_widgets (self, event);
294
295 switch (event->type)
296 {
297 case PUGL_BUTTON_PRESS:
298 {
299 const PuglEventButton * ev =
300 (const PuglEventButton *) event;
301 self->pressing = 1;
302 self->start_press_x = ev->x;
303 self->start_press_y = ev->y;
304 self->prev_press_x = ev->x;
305 self->prev_press_y = ev->y;
306 self->offset_press_x = ev->x;
307 self->offset_press_y = ev->y;
308 }
309 puglPostRedisplay(view);
310 break;
311 case PUGL_BUTTON_RELEASE:
312 {
313 /*const PuglEventButton * ev =*/
314 /*(const PuglEventButton *) event;*/
315 self->pressing = 0;
316 self->start_press_x = 0;
317 self->start_press_y = 0;
318 self->prev_press_x = 0;
319 self->prev_press_y = 0;
320 self->offset_press_x = 0;
321 self->offset_press_y = 0;
322 }
323 puglPostRedisplay(view);
324 break;
325 case PUGL_MOTION_NOTIFY:
326 {
327 const PuglEventMotion * ev =
328 (const PuglEventMotion *) event;
329 if (self->pressing)
330 {
331 self->prev_press_x =
332 self->offset_press_x;
333 self->prev_press_y =
334 self->offset_press_y;
335 self->offset_press_x = ev->x;
336 self->offset_press_y = ev->y;
337 }
338 }
339 puglPostRedisplay(view);
340 break;
341 case PUGL_ENTER_NOTIFY:
342 case PUGL_LEAVE_NOTIFY:
343 case PUGL_SCROLL:
344 case PUGL_KEY_PRESS:
345 case PUGL_KEY_RELEASE:
346 puglPostRedisplay(view);
347 break;
348 case PUGL_EXPOSE:
349 on_expose (view, &event->expose);
350 break;
351 case PUGL_CLOSE:
352 on_close(view);
353 break;
354 default:
355 puglPostRedisplay(view);
356 break;
357 }
358
359 return PUGL_SUCCESS;
360 }
361
362 /**
363 * Creates a new ZtkApp.
364 *
365 * @param parent Parent window, if any.
366 */
367 ZtkApp *
ztk_app_new(const char * title,void * parent,int width,int height)368 ztk_app_new (
369 const char * title,
370 void* parent,
371 int width,
372 int height)
373 {
374 ZtkApp * self = calloc (1, sizeof (ZtkApp));
375
376 ztk_theme_init (&self->theme);
377
378 self->world = puglNewWorld ();
379 self->title = strdup (title);
380 self->width = width;
381 self->height = height;
382 self->widgets = calloc (1, sizeof (ZtkWidget *));
383 self->widgets_size = 1;
384
385 puglSetClassName (self->world, title);
386 PuglRect frame = { 0, 0, width, height };
387 self->view = puglNewView (self->world);
388 puglSetFrame (self->view, frame);
389 puglSetMinSize (self->view, width, height);
390 puglSetViewHint (
391 self->view, PUGL_RESIZABLE, 0);
392 puglSetBackend (self->view, puglCairoBackend());
393 puglSetHandle (self->view, self);
394 puglSetViewHint (
395 self->view, PUGL_IGNORE_KEY_REPEAT, 1);
396 puglSetEventFunc (self->view, on_event);
397
398 if (parent)
399 {
400 puglSetParentWindow (
401 self->view, (PuglNativeWindow) parent);
402 }
403
404 if (puglCreateWindow (self->view, "Pugl Test"))
405 {
406 printf ("error, can't create window\n");
407 }
408
409 puglShowWindow (self->view);
410
411 return self;
412 }
413
414 static int
cmp_z(const void * a,const void * b)415 cmp_z (
416 const void * a,
417 const void * b)
418 {
419 return
420 (*(ZtkWidget **) a)->z -
421 (*(ZtkWidget **) b)->z;
422 }
423
424 /**
425 * Adds a widget with the given Z axis.
426 */
427 void
ztk_app_add_widget(ZtkApp * self,ZtkWidget * widget,int z)428 ztk_app_add_widget (
429 ZtkApp * self,
430 ZtkWidget * widget,
431 int z)
432 {
433 /* skip if already in app */
434 if (ztk_app_contains_widget (self, widget))
435 {
436 ztk_warning (
437 "Attempted to add widget %p to ZtkApp, "
438 "but the widget is already in ZtkApp",
439 widget);
440 return;
441 }
442
443 if (self->widgets_size == 0)
444 {
445 self->widgets_size = 2;
446 self->widgets =
447 (ZtkWidget **)
448 realloc (
449 self->widgets,
450 (size_t) self->widgets_size *
451 sizeof (ZtkWidget *));
452 }
453 else if (self->num_widgets == self->widgets_size)
454 {
455 self->widgets_size = self->widgets_size * 2;
456 self->widgets =
457 (ZtkWidget **)
458 realloc (
459 self->widgets,
460 (size_t) self->widgets_size *
461 sizeof (ZtkWidget *));
462 }
463 self->widgets[self->num_widgets++] = widget;
464 widget->app = self;
465 widget->z = z;
466
467 /* TODO sort by z */
468 qsort (
469 self->widgets, (size_t) self->num_widgets,
470 sizeof (ZtkWidget *), cmp_z);
471 }
472
473 /**
474 * Removes the given widget from the app.
475 */
476 void
ztk_app_remove_widget(ZtkApp * self,ZtkWidget * widget)477 ztk_app_remove_widget (
478 ZtkApp * self,
479 ZtkWidget * widget)
480 {
481 int match = 0;
482 for (int i = self->num_widgets - 1;
483 i >= 0; i--)
484 {
485 ZtkWidget * w = self->widgets[i];
486 if (w == widget)
487 {
488 match = 1;
489 for (int j = i; j < self->num_widgets - 1;
490 j++)
491 {
492 self->widgets[j] =
493 self->widgets[j + 1];
494 }
495 break;
496 }
497 }
498 if (!match)
499 {
500 ztk_warning (
501 "Tried to remove widget %p from ZtkApp but "
502 "it wasn't found", widget);
503 return;
504 }
505
506 self->num_widgets--;
507 }
508
509 int
ztk_app_contains_widget(ZtkApp * self,ZtkWidget * widget)510 ztk_app_contains_widget (
511 ZtkApp * self,
512 ZtkWidget * widget)
513 {
514 for (int i = 0; i < self->num_widgets; i++)
515 {
516 ZtkWidget * w = self->widgets[i];
517 if (w == widget)
518 return 1;
519 }
520 return 0;
521 }
522
523 /**
524 * Draws each hit widget.
525 */
526 void
ztk_app_draw(ZtkApp * self,cairo_t * cr,ZtkRect * rect)527 ztk_app_draw (
528 ZtkApp * self,
529 cairo_t * cr,
530 ZtkRect * rect)
531 {
532 for (int i = 0; i < self->num_widgets; i++)
533 {
534 ZtkWidget * widget = self->widgets[i];
535 if (!widget->visible ||
536 !ztk_widget_is_hit_by_rect (widget, rect))
537 continue;
538
539 widget->draw_cb (
540 widget, cr, rect, widget->user_data);
541 }
542 }
543
544 void
ztk_app_idle(ZtkApp * self)545 ztk_app_idle (
546 ZtkApp * self)
547 {
548 puglPollEvents (self->world, 0);
549 puglDispatchEvents (self->world);
550 }
551
552 /**
553 * Shows the window.
554 */
555 void
ztk_app_show_window(ZtkApp * self)556 ztk_app_show_window (
557 ZtkApp * self)
558 {
559 puglShowWindow (self->view);
560 }
561
562 /**
563 * Hides the window.
564 */
565 void
ztk_app_hide_window(ZtkApp * self)566 ztk_app_hide_window (
567 ZtkApp * self)
568 {
569 puglHideWindow (self->view);
570 }
571
572 /**
573 * Frees the app.
574 */
575 void
ztk_app_free(ZtkApp * self)576 ztk_app_free (
577 ZtkApp * self)
578 {
579 puglFreeView (self->view);
580 puglFreeWorld (self->world);
581
582 if (self->title)
583 free (self->title);
584
585 free (self);
586 }
587