1 /***************************************************************************
2                           gui_widget.c  -  description
3                              -------------------
4     begin                : Fri Oct 11 2002
5     copyright            : (C) 2002 by Michael Speck
6     email                : kulkanie@gmx.net
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include <stdlib.h>
19 #include <string.h>
20 #include "gui_widget.h"
21 
22 extern GuiTheme *gui_theme;
23 extern SDL_Surface *stk_display;
24 extern List *gui_root_stack;
25 extern List *gui_visible_stack;
26 extern List *gui_timed_stack;
27 
28 GuiWidget *gui_key_widget = 0;
29     /* is the last child widget of _all_ visible key grabbing
30        widgets that was clicked on */
31 GuiWidget *gui_clicked_widget = 0;
32     /* is the last child widget of _all_ visible widgets
33        that was clicked on (used to clear 'pressed' flag) */
34 GuiWidget *gui_focused_widget = 0;
35     /* the very toplevel widget of all visible widgets
36        the mouse pointer hovers above */
37 
38 /*
39 ====================================================================
40 LOCALS
41 ====================================================================
42 */
43 
44 /*
45 ====================================================================
46 Free the memory of a widget and all its subwidgets.
47 ====================================================================
48 */
gui_widget_delete_intern(GuiWidget * widget)49 static void gui_widget_delete_intern( GuiWidget *widget )
50 {
51     GuiWidget *child;
52     if ( widget ) {
53         /* go recursive */
54         list_reset( widget->widgets );
55         while ( ( child = list_next( widget->widgets ) ) )
56             gui_widget_delete_intern( child );
57         /* free common data */
58         list_delete( widget->widgets );
59         stk_surface_free( &widget->surface );
60         /* free special data */
61         widget->default_event_handler(
62             widget, gui_event_get_simple( GUI_DESTROY ) );
63         /* widget itself */
64         free( widget );
65     }
66 }
67 
68 /*
69 ====================================================================
70 Draw the widget and its children. gui_widget_draw() disabled
71 the display monitoring and will add the widgets screen_region
72 as update rectangle.
73 ====================================================================
74 */
gui_widget_draw_intern(GuiWidget * widget)75 static void gui_widget_draw_intern( GuiWidget *widget )
76 {
77     GuiWidget *child = 0;
78     /* draw widget */
79     widget->default_event_handler(
80         widget, gui_event_get_simple( GUI_DRAW ) );
81     /* draw children */
82     list_reset( widget->widgets );
83     while ( ( child = list_next( widget->widgets ) ) )
84         gui_widget_draw_intern( child );
85 }
86 
87 /*
88 ====================================================================
89 Set visible flag to 'visible' for widget and all children
90 recursively.
91 ====================================================================
92 */
gui_widget_show_intern(GuiWidget * widget,int visible)93 static void gui_widget_show_intern( GuiWidget *widget, int visible )
94 {
95     GuiWidget *child;
96     widget->visible = visible;
97     list_reset( widget->widgets );
98     while ( ( child = list_next( widget->widgets ) ) )
99         gui_widget_show_intern( child, visible );
100 }
101 
102 /*
103 ====================================================================
104 If there are any root windows above this widget in the stack that
105 overlap this one they need to be redrawn. We clip to the widgets
106 screen region and redraw all root widgets above.
107 ====================================================================
108 */
gui_widget_redraw_overlapping_roots(GuiWidget * widget)109 static void gui_widget_redraw_overlapping_roots(
110     GuiWidget *widget )
111 {
112     ListEntry *entry;
113     if ( gui_visible_stack->count > 1 )
114     if ( list_last( gui_visible_stack ) != widget->root ) {
115         /* clip */
116         stk_surface_clip( stk_display,
117             widget->screen_region.x, widget->screen_region.y,
118             widget->screen_region.w, widget->screen_region.h );
119         /* get first root widget above */
120         entry = list_entry(
121             gui_visible_stack, widget->root );
122         if ( !entry ) {
123             fprintf( stderr,
124                 "'visible_stack' corrupted: root widget not found\n" );
125             return;
126         }
127         entry = entry->next;
128         /* redraw */
129         while ( entry != gui_visible_stack->tail ) {
130             gui_widget_draw_intern( (GuiWidget*)entry->item );
131             entry = entry->next;
132         }
133         /* unclip */
134         stk_surface_clip( stk_display, 0,0,-1,-1 );
135     }
136 }
137 
138 /*
139 ====================================================================
140 Move position by relative value for all widgets.
141 ====================================================================
142 */
gui_widget_move_intern(GuiWidget * widget,int rel_x,int rel_y)143 static void gui_widget_move_intern(
144     GuiWidget *widget, int rel_x, int rel_y)
145 {
146     GuiWidget *child;
147     widget->screen_region.x += rel_x;
148     widget->screen_region.y += rel_y;
149     widget->parent_region.x += rel_x;
150     widget->parent_region.y += rel_y;
151     list_reset( widget->widgets );
152     while ( ( child = list_next( widget->widgets ) ) )
153         gui_widget_move_intern( child, rel_x, rel_y );
154 }
155 
156 /*
157 ====================================================================
158 PUBLICS
159 ====================================================================
160 */
161 
162 /*
163 ====================================================================
164 GUI events
165 ====================================================================
166 */
167 
168 /*
169 ====================================================================
170 Return pointer to simple event (one that doesn't need
171 additional data)
172 ====================================================================
173 */
174 static GuiEvent aux_event;
gui_event_get_simple(int type)175 GuiEvent *gui_event_get_simple( int type )
176 {
177     aux_event.type = type;
178     return &aux_event;
179 }
180 
181 /*
182 ====================================================================
183 Initiate a GUI event from an SDL event.
184 ====================================================================
185 */
gui_event_init(GuiEvent * gui_event,SDL_Event * sdl_event)186 void gui_event_init( GuiEvent *gui_event, SDL_Event *sdl_event )
187 {
188     int i;
189     memset( gui_event, 0, sizeof( GuiEvent ) );
190     switch ( sdl_event->type ) {
191         case SDL_MOUSEMOTION:
192             gui_event->type = GUI_MOTION;
193             gui_event->motion.x = sdl_event->motion.x;
194             gui_event->motion.y = sdl_event->motion.y;
195             gui_event->motion.xrel = sdl_event->motion.xrel;
196             gui_event->motion.yrel = sdl_event->motion.yrel;
197             gui_event->motion.state = sdl_event->motion.state;
198             for ( i = 1; i <= 3; i++ )
199                 if ( sdl_event->motion.state & SDL_BUTTON(i) ) {
200                     gui_event->motion.button = i;
201                     break;
202                 }
203             break;
204         case SDL_MOUSEBUTTONUP:
205         case SDL_MOUSEBUTTONDOWN:
206             if ( sdl_event->type == SDL_MOUSEBUTTONUP )
207                 gui_event->type = GUI_BUTTON_RELEASED;
208             else
209                 gui_event->type = GUI_BUTTON_PRESSED;
210             gui_event->button.x = sdl_event->button.x;
211             gui_event->button.y = sdl_event->button.y;
212             gui_event->button.button = sdl_event->button.button;
213             break;
214         case SDL_KEYUP:
215         case SDL_KEYDOWN:
216             if ( sdl_event->type == SDL_KEYUP )
217                 gui_event->type = GUI_KEY_RELEASED;
218             else
219                 gui_event->type = GUI_KEY_PRESSED;
220             gui_event->key.keysym = sdl_event->key.keysym.sym;
221             gui_event->key.unicode = sdl_event->key.keysym.unicode;
222             break;
223     }
224 }
225 
226 /*
227 ====================================================================
228 GUI widget
229 ====================================================================
230 */
231 
232 /*
233 ====================================================================
234 Create a basic widget and setup things all different widget types
235 have in common. If a parent is specified this widget is added to
236 it's 'widgets' list. Per default following events are enabled:
237 GUI_ACTIVATED, GUI_DEACTIVATED, GUI_DRAW, GUI_DESTROY,
238 GUI_FOCUS_IN, GUI_FOCUS_OUT. 'x' or 'y' -1 means to center the
239 widget.
240 ====================================================================
241 */
gui_widget_create(GuiWidget * parent,int type,int x,int y,int width,int height,void (* default_event_handler)(GuiWidget *,GuiEvent *),void (* user_event_handler)(GuiWidget *,GuiEvent *))242 GuiWidget* gui_widget_create(
243     GuiWidget *parent, int type,
244     int x, int y, int width, int height,
245     void (*default_event_handler)(GuiWidget*,GuiEvent*),
246     void (*user_event_handler)(GuiWidget*,GuiEvent*) )
247 {
248     GuiWidget *widget = calloc( 1, sizeof( GuiWidget ) );
249     if ( widget == 0 )
250         GUI_ABORT( "Out Of Memory" )
251     /* create empty children list */
252     if ( ( widget->widgets =
253                list_create( LIST_NO_AUTO_DELETE,
254                             LIST_NO_CALLBACK ) ) == 0 )
255         GUI_ABORT( "Out Of Memory" )
256     /* various assignments */
257     if ( parent ) {
258         widget->parent = parent;
259         list_add( widget->parent->widgets, widget );
260     }
261     widget->type = type;
262     widget->root = (parent==0)?widget:parent->root;
263     widget->active = 1;
264     widget->default_event_handler = default_event_handler;
265     widget->user_event_handler = user_event_handler;
266     /* adjust x,y */
267     if ( x == -1 ) {
268         if ( parent )
269             x = ( parent->width - width ) / 2;
270         else
271             x = ( stk_display->w - width ) / 2;
272     }
273     if ( y == -1 ) {
274         if ( parent )
275             y = ( parent->height - height ) / 2;
276         else
277             y = ( stk_display->h - height ) / 2;
278     }
279     /* region in parent */
280     if ( parent == 0 ) {
281         widget->parent_region.x = x;
282         widget->parent_region.y = y;
283     }
284     else {
285         widget->parent_region.x = x + widget->parent->border;
286         widget->parent_region.y = y + widget->parent->border;
287     }
288     widget->parent_region.w = width;
289     widget->parent_region.h = height;
290     /* screen region */
291     if ( widget->parent == 0 )
292         widget->screen_region = widget->parent_region;
293     else {
294         widget->screen_region.x =
295             widget->parent_region.x + parent->screen_region.x;
296         widget->screen_region.y =
297             widget->parent_region.y + parent->screen_region.y;
298         widget->screen_region.w = widget->parent_region.w;
299         widget->screen_region.h = widget->parent_region.h;
300     }
301     /* children size */
302     widget->width = width; widget->height = height;
303     /* events */
304     gui_widget_enable_event( widget, GUI_ACTIVATED );
305     gui_widget_enable_event( widget, GUI_DEACTIVATED );
306     gui_widget_enable_event( widget, GUI_DRAW );
307     gui_widget_enable_event( widget, GUI_DESTROY );
308     gui_widget_enable_event( widget, GUI_FOCUS_IN );
309     gui_widget_enable_event( widget, GUI_FOCUS_OUT );
310     /* if this is a root widget add it to root_stack */
311     if ( widget->root == widget )
312         list_add( gui_root_stack, widget );
313     /* done */
314     return widget;
315 }
316 
317 /*
318 ====================================================================
319 This function will delete a root widget including all subwidgets.
320 Subwidgets can't be directly deleted. Resets the widget
321 pointer to NULL.
322 ====================================================================
323 */
gui_widget_delete(GuiWidget ** widget)324 void gui_widget_delete( GuiWidget **widget )
325 {
326     if ( *widget == 0 ) return;
327     if ( (*widget)->root != *widget ) {
328         fprintf( stderr, "You can only delete root widgets!\n" );
329         return;
330     }
331     if ( (*widget)->visible )
332         list_delete_item( gui_visible_stack, *widget );
333     if ( (*widget)->event_mask & GUI_TIME_PASSED )
334         list_delete_item( gui_timed_stack, *widget );
335     list_delete_item( gui_root_stack, *widget );
336     gui_widget_delete_intern( *widget );
337     *widget = 0;
338 }
339 
340 
341 /*
342 ====================================================================
343 If button is deactivated no input events (key,button,motion)
344 are handled.
345 ====================================================================
346 */
gui_widget_set_active(GuiWidget * widget,int active)347 void gui_widget_set_active( GuiWidget *widget, int active )
348 {
349     int type;
350     if ( widget->active == active ) return;
351     widget->active = active;
352     type = (active)?GUI_ACTIVATED:GUI_DEACTIVATED;
353     widget->default_event_handler(
354         widget, gui_event_get_simple( type ) );
355     gui_widget_call_user_event_handler(
356         widget, gui_event_get_simple( type ) );
357 }
358 
359 /*
360 ====================================================================
361 Draw the widget and its children if visible.
362 ====================================================================
363 */
gui_widget_draw(GuiWidget * widget)364 void gui_widget_draw( GuiWidget *widget )
365 {
366     /* update only if visible */
367     if ( !widget->visible ) return;
368     /* recursively draw widget */
369     gui_widget_draw_intern( widget );
370     /* redraw higher level roots that overlap this widget. */
371     gui_widget_redraw_overlapping_roots( widget );
372     /* store update rect */
373     stk_display_store_rect( &widget->screen_region );
374 }
375 
376 /*
377 ====================================================================
378 Set 'visible' flag and draw widget (store update rects)
379 if either parent is visible or it has no parent.
380 (thus is a root window). If it is a root window add it to the
381 root window stack. This new window will handle incoming events
382 first.
383 ====================================================================
384 */
gui_widget_show(GuiWidget * widget)385 void gui_widget_show( GuiWidget *widget )
386 {
387     if ( widget->visible ) return;
388     if ( widget->root == widget ) {
389         list_add( gui_visible_stack, widget );
390         if ( widget->event_mask & GUI_TIME_PASSED )
391             list_add( gui_timed_stack, widget );
392 #ifdef GUI_DEBUG
393         printf( "show root widget: %i,%i,%i,%i\n",
394                 widget->screen_region.x, widget->screen_region.y,
395                 widget->screen_region.w, widget->screen_region.h );
396 #endif
397         /* if this root widget grabs the input the old
398            gui_key_widget/gui_clicked_widget is obsolete */
399         if ( widget->grab_input ) {
400             if ( gui_key_widget ) {
401                 gui_key_widget->default_event_handler(
402                     gui_key_widget,
403                     gui_event_get_simple( GUI_FOCUS_OUT ) );
404                 gui_key_widget = 0;
405             }
406             gui_clicked_widget = 0;
407         }
408         /* maybe there is a default key grab widget? */
409         if ( widget->default_key_widget )
410             gui_key_widget = widget->default_key_widget;
411     }
412     gui_widget_show_intern( widget, 1 );
413     if ( widget->parent == 0 || widget->parent->visible )
414         gui_widget_draw( widget );
415 }
416 
417 /*
418 ====================================================================
419 Clear 'visible' flag and restore widget if parent is visible.
420 If there is no parent (thus is a root window) remove it from
421 stack and redraw the underlying window (which regains control). If
422 a root widget is hidden the background cannot be restored
423 as it is unknown.
424 ====================================================================
425 */
gui_widget_hide(GuiWidget * widget)426 void gui_widget_hide( GuiWidget *widget )
427 {
428     ListEntry *entry;
429     if ( !widget->visible ) return;
430     gui_widget_show_intern( widget, 0 );
431     if ( widget->root == widget ) {
432         entry = list_entry( gui_visible_stack, widget );
433         if ( entry ) {
434             /* remove */
435             entry = entry->next;
436             list_delete_entry(
437                 gui_visible_stack, entry->prev );
438             /* FIX ME! redraw all open roots */
439             entry = gui_visible_stack->head->next;
440             while ( entry != gui_visible_stack->tail ) {
441                 gui_widget_draw( (GuiWidget*)entry->item );
442                 entry = entry->next;
443             }
444         }
445         if ( widget->event_mask & GUI_TIME_PASSED )
446             list_delete_item( gui_timed_stack, widget );
447     }
448     else {
449         /* draw parent's background */
450         stk_surface_blit( widget->parent->surface,
451             widget->parent_region.x, widget->parent_region.y,
452             widget->parent_region.w, widget->parent_region.h,
453             stk_display,
454             widget->screen_region.x, widget->screen_region.y );
455         /* redraw higher level roots that overlap this widget. */
456         gui_widget_redraw_overlapping_roots( widget );
457         /* store update rect */
458         stk_display_store_rect( &widget->screen_region );
459     }
460     /* check if gui_key_widget is still valid */
461     if ( gui_key_widget )
462     if ( widget == gui_key_widget ||
463          widget == gui_key_widget->parent ||
464          widget == gui_key_widget->root )
465         gui_key_widget = 0;
466 }
467 
468 /*
469 ====================================================================
470 Modify the event mask of a widget to define which events will
471 be passed to user_event_handler.
472 ====================================================================
473 */
gui_widget_enable_event(GuiWidget * widget,int event)474 void gui_widget_enable_event( GuiWidget *widget, int event )
475 {
476     widget->event_mask |= (1L << event);
477 }
gui_widget_disable_event(GuiWidget * widget,int event)478 void gui_widget_disable_event( GuiWidget *widget, int event )
479 {
480     widget->event_mask &= ~(1L << event);
481 }
482 
483 /*
484 ====================================================================
485 Pass GuiEvent to user defined callback if it has been installed
486 and the event mask flag is True for this event.
487 ====================================================================
488 */
gui_widget_call_user_event_handler(GuiWidget * widget,GuiEvent * event)489 void gui_widget_call_user_event_handler(
490     GuiWidget *widget, GuiEvent *event )
491 {
492     if ( widget->user_event_handler )
493         if ( widget->event_mask & (1L << event->type) )
494             widget->user_event_handler( widget, event );
495 }
496 
497 /*
498 ====================================================================
499 Handle the GUI event by calling the default_event_handler()
500 and the user_event_handler() if one has been installed.
501 ====================================================================
502 */
gui_widget_handle_event(GuiWidget * widget,GuiEvent * event)503 void gui_widget_handle_event( GuiWidget *widget, GuiEvent *event )
504 {
505     widget->default_event_handler( widget, event );
506     gui_widget_call_user_event_handler( widget, event );
507 }
508 
509 /*
510 ====================================================================
511 Move widget within parent window by a relative value. If the
512 widget is visible the changes will be drawn to screen.
513 ====================================================================
514 */
gui_widget_move(GuiWidget * widget,int rel_x,int rel_y)515 void gui_widget_move( GuiWidget *widget, int rel_x, int rel_y )
516 {
517     if ( !widget->visible )
518         gui_widget_move_intern( widget, rel_x, rel_y );
519     else {
520         gui_widget_hide( widget );
521         gui_widget_move_intern( widget, rel_x, rel_y );
522         gui_widget_show( widget );
523     }
524 }
525 
526 /*
527 ====================================================================
528 Move widget within parent window by an absolute value. If the
529 widget is visible the changes will be drawn to screen.
530 ====================================================================
531 */
gui_widget_warp(GuiWidget * widget,int abs_x,int abs_y)532 void gui_widget_warp( GuiWidget *widget, int abs_x, int abs_y )
533 {
534     if ( widget->parent )
535         gui_widget_move( widget,
536             abs_x - widget->parent_region.x +
537                 widget->parent->border,
538             abs_y - widget->parent_region.y +
539                 widget->parent->border );
540     else
541         gui_widget_move( widget,
542             abs_x - widget->parent_region.x,
543             abs_y - widget->parent_region.y );
544 }
545 
546 /*
547 ====================================================================
548 Apply parents background or wallpaper within the frame (if
549 any) of the widget's surface keeping the frame.
550 ====================================================================
551 */
gui_widget_apply_wallpaper(GuiWidget * widget,SDL_Surface * wallpaper,int alpha)552 void gui_widget_apply_wallpaper(
553     GuiWidget *widget, SDL_Surface *wallpaper, int alpha )
554 {
555     if ( widget->parent )
556         stk_surface_blit( widget->parent->surface,
557                           widget->parent_region.x +
558                           widget->border,
559                           widget->parent_region.y +
560                           widget->border,
561                           widget->parent_region.w -
562                           (widget->border<<1),
563                           widget->parent_region.h -
564                           (widget->border<<1),
565                           widget->surface,
566                           widget->border, widget->border );
567     else
568         stk_surface_apply_wallpaper(
569             widget->surface, widget->border, widget->border,
570             widget->parent_region.w - (widget->border<<1),
571             widget->parent_region.h - (widget->border<<1),
572             wallpaper, alpha );
573 }
574 
575 /*
576 ====================================================================
577 Browse the widget tree and set 'focused' true for all widgets
578 that have the mouse pointer above them. 'focused_widget'
579 returns the deepest widget that is focused.
580 ====================================================================
581 */
gui_widget_update_focus(GuiWidget * widget,int mx,int my,GuiWidget ** focused_widget)582 void gui_widget_update_focus(
583     GuiWidget *widget, int mx, int my, GuiWidget **focused_widget )
584 {
585     GuiWidget *child;
586     if ( !widget->active )
587         return;
588     if ( !STK_IN_RECT( widget->screen_region, mx, my ) )
589         return;
590     widget->focused = 1;
591     *focused_widget = widget;
592     /* handle children recursively */
593     list_reset( widget->widgets );
594     while ( ( child = list_next( widget->widgets ) ) )
595         gui_widget_update_focus( child, mx, my, focused_widget );
596 }
597 
598 /*
599 ====================================================================
600 Get direct access to widget's surface.
601 ====================================================================
602 */
gui_widget_get_surface(GuiWidget * widget)603 SDL_Surface *gui_widget_get_surface( GuiWidget *widget )
604 {
605     return widget->surface;
606 }
607 
608 /*
609 ====================================================================
610 That key grabbing child of a root widget.
611 ====================================================================
612 */
gui_widget_set_default_key_widget(GuiWidget * root,GuiWidget * key_widget)613 void gui_widget_set_default_key_widget(
614     GuiWidget *root, GuiWidget *key_widget )
615 {
616     if ( root->root != root ) return;
617     root->default_key_widget = key_widget;
618 }
619