aboutsummaryrefslogtreecommitdiff
path: root/src/libui_sdl/libui/darwin/draw.m
diff options
context:
space:
mode:
Diffstat (limited to 'src/libui_sdl/libui/darwin/draw.m')
-rw-r--r--src/libui_sdl/libui/darwin/draw.m454
1 files changed, 454 insertions, 0 deletions
diff --git a/src/libui_sdl/libui/darwin/draw.m b/src/libui_sdl/libui/darwin/draw.m
new file mode 100644
index 0000000..262ad3e
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/draw.m
@@ -0,0 +1,454 @@
+// 6 september 2015
+#import "uipriv_darwin.h"
+
+struct uiDrawPath {
+ CGMutablePathRef path;
+ uiDrawFillMode fillMode;
+ BOOL ended;
+};
+
+uiDrawPath *uiDrawNewPath(uiDrawFillMode mode)
+{
+ uiDrawPath *p;
+
+ p = uiNew(uiDrawPath);
+ p->path = CGPathCreateMutable();
+ p->fillMode = mode;
+ return p;
+}
+
+void uiDrawFreePath(uiDrawPath *p)
+{
+ CGPathRelease((CGPathRef) (p->path));
+ uiFree(p);
+}
+
+void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)
+{
+ if (p->ended)
+ userbug("You cannot call uiDrawPathNewFigure() on a uiDrawPath that has already been ended. (path; %p)", p);
+ CGPathMoveToPoint(p->path, NULL, x, y);
+}
+
+void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
+{
+ double sinStart, cosStart;
+ double startx, starty;
+
+ if (p->ended)
+ userbug("You cannot call uiDrawPathNewFigureWithArc() on a uiDrawPath that has already been ended. (path; %p)", p);
+ sinStart = sin(startAngle);
+ cosStart = cos(startAngle);
+ startx = xCenter + radius * cosStart;
+ starty = yCenter + radius * sinStart;
+ CGPathMoveToPoint(p->path, NULL, startx, starty);
+ uiDrawPathArcTo(p, xCenter, yCenter, radius, startAngle, sweep, negative);
+}
+
+void uiDrawPathLineTo(uiDrawPath *p, double x, double y)
+{
+ // TODO refine this to require being in a path
+ if (p->ended)
+ implbug("attempt to add line to ended path in uiDrawPathLineTo()");
+ CGPathAddLineToPoint(p->path, NULL, x, y);
+}
+
+void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
+{
+ bool cw;
+
+ // TODO likewise
+ if (p->ended)
+ implbug("attempt to add arc to ended path in uiDrawPathArcTo()");
+ if (sweep > 2 * uiPi)
+ sweep = 2 * uiPi;
+ cw = false;
+ if (negative)
+ cw = true;
+ CGPathAddArc(p->path, NULL,
+ xCenter, yCenter,
+ radius,
+ startAngle, startAngle + sweep,
+ cw);
+}
+
+void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)
+{
+ // TODO likewise
+ if (p->ended)
+ implbug("attempt to add bezier to ended path in uiDrawPathBezierTo()");
+ CGPathAddCurveToPoint(p->path, NULL,
+ c1x, c1y,
+ c2x, c2y,
+ endX, endY);
+}
+
+void uiDrawPathCloseFigure(uiDrawPath *p)
+{
+ // TODO likewise
+ if (p->ended)
+ implbug("attempt to close figure of ended path in uiDrawPathCloseFigure()");
+ CGPathCloseSubpath(p->path);
+}
+
+void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)
+{
+ if (p->ended)
+ userbug("You cannot call uiDrawPathAddRectangle() on a uiDrawPath that has already been ended. (path; %p)", p);
+ CGPathAddRect(p->path, NULL, CGRectMake(x, y, width, height));
+}
+
+void uiDrawPathEnd(uiDrawPath *p)
+{
+ p->ended = TRUE;
+}
+
+struct uiDrawContext {
+ CGContextRef c;
+ CGFloat height; // needed for text; see below
+};
+
+uiDrawContext *newContext(CGContextRef ctxt, CGFloat height)
+{
+ uiDrawContext *c;
+
+ c = uiNew(uiDrawContext);
+ c->c = ctxt;
+ c->height = height;
+ return c;
+}
+
+void freeContext(uiDrawContext *c)
+{
+ uiFree(c);
+}
+
+// a stroke is identical to a fill of a stroked path
+// we need to do this in order to stroke with a gradient; see http://stackoverflow.com/a/25034854/3408572
+// doing this for other brushes works too
+void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p)
+{
+ CGLineCap cap;
+ CGLineJoin join;
+ CGPathRef dashPath;
+ CGFloat *dashes;
+ size_t i;
+ uiDrawPath p2;
+
+ if (!path->ended)
+ userbug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path);
+
+ switch (p->Cap) {
+ case uiDrawLineCapFlat:
+ cap = kCGLineCapButt;
+ break;
+ case uiDrawLineCapRound:
+ cap = kCGLineCapRound;
+ break;
+ case uiDrawLineCapSquare:
+ cap = kCGLineCapSquare;
+ break;
+ }
+ switch (p->Join) {
+ case uiDrawLineJoinMiter:
+ join = kCGLineJoinMiter;
+ break;
+ case uiDrawLineJoinRound:
+ join = kCGLineJoinRound;
+ break;
+ case uiDrawLineJoinBevel:
+ join = kCGLineJoinBevel;
+ break;
+ }
+
+ // create a temporary path identical to the previous one
+ dashPath = (CGPathRef) path->path;
+ if (p->NumDashes != 0) {
+ dashes = (CGFloat *) uiAlloc(p->NumDashes * sizeof (CGFloat), "CGFloat[]");
+ for (i = 0; i < p->NumDashes; i++)
+ dashes[i] = p->Dashes[i];
+ dashPath = CGPathCreateCopyByDashingPath(path->path,
+ NULL,
+ p->DashPhase,
+ dashes,
+ p->NumDashes);
+ uiFree(dashes);
+ }
+ // the documentation is wrong: this produces a path suitable for calling CGPathCreateCopyByStrokingPath(), not for filling directly
+ // the cast is safe; we never modify the CGPathRef and always cast it back to a CGPathRef anyway
+ p2.path = (CGMutablePathRef) CGPathCreateCopyByStrokingPath(dashPath,
+ NULL,
+ p->Thickness,
+ cap,
+ join,
+ p->MiterLimit);
+ if (p->NumDashes != 0)
+ CGPathRelease(dashPath);
+
+ // always draw stroke fills using the winding rule
+ // otherwise intersecting figures won't draw correctly
+ p2.fillMode = uiDrawFillModeWinding;
+ p2.ended = path->ended;
+ uiDrawFill(c, &p2, b);
+ // and clean up
+ CGPathRelease((CGPathRef) (p2.path));
+}
+
+// for a solid fill, we can merely have Core Graphics fill directly
+static void fillSolid(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
+{
+ // TODO this uses DeviceRGB; switch to sRGB
+ CGContextSetRGBFillColor(ctxt, b->R, b->G, b->B, b->A);
+ switch (p->fillMode) {
+ case uiDrawFillModeWinding:
+ CGContextFillPath(ctxt);
+ break;
+ case uiDrawFillModeAlternate:
+ CGContextEOFillPath(ctxt);
+ break;
+ }
+}
+
+// for a gradient fill, we need to clip to the path and then draw the gradient
+// see http://stackoverflow.com/a/25034854/3408572
+static void fillGradient(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
+{
+ CGGradientRef gradient;
+ CGColorSpaceRef colorspace;
+ CGFloat *colors;
+ CGFloat *locations;
+ size_t i;
+
+ // gradients need a color space
+ // for consistency with windows, use sRGB
+ colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
+
+ // make the gradient
+ colors = uiAlloc(b->NumStops * 4 * sizeof (CGFloat), "CGFloat[]");
+ locations = uiAlloc(b->NumStops * sizeof (CGFloat), "CGFloat[]");
+ for (i = 0; i < b->NumStops; i++) {
+ colors[i * 4 + 0] = b->Stops[i].R;
+ colors[i * 4 + 1] = b->Stops[i].G;
+ colors[i * 4 + 2] = b->Stops[i].B;
+ colors[i * 4 + 3] = b->Stops[i].A;
+ locations[i] = b->Stops[i].Pos;
+ }
+ gradient = CGGradientCreateWithColorComponents(colorspace, colors, locations, b->NumStops);
+ uiFree(locations);
+ uiFree(colors);
+
+ // because we're mucking with clipping, we need to save the graphics state and restore it later
+ CGContextSaveGState(ctxt);
+
+ // clip
+ switch (p->fillMode) {
+ case uiDrawFillModeWinding:
+ CGContextClip(ctxt);
+ break;
+ case uiDrawFillModeAlternate:
+ CGContextEOClip(ctxt);
+ break;
+ }
+
+ // draw the gradient
+ switch (b->Type) {
+ case uiDrawBrushTypeLinearGradient:
+ CGContextDrawLinearGradient(ctxt,
+ gradient,
+ CGPointMake(b->X0, b->Y0),
+ CGPointMake(b->X1, b->Y1),
+ kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
+ break;
+ case uiDrawBrushTypeRadialGradient:
+ CGContextDrawRadialGradient(ctxt,
+ gradient,
+ CGPointMake(b->X0, b->Y0),
+ // make the start circle radius 0 to make it a point
+ 0,
+ CGPointMake(b->X1, b->Y1),
+ b->OuterRadius,
+ kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
+ break;
+ }
+
+ // and clean up
+ CGContextRestoreGState(ctxt);
+ CGGradientRelease(gradient);
+ CGColorSpaceRelease(colorspace);
+}
+
+void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)
+{
+ if (!path->ended)
+ userbug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path);
+ CGContextAddPath(c->c, (CGPathRef) (path->path));
+ switch (b->Type) {
+ case uiDrawBrushTypeSolid:
+ fillSolid(c->c, path, b);
+ return;
+ case uiDrawBrushTypeLinearGradient:
+ case uiDrawBrushTypeRadialGradient:
+ fillGradient(c->c, path, b);
+ return;
+// case uiDrawBrushTypeImage:
+ // TODO
+ return;
+ }
+ userbug("Unknown brush type %d passed to uiDrawFill().", b->Type);
+}
+
+static void m2c(uiDrawMatrix *m, CGAffineTransform *c)
+{
+ c->a = m->M11;
+ c->b = m->M12;
+ c->c = m->M21;
+ c->d = m->M22;
+ c->tx = m->M31;
+ c->ty = m->M32;
+}
+
+static void c2m(CGAffineTransform *c, uiDrawMatrix *m)
+{
+ m->M11 = c->a;
+ m->M12 = c->b;
+ m->M21 = c->c;
+ m->M22 = c->d;
+ m->M31 = c->tx;
+ m->M32 = c->ty;
+}
+
+void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)
+{
+ CGAffineTransform c;
+
+ m2c(m, &c);
+ c = CGAffineTransformTranslate(c, x, y);
+ c2m(&c, m);
+}
+
+void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)
+{
+ CGAffineTransform c;
+ double xt, yt;
+
+ m2c(m, &c);
+ xt = x;
+ yt = y;
+ scaleCenter(xCenter, yCenter, &xt, &yt);
+ c = CGAffineTransformTranslate(c, xt, yt);
+ c = CGAffineTransformScale(c, x, y);
+ c = CGAffineTransformTranslate(c, -xt, -yt);
+ c2m(&c, m);
+}
+
+void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)
+{
+ CGAffineTransform c;
+
+ m2c(m, &c);
+ c = CGAffineTransformTranslate(c, x, y);
+ c = CGAffineTransformRotate(c, amount);
+ c = CGAffineTransformTranslate(c, -x, -y);
+ c2m(&c, m);
+}
+
+void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
+{
+ fallbackSkew(m, x, y, xamount, yamount);
+}
+
+void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)
+{
+ CGAffineTransform c;
+ CGAffineTransform d;
+
+ m2c(dest, &c);
+ m2c(src, &d);
+ c = CGAffineTransformConcat(c, d);
+ c2m(&c, dest);
+}
+
+// there is no test for invertibility; CGAffineTransformInvert() is merely documented as returning the matrix unchanged if it isn't invertible
+// therefore, special care must be taken to catch matrices who are their own inverses
+// TODO figure out which matrices these are and do so
+int uiDrawMatrixInvertible(uiDrawMatrix *m)
+{
+ CGAffineTransform c, d;
+
+ m2c(m, &c);
+ d = CGAffineTransformInvert(c);
+ return CGAffineTransformEqualToTransform(c, d) == false;
+}
+
+int uiDrawMatrixInvert(uiDrawMatrix *m)
+{
+ CGAffineTransform c, d;
+
+ m2c(m, &c);
+ d = CGAffineTransformInvert(c);
+ if (CGAffineTransformEqualToTransform(c, d))
+ return 0;
+ c2m(&d, m);
+ return 1;
+}
+
+void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)
+{
+ CGAffineTransform c;
+ CGPoint p;
+
+ m2c(m, &c);
+ p = CGPointApplyAffineTransform(CGPointMake(*x, *y), c);
+ *x = p.x;
+ *y = p.y;
+}
+
+void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)
+{
+ CGAffineTransform c;
+ CGSize s;
+
+ m2c(m, &c);
+ s = CGSizeApplyAffineTransform(CGSizeMake(*x, *y), c);
+ *x = s.width;
+ *y = s.height;
+}
+
+void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)
+{
+ CGAffineTransform cm;
+
+ m2c(m, &cm);
+ CGContextConcatCTM(c->c, cm);
+}
+
+void uiDrawClip(uiDrawContext *c, uiDrawPath *path)
+{
+ if (!path->ended)
+ userbug("You cannot call uiDrawCilp() on a uiDrawPath that has not been ended. (path: %p)", path);
+ CGContextAddPath(c->c, (CGPathRef) (path->path));
+ switch (path->fillMode) {
+ case uiDrawFillModeWinding:
+ CGContextClip(c->c);
+ break;
+ case uiDrawFillModeAlternate:
+ CGContextEOClip(c->c);
+ break;
+ }
+}
+
+// TODO figure out what besides transforms these save/restore on all platforms
+void uiDrawSave(uiDrawContext *c)
+{
+ CGContextSaveGState(c->c);
+}
+
+void uiDrawRestore(uiDrawContext *c)
+{
+ CGContextRestoreGState(c->c);
+}
+
+void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)
+{
+ doDrawText(c->c, c->height, x, y, layout);
+}