diff options
-rw-r--r-- | cocoa/PlatCocoa.h | 1 | ||||
-rw-r--r-- | cocoa/PlatCocoa.mm | 55 | ||||
-rwxr-xr-x | gtk/PlatGTK.cxx | 63 | ||||
-rw-r--r-- | qt/ScintillaEditBase/PlatQt.cpp | 58 | ||||
-rw-r--r-- | qt/ScintillaEditBase/PlatQt.h | 1 | ||||
-rw-r--r-- | src/Platform.h | 9 | ||||
-rw-r--r-- | win32/PlatWin.cxx | 103 |
7 files changed, 288 insertions, 2 deletions
diff --git a/cocoa/PlatCocoa.h b/cocoa/PlatCocoa.h index 36022286c..c14ca1928 100644 --- a/cocoa/PlatCocoa.h +++ b/cocoa/PlatCocoa.h @@ -113,6 +113,7 @@ public: void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override; void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) override; void Ellipse(PRectangle rc, FillStroke fillStroke) override; + void Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) override; void Copy(PRectangle rc, Scintilla::Point from, Surface &surfaceSource) override; std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override; diff --git a/cocoa/PlatCocoa.mm b/cocoa/PlatCocoa.mm index 55a6ee620..7bec2d73d 100644 --- a/cocoa/PlatCocoa.mm +++ b/cocoa/PlatCocoa.mm @@ -1231,6 +1231,61 @@ void SurfaceImpl::Ellipse(PRectangle rc, FillStroke fillStroke) { CGContextSetLineWidth(gc, 1.0f); } +void SurfaceImpl::Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) { + const CGFloat midLine = rc.Centre().y; + const CGFloat piOn2 = acos(0.0); + const XYPOSITION halfStroke = fillStroke.stroke.width / 2.0f; + const float radius = rc.Height() / 2.0f - halfStroke; + PRectangle rcInner = rc; + rcInner.left += radius; + rcInner.right -= radius; + + SetFillStroke(fillStroke); + CGContextBeginPath(gc); + + const Ends leftSide = static_cast<Ends>(static_cast<int>(ends) & 0xf); + const Ends rightSide = static_cast<Ends>(static_cast<int>(ends) & 0xf0); + switch (leftSide) { + case Ends::leftFlat: + CGContextMoveToPoint(gc, rc.left + halfStroke, rc.top + halfStroke); + CGContextAddLineToPoint(gc, rc.left + halfStroke, rc.bottom - halfStroke); + break; + case Ends::leftAngle: + CGContextMoveToPoint(gc, rcInner.left + halfStroke, rc.top + halfStroke); + CGContextAddLineToPoint(gc, rc.left + halfStroke, rc.Centre().y); + CGContextAddLineToPoint(gc, rcInner.left + halfStroke, rc.bottom - halfStroke); + break; + case Ends::semiCircles: + default: + CGContextMoveToPoint(gc, rcInner.left + halfStroke, rc.top + halfStroke); + CGContextAddArc(gc, rcInner.left + halfStroke, midLine, radius, -piOn2, piOn2, 1); + break; + } + + switch (rightSide) { + case Ends::rightFlat: + CGContextAddLineToPoint(gc, rc.right - halfStroke, rc.bottom - halfStroke); + CGContextAddLineToPoint(gc, rc.right - halfStroke, rc.top + halfStroke); + break; + case Ends::rightAngle: + CGContextAddLineToPoint(gc, rcInner.right - halfStroke, rc.bottom - halfStroke); + CGContextAddLineToPoint(gc, rc.right - halfStroke, rc.Centre().y); + CGContextAddLineToPoint(gc, rcInner.right - halfStroke, rc.top + halfStroke); + break; + case Ends::semiCircles: + default: + CGContextAddLineToPoint(gc, rcInner.right - halfStroke, rc.bottom - halfStroke); + CGContextAddArc(gc, rcInner.right - halfStroke, midLine, radius, piOn2, -piOn2, 1); + break; + } + + // Close the path to enclose it for stroking and for filling, then draw it + CGContextClosePath(gc); + CGContextDrawPath(gc, kCGPathFillStroke); + + CGContextSetLineWidth(gc, 1.0f); +} + void SurfaceImpl::CopyImageRectangle(Surface &surfaceSource, PRectangle srcRect, PRectangle dstRect) { SurfaceImpl &source = static_cast<SurfaceImpl &>(surfaceSource); CGImageRef image = source.CreateImage(); diff --git a/gtk/PlatGTK.cxx b/gtk/PlatGTK.cxx index 0b87b8743..aa0d69477 100755 --- a/gtk/PlatGTK.cxx +++ b/gtk/PlatGTK.cxx @@ -47,6 +47,8 @@ namespace { constexpr double kPi = 3.14159265358979323846; +constexpr double degrees = kPi / 180.0; + // The Pango version guard for pango_units_from_double and pango_units_to_double // is more complex than simply implementing these here. @@ -178,6 +180,7 @@ public: void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override; void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) override; void Ellipse(PRectangle rc, FillStroke fillStroke) override; + void Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) override; void Copy(PRectangle rc, Point from, Surface &surfaceSource) override; std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override; @@ -612,8 +615,6 @@ void SurfaceImpl::RoundedRectangle(PRectangle rc, FillStroke fillStroke) { } static void PathRoundRectangle(cairo_t *context, double left, double top, double width, double height, double radius) noexcept { - constexpr double degrees = kPi / 180.0; - cairo_new_sub_path(context); cairo_arc(context, left + width - radius, top + radius, radius, -90 * degrees, 0 * degrees); cairo_arc(context, left + width - radius, top + height - radius, radius, 0 * degrees, 90 * degrees); @@ -749,6 +750,64 @@ void SurfaceImpl::Ellipse(PRectangle rc, FillStroke fillStroke) { cairo_stroke(context); } +void SurfaceImpl::Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) { + const XYPOSITION midLine = rc.Centre().y; + const XYPOSITION halfStroke = fillStroke.stroke.width / 2.0f; + const XYPOSITION radius = rc.Height() / 2.0f - halfStroke; + PRectangle rcInner = rc; + rcInner.left += radius; + rcInner.right -= radius; + + cairo_new_sub_path(context); + + const Ends leftSide = static_cast<Ends>(static_cast<int>(ends) & 0xf); + const Ends rightSide = static_cast<Ends>(static_cast<int>(ends) & 0xf0); + switch (leftSide) { + case Ends::leftFlat: + cairo_move_to(context, rc.left + halfStroke, rc.top + halfStroke); + cairo_line_to(context, rc.left + halfStroke, rc.bottom - halfStroke); + break; + case Ends::leftAngle: + cairo_move_to(context, rcInner.left + halfStroke, rc.top + halfStroke); + cairo_line_to(context, rc.left + halfStroke, rc.Centre().y); + cairo_line_to(context, rcInner.left + halfStroke, rc.bottom - halfStroke); + break; + case Ends::semiCircles: + default: + cairo_move_to(context, rcInner.left + halfStroke, rc.top + halfStroke); + cairo_arc_negative(context, rcInner.left + halfStroke, midLine, radius, + 270 * degrees, 90 * degrees); + break; + } + + switch (rightSide) { + case Ends::rightFlat: + cairo_line_to(context, rc.right - halfStroke, rc.bottom - halfStroke); + cairo_line_to(context, rc.right - halfStroke, rc.top + halfStroke); + break; + case Ends::rightAngle: + cairo_line_to(context, rcInner.right - halfStroke, rc.bottom - halfStroke); + cairo_line_to(context, rc.right - halfStroke, rc.Centre().y); + cairo_line_to(context, rcInner.right - halfStroke, rc.top + halfStroke); + break; + case Ends::semiCircles: + default: + cairo_line_to(context, rcInner.right - halfStroke, rc.bottom - halfStroke); + cairo_arc_negative(context, rcInner.right - halfStroke, midLine, radius, + 90 * degrees, 270 * degrees); + break; + } + + // Close the path to enclose it for stroking and for filling, then draw it + cairo_close_path(context); + PenColourAlpha(fillStroke.fill.colour); + cairo_fill_preserve(context); + + PenColourAlpha(fillStroke.stroke.colour); + cairo_set_line_width(context, fillStroke.stroke.width); + cairo_stroke(context); +} + void SurfaceImpl::Copy(PRectangle rc, Point from, Surface &surfaceSource) { SurfaceImpl &surfi = static_cast<SurfaceImpl &>(surfaceSource); const bool canDraw = surfi.psurf != nullptr; diff --git a/qt/ScintillaEditBase/PlatQt.cpp b/qt/ScintillaEditBase/PlatQt.cpp index fa4c062d8..7e2fd3e98 100644 --- a/qt/ScintillaEditBase/PlatQt.cpp +++ b/qt/ScintillaEditBase/PlatQt.cpp @@ -524,6 +524,64 @@ void SurfaceImpl::Ellipse(PRectangle rc, FillStroke fillStroke) GetPainter()->drawEllipse(rect); } +void SurfaceImpl::Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) { + const XYPOSITION halfStroke = fillStroke.stroke.width / 2.0f; + const XYPOSITION radius = rc.Height() / 2.0f - halfStroke; + PRectangle rcInner = rc; + rcInner.left += radius; + rcInner.right -= radius; + const XYPOSITION arcHeight = rc.Height() - fillStroke.stroke.width; + + PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width); + BrushColour(fillStroke.fill.colour); + + QPainterPath path; + + const Ends leftSide = static_cast<Ends>(static_cast<int>(ends) & 0xf); + const Ends rightSide = static_cast<Ends>(static_cast<int>(ends) & 0xf0); + switch (leftSide) { + case Ends::leftFlat: + path.moveTo(rc.left + halfStroke, rc.top + halfStroke); + path.lineTo(rc.left + halfStroke, rc.bottom - halfStroke); + break; + case Ends::leftAngle: + path.moveTo(rcInner.left + halfStroke, rc.top + halfStroke); + path.lineTo(rc.left + halfStroke, rc.Centre().y); + path.lineTo(rcInner.left + halfStroke, rc.bottom - halfStroke); + break; + case Ends::semiCircles: + default: + path.moveTo(rcInner.left + halfStroke, rc.top + halfStroke); + QRectF rectangleArc(rc.left + halfStroke, rc.top + halfStroke, + arcHeight, arcHeight); + path.arcTo(rectangleArc, 90, 180); + break; + } + + switch (rightSide) { + case Ends::rightFlat: + path.lineTo(rc.right - halfStroke, rc.bottom - halfStroke); + path.lineTo(rc.right - halfStroke, rc.top + halfStroke); + break; + case Ends::rightAngle: + path.lineTo(rcInner.right - halfStroke, rc.bottom - halfStroke); + path.lineTo(rc.right - halfStroke, rc.Centre().y); + path.lineTo(rcInner.right - halfStroke, rc.top + halfStroke); + break; + case Ends::semiCircles: + default: + path.lineTo(rcInner.right - halfStroke, rc.bottom - halfStroke); + QRectF rectangleArc(rc.right - arcHeight - halfStroke, rc.top + halfStroke, + arcHeight, arcHeight); + path.arcTo(rectangleArc, 270, 180); + break; + } + + // Close the path to enclose it for stroking and for filling, then draw it + path.closeSubpath(); + GetPainter()->drawPath(path); +} + void SurfaceImpl::Copy(PRectangle rc, Point from, Surface &surfaceSource) { SurfaceImpl *source = dynamic_cast<SurfaceImpl *>(&surfaceSource); diff --git a/qt/ScintillaEditBase/PlatQt.h b/qt/ScintillaEditBase/PlatQt.h index 2ca31da2d..a95e1bd88 100644 --- a/qt/ScintillaEditBase/PlatQt.h +++ b/qt/ScintillaEditBase/PlatQt.h @@ -126,6 +126,7 @@ public: void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) override; void Ellipse(PRectangle rc, FillStroke fillStroke) override; + void Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) override; void Copy(PRectangle rc, Point from, Surface &surfaceSource) override; std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override; diff --git a/src/Platform.h b/src/Platform.h index 4c7b6523d..7f61967c3 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -179,6 +179,14 @@ public: virtual void Init(SurfaceID sid, WindowID wid)=0; virtual void InitPixMap(int width, int height, Surface *surface_, WindowID wid)=0; + enum class Ends { + semiCircles = 0x0, + leftFlat = 0x1, + leftAngle = 0x2, + rightFlat = 0x10, + rightAngle = 0x20, + }; + virtual void Release() noexcept=0; virtual int Supports(int feature) noexcept=0; virtual bool Initialised()=0; @@ -206,6 +214,7 @@ public: virtual void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) = 0; virtual void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back)=0; virtual void Ellipse(PRectangle rc, FillStroke fillStroke)=0; + virtual void Stadium(PRectangle rc, FillStroke fillStroke, Ends ends)=0; virtual void Copy(PRectangle rc, Point from, Surface &surfaceSource)=0; virtual std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) = 0; diff --git a/win32/PlatWin.cxx b/win32/PlatWin.cxx index eac743ef3..673df5c2b 100644 --- a/win32/PlatWin.cxx +++ b/win32/PlatWin.cxx @@ -505,6 +505,7 @@ public: void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override; void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) override; void Ellipse(PRectangle rc, FillStroke fillStroke) override; + void Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) override; void Copy(PRectangle rc, Point from, Surface &surfaceSource) override; std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override; @@ -1083,6 +1084,11 @@ void SurfaceGDI::Ellipse(PRectangle rc, FillStroke fillStroke) { ::Ellipse(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom); } +void SurfaceGDI::Stadium(PRectangle rc, FillStroke fillStroke, [[maybe_unused]] Ends ends) { + // TODO: Implement properly - the rectangle is just a placeholder + RectangleDraw(rc, fillStroke); +} + void SurfaceGDI::Copy(PRectangle rc, Point from, Surface &surfaceSource) { ::BitBlt(hdc, static_cast<int>(rc.left), static_cast<int>(rc.top), @@ -1436,6 +1442,7 @@ public: void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override; void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) override; void Ellipse(PRectangle rc, FillStroke fillStroke) override; + void Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) override; void Copy(PRectangle rc, Point from, Surface &surfaceSource) override; std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override; @@ -1999,6 +2006,102 @@ void SurfaceD2D::Ellipse(PRectangle rc, FillStroke fillStroke) { pRenderTarget->DrawEllipse(ellipseOutline, pBrush, fillStroke.stroke.width); } +void SurfaceD2D::Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) { + if (!pRenderTarget) + return; + if (rc.Width() < rc.Height()) { + // Can't draw nice ends so just draw a rectangle + RectangleDraw(rc, fillStroke); + return; + } + const float radius = rc.Height() / 2.0f; + const float radiusFill = radius - fillStroke.stroke.width; + const XYPOSITION halfStroke = fillStroke.stroke.width / 2.0f; + if (ends == Surface::Ends::semiCircles) { + const D2D1_RECT_F rect = { rc.left, rc.top, rc.right, rc.bottom }; + D2D1_ROUNDED_RECT roundedRectFill = { RectangleInset(rect, fillStroke.stroke.width), + radiusFill, radiusFill }; + D2DPenColourAlpha(fillStroke.fill.colour); + pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush); + + D2D1_ROUNDED_RECT roundedRect = { RectangleInset(rect, halfStroke), + radius, radius }; + D2DPenColourAlpha(fillStroke.stroke.colour); + pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush, fillStroke.stroke.width); + } else { + const Ends leftSide = static_cast<Ends>(static_cast<int>(ends) & 0xf); + const Ends rightSide = static_cast<Ends>(static_cast<int>(ends) & 0xf0); + PRectangle rcInner = rc; + rcInner.left += radius; + rcInner.right -= radius; + ID2D1PathGeometry *pathGeometry = nullptr; + const HRESULT hrGeometry = pD2DFactory->CreatePathGeometry(&pathGeometry); + if (FAILED(hrGeometry) || !pathGeometry) + return; + ID2D1GeometrySink *pSink = nullptr; + const HRESULT hrSink = pathGeometry->Open(&pSink); + if (SUCCEEDED(hrSink) && pSink) { + switch (leftSide) { + case Ends::leftFlat: + pSink->BeginFigure(D2D1::Point2F(rc.left + halfStroke, rc.top + halfStroke), D2D1_FIGURE_BEGIN_FILLED); + pSink->AddLine(D2D1::Point2F(rc.left + halfStroke, rc.bottom - halfStroke)); + break; + case Ends::leftAngle: + pSink->BeginFigure(D2D1::Point2F(rcInner.left + halfStroke, rc.top + halfStroke), D2D1_FIGURE_BEGIN_FILLED); + pSink->AddLine(D2D1::Point2F(rc.left + halfStroke, rc.Centre().y)); + pSink->AddLine(D2D1::Point2F(rcInner.left + halfStroke, rc.bottom - halfStroke)); + break; + case Ends::semiCircles: + default: { + pSink->BeginFigure(D2D1::Point2F(rcInner.left + halfStroke, rc.top + halfStroke), D2D1_FIGURE_BEGIN_FILLED); + D2D1_ARC_SEGMENT segment{}; + segment.point = D2D1::Point2F(rcInner.left + halfStroke, rc.bottom - halfStroke); + segment.size = D2D1::SizeF(radiusFill, radiusFill); + segment.rotationAngle = 0.0f; + segment.sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE; + segment.arcSize = D2D1_ARC_SIZE_SMALL; + pSink->AddArc(segment); + } + break; + } + + switch (rightSide) { + case Ends::rightFlat: + pSink->AddLine(D2D1::Point2F(rc.right - halfStroke, rc.bottom - halfStroke)); + pSink->AddLine(D2D1::Point2F(rc.right - halfStroke, rc.top + halfStroke)); + break; + case Ends::rightAngle: + pSink->AddLine(D2D1::Point2F(rcInner.right - halfStroke, rc.bottom - halfStroke)); + pSink->AddLine(D2D1::Point2F(rc.right - halfStroke, rc.Centre().y)); + pSink->AddLine(D2D1::Point2F(rcInner.right - halfStroke, rc.top + halfStroke)); + break; + case Ends::semiCircles: + default: { + pSink->AddLine(D2D1::Point2F(rcInner.right - halfStroke, rc.bottom - halfStroke)); + D2D1_ARC_SEGMENT segment{}; + segment.point = D2D1::Point2F(rcInner.right - halfStroke, rc.top + halfStroke); + segment.size = D2D1::SizeF(radiusFill, radiusFill); + segment.rotationAngle = 0.0f; + segment.sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE; + segment.arcSize = D2D1_ARC_SIZE_SMALL; + pSink->AddArc(segment); + } + break; + } + + pSink->EndFigure(D2D1_FIGURE_END_CLOSED); + + pSink->Close(); + } + ReleaseUnknown(pSink); + D2DPenColourAlpha(fillStroke.fill.colour); + pRenderTarget->FillGeometry(pathGeometry, pBrush); + D2DPenColourAlpha(fillStroke.stroke.colour); + pRenderTarget->DrawGeometry(pathGeometry, pBrush, fillStroke.stroke.width); + ReleaseUnknown(pathGeometry); + } +} + void SurfaceD2D::Copy(PRectangle rc, Point from, Surface &surfaceSource) { SurfaceD2D &surfOther = dynamic_cast<SurfaceD2D &>(surfaceSource); ID2D1Bitmap *pBitmap = nullptr; |