1/*
2 * Copyright (C) 2020-2022 The opuntiaOS Project Authors.
3 * + Contributed by Nikita Melekhin <nimelehin@gmail.com>
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9#include <libfoundation/EventLoop.h>
10#include <libg/Color.h>
11#include <libui/Context.h>
12#include <libui/View.h>
13
14namespace UI {
15
16View::View(View* superview, const LG::Rect& frame)
17 : m_frame(frame)
18 , m_superview(superview)
19 , m_window(superview ? superview->window() : nullptr)
20 , m_bounds(0, 0, frame.width(), frame.height())
21{
22}
23
24View::View(View* superview, Window* window, const LG::Rect& frame)
25 : m_frame(frame)
26 , m_superview(superview)
27 , m_window(window)
28 , m_bounds(0, 0, frame.width(), frame.height())
29{
30}
31
32void View::remove_from_superview()
33{
34}
35
36std::optional<View*> View::subview_at(const LG::Point<int>& point) const
37{
38 for (int i = subviews().size() - 1; i >= 0; --i) {
39 if (subviews()[i]->frame().contains(point)) {
40 return subviews()[i];
41 }
42 }
43 return {};
44}
45
46View& View::hit_test(const LG::Point<int>& point)
47{
48 auto subview = subview_at(point);
49 if (subview.has_value()) {
50 return subview.value()->hit_test(point - subview.value()->frame().origin());
51 }
52 return *this;
53}
54
55LG::Rect View::frame_in_window()
56{
57 View* view = this;
58 auto rect = frame();
59 while (view->has_superview()) {
60 view = view->superview();
61 rect.offset_by(view->frame().origin());
62 }
63 return rect;
64}
65
66void View::layout_subviews()
67{
68 // TODO: Apply topsort to find the right order.
69 for (const auto& constraint : m_constrints) {
70 constraint_interpreter(constraint);
71 }
72}
73
74std::optional<LG::Point<int>> View::subview_location(const View& subview) const
75{
76 return subview.frame().origin();
77}
78
79void View::set_needs_display(const LG::Rect& rect)
80{
81 auto display_rect = rect;
82 display_rect.intersect(bounds());
83 if (has_superview()) {
84 auto location = superview()->subview_location(*this);
85 display_rect.offset_by(location.value());
86 superview()->set_needs_display(display_rect);
87 } else {
88 send_display_message_to_self(*window(), display_rect);
89 }
90}
91
92void View::display(const LG::Rect& rect)
93{
94 LG::Context ctx = graphics_current_context();
95 ctx.set_fill_color(background_color());
96 ctx.fill_rounded(rect, layer().corner_mask());
97}
98
99void View::did_display(const LG::Rect& rect)
100{
101}
102
103void View::mouse_moved(const LG::Point<int>& location)
104{
105}
106
107void View::mouse_entered(const LG::Point<int>& location)
108{
109 set_hovered(true);
110 set_needs_display();
111}
112
113void View::mouse_exited()
114{
115 set_hovered(false);
116 set_active(false);
117 set_needs_display();
118}
119
120void View::mouse_down(const LG::Point<int>& location)
121{
122 m_active = true;
123 set_needs_display();
124}
125
126void View::mouse_up()
127{
128 m_active = false;
129 set_needs_display();
130}
131
132void View::mouse_wheel_event(int wheel_data)
133{
134}
135
136void View::receive_mouse_move_event(MouseEvent& event)
137{
138 auto location = LG::Point<int>(event.x(), event.y());
139 if (!is_hovered()) {
140 mouse_entered(location);
141 }
142
143 foreach_subview([&](View& subview) -> bool {
144 bool event_hits_subview = subview.frame().contains(event.x(), event.y());
145 if (subview.is_hovered() && !event_hits_subview) {
146 LG::Point<int> point(event.x(), event.y());
147 point.offset_by(-subview.frame().origin());
148 MouseLeaveEvent mle(point.x(), point.y());
149 subview.receive_mouse_leave_event(mle);
150 } else if (event_hits_subview) {
151 LG::Point<int> point(event.x(), event.y());
152 point.offset_by(-subview.frame().origin());
153 MouseEvent me(point.x(), point.y());
154 subview.receive_mouse_move_event(me);
155 }
156 return true;
157 });
158
159 mouse_moved(location);
160 Responder::receive_mouse_move_event(event);
161}
162
163void View::receive_mouse_action_event(MouseActionEvent& event)
164{
165 if (event.type() == MouseActionType::LeftMouseButtonPressed) {
166 mouse_down(LG::Point<int>(event.x(), event.y()));
167 } else if (event.type() == MouseActionType::LeftMouseButtonReleased) {
168 mouse_up();
169 }
170
171 Responder::receive_mouse_action_event(event);
172}
173
174void View::receive_mouse_leave_event(MouseLeaveEvent& event)
175{
176 if (!is_hovered()) {
177 return;
178 }
179
180 foreach_subview([&](View& subview) -> bool {
181 if (subview.is_hovered()) {
182 LG::Point<int> point(event.x(), event.y());
183 point.offset_by(-subview.frame().origin());
184 MouseLeaveEvent mle(point.x(), point.y());
185 subview.receive_mouse_leave_event(mle);
186 }
187 return true;
188 });
189
190 mouse_exited();
191 Responder::receive_mouse_leave_event(event);
192}
193
194void View::receive_mouse_wheel_event(MouseWheelEvent& event)
195{
196 bool found = false;
197 foreach_subview([&](View& subview) -> bool {
198 if (subview.is_hovered()) {
199 LG::Point<int> point(event.x(), event.y());
200 point.offset_by(-subview.frame().origin());
201 MouseWheelEvent mwe(point.x(), point.y(), event.wheel_data());
202 subview.receive_mouse_wheel_event(mwe);
203 found = true;
204 }
205 return true;
206 });
207
208 if (!found) {
209 mouse_wheel_event(event.wheel_data());
210 }
211}
212
213void View::receive_keyup_event(KeyUpEvent&)
214{
215}
216
217void View::receive_keydown_event(KeyDownEvent&)
218{
219}
220
221void View::receive_display_event(DisplayEvent& event)
222{
223 event.bounds().intersect(bounds());
224 display(event.bounds());
225 foreach_subview([&](View& subview) -> bool {
226 auto bounds = event.bounds();
227 if (bounds.intersects(subview.frame())) {
228 graphics_push_context(Context(subview, Context::RelativeToCurrentContext::Yes));
229 bounds.offset_by(-subview.frame().origin());
230 DisplayEvent own_event(bounds);
231 subview.receive_display_event(own_event);
232 graphics_pop_context();
233 }
234 return true;
235 });
236 did_display(event.bounds());
237
238 if (!has_superview()) {
239 // Only superview sends invalidate_message to server.
240 bool success = send_invalidate_message_to_server(event.bounds());
241 }
242
243 Responder::receive_display_event(event);
244}
245
246bool View::receive_layout_event(const LayoutEvent& event, bool force_layout_if_not_target)
247{
248 bool need_to_layout = (this == event.target()) | force_layout_if_not_target;
249 if (need_to_layout) {
250 layout_subviews();
251 }
252
253 foreach_subview([&](View& subview) -> bool {
254 bool found_target = subview.receive_layout_event(event, need_to_layout);
255 return need_to_layout || !found_target;
256 });
257
258 return need_to_layout;
259}
260
261} // namespace UI