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