First Commit
This commit is contained in:
commit
ae0fab9400
424 files changed
+71006
No files matched your search
@@ -0,0 +1,273 @@
|
||||
// 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 = uiprivNew(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();
|
||||
uiprivFree(p);
|
||||
}
|
||||
|
||||
void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)
|
||||
{
|
||||
D2D1_POINT_2F pt;
|
||||
|
||||
if (p->sink == NULL)
|
||||
uiprivUserBug("You cannot modify a uiDrawPath that has been ended. (path: %p)", p);
|
||||
|
||||
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.
|
||||
// TODO https://github.com/Microsoft/WinObjC/blob/develop/Frameworks/CoreGraphics/CGPath.mm#L313
|
||||
|
||||
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;
|
||||
|
||||
if (p->sink == NULL)
|
||||
uiprivUserBug("You cannot modify a uiDrawPath that has been ended. (path: %p)", p);
|
||||
|
||||
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;
|
||||
|
||||
if (p->sink == NULL)
|
||||
uiprivUserBug("You cannot modify a uiDrawPath that has been ended. (path: %p)", p);
|
||||
|
||||
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;
|
||||
|
||||
if (p->sink == NULL)
|
||||
uiprivUserBug("You cannot modify a uiDrawPath that has been ended. (path: %p)", p);
|
||||
|
||||
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;
|
||||
|
||||
if (p->sink == NULL)
|
||||
uiprivUserBug("You cannot modify a uiDrawPath that has been ended. (path: %p)", p);
|
||||
|
||||
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)
|
||||
{
|
||||
if (p->sink == NULL)
|
||||
uiprivUserBug("You cannot modify a uiDrawPath that has been ended. (path: %p)", p);
|
||||
|
||||
p->sink->EndFigure(D2D1_FIGURE_END_CLOSED);
|
||||
p->inFigure = FALSE;
|
||||
}
|
||||
|
||||
void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)
|
||||
{
|
||||
if (p->sink == NULL)
|
||||
uiprivUserBug("You cannot modify a uiDrawPath that has been ended. (path: %p)", p);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
int uiDrawPathEnded(uiDrawPath *p)
|
||||
{
|
||||
return p->sink == NULL ? 1 : 0;
|
||||
}
|
||||
|
||||
ID2D1PathGeometry *pathGeometry(uiDrawPath *p)
|
||||
{
|
||||
if (p->sink != NULL)
|
||||
uiprivUserBug("You cannot draw with a uiDrawPath that was not ended. (path: %p)", p);
|
||||
return p->path;
|
||||
}
|
||||
Reference in new issue
Block a user