#include #include namespace LibGUI::Widget { static BAN::Optional s_default_font; BAN::ErrorOr 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> Widget::create(BAN::RefPtr 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::adopt(widget_ptr); TRY(widget->initialize(color)); return widget; } BAN::ErrorOr 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 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 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(area.x * m_parent->width()), .y = static_cast(area.y * m_parent->height()), .w = static_cast(area.w * m_parent->width()), .h = static_cast(area.h * m_parent->height()), })); m_relative_area = area; return {}; } BAN::ErrorOr 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; } }