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:
parent
5e436b5463
commit
c85c367e11
9 changed files with 113 additions and 19 deletions
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
@ -74,20 +84,49 @@ 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;
|
||||
}
|
||||
|
|
|
@ -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)...);
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in a new issue