1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "GUI/Button.h"
23 #include "GUI/GameControl.h"
25 #include "defsounds.h"
29 #include "Interface.h"
31 #include "Variables.h"
36 Unpressed
= Pressed
= Selected
= Disabled
= NULL
;
37 State
= IE_GUI_BUTTON_UNPRESSED
;
38 ResetEventHandler( ButtonOnPress
);
39 ResetEventHandler( ButtonOnDoublePress
);
40 ResetEventHandler( ButtonOnShiftPress
);
41 ResetEventHandler( ButtonOnRightPress
);
42 ResetEventHandler( ButtonOnDragDrop
);
43 ResetEventHandler( ButtonOnDrag
);
44 ResetEventHandler( MouseEnterButton
);
45 ResetEventHandler( MouseLeaveButton
);
46 ResetEventHandler( MouseOverButton
);
47 //Text = ( char * ) calloc( 64, sizeof(char) );
50 font
= core
->GetButtonFont();
51 normal_palette
= NULL
;
52 disabled_palette
= font
->GetPalette()->Copy();
53 for (int i
= 0; i
< 256; i
++) {
54 disabled_palette
->col
[i
].r
= ( disabled_palette
->col
[i
].r
* 2 ) / 3;
55 disabled_palette
->col
[i
].g
= ( disabled_palette
->col
[i
].g
* 2 ) / 3;
56 disabled_palette
->col
[i
].b
= ( disabled_palette
->col
[i
].b
* 2 ) / 3;
58 Flags
= IE_GUI_BUTTON_NORMAL
;
62 memset(&SourceRGB
,0,sizeof(SourceRGB
));
63 memset(&DestRGB
,0,sizeof(DestRGB
));
64 memset( borders
, 0, sizeof( borders
));
69 Video
* video
= core
->GetVideoDriver();
70 video
->FreeSprite( Disabled
);
71 video
->FreeSprite( Selected
);
72 video
->FreeSprite( Pressed
);
73 video
->FreeSprite( Unpressed
);
74 video
->FreeSprite( Picture
);
79 gamedata
->FreePalette( normal_palette
);
80 gamedata
->FreePalette( disabled_palette
);
82 /** Sets the 'type' Image of the Button to 'img'.
83 'type' may assume the following values:
84 - IE_GUI_BUTTON_UNPRESSED
85 - IE_GUI_BUTTON_PRESSED
86 - IE_GUI_BUTTON_SELECTED
87 - IE_GUI_BUTTON_DISABLED */
88 void Button::SetImage(unsigned char type
, Sprite2D
* img
)
91 case IE_GUI_BUTTON_UNPRESSED
:
92 case IE_GUI_BUTTON_LOCKED
:
93 case IE_GUI_BUTTON_LOCKED_PRESSED
:
94 core
->GetVideoDriver()->FreeSprite( Unpressed
);
98 case IE_GUI_BUTTON_SECOND
:
99 case IE_GUI_BUTTON_PRESSED
:
100 core
->GetVideoDriver()->FreeSprite( Pressed
);
104 case IE_GUI_BUTTON_SELECTED
:
105 core
->GetVideoDriver()->FreeSprite( Selected
);
109 case IE_GUI_BUTTON_DISABLED
:
110 case IE_GUI_BUTTON_THIRD
:
111 core
->GetVideoDriver()->FreeSprite( Disabled
);
118 /** make SourceRGB go closer to DestRGB */
119 void Button::CloseUpColor()
121 if (!starttime
) return;
122 //using the realtime timer, because i don't want to
123 //handle Game at this point
124 unsigned long newtime
;
128 if (newtime
<starttime
) {
131 SourceRGB
.r
= (SourceRGB
.r
+ DestRGB
.r
) / 2;
132 SourceRGB
.g
= (SourceRGB
.g
+ DestRGB
.g
) / 2;
133 SourceRGB
.b
= (SourceRGB
.b
+ DestRGB
.b
) / 2;
134 SourceRGB
.a
= (SourceRGB
.a
+ DestRGB
.a
) / 2;
135 if (SourceRGB
.r
== DestRGB
.r
&&
136 SourceRGB
.g
== DestRGB
.g
&&
137 SourceRGB
.b
== DestRGB
.b
&&
138 SourceRGB
.a
== DestRGB
.a
) {
142 starttime
= newtime
+ 40;
145 /** Draws the Control on the Output Display */
146 void Button::Draw(unsigned short x
, unsigned short y
)
148 if (!Changed
&& !(Owner
->Flags
&WF_FLOAT
) ) {
152 if (XPos
== 65535 || Width
== 0) {
156 Video
* video
= core
->GetVideoDriver();
159 if (!( Flags
& IE_GUI_BUTTON_NO_IMAGE
)) {
160 Sprite2D
* Image
= NULL
;
163 case IE_GUI_BUTTON_UNPRESSED
:
164 case IE_GUI_BUTTON_LOCKED
:
165 case IE_GUI_BUTTON_LOCKED_PRESSED
:
169 case IE_GUI_BUTTON_SECOND
:
170 case IE_GUI_BUTTON_PRESSED
:
176 case IE_GUI_BUTTON_SELECTED
:
182 case IE_GUI_BUTTON_DISABLED
:
183 case IE_GUI_BUTTON_THIRD
:
190 // FIXME: maybe it's useless...
191 int xOffs
= ( Width
/ 2 ) - ( Image
->Width
/ 2 );
192 int yOffs
= ( Height
/ 2 ) - ( Image
->Height
/ 2 );
194 video
->BlitSprite( Image
, x
+ XPos
+ xOffs
, y
+ YPos
+ yOffs
, true );
198 if (State
== IE_GUI_BUTTON_PRESSED
) {
199 //shift the writing/border a bit
205 if (Picture
&& (Flags
& IE_GUI_BUTTON_PICTURE
) ) {
206 // Picture is drawn centered
207 int xOffs
= ( Width
/ 2 ) - ( Picture
->Width
/ 2 );
208 int yOffs
= ( Height
/ 2 ) - ( Picture
->Height
/ 2 );
209 if (Flags
& IE_GUI_BUTTON_HORIZONTAL
) {
210 xOffs
+= x
+ XPos
+ Picture
->XPos
;
211 yOffs
+= y
+ YPos
+ Picture
->YPos
;
212 video
->BlitSprite( Picture
, xOffs
, yOffs
, true );
213 Region r
= Region( xOffs
, yOffs
+ (int) (Picture
->Height
* Clipping
), Picture
->Width
, (int) (Picture
->Height
*(1.0 - Clipping
)) );
214 video
->DrawRect( r
, SourceRGB
, true );
215 // do NOT uncomment this, you can't change Changed or invalidate things from
216 // the middle of Window::DrawWindow() -- it needs moving to somewhere else
220 Region
r( x
+ XPos
+ xOffs
, y
+ YPos
+ yOffs
, (int)(Picture
->Width
* Clipping
), Picture
->Height
);
221 video
->BlitSprite( Picture
, x
+ XPos
+ xOffs
+ Picture
->XPos
, y
+ YPos
+ yOffs
+ Picture
->YPos
, true, &r
);
225 // Composite pictures (paperdolls/description icons)
226 if (!PictureList
.empty() && (Flags
& IE_GUI_BUTTON_PICTURE
) ) {
227 std::list
<Sprite2D
*>::iterator iter
= PictureList
.begin();
228 int xOffs
= 0, yOffs
= 0;
229 if (Flags
& IE_GUI_BUTTON_CENTER_PICTURES
) {
230 // Center the hotspots of all pictures
233 } else if (Flags
& IE_GUI_BUTTON_BG1_PAPERDOLL
) {
238 // Center the first picture, and align the rest to that
239 xOffs
= Width
/2 - (*iter
)->Width
/2 + (*iter
)->XPos
;
240 yOffs
= Height
/2 - (*iter
)->Height
/2 + (*iter
)->YPos
;
243 for (; iter
!= PictureList
.end(); ++iter
) {
244 video
->BlitSprite( *iter
, x
+ XPos
+ xOffs
, y
+ YPos
+ yOffs
, true );
250 int xOffs
= ( Width
/ 2 ) - ( AnimPicture
->Width
/ 2 );
251 int yOffs
= ( Height
/ 2 ) - ( AnimPicture
->Height
/ 2 );
252 Region
r( x
+ XPos
+ xOffs
, y
+ YPos
+ yOffs
, (int)(AnimPicture
->Width
* Clipping
), AnimPicture
->Height
);
254 if (Flags
& IE_GUI_BUTTON_CENTER_PICTURES
) {
255 video
->BlitSprite( AnimPicture
, x
+ XPos
+ xOffs
+ AnimPicture
->XPos
, y
+ YPos
+ yOffs
+ AnimPicture
->YPos
, true, &r
);
257 video
->BlitSprite( AnimPicture
, x
+ XPos
+ xOffs
, y
+ YPos
+ yOffs
, true, &r
);
262 if (hasText
&& ! ( Flags
& IE_GUI_BUTTON_NO_TEXT
)) {
263 Palette
* ppoi
= normal_palette
;
266 if (State
== IE_GUI_BUTTON_DISABLED
)
267 ppoi
= disabled_palette
;
268 // FIXME: hopefully there's no button which sinks when selected
269 // AND has text label
270 //else if (State == IE_GUI_BUTTON_PRESSED || State == IE_GUI_BUTTON_SELECTED) {
272 if (Flags
& IE_GUI_BUTTON_ALIGN_LEFT
)
273 align
|= IE_FONT_ALIGN_LEFT
;
274 else if (Flags
& IE_GUI_BUTTON_ALIGN_RIGHT
)
275 align
|= IE_FONT_ALIGN_RIGHT
;
277 align
|= IE_FONT_ALIGN_CENTER
;
279 if (Flags
& IE_GUI_BUTTON_ALIGN_TOP
)
280 align
|= IE_FONT_ALIGN_TOP
;
281 else if (Flags
& IE_GUI_BUTTON_ALIGN_BOTTOM
)
282 align
|= IE_FONT_ALIGN_BOTTOM
;
284 align
|= IE_FONT_ALIGN_MIDDLE
;
286 if (! (Flags
& IE_GUI_BUTTON_MULTILINE
)) {
287 align
|= IE_FONT_SINGLE_LINE
;
289 font
->Print( Region( x
+ XPos
, y
+ YPos
, Width
- 2, Height
- 2),
290 ( unsigned char * ) Text
, ppoi
,
291 (ieByte
) align
, true );
294 if (! (Flags
&IE_GUI_BUTTON_NO_IMAGE
)) {
295 for (int i
= 0; i
< MAX_NUM_BORDERS
; i
++) {
296 ButtonBorder
*fr
= &borders
[i
];
297 if (! fr
->enabled
) continue;
299 Region r
= Region( x
+ XPos
+ fr
->dx1
, y
+ YPos
+ fr
->dy1
, Width
- (fr
->dx1
+ fr
->dx2
+ 1), Height
- (fr
->dy1
+ fr
->dy2
+ 1) );
300 video
->DrawRect( r
, fr
->color
, fr
->filled
);
304 /** Sets the Button State */
305 void Button::SetState(unsigned char state
)
307 if (state
> IE_GUI_BUTTON_LOCKED_PRESSED
) {// If wrong value inserted
310 if (State
!= state
) {
315 void Button::SetBorder(int index
, int dx1
, int dy1
, int dx2
, int dy2
, const Color
&color
, bool enabled
, bool filled
)
317 if (index
>= MAX_NUM_BORDERS
)
320 ButtonBorder
*fr
= &borders
[index
];
326 fr
->enabled
= enabled
;
331 void Button::EnableBorder(int index
, bool enabled
)
333 if (index
>= MAX_NUM_BORDERS
)
336 if (borders
[index
].enabled
!= enabled
) {
337 borders
[index
].enabled
= enabled
;
342 void Button::SetFont(Font
* newfont
)
346 /** Handling The default button (enter) */
347 void Button::OnSpecialKeyPress(unsigned char Key
)
349 if (State
!= IE_GUI_BUTTON_DISABLED
&& State
!= IE_GUI_BUTTON_LOCKED
) {
350 if (Key
== GEM_RETURN
) {
351 if (Flags
& IE_GUI_BUTTON_DEFAULT
) {
352 RunEventHandler( ButtonOnPress
);
356 else if (Key
== GEM_ESCAPE
) {
357 if (Flags
& IE_GUI_BUTTON_CANCEL
) {
358 RunEventHandler( ButtonOnPress
);
363 Control::OnSpecialKeyPress(Key
);
366 /** Mouse Button Down */
367 void Button::OnMouseDown(unsigned short x
, unsigned short y
,
368 unsigned short Button
, unsigned short Mod
)
370 if (State
== IE_GUI_BUTTON_DISABLED
) {
371 Control::OnMouseDown(x
,y
,Button
,Mod
);
375 if (core
->GetDraggedItem () && !ButtonOnDragDrop
) {
376 Control::OnMouseDown(x
,y
,Button
,Mod
);
380 ScrollBar
* scrlbr
= (ScrollBar
*) sb
;
382 Control
*ctrl
= Owner
->GetScrollControl();
383 if (ctrl
&& (ctrl
->ControlType
== IE_GUI_SCROLLBAR
)) {
384 scrlbr
= (ScrollBar
*) ctrl
;
388 //Button == 1 means Left Mouse Button
389 switch(Button
&GEM_MB_NORMAL
) {
391 // We use absolute screen position here, so drag_start
392 // remains valid even after window/control is moved
393 drag_start
.x
= Owner
->XPos
+ XPos
+ x
;
394 drag_start
.y
= Owner
->YPos
+ YPos
+ y
;
396 if (State
== IE_GUI_BUTTON_LOCKED
) {
397 SetState( IE_GUI_BUTTON_LOCKED_PRESSED
);
400 SetState( IE_GUI_BUTTON_PRESSED
);
401 if (Flags
& IE_GUI_BUTTON_SOUND
) {
402 core
->PlaySound( DS_BUTTON_PRESSED
);
404 if ((Button
& GEM_MB_DOUBLECLICK
) && ButtonOnDoublePress
) {
405 RunEventHandler( ButtonOnDoublePress
);
406 printMessage("Button","Doubleclick detected\n",GREEN
);
415 case GEM_MB_SCRLDOWN
:
417 scrlbr
->ScrollDown();
423 /** Mouse Button Up */
424 void Button::OnMouseUp(unsigned short x
, unsigned short y
,
425 unsigned short Button
, unsigned short Mod
)
427 if (State
== IE_GUI_BUTTON_DISABLED
) {
431 //what was just dropped?
433 if (core
->GetDraggedItem ()) dragtype
=1;
434 if (core
->GetDraggedPortrait ()) dragtype
=2;
436 //if something was dropped, but it isn't handled here: it didn't happen
437 if (dragtype
&& !ButtonOnDragDrop
)
441 case IE_GUI_BUTTON_PRESSED
:
443 SetState( IE_GUI_BUTTON_SELECTED
);
445 SetState( IE_GUI_BUTTON_UNPRESSED
);
448 case IE_GUI_BUTTON_LOCKED_PRESSED
:
449 SetState( IE_GUI_BUTTON_LOCKED
);
453 //in case of dragged/dropped portraits, allow the event to happen even
454 //when we are out of bound
456 if (( x
>= Width
) || ( y
>= Height
)) {
460 if (Flags
& IE_GUI_BUTTON_CHECKBOX
) {
462 ToggleState
= !ToggleState
;
464 SetState( IE_GUI_BUTTON_SELECTED
);
466 SetState( IE_GUI_BUTTON_UNPRESSED
);
467 if (VarName
[0] != 0) {
469 core
->GetDictionary()->Lookup( VarName
, tmp
);
471 core
->GetDictionary()->SetAt( VarName
, tmp
);
472 Owner
->RedrawControls( VarName
, tmp
);
475 if (Flags
& IE_GUI_BUTTON_RADIOBUTTON
) {
478 SetState( IE_GUI_BUTTON_SELECTED
);
480 if (VarName
[0] != 0) {
481 core
->GetDictionary()->SetAt( VarName
, Value
);
482 Owner
->RedrawControls( VarName
, Value
);
488 RunEventHandler( ButtonOnDragDrop
);
491 RunEventHandler( ButtonOnDragDropPortrait
);
495 if ((Button
&GEM_MB_NORMAL
) == GEM_MB_ACTION
) {
496 if ((Mod
& GEM_MOD_SHIFT
) && ButtonOnShiftPress
)
497 RunEventHandler( ButtonOnShiftPress
);
499 RunEventHandler( ButtonOnPress
);
501 if (Button
== GEM_MB_MENU
&& ButtonOnRightPress
)
502 RunEventHandler( ButtonOnRightPress
);
506 void Button::OnMouseOver(unsigned short x
, unsigned short y
)
508 Owner
->Cursor
= IE_CURSOR_NORMAL
;
509 if (State
== IE_GUI_BUTTON_DISABLED
) {
513 if ( RunEventHandler( MouseOverButton
)) {
514 //event handler destructed this object
518 //well, no more flags for buttons, and the portraits we can perform action on
519 //are in fact 'draggable multiline pictures' (with image)
520 if ((Flags
& IE_GUI_BUTTON_DISABLED_P
) == IE_GUI_BUTTON_PORTRAIT
) {
521 GameControl
*gc
= core
->GetGameControl();
523 Owner
->Cursor
= gc
->GetDefaultCursor();
527 if (State
== IE_GUI_BUTTON_LOCKED
) {
531 //portrait buttons are draggable and locked
532 if ((Flags
& IE_GUI_BUTTON_DRAGGABLE
) &&
533 (State
== IE_GUI_BUTTON_PRESSED
|| State
==IE_GUI_BUTTON_LOCKED_PRESSED
)) {
534 // We use absolute screen position here, so drag_start
535 // remains valid even after window/control is moved
536 int dx
= Owner
->XPos
+ XPos
+ x
- drag_start
.x
;
537 int dy
= Owner
->YPos
+ YPos
+ y
- drag_start
.y
;
538 core
->GetDictionary()->SetAt( "DragX", dx
);
539 core
->GetDictionary()->SetAt( "DragY", dy
);
540 drag_start
.x
= (ieWord
) (drag_start
.x
+ dx
);
541 drag_start
.y
= (ieWord
) (drag_start
.y
+ dy
);
542 RunEventHandler( ButtonOnDrag
);
546 void Button::OnMouseEnter(unsigned short /*x*/, unsigned short /*y*/)
548 if (State
== IE_GUI_BUTTON_DISABLED
) {
552 if (MouseEnterButton
!=0 && VarName
[0] != 0) {
553 core
->GetDictionary()->SetAt( VarName
, Value
);
556 RunEventHandler( MouseEnterButton
);
559 void Button::OnMouseLeave(unsigned short /*x*/, unsigned short /*y*/)
561 if (State
== IE_GUI_BUTTON_DISABLED
) {
565 if (MouseLeaveButton
!=0 && VarName
[0] != 0) {
566 core
->GetDictionary()->SetAt( VarName
, Value
);
569 RunEventHandler( MouseLeaveButton
);
573 /** Sets the Text of the current control */
574 int Button::SetText(const char* string
, int /*pos*/)
578 if (string
== NULL
) {
580 } else if (string
[0] == 0) {
583 Text
= strndup( string
, 255 );
584 if (Flags
&IE_GUI_BUTTON_LOWERCASE
)
586 else if (Flags
&IE_GUI_BUTTON_CAPS
)
594 /** Set Event Handler */
595 bool Button::SetEvent(int eventType
, EventHandler handler
)
600 case IE_GUI_BUTTON_ON_PRESS
:
601 ButtonOnPress
= handler
;
603 case IE_GUI_MOUSE_OVER_BUTTON
:
604 MouseOverButton
= handler
;
606 case IE_GUI_MOUSE_ENTER_BUTTON
:
607 MouseEnterButton
= handler
;
609 case IE_GUI_MOUSE_LEAVE_BUTTON
:
610 MouseLeaveButton
= handler
;
612 case IE_GUI_BUTTON_ON_SHIFT_PRESS
:
613 ButtonOnShiftPress
= handler
;
615 case IE_GUI_BUTTON_ON_RIGHT_PRESS
:
616 ButtonOnRightPress
= handler
;
618 case IE_GUI_BUTTON_ON_DRAG_DROP
:
619 ButtonOnDragDrop
= handler
;
621 case IE_GUI_BUTTON_ON_DRAG_DROP_PORTRAIT
:
622 ButtonOnDragDropPortrait
= handler
;
624 case IE_GUI_BUTTON_ON_DRAG
:
625 ButtonOnDrag
= handler
;
627 case IE_GUI_BUTTON_ON_DOUBLE_PRESS
:
628 ButtonOnDoublePress
= handler
;
637 /** Redraws a button from a given radio button group */
638 void Button::RedrawButton(const char* VariableName
, unsigned int Sum
)
640 if (strnicmp( VarName
, VariableName
, MAX_VARIABLE_LENGTH
)) {
643 if (State
== IE_GUI_BUTTON_DISABLED
) {
646 if (Flags
& IE_GUI_BUTTON_RADIOBUTTON
) {
647 ToggleState
= ( Sum
== Value
);
648 } //radio button, exact value
649 else if (Flags
& IE_GUI_BUTTON_CHECKBOX
) {
650 ToggleState
= !!( Sum
& Value
);
651 } //checkbox, bitvalue
654 } //other buttons, nothing to redraw
656 SetState(IE_GUI_BUTTON_SELECTED
);
658 SetState(IE_GUI_BUTTON_UNPRESSED
);
661 /** Sets the Picture */
662 void Button::SetPicture(Sprite2D
* newpic
)
664 core
->GetVideoDriver()->FreeSprite( Picture
);
668 Flags
|= IE_GUI_BUTTON_PICTURE
;
672 /** Clears the list of Pictures */
673 void Button::ClearPictureList()
675 Video
* video
= core
->GetVideoDriver();
676 for (std::list
<Sprite2D
*>::iterator iter
= PictureList
.begin();
677 iter
!= PictureList
.end(); ++iter
)
678 video
->FreeSprite( *iter
);
684 /** Add picture to the end of the list of Pictures */
685 void Button::StackPicture(Sprite2D
* Picture
)
687 PictureList
.push_back(Picture
);
689 Flags
|= IE_GUI_BUTTON_PICTURE
;
693 bool Button::IsPixelTransparent(unsigned short x
, unsigned short y
)
695 // some buttons have hollow Image frame filled w/ Picture
696 // some buttons in BG2 are text only (if BAM == 'GUICTRL')
697 if (Picture
|| PictureList
.size() || ! Unpressed
) return false;
698 return Unpressed
->IsPixelTransparent(x
, y
);
701 // Set palette used for drawing button label in normal state
702 void Button::SetTextColor(const Color
&fore
, const Color
&back
)
704 gamedata
->FreePalette( normal_palette
);
705 normal_palette
= core
->CreatePalette( fore
, back
);
709 void Button::SetHorizontalOverlay(double clip
, const Color
&src
, const Color
&dest
)
711 if ((Clipping
>clip
) || !(Flags
&IE_GUI_BUTTON_HORIZONTAL
) ) {
712 Flags
|= IE_GUI_BUTTON_HORIZONTAL
;
715 GetTime( starttime
);