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/GADGET.cpp $
15 * Functions for the base gadget class
18 * Revision 1.8 2005/10/01 21:55:00 taylor
19 * fix a small bug in UI_GADGET that could leave a control animation loaded in memory without a way to unload it
20 * allow a slider with no defined hotspot to function properly (fixes the FS1 skill slider mask problem)
22 * Revision 1.7 2003/05/25 02:30:44 taylor
25 * Revision 1.6 2002/06/09 04:41:29 relnev
26 * added copyright header
28 * Revision 1.5 2002/05/28 21:03:38 relnev
31 * Revision 1.4 2002/05/26 20:22:48 theoddone33
32 * Most of network/ works
34 * Revision 1.3 2002/05/07 03:16:53 theoddone33
35 * The Great Newline Fix
37 * Revision 1.2 2002/05/04 04:36:56 theoddone33
38 * More changes, took out a lot of the sound stuff which will bite later but
41 * Revision 1.1.1.1 2002/05/03 03:28:11 root
45 * 18 8/11/99 3:21p Jefff
46 * set_bmaps clarification by daveb
48 * 17 8/10/99 6:54p Dave
49 * Mad optimizations. Added paging to the nebula effect.
51 * 16 7/16/99 1:50p Dave
52 * 8 bit aabitmaps. yay.
54 * 15 6/25/99 11:59a Dave
55 * Multi options screen.
57 * 14 5/21/99 6:45p Dave
58 * Sped up ui loading a bit. Sped up localization disk access stuff. Multi
59 * start game screen, multi password, and multi pxo-help screen.
61 * 13 5/03/99 8:33p Dave
62 * New version of multi host options screen.
64 * 12 4/16/99 5:23p Neilk
65 * Removed bitmap mprintf's to make debugging faster
67 * 11 2/11/99 3:08p Dave
68 * PXO refresh button. Very preliminary squad war support.
70 * 10 2/05/99 4:09p Anoop
71 * Hopefully the last fix for this file :)
73 * 9 2/05/99 3:26p Enricco
74 * Oops. Some .ani's can have 4 frames.
76 * 8 2/05/99 3:07p Davidg
77 * Fixed braindead bmap setting code.
79 * 7 2/01/99 5:55p Dave
80 * Removed the idea of explicit bitmaps for buttons. Fixed text
81 * highlighting for disabled gadgets.
83 * 6 12/21/98 9:05a Dave
84 * Changed UI code so it doesn't require 3 bitmaps for a 3 state button.
86 * 5 12/18/98 1:13a Dave
87 * Rough 1024x768 support for Direct3D. Proper detection and usage through
90 * 4 12/02/98 5:47p Dave
91 * Put in interface xstr code. Converted barracks screen to new format.
93 * 3 11/30/98 1:07p Dave
94 * 16 bit conversion, first run.
96 * 2 10/07/98 10:54a Dave
99 * 1 10/07/98 10:51a Dave
101 * 22 5/11/98 5:29p Hoffoss
102 * Added mouse button mapped to joystick button support.
104 * 21 4/12/98 2:09p Dave
105 * Make main hall door text less stupid. Make sure inputbox focus in the
106 * multi host options screen is managed more intelligently.
108 * 20 4/09/98 7:14p Hoffoss
109 * Did some cool changes for tooltips.
111 * 19 2/11/98 6:24p Hoffoss
112 * Fixed bug where disabled and hidden buttons give failed sound when
113 * pressed. Shouldn't happen when they are hidden.
115 * 18 1/15/98 11:54a Hoffoss
116 * Embelished file with nifty comments.
118 * 17 1/14/98 6:43p Hoffoss
119 * Massive changes to UI code. A lot cleaner and better now. Did all
120 * this to get the new UI_DOT_SLIDER to work properly, which the old code
121 * wasn't flexible enough to handle.
123 * 16 12/30/97 4:26p Lawrance
124 * Disable right-click movement of ui controls
126 * 15 10/24/97 10:58p Hoffoss
127 * Made some changes to the UI code to do some things I need it to do.
129 * 14 10/12/97 3:45p Lawrance
130 * only allow movement of UI controls in debug
132 * 13 10/09/97 4:56p Lawrance
133 * init GADGET stuff in base_create(), not the constructor
135 * 12 9/07/97 10:05p Lawrance
136 * don't set hotspot_num, done in gadget constructor
138 * 11 8/29/97 7:34p Lawrance
139 * use bmpman for storing bitmaps for ui controls
141 * 10 8/18/97 5:28p Lawrance
142 * integrating sounds for when mouse goes over an active control
144 * 9 7/20/97 7:00p Lawrance
145 * changed name of some anim_ functions to be more consistent
147 * 8 5/26/97 10:26a Lawrance
148 * get slider control working 100%
150 * 7 5/22/97 5:36p Lawrance
151 * allowing custom art for scrollbars
153 * 6 5/21/97 11:07a Lawrance
154 * integrate masks and custom bitmaps
156 * 5 4/28/97 2:19p Lawrance
157 * added clear_focus() function
159 * 4 1/28/97 4:58p Lawrance
160 * allowing hidden UI components
162 * 3 12/02/96 2:17p John
163 * Made right button drag UI gadgets around and
164 * Ctrl+Shift+Alt+F12 dumps out where they are.
166 * 2 11/21/96 10:58a John
167 * Started adding code to drag buttons.
169 * 1 11/14/96 6:55p John
177 #include "animplay.h"
180 UI_GADGET::UI_GADGET()
185 UI_GADGET::~UI_GADGET()
189 int UI_GADGET::get_hotspot()
191 if (!linked_to_hotspot)
197 void UI_GADGET::reset()
202 // --------------------------------------------------------------------
203 // Links a hotspot (palette index in mask) to the given gadget.
205 void UI_GADGET::link_hotspot(int num)
208 linked_to_hotspot = 1;
211 // --------------------------------------------------------------------
212 // Extract MAX_BMAPS_PER_CONTROL bitmaps for the different states of the control.
214 // The bitmap ids are stored in the bmap_ids[] array. If you don't want to store
215 // from the zero index, fill in the offset parameter. offset is a default parameter
216 // with a default value of zero.
218 // NOTE: The bitmaps stored in a .ani file. What each frame is used for
219 // is left up to the component to decide.
222 // loads nframes bitmaps, starting at index start_frame.
223 // anything < start_frame will not be loaded.
224 // this keeps the loading code from trying to load bitmaps which don't exist
225 // and taking an unnecessary disk hit.
226 int UI_GADGET::set_bmaps(const char *ani_fname, int nframes, int start_frame)
230 char full_name[MAX_FILENAME_LEN] = "";
233 int its_all_good = 0;
238 // clear out all frames
239 for(idx=0; idx<MAX_BMAPS_PER_GADGET; idx++){
243 // load all the bitmaps
245 SDL_assert(nframes < MAX_BMAPS_PER_GADGET);
246 m_num_frames = nframes;
248 // FS1 uses real anis instead of frame based pcxs so this code just slows down
249 // searching and therefore loading
250 for(idx=start_frame; idx<nframes; idx++){
252 SDL_strlcpy(full_name, "", sizeof(full_name));
254 // get the # of digits for this index
255 num_digits = (idx < 10) ? 1 : (idx < 100) ? 2 : (idx < 1000) ? 3 : 4;
257 // build the actual filename
258 SDL_strlcpy(full_name, ani_fname, sizeof(full_name));
259 for(s_idx=0; s_idx<(4-num_digits); s_idx++){
260 SDL_strlcat(full_name, NOX("0"), sizeof(full_name));
263 SDL_snprintf(tmp, sizeof(tmp), "%d", idx);
264 SDL_strlcat(full_name, tmp, sizeof(full_name));
266 // try and load the bitmap
267 bmap_ids[idx] = bm_load(full_name);
268 if(bmap_ids[idx] != -1){
269 // bm_lock(bmap_ids[idx], 16, 0);
270 // bm_unlock(bmap_ids[idx]);
274 // mprintf(("Skipping %s\n", full_name));
286 // no go, so try and load as an ani. try and load as an .ani
287 bmap_ids[0] = bm_load_animation(ani_fname, &m_num_frames);
288 if((bmap_ids[0] >= 0) && (m_num_frames <= MAX_BMAPS_PER_GADGET)){
289 // seems pretty stupid that we didn't just use a variable for the first frame and access all
290 // other frames offset from it instead of accessing this bmap_ids[] array, but probably too
291 // much trouble to go through and change this anymore. How sad..
292 for ( i=1; i<m_num_frames; i++ ) {
293 bmap_ids[i] = bmap_ids[0] + i;
297 // flag that this control is using bitmaps for art
302 // Handle drawing of all children of the gadget. Since this the base of all other gadgets,
303 // it doesn't have any look to it (kind of like a soul. Can you see someone's soul?) and thus
304 // doesn't actually render itself.
306 void UI_GADGET::draw()
310 // if hidden, hide children as well
321 } while (cur != children);
325 // Free up bitmaps used by the gadget, and call children to destroy themselves as well.
327 void UI_GADGET::destroy()
332 for ( i=0; i<m_num_frames; i++ ) {
333 if (bmap_ids[i] != -1) {
334 bm_release(bmap_ids[i]);
345 } while (cur != children);
349 // Use this if you need to change the size and position of a gadget after you have created it.
351 void UI_GADGET::update_dimensions(int _x, int _y, int _w, int _h)
353 if ( _x != -1 ) x = _x;
354 if ( _y != -1 ) y = _y;
355 if ( _w != -1 ) w = _w;
356 if ( _h != -1 ) h = _h;
359 void UI_GADGET::get_dimensions(int *x_, int *y_, int *w_, int *h_)
367 // Hide (or show) a gadget.
368 // n != 0: Hide gadget
369 // n == 0: Show gadget
371 void UI_GADGET::hide(int n)
376 void UI_GADGET::unhide()
381 // Capture mouse with this gadget, which basically means only this gadget will get process()
382 // called on it until the mouse button 1 is released.
384 void UI_GADGET::capture_mouse()
386 my_wnd->capture_mouse(this);
389 // Check if (return true if):
390 // mouse_captured(): this gadget has the mouse captured.
391 // mouse_captured(x): gadget x has the mouse captured.
393 int UI_GADGET::mouse_captured(UI_GADGET *gadget)
398 return (my_wnd->mouse_captured_gadget == gadget);
401 // Set up the gadget and registers it with the UI window
403 void UI_GADGET::base_create(UI_WINDOW *wnd, int _kind, int _x, int _y, int _w, int _h)
407 // initialize data with passed values
414 // set up reference to UI window and initialize as having no family
420 // this actually links the gadget into the UI window's top level family (as the youngest gadget)
423 // initialize variables
425 user_function = NULL;
428 base_drag_x = base_drag_y = 0;
431 linked_to_hotspot = 0;
434 for ( i=0; i<MAX_BMAPS_PER_GADGET; i++ ) {
439 void UI_GADGET::set_hotkey(int key)
444 // Far as I can tell, the callback function is handled differently for each gadget type, if
445 // handled by a given gadget type at all.
447 void UI_GADGET::set_callback(void (*_user_function)(void))
449 user_function = _user_function;
452 // get the next enabled gadget in sibling list or self if none
454 UI_GADGET *UI_GADGET::get_next()
459 while ((tmp != this) && tmp->disabled_flag)
465 // get the previous enabled gadget in sibling list or self if none
467 UI_GADGET *UI_GADGET::get_prev()
472 while ((tmp != this) && tmp->disabled_flag)
478 // Set this gadget as the focus (selected gadget) of the UI window
480 void UI_GADGET::set_focus()
482 my_wnd->selected_gadget = this;
485 // Make no gadget have focus in the UI window.
487 void UI_GADGET::clear_focus()
489 my_wnd->selected_gadget = NULL;
492 // Return true or false if this gadget currently has the focus
493 int UI_GADGET::has_focus()
495 return my_wnd->selected_gadget == this ? 1 : 0;
498 // get mouse pointer position relative to gadget's origin (UL corner)
500 void UI_GADGET::get_mouse_pos(int *xx, int *yy)
503 *xx = ui_mouse.x - x;
505 *yy = ui_mouse.y - y;
508 // Call process() for all children of gadget. As a base gadget for all other gadget types,
509 // it doesn't actually do anything for itself.
511 void UI_GADGET::process(int focus)
518 tmp = children; // process all children of gadget
524 } while (tmp != children);
528 // Check if the mouse is over the gadget's area or not,
530 int UI_GADGET::is_mouse_on()
532 int offset, pixel_val;
536 // if linked to a hotspot, use the mask for determination
537 if (linked_to_hotspot) {
538 mask_data = (ubyte*)my_wnd->get_mask_data(&mask_w, &mask_h);
539 if ( mask_data == NULL ) {
540 nprintf(("Warning", "No mask defined, but control is linked to hotspot\n"));
545 // if the mouse values are out of range of the bitmap
546 // NOTE : this happens when using smaller mask bitmaps than the screen resolution (during development)
547 if((ui_mouse.x >= mask_w) || (ui_mouse.y >= mask_h)){
551 // check the pixel value under the mouse
552 offset = ui_mouse.y * mask_w + ui_mouse.x;
553 pixel_val = *(mask_data + offset);
554 if (pixel_val == hotspot_num){
559 // otherwise, we just check the bounding box area
561 if ((ui_mouse.x >= x) && (ui_mouse.x < x + w) && (ui_mouse.y >= y) && (ui_mouse.y < y + h) ){
569 // check if mouse is over any child of this gadget
571 int UI_GADGET::is_mouse_on_children()
578 if (tmp->is_mouse_on())
580 if (tmp->is_mouse_on_children())
585 } while (tmp != children);
591 void UI_GADGET::disable()
596 // Enables (or possibly disables) the gadget. n is an optional argument. If not supplied,
597 // enables the garget. If supplied, enables garget if n is true, disables it if n is false.
599 void UI_GADGET::enable(int n)
601 disabled_flag = n ? 0 : 1;
604 // Check if gadget is disabled
606 int UI_GADGET::disabled()
608 return disabled_flag;
611 // Check if gadget is enabled
613 int UI_GADGET::enabled()
615 return !disabled_flag;
618 void UI_GADGET::drag_with_children( int dx, int dy )
622 x = dx + base_start_x;
623 y = dy + base_start_y;
628 tmp->drag_with_children(dx, dy);
631 } while (tmp != children);
635 void UI_GADGET::start_drag_with_children()
646 tmp->start_drag_with_children();
649 } while (tmp != children);
653 void UI_GADGET::stop_drag_with_children()
661 tmp->stop_drag_with_children();
664 } while (tmp != children);
668 // Returns 1 if moving
669 int UI_GADGET::check_move()
672 if ( parent != NULL ) return base_dragging;
674 if ( !base_dragging ) {
676 if ( B2_JUST_PRESSED ) {
677 if ( is_mouse_on() || is_mouse_on_children() ) {
678 start_drag_with_children();
679 base_drag_x = ui_mouse.x;
680 base_drag_y = ui_mouse.y;
688 drag_with_children(ui_mouse.x - base_drag_x,ui_mouse.y - base_drag_y);
689 nprintf(( "UI", "UI: X=%d, Y=%d, Delta=(%d,%d)\n", x, y, (ui_mouse.x - base_drag_x), (ui_mouse.y - base_drag_y) ));
691 stop_drag_with_children();
699 // Take gadget out of family. Children of this gadget stay with gadget, though.
701 // A family is basically the whole parent/sibling/children hierarchy for gadgets. Any gadget
702 // that is linked to another gadget by one of these pointers is said to be in the same family
703 // as that gadget. This function, therefore, caused all references to a gadget by it's
704 // family's gadgets' sibling or children pointers to be broken, and all references to any of them
705 // by this gadget's parent or sibling pointers to also be broken. This isolates the gadget and
706 // it's children into a new family.
708 void UI_GADGET::remove_from_family()
711 if (parent->children == this) {
712 if (next == this) // an only child?
713 parent->children = NULL; // if so, parent now has no children
715 parent->children = next; // next sibling is now the eldest
719 if (my_wnd->first_gadget == this) {
720 if (next == this) // an only child?
721 my_wnd->first_gadget = NULL; // if so, parent now has no children
723 my_wnd->first_gadget = next; // next sibling is now the eldest
728 if (next != this) { // does gadget have siblings?
736 // Put gadget into a new family (removing from old one if needed first).
737 // See remove_from_family() for definition of what a family is.
739 void UI_GADGET::set_parent(UI_GADGET *daddy)
741 remove_from_family();
745 if (!my_wnd->first_gadget) {
746 my_wnd->first_gadget = this;
749 UI_GADGET *eldest_sibling, *youngest_sibling;
751 eldest_sibling = my_wnd->first_gadget;
752 youngest_sibling = eldest_sibling->prev;
754 next = eldest_sibling;
755 prev = youngest_sibling;
757 eldest_sibling->prev = this;
758 youngest_sibling->next = this;
764 if (!daddy->children) {
765 daddy->children = this;
768 UI_GADGET *eldest_sibling, *youngest_sibling;
770 eldest_sibling = daddy->children;
771 youngest_sibling = eldest_sibling->prev;
773 next = eldest_sibling;
774 prev = youngest_sibling->prev;
776 eldest_sibling->prev = this;
777 youngest_sibling->next = this;