2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
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
10 * $Logfile: /Freespace2/code/UI/BUTTON.cpp $
15 * Code for pushbuttons
18 * Revision 1.4 2004/09/20 01:31:45 theoddone33
21 * Revision 1.3 2002/06/09 04:41:29 relnev
22 * added copyright header
24 * Revision 1.2 2002/05/07 03:16:53 theoddone33
25 * The Great Newline Fix
27 * Revision 1.1.1.1 2002/05/03 03:28:11 root
31 * 9 8/16/99 9:45a Jefff
32 * changes to cursor management to allow a 2nd temporary cursor
34 * 8 8/05/99 2:44p Jefff
35 * added disabled callback to UI_BUTTON
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.
41 * 6 2/11/99 3:08p Dave
42 * PXO refresh button. Very preliminary squad war support.
44 * 5 12/21/98 9:05a Dave
45 * Changed UI code so it doesn't require 3 bitmaps for a 3 state button.
47 * 4 12/02/98 5:47p Dave
48 * Put in interface xstr code. Converted barracks screen to new format.
50 * 3 10/13/98 9:29a Dave
51 * Started neatening up freespace.h. Many variables renamed and
52 * reorganized. Added AlphaColors.[h,cpp]
54 * 2 10/07/98 10:54a Dave
57 * 1 10/07/98 10:51a Dave
59 * 42 5/12/98 11:59p Dave
60 * Put in some more functionality for Parallax Online.
62 * 41 5/11/98 5:29p Hoffoss
63 * Added mouse button mapped to joystick button support.
65 * 40 5/03/98 1:55a Lawrance
66 * Add function call that forces button code to skip first callback for
69 * 39 4/22/98 5:19p Lawrance
70 * only strdup strings that are not zero length
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.
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.
81 * 36 4/11/98 7:59p Lawrance
82 * Add support for help overlays
84 * 35 4/10/98 4:51p Hoffoss
85 * Made several changes related to tooltips.
87 * 34 3/12/98 4:03p Lawrance
88 * Only allow one pressed button at a time
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.
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.
98 * 31 2/03/98 4:21p Hoffoss
99 * Made UI controls draw white text when disabled.
101 * 30 1/26/98 6:28p Lawrance
102 * Add ability to for a button press event externally.
104 * 29 1/16/98 4:36p Hoffoss
105 * Fixed hotkey so it doesn't register twice if mouse is also over button.
107 * 28 1/15/98 3:07p Hoffoss
108 * Fixed bug where button couldn't be pressed by the mouse now. Argh.
110 * 27 1/15/98 2:59p Hoffoss
111 * Fixed bug with hotkeys not working unless mouse was actually over
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.
118 * 25 1/15/98 11:09a Hoffoss
119 * Embelished this file with nifty comments.
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.
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.
130 * 22 1/02/98 9:11p Lawrance
131 * Add button_hot() function
133 * 21 11/19/97 8:32p Hoffoss
134 * Changed UI buttons so they go back to unpressed when they are disabled.
136 * 20 11/16/97 3:19p Hoffoss
137 * Fixed the code style so I could read the damn stuff.
139 * 19 10/29/97 7:25p Hoffoss
140 * Added crude support for UI button double click checking.
142 * 18 10/01/97 4:39p Lawrance
143 * null out text when free'ed
145 * 17 9/30/97 8:50p Lawrance
146 * don't eat keypress when a hotkey is used
148 * 16 9/18/97 10:32p Lawrance
149 * fix a bug that was wiping out some flags when reset was called
151 * 15 9/07/97 10:05p Lawrance
152 * remove sound refrences in code, use callbacks instead
154 * 14 8/30/97 12:23p Lawrance
155 * add button function to reset the status of a button
157 * 13 8/26/97 9:23a Lawrance
158 * fix bug with repeatable buttons
160 * 12 8/24/97 5:24p Lawrance
161 * improve drawing of buttons
163 * 11 8/18/97 5:28p Lawrance
164 * integrating sounds for when mouse goes over an active control
166 * 10 8/17/97 2:42p Lawrance
167 * add code to have selected bitmap for buttons linger for a certain time
169 * 9 6/13/97 5:51p Lawrance
170 * add in support for repeating buttons
172 * 8 6/12/97 11:09p Lawrance
173 * getting map and text integrated into briefing
175 * 7 6/12/97 12:39p John
176 * made ui use freespace colors
178 * 6 6/11/97 1:13p John
179 * Started fixing all the text colors in the game.
181 * 5 5/22/97 5:36p Lawrance
182 * allowing custom art for scrollbars
184 * 4 5/21/97 11:07a Lawrance
185 * integrate masks and custom bitmaps
187 * 3 1/28/97 4:58p Lawrance
188 * allowing hidden UI components
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.
194 * 1 11/14/96 6:55p John
203 #include "alphacolors.h"
206 // ---------------------------------------------------------------------------------------
208 // do_repeat => property of button, set to 1 to allow pressed events if mouse
209 // pointer is held over button with left mouse button down,
210 // otherwise 0 (useful for buttons that scroll items)
211 // ignore_focus => whether to allow Enter/Spacebar to affect pressed state when
214 void UI_BUTTON::create(UI_WINDOW *wnd, const char *_text, int _x, int _y, int _w, int _h, int do_repeat, int ignore_focus)
219 if ( strlen(_text) > 0 ) {
220 text = strdup(_text);
224 // register gadget with UI window
225 base_create( wnd, UI_KIND_BUTTON, _x, _y, _w, _h );
227 // initialize variables
230 m_just_highlighted_function = NULL; // assume there is no callback
231 m_disabled_function = NULL; // ditto
233 m_flags |= BF_REPEATS;
240 hotkey_if_focus = SDLK_SPACE;
243 m_flags |= BF_IGNORE_FOCUS;
246 custom_cursor_bmap = -1;
247 previous_cursor_bmap = -1;
250 void UI_BUTTON::destroy()
257 UI_GADGET::destroy(); // call base as well
260 // sets a hotkey for button that works only when it had focus (or derived focus)
261 void UI_BUTTON::set_hotkey_if_focus(int key)
263 hotkey_if_focus = key;
266 void UI_BUTTON::reset_status()
268 m_flags &= ~BF_HIGHLIGHTED;
269 m_flags &= ~BF_HOTKEY_JUST_PRESSED;
271 m_flags &= ~BF_DOUBLE_CLICKED;
272 m_flags &= ~BF_JUST_HIGHLIGHTED;
273 m_flags &= ~BF_CLICKED;
276 // reset anything that needs to be at the start of a new frame before processing
277 void UI_BUTTON::frame_reset()
279 m_flags &= ~BF_HIGHLIGHTED;
280 m_flags &= ~BF_HOTKEY_JUST_PRESSED;
282 m_flags &= ~BF_JUST_PRESSED;
283 m_flags &= ~BF_JUST_RELEASED;
284 m_flags &= ~BF_CLICKED;
285 m_flags &= ~BF_DOUBLE_CLICKED;
286 m_flags &= ~BF_JUST_HIGHLIGHTED;
288 restore_previous_cursor();
291 // Force button to draw a specified frame
292 void UI_BUTTON::draw_forced(int frame_num)
295 if (bmap_ids[frame_num] >= 0) {
296 gr_set_bitmap(bmap_ids[frame_num], GR_ALPHABLEND_NONE, GR_BITBLT_MODE_NORMAL, 1.0f, -1, -1);
299 // my_wnd->draw_tooltip();
301 // redraw any associated xstr
302 my_wnd->draw_XSTR_forced(this, frame_num);
307 // Render button. How it draws exactly depends on it's current state.
308 void UI_BUTTON::draw()
310 int offset, frame_num = -1;
314 // if button is down, draw it that way
316 if (bmap_ids[B_PRESSED] >= 0){
317 frame_num = B_PRESSED;
319 // otherwise if button is disabled, draw it that way
320 } else if (disabled_flag) {
321 if (bmap_ids[B_DISABLED] >= 0){
322 frame_num = B_DISABLED;
324 // otherwise, if button is highlighted (mouse is over it, but mouse buttons not down) draw it that way
325 } else if (m_flags & BF_HIGHLIGHTED) {
326 if (bmap_ids[B_HIGHLIGHT] >= 0){
327 frame_num = B_HIGHLIGHT;
329 // otherwise, just draw it normally
331 if (bmap_ids[B_NORMAL] >= 0){
332 frame_num = B_NORMAL;
336 if (frame_num >= 0) {
337 gr_set_bitmap(bmap_ids[frame_num], GR_ALPHABLEND_NONE, GR_BITBLT_MODE_NORMAL, 1.0f, -1, -1);
341 gr_set_font(my_wnd->f_id);
342 gr_set_clip( x, y, w, h );
344 // draw the button's box
346 ui_draw_box_in( 0, 0, w-1, h-1 );
350 ui_draw_box_out( 0, 0, w-1, h-1 );
354 // now draw the button's text
356 gr_set_color_fast(&CDARK_GRAY);
357 } else if (my_wnd->selected_gadget == this){
358 gr_set_color_fast(&CBRIGHT_GREEN);
360 gr_set_color_fast(&CBLACK);
364 ui_string_centered( Middle(w) + offset, Middle(h) + offset, text );
371 // process() is called to process the button, which amounts to:
372 // If mouse is over button, hilight it
373 // If highlighted and mouse button down, flag button as down
374 // If hotkey pressed, flag button as down
375 // If hotkey_if_focus pressed, and button has focus, flag button as down
376 // Set various BF_JUST_* flags if events changed from last frame
378 void UI_BUTTON::process(int focus)
380 int mouse_on_me, old_flags;
385 // check mouse over control and handle hilighting state
386 mouse_on_me = is_mouse_on();
388 // if gadget is disabled, force button up and return
390 if (old_flags & BF_DOWN){
391 m_flags |= BF_JUST_RELEASED;
394 if (!hidden && !my_wnd->use_hack_to_get_around_stupid_problem_flag) {
395 if (mouse_on_me && B1_JUST_PRESSED){
396 gamesnd_play_iface(SND_GENERAL_FAIL);
399 if ( (hotkey >= 0) && (my_wnd->keypress == hotkey) ){
400 gamesnd_play_iface(SND_GENERAL_FAIL);
404 // do callback if the button is disabled
405 if (mouse_on_me && B1_JUST_PRESSED){
406 if (m_disabled_function != NULL) {
407 m_disabled_function();
414 // check focus and derived focus with one variable
415 if (my_wnd->selected_gadget == this) {
419 // show alternate cursor, perhaps?
420 maybe_show_custom_cursor();
422 if ( !mouse_on_me ) {
425 m_flags |= BF_HIGHLIGHTED;
426 if ( !(old_flags & BF_HIGHLIGHTED) ) {
428 m_flags |= BF_JUST_HIGHLIGHTED;
429 // if a callback exists, call it
430 if (m_just_highlighted_function) {
432 if ( m_flags & BF_SKIP_FIRST_HIGHLIGHT_CALLBACK ) {
433 if ( first_callback ) {
440 m_just_highlighted_function();
446 // check if mouse is pressed
447 if ( B1_PRESSED && mouse_on_me ) {
452 // check if hotkey is down or not
453 if ( (hotkey >= 0) && (my_wnd->keypress == hotkey) ) {
454 m_flags |= BF_DOWN | BF_CLICKED;
457 // only check for space/enter keystrokes if we are not ignoring the focus (this is the
459 if ( !(m_flags & BF_IGNORE_FOCUS) ) {
460 if ( focus && (hotkey_if_focus >= 0) ) {
461 if (my_wnd->keypress == hotkey_if_focus)
462 m_flags |= BF_DOWN | BF_CLICKED;
464 if ( (hotkey_if_focus == SDLK_SPACE) && (my_wnd->keypress == SDLK_RETURN) )
465 m_flags |= BF_DOWN | BF_CLICKED;
469 // handler for button not down
470 if ( !(m_flags & BF_DOWN) ) {
472 if ( (old_flags & BF_DOWN) && !(old_flags & BF_CLICKED) ) // check for release of mouse, not hotkey
473 m_flags |= BF_JUST_RELEASED;
475 // non-repeating buttons behave sort of uniquely.. They activate when released over button
476 if (!(m_flags & BF_REPEATS)) {
477 if ( (m_flags & BF_JUST_RELEASED) && (m_flags & BF_HIGHLIGHTED) )
478 m_flags |= BF_CLICKED;
484 // check if button just went down this frame
485 if ( !(old_flags & BF_DOWN) ) {
486 m_flags |= BF_JUST_PRESSED;
487 m_press_linger = timestamp(100);
491 if (m_flags & BF_REPEATS) {
492 next_repeat = timestamp(B_REPEAT_TIME * 3);
493 m_flags |= BF_CLICKED;
497 // check if a repeat event should occur
498 if ( timestamp_elapsed(next_repeat) && (m_flags & BF_REPEATS) ) {
499 next_repeat = timestamp(B_REPEAT_TIME);
500 m_flags |= BF_CLICKED;
501 m_press_linger = timestamp(100);
504 // check for double click occurance
505 if (B1_DOUBLE_CLICKED && mouse_on_me) {
506 m_flags |= BF_DOUBLE_CLICKED;
507 m_press_linger = timestamp(100);
511 // Check if button should do it's function in life (trigger the event)
513 int UI_BUTTON::pressed()
515 if (m_flags & BF_CLICKED)
521 int UI_BUTTON::double_clicked()
523 if ( m_flags & BF_DOUBLE_CLICKED )
529 int UI_BUTTON::just_pressed()
531 if ( m_flags & BF_JUST_PRESSED )
537 int UI_BUTTON::just_highlighted()
539 if ( m_flags & BF_JUST_HIGHLIGHTED )
545 // ----------------------------------------------------------------------------------
546 // Checks if button is down (or up). This checks the button instead, rather than any
547 // events that may have caused it to be down. Buttons also stay down for a certain amount
548 // of time minimum, to make sure it's long enough for the user to see it has went down (since
549 // one frame is probably far to quick for users to notice it). Basically, this indicates
550 // how the button is being drawn, if you want to think of it that way.
551 int UI_BUTTON::button_down()
553 if ( (m_flags & BF_DOWN) || !timestamp_elapsed(m_press_linger) )
559 // ------------------------------------------------------------
560 // set the callback function for when the mouse first goes over
563 void UI_BUTTON::set_highlight_action( void (*func)(void) )
565 m_just_highlighted_function = func;
568 void UI_BUTTON::set_disabled_action( void (*func)(void) )
570 m_disabled_function = func;
574 // Is the mouse over this button?
575 int UI_BUTTON::button_hilighted()
577 return m_flags & BF_HIGHLIGHTED;
580 // Is the mouse over this button?
581 void UI_BUTTON::set_button_hilighted()
583 m_flags |= BF_HIGHLIGHTED;
586 // Force button to get pressed
587 void UI_BUTTON::press_button()
589 if ( !disabled_flag ) {
590 m_flags |= BF_DOWN | BF_CLICKED;
591 //m_flags |= BF_JUST_PRESSED;
595 // reset the "pressed" timestamps
596 void UI_BUTTON::reset_timestamps()
602 void UI_BUTTON::skip_first_highlight_callback()
604 m_flags |= BF_SKIP_FIRST_HIGHLIGHT_CALLBACK;
608 void UI_BUTTON::repeatable(int yes)
611 m_flags |= BF_REPEATS;
614 m_flags &= ~(BF_REPEATS);
619 void UI_BUTTON::maybe_show_custom_cursor()
624 // set the mouseover cursor
626 if ((custom_cursor_bmap >= 0) && (previous_cursor_bmap < 0)) {
627 previous_cursor_bmap = gr_get_cursor_bitmap();
628 gr_set_cursor_bitmap(custom_cursor_bmap, GR_CURSOR_LOCK); // set and lock
633 void UI_BUTTON::restore_previous_cursor()
635 if (previous_cursor_bmap >= 0) {
636 gr_set_cursor_bitmap(previous_cursor_bmap, GR_CURSOR_UNLOCK); // restore and unlock
637 previous_cursor_bmap = -1;