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 +}