diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index ab89b579e8..5bebd3d8f2 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -41,7 +41,7 @@ jobs: with: submodules: true - name: Install boost - uses: MarkusJx/install-boost@v2.4.5 + uses: MarkusJx/install-boost@v2 id: install-boost with: boost_version: ${{env.BOOST_VERSION}} diff --git a/extras/videoDrivers/SDL2/VideoSDL2.cpp b/extras/videoDrivers/SDL2/VideoSDL2.cpp index c8763848c2..210eac5324 100644 --- a/extras/videoDrivers/SDL2/VideoSDL2.cpp +++ b/extras/videoDrivers/SDL2/VideoSDL2.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -404,9 +404,16 @@ bool VideoSDL2::MessageLoop() } break; case SDL_MOUSEMOTION: - mouse_xy.pos = getGuiScale().screenToView(Position(ev.motion.x, ev.motion.y)); - CallBack->Msg_MouseMove(mouse_xy); - break; + { + const auto newPos = getGuiScale().screenToView(Position(ev.motion.x, ev.motion.y)); + // Avoid duplicate events especially when warping the mouse + if(newPos != mouse_xy.pos) + { + mouse_xy.pos = newPos; + CallBack->Msg_MouseMove(mouse_xy); + } + } + break; } } diff --git a/libs/s25main/Window.cpp b/libs/s25main/Window.cpp index 631dee92f0..00dbc81c2a 100644 --- a/libs/s25main/Window.cpp +++ b/libs/s25main/Window.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2024 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -564,3 +564,13 @@ bool Window::IsInLockedRegion(const Position& pos, const Window* exception) cons } return false; } + +bool Window::IsMouseOver() const +{ + return IsMouseOver(VIDEODRIVER.GetMousePos()); +} + +bool Window::IsMouseOver(const MouseCoords& mousePos) const +{ + return IsPointInRect(mousePos.GetPos(), GetBoundaryRect()); +} diff --git a/libs/s25main/Window.h b/libs/s25main/Window.h index ad466a7293..c595ccb433 100644 --- a/libs/s25main/Window.h +++ b/libs/s25main/Window.h @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -98,8 +98,12 @@ class Window void LockRegion(Window* window, const Rect& rect); /// Gibt eine gesperrte Region wieder frei. void FreeRegion(Window* window); - /// Check if the gicen point is in a region locked by any window other than exception + /// Check if the given point is in a region locked by any window other than exception bool IsInLockedRegion(const Position& pos, const Window* exception = nullptr) const; + /// Check if the mouse is hovering over this control, i.e. inside its boundary. + bool IsMouseOver() const; + /// Check if the given mouse position inside the boundary of this control. + bool IsMouseOver(const MouseCoords& mousePos) const; /// Set the position for the window void SetPos(const DrawPoint& newPos); diff --git a/libs/s25main/WindowManager.h b/libs/s25main/WindowManager.h index 565cd06e97..9a43186210 100644 --- a/libs/s25main/WindowManager.h +++ b/libs/s25main/WindowManager.h @@ -102,7 +102,10 @@ class WindowManager : public Singleton, public VideoDriverLoaderI void Msg_MouseMove(const MouseCoords& mc) override; /// Verarbeitung Keyboard-Event void Msg_KeyDown(const KeyEvent& ke) override; - // setzt den Tooltip + // Show a tooltip + // ttw: Window that the tooltip is for, used when updating current tooltip + // tooltip: The tooltip text, empty to hide + // updateCurrent: If true, only update if the current tooltip is for ttw void SetToolTip(const ctrlBaseTooltip* ttw, const std::string& tooltip, bool updateCurrent = false); /// Verarbeitung Spielfenstergröße verändert (vom Betriebssystem aus) diff --git a/libs/s25main/controls/ctrlBaseTooltip.h b/libs/s25main/controls/ctrlBaseTooltip.h index dd67989142..23330bcd51 100644 --- a/libs/s25main/controls/ctrlBaseTooltip.h +++ b/libs/s25main/controls/ctrlBaseTooltip.h @@ -14,6 +14,7 @@ class ctrlBaseTooltip ctrlBaseTooltip(std::string tooltip = "") : tooltip_(std::move(tooltip)) {} virtual ~ctrlBaseTooltip(); + /// Set the text to be shown, updates the tooltip if currently shown void SetTooltip(const std::string& tooltip); const std::string& GetTooltip() const { return tooltip_; } /// Swap the tooltips of those controls diff --git a/libs/s25main/controls/ctrlButton.cpp b/libs/s25main/controls/ctrlButton.cpp index 6b4c7e88e9..b07c65393c 100644 --- a/libs/s25main/controls/ctrlButton.cpp +++ b/libs/s25main/controls/ctrlButton.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -26,13 +26,13 @@ void ctrlButton::SetActive(bool activate) Window::SetActive(activate); if(!activate) state = ButtonState::Up; - else if(IsMouseOver(VIDEODRIVER.GetMousePos())) + else if(IsMouseOver()) state = ButtonState::Hover; } bool ctrlButton::Msg_MouseMove(const MouseCoords& mc) { - if(isEnabled && IsMouseOver(mc.GetPos())) + if(isEnabled && IsMouseOver(mc)) { if(state != ButtonState::Pressed) state = ButtonState::Hover; @@ -47,14 +47,9 @@ bool ctrlButton::Msg_MouseMove(const MouseCoords& mc) } } -bool ctrlButton::IsMouseOver(const Position& mousePos) const -{ - return IsPointInRect(mousePos, GetDrawRect()); -} - bool ctrlButton::Msg_LeftDown(const MouseCoords& mc) { - if(isEnabled && IsMouseOver(mc.GetPos())) + if(isEnabled && IsMouseOver(mc)) { state = ButtonState::Pressed; return true; @@ -67,7 +62,7 @@ bool ctrlButton::Msg_LeftUp(const MouseCoords& mc) { if(state == ButtonState::Pressed) { - if(isEnabled && IsMouseOver(mc.GetPos())) + if(isEnabled && IsMouseOver(mc)) { state = ButtonState::Hover; GetParent()->Msg_ButtonClick(GetID()); diff --git a/libs/s25main/controls/ctrlButton.h b/libs/s25main/controls/ctrlButton.h index 2420ee7d5a..bc70835e5c 100644 --- a/libs/s25main/controls/ctrlButton.h +++ b/libs/s25main/controls/ctrlButton.h @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -41,7 +41,6 @@ class ctrlButton : public Window, public ctrlBaseTooltip void Draw_() override; /// Abgeleitete Klassen müssen erweiterten Button-Inhalt zeichnen virtual void DrawContent() const = 0; - bool IsMouseOver(const Position& mousePos) const; /// Texturfarbe des Buttons TextureColor tc; diff --git a/libs/s25main/controls/ctrlCheck.cpp b/libs/s25main/controls/ctrlCheck.cpp index 69c32829ce..9308ce9762 100644 --- a/libs/s25main/controls/ctrlCheck.cpp +++ b/libs/s25main/controls/ctrlCheck.cpp @@ -17,14 +17,6 @@ ctrlCheck::ctrlCheck(Window* parent, unsigned id, const DrawPoint& pos, const Ex : Window(parent, id, pos, size), tc(tc), text(std::move(text)), font(font), check(false), readonly(readonly) {} -/** - * der Messagehandler. - * - * @param[in] msg Die Nachricht. - * @param[in] id Die ID des Quellsteuerelements. - * @param[in] param Ein nachrichtenspezifischer Parameter. - */ - bool ctrlCheck::Msg_LeftDown(const MouseCoords& mc) { if(!readonly && IsPointInRect(mc.GetPos(), GetDrawRect())) @@ -37,9 +29,19 @@ bool ctrlCheck::Msg_LeftDown(const MouseCoords& mc) return false; } -/** - * zeichnet das Fenster. - */ +bool ctrlCheck::Msg_MouseMove(const MouseCoords& mc) +{ + if(IsMouseOver(mc.GetPos())) + { + ShowTooltip(); + return true; + } else + { + HideTooltip(); + return false; + } +} + void ctrlCheck::Draw_() { const unsigned short boxSize = 20; diff --git a/libs/s25main/controls/ctrlCheck.h b/libs/s25main/controls/ctrlCheck.h index d7ee434291..5c01fb631c 100644 --- a/libs/s25main/controls/ctrlCheck.h +++ b/libs/s25main/controls/ctrlCheck.h @@ -1,25 +1,36 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "Window.h" +#include "ctrlBaseTooltip.h" +#include class MouseCoords; class glFont; -class ctrlCheck : public Window +class ctrlCheck : public Window, public ctrlBaseTooltip { public: ctrlCheck(Window* parent, unsigned id, const DrawPoint& pos, const Extent& size, TextureColor tc, std::string text, const glFont* font, bool readonly); - void setChecked(bool checked) { this->check = checked; } + ctrlCheck* setChecked(bool checked) + { + this->check = checked; + return this; + } bool isChecked() const { return check; } - void setReadOnly(bool readonly) { this->readonly = readonly; } + ctrlCheck* setReadOnly(bool readonly) + { + this->readonly = readonly; + return this; + } bool isReadOnly() const { return readonly; } bool Msg_LeftDown(const MouseCoords& mc) override; + bool Msg_MouseMove(const MouseCoords& mc) override; protected: void Draw_() override; diff --git a/libs/s25main/controls/ctrlImage.cpp b/libs/s25main/controls/ctrlImage.cpp index 8b9cefe6b6..3de64e48eb 100644 --- a/libs/s25main/controls/ctrlImage.cpp +++ b/libs/s25main/controls/ctrlImage.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -12,9 +12,6 @@ ctrlImage::ctrlImage(Window* parent, unsigned id, const DrawPoint& pos, ITexture ctrlImage::~ctrlImage() = default; -/** - * zeichnet das Fenster. - */ void ctrlImage::Draw_() { DrawImage(Rect(GetDrawPos(), GetImageRect().getSize())); @@ -22,11 +19,9 @@ void ctrlImage::Draw_() bool ctrlImage::Msg_MouseMove(const MouseCoords& mc) { - // gültiges Bild? if(GetImage()) { - // Jeweils Tooltip ein- und ausblenden, wenn die Maus über dem Bild ist - if(IsPointInRect(mc.GetPos(), Rect::move(GetImageRect(), GetDrawPos()))) + if(IsMouseOver(mc.GetPos())) ShowTooltip(); else HideTooltip(); @@ -34,3 +29,8 @@ bool ctrlImage::Msg_MouseMove(const MouseCoords& mc) return false; } + +Rect ctrlImage::GetBoundaryRect() const +{ + return Rect::move(GetImageRect(), GetDrawPos()); +} diff --git a/libs/s25main/controls/ctrlImage.h b/libs/s25main/controls/ctrlImage.h index db30d512fd..1cce64b8ba 100644 --- a/libs/s25main/controls/ctrlImage.h +++ b/libs/s25main/controls/ctrlImage.h @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -18,6 +18,7 @@ class ctrlImage : public Window, public ctrlBaseTooltip, public ctrlBaseImage ~ctrlImage() override; bool Msg_MouseMove(const MouseCoords& mc) override; + Rect GetBoundaryRect() const; protected: void Draw_() override; diff --git a/libs/s25main/controls/ctrlMapSelection.cpp b/libs/s25main/controls/ctrlMapSelection.cpp index 126854cdf6..e742595e4c 100644 --- a/libs/s25main/controls/ctrlMapSelection.cpp +++ b/libs/s25main/controls/ctrlMapSelection.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2024 - 2025 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -119,7 +119,7 @@ void ctrlMapSelection::setPreview(bool previewOnly) bool ctrlMapSelection::Msg_LeftUp(const MouseCoords& mc) { - if(!preview && IsMouseOver(mc.GetPos())) + if(!preview && IsMouseOver(mc)) { const auto pickPos = invertScale(mc.GetPos() - getMapPosition()); @@ -141,11 +141,6 @@ bool ctrlMapSelection::Msg_LeftUp(const MouseCoords& mc) return false; } -bool ctrlMapSelection::IsMouseOver(const Position& mousePos) const -{ - return IsPointInRect(mousePos, GetDrawRect()); -} - float ctrlMapSelection::getScaleFactor() { const auto ratio = PointF(GetSize()) / mapImages.background->GetSize(); diff --git a/libs/s25main/controls/ctrlMapSelection.h b/libs/s25main/controls/ctrlMapSelection.h index 35e7b3b3c8..1abb86c36e 100644 --- a/libs/s25main/controls/ctrlMapSelection.h +++ b/libs/s25main/controls/ctrlMapSelection.h @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2024 - 2025 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -35,7 +35,6 @@ class ctrlMapSelection : public Window bool Msg_LeftUp(const MouseCoords& mc) override; protected: - bool IsMouseOver(const Position& mousePos) const; void Draw_() override; void updateEnabledMask(); diff --git a/libs/s25main/desktops/dskMainMenu.cpp b/libs/s25main/desktops/dskMainMenu.cpp index 2f886ff5b1..c66811b419 100644 --- a/libs/s25main/desktops/dskMainMenu.cpp +++ b/libs/s25main/desktops/dskMainMenu.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -36,22 +36,15 @@ dskMainMenu::dskMainMenu() { RTTR_Assert(dskMenuBase::ID_FIRST_FREE <= 3); - // "Einzelspieler" AddTextButton(ID_btSingleplayer, DrawPoint(115, 180), Extent(220, 22), TextureColor::Green2, _("Singleplayer"), NormalFont); - // "Mehrspieler" AddTextButton(ID_btMultiplayer, DrawPoint(115, 210), Extent(220, 22), TextureColor::Green2, _("Multiplayer"), NormalFont); - // "Optionen" AddTextButton(ID_btOptions, DrawPoint(115, 250), Extent(220, 22), TextureColor::Green2, _("Options"), NormalFont); - // "Intro" AddTextButton(ID_btIntro, DrawPoint(115, 280), Extent(220, 22), TextureColor::Green2, _("Intro"), NormalFont) ->SetEnabled(false); - // "ReadMe" AddTextButton(ID_btReadme, DrawPoint(115, 310), Extent(220, 22), TextureColor::Green2, _("Readme"), NormalFont); - // "Credits" AddTextButton(ID_btCredits, DrawPoint(115, 340), Extent(220, 22), TextureColor::Green2, _("Credits"), NormalFont); - // "Programm verlassen" AddTextButton(ID_btQuit, DrawPoint(115, 390), Extent(220, 22), TextureColor::Red1, _("Quit program"), NormalFont); AddImage(ID_logo, DrawPoint(20, 20), LOADER.GetImageN("logo", 0)); diff --git a/libs/s25main/ingameWindows/iwSettings.cpp b/libs/s25main/ingameWindows/iwSettings.cpp index b98d9a91ca..fa73dc5c7a 100644 --- a/libs/s25main/ingameWindows/iwSettings.cpp +++ b/libs/s25main/ingameWindows/iwSettings.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -23,6 +23,7 @@ enum ID_grpFullscreen, ID_cbResolution, ID_cbInvertMouse, + ID_cbSmartCursor, ID_cbStatisticScale, }; constexpr auto ID_btOn = 1; @@ -30,7 +31,7 @@ constexpr auto ID_btOff = 0; } // namespace iwSettings::iwSettings() - : IngameWindow(CGI_SETTINGS, IngameWindow::posLastOrCenter, Extent(370, 199), _("Settings"), + : IngameWindow(CGI_SETTINGS, IngameWindow::posLastOrCenter, Extent(370, 228), _("Settings"), LOADER.GetImageN("resource", 41)) { // Controls are in 2 columns, the left might be the label for the control on the right @@ -72,6 +73,10 @@ iwSettings::iwSettings() AddCheckBox(ID_cbInvertMouse, curPos, cbSize, TextureColor::Grey, _("Invert Mouse Pan"), NormalFont, false) ->setChecked(SETTINGS.interface.invertMouse); curPos.y += cbSize.y + 3; + AddCheckBox(ID_cbSmartCursor, curPos, cbSize, TextureColor::Grey, _("Smart cursor placement"), NormalFont, false) + ->setChecked(SETTINGS.global.smartCursor) + ->SetTooltip(_("Place cursor on default button for new dialogs / action windows (default)")); + curPos.y += cbSize.y + 3; AddCheckBox(ID_cbStatisticScale, curPos, cbSize, TextureColor::Grey, _("Statistics Scale"), NormalFont, false) ->setChecked(SETTINGS.ingame.scale_statistics); } @@ -114,6 +119,10 @@ void iwSettings::Msg_CheckboxChange(const unsigned ctrl_id, const bool checked) switch(ctrl_id) { case ID_cbInvertMouse: SETTINGS.interface.invertMouse = checked; break; + case ID_cbSmartCursor: + SETTINGS.global.smartCursor = checked; + VIDEODRIVER.SetMouseWarping(checked); + break; case ID_cbStatisticScale: SETTINGS.ingame.scale_statistics = checked; break; } } diff --git a/tests/s25Main/UI/testControls.cpp b/tests/s25Main/UI/testControls.cpp index 74599ec2c9..4009eab5d3 100644 --- a/tests/s25Main/UI/testControls.cpp +++ b/tests/s25Main/UI/testControls.cpp @@ -14,6 +14,7 @@ #include "driver/KeyEvent.h" #include "driver/MouseCoords.h" #include "helpers/mathFuncs.h" +#include "mockupDrivers/MockupVideoDriver.h" #include "ogl/glFont.h" #include "uiHelper/uiHelpers.hpp" #include "libsiedler2/ArchivItem_Bitmap_Player.h" @@ -27,6 +28,7 @@ #include #include #include +#include #include // LCOV_EXCL_START @@ -54,6 +56,26 @@ static std::unique_ptr createMockFont(const std::vector& chars BOOST_AUTO_TEST_SUITE(Controls) +BOOST_FIXTURE_TEST_CASE(MouseOver, uiHelper::Fixture) +{ + const auto pos = rttr::test::randomPoint(0, std::numeric_limits::max() / 2); + const auto size = rttr::test::randomPoint(10, std::numeric_limits::max() / 2); + const auto font = createMockFont({'H', 'e', 'l', 'o', '?'}); + ctrlTextButton bt(nullptr, 1, pos, size, TextureColor::Bricks, "Hello", font.get(), ""); + BOOST_TEST(bt.IsMouseOver(pos)); + BOOST_TEST(!bt.IsMouseOver(pos - DrawPoint(1, 0))); + BOOST_TEST(!bt.IsMouseOver(pos - DrawPoint(0, 1))); + BOOST_TEST(bt.IsMouseOver(pos + size / 2u)); + BOOST_TEST(bt.IsMouseOver(pos + size - DrawPoint(1, 1))); + BOOST_TEST(!bt.IsMouseOver(pos + size)); + BOOST_TEST(!bt.IsMouseOver(pos + size + DrawPoint(0, 1))); + // Without argument asks the video driver for the mouse position + uiHelper::GetVideoDriver()->SetMousePos(pos + size / 2u); + BOOST_TEST(bt.IsMouseOver()); + uiHelper::GetVideoDriver()->SetMousePos(pos + size * 2u); + BOOST_TEST(!bt.IsMouseOver()); +} + static void resizeMap(libsiedler2::ArchivItem_Map& glMap, const Extent& size) { libsiedler2::ArchivItem_Map map;