]> icculus.org git repositories - taylor/freespace2.git/blob - src/ui/gadget.cpp
Initial revision
[taylor/freespace2.git] / src / ui / gadget.cpp
1 /*
2  * $Logfile: /Freespace2/code/Ui/GADGET.cpp $
3  * $Revision$
4  * $Date$
5  * $Author$
6  *
7  * Functions for the base gadget class
8  *
9  * $Log$
10  * Revision 1.1  2002/05/03 03:28:11  root
11  * Initial revision
12  *
13  * 
14  * 18    8/11/99 3:21p Jefff
15  * set_bmaps clarification by daveb
16  * 
17  * 17    8/10/99 6:54p Dave
18  * Mad optimizations. Added paging to the nebula effect.
19  * 
20  * 16    7/16/99 1:50p Dave
21  * 8 bit aabitmaps. yay.
22  * 
23  * 15    6/25/99 11:59a Dave
24  * Multi options screen.
25  * 
26  * 14    5/21/99 6:45p Dave
27  * Sped up ui loading a bit. Sped up localization disk access stuff. Multi
28  * start game screen, multi password, and multi pxo-help screen.
29  * 
30  * 13    5/03/99 8:33p Dave
31  * New version of multi host options screen.
32  * 
33  * 12    4/16/99 5:23p Neilk
34  * Removed bitmap mprintf's to make debugging faster
35  * 
36  * 11    2/11/99 3:08p Dave
37  * PXO refresh button. Very preliminary squad war support.
38  * 
39  * 10    2/05/99 4:09p Anoop
40  * Hopefully the last fix for this file :)
41  * 
42  * 9     2/05/99 3:26p Enricco
43  * Oops. Some .ani's can have 4 frames.
44  * 
45  * 8     2/05/99 3:07p Davidg
46  * Fixed braindead bmap setting code.
47  * 
48  * 7     2/01/99 5:55p Dave
49  * Removed the idea of explicit bitmaps for buttons. Fixed text
50  * highlighting for disabled gadgets.
51  * 
52  * 6     12/21/98 9:05a Dave
53  * Changed UI code so it doesn't require 3 bitmaps for a 3 state button.
54  * 
55  * 5     12/18/98 1:13a Dave
56  * Rough 1024x768 support for Direct3D. Proper detection and usage through
57  * the launcher.
58  * 
59  * 4     12/02/98 5:47p Dave
60  * Put in interface xstr code. Converted barracks screen to new format.
61  * 
62  * 3     11/30/98 1:07p Dave
63  * 16 bit conversion, first run.
64  * 
65  * 2     10/07/98 10:54a Dave
66  * Initial checkin.
67  * 
68  * 1     10/07/98 10:51a Dave
69  * 
70  * 22    5/11/98 5:29p Hoffoss
71  * Added mouse button mapped to joystick button support.
72  * 
73  * 21    4/12/98 2:09p Dave
74  * Make main hall door text less stupid. Make sure inputbox focus in the
75  * multi host options screen is managed more intelligently.
76  * 
77  * 20    4/09/98 7:14p Hoffoss
78  * Did some cool changes for tooltips.
79  * 
80  * 19    2/11/98 6:24p Hoffoss
81  * Fixed bug where disabled and hidden buttons give failed sound when
82  * pressed.  Shouldn't happen when they are hidden.
83  * 
84  * 18    1/15/98 11:54a Hoffoss
85  * Embelished file with nifty comments.
86  * 
87  * 17    1/14/98 6:43p Hoffoss
88  * Massive changes to UI code.  A lot cleaner and better now.  Did all
89  * this to get the new UI_DOT_SLIDER to work properly, which the old code
90  * wasn't flexible enough to handle.
91  * 
92  * 16    12/30/97 4:26p Lawrance
93  * Disable right-click movement of ui controls
94  * 
95  * 15    10/24/97 10:58p Hoffoss
96  * Made some changes to the UI code to do some things I need it to do.
97  * 
98  * 14    10/12/97 3:45p Lawrance
99  * only allow movement of UI controls in debug
100  * 
101  * 13    10/09/97 4:56p Lawrance
102  * init GADGET stuff in base_create(), not the constructor
103  * 
104  * 12    9/07/97 10:05p Lawrance
105  * don't set hotspot_num, done in gadget constructor
106  * 
107  * 11    8/29/97 7:34p Lawrance
108  * use bmpman for storing bitmaps for ui controls
109  * 
110  * 10    8/18/97 5:28p Lawrance
111  * integrating sounds for when mouse goes over an active control
112  * 
113  * 9     7/20/97 7:00p Lawrance
114  * changed name of some anim_ functions to be more consistent
115  * 
116  * 8     5/26/97 10:26a Lawrance
117  * get slider control working 100%
118  * 
119  * 7     5/22/97 5:36p Lawrance
120  * allowing custom art for scrollbars
121  * 
122  * 6     5/21/97 11:07a Lawrance
123  * integrate masks and custom bitmaps
124  * 
125  * 5     4/28/97 2:19p Lawrance
126  * added clear_focus() function
127  * 
128  * 4     1/28/97 4:58p Lawrance
129  * allowing hidden UI components
130  * 
131  * 3     12/02/96 2:17p John
132  * Made right button drag UI gadgets around and
133  * Ctrl+Shift+Alt+F12 dumps out where they are.
134  * 
135  * 2     11/21/96 10:58a John
136  * Started adding code to drag buttons.
137  * 
138  * 1     11/14/96 6:55p John
139  *
140  * $NoKeywords: $
141  */
142
143 #include "uidefs.h"
144 #include "ui.h"
145 #include "bmpman.h"
146 #include "animplay.h"
147
148 // constructor
149 UI_GADGET::UI_GADGET()
150 {
151         bm_filename = NULL;
152 }
153
154 // destructor
155 UI_GADGET::~UI_GADGET()
156 {
157 }
158
159 int UI_GADGET::get_hotspot()
160 {
161         if (!linked_to_hotspot)
162                 return -1;
163
164         return hotspot_num;
165 }
166
167 void UI_GADGET::reset()
168 {
169         m_flags = 0;
170 }
171
172 // --------------------------------------------------------------------
173 // Links a hotspot (palette index in mask) to the given gadget.
174 //
175 void UI_GADGET::link_hotspot(int num)
176 {
177         hotspot_num = num;
178         linked_to_hotspot = 1;
179 }
180
181 // --------------------------------------------------------------------
182 // Extract MAX_BMAPS_PER_CONTROL bitmaps for the different states of the control.
183 //
184 // The bitmap ids are stored in the bmap_ids[] array.  If you don't want to store
185 // from the zero index, fill in the offset parameter.  offset is a default parameter
186 // with a default value of zero.
187 //
188 // NOTE:  The bitmaps stored in a .ani file.  What each frame is used for
189 //                       is left up to the component to decide.
190 //
191
192 // loads nframes bitmaps, starting at index start_frame.
193 // anything < start_frame will not be loaded.
194 // this keeps the loading code from trying to load bitmaps which don't exist
195 // and taking an unnecessary disk hit.          
196 int UI_GADGET::set_bmaps(char *ani_fname, int nframes, int start_frame)
197 {
198         int first_frame, i;     
199         char full_name[MAX_FILENAME_LEN] = "";
200         char tmp[10];
201         int idx, s_idx; 
202         int num_digits;
203         int its_all_good = 0;
204         
205         // clear out all frames
206         for(idx=0; idx<MAX_BMAPS_PER_GADGET; idx++){
207                 bmap_ids[idx] = -1;
208         }
209         
210         // load all the bitmaps
211         bm_filename = ani_fname;
212
213         Assert(nframes < MAX_BMAPS_PER_GADGET);         
214         m_num_frames = nframes;         
215         for(idx=start_frame; idx<nframes; idx++){
216                 // clear the string
217                 strcpy(full_name, "");
218
219                 // get the # of digits for this index
220                 num_digits = (idx < 10) ? 1 : (idx < 100) ? 2 : (idx < 1000) ? 3 : 4;
221
222                 // build the actual filename
223                 strcpy(full_name, ani_fname);           
224                 for(s_idx=0; s_idx<(4-num_digits); s_idx++){
225                         strcat(full_name, NOX("0"));
226                 }
227                 strcat(full_name, itoa(idx, tmp, 10));          
228
229                 // try and load the bitmap                              
230                 bmap_ids[idx] = bm_load(full_name);     
231                 if(bmap_ids[idx] != -1){                
232                         // bm_lock(bmap_ids[idx], 16, 0);
233                         // bm_unlock(bmap_ids[idx]);
234                         
235                         its_all_good = 1;
236                 } else {
237                         // mprintf(("Skipping %s\n", full_name));
238                         // its_all_good = 0;
239                 }
240         }
241
242         // done
243         if(its_all_good){
244                 uses_bmaps = 1;         
245                 return 0;
246         }
247
248         // no go, so try and load as an ani. try and load as an .ani    
249         first_frame = bm_load_animation(ani_fname, &m_num_frames);      
250         if((first_frame >= 0) && (m_num_frames <= MAX_BMAPS_PER_GADGET)){                                       
251                 // seems pretty stupid that we didn't just use a variable for the first frame and access all
252                 // other frames offset from it instead of accessing this bmap_ids[] array, but probably too
253                 // much trouble to go through and change this anymore.  How sad..
254                 for ( i=0; i<m_num_frames; i++ ) {
255                         bmap_ids[i] = first_frame + i;
256                 }       
257         }       
258
259         // flag that this control is using bitmaps for art      
260         uses_bmaps = 1;
261         return 0;
262 }
263
264 // Handle drawing of all children of the gadget.  Since this the base of all other gadgets,
265 // it doesn't have any look to it (kind of like a soul.  Can you see someone's soul?) and thus
266 // doesn't actually render itself.
267 //
268 void UI_GADGET::draw()
269 {
270         UI_GADGET *cur;
271
272         // if hidden, hide children as well
273         if (hidden){
274                 return;
275         }
276
277         if (children) {
278                 cur = children;
279                 do {
280                         cur->draw();
281                         cur = cur->next;
282
283                 } while (cur != children);
284         }
285 }
286
287 //      Free up bitmaps used by the gadget, and call children to destroy themselves as well.
288 //
289 void UI_GADGET::destroy()
290 {
291         int i;
292         UI_GADGET *cur;
293
294         for ( i=0; i<m_num_frames; i++ ) {
295                 if (bmap_ids[i] != -1) {
296                         bm_release(bmap_ids[i]);
297                         bmap_ids[i] = -1;
298                 }
299         }
300
301         if (children) {
302                 cur = children;
303                 do {
304                         cur->destroy();
305                         cur = cur->next;
306
307                 } while (cur != children);
308         }
309 }
310
311 // Use this if you need to change the size and position of a gadget after you have created it.
312 //
313 void UI_GADGET::update_dimensions(int _x, int _y, int _w, int _h)
314 {
315         if ( _x != -1 ) x = _x;
316         if ( _y != -1 ) y = _y;
317         if ( _w != -1 ) w = _w;
318         if ( _h != -1 ) h = _h; 
319 }
320
321 void UI_GADGET::get_dimensions(int *x_, int *y_, int *w_, int *h_)
322 {
323         *x_ = x;
324         *y_ = y;
325         *w_ = w;
326         *h_ = h;        
327 }
328
329 // Hide (or show) a gadget.
330 //  n != 0: Hide gadget
331 //  n == 0: Show gadget
332 //
333 void UI_GADGET::hide(int n)
334 {
335         hidden = n ? 1 : 0;
336 }
337
338 void UI_GADGET::unhide()
339 {
340         hidden = 0;
341 }
342
343 // Capture mouse with this gadget, which basically means only this gadget will get process()
344 // called on it until the mouse button 1 is released.
345 //
346 void UI_GADGET::capture_mouse()
347 {
348         my_wnd->capture_mouse(this);
349 }
350
351 // Check if (return true if):
352 //   mouse_captured():  this gadget has the mouse captured.
353 //   mouse_captured(x): gadget x has the mouse captured.
354 //
355 int UI_GADGET::mouse_captured(UI_GADGET *gadget)
356 {
357         if (!gadget)
358                 gadget = this;
359
360         return (my_wnd->mouse_captured_gadget == gadget);
361 }
362
363 // Set up the gadget and registers it with the UI window
364 //
365 void UI_GADGET::base_create(UI_WINDOW *wnd, int _kind, int _x, int _y, int _w, int _h)
366 {
367         int i;
368
369         // initialize data with passed values
370         kind = _kind;
371         x = _x;
372         y = _y;
373         w = _w;
374         h = _h;
375
376         // set up reference to UI window and initialize as having no family
377         my_wnd = wnd;
378         parent = NULL;
379         children = NULL;
380         next = prev = this;
381
382         // this actually links the gadget into the UI window's top level family (as the youngest gadget)
383         set_parent(NULL);
384
385         // initialize variables
386         hotkey = -1;
387         user_function = NULL;
388         disabled_flag = 0;
389         base_dragging = 0;
390         base_drag_x = base_drag_y = 0;
391         hotspot_num = -1;
392         hidden = 0;
393         linked_to_hotspot = 0;
394         uses_bmaps = 0;
395         m_num_frames = 0;
396         for ( i=0; i<MAX_BMAPS_PER_GADGET; i++ ) {
397                 bmap_ids[i] = -1;
398         }
399 }
400
401 void UI_GADGET::set_hotkey(int key)
402 {
403         hotkey = key;
404 }
405
406 // Far as I can tell, the callback function is handled differently for each gadget type, if
407 // handled by a given gadget type at all.
408 //
409 void UI_GADGET::set_callback(void (*_user_function)(void))
410 {
411         user_function = _user_function;
412 }
413
414 // get the next enabled gadget in sibling list or self if none
415 //
416 UI_GADGET *UI_GADGET::get_next()
417 {
418         UI_GADGET *tmp;
419
420         tmp = next;
421         while ((tmp != this) && tmp->disabled_flag)
422                 tmp = tmp->next;
423
424         return tmp;
425 }
426
427 // get the previous enabled gadget in sibling list or self if none
428 //
429 UI_GADGET *UI_GADGET::get_prev()
430 {
431         UI_GADGET *tmp;
432
433         tmp = prev;
434         while ((tmp != this) && tmp->disabled_flag)
435                 tmp = tmp->prev;
436
437         return tmp;
438 }
439
440 // Set this gadget as the focus (selected gadget) of the UI window
441 //
442 void UI_GADGET::set_focus()
443 {
444         my_wnd->selected_gadget = this;
445 }
446
447 // Make no gadget have focus in the UI window.
448 //
449 void UI_GADGET::clear_focus()
450 {
451         my_wnd->selected_gadget = NULL;
452 }
453
454 // Return true or false if this gadget currently has the focus
455 int UI_GADGET::has_focus()
456 {
457         return my_wnd->selected_gadget == this ? 1 : 0;
458 }
459
460 // get mouse pointer position relative to gadget's origin (UL corner)
461 //
462 void UI_GADGET::get_mouse_pos(int *xx, int *yy)
463 {
464         if (xx)
465                 *xx = ui_mouse.x - x;
466         if (yy)
467                 *yy = ui_mouse.y - y;
468 }
469
470 // Call process() for all children of gadget.  As a base gadget for all other gadget types,
471 // it doesn't actually do anything for itself.
472 //
473 void UI_GADGET::process(int focus)
474 {
475         UI_GADGET *tmp;
476
477         if (disabled_flag)
478                 return;
479
480         tmp = children;  // process all children of gadget
481         if (tmp) {
482                 do {
483                         tmp->process();
484                         tmp = tmp->next;
485
486                 } while (tmp != children);
487         }
488 }
489
490 // Check if the mouse is over the gadget's area or not,
491 //
492 int UI_GADGET::is_mouse_on()
493 {
494         int offset, pixel_val;
495         ubyte *mask_data;
496         int mask_w, mask_h;
497
498         // if linked to a hotspot, use the mask for determination
499         if (linked_to_hotspot) {
500                 mask_data = (ubyte*)my_wnd->get_mask_data(&mask_w, &mask_h);
501                 if ( mask_data == NULL ) {
502                         nprintf(("Warning", "No mask defined, but control is linked to hotspot\n"));
503                         Int3();
504                         return 0;
505                 }
506
507                 // if the mouse values are out of range of the bitmap
508                 // NOTE : this happens when using smaller mask bitmaps than the screen resolution (during development)
509                 if((ui_mouse.x >= mask_w) || (ui_mouse.y >= mask_h)){
510                         return 0;
511                 }
512
513                 // check the pixel value under the mouse
514                 offset = ui_mouse.y * mask_w + ui_mouse.x;
515                 pixel_val = *(mask_data + offset);
516                 if (pixel_val == hotspot_num){
517                         return 1;
518                 } else {
519                         return 0;
520                 }
521         // otherwise, we just check the bounding box area
522         } else {
523                 if ((ui_mouse.x >= x) && (ui_mouse.x < x + w) && (ui_mouse.y >= y) && (ui_mouse.y < y + h) ){
524                         return 1;
525                 } else {
526                         return 0;
527                 }
528         }
529 }
530
531 // check if mouse is over any child of this gadget
532 //
533 int UI_GADGET::is_mouse_on_children()
534 {
535         UI_GADGET *tmp;
536         
537         tmp = children;
538         if (tmp) {
539                 do {
540                         if (tmp->is_mouse_on())
541                                 return 1;
542                         if (tmp->is_mouse_on_children())
543                                 return 1;
544
545                         tmp = tmp->next;
546
547                 } while (tmp != children);
548         }
549
550         return 0;       
551 }
552
553 void UI_GADGET::disable()
554 {
555         disabled_flag = 1;
556 }
557
558 // Enables (or possibly disables) the gadget.  n is an optional argument.  If not supplied,
559 // enables the garget.  If supplied, enables garget if n is true, disables it if n is false.
560 //
561 void UI_GADGET::enable(int n)
562 {
563         disabled_flag = n ? 0 : 1;
564 }
565
566 // Check if gadget is disabled
567 //
568 int UI_GADGET::disabled()
569 {
570         return disabled_flag;
571 }
572
573 // Check if gadget is enabled
574 //
575 int UI_GADGET::enabled()
576 {
577         return !disabled_flag;
578 }
579
580 void UI_GADGET::drag_with_children( int dx, int dy )
581 {
582         UI_GADGET *tmp;
583
584         x = dx + base_start_x;
585         y = dy + base_start_y;
586         
587         tmp = children;
588         if (tmp) {
589                 do {
590                         tmp->drag_with_children(dx, dy);
591                         tmp = tmp->next;
592
593                 } while (tmp != children);
594         }
595 }
596
597 void UI_GADGET::start_drag_with_children()
598 {
599         UI_GADGET *tmp;
600
601         base_dragging = 1;
602         base_start_x = x;
603         base_start_y = y;
604         
605         tmp = children;
606         if (tmp) {
607                 do {
608                         tmp->start_drag_with_children();
609                         tmp = tmp->next;
610
611                 } while (tmp != children);
612         }
613 }
614
615 void UI_GADGET::stop_drag_with_children()
616 {
617         UI_GADGET *tmp;
618
619         base_dragging = 0;
620         tmp = children;
621         if (tmp) {
622                 do {
623                         tmp->stop_drag_with_children();
624                         tmp = tmp->next;
625
626                 } while (tmp != children);
627         }
628 }
629
630 // Returns 1 if moving
631 int UI_GADGET::check_move()
632 {
633         #if 0
634                 if ( parent != NULL ) return base_dragging;
635
636                 if ( !base_dragging )   {
637
638                         if ( B2_JUST_PRESSED )  {
639                                 if ( is_mouse_on() || is_mouse_on_children() ) {
640                                         start_drag_with_children();
641                                         base_drag_x = ui_mouse.x;
642                                         base_drag_y = ui_mouse.y;
643                                         return 1;
644                                 } else {
645                                         return 0;
646                                 }
647                         } else 
648                                 return 0;
649                 } else {
650                         drag_with_children(ui_mouse.x - base_drag_x,ui_mouse.y - base_drag_y);
651                         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) ));
652                         if (B2_RELEASED)        {
653                                 stop_drag_with_children();
654                         }
655                         return 1;
656                 }
657         #endif
658         return 0;
659 }
660
661 // Take gadget out of family.  Children of this gadget stay with gadget, though.
662 //
663 // A family is basically the whole parent/sibling/children hierarchy for gadgets.  Any gadget
664 // that is linked to another gadget by one of these pointers is said to be in the same family
665 // as that gadget.  This function, therefore, caused all references to a gadget by it's
666 // family's gadgets' sibling or children pointers to be broken, and all references to any of them
667 // by this gadget's parent or sibling pointers to also be broken.  This isolates the gadget and
668 // it's children into a new family.
669 //
670 void UI_GADGET::remove_from_family()
671 {
672         if (parent) {
673                 if (parent->children == this) {
674                         if (next == this)  // an only child?
675                                 parent->children = NULL;  // if so, parent now has no children
676                         else
677                                 parent->children = next;  // next sibling is now the eldest
678                 }
679
680         } else {
681                 if (my_wnd->first_gadget == this) {
682                         if (next == this)  // an only child?
683                                 my_wnd->first_gadget = NULL;  // if so, parent now has no children
684                         else
685                                 my_wnd->first_gadget = next;  // next sibling is now the eldest
686                 }
687         }
688
689         parent = NULL;
690         if (next != this) {  // does gadget have siblings?
691                 next->prev = prev;
692                 prev->next = next;
693         }
694
695         next = prev = this;
696 }
697
698 // Put gadget into a new family (removing from old one if needed first).
699 // See remove_from_family() for definition of what a family is.
700 //
701 void UI_GADGET::set_parent(UI_GADGET *daddy)
702 {
703         remove_from_family();
704         parent = daddy;
705
706         if (!daddy) {
707                 if (!my_wnd->first_gadget) {
708                         my_wnd->first_gadget = this;
709
710                 } else {
711                         UI_GADGET *eldest_sibling, *youngest_sibling;
712
713                         eldest_sibling = my_wnd->first_gadget;
714                         youngest_sibling = eldest_sibling->prev;
715
716                         next = eldest_sibling;
717                         prev = youngest_sibling;
718
719                         eldest_sibling->prev = this;
720                         youngest_sibling->next = this;
721                 }
722
723                 return;
724         }
725
726         if (!daddy->children) {
727                 daddy->children = this;
728
729         } else {
730                 UI_GADGET *eldest_sibling, *youngest_sibling;
731
732                 eldest_sibling = daddy->children;
733                 youngest_sibling = eldest_sibling->prev;
734
735                 next = eldest_sibling;
736                 prev = youngest_sibling->prev;
737
738                 eldest_sibling->prev = this;
739                 youngest_sibling->next = this;
740         }
741 }