Initial commit
This commit is contained in:
19
LibGUI/CMakeLists.txt
Normal file
19
LibGUI/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
set(LIBGUI_SOURCES
|
||||
MessageBox.cpp
|
||||
Texture.cpp
|
||||
Widget/Button.cpp
|
||||
Widget/Grid.cpp
|
||||
Widget/Label.cpp
|
||||
Widget/RoundedWidget.cpp
|
||||
Widget/TextArea.cpp
|
||||
Widget/Widget.cpp
|
||||
Window.cpp
|
||||
)
|
||||
|
||||
add_library(libgui ${LIBGUI_SOURCES})
|
||||
banan_link_library(libgui ban)
|
||||
banan_link_library(libgui libfont)
|
||||
banan_link_library(libgui libinput)
|
||||
|
||||
banan_install_headers(libgui)
|
||||
install(TARGETS libgui OPTIONAL)
|
||||
67
LibGUI/MessageBox.cpp
Normal file
67
LibGUI/MessageBox.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include <LibGUI/MessageBox.h>
|
||||
#include <LibGUI/Window.h>
|
||||
#include <LibGUI/Widget/Button.h>
|
||||
#include <LibGUI/Widget/Grid.h>
|
||||
#include <LibGUI/Widget/TextArea.h>
|
||||
#include <LibGUI/Widget/Widget.h>
|
||||
|
||||
namespace LibGUI
|
||||
{
|
||||
|
||||
BAN::ErrorOr<void> MessageBox::create(BAN::StringView message, BAN::StringView title)
|
||||
{
|
||||
BAN::StringView ok_button = "OK";
|
||||
TRY(create(message, title, { &ok_button, 1 }));
|
||||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<size_t> MessageBox::create(BAN::StringView message, BAN::StringView title, BAN::Span<BAN::StringView> buttons)
|
||||
{
|
||||
if (buttons.empty())
|
||||
return BAN::Error::from_errno(EINVAL);
|
||||
|
||||
const uint32_t window_width = 300;
|
||||
|
||||
auto root_widget = TRY(Widget::Widget::create({}, 0xFFFFFF, { 0, 0, window_width, 0 }));
|
||||
|
||||
auto text_area = TRY(Widget::TextArea::create(root_widget, message, { 0, 0, window_width, 0}));
|
||||
text_area->style().border_width = 0;
|
||||
text_area->style().color_normal = Widget::Widget::color_invisible;
|
||||
text_area->style().corner_radius = 0;
|
||||
TRY(text_area->set_relative_geometry({ 0.0, 0.0, 1.0, 0.8 }));
|
||||
text_area->show();
|
||||
|
||||
bool waiting = true;
|
||||
size_t result = 0;
|
||||
|
||||
auto button_area = TRY(Widget::Grid::create(root_widget, buttons.size(), 1));
|
||||
for (size_t i = 0; i < buttons.size(); i++)
|
||||
{
|
||||
auto button = TRY(Widget::Button::create(button_area, buttons[i]));
|
||||
TRY(button_area->set_widget_position(button, i, 1, 0, 1));
|
||||
button->set_click_callback([&result, &waiting, i] { result = i; waiting = false; });
|
||||
button->show();
|
||||
}
|
||||
TRY(button_area->set_relative_geometry({ 0.0, 0.8, 1.0, 0.2 }));
|
||||
button_area->show();
|
||||
|
||||
const uint32_t button_height = 20;
|
||||
const uint32_t window_height = text_area->get_required_height() + button_height;
|
||||
|
||||
auto attributes = Window::default_attributes;
|
||||
attributes.resizable = true;
|
||||
|
||||
auto window = TRY(Window::create(window_width, window_height, title, attributes));
|
||||
TRY(window->set_root_widget(root_widget));
|
||||
window->set_close_window_event_callback([&waiting] { waiting = false; });
|
||||
|
||||
while (waiting)
|
||||
{
|
||||
window->wait_events();
|
||||
window->poll_events();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
258
LibGUI/Texture.cpp
Normal file
258
LibGUI/Texture.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
#include <LibGUI/Texture.h>
|
||||
#include <LibFont/Font.h>
|
||||
|
||||
namespace LibGUI
|
||||
{
|
||||
|
||||
BAN::ErrorOr<Texture> Texture::create(uint32_t width, uint32_t height, uint32_t color)
|
||||
{
|
||||
if (BAN::Math::will_addition_overflow(width, height))
|
||||
return BAN::Error::from_errno(EOVERFLOW);
|
||||
|
||||
BAN::Vector<uint32_t> pixels;
|
||||
TRY(pixels.resize(width * height, color));
|
||||
return Texture(BAN::move(pixels), width, height, color);
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Texture::resize(uint32_t new_width, uint32_t new_height)
|
||||
{
|
||||
if (BAN::Math::will_addition_overflow(new_width, new_height))
|
||||
return BAN::Error::from_errno(EOVERFLOW);
|
||||
|
||||
BAN::Vector<uint32_t> pixels;
|
||||
TRY(pixels.resize(new_width * new_height, m_bg_color));
|
||||
|
||||
const uint32_t max_x = BAN::Math::min(new_width, m_width);
|
||||
const uint32_t max_y = BAN::Math::min(new_height, m_height);
|
||||
for (uint32_t y = 0; y < max_y; y++)
|
||||
for (uint32_t x = 0; x < max_x; x++)
|
||||
pixels[y * new_width + x] = m_pixels[y * m_width + x];
|
||||
|
||||
m_width = new_width;
|
||||
m_height = new_height;
|
||||
m_pixels = BAN::move(pixels);
|
||||
|
||||
if (m_has_set_clip)
|
||||
set_clip_area(m_clip_x, m_clip_y, m_clip_w, m_clip_h);
|
||||
else
|
||||
{
|
||||
m_clip_w = new_width;
|
||||
m_clip_h = new_height;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void Texture::set_clip_area(int32_t x, int32_t y, uint32_t width, uint32_t height)
|
||||
{
|
||||
m_clip_x = 0;
|
||||
m_clip_y = 0;
|
||||
m_clip_w = this->width();
|
||||
m_clip_h = this->height();
|
||||
|
||||
|
||||
if (!clamp_to_texture(x, y, width, height))
|
||||
{
|
||||
m_clip_h = 0;
|
||||
m_clip_w = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_clip_x = x;
|
||||
m_clip_y = y;
|
||||
m_clip_w = width;
|
||||
m_clip_h = height;
|
||||
}
|
||||
|
||||
m_has_set_clip = true;
|
||||
}
|
||||
|
||||
void Texture::fill_rect(int32_t x, int32_t y, uint32_t width, uint32_t height, uint32_t color)
|
||||
{
|
||||
if (!clamp_to_texture(x, y, width, height))
|
||||
return;
|
||||
for (uint32_t y_off = 0; y_off < height; y_off++)
|
||||
for (uint32_t x_off = 0; x_off < width; x_off++)
|
||||
set_pixel(x + x_off, y + y_off, color);
|
||||
}
|
||||
|
||||
void Texture::copy_texture(const Texture& texture, int32_t x, int32_t y, uint32_t sub_x, uint32_t sub_y, uint32_t width, uint32_t height)
|
||||
{
|
||||
int32_t src_x = sub_x, src_y = sub_y;
|
||||
if (!clamp_to_texture(x, y, src_x, src_y, width, height, texture))
|
||||
return;
|
||||
sub_x = src_x;
|
||||
sub_y = src_y;
|
||||
|
||||
for (uint32_t y_off = 0; y_off < height; y_off++)
|
||||
for (uint32_t x_off = 0; x_off < width; x_off++)
|
||||
if (const uint32_t color = texture.get_pixel(sub_x + x_off, sub_y + y_off); color != color_invisible)
|
||||
set_pixel(x + x_off, y + y_off, color);
|
||||
}
|
||||
|
||||
void Texture::draw_character(uint32_t codepoint, const LibFont::Font& font, int32_t tl_x, int32_t tl_y, uint32_t color)
|
||||
{
|
||||
if (tl_y + (int32_t)font.height() < 0 || tl_y >= (int32_t)height())
|
||||
return;
|
||||
if (tl_x + (int32_t)font.width() < 0 || tl_x >= (int32_t)width())
|
||||
return;
|
||||
|
||||
auto glyph = font.glyph(codepoint);
|
||||
if (glyph == nullptr)
|
||||
return;
|
||||
|
||||
for (int32_t off_y = 0; off_y < (int32_t)font.height(); off_y++)
|
||||
{
|
||||
if (tl_y + off_y < 0)
|
||||
continue;
|
||||
uint32_t abs_y = tl_y + off_y;
|
||||
if (abs_y >= height())
|
||||
break;
|
||||
for (int32_t off_x = 0; off_x < (int32_t)font.width(); off_x++)
|
||||
{
|
||||
if (tl_x + off_x < 0)
|
||||
continue;
|
||||
uint32_t abs_x = tl_x + off_x;
|
||||
if (abs_x >= width())
|
||||
break;
|
||||
const uint8_t bitmask = 1 << (font.width() - off_x - 1);
|
||||
if (glyph[off_y * font.pitch()] & bitmask)
|
||||
set_pixel(abs_x, abs_y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Texture::draw_text(BAN::StringView text, const LibFont::Font& font, int32_t tl_x, int32_t tl_y, uint32_t color)
|
||||
{
|
||||
for (size_t i = 0; i < text.size(); i++)
|
||||
draw_character(text[i], font, tl_x + (int32_t)(i * font.width()), tl_y, color);
|
||||
}
|
||||
|
||||
void Texture::shift_vertical(int32_t amount)
|
||||
{
|
||||
const uint32_t amount_abs = BAN::Math::abs(amount);
|
||||
if (amount_abs == 0 || amount_abs >= height())
|
||||
return;
|
||||
|
||||
uint32_t* dst = (amount > 0) ? m_pixels.data() + width() * amount_abs : m_pixels.data();
|
||||
uint32_t* src = (amount < 0) ? m_pixels.data() + width() * amount_abs : m_pixels.data();
|
||||
memmove(dst, src, width() * (height() - amount_abs) * 4);
|
||||
}
|
||||
|
||||
void Texture::copy_horizontal_slice(int32_t dst_y, int32_t src_y, uint32_t uamount)
|
||||
{
|
||||
int32_t amount = uamount;
|
||||
if (dst_y < 0)
|
||||
{
|
||||
amount -= -dst_y;
|
||||
src_y += -dst_y;
|
||||
dst_y = 0;
|
||||
}
|
||||
|
||||
amount = BAN::Math::min<int32_t>(amount, height() - dst_y);
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
||||
const int32_t copy_src_y = BAN::Math::clamp<int32_t>(src_y, 0, height());
|
||||
const int32_t copy_amount = BAN::Math::clamp<int32_t>(src_y + amount, 0, height()) - copy_src_y;
|
||||
if (copy_amount > 0)
|
||||
{
|
||||
memmove(
|
||||
&m_pixels[width() * (dst_y + (copy_src_y - src_y))],
|
||||
&m_pixels[width() * copy_src_y],
|
||||
copy_amount * width() * 4
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Texture::copy_rect(int32_t dst_x, int32_t dst_y, int32_t src_x, int32_t src_y, uint32_t width, uint32_t height)
|
||||
{
|
||||
if (!clamp_to_texture(dst_x, dst_y, src_x, src_y, width, height, *this))
|
||||
return;
|
||||
|
||||
const bool copy_dir = dst_y < src_y;
|
||||
for (uint32_t i = 0; i < height; i++)
|
||||
{
|
||||
const uint32_t y_off = copy_dir ? i : height - i - 1;
|
||||
memmove(
|
||||
&m_pixels[(dst_y + y_off) * this->width() + dst_x],
|
||||
&m_pixels[(src_y + y_off) * this->width() + src_x],
|
||||
width * 4
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool Texture::clamp_to_texture(int32_t& signed_x, int32_t& signed_y, uint32_t& width, uint32_t& height) const
|
||||
{
|
||||
const int32_t min_x = BAN::Math::max<int32_t>(signed_x, m_clip_x);
|
||||
const int32_t min_y = BAN::Math::max<int32_t>(signed_y, m_clip_y);
|
||||
const int32_t max_x = BAN::Math::min<int32_t>(signed_x + (int32_t)width, m_clip_x + m_clip_w);
|
||||
const int32_t max_y = BAN::Math::min<int32_t>(signed_y + (int32_t)height, m_clip_y + m_clip_h);
|
||||
|
||||
if (min_x >= max_x)
|
||||
return false;
|
||||
if (min_y >= max_y)
|
||||
return false;
|
||||
|
||||
signed_x = min_x;
|
||||
signed_y = min_y;
|
||||
width = max_x - min_x;
|
||||
height = max_y - min_y;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Texture::clamp_to_texture(int32_t& dst_x, int32_t& dst_y, int32_t& src_x, int32_t& src_y, uint32_t& width, uint32_t& height, const Texture& texture) const
|
||||
{
|
||||
if (width > texture.width())
|
||||
width = texture.width();
|
||||
if (height > texture.height())
|
||||
height = texture.height();
|
||||
|
||||
if (dst_x >= static_cast<int32_t>(m_clip_x + m_clip_w))
|
||||
return false;
|
||||
if (dst_y >= static_cast<int32_t>(m_clip_y + m_clip_h))
|
||||
return false;
|
||||
|
||||
if (src_x >= static_cast<int32_t>(texture.width()))
|
||||
return false;
|
||||
if (src_y >= static_cast<int32_t>(texture.height()))
|
||||
return false;
|
||||
|
||||
if (dst_x + static_cast<int32_t>(width) > static_cast<int32_t>(m_clip_x + m_clip_w))
|
||||
width = m_clip_x + m_clip_w - dst_x;
|
||||
if (src_x + static_cast<int32_t>(width) > static_cast<int32_t>(texture.width()))
|
||||
width = texture.width() - src_x;
|
||||
|
||||
if (dst_y + static_cast<int32_t>(height) > static_cast<int32_t>(m_clip_y + m_clip_h))
|
||||
height = m_clip_y + m_clip_h - dst_y;
|
||||
if (src_y + static_cast<int32_t>(height) > static_cast<int32_t>(texture.height()))
|
||||
height = texture.height() - src_y;
|
||||
|
||||
int32_t off_x = 0;
|
||||
if (dst_x < static_cast<int32_t>(m_clip_x))
|
||||
off_x = m_clip_x - dst_x;
|
||||
if (src_x + off_x < 0)
|
||||
off_x = -src_x;
|
||||
if (off_x >= static_cast<int32_t>(width))
|
||||
return false;
|
||||
|
||||
int32_t off_y = 0;
|
||||
if (dst_y < static_cast<int32_t>(m_clip_y))
|
||||
off_y = m_clip_y - dst_y;
|
||||
if (src_y + off_y < 0)
|
||||
off_y = -src_y;
|
||||
if (off_y >= static_cast<int32_t>(height))
|
||||
return false;
|
||||
|
||||
dst_x += off_x;
|
||||
src_x += off_x;
|
||||
dst_y += off_y;
|
||||
src_y += off_y;
|
||||
|
||||
width -= off_x;
|
||||
height -= off_y;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
60
LibGUI/Widget/Button.cpp
Normal file
60
LibGUI/Widget/Button.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include <LibFont/Font.h>
|
||||
#include <LibGUI/Widget/Button.h>
|
||||
#include <LibGUI/Window.h>
|
||||
|
||||
namespace LibGUI::Widget
|
||||
{
|
||||
|
||||
BAN::ErrorOr<BAN::RefPtr<Button>> Button::create(BAN::RefPtr<Widget> parent, BAN::StringView text, Rectangle geometry)
|
||||
{
|
||||
auto* button_ptr = new Button(parent, geometry);
|
||||
if (button_ptr == nullptr)
|
||||
return BAN::Error::from_errno(ENOMEM);
|
||||
auto button = BAN::RefPtr<Button>::adopt(button_ptr);
|
||||
TRY(button->initialize(color_invisible));
|
||||
TRY(button->m_text.append(text));
|
||||
return button;
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Button::set_text(BAN::StringView text)
|
||||
{
|
||||
m_text.clear();
|
||||
TRY(m_text.append(text));
|
||||
if (is_shown())
|
||||
show();
|
||||
return {};
|
||||
}
|
||||
|
||||
void Button::update_impl()
|
||||
{
|
||||
const bool hover_color = is_hovered() && !is_child_hovered();
|
||||
if (hover_color != m_hover_state)
|
||||
show();
|
||||
}
|
||||
|
||||
void Button::show_impl()
|
||||
{
|
||||
m_hover_state = is_hovered() && !is_child_hovered();
|
||||
|
||||
const auto& font = default_font();
|
||||
const int32_t text_h = font.height();
|
||||
const int32_t text_w = font.width() * m_text.size();
|
||||
|
||||
const int32_t off_x = (static_cast<int32_t>(width()) - text_w) / 2;
|
||||
const int32_t off_y = (static_cast<int32_t>(height()) - text_h) / 2;
|
||||
|
||||
m_texture.fill(m_hover_state ? m_style.color_hovered : m_style.color_normal);
|
||||
m_texture.draw_text(m_text, font, off_x, off_y, m_style.color_text);
|
||||
|
||||
RoundedWidget::style() = m_style;
|
||||
RoundedWidget::show_impl();
|
||||
}
|
||||
|
||||
bool Button::on_mouse_button_impl(LibGUI::EventPacket::MouseButtonEvent::event_t event)
|
||||
{
|
||||
if (event.pressed && event.button == LibInput::MouseButton::Left && m_click_callback)
|
||||
m_click_callback();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
53
LibGUI/Widget/Grid.cpp
Normal file
53
LibGUI/Widget/Grid.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include <LibGUI/Widget/Grid.h>
|
||||
|
||||
namespace LibGUI::Widget
|
||||
{
|
||||
|
||||
BAN::ErrorOr<BAN::RefPtr<Grid>> Grid::create(BAN::RefPtr<Widget> parent, uint32_t cols, uint32_t rows, uint32_t color, Rectangle geometry)
|
||||
{
|
||||
auto* grid_ptr = new Grid(parent, geometry, cols, rows);
|
||||
if (grid_ptr == nullptr)
|
||||
return BAN::Error::from_errno(ENOMEM);
|
||||
auto grid = BAN::RefPtr<Grid>::adopt(grid_ptr);
|
||||
TRY(grid->initialize(color));
|
||||
return grid;
|
||||
}
|
||||
|
||||
Widget::Rectangle Grid::grid_element_area(const GridElement& element) const
|
||||
{
|
||||
const uint32_t this_x = element.col * width() / m_cols;
|
||||
const uint32_t this_y = element.row * height() / m_rows;
|
||||
|
||||
const uint32_t next_x = (element.col + element.col_span) * width() / m_cols;
|
||||
const uint32_t next_y = (element.row + element.row_span) * height() / m_rows;
|
||||
|
||||
return Widget::Rectangle {
|
||||
.x = static_cast<int32_t>(this_x),
|
||||
.y = static_cast<int32_t>(this_y),
|
||||
.w = next_x - this_x,
|
||||
.h = next_y - this_y,
|
||||
};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Grid::update_geometry_impl()
|
||||
{
|
||||
for (auto& grid_element : m_grid_elements)
|
||||
TRY(grid_element.widget->set_fixed_geometry(grid_element_area(grid_element)));
|
||||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Grid::set_widget_position(BAN::RefPtr<Widget> widget, uint32_t col, uint32_t col_span, uint32_t row, uint32_t row_span)
|
||||
{
|
||||
if (col_span == 0 || row_span == 0)
|
||||
return BAN::Error::from_errno(EINVAL);
|
||||
if (col + col_span > m_cols)
|
||||
return BAN::Error::from_errno(EINVAL);
|
||||
if (row + row_span > m_rows)
|
||||
return BAN::Error::from_errno(EINVAL);
|
||||
ASSERT(widget->parent() == this);
|
||||
TRY(m_grid_elements.emplace_back(widget, col, col_span, row, row_span));
|
||||
TRY(widget->set_fixed_geometry(grid_element_area(m_grid_elements.back())));
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
44
LibGUI/Widget/Label.cpp
Normal file
44
LibGUI/Widget/Label.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include <LibFont/Font.h>
|
||||
#include <LibGUI/Widget/Label.h>
|
||||
#include <LibGUI/Window.h>
|
||||
|
||||
namespace LibGUI::Widget
|
||||
{
|
||||
|
||||
BAN::ErrorOr<BAN::RefPtr<Label>> Label::create(BAN::RefPtr<Widget> parent, BAN::StringView text, Rectangle geometry)
|
||||
{
|
||||
auto* label_ptr = new Label(parent, geometry);
|
||||
if (label_ptr == nullptr)
|
||||
return BAN::Error::from_errno(ENOMEM);
|
||||
auto label = BAN::RefPtr<Label>::adopt(label_ptr);
|
||||
TRY(label->initialize(color_invisible));
|
||||
TRY(label->m_text.append(text));
|
||||
return label;
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Label::set_text(BAN::StringView text)
|
||||
{
|
||||
m_text.clear();
|
||||
TRY(m_text.append(text));
|
||||
if (is_shown())
|
||||
show();
|
||||
return {};
|
||||
}
|
||||
|
||||
void Label::show_impl()
|
||||
{
|
||||
const auto& font = default_font();
|
||||
const int32_t text_h = font.height();
|
||||
const int32_t text_w = font.width() * m_text.size();
|
||||
|
||||
const int32_t off_x = (static_cast<int32_t>(width()) - text_w) / 2;
|
||||
const int32_t off_y = (static_cast<int32_t>(height()) - text_h) / 2;
|
||||
|
||||
m_texture.fill(m_style.color_normal);
|
||||
m_texture.draw_text(m_text, font, off_x, off_y, m_style.color_text);
|
||||
|
||||
RoundedWidget::style() = m_style;
|
||||
RoundedWidget::show_impl();
|
||||
}
|
||||
|
||||
}
|
||||
117
LibGUI/Widget/RoundedWidget.cpp
Normal file
117
LibGUI/Widget/RoundedWidget.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
#include <LibGUI/Widget/RoundedWidget.h>
|
||||
|
||||
namespace LibGUI::Widget
|
||||
{
|
||||
|
||||
bool RoundedWidget::contains(Point point) const
|
||||
{
|
||||
if (!Widget::contains(point))
|
||||
return false;
|
||||
|
||||
const auto is_outside_corner =
|
||||
[this]<uint8_t quadrant>(Point point) -> bool
|
||||
{
|
||||
const auto radius = m_style.corner_radius;
|
||||
const uint32_t base_x = (quadrant % 2) ? m_texture.width() - radius - 1 : 0;
|
||||
const uint32_t base_y = (quadrant / 2) ? m_texture.height() - radius - 1 : 0;
|
||||
|
||||
if (point.x < static_cast<int32_t>(base_x) || point.x > static_cast<int32_t>(base_x + radius))
|
||||
return false;
|
||||
if (point.y < static_cast<int32_t>(base_y) || point.y > static_cast<int32_t>(base_y + radius))
|
||||
return false;
|
||||
|
||||
const uint32_t x_off = point.x - base_x;
|
||||
const uint32_t y_off = point.y - base_y;
|
||||
|
||||
const uint32_t dx = ((quadrant % 2) ? x_off : radius - x_off);
|
||||
const uint32_t dy = ((quadrant / 2) ? y_off : radius - y_off);
|
||||
const uint32_t distance = dx * dx + dy * dy;
|
||||
|
||||
return distance >= radius * radius;
|
||||
};
|
||||
|
||||
if (is_outside_corner.operator()<0>(point))
|
||||
return false;
|
||||
if (is_outside_corner.operator()<1>(point))
|
||||
return false;
|
||||
if (is_outside_corner.operator()<2>(point))
|
||||
return false;
|
||||
if (is_outside_corner.operator()<3>(point))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RoundedWidget::show_impl()
|
||||
{
|
||||
if (m_style.border_width)
|
||||
{
|
||||
m_texture.fill_rect(
|
||||
0,
|
||||
0,
|
||||
m_texture.width(),
|
||||
m_style.border_width,
|
||||
m_style.color_border
|
||||
);
|
||||
m_texture.fill_rect(
|
||||
0,
|
||||
0,
|
||||
m_style.border_width,
|
||||
m_texture.height(),
|
||||
m_style.color_border
|
||||
);
|
||||
m_texture.fill_rect(
|
||||
0,
|
||||
m_texture.height() - m_style.border_width,
|
||||
m_texture.width(),
|
||||
m_style.border_width,
|
||||
m_style.color_border
|
||||
);
|
||||
m_texture.fill_rect(
|
||||
m_texture.width() - m_style.border_width,
|
||||
0,
|
||||
m_style.border_width,
|
||||
m_texture.height(),
|
||||
m_style.color_border
|
||||
);
|
||||
}
|
||||
|
||||
if (m_style.corner_radius)
|
||||
{
|
||||
const auto draw_corner =
|
||||
[this]<uint8_t quadrant>()
|
||||
{
|
||||
const auto radius = m_style.corner_radius;
|
||||
const uint32_t base_x = (quadrant % 2) ? m_texture.width() - radius - 1 : 0;
|
||||
const uint32_t base_y = (quadrant / 2) ? m_texture.height() - radius - 1 : 0;
|
||||
const uint32_t distance_max = radius * radius;
|
||||
const uint32_t distance_min = (radius - m_style.border_width) * (radius - m_style.border_width);
|
||||
|
||||
for (uint32_t y_off = 0; y_off <= radius; y_off++)
|
||||
{
|
||||
for (uint32_t x_off = 0; x_off <= radius; x_off++)
|
||||
{
|
||||
const uint32_t dx = ((quadrant % 2) ? x_off : radius - x_off);
|
||||
const uint32_t dy = ((quadrant / 2) ? y_off : radius - y_off);
|
||||
const uint32_t distance = dx * dx + dy * dy;
|
||||
|
||||
if (base_x + x_off >= m_texture.width())
|
||||
continue;
|
||||
if (base_y + y_off >= m_texture.height())
|
||||
continue;
|
||||
|
||||
if (distance > distance_max)
|
||||
m_texture.set_pixel(base_x + x_off, base_y + y_off, color_invisible);
|
||||
else if (distance >= distance_min)
|
||||
m_texture.set_pixel(base_x + x_off, base_y + y_off, m_style.color_border);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
draw_corner.operator()<0>();
|
||||
draw_corner.operator()<1>();
|
||||
draw_corner.operator()<2>();
|
||||
draw_corner.operator()<3>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
96
LibGUI/Widget/TextArea.cpp
Normal file
96
LibGUI/Widget/TextArea.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include <LibFont/Font.h>
|
||||
#include <LibGUI/Widget/TextArea.h>
|
||||
#include <LibGUI/Window.h>
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
namespace LibGUI::Widget
|
||||
{
|
||||
|
||||
BAN::ErrorOr<BAN::RefPtr<TextArea>> TextArea::create(BAN::RefPtr<Widget> parent, BAN::StringView text, Rectangle geometry)
|
||||
{
|
||||
auto* text_area_ptr = new TextArea(parent, geometry);
|
||||
if (text_area_ptr == nullptr)
|
||||
return BAN::Error::from_errno(ENOMEM);
|
||||
auto text_area = BAN::RefPtr<TextArea>::adopt(text_area_ptr);
|
||||
TRY(text_area->initialize(color_invisible));
|
||||
TRY(text_area->set_text(text));
|
||||
return text_area;
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> TextArea::set_text(BAN::StringView text)
|
||||
{
|
||||
m_text.clear();
|
||||
TRY(m_text.append(text));
|
||||
TRY(wrap_text());
|
||||
if (is_shown())
|
||||
show();
|
||||
return {};
|
||||
}
|
||||
|
||||
uint32_t TextArea::get_required_height() const
|
||||
{
|
||||
auto& font = default_font();
|
||||
return m_wrapped_text.size() * font.height();
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> TextArea::wrap_text()
|
||||
{
|
||||
m_wrapped_text.clear();
|
||||
|
||||
if (width() == 0)
|
||||
return {};
|
||||
|
||||
auto& font = default_font();
|
||||
|
||||
const uint32_t total_columns = width() / font.width();
|
||||
ASSERT(total_columns != 0);
|
||||
|
||||
TRY(m_wrapped_text.emplace_back());
|
||||
|
||||
for (size_t i = 0; i < m_text.size(); i++)
|
||||
{
|
||||
if (m_text[i] == '\n')
|
||||
TRY(m_wrapped_text.emplace_back());
|
||||
else if (isspace(m_text[i]) && m_wrapped_text.back().size() == 0)
|
||||
;
|
||||
else
|
||||
{
|
||||
TRY(m_wrapped_text.back().push_back(m_text[i]));
|
||||
|
||||
if (i + 1 < m_text.size() && isspace(m_text[i]) && !isspace(m_text[i + 1]))
|
||||
{
|
||||
size_t word_len = 0;
|
||||
for (size_t j = i + 1; j < m_text.size() && !isspace(m_text[j]); j++)
|
||||
word_len++;
|
||||
if (word_len <= total_columns && m_wrapped_text.back().size() + word_len > total_columns)
|
||||
TRY(m_wrapped_text.emplace_back());
|
||||
}
|
||||
|
||||
if (m_wrapped_text.back().size() >= total_columns)
|
||||
TRY(m_wrapped_text.emplace_back());
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> TextArea::update_geometry_impl()
|
||||
{
|
||||
TRY(wrap_text());
|
||||
return Widget::update_geometry_impl();
|
||||
}
|
||||
|
||||
void TextArea::show_impl()
|
||||
{
|
||||
const auto& font = default_font();
|
||||
|
||||
m_texture.fill(m_style.color_normal);
|
||||
for (int32_t row = 0; row < static_cast<int32_t>(m_wrapped_text.size()); row++)
|
||||
m_texture.draw_text(m_wrapped_text[row].sv(), font, 0, row * font.height(), m_style.color_text);
|
||||
|
||||
RoundedWidget::style() = m_style;
|
||||
RoundedWidget::show_impl();
|
||||
}
|
||||
|
||||
}
|
||||
241
LibGUI/Widget/Widget.cpp
Normal file
241
LibGUI/Widget/Widget.cpp
Normal file
@@ -0,0 +1,241 @@
|
||||
#include <LibGUI/Widget/Widget.h>
|
||||
#include <LibFont/Font.h>
|
||||
|
||||
namespace LibGUI::Widget
|
||||
{
|
||||
|
||||
static BAN::Optional<LibFont::Font> s_default_font;
|
||||
|
||||
BAN::ErrorOr<void> Widget::set_default_font(BAN::StringView path)
|
||||
{
|
||||
s_default_font = TRY(LibFont::Font::load(path));
|
||||
return {};
|
||||
}
|
||||
|
||||
const LibFont::Font& Widget::default_font()
|
||||
{
|
||||
if (!s_default_font.has_value())
|
||||
MUST(set_default_font("/usr/share/fonts/lat0-16.psfu"_sv));
|
||||
return s_default_font.value();
|
||||
}
|
||||
|
||||
BAN::ErrorOr<BAN::RefPtr<Widget>> Widget::create(BAN::RefPtr<Widget> parent, uint32_t color, Rectangle area)
|
||||
{
|
||||
auto* widget_ptr = new Widget(parent, area);
|
||||
if (widget_ptr == nullptr)
|
||||
return BAN::Error::from_errno(ENOMEM);
|
||||
auto widget = BAN::RefPtr<Widget>::adopt(widget_ptr);
|
||||
TRY(widget->initialize(color));
|
||||
return widget;
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Widget::initialize(uint32_t color)
|
||||
{
|
||||
m_texture = TRY(Texture::create(width(), height(), color));
|
||||
if (m_parent)
|
||||
TRY(m_parent->m_children.push_back(this));
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Widget::is_child_hovered() const
|
||||
{
|
||||
for (auto child : m_children)
|
||||
if (child->m_hovered || child->is_child_hovered())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Widget::set_fixed_geometry(Rectangle area)
|
||||
{
|
||||
TRY(m_texture.resize(area.w, area.h));
|
||||
m_fixed_area = area;
|
||||
m_relative_area.clear();
|
||||
|
||||
TRY(update_geometry_impl());
|
||||
|
||||
if (is_shown())
|
||||
show();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Widget::set_relative_geometry(FloatRectangle area)
|
||||
{
|
||||
if (area.w < 0.0f || area.h < 0.0f)
|
||||
return BAN::Error::from_errno(EINVAL);
|
||||
ASSERT(m_parent);
|
||||
|
||||
TRY(set_fixed_geometry({
|
||||
.x = static_cast<int32_t>(area.x * m_parent->width()),
|
||||
.y = static_cast<int32_t>(area.y * m_parent->height()),
|
||||
.w = static_cast<uint32_t>(area.w * m_parent->width()),
|
||||
.h = static_cast<uint32_t>(area.h * m_parent->height()),
|
||||
}));
|
||||
|
||||
m_relative_area = area;
|
||||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Widget::update_geometry_impl()
|
||||
{
|
||||
for (auto child : m_children)
|
||||
{
|
||||
if (!child->m_relative_area.has_value())
|
||||
continue;
|
||||
TRY(child->set_relative_geometry(child->m_relative_area.value()));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void Widget::show()
|
||||
{
|
||||
m_shown = true;
|
||||
|
||||
show_impl();
|
||||
m_changed = true;
|
||||
|
||||
for (auto child : m_children)
|
||||
if (child->is_shown())
|
||||
child->show();
|
||||
}
|
||||
|
||||
void Widget::hide()
|
||||
{
|
||||
if (!is_shown())
|
||||
return;
|
||||
m_shown = false;
|
||||
|
||||
auto root = m_parent;
|
||||
while (root && root->m_parent)
|
||||
root = root->m_parent;
|
||||
if (root)
|
||||
root->show();
|
||||
}
|
||||
|
||||
void Widget::before_mouse_move()
|
||||
{
|
||||
if (!is_shown())
|
||||
return;
|
||||
|
||||
m_old_hovered = m_hovered;
|
||||
m_hovered = false;
|
||||
|
||||
for (auto child : m_children)
|
||||
child->before_mouse_move();
|
||||
}
|
||||
|
||||
void Widget::after_mouse_move()
|
||||
{
|
||||
if (!is_shown())
|
||||
return;
|
||||
|
||||
if (m_old_hovered != m_hovered)
|
||||
on_hover_change_impl(m_hovered);
|
||||
|
||||
for (auto child : m_children)
|
||||
child->after_mouse_move();
|
||||
}
|
||||
|
||||
bool Widget::on_mouse_move(LibGUI::EventPacket::MouseMoveEvent::event_t event)
|
||||
{
|
||||
if (!Rectangle { 0, 0, width(), height() }.contains({ event.x, event.y }))
|
||||
return false;
|
||||
if (!is_shown())
|
||||
return false;
|
||||
|
||||
m_hovered = contains({ event.x, event.y });
|
||||
|
||||
for (auto child : m_children)
|
||||
{
|
||||
auto rel_event = event;
|
||||
rel_event.x -= child->m_fixed_area.x;
|
||||
rel_event.y -= child->m_fixed_area.y;
|
||||
if (child->on_mouse_move(rel_event))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_hovered)
|
||||
return false;
|
||||
return on_mouse_move_impl(event);
|
||||
}
|
||||
|
||||
bool Widget::on_mouse_button(LibGUI::EventPacket::MouseButtonEvent::event_t event)
|
||||
{
|
||||
if (!Rectangle { 0, 0, width(), height() }.contains({ event.x, event.y }))
|
||||
return false;
|
||||
if (!is_shown())
|
||||
return false;
|
||||
|
||||
for (auto child : m_children)
|
||||
{
|
||||
auto rel_event = event;
|
||||
rel_event.x -= child->m_fixed_area.x;
|
||||
rel_event.y -= child->m_fixed_area.y;
|
||||
if (child->on_mouse_button(rel_event))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!contains({ event.x, event.y }))
|
||||
return false;
|
||||
return on_mouse_button_impl(event);
|
||||
}
|
||||
|
||||
Widget::Rectangle Widget::render(Texture& output, Point parent_position, Rectangle out_area)
|
||||
{
|
||||
if (!is_shown())
|
||||
return {};
|
||||
|
||||
update_impl();
|
||||
|
||||
Rectangle invalidated {};
|
||||
if (m_changed)
|
||||
{
|
||||
auto where_i_would_be = Rectangle {
|
||||
.x = parent_position.x + m_fixed_area.x,
|
||||
.y = parent_position.y + m_fixed_area.y,
|
||||
.w = m_fixed_area.w,
|
||||
.h = m_fixed_area.h,
|
||||
};
|
||||
|
||||
auto where_to_draw = out_area.overlap(where_i_would_be);
|
||||
|
||||
if (where_to_draw.w && where_to_draw.h)
|
||||
{
|
||||
output.copy_texture(
|
||||
m_texture,
|
||||
where_to_draw.x,
|
||||
where_to_draw.y,
|
||||
where_to_draw.x - where_i_would_be.x,
|
||||
where_to_draw.y - where_i_would_be.y,
|
||||
where_to_draw.w,
|
||||
where_to_draw.h
|
||||
);
|
||||
}
|
||||
|
||||
invalidated = where_to_draw;
|
||||
m_changed = false;
|
||||
}
|
||||
|
||||
out_area = out_area.overlap({
|
||||
.x = parent_position.x + m_fixed_area.x,
|
||||
.y = parent_position.y + m_fixed_area.y,
|
||||
.w = m_fixed_area.w,
|
||||
.h = m_fixed_area.h,
|
||||
});
|
||||
|
||||
parent_position = {
|
||||
.x = parent_position.x + m_fixed_area.x,
|
||||
.y = parent_position.y + m_fixed_area.y,
|
||||
};
|
||||
|
||||
for (auto child : m_children)
|
||||
{
|
||||
invalidated = invalidated.bounding_box(
|
||||
child->render(output, parent_position, out_area)
|
||||
);
|
||||
}
|
||||
|
||||
return invalidated;
|
||||
}
|
||||
|
||||
}
|
||||
393
LibGUI/Window.cpp
Normal file
393
LibGUI/Window.cpp
Normal file
@@ -0,0 +1,393 @@
|
||||
#include <LibGUI/Window.h>
|
||||
|
||||
#include <BAN/ScopeGuard.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/shm.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace LibGUI
|
||||
{
|
||||
|
||||
struct ReceivePacket
|
||||
{
|
||||
PacketType type;
|
||||
BAN::Vector<uint8_t> data_with_type;
|
||||
};
|
||||
|
||||
static BAN::ErrorOr<ReceivePacket> recv_packet(int socket)
|
||||
{
|
||||
uint32_t packet_size;
|
||||
|
||||
{
|
||||
const ssize_t nrecv = recv(socket, &packet_size, sizeof(uint32_t), 0);
|
||||
if (nrecv < 0)
|
||||
return BAN::Error::from_errno(errno);
|
||||
if (nrecv == 0)
|
||||
return BAN::Error::from_errno(ECONNRESET);
|
||||
}
|
||||
|
||||
if (packet_size < sizeof(uint32_t))
|
||||
return BAN::Error::from_literal("invalid packet, does not fit packet id");
|
||||
|
||||
BAN::Vector<uint8_t> packet_data;
|
||||
TRY(packet_data.resize(packet_size));
|
||||
|
||||
size_t total_recv = 0;
|
||||
while (total_recv < packet_size)
|
||||
{
|
||||
const ssize_t nrecv = recv(socket, packet_data.data() + total_recv, packet_size - total_recv, 0);
|
||||
if (nrecv < 0)
|
||||
return BAN::Error::from_errno(errno);
|
||||
if (nrecv == 0)
|
||||
return BAN::Error::from_errno(ECONNRESET);
|
||||
total_recv += nrecv;
|
||||
}
|
||||
|
||||
return ReceivePacket {
|
||||
*reinterpret_cast<PacketType*>(packet_data.data()),
|
||||
packet_data
|
||||
};
|
||||
}
|
||||
|
||||
Window::~Window()
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
|
||||
BAN::ErrorOr<BAN::UniqPtr<Window>> Window::create(uint32_t width, uint32_t height, BAN::StringView title, Attributes attributes)
|
||||
{
|
||||
int server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (server_fd == -1)
|
||||
return BAN::Error::from_errno(errno);
|
||||
BAN::ScopeGuard server_closer([server_fd] { close(server_fd); });
|
||||
|
||||
int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
|
||||
if (epoll_fd == -1)
|
||||
return BAN::Error::from_errno(errno);
|
||||
BAN::ScopeGuard epoll_closer([epoll_fd] { close(epoll_fd); });
|
||||
|
||||
epoll_event epoll_event {
|
||||
.events = EPOLLIN,
|
||||
.data = { .fd = server_fd },
|
||||
};
|
||||
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &epoll_event) == -1)
|
||||
return BAN::Error::from_errno(errno);
|
||||
|
||||
if (fcntl(server_fd, F_SETFD, fcntl(server_fd, F_GETFD) | FD_CLOEXEC) == -1)
|
||||
return BAN::Error::from_errno(errno);
|
||||
|
||||
timespec start_time;
|
||||
clock_gettime(CLOCK_MONOTONIC, &start_time);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
sockaddr_un server_address;
|
||||
server_address.sun_family = AF_UNIX;
|
||||
strcpy(server_address.sun_path, s_window_server_socket.data());
|
||||
if (connect(server_fd, (sockaddr*)&server_address, sizeof(server_address)) == 0)
|
||||
break;
|
||||
|
||||
timespec current_time;
|
||||
clock_gettime(CLOCK_MONOTONIC, ¤t_time);
|
||||
time_t duration_s = (current_time.tv_sec - start_time.tv_sec) + (current_time.tv_nsec >= start_time.tv_nsec);
|
||||
if (duration_s > 1)
|
||||
return BAN::Error::from_errno(ETIMEDOUT);
|
||||
|
||||
timespec sleep_time;
|
||||
sleep_time.tv_sec = 0;
|
||||
sleep_time.tv_nsec = 1'000'000;
|
||||
nanosleep(&sleep_time, nullptr);
|
||||
}
|
||||
|
||||
WindowPacket::WindowCreate create_packet;
|
||||
create_packet.width = width;
|
||||
create_packet.height = height;
|
||||
create_packet.attributes = attributes;
|
||||
TRY(create_packet.title.append(title));
|
||||
TRY(create_packet.send_serialized(server_fd));
|
||||
|
||||
auto window = TRY(BAN::UniqPtr<Window>::create(server_fd, epoll_fd, attributes));
|
||||
|
||||
bool resized = false;
|
||||
window->set_resize_window_event_callback([&]() { resized = true; });
|
||||
while (!resized)
|
||||
window->poll_events();
|
||||
window->set_resize_window_event_callback({});
|
||||
|
||||
server_closer.disable();
|
||||
epoll_closer.disable();
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Window::set_root_widget(BAN::RefPtr<Widget::Widget> widget)
|
||||
{
|
||||
TRY(widget->set_fixed_geometry({ 0, 0, m_width, m_height }));
|
||||
m_root_widget = widget;
|
||||
m_root_widget->show();
|
||||
const auto invalidated = m_root_widget->render(m_texture, { 0, 0 }, { 0, 0, m_width, m_height });
|
||||
if (invalidated.w && invalidated.h)
|
||||
invalidate(invalidated.x, invalidated.y, invalidated.w, invalidated.h);
|
||||
return {};
|
||||
}
|
||||
|
||||
void Window::invalidate(int32_t x, int32_t y, uint32_t width, uint32_t height)
|
||||
{
|
||||
if (!m_texture.clamp_to_texture(x, y, width, height))
|
||||
return;
|
||||
|
||||
for (uint32_t i = 0; i < height; i++)
|
||||
memcpy(&m_framebuffer_smo[(y + i) * m_width + x], &m_texture.pixels()[(y + i) * m_width + x], width * sizeof(uint32_t));
|
||||
|
||||
WindowPacket::WindowInvalidate packet;
|
||||
packet.x = x;
|
||||
packet.y = y;
|
||||
packet.width = width;
|
||||
packet.height = height;
|
||||
|
||||
if (auto ret = packet.send_serialized(m_server_fd); ret.is_error())
|
||||
return on_socket_error(__FUNCTION__);
|
||||
}
|
||||
|
||||
void Window::set_mouse_relative(bool enabled)
|
||||
{
|
||||
WindowPacket::WindowSetMouseRelative packet;
|
||||
packet.enabled = enabled;
|
||||
|
||||
if (auto ret = packet.send_serialized(m_server_fd); ret.is_error())
|
||||
return on_socket_error(__FUNCTION__);
|
||||
}
|
||||
|
||||
void Window::set_fullscreen(bool fullscreen)
|
||||
{
|
||||
WindowPacket::WindowSetFullscreen packet;
|
||||
packet.fullscreen = fullscreen;
|
||||
|
||||
if (auto ret = packet.send_serialized(m_server_fd); ret.is_error())
|
||||
return on_socket_error(__FUNCTION__);
|
||||
}
|
||||
|
||||
void Window::set_title(BAN::StringView title)
|
||||
{
|
||||
WindowPacket::WindowSetTitle packet;
|
||||
MUST(packet.title.append(title));
|
||||
|
||||
if (auto ret = packet.send_serialized(m_server_fd); ret.is_error())
|
||||
return on_socket_error(__FUNCTION__);
|
||||
}
|
||||
|
||||
void Window::set_position(int32_t x, int32_t y)
|
||||
{
|
||||
WindowPacket::WindowSetPosition packet;
|
||||
packet.x = x;
|
||||
packet.y = y;
|
||||
|
||||
if (auto ret = packet.send_serialized(m_server_fd); ret.is_error())
|
||||
return on_socket_error(__FUNCTION__);
|
||||
}
|
||||
|
||||
void Window::set_cursor_visible(bool visible)
|
||||
{
|
||||
auto attributes = m_attributes;
|
||||
if (attributes.cursor_visible == visible)
|
||||
return;
|
||||
attributes.cursor_visible = visible;
|
||||
set_attributes(attributes);
|
||||
}
|
||||
|
||||
void Window::set_cursor(uint32_t width, uint32_t height, BAN::Span<uint32_t> pixels)
|
||||
{
|
||||
WindowPacket::WindowSetCursor packet;
|
||||
packet.width = width;
|
||||
packet.height = height;
|
||||
MUST(packet.pixels.resize(pixels.size()));
|
||||
for (size_t i = 0; i < packet.pixels.size(); i++)
|
||||
packet.pixels[i] = pixels[i];
|
||||
|
||||
if (auto ret = packet.send_serialized(m_server_fd); ret.is_error())
|
||||
return on_socket_error(__FUNCTION__);
|
||||
}
|
||||
|
||||
void Window::set_min_size(uint32_t width, uint32_t height)
|
||||
{
|
||||
WindowPacket::WindowSetMinSize packet;
|
||||
packet.width = width;
|
||||
packet.height = height;
|
||||
|
||||
if (auto ret = packet.send_serialized(m_server_fd); ret.is_error())
|
||||
return on_socket_error(__FUNCTION__);
|
||||
}
|
||||
|
||||
void Window::set_max_size(uint32_t width, uint32_t height)
|
||||
{
|
||||
WindowPacket::WindowSetMaxSize packet;
|
||||
packet.width = width;
|
||||
packet.height = height;
|
||||
|
||||
if (auto ret = packet.send_serialized(m_server_fd); ret.is_error())
|
||||
return on_socket_error(__FUNCTION__);
|
||||
}
|
||||
|
||||
void Window::set_attributes(Attributes attributes)
|
||||
{
|
||||
WindowPacket::WindowSetAttributes packet;
|
||||
packet.attributes = attributes;
|
||||
|
||||
if (auto ret = packet.send_serialized(m_server_fd); ret.is_error())
|
||||
return on_socket_error(__FUNCTION__);
|
||||
|
||||
m_attributes = attributes;
|
||||
}
|
||||
|
||||
void Window::request_resize(uint32_t width, uint32_t height)
|
||||
{
|
||||
WindowPacket::WindowSetSize packet;
|
||||
packet.width = width;
|
||||
packet.height = height;
|
||||
|
||||
if (auto ret = packet.send_serialized(m_server_fd); ret.is_error())
|
||||
return on_socket_error(__FUNCTION__);
|
||||
}
|
||||
|
||||
void Window::on_socket_error(BAN::StringView function)
|
||||
{
|
||||
if (m_handling_socket_error)
|
||||
return;
|
||||
m_handling_socket_error = true;
|
||||
|
||||
dprintln("Socket error while running Window::{}", function);
|
||||
|
||||
if (!m_socket_error_callback)
|
||||
exit(1);
|
||||
|
||||
m_socket_error_callback();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
void Window::cleanup()
|
||||
{
|
||||
shmdt(m_framebuffer_smo);
|
||||
close(m_server_fd);
|
||||
close(m_epoll_fd);
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Window::handle_resize_event(const EventPacket::ResizeWindowEvent& event)
|
||||
{
|
||||
if (m_framebuffer_smo)
|
||||
shmdt(m_framebuffer_smo);
|
||||
m_framebuffer_smo = nullptr;
|
||||
|
||||
TRY(m_texture.resize(event.width, event.height));
|
||||
|
||||
if (m_root_widget)
|
||||
TRY(m_root_widget->set_fixed_geometry({ 0, 0, event.width, event.height }));
|
||||
|
||||
void* framebuffer_addr = shmat(event.smo_key, nullptr, 0);
|
||||
if (framebuffer_addr == (void*)-1)
|
||||
return BAN::Error::from_errno(errno);
|
||||
|
||||
m_framebuffer_smo = static_cast<uint32_t*>(framebuffer_addr);
|
||||
m_width = event.width;
|
||||
m_height = event.height;
|
||||
|
||||
invalidate();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void Window::wait_events()
|
||||
{
|
||||
epoll_event dummy;
|
||||
epoll_wait(m_epoll_fd, &dummy, 1, -1);
|
||||
}
|
||||
|
||||
void Window::poll_events()
|
||||
{
|
||||
#define TRY_OR_BREAK(...) ({ auto&& e = (__VA_ARGS__); if (e.is_error()) break; e.release_value(); })
|
||||
for (;;)
|
||||
{
|
||||
epoll_event event;
|
||||
if (epoll_wait(m_epoll_fd, &event, 1, 0) == 0)
|
||||
break;
|
||||
|
||||
auto packet_or_error = recv_packet(m_server_fd);
|
||||
if (packet_or_error.is_error())
|
||||
return on_socket_error(__FUNCTION__);
|
||||
|
||||
const auto [packet_type, packet_data] = packet_or_error.release_value();
|
||||
switch (packet_type)
|
||||
{
|
||||
case PacketType::DestroyWindowEvent:
|
||||
exit(1);
|
||||
case PacketType::CloseWindowEvent:
|
||||
if (m_close_window_event_callback)
|
||||
m_close_window_event_callback();
|
||||
else
|
||||
exit(0);
|
||||
break;
|
||||
case PacketType::ResizeWindowEvent:
|
||||
{
|
||||
MUST(handle_resize_event(TRY_OR_BREAK(EventPacket::ResizeWindowEvent::deserialize(packet_data.span()))));
|
||||
if (m_resize_window_event_callback)
|
||||
m_resize_window_event_callback();
|
||||
break;
|
||||
}
|
||||
case PacketType::WindowShownEvent:
|
||||
if (m_window_shown_event_callback)
|
||||
m_window_shown_event_callback(TRY_OR_BREAK(EventPacket::WindowShownEvent::deserialize(packet_data.span())).event);
|
||||
break;
|
||||
case PacketType::WindowFocusEvent:
|
||||
if (m_window_focus_event_callback)
|
||||
m_window_focus_event_callback(TRY_OR_BREAK(EventPacket::WindowFocusEvent::deserialize(packet_data.span())).event);
|
||||
break;
|
||||
case PacketType::KeyEvent:
|
||||
if (m_key_event_callback)
|
||||
m_key_event_callback(TRY_OR_BREAK(EventPacket::KeyEvent::deserialize(packet_data.span())).event);
|
||||
break;
|
||||
case PacketType::MouseButtonEvent:
|
||||
{
|
||||
auto event = TRY_OR_BREAK(EventPacket::MouseButtonEvent::deserialize(packet_data.span())).event;
|
||||
if (m_mouse_button_event_callback)
|
||||
m_mouse_button_event_callback(event);
|
||||
if (m_root_widget)
|
||||
m_root_widget->on_mouse_button(event);
|
||||
break;
|
||||
}
|
||||
case PacketType::MouseMoveEvent:
|
||||
{
|
||||
auto event = TRY_OR_BREAK(EventPacket::MouseMoveEvent::deserialize(packet_data.span())).event;
|
||||
if (m_mouse_move_event_callback)
|
||||
m_mouse_move_event_callback(event);
|
||||
if (m_root_widget)
|
||||
{
|
||||
m_root_widget->before_mouse_move();
|
||||
m_root_widget->on_mouse_move(event);
|
||||
m_root_widget->after_mouse_move();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PacketType::MouseScrollEvent:
|
||||
if (m_mouse_scroll_event_callback)
|
||||
m_mouse_scroll_event_callback(TRY_OR_BREAK(EventPacket::MouseScrollEvent::deserialize(packet_data.span())).event);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#undef TRY_OR_BREAK
|
||||
|
||||
if (m_root_widget)
|
||||
{
|
||||
const auto invalidated = m_root_widget->render(m_texture, { 0, 0 }, { 0, 0, m_width, m_height });
|
||||
if (invalidated.w && invalidated.h)
|
||||
invalidate(invalidated.x, invalidated.y, invalidated.w, invalidated.h);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
18
LibGUI/include/LibGUI/MessageBox.h
Normal file
18
LibGUI/include/LibGUI/MessageBox.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <BAN/Span.h>
|
||||
#include <BAN/StringView.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace LibGUI
|
||||
{
|
||||
|
||||
class MessageBox
|
||||
{
|
||||
public:
|
||||
static BAN::ErrorOr<void> create(BAN::StringView message, BAN::StringView title);
|
||||
static BAN::ErrorOr<size_t> create(BAN::StringView message, BAN::StringView title, BAN::Span<BAN::StringView> buttons);
|
||||
};
|
||||
|
||||
}
|
||||
381
LibGUI/include/LibGUI/Packet.h
Normal file
381
LibGUI/include/LibGUI/Packet.h
Normal file
@@ -0,0 +1,381 @@
|
||||
#pragma once
|
||||
|
||||
#include <BAN/String.h>
|
||||
#include <BAN/StringView.h>
|
||||
#include <BAN/ByteSpan.h>
|
||||
|
||||
#include <LibInput/KeyEvent.h>
|
||||
#include <LibInput/MouseEvent.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#define FOR_EACH_0(macro)
|
||||
#define FOR_EACH_2(macro, type, name) macro(type, name)
|
||||
#define FOR_EACH_4(macro, type, name, ...) macro(type, name) FOR_EACH_2(macro, __VA_ARGS__)
|
||||
#define FOR_EACH_6(macro, type, name, ...) macro(type, name) FOR_EACH_4(macro, __VA_ARGS__)
|
||||
#define FOR_EACH_8(macro, type, name, ...) macro(type, name) FOR_EACH_6(macro, __VA_ARGS__)
|
||||
|
||||
#define CONCATENATE_2(arg1, arg2) arg1 ## arg2
|
||||
#define CONCATENATE_1(arg1, arg2) CONCATENATE_2(arg1, arg2)
|
||||
#define CONCATENATE(arg1, arg2) CONCATENATE_1(arg1, arg2)
|
||||
|
||||
#define FOR_EACH_NARG(...) FOR_EACH_NARG_(__VA_ARGS__ __VA_OPT__(,) FOR_EACH_RSEQ_N())
|
||||
#define FOR_EACH_NARG_(...) FOR_EACH_ARG_N(__VA_ARGS__)
|
||||
#define FOR_EACH_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
|
||||
#define FOR_EACH_RSEQ_N() 8, 7, 6, 5, 4, 3, 2, 1, 0
|
||||
|
||||
#define FOR_EACH_(N, what, ...) CONCATENATE(FOR_EACH_, N)(what __VA_OPT__(,) __VA_ARGS__)
|
||||
#define FOR_EACH(what, ...) FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what __VA_OPT__(,) __VA_ARGS__)
|
||||
|
||||
#define FIELD_DECL(type, name) type name;
|
||||
#define ADD_SERIALIZED_SIZE(type, name) serialized_size += Serialize::serialized_size_impl<type>(this->name);
|
||||
#define SEND_SERIALIZED(type, name) TRY(Serialize::send_serialized_impl<type>(socket, this->name));
|
||||
#define DESERIALIZE(type, name) value.name = TRY(Serialize::deserialize_impl<type>(buffer));
|
||||
|
||||
#define DEFINE_PACKET_EXTRA(name, extra, ...) \
|
||||
struct name \
|
||||
{ \
|
||||
static constexpr PacketType type = PacketType::name; \
|
||||
static constexpr uint32_t type_u32 = static_cast<uint32_t>(type); \
|
||||
\
|
||||
extra; \
|
||||
\
|
||||
FOR_EACH(FIELD_DECL, __VA_ARGS__) \
|
||||
\
|
||||
size_t serialized_size() \
|
||||
{ \
|
||||
size_t serialized_size = Serialize::serialized_size_impl<uint32_t>(type_u32); \
|
||||
FOR_EACH(ADD_SERIALIZED_SIZE, __VA_ARGS__) \
|
||||
return serialized_size; \
|
||||
} \
|
||||
\
|
||||
BAN::ErrorOr<void> send_serialized(int socket) \
|
||||
{ \
|
||||
const uint32_t serialized_size = this->serialized_size(); \
|
||||
TRY(Serialize::send_serialized_impl<uint32_t>(socket, serialized_size)); \
|
||||
TRY(Serialize::send_serialized_impl<uint32_t>(socket, type_u32)); \
|
||||
FOR_EACH(SEND_SERIALIZED, __VA_ARGS__) \
|
||||
return {}; \
|
||||
} \
|
||||
\
|
||||
static BAN::ErrorOr<name> deserialize(BAN::ConstByteSpan buffer) \
|
||||
{ \
|
||||
const uint32_t type_u32 = TRY(Serialize::deserialize_impl<uint32_t>(buffer)); \
|
||||
if (type_u32 != name::type_u32) \
|
||||
return BAN::Error::from_errno(EINVAL); \
|
||||
name value; \
|
||||
FOR_EACH(DESERIALIZE, __VA_ARGS__) \
|
||||
return value; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define DEFINE_PACKET(name, ...) DEFINE_PACKET_EXTRA(name, , __VA_ARGS__)
|
||||
|
||||
namespace LibGUI
|
||||
{
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template<typename T>
|
||||
concept Vector = requires {
|
||||
requires BAN::same_as<T, BAN::Vector<typename T::value_type>>;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
static constexpr BAN::StringView s_window_server_socket = "/tmp/window-server.socket"_sv;
|
||||
|
||||
namespace Serialize
|
||||
{
|
||||
|
||||
inline BAN::ErrorOr<void> send_raw_data(int socket, BAN::ConstByteSpan data)
|
||||
{
|
||||
size_t send_done = 0;
|
||||
while (send_done < data.size())
|
||||
{
|
||||
const ssize_t nsend = ::send(socket, data.data() + send_done, data.size() - send_done, 0);
|
||||
if (nsend < 0)
|
||||
return BAN::Error::from_errno(errno);
|
||||
if (nsend == 0)
|
||||
return BAN::Error::from_errno(ECONNRESET);
|
||||
send_done += nsend;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T> requires BAN::is_pod_v<T>
|
||||
inline size_t serialized_size_impl(const T&)
|
||||
{
|
||||
return sizeof(T);
|
||||
}
|
||||
|
||||
template<typename T> requires BAN::is_pod_v<T>
|
||||
inline BAN::ErrorOr<void> send_serialized_impl(int socket, const T& value)
|
||||
{
|
||||
TRY(send_raw_data(socket, BAN::ConstByteSpan::from(value)));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T> requires BAN::is_pod_v<T>
|
||||
inline BAN::ErrorOr<T> deserialize_impl(BAN::ConstByteSpan& buffer)
|
||||
{
|
||||
if (buffer.size() < sizeof(T))
|
||||
return BAN::Error::from_errno(ENOBUFS);
|
||||
const T value = buffer.as<const T>();
|
||||
buffer = buffer.slice(sizeof(T));
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename T> requires BAN::is_same_v<T, BAN::String>
|
||||
inline size_t serialized_size_impl(const T& value)
|
||||
{
|
||||
return sizeof(uint32_t) + value.size();
|
||||
}
|
||||
|
||||
template<typename T> requires BAN::is_same_v<T, BAN::String>
|
||||
inline BAN::ErrorOr<void> send_serialized_impl(int socket, const T& value)
|
||||
{
|
||||
const uint32_t value_size = value.size();
|
||||
TRY(send_raw_data(socket, BAN::ConstByteSpan::from(value_size)));
|
||||
auto* u8_data = reinterpret_cast<const uint8_t*>(value.data());
|
||||
TRY(send_raw_data(socket, BAN::ConstByteSpan(u8_data, value.size())));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T> requires BAN::is_same_v<T, BAN::String>
|
||||
inline BAN::ErrorOr<T> deserialize_impl(BAN::ConstByteSpan& buffer)
|
||||
{
|
||||
if (buffer.size() < sizeof(uint32_t))
|
||||
return BAN::Error::from_errno(ENOBUFS);
|
||||
const uint32_t string_len = buffer.as<const uint32_t>();
|
||||
buffer = buffer.slice(sizeof(uint32_t));
|
||||
|
||||
if (buffer.size() < string_len)
|
||||
return BAN::Error::from_errno(ENOBUFS);
|
||||
|
||||
BAN::String string;
|
||||
TRY(string.resize(string_len));
|
||||
memcpy(string.data(), buffer.data(), string_len);
|
||||
buffer = buffer.slice(string_len);
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
template<detail::Vector T>
|
||||
inline size_t serialized_size_impl(const T& vector)
|
||||
{
|
||||
size_t result = sizeof(uint32_t);
|
||||
for (const auto& element : vector)
|
||||
result += serialized_size_impl(element);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<detail::Vector T>
|
||||
inline BAN::ErrorOr<void> send_serialized_impl(int socket, const T& vector)
|
||||
{
|
||||
const uint32_t value_size = vector.size();
|
||||
TRY(send_raw_data(socket, BAN::ConstByteSpan::from(value_size)));
|
||||
for (const auto& element : vector)
|
||||
TRY(send_serialized_impl(socket, element));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<detail::Vector T>
|
||||
inline BAN::ErrorOr<T> deserialize_impl(BAN::ConstByteSpan& buffer)
|
||||
{
|
||||
if (buffer.size() < sizeof(uint32_t))
|
||||
return BAN::Error::from_errno(ENOBUFS);
|
||||
const uint32_t vector_size = buffer.as<const uint32_t>();
|
||||
buffer = buffer.slice(sizeof(uint32_t));
|
||||
|
||||
T vector;
|
||||
TRY(vector.resize(vector_size));
|
||||
for (auto& element : vector)
|
||||
element = TRY(deserialize_impl<typename T::value_type>(buffer));
|
||||
|
||||
return vector;
|
||||
}
|
||||
}
|
||||
|
||||
enum class PacketType : uint32_t
|
||||
{
|
||||
WindowCreate,
|
||||
WindowCreateResponse,
|
||||
WindowInvalidate,
|
||||
WindowSetPosition,
|
||||
WindowSetAttributes,
|
||||
WindowSetMouseRelative,
|
||||
WindowSetSize,
|
||||
WindowSetMinSize,
|
||||
WindowSetMaxSize,
|
||||
WindowSetFullscreen,
|
||||
WindowSetTitle,
|
||||
WindowSetCursor,
|
||||
|
||||
DestroyWindowEvent,
|
||||
CloseWindowEvent,
|
||||
ResizeWindowEvent,
|
||||
WindowShownEvent,
|
||||
WindowFocusEvent,
|
||||
KeyEvent,
|
||||
MouseButtonEvent,
|
||||
MouseMoveEvent,
|
||||
MouseScrollEvent,
|
||||
};
|
||||
|
||||
namespace WindowPacket
|
||||
{
|
||||
|
||||
struct Attributes
|
||||
{
|
||||
bool title_bar;
|
||||
bool movable;
|
||||
bool focusable;
|
||||
bool rounded_corners;
|
||||
bool alpha_channel;
|
||||
bool resizable;
|
||||
bool shown;
|
||||
bool cursor_visible;
|
||||
};
|
||||
|
||||
DEFINE_PACKET(
|
||||
WindowCreate,
|
||||
uint32_t, width,
|
||||
uint32_t, height,
|
||||
Attributes, attributes,
|
||||
BAN::String, title
|
||||
);
|
||||
|
||||
DEFINE_PACKET(
|
||||
WindowInvalidate,
|
||||
uint32_t, x,
|
||||
uint32_t, y,
|
||||
uint32_t, width,
|
||||
uint32_t, height
|
||||
);
|
||||
|
||||
DEFINE_PACKET(
|
||||
WindowSetPosition,
|
||||
int32_t, x,
|
||||
int32_t, y
|
||||
);
|
||||
|
||||
DEFINE_PACKET(
|
||||
WindowSetAttributes,
|
||||
Attributes, attributes
|
||||
);
|
||||
|
||||
DEFINE_PACKET(
|
||||
WindowSetMouseRelative,
|
||||
bool, enabled
|
||||
);
|
||||
|
||||
DEFINE_PACKET(
|
||||
WindowSetSize,
|
||||
uint32_t, width,
|
||||
uint32_t, height
|
||||
);
|
||||
|
||||
DEFINE_PACKET(
|
||||
WindowSetMinSize,
|
||||
uint32_t, width,
|
||||
uint32_t, height
|
||||
);
|
||||
|
||||
DEFINE_PACKET(
|
||||
WindowSetMaxSize,
|
||||
uint32_t, width,
|
||||
uint32_t, height
|
||||
);
|
||||
|
||||
DEFINE_PACKET(
|
||||
WindowSetFullscreen,
|
||||
bool, fullscreen
|
||||
);
|
||||
|
||||
DEFINE_PACKET(
|
||||
WindowSetTitle,
|
||||
BAN::String, title
|
||||
);
|
||||
|
||||
DEFINE_PACKET(
|
||||
WindowSetCursor,
|
||||
uint32_t, width,
|
||||
uint32_t, height,
|
||||
BAN::Vector<uint32_t>, pixels
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
namespace EventPacket
|
||||
{
|
||||
|
||||
DEFINE_PACKET(
|
||||
DestroyWindowEvent
|
||||
);
|
||||
|
||||
DEFINE_PACKET(
|
||||
CloseWindowEvent
|
||||
);
|
||||
|
||||
DEFINE_PACKET(
|
||||
ResizeWindowEvent,
|
||||
uint32_t, width,
|
||||
uint32_t, height,
|
||||
long, smo_key
|
||||
);
|
||||
|
||||
DEFINE_PACKET_EXTRA(
|
||||
WindowShownEvent,
|
||||
struct event_t {
|
||||
bool shown;
|
||||
},
|
||||
event_t, event
|
||||
);
|
||||
|
||||
DEFINE_PACKET_EXTRA(
|
||||
WindowFocusEvent,
|
||||
struct event_t {
|
||||
bool focused;
|
||||
},
|
||||
event_t, event
|
||||
);
|
||||
|
||||
DEFINE_PACKET_EXTRA(
|
||||
KeyEvent,
|
||||
using event_t = LibInput::KeyEvent,
|
||||
event_t, event
|
||||
);
|
||||
|
||||
DEFINE_PACKET_EXTRA(
|
||||
MouseButtonEvent,
|
||||
struct event_t {
|
||||
LibInput::MouseButton button;
|
||||
bool pressed;
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
},
|
||||
event_t, event
|
||||
);
|
||||
|
||||
DEFINE_PACKET_EXTRA(
|
||||
MouseMoveEvent,
|
||||
struct event_t {
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
},
|
||||
event_t, event
|
||||
);
|
||||
|
||||
DEFINE_PACKET_EXTRA(
|
||||
MouseScrollEvent,
|
||||
struct event_t {
|
||||
int32_t scroll;
|
||||
},
|
||||
event_t, event
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
103
LibGUI/include/LibGUI/Texture.h
Normal file
103
LibGUI/include/LibGUI/Texture.h
Normal file
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <BAN/StringView.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace LibFont { class Font; }
|
||||
|
||||
namespace LibGUI
|
||||
{
|
||||
|
||||
class Texture
|
||||
{
|
||||
public:
|
||||
static constexpr uint32_t color_invisible = 0x69000000;
|
||||
|
||||
public:
|
||||
static BAN::ErrorOr<Texture> create(uint32_t width, uint32_t height, uint32_t color);
|
||||
Texture() = default;
|
||||
|
||||
BAN::ErrorOr<void> resize(uint32_t width, uint32_t height);
|
||||
|
||||
void set_pixel(uint32_t x, uint32_t y, uint32_t color)
|
||||
{
|
||||
ASSERT(x < m_width);
|
||||
ASSERT(y < m_height);
|
||||
if (x < m_clip_x || x >= m_clip_x + m_clip_w)
|
||||
return;
|
||||
if (y < m_clip_y || y >= m_clip_y + m_clip_h)
|
||||
return;
|
||||
m_pixels[y * m_width + x] = color;
|
||||
}
|
||||
|
||||
uint32_t get_pixel(uint32_t x, uint32_t y) const
|
||||
{
|
||||
ASSERT(x < m_width);
|
||||
ASSERT(y < m_height);
|
||||
return m_pixels[y * m_width + x];
|
||||
}
|
||||
|
||||
BAN::Span<uint32_t> pixels() { return m_pixels.span(); }
|
||||
BAN::Span<const uint32_t> pixels() const { return m_pixels.span(); }
|
||||
|
||||
void set_clip_area(int32_t x, int32_t y, uint32_t width, uint32_t height);
|
||||
|
||||
void fill_rect(int32_t x, int32_t y, uint32_t width, uint32_t height, uint32_t color);
|
||||
void fill(uint32_t color) { return fill_rect(0, 0, width(), height(), color); }
|
||||
|
||||
void clear_rect(int32_t x, int32_t y, uint32_t width, uint32_t height) { fill_rect(x, y, width, height, m_bg_color); }
|
||||
void clear() { return clear_rect(0, 0, width(), height()); }
|
||||
|
||||
void copy_texture(const Texture& texture, int32_t x, int32_t y, uint32_t sub_x = 0, uint32_t sub_y = 0, uint32_t width = -1, uint32_t height = -1);
|
||||
|
||||
void draw_character(uint32_t codepoint, const LibFont::Font& font, int32_t x, int32_t y, uint32_t color);
|
||||
void draw_text(BAN::StringView text, const LibFont::Font& font, int32_t x, int32_t y, uint32_t color);
|
||||
|
||||
// shift whole vertically by amount pixels, sign determines the direction
|
||||
void shift_vertical(int32_t amount);
|
||||
|
||||
// copy horizontal slice [src_y, src_y + amount[ to [dst_y, dst_y + amount[
|
||||
void copy_horizontal_slice(int32_t dst_y, int32_t src_y, uint32_t amount);
|
||||
|
||||
// copy rect (src_x, src_y, width, height) to (dst_x, dst_y, width, height)
|
||||
void copy_rect(int32_t dst_x, int32_t dst_y, int32_t src_x, int32_t src_y, uint32_t width, uint32_t height);
|
||||
|
||||
uint32_t width() const { return m_width; }
|
||||
uint32_t height() const { return m_height; }
|
||||
|
||||
// used on resize to fill empty space
|
||||
void set_bg_color(uint32_t bg_color) { m_bg_color = bg_color; }
|
||||
|
||||
private:
|
||||
Texture(BAN::Vector<uint32_t>&& pixels, uint32_t width, uint32_t height, uint32_t color)
|
||||
: m_pixels(BAN::move(pixels))
|
||||
, m_width(width)
|
||||
, m_height(height)
|
||||
, m_bg_color(color)
|
||||
, m_clip_x(0)
|
||||
, m_clip_y(0)
|
||||
, m_clip_w(width)
|
||||
, m_clip_h(height)
|
||||
{}
|
||||
|
||||
bool clamp_to_texture(int32_t& x, int32_t& y, uint32_t& width, uint32_t& height) const;
|
||||
bool clamp_to_texture(int32_t& dst_x, int32_t& dst_y, int32_t& src_x, int32_t& src_y, uint32_t& width, uint32_t& height, const Texture&) const;
|
||||
|
||||
private:
|
||||
BAN::Vector<uint32_t> m_pixels;
|
||||
uint32_t m_width { 0 };
|
||||
uint32_t m_height { 0 };
|
||||
uint32_t m_bg_color { 0xFFFFFFFF };
|
||||
|
||||
uint32_t m_clip_x { 0 };
|
||||
uint32_t m_clip_y { 0 };
|
||||
uint32_t m_clip_w { 0 };
|
||||
uint32_t m_clip_h { 0 };
|
||||
bool m_has_set_clip { false };
|
||||
|
||||
friend class Window;
|
||||
};
|
||||
|
||||
}
|
||||
54
LibGUI/include/LibGUI/Widget/Button.h
Normal file
54
LibGUI/include/LibGUI/Widget/Button.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <BAN/Function.h>
|
||||
#include <BAN/StringView.h>
|
||||
|
||||
#include <LibGUI/Widget/RoundedWidget.h>
|
||||
|
||||
namespace LibGUI::Widget
|
||||
{
|
||||
|
||||
class Button : public RoundedWidget
|
||||
{
|
||||
public:
|
||||
struct Style : RoundedWidget::Style
|
||||
{
|
||||
Style()
|
||||
: RoundedWidget::Style()
|
||||
, color_hovered(0x808080)
|
||||
, color_text(0x000000)
|
||||
{}
|
||||
|
||||
uint32_t color_hovered;
|
||||
uint32_t color_text;
|
||||
};
|
||||
|
||||
public:
|
||||
static BAN::ErrorOr<BAN::RefPtr<Button>> create(BAN::RefPtr<Widget> parent, BAN::StringView text, Rectangle geometry = {});
|
||||
|
||||
BAN::ErrorOr<void> set_text(BAN::StringView);
|
||||
|
||||
Style& style() { return m_style; }
|
||||
const Style& style() const { return m_style; }
|
||||
|
||||
void set_click_callback(BAN::Function<void()> callback) { m_click_callback = callback; }
|
||||
|
||||
protected:
|
||||
Button(BAN::RefPtr<Widget> parent, Rectangle area)
|
||||
: RoundedWidget(parent, area)
|
||||
{ }
|
||||
|
||||
void update_impl() override;
|
||||
void show_impl() override;
|
||||
|
||||
bool on_mouse_button_impl(LibGUI::EventPacket::MouseButtonEvent::event_t) override;
|
||||
|
||||
private:
|
||||
Style m_style;
|
||||
bool m_hover_state { false };
|
||||
BAN::String m_text;
|
||||
|
||||
BAN::Function<void()> m_click_callback;
|
||||
};
|
||||
|
||||
}
|
||||
42
LibGUI/include/LibGUI/Widget/Grid.h
Normal file
42
LibGUI/include/LibGUI/Widget/Grid.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Widget/Widget.h>
|
||||
|
||||
namespace LibGUI::Widget
|
||||
{
|
||||
|
||||
class Grid : public Widget
|
||||
{
|
||||
public:
|
||||
static BAN::ErrorOr<BAN::RefPtr<Grid>> create(BAN::RefPtr<Widget> parent, uint32_t cols, uint32_t rows, uint32_t color = color_invisible, Rectangle geometry = {});
|
||||
|
||||
BAN::ErrorOr<void> set_widget_position(BAN::RefPtr<Widget> widget, uint32_t col, uint32_t col_span, uint32_t row, uint32_t row_span);
|
||||
|
||||
protected:
|
||||
Grid(BAN::RefPtr<Widget> parent, Rectangle geometry, uint32_t cols, uint32_t rows)
|
||||
: Widget(parent, geometry)
|
||||
, m_cols(cols)
|
||||
, m_rows(rows)
|
||||
{ }
|
||||
|
||||
BAN::ErrorOr<void> update_geometry_impl() override;
|
||||
|
||||
private:
|
||||
struct GridElement
|
||||
{
|
||||
BAN::RefPtr<Widget> widget;
|
||||
uint32_t col;
|
||||
uint32_t col_span;
|
||||
uint32_t row;
|
||||
uint32_t row_span;
|
||||
};
|
||||
|
||||
Rectangle grid_element_area(const GridElement& element) const;
|
||||
|
||||
private:
|
||||
const uint32_t m_cols;
|
||||
const uint32_t m_rows;
|
||||
BAN::Vector<GridElement> m_grid_elements;
|
||||
};
|
||||
|
||||
}
|
||||
44
LibGUI/include/LibGUI/Widget/Label.h
Normal file
44
LibGUI/include/LibGUI/Widget/Label.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <BAN/StringView.h>
|
||||
|
||||
#include <LibGUI/Widget/RoundedWidget.h>
|
||||
|
||||
namespace LibGUI::Widget
|
||||
{
|
||||
|
||||
class Label : public RoundedWidget
|
||||
{
|
||||
public:
|
||||
struct Style : RoundedWidget::Style
|
||||
{
|
||||
Style()
|
||||
: RoundedWidget::Style()
|
||||
, color_text(0x000000)
|
||||
{}
|
||||
|
||||
uint32_t color_text;
|
||||
};
|
||||
|
||||
public:
|
||||
static BAN::ErrorOr<BAN::RefPtr<Label>> create(BAN::RefPtr<Widget> parent, BAN::StringView text, Rectangle geometry = {});
|
||||
|
||||
BAN::StringView text() const { return m_text; }
|
||||
BAN::ErrorOr<void> set_text(BAN::StringView);
|
||||
|
||||
Style& style() { return m_style; }
|
||||
const Style& style() const { return m_style; }
|
||||
|
||||
protected:
|
||||
Label(BAN::RefPtr<Widget> parent, Rectangle area)
|
||||
: RoundedWidget(parent, area)
|
||||
{ }
|
||||
|
||||
void show_impl() override;
|
||||
|
||||
private:
|
||||
Style m_style;
|
||||
BAN::String m_text;
|
||||
};
|
||||
|
||||
}
|
||||
42
LibGUI/include/LibGUI/Widget/RoundedWidget.h
Normal file
42
LibGUI/include/LibGUI/Widget/RoundedWidget.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Widget/Widget.h>
|
||||
|
||||
namespace LibGUI::Widget
|
||||
{
|
||||
|
||||
class RoundedWidget : public Widget
|
||||
{
|
||||
public:
|
||||
struct Style
|
||||
{
|
||||
Style(uint32_t color_normal = 0xA0A0A0, uint32_t border_width = 1, uint32_t color_border = 0x000000, uint32_t corner_radius = 5)
|
||||
: color_normal(color_normal)
|
||||
, border_width(border_width)
|
||||
, color_border(color_border)
|
||||
, corner_radius(corner_radius)
|
||||
{}
|
||||
|
||||
uint32_t color_normal;
|
||||
uint32_t border_width;
|
||||
uint32_t color_border;
|
||||
uint32_t corner_radius;
|
||||
};
|
||||
|
||||
Style& style() { return m_style; }
|
||||
const Style& style() const { return m_style; }
|
||||
|
||||
protected:
|
||||
RoundedWidget(BAN::RefPtr<Widget> parent, Rectangle area)
|
||||
: Widget(parent, area)
|
||||
{ }
|
||||
|
||||
bool contains(Point point) const override;
|
||||
|
||||
void show_impl() override;
|
||||
|
||||
private:
|
||||
Style m_style;
|
||||
};
|
||||
|
||||
}
|
||||
50
LibGUI/include/LibGUI/Widget/TextArea.h
Normal file
50
LibGUI/include/LibGUI/Widget/TextArea.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <BAN/StringView.h>
|
||||
|
||||
#include <LibGUI/Widget/RoundedWidget.h>
|
||||
|
||||
namespace LibGUI::Widget
|
||||
{
|
||||
|
||||
class TextArea : public RoundedWidget
|
||||
{
|
||||
public:
|
||||
struct Style : RoundedWidget::Style
|
||||
{
|
||||
Style()
|
||||
: RoundedWidget::Style()
|
||||
, color_text(0x000000)
|
||||
{}
|
||||
|
||||
uint32_t color_text;
|
||||
};
|
||||
|
||||
public:
|
||||
static BAN::ErrorOr<BAN::RefPtr<TextArea>> create(BAN::RefPtr<Widget> parent, BAN::StringView text, Rectangle geometry = {});
|
||||
|
||||
BAN::StringView text() const { return m_text; }
|
||||
BAN::ErrorOr<void> set_text(BAN::StringView);
|
||||
|
||||
uint32_t get_required_height() const;
|
||||
|
||||
Style& style() { return m_style; }
|
||||
const Style& style() const { return m_style; }
|
||||
|
||||
protected:
|
||||
TextArea(BAN::RefPtr<Widget> parent, Rectangle area)
|
||||
: RoundedWidget(parent, area)
|
||||
{ }
|
||||
|
||||
BAN::ErrorOr<void> wrap_text();
|
||||
|
||||
BAN::ErrorOr<void> update_geometry_impl() override;
|
||||
void show_impl() override;
|
||||
|
||||
private:
|
||||
Style m_style;
|
||||
BAN::String m_text;
|
||||
BAN::Vector<BAN::String> m_wrapped_text;
|
||||
};
|
||||
|
||||
}
|
||||
163
LibGUI/include/LibGUI/Widget/Widget.h
Normal file
163
LibGUI/include/LibGUI/Widget/Widget.h
Normal file
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
|
||||
#include <BAN/RefPtr.h>
|
||||
|
||||
#include <LibGUI/Texture.h>
|
||||
#include <LibGUI/Packet.h>
|
||||
|
||||
namespace LibGUI { class Window; }
|
||||
|
||||
namespace LibGUI::Widget
|
||||
{
|
||||
|
||||
class Widget : public BAN::RefCounted<Widget>
|
||||
{
|
||||
public:
|
||||
static constexpr uint32_t color_invisible = Texture::color_invisible;
|
||||
|
||||
struct Point
|
||||
{
|
||||
int32_t x, y;
|
||||
};
|
||||
|
||||
struct FloatRectangle
|
||||
{
|
||||
float x, y;
|
||||
float w, h;
|
||||
};
|
||||
|
||||
struct Rectangle
|
||||
{
|
||||
int32_t x, y;
|
||||
uint32_t w, h;
|
||||
|
||||
struct Bounds
|
||||
{
|
||||
int32_t min_x, min_y;
|
||||
int32_t max_x, max_y;
|
||||
};
|
||||
|
||||
bool contains(Point point) const
|
||||
{
|
||||
if (point.x < x || point.x >= x + static_cast<int32_t>(w))
|
||||
return false;
|
||||
if (point.y < y || point.y >= y + static_cast<int32_t>(h))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bounds bounds(Rectangle other) const
|
||||
{
|
||||
return Bounds {
|
||||
.min_x = BAN::Math::max(x, other.x),
|
||||
.min_y = BAN::Math::max(y, other.y),
|
||||
.max_x = BAN::Math::min(x + static_cast<int32_t>(w), other.x + static_cast<int32_t>(other.w)),
|
||||
.max_y = BAN::Math::min(y + static_cast<int32_t>(h), other.y + static_cast<int32_t>(other.h)),
|
||||
};
|
||||
};
|
||||
|
||||
Rectangle overlap(Rectangle other) const
|
||||
{
|
||||
const auto min_x = BAN::Math::max(x, other.x);
|
||||
const auto min_y = BAN::Math::max(y, other.y);
|
||||
const auto max_x = BAN::Math::min(x + static_cast<int32_t>(w), other.x + static_cast<int32_t>(other.w));
|
||||
const auto max_y = BAN::Math::min(y + static_cast<int32_t>(h), other.y + static_cast<int32_t>(other.h));
|
||||
if (min_x >= max_x || min_y >= max_y)
|
||||
return {};
|
||||
return Rectangle {
|
||||
.x = min_x,
|
||||
.y = min_y,
|
||||
.w = static_cast<uint32_t>(max_x - min_x),
|
||||
.h = static_cast<uint32_t>(max_y - min_y),
|
||||
};
|
||||
}
|
||||
|
||||
Rectangle bounding_box(Rectangle other) const
|
||||
{
|
||||
if (w == 0 || h == 0)
|
||||
return other;
|
||||
if (other.w == 0 || other.h == 0)
|
||||
return *this;
|
||||
const auto min_x = BAN::Math::min(x, other.x);
|
||||
const auto min_y = BAN::Math::min(y, other.y);
|
||||
const auto max_x = BAN::Math::max(x + static_cast<int32_t>(w), other.x + static_cast<int32_t>(other.w));
|
||||
const auto max_y = BAN::Math::max(y + static_cast<int32_t>(h), other.y + static_cast<int32_t>(other.h));
|
||||
return Rectangle {
|
||||
.x = min_x,
|
||||
.y = min_y,
|
||||
.w = static_cast<uint32_t>(max_x - min_x),
|
||||
.h = static_cast<uint32_t>(max_y - min_y),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
static BAN::ErrorOr<BAN::RefPtr<Widget>> create(BAN::RefPtr<Widget> parent, uint32_t color = color_invisible, Rectangle geometry = {});
|
||||
|
||||
static BAN::ErrorOr<void> set_default_font(BAN::StringView path);
|
||||
static const LibFont::Font& default_font();
|
||||
|
||||
void show();
|
||||
void hide();
|
||||
|
||||
BAN::ErrorOr<void> set_fixed_geometry(Rectangle);
|
||||
BAN::ErrorOr<void> set_relative_geometry(FloatRectangle);
|
||||
|
||||
BAN::RefPtr<Widget> parent() { return m_parent; }
|
||||
|
||||
uint32_t width() const { return m_fixed_area.w; }
|
||||
uint32_t height() const { return m_fixed_area.h; }
|
||||
|
||||
private:
|
||||
void before_mouse_move();
|
||||
void after_mouse_move();
|
||||
bool on_mouse_move(LibGUI::EventPacket::MouseMoveEvent::event_t);
|
||||
|
||||
bool on_mouse_button(LibGUI::EventPacket::MouseButtonEvent::event_t);
|
||||
|
||||
protected:
|
||||
Widget(BAN::RefPtr<Widget> parent, Rectangle area)
|
||||
: m_parent(parent)
|
||||
, m_fixed_area(area)
|
||||
{ }
|
||||
|
||||
BAN::ErrorOr<void> initialize(uint32_t color);
|
||||
|
||||
virtual bool contains(Point point) const { return Rectangle { 0, 0, width(), height() }.contains(point); }
|
||||
|
||||
bool is_hovered() const { return m_hovered; }
|
||||
bool is_child_hovered() const;
|
||||
|
||||
bool is_shown() const { return m_shown; }
|
||||
|
||||
Rectangle render(Texture& output, Point parent_position, Rectangle out_area);
|
||||
|
||||
virtual void update_impl() {}
|
||||
virtual void show_impl() {}
|
||||
|
||||
virtual BAN::ErrorOr<void> update_geometry_impl();
|
||||
|
||||
virtual void on_hover_change_impl(bool hovered) { (void)hovered; }
|
||||
virtual bool on_mouse_move_impl(LibGUI::EventPacket::MouseMoveEvent::event_t) { return true; }
|
||||
virtual bool on_mouse_button_impl(LibGUI::EventPacket::MouseButtonEvent::event_t) { return true; }
|
||||
|
||||
protected:
|
||||
Texture m_texture;
|
||||
|
||||
private:
|
||||
BAN::RefPtr<Widget> m_parent;
|
||||
BAN::Vector<BAN::RefPtr<Widget>> m_children;
|
||||
bool m_shown { false };
|
||||
|
||||
Rectangle m_fixed_area;
|
||||
BAN::Optional<FloatRectangle> m_relative_area;
|
||||
|
||||
bool m_changed { false };
|
||||
|
||||
bool m_hovered { false };
|
||||
bool m_old_hovered { false };
|
||||
|
||||
friend class LibGUI::Window;
|
||||
};
|
||||
|
||||
}
|
||||
123
LibGUI/include/LibGUI/Window.h
Normal file
123
LibGUI/include/LibGUI/Window.h
Normal file
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include <BAN/Function.h>
|
||||
#include <BAN/StringView.h>
|
||||
#include <BAN/UniqPtr.h>
|
||||
|
||||
#include <LibGUI/Packet.h>
|
||||
#include <LibGUI/Texture.h>
|
||||
#include <LibGUI/Widget/Widget.h>
|
||||
|
||||
namespace LibFont { class Font; }
|
||||
|
||||
namespace LibGUI
|
||||
{
|
||||
|
||||
class Window
|
||||
{
|
||||
public:
|
||||
using Attributes = WindowPacket::Attributes;
|
||||
|
||||
static constexpr Attributes default_attributes = {
|
||||
.title_bar = true,
|
||||
.movable = true,
|
||||
.focusable = true,
|
||||
.rounded_corners = true,
|
||||
.alpha_channel = false,
|
||||
.resizable = false,
|
||||
.shown = true,
|
||||
.cursor_visible = true,
|
||||
};
|
||||
|
||||
public:
|
||||
~Window();
|
||||
|
||||
static BAN::ErrorOr<BAN::UniqPtr<Window>> create(uint32_t width, uint32_t height, BAN::StringView title, Attributes attributes = default_attributes);
|
||||
|
||||
BAN::ErrorOr<void> set_root_widget(BAN::RefPtr<Widget::Widget> widget);
|
||||
BAN::RefPtr<Widget::Widget> root_widget() { return m_root_widget; }
|
||||
|
||||
Texture& texture() { return m_texture; }
|
||||
const Texture& texture() const { return m_texture; }
|
||||
|
||||
void invalidate(int32_t x, int32_t y, uint32_t width, uint32_t height);
|
||||
void invalidate() { return invalidate(0, 0, width(), height()); }
|
||||
|
||||
void set_mouse_relative(bool enabled);
|
||||
void set_fullscreen(bool fullscreen);
|
||||
void set_title(BAN::StringView title);
|
||||
|
||||
void set_position(int32_t x, int32_t y);
|
||||
|
||||
void set_cursor_visible(bool visible);
|
||||
void set_cursor(uint32_t width, uint32_t height, BAN::Span<uint32_t> pixels);
|
||||
|
||||
Attributes get_attributes() const { return m_attributes; }
|
||||
void set_attributes(Attributes attributes);
|
||||
|
||||
void set_min_size(uint32_t width, uint32_t height);
|
||||
void set_max_size(uint32_t width, uint32_t height);
|
||||
|
||||
// send resize request to window server
|
||||
// actual resize is only done after resize callback is called
|
||||
void request_resize(uint32_t width, uint32_t height);
|
||||
|
||||
uint32_t width() const { return m_width; }
|
||||
uint32_t height() const { return m_height; }
|
||||
|
||||
void wait_events();
|
||||
void poll_events();
|
||||
|
||||
void set_socket_error_callback(BAN::Function<void()> callback) { m_socket_error_callback = callback; }
|
||||
void set_close_window_event_callback(BAN::Function<void()> callback) { m_close_window_event_callback = callback; }
|
||||
void set_resize_window_event_callback(BAN::Function<void()> callback) { m_resize_window_event_callback = callback; }
|
||||
void set_key_event_callback(BAN::Function<void(EventPacket::KeyEvent::event_t)> callback) { m_key_event_callback = callback; }
|
||||
void set_mouse_button_event_callback(BAN::Function<void(EventPacket::MouseButtonEvent::event_t)> callback) { m_mouse_button_event_callback = callback; }
|
||||
void set_mouse_move_event_callback(BAN::Function<void(EventPacket::MouseMoveEvent::event_t)> callback) { m_mouse_move_event_callback = callback; }
|
||||
void set_mouse_scroll_event_callback(BAN::Function<void(EventPacket::MouseScrollEvent::event_t)> callback) { m_mouse_scroll_event_callback = callback; }
|
||||
void set_window_shown_event_callback(BAN::Function<void(EventPacket::WindowShownEvent::event_t)> callback) { m_window_shown_event_callback = callback; }
|
||||
void set_window_focus_event_callback(BAN::Function<void(EventPacket::WindowFocusEvent::event_t)> callback) { m_window_focus_event_callback = callback; }
|
||||
|
||||
int server_fd() const { return m_server_fd; }
|
||||
|
||||
private:
|
||||
Window(int server_fd, int epoll_fd, Attributes attributes)
|
||||
: m_server_fd(server_fd)
|
||||
, m_epoll_fd(epoll_fd)
|
||||
, m_attributes(attributes)
|
||||
{ }
|
||||
|
||||
void on_socket_error(BAN::StringView function);
|
||||
void cleanup();
|
||||
|
||||
BAN::ErrorOr<void> handle_resize_event(const EventPacket::ResizeWindowEvent&);
|
||||
|
||||
private:
|
||||
const int m_server_fd;
|
||||
const int m_epoll_fd;
|
||||
|
||||
bool m_handling_socket_error { false };
|
||||
|
||||
Attributes m_attributes;
|
||||
|
||||
uint32_t* m_framebuffer_smo { nullptr };
|
||||
uint32_t m_width { 0 };
|
||||
uint32_t m_height { 0 };
|
||||
|
||||
Texture m_texture;
|
||||
BAN::RefPtr<Widget::Widget> m_root_widget;
|
||||
|
||||
BAN::Function<void()> m_socket_error_callback;
|
||||
BAN::Function<void()> m_close_window_event_callback;
|
||||
BAN::Function<void()> m_resize_window_event_callback;
|
||||
BAN::Function<void(EventPacket::WindowShownEvent::event_t)> m_window_shown_event_callback;
|
||||
BAN::Function<void(EventPacket::WindowFocusEvent::event_t)> m_window_focus_event_callback;
|
||||
BAN::Function<void(EventPacket::KeyEvent::event_t)> m_key_event_callback;
|
||||
BAN::Function<void(EventPacket::MouseButtonEvent::event_t)> m_mouse_button_event_callback;
|
||||
BAN::Function<void(EventPacket::MouseMoveEvent::event_t)> m_mouse_move_event_callback;
|
||||
BAN::Function<void(EventPacket::MouseScrollEvent::event_t)> m_mouse_scroll_event_callback;
|
||||
|
||||
friend class BAN::UniqPtr<Window>;
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user