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/Memory.h>
10#include <libui/App.h>
11#include <libui/Connection.h>
12#include <libui/Context.h>
13#include <libui/Window.h>
14
15namespace UI {
16
17Window::Window(const std::string& title, const LG::Size& size, WindowType type)
18 : m_bounds(0, 0, size.width(), size.height())
19 , m_buffer(size_t(size.width() * size.height()))
20 , m_bitmap()
21 , m_title(title)
22 , m_type(type)
23 , m_status_bar_style()
24{
25 m_id = Connection::the().new_window(*this);
26 m_menubar.set_host_window_id(m_id);
27 m_popup.set_host_window_id(m_id);
28 m_bitmap = LG::PixelBitmap(m_buffer.data(), bounds().width(), bounds().height());
29 App::the().set_window(this);
30}
31
32Window::Window(const std::string& title, const LG::Size& size, const std::string& icon_path)
33 : m_bounds(0, 0, size.width(), size.height())
34 , m_buffer(size_t(size.width() * size.height()))
35 , m_bitmap()
36 , m_title(title)
37 , m_icon_path(icon_path)
38 , m_status_bar_style()
39{
40 m_id = Connection::the().new_window(*this);
41 m_menubar.set_host_window_id(m_id);
42 m_popup.set_host_window_id(m_id);
43 m_bitmap = LG::PixelBitmap(m_buffer.data(), bounds().width(), bounds().height());
44 App::the().set_window(this);
45}
46
47Window::Window(const std::string& title, const LG::Size& size, const std::string& icon_path, const StatusBarStyle& style)
48 : m_bounds(0, 0, size.width(), size.height())
49 , m_buffer(size_t(size.width() * size.height()))
50 , m_bitmap()
51 , m_title(title)
52 , m_icon_path(icon_path)
53 , m_status_bar_style(style)
54{
55 m_id = Connection::the().new_window(*this);
56 m_menubar.set_host_window_id(m_id);
57 m_popup.set_host_window_id(m_id);
58 m_bitmap = LG::PixelBitmap(m_buffer.data(), bounds().width(), bounds().height());
59 App::the().set_window(this);
60}
61
62bool Window::set_title(const std::string& title)
63{
64 m_title = title;
65 SetTitleMessage msg(Connection::the().key(), id(), title);
66 return App::the().connection().send_async_message(msg);
67}
68
69bool Window::set_status_bar_style(StatusBarStyle style)
70{
71 m_status_bar_style = style;
72 SetBarStyleMessage msg(Connection::the().key(), id(), style.color().u32(), style.flags());
73 return App::the().connection().send_async_message(msg);
74}
75
76bool Window::did_buffer_change()
77{
78 m_bitmap.set_data(buffer().data());
79 m_bitmap.set_size({ bounds().width(), bounds().height() });
80
81 // If we have a superview, we also have a context for the superview.
82 // This context should be updated, since the superview is resized.
83 if (m_superview) {
84 graphics_pop_context();
85 graphics_push_context(Context(*m_superview));
86 }
87
88 SetBufferMessage msg(Connection::the().key(), id(), buffer().id(), bitmap().format(), bounds());
89 return App::the().connection().send_async_message(msg);
90}
91
92bool Window::did_format_change()
93{
94 if (bitmap().format() == LG::PixelBitmapFormat::RGBA) {
95 // Set full bitmap as opaque, to mix colors correctly.
96 fill_with_opaque(bounds());
97 }
98
99 return did_buffer_change();
100}
101
102void Window::receive_event(std::unique_ptr<LFoundation::Event> event)
103{
104 if (event->type() == Event::Type::MouseEvent) {
105 if (m_superview) {
106 MouseEvent& own_event = *(MouseEvent*)event.get();
107 m_superview->receive_mouse_move_event(own_event);
108 }
109 }
110
111 if (event->type() == Event::Type::MouseActionEvent) {
112 if (m_superview) {
113 MouseActionEvent& own_event = *(MouseActionEvent*)event.get();
114 auto& view = m_superview->hit_test({ (int)own_event.x(), (int)own_event.y() });
115 view.receive_mouse_action_event(own_event);
116 }
117 }
118
119 if (event->type() == Event::Type::MouseLeaveEvent) {
120 if (m_superview) {
121 MouseLeaveEvent& own_event = *(MouseLeaveEvent*)event.get();
122 m_superview->receive_mouse_leave_event(own_event);
123 }
124 }
125
126 if (event->type() == Event::Type::MouseWheelEvent) {
127 if (m_superview) {
128 MouseWheelEvent& own_event = *(MouseWheelEvent*)event.get();
129 m_superview->receive_mouse_wheel_event(own_event);
130 }
131 }
132
133 if (event->type() == Event::Type::KeyUpEvent) {
134 if (m_focused_view) {
135 KeyUpEvent& own_event = *(KeyUpEvent*)event.get();
136 m_focused_view->receive_keyup_event(own_event);
137 }
138 }
139
140 if (event->type() == Event::Type::KeyDownEvent) {
141 if (m_focused_view) {
142 KeyDownEvent& own_event = *(KeyDownEvent*)event.get();
143 m_focused_view->receive_keydown_event(own_event);
144 }
145 }
146
147 if (event->type() == Event::Type::DisplayEvent) {
148 if (m_superview) {
149 DisplayEvent& own_event = *(DisplayEvent*)event.get();
150
151 // If the window is in RGBA mode, we have to fill this rect
152 // with opaque color before superview will mix it's color on
153 // top of bitmap.
154 if (bitmap().format() == LG::PixelBitmapFormat::RGBA) {
155 fill_with_opaque(own_event.bounds());
156 }
157
158 m_superview->receive_display_event(own_event);
159 }
160 }
161
162 if (event->type() == Event::Type::LayoutEvent) {
163 if (m_superview) {
164 LayoutEvent& own_event = *(LayoutEvent*)event.get();
165 m_superview->receive_layout_event(own_event);
166 }
167 }
168
169 if (event->type() == Event::Type::MenuBarActionEvent) {
170 MenuBarActionEvent& own_event = *(MenuBarActionEvent*)event.get();
171 for (Menu& menu : menubar().menus()) {
172 if (menu.menu_id() == own_event.menu_id()) {
173 if (own_event.item_id() < menu.items().size()) [[likely]] {
174 menu.items()[own_event.item_id()].invoke();
175 }
176 }
177 }
178 }
179
180 if (event->type() == Event::Type::PopupActionEvent) {
181 PopupActionEvent& own_event = *(PopupActionEvent*)event.get();
182 auto& menu = popup_manager().menu();
183 if (own_event.item_id() < menu.items().size()) [[likely]] {
184 menu.items()[own_event.item_id()].invoke();
185 }
186 }
187
188 if (event->type() == Event::Type::ResizeEvent) {
189 ResizeEvent& own_event = *(ResizeEvent*)event.get();
190 resize(own_event);
191 }
192}
193
194void Window::resize(ResizeEvent& resize_event)
195{
196 m_bounds = resize_event.bounds();
197
198 if (m_superview) [[likely]] {
199 m_superview->frame() = resize_event.bounds();
200 m_superview->bounds() = resize_event.bounds();
201 m_superview->set_needs_layout();
202 }
203
204 size_t new_size = resize_event.bounds().width() * resize_event.bounds().height();
205 if (m_buffer.size() != new_size) [[likely]] {
206 m_buffer.resize(resize_event.bounds().width() * resize_event.bounds().height());
207 did_buffer_change();
208 }
209}
210
211void Window::setup_superview()
212{
213 graphics_push_context(Context(*m_superview));
214}
215
216void Window::fill_with_opaque(const LG::Rect& rect)
217{
218 const auto color = LG::Color(0, 0, 0, 0).u32();
219 auto draw_bounds = rect;
220 draw_bounds.intersect(bounds());
221 if (draw_bounds.empty()) {
222 return;
223 }
224
225 int min_x = draw_bounds.min_x();
226 int min_y = draw_bounds.min_y();
227 int max_x = draw_bounds.max_x();
228 int max_y = draw_bounds.max_y();
229 int len_x = max_x - min_x + 1;
230 for (int y = min_y; y <= max_y; y++) {
231 LFoundation::fast_set((uint32_t*)&m_bitmap[y][min_x], color, len_x);
232 }
233}
234
235} // namespace UI