diff --git a/src/engine/image.cpp b/src/engine/image.cpp index 0263c094350..f8bd5124dd5 100644 --- a/src/engine/image.cpp +++ b/src/engine/image.cpp @@ -1271,6 +1271,11 @@ namespace fheroes2 Copy( in, 0, 0, out, 0, 0, in.width(), in.height() ); } + void Copy( const Image & in, int32_t inX, int32_t inY, Image & out, const Rect & outRoi ) + { + Copy( in, inX, inY, out, outRoi.x, outRoi.y, outRoi.width, outRoi.height ); + } + void Copy( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height ) { if ( !Verify( in, inX, inY, out, outX, outY, width, height ) ) { @@ -2645,6 +2650,25 @@ namespace fheroes2 } } + void ReplaceTransformIdByColorId( Image & image, const uint8_t transformId, const uint8_t colorId ) + { + if ( image.empty() || image.singleLayer() ) { + return; + } + + const int32_t size = image.width() * image.height(); + + uint8_t * imageIn = image.image(); + uint8_t * transformIn = image.transform(); + const uint8_t * imageInEnd = imageIn + size; + for ( ; imageIn != imageInEnd; ++imageIn, ++transformIn ) { + if ( *transformIn == transformId ) { + *transformIn = 0U; + *imageIn = colorId; + } + } + } + void Resize( const Image & in, Image & out, const bool isSubpixelAccuracy ) { if ( in.empty() || out.empty() ) { diff --git a/src/engine/image.h b/src/engine/image.h index 9a746cc2210..cf65ee08fbe 100644 --- a/src/engine/image.h +++ b/src/engine/image.h @@ -236,6 +236,7 @@ namespace fheroes2 void Blit( const Image & in, const Point & inPos, Image & out, const Point & outPos, const Size & size, bool flip = false ); void Copy( const Image & in, Image & out ); + void Copy( const Image & in, int32_t inX, int32_t inY, Image & out, const Rect & outRoi ); void Copy( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height ); // Copies transform the layer from in to out. Both images must be of the same size. @@ -295,6 +296,9 @@ namespace fheroes2 // Use this function only when you need to convert pixel value into transform layer void ReplaceColorIdByTransformId( Image & image, const uint8_t colorId, const uint8_t transformId ); + // Use this function only when you need to convert transform value into non-transparent pixel with the given color. + void ReplaceTransformIdByColorId( Image & image, const uint8_t transformId, const uint8_t colorId ); + // Please remember that subpixel accuracy resizing is extremely slow! void Resize( const Image & in, Image & out, const bool isSubpixelAccuracy = false ); diff --git a/src/fheroes2/agg/agg_image.cpp b/src/fheroes2/agg/agg_image.cpp index e36590d71c7..d747fa18d91 100644 --- a/src/fheroes2/agg/agg_image.cpp +++ b/src/fheroes2/agg/agg_image.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -547,6 +548,45 @@ namespace std::fill( imageTransform + ( imageHeight - 1 ) * imageWidth - 3, imageTransform + ( imageHeight - 1 ) * imageWidth, transparencyValue ); std::fill( imageTransform + imageHeight * imageWidth - 4, imageTransform + imageHeight * imageWidth, transparencyValue ); } + + // Remove all shadows from the image and make them fully transparent. + void setTransformLayerTransparent( fheroes2::Image & image ) + { + assert( !image.empty() && !image.singleLayer() ); + + uint8_t * transform = image.transform(); + const uint8_t * transformEnd = transform + static_cast( image.width() ) * image.height(); + + for ( ; transform != transformEnd; ++transform ) { + if ( *transform > 1 ) { + *transform = 1U; + } + } + } + + // Draw the given image in the center of the button images (released and pressed states) and add extra shading and brightening at the edges of the image. + void drawImageOnButton( const fheroes2::Image & image, const int32_t maxImageWidth, const int32_t maxImageHeight, fheroes2::Image & releasedSprite, + fheroes2::Image & pressedSprite ) + { + assert( !image.empty() && !releasedSprite.empty() && !pressedSprite.empty() ); + + fheroes2::Image newImage( std::min( maxImageWidth, image.width() + 4 ), std::min( maxImageHeight, image.height() + 4 ) ); + newImage.reset(); + fheroes2::Blit( image, 0, 0, newImage, 2, 2, 35, 25 ); + // Remove shadow from the image. + setTransformLayerTransparent( newImage ); + // Add extra shading and brightening at the edges of the image. + fheroes2::updateShadow( newImage, { 1, -1 }, 2U, false ); + fheroes2::updateShadow( newImage, { 2, -2 }, 5U, false ); + fheroes2::updateShadow( newImage, { -1, 1 }, 6U, false ); + fheroes2::updateShadow( newImage, { -2, 2 }, 9U, false ); + // Draw the image on the button. + const int32_t offsetX = ( pressedSprite.width() - newImage.width() ) / 2; + const int32_t offsetY = ( pressedSprite.height() - newImage.height() ) / 2; + fheroes2::Blit( newImage, pressedSprite, offsetX + 2, offsetY + 1 ); + fheroes2::ReplaceTransformIdByColorId( newImage, 6U, 10U ); + fheroes2::Blit( newImage, releasedSprite, offsetX + 3, offsetY ); + } } namespace fheroes2 @@ -2507,7 +2547,7 @@ namespace fheroes2 // Generate random spell image for Editor. { const Sprite & randomSpellImage = _icnVsSprite[id][2]; - int32_t imageWidth = randomSpellImage.width(); + const int32_t imageWidth = randomSpellImage.width(); Copy( randomSpellImage, _icnVsSprite[id][67] ); @@ -2667,9 +2707,7 @@ namespace fheroes2 Sprite & out = _icnVsSprite[id][23]; std::vector indexes( 256 ); - for ( uint32_t i = 0; i < 256; ++i ) { - indexes[i] = static_cast( i ); - } + std::iota( indexes.begin(), indexes.end(), static_cast( 0 ) ); indexes[69] = 187; indexes[71] = 195; @@ -2732,9 +2770,7 @@ namespace fheroes2 } std::vector indexes( 256 ); - for ( uint32_t i = 0; i < 256; ++i ) { - indexes[i] = static_cast( i ); - } + std::iota( indexes.begin(), indexes.end(), static_cast( 0 ) ); indexes[10] = 152; indexes[11] = 153; @@ -3029,35 +3065,108 @@ namespace fheroes2 return true; } + case ICN::EDITBTNS: + LoadOriginalICN( id ); + if ( _icnVsSprite[id].size() == 35 ) { + // We add three buttons for new object groups: Adventure, Kingdom, Monsters. + _icnVsSprite[id].resize( 41 ); + + // First make clean button sprites (pressed and released). + Sprite released = GetICN( ICN::EDITBTNS, 4 ); + Sprite pressed = GetICN( ICN::EDITBTNS, 5 ); + // Clean the image from the button. + Fill( released, 16, 6, 18, 24, 41U ); + Fill( pressed, 16, 7, 17, 23, 46U ); + + for ( size_t i = 0; i < 4; i += 2 ) { + _icnVsSprite[id][35 + i] = released; + _icnVsSprite[id][35 + 1 + i] = pressed; + } + _icnVsSprite[id][39] = std::move( released ); + _icnVsSprite[id][40] = std::move( pressed ); + + // Adventure objects button. + drawImageOnButton( GetICN( ICN::X_LOC1, 0 ), 39, 29, _icnVsSprite[id][35], _icnVsSprite[id][36] ); + + // Kingdom objects button. + drawImageOnButton( GetICN( ICN::OBJNARTI, 13 ), 39, 29, _icnVsSprite[id][37], _icnVsSprite[id][38] ); + + // Monsters objects button. + drawImageOnButton( GetICN( ICN::MONS32, 11 ), 39, 29, _icnVsSprite[id][39], _icnVsSprite[id][40] ); + } + return true; + case ICN::EDITBTNS_EVIL: { + loadICN( ICN::EDITBTNS ); + _icnVsSprite[id] = _icnVsSprite[ICN::EDITBTNS]; + for ( auto & image : _icnVsSprite[id] ) { + convertToEvilInterface( image, { 0, 0, image.width(), image.height() } ); + } + return true; + } case ICN::EDITPANL: LoadOriginalICN( id ); - if ( _icnVsSprite[id].size() > 5 ) { - Sprite & erasePanel = _icnVsSprite[id][5]; - // To select object types for erasure we copy object buttons. - Copy( _icnVsSprite[id][1], 15, 68, erasePanel, 15, 68, 114, 55 ); - - // Make 3 empty buttons for terrain objects, roads and streams. - Fill( erasePanel, 16, 69, 24, 24, 65U ); - Copy( erasePanel, 15, 68, erasePanel, 44, 96, 27, 27 ); - Copy( erasePanel, 15, 68, erasePanel, 73, 96, 27, 27 ); - - // Make erase terrain objects button image. - Image objectsImage( 24, 24 ); - Copy( GetICN( ICN::EDITBTNS, 2 ), 13, 4, objectsImage, 0, 0, 24, 24 ); - AddTransparency( objectsImage, 10U ); - AddTransparency( objectsImage, 38U ); - AddTransparency( objectsImage, 39U ); - AddTransparency( objectsImage, 40U ); - AddTransparency( objectsImage, 41U ); - AddTransparency( objectsImage, 46U ); - Blit( objectsImage, 0, 0, erasePanel, 16, 69, 24, 24 ); - - // Make erase roads button image. - Blit( GetICN( ICN::ROAD, 2 ), 0, 0, erasePanel, 45, 104, 24, 5 ); - Blit( GetICN( ICN::ROAD, 1 ), 1, 0, erasePanel, 45, 109, 24, 5 ); - - // Make erase streams button image. - Blit( GetICN( ICN::STREAM, 2 ), 0, 0, erasePanel, 74, 104, 24, 11 ); + if ( _icnVsSprite[id].size() == 6 ) { + _icnVsSprite[id].resize( 18 ); + + // Make empty buttons for object types. + _icnVsSprite[id][6].resize( 27, 27 ); + _icnVsSprite[id][6]._disableTransformLayer(); + _icnVsSprite[id][6].reset(); + Fill( _icnVsSprite[id][6], 1, 1, 24, 24, 65U ); + for ( size_t i = 7; i < _icnVsSprite[id].size(); ++i ) { + _icnVsSprite[id][i] = _icnVsSprite[id][6]; + } + + // Make Mountains objects button. + Blit( GetICN( ICN::MTNCRCK, 3 ), 4, 0, _icnVsSprite[id][6], 1, 1, 24, 24 ); + + // Make Rocks objects button. + Blit( GetICN( ICN::OBJNGRAS, 41 ), 1, 0, _icnVsSprite[id][7], 1, 9, 23, 12 ); + + // Make Trees object button. Also used as erase terrain objects button image. + Copy( GetICN( ICN::EDITBTNS, 2 ), 13, 4, _icnVsSprite[id][8], 1, 1, 24, 24 ); + + // Replace image contour colors with the background color. + std::vector indexes( 256 ); + std::iota( indexes.begin(), indexes.end(), static_cast( 0 ) ); + + indexes[10] = 65U; + indexes[38] = 65U; + indexes[39] = 65U; + indexes[40] = 65U; + indexes[41] = 65U; + indexes[46] = 65U; + + ApplyPalette( _icnVsSprite[id][8], indexes ); + + // Make Landscape Water objects button. + Blit( GetICN( ICN::OBJNWAT2, 0 ), 0, 3, _icnVsSprite[id][9], 5, 1, 13, 3 ); + Blit( GetICN( ICN::OBJNWAT2, 2 ), 5, 0, _icnVsSprite[id][9], 1, 4, 24, 21 ); + + // Make Landscape Miscellaneous objects button. + Blit( GetICN( ICN::OBJNMUL2, 16 ), 3, 0, _icnVsSprite[id][10], 1, 4, 24, 19 ); + + // Make erase Dwellings button image. + Blit( GetICN( ICN::OBJNMULT, 114 ), 7, 0, _icnVsSprite[id][11], 1, 1, 24, 24 ); + + // Make erase Mines button image. + Blit( GetICN( ICN::MTNMULT, 82 ), 8, 4, _icnVsSprite[id][12], 1, 1, 24, 24 ); + + // Make erase Power-ups button image. + Blit( GetICN( ICN::OBJNMULT, 72 ), 0, 6, _icnVsSprite[id][13], 1, 1, 24, 24 ); + + // Make Adventure Water objects button. + Blit( GetICN( ICN::OBJNWATR, 24 ), 3, 0, _icnVsSprite[id][14], 1, 1, 24, 24 ); + + // Make Adventure Miscellaneous objects button. + Blit( GetICN( ICN::OBJNMUL2, 198 ), 2, 0, _icnVsSprite[id][15], 1, 1, 24, 24 ); + + // Make erase Roads button image. + Blit( GetICN( ICN::ROAD, 2 ), 0, 0, _icnVsSprite[id][16], 1, 8, 24, 5 ); + Blit( GetICN( ICN::ROAD, 1 ), 1, 0, _icnVsSprite[id][16], 1, 13, 24, 5 ); + + // Make erase Streams button image. + Blit( GetICN( ICN::STREAM, 2 ), 0, 0, _icnVsSprite[id][17], 1, 8, 24, 11 ); } return true; case ICN::EDITOR: diff --git a/src/fheroes2/agg/icn.h b/src/fheroes2/agg/icn.h index 3b5a54467a8..f687b198cfd 100644 --- a/src/fheroes2/agg/icn.h +++ b/src/fheroes2/agg/icn.h @@ -977,6 +977,7 @@ namespace ICN CASLXTRA_EVIL, RECRBKG_EVIL, STRIP_BACKGROUND_EVIL, + EDITBTNS_EVIL, GOOD_CAMPAIGN_BUTTONS, EVIL_CAMPAIGN_BUTTONS, diff --git a/src/fheroes2/dialog/dialog_selectitems.cpp b/src/fheroes2/dialog/dialog_selectitems.cpp index 469467f02c1..71291e2c3ec 100644 --- a/src/fheroes2/dialog/dialog_selectitems.cpp +++ b/src/fheroes2/dialog/dialog_selectitems.cpp @@ -866,13 +866,22 @@ int Dialog::selectTreasureType( const int resourceType ) return selectObjectType( resourceType, objectInfo.size(), listbox ); } -int Dialog::selectOceanObjectType( const int resourceType ) +int Dialog::selectOceanObjectType( const int objectType ) { const auto & objectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::ADVENTURE_WATER ); OceanObjectTypeSelection listbox( objectInfo, { 350, fheroes2::Display::instance().height() - 200 }, _( "Select Ocean Object:" ) ); - return selectObjectType( resourceType, objectInfo.size(), listbox ); + return selectObjectType( objectType, objectInfo.size(), listbox ); +} + +int Dialog::selectLandscapeOceanObjectType( const int objectType ) +{ + const auto & objectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::LANDSCAPE_WATER ); + + OceanObjectTypeSelection listbox( objectInfo, { 350, fheroes2::Display::instance().height() - 200 }, _( "Select Ocean Object:" ) ); + + return selectObjectType( objectType, objectInfo.size(), listbox ); } void Dialog::selectTownType( int & type, int & color ) diff --git a/src/fheroes2/dialog/dialog_selectitems.h b/src/fheroes2/dialog/dialog_selectitems.h index 71e6ac44731..5393e29386c 100644 --- a/src/fheroes2/dialog/dialog_selectitems.h +++ b/src/fheroes2/dialog/dialog_selectitems.h @@ -59,7 +59,9 @@ namespace Dialog int selectTreasureType( const int resourceType ); - int selectOceanObjectType( const int resourceType ); + int selectOceanObjectType( const int objectType ); + + int selectLandscapeOceanObjectType( const int objectType ); void selectTownType( int & type, int & color ); } diff --git a/src/fheroes2/editor/editor_interface.cpp b/src/fheroes2/editor/editor_interface.cpp index a6dab46ae04..edaf6504381 100644 --- a/src/fheroes2/editor/editor_interface.cpp +++ b/src/fheroes2/editor/editor_interface.cpp @@ -46,7 +46,6 @@ #include "interface_border.h" #include "interface_gamearea.h" #include "interface_radar.h" -#include "interface_status.h" #include "localevent.h" #include "map_format_helper.h" #include "map_format_info.h" @@ -164,13 +163,7 @@ namespace Interface const int32_t xOffset = display.width() - BORDERWIDTH - RADARWIDTH; _radar.SetPos( xOffset, BORDERWIDTH ); - if ( display.height() > display.DEFAULT_HEIGHT + BORDERWIDTH ) { - _editorPanel.setPos( xOffset, _radar.GetArea().y + _radar.GetArea().height + BORDERWIDTH ); - _statusWindow.SetPos( xOffset, _editorPanel.getRect().y + _editorPanel.getRect().height ); - } - else { - _editorPanel.setPos( xOffset, _radar.GetArea().y + _radar.GetArea().height ); - } + _editorPanel.setPos( xOffset, _radar.GetArea().y + _radar.GetArea().height + ( ( display.height() > display.DEFAULT_HEIGHT + BORDERWIDTH ) ? BORDERWIDTH : 0 ) ); const fheroes2::Point prevCenter = _gameArea.getCurrentCenterInPixels(); const fheroes2::Rect prevRoi = _gameArea.GetROI(); @@ -227,13 +220,6 @@ namespace Interface _editorPanel._redraw(); } - if ( ( combinedRedraw & REDRAW_STATUS ) && ( display.height() > display.DEFAULT_HEIGHT + BORDERWIDTH ) ) { - // Currently the Adventure Map status is rendered to fill the space under the Editor buttons on high resolutions. - // TODO: Make special status for Editor to display some map info, e.g. object properties under the cursor (castle garrison, amount of resources, etc.) - // TODO: Decide where to output the status for low resolutions (reduce the number of displayed buttons - put some into sub-menu). - _statusWindow._redraw(); - } - _redraw = 0; } @@ -832,7 +818,7 @@ namespace Interface setObjectOnTile( tile, objectInfo ); } } - else if ( groupType == Maps::ObjectGroup::ADVENTURE_WATER ) { + else if ( groupType == Maps::ObjectGroup::ADVENTURE_WATER || groupType == Maps::ObjectGroup::LANDSCAPE_WATER ) { const auto & objectInfo = getObjectInfo( groupType, _editorPanel.getSelectedObjectType() ); if ( !isObjectPlacementAllowed( objectInfo, tilePos ) ) { diff --git a/src/fheroes2/editor/editor_interface_panel.cpp b/src/fheroes2/editor/editor_interface_panel.cpp index f43e700f223..e1d72a8f2ce 100644 --- a/src/fheroes2/editor/editor_interface_panel.cpp +++ b/src/fheroes2/editor/editor_interface_panel.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -33,39 +34,52 @@ #include "dialog_selectitems.h" #include "dialog_system_options.h" #include "editor_interface.h" +#include "gamedefs.h" #include "ground.h" #include "icn.h" #include "image.h" #include "interface_base.h" #include "localevent.h" #include "maps_tiles.h" +#include "monster.h" +#include "pal.h" #include "screen.h" +#include "settings.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_map_object.h" #include "ui_text.h" +#include "ui_window.h" #include "world.h" namespace { - void setCustomCursor( const Maps::ObjectGroup group, const int32_t type ) + fheroes2::Sprite getObjectImage( const Maps::ObjectGroup group, const int32_t type ) { if ( type == -1 ) { - // The object type is not set. We show the POINTER cursor for this case. - Cursor::Get().SetThemes( Cursor::POINTER ); - return; + return {}; } - const auto & objectInfo = Maps::getObjectsByGroup( group ); if ( type < 0 || type >= static_cast( objectInfo.size() ) ) { // You are trying to render some unknown stuff! assert( 0 ); + return {}; + } + + return fheroes2::generateMapObjectImage( objectInfo[type] ); + } + + void setCustomCursor( const Maps::ObjectGroup group, const int32_t type ) + { + if ( type == -1 ) { + // The object type is not set. We show the POINTER cursor for this case. + Cursor::Get().SetThemes( Cursor::POINTER ); return; } - const fheroes2::Sprite & image = fheroes2::generateMapObjectImage( objectInfo[type] ); + const fheroes2::Sprite & image = getObjectImage( group, type ); Cursor::Get().setCustomImage( image, { image.x(), image.y() } ); } @@ -100,6 +114,82 @@ namespace return { minPos.x, minPos.y, maxPos.x - minPos.x + 1, maxPos.y - minPos.y + 1 }; } + + fheroes2::Image makeInstrumentPanelBackground( const int32_t width, const int32_t height ) + { + fheroes2::Image background( width, height ); + background._disableTransformLayer(); + fheroes2::StandardWindow::renderBackgroundImage( background, { 0, 0, width, height }, 0, Settings::Get().isEvilInterfaceEnabled() ); + + // Make background borders: it consists of rectangles with different transform shading. + auto applyRectTransform = [width, height]( fheroes2::Image & output, const int32_t offset, const uint8_t transformId ) { + const int32_t doubleOffset = 2 * offset; + // Top horizontal line. + ApplyTransform( output, offset, offset, width - doubleOffset, 1, transformId ); + // Left vertical line without pixels that are parts of horizontal lines. + ApplyTransform( output, offset, offset + 1, 1, height - doubleOffset - 2, transformId ); + // Bottom horizontal line. + ApplyTransform( output, offset, height - 1 - offset, width - doubleOffset, 1, transformId ); + // Right vertical line without pixels that are parts of horizontal lines. + ApplyTransform( output, width - 1 - offset, offset + 1, 1, height - doubleOffset - 2, transformId ); + }; + + applyRectTransform( background, 0, 2 ); + applyRectTransform( background, 1, 4 ); + applyRectTransform( background, 2, 5 ); + applyRectTransform( background, 2, 9 ); + applyRectTransform( background, 3, 8 ); + applyRectTransform( background, 4, 9 ); + + return background; + } + + void drawInstrumentName( fheroes2::Image & output, const fheroes2::Point & pos, std::string text ) + { + const fheroes2::Sprite & originalPanel = fheroes2::AGG::GetICN( ICN::EDITPANL, 0 ); + + const int32_t nameBackgroundOffsetX{ 7 }; + const int32_t nameBackgroundOffsetY{ 104 }; + const int32_t nameBackgroundWidth{ 130 }; + const int32_t nameBackgroundHeight{ 14 }; + + if ( Settings::Get().isEvilInterfaceEnabled() ) { + fheroes2::ApplyPalette( originalPanel, nameBackgroundOffsetX, nameBackgroundOffsetY, output, pos.x, pos.y, nameBackgroundWidth, nameBackgroundHeight, + PAL::GetPalette( PAL::PaletteType::GOOD_TO_EVIL_INTERFACE ) ); + } + else { + fheroes2::Copy( originalPanel, nameBackgroundOffsetX, nameBackgroundOffsetY, output, pos.x, pos.y, nameBackgroundWidth, nameBackgroundHeight ); + } + const fheroes2::Text terrainText( std::move( text ), fheroes2::FontType::smallWhite() ); + terrainText.draw( pos.x + ( nameBackgroundWidth - terrainText.width() ) / 2, pos.y + 3, output ); + } + + void drawObjectTypeSelectionRect( fheroes2::Image & output, const fheroes2::Point & pos ) + { + const fheroes2::Sprite & selection = fheroes2::AGG::GetICN( ICN::TERRAINS, 9 ); + fheroes2::Blit( selection, 0, 0, output, pos.x - 2, pos.y - 2, selection.width(), selection.height() ); + } + + void showObjectTypeInfoText( std::string objectName ) + { + std::string text = _( "Used to place %{object}." ); + StringReplaceWithLowercase( text, "%{object}", objectName ); + fheroes2::showStandardTextMessage( std::move( objectName ), std::move( text ), Dialog::ZERO ); + } + + template + void updateObjectTypeSelection( const int8_t objectId, const std::array & buttonAreas, + const std::function & getObjectTypeName, const fheroes2::Point & ObjectTypeNamePosition, + fheroes2::Image & output ) + { + if ( objectId < 0 ) { + drawInstrumentName( output, ObjectTypeNamePosition, _( "Select object type" ) ); + } + else { + drawObjectTypeSelectionRect( output, buttonAreas[objectId].getPosition() ); + drawInstrumentName( output, ObjectTypeNamePosition, getObjectTypeName( objectId ) ); + } + } } namespace Interface @@ -107,42 +197,56 @@ namespace Interface EditorPanel::EditorPanel( EditorInterface & interface_ ) : _interface( interface_ ) { - int32_t icnIndex = 0; + uint32_t icnIndex = 0; + + const int icnId = Settings::Get().isEvilInterfaceEnabled() ? ICN::EDITBTNS_EVIL : ICN::EDITBTNS; - // Editor Instruments go in this order in ICN: TERRAIN, OBJECT, DETAIL, STREAM, ROAD, ERASE. - for ( fheroes2::Button & button : _instrumentButtons ) { - button.setICNInfo( ICN::EDITBTNS, icnIndex, icnIndex + 1 ); + // Editor Instruments go in this order in ICN: TERRAIN, LANDSCAPE_OBJECTS, DETAIL, ADVENTURE_OBJECTS, KINGDOM_OBJECTS, MONSTERS, STREAM, ROAD, ERASE. + for ( size_t i = 0; i < Instrument::INSTRUMENTS_COUNT; ++i ) { + if ( i == Instrument::ADVENTURE_OBJECTS ) { + // Second row buttons ICN index starts from 53. + icnIndex = 35; + } + else if ( i == Instrument::STREAM ) { + // Third row buttons ICN index starts from 53. + icnIndex = 6; + } + _instrumentButtons[i].setICNInfo( icnId, icnIndex, icnIndex + 1 ); icnIndex += 2; } - _buttonMagnify.setICNInfo( ICN::EDITBTNS, 12, 13 ); - _buttonUndo.setICNInfo( ICN::EDITBTNS, 14, 15 ); - _buttonNew.setICNInfo( ICN::EDITBTNS, 16, 17 ); - _buttonSpecs.setICNInfo( ICN::EDITBTNS, 18, 19 ); - _buttonFile.setICNInfo( ICN::EDITBTNS, 20, 21 ); - _buttonSystem.setICNInfo( ICN::EDITBTNS, 22, 23 ); + _buttonMagnify.setICNInfo( icnId, 12, 13 ); + _buttonUndo.setICNInfo( icnId, 14, 15 ); + _buttonNew.setICNInfo( icnId, 16, 17 ); + _buttonSpecs.setICNInfo( icnId, 18, 19 ); + _buttonFile.setICNInfo( icnId, 20, 21 ); + _buttonSystem.setICNInfo( icnId, 22, 23 ); // Brush Size buttons go in this order in ICN: SMALL (1x), MEDIUM (2x), LARGE (4x), AREA. icnIndex = 24; for ( fheroes2::Button & button : _brushSizeButtons ) { - button.setICNInfo( ICN::EDITBTNS, icnIndex, icnIndex + 1 ); + button.setICNInfo( icnId, icnIndex, icnIndex + 1 ); icnIndex += 2; } _instrumentButtons[_selectedInstrument].press(); _brushSizeButtons[_selectedBrushSize].press(); - _selectedObjectType.fill( -1 ); + _selectedLandscapeObjectType.fill( -1 ); + _selectedAdventureObjectType.fill( -1 ); + _selectedKingdomObjectType.fill( -1 ); } fheroes2::Rect EditorPanel::getBrushArea() const { // Roads and streams are placed using only 1x1 brush. - if ( _selectedInstrument == Instrument::STREAM || _selectedInstrument == Instrument::ROAD || _selectedInstrument == Instrument::DETAIL ) { + if ( _selectedInstrument == Instrument::STREAM || _selectedInstrument == Instrument::ROAD || _selectedInstrument == Instrument::DETAIL + || _selectedInstrument == Instrument::MONSTERS ) { return { 0, 0, 1, 1 }; } - if ( _selectedInstrument == OBJECT ) { + if ( _selectedInstrument == Instrument::LANDSCAPE_OBJECTS || _selectedInstrument == Instrument::ADVENTURE_OBJECTS + || _selectedInstrument == Instrument::KINGDOM_OBJECTS ) { const int32_t objectType = getSelectedObjectType(); if ( objectType >= 0 ) { return getObjectOccupiedArea( getSelectedObjectGroup(), objectType ); @@ -173,6 +277,15 @@ namespace Interface { int32_t offsetX = displayX; + // Editor panel consists of 3 instrument button rows, instrument panel and 2 system button rows. + // Each button row is 36 pixels height. + const fheroes2::Display & display = fheroes2::Display::instance(); + const int32_t instrumentPanelWidth = display.width() - displayX - BORDERWIDTH; + const int32_t bottomBorderOffset = ( display.height() > fheroes2::Display::DEFAULT_HEIGHT + BORDERWIDTH ) ? BORDERWIDTH : 0; + const int32_t instrumentPanelHeight = display.height() - displayY - fheroes2::AGG::GetICN( ICN::EDITBTNS, 0 ).height() * 5 - bottomBorderOffset; + + _instrumentPanelBackground = makeInstrumentPanelBackground( instrumentPanelWidth, instrumentPanelHeight ); + for ( size_t i = 0; i < _instrumentButtonsRect.size(); ++i ) { _instrumentButtons[i].setPosition( offsetX, displayY ); _instrumentButtonsRect[i] = _instrumentButtons[i].area(); @@ -192,34 +305,55 @@ namespace Interface _instrumentButtonsRect.back().x + _instrumentButtonsRect.back().width - _instrumentButtonsRect.front().x, displayY - _instrumentButtonsRect.front().y }; // Instrument panel. - const fheroes2::Sprite & instrumentPanel = fheroes2::AGG::GetICN( ICN::EDITPANL, _selectedInstrument ); - _rectInstrumentPanel = { displayX, displayY, instrumentPanel.width(), instrumentPanel.height() }; + _rectInstrumentPanel = { displayX, displayY, instrumentPanelWidth, instrumentPanelHeight }; + // Brush size buttons position. Shown on the terrain and erasure instrument panels. offsetX = displayX + 14; - int32_t offsetY = displayY + 128; + int32_t offsetY = displayY + std::min( instrumentPanelHeight - 27, 135 ); for ( size_t i = 0; i < _brushSizeButtonsRect.size(); ++i ) { _brushSizeButtons[i].setPosition( offsetX, offsetY ); _brushSizeButtonsRect[i] = _brushSizeButtons[i].area(); offsetX += 30; } - offsetX = displayX + 30; + const int32_t buttonStepX{ 36 }; + const int32_t buttonStepY{ 33 }; + const int32_t buttonWidth{ 27 }; + const int32_t buttonHeight{ 27 }; + const int32_t buttonHalfStepX = buttonStepX / 2; + + // Terrain type select buttons position. Shown on the terrain instrument panel. + offsetX = displayX + 23; offsetY = displayY + 11; for ( size_t i = 0; i < _terrainButtonsRect.size(); ++i ) { - _terrainButtonsRect[i] = { offsetX + static_cast( i % 3 ) * 29, offsetY + static_cast( i / 3 ) * 29, 27, 27 }; + _terrainButtonsRect[i] + = { offsetX + static_cast( i % 3 ) * buttonStepX, offsetY + static_cast( i / 3 ) * buttonStepY, buttonWidth, buttonHeight }; + } + + // Landscape objects buttons position. + for ( size_t i = 0; i < _landscapeObjectButtonsRect.size(); ++i ) { + _landscapeObjectButtonsRect[i] = { offsetX + static_cast( i % 3 ) * buttonStepX + ( i > 2 ? buttonHalfStepX : 0 ), + offsetY + static_cast( i / 3 ) * buttonStepY, buttonWidth, buttonHeight }; + } + + // Adventure objects buttons position. + for ( size_t i = 0; i < _adventureObjectButtonsRect.size(); ++i ) { + _adventureObjectButtonsRect[i] = { offsetX + static_cast( i % 2 ) * buttonStepX + ( i < 4 ? buttonHalfStepX : ( i < 6 ? 0 : buttonStepX * 2 ) ), + offsetY + static_cast( i / 2 ) * buttonStepY - ( i < 6 ? 0 : buttonStepY ), buttonWidth, buttonHeight }; } - offsetX = displayX + 15; - ++offsetY; - for ( size_t i = 0; i < _objectButtonsRect.size(); ++i ) { - _objectButtonsRect[i] = { offsetX + static_cast( i % 4 ) * 29, offsetY + static_cast( i / 4 ) * 28, 27, 27 }; + // Kingdom objects buttons position. + for ( size_t i = 0; i < _kingdomObjectButtonsRect.size(); ++i ) { + // We have only two buttons and have enough space to make a double stepX. + _kingdomObjectButtonsRect[i] = { offsetX + static_cast( i ) * buttonStepX + buttonHalfStepX, offsetY, buttonWidth, buttonHeight }; } - // The last object button is located not next to previous one and needs to be shifted to the right. - _objectButtonsRect[Brush::TREASURES].x += 29 * 2; - offsetY += 56; + // Erase tool object type buttons. + offsetX -= 28; + offsetY += 20; for ( size_t i = 0; i < _eraseButtonsRect.size(); ++i ) { - _eraseButtonsRect[i] = { offsetX + static_cast( i % 4 ) * 29, offsetY + static_cast( i / 4 ) * 28, 27, 27 }; + _eraseButtonsRect[i] + = { offsetX + static_cast( i % 4 ) * buttonStepX, offsetY + static_cast( i / 4 ) * buttonStepY, buttonWidth, buttonHeight }; } displayY += _rectInstrumentPanel.height; @@ -263,9 +397,8 @@ namespace Interface fheroes2::Display & display = fheroes2::Display::instance(); - const fheroes2::Sprite & instrumentPanel = fheroes2::AGG::GetICN( ICN::EDITPANL, _selectedInstrument ); - fheroes2::Copy( instrumentPanel, 0, 0, display, _rectEditorPanel.x, _rectInstruments.y + _rectInstruments.height, instrumentPanel.width(), - instrumentPanel.height() ); + fheroes2::Copy( _instrumentPanelBackground, 0, 0, display, _rectInstrumentPanel.x, _rectInstrumentPanel.y, _rectInstrumentPanel.width, + _rectInstrumentPanel.height ); if ( _selectedInstrument == Instrument::TERRAIN || _selectedInstrument == Instrument::ERASE ) { for ( const fheroes2::Button & button : _brushSizeButtons ) { @@ -274,22 +407,119 @@ namespace Interface } if ( _selectedInstrument == Instrument::TERRAIN ) { - const fheroes2::Sprite & selection = fheroes2::AGG::GetICN( ICN::TERRAINS, 9 ); - fheroes2::Blit( selection, 0, 0, display, _terrainButtonsRect[_selectedTerrain].x - 2, _terrainButtonsRect[_selectedTerrain].y - 2, selection.width(), - selection.height() ); + // We use terrain images from the original terrain instrument panel sprite. + const fheroes2::Sprite & originalPanel = fheroes2::AGG::GetICN( ICN::EDITPANL, 0 ); + for ( size_t i = 0; i < TerrainBrush::TERRAIN_COUNT; ++i ) { + const int32_t originalOffsetX = 30 + static_cast( i % 3 ) * 29; + const int32_t originalOffsetY = 11 + static_cast( i / 3 ) * 29; + fheroes2::Copy( originalPanel, originalOffsetX, originalOffsetY, display, _terrainButtonsRect[i] ); + } + + // Terrain type selection yellow rectangle. + drawObjectTypeSelectionRect( display, _terrainButtonsRect[_selectedTerrain].getPosition() ); + + // On high resolutions we have space to show selected terrain text. + if ( _rectInstrumentPanel.height > 160 ) { + drawInstrumentName( display, { _rectInstrumentPanel.x + 7, _rectInstrumentPanel.y + 113 }, _getTerrainTypeName( _selectedTerrain ) ); + } + } + else if ( _selectedInstrument == Instrument::LANDSCAPE_OBJECTS ) { + // Landscape objects buttons. + for ( uint32_t i = 0; i < LandscapeObjectBrush::LANDSCAPE_COUNT; ++i ) { + fheroes2::Copy( fheroes2::AGG::GetICN( ICN::EDITPANL, i + 6 ), 0, 0, display, _landscapeObjectButtonsRect[i] ); + } - const fheroes2::Text terrainText( _getTerrainTypeName( _selectedTerrain ), fheroes2::FontType::smallWhite() ); - terrainText.draw( _rectInstrumentPanel.x + 72 - terrainText.width() / 2, _rectInstrumentPanel.y + 107, display ); + updateObjectTypeSelection( _selectedLandscapeObject, _landscapeObjectButtonsRect, _getLandscapeObjectTypeName, + { _rectInstrumentPanel.x + 7, _rectInstrumentPanel.y + 80 }, display ); } - else if ( _selectedInstrument == Instrument::OBJECT ) { - const fheroes2::Sprite & selection = fheroes2::AGG::GetICN( ICN::TERRAINS, 9 ); - fheroes2::Blit( selection, 0, 0, display, _objectButtonsRect[_selectedObject].x - 2, _objectButtonsRect[_selectedObject].y - 2, selection.width(), - selection.height() ); + else if ( _selectedInstrument == Instrument::ADVENTURE_OBJECTS ) { + // Adventure objects buttons. + const fheroes2::Sprite & originalPanel = fheroes2::AGG::GetICN( ICN::EDITPANL, 1 ); + const int32_t originalArtifactsImageOffsetX{ 15 }; + const int32_t originalTreasuresImageOffsetX{ 102 }; + const int32_t originalImagesOffsetY{ 96 }; + + fheroes2::Copy( originalPanel, originalArtifactsImageOffsetX, originalImagesOffsetY, display, _adventureObjectButtonsRect[AdventureObjectBrush::ARTIFACTS] ); + for ( uint32_t i = AdventureObjectBrush::DWELLINGS; i < AdventureObjectBrush::TREASURES; ++i ) { + fheroes2::Copy( fheroes2::AGG::GetICN( ICN::EDITPANL, i + 10 ), 0, 0, display, _adventureObjectButtonsRect[i] ); + } + fheroes2::Copy( originalPanel, originalTreasuresImageOffsetX, originalImagesOffsetY, display, _adventureObjectButtonsRect[AdventureObjectBrush::TREASURES] ); + for ( uint32_t i = AdventureObjectBrush::WATER_ADVENTURE; i < AdventureObjectBrush::ADVENTURE_COUNT; ++i ) { + fheroes2::Copy( fheroes2::AGG::GetICN( ICN::EDITPANL, i + 9 ), 0, 0, display, _adventureObjectButtonsRect[i] ); + } - const fheroes2::Text terrainText( _getObjectTypeName( _selectedObject ), fheroes2::FontType::smallWhite() ); - terrainText.draw( _rectInstrumentPanel.x + 72 - terrainText.width() / 2, _rectInstrumentPanel.y + 135, display ); + updateObjectTypeSelection( _selectedAdventureObject, _adventureObjectButtonsRect, _getAdventureObjectTypeName, + { _rectInstrumentPanel.x + 7, _rectInstrumentPanel.y + 113 }, display ); + } + else if ( _selectedInstrument == Instrument::KINGDOM_OBJECTS ) { + // Kingdom objects buttons. + const fheroes2::Sprite & originalPanel = fheroes2::AGG::GetICN( ICN::EDITPANL, 1 ); + const int32_t originalHeroesImageOffsetX{ 102 }; + const int32_t originalTownsImageOffsetX{ 44 }; + const int32_t originalImagesOffsetY{ 68 }; + + fheroes2::Copy( originalPanel, originalHeroesImageOffsetX, originalImagesOffsetY, display, _kingdomObjectButtonsRect[KingdomObjectBrush::HEROES] ); + fheroes2::Copy( originalPanel, originalTownsImageOffsetX, originalImagesOffsetY, display, _kingdomObjectButtonsRect[KingdomObjectBrush::TOWNS] ); + + updateObjectTypeSelection( _selectedKingdomObject, _kingdomObjectButtonsRect, _getKingdomObjectTypeName, + { _rectInstrumentPanel.x + 7, _rectInstrumentPanel.y + 48 }, display ); + } + else if ( _selectedInstrument == Instrument::MONSTERS ) { + const fheroes2::Text instrumentName( _( "Monsters" ), fheroes2::FontType::normalWhite() ); + instrumentName.draw( _rectInstrumentPanel.x + ( _rectInstrumentPanel.width - instrumentName.width() ) / 2, _rectInstrumentPanel.y + 8, display ); + + if ( _selectedMonsterType < 0 ) { + // Show a tip. + const fheroes2::Text text( _( "Click here to\nselect a monster." ), fheroes2::FontType::smallWhite() ); + text.draw( _rectInstrumentPanel.x + 5, _rectInstrumentPanel.y + 38, _rectInstrumentPanel.width - 10, display ); + } + else { + // Show the selected monster image on the panel. + const fheroes2::Sprite & image = getObjectImage( Maps::ObjectGroup::MONSTERS, _selectedMonsterType ); + fheroes2::Blit( image, display, _rectInstrumentPanel.x + ( _rectInstrumentPanel.width - image.width() ) / 2, + _rectInstrumentPanel.y + 67 - image.height() ); + + fheroes2::Text text( Monster( _selectedMonsterType + 1 ).GetName(), fheroes2::FontType::smallWhite() ); + text.draw( _rectInstrumentPanel.x + 5, _rectInstrumentPanel.y + 70, _rectInstrumentPanel.width - 10, display ); + text.set( _( "Click here to\nselect another monster." ), fheroes2::FontType::smallWhite() ); + text.draw( _rectInstrumentPanel.x + 5, _rectInstrumentPanel.y + 95, _rectInstrumentPanel.width - 10, display ); + } + } + else if ( _selectedInstrument == Instrument::DETAIL ) { + const fheroes2::Text instrumentName( _( "Cell\nDetails" ), fheroes2::FontType::normalWhite() ); + instrumentName.draw( _rectInstrumentPanel.x + 5, _rectInstrumentPanel.y + 8, _rectInstrumentPanel.width - 10, display ); + } + else if ( _selectedInstrument == Instrument::ROAD ) { + const fheroes2::Text instrumentName( _( "Roads" ), fheroes2::FontType::normalWhite() ); + instrumentName.draw( _rectInstrumentPanel.x + ( _rectInstrumentPanel.width - instrumentName.width() ) / 2, _rectInstrumentPanel.y + 8, display ); + } + else if ( _selectedInstrument == Instrument::STREAM ) { + const fheroes2::Text instrumentName( _( "Streams" ), fheroes2::FontType::normalWhite() ); + instrumentName.draw( _rectInstrumentPanel.x + ( _rectInstrumentPanel.width - instrumentName.width() ) / 2, _rectInstrumentPanel.y + 8, display ); } else if ( _selectedInstrument == Instrument::ERASE ) { + const fheroes2::Text instrumentName( _( "Erase" ), fheroes2::FontType::normalWhite() ); + instrumentName.draw( _rectInstrumentPanel.x + ( _rectInstrumentPanel.width - instrumentName.width() ) / 2, _rectInstrumentPanel.y + 8, display ); + + // Object type to erase buttons. + const fheroes2::Sprite & originalPanel = fheroes2::AGG::GetICN( ICN::EDITPANL, 1 ); + const int32_t originalArtifactsImageOffsetX{ 15 }; + const int32_t originalTownsImageOffsetX{ 44 }; + const int32_t originalMonstersImageOffsetX{ 73 }; + const int32_t originalHeroesTreasuresImageOffsetX{ 102 }; + const int32_t originalTownsMonstersHeroesOffsetY{ 68 }; + const int32_t originalArtifactsTreasresOffsetY{ 96 }; + + fheroes2::Copy( fheroes2::AGG::GetICN( ICN::EDITPANL, 8 ), 0, 0, display, _eraseButtonsRect[0] ); + fheroes2::Copy( originalPanel, originalTownsImageOffsetX, originalTownsMonstersHeroesOffsetY, display, _eraseButtonsRect[1] ); + fheroes2::Copy( originalPanel, originalMonstersImageOffsetX, originalTownsMonstersHeroesOffsetY, display, _eraseButtonsRect[2] ); + fheroes2::Copy( originalPanel, originalHeroesTreasuresImageOffsetX, originalTownsMonstersHeroesOffsetY, display, _eraseButtonsRect[3] ); + fheroes2::Copy( originalPanel, originalArtifactsImageOffsetX, originalArtifactsTreasresOffsetY, display, _eraseButtonsRect[4] ); + fheroes2::Copy( fheroes2::AGG::GetICN( ICN::EDITPANL, 16 ), 0, 0, display, _eraseButtonsRect[5] ); + fheroes2::Copy( fheroes2::AGG::GetICN( ICN::EDITPANL, 17 ), 0, 0, display, _eraseButtonsRect[6] ); + fheroes2::Copy( originalPanel, originalHeroesTreasuresImageOffsetX, originalArtifactsTreasresOffsetY, display, _eraseButtonsRect[7] ); + + // Object type to erase selection marks. const fheroes2::Sprite & selectionMark = fheroes2::AGG::GetICN( ICN::TOWNWIND, 11 ); for ( size_t i = 0; i < _eraseButtonsRect.size(); ++i ) { if ( _eraseButtonObjectTypes[i] & _eraseTypes ) { @@ -309,26 +539,74 @@ namespace Interface display.render( _rectInstrumentPanel ); } + int32_t EditorPanel::getSelectedObjectType() const + { + switch ( _selectedInstrument ) { + case Instrument::MONSTERS: + return _selectedMonsterType; + case Instrument::LANDSCAPE_OBJECTS: + if ( _selectedLandscapeObject < 0 ) { + return -1; + } + return _selectedLandscapeObjectType[_selectedLandscapeObject]; + case Instrument::ADVENTURE_OBJECTS: + if ( _selectedAdventureObject < 0 ) { + return -1; + } + return _selectedAdventureObjectType[_selectedAdventureObject]; + case Instrument::KINGDOM_OBJECTS: + if ( _selectedKingdomObject < 0 ) { + return -1; + } + return _selectedKingdomObjectType[_selectedKingdomObject]; + default: + // Why are you trying to get type for the non-object instrument. Check your logic! + assert( 0 ); + return -1; + } + } + + Maps::ObjectGroup EditorPanel::getSelectedObjectGroup() const + { + switch ( _selectedInstrument ) { + case Instrument::MONSTERS: + return Maps::ObjectGroup::MONSTERS; + case Instrument::LANDSCAPE_OBJECTS: + assert( _selectedLandscapeObject > -1 ); + return _selectedLandscapeObjectGroup[_selectedLandscapeObject]; + case Instrument::ADVENTURE_OBJECTS: + assert( _selectedAdventureObject > -1 ); + return _selectedAdventureObjectGroup[_selectedAdventureObject]; + case Instrument::KINGDOM_OBJECTS: + assert( _selectedKingdomObject > -1 ); + return _selectedKingdomObjectGroup[_selectedKingdomObject]; + default: + // Why are you trying to get object group for the non-object instrument. Check your logic! + assert( 0 ); + return Maps::ObjectGroup::GROUP_COUNT; + } + } + int EditorPanel::_getGroundId( const uint8_t brushId ) { switch ( brushId ) { - case Brush::WATER: + case TerrainBrush::WATER: return Maps::Ground::WATER; - case Brush::GRASS: + case TerrainBrush::GRASS: return Maps::Ground::GRASS; - case Brush::SNOW: + case TerrainBrush::SNOW: return Maps::Ground::SNOW; - case Brush::SWAMP: + case TerrainBrush::SWAMP: return Maps::Ground::SWAMP; - case Brush::LAVA: + case TerrainBrush::LAVA: return Maps::Ground::LAVA; - case Brush::DESERT: + case TerrainBrush::DESERT: return Maps::Ground::DESERT; - case Brush::DIRT: + case TerrainBrush::DIRT: return Maps::Ground::DIRT; - case Brush::WASTELAND: + case TerrainBrush::WASTELAND: return Maps::Ground::WASTELAND; - case Brush::BEACH: + case TerrainBrush::BEACH: return Maps::Ground::BEACH; default: // Have you added a new terrain type? Add the logic above! @@ -338,37 +616,61 @@ namespace Interface return Maps::Ground::UNKNOWN; } - const char * EditorPanel::_getObjectTypeName( const uint8_t brushId ) + const char * EditorPanel::_getLandscapeObjectTypeName( const uint8_t brushId ) { switch ( brushId ) { - case Brush::WATER: - return _( "Ocean Objects" ); - case Brush::GRASS: - return _( "Grass Objects" ); - case Brush::SNOW: - return _( "Snow Objects" ); - case Brush::SWAMP: - return _( "Swamp Objects" ); - case Brush::LAVA: - return _( "Lava Objects" ); - case Brush::DESERT: - return _( "Desert Objects" ); - case Brush::DIRT: - return _( "Dirt Objects" ); - case Brush::WASTELAND: - return _( "Wasteland Objects" ); - case Brush::BEACH: - return _( "Beach Objects" ); - case Brush::TOWNS: - return _( "Towns" ); - case Brush::MONSTERS: - return _( "Monsters" ); - case Brush::HEROES: - return _( "Heroes" ); - case Brush::ARTIFACTS: + case LandscapeObjectBrush::MOUNTAINS: + return _( "Mountains" ); + case LandscapeObjectBrush::ROCKS: + return _( "Rocks" ); + case LandscapeObjectBrush::TREES: + return _( "Trees" ); + case LandscapeObjectBrush::WATER_OBJECTS: + return _( "Water Objects" ); + case LandscapeObjectBrush::LANDSCAPE_MISC: + return _( "Miscellaneous" ); + default: + // Have you added a new object type? Add the logic above! + assert( 0 ); + break; + } + + return "Unknown object type"; + } + + const char * EditorPanel::_getAdventureObjectTypeName( const uint8_t brushId ) + { + switch ( brushId ) { + case AdventureObjectBrush::ARTIFACTS: return _( "Artifacts" ); - case Brush::TREASURES: + case AdventureObjectBrush::DWELLINGS: + return _( "Dwellings" ); + case AdventureObjectBrush::MINES: + return _( "Mines" ); + case AdventureObjectBrush::POWER_UPS: + return _( "Power-ups" ); + case AdventureObjectBrush::TREASURES: return _( "Treasures" ); + case AdventureObjectBrush::WATER_ADVENTURE: + return _( "Water Objects" ); + case AdventureObjectBrush::ADVENTURE_MISC: + return _( "Miscellaneous" ); + default: + // Have you added a new object type? Add the logic above! + assert( 0 ); + break; + } + + return "Unknown object type"; + } + + const char * EditorPanel::_getKingdomObjectTypeName( const uint8_t brushId ) + { + switch ( brushId ) { + case KingdomObjectBrush::TOWNS: + return _( "Towns" ); + case KingdomObjectBrush::HEROES: + return _( "Heroes" ); default: // Have you added a new object type? Add the logic above! assert( 0 ); @@ -408,59 +710,88 @@ namespace Interface void EditorPanel::_setCursor() { - if ( _selectedInstrument != Instrument::OBJECT ) { - _interface.setCursorUpdater( {} ); - return; - } - - switch ( _selectedObject ) { - case Brush::MONSTERS: - case Brush::TREASURES: - case Brush::ARTIFACTS: - case Brush::WATER: + switch ( _selectedInstrument ) { + case Instrument::MONSTERS: _interface.setCursorUpdater( [type = getSelectedObjectType(), group = getSelectedObjectGroup()]( const int32_t /*tileIndex*/ ) { setCustomCursor( group, type ); } ); + return; + case Instrument::LANDSCAPE_OBJECTS: + switch ( _selectedLandscapeObject ) { + case LandscapeObjectBrush::MOUNTAINS: + case LandscapeObjectBrush::ROCKS: + case LandscapeObjectBrush::TREES: + case LandscapeObjectBrush::WATER_OBJECTS: + case LandscapeObjectBrush::LANDSCAPE_MISC: + _interface.setCursorUpdater( + [type = getSelectedObjectType(), group = getSelectedObjectGroup()]( const int32_t /*tileIndex*/ ) { setCustomCursor( group, type ); } ); + return; + default: + break; + } break; - case Brush::HEROES: - _interface.setCursorUpdater( [type = getSelectedObjectType()]( const int32_t /*tileIndex*/ ) { - if ( type == -1 ) { - // The object type is not set. We show the POINTER cursor for this case. - Cursor::Get().SetThemes( Cursor::POINTER ); - return; - } + case Instrument::ADVENTURE_OBJECTS: + switch ( _selectedAdventureObject ) { + case AdventureObjectBrush::ARTIFACTS: + case AdventureObjectBrush::DWELLINGS: + case AdventureObjectBrush::MINES: + case AdventureObjectBrush::POWER_UPS: + case AdventureObjectBrush::TREASURES: + case AdventureObjectBrush::WATER_ADVENTURE: + case AdventureObjectBrush::ADVENTURE_MISC: + _interface.setCursorUpdater( + [type = getSelectedObjectType(), group = getSelectedObjectGroup()]( const int32_t /*tileIndex*/ ) { setCustomCursor( group, type ); } ); + return; + default: + break; + } + break; + case Instrument::KINGDOM_OBJECTS: + switch ( _selectedKingdomObject ) { + case KingdomObjectBrush::HEROES: + _interface.setCursorUpdater( [type = getSelectedObjectType()]( const int32_t /*tileIndex*/ ) { + if ( type == -1 ) { + // The object type is not set. We show the POINTER cursor for this case. + Cursor::Get().SetThemes( Cursor::POINTER ); + return; + } - // TODO: render ICN::MINIHERO from the existing hero images. - const fheroes2::Sprite & image = fheroes2::AGG::GetICN( ICN::MINIHERO, type ); + // TODO: render ICN::MINIHERO from the existing hero images. + const fheroes2::Sprite & image = fheroes2::AGG::GetICN( ICN::MINIHERO, type ); - // Mini-hero images contain a pole with a flag. - // This causes a situation that a selected tile does not properly correspond to the position of cursor. - // We need to add a hardcoded correction. - const int32_t heroCorrectionY{ 12 }; - Cursor::Get().setCustomImage( image, { -image.width() / 2, -image.height() / 2 - heroCorrectionY } ); - } ); - break; - case Brush::TOWNS: - _interface.setCursorUpdater( [this]( const int32_t tileIndex ) { - int32_t type = -1; - int32_t color = -1; - getTownObjectProperties( type, color ); - - if ( type == -1 || color == -1 ) { - // The object type is not set. We show the POINTER cursor for this case. - Cursor::Get().SetThemes( Cursor::POINTER ); - return; - } + // Mini-hero images contain a pole with a flag. + // This causes a situation that a selected tile does not properly correspond to current position of the cursor. + // We need to add a hardcoded correction. + const int32_t heroCorrectionY{ 12 }; + Cursor::Get().setCustomImage( image, { -image.width() / 2, -image.height() / 2 - heroCorrectionY } ); + } ); + return; + case KingdomObjectBrush::TOWNS: + _interface.setCursorUpdater( [this]( const int32_t tileIndex ) { + int32_t type = -1; + int32_t color = -1; + getTownObjectProperties( type, color ); - // TODO: render ICN::MINIHERO from the existing hero images. - const fheroes2::Sprite & image = fheroes2::generateTownObjectImage( type, color, world.GetTiles( tileIndex ).GetGround() ); + if ( type == -1 || color == -1 ) { + // The object type is not set. We show the POINTER cursor for this case. + Cursor::Get().SetThemes( Cursor::POINTER ); + return; + } + + // TODO: render ICN::MINIHERO from the existing hero images. + const fheroes2::Sprite & image = fheroes2::generateTownObjectImage( type, color, world.GetTiles( tileIndex ).GetGround() ); - Cursor::Get().setCustomImage( image, { image.x(), image.y() } ); - } ); + Cursor::Get().setCustomImage( image, { image.x(), image.y() } ); + } ); + return; + default: + break; + } break; default: - _interface.setCursorUpdater( {} ); break; } + + _interface.setCursorUpdater( {} ); } fheroes2::GameMode EditorPanel::queueEventProcessing() @@ -474,10 +805,18 @@ namespace Interface if ( _instrumentButtons[i].drawOnPress() ) { _selectedInstrument = static_cast( i ); + // When opening Monsters placing and no monster was previously selected force open the Select Monster dialog. + if ( _selectedInstrument == Instrument::MONSTERS && _selectedMonsterType == -1 ) { + // Update panel image and then open the Select Monster dialog. + _redraw(); + handleObjectMouseClick( Dialog::selectMonsterType ); + } + // Reset cursor updater since this UI element was clicked. _setCursor(); setRedraw(); + return res; } } else { @@ -542,76 +881,117 @@ namespace Interface return text; }; - if ( le.MousePressRight( _terrainButtonsRect[Brush::WATER] ) ) { - fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::WATER ), _( "Traversable only by boat." ), Dialog::ZERO ); + if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::WATER] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::WATER ), _( "Traversable only by boat." ), Dialog::ZERO ); } - else if ( le.MousePressRight( _terrainButtonsRect[Brush::GRASS] ) ) { - fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::GRASS ), _( "No special modifiers." ), Dialog::ZERO ); + else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::GRASS] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::GRASS ), _( "No special modifiers." ), Dialog::ZERO ); } - else if ( le.MousePressRight( _terrainButtonsRect[Brush::SNOW] ) ) { - fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::SNOW ), movePenaltyText( "1.5" ), Dialog::ZERO ); + else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::SNOW] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::SNOW ), movePenaltyText( "1.5" ), Dialog::ZERO ); } - else if ( le.MousePressRight( _terrainButtonsRect[Brush::SWAMP] ) ) { - fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::SWAMP ), movePenaltyText( "1.75" ), Dialog::ZERO ); + else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::SWAMP] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::SWAMP ), movePenaltyText( "1.75" ), Dialog::ZERO ); } - else if ( le.MousePressRight( _terrainButtonsRect[Brush::LAVA] ) ) { - fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::LAVA ), _( "No special modifiers." ), Dialog::ZERO ); + else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::LAVA] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::LAVA ), _( "No special modifiers." ), Dialog::ZERO ); } - else if ( le.MousePressRight( _terrainButtonsRect[Brush::DESERT] ) ) { - fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::DESERT ), movePenaltyText( "2" ), Dialog::ZERO ); + else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::DESERT] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::DESERT ), movePenaltyText( "2" ), Dialog::ZERO ); } - else if ( le.MousePressRight( _terrainButtonsRect[Brush::DIRT] ) ) { - fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::DIRT ), _( "No special modifiers." ), Dialog::ZERO ); + else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::DIRT] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::DIRT ), _( "No special modifiers." ), Dialog::ZERO ); } - else if ( le.MousePressRight( _terrainButtonsRect[Brush::WASTELAND] ) ) { - fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::WASTELAND ), movePenaltyText( "1.25" ), Dialog::ZERO ); + else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::WASTELAND] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::WASTELAND ), movePenaltyText( "1.25" ), Dialog::ZERO ); } - else if ( le.MousePressRight( _terrainButtonsRect[Brush::BEACH] ) ) { - fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::BEACH ), movePenaltyText( "1.25" ), Dialog::ZERO ); + else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::BEACH] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::BEACH ), movePenaltyText( "1.25" ), Dialog::ZERO ); } } - else if ( _selectedInstrument == Instrument::OBJECT ) { - for ( size_t i = 0; i < _objectButtonsRect.size(); ++i ) { - if ( ( _selectedObject != i ) && le.MousePressLeft( _objectButtonsRect[i] ) ) { - _selectedObject = static_cast( i ); + else if ( _selectedInstrument == Instrument::LANDSCAPE_OBJECTS ) { + for ( size_t i = 0; i < _landscapeObjectButtonsRect.size(); ++i ) { + if ( ( _selectedLandscapeObject != static_cast( i ) ) && le.MousePressLeft( _landscapeObjectButtonsRect[i] ) ) { + _selectedLandscapeObject = static_cast( i ); // Reset cursor updater since this UI element was clicked. _setCursor(); setRedraw(); + break; + } + } - // There is no need to continue the loop as only one button can be pressed at one moment. + for ( uint8_t objectId = LandscapeObjectBrush::MOUNTAINS; objectId < LandscapeObjectBrush::LANDSCAPE_COUNT; ++objectId ) { + if ( le.MousePressRight( _landscapeObjectButtonsRect[objectId] ) ) { + showObjectTypeInfoText( _getLandscapeObjectTypeName( objectId ) ); break; } } - for ( uint8_t objectId = Brush::WATER; objectId < Brush::TOWNS; ++objectId ) { - if ( le.MousePressRight( _objectButtonsRect[objectId] ) ) { - std::string text = _( "Used to place objects most appropriate for use on %{terrain}." ); - StringReplaceWithLowercase( text, "%{terrain}", _getTerrainTypeName( objectId ) ); - fheroes2::showStandardTextMessage( _getObjectTypeName( objectId ), std::move( text ), Dialog::ZERO ); + if ( le.MouseClickLeft( _landscapeObjectButtonsRect[LandscapeObjectBrush::WATER_OBJECTS] ) ) { + handleObjectMouseClick( Dialog::selectLandscapeOceanObjectType ); + return res; + } + } + else if ( _selectedInstrument == Instrument::ADVENTURE_OBJECTS ) { + for ( size_t i = 0; i < _adventureObjectButtonsRect.size(); ++i ) { + if ( ( _selectedAdventureObject != static_cast( i ) ) && le.MousePressLeft( _adventureObjectButtonsRect[i] ) ) { + _selectedAdventureObject = static_cast( i ); - // There is no need to continue the loop as only one button can be pressed at one moment. + // Reset cursor updater since this UI element was clicked. + _setCursor(); + + setRedraw(); + break; + } + } + + for ( uint8_t objectId = AdventureObjectBrush::ARTIFACTS; objectId < AdventureObjectBrush::ADVENTURE_COUNT; ++objectId ) { + if ( le.MousePressRight( _adventureObjectButtonsRect[objectId] ) ) { + showObjectTypeInfoText( _getAdventureObjectTypeName( objectId ) ); break; } } - if ( le.MousePressRight( _objectButtonsRect[Brush::TOWNS] ) ) { - fheroes2::showStandardTextMessage( _getObjectTypeName( Brush::TOWNS ), _( "Used to place\na town or castle." ), Dialog::ZERO ); + if ( le.MouseClickLeft( _adventureObjectButtonsRect[AdventureObjectBrush::ARTIFACTS] ) ) { + handleObjectMouseClick( Dialog::selectArtifactType ); + return res; } - else if ( le.MousePressRight( _objectButtonsRect[Brush::MONSTERS] ) ) { - fheroes2::showStandardTextMessage( _getObjectTypeName( Brush::MONSTERS ), _( "Used to place\na monster group." ), Dialog::ZERO ); + if ( le.MouseClickLeft( _adventureObjectButtonsRect[AdventureObjectBrush::TREASURES] ) ) { + handleObjectMouseClick( Dialog::selectTreasureType ); + return res; } - else if ( le.MousePressRight( _objectButtonsRect[Brush::HEROES] ) ) { - fheroes2::showStandardTextMessage( _getObjectTypeName( Brush::HEROES ), _( "Used to place a hero." ), Dialog::ZERO ); + if ( le.MouseClickLeft( _adventureObjectButtonsRect[AdventureObjectBrush::WATER_ADVENTURE] ) ) { + handleObjectMouseClick( Dialog::selectOceanObjectType ); + return res; } - else if ( le.MousePressRight( _objectButtonsRect[Brush::ARTIFACTS] ) ) { - fheroes2::showStandardTextMessage( _getObjectTypeName( Brush::ARTIFACTS ), _( "Used to place an artifact." ), Dialog::ZERO ); + } + else if ( _selectedInstrument == Instrument::KINGDOM_OBJECTS ) { + for ( size_t i = 0; i < _kingdomObjectButtonsRect.size(); ++i ) { + if ( ( _selectedKingdomObject != static_cast( i ) ) && le.MousePressLeft( _kingdomObjectButtonsRect[i] ) ) { + _selectedKingdomObject = static_cast( i ); + + // Reset cursor updater since this UI element was clicked. + _setCursor(); + + setRedraw(); + break; + } + } + + for ( uint8_t objectId = KingdomObjectBrush::HEROES; objectId < KingdomObjectBrush::KINGDOM_OBJECTS_COUNT; ++objectId ) { + if ( le.MousePressRight( _kingdomObjectButtonsRect[objectId] ) ) { + showObjectTypeInfoText( _getKingdomObjectTypeName( objectId ) ); + break; + } } - else if ( le.MousePressRight( _objectButtonsRect[Brush::TREASURES] ) ) { - fheroes2::showStandardTextMessage( _getObjectTypeName( Brush::TREASURES ), _( "Used to place\na resource or treasure." ), Dialog::ZERO ); + + if ( le.MouseClickLeft( _kingdomObjectButtonsRect[KingdomObjectBrush::HEROES] ) ) { + handleObjectMouseClick( Dialog::selectHeroType ); + return res; } - else if ( le.MouseClickLeft( _objectButtonsRect[Brush::TOWNS] ) ) { + if ( le.MouseClickLeft( _kingdomObjectButtonsRect[KingdomObjectBrush::TOWNS] ) ) { handleObjectMouseClick( [this]( const int32_t /* type */ ) -> int32_t { int32_t type = -1; int32_t color = -1; @@ -623,26 +1003,11 @@ namespace Interface } ); return res; } - else if ( le.MouseClickLeft( _objectButtonsRect[Brush::MONSTERS] ) ) { - handleObjectMouseClick( Dialog::selectMonsterType ); - return res; - } - else if ( le.MouseClickLeft( _objectButtonsRect[Brush::TREASURES] ) ) { - handleObjectMouseClick( Dialog::selectTreasureType ); - return res; - } - else if ( le.MouseClickLeft( _objectButtonsRect[Brush::HEROES] ) ) { - handleObjectMouseClick( Dialog::selectHeroType ); - return res; - } - else if ( le.MouseClickLeft( _objectButtonsRect[Brush::ARTIFACTS] ) ) { - handleObjectMouseClick( Dialog::selectArtifactType ); - return res; - } - else if ( le.MouseClickLeft( _objectButtonsRect[Brush::WATER] ) ) { - handleObjectMouseClick( Dialog::selectOceanObjectType ); - return res; - } + } + else if ( _selectedInstrument == Instrument::MONSTERS && le.MouseClickLeft( _rectInstrumentPanel ) ) { + handleObjectMouseClick( Dialog::selectMonsterType ); + setRedraw(); + return res; } else if ( _selectedInstrument == Instrument::ERASE ) { for ( size_t i = 0; i < _eraseButtonsRect.size(); ++i ) { @@ -702,12 +1067,23 @@ namespace Interface else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::TERRAIN] ) ) { fheroes2::showStandardTextMessage( _( "Terrain Mode" ), _( "Used to draw the underlying grass, dirt, water, etc. on the map." ), Dialog::ZERO ); } - else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::OBJECT] ) ) { - fheroes2::showStandardTextMessage( _( "Object Mode" ), _( "Used to place objects (mountains, trees, treasure, etc.) on the map." ), Dialog::ZERO ); + else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::LANDSCAPE_OBJECTS] ) ) { + fheroes2::showStandardTextMessage( _( "Landscape Objects Mode" ), _( "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." ), + Dialog::ZERO ); } else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::DETAIL] ) ) { fheroes2::showStandardTextMessage( _( "Detail Mode" ), _( "Used for special editing of monsters, heroes and towns." ), Dialog::ZERO ); } + else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::ADVENTURE_OBJECTS] ) ) { + fheroes2::showStandardTextMessage( _( "Adventure Objects Mode" ), + _( "Used to place adventure objects (artifacts, dwellings, mines, treasures, etc.) on the map." ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::KINGDOM_OBJECTS] ) ) { + fheroes2::showStandardTextMessage( _( "Kingdom Objects Mode" ), _( "Used to place kingdom objects (towns, castles and heroes) on the map." ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::MONSTERS] ) ) { + fheroes2::showStandardTextMessage( _( "Monsters Mode" ), _( "Used to place monsters on the map." ), Dialog::ZERO ); + } else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::STREAM] ) ) { fheroes2::showStandardTextMessage( _( "Stream Mode" ), _( "Allows you to draw streams by clicking and dragging." ), Dialog::ZERO ); } @@ -742,9 +1118,25 @@ namespace Interface void EditorPanel::handleObjectMouseClick( const std::function & typeSelection ) { - const int type = typeSelection( _selectedObjectType[_selectedObject] ); + const int type = typeSelection( getSelectedObjectType() ); if ( type >= 0 ) { - _selectedObjectType[_selectedObject] = type; + switch ( _selectedInstrument ) { + case Instrument::MONSTERS: + _selectedMonsterType = type; + break; + case Instrument::LANDSCAPE_OBJECTS: + _selectedLandscapeObjectType[_selectedLandscapeObject] = type; + break; + case Instrument::ADVENTURE_OBJECTS: + _selectedAdventureObjectType[_selectedAdventureObject] = type; + break; + case Instrument::KINGDOM_OBJECTS: + _selectedKingdomObjectType[_selectedKingdomObject] = type; + break; + default: + // Why are you trying to get type for the non-object instrument. Check your logic! + assert( 0 ); + } } _setCursor(); _interface.updateCursor( 0 ); @@ -761,8 +1153,8 @@ namespace Interface return; } - type = _selectedObjectType[Brush::TOWNS] % static_cast( townObjects.size() ); - color = _selectedObjectType[Brush::TOWNS] / static_cast( townObjects.size() ); + type = _selectedKingdomObjectType[KingdomObjectBrush::TOWNS] % static_cast( townObjects.size() ); + color = _selectedKingdomObjectType[KingdomObjectBrush::TOWNS] / static_cast( townObjects.size() ); } int32_t EditorPanel::_generateTownObjectProperties( const int32_t type, const int32_t color ) diff --git a/src/fheroes2/editor/editor_interface_panel.h b/src/fheroes2/editor/editor_interface_panel.h index a9ef3fd4f80..060cf45bd1a 100644 --- a/src/fheroes2/editor/editor_interface_panel.h +++ b/src/fheroes2/editor/editor_interface_panel.h @@ -26,6 +26,7 @@ #include "game_mode.h" #include "ground.h" +#include "image.h" #include "map_object_info.h" #include "maps_tiles_helper.h" #include "math_base.h" @@ -76,7 +77,8 @@ namespace Interface bool isObjectMode() const { - return _selectedInstrument == Instrument::OBJECT; + return _selectedInstrument == Instrument::LANDSCAPE_OBJECTS || _selectedInstrument == Instrument::ADVENTURE_OBJECTS + || _selectedInstrument == Instrument::KINGDOM_OBJECTS || _selectedInstrument == Instrument::MONSTERS; } uint32_t getEraseTypes() const @@ -87,7 +89,7 @@ namespace Interface bool showAreaSelectRect() const { return _selectedInstrument == Instrument::TERRAIN || _selectedInstrument == Instrument::STREAM || _selectedInstrument == Instrument::ROAD - || _selectedInstrument == Instrument::ERASE || _selectedInstrument == Instrument::OBJECT || _selectedInstrument == Instrument::DETAIL; + || _selectedInstrument == Instrument::ERASE || isObjectMode() || _selectedInstrument == Instrument::DETAIL; } bool useMouseDragMovement() const @@ -107,15 +109,9 @@ namespace Interface // The name of this method starts from _ on purpose to do not mix with other public methods. void _redraw() const; - int32_t getSelectedObjectType() const - { - return _selectedObjectType[_selectedObject]; - } + int32_t getSelectedObjectType() const; - Maps::ObjectGroup getSelectedObjectGroup() const - { - return _selectedObjectGroup[_selectedObject]; - } + Maps::ObjectGroup getSelectedObjectGroup() const; void getTownObjectProperties( int32_t & type, int32_t & color ) const; @@ -127,7 +123,9 @@ namespace Interface return Maps::Ground::String( _getGroundId( brushId ) ); } - static const char * _getObjectTypeName( const uint8_t brushId ); + static const char * _getLandscapeObjectTypeName( const uint8_t brushId ); + static const char * _getAdventureObjectTypeName( const uint8_t brushId ); + static const char * _getKingdomObjectTypeName( const uint8_t brushId ); static const char * _getEraseObjectTypeName( const uint32_t eraseObjectType ); static int32_t _generateTownObjectProperties( const int32_t type, const int32_t color ); @@ -140,17 +138,20 @@ namespace Interface { // IMPORTANT. This enumeration corresponds with the order of instruments in original assets. Do not change this order. TERRAIN = 0U, - OBJECT = 1U, + LANDSCAPE_OBJECTS = 1U, DETAIL = 2U, - STREAM = 3U, - ROAD = 4U, - ERASE = 5U, + ADVENTURE_OBJECTS = 3U, + KINGDOM_OBJECTS = 4U, + MONSTERS = 5U, + STREAM = 6U, + ROAD = 7U, + ERASE = 8U, // The last element corresponds to the editor instruments count. - INSTRUMENTS_COUNT = 6U + INSTRUMENTS_COUNT = 9U }; - enum Brush : uint8_t + enum TerrainBrush : uint8_t { // IMPORTANT. This enumeration corresponds with the order of instruments in original assets. Do not change this order. WATER = 0U, @@ -163,18 +164,40 @@ namespace Interface WASTELAND = 7U, BEACH = 8U, - // This element corresponds to the editor terrain types count. + // The last element corresponds to the editor terrain types count. TERRAIN_COUNT = 9U, + }; + + enum LandscapeObjectBrush : int8_t + { + MOUNTAINS = 0, + ROCKS = 1, + TREES = 2, + WATER_OBJECTS = 3, + LANDSCAPE_MISC = 4, - // For objects this enumeration continues. - TOWNS = 9U, - MONSTERS = 10U, - HEROES = 11U, - ARTIFACTS = 12U, - TREASURES = 13U, + LANDSCAPE_COUNT = 5, + }; - // The last element corresponds to the editor object types count. - OBJECT_COUNT = 14U + enum AdventureObjectBrush : int8_t + { + ARTIFACTS = 0U, + DWELLINGS = 1U, + MINES = 2U, + POWER_UPS = 3U, + TREASURES = 4U, + WATER_ADVENTURE = 5U, + ADVENTURE_MISC = 6U, + + ADVENTURE_COUNT = 7U, + }; + + enum KingdomObjectBrush : int8_t + { + HEROES = 0U, + TOWNS = 1U, + + KINGDOM_OBJECTS_COUNT = 2U, }; enum BrushSize : uint8_t @@ -215,39 +238,47 @@ namespace Interface fheroes2::Rect _rectInstrumentPanel; fheroes2::Rect _rectEditorPanel; + fheroes2::Image _instrumentPanelBackground; + std::array _instrumentButtons; std::array _brushSizeButtons; std::array _instrumentButtonsRect; - std::array _terrainButtonsRect; - std::array _objectButtonsRect; + std::array _terrainButtonsRect; + std::array _landscapeObjectButtonsRect; + std::array _adventureObjectButtonsRect; + std::array _kingdomObjectButtonsRect; std::array _brushSizeButtonsRect; std::array _eraseButtonsRect; uint8_t _selectedInstrument{ Instrument::TERRAIN }; // A brand new map is always filled with Water so there is no need to make Water terrain brush as a default terrain selection. - uint8_t _selectedTerrain{ Brush::GRASS }; - uint8_t _selectedObject{ Brush::WATER }; + uint8_t _selectedTerrain{ TerrainBrush::GRASS }; + int8_t _selectedLandscapeObject{ -1 }; + int8_t _selectedAdventureObject{ -1 }; + int8_t _selectedKingdomObject{ -1 }; uint8_t _selectedBrushSize{ BrushSize::MEDIUM }; uint32_t _eraseTypes{ Maps::ObjectErasureType::ALL_OBJECTS }; - std::array _selectedObjectType; - - // TODO: this list is going to be modified as per proper object groups. - const std::array _selectedObjectGroup{ Maps::ObjectGroup::ADVENTURE_WATER, - Maps::ObjectGroup::LANDSCAPE_MOUNTAINS, - Maps::ObjectGroup::LANDSCAPE_ROCKS, - Maps::ObjectGroup::LANDSCAPE_TREES, - Maps::ObjectGroup::ADVENTURE_DWELLINGS, - Maps::ObjectGroup::LANDSCAPE_MISCELLANEOUS, - Maps::ObjectGroup::ADVENTURE_MINES, - Maps::ObjectGroup::ADVENTURE_POWER_UPS, - Maps::ObjectGroup::LANDSCAPE_WATER, - Maps::ObjectGroup::KINGDOM_TOWNS, - Maps::ObjectGroup::MONSTERS, - Maps::ObjectGroup::KINGDOM_HEROES, - Maps::ObjectGroup::ADVENTURE_ARTIFACTS, - Maps::ObjectGroup::ADVENTURE_TREASURES }; + std::array _selectedLandscapeObjectType; + std::array _selectedAdventureObjectType; + std::array _selectedKingdomObjectType; + int32_t _selectedMonsterType{ -1 }; + + const std::array _selectedLandscapeObjectGroup{ Maps::ObjectGroup::LANDSCAPE_MOUNTAINS, + Maps::ObjectGroup::LANDSCAPE_ROCKS, + Maps::ObjectGroup::LANDSCAPE_TREES, + Maps::ObjectGroup::LANDSCAPE_WATER, + Maps::ObjectGroup::LANDSCAPE_MISCELLANEOUS }; + const std::array _selectedAdventureObjectGroup{ Maps::ObjectGroup::ADVENTURE_ARTIFACTS, + Maps::ObjectGroup::ADVENTURE_DWELLINGS, + Maps::ObjectGroup::ADVENTURE_MINES, + Maps::ObjectGroup::ADVENTURE_POWER_UPS, + Maps::ObjectGroup::ADVENTURE_TREASURES, + Maps::ObjectGroup::ADVENTURE_WATER, + Maps::ObjectGroup::ADVENTURE_MISCELLANEOUS }; + const std::array _selectedKingdomObjectGroup{ Maps::ObjectGroup::KINGDOM_HEROES, + Maps::ObjectGroup::KINGDOM_TOWNS }; }; } diff --git a/src/fheroes2/game/game_interface.cpp b/src/fheroes2/game/game_interface.cpp index 9df4c78fb6d..f15204e94bf 100644 --- a/src/fheroes2/game/game_interface.cpp +++ b/src/fheroes2/game/game_interface.cpp @@ -52,6 +52,7 @@ Interface::AdventureMap::AdventureMap() , iconsPanel( *this ) , buttonsArea( *this ) , controlPanel( *this ) + , _statusWindow( *this ) , _lockRedraw( false ) { AdventureMap::reset(); diff --git a/src/fheroes2/game/game_interface.h b/src/fheroes2/game/game_interface.h index 5efb7f9959c..22ead77b9fb 100644 --- a/src/fheroes2/game/game_interface.h +++ b/src/fheroes2/game/game_interface.h @@ -31,6 +31,7 @@ #include "interface_buttons.h" #include "interface_cpanel.h" #include "interface_icons.h" +#include "interface_status.h" #include "players.h" class Castle; @@ -103,6 +104,11 @@ namespace Interface return controlPanel; } + StatusWindow & getStatusWindow() + { + return _statusWindow; + } + void SetFocus( Heroes *, const bool retainScrollBarPosition ); void SetFocus( Castle * ); void ResetFocus( const int priority, const bool retainScrollBarPosition ); @@ -165,6 +171,7 @@ namespace Interface IconsPanel iconsPanel; ButtonsArea buttonsArea; ControlPanel controlPanel; + StatusWindow _statusWindow; bool _lockRedraw; }; diff --git a/src/fheroes2/gui/interface_base.h b/src/fheroes2/gui/interface_base.h index 6c91d967981..0d5677e9623 100644 --- a/src/fheroes2/gui/interface_base.h +++ b/src/fheroes2/gui/interface_base.h @@ -26,7 +26,6 @@ #include "gamedefs.h" #include "interface_gamearea.h" #include "interface_radar.h" -#include "interface_status.h" #include "math_base.h" #include "screen.h" @@ -63,7 +62,6 @@ namespace Interface explicit BaseInterface( const bool isEditor_ ) : _gameArea( *this ) , _radar( *this ) - , _statusWindow( *this ) , _isEditor( isEditor_ ) { // Do nothing @@ -122,11 +120,6 @@ namespace Interface return _radar; } - StatusWindow & getStatusWindow() - { - return _statusWindow; - } - static fheroes2::GameMode EventExit(); virtual bool useMouseDragMovement() @@ -153,7 +146,6 @@ namespace Interface GameArea _gameArea; Radar _radar; - StatusWindow _statusWindow; uint32_t _redraw{ 0 }; diff --git a/src/fheroes2/gui/ui_window.cpp b/src/fheroes2/gui/ui_window.cpp index 64dad126e82..1c2a28f84e6 100644 --- a/src/fheroes2/gui/ui_window.cpp +++ b/src/fheroes2/gui/ui_window.cpp @@ -115,7 +115,7 @@ namespace fheroes2 if ( _hasBackground ) { // Render the background image. - _renderBackground( isEvilInterface ); + renderBackgroundImage( _output, _windowArea, backgroundOffset, isEvilInterface ); // Make a transition from borders to the background in the corners. CreateDitheringTransition( verticalSprite, cornerSize, cornerSize, _output, cornerOffset.x, cornerOffset.y, extraCornerSize, transitionSize, false, true ); @@ -276,7 +276,7 @@ namespace fheroes2 void StandardWindow::applyTextBackgroundShading( const Rect & roi ) { - const fheroes2::Rect shadingRoi = roi ^ _activeArea; + const Rect shadingRoi = roi ^ _activeArea; // The text background is darker than original background. The shadow strength 2 is too much so we do two shading transforms: 3 and 5. ApplyTransform( _output, shadingRoi.x + 2, shadingRoi.y + 2, shadingRoi.width - 4, shadingRoi.height - 4, 3 ); @@ -308,7 +308,7 @@ namespace fheroes2 void StandardWindow::renderScrollbarBackground( const Rect & roi, const bool isEvilInterface ) { - const fheroes2::Sprite & scrollBar = fheroes2::AGG::GetICN( isEvilInterface ? ICN::ADVBORDE : ICN::ADVBORD, 0 ); + const Sprite & scrollBar = AGG::GetICN( isEvilInterface ? ICN::ADVBORDE : ICN::ADVBORD, 0 ); const int32_t topPartHeight = 19; const int32_t scrollBarWidth = 16; @@ -317,34 +317,33 @@ namespace fheroes2 const int32_t middleAndBottomPartsHeight = roi.height - topPartHeight; // Top part of scrollbar background. - fheroes2::Copy( scrollBar, icnOffsetX, 176, _output, roi.x, roi.y, scrollBarWidth, topPartHeight ); + Copy( scrollBar, icnOffsetX, 176, _output, roi.x, roi.y, scrollBarWidth, topPartHeight ); // Middle part of scrollbar background. const int32_t middlePartCount = ( roi.height - 2 * topPartHeight + middlePartHeight - 1 ) / middlePartHeight; int32_t offsetY = topPartHeight; for ( int32_t i = 0; i < middlePartCount; ++i ) { - fheroes2::Copy( scrollBar, icnOffsetX, 196, _output, roi.x, roi.y + offsetY, scrollBarWidth, - std::min( middlePartHeight, middleAndBottomPartsHeight - offsetY ) ); + Copy( scrollBar, icnOffsetX, 196, _output, roi.x, roi.y + offsetY, scrollBarWidth, std::min( middlePartHeight, middleAndBottomPartsHeight - offsetY ) ); offsetY += middlePartHeight; } // Bottom part of scrollbar background. - fheroes2::Copy( scrollBar, icnOffsetX, 285, _output, roi.x, roi.y + middleAndBottomPartsHeight, scrollBarWidth, topPartHeight ); + Copy( scrollBar, icnOffsetX, 285, _output, roi.x, roi.y + middleAndBottomPartsHeight, scrollBarWidth, topPartHeight ); // Make scrollbar shadow. for ( uint8_t i = 0; i < 4; ++i ) { const uint8_t transformId = i + 1; - fheroes2::ApplyTransform( _output, roi.x - transformId, roi.y + transformId, 1, roi.height - transformId, transformId ); - fheroes2::ApplyTransform( _output, roi.x - transformId, roi.y + roi.height + i, scrollBarWidth, 1, transformId ); + ApplyTransform( _output, roi.x - transformId, roi.y + transformId, 1, roi.height - transformId, transformId ); + ApplyTransform( _output, roi.x - transformId, roi.y + roi.height + i, scrollBarWidth, 1, transformId ); } } void StandardWindow::renderButtonSprite( ButtonSprite & button, const std::string & buttonText, const int32_t buttonWidth, const Point & offset, const bool isEvilInterface, const Padding padding ) { - fheroes2::Sprite released; - fheroes2::Sprite pressed; + Sprite released; + Sprite pressed; makeButtonSprites( released, pressed, buttonText, buttonWidth, isEvilInterface, false ); @@ -352,20 +351,20 @@ namespace fheroes2 button.setSprite( released, pressed ); button.setPosition( pos.x, pos.y ); - fheroes2::addGradientShadow( released, _output, button.area().getPosition(), { -5, 5 } ); + addGradientShadow( released, _output, button.area().getPosition(), { -5, 5 } ); button.draw(); } void StandardWindow::renderButton( Button & button, const int icnId, const uint32_t releasedIndex, const uint32_t pressedIndex, const Point & offset, const Padding padding ) { - const fheroes2::Sprite & buttonSprite = fheroes2::AGG::GetICN( icnId, 0 ); + const Sprite & buttonSprite = AGG::GetICN( icnId, 0 ); const Point pos = _getRenderPos( offset, { buttonSprite.width(), buttonSprite.height() }, padding ); button.setICNInfo( icnId, releasedIndex, pressedIndex ); button.setPosition( pos.x, pos.y ); - fheroes2::addGradientShadow( buttonSprite, _output, button.area().getPosition(), { -5, 5 } ); + addGradientShadow( buttonSprite, _output, button.area().getPosition(), { -5, 5 } ); button.draw(); } @@ -433,53 +432,53 @@ namespace fheroes2 return pos; } - void StandardWindow::_renderBackground( const bool isEvilInterface ) + void StandardWindow::renderBackgroundImage( Image & output, const Rect & roi, const int32_t borderOffset, const bool isEvilInterface ) { const Sprite & backgroundSprite = AGG::GetICN( ( isEvilInterface ? ICN::STONEBAK_EVIL : ICN::STONEBAK ), 0 ); const int32_t backgroundSpriteWidth{ backgroundSprite.width() }; const int32_t backgroundSpriteHeight{ backgroundSprite.height() }; - const int32_t backgroundWidth = _windowArea.width - backgroundOffset * 2; - const int32_t backgroundHeight = _windowArea.height - backgroundOffset * 2; + const int32_t backgroundWidth = roi.width - borderOffset * 2; + const int32_t backgroundHeight = roi.height - borderOffset * 2; const int32_t backgroundHorizontalCopies = ( backgroundWidth - 1 - transitionSize ) / ( backgroundSpriteWidth - transitionSize ); const int32_t backgroundVerticalCopies = ( backgroundHeight - 1 - transitionSize ) / ( backgroundSpriteHeight - transitionSize ); const int32_t backgroundCopyWidth = std::min( backgroundSpriteWidth, backgroundWidth ); const int32_t backgroundCopyHeight = std::min( backgroundSpriteHeight, backgroundHeight ); - const int32_t backgroundOffsetX = _windowArea.x + backgroundOffset; - const int32_t backgroundOffsetY = _windowArea.y + backgroundOffset; + const int32_t backgroundOffsetX = roi.x + borderOffset; + const int32_t backgroundOffsetY = roi.y + borderOffset; // We do a copy as the background image does not have transparent pixels. - Copy( backgroundSprite, 0, 0, _output, backgroundOffsetX, backgroundOffsetY, backgroundCopyWidth, backgroundCopyHeight ); + Copy( backgroundSprite, 0, 0, output, backgroundOffsetX, backgroundOffsetY, backgroundCopyWidth, backgroundCopyHeight ); // If we need more copies to fill background horizontally we make a transition and copy existing image. if ( backgroundHorizontalCopies > 0 ) { - int32_t toOffsetX = backgroundOffset + backgroundSpriteWidth; - CreateDitheringTransition( backgroundSprite, 0, 0, _output, _windowArea.x + toOffsetX - transitionSize, backgroundOffsetY, transitionSize, - backgroundCopyHeight, true, false ); + int32_t toOffsetX = borderOffset + backgroundSpriteWidth; + CreateDitheringTransition( backgroundSprite, 0, 0, output, roi.x + toOffsetX - transitionSize, backgroundOffsetY, transitionSize, backgroundCopyHeight, true, + false ); const int32_t stepX = backgroundSpriteWidth - transitionSize; - const int32_t fromOffsetX = backgroundOffset + transitionSize; + const int32_t fromOffsetX = borderOffset + transitionSize; for ( int32_t i = 0; i < backgroundHorizontalCopies; ++i ) { - Copy( _output, _windowArea.x + fromOffsetX, backgroundOffsetY, _output, _windowArea.x + toOffsetX, backgroundOffsetY, - std::min( backgroundSpriteWidth, _windowArea.width - backgroundOffset - toOffsetX ), backgroundCopyHeight ); + Copy( output, roi.x + fromOffsetX, backgroundOffsetY, output, roi.x + toOffsetX, backgroundOffsetY, + std::min( backgroundSpriteWidth, roi.width - borderOffset - toOffsetX ), backgroundCopyHeight ); toOffsetX += stepX; } } // If we need more copies to fill background vertically we make a transition and copy existing image in full background width. if ( backgroundVerticalCopies > 0 ) { - int32_t toOffsetY = backgroundOffset + backgroundSpriteHeight; - CreateDitheringTransition( _output, backgroundOffsetX, backgroundOffsetY, _output, backgroundOffsetX, _windowArea.y + toOffsetY - transitionSize, - backgroundWidth, transitionSize, false, false ); + int32_t toOffsetY = borderOffset + backgroundSpriteHeight; + CreateDitheringTransition( output, backgroundOffsetX, backgroundOffsetY, output, backgroundOffsetX, roi.y + toOffsetY - transitionSize, backgroundWidth, + transitionSize, false, false ); const int32_t stepY = backgroundSpriteHeight - transitionSize; - const int32_t fromOffsetY = backgroundOffset + transitionSize; + const int32_t fromOffsetY = borderOffset + transitionSize; for ( int32_t i = 0; i < backgroundVerticalCopies; ++i ) { - Copy( _output, backgroundOffsetX, _windowArea.y + fromOffsetY, _output, backgroundOffsetX, _windowArea.y + toOffsetY, backgroundWidth, - std::min( backgroundSpriteHeight, _windowArea.height - backgroundOffset - toOffsetY ) ); + Copy( output, backgroundOffsetX, roi.y + fromOffsetY, output, backgroundOffsetX, roi.y + toOffsetY, backgroundWidth, + std::min( backgroundSpriteHeight, roi.height - borderOffset - toOffsetY ) ); toOffsetY += stepY; } } diff --git a/src/fheroes2/gui/ui_window.h b/src/fheroes2/gui/ui_window.h index 21b219542eb..c185b34589b 100644 --- a/src/fheroes2/gui/ui_window.h +++ b/src/fheroes2/gui/ui_window.h @@ -92,6 +92,8 @@ namespace fheroes2 void applyTextBackgroundShading( const Rect & roi ); + static void renderBackgroundImage( fheroes2::Image & output, const Rect & roi, const int32_t borderOffset, const bool isEvilInterface ); + private: Image & _output; const Rect _activeArea; @@ -101,6 +103,5 @@ namespace fheroes2 const bool _hasBackground{ true }; Point _getRenderPos( const Point & offset, const Size & itemSize, const Padding padding ) const; - void _renderBackground( const bool isEvilInterface ); }; }