// 19 november 2015 #include #include "uipriv_haiku.hpp" using namespace std; struct uiDrawPath { BShape *shape; uiDrawFillMode fillMode; bool ended; }; uiDrawPath *uiDrawNewPath(uiDrawFillMode fillMode) { uiDrawPath *p; p = uiNew(uiDrawPath); p->shape = new BShape(); p->fillMode = fillMode; return p; } void uiDrawFreePath(uiDrawPath *p) { delete p->shape; uiFree(p); } // TODO add ended checks // TODO add error checks void uiDrawPathNewFigure(uiDrawPath *p, double x, double y) { p->shape->MoveTo(BPoint(x, y)); } // Even though BView::FillArc()/StrokeArc() existed and used the sane arc drawing model, Haiku decided to use the Direct2D arc drawing model when it added BShape::ArcTo(). // So refer to windows/draw.c for details. // This is slightly better as it uses center points and sweep amounts instead of endpoints, but *still*. // TODO split into common/d2darc.c 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; bool largeArc; bool counterclockwise; bool fullCircle; double absSweep; // TODO is this relevaith Haiku? // 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 * M_PI)) // this part is easy fullCircle = true; else { double aerDiff; aerDiff = fabs(absSweep - (2 * M_PI)); // 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 = M_PI; drawArc(p, a, startFunction); a->startAngle += M_PI; drawArc(p, a, NULL); return; } // first, figure out the arc's endpoints 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 if (a->negative) counterclockwise = true; else counterclockwise = false; // TODO explain the outer if if (!a->negative) if (a->sweep > M_PI) largeArc = true; else largeArc = false; else // TODO especially this part if (a->sweep > M_PI) largeArc = false; else largeArc = true; p->shape->ArcTo(a->radius, a->radius, // TODO should this be sweep amount? a->startAngle, // TODO convert to degrees largeArc, counterclockwise, BPoint(a->xCenter, a->yCenter)); } 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) { p->shape->LineTo(BPoint(x, y)); } 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) { p->shape->BezierTo(BPoint(c1x, c1y), BPoint(c2x, c2y), BPoint(endX, endY)); } void uiDrawPathCloseFigure(uiDrawPath *p) { p->shape->Close(); } 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) { p->ended = true; } struct uiDrawContext { BView *view; }; uiDrawContext *newContext(BView *view) { uiDrawContext *c; c = uiNew(uiDrawContext); c->view = view; return c; } void freeContext(uiDrawContext *c) { uiFree(c); } // TODO verify this static void setHighColor(BView *view, uiDrawBrush *b) { view->SetHighColor(b->R * 255, b->G * 255, b->B * 255, b->A * 255); } // TODO path ended checks; error checks void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p) { cap_mode cap; join_mode join; switch (p->Cap) { case uiDrawLineCapFlat: cap = B_BUTT_CAP; break; case uiDrawLineCapRound: cap = B_ROUND_CAP; break; case uiDrawLineCapSquare: cap = B_SQUARE_CAP; break; } switch (p->Join) { case uiDrawLineJoinMiter: join = B_MITER_JOIN; break; case uiDrawLineJoinRound: join = B_ROUND_JOIN; break; case uiDrawLineJoinBevel: join = B_BEVEL_JOIN; break; } c->view->SetLineMode(cap, join, p->MiterLimit); c->view->SetPenSize(p->Thickness); // TODO line dashes switch (b->Type) { case uiDrawBrushTypeSolid: setHighColor(c->view, b); c->view->StrokeShape(path->shape); break; case uiDrawBrushTypeLinearGradient: // TODO case uiDrawBrushTypeRadialGradient: // TODO break; // case uiDrawBrushTypeImage: } } void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b) { // TODO not documented on api.haiku-os.org switch (path->fillMode) { case uiDrawFillModeWinding: c->view->SetFillRule(B_NONZERO); break; case uiDrawFillModeAlternate: c->view->SetFillRule(B_EVEN_ODD); break; } switch (b->Type) { case uiDrawBrushTypeSolid: setHighColor(c->view, b); c->view->FillShape(path->shape); break; case uiDrawBrushTypeLinearGradient: // TODO case uiDrawBrushTypeRadialGradient: // TODO break; // case uiDrawBrushTypeImage: } } // TODO none of this is documented on api.haiku-os.org static void m2a(uiDrawMatrix *m, BAffineTransform *a) { a->sx = m->M11; a->shy = m->M12; a->shx = m->M21; a->sy = m->M22; a->tx = m->M31; a->ty = m->M32; } static void a2m(BAffineTransform *a, uiDrawMatrix *m) { m->M11 = a->sx; m->M12 = a->shy; m->M21 = a->shx; m->M22 = a->sy; m->M31 = a->tx; m->M32 = a->ty; } void uiDrawMatrixSetIdentity(uiDrawMatrix *m) { setIdentity(m); } void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y) { BAffineTransform a; m2a(m, &a); a.TranslateBy(x, y); a2m(&a, m); } void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y) { BAffineTransform a; m2a(m, &a); a.ScaleBy(BPoint(xCenter, yCenter), BPoint(x, y)); a2m(&a, m); } void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount) { BAffineTransform a; m2a(m, &a); // TODO degrees or radians? a.RotateBy(BPoint(x, y), amount); a2m(&a, m); } void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount) { BAffineTransform a; m2a(m, &a); // TODO degrees or radians? a.ShearBy(BPoint(x, y), BPoint(xamount, yamount)); a2m(&a, m); } void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src) { BAffineTransform c; BAffineTransform d; m2a(dest, &c); m2a(src, &d); c.Multiply(d); a2m(&c, dest); } int uiDrawMatrixInvertible(uiDrawMatrix *m) { // TODO return 0; } int uiDrawMatrixInvert(uiDrawMatrix *m) { // TODO return 0; } void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y) { // TODO } void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y) { // TODO } void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m) { BAffineTransform a; m2a(m, &a); // see windows/draw.c a.Multiply(c->view->Transform()); c->view->SetTransform(a); } // TODO not documented on api.haiku-os.org void uiDrawClip(uiDrawContext *c, uiDrawPath *path) { c->view->ClipToShape(path->shape); } void uiDrawSave(uiDrawContext *c) { c->view->PushState(); } void uiDrawRestore(uiDrawContext *c) { c->view->PopState(); }