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).
This commit is contained in:
hippoz 2022-05-13 17:33:53 +03:00
parent 5e436b5463
commit c85c367e11
Signed by: hippoz
GPG key ID: 7C52899193467641
9 changed files with 113 additions and 19 deletions

View file

@ -8,9 +8,26 @@
namespace Raven { 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() { void Button::on_init() {
set_background_fill_color(styles()->button_normal_color()); set_background_fill_color(styles()->button_normal_color());
set_do_background_fill(true); set_do_background_fill(true);
recompute_text_size();
set_did_init(true); set_did_init(true);
} }
@ -30,13 +47,10 @@ void Button::on_paint() {
auto painter = window()->painter(); auto painter = window()->painter();
auto text_color = styles()->button_text_color(); 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.source_rgb(text_color);
painter.set_pango_font_description(styles()->controls_font_description()); painter.set_pango_font_description(styles()->controls_font_description());
auto point = painter.text(max_geometry, m_text, PaintTextAlign::Center, PANGO_ELLIPSIZE_END, set_size); painter.text(current_geometry(), m_text, PaintTextAlign::Center, PANGO_ELLIPSIZE_END, true);
resize(point);
painter.fill(); painter.fill();
} }

View file

@ -17,7 +17,7 @@ public:
: Widget(WidgetType::Button) : Widget(WidgetType::Button)
, m_text(text) {} , 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::string &text() { return m_text; }
std::function<void()> on_click { [](){} }; std::function<void()> on_click { [](){} };
@ -28,6 +28,7 @@ protected:
void on_activation_update(ActivationUpdateEvent &event); void on_activation_update(ActivationUpdateEvent &event);
private: private:
void update_color(); void update_color();
void recompute_text_size();
}; };
} }

View file

@ -2,6 +2,7 @@
#include "RGB.hpp" #include "RGB.hpp"
#include "pango/pango-layout.h" #include "pango/pango-layout.h"
#include "pango/pango-types.h" #include "pango/pango-types.h"
#include "pango/pangocairo.h"
namespace Raven { namespace Raven {
@ -39,6 +40,29 @@ bool Painter::text(Point &where, std::string &text) {
return true; 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) { Point Painter::text(Box &geometry, std::string &text, PaintTextAlign align, PangoEllipsizeMode ellipsize, bool set_size) {
if (m_pango_font_description == nullptr) if (m_pango_font_description == nullptr)
return {-1,-1}; return {-1,-1};
@ -95,4 +119,10 @@ void Painter::end_paint_group() {
m_cairo->paint(); m_cairo->paint();
} }
void Painter::flush_paint_group() {
m_cairo->pop_group_to_source();
m_cairo->paint();
m_cairo->get_target()->flush();
}
} }

View file

@ -30,6 +30,7 @@ public:
void rounded_rectangle(Box &geometry, double border_radius); void rounded_rectangle(Box &geometry, double border_radius);
bool text(Point &where, std::string &text); bool text(Point &where, std::string &text);
Point text(Box &geometry, std::string &text, PaintTextAlign align, PangoEllipsizeMode ellipsize, bool set_size); 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; } bool can_paint() { if (m_cairo) return true; else return false; }
@ -38,6 +39,7 @@ public:
void begin_paint_group(); void begin_paint_group();
void end_paint_group(); void end_paint_group();
void flush_paint_group();
}; };
} }

View file

@ -9,16 +9,17 @@
namespace Raven { 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) if (m_current_geometry.width() == width && m_current_geometry.height() == height)
return; return false;
if (width < 0 || height < 0) if (width < 0 || height < 0)
return; return false;
m_current_geometry.set_width(width); m_current_geometry.set_width(width);
m_current_geometry.set_height(height); m_current_geometry.set_height(height);
wants_full_relayout(); wants_full_relayout();
return true;
} }
void Widget::move_to(double x, double y) { void Widget::move_to(double x, double y) {
@ -59,12 +60,13 @@ bool Widget::add_child(std::shared_ptr<Widget> child) {
// children inherit the window from the parent // children inherit the window from the parent
// TODO?: what happens when the parent changes its window? // TODO?: what happens when the parent changes its window?
child->set_window(m_window); child->set_window(m_window);
do_layout(); wants_full_relayout();
return true; return true;
} }
void Widget::remove_child(std::shared_ptr<Widget> child) { void Widget::remove_child(std::shared_ptr<Widget> child) {
m_children.erase(std::remove(m_children.begin(), m_children.end(), child), m_children.end()); m_children.erase(std::remove(m_children.begin(), m_children.end(), child), m_children.end());
wants_full_relayout();
} }
void Widget::do_generic_paint() { void Widget::do_generic_paint() {

View file

@ -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 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 move_to(double x, double y);
void resize(double width, double height); bool resize(double width, double height);
void resize(Point &point) { resize(point.x(), point.y()); } bool resize(Point &point) { return resize(point.x(), point.y()); }
std::vector<std::shared_ptr<Widget>> &children() { return m_children; } std::vector<std::shared_ptr<Widget>> &children() { return m_children; }
bool add_child(std::shared_ptr<Widget> child); bool add_child(std::shared_ptr<Widget> child);
@ -128,9 +128,9 @@ protected:
virtual void on_paint() { do_generic_paint(); } virtual void on_paint() { do_generic_paint(); }
void do_generic_paint(); void do_generic_paint();
private:
void wants_full_repaint(); void wants_full_repaint();
void wants_full_relayout(); void wants_full_relayout();
private:
void do_layout(); void do_layout();
void handle_reflow(ReflowEvent& event); void handle_reflow(ReflowEvent& event);
void handle_mouse_move_event(MouseMoveEvent &event); void handle_mouse_move_event(MouseMoveEvent &event);

View file

@ -61,11 +61,21 @@ bool Window::spawn_window() {
} }
void Window::widget_repaint(Widget *target) { 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); auto event = ReflowEvent(ReflowType::RepaintSubtree, ReflowGrouping::Yes);
target->dispatch_event(event); target->dispatch_event(event);
} }
void Window::widget_relayout(Widget *target) { 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); auto event = ReflowEvent(ReflowType::RelayoutSubtree, ReflowGrouping::Yes);
target->dispatch_event(event); target->dispatch_event(event);
} }
@ -74,20 +84,49 @@ bool Window::dispatch_to_main_widget(Event &event) {
if (!m_main_widget) if (!m_main_widget)
return false; return false;
std::cout << "Dispatched " << event.name() << " to main widget" << std::endl;
m_main_widget->dispatch_event(event); m_main_widget->dispatch_event(event);
return true; return true;
} }
bool Window::dispatch_full_repaint() { bool Window::dispatch_full_repaint() {
m_did_repaint_during_batch = true;
if (m_is_batching)
return false;
auto event = ReflowEvent(ReflowType::RepaintSubtree, ReflowGrouping::Yes); auto event = ReflowEvent(ReflowType::RepaintSubtree, ReflowGrouping::Yes);
return dispatch_to_main_widget(event); return dispatch_to_main_widget(event);
} }
bool Window::dispatch_full_relayout() { bool Window::dispatch_full_relayout() {
m_did_relayout_during_batch = true;
if (m_is_batching)
return false;
auto event = ReflowEvent(ReflowType::RelayoutSubtree, ReflowGrouping::Yes); auto event = ReflowEvent(ReflowType::RelayoutSubtree, ReflowGrouping::Yes);
return dispatch_to_main_widget(event); 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) { void Window::run(bool block) {
XEvent e; XEvent e;
@ -99,7 +138,7 @@ void Window::run(bool block) {
switch (e.type) { switch (e.type) {
case MapNotify: { case MapNotify: {
dispatch_full_repaint(); end_batch();
break; break;
} }
case ConfigureNotify: { 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_width(m_current_geometry.width());
m_main_widget->current_geometry().set_height(m_current_geometry.height()); m_main_widget->current_geometry().set_height(m_current_geometry.height());
} }
dispatch_full_relayout();
} }
break; break;
} }
case Expose: { case Expose: {
if (e.xexpose.count == 0) { if (e.xexpose.count == 0) {
// might run into issues with other X implementations... :( dispatch_full_relayout();
dispatch_full_repaint();
} }
break; break;
} }

View file

@ -18,6 +18,9 @@ private:
Painter m_painter {}; Painter m_painter {};
std::shared_ptr<TopLevelStyles> m_top_level_styles = std::make_shared<TopLevelStyles>(this); std::shared_ptr<TopLevelStyles> m_top_level_styles = std::make_shared<TopLevelStyles>(this);
Cairo::RefPtr<Cairo::XlibSurface> m_xlib_surface { nullptr }; Cairo::RefPtr<Cairo::XlibSurface> 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 }; Display *m_x_display { nullptr };
public: public:
@ -49,6 +52,9 @@ public:
bool spawn_window(); bool spawn_window();
void run(bool block); void run(bool block);
void start_batch();
void end_batch();
template<typename T, class... Args> template<typename T, class... Args>
std::shared_ptr<T> set_main_widget(Args&&... args) { std::shared_ptr<T> set_main_widget(Args&&... args) {
std::shared_ptr<T> widget = std::make_shared<T>(std::forward<Args>(args)...); std::shared_ptr<T> widget = std::make_shared<T>(std::forward<Args>(args)...);

View file

@ -12,27 +12,29 @@
int main() { int main() {
Raven::Window window {}; Raven::Window window {};
window.spawn_window();
auto main_widget = window.set_main_widget<Raven::Widget>(); auto main_widget = window.set_main_widget<Raven::Widget>();
main_widget->set_layout<Raven::DocumentLayout>(6.0); main_widget->set_layout<Raven::DocumentLayout>(6.0);
int number = 0; int number = 0;
for (int i = 0; i < 100; i++) { for (int i = 0; i < 500; i++) {
auto button = main_widget->add<Raven::Button>("0"); auto button = main_widget->add<Raven::Button>("0");
button->on_click = [&]() { button->on_click = [&]() {
number++; number++;
window.start_batch();
for (auto& c : main_widget->children()) { for (auto& c : main_widget->children()) {
if (c->type() == Raven::WidgetType::Button) { if (c->type() == Raven::WidgetType::Button) {
auto button_child = std::static_pointer_cast<Raven::Button>(c); auto button_child = std::static_pointer_cast<Raven::Button>(c);
button_child->set_text(std::to_string(number)); button_child->set_text(std::to_string(number));
} }
} }
window.end_batch();
}; };
} }
window.spawn_window();
window.run(true); window.run(true);
return 0; return 0;
} }