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 {
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)...);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
Loading…
Reference in a new issue