Migrated the OS X uiArea. Now to fix build issues and test.
This commit is contained in:
parent
447396d18d
commit
52c6888a6b
311
macarea/draw.m
311
macarea/draw.m
|
@ -1,16 +1,99 @@
|
|||
// 6 september 2015
|
||||
#include "area.h"
|
||||
|
||||
// TODO some pixel thick lines aren't actually pixel thick
|
||||
struct uiDrawPath {
|
||||
CGMutablePathRef path;
|
||||
uiDrawFillMode fillMode;
|
||||
BOOL ended;
|
||||
};
|
||||
|
||||
uiDrawPath *uiDrawNewPath(uiDrawFillMode mode)
|
||||
{
|
||||
uiDrawPath *p;
|
||||
|
||||
// TODO uiNew
|
||||
p = malloc(sizeof (uiDrawPath));
|
||||
p->path = CGPathCreateMutable()
|
||||
p->fillMode = mode;
|
||||
p->ended = NO;
|
||||
return p;
|
||||
}
|
||||
|
||||
void uiDrawFreePath(uiDrawPath *p)
|
||||
{
|
||||
CGPathRelease((CGPathRef) (p->path));
|
||||
// TODO uiFree
|
||||
free(p);
|
||||
}
|
||||
|
||||
// TODO
|
||||
#define complain(...) ;
|
||||
|
||||
void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)
|
||||
{
|
||||
if (p->ended)
|
||||
complain("attempt to add figure to ended path in uiDrawPathNewFigure()");
|
||||
CGPathMoveToPoint(p->path, NULL, x, y);
|
||||
}
|
||||
|
||||
void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double endAngle)
|
||||
{
|
||||
if (p->ended)
|
||||
complain("attempt to add figure to ended path in uiDrawPathNewFigureWithArc()");
|
||||
// TODO
|
||||
CGPathMoveToPoint(p->path, NULL, startx, starty);
|
||||
uiDrawPathArcTo(p, xCenter, yCenter, radius, startAngle, endAngle);
|
||||
}
|
||||
|
||||
void uiDrawPathLineTo(uiDrawPath *p, double x, double y)
|
||||
{
|
||||
if (p->ended)
|
||||
complain("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 endAngle)
|
||||
{
|
||||
if (p->ended)
|
||||
complain("attempt to add arc to ended path in uiDrawPathArcTo()");
|
||||
CGPathAddArc(p->path, NULL,
|
||||
xCenter, yCenter,
|
||||
radius,
|
||||
startAngle, endAngle,
|
||||
NO);
|
||||
}
|
||||
|
||||
void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)
|
||||
{
|
||||
if (p->ended)
|
||||
complain("attempt to add bezier to ended path in uiDrawPathBezierTo()");
|
||||
CGPathAddCurveToPoint(p->path, NULL,
|
||||
c1x, c1y,
|
||||
c2x, c2y,
|
||||
endX, endY);
|
||||
}
|
||||
|
||||
void uiDrawPathCloseFigure(uiDrawPath *p)
|
||||
{
|
||||
if (p->ended)
|
||||
complain("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)
|
||||
complain("attempt to add rectangle to ended path in uiDrawPathAddRectangle()");
|
||||
CGPathAddRect(p->path, NULL, CGRectMake(x, y, width, height));
|
||||
}
|
||||
|
||||
void uiDrawPathEnd(uiDrawPath *p)
|
||||
{
|
||||
p->ended = TRUE;
|
||||
}
|
||||
|
||||
struct uiDrawContext {
|
||||
CGContextRef c;
|
||||
|
||||
BOOL useRGBA;
|
||||
CGFloat r;
|
||||
CGFloat g;
|
||||
CGFloat b;
|
||||
CGFloat a;
|
||||
};
|
||||
|
||||
uiDrawContext *newContext(CGContextRef ctxt)
|
||||
|
@ -23,126 +106,152 @@ uiDrawContext *newContext(CGContextRef ctxt)
|
|||
return c;
|
||||
}
|
||||
|
||||
void uiDrawBeginPathRGB(uiDrawContext *c, uint8_t r, uint8_t g, uint8_t b)
|
||||
// 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)
|
||||
{
|
||||
c->useRGBA = YES;
|
||||
c->r = ((CGFloat) r) / 255;
|
||||
c->g = ((CGFloat) g) / 255;
|
||||
c->b = ((CGFloat) b) / 255;
|
||||
c->a = 1.0;
|
||||
CGContextBeginPath(c->c);
|
||||
}
|
||||
CGLineCap cap;
|
||||
CGLineJoin join;
|
||||
uiDrawPath p2;
|
||||
|
||||
void uiDrawBeginPathRGBA(uiDrawContext *c, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||
{
|
||||
c->useRGBA = YES;
|
||||
c->r = ((CGFloat) r) / 255;
|
||||
c->g = ((CGFloat) g) / 255;
|
||||
c->b = ((CGFloat) b) / 255;
|
||||
c->a = ((CGFloat) a) / 255;
|
||||
CGContextBeginPath(c->c);
|
||||
}
|
||||
if (!path->ended)
|
||||
complain("path not ended in uiDrawStroke()");
|
||||
|
||||
// TODO 0.25 for retina? some say yes, some say no
|
||||
// TODO same adjustment for cairo
|
||||
#define topoint(x) (((CGFloat) x) + 0.5)
|
||||
|
||||
void uiDrawMoveTo(uiDrawContext *c, intmax_t x, intmax_t y)
|
||||
{
|
||||
CGContextMoveToPoint(c->c, topoint(x), topoint(y));
|
||||
}
|
||||
|
||||
void uiDrawLineTo(uiDrawContext *c, intmax_t x, intmax_t y)
|
||||
{
|
||||
CGContextAddLineToPoint(c->c, topoint(x), topoint(y));
|
||||
}
|
||||
|
||||
// TODO width-1/height-1? (also for cairo)
|
||||
void uiDrawRectangle(uiDrawContext *c, intmax_t x, intmax_t y, intmax_t width, intmax_t height)
|
||||
{
|
||||
CGContextAddRect(c->c, CGRectMake(topoint(x), topoint(y), width, height));
|
||||
}
|
||||
|
||||
void uiDrawArcTo(uiDrawContext *c, intmax_t xCenter, intmax_t yCenter, intmax_t radius, double startAngle, double endAngle, int lineFromCurrentPointToStart)
|
||||
{
|
||||
if (!lineFromCurrentPointToStart) {
|
||||
// see http://stackoverflow.com/questions/31489157/extra-line-when-drawing-an-arc-in-swift
|
||||
// TODO verify correctness
|
||||
CGFloat x, y;
|
||||
|
||||
x = topoint(xCenter);
|
||||
y = topoint(yCenter);
|
||||
x += radius * cos(startAngle);
|
||||
y -= radius * sin(startAngle);
|
||||
CGContextMoveToPoint(c->c, x, y);
|
||||
}
|
||||
CGContextAddArc(c->c,
|
||||
topoint(xCenter), topoint(yCenter),
|
||||
radius,
|
||||
startAngle, endAngle,
|
||||
0);
|
||||
}
|
||||
|
||||
void uiDrawBezierTo(uiDrawContext *c, intmax_t c1x, intmax_t c1y, intmax_t c2x, intmax_t c2y, intmax_t endX, intmax_t endY)
|
||||
{
|
||||
CGContextAddCurveToPoint(c->c,
|
||||
topoint(c1x), topoint(c1y),
|
||||
topoint(c2x), topoint(c2y),
|
||||
topoint(endX), topoint(endY));
|
||||
}
|
||||
|
||||
void uiDrawCloseFigure(uiDrawContext *c)
|
||||
{
|
||||
CGContextClosePath(c->c);
|
||||
}
|
||||
|
||||
void uiDrawStroke(uiDrawContext *c, uiDrawStrokeParams *p)
|
||||
{
|
||||
switch (p->Cap) {
|
||||
case uiDrawLineCapFlat:
|
||||
CGContextSetLineCap(c->c, kCGLineCapButt);
|
||||
cap = kCGLineCapButt;
|
||||
break;
|
||||
case uiDrawLineCapRound:
|
||||
CGContextSetLineCap(c->c, kCGLineCapRound);
|
||||
cap = kCGLineCapRound;
|
||||
break;
|
||||
case uiDrawLineCapSquare:
|
||||
CGContextSetLineCap(c->c, kCGLineCapSquare);
|
||||
cap = kCGLineCapSquare;
|
||||
break;
|
||||
}
|
||||
switch (p->Join) {
|
||||
case uiDrawLineJoinMiter:
|
||||
CGContextSetLineJoin(c->c, kCGLineJoinMiter);
|
||||
join = kCGLineJoinMiter;
|
||||
CGContextSetMiterLimit(c->c, p->MiterLimit);
|
||||
break;
|
||||
case uiDrawLineJoinRound:
|
||||
CGContextSetLineJoin(c->c, kCGLineJoinRound);
|
||||
join = kCGLineJoinRound;
|
||||
break;
|
||||
case uiDrawLineJoinBevel:
|
||||
CGContextSetLineJoin(c->c, kCGLineJoinBevel);
|
||||
join = kCGLineJoinBevel;
|
||||
break;
|
||||
}
|
||||
CGContextSetLineWidth(c->c, p->Thickness);
|
||||
if (c->useRGBA)
|
||||
CGContextSetRGBStrokeColor(c->c, c->r, c->g, c->b, c->a);
|
||||
else {
|
||||
// TODO
|
||||
}
|
||||
CGContextStrokePath(c->c);
|
||||
|
||||
// create a temporary path identical to the previous one
|
||||
// the cast is safe; we never modify the CGPathRef and always cast it back to a CGPathRef anyway
|
||||
p2.path = (CGMutablePathRef) CGPathCreateCopyByStrokingPath(path->path,
|
||||
NULL,
|
||||
p->Thickness,
|
||||
cap,
|
||||
join,
|
||||
p->MiterLimit);
|
||||
p2.fillMode = path->fillMode;
|
||||
p2.ended = path->ended;
|
||||
uiDrawFill(c, &p2, b);
|
||||
// and clean up
|
||||
CGPathRelease((CGPathRef) (p2.path));
|
||||
}
|
||||
|
||||
void uiDrawFill(uiDrawContext *c, uiDrawFillMode mode)
|
||||
// for a solid fill, we can merely have Core Graphics fill directly
|
||||
static void fillSolid(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
|
||||
{
|
||||
if (c->useRGBA)
|
||||
CGContextSetRGBFillColor(c->c, c->r, c->g, c->b, c->a);
|
||||
else {
|
||||
// TODO
|
||||
}
|
||||
switch (mode) {
|
||||
CGContextSetRGBFillColor(ctxt, b->R, b->G, b->B, b->A);
|
||||
switch (p->fillMode) {
|
||||
case uiDrawFillModeWinding:
|
||||
CGContextFillPath(c->c);
|
||||
CGContextFillPath(ctxt);
|
||||
break;
|
||||
case uiDrawFillModeAlternate:
|
||||
CGContextEOFillPath(c->c);
|
||||
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;
|
||||
|
||||
// gradients need a color space
|
||||
// for consistency with windows, use sRGB
|
||||
colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
||||
|
||||
// make the gradient
|
||||
// TODO uiAlloc
|
||||
colors = malloc(b->NumStops * 4 * sizeof (CGFloat));
|
||||
locations = malloc(b->NumStops * sizeof (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);
|
||||
// TODO uiFree
|
||||
free(locations);
|
||||
free(colors);
|
||||
|
||||
// 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
|
||||
CGGradientRelease(gradient);
|
||||
CGColorSpaceRelease(colorspace);
|
||||
}
|
||||
|
||||
void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)
|
||||
{
|
||||
if (!path->ended)
|
||||
complain("path not ended in uiDrawFill()");
|
||||
CGContextAddPath(c->c, (CGPathRef) (p->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;
|
||||
}
|
||||
complain("unknown brush type %d in uiDrawFill()", b->Type);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue