diff --git a/src/Button.cpp b/src/Button.cpp index ad7c111..8faf7cb 100644 --- a/src/Button.cpp +++ b/src/Button.cpp @@ -17,6 +17,7 @@ void Button::set_text(std::string text) { } void Button::on_init() { + set_background_border_radius(styles()->button_border_radius()); set_background_fill_color(styles()->button_normal_color()); set_do_background_fill(true); fit_text(m_text); diff --git a/src/DocumentLayout.cpp b/src/DocumentLayout.cpp index 9b8d27d..dae8fd3 100644 --- a/src/DocumentLayout.cpp +++ b/src/DocumentLayout.cpp @@ -11,9 +11,9 @@ void DocumentLayout::run() { Point current_position { m_margin, m_margin }; double largest_height_so_far = -1.0; - auto& children = m_target->children(); - for (auto& child : children) { + auto& children = m_target->children(); + for (auto child : children) { if (child->absolute()) continue; diff --git a/src/Events.hpp b/src/Events.hpp index e07eec6..9b2391a 100644 --- a/src/Events.hpp +++ b/src/Events.hpp @@ -11,21 +11,12 @@ enum class EventType { MouseButton, MouseMove, - Reflow, + RelayoutSubtree, + RepaintRect, FocusUpdate, ActivationUpdate, }; -enum class ReflowType { - RepaintRect, - RelayoutRect -}; - -enum class ReflowGrouping { - Yes, - No -}; - class Event { private: bool m_accepted { false }; @@ -72,31 +63,36 @@ public: const char *name() { return "MouseMove"; } Point &point() { return m_point; } + void set_point(Point point) { m_point = point; } }; -class ReflowEvent: public Event { +class RepaintRectEvent : public Event { private: - ReflowType m_reflow_type; - ReflowGrouping m_grouping; + bool m_grouping { true }; Box m_box; public: - ReflowEvent(ReflowType type, ReflowGrouping grouping, Box box) - : m_reflow_type(type) - , m_grouping(grouping) + RepaintRectEvent(bool grouping, Box box) + : m_grouping(grouping) , m_box(box) {} - EventType type() { return EventType::Reflow; } - const char *name() { return "Reflow"; } + EventType type() { return EventType::RepaintRect; } + const char *name() { return "RepaintRect"; } - ReflowType reflow_type() { return m_reflow_type; } - ReflowGrouping grouping() { return m_grouping; } + bool grouping() { return m_grouping; } Box &box() { return m_box; } - void set_grouping(ReflowGrouping grouping) { m_grouping = grouping; } - void set_reflow_type(ReflowType type) { m_reflow_type = type; } + void set_grouping(bool grouping) { m_grouping = grouping; } void set_box(Box box) { m_box = box; } }; +class RelayoutSubtreeEvent : public Event { + public: + RelayoutSubtreeEvent() {} + + EventType type() { return EventType::RelayoutSubtree; } + const char *name() { return "RelayoutSubtree"; } +}; + class FocusUpdateEvent : public Event { private: bool m_focus_status; diff --git a/src/Painter.cpp b/src/Painter.cpp index a5b2042..2caf394 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 = 0; - double y = 0; + double x = geometry.x(); + double y = geometry.y(); double w = geometry.width(); double h = geometry.height(); diff --git a/src/PropMacros.hpp b/src/PropMacros.hpp index 2c6dd5b..ed98e92 100644 --- a/src/PropMacros.hpp +++ b/src/PropMacros.hpp @@ -4,6 +4,6 @@ private: \ type m_##name {__VA_ARGS__}; \ public: \ - void set_##name(type new_prop_value) { m_##name = new_prop_value; wants_repaint(); } \ + void set_##name(type new_prop_value) { m_##name = new_prop_value; repaint(); } \ type name() { return m_##name; } \ private: diff --git a/src/TopLevelStyles.hpp b/src/TopLevelStyles.hpp index e9f0a03..386f371 100644 --- a/src/TopLevelStyles.hpp +++ b/src/TopLevelStyles.hpp @@ -23,7 +23,7 @@ DEF_WIDGET_STYLE_PROP(button_text_color, RGB, m_text_color) DEF_WIDGET_STYLE_PROP(button_normal_color, RGB, m_accent_color) DEF_WIDGET_STYLE_PROP(button_focused_color, RGB, m_accent_color_darker) DEF_WIDGET_STYLE_PROP(button_active_color, RGB, m_accent_color_darkest) -DEF_WIDGET_STYLE_PROP(button_border_radius, double, 8.0) +DEF_WIDGET_STYLE_PROP(button_border_radius, double, 6.0) DEF_WIDGET_STYLE_PROP(label_text_color, RGB, m_text_color) @@ -51,7 +51,7 @@ public: Window *window() { return m_window; } void set_window(Window *window) { m_window = window; } - void wants_repaint(); + void repaint(); }; } diff --git a/src/Widget.cpp b/src/Widget.cpp index 82baaa8..5beeb32 100644 --- a/src/Widget.cpp +++ b/src/Widget.cpp @@ -9,6 +9,14 @@ namespace Raven { +Point Widget::window_relative() { + Point point = { 0, 0 }; + for (Widget* parent = m_parent; parent; parent = parent->parent()) { + point.add(parent->current_geometry().x(), parent->current_geometry().y()); + } + return point; +} + void Widget::fit_text(std::string &text) { if (!window()) return; @@ -16,7 +24,7 @@ void Widget::fit_text(std::string &text) { window()->painter().set_pango_font_description(styles()->controls_font_description()); auto size = window()->painter().compute_text_size(current_geometry(), text); if (!resize(size)) { - wants_full_relayout(); + reflow(); } } @@ -29,7 +37,7 @@ bool Widget::resize(double width, double height) { m_current_geometry.set_width(width); m_current_geometry.set_height(height); - wants_full_relayout(); + reflow(); return true; } @@ -39,7 +47,7 @@ void Widget::move_to(double x, double y) { m_current_geometry.set_x(x); m_current_geometry.set_y(y); - wants_full_relayout(); + reflow(); } void Widget::do_layout() { @@ -73,36 +81,26 @@ bool Widget::add_child(std::shared_ptr child) { // TODO?: what happens when the parent changes its window? child->set_window(m_window); - wants_full_relayout(); + reflow(); return true; } void Widget::remove_child(std::shared_ptr child) { m_children.erase(std::remove(m_children.begin(), m_children.end(), child), m_children.end()); - wants_full_relayout(); + reflow(); } -void Widget::wants_repaint() { +void Widget::repaint() { if (m_window) - m_window->reflow(this, ReflowType::RepaintRect); + m_window->repaint(this); } -void Widget::wants_relayout() { +void Widget::reflow() { if (m_window) - m_window->reflow(this, ReflowType::RelayoutRect); + m_window->reflow(); } -void Widget::wants_full_repaint() { - if (m_window) - m_window->reflow(ReflowType::RepaintRect); -} - -void Widget::wants_full_relayout() { - if (m_window) - m_window->reflow(ReflowType::RelayoutRect); -} - -void Widget::handle_reflow(ReflowEvent &event) { +void Widget::handle_repaint_rect(RepaintRectEvent &event) { event.accept(); // immediately accept the event - we will do our own propagation logic auto painter = m_window->painter(); @@ -112,36 +110,40 @@ void Widget::handle_reflow(ReflowEvent &event) { if (!event.box().contains_box(current_geometry())) return; // widgets contain their children, thus we don't need to recurse further + // using a "group" in cairo reduces flickering bool should_end_paint_group = false; - if (event.grouping() == ReflowGrouping::Yes) { + if (event.grouping()) { painter.begin_paint_group(); should_end_paint_group = true; } - if (event.reflow_type() == ReflowType::RelayoutRect) { - do_layout(); - } - auto cr = painter.cairo(); cr->save(); - cr->translate(current_geometry().x(), current_geometry().y()); + // clip this widget. this ensuers that the background fill or children won't overflow the bounds of it painter.rounded_rectangle(m_current_geometry, m_background_border_radius); cr->clip(); + + // paint the background fill + // note that we're using the bounds of the paint event as the rectangle, this ensures that we dont draw over other widgets which won't be repainted. if (m_do_background_fill) { painter.source_rgb(m_background_fill_color); painter.rounded_rectangle(event.box(), m_background_border_radius); cr->fill(); } + // translate to the widget's position + // the origin of the painter is now the widget's origin + // this is because the position of children is relative to the origin of their parent + cr->translate(current_geometry().x(), current_geometry().y()); + 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); + // convert the paint event's origin to our coordinate space because all positions of children are relative to the origin of their parent + auto local_box = event.box().offset(-m_current_geometry.x(), -m_current_geometry.y()); + auto local_event = RepaintRectEvent(false, local_box); + for (auto child : m_children) { child->dispatch_event(local_event); } @@ -152,59 +154,72 @@ void Widget::handle_reflow(ReflowEvent &event) { } } +void Widget::handle_relayout_subtree() { + do_layout(); +} + void Widget::handle_mouse_move_event(MouseMoveEvent &event) { + event.accept(); // we will do our own propagation logic + 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) { - update_focus_to = false; - } else { - event.accept(); - return; - } + update_focus_to = false; } - if (m_is_focused == update_focus_to) - return; - - m_is_focused = update_focus_to; - auto focus_update_event = FocusUpdateEvent(update_focus_to); - on_focus_update(focus_update_event); on_mouse_move(event); - if (m_consumes_hits) { - event.accept(); + if (m_is_focused != update_focus_to) { + m_is_focused = update_focus_to; + if (m_is_focused && m_window) + m_window->set_focused_widget(this); + + auto focus_update_event = FocusUpdateEvent(update_focus_to); + on_focus_update(focus_update_event); } - if (m_is_focused && m_window) - m_window->set_focused_widget(this); + if (!m_consumes_hits) { + // translate the event's point to our coordinate space because the position of all children is relative to the origin of their parent + auto local_event = MouseMoveEvent(Point( + event.point().x() - m_current_geometry.x(), + event.point().y() - m_current_geometry.y() + )); + for (auto child : m_children) { + child->dispatch_event(local_event); + } + } } void Widget::handle_mouse_button_event(MouseButtonEvent &event) { + event.accept(); // we will do our own propagation logic + bool update_activation_to = event.was_left_button_pressed(); if (!m_current_geometry.contains_point(event.point())) { - event.accept(); return; } - if (m_is_active == update_activation_to) - return; - - m_is_active = update_activation_to; - auto activation_update_event = ActivationUpdateEvent(update_activation_to); - on_activation_update(activation_update_event); - on_mouse_button(event); - if (m_is_active && m_window) - m_window->set_active_widget(this); + if (m_is_active != update_activation_to) { + m_is_active = update_activation_to; + if (m_is_active && m_window) + m_window->set_active_widget(this); - if (m_consumes_hits) - event.accept(); + auto activation_update_event = ActivationUpdateEvent(update_activation_to); + on_activation_update(activation_update_event); + } + + if (!m_consumes_hits) { + // translate the event's point to our coordinate space because the position of all children is relative to the origin of their parent + auto local_event = MouseButtonEvent(event.was_left_button_pressed(), event.was_right_button_pressed(), Point( + event.point().x() - m_current_geometry.x(), + event.point().y() - m_current_geometry.y() + )); + for (auto child : m_children) { + child->dispatch_event(local_event); + } + } } void Widget::dispatch_event(Event &event) { @@ -222,8 +237,12 @@ void Widget::dispatch_event(Event &event) { handle_mouse_button_event(reinterpret_cast(event)); break; } - case EventType::Reflow: { - handle_reflow(reinterpret_cast(event)); + case EventType::RepaintRect: { + handle_repaint_rect(reinterpret_cast(event)); + break; + } + case EventType::RelayoutSubtree: { + handle_relayout_subtree(); break; } /* these events aren't handled here, as they won't be dispatched to us from other places */ diff --git a/src/Widget.hpp b/src/Widget.hpp index e51374c..969cacb 100644 --- a/src/Widget.hpp +++ b/src/Widget.hpp @@ -59,13 +59,14 @@ public: void remove_child(std::shared_ptr child); Box ¤t_geometry() { return m_current_geometry; } - void set_current_geometry(Box current_geometry) { m_current_geometry = current_geometry; wants_full_relayout(); } - void set_current_geometry(Box current_geometry, bool is_pure) { m_current_geometry = current_geometry; if (!is_pure) { wants_full_relayout(); } } + void set_current_geometry(Box current_geometry) { m_current_geometry = current_geometry; reflow(); } + void set_current_geometry(Box current_geometry, bool is_pure) { m_current_geometry = current_geometry; if (!is_pure) { reflow(); } } + + Point window_relative(); WidgetType type() { return m_type; } ControlWidgetType control_type() { return m_control_type; } - Widget *parent() { return m_parent; } void set_parent(Widget *parent) { m_parent = parent; } @@ -121,11 +122,12 @@ protected: virtual void on_activation_update(ActivationUpdateEvent &event) {} virtual void on_paint() {} - void wants_full_repaint(); - void wants_full_relayout(); + void repaint(); + void reflow(); private: void do_layout(); - void handle_reflow(ReflowEvent& event); + void handle_repaint_rect(RepaintRectEvent &event); + void handle_relayout_subtree(); void handle_mouse_move_event(MouseMoveEvent &event); void handle_mouse_button_event(MouseButtonEvent &event); diff --git a/src/Window.cpp b/src/Window.cpp index 29de1be..75fb463 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -64,55 +64,57 @@ bool Window::dispatch_to_main_widget(Event &event) { if (!m_main_widget) return false; - std::cout << "Dispatched " << event.name() << " to main widget" << std::endl; + //std::cout << "Dispatched " << event.name() << " to main widget" << std::endl; m_main_widget->dispatch_event(event); return 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; - } +void Window::repaint(Box geometry) { + m_did_repaint_during_batch = true; if (m_is_batching) { return; } - auto event = ReflowEvent(type, ReflowGrouping::Yes, target->current_geometry()); + auto event = RepaintRectEvent(true, geometry); + dispatch_to_main_widget(event); +} + +void Window::repaint(Widget *target) { + std::cout << target->window_relative().x() << ", " << target->window_relative().y() << std::endl; + repaint(target->current_geometry().offset(target->window_relative().x(), target->window_relative().y())); +} + +void Window::repaint() { + repaint(m_current_geometry); +} + +void Window::relayout(Widget *target) { + m_did_relayout_during_batch = true; + + if (m_is_batching) { + return; + } + + auto event = RelayoutSubtreeEvent(); target->dispatch_event(event); } -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; - } +void Window::relayout() { + m_did_relayout_during_batch = true; if (m_is_batching) { return; } - auto event = ReflowEvent(type, ReflowGrouping::Yes, m_current_geometry); + auto event = RelayoutSubtreeEvent(); 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::reflow() { + relayout(); + repaint(); } void Window::start_batch() { @@ -125,9 +127,9 @@ void Window::end_batch() { if (m_is_batching) { m_is_batching = false; if (m_did_relayout_during_batch) { - reflow(ReflowType::RelayoutRect); + reflow(); } else if (m_did_repaint_during_batch) { - reflow(ReflowType::RelayoutRect); + repaint(); } } } @@ -161,7 +163,7 @@ void Window::run(bool block) { } case Expose: { if (e.xexpose.count == 0) { - reflow(ReflowType::RelayoutRect); + reflow(); } break; } diff --git a/src/Window.hpp b/src/Window.hpp index e31f269..08aa7dd 100644 --- a/src/Window.hpp +++ b/src/Window.hpp @@ -36,9 +36,12 @@ public: std::shared_ptr top_level_styles() { return m_top_level_styles; } - void reflow(Widget *target, ReflowType type); - void reflow(Box &box, ReflowType type); - void reflow(ReflowType type); + void repaint(); + void repaint(Box geometry); + void repaint(Widget *target); + void relayout(Widget *target); + void relayout(); + void reflow(); Box ¤t_geometry() { return m_current_geometry; } diff --git a/src/main.cpp b/src/main.cpp index 8cdc1ab..07dcc19 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include "Box.hpp" #include "Label.hpp" #include "Layout.hpp" +#include "RGB.hpp" #include "src/DocumentLayout.hpp" #include "src/Events.hpp" #include @@ -17,9 +18,13 @@ int main() { auto main_widget = window.set_main_widget(); - auto inner_widget = main_widget->add(); + auto second_widget = main_widget->add(); + second_widget->set_layout(16.0); + second_widget->resize(800, 800); + + auto inner_widget = second_widget->add(); inner_widget->set_layout(8.0); - inner_widget->resize(400, 400); + inner_widget->resize(600, 600); int number = 0;