diff --git a/src/Box.cpp b/src/Box.cpp index 3fe7452..aa5e0ae 100644 --- a/src/Box.cpp +++ b/src/Box.cpp @@ -4,6 +4,15 @@ namespace Raven { +Box Box::offset(double x, double y) { + return Box{ + m_x + x, + m_y + y, + m_width, + m_height + }; +} + bool Box::contains_point(double x, double y) const { return x >= m_x && m_x + m_width >= x && y >= m_y && m_y + m_height >= y; } diff --git a/src/Box.hpp b/src/Box.hpp index 68e3b5f..920816e 100644 --- a/src/Box.hpp +++ b/src/Box.hpp @@ -59,6 +59,8 @@ public: bool contains_point(double x, double y) const; bool contains_point(const Point &point) const; bool contains_box(const Box &other) const; + + Box offset(double x, double y); }; } diff --git a/src/Button.cpp b/src/Button.cpp index 992154d..ad7c111 100644 --- a/src/Button.cpp +++ b/src/Button.cpp @@ -35,8 +35,6 @@ void Button::update_color() { } void Button::on_paint() { - do_generic_paint(); - auto painter = window()->painter(); auto text_color = styles()->button_text_color(); diff --git a/src/DocumentLayout.cpp b/src/DocumentLayout.cpp index b28422b..9b8d27d 100644 --- a/src/DocumentLayout.cpp +++ b/src/DocumentLayout.cpp @@ -9,7 +9,7 @@ void DocumentLayout::run() { if (!m_target) return; - Point current_position { m_target->current_geometry().x() + m_margin, m_target->current_geometry().y() + m_margin }; + Point current_position { m_margin, m_margin }; double largest_height_so_far = -1.0; auto& children = m_target->children(); diff --git a/src/Events.hpp b/src/Events.hpp index e399ac1..e07eec6 100644 --- a/src/Events.hpp +++ b/src/Events.hpp @@ -17,9 +17,8 @@ enum class EventType { }; enum class ReflowType { - RepaintSubtree, - RelayoutSubtree, - RepaintSelf + RepaintRect, + RelayoutRect }; enum class ReflowGrouping { @@ -78,20 +77,24 @@ public: class ReflowEvent: public Event { private: ReflowType m_reflow_type; - ReflowGrouping m_reflow_grouping; + ReflowGrouping m_grouping; + Box m_box; public: - ReflowEvent(ReflowType type, ReflowGrouping grouping) + ReflowEvent(ReflowType type, ReflowGrouping grouping, Box box) : m_reflow_type(type) - , m_reflow_grouping(grouping) {} + , m_grouping(grouping) + , m_box(box) {} EventType type() { return EventType::Reflow; } const char *name() { return "Reflow"; } ReflowType reflow_type() { return m_reflow_type; } - ReflowGrouping reflow_grouping() { return m_reflow_grouping; } + ReflowGrouping grouping() { return m_grouping; } + Box &box() { return m_box; } - void set_reflow_grouping(ReflowGrouping reflow_grouping) { m_reflow_grouping = reflow_grouping; } - void set_reflow_type(ReflowType reflow_type) { m_reflow_type = reflow_type; } + void set_grouping(ReflowGrouping grouping) { m_grouping = grouping; } + void set_reflow_type(ReflowType type) { m_reflow_type = type; } + void set_box(Box box) { m_box = box; } }; class FocusUpdateEvent : public Event { diff --git a/src/Label.cpp b/src/Label.cpp index c6d6fa1..b3b24a7 100644 --- a/src/Label.cpp +++ b/src/Label.cpp @@ -16,11 +16,10 @@ void Label::on_init() { fit_text(m_text); set_did_init(true); + set_do_background_fill(false); } void Label::on_paint() { - do_generic_paint(); - auto painter = window()->painter(); auto text_color = styles()->label_text_color(); diff --git a/src/Painter.cpp b/src/Painter.cpp index 3eca6c9..a5b2042 100644 --- a/src/Painter.cpp +++ b/src/Painter.cpp @@ -10,8 +10,8 @@ void Painter::rounded_rectangle(Box &geometry, double border_radius) { double aspect = 1.0; double radius = border_radius / aspect; double degrees = M_PI / 180.0; - double x = geometry.x(); - double y = geometry.y(); + double x = 0; + double y = 0; double w = geometry.width(); double h = geometry.height(); @@ -23,23 +23,6 @@ void Painter::rounded_rectangle(Box &geometry, double border_radius) { m_cairo->close_path(); } -bool Painter::text(Point &where, std::string &text) { - if (m_pango_font_description == nullptr) - return false; - - PangoLayout *layout = pango_cairo_create_layout(m_cairo->cobj()); - - pango_layout_set_font_description(layout, m_pango_font_description); - pango_layout_set_text(layout, text.c_str(), -1); - - m_cairo->move_to(where.x(), where.y()); - pango_cairo_show_layout(m_cairo->cobj(), layout); - - g_object_unref(layout); - - return true; -} - Point Painter::compute_text_size(Box &widget_geometry, std::string &text) { if (m_pango_font_description == nullptr) return {-1, -1}; @@ -85,13 +68,11 @@ Point Painter::text(Box &geometry, std::string &text, PaintTextAlign align, Pang pango_layout_get_pixel_size(layout, &font_width, &font_height); pango_layout_get_size(layout, &pango_width, &pango_height); - double x = -1; - double y = geometry.y() + ((geometry.height() - font_height) / 2); + double x = 0; + double y = ((geometry.height() - font_height) / 2); if (align == PaintTextAlign::Center) { - x = geometry.x() + ((geometry.width() - font_width) / 2); - } else { - x = geometry.x(); + x = ((geometry.width() - font_width) / 2); } m_cairo->move_to(x, y); diff --git a/src/Painter.hpp b/src/Painter.hpp index a6d1afc..c1e2ae3 100644 --- a/src/Painter.hpp +++ b/src/Painter.hpp @@ -28,7 +28,6 @@ public: PangoFontDescription *pango_font_description() { return m_pango_font_description; } void rounded_rectangle(Box &geometry, double border_radius); - bool text(Point &where, std::string &text); Point text(Box &geometry, std::string &text, PaintTextAlign align, PangoEllipsizeMode ellipsize, bool set_size); Point compute_text_size(Box &widget_geometry, std::string &text); diff --git a/src/Widget.cpp b/src/Widget.cpp index ff66fe1..bd43c50 100644 --- a/src/Widget.cpp +++ b/src/Widget.cpp @@ -68,9 +68,11 @@ bool Widget::add_child(std::shared_ptr child) { } m_children.push_back(child); child->set_parent(this); + // children inherit the window from the parent // TODO?: what happens when the parent changes its window? child->set_window(m_window); + wants_full_relayout(); return true; } @@ -80,95 +82,83 @@ void Widget::remove_child(std::shared_ptr child) { wants_full_relayout(); } -void Widget::do_generic_paint() { - if (m_do_background_fill) { - auto painter = m_window->painter(); - auto cr = painter.cairo(); - - cr->save(); - - painter.source_rgb(m_background_fill_color); - painter.rounded_rectangle(m_current_geometry, m_background_border_radius); - painter.fill(); - - cr->restore(); - } -} - void Widget::wants_repaint() { - if (!m_window) - return; - - // TODO: not necessary? - m_window->widget_repaint(this); + if (m_window) + m_window->reflow(this, ReflowType::RepaintRect); } void Widget::wants_relayout() { - if (!m_window) - return; - - // TODO: not necessary? - m_window->widget_relayout(this); + if (m_window) + m_window->reflow(this, ReflowType::RelayoutRect); } void Widget::wants_full_repaint() { - if (!m_window) - return; - - m_window->dispatch_full_repaint(); + if (m_window) + m_window->reflow(ReflowType::RepaintRect); } void Widget::wants_full_relayout() { - if (!m_window) - return; - - m_window->dispatch_full_relayout(); + if (m_window) + m_window->reflow(ReflowType::RelayoutRect); } void Widget::handle_reflow(ReflowEvent &event) { - // immediately accept the event - we will do our own propagation logic - event.accept(); - - if (!m_did_init) - return; + event.accept(); // immediately accept the event - we will do our own propagation logic auto painter = m_window->painter(); - if (!painter.can_paint()) + if (!m_did_init || !painter.can_paint()) return; - auto cr = painter.cairo(); - auto grouping = event.reflow_grouping(); - auto type = event.reflow_type(); + if (!event.box().contains_box(current_geometry())) + return; // widgets contain their children, thus we don't need to recurse further - if (grouping == ReflowGrouping::Yes) { + bool should_end_paint_group = false; + if (event.grouping() == ReflowGrouping::Yes) { painter.begin_paint_group(); + should_end_paint_group = true; } - if (type == ReflowType::RelayoutSubtree) { + if (event.reflow_type() == ReflowType::RelayoutRect) { do_layout(); } - cr->save(); - on_paint(); - cr->restore(); + auto cr = painter.cairo(); - if (type != ReflowType::RepaintSelf) { - // we will propagate this event to all of our children, except it will have - // should_do_group set to false - event.set_reflow_grouping(ReflowGrouping::No); - for (auto& child : m_children) { - child->dispatch_event(event); - } + cr->save(); + + cr->translate(current_geometry().x(), current_geometry().y()); + painter.rounded_rectangle(m_current_geometry, m_background_border_radius); + cr->clip(); + if (m_do_background_fill) { + painter.source_rgb(m_background_fill_color); + painter.rounded_rectangle(event.box(), m_background_border_radius); + cr->fill(); } - if (grouping == ReflowGrouping::Yes) { + on_paint(); + + Box box = event.box(); + for (auto& child : m_children) { + // convert the invalidation rectangle to the child's coordinate space + auto local_rect = box.offset(child->current_geometry().x(), child->current_geometry().y()); + auto local_event = ReflowEvent(event.reflow_type(), ReflowGrouping::No, local_rect); + child->dispatch_event(local_event); + } + + cr->restore(); + + if (should_end_paint_group) { painter.end_paint_group(); } } void Widget::handle_mouse_move_event(MouseMoveEvent &event) { + event.accept(); // we will do our own propagation logic. TODO: remove automatic event propagation? + bool update_focus_to = true; + std::cout << "point: " << event.point().x() << ", " << event.point().y() << std::endl; + if (!m_current_geometry.contains_point(event.point())) { // we just became unfocused if (m_is_focused) { @@ -186,11 +176,20 @@ void Widget::handle_mouse_move_event(MouseMoveEvent &event) { on_focus_update(focus_update_event); on_mouse_move(event); + if (!m_consumes_hits) { + for (auto &c : children()) { + auto e = MouseMoveEvent { + Point { + event.point().x() - current_geometry().x(), + event.point().y() - current_geometry().y() + } + }; + c->dispatch_event(e); + } + } + if (m_is_focused && m_window) m_window->set_focused_widget(this); - - if (m_consumes_hits) - event.accept(); } void Widget::handle_mouse_button_event(MouseButtonEvent &event) { @@ -253,7 +252,6 @@ void Widget::dispatch_event(Event &event) { void Widget::on_init() { set_background_fill_color(styles()->background_color()); - set_do_background_fill(true); set_did_init(true); } diff --git a/src/Widget.hpp b/src/Widget.hpp index 111ebc6..e51374c 100644 --- a/src/Widget.hpp +++ b/src/Widget.hpp @@ -27,7 +27,7 @@ enum class ControlWidgetType { }; class Widget { -DEF_WIDGET_STYLE_PROP(do_background_fill, bool, false) +DEF_WIDGET_STYLE_PROP(do_background_fill, bool, true) DEF_WIDGET_STYLE_PROP(background_fill_color, RGB, 0, 0, 0) DEF_WIDGET_STYLE_PROP(background_border_radius, double, 0.0) @@ -119,9 +119,8 @@ protected: virtual void on_mouse_move(MouseMoveEvent &event) {} virtual void on_focus_update(FocusUpdateEvent &event) {} virtual void on_activation_update(ActivationUpdateEvent &event) {} - virtual void on_paint() { do_generic_paint(); } + virtual void on_paint() {} - void do_generic_paint(); void wants_full_repaint(); void wants_full_relayout(); private: diff --git a/src/Window.cpp b/src/Window.cpp index c044f59..29de1be 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -22,7 +22,7 @@ void Window::set_main_widget(std::shared_ptr main_widget) { bool Window::spawn_window() { Display *dsp = XOpenDisplay(NULL); if (dsp == NULL) { - std::cout << "error: XOpenDisplay(NULL)" << "\n"; + std::cerr << "error: XOpenDisplay(NULL)" << "\n"; return false; } m_x_display = dsp; @@ -60,54 +60,59 @@ bool Window::spawn_window() { return true; } -void Window::widget_repaint(Widget *target) { - // TODO: HACK, maybe instad have a vector of widgets that need to be repainted once the batch is ended? - m_did_repaint_during_batch = true; - if (m_is_batching) - return; - - auto event = ReflowEvent(ReflowType::RepaintSubtree, ReflowGrouping::Yes); - target->dispatch_event(event); -} - -void Window::widget_relayout(Widget *target) { - // TODO: HACK, maybe instad have a vector of widgets that need to be relayouted once the batch is ended? - m_did_relayout_during_batch = true; - if (m_is_batching) - return; - - auto event = ReflowEvent(ReflowType::RelayoutSubtree, ReflowGrouping::Yes); - target->dispatch_event(event); -} - bool Window::dispatch_to_main_widget(Event &event) { if (!m_main_widget) return false; - + std::cout << "Dispatched " << event.name() << " to main widget" << std::endl; m_main_widget->dispatch_event(event); return true; } -bool Window::dispatch_full_repaint() { - m_did_repaint_during_batch = true; +void Window::reflow(Widget *target, ReflowType type) { + if (type == ReflowType::RelayoutRect) { + m_did_relayout_during_batch = true; + } else if (type == ReflowType::RepaintRect) { + m_did_repaint_during_batch = true; + } - if (m_is_batching) - return false; + if (m_is_batching) { + return; + } - auto event = ReflowEvent(ReflowType::RepaintSubtree, ReflowGrouping::Yes); - return dispatch_to_main_widget(event); + auto event = ReflowEvent(type, ReflowGrouping::Yes, target->current_geometry()); + target->dispatch_event(event); } -bool Window::dispatch_full_relayout() { - m_did_relayout_during_batch = true; +void Window::reflow(ReflowType type) { + if (type == ReflowType::RelayoutRect) { + m_did_relayout_during_batch = true; + } else if (type == ReflowType::RepaintRect) { + m_did_repaint_during_batch = true; + } - if (m_is_batching) - return false; + if (m_is_batching) { + return; + } - auto event = ReflowEvent(ReflowType::RelayoutSubtree, ReflowGrouping::Yes); - return dispatch_to_main_widget(event); + auto event = ReflowEvent(type, ReflowGrouping::Yes, m_current_geometry); + dispatch_to_main_widget(event); +} + +void Window::reflow(Box &box, ReflowType type) { + if (type == ReflowType::RelayoutRect) { + m_did_relayout_during_batch = true; + } else if (type == ReflowType::RepaintRect) { + m_did_repaint_during_batch = true; + } + + if (m_is_batching) { + return; + } + + auto event = ReflowEvent(type, ReflowGrouping::Yes, box); + dispatch_to_main_widget(event); } void Window::start_batch() { @@ -120,9 +125,9 @@ void Window::end_batch() { if (m_is_batching) { m_is_batching = false; if (m_did_relayout_during_batch) { - dispatch_full_relayout(); + reflow(ReflowType::RelayoutRect); } else if (m_did_repaint_during_batch) { - dispatch_full_repaint(); + reflow(ReflowType::RelayoutRect); } } } @@ -156,25 +161,25 @@ void Window::run(bool block) { } case Expose: { if (e.xexpose.count == 0) { - dispatch_full_relayout(); + reflow(ReflowType::RelayoutRect); } break; } case ButtonRelease: { auto point = Point(e.xbutton.x, e.xbutton.y); - auto event = MouseButtonEvent(false, false, std::move(point)); + auto event = MouseButtonEvent(false, false, point); dispatch_to_main_widget(event); break; } case ButtonPress: { auto point = Point(e.xbutton.x, e.xbutton.y); - auto event = MouseButtonEvent(e.xbutton.button == Button1, e.xbutton.button == Button2, std::move(point)); + auto event = MouseButtonEvent(e.xbutton.button == Button1, e.xbutton.button == Button2, point); dispatch_to_main_widget(event); break; } case MotionNotify: { auto point = Point(e.xmotion.x, e.xmotion.y); - auto event = MouseMoveEvent(std::move(point)); + auto event = MouseMoveEvent(point); dispatch_to_main_widget(event); } default: { diff --git a/src/Window.hpp b/src/Window.hpp index ece35c5..e31f269 100644 --- a/src/Window.hpp +++ b/src/Window.hpp @@ -4,6 +4,7 @@ #include #include "Widget.hpp" #include "Painter.hpp" +#include "src/Events.hpp" #include #include #include @@ -35,12 +36,9 @@ public: std::shared_ptr top_level_styles() { return m_top_level_styles; } - void widget_repaint(Widget *target); - void widget_relayout(Widget *target); - - bool dispatch_full_repaint(); - bool dispatch_full_relayout(); - bool dispatch_to_main_widget(Event &event); + void reflow(Widget *target, ReflowType type); + void reflow(Box &box, ReflowType type); + void reflow(ReflowType type); Box ¤t_geometry() { return m_current_geometry; } @@ -63,6 +61,8 @@ private: bool m_did_repaint_during_batch { false }; Display *m_x_display { nullptr }; + + bool dispatch_to_main_widget(Event &event); }; } diff --git a/src/main.cpp b/src/main.cpp index 4e6646f..8cdc1ab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,24 +16,32 @@ int main() { window.spawn_window(); auto main_widget = window.set_main_widget(); - main_widget->set_layout(6.0); - bool is_dragging = false; + auto inner_widget = main_widget->add(); + inner_widget->set_layout(8.0); + inner_widget->resize(400, 400); - auto button = main_widget->add("hello"); - button->set_absolute(true); - button->on_click = [&]() { - is_dragging = !is_dragging; - }; + int number = 0; - main_widget->on_event = [&](Raven::Event &event) { - if (event.type() == Raven::EventType::MouseMove) { - auto mouse_move = reinterpret_cast(event); - if (is_dragging) { - button->move_to(mouse_move.point().x(), mouse_move.point().y()); + for (int i = 0; i < 250; i++) { + auto button = inner_widget->add("click me"); + auto label = inner_widget->add("click one of the buttons!"); + button->on_click = [&]() { + number += 10; + + window.start_batch(); + auto& children = inner_widget->children(); + for (auto& child : children) { + if (child->type() == Raven::WidgetType::Button) { + std::static_pointer_cast(child)->set_text(std::to_string(number)); + } + if (child->type() == Raven::WidgetType::Label) { + std::static_pointer_cast(child)->set_text(std::to_string(number)); + } } - } - }; + window.end_batch(); + }; + } window.run(true); return 0;