• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Return-To-The-Roots / s25client / 20618416952

31 Dec 2025 11:51AM UTC coverage: 50.569% (+0.06%) from 50.506%
20618416952

Pull #1850

github

web-flow
Merge c015ce8be into 109e7720c
Pull Request #1850: Refactor handling of mouse messages

57 of 103 new or added lines in 4 files covered. (55.34%)

14 existing lines in 4 files now uncovered.

22569 of 44630 relevant lines covered (50.57%)

35759.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.39
/libs/s25main/WindowManager.cpp
1
// Copyright (C) 2005 - 2024 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
#include "WindowManager.h"
6
#include "CollisionDetection.h"
7
#include "Loader.h"
8
#include "RttrConfig.h"
9
#include "Settings.h"
10
#include "Window.h"
11
#include "commonDefines.h"
12
#include "desktops/Desktop.h"
13
#include "drivers/ScreenResizeEvent.h"
14
#include "drivers/VideoDriverWrapper.h"
15
#include "files.h"
16
#include "helpers/pointerContainerUtils.h"
17
#include "helpers/reverse.h"
18
#include "ingameWindows/IngameWindow.h"
19
#include "ogl/FontStyle.h"
20
#include "ogl/SoundEffectItem.h"
21
#include "ogl/glFont.h"
22
#include "ogl/saveBitmap.h"
23
#include "gameData/const_gui_ids.h"
24
#include "libsiedler2/PixelBufferBGRA.h"
25
#include "s25util/Log.h"
26
#include "s25util/MyTime.h"
27
#include <algorithm>
28

29
WindowManager::WindowManager()
4✔
30
    : cursor_(Cursor::Hand), disable_mouse(false), lastMousePos(Position::Invalid()), curRenderSize(0, 0),
4✔
31
      lastLeftClickTime(0), lastLeftClickPos(0, 0)
8✔
32
{}
4✔
33

34
WindowManager::~WindowManager() = default;
8✔
35

36
void WindowManager::CleanUp()
1✔
37
{
38
    windows.clear();
1✔
39
    curDesktop.reset();
1✔
40
    nextdesktop.reset();
1✔
41
}
1✔
42

43
void WindowManager::SetCursor(Cursor cursor)
29✔
44
{
45
    cursor_ = cursor;
29✔
46
}
29✔
47

48
void WindowManager::DrawCursor()
80✔
49
{
50
    auto resId = static_cast<unsigned>(cursor_);
80✔
51
    switch(cursor_)
80✔
52
    {
53
        case Cursor::Hand:
79✔
54
        case Cursor::Remove: resId += VIDEODRIVER.IsLeftDown() ? 1 : 0; break;
79✔
55
        default: break;
1✔
56
    }
57
    if(resId)
80✔
58
        LOADER.GetImageN("resource", resId)->DrawFull(VIDEODRIVER.GetMousePos());
160✔
59
}
80✔
60

61
/**
62
 *  Zeichenfunktion WindowManager-Klasse.
63
 *  Zeichnet Desktop und alle Fenster.
64
 */
65
void WindowManager::Draw()
80✔
66
{
67
    // ist ein neuer Desktop eingetragen? Wenn ja, wechseln
68
    if(nextdesktop)
80✔
69
        DoDesktopSwitch();
29✔
70

71
    if(!curDesktop)
80✔
72
        return;
×
73

74
    curDesktop->Msg_PaintBefore();
80✔
75
    curDesktop->Draw();
80✔
76
    curDesktop->Msg_PaintAfter();
80✔
77

78
    // First close all marked windows
79
    CloseMarkedIngameWnds();
80✔
80
    for(auto& wnd : windows)
146✔
81
    {
82
        // If the window is not minimized, call paintAfter
83
        if(!wnd->IsMinimized())
66✔
84
            wnd->Msg_PaintBefore();
66✔
85
        wnd->Draw();
66✔
86
        // If the window is not minimized, call paintAfter
87
        if(!wnd->IsMinimized())
66✔
88
            wnd->Msg_PaintAfter();
66✔
89
    }
90

91
    DrawToolTip();
80✔
92
    DrawCursor();
80✔
93
}
94

95
bool WindowManager::IsDesktopActive() const
66✔
96
{
97
    if(curDesktop)
66✔
98
        return curDesktop->IsActive();
66✔
UNCOV
99
    return false;
×
100
}
101

102
void WindowManager::RelayKeyboardMessage(KeyboardMsgHandler msg, const KeyEvent& ke)
19✔
103
{
104
    // When there is no desktop, don't check it or any window
105
    if(!curDesktop)
19✔
106
        return;
×
107
    if(curDesktop->IsActive())
19✔
108
    {
109
        // Desktop active -> relay msg to desktop
110
        CALL_MEMBER_FN(*curDesktop, msg)(ke);
2✔
111
        curDesktop->RelayKeyboardMessage(msg, ke);
2✔
112
        return;
2✔
113
    }
114

115
    if(windows.empty())
17✔
116
        return; // No windows -> nothing to do
×
117

118
    // ESC or ALT+W closes the active window
119
    const auto escape = (ke.kt == KeyType::Escape);
17✔
120
    if(escape || (ke.c == 'w' && ke.alt))
17✔
121
    {
122
        // Find one which isn't yet marked for closing so multiple ESC in between draw calls can close multiple windows
123
        // ESC doesn't close pinned windows
124
        const auto itActiveWnd = std::find_if(windows.rbegin(), windows.rend(), [escape](const auto& wnd) {
11✔
125
            return !wnd->ShouldBeClosed() && !(escape && wnd->IsPinned());
12✔
126
        });
22✔
127
        if(itActiveWnd != windows.rend() && (*itActiveWnd)->getCloseBehavior() != CloseBehavior::Custom)
11✔
128
            (*itActiveWnd)->Close();
11✔
129
    } else if(!CALL_MEMBER_FN(*windows.back(), msg)(ke)) // send to active window
6✔
130
    {
131
        // If not handled yet, relay to active window
132
        if(!windows.back()->RelayKeyboardMessage(msg, ke))
×
133
        {
134
            // If message was not handled send to desktop
135
            CALL_MEMBER_FN(*curDesktop, msg)(ke);
×
136
            curDesktop->RelayKeyboardMessage(msg, ke);
×
137
        }
138
    }
139
}
140

141
void WindowManager::RelayMouseMessage(MouseMsgHandler msg, const MouseCoords& mc, Window* window)
84✔
142
{
143
    if(!window)
84✔
144
        window = getActiveWindow();
66✔
145
    if(window)
84✔
146
    {
147
        // If no sub-window/control handled the message, let the window itself handle it
148
        if(!window->RelayMouseMessage(msg, mc))
84✔
149
            CALL_MEMBER_FN(*window, msg)(mc);
83✔
150
    }
151
}
84✔
152

153
/**
154
 *  Öffnet ein IngameWindow und fügt es zur Fensterliste hinzu.
155
 */
156
IngameWindow& WindowManager::DoShow(std::unique_ptr<IngameWindow> window, bool mouse)
60✔
157
{
158
    RTTR_Assert(window);
60✔
159
    RTTR_Assert(!helpers::contains(windows, window));
60✔
160
    // No desktop -> Out
161
    if(!curDesktop)
60✔
162
        throw std::runtime_error("No desktop active for window to be shown on");
×
163

164
    SetToolTip(nullptr, "");
60✔
165

166
    // All windows are inserted before the first modal window (shown behind)
167
    auto itModal = helpers::find_if(windows, [](const auto& curWnd) { return curWnd->IsModal(); });
94✔
168
    // Note that if there is no other modal window it will be put at the back which is what we want
169
    auto& result = **windows.emplace(itModal, std::move(window));
60✔
170

171
    // Make the new window active (special cases handled in the function)
172
    SetActiveWindow(result);
60✔
173

174
    // Maus deaktivieren, bis sie losgelassen wurde (Fix des Switch-Anschließend-Drück-Bugs)
175
    disable_mouse = mouse;
60✔
176
    return result;
60✔
177
}
178

179
IngameWindow* WindowManager::ShowAfterSwitch(std::unique_ptr<IngameWindow> window)
1✔
180
{
181
    RTTR_Assert(window);
1✔
182
    nextWnds.emplace_back(std::move(window));
1✔
183
    return nextWnds.back().get();
1✔
184
}
185

186
/**
187
 *  merkt einen Desktop zum Wechsel vor.
188
 *
189
 *  @param[in] desktop       Pointer zum neuen Desktop, auf dem gewechselt werden soll
190
 *  @param[in] data          Daten für den neuen Desktop
191
 */
192
Desktop* WindowManager::Switch(std::unique_ptr<Desktop> desktop)
29✔
193
{
194
    nextdesktop = std::move(desktop);
29✔
195
    // Disable the mouse till the next desktop is shown to avoid e.g. double-switching
196
    disable_mouse = true;
29✔
197
    return nextdesktop.get();
29✔
198
}
199

200
IngameWindow* WindowManager::FindWindowAtPos(const Position& pos) const
10✔
201
{
202
    // Fenster durchgehen ( von hinten nach vorn, da die vordersten ja zuerst geprüft werden müssen !! )
203
    for(const auto& window : helpers::reverse(windows))
10✔
204
    {
205
        // FensterRect für Kollisionsabfrage
206
        Rect window_rect = window->GetDrawRect();
10✔
207

208
        // trifft die Maus auf ein Fenster?
209
        if(IsPointInRect(pos, window_rect))
10✔
210
        {
211
            return window.get();
10✔
212
        }
213
        // Check also if we are in the locked area of a window (e.g. dropdown extends outside of window)
214
        if(window->IsInLockedRegion(pos))
×
215
            return window.get();
×
216
    }
217
    return nullptr;
×
218
}
219

220
IngameWindow* WindowManager::FindNonModalWindow(unsigned id) const
19✔
221
{
222
    auto itWnd = helpers::find_if(
223
      windows, [id](const auto& wnd) { return !wnd->ShouldBeClosed() && !wnd->IsModal() && wnd->GetID() == id; });
46✔
224
    return itWnd == windows.end() ? nullptr : itWnd->get();
19✔
225
}
226

227
Window* WindowManager::findAndActivateWindow(const Position mousePos)
18✔
228
{
229
    Window* activeWindow = nullptr;
18✔
230
    if(!windows.empty())
18✔
231
    {
232
        if(windows.back()->IsModal())
2✔
233
            activeWindow = windows.back().get();
1✔
234
        else
235
            activeWindow = FindWindowAtPos(mousePos);
1✔
236
    }
237
    if(!activeWindow)
18✔
238
        activeWindow = curDesktop.get();
16✔
239
    if(activeWindow && !activeWindow->IsActive())
18✔
NEW
240
        SetActiveWindow(*activeWindow);
×
241
    return activeWindow;
18✔
242
}
243

244
Window* WindowManager::getActiveWindow() const
66✔
245
{
246
    if(IsDesktopActive())
66✔
247
        return curDesktop.get();
63✔
248
    if(!windows.empty())
3✔
249
        return windows.back().get();
3✔
NEW
250
    return nullptr;
×
251
}
252

253
void WindowManager::Msg_LeftDown(MouseCoords mc)
11✔
254
{
255
    // play click sound
256
    SoundEffectItem* sound = LOADER.GetSoundN("sound", 112);
22✔
257
    if(sound)
11✔
258
        sound->Play(255, false);
11✔
259

260
    Window* activeWindow = findAndActivateWindow(mc.pos);
11✔
261
    // Ignore mouse message, e.g. right after switching desktop, to avoid unwanted clicks
262
    if(!disable_mouse && activeWindow)
11✔
263
        RelayMouseMessage(&Window::Msg_LeftDown, mc, activeWindow);
11✔
264
}
11✔
265

266
void WindowManager::Msg_LeftUp(MouseCoords mc)
11✔
267
{
268
    // Any desktop active?
269
    if(!curDesktop)
11✔
270
        return;
×
271

272
    // Check for double-click
273
    const auto time_now = VIDEODRIVER.GetTickCount();
11✔
274
    if(time_now - lastLeftClickTime < DOUBLE_CLICK_INTERVAL && mc.pos == lastLeftClickPos)
11✔
275
        mc.dbl_click = true;
1✔
276
    else
277
    {
278
        // Just single click, store values for next possible double click
279
        lastLeftClickPos = mc.pos;
10✔
280
        lastLeftClickTime = time_now;
10✔
281
    }
282

283
    // Don't relay message if mouse is disabled (e.g. right after desktop switch)
284
    if(!disable_mouse)
11✔
285
        RelayMouseMessage(&Window::Msg_LeftUp, mc);
10✔
286
    else if(!nextdesktop)
1✔
287
        disable_mouse = false;
1✔
288
}
289

290
void WindowManager::Msg_RightDown(const MouseCoords& mc)
14✔
291
{
292
    if(!curDesktop)
14✔
UNCOV
293
        return;
×
294

295
    // Right-click closes (most) windows, so handle that first
296
    if(!windows.empty())
14✔
297
    {
298
        IngameWindow* foundWindow = FindWindowAtPos(mc.pos);
9✔
299
        if(windows.back()->IsModal())
9✔
300
        {
301
            // We have a modal window -> Activate it
302
            SetActiveWindow(*windows.back());
2✔
303
            // Ignore actions in all other windows
304
            if(foundWindow != GetTopMostWindow())
2✔
305
                return;
×
306
        }
307
        if(foundWindow)
9✔
308
        {
309
            // Close it if requested (unless pinned)
310
            if(foundWindow->getCloseBehavior() == CloseBehavior::Regular)
9✔
311
            {
312
                if(!foundWindow->IsPinned())
7✔
313
                    foundWindow->Close();
6✔
314
                return;
7✔
315
            }
316
        }
317
    }
318
    RelayMouseMessage(&Window::Msg_RightDown, mc, findAndActivateWindow(mc.pos));
7✔
319
}
320

321
void WindowManager::Msg_RightUp(const MouseCoords& mc)
3✔
322
{
323
    RelayMouseMessage(&Window::Msg_RightUp, mc);
3✔
324
}
3✔
325

UNCOV
326
void WindowManager::Msg_WheelUp(const MouseCoords& mc)
×
327
{
NEW
328
    RelayMouseMessage(&Window::Msg_WheelUp, mc, findAndActivateWindow(mc.pos));
×
329
}
×
330

UNCOV
331
void WindowManager::Msg_WheelDown(const MouseCoords& mc)
×
332
{
NEW
333
    RelayMouseMessage(&Window::Msg_WheelDown, mc, findAndActivateWindow(mc.pos));
×
334
}
×
335

336
void WindowManager::Msg_MouseMove(const MouseCoords& mc)
53✔
337
{
338
    lastMousePos = mc.pos;
53✔
339
    RelayMouseMessage(&Window::Msg_MouseMove, mc);
53✔
340
}
53✔
341

342
void WindowManager::Msg_KeyDown(const KeyEvent& ke)
19✔
343
{
344
    if(ke.alt && (ke.kt == KeyType::Return))
19✔
345
    {
346
        // Switch Fullscreen/Windowed
347
        const auto newScreenSize =
348
          !SETTINGS.video.fullscreen ? SETTINGS.video.fullscreenSize : SETTINGS.video.windowedSize; //-V807
×
349
        VIDEODRIVER.ResizeScreen(newScreenSize, !SETTINGS.video.fullscreen);
×
350
        SETTINGS.video.fullscreen = VIDEODRIVER.IsFullscreen();
×
351
    } else if(ke.kt == KeyType::Print)
19✔
352
        TakeScreenshot();
×
353
    else
354
        RelayKeyboardMessage(&Window::Msg_KeyDown, ke);
19✔
355
}
19✔
356

357
/**
358
 *  Handle resize of the window or change of resolution
359
 */
360
void WindowManager::WindowResized()
9✔
361
{
362
    VIDEODRIVER.RenewViewport();
9✔
363
    Msg_ScreenResize(VIDEODRIVER.GetRenderSize());
9✔
364
}
9✔
365

366
/**
367
 *  React to change of the render size
368
 */
369
void WindowManager::Msg_ScreenResize(const Extent& newSize)
14✔
370
{
371
    // Don't handle it if nothing changed
372
    if(newSize == curRenderSize)
14✔
373
        return;
6✔
374

375
    ScreenResizeEvent sr(curRenderSize, elMax(Extent(800, 600), newSize));
11✔
376
    curRenderSize = sr.newSize;
11✔
377

378
    // Don't change fullscreen size (only in menu)
379
    if(!SETTINGS.video.fullscreen)
11✔
380
        SETTINGS.video.windowedSize = VIDEODRIVER.GetWindowSize();
11✔
381

382
    // ist unser Desktop gültig?
383
    if(!curDesktop)
11✔
384
        return;
3✔
385

386
    curDesktop->Msg_ScreenResize(sr);
8✔
387

388
    // IngameWindow verschieben falls nötig, so dass sie komplett sichtbar sind
389
    for(const auto& window : windows)
8✔
390
    {
391
        DrawPoint delta = window->GetPos() + DrawPoint(window->GetSize()) - DrawPoint(sr.newSize);
×
392
        if(delta.x > 0 || delta.y > 0)
×
393
            window->SetPos(window->GetPos() - elMax(delta, DrawPoint(0, 0)));
×
394
    }
395
}
396

397
IngameWindow* WindowManager::GetTopMostWindow() const
132✔
398
{
399
    if(windows.empty())
132✔
400
        return nullptr;
44✔
401
    else
402
        return windows.back().get();
88✔
403
}
404

405
void WindowManager::DoClose(IngameWindow* window)
43✔
406
{
407
    const auto it = helpers::findPtr(windows, window);
43✔
408

409
    RTTR_Assert(it != windows.end());
43✔
410

411
    SetToolTip(nullptr, "");
43✔
412

413
    // Store if this was the active window
414
    const bool isActiveWnd = window == GetTopMostWindow();
43✔
415

416
    // Remove from list and notify parent, hold onto it till parent is notified
417
    const auto tmpHolder = std::move(*it);
86✔
418
    windows.erase(it);
43✔
419
    if(isActiveWnd)
43✔
420
    {
421
        if(windows.empty())
35✔
422
            SetActiveWindow(*curDesktop);
25✔
423
        else
424
            SetActiveWindow(*windows.back());
10✔
425
    }
426
    curDesktop->Msg_WindowClosed(*tmpHolder);
43✔
427
}
43✔
428

429
/**
430
 *  Closes _ALL_ windows with the given ID
431
 *
432
 *  @param[in] id ID of the window to be closed
433
 */
434
void WindowManager::Close(unsigned id)
3✔
435
{
436
    for(auto& wnd : windows)
5✔
437
    {
438
        if(wnd->GetID() == id && !wnd->ShouldBeClosed())
2✔
439
            wnd->Close();
2✔
440
    }
441
}
3✔
442

443
void WindowManager::CloseNow(IngameWindow* window)
9✔
444
{
445
    if(!window->ShouldBeClosed())
9✔
446
        window->Close();
9✔
447
    DoClose(window);
9✔
448
}
9✔
449

450
/**
451
 *  Actually process the desktop change
452
 */
453
void WindowManager::DoDesktopSwitch()
29✔
454
{
455
    RTTR_Assert(nextdesktop);
29✔
456
    VIDEODRIVER.ClearScreen();
29✔
457

458
    SetToolTip(nullptr, "");
29✔
459

460
    // If we have a current desktop close all windows
461
    if(curDesktop)
29✔
462
        windows.clear();
25✔
463

464
    // Do the switch
465
    curDesktop = std::move(nextdesktop);
29✔
466
    curDesktop->SetActive(true);
29✔
467

468
    for(auto& nextWnd : nextWnds)
30✔
469
        Show(std::move(nextWnd));
1✔
470
    nextWnds.clear();
29✔
471

472
    if(!VIDEODRIVER.IsLeftDown())
29✔
473
        disable_mouse = false;
29✔
474

475
    // Dummy mouse move to init hovering etc
476
    Msg_MouseMove(MouseCoords(VIDEODRIVER.GetMousePos()));
29✔
477
}
29✔
478

479
void WindowManager::CloseMarkedIngameWnds()
80✔
480
{
481
    auto isWndMarkedForClose = [](const auto& wnd) { return wnd->ShouldBeClosed(); };
121✔
482
    auto it = helpers::find_if(windows, isWndMarkedForClose);
80✔
483
    while(it != windows.end())
114✔
484
    {
485
        DoClose(it->get());
34✔
486
        it = helpers::find_if(windows, isWndMarkedForClose);
34✔
487
    }
488
}
80✔
489

490
template<class T_Windows>
491
void SetActiveWindowImpl(const Window& wnd, Desktop& desktop, T_Windows& windows)
97✔
492
{
493
    auto itWnd = helpers::find_if(windows, [&wnd](const auto& it) { return it.get() == &wnd; });
209✔
494
    if(itWnd != windows.end())
97✔
495
    {
496
        // If we have a modal window, don't make this active unless it is the top most one
497
        if(&wnd != windows.back().get() && helpers::contains_if(windows, [](const auto& it) { return it->IsModal(); }))
88✔
498
        {
499
            return;
8✔
500
        }
501
        desktop.SetActive(false);
64✔
502
        // Move window to the end (itWnd+1 -> itWnd -> end())
503
        std::rotate(itWnd, std::next(itWnd), windows.end());
64✔
504
    } else if(&wnd == &desktop)
25✔
505
        desktop.SetActive(true);
25✔
506
    else
507
        return;
×
508
    for(const auto& curWnd : windows)
188✔
509
        curWnd->SetActive(&wnd == curWnd.get());
99✔
510
}
511

512
void WindowManager::SetActiveWindow(Window& wnd)
97✔
513
{
514
    if(curDesktop)
97✔
515
        SetActiveWindowImpl(wnd, *curDesktop, windows);
97✔
516
    if(nextdesktop) // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move)
97✔
517
        SetActiveWindowImpl(wnd, *nextdesktop, nextWnds);
×
518
}
97✔
519

520
void WindowManager::TakeScreenshot() const
×
521
{
522
    const auto windowSize = VIDEODRIVER.GetWindowSize();
×
523
    libsiedler2::PixelBufferBGRA buffer(windowSize.width, windowSize.height);
×
524
    glReadPixels(0, 0, windowSize.width, windowSize.height, GL_BGRA, GL_UNSIGNED_BYTE, buffer.getPixelPtr());
×
525
    flipVertical(buffer);
×
526
    const bfs::path outFilepath =
527
      RTTRCONFIG.ExpandPath(s25::folders::screenshots) / (s25util::Time::FormatTime("%Y-%m-%d_%H-%i-%s") + ".bmp");
×
528
    try
529
    {
530
        saveBitmap(buffer, outFilepath);
×
531
        LOG.write(_("Screenshot saved to %1%\n")) % outFilepath;
×
532
    } catch(const std::runtime_error& e)
×
533
    {
534
        LOG.write(_("Error writing screenshot: %1%\n")) % e.what();
×
535
    }
536
}
×
537

538
class WindowManager::Tooltip
539
{
540
    static constexpr unsigned BORDER_SIZE = 2;
541
    const ctrlBaseTooltip* showingCtrl;
542
    const glFont* font;
543
    std::vector<std::string> lines;
544
    unsigned width = 0, height = 0;
545
    unsigned short maxWidth;
546

547
public:
548
    Tooltip(const ctrlBaseTooltip* showingCtrl, const std::string& text, unsigned short maxWidth)
×
549
        : showingCtrl(showingCtrl), font(NormalFont), maxWidth(maxWidth)
×
550
    {
551
        setText(text);
×
552
    }
×
553

554
    auto getShowingCtrl() const { return showingCtrl; }
×
555
    auto getWidth() const { return width; }
×
556

557
    void setText(const std::string& text)
×
558
    {
559
        lines = font->GetWrapInfo(text, maxWidth, maxWidth).CreateSingleStrings(text);
×
560
        if(lines.empty())
×
561
            return;
×
562
        width = 0;
×
563
        for(const auto& line : lines)
×
564
            width = std::max(width, font->getWidth(line));
×
565
        width += BORDER_SIZE * 2;
×
566
        height = lines.size() * font->getHeight() + BORDER_SIZE * 2;
×
567
    }
568

569
    void draw(DrawPoint pos) const
×
570
    {
571
        Window::DrawRectangle(Rect(pos, width, height), 0x9F000000);
×
572
        pos += DrawPoint::all(BORDER_SIZE);
×
573
        const auto fontHeight = font->getHeight();
×
574
        for(const auto& line : lines)
×
575
        {
576
            font->Draw(pos, line, FontStyle::TOP, COLOR_YELLOW);
×
577
            pos.y += fontHeight;
×
578
        }
579
    }
×
580
};
581

582
void WindowManager::SetToolTip(const ctrlBaseTooltip* ttw, const std::string& tooltip, bool updateCurrent)
863✔
583
{
584
    // Max width of tooltip
585
    constexpr unsigned short MAX_TOOLTIP_WIDTH = 260;
863✔
586

587
    if(tooltip.empty())
863✔
588
    {
589
        if(curTooltip && (!ttw || curTooltip->getShowingCtrl() == ttw))
862✔
590
            curTooltip.reset();
×
591
    } else if(updateCurrent)
1✔
592
    {
593
        if(curTooltip && curTooltip->getShowingCtrl() == ttw)
1✔
594
            curTooltip->setText(tooltip);
×
595
    } else
596
        curTooltip = std::make_unique<Tooltip>(ttw, tooltip, MAX_TOOLTIP_WIDTH);
×
597
}
863✔
598

599
void WindowManager::DrawToolTip()
80✔
600
{
601
    // Tooltip zeichnen
602
    if(curTooltip && lastMousePos.isValid())
80✔
603
    {
604
        constexpr unsigned cursorWidth = 32;
×
605
        constexpr unsigned cursorPadding = 5;
×
606
        // Horizontal space between mouse position and tooltip border
607
        constexpr unsigned rightSpacing = cursorWidth + cursorPadding;
×
608
        constexpr unsigned leftSpacing = cursorPadding;
×
609
        DrawPoint ttPos = DrawPoint(lastMousePos.x + rightSpacing, lastMousePos.y);
×
610
        unsigned right_edge = ttPos.x + curTooltip->getWidth();
×
611

612
        // links neben der Maus, wenn es über den Rand gehen würde
613
        if(right_edge > curRenderSize.x)
×
614
            ttPos.x = lastMousePos.x - leftSpacing - curTooltip->getWidth();
×
615
        curTooltip->draw(ttPos);
×
616
    }
617
}
80✔
618

619
SnapOffset WindowManager::snapWindow(Window* wnd, const Rect& wndRect) const
8✔
620
{
621
    const auto snapDistance = static_cast<int>(SETTINGS.interface.windowSnapDistance);
8✔
622
    if(snapDistance == 0)
8✔
623
        return SnapOffset::all(0); // No snapping
×
624

625
    /// Progressive minimum distance to another window edge; initial value limits to at most snapDistance
626
    auto minDist = Extent::all(snapDistance + 1);
8✔
627
    /// (Smallest) offset (signed distance) the window should be moved; initially zero, so no snapping
628
    SnapOffset minOffset = SnapOffset::all(0);
8✔
629

630
    const auto setOffsetIfLowerDist = [](int a, int b, unsigned& minDist, int& minOffset) {
72✔
631
        const int offset = b - a;
72✔
632
        const unsigned dist = std::abs(offset);
72✔
633
        if(dist < minDist)
72✔
634
        {
635
            minDist = dist;
12✔
636
            minOffset = offset;
12✔
637
        }
638
    };
72✔
639
    const auto setOffsetIfLowerDistX = [&](int x1, int x2) { setOffsetIfLowerDist(x1, x2, minDist.x, minOffset.x); };
40✔
640
    const auto setOffsetIfLowerDistY = [&](int y1, int y2) { setOffsetIfLowerDist(y1, y2, minDist.y, minOffset.y); };
32✔
641

642
    for(const auto& curWnd : windows)
26✔
643
    {
644
        if(curWnd.get() == wnd)
18✔
645
            continue;
8✔
646

647
        const Rect curWndRect = curWnd->GetBoundaryRect();
10✔
648

649
        // Calculate smallest offset for each parallel pair of window edges, iff the windows overlap along the
650
        // orthogonal axis (± snap distance)
651
        if(wndRect.top <= (curWndRect.bottom + snapDistance) && wndRect.bottom >= (curWndRect.top - snapDistance))
10✔
652
        {
653
            setOffsetIfLowerDistX(wndRect.left, curWndRect.left);
10✔
654
            setOffsetIfLowerDistX(wndRect.left, curWndRect.right);
10✔
655
            setOffsetIfLowerDistX(wndRect.right, curWndRect.left);
10✔
656
            setOffsetIfLowerDistX(wndRect.right, curWndRect.right);
10✔
657
        }
658

659
        if(wndRect.left <= (curWndRect.right + snapDistance) && wndRect.right >= (curWndRect.left - snapDistance))
10✔
660
        {
661
            setOffsetIfLowerDistY(wndRect.top, curWndRect.top);
8✔
662
            setOffsetIfLowerDistY(wndRect.top, curWndRect.bottom);
8✔
663
            setOffsetIfLowerDistY(wndRect.bottom, curWndRect.top);
8✔
664
            setOffsetIfLowerDistY(wndRect.bottom, curWndRect.bottom);
8✔
665
        }
666
    }
667

668
    return minOffset;
8✔
669
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc