diff --git a/src/env/Debug.h b/src/env/Debug.h index d8c48a16..ff0182c2 100644 --- a/src/env/Debug.h +++ b/src/env/Debug.h @@ -85,7 +85,7 @@ extern debug_stats_t g_debug_stats; #define NEW( _var, _class, ... ) \ _var = new _class( __VA_ARGS__ ); \ - debug::g_memory_watcher->New( _var, sizeof( _class ), __FILE__, __LINE__ ); + debug::g_memory_watcher->New( _var, sizeof( _class ), __FILE__, __LINE__ ) #define NEWV( _var, _class, ... ) \ _class* _var; \ diff --git a/src/game/frontend/Game.cpp b/src/game/frontend/Game.cpp index a44dfbd4..919f2759 100644 --- a/src/game/frontend/Game.cpp +++ b/src/game/frontend/Game.cpp @@ -1325,6 +1325,10 @@ void Game::Initialize( break; } case ::ui::event::K_ENTER: { + if ( m_turn_status == backend::turn::TS_TURN_COMPLETE ) { + CompleteTurn(); + break; + } auto* const unit = m_um->GetSelectedUnit(); if ( unit ) { ASSERT( unit->IsActive(), "selected unit not active" ); @@ -1345,10 +1349,6 @@ void Game::Initialize( } } } - if ( m_turn_status == backend::turn::TS_TURN_COMPLETE ) { - CompleteTurn(); - break; - } break; } default: { @@ -1891,6 +1891,8 @@ void Game::DeselectTileOrUnit() { void Game::OpenBasePopup( base::Base* base ) { if ( !m_base_popup ) { + m_tile_selected_before_base_popup = m_tm->GetPreviouslyDeselectedTile(); + m_unit_selected_before_base_popup = m_um->GetPreviouslyDeselectedUnit(); NEW( m_base_popup, ui::popup::base_popup::BasePopup, this, base ); m_base_popup->Open(); } @@ -2355,6 +2357,12 @@ void Game::ScrollToSelectedTile( const bool center_on_tile ) { ScrollToTile( m_tm->GetSelectedTile(), center_on_tile ); } +void Game::SelectAnyUnitAtTile( tile::Tile* tile ) { + m_tile_at_query_purpose = backend::TQP_UNIT_SELECT; + SelectTileOrUnit( tile ); + ScrollToTile( tile, true ); +} + void Game::SelectUnitOrSelectedTile( unit::Unit* selected_unit ) { ASSERT( m_tm->GetSelectedTile(), "tile not selected" ); m_tile_at_query_purpose = backend::TQP_UNIT_SELECT; @@ -2383,15 +2391,21 @@ Game::map_data_t::map_data_t() void Game::OnBasePopupClose() { ASSERT( m_base_popup, "base popup not open" ); - auto* const unit = m_um->GetPreviouslyDeselectedUnit(); - if ( unit ) { - m_um->SelectUnit( unit, true ); + if ( m_unit_selected_before_base_popup ) { + m_um->SelectUnit( m_unit_selected_before_base_popup, true ); + //ScrollToTile( m_unit_selected_before_base_popup->GetTile(), true ); } - else { - m_tile_at_query_purpose = backend::TQP_UNIT_SELECT; - SelectTileOrUnit( m_base_popup->GetBase()->GetTile() ); + else if ( m_tile_selected_before_base_popup ) { + m_tile_at_query_purpose = backend::TQP_TILE_SELECT; + SelectTileOrUnit( m_tile_selected_before_base_popup ); + //ScrollToTile( m_tile_selected_before_base_popup, true ); } m_base_popup = nullptr; + m_unit_selected_before_base_popup = nullptr; +} + +void Game::SetBasePopupSelectedUnit( unit::Unit* unit ) { + m_unit_selected_before_base_popup = unit; } } diff --git a/src/game/frontend/Game.h b/src/game/frontend/Game.h index fee0b27f..fb657bf7 100644 --- a/src/game/frontend/Game.h +++ b/src/game/frontend/Game.h @@ -122,6 +122,9 @@ class Theme; } namespace popup::base_popup { class BasePopup; +namespace bottom_bar { +class UnitsList; +} } } @@ -468,6 +471,8 @@ CLASS( Game, common::Module ) void SendAnimationFinished( const size_t animation_id ); ui::popup::base_popup::BasePopup* m_base_popup = nullptr; + unit::Unit* m_unit_selected_before_base_popup = nullptr; + tile::Tile* m_tile_selected_before_base_popup = nullptr; private: friend class unit::UnitManager; @@ -481,6 +486,7 @@ CLASS( Game, common::Module ) void RefreshSelectedTile( unit::Unit* selected_unit ); void RefreshSelectedTileIf( tile::Tile* if_tile, unit::Unit* selected_unit ); void ScrollToSelectedTile( const bool center_on_tile ); + void SelectAnyUnitAtTile( tile::Tile* tile ); void SelectUnitOrSelectedTile( unit::Unit* selected_unit ); unit::Unit* GetSelectedTileMostImportantUnit() const; @@ -488,6 +494,10 @@ CLASS( Game, common::Module ) friend class ui::popup::base_popup::BasePopup; void OnBasePopupClose(); +private: + friend class ui::popup::base_popup::bottom_bar::UnitsList; + void SetBasePopupSelectedUnit( unit::Unit* unit ); + }; } diff --git a/src/game/frontend/base/Base.cpp b/src/game/frontend/base/Base.cpp index 639bfbdb..a695490a 100644 --- a/src/game/frontend/base/Base.cpp +++ b/src/game/frontend/base/Base.cpp @@ -74,6 +74,14 @@ const size_t Base::GetId() const { return m_id; } +const std::string& Base::GetName() const { + return m_name; +} + +faction::Faction* const Base::GetFaction() const { + return m_faction; +} + const bool Base::IsOwned() const { return m_is_owned; } diff --git a/src/game/frontend/base/Base.h b/src/game/frontend/base/Base.h index ecb11ded..369d7607 100644 --- a/src/game/frontend/base/Base.h +++ b/src/game/frontend/base/Base.h @@ -64,6 +64,8 @@ class Base : public TileObject { ~Base(); const size_t GetId() const; + const std::string& GetName() const; + faction::Faction* const GetFaction() const; const bool IsOwned() const; tile::Tile* GetTile() const; diff --git a/src/game/frontend/base/BaseManager.cpp b/src/game/frontend/base/BaseManager.cpp index fd9b76bc..2475e654 100644 --- a/src/game/frontend/base/BaseManager.cpp +++ b/src/game/frontend/base/BaseManager.cpp @@ -73,6 +73,17 @@ void BaseManager::SpawnBase( ) } ); + auto it = m_faction_base_ids.find( faction ); + if ( it == m_faction_base_ids.end() ) { + it = m_faction_base_ids.insert( + { + faction, + {} + } + ).first; + } + ASSERT( it->second.find( base_id ) == it->second.end(), "faction base id already exists" ); + it->second.insert( base_id ); m_game->RenderTile( tile, m_game->GetUM()->GetSelectedUnit() ); @@ -115,6 +126,34 @@ void BaseManager::SelectBase( Base* base ) { m_game->OpenBasePopup( base ); } +Base* BaseManager::GetBaseBefore( Base* base ) const { + const auto& ids_it = m_faction_base_ids.find( base->GetFaction() ); + ASSERT( ids_it != m_faction_base_ids.end(), "faction ids not found" ); + const auto& ids = ids_it->second; + auto it = ids.find( base->GetId() ); + ASSERT( it != ids.end(), "base not in faction ids" ); + if ( it == ids.begin() ) { + it = ids.end(); + } + it--; + ASSERT( m_bases.find( *it ) != m_bases.end(), "base id not found" ); + return m_bases.at( *it ); +} + +Base* BaseManager::GetBaseAfter( Base* base ) const { + const auto& ids_it = m_faction_base_ids.find( base->GetFaction() ); + ASSERT( ids_it != m_faction_base_ids.end(), "faction ids not found" ); + const auto& ids = ids_it->second; + auto it = ids.find( base->GetId() ); + ASSERT( it != ids.end(), "base not in faction ids" ); + it++; + if ( it == ids.end() ) { + it = ids.begin(); + } + ASSERT( m_bases.find( *it ) != m_bases.end(), "base id not found" ); + return m_bases.at( *it ); +} + text::InstancedFont* BaseManager::GetBadgeFont() const { return m_badge_font; } diff --git a/src/game/frontend/base/BaseManager.h b/src/game/frontend/base/BaseManager.h index e13e2d35..52e6ecc0 100644 --- a/src/game/frontend/base/BaseManager.h +++ b/src/game/frontend/base/BaseManager.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include "common/Common.h" @@ -65,6 +65,8 @@ CLASS( BaseManager, common::Class ) void DefineSlotBadges( const size_t slot_index, const faction::Faction* faction ); void SelectBase( Base* base ); + Base* GetBaseBefore( Base* base ) const; + Base* GetBaseAfter( Base* base ) const; private: friend class Base; @@ -84,6 +86,8 @@ CLASS( BaseManager, common::Class ) std::unordered_map< size_t, SlotBadges* > m_slot_badges = {}; std::unordered_map< size_t, base::Base* > m_bases = {}; + typedef std::set< size_t > ordered_base_ids_t; + std::unordered_map< faction::Faction*, ordered_base_ids_t > m_faction_base_ids = {}; }; diff --git a/src/game/frontend/tile/TileManager.cpp b/src/game/frontend/tile/TileManager.cpp index 387a3616..ec366013 100644 --- a/src/game/frontend/tile/TileManager.cpp +++ b/src/game/frontend/tile/TileManager.cpp @@ -103,12 +103,17 @@ void TileManager::SelectTile( Tile* tile ) { } void TileManager::DeselectTile() { + m_previously_deselected_tile = m_selected_tile; if ( m_selected_tile ) { m_selected_tile->Render(); m_selected_tile = nullptr; } } +Tile* TileManager::GetPreviouslyDeselectedTile() const { + return m_previously_deselected_tile; +} + } } } diff --git a/src/game/frontend/tile/TileManager.h b/src/game/frontend/tile/TileManager.h index 0c5f8661..2c03b7ef 100644 --- a/src/game/frontend/tile/TileManager.h +++ b/src/game/frontend/tile/TileManager.h @@ -28,6 +28,7 @@ CLASS( TileManager, ::common::Class ) Tile* GetSelectedTile() const; void SelectTile( Tile* tile ); void DeselectTile(); + Tile* GetPreviouslyDeselectedTile() const; private: @@ -37,6 +38,7 @@ CLASS( TileManager, ::common::Class ) std::unordered_map< size_t, Tile > m_tiles = {}; Tile* m_selected_tile = nullptr; + Tile* m_previously_deselected_tile = nullptr; }; diff --git a/src/game/frontend/ui/bottom_bar/objects_list/CMakeLists.txt b/src/game/frontend/ui/bottom_bar/objects_list/CMakeLists.txt index 2bcdccec..4cd5e1a0 100644 --- a/src/game/frontend/ui/bottom_bar/objects_list/CMakeLists.txt +++ b/src/game/frontend/ui/bottom_bar/objects_list/CMakeLists.txt @@ -1,5 +1,6 @@ SET( SRC ${SRC} + ${PWD}/ObjectsListBase.cpp ${PWD}/ObjectsList.cpp ${PWD}/ObjectsListItem.cpp diff --git a/src/game/frontend/ui/bottom_bar/objects_list/ObjectsList.cpp b/src/game/frontend/ui/bottom_bar/objects_list/ObjectsList.cpp index 7246ae0c..94d2fe4f 100644 --- a/src/game/frontend/ui/bottom_bar/objects_list/ObjectsList.cpp +++ b/src/game/frontend/ui/bottom_bar/objects_list/ObjectsList.cpp @@ -1,86 +1,23 @@ #include "ObjectsList.h" -#include "engine/Engine.h" -#include "game/frontend/Game.h" -#include "ui/object/ScrollView.h" #include "game/frontend/ui/bottom_bar/ObjectPreview.h" -#include "ObjectsListItem.h" -#include "game/frontend/unit/Unit.h" -#include "game/frontend/unit/UnitManager.h" namespace game { namespace frontend { namespace ui { ObjectsList::ObjectsList( Game* game, ObjectPreview* object_preview ) - : BBSection( game, "ObjectsList" ) + : ObjectsListBase( game ) , m_object_preview( object_preview ) {} -void ObjectsList::Create() { - BBSection::Create(); - +bool ObjectsList::OnObjectMouseOver( TileObject* const object ) { + PreviewObject( object ); + return true; } -void ObjectsList::Destroy() { - - ClearObjects(); - - BBSection::Destroy(); -} - -void ObjectsList::ClearObjects() { - if ( m_body ) { - m_object_preview->HideObjectPreview(); - m_selected_object = nullptr; - for ( const auto& it : m_items ) { - m_body->RemoveChild( it.second ); - } - m_items.clear(); - RemoveChild( m_body ); - m_body = nullptr; - } -} - -void ObjectsList::ListObjects( const std::vector< TileObject* >& objects, const size_t selected_unit_id ) { - const auto last_scroll_x = m_body - ? m_body->GetScrollX() - : 0; - ClearObjects(); // TODO: avoid redraws? - NEW( m_body, ::ui::object::ScrollView, ::ui::object::ScrollView::ST_HORIZONTAL_INLINE ); - m_body->SetSticky( false ); - m_body->SetScrollSpeed( 70 ); - AddChild( m_body ); - float left = 0; - unit::Unit* selected_unit = nullptr; - for ( const auto& object : objects ) { - NEWV( item, ObjectsListItem, m_game, this, object ); - item->SetLeft( left ); - m_body->AddChild( item ); - if ( - ( - selected_unit_id && - object->GetType() == TileObject::TOT_UNIT && - ( (unit::Unit*)object )->GetId() == selected_unit_id // select unit as requested - ) || - ( - !selected_unit_id && m_items.empty() // auto-select first unit by default - ) - ) { - selected_unit = (unit::Unit*)object; - } - m_items.insert( - { - object, - item - } - ); - left += item->GetWidth(); - } - m_body->SetScrollX( last_scroll_x ); - if ( selected_unit ) { - SelectObject( selected_unit, false ); - m_object_preview->PreviewObject( selected_unit ); - } +bool ObjectsList::OnObjectMouseOut( TileObject* const object ) { + HideObjectPreview( object ); + return true; } void ObjectsList::PreviewObject( TileObject* object ) { @@ -91,50 +28,26 @@ void ObjectsList::PreviewObject( TileObject* object ) { } void ObjectsList::HideObjectPreview( TileObject* object ) { + auto* selected_object = GetSelectedObject(); if ( object != m_previewing_object ) { if ( m_previewing_object ) { m_object_preview->PreviewObject( m_previewing_object ); } - else if ( m_selected_object ) { - m_object_preview->PreviewObject( m_selected_object ); + else if ( selected_object ) { + m_object_preview->PreviewObject( selected_object ); } else if ( object == m_previewing_object ) { // TODO: test m_object_preview->HideObjectPreview(); } } if ( object == m_previewing_object ) { - if ( m_selected_object ) { - m_object_preview->PreviewObject( m_selected_object ); + if ( selected_object ) { + m_object_preview->PreviewObject( selected_object ); } m_previewing_object = nullptr; } } -void ObjectsList::SelectObject( TileObject* object, const bool activate_object ) { - if ( - object != m_selected_object || - object->GetType() == TileObject::TOT_BASE // select base again unless popup is open - ) { - m_selected_object = object; - bool was_selected = true; - if ( activate_object ) { - was_selected = m_selected_object->OnBottomBarListActivate( m_game ); - } - if ( was_selected ) { - for ( auto& it : m_items ) { - const auto& item = it.second; - if ( it.first == object ) { - item->SelectObject(); - m_body->ScrollToItem( item ); - } - else { - item->DeselectObject(); - } - } - } - } -} - } } } diff --git a/src/game/frontend/ui/bottom_bar/objects_list/ObjectsList.h b/src/game/frontend/ui/bottom_bar/objects_list/ObjectsList.h index fe278bef..fe519e30 100644 --- a/src/game/frontend/ui/bottom_bar/objects_list/ObjectsList.h +++ b/src/game/frontend/ui/bottom_bar/objects_list/ObjectsList.h @@ -2,43 +2,27 @@ #include -#include "game/frontend/ui/bottom_bar/BBSection.h" - -namespace ui::object { -class ScrollView; -} +#include "ObjectsListBase.h" namespace game { namespace frontend { - -class TileObject; - namespace ui { class ObjectPreview; -class ObjectsListItem; -CLASS( ObjectsList, BBSection ) +CLASS( ObjectsList, ObjectsListBase ) ObjectsList( Game* game, ObjectPreview* object_preview ); - void Create() override; - void Destroy() override; - - void ClearObjects(); - void ListObjects( const std::vector< TileObject* >& objects, const size_t selected_unit_id ); +protected: + bool OnObjectMouseOver( TileObject* const object ) override; + bool OnObjectMouseOut( TileObject* const object ) override; private: - friend class ObjectsListItem; void PreviewObject( TileObject* object ); void HideObjectPreview( TileObject* object ); - void SelectObject( TileObject* object, const bool activate_object ); -private: - ::ui::object::ScrollView* m_body = nullptr; ObjectPreview* m_object_preview; - std::unordered_map< TileObject*, ObjectsListItem* > m_items = {}; - TileObject* m_selected_object = nullptr; TileObject* m_previewing_object = nullptr; }; diff --git a/src/game/frontend/ui/bottom_bar/objects_list/ObjectsListBase.cpp b/src/game/frontend/ui/bottom_bar/objects_list/ObjectsListBase.cpp new file mode 100644 index 00000000..1318221b --- /dev/null +++ b/src/game/frontend/ui/bottom_bar/objects_list/ObjectsListBase.cpp @@ -0,0 +1,150 @@ +#include "ObjectsListBase.h" + +#include "engine/Engine.h" +#include "game/frontend/Game.h" +#include "ui/object/ScrollView.h" +#include "ObjectsListItem.h" +#include "game/frontend/unit/Unit.h" +#include "game/frontend/unit/UnitManager.h" + +namespace game { +namespace frontend { +namespace ui { + +ObjectsListBase::ObjectsListBase( Game* game ) + : BBSection( game, "ObjectsList" ) { + // +} + +void ObjectsListBase::Create() { + BBSection::Create(); + +} + +void ObjectsListBase::Destroy() { + + ClearObjects(); + + BBSection::Destroy(); +} + +void ObjectsListBase::ClearObjects() { + if ( m_body ) { + if ( m_hovered_object ) { + ObjectMouseOut( m_hovered_object ); + } + m_selected_object = nullptr; + for ( const auto& it : m_items ) { + m_body->RemoveChild( it.second ); + } + m_items.clear(); + RemoveChild( m_body ); + m_body = nullptr; + } +} + +void ObjectsListBase::ListObjects( const std::vector< TileObject* >& objects, const size_t selected_unit_id ) { + const auto last_scroll_x = m_body + ? m_body->GetScrollX() + : 0; + ClearObjects(); // TODO: avoid redraws? + NEW( m_body, ::ui::object::ScrollView, ::ui::object::ScrollView::ST_HORIZONTAL_INLINE ); + m_body->SetSticky( false ); + m_body->SetScrollSpeed( 70 ); + AddChild( m_body ); + float left = 0; + unit::Unit* selected_unit = nullptr; + for ( const auto& object : objects ) { + NEWV( item, ObjectsListItem, m_game, this, object ); + item->SetLeft( left ); + m_body->AddChild( item ); + if ( + ( + selected_unit_id && + object->GetType() == TileObject::TOT_UNIT && + ( (unit::Unit*)object )->GetId() == selected_unit_id // select unit as requested + ) || + ( + !selected_unit_id && m_items.empty() // auto-select first unit by default + ) + ) { + selected_unit = (unit::Unit*)object; + } + m_items.insert( + { + object, + item + } + ); + left += item->GetWidth(); + } + m_body->SetScrollX( last_scroll_x ); + if ( selected_unit ) { + SelectObject( selected_unit, false ); + OnObjectMouseOver( selected_unit ); // ??? + } +} + +TileObject* const ObjectsListBase::GetSelectedObject() const { + return m_selected_object; +} + +bool ObjectsListBase::OnObjectMouseOver( TileObject* const object ) { + return false; +} + +bool ObjectsListBase::OnObjectMouseOut( TileObject* const object ) { + return false; +} + +bool ObjectsListBase::OnObjectClick( TileObject* const object ) { + return false; +} + +void ObjectsListBase::SelectObject( TileObject* object, const bool activate_object ) { + if ( + object != m_selected_object || + object->GetType() == TileObject::TOT_BASE // select base again unless popup is open + ) { + m_selected_object = object; + bool was_selected = true; + if ( activate_object ) { + was_selected = m_selected_object->OnBottomBarListActivate( m_game ); + } + if ( was_selected ) { + for ( auto& it : m_items ) { + const auto& item = it.second; + if ( it.first == object ) { + item->SelectObject(); + OnObjectClick( object ); + m_body->ScrollToItem( item ); + } + else { + item->DeselectObject(); + } + } + } + } +} + +bool ObjectsListBase::ObjectMouseOver( TileObject* const object ) { + if ( m_hovered_object ) { + OnObjectMouseOut( m_hovered_object ); + } + m_hovered_object = object; + return OnObjectMouseOver( object ); +} + +bool ObjectsListBase::ObjectMouseOut( TileObject* const object ) { + if ( object == m_hovered_object ) { + m_hovered_object = nullptr; + return OnObjectMouseOut( object ); + } + else { + return true; + } +} + +} +} +} diff --git a/src/game/frontend/ui/bottom_bar/objects_list/ObjectsListBase.h b/src/game/frontend/ui/bottom_bar/objects_list/ObjectsListBase.h new file mode 100644 index 00000000..7a4d87fa --- /dev/null +++ b/src/game/frontend/ui/bottom_bar/objects_list/ObjectsListBase.h @@ -0,0 +1,52 @@ +#pragma once + +#include + +#include "game/frontend/ui/bottom_bar/BBSection.h" + +namespace ui::object { +class ScrollView; +} + +namespace game { +namespace frontend { + +class TileObject; + +namespace ui { + +class ObjectsListItem; + +CLASS( ObjectsListBase, BBSection ) + + ObjectsListBase( Game* game ); + + void Create() override; + void Destroy() override; + + void ClearObjects(); + void ListObjects( const std::vector< TileObject* >& objects, const size_t selected_unit_id ); + +protected: + TileObject* const GetSelectedObject() const; + + virtual bool OnObjectMouseOver( TileObject* const object ); + virtual bool OnObjectMouseOut( TileObject* const object ); + virtual bool OnObjectClick( TileObject* const object ); + +protected: + friend class ObjectsListItem; + void SelectObject( TileObject* object, const bool activate_object ); + bool ObjectMouseOver( TileObject* const object ); + bool ObjectMouseOut( TileObject* const object ); + +private: + ::ui::object::ScrollView* m_body = nullptr; + std::unordered_map< TileObject*, ObjectsListItem* > m_items = {}; + TileObject* m_selected_object = nullptr; + TileObject* m_hovered_object = nullptr; +}; + +} +} +} diff --git a/src/game/frontend/ui/bottom_bar/objects_list/ObjectsListItem.cpp b/src/game/frontend/ui/bottom_bar/objects_list/ObjectsListItem.cpp index f9d0a9e4..7474a02b 100644 --- a/src/game/frontend/ui/bottom_bar/objects_list/ObjectsListItem.cpp +++ b/src/game/frontend/ui/bottom_bar/objects_list/ObjectsListItem.cpp @@ -1,6 +1,6 @@ #include "ObjectsListItem.h" -#include "ObjectsList.h" +#include "ObjectsListBase.h" #include "game/frontend/unit/Unit.h" #include "ui/object/Label.h" @@ -11,7 +11,7 @@ namespace game { namespace frontend { namespace ui { -ObjectsListItem::ObjectsListItem( Game* game, ObjectsList* objects_list, TileObject* object ) +ObjectsListItem::ObjectsListItem( Game* game, ObjectsListBase* objects_list, TileObject* object ) : Section( game, "BBObjectsListItem", "BB" ) , m_objects_list( objects_list ) , m_object( object ) { @@ -27,14 +27,12 @@ void ObjectsListItem::Create() { On( ::ui::event::EV_MOUSE_OVER, EH( this ) { - m_objects_list->PreviewObject( m_object ); - return true; + return m_objects_list->ObjectMouseOver( m_object ); } ); On( ::ui::event::EV_MOUSE_OUT, EH( this ) { - m_objects_list->HideObjectPreview( m_object ); - return true; + return m_objects_list->ObjectMouseOut( m_object ); } ); On( @@ -53,7 +51,7 @@ void ObjectsListItem::Create() { void ObjectsListItem::Destroy() { RemoveChild( m_selection_frame ); - m_objects_list->HideObjectPreview( m_object ); + m_objects_list->ObjectMouseOut( m_object ); m_object->DestroyOnBottomBarList( this, m_ui_state ); Section::Destroy(); diff --git a/src/game/frontend/ui/bottom_bar/objects_list/ObjectsListItem.h b/src/game/frontend/ui/bottom_bar/objects_list/ObjectsListItem.h index c5e3d008..17834b3c 100644 --- a/src/game/frontend/ui/bottom_bar/objects_list/ObjectsListItem.h +++ b/src/game/frontend/ui/bottom_bar/objects_list/ObjectsListItem.h @@ -19,24 +19,24 @@ class TileObject; namespace ui { -class ObjectsList; +class ObjectsListBase; class ObjectPreview; CLASS( ObjectsListItem, Section ) - ObjectsListItem( Game* game, ObjectsList* objects_list, TileObject* object ); + ObjectsListItem( Game* game, ObjectsListBase* objects_list, TileObject* object ); void Create() override; void Destroy() override; private: - friend class ObjectsList; + friend class ObjectsListBase; void SelectObject(); void DeselectObject(); const TileObject* GetObject() const; private: - ObjectsList* m_objects_list; + ObjectsListBase* m_objects_list; TileObject* m_object; void* m_ui_state = nullptr; diff --git a/src/game/frontend/ui/popup/base_popup/BasePopup.cpp b/src/game/frontend/ui/popup/base_popup/BasePopup.cpp index 74e77cdc..91a3336a 100644 --- a/src/game/frontend/ui/popup/base_popup/BasePopup.cpp +++ b/src/game/frontend/ui/popup/base_popup/BasePopup.cpp @@ -2,6 +2,7 @@ #include "game/frontend/Game.h" #include "game/frontend/base/Base.h" +#include "game/frontend/base/BaseManager.h" #include "game/frontend/ui/popup/base_popup/bottom_bar/BottomBar.h" #include "engine/Engine.h" #include "ui/UI.h" @@ -24,11 +25,23 @@ void BasePopup::Create() { On( ::ui::event::EV_KEY_DOWN, EH( this ) { - if ( data->key.code == ::ui::event::K_ENTER ) { - Close(); - return true; + switch ( data->key.code ) { + case ::ui::event::K_ENTER: { + Close(); + break; + } + case ::ui::event::K_LEFT: { + SelectPrevBase(); + break; + } + case ::ui::event::K_RIGHT: { + SelectNextBase(); + break; + } + default: + return false; } - return false; + return true; } ); @@ -49,18 +62,40 @@ void BasePopup::OnOpen() { m_game->HideBottomBar(); NEW( m_bottom_bar, bottom_bar::BottomBar, m_game, this ); g_engine->GetUI()->AddObject( m_bottom_bar ); + AddEventsTarget( m_bottom_bar ); + + Update( m_base ); } void BasePopup::OnClose() { // restore main bottom bar + RemoveEventsTarget( m_bottom_bar ); g_engine->GetUI()->RemoveObject( m_bottom_bar ); m_game->ShowBottomBar(); m_game->OnBasePopupClose(); } +void BasePopup::Update( base::Base* base ) { + if ( m_base != base ) { + m_base = base; + m_game->SelectAnyUnitAtTile( m_base->GetTile() ); + } + m_bottom_bar->Update( m_base ); +} + +void BasePopup::SelectNextBase() { + auto* base = m_game->GetBM()->GetBaseAfter( m_base ); + Update( base ); +} + +void BasePopup::SelectPrevBase() { + auto* base = m_game->GetBM()->GetBaseBefore( m_base ); + Update( base ); +} + } } } diff --git a/src/game/frontend/ui/popup/base_popup/BasePopup.h b/src/game/frontend/ui/popup/base_popup/BasePopup.h index 7761c100..422e6eea 100644 --- a/src/game/frontend/ui/popup/base_popup/BasePopup.h +++ b/src/game/frontend/ui/popup/base_popup/BasePopup.h @@ -21,6 +21,7 @@ namespace base_popup { namespace bottom_bar { class BottomBar; +class BaseTitle; } CLASS( BasePopup, Popup ) @@ -41,6 +42,13 @@ CLASS( BasePopup, Popup ) bottom_bar::BottomBar* m_bottom_bar = nullptr; + void Update( base::Base* base ); + +private: + friend class bottom_bar::BaseTitle; + void SelectNextBase(); + void SelectPrevBase(); + }; } diff --git a/src/game/frontend/ui/popup/base_popup/bottom_bar/BaseTitle.cpp b/src/game/frontend/ui/popup/base_popup/bottom_bar/BaseTitle.cpp index 3d06f595..f1d9ce57 100644 --- a/src/game/frontend/ui/popup/base_popup/bottom_bar/BaseTitle.cpp +++ b/src/game/frontend/ui/popup/base_popup/bottom_bar/BaseTitle.cpp @@ -1,5 +1,13 @@ #include "BaseTitle.h" +#include "ui/object/Label.h" +#include "ui/object/SimpleButton.h" + +#include "game/frontend/ui/popup/base_popup/BasePopup.h" +#include "game/frontend/base/Base.h" +#include "game/frontend/faction/Faction.h" +#include "util/String.h" + namespace game { namespace frontend { namespace ui { @@ -7,21 +15,52 @@ namespace popup { namespace base_popup { namespace bottom_bar { -BaseTitle::BaseTitle( Game* game ) - : BBSection( game, "BaseTitle" ) { +BaseTitle::BaseTitle( Game* game, BasePopup* popup ) + : BBSection( game, "BaseTitle" ) + , m_popup( popup ) { // } void BaseTitle::Create() { BBSection::Create(); + NEW( m_label, ::ui::object::Label, SubClass( "Text" ) ); + AddChild( m_label ); + + NEW( m_arrows.left, ::ui::object::SimpleButton, SubClass( "LeftArrow" ) ); + m_arrows.left->On( + ::ui::event::EV_BUTTON_CLICK, EH( this ) { + m_popup->SelectNextBase(); + return true; + } + ); + AddChild( m_arrows.left ); + + NEW( m_arrows.right, ::ui::object::SimpleButton, SubClass( "RightArrow" ) ); + m_arrows.right->On( + ::ui::event::EV_BUTTON_CLICK, EH( this ) { + m_popup->SelectPrevBase(); + return true; + } + ); + AddChild( m_arrows.right ); + } void BaseTitle::Destroy() { + RemoveChild( m_label ); + RemoveChild( m_arrows.left ); + RemoveChild( m_arrows.right ); + BBSection::Destroy(); } +void BaseTitle::Update( base::Base* base ) { + m_label->SetText( util::String::ToUpperCase( base->GetName() ) ); + m_label->SetTextColor( base->GetFaction()->m_colors.text ); +} + } } } diff --git a/src/game/frontend/ui/popup/base_popup/bottom_bar/BaseTitle.h b/src/game/frontend/ui/popup/base_popup/bottom_bar/BaseTitle.h index 60bb379f..73b745ac 100644 --- a/src/game/frontend/ui/popup/base_popup/bottom_bar/BaseTitle.h +++ b/src/game/frontend/ui/popup/base_popup/bottom_bar/BaseTitle.h @@ -2,22 +2,44 @@ #include "game/frontend/ui/bottom_bar/BBSection.h" +namespace ui::object { +class Label; +class SimpleButton; +} + namespace game { namespace frontend { - +namespace base { +class Base; +} namespace ui { namespace popup { namespace base_popup { + +class BasePopup; + namespace bottom_bar { CLASS( BaseTitle, BBSection ) - BaseTitle( Game* game ); + BaseTitle( Game* game, BasePopup* popup ); void Create() override; void Destroy() override; private: + BasePopup* m_popup; + + ::ui::object::Label* m_label = nullptr; + + struct { + ::ui::object::SimpleButton* left = nullptr; + ::ui::object::SimpleButton* right = nullptr; + } m_arrows = {}; + +private: + friend class BottomBar; + void Update( base::Base* base ); }; diff --git a/src/game/frontend/ui/popup/base_popup/bottom_bar/BottomBar.cpp b/src/game/frontend/ui/popup/base_popup/bottom_bar/BottomBar.cpp index 3ef5081e..43c4cc2d 100644 --- a/src/game/frontend/ui/popup/base_popup/bottom_bar/BottomBar.cpp +++ b/src/game/frontend/ui/popup/base_popup/bottom_bar/BottomBar.cpp @@ -5,8 +5,10 @@ #include "BuildQueue.h" #include "BaseTitle.h" #include "Population.h" -#include "game/frontend/ui/bottom_bar/objects_list/ObjectsList.h" +#include "UnitsList.h" #include "SupportedUnits.h" +#include "game/frontend/base/Base.h" +#include "game/frontend/tile/Tile.h" namespace game { namespace frontend { @@ -32,14 +34,14 @@ void BottomBar::Create() { NEW( m_sections.build_queue, BuildQueue, m_game ); AddChild( m_sections.build_queue ); - NEW( m_sections.base_title, BaseTitle, m_game ); + NEW( m_sections.base_title, BaseTitle, m_game, m_popup ); AddChild( m_sections.base_title ); NEW( m_sections.population, Population, m_game ); AddChild( m_sections.population ); - NEW( m_sections.objects_list, ui::ObjectsList, m_game, nullptr ); - AddChild( m_sections.objects_list ); + NEW( m_sections.units_list, UnitsList, m_game, m_popup ); + AddChild( m_sections.units_list ); NEW( m_sections.supported_units, SupportedUnits, m_game ); AddChild( m_sections.supported_units ); @@ -52,12 +54,17 @@ void BottomBar::Destroy() { RemoveChild( m_sections.build_queue ); RemoveChild( m_sections.base_title ); RemoveChild( m_sections.population ); - RemoveChild( m_sections.objects_list ); + RemoveChild( m_sections.units_list ); RemoveChild( m_sections.supported_units ); BottomBarBase::Destroy(); } +void BottomBar::Update( base::Base* base ) { + m_sections.base_title->Update( base ); + m_sections.units_list->ListObjects( base->GetTile()->GetOrderedObjects(), 0 ); +} + } } } diff --git a/src/game/frontend/ui/popup/base_popup/bottom_bar/BottomBar.h b/src/game/frontend/ui/popup/base_popup/bottom_bar/BottomBar.h index 00edeb60..e042109a 100644 --- a/src/game/frontend/ui/popup/base_popup/bottom_bar/BottomBar.h +++ b/src/game/frontend/ui/popup/base_popup/bottom_bar/BottomBar.h @@ -8,12 +8,14 @@ namespace game { namespace frontend { -class Game; -namespace ui { +class Game; -class ObjectsList; +namespace base { +class Base; +} +namespace ui { namespace popup { namespace base_popup { @@ -25,6 +27,7 @@ class BuildPreview; class BuildQueue; class BaseTitle; class Population; +class UnitsList; class SupportedUnits; CLASS( BottomBar, BottomBarBase ) @@ -43,10 +46,14 @@ CLASS( BottomBar, BottomBarBase ) BuildQueue* build_queue = nullptr; BaseTitle* base_title = nullptr; Population* population = nullptr; - ui::ObjectsList* objects_list = nullptr; + UnitsList* units_list = nullptr; SupportedUnits* supported_units = nullptr; } m_sections = {}; +private: + friend class base_popup::BasePopup; + void Update( base::Base* base ); + }; } diff --git a/src/game/frontend/ui/popup/base_popup/bottom_bar/CMakeLists.txt b/src/game/frontend/ui/popup/base_popup/bottom_bar/CMakeLists.txt index 006f6818..02a96673 100644 --- a/src/game/frontend/ui/popup/base_popup/bottom_bar/CMakeLists.txt +++ b/src/game/frontend/ui/popup/base_popup/bottom_bar/CMakeLists.txt @@ -5,6 +5,7 @@ SET( SRC ${SRC} ${PWD}/BuildQueue.cpp ${PWD}/BaseTitle.cpp ${PWD}/Population.cpp + ${PWD}/UnitsList.cpp ${PWD}/SupportedUnits.cpp PARENT_SCOPE ) diff --git a/src/game/frontend/ui/popup/base_popup/bottom_bar/UnitsList.cpp b/src/game/frontend/ui/popup/base_popup/bottom_bar/UnitsList.cpp new file mode 100644 index 00000000..8edfbd62 --- /dev/null +++ b/src/game/frontend/ui/popup/base_popup/bottom_bar/UnitsList.cpp @@ -0,0 +1,40 @@ +#include "UnitsList.h" + +#include "game/frontend/TileObject.h" +#include "game/frontend/ui/popup/base_popup/BasePopup.h" +#include "game/frontend/Game.h" +#include "game/frontend/unit/UnitManager.h" + +namespace game { +namespace frontend { +namespace ui { +namespace popup { +namespace base_popup { +namespace bottom_bar { + +UnitsList::UnitsList( Game* game, BasePopup* popup ) + : ObjectsListBase( game ) + , m_popup( popup ) {} + +bool UnitsList::OnObjectMouseOver( TileObject* const object ) { + return true; +} + +bool UnitsList::OnObjectMouseOut( TileObject* const object ) { + return true; +} + +bool UnitsList::OnObjectClick( TileObject* const object ) { + if ( object->GetType() == TileObject::TOT_UNIT ) { + m_game->SetBasePopupSelectedUnit( (unit::Unit*)object ); + m_popup->Close(); + } + return true; +} + +} +} +} +} +} +} diff --git a/src/game/frontend/ui/popup/base_popup/bottom_bar/UnitsList.h b/src/game/frontend/ui/popup/base_popup/bottom_bar/UnitsList.h new file mode 100644 index 00000000..6a02a91b --- /dev/null +++ b/src/game/frontend/ui/popup/base_popup/bottom_bar/UnitsList.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "game/frontend/ui/bottom_bar/objects_list/ObjectsListBase.h" + +namespace game { +namespace frontend { +namespace ui { +namespace popup { +namespace base_popup { + +class BasePopup; + +namespace bottom_bar { + +class ObjectPreview; + +CLASS( UnitsList, ObjectsListBase ) + + UnitsList( Game* game, BasePopup* popup ); + +protected: + bool OnObjectMouseOver( TileObject* const object ) override; + bool OnObjectMouseOut( TileObject* const object ) override; + bool OnObjectClick( TileObject* const object ) override; + +private: + BasePopup* m_popup; +}; + +} +} +} +} +} +} diff --git a/src/game/frontend/ui/style/BottomBar.cpp b/src/game/frontend/ui/style/BottomBar.cpp index f2a4f55b..3b4e7aa8 100644 --- a/src/game/frontend/ui/style/BottomBar.cpp +++ b/src/game/frontend/ui/style/BottomBar.cpp @@ -878,14 +878,60 @@ void BottomBar::AddStyles() { s->Set( ::ui::A_WIDTH, 108 ); } ); + + // base title AddStyle( "BaseTitle", SH() { s->Set( ::ui::A_LEFT, 262 ); s->Set( ::ui::A_TOP, 67 ); s->Set( ::ui::A_RIGHT, 262 ); - s->Set( ::ui::A_HEIGHT, 47 ); // 41 + s->Set( ::ui::A_HEIGHT, 49 ); // 41 + } + ); + AddStyle( + "BaseTitleText", SH() { + s->Set( ::ui::A_ALIGN, ::ui::ALIGN_CENTER ); + s->SetFont( ::ui::A_FONT, resource::TTF_ARIALNB, 28 ); + } + ); + AddStyle( + "BaseTitleArrow", SH() { + s->Set( ::ui::A_WIDTH, 18 ); + s->Set( ::ui::A_HEIGHT, 33 ); + s->SetSound( ::ui::A_BUTTON_CLICK_SOUND, resource::WAV_OK ); + } + ); + AddStyle( + "BaseTitleLeftArrow", { "BaseTitleArrow" }, SH() { + if ( s->Is( ::ui::M_ACTIVE ) || s->Is( ::ui::M_HIGHLIGHT ) ) { + s->SetTexture( ::ui::A_TEXTURE, resource::PCX_INTERFACE, 291, 137, 308, 169 ); + } + else if ( s->Is( ::ui::M_HOVER ) ) { + s->SetTexture( ::ui::A_TEXTURE, resource::PCX_INTERFACE, 291, 101, 308, 133 ); + } + else { + s->SetTexture( ::ui::A_TEXTURE, resource::PCX_INTERFACE, 291, 65, 308, 97 ); + } + s->Set( ::ui::A_ALIGN, ::ui::ALIGN_LEFT | ::ui::ALIGN_VCENTER ); + s->Set( ::ui::A_LEFT, 3 ); + } + ); + AddStyle( + "BaseTitleRightArrow", { "BaseTitleArrow" }, SH() { + if ( s->Is( ::ui::M_ACTIVE ) || s->Is( ::ui::M_HIGHLIGHT ) ) { + s->SetTexture( ::ui::A_TEXTURE, resource::PCX_INTERFACE, 312, 137, 329, 169 ); + } + else if ( s->Is( ::ui::M_HOVER ) ) { + s->SetTexture( ::ui::A_TEXTURE, resource::PCX_INTERFACE, 312, 101, 329, 133 ); + } + else { + s->SetTexture( ::ui::A_TEXTURE, resource::PCX_INTERFACE, 312, 65, 329, 97 ); + } + s->Set( ::ui::A_ALIGN, ::ui::ALIGN_RIGHT | ::ui::ALIGN_VCENTER ); + s->Set( ::ui::A_RIGHT, 3 ); } ); + AddStyle( "Population", SH() { s->Set( ::ui::A_LEFT, 262 ); diff --git a/src/game/frontend/unit/UnitManager.h b/src/game/frontend/unit/UnitManager.h index 98f34654..54882c58 100644 --- a/src/game/frontend/unit/UnitManager.h +++ b/src/game/frontend/unit/UnitManager.h @@ -69,7 +69,7 @@ CLASS( UnitManager, common::Class ) void MoveUnit_deprecated( Unit* unit, tile::Tile* dst_tile, const types::Vec3& dst_render_coords ); Unit* GetSelectedUnit() const; - void SelectUnit( Unit* unit_data, const bool actually_select_unit ); + void SelectUnit( Unit* unit, const bool actually_select_unit ); void DeselectUnit(); Unit* GetPreviouslyDeselectedUnit() const; const bool SelectNextUnitMaybe(); diff --git a/src/ui/UI.cpp b/src/ui/UI.cpp index f530fa15..62c57a39 100644 --- a/src/ui/UI.cpp +++ b/src/ui/UI.cpp @@ -306,6 +306,7 @@ void UI::ProcessEvent( event::UIEvent* event ) { if ( !event->IsProcessed() ) { TriggerGlobalEventHandlers( GH_AFTER, event ); } + } void UI::SendMouseMoveEvent( object::UIObject* object ) { diff --git a/src/ui/object/Popup.cpp b/src/ui/object/Popup.cpp index 926a1d93..c991e0ba 100644 --- a/src/ui/object/Popup.cpp +++ b/src/ui/object/Popup.cpp @@ -4,6 +4,7 @@ #include "loader/sound/SoundLoader.h" #include "ui/UI.h" #include "SoundEffect.h" +#include "ui/event/UIEvent.h" namespace ui { namespace object { @@ -53,6 +54,20 @@ void Popup::Destroy() { UIContainer::Destroy(); } +void Popup::ProcessEvent( event::UIEvent* event ) { + UIContainer::ProcessEvent( event ); + + if ( !event->IsProcessed() ) { + // order is unspecified. improve it if ordering will be needed + for ( const auto& events_target : m_events_targets ) { + events_target->ProcessEvent( event ); + if ( event->IsProcessed() ) { + break; + } + } + } +} + void Popup::Open() { ASSERT( !m_is_opened, "popup already opened" ); m_is_opened = true; @@ -73,6 +88,16 @@ bool Popup::MaybeClose() { return true; } +void Popup::AddEventsTarget( UIObject* const object ) { + ASSERT( m_events_targets.find( object ) == m_events_targets.end(), "object already in events targets" ); + m_events_targets.insert( object ); +} + +void Popup::RemoveEventsTarget( UIObject* const object ) { + ASSERT( m_events_targets.find( object ) != m_events_targets.end(), "object not in events targets" ); + m_events_targets.erase( object ); +} + void Popup::PlayOpenSound() { if ( !m_sounds_played.open ) { m_sounds_played.open = true; diff --git a/src/ui/object/Popup.h b/src/ui/object/Popup.h index a08841c0..b7b5b37a 100644 --- a/src/ui/object/Popup.h +++ b/src/ui/object/Popup.h @@ -13,6 +13,7 @@ CLASS( Popup, UIContainer ) void Create() override; void Destroy() override; + void ProcessEvent( event::UIEvent* event ) override; // open or close popup void Open(); @@ -21,6 +22,10 @@ CLASS( Popup, UIContainer ) // return true if ready to close, false if not yet (in that case you'll need to call ClosePopup manually when it's done) virtual bool MaybeClose(); + // process events by other object(s) if not processed by popup + void AddEventsTarget( UIObject* const object ); + void RemoveEventsTarget( UIObject* const object ); + protected: // sometimes it's needed to play sound at different time, i.e. when slidedown starts @@ -45,6 +50,7 @@ CLASS( Popup, UIContainer ) bool m_is_opened = false; + std::unordered_set< UIObject* > m_events_targets = {}; }; } diff --git a/src/ui/object/SimpleButton.cpp b/src/ui/object/SimpleButton.cpp index cdb80280..d2a39ef2 100644 --- a/src/ui/object/SimpleButton.cpp +++ b/src/ui/object/SimpleButton.cpp @@ -3,6 +3,7 @@ #define DOUBLECLICK_MAX_MS 1000 #include "Surface.h" +#include "SoundEffect.h" namespace ui { namespace object { @@ -15,13 +16,19 @@ SimpleButton::SimpleButton( const std::string& class_name ) void SimpleButton::Create() { UIContainer::Create(); - NEW( m_background, ui::object::Surface ); + NEW( m_background, Surface ); m_background->ForwardStyleAttribute( A_TEXTURE ); AddChild( m_background ); + + NEW( m_click_sound, SoundEffect ); + m_click_sound->ForwardStyleAttribute( A_BUTTON_CLICK_SOUND, A_SOUND ); + m_click_sound->ForwardStyleAttribute( A_SOUND_VOLUME ); + AddChild( m_click_sound ); } void SimpleButton::Destroy() { RemoveChild( m_background ); + RemoveChild( m_click_sound ); UIContainer::Destroy(); } @@ -66,6 +73,7 @@ bool SimpleButton::OnMouseUp( const event::event_data_t* data ) { m_doubleclick_timer.SetTimeout( DOUBLECLICK_MAX_MS ); m_maybe_doubleclick = true; } + m_click_sound->Play(); // double click event should go after normal click bool ret = Trigger( event::EV_BUTTON_CLICK, data ); if ( is_double_click ) { diff --git a/src/ui/object/SimpleButton.h b/src/ui/object/SimpleButton.h index e4794dbf..c6eccaf4 100644 --- a/src/ui/object/SimpleButton.h +++ b/src/ui/object/SimpleButton.h @@ -10,6 +10,7 @@ namespace ui { namespace object { class Surface; +class SoundEffect; CLASS( SimpleButton, UIContainer ) @@ -28,6 +29,7 @@ CLASS( SimpleButton, UIContainer ) bool m_is_clicking = false; // mouseup at different position after mousedown is still counted as click, as long as it's inside button Surface* m_background = nullptr; + SoundEffect* m_click_sound = nullptr; private: bool m_maybe_doubleclick = false; diff --git a/src/ui/object/UIContainer.h b/src/ui/object/UIContainer.h index 4f1b7fdb..7072452b 100644 --- a/src/ui/object/UIContainer.h +++ b/src/ui/object/UIContainer.h @@ -32,7 +32,7 @@ CLASS( UIContainer, UIObject ) const coord_t GetPadding() const; void SetOverflow( const overflow_t overflow ) override; - void ProcessEvent( event::UIEvent* event ) override; + virtual void ProcessEvent( event::UIEvent* event ) override; virtual const object_area_t GetInternalObjectArea() override; diff --git a/src/util/String.cpp b/src/util/String.cpp index 421dd020..ce412f03 100644 --- a/src/util/String.cpp +++ b/src/util/String.cpp @@ -63,4 +63,16 @@ const std::string String::ApproximateFloat( const float value ) { return result; } +const std::string String::ToUpperCase( const std::string& s ) { + std::string result = s; + std::transform( s.begin(), s.end(), result.begin(), ::toupper ); + return result; +} + +const std::string String::ToLowerCase( const std::string& s ) { + std::string result = s; + std::transform( s.begin(), s.end(), result.begin(), ::tolower ); + return result; +} + } diff --git a/src/util/String.h b/src/util/String.h index bb2ae380..3acec722 100644 --- a/src/util/String.h +++ b/src/util/String.h @@ -15,6 +15,9 @@ CLASS( String, Util ) static const std::string TrimCopy( const std::string& s ); static const std::string ApproximateFloat( const float value ); + static const std::string ToUpperCase( const std::string& s ); + static const std::string ToLowerCase( const std::string& s ); + }; }