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

KazDragon / munin / 34351217336

09 Sep 2026 12:28PM UTC coverage: 97.988% (-0.2%) from 98.216%
34351217336

Pull #285

github

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

176 of 185 new or added lines in 16 files covered. (95.14%)

2776 of 2833 relevant lines covered (97.99%)

476.73 hits per line

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

94.92
/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)
20 ✔
18
    {
19
    }
20 ✔
20

21
    void operator()(
20 ✔
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(
20 ✔
27
            beh.supports_basic_mouse_tracking
20 ✔
28
            && beh.supports_sgr_mouse_encoding);
20 ✔
29
    }
20 ✔
30

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

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

41
auto is_button_up(terminalpp::mouse::event const &ev) -> bool
6 ✔
42
{
43
    return ev.action_ == terminalpp::mouse::event_type::button_up;
6 ✔
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(
13 ✔
54
    mouse_event_type action,
55
    terminalpp::mouse::event const &ev) -> munin::mouse_event
56
{
57
    return munin::mouse_event{
58
        .action_ = action,
59
        .position_ = ev.position_,
60
        .button_ = ev.button_,
13 ✔
61
        .modifiers_ = ev.modifiers_};
13 ✔
62
}
63

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

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

88
}  // namespace
89

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

99
// ==========================================================================
100
// CONSTRUCTOR
101
// ==========================================================================
102
window::window(
20 ✔
103
    terminalpp::terminal &terminal,
104
    std::shared_ptr<component> content,
105
    render_surface_capabilities const &capabilities)
20 ✔
106
  : content_(std::move(content)), screen_{terminal}, capabilities_(capabilities)
20 ✔
107
{
108
    terminal << inspect_mouse_capabilities{event_context_};
20 ✔
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);
20 ✔
123
}
20 ✔
124

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

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

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

160
            if (captured_component)
3 ✔
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
            {
NEW
167
                content_->event(ev, event_context_);
×
NEW
168
                content_->event(
×
NEW
169
                    derived_mouse_event(mouse_event_type::button_up, *mouse_ev),
×
NEW
170
                    event_context_);
×
171
            }
172

173
            if (event_context_.has_click_interest() && captured_component
5 ✔
174
                && is_inside(
5 ✔
175
                    local_mouse_event.position_,
176
                    captured_component->get_size()))
5 ✔
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();
3 ✔
186
            has_mouse_capture_ = false;
3 ✔
187
            return;
3 ✔
188
        }
3 ✔
189

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

197
            if (captured_component)
3 ✔
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
            {
NEW
220
                content_->event(ev, event_context_);
×
221
            }
222

223
            return;
3 ✔
224
        }
3 ✔
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