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

KazDragon / munin / 34355867569

09 Sep 2026 01:14PM UTC coverage: 98.269% (+0.05%) from 98.216%
34355867569

Pull #285

github

web-flow
Merge b9e100499 into f24662279
Pull Request #285: Better mouse handling

181 of 182 new or added lines in 16 files covered. (99.45%)

2781 of 2830 relevant lines covered (98.27%)

477.32 hits per line

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

99.15
/src/window.cpp
1
#include "munin/window.hpp"
2

3
#include "munin/mouse_event.hpp"
4
#include "munin/render_surface.hpp"
5

6
#include <terminalpp/behaviour.hpp>
7
#include <terminalpp/mouse.hpp>
8
#include <terminalpp/terminal.hpp>
9

10
namespace munin {
11

12
namespace {
13

14
class inspect_mouse_capabilities
15
{
16
public:
17
    explicit inspect_mouse_capabilities(event_context &ctx) : context_(ctx)
22 ✔
18
    {
19
    }
22 ✔
20

21
    void operator()(
22 ✔
22
        terminalpp::behaviour const &beh,
23
        terminalpp::terminal_state &state,
24
        terminalpp::terminal::write_function const &write_fn) const
25
    {
26
        context_.set_click_synthesis_supported(
22 ✔
27
            beh.supports_basic_mouse_tracking
22 ✔
28
            && beh.supports_sgr_mouse_encoding);
22 ✔
29
    }
22 ✔
30

31
private:
32
    event_context &context_;
33
};
34

35
auto is_left_button_down(terminalpp::mouse::event const &ev) -> bool
15 ✔
36
{
37
    return ev.action_ == terminalpp::mouse::event_type::left_button_down
15 ✔
38
        && !ev.is_motion_ && !ev.is_release_;
15 ✔
39
}
40

41
auto is_button_up(terminalpp::mouse::event const &ev) -> bool
8 ✔
42
{
43
    return ev.action_ == terminalpp::mouse::event_type::button_up;
8 ✔
44
}
45

46
auto is_inside(terminalpp::point const &point, terminalpp::extent const &size)
5 ✔
47
    -> bool
48
{
49
    return point.x_ >= 0 && point.x_ < size.width_ && point.y_ >= 0
5 ✔
50
        && point.y_ < size.height_;
10 ✔
51
}
52

53
auto derived_mouse_event(
17 ✔
54
    mouse_event_type action, terminalpp::mouse::event const &ev)
55
    -> munin::mouse_event
56
{
57
    return munin::mouse_event{
58
        .action_ = action,
59
        .position_ = ev.position_,
60
        .button_ = ev.button_,
17 ✔
61
        .modifiers_ = ev.modifiers_};
17 ✔
62
}
63

64
auto translate(
8 ✔
65
    terminalpp::mouse::event const &ev, terminalpp::point const &origin)
66
    -> terminalpp::mouse::event
67
{
68
    return terminalpp::mouse::event{
69
        .action_ = ev.action_,
8 ✔
70
        .position_ = ev.position_ - origin,
8 ✔
71
        .button_ = ev.button_,
8 ✔
72
        .button_code_ = ev.button_code_,
73
        .modifiers_ = ev.modifiers_,
8 ✔
74
        .is_motion_ = ev.is_motion_,
8 ✔
75
        .is_release_ = ev.is_release_};
8 ✔
76
}
77

78
auto translate(munin::mouse_event const &ev, terminalpp::point const &origin)
9 ✔
79
    -> munin::mouse_event
80
{
81
    return munin::mouse_event{
82
        .action_ = ev.action_,
9 ✔
83
        .position_ = ev.position_ - origin,
9 ✔
84
        .button_ = ev.button_,
9 ✔
85
        .modifiers_ = ev.modifiers_};
9 ✔
86
}
87

88
}  // namespace
89

90
// ==========================================================================
91
// CONSTRUCTOR
92
// ==========================================================================
93
window::window(
21 ✔
94
    terminalpp::terminal &terminal, std::shared_ptr<component> content)
21 ✔
95
  : window(terminal, std::move(content), default_capabilities)
21 ✔
96
{
97
}
21 ✔
98

99
// ==========================================================================
100
// CONSTRUCTOR
101
// ==========================================================================
102
window::window(
22 ✔
103
    terminalpp::terminal &terminal,
104
    std::shared_ptr<component> content,
105
    render_surface_capabilities const &capabilities)
22 ✔
106
  : content_(std::move(content)), screen_{terminal}, capabilities_(capabilities)
22 ✔
107
{
108
    terminal << inspect_mouse_capabilities{event_context_};
22 ✔
109

110
    auto const &request_repaint = [this](auto const &regions) {
10 ✔
111
        bool first_request = repaint_regions_.empty();
10 ✔
112

113
        repaint_regions_.insert(
20 ✔
114
            repaint_regions_.end(), regions.begin(), regions.end());
10 ✔
115

116
        if (first_request)
10 ✔
117
        {
118
            this->on_repaint_request();
9 ✔
119
        }
120
    };
10 ✔
121

122
    content_->on_redraw.connect(request_repaint);
22 ✔
123
}
22 ✔
124

125
// ==========================================================================
126
// DESTRUCTOR
127
// ==========================================================================
128
window::~window() = default;
22 ✔
129

130
// ==========================================================================
131
// EVENT
132
// ==========================================================================
133
void window::event(std::any const &ev)
17 ✔
134
{
135
    if (auto const *mouse_ev = std::any_cast<terminalpp::mouse::event>(&ev);
17 ✔
136
        mouse_ev != nullptr)
137
    {
138
        if (is_left_button_down(*mouse_ev))
15 ✔
139
        {
140
            event_context_.reset_mouse_dispatch_state();
7 ✔
141
            has_mouse_capture_ = true;
7 ✔
142
            event_context_.capture_mouse(content_, {});
7 ✔
143
            content_->event(ev, event_context_);
7 ✔
144
            content_->event(
14 ✔
NEW
145
                derived_mouse_event(mouse_event_type::button_down, *mouse_ev),
×
146
                event_context_);
7 ✔
147
            return;
7 ✔
148
        }
149

150
        if (has_mouse_capture_ && is_button_up(*mouse_ev))
8 ✔
151
        {
152
            auto captured_component = event_context_.captured_component();
4 ✔
153
            auto const captured_origin = event_context_.captured_origin();
4 ✔
154
            auto const local_mouse_event =
155
                translate(*mouse_ev, captured_origin);
4 ✔
156
            auto const local_button_up = translate(
4 ✔
157
                derived_mouse_event(mouse_event_type::button_up, *mouse_ev),
4 ✔
158
                captured_origin);
159

160
            if (captured_component)
4 ✔
161
            {
162
                captured_component->event(local_mouse_event, event_context_);
3 ✔
163
                captured_component->event(local_button_up, event_context_);
3 ✔
164
            }
165
            else
166
            {
167
                content_->event(ev, event_context_);
1 ✔
168
                content_->event(
2 ✔
169
                    derived_mouse_event(mouse_event_type::button_up, *mouse_ev),
1 ✔
170
                    event_context_);
1 ✔
171
            }
172

173
            if (event_context_.has_click_interest() && captured_component
6 ✔
174
                && is_inside(
6 ✔
175
                    local_mouse_event.position_,
176
                    captured_component->get_size()))
6 ✔
177
            {
178
                captured_component->event(
4 ✔
179
                    translate(
2 ✔
180
                        derived_mouse_event(mouse_event_type::click, *mouse_ev),
2 ✔
181
                        captured_origin),
182
                    event_context_);
2 ✔
183
            }
184

185
            event_context_.reset_mouse_dispatch_state();
4 ✔
186
            has_mouse_capture_ = false;
4 ✔
187
            return;
4 ✔
188
        }
4 ✔
189

190
        if (has_mouse_capture_)
4 ✔
191
        {
192
            auto captured_component = event_context_.captured_component();
4 ✔
193
            auto const captured_origin = event_context_.captured_origin();
4 ✔
194
            auto const local_mouse_event =
195
                translate(*mouse_ev, captured_origin);
4 ✔
196

197
            if (captured_component)
4 ✔
198
            {
199
                captured_component->event(local_mouse_event, event_context_);
3 ✔
200

201
                auto const is_now_inside = is_inside(
3 ✔
202
                    local_mouse_event.position_,
203
                    captured_component->get_size());
3 ✔
204

205
                if (is_now_inside != event_context_.captured_mouse_inside())
3 ✔
206
                {
207
                    captured_component->event(
6 ✔
208
                        translate(
3 ✔
209
                            derived_mouse_event(
3 ✔
210
                                is_now_inside ? mouse_event_type::in
211
                                              : mouse_event_type::out,
212
                                *mouse_ev),
213
                            captured_origin),
214
                        event_context_);
3 ✔
215
                    event_context_.set_captured_mouse_inside(is_now_inside);
3 ✔
216
                }
217
            }
218
            else
219
            {
220
                content_->event(ev, event_context_);
1 ✔
221
            }
222

223
            return;
4 ✔
224
        }
4 ✔
225
    }
226

227
    content_->event(ev, event_context_);
2 ✔
228
}
229

230
// ==========================================================================
231
// REPAINT
232
// ==========================================================================
233
void window::repaint(terminalpp::canvas &cvs)
15 ✔
234
{
235
    auto const canvas_size = cvs.size();
15 ✔
236

237
    std::vector<terminalpp::rectangle> repaint_regions;
15 ✔
238

239
    if (cvs.size() != content_->get_size())
15 ✔
240
    {
241
        content_->set_size(cvs.size());
10 ✔
242
        repaint_regions.clear();
10 ✔
243
        repaint_regions.push_back({{}, canvas_size});
10 ✔
244
    }
245
    else
246
    {
247
        repaint_regions.swap(repaint_regions_);
5 ✔
248
    }
249

250
    render_surface surface(cvs, capabilities_);
15 ✔
251
    for (auto const &region : repaint_regions)
30 ✔
252
    {
253
        content_->draw(surface, region);
15 ✔
254
    }
255

256
    screen_.draw(cvs);
15 ✔
257
}
15 ✔
258

259
// ==========================================================================
260
// TO_JSON
261
// ==========================================================================
262
nlohmann::json window::to_json() const
1 ✔
263
{
264
    return {
265
        {"type",    "window"           },
266
        {"content", content_->to_json()}
2 ✔
267
    };
8 ✔
268
}
8 ✔
269

270
}  // namespace munin
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc