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