]> icculus.org git repositories - taylor/freespace2.git/blob - src/ui/button.cpp
SDL2 port - stage 2
[taylor/freespace2.git] / src / ui / button.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell 
5  * or otherwise commercially exploit the source or things you created based on
6  * the source.
7  */
8
9 /*
10  * $Logfile: /Freespace2/code/UI/BUTTON.cpp $
11  * $Revision$
12  * $Date$
13  * $Author$
14  *
15  * Code for pushbuttons
16  *
17  * $Log$
18  * Revision 1.4  2004/09/20 01:31:45  theoddone33
19  * GCC 3.4 fixes.
20  *
21  * Revision 1.3  2002/06/09 04:41:29  relnev
22  * added copyright header
23  *
24  * Revision 1.2  2002/05/07 03:16:53  theoddone33
25  * The Great Newline Fix
26  *
27  * Revision 1.1.1.1  2002/05/03 03:28:11  root
28  * Initial import.
29  *
30  * 
31  * 9     8/16/99 9:45a Jefff
32  * changes to cursor management to allow a 2nd temporary cursor
33  * 
34  * 8     8/05/99 2:44p Jefff
35  * added disabled callback to UI_BUTTON
36  * 
37  * 7     5/21/99 6:45p Dave
38  * Sped up ui loading a bit. Sped up localization disk access stuff. Multi
39  * start game screen, multi password, and multi pxo-help screen.
40  * 
41  * 6     2/11/99 3:08p Dave
42  * PXO refresh button. Very preliminary squad war support.
43  * 
44  * 5     12/21/98 9:05a Dave
45  * Changed UI code so it doesn't require 3 bitmaps for a 3 state button.
46  * 
47  * 4     12/02/98 5:47p Dave
48  * Put in interface xstr code. Converted barracks screen to new format.
49  * 
50  * 3     10/13/98 9:29a Dave
51  * Started neatening up freespace.h. Many variables renamed and
52  * reorganized. Added AlphaColors.[h,cpp]
53  * 
54  * 2     10/07/98 10:54a Dave
55  * Initial checkin.
56  * 
57  * 1     10/07/98 10:51a Dave
58  * 
59  * 42    5/12/98 11:59p Dave
60  * Put in some more functionality for Parallax Online.
61  * 
62  * 41    5/11/98 5:29p Hoffoss
63  * Added mouse button mapped to joystick button support.
64  * 
65  * 40    5/03/98 1:55a Lawrance
66  * Add function call that forces button code to skip first callback for
67  * highlighing
68  * 
69  * 39    4/22/98 5:19p Lawrance
70  * only strdup strings that are not zero length
71  * 
72  * 38    4/14/98 5:06p Dave
73  * Don't load or send invalid pilot pics. Fixed chatbox graphic errors.
74  * Made chatbox display team icons in a team vs. team game. Fixed up pause
75  * and endgame sequencing issues.
76  * 
77  * 37    4/13/98 4:30p Hoffoss
78  * Fixed a bunch of stupid little bugs in options screen.  Also changed
79  * forced_draw() to work like it used to.
80  * 
81  * 36    4/11/98 7:59p Lawrance
82  * Add support for help overlays
83  * 
84  * 35    4/10/98 4:51p Hoffoss
85  * Made several changes related to tooltips.
86  * 
87  * 34    3/12/98 4:03p Lawrance
88  * Only allow one pressed button at a time
89  * 
90  * 33    2/11/98 6:24p Hoffoss
91  * Fixed bug where disabled and hidden buttons give failed sound when
92  * pressed.  Shouldn't happen when they are hidden.
93  * 
94  * 32    2/06/98 3:36p Hoffoss
95  * Made disabled buttons play failed sound if clicked on.  This is now
96  * standard behavior for all UI buttons everywhere.
97  * 
98  * 31    2/03/98 4:21p Hoffoss
99  * Made UI controls draw white text when disabled.
100  * 
101  * 30    1/26/98 6:28p Lawrance
102  * Add ability to for a button press event externally.
103  * 
104  * 29    1/16/98 4:36p Hoffoss
105  * Fixed hotkey so it doesn't register twice if mouse is also over button.
106  * 
107  * 28    1/15/98 3:07p Hoffoss
108  * Fixed bug where button couldn't be pressed by the mouse now.  Argh.
109  * 
110  * 27    1/15/98 2:59p Hoffoss
111  * Fixed bug with hotkeys not working unless mouse was actually over
112  * button as well.
113  * 
114  * 26    1/15/98 1:58p Hoffoss
115  * Made buttons that don't repeat only trigger when the mouse button goes
116  * up while over the button.
117  * 
118  * 25    1/15/98 11:09a Hoffoss
119  * Embelished this file with nifty comments.
120  * 
121  * 24    1/15/98 10:28a Hoffoss
122  * Fixed ui buttons to handle modified hotkeys (like Shift-arrow) and
123  * fixed bug in debriefing.
124  * 
125  * 23    1/14/98 6:43p Hoffoss
126  * Massive changes to UI code.  A lot cleaner and better now.  Did all
127  * this to get the new UI_DOT_SLIDER to work properly, which the old code
128  * wasn't flexible enough to handle.
129  * 
130  * 22    1/02/98 9:11p Lawrance
131  * Add button_hot() function
132  * 
133  * 21    11/19/97 8:32p Hoffoss
134  * Changed UI buttons so they go back to unpressed when they are disabled.
135  * 
136  * 20    11/16/97 3:19p Hoffoss
137  * Fixed the code style so I could read the damn stuff.
138  * 
139  * 19    10/29/97 7:25p Hoffoss
140  * Added crude support for UI button double click checking.
141  * 
142  * 18    10/01/97 4:39p Lawrance
143  * null out text when free'ed
144  * 
145  * 17    9/30/97 8:50p Lawrance
146  * don't eat keypress when a hotkey is used
147  * 
148  * 16    9/18/97 10:32p Lawrance
149  * fix a bug that was wiping out some flags when reset was called
150  * 
151  * 15    9/07/97 10:05p Lawrance
152  * remove sound refrences in code, use callbacks instead
153  * 
154  * 14    8/30/97 12:23p Lawrance
155  * add button function to reset the status of a button
156  * 
157  * 13    8/26/97 9:23a Lawrance
158  * fix bug with repeatable buttons
159  * 
160  * 12    8/24/97 5:24p Lawrance
161  * improve drawing of buttons 
162  * 
163  * 11    8/18/97 5:28p Lawrance
164  * integrating sounds for when mouse goes over an active control
165  * 
166  * 10    8/17/97 2:42p Lawrance
167  * add code to have selected bitmap for buttons linger for a certain time
168  * 
169  * 9     6/13/97 5:51p Lawrance
170  * add in support for repeating buttons
171  * 
172  * 8     6/12/97 11:09p Lawrance
173  * getting map and text integrated into briefing
174  * 
175  * 7     6/12/97 12:39p John
176  * made ui use freespace colors
177  * 
178  * 6     6/11/97 1:13p John
179  * Started fixing all the text colors in the game.
180  * 
181  * 5     5/22/97 5:36p Lawrance
182  * allowing custom art for scrollbars
183  * 
184  * 4     5/21/97 11:07a Lawrance
185  * integrate masks and custom bitmaps
186  * 
187  * 3     1/28/97 4:58p Lawrance
188  * allowing hidden UI components
189  * 
190  * 2     12/03/96 11:29a John
191  * Made scroll buttons on listbox scroll once, then delay, then repeat
192  * when the buttons are held down.
193  * 
194  * 1     11/14/96 6:55p John
195  *
196  * $NoKeywords: $
197  */
198
199 #include "uidefs.h"
200 #include "ui.h"
201 #include "timer.h"
202 #include "gamesnd.h"
203 #include "alphacolors.h"
204
205 // ---------------------------------------------------------------------------------------
206 // input:
207 //                      do_repeat               =>              property of button, set to 1 to allow pressed events if mouse
208 //                                                                              pointer is held over button with left mouse button down,
209 //                                                                              otherwise 0 (useful for buttons that scroll items)
210 //                      ignore_focus    =>              whether to allow Enter/Spacebar to affect pressed state when
211 //                                                                              control has focus
212 //
213 void UI_BUTTON::create(UI_WINDOW *wnd, const char *_text, int _x, int _y, int _w, int _h, int do_repeat, int ignore_focus)
214 {
215         text = NULL;
216
217         if (_text) {
218                 if ( strlen(_text) > 0 ) {
219                         text = strdup(_text);
220                 }
221         }
222
223         // register gadget with UI window
224         base_create( wnd, UI_KIND_BUTTON, _x, _y, _w, _h );
225
226         // initialize variables
227         m_flags = 0;
228         next_repeat = 0;
229         m_just_highlighted_function = NULL;             // assume there is no callback
230         m_disabled_function = NULL;                             // ditto
231         if (do_repeat) {
232                 m_flags |= BF_REPEATS;
233                 next_repeat = 1;
234         }
235
236         m_press_linger = 1;
237         first_callback = 1;
238
239         hotkey_if_focus = SDLK_SPACE;
240
241         if (ignore_focus){
242                 m_flags |= BF_IGNORE_FOCUS;
243         }
244
245         custom_cursor_bmap = -1;
246         previous_cursor_bmap = -1;
247 };
248
249 void UI_BUTTON::destroy()
250 {
251         if (text) {
252                 free(text);
253                 text = NULL;
254         }
255
256         UI_GADGET::destroy();  // call base as well
257 }
258
259 // sets a hotkey for button that works only when it had focus (or derived focus)
260 void UI_BUTTON::set_hotkey_if_focus(int key)
261 {
262         hotkey_if_focus = key;
263 }
264
265 void UI_BUTTON::reset_status()
266 {
267         m_flags &= ~BF_HIGHLIGHTED;
268         m_flags &= ~BF_HOTKEY_JUST_PRESSED;
269         m_flags &= ~BF_DOWN;
270         m_flags &= ~BF_DOUBLE_CLICKED;
271         m_flags &= ~BF_JUST_HIGHLIGHTED;
272         m_flags &= ~BF_CLICKED;
273 }
274
275 // reset anything that needs to be at the start of a new frame before processing
276 void UI_BUTTON::frame_reset()
277 {
278         m_flags &= ~BF_HIGHLIGHTED;
279         m_flags &= ~BF_HOTKEY_JUST_PRESSED;
280         m_flags &= ~BF_DOWN;
281         m_flags &= ~BF_JUST_PRESSED;
282         m_flags &= ~BF_JUST_RELEASED;
283         m_flags &= ~BF_CLICKED;
284         m_flags &= ~BF_DOUBLE_CLICKED;
285         m_flags &= ~BF_JUST_HIGHLIGHTED;
286
287         restore_previous_cursor();
288 }
289
290 // Force button to draw a specified frame
291 void UI_BUTTON::draw_forced(int frame_num)
292 {
293         if (uses_bmaps) {
294                 if (bmap_ids[frame_num] >= 0) {
295                         gr_set_bitmap(bmap_ids[frame_num], GR_ALPHABLEND_NONE, GR_BITBLT_MODE_NORMAL, 1.0f, -1, -1);
296                         gr_bitmap(x, y);
297                         
298                         // my_wnd->draw_tooltip();
299
300                         // redraw any associated xstr
301                         my_wnd->draw_XSTR_forced(this, frame_num);
302                 }
303         }
304 }
305
306 // Render button.  How it draws exactly depends on it's current state.
307 void UI_BUTTON::draw()
308 {
309         int offset, frame_num = -1;
310
311         if (uses_bmaps) {
312                 gr_reset_clip();
313                 // if button is down, draw it that way
314                 if (button_down()) {
315                         if (bmap_ids[B_PRESSED] >= 0){
316                                 frame_num = B_PRESSED;
317                         }
318                 // otherwise if button is disabled, draw it that way
319                 } else if (disabled_flag) {
320                         if (bmap_ids[B_DISABLED] >= 0){
321                                 frame_num = B_DISABLED;
322                         }
323                 // otherwise, if button is highlighted (mouse is over it, but mouse buttons not down) draw it that way
324                 } else if (m_flags & BF_HIGHLIGHTED) {
325                         if (bmap_ids[B_HIGHLIGHT] >= 0){
326                                 frame_num = B_HIGHLIGHT;
327                         }
328                 // otherwise, just draw it normally
329                 } else {
330                         if (bmap_ids[B_NORMAL] >= 0){
331                                 frame_num = B_NORMAL;
332                         }
333                 }
334
335                 if (frame_num >= 0) {
336                         gr_set_bitmap(bmap_ids[frame_num], GR_ALPHABLEND_NONE, GR_BITBLT_MODE_NORMAL, 1.0f, -1, -1);
337                         gr_bitmap(x, y);
338                 }
339         } else {
340                 gr_set_font(my_wnd->f_id);
341                 gr_set_clip( x, y, w, h );
342
343                 // draw the button's box
344                 if (button_down()) {
345                         ui_draw_box_in( 0, 0, w-1, h-1 );
346                         offset = 1;
347
348                 } else {
349                         ui_draw_box_out( 0, 0, w-1, h-1 );
350                         offset = 0;
351                 }
352
353                 // now draw the button's text
354                 if (disabled_flag){
355                         gr_set_color_fast(&CDARK_GRAY);
356                 } else if (my_wnd->selected_gadget == this){
357                         gr_set_color_fast(&CBRIGHT_GREEN);
358                 } else {
359                         gr_set_color_fast(&CBLACK);
360                 }
361
362                 if (text){
363                         ui_string_centered( Middle(w) + offset, Middle(h) + offset, text );
364                 }
365
366                 gr_reset_clip();
367         }
368 }
369
370 // process() is called to process the button, which amounts to:
371 //   If mouse is over button, hilight it
372 //   If highlighted and mouse button down, flag button as down
373 //   If hotkey pressed, flag button as down
374 //   If hotkey_if_focus pressed, and button has focus, flag button as down
375 //   Set various BF_JUST_* flags if events changed from last frame
376 //
377 void UI_BUTTON::process(int focus)
378 {
379         int mouse_on_me, old_flags;
380
381         old_flags = m_flags;
382         frame_reset();
383
384         // check mouse over control and handle hilighting state
385         mouse_on_me = is_mouse_on();
386
387         // if gadget is disabled, force button up and return
388         if (disabled_flag) {
389                 if (old_flags & BF_DOWN){
390                         m_flags |= BF_JUST_RELEASED;
391                 }
392
393                 if (!hidden && !my_wnd->use_hack_to_get_around_stupid_problem_flag) {
394                         if (mouse_on_me && B1_JUST_PRESSED){
395                                 gamesnd_play_iface(SND_GENERAL_FAIL);
396                         }
397
398                         if ( (hotkey >= 0) && (my_wnd->keypress == hotkey) ){
399                                 gamesnd_play_iface(SND_GENERAL_FAIL);
400                         }
401                 }
402
403                 // do callback if the button is disabled
404                 if (mouse_on_me && B1_JUST_PRESSED){
405                         if (m_disabled_function != NULL) {
406                                 m_disabled_function();
407                         }
408                 }
409
410                 return;
411         }
412
413         // check focus and derived focus with one variable
414         if (my_wnd->selected_gadget == this) {
415                 focus = 1;
416         }
417
418         // show alternate cursor, perhaps?
419         maybe_show_custom_cursor();
420
421         if ( !mouse_on_me ) {
422                 next_repeat = 0;
423         } else {
424                 m_flags |= BF_HIGHLIGHTED;
425                 if ( !(old_flags & BF_HIGHLIGHTED) ) {
426                         int do_callback = 1;
427                         m_flags |= BF_JUST_HIGHLIGHTED;
428                         // if a callback exists, call it
429                         if (m_just_highlighted_function) {
430
431                                 if ( m_flags & BF_SKIP_FIRST_HIGHLIGHT_CALLBACK ) {
432                                         if ( first_callback ) {
433                                                 do_callback = 0;
434                                         }
435                                 }
436
437                                 first_callback = 0;                                             
438                                 if ( do_callback ) {
439                                         m_just_highlighted_function();
440                                 }
441                         }
442                 }
443         }
444
445         // check if mouse is pressed
446         if ( B1_PRESSED && mouse_on_me )        {
447                 m_flags |= BF_DOWN;
448                 capture_mouse();
449         }
450
451         // check if hotkey is down or not
452         if ( (hotkey >= 0) && (my_wnd->keypress == hotkey) ) {
453                 m_flags |= BF_DOWN | BF_CLICKED;
454         }
455
456         // only check for space/enter keystrokes if we are not ignoring the focus (this is the
457         // default behavior)
458         if ( !(m_flags & BF_IGNORE_FOCUS) ) {
459                 if ( focus && (hotkey_if_focus >= 0) ) {
460                         if (my_wnd->keypress == hotkey_if_focus)
461                                 m_flags |= BF_DOWN | BF_CLICKED;
462
463                         if ( (hotkey_if_focus == SDLK_SPACE) && (my_wnd->keypress == SDLK_RETURN) )
464                                 m_flags |= BF_DOWN | BF_CLICKED;
465                 }
466         }
467
468         // handler for button not down
469         if ( !(m_flags & BF_DOWN) ) {
470                 next_repeat = 0;
471                 if ( (old_flags & BF_DOWN) && !(old_flags & BF_CLICKED) )  // check for release of mouse, not hotkey
472                         m_flags |= BF_JUST_RELEASED;
473
474                 // non-repeating buttons behave sort of uniquely..  They activate when released over button
475                 if (!(m_flags & BF_REPEATS)) {
476                         if ( (m_flags & BF_JUST_RELEASED) && (m_flags & BF_HIGHLIGHTED) )
477                                 m_flags |= BF_CLICKED;
478                 }
479
480                 return;
481         }
482
483         // check if button just went down this frame
484         if ( !(old_flags & BF_DOWN) ) {
485                 m_flags |= BF_JUST_PRESSED;
486                 m_press_linger = timestamp(100);
487                 if (user_function)
488                         user_function();
489
490                 if (m_flags & BF_REPEATS) {
491                         next_repeat = timestamp(B_REPEAT_TIME * 3);
492                         m_flags |= BF_CLICKED;
493                 }
494         }
495
496         // check if a repeat event should occur
497         if ( timestamp_elapsed(next_repeat) && (m_flags & BF_REPEATS) ) {
498                 next_repeat = timestamp(B_REPEAT_TIME);
499                 m_flags |= BF_CLICKED;
500                 m_press_linger = timestamp(100);
501         }
502
503         // check for double click occurance
504         if (B1_DOUBLE_CLICKED && mouse_on_me) {
505                 m_flags |= BF_DOUBLE_CLICKED;
506                 m_press_linger = timestamp(100);
507         }
508 }
509
510 // Check if button should do it's function in life (trigger the event)
511 //
512 int UI_BUTTON::pressed()
513 {
514         if (m_flags & BF_CLICKED)
515                 return TRUE;
516
517         return FALSE;
518 }
519
520 int UI_BUTTON::double_clicked()
521 {
522         if ( m_flags & BF_DOUBLE_CLICKED )
523                 return TRUE;
524         else
525                 return FALSE;
526 }
527
528 int UI_BUTTON::just_pressed()
529 {
530         if ( m_flags & BF_JUST_PRESSED )
531                 return TRUE;
532         else
533                 return FALSE;
534 }
535
536 int UI_BUTTON::just_highlighted()
537 {
538         if ( m_flags & BF_JUST_HIGHLIGHTED )
539                 return TRUE;
540         else
541                 return FALSE;
542 }
543
544 // ----------------------------------------------------------------------------------
545 // Checks if button is down (or up).  This checks the button instead, rather than any
546 // events that may have caused it to be down.  Buttons also stay down for a certain amount
547 // of time minimum, to make sure it's long enough for the user to see it has went down (since
548 // one frame is probably far to quick for users to notice it).  Basically, this indicates
549 // how the button is being drawn, if you want to think of it that way.
550 int UI_BUTTON::button_down()
551 {
552         if ( (m_flags & BF_DOWN) || !timestamp_elapsed(m_press_linger) )
553                 return TRUE;
554         else
555                 return FALSE;
556 }
557
558 // ------------------------------------------------------------
559 // set the callback function for when the mouse first goes over
560 // a button
561 //
562 void UI_BUTTON::set_highlight_action( void (*user_function)(void) )
563 {
564         m_just_highlighted_function = user_function;
565 }
566
567 void UI_BUTTON::set_disabled_action( void (*user_function)(void) )
568 {
569         m_disabled_function = user_function;
570 }
571
572
573 // Is the mouse over this button?
574 int UI_BUTTON::button_hilighted()
575 {
576         return m_flags & BF_HIGHLIGHTED;
577 }
578
579 // Is the mouse over this button?
580 void UI_BUTTON::set_button_hilighted()
581 {
582         m_flags |= BF_HIGHLIGHTED;
583 }
584
585 // Force button to get pressed
586 void UI_BUTTON::press_button()
587 {
588         if ( !disabled_flag ) {
589                 m_flags |= BF_DOWN | BF_CLICKED;
590                 //m_flags |= BF_JUST_PRESSED;
591         }
592 }
593                 
594 // reset the "pressed" timestamps
595 void UI_BUTTON::reset_timestamps()
596 {
597         m_press_linger = 1;
598         next_repeat = 0;
599 }
600
601 void UI_BUTTON::skip_first_highlight_callback()
602 {
603         m_flags |= BF_SKIP_FIRST_HIGHLIGHT_CALLBACK;
604         first_callback = 1;
605 }
606
607 void UI_BUTTON::repeatable(int yes)
608 {
609         if(yes){
610                 m_flags |= BF_REPEATS;
611                 next_repeat = 1;
612         } else {
613                 m_flags &= ~(BF_REPEATS);
614                 next_repeat = 0;
615         }
616 }
617
618 void UI_BUTTON::maybe_show_custom_cursor()
619 {
620         if (disabled_flag)
621                 return;
622
623         // set the mouseover cursor 
624         if (is_mouse_on()) {
625                 if ((custom_cursor_bmap >= 0) && (previous_cursor_bmap < 0)) {
626                         previous_cursor_bmap = gr_get_cursor_bitmap();
627                         gr_set_cursor_bitmap(custom_cursor_bmap, GR_CURSOR_LOCK);                       // set and lock
628                 }
629         }
630 }
631
632 void UI_BUTTON::restore_previous_cursor()
633 {
634         if (previous_cursor_bmap >= 0) {
635                 gr_set_cursor_bitmap(previous_cursor_bmap, GR_CURSOR_UNLOCK);           // restore and unlock
636                 previous_cursor_bmap = -1;
637         }
638 }