1 #include <cppurses/terminal/input.hpp>
2 
3 #include <cstddef>
4 #include <memory>
5 #include <utility>
6 
7 #include <ncurses.h>
8 
9 #include <cppurses/system/detail/find_widget_at.hpp>
10 #include <cppurses/system/event.hpp>
11 #include <cppurses/system/events/key.hpp>
12 #include <cppurses/system/events/mouse.hpp>
13 #include <cppurses/system/events/resize_event.hpp>
14 #include <cppurses/system/focus.hpp>
15 #include <cppurses/system/shortcuts.hpp>
16 #include <cppurses/system/system.hpp>
17 #include <cppurses/widget/area.hpp>
18 #include <cppurses/widget/point.hpp>
19 #include <cppurses/widget/widget.hpp>
20 
21 namespace {
22 using namespace cppurses;
23 
24 /// Check if mouse_event is a button_mask type of event.
25 template <typename Mask_t>
is(Mask_t button_mask,const::MEVENT & mouse_event)26 auto is(Mask_t button_mask, const ::MEVENT& mouse_event) -> bool
27 {
28     return static_cast<bool>(mouse_event.bstate & button_mask);
29 }
30 
31 /// Extract the Event type and Mouse::Button from a given MEVENT object.
extract_info(const::MEVENT & mouse_event)32 auto extract_info(const ::MEVENT& mouse_event)
33     -> std::pair<Event::Type, Mouse::Button>
34 {
35     auto type_button = std::make_pair(Event::None, Mouse::Button::None);
36     auto& type       = type_button.first;
37     auto& button     = type_button.second;
38     // Button 1 / Left Button
39     if (is(BUTTON1_PRESSED, mouse_event)) {
40         type   = Event::MouseButtonPress;
41         button = Mouse::Button::Left;
42     }
43     else if (is(BUTTON1_RELEASED, mouse_event)) {
44         type   = Event::MouseButtonRelease;
45         button = Mouse::Button::Left;
46     }
47     // Button 2 / Middle Button
48     else if (is(BUTTON2_PRESSED, mouse_event)) {
49         type   = Event::MouseButtonPress;
50         button = Mouse::Button::Middle;
51     }
52     else if (is(BUTTON2_RELEASED, mouse_event)) {
53         type   = Event::MouseButtonRelease;
54         button = Mouse::Button::Middle;
55     }
56     // Button 3 / Right Button
57     else if (is(BUTTON3_PRESSED, mouse_event)) {
58         type   = Event::MouseButtonPress;
59         button = Mouse::Button::Right;
60     }
61     else if (is(BUTTON3_RELEASED, mouse_event)) {
62         type   = Event::MouseButtonRelease;
63         button = Mouse::Button::Right;
64     }
65     // Button 4 / Scroll Up
66     else if (is(BUTTON4_PRESSED, mouse_event)) {
67         type   = Event::MouseButtonPress;
68         button = Mouse::Button::ScrollUp;
69     }
70     else if (is(BUTTON4_RELEASED, mouse_event)) {
71         type   = Event::MouseButtonRelease;
72         button = Mouse::Button::ScrollUp;
73     }
74     // Button 5 / Scroll Down
75 #if defined(BUTTON5_PRESSED) && defined(BUTTON5_RELEASED)
76     else if (is(BUTTON5_PRESSED, mouse_event)) {
77         type   = Event::MouseButtonPress;
78         button = Mouse::Button::ScrollDown;
79     }
80     else if (is(BUTTON5_RELEASED, mouse_event)) {
81         type   = Event::MouseButtonRelease;
82         button = Mouse::Button::ScrollDown;
83     }
84 #endif
85     return type_button;
86 }
87 
make_mouse_event()88 auto make_mouse_event() -> std::unique_ptr<Event>
89 {
90     auto mouse_event = ::MEVENT{};
91     if (::getmouse(&mouse_event) != OK)
92         return nullptr;
93     Widget* receiver = detail::find_widget_at(mouse_event.x, mouse_event.y);
94     if (receiver == nullptr)
95         return nullptr;
96 
97     // Coordinates
98     const auto global = Point{static_cast<std::size_t>(mouse_event.x),
99                               static_cast<std::size_t>(mouse_event.y)};
100     const auto local =
101         Point{global.x - receiver->inner_x(), global.y - receiver->inner_y()};
102 
103     // Create Event
104     const auto type_button = extract_info(mouse_event);
105     const auto type        = type_button.first;
106     const auto button      = type_button.second;
107     if (type == Event::MouseButtonPress) {
108         return std::make_unique<Mouse::Press>(
109             *receiver, Mouse::State{button, global, local, mouse_event.id});
110     }
111     if (type == Event::MouseButtonRelease) {
112         return std::make_unique<Mouse::Release>(
113             *receiver, Mouse::State{button, global, local, mouse_event.id});
114     }
115     return nullptr;
116 }
117 
make_resize_event()118 auto make_resize_event() -> std::unique_ptr<Event>
119 {
120     Widget* const receiver = System::head();
121     if (receiver != nullptr) {
122         const auto width  = System::terminal.width();
123         const auto height = System::terminal.height();
124         return std::make_unique<Resize_event>(*receiver, Area{width, height});
125     }
126     return nullptr;
127 }
128 
make_keyboard_event(int input)129 auto make_keyboard_event(int input) -> std::unique_ptr<Event>
130 {
131     const auto code = static_cast<Key::Code>(input);
132     Widget* const receiver =
133         Shortcuts::send_key(code) ? nullptr : Focus::focus_widget();
134     return receiver == nullptr ? nullptr
135                                : std::make_unique<Key::Press>(*receiver, code);
136 }
137 }  // namespace
138 
139 namespace cppurses {
140 namespace input {
141 
get()142 auto get() -> std::unique_ptr<Event>
143 {
144     const auto input = ::getch();
145     switch (input) {
146         case ERR: return nullptr;  // Timeout and no event.
147         case KEY_MOUSE: return make_mouse_event();
148         case KEY_RESIZE: return make_resize_event();
149         default: return make_keyboard_event(input);  // Key_event
150     }
151 }
152 
153 }  // namespace input
154 }  // namespace cppurses
155