Skip to content

Commit c49d2b6

Browse files
Elevation rate limit
1 parent 4a2d0df commit c49d2b6

3 files changed

Lines changed: 220 additions & 2 deletions

File tree

CGame_Render.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ void CGame::Render()
8080
else if(MapObj->isVerticalMovementLocked())
8181
CFont::writeText(Surf_Display, "Vertical movement locked (F10 to unlock)", 20, 40, FontSize::Large,
8282
FontColor::Orange);
83+
84+
// Draw elevation rate control panel (hover above elevation tool icon)
85+
MapObj->drawElevationRateControl(Surf_Display.get());
8386
}
8487

8588
// render active menus

CMap.cpp

Lines changed: 202 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
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"
@@ -16,6 +18,29 @@
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+
1944
void 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

513576
void 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

625699
void 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

693779
void 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+
12821482
static void getTriangleColor(TriangleTerrainType terrainType, MapType mapType, Sint16& r, Sint16& g, Sint16& b)
12831483
{
12841484
switch(terrainType)

CMap.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
#include <optional>
1616
#include <string>
1717

18+
class CButton;
19+
class CTextfield;
20+
1821
struct SavedVertex
1922
{
2023
template<typename T>
@@ -104,6 +107,12 @@ class CMap
104107
bool HorizontalMovementLocked;
105108
bool VerticalMovementLocked;
106109
std::optional<Position> startScrollPos;
110+
Uint32 lastModifyTime_ = 0;
111+
int elevationRate_ = 10;
112+
bool showElevationRateControl_ = false;
113+
std::unique_ptr<CTextfield> elevationRateTextfield_;
114+
std::unique_ptr<CButton> elevationRateUpBtn_;
115+
std::unique_ptr<CButton> elevationRateDownBtn_;
107116

108117
public:
109118
CMap(const boost::filesystem::path& filepath);
@@ -128,6 +137,8 @@ class CMap
128137
int getVertexY() const { return Vertex_.y; }
129138
auto getMaxRaiseHeight() const { return MaxRaiseHeight; }
130139
auto getMinReduceHeight() const { return MinReduceHeight; }
140+
int getElevationRate() const { return elevationRate_; }
141+
void setElevationRate(int rate) { elevationRate_ = rate; }
131142
bool isHorizontalMovementLocked() const { return HorizontalMovementLocked; }
132143
bool isVerticalMovementLocked() const { return VerticalMovementLocked; }
133144
bool getRenderBuildHelp() const { return RenderBuildHelp; }
@@ -161,6 +172,10 @@ class CMap
161172
std::string getAuthor() const { return map->getAuthor(); }
162173
void setAuthor(const std::string& author) { map->setAuthor(author); }
163174

175+
void drawElevationRateControl(SDL_Surface* target);
176+
int getElevationRateTextValue() const;
177+
void setElevationRateText(const std::string& text);
178+
void commitElevationRateText();
164179
void drawMinimap(SDL_Surface* Window);
165180
void render();
166181
// get and set some variables necessary for cursor behavior

0 commit comments

Comments
 (0)