summaryrefslogtreecommitdiffstats
path: root/src/ui/widget/canvas/graphics.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/ui/widget/canvas/graphics.cpp166
1 files changed, 166 insertions, 0 deletions
diff --git a/src/ui/widget/canvas/graphics.cpp b/src/ui/widget/canvas/graphics.cpp
new file mode 100644
index 0000000..28972e2
--- /dev/null
+++ b/src/ui/widget/canvas/graphics.cpp
@@ -0,0 +1,166 @@
+// 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<Cairo::Pattern> 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<Cairo::Context> 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<Geom::IntRect, Geom::IntRect> 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<Cairo::Context> 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