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
No known key found for this signature in database
GPG key ID: 7C52899193467641
9 changed files with 113 additions and 19 deletions

View file

@ -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();
}

View file

@ -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<void()> on_click { [](){} };
@ -28,6 +28,7 @@ protected:
void on_activation_update(ActivationUpdateEvent &event);
private:
void update_color();
void recompute_text_size();
};
}

View file

@ -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();
}
}

View file

@ -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();
};
}

View file

@ -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<Widget> 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<Widget> child) {
m_children.erase(std::remove(m_children.begin(), m_children.end(), child), m_children.end());
wants_full_relayout();
}
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 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<std::shared_ptr<Widget>> &children() { return m_children; }
bool add_child(std::shared_ptr<Widget> 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);

View file

@ -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;
}

View file

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

View file

@ -12,27 +12,29 @@
int main() {
Raven::Window window {};
window.spawn_window();
auto main_widget = window.set_main_widget<Raven::Widget>();
main_widget->set_layout<Raven::DocumentLayout>(6.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");
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<Raven::Button>(c);
button_child->set_text(std::to_string(number));
}
}
window.end_batch();
};
}
window.spawn_window();
window.run(true);
return 0;
}
}