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