88#include " CIO/CFile.h"
99#include " CIO/CFont.h"
1010#include " CSurface.h"
11+ #include " CIO/CButton.h"
12+ #include " CIO/CTextfield.h"
1113#include " callbacks.h"
1214#include " globals.h"
1315#include " gameData/LandscapeDesc.h"
1618#include < iostream>
1719#include < string>
1820
21+ namespace {
22+ // Width of the elevation rate control panel (text field + gap + arrow + padding)
23+ constexpr int ELEVATION_RATE_PANEL_WIDTH = 67 ;
24+ constexpr int ELEVATION_RATE_PANEL_HEIGHT = 50 ;
25+ constexpr int ELEVATION_RATE_TEXTFIELD_LEFT = 6 ;
26+ constexpr int ELEVATION_RATE_TEXTFIELD_TOP = 20 ;
27+ constexpr int ELEVATION_RATE_TEXTFIELD_GAP = 4 ;
28+ constexpr int ELEVATION_RATE_MIN = 1 ;
29+ constexpr int ELEVATION_RATE_MAX = 20 ;
30+
31+ void elevationRateBtnCallback (int param)
32+ {
33+ auto * map = global::s2 ? global::s2->getMapObj () : nullptr ;
34+ if (!map) return ;
35+ int val = map->getElevationRateTextValue ();
36+ if (param > 0 && val < ELEVATION_RATE_MAX )
37+ val++;
38+ else if (param < 0 && val > ELEVATION_RATE_MIN )
39+ val--;
40+ map->setElevationRateText (std::to_string (val));
41+ }
42+ } // namespace
43+
1944void bobMAP::setName (const std::string& newName)
2045{
2146 name = newName;
@@ -154,6 +179,9 @@ void CMap::constructMap(const boost::filesystem::path& filepath, int width, int
154179 MaxRaiseHeight = 0x3C ;
155180 MinReduceHeight = 0x00 ;
156181 saveCurrentVertices = false ;
182+ lastModifyTime_ = 0 ;
183+ elevationRate_ = 10 ;
184+ showElevationRateControl_ = false ;
157185 undoBuffer.clear ();
158186 redoBuffer.clear ();
159187
@@ -508,6 +536,41 @@ void CMap::setMouseData(const SDL_MouseMotionEvent& motion)
508536 }
509537
510538 storeVerticesFromMouse (motion.x , motion.y , motion.state );
539+
540+ // Hover detection for elevation rate control
541+ // Only the icon itself (37x32) triggers opening.
542+ // Once open, the panel area keeps it open so the cursor can reach the arrows.
543+ const Point32 ds (displayRect.getSize ());
544+ const int iconLeft = ds.x / 2 - 236 ;
545+ const int iconRight = ds.x / 2 - 199 ;
546+ const int iconTop = ds.y - 36 ;
547+ const int iconBot = ds.y - 3 ;
548+ const bool overIcon = motion.x >= iconLeft && motion.x < iconRight
549+ && motion.y >= iconTop && motion.y < iconBot;
550+ const int panelLeft = iconLeft;
551+ const int panelRight = iconLeft + ELEVATION_RATE_PANEL_WIDTH ;
552+ const int panelTop = ds.y - 36 - ELEVATION_RATE_PANEL_HEIGHT - 2 ;
553+ const bool overPanel = showElevationRateControl_
554+ && motion.x >= panelLeft && motion.x < panelRight
555+ && motion.y >= panelTop && motion.y < iconTop;
556+ bool wasShowing = showElevationRateControl_;
557+ showElevationRateControl_ = overIcon || overPanel;
558+ // When panel closes, commit pending edit and destroy controls
559+ if (wasShowing && !showElevationRateControl_)
560+ {
561+ if (elevationRateTextfield_ && elevationRateTextfield_->isActive ())
562+ commitElevationRateText ();
563+ elevationRateTextfield_.reset ();
564+ elevationRateUpBtn_.reset ();
565+ elevationRateDownBtn_.reset ();
566+ }
567+
568+ // Forward motion events to arrow buttons (for hover highlighting)
569+ if (showElevationRateControl_ && elevationRateUpBtn_ && elevationRateDownBtn_)
570+ {
571+ elevationRateUpBtn_->setMouseData (motion);
572+ elevationRateDownBtn_->setMouseData (motion);
573+ }
511574}
512575
513576void CMap::onLeftMouseDown (const Point32& pos)
@@ -613,6 +676,17 @@ void CMap::onLeftMouseDown(const Point32& pos)
613676 {
614677 // the cursor picture was clicked
615678 callback::EditorCursorMenu (INITIALIZING_CALL );
679+ }
680+ // Elevation rate control: panel click sets height mode (arrows handled via callbacks)
681+ else if (showElevationRateControl_)
682+ {
683+ const int panelX = displaySize.x / 2 - 236 ;
684+ const int panelY = displaySize.y - 36 - ELEVATION_RATE_PANEL_HEIGHT - 2 ;
685+ if (pos.x >= panelX && pos.x < panelX + ELEVATION_RATE_PANEL_WIDTH
686+ && pos.y >= panelY && pos.y < panelY + ELEVATION_RATE_PANEL_HEIGHT )
687+ {
688+ mode = EDITOR_MODE_HEIGHT_RAISE ;
689+ }
616690 } else
617691 {
618692 // no picture was clicked
@@ -624,6 +698,18 @@ void CMap::onLeftMouseDown(const Point32& pos)
624698
625699void CMap::setMouseData (const SDL_MouseButtonEvent& button)
626700{
701+ // Forward mouse events to the elevation rate controls when the panel is shown
702+ if (showElevationRateControl_)
703+ {
704+ if (elevationRateTextfield_)
705+ elevationRateTextfield_->setMouseData (button);
706+ // Forward button events to arrow buttons (for press/release visual feedback)
707+ if (elevationRateUpBtn_)
708+ elevationRateUpBtn_->setMouseData (button);
709+ if (elevationRateDownBtn_)
710+ elevationRateDownBtn_->setMouseData (button);
711+ }
712+
627713 if (button.state == SDL_PRESSED )
628714 {
629715 if (button.button == SDL_BUTTON_LEFT )
@@ -692,6 +778,20 @@ void restoreVertex(const SavedVertex& vertex, bobMAP& map)
692778
693779void CMap::setKeyboardData (const SDL_KeyboardEvent& key)
694780{
781+ // Forward keyboard events to the elevation rate text field when active
782+ if (showElevationRateControl_ && elevationRateTextfield_ && elevationRateTextfield_->isActive ())
783+ {
784+ // Commit on Enter and close the panel
785+ if (key.type == SDL_KEYDOWN && (key.keysym .sym == SDLK_RETURN || key.keysym .sym == SDLK_KP_ENTER ))
786+ {
787+ commitElevationRateText ();
788+ showElevationRateControl_ = false ;
789+ return ;
790+ }
791+ elevationRateTextfield_->setKeyboardData (key);
792+ return ;
793+ }
794+
695795 if (key.type == SDL_KEYDOWN )
696796 {
697797 switch (key.keysym .sym )
@@ -1089,9 +1189,17 @@ void CMap::render()
10891189 // clear the surface before drawing new (in normal case not needed)
10901190 // SDL_FillRect( Surf_Map, nullptr, SDL_MapRGB(Surf_Map->format,0,0,0) );
10911191
1092- // touch vertex data if user modifies it
1192+ // touch vertex data if user modifies it (rate-limited)
10931193 if (modify)
1094- modifyVertex ();
1194+ {
1195+ Uint32 now = SDL_GetTicks ();
1196+ Uint32 interval = 1000 / std::max (elevationRate_, 1 );
1197+ if (now - lastModifyTime_ >= interval)
1198+ {
1199+ modifyVertex ();
1200+ lastModifyTime_ = now;
1201+ }
1202+ }
10951203
10961204 if (!map->vertex .empty ())
10971205 CSurface::DrawTriangleField (Surf_Map.get (), displayRect, *map);
@@ -1279,6 +1387,98 @@ void CMap::render()
12791387 CFont::writeText (Surf_Map, " Save" , rightMenubarPos.x - 35 , rightMenubarPos.y + 231 );
12801388}
12811389
1390+ void CMap::drawElevationRateControl (SDL_Surface* target)
1391+ {
1392+ if (!showElevationRateControl_)
1393+ return ;
1394+
1395+ const Point32 ds (displayRect.getSize ());
1396+ const Position menubarPos = Position (ds.x / 2 , ds.y );
1397+
1398+ // Panel positioned above the elevation icon (first icon in bottom menu)
1399+ const int panelX = menubarPos.x - 236 ;
1400+ const int panelY = menubarPos.y - 36 - ELEVATION_RATE_PANEL_HEIGHT - 2 ; // 2px gap above icon
1401+
1402+ // Lazy-create controls (persist across frames, destroyed on close)
1403+ constexpr int arrowW = 16 , arrowH = 11 ;
1404+ if (!elevationRateTextfield_)
1405+ {
1406+ elevationRateTextfield_ = std::make_unique<CTextfield>(0 , 0 , 2 , 1 , FontSize::Large, FontColor::Yellow, -1 , false );
1407+ elevationRateTextfield_->setText (std::to_string (elevationRate_));
1408+ elevationRateUpBtn_ = std::make_unique<CButton>(elevationRateBtnCallback, 1 , 0 , 0 , arrowW, arrowH, BUTTON_GREY , nullptr , PICTURE_SMALL_ARROW_UP );
1409+ elevationRateDownBtn_ = std::make_unique<CButton>(elevationRateBtnCallback, -1 , 0 , 0 , arrowW, arrowH, BUTTON_GREY , nullptr , PICTURE_SMALL_ARROW_DOWN );
1410+ // Set positions once (they don't change while panel is open)
1411+ const int tfX = panelX + ELEVATION_RATE_TEXTFIELD_LEFT ;
1412+ const int tfY = panelY + ELEVATION_RATE_TEXTFIELD_TOP ;
1413+ elevationRateTextfield_->setX (tfX);
1414+ elevationRateTextfield_->setY (tfY);
1415+ const int arrowsX = tfX + elevationRateTextfield_->getW () + ELEVATION_RATE_TEXTFIELD_GAP ;
1416+ elevationRateUpBtn_->setX (arrowsX);
1417+ elevationRateUpBtn_->setY (tfY);
1418+ elevationRateDownBtn_->setX (arrowsX);
1419+ elevationRateDownBtn_->setY (tfY + arrowH);
1420+ }
1421+ // Positions are stable after creation
1422+ const int tfX = panelX + ELEVATION_RATE_TEXTFIELD_LEFT ;
1423+ const int tfY = panelY + ELEVATION_RATE_TEXTFIELD_TOP ;
1424+ const int arrowsX = tfX + elevationRateTextfield_->getW () + ELEVATION_RATE_TEXTFIELD_GAP ;
1425+
1426+ // Calculate panel width to fit content
1427+ // Draw panel background (solid dark rect matching tooltip style)
1428+ SDL_Rect bgRect = { panelX, panelY, ELEVATION_RATE_PANEL_WIDTH , ELEVATION_RATE_PANEL_HEIGHT };
1429+ SDL_FillRect (target, &bgRect, SDL_MapRGB (target->format , 20 , 20 , 20 ));
1430+ // Draw a subtle border
1431+ SDL_Rect borderRect = { panelX, panelY, ELEVATION_RATE_PANEL_WIDTH , 1 };
1432+ SDL_FillRect (target, &borderRect, SDL_MapRGB (target->format , 60 , 60 , 60 ));
1433+ borderRect = { panelX, panelY + ELEVATION_RATE_PANEL_HEIGHT - 1 , ELEVATION_RATE_PANEL_WIDTH , 1 };
1434+ SDL_FillRect (target, &borderRect, SDL_MapRGB (target->format , 60 , 60 , 60 ));
1435+
1436+ // Draw "Rate:" label (above the text field)
1437+ CFont::writeText (target, " Rate:" , panelX + 4 , panelY + 4 , FontSize::Small, FontColor::Yellow);
1438+
1439+ // Blit the CTextfield surface onto the panel
1440+ CSurface::Draw (target, elevationRateTextfield_->getSurface (), tfX, tfY);
1441+
1442+ // Blit the arrow button surfaces (they render themselves with proper button look)
1443+ CSurface::Draw (target, elevationRateUpBtn_->getSurface (), arrowsX, tfY);
1444+ CSurface::Draw (target, elevationRateDownBtn_->getSurface (), arrowsX, tfY + arrowH);
1445+ }
1446+
1447+ int CMap::getElevationRateTextValue () const
1448+ {
1449+ if (!elevationRateTextfield_)
1450+ return elevationRate_;
1451+ std::string text = elevationRateTextfield_->getText ();
1452+ if (text.empty ())
1453+ return elevationRate_;
1454+ try { return std::stoi (text); }
1455+ catch (...) { return elevationRate_; }
1456+ }
1457+
1458+ void CMap::setElevationRateText (const std::string& text)
1459+ {
1460+ if (elevationRateTextfield_)
1461+ elevationRateTextfield_->setText (text);
1462+ }
1463+
1464+ void CMap::commitElevationRateText ()
1465+ {
1466+ if (!elevationRateTextfield_)
1467+ return ;
1468+ std::string text = elevationRateTextfield_->getText ();
1469+ if (!text.empty ())
1470+ {
1471+ try
1472+ {
1473+ int val = std::stoi (text);
1474+ elevationRate_ = std::max (ELEVATION_RATE_MIN , std::min (ELEVATION_RATE_MAX , val));
1475+ }
1476+ catch (...) {}
1477+ }
1478+ elevationRateTextfield_->setText (std::to_string (elevationRate_));
1479+ elevationRateTextfield_->setInactive ();
1480+ }
1481+
12821482static void getTriangleColor (TriangleTerrainType terrainType, MapType mapType, Sint16& r, Sint16& g, Sint16& b)
12831483{
12841484 switch (terrainType)
0 commit comments