1 /***************************************************************************
2  *   Free Heroes of Might and Magic II: https://github.com/ihhub/fheroes2  *
3  *   Copyright (C) 2020                                                    *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20 
21 #include "ui_button.h"
22 #include "agg_image.h"
23 #include "dialog.h"
24 #include "game.h"
25 #include "icn.h"
26 #include "localevent.h"
27 #include "pal.h"
28 #include "settings.h"
29 
30 namespace fheroes2
31 {
ButtonBase(int32_t offsetX,int32_t offsetY)32     ButtonBase::ButtonBase( int32_t offsetX, int32_t offsetY )
33         : _offsetX( offsetX )
34         , _offsetY( offsetY )
35         , _isPressed( false )
36         , _isEnabled( true )
37         , _isVisible( true )
38         , _releasedSprite( nullptr )
39         , _disabledSprite()
40     {}
41 
ButtonBase(ButtonBase && button)42     ButtonBase::ButtonBase( ButtonBase && button ) noexcept
43         : ButtonBase()
44     {
45         _swap( button );
46     }
47 
operator =(ButtonBase && button)48     ButtonBase & ButtonBase::operator=( ButtonBase && button ) noexcept
49     {
50         if ( this != &button ) {
51             _swap( button );
52         }
53         return *this;
54     }
55 
_swap(ButtonBase & button)56     void ButtonBase::_swap( ButtonBase & button )
57     {
58         std::swap( _offsetX, button._offsetX );
59         std::swap( _offsetY, button._offsetY );
60         std::swap( _isPressed, button._isPressed );
61         std::swap( _isEnabled, button._isEnabled );
62         std::swap( _isVisible, button._isVisible );
63         std::swap( _releasedSprite, button._releasedSprite );
64         std::swap( _disabledSprite, button._disabledSprite );
65     }
66 
isEnabled() const67     bool ButtonBase::isEnabled() const
68     {
69         return _isEnabled;
70     }
71 
isDisabled() const72     bool ButtonBase::isDisabled() const
73     {
74         return !_isEnabled;
75     }
76 
isPressed() const77     bool ButtonBase::isPressed() const
78     {
79         return _isPressed;
80     }
81 
isReleased() const82     bool ButtonBase::isReleased() const
83     {
84         return !_isPressed;
85     }
86 
isVisible() const87     bool ButtonBase::isVisible() const
88     {
89         return _isVisible;
90     }
91 
isHidden() const92     bool ButtonBase::isHidden() const
93     {
94         return !_isVisible;
95     }
96 
press()97     void ButtonBase::press()
98     {
99         if ( isEnabled() ) {
100             _isPressed = true;
101             updateSubscription();
102         }
103     }
104 
release()105     void ButtonBase::release()
106     {
107         if ( isEnabled() ) {
108             _isPressed = false;
109             updateSubscription();
110         }
111     }
112 
enable()113     void ButtonBase::enable()
114     {
115         _isEnabled = true;
116         updateSubscription();
117     }
118 
disable()119     void ButtonBase::disable()
120     {
121         _isEnabled = false;
122         _isPressed = false; // button can't be disabled and pressed
123         updateSubscription();
124     }
125 
show()126     void ButtonBase::show()
127     {
128         _isVisible = true;
129         updateSubscription();
130     }
131 
hide()132     void ButtonBase::hide()
133     {
134         _isVisible = false;
135         updateSubscription();
136     }
137 
setPosition(int32_t offsetX_,int32_t offsetY_)138     void ButtonBase::setPosition( int32_t offsetX_, int32_t offsetY_ )
139     {
140         _offsetX = offsetX_;
141         _offsetY = offsetY_;
142     }
143 
draw(Image & output) const144     void ButtonBase::draw( Image & output ) const
145     {
146         if ( !isVisible() )
147             return;
148 
149         if ( isPressed() ) {
150             // button can't be disabled and pressed
151             const Sprite & sprite = _getPressed();
152             Blit( sprite, output, _offsetX + sprite.x(), _offsetY + sprite.y() );
153         }
154         else {
155             const Sprite & sprite = isEnabled() ? _getReleased() : _getDisabled();
156             Blit( sprite, output, _offsetX + sprite.x(), _offsetY + sprite.y() );
157         }
158     }
159 
drawOnPress(Image & output)160     bool ButtonBase::drawOnPress( Image & output )
161     {
162         if ( !isPressed() ) {
163             press();
164             draw( output );
165             Display::instance().render( area() );
166             return true;
167         }
168         return false;
169     }
170 
drawOnRelease(Image & output)171     bool ButtonBase::drawOnRelease( Image & output )
172     {
173         if ( isPressed() ) {
174             release();
175             draw( output );
176             Display::instance().render( area() );
177             return true;
178         }
179         return false;
180     }
181 
area() const182     Rect ButtonBase::area() const
183     {
184         const Sprite & sprite = isPressed() ? _getPressed() : _getReleased();
185         return Rect( _offsetX + sprite.x(), _offsetY + sprite.y(), sprite.width(), sprite.height() );
186     }
187 
_getDisabled() const188     const Sprite & ButtonBase::_getDisabled() const
189     {
190         const Sprite & sprite = _getReleased();
191         if ( !_disabledSprite || ( _releasedSprite != &sprite ) ) {
192             _releasedSprite = &sprite;
193             _disabledSprite.reset( new Sprite( sprite ) );
194             ApplyPalette( *_disabledSprite, PAL::GetPalette( PAL::PaletteType::DARKENING ) );
195         }
196 
197         return *_disabledSprite.get();
198     }
199 
Button(int32_t offsetX,int32_t offsetY)200     Button::Button( int32_t offsetX, int32_t offsetY )
201         : ButtonBase( offsetX, offsetY )
202         , _icnId( -1 )
203         , _releasedIndex( 0 )
204         , _pressedIndex( 0 )
205     {}
206 
Button(int32_t offsetX,int32_t offsetY,int icnId,uint32_t releasedIndex,uint32_t pressedIndex)207     Button::Button( int32_t offsetX, int32_t offsetY, int icnId, uint32_t releasedIndex, uint32_t pressedIndex )
208         : ButtonBase( offsetX, offsetY )
209         , _icnId( icnId )
210         , _releasedIndex( releasedIndex )
211         , _pressedIndex( pressedIndex )
212     {}
213 
setICNInfo(int icnId,uint32_t releasedIndex,uint32_t pressedIndex)214     void Button::setICNInfo( int icnId, uint32_t releasedIndex, uint32_t pressedIndex )
215     {
216         _icnId = icnId;
217         _releasedIndex = releasedIndex;
218         _pressedIndex = pressedIndex;
219     }
220 
_getPressed() const221     const Sprite & Button::_getPressed() const
222     {
223         return AGG::GetICN( _icnId, _pressedIndex );
224     }
225 
_getReleased() const226     const Sprite & Button::_getReleased() const
227     {
228         return AGG::GetICN( _icnId, _releasedIndex );
229     }
230 
ButtonSprite(int32_t offsetX,int32_t offsetY)231     ButtonSprite::ButtonSprite( int32_t offsetX, int32_t offsetY )
232         : ButtonBase( offsetX, offsetY )
233     {}
234 
ButtonSprite(int32_t offsetX,int32_t offsetY,const Sprite & released,const Sprite & pressed,const Sprite & disabled)235     ButtonSprite::ButtonSprite( int32_t offsetX, int32_t offsetY, const Sprite & released, const Sprite & pressed, const Sprite & disabled )
236         : ButtonBase( offsetX, offsetY )
237         , _released( released )
238         , _pressed( pressed )
239         , _disabled( disabled )
240     {}
241 
ButtonSprite(ButtonSprite && button)242     ButtonSprite::ButtonSprite( ButtonSprite && button ) noexcept
243         : ButtonBase( std::move( button ) )
244     {
245         std::swap( _released, button._released );
246         std::swap( _pressed, button._pressed );
247         std::swap( _disabled, button._disabled );
248     }
249 
operator =(ButtonSprite && button)250     ButtonSprite & ButtonSprite::operator=( ButtonSprite && button ) noexcept
251     {
252         if ( this != &button ) {
253             ButtonBase::_swap( button );
254             std::swap( _released, button._released );
255             std::swap( _pressed, button._pressed );
256             std::swap( _disabled, button._disabled );
257         }
258         return *this;
259     }
260 
setSprite(const Sprite & released,const Sprite & pressed,const Sprite & disabled)261     void ButtonSprite::setSprite( const Sprite & released, const Sprite & pressed, const Sprite & disabled )
262     {
263         _released = released;
264         _pressed = pressed;
265         _disabled = disabled;
266     }
267 
_getPressed() const268     const Sprite & ButtonSprite::_getPressed() const
269     {
270         return _pressed;
271     }
272 
_getReleased() const273     const Sprite & ButtonSprite::_getReleased() const
274     {
275         return _released;
276     }
277 
_getDisabled() const278     const Sprite & ButtonSprite::_getDisabled() const
279     {
280         if ( _disabled.empty() ) {
281             return ButtonBase::_getDisabled();
282         }
283 
284         return _disabled;
285     }
286 
ButtonGroup(const Rect & area,int buttonTypes)287     ButtonGroup::ButtonGroup( const Rect & area, int buttonTypes )
288     {
289         const int icnId = Settings::Get().ExtGameEvilInterface() ? ICN::SYSTEME : ICN::SYSTEM;
290 
291         Point offset;
292 
293         switch ( buttonTypes ) {
294         case Dialog::YES | Dialog::NO:
295             offset.x = area.x;
296             offset.y = area.y + area.height - AGG::GetICN( icnId, 5 ).height();
297             createButton( offset.x, offset.y, icnId, 5, 6, Dialog::YES );
298 
299             offset.x = area.x + area.width - AGG::GetICN( icnId, 7 ).width();
300             offset.y = area.y + area.height - AGG::GetICN( icnId, 7 ).height();
301             createButton( offset.x, offset.y, icnId, 7, 8, Dialog::NO );
302             break;
303 
304         case Dialog::OK | Dialog::CANCEL:
305             offset.x = area.x;
306             offset.y = area.y + area.height - AGG::GetICN( icnId, 1 ).height();
307             createButton( offset.x, offset.y, icnId, 1, 2, Dialog::OK );
308 
309             offset.x = area.x + area.width - AGG::GetICN( icnId, 3 ).width();
310             offset.y = area.y + area.height - AGG::GetICN( icnId, 3 ).height();
311             createButton( offset.x, offset.y, icnId, 3, 4, Dialog::CANCEL );
312             break;
313 
314         case Dialog::OK:
315             offset.x = area.x + ( area.width - AGG::GetICN( icnId, 1 ).width() ) / 2;
316             offset.y = area.y + area.height - AGG::GetICN( icnId, 1 ).height();
317             createButton( offset.x, offset.y, icnId, 1, 2, Dialog::OK );
318             break;
319 
320         case Dialog::CANCEL:
321             offset.x = area.x + ( area.width - AGG::GetICN( icnId, 3 ).width() ) / 2;
322             offset.y = area.y + area.height - AGG::GetICN( icnId, 3 ).height();
323             createButton( offset.x, offset.y, icnId, 3, 4, Dialog::CANCEL );
324             break;
325 
326         default:
327             break;
328         }
329     }
330 
~ButtonGroup()331     ButtonGroup::~ButtonGroup()
332     {
333         for ( size_t i = 0; i < _button.size(); ++i ) {
334             delete _button[i];
335         }
336 
337         _button.clear();
338         _value.clear();
339     }
340 
createButton(int32_t offsetX,int32_t offsetY,int icnId,uint32_t releasedIndex,uint32_t pressedIndex,int returnValue)341     void ButtonGroup::createButton( int32_t offsetX, int32_t offsetY, int icnId, uint32_t releasedIndex, uint32_t pressedIndex, int returnValue )
342     {
343         _button.push_back( new Button( offsetX, offsetY, icnId, releasedIndex, pressedIndex ) );
344         _value.emplace_back( returnValue );
345     }
346 
createButton(int32_t offsetX,int32_t offsetY,const Sprite & released,const Sprite & pressed,int returnValue)347     void ButtonGroup::createButton( int32_t offsetX, int32_t offsetY, const Sprite & released, const Sprite & pressed, int returnValue )
348     {
349         _button.push_back( new ButtonSprite( offsetX, offsetY, released, pressed ) );
350         _value.emplace_back( returnValue );
351     }
352 
addButton(ButtonSprite && button,int returnValue)353     void ButtonGroup::addButton( ButtonSprite && button, int returnValue )
354     {
355         _button.push_back( new ButtonSprite( std::move( button ) ) );
356         _value.emplace_back( returnValue );
357     }
358 
draw(Image & area) const359     void ButtonGroup::draw( Image & area ) const
360     {
361         for ( size_t i = 0; i < _button.size(); ++i ) {
362             _button[i]->draw( area );
363         }
364     }
365 
button(size_t id)366     ButtonBase & ButtonGroup::button( size_t id )
367     {
368         return *_button[id];
369     }
370 
button(size_t id) const371     const ButtonBase & ButtonGroup::button( size_t id ) const
372     {
373         return *_button[id];
374     }
375 
size() const376     size_t ButtonGroup::size() const
377     {
378         return _button.size();
379     }
380 
processEvents()381     int ButtonGroup::processEvents()
382     {
383         LocalEvent & le = LocalEvent::Get();
384 
385         for ( size_t i = 0; i < _button.size(); ++i ) {
386             if ( _button[i]->isEnabled() ) {
387                 le.MousePressLeft( _button[i]->area() ) ? _button[i]->drawOnPress() : _button[i]->drawOnRelease();
388             }
389         }
390 
391         for ( size_t i = 0; i < _button.size(); ++i ) {
392             if ( _button[i]->isEnabled() && le.MouseClickLeft( _button[i]->area() ) ) {
393                 return _value[i];
394             }
395         }
396 
397         for ( size_t i = 0; i < _button.size(); ++i ) {
398             if ( _button[i]->isEnabled() ) {
399                 if ( ( _value[i] == Dialog::YES || _value[i] == Dialog::OK ) && Game::HotKeyPressEvent( Game::EVENT_DEFAULT_READY ) ) {
400                     return _value[i];
401                 }
402                 if ( ( _value[i] == Dialog::CANCEL || _value[i] == Dialog::NO ) && Game::HotKeyPressEvent( Game::EVENT_DEFAULT_EXIT ) ) {
403                     return _value[i];
404                 }
405             }
406         }
407 
408         return Dialog::ZERO;
409     }
410 
ButtonRestorer(ButtonBase & button,Image & area)411     ButtonRestorer::ButtonRestorer( ButtonBase & button, Image & area )
412         : _button( button )
413         , _area( area )
414         , _isDisabled( button.isDisabled() )
415     {
416         if ( !_isDisabled ) {
417             _button.disable();
418             _button.draw( _area );
419         }
420     }
421 
~ButtonRestorer()422     ButtonRestorer::~ButtonRestorer()
423     {
424         if ( !_isDisabled ) {
425             _button.enable();
426             _button.draw( _area );
427         }
428     }
429 
addButton(ButtonBase * button)430     void OptionButtonGroup::addButton( ButtonBase * button )
431     {
432         if ( button == nullptr )
433             return;
434 
435         _button.push_back( button );
436         button->subscribe( this );
437     }
438 
draw(Image & area) const439     void OptionButtonGroup::draw( Image & area ) const
440     {
441         for ( size_t i = 0; i < _button.size(); ++i ) {
442             _button[i]->draw( area );
443         }
444     }
445 
senderUpdate(const ActionObject * sender)446     void OptionButtonGroup::senderUpdate( const ActionObject * sender )
447     {
448         if ( sender == nullptr ) // how is it even possible?
449             return;
450 
451         for ( size_t i = 0; i < _button.size(); ++i ) {
452             if ( sender == _button[i] ) {
453                 const ButtonBase * button = _button[i];
454                 if ( button->isPressed() ) {
455                     unsubscribeAll();
456 
457                     for ( size_t buttonId = 0; buttonId < _button.size(); ++buttonId ) {
458                         if ( i != buttonId ) {
459                             _button[buttonId]->release();
460                         }
461                     }
462 
463                     subscribeAll();
464                 }
465             }
466         }
467     }
468 
subscribeAll()469     void OptionButtonGroup::subscribeAll()
470     {
471         for ( size_t i = 0; i < _button.size(); ++i ) {
472             _button[i]->subscribe( this );
473         }
474     }
475 
unsubscribeAll()476     void OptionButtonGroup::unsubscribeAll()
477     {
478         for ( size_t i = 0; i < _button.size(); ++i ) {
479             _button[i]->unsubscribe();
480         }
481     }
482 
makeButtonWithBackground(int32_t offsetX,int32_t offsetY,const Sprite & released,const Sprite & pressed,const Image & background)483     ButtonSprite makeButtonWithBackground( int32_t offsetX, int32_t offsetY, const Sprite & released, const Sprite & pressed, const Image & background )
484     {
485         const Sprite croppedBackground = Crop( background, offsetX, offsetY, released.width(), released.height() );
486 
487         Sprite releasedWithBackground( croppedBackground.width(), croppedBackground.height(), 0, 0 );
488         Copy( croppedBackground, releasedWithBackground );
489         Blit( released, releasedWithBackground, released.x(), released.y() );
490 
491         Sprite pressedWithBackground( croppedBackground.width(), croppedBackground.height(), 0, 0 );
492         Copy( croppedBackground, pressedWithBackground );
493         Blit( pressed, pressedWithBackground, pressed.x(), pressed.y() );
494 
495         Sprite disabled( released );
496         ApplyPalette( disabled, PAL::GetPalette( PAL::PaletteType::DARKENING ) );
497 
498         Sprite disabledWithBackground( croppedBackground.width(), croppedBackground.height(), 0, 0 );
499         Copy( croppedBackground, disabledWithBackground );
500         disabledWithBackground.setPosition( 0, 0 );
501         Blit( disabled, disabledWithBackground, disabled.x(), disabled.y() );
502 
503         return { offsetX, offsetY, releasedWithBackground, pressedWithBackground, disabledWithBackground };
504     }
505 
makeButtonWithShadow(int32_t offsetX,int32_t offsetY,const Sprite & released,const Sprite & pressed,const Image & background,const Point & shadowOffset)506     ButtonSprite makeButtonWithShadow( int32_t offsetX, int32_t offsetY, const Sprite & released, const Sprite & pressed, const Image & background,
507                                        const Point & shadowOffset )
508     {
509         const Sprite & shadow = fheroes2::makeShadow( released, shadowOffset, 3 );
510 
511         Sprite croppedBackground = Crop( background, offsetX + shadow.x(), offsetY + shadow.y(), shadow.width(), shadow.height() );
512         Blit( shadow, croppedBackground );
513 
514         Sprite releasedWithBackground( croppedBackground.width(), croppedBackground.height(), 0, 0 );
515         Copy( croppedBackground, releasedWithBackground );
516         Blit( released, releasedWithBackground, released.x() - shadow.x(), released.y() - shadow.y() );
517 
518         Sprite pressedWithBackground( croppedBackground.width(), croppedBackground.height(), 0, 0 );
519         Copy( croppedBackground, pressedWithBackground );
520         Blit( pressed, pressedWithBackground, pressed.x() - shadow.x(), pressed.y() - shadow.y() );
521 
522         Sprite disabled( released );
523         ApplyPalette( disabled, PAL::GetPalette( PAL::PaletteType::DARKENING ) );
524 
525         Sprite disabledWithBackground( croppedBackground.width(), croppedBackground.height(), 0, 0 );
526         Copy( croppedBackground, disabledWithBackground );
527         disabledWithBackground.setPosition( 0, 0 );
528         Blit( disabled, disabledWithBackground, disabled.x() - shadow.x(), disabled.y() - shadow.y() );
529 
530         return { offsetX + shadow.x(), offsetY + shadow.y(), releasedWithBackground, pressedWithBackground, disabledWithBackground };
531     }
532 }
533