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

Return-To-The-Roots / s25client / 22039819810

15 Feb 2026 05:18PM UTC coverage: 50.781% (-0.04%) from 50.82%
22039819810

Pull #1890

github

web-flow
Merge ededb8a2f into 6db06730b
Pull Request #1890: Add support for borderless Windows

13 of 115 new or added lines in 10 files covered. (11.3%)

7 existing lines in 4 files now uncovered.

22798 of 44895 relevant lines covered (50.78%)

43455.62 hits per line

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

77.75
/libs/s25main/WindowManager.cpp
1
// Copyright (C) 2005 - 2026 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 "Point.h"
9
#include "RttrConfig.h"
10
#include "Settings.h"
11
#include "Window.h"
12
#include "commonDefines.h"
13
#include "desktops/Desktop.h"
14
#include "driver/MouseCoords.h"
15
#include "driver/VideoInterface.h"
16
#include "drivers/ScreenResizeEvent.h"
17
#include "drivers/VideoDriverWrapper.h"
18
#include "files.h"
19
#include "helpers/pointerContainerUtils.h"
20
#include "helpers/reverse.h"
21
#include "ingameWindows/IngameWindow.h"
22
#include "ogl/FontStyle.h"
23
#include "ogl/SoundEffectItem.h"
24
#include "ogl/glFont.h"
25
#include "ogl/saveBitmap.h"
26
#include "gameData/const_gui_ids.h"
27
#include "libsiedler2/PixelBufferBGRA.h"
28
#include "s25util/Log.h"
29
#include "s25util/MyTime.h"
30
#include <algorithm>
31
#include <type_traits>
32

33
namespace {
34
template<typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
35
constexpr std::make_unsigned_t<T> square(T x)
6✔
36
{
37
    return x * x;
6✔
38
}
39
} // namespace
40

41
WindowManager::WindowManager()
4✔
42
    : cursor_(Cursor::Hand), disable_mouse(false), lastMousePos(Position::Invalid()), curRenderSize(0, 0),
4✔
43
      lastLeftClickTime(0), lastLeftClickPos(0, 0)
8✔
44
{}
4✔
45

46
WindowManager::~WindowManager() = default;
8✔
47

48
void WindowManager::CleanUp()
1✔
49
{
50
    windows.clear();
1✔
51
    curDesktop.reset();
1✔
52
    nextdesktop.reset();
1✔
53
}
1✔
54

55
void WindowManager::SetCursor(Cursor cursor)
31✔
56
{
57
    cursor_ = cursor;
31✔
58
}
31✔
59

60
void WindowManager::DrawCursor()
81✔
61
{
62
    auto resId = static_cast<unsigned>(cursor_);
81✔
63
    switch(cursor_)
81✔
64
    {
65
        case Cursor::Hand:
80✔
66
        case Cursor::Remove: resId += VIDEODRIVER.IsLeftDown() ? 1 : 0; break;
80✔
67
        default: break;
1✔
68
    }
69
    if(resId)
81✔
70
        LOADER.GetImageN("resource", resId)->DrawFull(VIDEODRIVER.GetMousePos());
162✔
71
}
81✔
72

73
/**
74
 *  Zeichenfunktion WindowManager-Klasse.
75
 *  Zeichnet Desktop und alle Fenster.
76
 */
77
void WindowManager::Draw()
81✔
78
{
79
    // ist ein neuer Desktop eingetragen? Wenn ja, wechseln
80
    if(nextdesktop)
81✔
81
        DoDesktopSwitch();
30✔
82

83
    if(!curDesktop)
81✔
84
        return;
×
85

86
    curDesktop->Msg_PaintBefore();
81✔
87
    curDesktop->Draw();
81✔
88
    curDesktop->Msg_PaintAfter();
81✔
89

90
    // First close all marked windows
91
    CloseMarkedIngameWnds();
81✔
92
    for(auto& wnd : windows)
147✔
93
    {
94
        // If the window is not minimized, call paintAfter
95
        if(!wnd->IsMinimized())
66✔
96
            wnd->Msg_PaintBefore();
66✔
97
        wnd->Draw();
66✔
98
        // If the window is not minimized, call paintAfter
99
        if(!wnd->IsMinimized())
66✔
100
            wnd->Msg_PaintAfter();
66✔
101
    }
102

103
    DrawToolTip();
81✔
104
    DrawCursor();
81✔
105
}
106

107
bool WindowManager::IsDesktopActive() const
74✔
108
{
109
    if(curDesktop)
74✔
110
        return curDesktop->IsActive();
74✔
111
    return false;
×
112
}
113

114
void WindowManager::RelayKeyboardMessage(KeyboardMsgHandler msg, const KeyEvent& ke)
19✔
115
{
116
    // When there is no desktop, don't check it or any window
117
    if(!curDesktop)
19✔
118
        return;
×
119
    if(curDesktop->IsActive())
19✔
120
    {
121
        // Desktop active -> relay msg to desktop
122
        CALL_MEMBER_FN(*curDesktop, msg)(ke);
2✔
123
        curDesktop->RelayKeyboardMessage(msg, ke);
2✔
124
        return;
2✔
125
    }
126

127
    if(windows.empty())
17✔
128
        return; // No windows -> nothing to do
×
129

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

153
void WindowManager::RelayMouseMessage(MouseMsgHandler msg, const MouseCoords& mc, Window* window)
98✔
154
{
155
    if(!window)
98✔
156
        window = getActiveWindow();
74✔
157
    if(window)
98✔
158
    {
159
        // If no sub-window/control handled the message, let the window itself handle it
160
        if(!window->RelayMouseMessage(msg, mc))
98✔
161
            CALL_MEMBER_FN(*window, msg)(mc);
97✔
162
    }
163
}
98✔
164

165
/**
166
 *  Öffnet ein IngameWindow und fügt es zur Fensterliste hinzu.
167
 */
168
IngameWindow& WindowManager::DoShow(std::unique_ptr<IngameWindow> window, bool mouse)
60✔
169
{
170
    RTTR_Assert(window);
60✔
171
    RTTR_Assert(!helpers::contains(windows, window));
60✔
172
    // No desktop -> Out
173
    if(!curDesktop)
60✔
174
        throw std::runtime_error("No desktop active for window to be shown on");
×
175

176
    SetToolTip(nullptr, "");
60✔
177

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

183
    // Make the new window active (special cases handled in the function)
184
    SetActiveWindow(result);
60✔
185

186
    // Maus deaktivieren, bis sie losgelassen wurde (Fix des Switch-Anschließend-Drück-Bugs)
187
    if(!VIDEODRIVER.IsTouch())
60✔
188
        disable_mouse = mouse;
17✔
189
    return result;
60✔
190
}
191

192
IngameWindow* WindowManager::ShowAfterSwitch(std::unique_ptr<IngameWindow> window)
1✔
193
{
194
    RTTR_Assert(window);
1✔
195
    nextWnds.emplace_back(std::move(window));
1✔
196
    return nextWnds.back().get();
1✔
197
}
198

199
/**
200
 *  merkt einen Desktop zum Wechsel vor.
201
 *
202
 *  @param[in] desktop       Pointer zum neuen Desktop, auf dem gewechselt werden soll
203
 *  @param[in] data          Daten für den neuen Desktop
204
 */
205
Desktop* WindowManager::Switch(std::unique_ptr<Desktop> desktop)
30✔
206
{
207
    nextdesktop = std::move(desktop);
30✔
208
    // Disable the mouse till the next desktop is shown to avoid e.g. double-switching
209
    disable_mouse = true;
30✔
210
    return nextdesktop.get();
30✔
211
}
212

213
IngameWindow* WindowManager::FindWindowAtPos(const Position& pos) const
10✔
214
{
215
    // Fenster durchgehen ( von hinten nach vorn, da die vordersten ja zuerst geprüft werden müssen !! )
216
    for(const auto& window : helpers::reverse(windows))
10✔
217
    {
218
        // FensterRect für Kollisionsabfrage
219
        Rect window_rect = window->GetDrawRect();
10✔
220

221
        // trifft die Maus auf ein Fenster?
222
        if(IsPointInRect(pos, window_rect))
10✔
223
        {
224
            return window.get();
10✔
225
        }
226
        // Check also if we are in the locked area of a window (e.g. dropdown extends outside of window)
227
        if(window->IsInLockedRegion(pos))
×
228
            return window.get();
×
229
    }
230
    return nullptr;
×
231
}
232

233
IngameWindow* WindowManager::FindNonModalWindow(unsigned id) const
19✔
234
{
235
    auto itWnd = helpers::find_if(
236
      windows, [id](const auto& wnd) { return !wnd->ShouldBeClosed() && !wnd->IsModal() && wnd->GetID() == id; });
46✔
237
    return itWnd == windows.end() ? nullptr : itWnd->get();
19✔
238
}
239

240
Window* WindowManager::findAndActivateWindow(const Position mousePos)
24✔
241
{
242
    Window* activeWindow = nullptr;
24✔
243
    if(!windows.empty())
24✔
244
    {
245
        if(windows.back()->IsModal())
2✔
246
            activeWindow = windows.back().get();
1✔
247
        else
248
            activeWindow = FindWindowAtPos(mousePos);
1✔
249
    }
250
    if(!activeWindow)
24✔
251
        activeWindow = curDesktop.get();
22✔
252
    if(activeWindow && !activeWindow->IsActive())
24✔
253
        SetActiveWindow(*activeWindow);
×
254
    return activeWindow;
24✔
255
}
256

257
Window* WindowManager::getActiveWindow() const
74✔
258
{
259
    if(IsDesktopActive())
74✔
260
        return curDesktop.get();
71✔
261
    if(!windows.empty())
3✔
262
        return windows.back().get();
3✔
263
    return nullptr;
×
264
}
265

266
void WindowManager::Msg_LeftDown(MouseCoords mc)
16✔
267
{
268
    // play click sound
269
    SoundEffectItem* sound = LOADER.GetSoundN("sound", 112);
32✔
270
    if(sound)
16✔
271
        sound->Play(255, false);
16✔
272

273
    Window* activeWindow = findAndActivateWindow(mc.pos);
16✔
274
    // Ignore mouse message, e.g. right after switching desktop, to avoid unwanted clicks
275
    if(!disable_mouse && activeWindow)
16✔
276
        RelayMouseMessage(&Window::Msg_LeftDown, mc, activeWindow);
16✔
277
}
16✔
278

279
void WindowManager::Msg_LeftUp(MouseCoords mc)
16✔
280
{
281
    // Any desktop active?
282
    if(!curDesktop)
16✔
283
        return;
×
284

285
    unsigned time_now = VIDEODRIVER.GetTickCount();
16✔
286

287
    if(VIDEODRIVER.IsTouch())
16✔
288
    {
289
        if(time_now - lastLeftClickTime < TOUCH_DOUBLE_CLICK_INTERVAL)
5✔
290
        {
291
            // Calculate distance between two points
292
            const Point<int> diff = mc.pos - lastLeftClickPos;
2✔
293
            if(square(diff.x) + square(diff.y) <= square(TOUCH_MAX_DOUBLE_CLICK_DISTANCE))
2✔
294
                mc.dbl_click = true;
1✔
295
        }
296

297
    } else if(time_now - lastLeftClickTime < DOUBLE_CLICK_INTERVAL && mc.pos == lastLeftClickPos)
11✔
298
        mc.dbl_click = true;
1✔
299

300
    if(!mc.dbl_click)
16✔
301
    {
302
        // Save values for next potential dbl click
303
        lastLeftClickPos = mc.pos;
14✔
304
        lastLeftClickTime = time_now;
14✔
305
    }
306

307
    // Don't relay message if mouse is disabled (e.g. right after desktop switch)
308
    if(!disable_mouse)
16✔
309
        RelayMouseMessage(&Window::Msg_LeftUp, mc);
15✔
310
    else if(!nextdesktop)
1✔
311
        disable_mouse = false;
1✔
312
}
313

314
void WindowManager::Msg_RightDown(const MouseCoords& mc)
15✔
315
{
316
    if(!curDesktop)
15✔
317
        return;
×
318

319
    // Right-click closes (most) windows, so handle that first
320
    if(!windows.empty())
15✔
321
    {
322
        IngameWindow* foundWindow = FindWindowAtPos(mc.pos);
9✔
323
        if(windows.back()->IsModal())
9✔
324
        {
325
            // We have a modal window -> Activate it
326
            SetActiveWindow(*windows.back());
2✔
327
            // Ignore actions in all other windows
328
            if(foundWindow != GetTopMostWindow())
2✔
329
                return;
×
330
        }
331
        if(foundWindow)
9✔
332
        {
333
            // Close it if requested (unless pinned)
334
            if(foundWindow->getCloseBehavior() == CloseBehavior::Regular)
9✔
335
            {
336
                if(!foundWindow->IsPinned())
7✔
337
                    foundWindow->Close();
6✔
338
                return;
7✔
339
            }
340
        }
341
    }
342
    RelayMouseMessage(&Window::Msg_RightDown, mc, findAndActivateWindow(mc.pos));
8✔
343
}
344

345
void WindowManager::Msg_RightUp(const MouseCoords& mc)
4✔
346
{
347
    RelayMouseMessage(&Window::Msg_RightUp, mc);
4✔
348
}
4✔
349

350
void WindowManager::Msg_WheelUp(const MouseCoords& mc)
×
351
{
352
    RelayMouseMessage(&Window::Msg_WheelUp, mc, findAndActivateWindow(mc.pos));
×
353
}
×
354

355
void WindowManager::Msg_WheelDown(const MouseCoords& mc)
×
356
{
357
    RelayMouseMessage(&Window::Msg_WheelDown, mc, findAndActivateWindow(mc.pos));
×
358
}
×
359

360
void WindowManager::Msg_MouseMove(const MouseCoords& mc)
55✔
361
{
362
    lastMousePos = mc.pos;
55✔
363
    RelayMouseMessage(&Window::Msg_MouseMove, mc);
55✔
364
}
55✔
365

366
void WindowManager::Msg_KeyDown(const KeyEvent& ke)
19✔
367
{
368
    if(ke.alt && (ke.kt == KeyType::Return))
19✔
369
    {
370
        // Switch Fullscreen/Windowed
371
        const auto newMode =
NEW
372
          (SETTINGS.video.displayMode == DisplayMode::Fullscreen) ? DisplayMode::Windowed : DisplayMode::Fullscreen;
×
373
        const auto newScreenSize =
NEW
374
          (newMode == DisplayMode::Fullscreen) ? SETTINGS.video.fullscreenSize : SETTINGS.video.windowedSize;
×
NEW
375
        VIDEODRIVER.ResizeScreen(newScreenSize, newMode);
×
376

NEW
377
        SETTINGS.video.displayMode = VIDEODRIVER.GetDisplayMode();
×
378
    } else if(ke.kt == KeyType::Print)
19✔
379
        TakeScreenshot();
×
380
    else
381
        RelayKeyboardMessage(&Window::Msg_KeyDown, ke);
19✔
382
}
19✔
383

384
/**
385
 *  Handle resize of the window or change of resolution
386
 */
387
void WindowManager::WindowResized()
9✔
388
{
389
    VIDEODRIVER.RenewViewport();
9✔
390
    Msg_ScreenResize(VIDEODRIVER.GetRenderSize());
9✔
391
}
9✔
392

393
/**
394
 *  React to change of the render size
395
 */
396
void WindowManager::Msg_ScreenResize(const Extent& newSize)
14✔
397
{
398
    // Don't handle it if nothing changed
399
    if(newSize == curRenderSize)
14✔
400
        return;
6✔
401

402
    ScreenResizeEvent sr(curRenderSize, elMax(Extent(800, 600), newSize));
11✔
403
    curRenderSize = sr.newSize;
11✔
404

405
    // Don't change fullscreen size (only in menu)
406
    if(SETTINGS.video.displayMode != DisplayMode::Fullscreen)
11✔
407
        SETTINGS.video.windowedSize = VIDEODRIVER.GetWindowSize();
11✔
408

409
    // Desktop (still) valid?
410
    if(!curDesktop)
11✔
411
        return;
3✔
412

413
    curDesktop->Msg_ScreenResize(sr);
8✔
414

415
    // Move IngameWindows so they are completely visible
416
    for(const auto& window : windows)
8✔
417
    {
418
        DrawPoint delta = window->GetPos() + DrawPoint(window->GetSize()) - DrawPoint(sr.newSize);
×
419
        if(delta.x > 0 || delta.y > 0)
×
420
            window->SetPos(window->GetPos() - elMax(delta, DrawPoint(0, 0)));
×
421
    }
422
}
423

424
IngameWindow* WindowManager::GetTopMostWindow() const
132✔
425
{
426
    if(windows.empty())
132✔
427
        return nullptr;
44✔
428
    else
429
        return windows.back().get();
88✔
430
}
431

432
void WindowManager::DoClose(IngameWindow* window)
43✔
433
{
434
    const auto it = helpers::findPtr(windows, window);
43✔
435

436
    RTTR_Assert(it != windows.end());
43✔
437

438
    SetToolTip(nullptr, "");
43✔
439

440
    // Store if this was the active window
441
    const bool isActiveWnd = window == GetTopMostWindow();
43✔
442

443
    // Remove from list and notify parent, hold onto it till parent is notified
444
    const auto tmpHolder = std::move(*it);
86✔
445
    windows.erase(it);
43✔
446
    if(isActiveWnd)
43✔
447
    {
448
        if(windows.empty())
35✔
449
            SetActiveWindow(*curDesktop);
25✔
450
        else
451
            SetActiveWindow(*windows.back());
10✔
452
    }
453
    curDesktop->Msg_WindowClosed(*tmpHolder);
43✔
454
}
43✔
455

456
/**
457
 *  Closes _ALL_ windows with the given ID
458
 *
459
 *  @param[in] id ID of the window to be closed
460
 */
461
void WindowManager::Close(unsigned id)
3✔
462
{
463
    for(auto& wnd : windows)
5✔
464
    {
465
        if(wnd->GetID() == id && !wnd->ShouldBeClosed())
2✔
466
            wnd->Close();
2✔
467
    }
468
}
3✔
469

470
void WindowManager::CloseNow(IngameWindow* window)
9✔
471
{
472
    if(!window->ShouldBeClosed())
9✔
473
        window->Close();
9✔
474
    DoClose(window);
9✔
475
}
9✔
476

477
/**
478
 *  Actually process the desktop change
479
 */
480
void WindowManager::DoDesktopSwitch()
30✔
481
{
482
    RTTR_Assert(nextdesktop);
30✔
483
    VIDEODRIVER.ClearScreen();
30✔
484

485
    SetToolTip(nullptr, "");
30✔
486

487
    // If we have a current desktop close all windows
488
    if(curDesktop)
30✔
489
        windows.clear();
26✔
490

491
    // Do the switch
492
    curDesktop = std::move(nextdesktop);
30✔
493
    curDesktop->SetActive(true);
30✔
494

495
    for(auto& nextWnd : nextWnds)
31✔
496
        Show(std::move(nextWnd));
1✔
497
    nextWnds.clear();
30✔
498

499
    if(!VIDEODRIVER.IsLeftDown())
30✔
500
        disable_mouse = false;
30✔
501

502
    // Dummy mouse move to init hovering etc
503
    Msg_MouseMove(MouseCoords(VIDEODRIVER.GetMousePos()));
30✔
504
}
30✔
505

506
void WindowManager::CloseMarkedIngameWnds()
81✔
507
{
508
    auto isWndMarkedForClose = [](const auto& wnd) { return wnd->ShouldBeClosed(); };
121✔
509
    auto it = helpers::find_if(windows, isWndMarkedForClose);
81✔
510
    while(it != windows.end())
115✔
511
    {
512
        DoClose(it->get());
34✔
513
        it = helpers::find_if(windows, isWndMarkedForClose);
34✔
514
    }
515
}
81✔
516

517
template<class T_Windows>
518
void SetActiveWindowImpl(const Window& wnd, Desktop& desktop, T_Windows& windows)
97✔
519
{
520
    auto itWnd = helpers::find_if(windows, [&wnd](const auto& it) { return it.get() == &wnd; });
209✔
521
    if(itWnd != windows.end())
97✔
522
    {
523
        // If we have a modal window, don't make this active unless it is the top most one
524
        if(&wnd != windows.back().get() && helpers::contains_if(windows, [](const auto& it) { return it->IsModal(); }))
88✔
525
        {
526
            return;
8✔
527
        }
528
        desktop.SetActive(false);
64✔
529
        // Move window to the end (itWnd+1 -> itWnd -> end())
530
        std::rotate(itWnd, std::next(itWnd), windows.end());
64✔
531
    } else if(&wnd == &desktop)
25✔
532
        desktop.SetActive(true);
25✔
533
    else
534
        return;
×
535
    for(const auto& curWnd : windows)
188✔
536
        curWnd->SetActive(&wnd == curWnd.get());
99✔
537
}
538

539
void WindowManager::SetActiveWindow(Window& wnd)
97✔
540
{
541
    if(curDesktop)
97✔
542
        SetActiveWindowImpl(wnd, *curDesktop, windows);
97✔
543
    if(nextdesktop) // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move)
97✔
544
        SetActiveWindowImpl(wnd, *nextdesktop, nextWnds);
×
545
}
97✔
546

547
void WindowManager::TakeScreenshot() const
×
548
{
549
    const auto windowSize = VIDEODRIVER.GetWindowSize();
×
550
    libsiedler2::PixelBufferBGRA buffer(windowSize.width, windowSize.height);
×
551
    glReadPixels(0, 0, windowSize.width, windowSize.height, GL_BGRA, GL_UNSIGNED_BYTE, buffer.getPixelPtr());
×
552
    flipVertical(buffer);
×
553
    const bfs::path outFilepath =
554
      RTTRCONFIG.ExpandPath(s25::folders::screenshots) / (s25util::Time::FormatTime("%Y-%m-%d_%H-%i-%s") + ".bmp");
×
555
    try
556
    {
557
        saveBitmap(buffer, outFilepath);
×
558
        LOG.write(_("Screenshot saved to %1%\n")) % outFilepath;
×
559
    } catch(const std::runtime_error& e)
×
560
    {
561
        LOG.write(_("Error writing screenshot: %1%\n")) % e.what();
×
562
    }
563
}
×
564

565
class WindowManager::Tooltip
566
{
567
    static constexpr unsigned BORDER_SIZE = 2;
568
    const ctrlBaseTooltip* showingCtrl;
569
    const glFont* font;
570
    std::vector<std::string> lines;
571
    unsigned width = 0, height = 0;
572
    unsigned short maxWidth;
573

574
public:
575
    Tooltip(const ctrlBaseTooltip* showingCtrl, const std::string& text, unsigned short maxWidth)
×
576
        : showingCtrl(showingCtrl), font(NormalFont), maxWidth(maxWidth)
×
577
    {
578
        setText(text);
×
579
    }
×
580

581
    auto getShowingCtrl() const { return showingCtrl; }
×
582
    auto getWidth() const { return width; }
×
583

584
    void setText(const std::string& text)
×
585
    {
586
        lines = font->GetWrapInfo(text, maxWidth, maxWidth).CreateSingleStrings(text);
×
587
        if(lines.empty())
×
588
            return;
×
589
        width = 0;
×
590
        for(const auto& line : lines)
×
591
            width = std::max(width, font->getWidth(line));
×
592
        width += BORDER_SIZE * 2;
×
593
        height = lines.size() * font->getHeight() + BORDER_SIZE * 2;
×
594
    }
595

596
    void draw(DrawPoint pos) const
×
597
    {
598
        Window::DrawRectangle(Rect(pos, width, height), 0x9F000000);
×
599
        pos += DrawPoint::all(BORDER_SIZE);
×
600
        const auto fontHeight = font->getHeight();
×
601
        for(const auto& line : lines)
×
602
        {
603
            font->Draw(pos, line, FontStyle::TOP, COLOR_YELLOW);
×
604
            pos.y += fontHeight;
×
605
        }
606
    }
×
607
};
608

609
void WindowManager::SetToolTip(const ctrlBaseTooltip* ttw, const std::string& tooltip, bool updateCurrent)
874✔
610
{
611
    // Max width of tooltip
612
    constexpr unsigned short MAX_TOOLTIP_WIDTH = 260;
874✔
613

614
    if(tooltip.empty())
874✔
615
    {
616
        if(curTooltip && (!ttw || curTooltip->getShowingCtrl() == ttw))
873✔
617
            curTooltip.reset();
×
618
    } else if(updateCurrent)
1✔
619
    {
620
        if(curTooltip && curTooltip->getShowingCtrl() == ttw)
1✔
621
            curTooltip->setText(tooltip);
×
622
    } else
623
        curTooltip = std::make_unique<Tooltip>(ttw, tooltip, MAX_TOOLTIP_WIDTH);
×
624
}
874✔
625

626
void WindowManager::DrawToolTip()
81✔
627
{
628
    // Tooltip zeichnen
629
    if(curTooltip && lastMousePos.isValid())
81✔
630
    {
631
        constexpr unsigned cursorWidth = 32;
×
632
        constexpr unsigned cursorPadding = 5;
×
633
        // Horizontal space between mouse position and tooltip border
634
        constexpr unsigned rightSpacing = cursorWidth + cursorPadding;
×
635
        constexpr unsigned leftSpacing = cursorPadding;
×
636
        DrawPoint ttPos = DrawPoint(lastMousePos.x + rightSpacing, lastMousePos.y);
×
637
        unsigned right_edge = ttPos.x + curTooltip->getWidth();
×
638

639
        // links neben der Maus, wenn es über den Rand gehen würde
640
        if(right_edge > curRenderSize.x)
×
641
            ttPos.x = lastMousePos.x - leftSpacing - curTooltip->getWidth();
×
642
        curTooltip->draw(ttPos);
×
643
    }
644
}
81✔
645

646
SnapOffset WindowManager::snapWindow(Window* wnd, const Rect& wndRect) const
8✔
647
{
648
    const auto snapDistance = static_cast<int>(SETTINGS.interface.windowSnapDistance);
8✔
649
    if(snapDistance == 0)
8✔
650
        return SnapOffset::all(0); // No snapping
×
651

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

657
    const auto setOffsetIfLowerDist = [](int a, int b, unsigned& minDist, int& minOffset) {
72✔
658
        const int offset = b - a;
72✔
659
        const unsigned dist = std::abs(offset);
72✔
660
        if(dist < minDist)
72✔
661
        {
662
            minDist = dist;
12✔
663
            minOffset = offset;
12✔
664
        }
665
    };
72✔
666
    const auto setOffsetIfLowerDistX = [&](int x1, int x2) { setOffsetIfLowerDist(x1, x2, minDist.x, minOffset.x); };
40✔
667
    const auto setOffsetIfLowerDistY = [&](int y1, int y2) { setOffsetIfLowerDist(y1, y2, minDist.y, minOffset.y); };
32✔
668

669
    for(const auto& curWnd : windows)
26✔
670
    {
671
        if(curWnd.get() == wnd)
18✔
672
            continue;
8✔
673

674
        const Rect curWndRect = curWnd->GetBoundaryRect();
10✔
675

676
        // Calculate smallest offset for each parallel pair of window edges, iff the windows overlap along the
677
        // orthogonal axis (± snap distance)
678
        if(wndRect.top <= (curWndRect.bottom + snapDistance) && wndRect.bottom >= (curWndRect.top - snapDistance))
10✔
679
        {
680
            setOffsetIfLowerDistX(wndRect.left, curWndRect.left);
10✔
681
            setOffsetIfLowerDistX(wndRect.left, curWndRect.right);
10✔
682
            setOffsetIfLowerDistX(wndRect.right, curWndRect.left);
10✔
683
            setOffsetIfLowerDistX(wndRect.right, curWndRect.right);
10✔
684
        }
685

686
        if(wndRect.left <= (curWndRect.right + snapDistance) && wndRect.right >= (curWndRect.left - snapDistance))
10✔
687
        {
688
            setOffsetIfLowerDistY(wndRect.top, curWndRect.top);
8✔
689
            setOffsetIfLowerDistY(wndRect.top, curWndRect.bottom);
8✔
690
            setOffsetIfLowerDistY(wndRect.bottom, curWndRect.top);
8✔
691
            setOffsetIfLowerDistY(wndRect.bottom, curWndRect.bottom);
8✔
692
        }
693
    }
694

695
    return minOffset;
8✔
696
}
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