From c85c367e11dde6c7e452fb7b89262327133675af Mon Sep 17 00:00:00 2001 From: hippoz <10706925-hippoz@users.noreply.gitlab.com> Date: Fri, 13 May 2022 17:33:53 +0300 Subject: [PATCH] Greatly improve performance on startup or when operating on lots of widgets This commit introduces "batches", which makes Raven hold off on doing full reflows until the batch is ended. When a window is created, a batch is automatically started to ensure that no useless reflows are done during initialization. The batch is automatically ended in window.run(). Users of the library can create and end batches as well. This commit also factors out automatic scaling based on text size into set_text for buttons (should be done for labels as well). --- src/Button.cpp | 22 ++++++++++++++++++---- src/Button.hpp | 3 ++- src/Painter.cpp | 30 ++++++++++++++++++++++++++++++ src/Painter.hpp | 2 ++ src/Widget.cpp | 10 ++++++---- src/Widget.hpp | 6 +++--- src/Window.cpp | 45 +++++++++++++++++++++++++++++++++++++++++---- src/Window.hpp | 6 ++++++ src/main.cpp | 8 +++++--- 9 files changed, 113 insertions(+), 19 deletions(-) diff --git a/src/Button.cpp b/src/Button.cpp index 6bfbfd8..e559250 100644 --- a/src/Button.cpp +++ b/src/Button.cpp @@ -8,9 +8,26 @@ namespace Raven { +void Button::recompute_text_size() { + window()->painter().set_pango_font_description(styles()->controls_font_description()); + auto size = window()->painter().compute_text_size(current_geometry(), m_text); + if (!resize(size)) { + wants_full_relayout(); + } +} + +void Button::set_text(std::string text) { + m_text = text; + + if (window()) { + recompute_text_size(); + } +} + void Button::on_init() { set_background_fill_color(styles()->button_normal_color()); set_do_background_fill(true); + recompute_text_size(); set_did_init(true); } @@ -30,13 +47,10 @@ void Button::on_paint() { auto painter = window()->painter(); auto text_color = styles()->button_text_color(); - auto max_geometry = current_geometry().max_geometry(); - bool set_size = current_geometry().max_width() != -1 && current_geometry().max_height() != -1; painter.source_rgb(text_color); painter.set_pango_font_description(styles()->controls_font_description()); - auto point = painter.text(max_geometry, m_text, PaintTextAlign::Center, PANGO_ELLIPSIZE_END, set_size); - resize(point); + painter.text(current_geometry(), m_text, PaintTextAlign::Center, PANGO_ELLIPSIZE_END, true); painter.fill(); } diff --git a/src/Button.hpp b/src/Button.hpp index 38292c0..1063524 100644 --- a/src/Button.hpp +++ b/src/Button.hpp @@ -17,7 +17,7 @@ public: : Widget(WidgetType::Button) , m_text(text) {} - void set_text(std::string text) { m_text = text; wants_repaint(); } + void set_text(std::string text); std::string &text() { return m_text; } std::function on_click { [](){} }; @@ -28,6 +28,7 @@ protected: void on_activation_update(ActivationUpdateEvent &event); private: void update_color(); + void recompute_text_size(); }; } diff --git a/src/Painter.cpp b/src/Painter.cpp index a448a56..3eca6c9 100644 --- a/src/Painter.cpp +++ b/src/Painter.cpp @@ -2,6 +2,7 @@ #include "RGB.hpp" #include "pango/pango-layout.h" #include "pango/pango-types.h" +#include "pango/pangocairo.h" namespace Raven { @@ -39,6 +40,29 @@ bool Painter::text(Point &where, std::string &text) { return true; } +Point Painter::compute_text_size(Box &widget_geometry, std::string &text) { + if (m_pango_font_description == nullptr) + return {-1, -1}; + + PangoLayout *layout = pango_cairo_create_layout(m_cairo->cobj()); + + int font_width; + int font_height; + + pango_layout_set_font_description(layout, m_pango_font_description); + if (widget_geometry.max_width() > 0) + pango_layout_set_width(layout, pango_units_from_double(widget_geometry.max_width())); + if (widget_geometry.max_height() > 0) + pango_layout_set_height(layout, pango_units_from_double(widget_geometry.max_height())); + + pango_layout_set_text(layout, text.c_str(), -1); + pango_layout_get_size(layout, &font_width, &font_height); + + g_object_unref(layout); + + return { pango_units_to_double(font_width), pango_units_to_double(font_height) }; +} + Point Painter::text(Box &geometry, std::string &text, PaintTextAlign align, PangoEllipsizeMode ellipsize, bool set_size) { if (m_pango_font_description == nullptr) return {-1,-1}; @@ -95,4 +119,10 @@ void Painter::end_paint_group() { m_cairo->paint(); } +void Painter::flush_paint_group() { + m_cairo->pop_group_to_source(); + m_cairo->paint(); + m_cairo->get_target()->flush(); +} + } diff --git a/src/Painter.hpp b/src/Painter.hpp index ae2224b..a6d1afc 100644 --- a/src/Painter.hpp +++ b/src/Painter.hpp @@ -30,6 +30,7 @@ public: 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); bool can_paint() { if (m_cairo) return true; else return false; } @@ -38,6 +39,7 @@ public: void begin_paint_group(); void end_paint_group(); + void flush_paint_group(); }; } diff --git a/src/Widget.cpp b/src/Widget.cpp index 69de66c..efb57fc 100644 --- a/src/Widget.cpp +++ b/src/Widget.cpp @@ -9,16 +9,17 @@ namespace Raven { -void Widget::resize(double width, double height) { +bool Widget::resize(double width, double height) { if (m_current_geometry.width() == width && m_current_geometry.height() == height) - return; + return false; if (width < 0 || height < 0) - return; + return false; m_current_geometry.set_width(width); m_current_geometry.set_height(height); wants_full_relayout(); + return true; } void Widget::move_to(double x, double y) { @@ -59,12 +60,13 @@ bool Widget::add_child(std::shared_ptr child) { // children inherit the window from the parent // TODO?: what happens when the parent changes its window? child->set_window(m_window); - do_layout(); + wants_full_relayout(); 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(); } void Widget::do_generic_paint() { diff --git a/src/Widget.hpp b/src/Widget.hpp index 3792677..f0fe571 100644 --- a/src/Widget.hpp +++ b/src/Widget.hpp @@ -63,8 +63,8 @@ public: void set_current_geometry(Box current_geometry, bool is_pure) { m_current_geometry = current_geometry; if (!is_pure) { wants_full_relayout(); } } void move_to(double x, double y); - void resize(double width, double height); - void resize(Point &point) { resize(point.x(), point.y()); } + bool resize(double width, double height); + bool resize(Point &point) { return resize(point.x(), point.y()); } std::vector> &children() { return m_children; } bool add_child(std::shared_ptr child); @@ -128,9 +128,9 @@ protected: virtual void on_paint() { do_generic_paint(); } void do_generic_paint(); -private: void wants_full_repaint(); void wants_full_relayout(); +private: void do_layout(); void handle_reflow(ReflowEvent& event); void handle_mouse_move_event(MouseMoveEvent &event); diff --git a/src/Window.cpp b/src/Window.cpp index f417bcb..c044f59 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -61,11 +61,21 @@ bool Window::spawn_window() { } 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); } @@ -73,21 +83,50 @@ void Window::widget_relayout(Widget *target) { 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; + + if (m_is_batching) + return false; + auto event = ReflowEvent(ReflowType::RepaintSubtree, ReflowGrouping::Yes); return dispatch_to_main_widget(event); } bool Window::dispatch_full_relayout() { + m_did_relayout_during_batch = true; + + if (m_is_batching) + return false; + auto event = ReflowEvent(ReflowType::RelayoutSubtree, ReflowGrouping::Yes); return dispatch_to_main_widget(event); } +void Window::start_batch() { + m_is_batching = true; + m_did_relayout_during_batch = false; + m_did_repaint_during_batch = false; +} + +void Window::end_batch() { + if (m_is_batching) { + m_is_batching = false; + if (m_did_relayout_during_batch) { + dispatch_full_relayout(); + } else if (m_did_repaint_during_batch) { + dispatch_full_repaint(); + } + } +} + void Window::run(bool block) { XEvent e; @@ -99,7 +138,7 @@ void Window::run(bool block) { switch (e.type) { case MapNotify: { - dispatch_full_repaint(); + end_batch(); break; } case ConfigureNotify: { @@ -112,14 +151,12 @@ void Window::run(bool block) { m_main_widget->current_geometry().set_width(m_current_geometry.width()); m_main_widget->current_geometry().set_height(m_current_geometry.height()); } - dispatch_full_relayout(); } break; } case Expose: { if (e.xexpose.count == 0) { - // might run into issues with other X implementations... :( - dispatch_full_repaint(); + dispatch_full_relayout(); } break; } diff --git a/src/Window.hpp b/src/Window.hpp index f319840..b2ec63d 100644 --- a/src/Window.hpp +++ b/src/Window.hpp @@ -18,6 +18,9 @@ private: Painter m_painter {}; std::shared_ptr m_top_level_styles = std::make_shared(this); Cairo::RefPtr m_xlib_surface { nullptr }; + bool m_is_batching { true }; + bool m_did_relayout_during_batch { true }; + bool m_did_repaint_during_batch { false }; Display *m_x_display { nullptr }; public: @@ -49,6 +52,9 @@ public: bool spawn_window(); void run(bool block); + void start_batch(); + void end_batch(); + template std::shared_ptr set_main_widget(Args&&... args) { std::shared_ptr widget = std::make_shared(std::forward(args)...); diff --git a/src/main.cpp b/src/main.cpp index 56caa7d..3d41aa6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,27 +12,29 @@ int main() { Raven::Window window {}; + window.spawn_window(); auto main_widget = window.set_main_widget(); main_widget->set_layout(6.0); int number = 0; - for (int i = 0; i < 100; i++) { + for (int i = 0; i < 500; i++) { auto button = main_widget->add("0"); button->on_click = [&]() { number++; + window.start_batch(); for (auto& c : main_widget->children()) { if (c->type() == Raven::WidgetType::Button) { auto button_child = std::static_pointer_cast(c); button_child->set_text(std::to_string(number)); } } + window.end_batch(); }; } - window.spawn_window(); window.run(true); return 0; -} \ No newline at end of file +}