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 {
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 {
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(const Point &point) 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() {
do_generic_paint();
auto painter = window()->painter();
auto text_color = styles()->button_text_color();

View file

@ -9,7 +9,7 @@ void DocumentLayout::run() {
if (!m_target)
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;
auto& children = m_target->children();

View file

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

View file

@ -16,11 +16,10 @@ void Label::on_init() {
fit_text(m_text);
set_did_init(true);
set_do_background_fill(false);
}
void Label::on_paint() {
do_generic_paint();
auto painter = window()->painter();
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 radius = border_radius / aspect;
double degrees = M_PI / 180.0;
double x = geometry.x();
double y = geometry.y();
double x = 0;
double y = 0;
double w = geometry.width();
double h = geometry.height();
@ -23,23 +23,6 @@ void Painter::rounded_rectangle(Box &geometry, double border_radius) {
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) {
if (m_pango_font_description == nullptr)
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_size(layout, &pango_width, &pango_height);
double x = -1;
double y = geometry.y() + ((geometry.height() - font_height) / 2);
double x = 0;
double y = ((geometry.height() - font_height) / 2);
if (align == PaintTextAlign::Center) {
x = geometry.x() + ((geometry.width() - font_width) / 2);
} else {
x = geometry.x();
x = ((geometry.width() - font_width) / 2);
}
m_cairo->move_to(x, y);

View file

@ -28,7 +28,6 @@ public:
PangoFontDescription *pango_font_description() { return m_pango_font_description; }
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);

View file

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

View file

@ -27,7 +27,7 @@ enum class ControlWidgetType {
};
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_border_radius, double, 0.0)
@ -119,9 +119,8 @@ protected:
virtual void on_mouse_move(MouseMoveEvent &event) {}
virtual void on_focus_update(FocusUpdateEvent &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_relayout();
private:

View file

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

View file

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

View file

@ -16,24 +16,32 @@ int main() {
window.spawn_window();
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");
button->set_absolute(true);
button->on_click = [&]() {
is_dragging = !is_dragging;
};
int number = 0;
main_widget->on_event = [&](Raven::Event &event) {
if (event.type() == Raven::EventType::MouseMove) {
auto mouse_move = reinterpret_cast<Raven::MouseMoveEvent&>(event);
if (is_dragging) {
button->move_to(mouse_move.point().x(), mouse_move.point().y());
for (int i = 0; i < 250; i++) {
auto button = inner_widget->add<Raven::Button>("click me");
auto label = inner_widget->add<Raven::Label>("click one of the buttons!");
button->on_click = [&]() {
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);
return 0;