diff options
Diffstat (limited to 'src/libui_sdl/libui/windows/drawpath.cpp')
-rw-r--r-- | src/libui_sdl/libui/windows/drawpath.cpp | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/src/libui_sdl/libui/windows/drawpath.cpp b/src/libui_sdl/libui/windows/drawpath.cpp new file mode 100644 index 0000000..49855be --- /dev/null +++ b/src/libui_sdl/libui/windows/drawpath.cpp @@ -0,0 +1,246 @@ +// 7 september 2015 +#include "uipriv_windows.hpp" +#include "draw.hpp" + +// TODO +// - write a test for transform followed by clip and clip followed by transform to make sure they work the same as on gtk+ and cocoa +// - write a test for nested transforms for gtk+ + +struct uiDrawPath { + ID2D1PathGeometry *path; + ID2D1GeometrySink *sink; + BOOL inFigure; +}; + +uiDrawPath *uiDrawNewPath(uiDrawFillMode fillmode) +{ + uiDrawPath *p; + HRESULT hr; + + p = uiNew(uiDrawPath); + hr = d2dfactory->CreatePathGeometry(&(p->path)); + if (hr != S_OK) + logHRESULT(L"error creating path", hr); + hr = p->path->Open(&(p->sink)); + if (hr != S_OK) + logHRESULT(L"error opening path", hr); + switch (fillmode) { + case uiDrawFillModeWinding: + p->sink->SetFillMode(D2D1_FILL_MODE_WINDING); + break; + case uiDrawFillModeAlternate: + p->sink->SetFillMode(D2D1_FILL_MODE_ALTERNATE); + break; + } + return p; +} + +void uiDrawFreePath(uiDrawPath *p) +{ + if (p->inFigure) + p->sink->EndFigure(D2D1_FIGURE_END_OPEN); + if (p->sink != NULL) + // TODO close sink first? + p->sink->Release(); + p->path->Release(); + uiFree(p); +} + +void uiDrawPathNewFigure(uiDrawPath *p, double x, double y) +{ + D2D1_POINT_2F pt; + + if (p->inFigure) + p->sink->EndFigure(D2D1_FIGURE_END_OPEN); + pt.x = x; + pt.y = y; + p->sink->BeginFigure(pt, D2D1_FIGURE_BEGIN_FILLED); + p->inFigure = TRUE; +} + +// Direct2D arcs require a little explanation. +// An arc in Direct2D is defined by the chord between the endpoints. +// There are four possible arcs with the same two endpoints that you can draw this way. +// See https://www.youtube.com/watch?v=ATS0ANW1UxQ for a demonstration. +// There is a property rotationAngle which deals with the rotation /of the entire ellipse that forms an ellpitical arc/ - it's effectively a transformation on the arc. +// That is to say, it's NOT THE SWEEP. +// The sweep is defined by the start and end points and whether the arc is "large". +// As a result, this design does not allow for full circles or ellipses with a single arc; they have to be simulated with two. + +struct arc { + double xCenter; + double yCenter; + double radius; + double startAngle; + double sweep; + int negative; +}; + +// this is used for the comparison below +// if it falls apart it can be changed later +#define aerMax 6 * DBL_EPSILON + +static void drawArc(uiDrawPath *p, struct arc *a, void (*startFunction)(uiDrawPath *, double, double)) +{ + double sinx, cosx; + double startX, startY; + double endX, endY; + D2D1_ARC_SEGMENT as; + BOOL fullCircle; + double absSweep; + + // as above, we can't do a full circle with one arc + // simulate it with two half-circles + // of course, we have a dragon: equality on floating-point values! + // I've chosen to do the AlmostEqualRelative() technique in https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + fullCircle = FALSE; + // use the absolute value to tackle both ≥2π and ≤-2π at the same time + absSweep = fabs(a->sweep); + if (absSweep > (2 * uiPi)) // this part is easy + fullCircle = TRUE; + else { + double aerDiff; + + aerDiff = fabs(absSweep - (2 * uiPi)); + // if we got here then we know a->sweep is larger (or the same!) + fullCircle = aerDiff <= absSweep * aerMax; + } + // TODO make sure this works right for the negative direction + if (fullCircle) { + a->sweep = uiPi; + drawArc(p, a, startFunction); + a->startAngle += uiPi; + drawArc(p, a, NULL); + return; + } + + // first, figure out the arc's endpoints + // unfortunately D2D1SinCos() is only defined on Windows 8 and newer + // the MSDN page doesn't say this, but says it requires d2d1_1.h, which is listed as only supported on Windows 8 and newer elsewhere on MSDN + // so we must use sin() and cos() and hope it's right... + sinx = sin(a->startAngle); + cosx = cos(a->startAngle); + startX = a->xCenter + a->radius * cosx; + startY = a->yCenter + a->radius * sinx; + sinx = sin(a->startAngle + a->sweep); + cosx = cos(a->startAngle + a->sweep); + endX = a->xCenter + a->radius * cosx; + endY = a->yCenter + a->radius * sinx; + + // now do the initial step to get the current point to be the start point + // this is either creating a new figure, drawing a line, or (in the case of our full circle code above) doing nothing + if (startFunction != NULL) + (*startFunction)(p, startX, startY); + + // now we can draw the arc + as.point.x = endX; + as.point.y = endY; + as.size.width = a->radius; + as.size.height = a->radius; + as.rotationAngle = 0; // as above, not relevant for circles + if (a->negative) + as.sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE; + else + as.sweepDirection = D2D1_SWEEP_DIRECTION_CLOCKWISE; + // TODO explain the outer if + if (!a->negative) + if (a->sweep > uiPi) + as.arcSize = D2D1_ARC_SIZE_LARGE; + else + as.arcSize = D2D1_ARC_SIZE_SMALL; + else + // TODO especially this part + if (a->sweep > uiPi) + as.arcSize = D2D1_ARC_SIZE_SMALL; + else + as.arcSize = D2D1_ARC_SIZE_LARGE; + p->sink->AddArc(&as); +} + +void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative) +{ + struct arc a; + + a.xCenter = xCenter; + a.yCenter = yCenter; + a.radius = radius; + a.startAngle = startAngle; + a.sweep = sweep; + a.negative = negative; + drawArc(p, &a, uiDrawPathNewFigure); +} + +void uiDrawPathLineTo(uiDrawPath *p, double x, double y) +{ + D2D1_POINT_2F pt; + + pt.x = x; + pt.y = y; + p->sink->AddLine(pt); +} + +void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative) +{ + struct arc a; + + a.xCenter = xCenter; + a.yCenter = yCenter; + a.radius = radius; + a.startAngle = startAngle; + a.sweep = sweep; + a.negative = negative; + drawArc(p, &a, uiDrawPathLineTo); +} + +void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY) +{ + D2D1_BEZIER_SEGMENT s; + + s.point1.x = c1x; + s.point1.y = c1y; + s.point2.x = c2x; + s.point2.y = c2y; + s.point3.x = endX; + s.point3.y = endY; + p->sink->AddBezier(&s); +} + +void uiDrawPathCloseFigure(uiDrawPath *p) +{ + p->sink->EndFigure(D2D1_FIGURE_END_CLOSED); + p->inFigure = FALSE; +} + +void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height) +{ + // this is the same algorithm used by cairo and Core Graphics, according to their documentations + uiDrawPathNewFigure(p, x, y); + uiDrawPathLineTo(p, x + width, y); + uiDrawPathLineTo(p, x + width, y + height); + uiDrawPathLineTo(p, x, y + height); + uiDrawPathCloseFigure(p); +} + +void uiDrawPathEnd(uiDrawPath *p) +{ + HRESULT hr; + + if (p->inFigure) { + p->sink->EndFigure(D2D1_FIGURE_END_OPEN); + // needed for uiDrawFreePath() + p->inFigure = FALSE; + } + hr = p->sink->Close(); + if (hr != S_OK) + logHRESULT(L"error closing path", hr); + p->sink->Release(); + // also needed for uiDrawFreePath() + p->sink = NULL; +} + +ID2D1PathGeometry *pathGeometry(uiDrawPath *p) +{ + if (p->sink != NULL) + userbug("You cannot draw with a uiDrawPath that was not ended. (path: %p)", p); + return p->path; +} |