27#include <boost/asio/thread_pool.hpp>
28#include <boost/asio/post.hpp>
29#include <gtkmm/eventcontrollerfocus.h>
30#include <gtkmm/eventcontrollerkey.h>
31#include <gtkmm/eventcontrollermotion.h>
32#include <gtkmm/eventcontrollerscroll.h>
33#include <gdkmm/frameclock.h>
34#include <gdkmm/glcontext.h>
35#include <gtkmm/applicationwindow.h>
36#include <gtkmm/gestureclick.h>
37#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)
118 assert(1 <= index && index <= arr.size());
119 return arr[index - 1];
122std::optional<Antialiasing> get_antialiasing_override(
bool enabled)
142enum class AbortFlags :
int
215 CanvasPrivate(Canvas *q)
225 std::optional<CanvasItemContext> canvasitem_ctx;
235 std::unique_ptr<Updater> updater;
236 Cairo::RefPtr<Cairo::Region> invalidated;
239 std::unique_ptr<Graphics> graphics;
240 void activate_graphics();
241 void deactivate_graphics();
244 bool redraw_active =
false;
245 bool redraw_requested =
false;
246 sigc::connection schedule_redraw_conn;
247 void schedule_redraw(
int priority = Glib::PRIORITY_DEFAULT);
248 void launch_redraw();
253 bool process_event(CanvasEvent &event);
256 bool emit_event(CanvasEvent &event);
257 void ensure_geometry_uptodate();
258 CanvasItem *pre_scroll_grabbed_item;
259 unsigned unreleased_presses = 0;
260 bool delayed_leave_event =
false;
263 uint32_t
desk = 0xffffffff;
264 uint32_t
border = 0x000000ff;
265 uint32_t
page = 0xffffffff;
267 bool clip_to_page =
false;
269 std::optional<Geom::PathVector> calc_page_clip()
const;
270 bool is_point_on_page(
const Geom::Point &point)
const;
272 int scale_factor = 1;
277 bool outlines_enabled =
false;
280 bool background_in_stores_enabled =
false;
284 std::optional<boost::asio::thread_pool> pool;
286 int get_numthreads()
const;
290 std::atomic<int> abort_flags;
296 void render_tile(
int debug_id);
298 void paint_single_buffer(
const Cairo::RefPtr<Cairo::ImageSurface> &
surface,
const Geom::IntRect &rect,
bool need_background,
bool outline_pass);
299 void paint_error_buffer(
const Cairo::RefPtr<Cairo::ImageSurface> &
surface);
305 std::optional<Geom::IntPoint> last_mouse;
311 std::optional<guint> tick_callback;
312 std::optional<gint64> last_time;
316 void autoscroll_end();
324 : d(
std::make_unique<CanvasPrivate>(this))
326 set_name(
"InkscapeCanvas");
329 Controller::add_scroll<nullptr, &Canvas::on_scroll, nullptr>
330 (*
this, *
this, GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
333 Controller::add_key<&Canvas::on_key_pressed, &Canvas::on_key_released, nullptr>(*
this, *
this);
334 Controller::add_motion<&Canvas::on_enter, &Canvas::on_motion, &Canvas::on_leave>(*
this, *
this);
337 focus->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
338 add_controller(focus);
339 focus->signal_enter().connect([
this] {
on_focus_in(); });
340 focus->signal_leave().connect([
this] {
on_focus_out(); });
348 d->prefs.grabsize.action = [=] {
d->canvasitem_ctx->root()->update_canvas_item_ctrl_sizes(
d->prefs.grabsize); };
349 d->prefs.debug_show_unclean.action = [=] { queue_draw(); };
350 d->prefs.debug_show_clean.action = [=] { queue_draw(); };
351 d->prefs.debug_disable_redraw.action = [=] {
d->schedule_redraw(); };
352 d->prefs.debug_sticky_decoupled.action = [=] {
d->schedule_redraw(); };
353 d->prefs.debug_animate.action = [=] { queue_draw(); };
354 d->prefs.outline_overlay_opacity.action = [=] { queue_draw(); };
357 d->prefs.request_opengl.action = [=] {
358 if (get_realized()) {
360 d->deactivate_graphics();
363 d->activate_graphics();
367 d->prefs.pixelstreamer_method.action = [=] {
370 d->deactivate_graphics();
371 d->activate_graphics();
375 d->prefs.numthreads.action = [=] {
376 if (!
d->active)
return;
377 int const new_numthreads =
d->get_numthreads();
378 if (
d->numthreads == new_numthreads)
return;
379 d->numthreads = new_numthreads;
381 d->deactivate_graphics();
382 d->pool.emplace(
d->numthreads);
383 d->activate_graphics();
388 d->canvasitem_ctx.emplace(
this);
399 property_scale_factor().signal_changed().connect([
this] {
d->schedule_redraw(); });
405 d->numthreads =
d->get_numthreads();
406 d->pool.emplace(
d->numthreads);
408 d->sync.connectExit([
this] {
d->after_redraw(); });
411int CanvasPrivate::get_numthreads()
const
413 if (
int n = prefs.numthreads;
n > 0) {
416 }
else if (
int n = std::thread::hardware_concurrency();
n > 0) {
418 return n == 1 ? 1 :
n - 1;
426void CanvasPrivate::activate_graphics()
428 if (q->get_opengl_enabled()) {
434 stores.set_graphics(graphics.get());
441 q->_left_grabbed_item =
false;
442 q->_all_enter_events =
false;
443 q->_is_dragging =
false;
446 q->_current_canvas_item =
nullptr;
447 q->_current_canvas_item_new =
nullptr;
448 q->_grabbed_canvas_item =
nullptr;
449 q->_grabbed_event_mask = {};
450 pre_scroll_grabbed_item =
nullptr;
453 q->_need_update =
true;
456 q->_split_dragging =
false;
461 schedule_redraw(Glib::PRIORITY_HIGH);
464void CanvasPrivate::deactivate()
469 if (schedule_redraw_conn.connected()) {
471 schedule_redraw_conn.disconnect();
474 abort_flags.store((
int)AbortFlags::Hard, std::memory_order_relaxed);
475 if (prefs.debug_logging) std::cout <<
"Hard exit request" << std::endl;
479 canvasitem_ctx->unsnapshot();
480 q->_drawing->unsnapshot();
483 redraw_active =
false;
484 redraw_requested =
false;
485 assert(!schedule_redraw_conn.connected());
489void CanvasPrivate::deactivate_graphics()
491 if (q->get_opengl_enabled()) q->make_current();
493 stores.set_graphics(
nullptr);
500 d->canvasitem_ctx.reset();
505 if (
d->active && !drawing)
d->deactivate();
513 if (!
d->active && get_realized() && drawing)
d->activate();
518 return d->canvasitem_ctx->root();
524 d->activate_graphics();
531 d->deactivate_graphics();
540void CanvasPrivate::schedule_redraw(
int priority)
548 redraw_requested =
true;
554 redraw_active =
true;
557 assert(!schedule_redraw_conn.connected());
558 schedule_redraw_conn = Glib::signal_idle().connect([
this] {
559 if (q->get_opengl_enabled()) {
562 if (prefs.debug_logging) std::cout <<
"Redraw start" << std::endl;
569void CanvasPrivate::launch_redraw()
571 assert(redraw_active);
573 if (q->_render_mode != render_mode) {
577 render_mode = q->_render_mode;
579 q->_drawing->setOutlineOverlay(outlines_required());
582 if (q->_split_mode != split_mode) {
584 split_mode = q->_split_mode;
585 q->_drawing->setOutlineOverlay(outlines_required());
589 if ((outlines_required() && !outlines_enabled) || scale_factor != q->get_scale_factor()) {
593 outlines_enabled = outlines_required();
594 scale_factor = q->get_scale_factor();
596 graphics->set_outlines_enabled(outlines_enabled);
597 graphics->set_scale_factor(scale_factor);
605 canvasitem_ctx->root()->visit_page_rects([
this] (
auto &rect) {
606 pi.pages.emplace_back(rect);
612 q->_drawing->setClip(calc_page_clip());
615 handle_stores_action(stores.update(Fragment{ q->_affine, q->get_area_world() }));
618 bool const affine_changed = canvasitem_ctx->affine() != stores.store().affine;
619 if (q->_need_update || affine_changed) {
620 FrameCheck::Event fc;
621 if (prefs.debug_framecheck) fc = FrameCheck::Event(
"update");
622 q->_need_update =
false;
623 canvasitem_ctx->setAffine(stores.store().affine);
624 canvasitem_ctx->root()->update(affine_changed);
628 auto const strategy = pref_to_updater(prefs.update_strategy);
629 if (updater->get_strategy() != strategy) {
631 new_updater->clean_region = std::move(updater->clean_region);
632 updater = std::move(new_updater);
635 updater->mark_dirty(invalidated);
638 updater->next_frame();
645 if (prefs.debug_disable_redraw) {
646 redraw_active =
false;
651 canvasitem_ctx->snapshot();
652 q->_drawing->snapshot();
655 rd.mouse_loc = last_mouse.value_or((
Geom::Point(q->get_dimensions()) / 2).round());
658 rd.mouse_loc += q->_pos;
660 rd.mouse_loc = (
Geom::Point(
rd.mouse_loc) * q->_affine.inverse() * stores.store().affine).round();
664 rd.visible = q->get_area_world();
670 rd.store = Fragment{ stores.store().affine, stores.store().rect };
672 rd.coarsener_min_size = prefs.coarsener_min_size;
673 rd.coarsener_glue_size = prefs.coarsener_glue_size;
674 rd.coarsener_min_fullness = prefs.coarsener_min_fullness;
675 rd.tile_size = prefs.tile_size;
676 rd.preempt = prefs.preempt;
677 rd.margin = prefs.prerender;
678 rd.redraw_delay = prefs.debug_delay_redraw ? std::make_optional<int>(prefs.debug_delay_redraw_time) :
std::nullopt;
679 rd.render_time_limit = prefs.render_time_limit;
680 rd.numthreads = get_numthreads();
684 rd.debug_framecheck = prefs.debug_framecheck;
685 rd.debug_show_redraw = prefs.debug_show_redraw;
687 rd.snapshot_drawn = stores.snapshot().drawn ? stores.snapshot().drawn->copy() : Cairo::RefPtr<Cairo::Region>();
688 rd.grabbed = q->_grabbed_canvas_item && prefs.block_updates ? (
roundedOutwards(q->_grabbed_canvas_item->get_bounds()) &
rd.visible &
rd.store.rect).regularized() :
Geom::OptIntRect();
689 rd.cms_transform = q->_cms_active ? q->_cms_transform :
nullptr;
693 boost::asio::post(*pool, [
this] { init_tiler(); });
696void CanvasPrivate::after_redraw()
698 assert(redraw_active);
701 canvasitem_ctx->unsnapshot();
702 q->_drawing->unsnapshot();
705 if (q->get_opengl_enabled()) {
713 bool stores_changed =
false;
714 if (!
rd.timeoutflag) {
715 auto const ret = stores.finished_draw(Fragment{ q->_affine, q->get_area_world() });
716 handle_stores_action(ret);
718 stores_changed =
true;
723 if (
rd.timeoutflag || redraw_requested || stores_changed) {
724 if (prefs.debug_logging) std::cout <<
"Continuing redrawing" << std::endl;
725 redraw_requested =
false;
728 if (prefs.debug_logging) std::cout <<
"Redraw exit" << std::endl;
729 redraw_active =
false;
741 if (prefs.debug_show_unclean) q->queue_draw();
746 updater->intersect(stores.store().rect);
748 if (prefs.debug_show_unclean) q->queue_draw();
756 q->_drawing->setCacheLimit(stores.store().rect);
761void CanvasPrivate::commit_tiles()
763 framecheck_whole_function(
this)
768 auto lock = std::lock_guard(
rd.tiles_mutex);
772 for (
auto &tile :
tiles) {
774 graphics->draw_tile(tile.fragment, std::move(tile.surface), std::move(tile.outline_surface));
777 assert(stores.store().rect.contains(tile.fragment.rect));
778 stores.mark_drawn(tile.fragment.rect);
784 repaint_rect = tile.fragment.rect - q->_pos;
788 pl *= stores.store().affine.inverse() * q->_affine;
790 repaint_rect = pl.bounds().roundOutwards();
794 auto screen_rect =
Geom::IntRect({0, 0}, q->get_dimensions());
795 if ((repaint_rect & screen_rect).regularized()) {
797 queue_draw_area(repaint_rect);
808 auto const r = pt.
length();
809 return r <=
max ? pt : pt * (
max / r);
814 constexpr double max_speed = 30.0;
815 constexpr double max_distance = 25.0;
821 auto const r = pt.
length();
833 strain = to - rect.clamp(to);
839 tick_callback = q->add_tick_callback([
this] (Glib::RefPtr<Gdk::FrameClock>
const &clock) {
840 auto timings = clock->get_current_timings();
841 auto const t = timings->get_frame_time();
846 dt = timings->get_refresh_interval();
849 dt *= 60.0 / 1e6 * prefs.autoscrollspeed;
853 if (strain.x() * velocity.x() < 0) velocity.x() = 0;
854 if (strain.y() * velocity.y() < 0) velocity.y() = 0;
856 auto const max_accel = strain_zero ? 3 : 2;
857 velocity +=
cap_length(tgtvel - velocity, max_accel * dt);
858 displacement += velocity * dt;
859 auto const dpos = displacement.round();
860 q->_desktop->scroll_relative(-dpos);
861 displacement -= dpos;
864 ensure_geometry_uptodate();
865 auto event = MotionEvent();
866 event.modifiers = q->_state;
867 event.pos = *last_mouse;
871 if (strain_zero && velocity.length() <= 0.1) {
874 displacement = velocity = {};
882void CanvasPrivate::autoscroll_end()
892 d->autoscroll_begin(*
d->last_mouse);
904 auto controller =
const_wrap(controller_c,
true);
905 auto gdkevent = controller->get_current_event();
906 _state = (int)controller->get_current_event_state();
910 event.device = controller->get_current_event_device();
911 event.delta = {
dx, dy };
912 event.unit = controller->get_unit();
915 return d->process_event(event);
919 int const n_press,
double const x,
double const y)
921 _state = (int)controller.get_current_event_state();
923 d->unreleased_presses |= 1 << controller.get_current_button();
927 if (controller.get_current_button() == 3) {
936 return Gtk::EventSequenceState::CLAIMED;
937 }
else if (n_press == 2) {
941 return Gtk::EventSequenceState::CLAIMED;
947 event.device = controller.get_current_event_device();
948 event.pos = *
d->last_mouse;
949 event.button = controller.get_current_button();
950 event.time = controller.get_current_event_time();
954 bool result =
d->process_event(event);
957 event.num_press = n_press;
958 result =
d->process_event(event);
965 int ,
double const x,
double const y)
967 _state = (int)controller.get_current_event_state();
969 d->unreleased_presses &= ~(1 << controller.get_current_button());
978 x > get_allocation().get_width() - 5 ||
979 y > get_allocation().get_height() - 5)
986 auto window =
dynamic_cast<Gtk::ApplicationWindow*
>(get_root());
988 std::cerr <<
"Canvas::on_motion_notify_event: window missing!" << std::endl;
989 return Gtk::EventSequenceState::CLAIMED;
992 auto action = window->lookup_action(
"canvas-split-mode");
994 std::cerr <<
"Canvas::on_motion_notify_event: action 'canvas-split-mode' missing!" << std::endl;
995 return Gtk::EventSequenceState::CLAIMED;
998 auto saction = std::dynamic_pointer_cast<Gio::SimpleAction>(action);
1000 std::cerr <<
"Canvas::on_motion_notify_event: action 'canvas-split-mode' not SimpleAction!" << std::endl;
1001 return Gtk::EventSequenceState::CLAIMED;
1008 auto const button = controller.get_current_button();
1010 d->autoscroll_end();
1014 event.modifiers =
_state;
1015 event.device = controller.get_current_event_device();
1016 event.pos = *
d->last_mouse;
1017 event.button = controller.get_current_button();
1018 event.time = controller.get_current_event_time();
1022 if (
d->unreleased_presses == 0 &&
d->delayed_leave_event) {
1024 d->delayed_leave_event =
false;
1027 event.modifiers =
_state;
1029 d->process_event(event);
1037 if (
d->delayed_leave_event) {
1038 d->delayed_leave_event =
false;
1042 auto controller =
const_wrap(controller_c,
true);
1043 _state = (int)controller->get_current_event_state();
1047 event.modifiers =
_state;
1048 event.pos = *
d->last_mouse;
1050 d->process_event(event);
1055 if (
d->unreleased_presses != 0) {
1056 d->delayed_leave_event =
true;
1060 auto controller =
const_wrap(controller_c,
true);
1061 _state = (int)controller->get_current_event_state();
1065 event.modifiers =
_state;
1067 d->process_event(event);
1082 unsigned keyval,
unsigned keycode, GdkModifierType
const state)
1084 auto controller =
const_wrap(controller_c,
true);
1088 event.modifiers =
_state;
1089 event.device = controller->get_current_event_device();
1090 event.keyval = keyval;
1091 event.keycode = keycode;
1092 event.group = controller->get_group();
1093 event.time = controller->get_current_event_time();
1094 event.pos =
d->last_mouse;
1096 return d->process_event(event);
1100 unsigned keyval,
unsigned keycode, GdkModifierType
const state)
1102 auto controller =
const_wrap(controller_c,
true);
1106 event.modifiers =
_state;
1107 event.device = controller->get_current_event_device();
1108 event.keyval = keyval;
1109 event.keycode = keycode;
1110 event.group = controller->get_group();
1111 event.time = controller->get_current_event_time();
1112 event.pos =
d->last_mouse;
1114 return d->process_event(event);
1119 auto controller =
const_wrap(controller_c,
true);
1120 _state = (int)controller->get_current_event_state();
1144 auto diff = cursor_position - split_position;
1148 if (diff.y() - diff.x() > 0) {
1149 if (diff.y() + diff.x() > 0) {
1155 if (diff.y() + diff.x() > 0) {
1188 if (!(
_state & (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK))) {
1189 d->autoscroll_end();
1193 event.modifiers =
_state;
1194 event.device = controller->get_current_event_device();
1195 event.pos = *
d->last_mouse;
1196 event.time = controller->get_current_event_time();
1199 d->process_event(event);
1207bool CanvasPrivate::process_event(
CanvasEvent &event)
1209 framecheck_whole_function(
this)
1212 std::cerr <<
"Canvas::process_event: Called while not active!" << std::endl;
1217 switch (event.
type()) {
1220 if (!pre_scroll_grabbed_item) {
1221 pre_scroll_grabbed_item = q->_current_canvas_item;
1222 if (q->_grabbed_canvas_item && !q->_current_canvas_item->is_descendant_of(q->_grabbed_canvas_item)) {
1223 pre_scroll_grabbed_item = q->_grabbed_canvas_item;
1228 bool retval = emit_event(event);
1237 pre_scroll_grabbed_item =
nullptr;
1243 q->_state =
event.modifiersAfter();
1244 return emit_event(event);
1248 pre_scroll_grabbed_item =
nullptr;
1251 bool retval = emit_event(event);
1254 q->_state =
event.modifiersAfter();
1261 pre_scroll_grabbed_item =
nullptr;
1265 pre_scroll_grabbed_item =
nullptr;
1268 q->_desktop->getSnapIndicator()->remove_snaptarget();
1274 return emit_event(event);
1277 pre_scroll_grabbed_item =
nullptr;
1279 return emit_event(event);
1294CanvasItem *CanvasPrivate::find_item_at(
Geom::Point pt)
1297 bool outline = q->canvas_point_in_outline_zone(pt);
1302 pt *= q->_affine.inverse() * canvasitem_ctx->affine();
1305 q->_drawing->getCanvasItemDrawing()->set_pick_outline(
outline);
1306 return canvasitem_ctx->root()->pick_item(pt);
1324bool CanvasPrivate::repick()
1327 ensure_geometry_uptodate();
1329 bool button_down =
false;
1330 if (!q->_all_enter_events) {
1336 button_down = q->_state & (GDK_BUTTON1_MASK |
1342 q->_left_grabbed_item =
false;
1347 q->_current_canvas_item_new =
nullptr;
1350 if (last_mouse && canvasitem_ctx->root()->is_visible()) {
1351 q->_current_canvas_item_new = find_item_at(*last_mouse);
1359 if (q->_current_canvas_item_new == q->_current_canvas_item && !q->_left_grabbed_item) {
1365 bool retval =
false;
1366 if (q->_current_canvas_item_new != q->_current_canvas_item &&
1367 q->_current_canvas_item &&
1368 !q->_left_grabbed_item)
1370 auto event = LeaveEvent();
1371 event.modifiers = q->_state;
1372 retval = emit_event(event);
1375 if (!q->_all_enter_events) {
1377 if (q->_current_canvas_item_new != q->_current_canvas_item && button_down) {
1378 q->_left_grabbed_item =
true;
1384 q->_left_grabbed_item =
false;
1385 q->_current_canvas_item = q->_current_canvas_item_new;
1387 if (q->_current_canvas_item) {
1388 auto event = EnterEvent();
1389 event.modifiers = q->_state;
1390 event.pos = *last_mouse;
1391 retval = emit_event(event);
1412bool CanvasPrivate::emit_event(CanvasEvent &event)
1414 ensure_geometry_uptodate();
1417 if (q->_grabbed_canvas_item && !(event.type() & q->_grabbed_event_mask)) {
1430 p = p * q->_affine.inverse() * canvasitem_ctx->affine();
1435 [&] (EnterEvent &event) { conv(event.pos); },
1436 [&] (MotionEvent &event) { conv(event.pos); },
1437 [&] (ButtonEvent &event) { conv(event.pos, &event.orig_pos); },
1438 [&] (KeyEvent &event) {
1440 event.orig_pos.emplace();
1441 conv(*event.pos, &*event.orig_pos);
1444 [&] (CanvasEvent &event) {}
1449 [&] (ButtonPressEvent &event) {
1450 if (event.button == 1) {
1451 q->_is_dragging = true;
1454 [&] (ButtonReleaseEvent &event) { q->_is_dragging =
false; },
1455 [&] (CanvasEvent &event) {}
1458 if (q->_current_canvas_item) {
1460 auto item = q->_current_canvas_item;
1462 if (q->_grabbed_canvas_item && !q->_current_canvas_item->is_descendant_of(q->_grabbed_canvas_item)) {
1463 item = q->_grabbed_canvas_item;
1467 item = pre_scroll_grabbed_item;
1472 if (
item->handle_event(event))
return true;
1480void CanvasPrivate::ensure_geometry_uptodate()
1482 if (q->_need_update && !q->_drawing->snapshotted() && !canvasitem_ctx->snapshotted()) {
1483 FrameCheck::Event fc;
1484 if (prefs.debug_framecheck) fc = FrameCheck::Event(
"update", 1);
1485 q->_need_update =
false;
1486 canvasitem_ctx->root()->update(
false);
1512 return point +
_pos;
1538 default:
return false;
1550 return d->last_mouse;
1555 return d->canvasitem_ctx->affine();
1558void CanvasPrivate::queue_draw_area(
const Geom::IntRect &rect)
1576 d->schedule_redraw();
1577 if (
d->prefs.debug_show_unclean) queue_draw();
1593 constexpr int min_coord = -(1 << 30);
1594 constexpr int max_coord = (1 << 30) - 1;
1601 if (x0 >= x1 || y0 >= y1) {
1605 if (
d->redraw_active &&
d->invalidated->empty()) {
1606 d->abort_flags.store((
int)AbortFlags::Soft, std::memory_order_relaxed);
1607 if (
d->prefs.debug_logging) std::cout <<
"Soft exit request" << std::endl;
1612 d->schedule_redraw();
1613 if (
d->prefs.debug_show_unclean) queue_draw();
1626 (
int)std::ceil (
std::clamp(x1, min_int, max_int)),
1627 (
int)std::ceil (
std::clamp(y1, min_int, max_int))
1645 d->schedule_redraw();
1659 d->schedule_redraw();
1674 d->schedule_redraw();
1683 if (
d->desk == rgba)
return;
1684 bool invalidated =
d->background_in_stores_enabled;
1686 invalidated |=
d->background_in_stores_enabled =
d->background_in_stores_required();
1687 if (get_realized() && invalidated)
redraw_all();
1696 if (
d->border == rgba)
return;
1706 if (
d->page == rgba)
return;
1707 bool invalidated =
d->background_in_stores_enabled;
1709 invalidated |=
d->background_in_stores_enabled =
d->background_in_stores_required();
1710 if (get_realized() && invalidated)
redraw_all();
1718 d->schedule_redraw();
1739 d->schedule_redraw();
1756 if (
clip !=
d->clip_to_page) {
1757 d->clip_to_page =
clip;
1758 d->schedule_redraw();
1783 if (
item ==
d->pre_scroll_grabbed_item) {
1784 d->pre_scroll_grabbed_item =
nullptr;
1788std::optional<Geom::PathVector> CanvasPrivate::calc_page_clip()
const
1790 if (!clip_to_page) {
1795 for (
auto &rect : pi.pages) {
1801bool CanvasPrivate::is_point_on_page(
const Geom::Point &point)
const
1803 for (
auto &rect : pi.pages) {
1844 set_cursor(
"pointer");
1850 set_cursor(
"ns-resize");
1856 set_cursor(
"ew-resize");
1862 std::cerr <<
"Canvas::update_cursor: Unknown hover direction!" << std::endl;
1868 parent_type::size_allocate_vfunc(
width,
height, baseline);
1873 d->graphics->invalidated_glstate();
1877 d->schedule_redraw();
1880 if (
_desktop && new_dimensions !=
d->old_dimensions) {
1885 if (prefs->getBool(
"/options/stickyzoom/value",
false)) {
1887 auto const old_minextent =
min(
d->old_dimensions);
1888 auto const new_minextent =
min(new_dimensions);
1889 if (old_minextent != 0) {
1890 zoom *= (double)new_minextent / old_minextent;
1897 d->old_dimensions = new_dimensions;
1902 Glib::RefPtr<Gdk::GLContext>
result;
1905 result =
dynamic_cast<Gtk::Window &
>(*get_root()).get_surface()->create_gl_context();
1906 }
catch (
const Gdk::GLError &e) {
1907 std::cerr <<
"Failed to create OpenGL context: " << e.what() << std::endl;
1911 result->set_allowed_apis(Gdk::GLApi::GL);
1915 }
catch (
const Glib::Error &e) {
1916 std::cerr <<
"Failed to realize OpenGL context: " << e.what() << std::endl;
1925 framecheck_whole_function(
d)
1928 std::cerr <<
"Canvas::paint_widget: Called while not active!" << std::endl;
1934 if constexpr (
false)
d->canvasitem_ctx->root()->canvas_item_print_tree();
1940 std::cerr <<
"Canvas::paint_widget: Called while active but uninitialised!" << std::endl;
1945 if (!
d->redraw_active) {
1954 args.
mouse =
d->last_mouse;
1965 if (
d->prefs.debug_animate) {
1966 auto t = g_get_monotonic_time() / 1700000.0;
1979auto coarsen(
const Cairo::RefPtr<Cairo::Region> ®ion,
int min_size,
int glue_size,
double min_fullness)
1988 std::multiset<Geom::IntRect, Compare>
rects;
1989 int nrects = region->get_num_rectangles();
1990 for (
int i = 0; i < nrects; i++) {
1995 std::vector<Geom::IntRect> processed;
1996 processed.reserve(nrects);
1999 std::vector<
decltype(
rects)::iterator> remove_rects;
2000 std::vector<int> remove_processed;
2003 while (!
rects.empty() &&
rects.begin()->minExtent() < min_size) {
2005 auto rect = *
rects.begin();
2009 int effective_glue_size = glue_size;
2013 auto glue_zone = rect;
2014 glue_zone.
expandBy(effective_glue_size);
2017 auto newrect = rect;
2018 int absorbed_area = 0;
2020 remove_rects.clear();
2021 for (
auto it =
rects.begin(); it !=
rects.end(); ++it) {
2022 if (glue_zone.contains(*it)) {
2024 absorbed_area += it->area();
2025 remove_rects.emplace_back(it);
2029 remove_processed.clear();
2030 for (
int i = 0; i < processed.size(); i++) {
2031 auto &r = processed[i];
2032 if (glue_zone.contains(r)) {
2033 newrect.unionWith(r);
2034 absorbed_area += r.area();
2035 remove_processed.emplace_back(i);
2040 double fullness = (double)(rect.
area() + absorbed_area) / newrect.area();
2041 if (fullness < min_fullness) {
2042 effective_glue_size /= 2;
2049 for (
auto &it : remove_rects) {
2053 for (
int j = (
int)remove_processed.size() - 1; j >= 0; j--) {
2054 int i = remove_processed[j];
2055 processed[i] = processed.back();
2056 processed.pop_back();
2060 bool finished = absorbed_area == 0 || rect.
minExtent() >= min_size;
2066 effective_glue_size = glue_size;
2070 processed.emplace_back(rect);
2074 for (
auto &rect :
rects) {
2075 processed.emplace_back(rect);
2083 int bw = rect.
width();
2100void CanvasPrivate::init_tiler()
2103 rd.start_time = g_get_monotonic_time();
2105 rd.vis_store = (
rd.visible &
rd.store.rect).regularized();
2107 if (!init_redraw()) {
2113 rd.timeoutflag =
false;
2115 rd.numactive =
rd.numthreads;
2117 for (
int i = 0; i <
rd.numthreads - 1; i++) {
2118 boost::asio::post(*pool, [=] { render_tile(i); });
2121 render_tile(
rd.numthreads - 1);
2124bool CanvasPrivate::init_redraw()
2126 assert(
rd.rects.empty());
2130 if (
rd.vis_store &&
rd.decoupled_mode) {
2133 process_redraw(*
rd.vis_store,
unioned(updater->clean_region->copy(),
rd.snapshot_drawn));
2143 process_redraw(*
rd.grabbed, updater->clean_region,
false,
false);
2154 process_redraw(*
rd.vis_store, updater->get_next_clean_region());
2165 auto prerender_store = (prerender &
rd.store.rect).regularized();
2166 if (prerender_store) {
2167 process_redraw(*prerender_store, updater->clean_region);
2190 assert(
rd.store.rect.contains(
rd.bounds));
2194 region->subtract(
rd.clean);
2198 std::min<int>(
rd.coarsener_min_size,
rd.tile_size / 2),
2199 std::min<int>(
rd.coarsener_glue_size,
rd.tile_size / 2),
2200 rd.coarsener_min_fullness);
2203 std::make_heap(
rd.rects.begin(),
rd.rects.end(),
rd.getcmp());
2206 double adjust = (double)
cairo_to_geom(region->get_extents()).maxExtent() /
rd.visible.maxExtent();
2208 rd.effective_tile_size =
rd.tile_size * adjust;
2212void CanvasPrivate::render_tile(
int debug_id)
2217 FrameCheck::Event fc;
2218 if (
rd.debug_framecheck) {
2219 fc_str =
"render_thread_" + std::to_string(debug_id + 1);
2220 fc = FrameCheck::Event(fc_str.c_str());
2225 if (
rd.rects.empty()) {
2236 auto const flags = abort_flags.load(std::memory_order_relaxed);
2237 bool const soft = flags & (int)AbortFlags::Soft;
2238 bool const hard = flags & (int)AbortFlags::Hard;
2239 if (hard || (
rd.phase == 3 && soft)) {
2244 std::pop_heap(
rd.rects.begin(),
rd.rects.end(),
rd.getcmp());
2245 auto rect =
rd.rects.back();
2246 rd.rects.pop_back();
2255 if (
rd.clean->contains_rectangle(
geom_to_cairo(rect)) == Cairo::Region::Overlap::IN) {
2261 rd.rects.emplace_back(rect);
2262 std::push_heap(
rd.rects.begin(),
rd.rects.end(),
rd.getcmp());
2266 if (
auto axis =
bisect(rect,
rd.effective_tile_size)) {
2267 int mid = rect[*axis].middle();
2268 auto lo = rect; lo[*axis].
setMax(mid); add_rect(lo);
2269 auto hi = rect; hi[*axis].
setMin(mid); add_rect(hi);
2275 if (
rd.preemptible) {
2276 if (rect.
width() <
rd.preempt) {
2287 updater->mark_clean(rect);
2297 if (
rd.interruptible) {
2298 auto now = g_get_monotonic_time();
2299 auto elapsed =
now -
rd.start_time;
2300 if (elapsed >
rd.render_time_limit * 1000) {
2302 rd.timeoutflag =
true;
2308 if (
rd.debug_framecheck &&
rd.timeoutflag) {
2313 bool const done =
rd.numactive == 0;
2323bool CanvasPrivate::end_redraw()
2328 return init_redraw();
2333 rd.start_time = g_get_monotonic_time();
2334 return init_redraw();
2337 if (!updater->report_finished()) {
2340 return init_redraw();
2354 assert(
rd.store.rect.contains(rect));
2356 auto paint = [&,
this] (
bool need_background,
bool outline_pass) {
2358 auto surface = graphics->request_tile_surface(rect,
true);
2360 sync.runInMain([&] {
2361 if (prefs.debug_logging) std::cout <<
"Blocked - buffer mapping" << std::endl;
2362 if (q->get_opengl_enabled()) q->make_current();
2363 surface = graphics->request_tile_surface(rect,
false);
2369 paint_single_buffer(
surface, rect, need_background, outline_pass);
2371 }
catch (std::bad_alloc
const &) {
2374 sync.runInMain([&] {
2375 std::cerr <<
"Rendering failure. You probably need to zoom out!" << std::endl;
2376 if (q->get_opengl_enabled()) q->make_current();
2377 graphics->junk_tile_surface(std::move(
surface));
2378 surface = graphics->request_tile_surface(rect,
false);
2388 tile.fragment.affine =
rd.store.affine;
2389 tile.fragment.rect = rect;
2390 tile.surface = paint(
rd.background_in_stores_required,
false);
2391 if (outlines_enabled) {
2392 tile.outline_surface = paint(
false,
true);
2396 if (
rd.redraw_delay) g_usleep(*
rd.redraw_delay);
2400 auto g = std::lock_guard(
rd.tiles_mutex);
2401 rd.tiles.emplace_back(std::move(tile));
2405void CanvasPrivate::paint_single_buffer(Cairo::RefPtr<Cairo::ImageSurface>
const &
surface,
Geom::IntRect const &rect,
bool need_background,
bool outline_pass)
2412 if (need_background) {
2415 cr->set_operator(Cairo::Context::Operator::CLEAR);
2421 auto buf = CanvasItemBuffer{ rect, scale_factor, cr, outline_pass };
2422 canvasitem_ctx->root()->render(
buf);
2425 if (
rd.cms_transform) {
2427 auto px =
surface->get_data();
2429 for (
int i = 0; i <
surface->get_height(); i++) {
2430 auto row = px + i *
stride;
2437 if (
rd.debug_show_redraw) {
2438 cr->set_source_rgba((rand() % 256) / 255.0, (rand() % 256) / 255.0, (rand() % 256) / 255.0, 0.2);
2439 cr->set_operator(Cairo::Context::Operator::OVER);
2444void CanvasPrivate::paint_error_buffer(Cairo::RefPtr<Cairo::ImageSurface>
const &
surface)
2449 cr->set_source_rgb(0, 0, 0);
double coarsener_min_fullness
Cairo::RefPtr< Cairo::ImageSurface > surface
std::vector< Geom::IntRect > rects
bool background_in_stores_required
std::vector< Tile > tiles
Cairo::RefPtr< Cairo::Region > snapshot_drawn
std::shared_ptr< CMSTransform const > cms_transform
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).
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.
static CMSSystem * get()
Access the singleton CMSSystem object.
static void do_transform(cmsHTRANSFORM transform, unsigned char *inBuf, unsigned char *outBuf, unsigned size)
void set_sticky(bool sticky)
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 const zoom, bool keep_point=true)
Zoom to the given absolute zoom level.
Geom::Point getDimensions() const
TODO: insert short description here.
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
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 roundedOutwards(Geom::OptRect const &rect)
auto floor(Geom::Rect const &rect)
auto expandedBy(Geom::IntRect rect, int amount)
Point midpoint(Point a, Point b)
SBasisN< n > cos(LinearN< n > bo, int k)
MultiDegree< n > max(MultiDegree< n > const &p, MultiDegree< n > const &q)
Returns the maximal degree appearing in the two arguments for each variables.
GenericRect< IntCoord > IntRect
static double area(Geom::Point a, Geom::Point b, Geom::Point c)
GenericOptRect< IntCoord > OptIntRect
Piecewise< SBasis > min(SBasis const &f, SBasis const &g)
Return the more negative of the two functions pointwise.
SBasisN< n > sin(LinearN< n > bo, int k)
Point abs(Point const &b)
int clamp(int const val)
Clamps an integer value to a value between 0 and 255.
Gtk::GestureClick & add_click(Gtk::Widget &widget, ClickSlot on_pressed, ClickSlot on_released, Button const button, Gtk::PropagationPhase const phase, When const when)
Create a click gesture for the given widget; by default claim sequence.
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)
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)
pair< double, double > Point
Cairo::RectangleInt geom_to_cairo(const Geom::IntRect &rect)
Geom::IntRect cairo_to_geom(const Cairo::RectangleInt &rect)
auto const_wrap(T const *p, bool take_copy=false)