begin working on adding invalidation rects

This commit is contained in:
hippoz 2022-06-04 12:12:41 +03:00
parent b781db009a
commit 1f04b5d1dc
Signed by: hippoz
GPG key ID: 7C52899193467641
13 changed files with 162 additions and 161 deletions

View file

@ -4,6 +4,15 @@
namespace Raven { namespace Raven {
Box Box::offset(double x, double y) {
return Box{
m_x + x,
m_y + y,
m_width,
m_height
};
}
bool Box::contains_point(double x, double y) const { bool Box::contains_point(double x, double y) const {
return x >= m_x && m_x + m_width >= x && y >= m_y && m_y + m_height >= y; return x >= m_x && m_x + m_width >= x && y >= m_y && m_y + m_height >= y;
} }

View file

@ -59,6 +59,8 @@ public:
bool contains_point(double x, double y) const; bool contains_point(double x, double y) const;
bool contains_point(const Point &point) const; bool contains_point(const Point &point) const;
bool contains_box(const Box &other) const; bool contains_box(const Box &other) const;
Box offset(double x, double y);
}; };
} }

View file

@ -35,8 +35,6 @@ void Button::update_color() {
} }
void Button::on_paint() { void Button::on_paint() {
do_generic_paint();
auto painter = window()->painter(); auto painter = window()->painter();
auto text_color = styles()->button_text_color(); auto text_color = styles()->button_text_color();

View file

@ -9,7 +9,7 @@ void DocumentLayout::run() {
if (!m_target) if (!m_target)
return; return;
Point current_position { m_target->current_geometry().x() + m_margin, m_target->current_geometry().y() + m_margin }; Point current_position { m_margin, m_margin };
double largest_height_so_far = -1.0; double largest_height_so_far = -1.0;
auto& children = m_target->children(); auto& children = m_target->children();

View file

@ -17,9 +17,8 @@ enum class EventType {
}; };
enum class ReflowType { enum class ReflowType {
RepaintSubtree, RepaintRect,
RelayoutSubtree, RelayoutRect
RepaintSelf
}; };
enum class ReflowGrouping { enum class ReflowGrouping {
@ -78,20 +77,24 @@ public:
class ReflowEvent: public Event { class ReflowEvent: public Event {
private: private:
ReflowType m_reflow_type; ReflowType m_reflow_type;
ReflowGrouping m_reflow_grouping; ReflowGrouping m_grouping;
Box m_box;
public: public:
ReflowEvent(ReflowType type, ReflowGrouping grouping) ReflowEvent(ReflowType type, ReflowGrouping grouping, Box box)
: m_reflow_type(type) : m_reflow_type(type)
, m_reflow_grouping(grouping) {} , m_grouping(grouping)
, m_box(box) {}
EventType type() { return EventType::Reflow; } EventType type() { return EventType::Reflow; }
const char *name() { return "Reflow"; } const char *name() { return "Reflow"; }
ReflowType reflow_type() { return m_reflow_type; } ReflowType reflow_type() { return m_reflow_type; }
ReflowGrouping reflow_grouping() { return m_reflow_grouping; } ReflowGrouping grouping() { return m_grouping; }
Box &box() { return m_box; }
void set_reflow_grouping(ReflowGrouping reflow_grouping) { m_reflow_grouping = reflow_grouping; } void set_grouping(ReflowGrouping grouping) { m_grouping = grouping; }
void set_reflow_type(ReflowType reflow_type) { m_reflow_type = reflow_type; } void set_reflow_type(ReflowType type) { m_reflow_type = type; }
void set_box(Box box) { m_box = box; }
}; };
class FocusUpdateEvent : public Event { class FocusUpdateEvent : public Event {

View file

@ -16,11 +16,10 @@ void Label::on_init() {
fit_text(m_text); fit_text(m_text);
set_did_init(true); set_did_init(true);
set_do_background_fill(false);
} }
void Label::on_paint() { void Label::on_paint() {
do_generic_paint();
auto painter = window()->painter(); auto painter = window()->painter();
auto text_color = styles()->label_text_color(); auto text_color = styles()->label_text_color();

View file

@ -10,8 +10,8 @@ void Painter::rounded_rectangle(Box &geometry, double border_radius) {
double aspect = 1.0; double aspect = 1.0;
double radius = border_radius / aspect; double radius = border_radius / aspect;
double degrees = M_PI / 180.0; double degrees = M_PI / 180.0;
double x = geometry.x(); double x = 0;
double y = geometry.y(); double y = 0;
double w = geometry.width(); double w = geometry.width();
double h = geometry.height(); double h = geometry.height();
@ -23,23 +23,6 @@ void Painter::rounded_rectangle(Box &geometry, double border_radius) {
m_cairo->close_path(); m_cairo->close_path();
} }
bool Painter::text(Point &where, std::string &text) {
if (m_pango_font_description == nullptr)
return false;
PangoLayout *layout = pango_cairo_create_layout(m_cairo->cobj());
pango_layout_set_font_description(layout, m_pango_font_description);
pango_layout_set_text(layout, text.c_str(), -1);
m_cairo->move_to(where.x(), where.y());
pango_cairo_show_layout(m_cairo->cobj(), layout);
g_object_unref(layout);
return true;
}
Point Painter::compute_text_size(Box &widget_geometry, std::string &text) { Point Painter::compute_text_size(Box &widget_geometry, std::string &text) {
if (m_pango_font_description == nullptr) if (m_pango_font_description == nullptr)
return {-1, -1}; return {-1, -1};
@ -85,13 +68,11 @@ Point Painter::text(Box &geometry, std::string &text, PaintTextAlign align, Pang
pango_layout_get_pixel_size(layout, &font_width, &font_height); pango_layout_get_pixel_size(layout, &font_width, &font_height);
pango_layout_get_size(layout, &pango_width, &pango_height); pango_layout_get_size(layout, &pango_width, &pango_height);
double x = -1; double x = 0;
double y = geometry.y() + ((geometry.height() - font_height) / 2); double y = ((geometry.height() - font_height) / 2);
if (align == PaintTextAlign::Center) { if (align == PaintTextAlign::Center) {
x = geometry.x() + ((geometry.width() - font_width) / 2); x = ((geometry.width() - font_width) / 2);
} else {
x = geometry.x();
} }
m_cairo->move_to(x, y); m_cairo->move_to(x, y);

View file

@ -28,7 +28,6 @@ public:
PangoFontDescription *pango_font_description() { return m_pango_font_description; } PangoFontDescription *pango_font_description() { return m_pango_font_description; }
void rounded_rectangle(Box &geometry, double border_radius); 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 text(Box &geometry, std::string &text, PaintTextAlign align, PangoEllipsizeMode ellipsize, bool set_size);
Point compute_text_size(Box &widget_geometry, std::string &text); Point compute_text_size(Box &widget_geometry, std::string &text);

View file

@ -68,9 +68,11 @@ bool Widget::add_child(std::shared_ptr<Widget> child) {
} }
m_children.push_back(child); m_children.push_back(child);
child->set_parent(this); child->set_parent(this);
// 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);
wants_full_relayout(); wants_full_relayout();
return true; return true;
} }
@ -80,95 +82,83 @@ void Widget::remove_child(std::shared_ptr<Widget> child) {
wants_full_relayout(); wants_full_relayout();
} }
void Widget::do_generic_paint() {
if (m_do_background_fill) {
auto painter = m_window->painter();
auto cr = painter.cairo();
cr->save();
painter.source_rgb(m_background_fill_color);
painter.rounded_rectangle(m_current_geometry, m_background_border_radius);
painter.fill();
cr->restore();
}
}
void Widget::wants_repaint() { void Widget::wants_repaint() {
if (!m_window) if (m_window)
return; m_window->reflow(this, ReflowType::RepaintRect);
// TODO: not necessary?
m_window->widget_repaint(this);
} }
void Widget::wants_relayout() { void Widget::wants_relayout() {
if (!m_window) if (m_window)
return; m_window->reflow(this, ReflowType::RelayoutRect);
// TODO: not necessary?
m_window->widget_relayout(this);
} }
void Widget::wants_full_repaint() { void Widget::wants_full_repaint() {
if (!m_window) if (m_window)
return; m_window->reflow(ReflowType::RepaintRect);
m_window->dispatch_full_repaint();
} }
void Widget::wants_full_relayout() { void Widget::wants_full_relayout() {
if (!m_window) if (m_window)
return; m_window->reflow(ReflowType::RelayoutRect);
m_window->dispatch_full_relayout();
} }
void Widget::handle_reflow(ReflowEvent &event) { void Widget::handle_reflow(ReflowEvent &event) {
// immediately accept the event - we will do our own propagation logic event.accept(); // immediately accept the event - we will do our own propagation logic
event.accept();
if (!m_did_init)
return;
auto painter = m_window->painter(); auto painter = m_window->painter();
if (!painter.can_paint()) if (!m_did_init || !painter.can_paint())
return; return;
auto cr = painter.cairo(); if (!event.box().contains_box(current_geometry()))
auto grouping = event.reflow_grouping(); return; // widgets contain their children, thus we don't need to recurse further
auto type = event.reflow_type();
if (grouping == ReflowGrouping::Yes) { bool should_end_paint_group = false;
if (event.grouping() == ReflowGrouping::Yes) {
painter.begin_paint_group(); painter.begin_paint_group();
should_end_paint_group = true;
} }
if (type == ReflowType::RelayoutSubtree) { if (event.reflow_type() == ReflowType::RelayoutRect) {
do_layout(); do_layout();
} }
cr->save(); auto cr = painter.cairo();
on_paint();
cr->restore();
if (type != ReflowType::RepaintSelf) { cr->save();
// we will propagate this event to all of our children, except it will have
// should_do_group set to false cr->translate(current_geometry().x(), current_geometry().y());
event.set_reflow_grouping(ReflowGrouping::No); painter.rounded_rectangle(m_current_geometry, m_background_border_radius);
for (auto& child : m_children) { cr->clip();
child->dispatch_event(event); if (m_do_background_fill) {
} painter.source_rgb(m_background_fill_color);
painter.rounded_rectangle(event.box(), m_background_border_radius);
cr->fill();
} }
if (grouping == ReflowGrouping::Yes) { on_paint();
Box box = event.box();
for (auto& child : m_children) {
// convert the invalidation rectangle to the child's coordinate space
auto local_rect = box.offset(child->current_geometry().x(), child->current_geometry().y());
auto local_event = ReflowEvent(event.reflow_type(), ReflowGrouping::No, local_rect);
child->dispatch_event(local_event);
}
cr->restore();
if (should_end_paint_group) {
painter.end_paint_group(); painter.end_paint_group();
} }
} }
void Widget::handle_mouse_move_event(MouseMoveEvent &event) { void Widget::handle_mouse_move_event(MouseMoveEvent &event) {
event.accept(); // we will do our own propagation logic. TODO: remove automatic event propagation?
bool update_focus_to = true; bool update_focus_to = true;
std::cout << "point: " << event.point().x() << ", " << event.point().y() << std::endl;
if (!m_current_geometry.contains_point(event.point())) { if (!m_current_geometry.contains_point(event.point())) {
// we just became unfocused // we just became unfocused
if (m_is_focused) { if (m_is_focused) {
@ -186,11 +176,20 @@ void Widget::handle_mouse_move_event(MouseMoveEvent &event) {
on_focus_update(focus_update_event); on_focus_update(focus_update_event);
on_mouse_move(event); on_mouse_move(event);
if (!m_consumes_hits) {
for (auto &c : children()) {
auto e = MouseMoveEvent {
Point {
event.point().x() - current_geometry().x(),
event.point().y() - current_geometry().y()
}
};
c->dispatch_event(e);
}
}
if (m_is_focused && m_window) if (m_is_focused && m_window)
m_window->set_focused_widget(this); m_window->set_focused_widget(this);
if (m_consumes_hits)
event.accept();
} }
void Widget::handle_mouse_button_event(MouseButtonEvent &event) { void Widget::handle_mouse_button_event(MouseButtonEvent &event) {
@ -253,7 +252,6 @@ void Widget::dispatch_event(Event &event) {
void Widget::on_init() { void Widget::on_init() {
set_background_fill_color(styles()->background_color()); set_background_fill_color(styles()->background_color());
set_do_background_fill(true);
set_did_init(true); set_did_init(true);
} }

View file

@ -27,7 +27,7 @@ enum class ControlWidgetType {
}; };
class Widget { class Widget {
DEF_WIDGET_STYLE_PROP(do_background_fill, bool, false) DEF_WIDGET_STYLE_PROP(do_background_fill, bool, true)
DEF_WIDGET_STYLE_PROP(background_fill_color, RGB, 0, 0, 0) DEF_WIDGET_STYLE_PROP(background_fill_color, RGB, 0, 0, 0)
DEF_WIDGET_STYLE_PROP(background_border_radius, double, 0.0) DEF_WIDGET_STYLE_PROP(background_border_radius, double, 0.0)
@ -119,9 +119,8 @@ protected:
virtual void on_mouse_move(MouseMoveEvent &event) {} virtual void on_mouse_move(MouseMoveEvent &event) {}
virtual void on_focus_update(FocusUpdateEvent &event) {} virtual void on_focus_update(FocusUpdateEvent &event) {}
virtual void on_activation_update(ActivationUpdateEvent &event) {} virtual void on_activation_update(ActivationUpdateEvent &event) {}
virtual void on_paint() { do_generic_paint(); } virtual void on_paint() {}
void do_generic_paint();
void wants_full_repaint(); void wants_full_repaint();
void wants_full_relayout(); void wants_full_relayout();
private: private:

View file

@ -22,7 +22,7 @@ void Window::set_main_widget(std::shared_ptr<Widget> main_widget) {
bool Window::spawn_window() { bool Window::spawn_window() {
Display *dsp = XOpenDisplay(NULL); Display *dsp = XOpenDisplay(NULL);
if (dsp == NULL) { if (dsp == NULL) {
std::cout << "error: XOpenDisplay(NULL)" << "\n"; std::cerr << "error: XOpenDisplay(NULL)" << "\n";
return false; return false;
} }
m_x_display = dsp; m_x_display = dsp;
@ -60,54 +60,59 @@ bool Window::spawn_window() {
return true; return true;
} }
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);
}
bool Window::dispatch_to_main_widget(Event &event) { 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; 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() { void Window::reflow(Widget *target, ReflowType type) {
m_did_repaint_during_batch = true; if (type == ReflowType::RelayoutRect) {
m_did_relayout_during_batch = true;
} else if (type == ReflowType::RepaintRect) {
m_did_repaint_during_batch = true;
}
if (m_is_batching) if (m_is_batching) {
return false; return;
}
auto event = ReflowEvent(ReflowType::RepaintSubtree, ReflowGrouping::Yes); auto event = ReflowEvent(type, ReflowGrouping::Yes, target->current_geometry());
return dispatch_to_main_widget(event); target->dispatch_event(event);
} }
bool Window::dispatch_full_relayout() { void Window::reflow(ReflowType type) {
m_did_relayout_during_batch = true; if (type == ReflowType::RelayoutRect) {
m_did_relayout_during_batch = true;
} else if (type == ReflowType::RepaintRect) {
m_did_repaint_during_batch = true;
}
if (m_is_batching) if (m_is_batching) {
return false; return;
}
auto event = ReflowEvent(ReflowType::RelayoutSubtree, ReflowGrouping::Yes); auto event = ReflowEvent(type, ReflowGrouping::Yes, m_current_geometry);
return dispatch_to_main_widget(event); dispatch_to_main_widget(event);
}
void Window::reflow(Box &box, ReflowType type) {
if (type == ReflowType::RelayoutRect) {
m_did_relayout_during_batch = true;
} else if (type == ReflowType::RepaintRect) {
m_did_repaint_during_batch = true;
}
if (m_is_batching) {
return;
}
auto event = ReflowEvent(type, ReflowGrouping::Yes, box);
dispatch_to_main_widget(event);
} }
void Window::start_batch() { void Window::start_batch() {
@ -120,9 +125,9 @@ void Window::end_batch() {
if (m_is_batching) { if (m_is_batching) {
m_is_batching = false; m_is_batching = false;
if (m_did_relayout_during_batch) { if (m_did_relayout_during_batch) {
dispatch_full_relayout(); reflow(ReflowType::RelayoutRect);
} else if (m_did_repaint_during_batch) { } else if (m_did_repaint_during_batch) {
dispatch_full_repaint(); reflow(ReflowType::RelayoutRect);
} }
} }
} }
@ -156,25 +161,25 @@ void Window::run(bool block) {
} }
case Expose: { case Expose: {
if (e.xexpose.count == 0) { if (e.xexpose.count == 0) {
dispatch_full_relayout(); reflow(ReflowType::RelayoutRect);
} }
break; break;
} }
case ButtonRelease: { case ButtonRelease: {
auto point = Point(e.xbutton.x, e.xbutton.y); auto point = Point(e.xbutton.x, e.xbutton.y);
auto event = MouseButtonEvent(false, false, std::move(point)); auto event = MouseButtonEvent(false, false, point);
dispatch_to_main_widget(event); dispatch_to_main_widget(event);
break; break;
} }
case ButtonPress: { case ButtonPress: {
auto point = Point(e.xbutton.x, e.xbutton.y); auto point = Point(e.xbutton.x, e.xbutton.y);
auto event = MouseButtonEvent(e.xbutton.button == Button1, e.xbutton.button == Button2, std::move(point)); auto event = MouseButtonEvent(e.xbutton.button == Button1, e.xbutton.button == Button2, point);
dispatch_to_main_widget(event); dispatch_to_main_widget(event);
break; break;
} }
case MotionNotify: { case MotionNotify: {
auto point = Point(e.xmotion.x, e.xmotion.y); auto point = Point(e.xmotion.x, e.xmotion.y);
auto event = MouseMoveEvent(std::move(point)); auto event = MouseMoveEvent(point);
dispatch_to_main_widget(event); dispatch_to_main_widget(event);
} }
default: { default: {

View file

@ -4,6 +4,7 @@
#include <stack> #include <stack>
#include "Widget.hpp" #include "Widget.hpp"
#include "Painter.hpp" #include "Painter.hpp"
#include "src/Events.hpp"
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <cairomm/xlib_surface.h> #include <cairomm/xlib_surface.h>
#include <functional> #include <functional>
@ -35,12 +36,9 @@ public:
std::shared_ptr<TopLevelStyles> top_level_styles() { return m_top_level_styles; } std::shared_ptr<TopLevelStyles> top_level_styles() { return m_top_level_styles; }
void widget_repaint(Widget *target); void reflow(Widget *target, ReflowType type);
void widget_relayout(Widget *target); void reflow(Box &box, ReflowType type);
void reflow(ReflowType type);
bool dispatch_full_repaint();
bool dispatch_full_relayout();
bool dispatch_to_main_widget(Event &event);
Box &current_geometry() { return m_current_geometry; } Box &current_geometry() { return m_current_geometry; }
@ -63,6 +61,8 @@ private:
bool m_did_repaint_during_batch { false }; bool m_did_repaint_during_batch { false };
Display *m_x_display { nullptr }; Display *m_x_display { nullptr };
bool dispatch_to_main_widget(Event &event);
}; };
} }

View file

@ -16,24 +16,32 @@ int main() {
window.spawn_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);
bool is_dragging = false; auto inner_widget = main_widget->add<Raven::Widget>();
inner_widget->set_layout<Raven::DocumentLayout>(8.0);
inner_widget->resize(400, 400);
auto button = main_widget->add<Raven::Button>("hello"); int number = 0;
button->set_absolute(true);
button->on_click = [&]() {
is_dragging = !is_dragging;
};
main_widget->on_event = [&](Raven::Event &event) { for (int i = 0; i < 250; i++) {
if (event.type() == Raven::EventType::MouseMove) { auto button = inner_widget->add<Raven::Button>("click me");
auto mouse_move = reinterpret_cast<Raven::MouseMoveEvent&>(event); auto label = inner_widget->add<Raven::Label>("click one of the buttons!");
if (is_dragging) { button->on_click = [&]() {
button->move_to(mouse_move.point().x(), mouse_move.point().y()); number += 10;
window.start_batch();
auto& children = inner_widget->children();
for (auto& child : children) {
if (child->type() == Raven::WidgetType::Button) {
std::static_pointer_cast<Raven::Button>(child)->set_text(std::to_string(number));
}
if (child->type() == Raven::WidgetType::Label) {
std::static_pointer_cast<Raven::Label>(child)->set_text(std::to_string(number));
}
} }
} window.end_batch();
}; };
}
window.run(true); window.run(true);
return 0; return 0;