26#include <boost/asio/thread_pool.hpp>
27#include <boost/asio/post.hpp>
28#include <gtkmm/eventcontrollerfocus.h>
29#include <gtkmm/eventcontrollerkey.h>
30#include <gtkmm/eventcontrollermotion.h>
31#include <gtkmm/eventcontrollerscroll.h>
32#include <gdkmm/frameclock.h>
33#include <gdkmm/glcontext.h>
34#include <gtkmm/applicationwindow.h>
35#include <gtkmm/gestureclick.h>
36#include <sigc++/functors/mem_fun.h>
63#define framecheck_whole_function(D) \
64 auto framecheckobj = D->prefs.debug_framecheck ? FrameCheck::Event(__func__) : FrameCheck::Event();
113auto pref_to_updater(
int index)
119 return arr[
index - 1];
122std::optional<Antialiasing> get_antialiasing_override(
bool enabled)
142enum class AbortFlags :
int
214 CanvasPrivate(Canvas *q)
224 std::optional<CanvasItemContext> canvasitem_ctx;
234 std::unique_ptr<Updater> updater;
235 Cairo::RefPtr<Cairo::Region> invalidated;
238 std::unique_ptr<Graphics> graphics;
239 void activate_graphics();
240 void deactivate_graphics();
243 bool redraw_active =
false;
244 bool redraw_requested =
false;
245 sigc::connection schedule_redraw_conn;
246 void schedule_redraw(
bool instant =
false);
247 void launch_redraw();
252 bool process_event(CanvasEvent &event);
255 bool emit_event(CanvasEvent &event);
256 void ensure_geometry_uptodate();
257 CanvasItem *pre_scroll_grabbed_item;
258 unsigned unreleased_presses = 0;
259 bool delayed_leave_event =
false;
262 uint32_t
desk = 0xffffffff;
263 uint32_t
border = 0x00000000;
264 uint32_t
page = 0xffffffff;
266 bool clip_to_page =
false;
268 std::optional<Geom::PathVector> calc_page_clip()
const;
270 int scale_factor = 1;
275 bool outlines_enabled =
false;
278 bool background_in_stores_enabled =
false;
282 std::optional<boost::asio::thread_pool> pool;
284 int get_numthreads()
const;
288 std::atomic<int> abort_flags;
294 void render_tile(
int debug_id);
296 void paint_single_buffer(
const Cairo::RefPtr<Cairo::ImageSurface> &
surface,
const Geom::IntRect &rect,
bool need_background,
bool outline_pass);
297 void paint_error_buffer(
const Cairo::RefPtr<Cairo::ImageSurface> &
surface);
303 std::optional<Geom::Point> last_mouse;
309 std::optional<guint> tick_callback;
310 std::optional<gint64> last_time;
314 void autoscroll_end();
322 : d(
std::make_unique<CanvasPrivate>(this))
324 set_name(
"InkscapeCanvas");
327 auto const scroll = Gtk::EventControllerScroll::create();
328 scroll->set_flags(Gtk::EventControllerScroll::Flags::BOTH_AXES);
329 scroll->signal_scroll().connect([
this, &scroll = *scroll](
auto &&...args) {
return on_scroll(scroll, args...); },
true);
330 add_controller(scroll);
332 auto const click = Gtk::GestureClick::create();
333 click->set_button(0);
336 add_controller(click);
338 auto const key = Gtk::EventControllerKey::create();
343 auto const motion = Gtk::EventControllerMotion::create();
344 motion->signal_enter().connect([
this, &motion = *motion](
auto &&...args) {
on_enter(motion, args...); });
345 motion->signal_motion().connect([
this, &motion = *motion](
auto &&...args) {
on_motion(motion, args...); });
346 motion->signal_leave().connect([
this, &motion = *motion](
auto &&...args) {
on_leave(motion, args...); });
347 add_controller(motion);
349 auto const focus = Gtk::EventControllerFocus::create();
350 focus->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
353 add_controller(focus);
358 d->invalidated = Cairo::Region::create();
361 d->prefs.grabsize.action = [
this] {
d->canvasitem_ctx->root()->update_canvas_item_ctrl_sizes(
d->prefs.grabsize); };
362 d->prefs.debug_show_unclean.action = [
this] { queue_draw(); };
363 d->prefs.debug_show_clean.action = [
this] { queue_draw(); };
364 d->prefs.debug_disable_redraw.action = [
this] {
d->schedule_redraw(); };
365 d->prefs.debug_sticky_decoupled.action = [
this] {
d->schedule_redraw(); };
366 d->prefs.debug_animate.action = [
this] { queue_draw(); };
367 d->prefs.outline_overlay_opacity.action = [
this] { queue_draw(); };
370 d->prefs.request_opengl.action = [
this] {
371 if (get_realized()) {
373 d->deactivate_graphics();
376 d->activate_graphics();
380 d->prefs.pixelstreamer_method.action = [
this] {
383 d->deactivate_graphics();
384 d->activate_graphics();
388 d->prefs.numthreads.action = [
this] {
389 if (!
d->active)
return;
390 int const new_numthreads =
d->get_numthreads();
391 if (
d->numthreads == new_numthreads)
return;
392 d->numthreads = new_numthreads;
394 d->deactivate_graphics();
395 d->pool.emplace(
d->numthreads);
396 d->activate_graphics();
401 d->canvasitem_ctx.emplace(
this);
412 property_scale_factor().signal_changed().connect([
this] {
d->schedule_redraw(); });
418 d->numthreads =
d->get_numthreads();
419 d->pool.emplace(
d->numthreads);
421 d->sync.connectExit([
this] {
d->after_redraw(); });
424int CanvasPrivate::get_numthreads()
const
426 if (
int n = prefs.numthreads; n > 0) {
429 }
else if (
int n = std::thread::hardware_concurrency(); n > 0) {
431 return n == 1 ? 1 : n - 1;
439void CanvasPrivate::activate_graphics()
441 if (q->get_opengl_enabled()) {
447 stores.set_graphics(graphics.get());
452void CanvasPrivate::activate()
454 q->_left_grabbed_item =
false;
455 q->_all_enter_events =
false;
456 q->_is_dragging =
false;
459 q->_current_canvas_item =
nullptr;
460 q->_current_canvas_item_new =
nullptr;
461 q->_grabbed_canvas_item =
nullptr;
462 q->_grabbed_event_mask = {};
463 pre_scroll_grabbed_item =
nullptr;
466 q->_need_update =
true;
469 q->_split_dragging =
false;
473 schedule_redraw(
true);
476void CanvasPrivate::deactivate()
481 if (schedule_redraw_conn) {
483 schedule_redraw_conn.disconnect();
486 abort_flags.store((
int)AbortFlags::Hard, std::memory_order_relaxed);
487 if (prefs.debug_logging) std::cout <<
"Hard exit request" << std::endl;
491 canvasitem_ctx->unsnapshot();
492 q->_drawing->unsnapshot();
495 redraw_active =
false;
496 redraw_requested =
false;
497 assert(!schedule_redraw_conn);
501void CanvasPrivate::deactivate_graphics()
503 if (q->get_opengl_enabled()) q->make_current();
505 stores.set_graphics(
nullptr);
512 if (
d->active)
d->deactivate();
513 if (
d->graphics)
d->deactivate_graphics();
516 d->canvasitem_ctx.reset();
521 if (
d->active && !drawing)
d->deactivate();
529 if (!
d->active && get_realized() && drawing)
d->activate();
534 return d->canvasitem_ctx->root();
540 d->activate_graphics();
547 d->deactivate_graphics();
556void CanvasPrivate::schedule_redraw(
bool instant)
563 if (q->get_width() == 0 || q->get_height() == 0) {
570 redraw_requested =
true;
573 if (schedule_redraw_conn && instant) {
580 redraw_active =
true;
582 auto callback = [
this] {
583 if (q->get_opengl_enabled()) {
586 if (prefs.debug_logging) std::cout <<
"Redraw start" << std::endl;
591 schedule_redraw_conn.disconnect();
594 assert(!schedule_redraw_conn);
595 schedule_redraw_conn = Glib::signal_idle().connect([=] { callback();
return false; });
602void CanvasPrivate::launch_redraw()
604 assert(redraw_active);
606 if (q->_render_mode != render_mode) {
610 render_mode = q->_render_mode;
612 q->_drawing->setOutlineOverlay(outlines_required());
615 if (q->_split_mode != split_mode) {
617 split_mode = q->_split_mode;
618 q->_drawing->setOutlineOverlay(outlines_required());
622 if ((outlines_required() && !outlines_enabled) || scale_factor != q->get_scale_factor()) {
626 outlines_enabled = outlines_required();
627 scale_factor = q->get_scale_factor();
629 graphics->set_outlines_enabled(outlines_enabled);
630 graphics->set_scale_factor(scale_factor);
638 canvasitem_ctx->root()->visit_page_rects([
this] (
auto &rect) {
639 pi.pages.emplace_back(rect);
645 q->_drawing->setClip(calc_page_clip());
648 handle_stores_action(stores.update(Fragment{ q->_affine, q->get_area_world() }));
651 bool const affine_changed = canvasitem_ctx->affine() != stores.store().affine;
652 if (q->_need_update || affine_changed) {
653 FrameCheck::Event fc;
654 if (prefs.debug_framecheck) fc = FrameCheck::Event(
"update");
655 q->_need_update =
false;
656 canvasitem_ctx->setAffine(stores.store().affine);
657 canvasitem_ctx->root()->update(affine_changed);
661 auto const strategy = pref_to_updater(prefs.update_strategy);
662 if (updater->get_strategy() != strategy) {
664 new_updater->clean_region = std::move(updater->clean_region);
665 updater = std::move(new_updater);
668 updater->mark_dirty(invalidated);
669 invalidated = Cairo::Region::create();
671 updater->next_frame();
678 if (prefs.debug_disable_redraw) {
679 redraw_active =
false;
683 redraw_requested =
false;
686 canvasitem_ctx->snapshot();
687 q->_drawing->snapshot();
690 rd.mouse_loc = last_mouse.value_or(
Geom::Point(q->get_dimensions()) / 2).round();
693 rd.mouse_loc += q->_pos;
695 rd.mouse_loc = (
Geom::Point(
rd.mouse_loc) * q->_affine.inverse() * stores.store().affine).round();
699 rd.visible = q->get_area_world();
705 rd.store = Fragment{ stores.store().affine, stores.store().rect };
707 rd.coarsener_min_size = prefs.coarsener_min_size;
708 rd.coarsener_glue_size = prefs.coarsener_glue_size;
709 rd.coarsener_min_fullness = prefs.coarsener_min_fullness;
710 rd.tile_size = prefs.tile_size;
711 rd.preempt = prefs.preempt;
712 rd.margin = prefs.prerender;
713 rd.redraw_delay = prefs.debug_delay_redraw ? std::make_optional<int>(prefs.debug_delay_redraw_time) :
std::nullopt;
714 rd.render_time_limit = prefs.render_time_limit;
715 rd.numthreads = get_numthreads();
719 rd.debug_framecheck = prefs.debug_framecheck;
720 rd.debug_show_redraw = prefs.debug_show_redraw;
722 rd.snapshot_drawn = stores.snapshot().drawn ? stores.snapshot().drawn->copy() : Cairo::RefPtr<Cairo::Region>();
723 rd.cms_transform = q->_cms_active ? q->_cms_transform :
nullptr;
725 abort_flags.store((
int)AbortFlags::None, std::memory_order_relaxed);
727 boost::asio::post(*pool, [
this] { init_tiler(); });
730void CanvasPrivate::after_redraw()
732 assert(redraw_active);
735 canvasitem_ctx->unsnapshot();
736 q->_drawing->unsnapshot();
739 if (q->get_opengl_enabled()) {
747 bool stores_changed =
false;
748 if (!
rd.timeoutflag) {
749 auto const ret = stores.finished_draw(Fragment{ q->_affine, q->get_area_world() });
750 handle_stores_action(ret);
752 stores_changed =
true;
757 if (
rd.timeoutflag || redraw_requested || stores_changed) {
758 if (prefs.debug_logging) std::cout <<
"Continuing redrawing" << std::endl;
759 redraw_requested =
false;
762 if (prefs.debug_logging) std::cout <<
"Redraw exit" << std::endl;
763 redraw_active =
false;
775 if (prefs.debug_show_unclean) q->queue_draw();
780 updater->intersect(stores.store().rect);
782 if (prefs.debug_show_unclean) q->queue_draw();
790 q->_drawing->setCacheLimit(stores.store().rect);
795void CanvasPrivate::commit_tiles()
797 framecheck_whole_function(
this)
802 auto lock = std::lock_guard(
rd.tiles_mutex);
806 for (
auto &tile :
tiles) {
808 graphics->draw_tile(tile.fragment, std::move(tile.surface), std::move(tile.outline_surface));
811 assert(stores.store().rect.contains(tile.fragment.rect));
812 stores.mark_drawn(tile.fragment.rect);
818 repaint_rect = tile.fragment.rect - q->_pos;
822 pl *= stores.store().affine.inverse() * q->_affine;
824 repaint_rect = pl.bounds().roundOutwards();
828 auto screen_rect =
Geom::IntRect({0, 0}, q->get_dimensions());
829 if ((repaint_rect & screen_rect).regularized()) {
831 queue_draw_area(repaint_rect);
842 auto const r = pt.
length();
843 return r <= max ? pt : pt * (max / r);
848 constexpr double max_speed = 30.0;
849 constexpr double max_distance = 25.0;
850 return std::clamp(
Geom::sqr(r / max_distance) * max_speed, 1.0, max_speed);
855 auto const r = pt.
length();
860void CanvasPrivate::autoscroll_begin(
Geom::Point const &to)
866 auto const rect =
expandedBy(
Geom::Rect({}, q->get_dimensions()), -(
int)prefs.autoscrolldistance);
867 strain = to - rect.clamp(to);
869 if (strain ==
Geom::Point(0, 0) || tick_callback) {
873 tick_callback = q->add_tick_callback([
this] (Glib::RefPtr<Gdk::FrameClock>
const &clock) {
874 auto timings = clock->get_current_timings();
875 auto const t = timings->get_frame_time();
880 dt = timings->get_refresh_interval();
883 dt *= 60.0 / 1e6 * prefs.autoscrollspeed;
885 bool const strain_zero = strain ==
Geom::Point(0, 0);
887 if (strain.x() * velocity.x() < 0) velocity.x() = 0;
888 if (strain.y() * velocity.y() < 0) velocity.y() = 0;
890 auto const max_accel = strain_zero ? 3 : 2;
891 velocity +=
cap_length(tgtvel - velocity, max_accel * dt);
892 displacement += velocity * dt;
893 auto const dpos = displacement.round();
894 q->_desktop->scroll_relative(-dpos);
895 displacement -= dpos;
898 ensure_geometry_uptodate();
899 auto event = MotionEvent();
900 event.modifiers = q->_state;
901 event.pos = *last_mouse;
905 if (strain_zero && velocity.length() <= 0.1) {
908 displacement = velocity = {};
916void CanvasPrivate::autoscroll_end()
926 d->autoscroll_begin(*
d->last_mouse);
938 auto gdkevent = controller.get_current_event();
939 _state = (int)controller.get_current_event_state();
943 event.device = controller.get_current_event_device();
944 event.delta = {
dx, dy };
945 event.unit = controller.get_unit();
948 return d->process_event(event);
952 int n_press,
double x,
double y)
954 _state = (int)controller.get_current_event_state();
956 d->unreleased_presses |= 1 << controller.get_current_button();
960 if (controller.get_current_button() == 3) {
969 return Gtk::EventSequenceState::CLAIMED;
970 }
else if (n_press == 2) {
974 return Gtk::EventSequenceState::CLAIMED;
980 event.device = controller.get_current_event_device();
981 event.pos = *
d->last_mouse;
982 event.button = controller.get_current_button();
983 event.time = controller.get_current_event_time();
987 bool result =
d->process_event(event);
990 event.num_press = n_press;
991 result =
d->process_event(event);
994 return result ? Gtk::EventSequenceState::CLAIMED : Gtk::EventSequenceState::NONE;
998 int ,
double x,
double y)
1000 _state = (int)controller.get_current_event_state();
1002 d->unreleased_presses &= ~(1 << controller.get_current_button());
1011 x > get_allocation().get_width() - 5 ||
1012 y > get_allocation().get_height() - 5)
1019 auto window =
dynamic_cast<Gtk::ApplicationWindow*
>(get_root());
1021 std::cerr <<
"Canvas::on_motion_notify_event: window missing!" << std::endl;
1022 return Gtk::EventSequenceState::CLAIMED;
1025 auto action = window->lookup_action(
"canvas-split-mode");
1027 std::cerr <<
"Canvas::on_motion_notify_event: action 'canvas-split-mode' missing!" << std::endl;
1028 return Gtk::EventSequenceState::CLAIMED;
1031 auto saction = std::dynamic_pointer_cast<Gio::SimpleAction>(action);
1033 std::cerr <<
"Canvas::on_motion_notify_event: action 'canvas-split-mode' not SimpleAction!" << std::endl;
1034 return Gtk::EventSequenceState::CLAIMED;
1041 auto const button = controller.get_current_button();
1043 d->autoscroll_end();
1047 event.modifiers =
_state;
1048 event.device = controller.get_current_event_device();
1049 event.pos = *
d->last_mouse;
1050 event.button = controller.get_current_button();
1051 event.time = controller.get_current_event_time();
1053 auto result =
d->process_event(event) ? Gtk::EventSequenceState::CLAIMED : Gtk::EventSequenceState::NONE;
1055 if (
d->unreleased_presses == 0 &&
d->delayed_leave_event) {
1057 d->delayed_leave_event =
false;
1060 event.modifiers =
_state;
1062 d->process_event(event);
1070 if (
d->delayed_leave_event) {
1071 d->delayed_leave_event =
false;
1075 _state = (int)controller.get_current_event_state();
1079 event.modifiers =
_state;
1080 event.pos = *
d->last_mouse;
1082 d->process_event(event);
1087 if (
d->unreleased_presses != 0) {
1088 d->delayed_leave_event =
true;
1092 _state = (int)controller.get_current_event_state();
1096 event.modifiers =
_state;
1098 d->process_event(event);
1113 unsigned keyval,
unsigned keycode, Gdk::ModifierType state)
1115 _state =
static_cast<int>(state);
1118 event.modifiers =
_state;
1119 event.device = controller.get_current_event_device();
1120 event.keyval = keyval;
1121 event.keycode = keycode;
1122 event.group = controller.get_group();
1123 event.time = controller.get_current_event_time();
1124 event.pos =
d->last_mouse;
1126 return d->process_event(event);
1130 unsigned keyval,
unsigned keycode, Gdk::ModifierType state)
1132 _state =
static_cast<int>(state);
1135 event.modifiers =
_state;
1136 event.device = controller.get_current_event_device();
1137 event.keyval = keyval;
1138 event.keycode = keycode;
1139 event.group = controller.get_group();
1140 event.time = controller.get_current_event_time();
1141 event.pos =
d->last_mouse;
1143 d->process_event(event);
1149 if (mouse ==
d->last_mouse) {
1152 d->last_mouse = mouse;
1154 _state = (int)controller.get_current_event_state();
1160 auto cursor_position = mouse.floor();
1177 auto diff = cursor_position - split_position;
1181 if (diff.y() - diff.x() > 0) {
1182 if (diff.y() + diff.x() > 0) {
1188 if (diff.y() + diff.x() > 0) {
1197 if (std::abs(diff.y()) < 3) {
1202 if (std::abs(diff.x()) < 3) {
1221 if (!(
_state & (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK))) {
1222 d->autoscroll_end();
1226 event.modifiers =
_state;
1227 event.device = controller.get_current_event_device();
1228 event.pos = *
d->last_mouse;
1229 event.time = controller.get_current_event_time();
1232 d->process_event(event);
1240bool CanvasPrivate::process_event(
CanvasEvent &event)
1242 framecheck_whole_function(
this)
1245 std::cerr <<
"Canvas::process_event: Called while not active!" << std::endl;
1250 switch (event.
type()) {
1253 if (!pre_scroll_grabbed_item) {
1254 pre_scroll_grabbed_item = q->_current_canvas_item;
1255 if (q->_grabbed_canvas_item && !q->_current_canvas_item->is_descendant_of(q->_grabbed_canvas_item)) {
1256 pre_scroll_grabbed_item = q->_grabbed_canvas_item;
1261 bool retval = emit_event(event);
1270 pre_scroll_grabbed_item =
nullptr;
1276 q->_state =
event.modifiersAfter();
1277 return emit_event(event);
1281 pre_scroll_grabbed_item =
nullptr;
1284 bool retval = emit_event(event);
1287 q->_state =
event.modifiersAfter();
1294 pre_scroll_grabbed_item =
nullptr;
1298 pre_scroll_grabbed_item =
nullptr;
1301 q->_desktop->getSnapIndicator()->remove_snaptarget();
1307 return emit_event(event);
1310 pre_scroll_grabbed_item =
nullptr;
1312 return emit_event(event);
1327CanvasItem *CanvasPrivate::find_item_at(
Geom::Point pt)
1330 bool outline = q->canvas_point_in_outline_zone(pt);
1335 pt *= q->_affine.inverse() * canvasitem_ctx->affine();
1338 q->_drawing->getCanvasItemDrawing()->set_pick_outline(
outline);
1339 return canvasitem_ctx->root()->pick_item(pt);
1357bool CanvasPrivate::repick()
1360 ensure_geometry_uptodate();
1362 bool button_down =
false;
1363 if (!q->_all_enter_events) {
1369 button_down = q->_state & (GDK_BUTTON1_MASK |
1375 q->_left_grabbed_item =
false;
1380 q->_current_canvas_item_new =
nullptr;
1383 if (last_mouse && canvasitem_ctx->root()->is_visible()) {
1384 q->_current_canvas_item_new = find_item_at(*last_mouse);
1392 if (q->_current_canvas_item_new == q->_current_canvas_item && !q->_left_grabbed_item) {
1398 bool retval =
false;
1399 if (q->_current_canvas_item_new != q->_current_canvas_item &&
1400 q->_current_canvas_item &&
1401 !q->_left_grabbed_item)
1403 auto event = LeaveEvent();
1404 event.modifiers = q->_state;
1405 retval = emit_event(event);
1408 if (!q->_all_enter_events) {
1410 if (q->_current_canvas_item_new != q->_current_canvas_item && button_down) {
1411 q->_left_grabbed_item =
true;
1417 q->_left_grabbed_item =
false;
1418 q->_current_canvas_item = q->_current_canvas_item_new;
1420 if (q->_current_canvas_item) {
1421 auto event = EnterEvent();
1422 event.modifiers = q->_state;
1423 event.pos = *last_mouse;
1424 retval = emit_event(event);
1445bool CanvasPrivate::emit_event(CanvasEvent &event)
1447 ensure_geometry_uptodate();
1450 if (q->_grabbed_canvas_item && !(event.type() & q->_grabbed_event_mask)) {
1463 p = p * q->_affine.inverse() * canvasitem_ctx->affine();
1468 [&] (EnterEvent &event) { conv(event.pos); },
1469 [&] (MotionEvent &event) { conv(event.pos); },
1470 [&] (ButtonPressEvent &event) {
1472 if (event.num_press == 1) {
1473 conv(event.pos, &event.orig_pos);
1476 [&] (ButtonReleaseEvent &event) { conv(event.pos); },
1477 [&] (KeyEvent &event) {
1479 event.orig_pos.emplace();
1480 conv(*event.pos, &*event.orig_pos);
1483 [&] (CanvasEvent &event) {}
1488 [&] (ButtonPressEvent &event) {
1489 if (event.button == 1) {
1490 q->_is_dragging = true;
1493 [&] (ButtonReleaseEvent &event) { q->_is_dragging =
false; },
1494 [&] (CanvasEvent &event) {}
1497 if (q->_current_canvas_item) {
1499 auto item = q->_current_canvas_item;
1501 if (q->_grabbed_canvas_item && !q->_current_canvas_item->is_descendant_of(q->_grabbed_canvas_item)) {
1502 item = q->_grabbed_canvas_item;
1506 item = pre_scroll_grabbed_item;
1511 if (
item->handle_event(event))
return true;
1522void CanvasPrivate::ensure_geometry_uptodate()
1524 if (q->_need_update && !q->_drawing->snapshotted() && !canvasitem_ctx->snapshotted()) {
1525 FrameCheck::Event fc;
1526 if (prefs.debug_framecheck) fc = FrameCheck::Event(
"update", 1);
1527 q->_need_update =
false;
1528 canvasitem_ctx->root()->update(
false);
1538 return {get_width(), get_height()};
1554 return point +
_pos;
1580 default:
return false;
1592 return d->last_mouse;
1597 return d->canvasitem_ctx->affine();
1600void CanvasPrivate::queue_draw_area(
const Geom::IntRect &rect)
1618 d->schedule_redraw();
1619 if (
d->prefs.debug_show_unclean) queue_draw();
1635 constexpr int min_coord = -(1 << 30);
1636 constexpr int max_coord = (1 << 30) - 1;
1638 x0 = std::clamp(x0, min_coord, max_coord);
1639 y0 = std::clamp(y0, min_coord, max_coord);
1640 x1 = std::clamp(x1, min_coord, max_coord);
1641 y1 = std::clamp(y1, min_coord, max_coord);
1643 if (x0 >= x1 || y0 >= y1) {
1647 if (
d->redraw_active &&
d->invalidated->empty()) {
1648 d->abort_flags.store((
int)AbortFlags::Soft, std::memory_order_relaxed);
1649 if (
d->prefs.debug_logging) std::cout <<
"Soft exit request" << std::endl;
1654 d->schedule_redraw();
1655 if (
d->prefs.debug_show_unclean) queue_draw();
1662 constexpr Geom::Coord min_int = std::numeric_limits<int>::min();
1663 constexpr Geom::Coord max_int = std::numeric_limits<int>::max();
1666 (
int)std::floor(std::clamp(x0, min_int, max_int)),
1667 (
int)std::floor(std::clamp(y0, min_int, max_int)),
1668 (
int)std::ceil (std::clamp(x1, min_int, max_int)),
1669 (
int)std::ceil (std::clamp(y1, min_int, max_int))
1687 d->schedule_redraw();
1701 d->schedule_redraw();
1716 d->schedule_redraw();
1725 if (
d->desk == rgba)
return;
1726 bool invalidated =
d->background_in_stores_enabled;
1728 invalidated |=
d->background_in_stores_enabled =
d->background_in_stores_required();
1729 if (get_realized() && invalidated)
redraw_all();
1738 if (
d->border == rgba)
return;
1748 if (
d->page == rgba)
return;
1749 bool invalidated =
d->background_in_stores_enabled;
1751 invalidated |=
d->background_in_stores_enabled =
d->background_in_stores_required();
1752 if (get_realized() && invalidated)
redraw_all();
1760 d->schedule_redraw();
1775 d->schedule_redraw();
1792 if (
clip !=
d->clip_to_page) {
1793 d->clip_to_page =
clip;
1794 d->schedule_redraw();
1819 if (
item ==
d->pre_scroll_grabbed_item) {
1820 d->pre_scroll_grabbed_item =
nullptr;
1824std::optional<Geom::PathVector> CanvasPrivate::calc_page_clip()
const
1826 if (!clip_to_page) {
1831 for (
auto &rect : pi.pages) {
1869 set_cursor(
"pointer");
1875 set_cursor(
"ns-resize");
1881 set_cursor(
"ew-resize");
1887 std::cerr <<
"Canvas::update_cursor: Unknown hover direction!" << std::endl;
1893 parent_type::size_allocate_vfunc(
width,
height, baseline);
1908 if (prefs->getBool(
"/options/stickyzoom/value",
false)) {
1910 auto const old_minextent = min(
d->old_dimensions);
1911 auto const new_minextent = min(new_dimensions);
1912 if (old_minextent != 0) {
1913 zoom *= (double)new_minextent / old_minextent;
1920 d->old_dimensions = new_dimensions;
1924 d->schedule_redraw(
true);
1929 Glib::RefPtr<Gdk::GLContext>
result;
1932 result =
dynamic_cast<Gtk::Window &
>(*get_root()).get_surface()->create_gl_context();
1933 }
catch (
const Gdk::GLError &e) {
1934 std::cerr <<
"Failed to create OpenGL context: " << e.what() << std::endl;
1938 result->set_allowed_apis(Gdk::GLApi::GL);
1942 }
catch (
const Glib::Error &e) {
1943 std::cerr <<
"Failed to realize OpenGL context: " << e.what() << std::endl;
1952 framecheck_whole_function(
d)
1955 std::cerr <<
"Canvas::paint_widget: Called while not active!" << std::endl;
1959 if constexpr (
false)
d->canvasitem_ctx->root()->canvas_item_print_tree();
1965 std::cerr <<
"Canvas::paint_widget: Called while active but uninitialised!" << std::endl;
1971 if (
d->schedule_redraw_conn) {
1974 d->schedule_redraw_conn.disconnect();
1985 args.
mouse =
d->last_mouse;
1996 if (
d->prefs.debug_animate) {
1997 auto t = g_get_monotonic_time() / 1700000.0;
2010auto coarsen(
const Cairo::RefPtr<Cairo::Region> ®ion,
int min_size,
int glue_size,
double min_fullness)
2019 std::multiset<Geom::IntRect, Compare>
rects;
2020 int nrects = region->get_num_rectangles();
2021 for (
int i = 0; i < nrects; i++) {
2026 std::vector<Geom::IntRect> processed;
2027 processed.reserve(nrects);
2030 std::vector<
decltype(
rects)::iterator> remove_rects;
2031 std::vector<int> remove_processed;
2034 while (!
rects.empty() &&
rects.begin()->minExtent() < min_size) {
2036 auto rect = *
rects.begin();
2040 int effective_glue_size = glue_size;
2044 auto glue_zone = rect;
2045 glue_zone.
expandBy(effective_glue_size);
2048 auto newrect = rect;
2049 int absorbed_area = 0;
2051 remove_rects.clear();
2052 for (
auto it =
rects.begin(); it !=
rects.end(); ++it) {
2053 if (glue_zone.contains(*it)) {
2055 absorbed_area += it->area();
2056 remove_rects.emplace_back(it);
2060 remove_processed.clear();
2061 for (
int i = 0; i < processed.size(); i++) {
2062 auto &r = processed[i];
2063 if (glue_zone.contains(r)) {
2064 newrect.unionWith(r);
2065 absorbed_area += r.area();
2066 remove_processed.emplace_back(i);
2071 double fullness = (double)(rect.
area() + absorbed_area) / newrect.area();
2072 if (fullness < min_fullness) {
2073 effective_glue_size /= 2;
2080 for (
auto &it : remove_rects) {
2084 for (
int j = (
int)remove_processed.size() - 1; j >= 0; j--) {
2085 int i = remove_processed[j];
2086 processed[i] = processed.back();
2087 processed.pop_back();
2091 bool finished = absorbed_area == 0 || rect.
minExtent() >= min_size;
2097 effective_glue_size = glue_size;
2101 processed.emplace_back(rect);
2105 for (
auto &rect :
rects) {
2106 processed.emplace_back(rect);
2114 int bw = rect.
width();
2131void CanvasPrivate::init_tiler()
2134 rd.start_time = g_get_monotonic_time();
2136 rd.vis_store = (
rd.visible &
rd.store.rect).regularized();
2138 if (!init_redraw()) {
2144 rd.timeoutflag =
false;
2146 rd.numactive =
rd.numthreads;
2148 for (
int i = 0; i <
rd.numthreads - 1; i++) {
2149 boost::asio::post(*pool, [=,
this] { render_tile(i); });
2152 render_tile(
rd.numthreads - 1);
2155bool CanvasPrivate::init_redraw()
2157 assert(
rd.rects.empty());
2161 if (
rd.vis_store &&
rd.decoupled_mode) {
2164 process_redraw(*
rd.vis_store,
unioned(updater->clean_region->copy(),
rd.snapshot_drawn));
2175 process_redraw(*
rd.vis_store, updater->get_next_clean_region());
2186 auto prerender_store = (prerender &
rd.store.rect).regularized();
2187 if (prerender_store) {
2188 process_redraw(*prerender_store, updater->clean_region);
2211 assert(
rd.store.rect.contains(
rd.bounds));
2215 region->subtract(
rd.clean);
2219 std::min<int>(
rd.coarsener_min_size,
rd.tile_size / 2),
2220 std::min<int>(
rd.coarsener_glue_size,
rd.tile_size / 2),
2221 rd.coarsener_min_fullness);
2224 std::make_heap(
rd.rects.begin(),
rd.rects.end(),
rd.getcmp());
2228 adjust = std::clamp(adjust, 0.3, 1.0);
2229 rd.effective_tile_size =
rd.tile_size * adjust;
2233void CanvasPrivate::render_tile(
int debug_id)
2238 FrameCheck::Event fc;
2239 if (
rd.debug_framecheck) {
2240 fc_str =
"render_thread_" + std::to_string(debug_id + 1);
2241 fc = FrameCheck::Event(fc_str.c_str());
2246 if (
rd.rects.empty()) {
2257 auto const flags = abort_flags.load(std::memory_order_relaxed);
2258 bool const soft = flags & (int)AbortFlags::Soft;
2259 bool const hard = flags & (int)AbortFlags::Hard;
2260 if (hard || (
rd.phase == 3 && soft)) {
2265 std::pop_heap(
rd.rects.begin(),
rd.rects.end(),
rd.getcmp());
2266 auto rect =
rd.rects.back();
2267 rd.rects.pop_back();
2276 if (
rd.clean->contains_rectangle(
geom_to_cairo(rect)) == Cairo::Region::Overlap::IN) {
2282 rd.rects.emplace_back(rect);
2283 std::push_heap(
rd.rects.begin(),
rd.rects.end(),
rd.getcmp());
2287 if (
auto axis =
bisect(rect,
rd.effective_tile_size)) {
2288 int mid = rect[*axis].middle();
2289 auto lo = rect; lo[*axis].
setMax(mid); add_rect(lo);
2290 auto hi = rect; hi[*axis].
setMin(mid); add_rect(hi);
2296 if (
rd.preemptible) {
2297 if (rect.
width() <
rd.preempt) {
2298 if (rect.
left() ==
rd.bounds.left() ) rect.
setLeft (std::max(rect.
right() -
rd.preempt,
rd.store.rect.left() ));
2299 if (rect.
right() ==
rd.bounds.right()) rect.
setRight(std::min(rect.
left() +
rd.preempt,
rd.store.rect.right()));
2302 if (rect.
top() ==
rd.bounds.top() ) rect.
setTop (std::max(rect.
bottom() -
rd.preempt,
rd.store.rect.top() ));
2303 if (rect.
bottom() ==
rd.bounds.bottom()) rect.
setBottom(std::min(rect.
top() +
rd.preempt,
rd.store.rect.bottom()));
2308 updater->mark_clean(rect);
2318 if (
rd.interruptible) {
2319 auto now = g_get_monotonic_time();
2320 auto elapsed =
now -
rd.start_time;
2321 if (elapsed >
rd.render_time_limit * 1000) {
2323 rd.timeoutflag =
true;
2329 if (
rd.debug_framecheck &&
rd.timeoutflag) {
2334 bool const done =
rd.numactive == 0;
2344bool CanvasPrivate::end_redraw()
2349 return init_redraw();
2352 if (!updater->report_finished()) {
2355 return init_redraw();
2369 assert(
rd.store.rect.contains(rect));
2371 auto paint = [&,
this] (
bool need_background,
bool outline_pass) {
2373 auto surface = graphics->request_tile_surface(rect,
true);
2375 sync.runInMain([&] {
2376 if (prefs.debug_logging) std::cout <<
"Blocked - buffer mapping" << std::endl;
2377 if (q->get_opengl_enabled()) q->make_current();
2378 surface = graphics->request_tile_surface(rect,
false);
2382 auto on_error = [&,
this] (
char const *err) {
2383 std::cerr <<
"paint_rect: " << err << std::endl;
2384 sync.runInMain([&] {
2385 if (q->get_opengl_enabled()) q->make_current();
2386 graphics->junk_tile_surface(std::move(
surface));
2387 surface = graphics->request_tile_surface(rect,
false);
2393 paint_single_buffer(
surface, rect, need_background, outline_pass);
2394 }
catch (std::bad_alloc
const &e) {
2398 }
catch (Cairo::logic_error
const &e) {
2407 tile.fragment.affine =
rd.store.affine;
2408 tile.fragment.rect = rect;
2409 tile.surface = paint(
rd.background_in_stores_required,
false);
2410 if (outlines_enabled) {
2411 tile.outline_surface = paint(
false,
true);
2415 if (
rd.redraw_delay) g_usleep(*
rd.redraw_delay);
2419 auto g = std::lock_guard(
rd.tiles_mutex);
2420 rd.tiles.emplace_back(std::move(tile));
2424void CanvasPrivate::paint_single_buffer(Cairo::RefPtr<Cairo::ImageSurface>
const &
surface,
Geom::IntRect const &rect,
bool need_background,
bool outline_pass)
2427 auto cr = Cairo::Context::create(
surface);
2431 if (need_background) {
2434 cr->set_operator(Cairo::Context::Operator::CLEAR);
2440 auto buf = CanvasItemBuffer{ rect, scale_factor, cr, outline_pass };
2441 canvasitem_ctx->root()->render(
buf);
2446 if (
rd.cms_transform) {
2451 if (
rd.debug_show_redraw) {
2452 cr->set_source_rgba((rand() % 256) / 255.0, (rand() % 256) / 255.0, (rand() % 256) / 255.0, 0.2);
2453 cr->set_operator(Cairo::Context::Operator::OVER);
2458void CanvasPrivate::paint_error_buffer(Cairo::RefPtr<Cairo::ImageSurface>
const &
surface)
2462 auto cr = Cairo::Context::create(
surface);
2463 cr->set_source_rgb(0, 0, 0);
double coarsener_min_fullness
Cairo::RefPtr< Cairo::ImageSurface > surface
std::vector< Geom::IntRect > rects
std::shared_ptr< Colors::CMS::Transform > cms_transform
bool background_in_stores_required
std::vector< Tile > tiles
Cairo::RefPtr< Cairo::Region > snapshot_drawn
std::optional< int > redraw_delay
Geom::OptIntRect vis_store
Cairo::RefPtr< Cairo::ImageSurface > outline_surface
Cairo::RefPtr< Cairo::Region > clean
3x3 matrix representing an affine transformation.
Axis-aligned generic rectangle that can be empty.
Axis aligned, non-empty, generic rectangle.
C right() const
Return rightmost coordinate of the rectangle (+X is to the right).
C area() const
Compute the rectangle's area.
void setMin(CPoint const &p)
Set the upper left point of the rectangle.
bool contains(GenericRect< C > const &r) const
Check whether the rectangle includes all points in the given rectangle.
void setLeft(C val)
Set the minimum X coordinate of the rectangle.
C top() const
Return top coordinate of the rectangle (+Y is downwards).
void setTop(C val)
Set the minimum Y coordinate of the rectangle.
void setMax(CPoint const &p)
Set the lower right point of the rectangle.
void setRight(C val)
Set the maximum X coordinate of the rectangle.
void setBottom(C val)
Set the maximum Y coordinate of the rectangle.
void expandBy(C amount)
Expand the rectangle in both directions by the specified amount.
C left() const
Return leftmost coordinate of the rectangle (+X is to the right).
C height() const
Get the vertical extent of the rectangle.
C minExtent() const
Get the smaller extent (width or height) of the rectangle.
void unionWith(CRect const &b)
Enlarge the rectangle to contain the argument.
C width() const
Get the horizontal extent of the rectangle.
C bottom() const
Return bottom coordinate of the rectangle (+Y is downwards).
C maxExtent() const
Get the larger extent (width or height) of the rectangle.
bool hasZeroArea() const
Check whether the rectangle has zero area.
Two-dimensional point with integer coordinates.
constexpr IntCoord x() const noexcept
Paralellogram, representing a linear transformation of a rectangle.
void push_back(Path const &path)
Append a path at the end.
Sequence of contiguous curves, aka spline.
Two-dimensional point that doubles as a vector.
IntPoint floor() const
Round coordinates downwards.
Coord length() const
Compute the distance from origin.
constexpr Coord y() const noexcept
constexpr Coord x() const noexcept
Axis aligned, non-empty rectangle.
Rotation around the origin.
void set_sticky(bool sticky)
const std::shared_ptr< Transform > & getDisplayTransform()
Get the color managed trasform for the screen.
void setRenderMode(RenderMode)
CanvasItemDrawing * getCanvasItemDrawing()
void setColorMode(ColorMode)
void setAntialiasingOverride(std::optional< Antialiasing > antialiasing_override)
void setOutlineOverlay(bool)
static Preferences * get()
Access the singleton Preferences object.
double current_zoom() const
Inkscape::UI::Tools::ToolBase * getTool() const
void zoom_absolute(Geom::Point const &c, double zoom, bool keep_point=true)
Zoom to the given absolute zoom level.
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Geom::Point getDimensions() const
Access operating system wide information about color management.
constexpr uint32_t SP_RGBA32_A_U(uint32_t v)
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
bool sp_desktop_root_handler(Inkscape::CanvasEvent const &event, SPDesktop *desktop)
Editable view implementation.
Canvas item belonging to an SVG drawing element.
double Coord
Floating point type used to store coordinates.
constexpr Coord EPSILON
Default "acceptably small" value.
Specific geometry functions for Inkscape, not provided my lib2geom.
auto expandedBy(Geom::IntRect rect, int amount)
GenericRect< IntCoord > IntRect
auto use_state(Slot &&slot)
Geom::PathVector outline(Geom::Path const &input, double width, double miter, LineJoinType join, LineCapType butt, double tolerance)
Strokes the path given by input.
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
ExtendedInput extinput_from_gdkevent(Gdk::Event const &event)
Read the extended input data from a Gdk::Event.
time_t now()
parse current time from SOURCE_DATE_EPOCH environment variable
static T clip(T const &v, T const &a, T const &b)
static cairo_user_data_key_t key
Singleton class to access the preferences file in a convenient way.
Provides a class that shows a temporary indicator on the canvas of where the snap was,...
Abstraction of the store/snapshot mechanism.
Abstract base class for events.
virtual EventType type() const =0
Return the dynamic type of the CanvasEvent.
The pointer has entered a widget or item.
The pointer has exited a widget or item.
Movement of the mouse pointer.
static void activate(GApplication *app, gpointer)
Cairo::RectangleInt geom_to_cairo(const Geom::IntRect &rect)
Geom::IntRect cairo_to_geom(const Cairo::RectangleInt &rect)