// SPDX-License-Identifier: GPL-2.0-or-later #include <2geom/parallelogram.h> #include "ui/util.h" #include "helper/geom.h" #include "graphics.h" #include "util.h" namespace Inkscape { namespace UI { namespace Widget { namespace { // Convert an rgba into a pattern, turning transparency into checkerboard-ness. Cairo::RefPtr rgba_to_pattern(uint32_t rgba) { if (SP_RGBA32_A_U(rgba) == 255) { return Cairo::SolidPattern::create_rgb(SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba)); } else { int constexpr w = 6; int constexpr h = 6; auto dark = checkerboard_darken(rgba); auto surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, 2 * w, 2 * h); auto cr = Cairo::Context::create(surface); cr->set_operator(Cairo::OPERATOR_SOURCE); cr->set_source_rgb(SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba)); cr->paint(); cr->set_source_rgb(dark[0], dark[1], dark[2]); cr->rectangle(0, 0, w, h); cr->rectangle(w, h, w, h); cr->fill(); auto pattern = Cairo::SurfacePattern::create(surface); pattern->set_extend(Cairo::EXTEND_REPEAT); pattern->set_filter(Cairo::FILTER_NEAREST); return pattern; } } } // namespace // Paint the background and pages using Cairo into the given fragment. void Graphics::paint_background(Fragment const &fragment, PageInfo const &pi, uint32_t page, uint32_t desk, Cairo::RefPtr const &cr) { cr->save(); cr->set_operator(Cairo::OPERATOR_SOURCE); cr->rectangle(0, 0, fragment.rect.width(), fragment.rect.height()); cr->clip(); if (desk == page || check_single_page(fragment, pi)) { // Desk and page are the same, or a single page fills the whole screen; just clear the fragment to page. cr->set_source(rgba_to_pattern(page)); cr->paint(); } else { // Paint the background to the complement of the pages. (Slightly overpaints when pages overlap.) cr->save(); cr->set_source(rgba_to_pattern(desk)); cr->set_fill_rule(Cairo::FILL_RULE_EVEN_ODD); cr->rectangle(0, 0, fragment.rect.width(), fragment.rect.height()); cr->translate(-fragment.rect.left(), -fragment.rect.top()); cr->transform(geom_to_cairo(fragment.affine)); for (auto &rect : pi.pages) { cr->rectangle(rect.left(), rect.top(), rect.width(), rect.height()); } cr->fill(); cr->restore(); // Paint the pages. cr->save(); cr->set_source(rgba_to_pattern(page)); cr->translate(-fragment.rect.left(), -fragment.rect.top()); cr->transform(geom_to_cairo(fragment.affine)); for (auto &rect : pi.pages) { cr->rectangle(rect.left(), rect.top(), rect.width(), rect.height()); } cr->fill(); cr->restore(); } cr->restore(); } std::pair Graphics::calc_splitview_cliprects(Geom::IntPoint const &size, Geom::Point const &split_frac, SplitDirection split_direction) { auto window = Geom::IntRect({0, 0}, size); auto content = window; auto outline = window; auto split = [&] (Geom::Dim2 dim, Geom::IntRect &lo, Geom::IntRect &hi) { int s = std::round(split_frac[dim] * size[dim]); lo[dim].setMax(s); hi[dim].setMin(s); }; switch (split_direction) { case Inkscape::SplitDirection::NORTH: split(Geom::Y, content, outline); break; case Inkscape::SplitDirection::EAST: split(Geom::X, outline, content); break; case Inkscape::SplitDirection::SOUTH: split(Geom::Y, outline, content); break; case Inkscape::SplitDirection::WEST: split(Geom::X, content, outline); break; default: assert(false); break; } return std::make_pair(content, outline); } void Graphics::paint_splitview_controller(Geom::IntPoint const &size, Geom::Point const &split_frac, SplitDirection split_direction, SplitDirection hover_direction, Cairo::RefPtr const &cr) { auto split_position = (split_frac * size).round(); // Add dividing line. cr->set_source_rgb(0.0, 0.0, 0.0); cr->set_line_width(1.0); if (split_direction == Inkscape::SplitDirection::EAST || split_direction == Inkscape::SplitDirection::WEST) { cr->move_to(split_position.x() + 0.5, 0.0 ); cr->line_to(split_position.x() + 0.5, size.y()); cr->stroke(); } else { cr->move_to(0.0 , split_position.y() + 0.5); cr->line_to(size.x(), split_position.y() + 0.5); cr->stroke(); } // Add controller image. double a = hover_direction == Inkscape::SplitDirection::NONE ? 0.5 : 1.0; cr->set_source_rgba(0.2, 0.2, 0.2, a); cr->arc(split_position.x(), split_position.y(), 20, 0, 2 * M_PI); cr->fill(); for (int i = 0; i < 4; i++) { // The four direction triangles. cr->save(); // Position triangle. cr->translate(split_position.x(), split_position.y()); cr->rotate((i + 2) * M_PI / 2); // Draw triangle. cr->move_to(-5, 8); cr->line_to( 0, 18); cr->line_to( 5, 8); cr->close_path(); double b = (int)hover_direction == (i + 1) ? 0.9 : 0.7; cr->set_source_rgba(b, b, b, a); cr->fill(); cr->restore(); } } bool Graphics::check_single_page(Fragment const &view, PageInfo const &pi) { auto pl = Geom::Parallelogram(view.rect) * view.affine.inverse(); return std::any_of(pi.pages.begin(), pi.pages.end(), [&] (auto &rect) { return Geom::Parallelogram(rect).contains(pl); }); } } // namespace Widget } // namespace UI } // namespace Inkscape