Skip to content

Commit 6da68ac

Browse files
authored
Merge pull request #1823 from Flamefire/zoom-factor
Make sure zooming via mouse goes back to 1.0
2 parents 8dba0ce + 4072745 commit 6da68ac

File tree

7 files changed

+113
-45
lines changed

7 files changed

+113
-45
lines changed

.github/workflows/static-analysis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,6 @@ jobs:
5454
sudo apt-get update && sudo apt-get install -y \
5555
clang-18 clang-tidy-18 \
5656
libsdl2-dev libsdl2-mixer-dev libcurl4-openssl-dev libbz2-dev libminiupnpc-dev liblua5.2-dev gettext \
57-
libboost-filesystem-dev libboost-program-options-dev libboost-thread-dev libboost-test-dev libboost-locale-dev libboost-iostreams-dev
57+
libboost-filesystem-dev libboost-program-options-dev libboost-thread-dev libboost-test-dev libboost-locale-dev libboost-nowide-dev libboost-iostreams-dev
5858
- name: Run Clang-Tidy
5959
run: tools/ci/runClangTidy.sh

libs/s25main/desktops/dskGameInterface.cpp

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org)
1+
// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org)
22
//
33
// SPDX-License-Identifier: GPL-2.0-or-later
44

@@ -97,14 +97,30 @@ enum
9797
ID_btPost,
9898
ID_txtNumMsg
9999
};
100+
101+
float getNextZoomLevel(const float currentZoom)
102+
{
103+
// Get first level bigger than current zoom
104+
// NOLINTNEXTLINE(readability-qualified-auto)
105+
auto it = std::upper_bound(ZOOM_FACTORS.begin(), ZOOM_FACTORS.end(), currentZoom);
106+
return (it == ZOOM_FACTORS.end()) ? ZOOM_FACTORS.front() : *it;
100107
}
101108

109+
float getPreviousZoomLevel(const float currentZoom)
110+
{
111+
// Get last level bigger or equal than current zoom
112+
// NOLINTNEXTLINE(readability-qualified-auto)
113+
auto it = std::lower_bound(ZOOM_FACTORS.begin(), ZOOM_FACTORS.end(), currentZoom);
114+
return (it == ZOOM_FACTORS.begin()) ? ZOOM_FACTORS.back() : *(--it);
115+
}
116+
} // namespace
117+
102118
dskGameInterface::dskGameInterface(std::shared_ptr<Game> game, std::shared_ptr<const NWFInfo> nwfInfo,
103119
unsigned playerIdx, bool initOGL)
104120
: Desktop(nullptr), game_(std::move(game)), nwfInfo_(std::move(nwfInfo)),
105121
worldViewer(playerIdx, const_cast<Game&>(*game_).world_),
106122
gwv(worldViewer, Position(0, 0), VIDEODRIVER.GetRenderSize()), cbb(*LOADER.GetPaletteN("pal5")),
107-
actionwindow(nullptr), roadwindow(nullptr), minimap(worldViewer), isScrolling(false), zoomLvl(ZOOM_DEFAULT_INDEX),
123+
actionwindow(nullptr), roadwindow(nullptr), minimap(worldViewer), isScrolling(false),
108124
cheats_(const_cast<Game&>(*game_).world_), cheatCommandTracker_(cheats_)
109125
{
110126
road.mode = RoadBuildMode::Disabled;
@@ -880,18 +896,13 @@ bool dskGameInterface::Msg_KeyDown(const KeyEvent& ke)
880896
gwv.SetZoomFactor(ZOOM_FACTORS[ZOOM_DEFAULT_INDEX]);
881897
return true;
882898
case 'z': // zoom
883-
if(++zoomLvl >= ZOOM_FACTORS.size())
884-
zoomLvl = 0;
885-
886-
gwv.SetZoomFactor(ZOOM_FACTORS[zoomLvl]);
899+
if(ke.ctrl)
900+
gwv.SetZoomFactor(ZOOM_FACTORS[ZOOM_DEFAULT_INDEX]);
901+
else
902+
gwv.SetZoomFactor(getNextZoomLevel(gwv.GetCurrentTargetZoomFactor()));
887903
return true;
888904
case 'Z': // shift-z, reverse zoom
889-
if(zoomLvl == 0)
890-
zoomLvl = ZOOM_FACTORS.size() - 1;
891-
else
892-
zoomLvl--;
893-
894-
gwv.SetZoomFactor(ZOOM_FACTORS[zoomLvl]);
905+
gwv.SetZoomFactor(getPreviousZoomLevel(gwv.GetCurrentTargetZoomFactor()));
895906
return true;
896907
}
897908

@@ -909,24 +920,14 @@ bool dskGameInterface::Msg_WheelDown(const MouseCoords&)
909920
return true;
910921
}
911922

912-
void dskGameInterface::WheelZoom(float step)
923+
void dskGameInterface::WheelZoom(const float step)
913924
{
914-
float new_zoom = gwv.GetCurrentTargetZoomFactor() * (1 + step);
915-
gwv.SetZoomFactor(new_zoom);
925+
auto targetZoomFactor = gwv.GetCurrentTargetZoomFactor() * (1 + step);
926+
targetZoomFactor = std::clamp(targetZoomFactor, ZOOM_FACTORS.front(), ZOOM_FACTORS.back());
927+
if(targetZoomFactor > 1 - ZOOM_WHEEL_INCREMENT && targetZoomFactor < 1 + ZOOM_WHEEL_INCREMENT)
928+
targetZoomFactor = 1.f; // Snap to 100%
916929

917-
// also keep track in terms of fixed defined zoom levels
918-
zoomLvl = ZOOM_DEFAULT_INDEX;
919-
for(size_t i = ZOOM_DEFAULT_INDEX; i < ZOOM_FACTORS.size(); ++i)
920-
{
921-
if(ZOOM_FACTORS[i] < new_zoom)
922-
zoomLvl = i;
923-
}
924-
925-
for(size_t i = ZOOM_DEFAULT_INDEX; i-- > 0;)
926-
{
927-
if(ZOOM_FACTORS[i] > new_zoom)
928-
zoomLvl = i;
929-
}
930+
gwv.SetZoomFactor(targetZoomFactor);
930931
}
931932

932933
void dskGameInterface::OnBuildingNote(const BuildingNote& note)

libs/s25main/desktops/dskGameInterface.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2005 - 2024 Settlers Freaks (sf-team at siedler25.org)
1+
// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org)
22
//
33
// SPDX-License-Identifier: GPL-2.0-or-later
44

@@ -165,7 +165,6 @@ class dskGameInterface :
165165

166166
bool isScrolling;
167167
Position startScrollPt;
168-
size_t zoomLvl;
169168
Subscription evBld;
170169

171170
Cheats cheats_;

libs/s25main/gameData/GuiConsts.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org)
1+
// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org)
22
//
33
// SPDX-License-Identifier: GPL-2.0-or-later
44

55
#pragma once
66

77
#include <array>
88

9-
const std::array<float, 7> SUPPRESS_UNUSED ZOOM_FACTORS = {{0.5f, 0.75f, 1.f, 1.25f, 1.5f, 2.f, 3.f}};
10-
const size_t ZOOM_DEFAULT_INDEX = 2;
11-
const float ZOOM_ACCELERATION = 0.001f;
12-
const float ZOOM_WHEEL_INCREMENT = 0.03f;
9+
constexpr std::array<float, 7> SUPPRESS_UNUSED ZOOM_FACTORS = {{0.5f, 0.75f, 1.f, 1.25f, 1.5f, 2.f, 3.f}};
10+
constexpr size_t ZOOM_DEFAULT_INDEX = 2;
11+
constexpr float ZOOM_ACCELERATION = 0.001f;
12+
constexpr float ZOOM_WHEEL_INCREMENT = 0.03f;

libs/s25main/world/GameWorldView.cpp

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,20 +88,16 @@ void GameWorldView::SetNextZoomFactor()
8888
CalcFxLx();
8989
}
9090

91-
void GameWorldView::SetZoomFactor(float zoomFactor, bool smoothTransition /* = true*/)
91+
float GameWorldView::SetZoomFactor(float zoomFactor, bool smoothTransition /* = true*/)
9292
{
93-
if(zoomFactor < ZOOM_FACTORS.front())
94-
targetZoomFactor_ = ZOOM_FACTORS.front();
95-
else if(zoomFactor > ZOOM_FACTORS.back())
96-
targetZoomFactor_ = ZOOM_FACTORS.back();
97-
else
98-
targetZoomFactor_ = zoomFactor;
93+
targetZoomFactor_ = zoomFactor;
9994
if(!smoothTransition)
10095
{
10196
zoomFactor_ = targetZoomFactor_;
10297
updateEffectiveZoomFactor();
10398
CalcFxLx();
10499
}
100+
return targetZoomFactor_;
105101
}
106102

107103
float GameWorldView::GetCurrentTargetZoomFactor() const

libs/s25main/world/GameWorldView.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ class GameWorldView
7878
Position GetPos() const { return origin_; }
7979
Extent GetSize() const { return size_; }
8080

81-
void SetZoomFactor(float zoomFactor, bool smoothTransition = true);
81+
/// Set target zoom factor and start zooming if smoothTransition is true
82+
/// Returns actual zoom factor used, potentially clamped
83+
float SetZoomFactor(float zoomFactor, bool smoothTransition = true);
8284
float GetCurrentTargetZoomFactor() const;
8385
void SetNextZoomFactor();
8486

tests/s25Main/integration/testDskGameInterface.cpp

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org)
1+
// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org)
22
//
33
// SPDX-License-Identifier: GPL-2.0-or-later
44

@@ -14,6 +14,8 @@
1414
#include "uiHelper/uiHelpers.hpp"
1515
#include "worldFixtures/CreateEmptyWorld.h"
1616
#include "worldFixtures/WorldFixture.h"
17+
#include "gameData/GuiConsts.h"
18+
#include "rttr/test/random.hpp"
1719
#include <boost/test/unit_test.hpp>
1820

1921
// LCOV_EXCL_START
@@ -36,6 +38,9 @@ struct dskGameInterfaceMock : public dskGameInterface
3638
void Msg_PaintBefore() override {}
3739
void Msg_PaintAfter() override {}
3840
using dskGameInterface::actionwindow;
41+
using dskGameInterface::Msg_KeyDown;
42+
using dskGameInterface::Msg_WheelDown;
43+
using dskGameInterface::Msg_WheelUp;
3944
};
4045
struct GameInterfaceFixture : uiHelper::Fixture
4146
{
@@ -227,4 +232,69 @@ BOOST_FIXTURE_TEST_CASE(IwActionClose, GameInterfaceFixture)
227232
BOOST_TEST_REQUIRE(gameDesktop->actionwindow == nullptr);
228233
}
229234

235+
BOOST_FIXTURE_TEST_CASE(Zooming, GameInterfaceFixture)
236+
{
237+
const KeyEvent zoomInEv('z');
238+
const KeyEvent zoomOutEv('Z');
239+
KeyEvent zoomDefaultEv('z');
240+
zoomDefaultEv.ctrl = true;
241+
242+
BOOST_TEST(view->GetCurrentTargetZoomFactor() == ZOOM_FACTORS[ZOOM_DEFAULT_INDEX]);
243+
for(size_t i = ZOOM_DEFAULT_INDEX + 1; i < ZOOM_FACTORS.size(); i++)
244+
{
245+
gameDesktop->Msg_KeyDown(zoomInEv);
246+
BOOST_TEST(view->GetCurrentTargetZoomFactor() == ZOOM_FACTORS[i]);
247+
}
248+
// Wrap around
249+
gameDesktop->Msg_KeyDown(zoomInEv);
250+
BOOST_TEST(view->GetCurrentTargetZoomFactor() == ZOOM_FACTORS[0]);
251+
gameDesktop->Msg_KeyDown(zoomInEv);
252+
BOOST_TEST(view->GetCurrentTargetZoomFactor() == ZOOM_FACTORS[1]);
253+
254+
gameDesktop->Msg_KeyDown(zoomOutEv);
255+
BOOST_TEST(view->GetCurrentTargetZoomFactor() == ZOOM_FACTORS[0]);
256+
gameDesktop->Msg_KeyDown(zoomOutEv);
257+
BOOST_TEST(view->GetCurrentTargetZoomFactor() == ZOOM_FACTORS.back());
258+
259+
gameDesktop->Msg_KeyDown(zoomDefaultEv);
260+
BOOST_TEST(view->GetCurrentTargetZoomFactor() == ZOOM_FACTORS[ZOOM_DEFAULT_INDEX]);
261+
262+
gameDesktop->Msg_WheelDown({});
263+
auto curZoom = view->GetCurrentTargetZoomFactor();
264+
BOOST_TEST(curZoom < ZOOM_FACTORS[ZOOM_DEFAULT_INDEX]);
265+
BOOST_TEST(curZoom > ZOOM_FACTORS[ZOOM_DEFAULT_INDEX - 1]);
266+
// Go to prev index
267+
gameDesktop->Msg_KeyDown(zoomOutEv);
268+
BOOST_TEST(view->GetCurrentTargetZoomFactor() == ZOOM_FACTORS[ZOOM_DEFAULT_INDEX - 1]);
269+
270+
gameDesktop->Msg_KeyDown(zoomDefaultEv);
271+
gameDesktop->Msg_WheelUp({});
272+
curZoom = view->GetCurrentTargetZoomFactor();
273+
BOOST_TEST(curZoom > ZOOM_FACTORS[ZOOM_DEFAULT_INDEX]);
274+
BOOST_TEST(curZoom < ZOOM_FACTORS[ZOOM_DEFAULT_INDEX + 1]);
275+
// Go to next index
276+
gameDesktop->Msg_KeyDown(zoomInEv);
277+
BOOST_TEST(view->GetCurrentTargetZoomFactor() == ZOOM_FACTORS[ZOOM_DEFAULT_INDEX + 1]);
278+
279+
gameDesktop->Msg_WheelDown({});
280+
curZoom = view->GetCurrentTargetZoomFactor();
281+
BOOST_TEST(curZoom > ZOOM_FACTORS[ZOOM_DEFAULT_INDEX]);
282+
BOOST_TEST(curZoom < ZOOM_FACTORS[ZOOM_DEFAULT_INDEX + 1]);
283+
// Go to prev index
284+
gameDesktop->Msg_KeyDown(zoomOutEv);
285+
BOOST_TEST(view->GetCurrentTargetZoomFactor() == ZOOM_FACTORS[ZOOM_DEFAULT_INDEX]);
286+
287+
// Zoom in and out gets to 1.0 (default)
288+
static_assert(ZOOM_FACTORS[ZOOM_DEFAULT_INDEX] == 1.f);
289+
gameDesktop->Msg_WheelUp({});
290+
gameDesktop->Msg_WheelDown({});
291+
BOOST_TEST(view->GetCurrentTargetZoomFactor() == 1.f);
292+
const auto numZoom = rttr::test::randomValue(2, 10);
293+
for(int i = 0; i < numZoom; i++)
294+
gameDesktop->Msg_WheelDown({});
295+
for(int i = 0; i < numZoom; i++)
296+
gameDesktop->Msg_WheelUp({});
297+
BOOST_TEST(view->GetCurrentTargetZoomFactor() == 1.f);
298+
}
299+
230300
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)