+#include "Canvas.h"
+
+#include <algorithm>
+#include <cassert>
+#include <stdexcept>
+#include <string>
+
+using std::runtime_error;
+
+
+namespace gworm {
+
+Canvas::Canvas(SDL_Window *win, int index, Uint32 flags)
+: canv(SDL_CreateRenderer(win, index, flags)) {
+ if (!canv) {
+ throw runtime_error(std::string("create canvas: ") + SDL_GetError());
+ }
+}
+
+Canvas::~Canvas() {
+ if (canv) SDL_DestroyRenderer(canv);
+}
+
+Canvas::Canvas(Canvas &&other)
+: canv(other.canv) {
+ other.canv = nullptr;
+}
+
+Canvas &Canvas::operator =(Canvas &&other) {
+ std::swap(canv, other.canv);
+ return *this;
+}
+
+
+void Canvas::Present() {
+ SDL_RenderPresent(canv);
+}
+
+
+Vector<int> Canvas::Size() const {
+ assert(canv);
+ Vector<int> size;
+ SDL_GetRendererOutputSize(canv, &size.x, &size.y);
+ return size;
+}
+
+
+void Canvas::SetColor(Color c) {
+ SDL_SetRenderDrawColor(canv, c.r, c.g, c.b, c.a);
+}
+
+
+void Canvas::Fill() {
+ SDL_RenderClear(canv);
+}
+
+void Canvas::Outline() {
+ SDL_RenderDrawRect(canv, nullptr);
+}
+
+
+void Canvas::Line(Vector<int> from, Vector<int> to) {
+ SDL_RenderDrawLine(canv, from.x, from.y, to.x, to.y);
+}
+
+void Canvas::FillRect(Vector<int> pos, Vector<int> size) {
+ SDL_Rect destRect;
+ destRect.x = pos.x;
+ destRect.y = pos.y;
+ destRect.w = size.x;
+ destRect.h = size.y;
+ SDL_RenderFillRect(canv, &destRect);
+}
+
+void Canvas::OutlineRect(Vector<int> pos, Vector<int> size) {
+ SDL_Rect destRect;
+ destRect.x = pos.x;
+ destRect.y = pos.y;
+ destRect.w = size.x;
+ destRect.h = size.y;
+ SDL_RenderDrawRect(canv, &destRect);
+}
+
+
+void Canvas::Dot(Vector<int> pos) {
+ SDL_RenderDrawPoint(canv, pos.x, pos.y);
+}
+
+void Canvas::Cross(Vector<int> pos, int extent) {
+ Line(
+ Vector<int>(pos.x - extent, pos.y),
+ Vector<int>(pos.x + extent, pos.y));
+ Line(
+ Vector<int>(pos.x, pos.y - extent),
+ Vector<int>(pos.x, pos.y + extent));
+}
+
+void Canvas::Arrow(Vector<int> from, Vector<int> to) {
+ Line(from, to);
+ Vector<float> delta(to - from);
+ delta = delta / Length(delta);
+
+ Line(to, to + Vector<int>(Rotate90(delta) * 5.0f - (delta * 5.0f)));
+ Line(to, to + Vector<int>(Rotate270(delta) * 5.0f - (delta * 5.0f)));
+}
+
+void Canvas::Triangle(Vector<int> v1, Vector<int> v2, Vector<int> v3) {
+ SDL_Point points[4] = { v1, v2, v3, v1 };
+ SDL_RenderDrawLines(canv, points, 4);
+}
+
+void Canvas::Quad(Vector<int> v1, Vector<int> v2, Vector<int> v3, Vector<int> v4) {
+ SDL_Point points[5] = { v1, v2, v3, v4, v1 };
+ SDL_RenderDrawLines(canv, points, 5);
+}
+
+
+namespace {
+
+template<class Scalar>
+void GridImpl(
+ Canvas &canv,
+ Vector<int> pos,
+ Vector<int> size,
+ Vector<Scalar> step) {
+ Vector<int> from(pos);
+ Vector<int> to(pos + size);
+ Vector<int> clip(canv.Size());
+
+ if (from.x > clip.x || from.y > clip.y || to.x < 0 || to.y < 0) {
+ return;
+ }
+ if (step.x <= 1 || step.y <= 1) {
+ canv.FillRect(pos, size);
+ return;
+ }
+
+ if (from.x < -step.x) {
+ int skip = from.x / -step.x;
+ from.x += skip * step.x;
+ }
+ if (from.y < -step.y) {
+ int skip = from.y / -step.y;
+ from.y += skip * step.y;
+ }
+ if (to.x > clip.x + step.x) {
+ int skip = (to.x - clip.x) / step.x;
+ to.x -= skip * step.x;
+ }
+ if (to.y > clip.y + step.y) {
+ int skip = (to.y - clip.y) / step.y;
+ to.y -= skip * step.y;
+ }
+
+ int width = to.x - from.x;
+ int height = to.y - from.y;
+
+ for (Vector<Scalar> pos(from); pos.x <= to.x; pos.x += step.x) {
+ canv.Line(pos, Vector<int>(pos.x, pos.y + height));
+ }
+ for (Vector<Scalar> pos(from); pos.y <= to.y; pos.y += step.y) {
+ canv.Line(pos, Vector<int>(pos.x + width, pos.y));
+ }
+}
+
+template<class Scalar>
+void Grid2Impl(
+ Canvas &canv,
+ Vector<int> pos,
+ Vector<int> size,
+ Vector<Scalar> step,
+ Vector<int> n,
+ Color c1,
+ Color c2) {
+ Vector<int> from(pos);
+ Vector<int> to(pos + size);
+ Vector<int> clip(canv.Size());
+
+ if (from.x > clip.x || from.y > clip.y || to.x < 0 || to.y < 0) {
+ return;
+ }
+ if (step.x <= 1 || step.y <= 1) {
+ canv.SetColor(c1);
+ canv.FillRect(pos, size);
+ canv.SetColor(c2);
+ GridImpl(canv, pos, size, step * Vector<Scalar>(n));
+ return;
+ }
+
+ Vector<int> i(0, 0);
+
+ if (from.x < -step.x) {
+ int skip = from.x / -step.x;
+ from.x += skip * step.x;
+ i.x += skip;
+ }
+ if (from.y < -step.y) {
+ int skip = from.y / -step.y;
+ from.y += skip * step.y;
+ i.y += skip;
+ }
+ if (to.x > clip.x + step.x) {
+ int skip = (to.x - clip.x) / step.x;
+ to.x -= skip * step.x;
+ }
+ if (to.y > clip.y + step.y) {
+ int skip = (to.y - clip.y) / step.y;
+ to.y -= skip * step.y;
+ }
+
+ int width = to.x - from.x;
+ int height = to.y - from.y;
+
+ for (Vector<Scalar> pos(from); pos.x <= to.x; pos.x += step.x) {
+ canv.SetColor((i.x++ % n.x) ? c1 : c2);
+ canv.Line(pos, Vector<int>(pos.x, pos.y + height));
+ }
+ for (Vector<Scalar> pos(from); pos.y <= to.y; pos.y += step.y) {
+ canv.SetColor((i.y++ % n.y) ? c1 : c2);
+ canv.Line(pos, Vector<int>(pos.x + width, pos.y));
+ }
+}
+
+}
+
+void Canvas::Grid(Vector<int> pos, Vector<int> size, Vector<int> step) {
+ GridImpl(*this, pos, size, step);
+}
+
+void Canvas::Grid(Vector<int> pos, Vector<int> size, Vector<float> step) {
+ GridImpl(*this, pos, size, step);
+}
+
+void Canvas::Grid2(
+ Vector<int> pos, Vector<int> size, Vector<int> step,
+ Vector<int> n, Color c1, Color c2) {
+ Grid2Impl(*this, pos, size, step, n, c1, c2);
+}
+
+void Canvas::Grid2(
+ Vector<int> pos, Vector<int> size, Vector<float> step,
+ Vector<int> n, Color c1, Color c2) {
+ Grid2Impl(*this, pos, size, step, n, c1, c2);
+}
+
+}