diff options
author | Neil <nyamatongwe@gmail.com> | 2021-03-25 10:07:29 +1100 |
---|---|---|
committer | Neil <nyamatongwe@gmail.com> | 2021-03-25 10:07:29 +1100 |
commit | 6ca286d41a1ca8e6d948d131fdaaf8b8199c905e (patch) | |
tree | 417cd9fa371dac8432198aed2005a96ae6f3a6c3 | |
parent | 216fca2d218c3e0dcb5cdb4273cfb609da06cbd2 (diff) | |
download | scintilla-mirror-6ca286d41a1ca8e6d948d131fdaaf8b8199c905e.tar.gz |
Use FillStroke for parameters to Polygon, RectangleDraw, RoundedRectangle, and
Ellipse.
-rw-r--r-- | cocoa/PlatCocoa.h | 6 | ||||
-rw-r--r-- | cocoa/PlatCocoa.mm | 140 | ||||
-rwxr-xr-x | gtk/PlatGTK.cxx | 59 | ||||
-rw-r--r-- | qt/ScintillaEditBase/PlatQt.cpp | 47 | ||||
-rw-r--r-- | qt/ScintillaEditBase/PlatQt.h | 11 | ||||
-rw-r--r-- | src/Platform.h | 4 | ||||
-rw-r--r-- | win32/PlatWin.cxx | 156 |
7 files changed, 417 insertions, 6 deletions
diff --git a/cocoa/PlatCocoa.h b/cocoa/PlatCocoa.h index f50c0e7f7..4487d9945 100644 --- a/cocoa/PlatCocoa.h +++ b/cocoa/PlatCocoa.h @@ -64,6 +64,8 @@ private: void PenColourAlpha(ColourAlpha fore); + void SetFillStroke(FillStroke fillStroke); + // 24-bit RGB+A bitmap data constants static const int BITS_PER_COMPONENT = 8; static const int BITS_PER_PIXEL = BITS_PER_COMPONENT * 4; @@ -95,17 +97,21 @@ public: void MoveTo(int x_, int y_) override; void LineTo(int x_, int y_) override; void Polygon(Scintilla::Point *pts, size_t npts, ColourDesired fore, ColourDesired back) override; + void Polygon(const Scintilla::Point *pts, size_t npts, FillStroke fillStroke) override; void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) override; + void RectangleDraw(PRectangle rc, FillStroke fillStroke) override; void FillRectangle(PRectangle rc, ColourDesired back) override; void FillRectangle(PRectangle rc, Fill fill) override; void FillRectangle(PRectangle rc, Surface &surfacePattern) override; void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) override; + void RoundedRectangle(PRectangle rc, FillStroke fillStroke) override; void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, ColourDesired outline, int alphaOutline, int flags) override; void AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) override; void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override; 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 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 714e4f484..618d17de1 100644 --- a/cocoa/PlatCocoa.mm +++ b/cocoa/PlatCocoa.mm @@ -51,6 +51,15 @@ extern sptr_t scintilla_send_message(void *sci, unsigned int iMessage, uptr_t wP //-------------------------------------------------------------------------------------------------- /** + * Converts a Point as used by Scintilla to a Quartz-style CGPoint. + */ +inline CGPoint CGPointFromPoint(Scintilla::Point pt) { + return CGPointMake(pt.x, pt.y); +} + +//-------------------------------------------------------------------------------------------------- + +/** * Converts a PRectangle as used by Scintilla to standard Obj-C NSRect structure . */ NSRect PRectangleToNSRect(const PRectangle &rc) { @@ -77,6 +86,18 @@ inline CGRect PRectangleToCGRect(PRectangle &rc) { return CGRectMake(rc.left, rc.top, rc.Width(), rc.Height()); } +//-------------------------------------------------------------------------------------------------- + +/** + * Converts a PRectangle as used by Scintilla to a Quartz-style rectangle. + * Result is inset by strokeWidth / 2 so stroking does not go outside the rectangle. + */ +inline CGRect CGRectFromPRectangleInset(PRectangle rc, XYPOSITION strokeWidth) { + const XYPOSITION halfStroke = strokeWidth / 2.0f; + const CGRect rect = PRectangleToCGRect(rc); + return CGRectInset(rect, halfStroke, halfStroke); +} + //----------------- FontQuartz --------------------------------------------------------------------- class FontQuartz : public Font { @@ -513,6 +534,14 @@ void SurfaceImpl::PenColourAlpha(ColourAlpha fore) { //-------------------------------------------------------------------------------------------------- +void SurfaceImpl::SetFillStroke(FillStroke fillStroke) { + FillColour(fillStroke.fill.colour); + PenColourAlpha(fillStroke.stroke.colour); + CGContextSetLineWidth(gc, fillStroke.stroke.width); +} + +//-------------------------------------------------------------------------------------------------- + CGImageRef SurfaceImpl::CreateImage() { // For now, assume that CreateImage can only be called on PixMap surfaces. if (!bitmapData) @@ -655,6 +684,27 @@ void SurfaceImpl::Polygon(Scintilla::Point *pts, size_t npts, ColourDesired fore //-------------------------------------------------------------------------------------------------- +void SurfaceImpl::Polygon(const Scintilla::Point *pts, size_t npts, FillStroke fillStroke) { + std::vector<CGPoint> points; + std::transform(pts, pts + npts, std::back_inserter(points), CGPointFromPoint); + + CGContextBeginPath(gc); + + SetFillStroke(fillStroke); + + // Draw the polygon + CGContextAddLines(gc, points.data(), npts); + + // Explicitly close the path, so it is closed for stroking AND filling (implicit close = filling only) + CGContextClosePath(gc); + CGContextDrawPath(gc, kCGPathFillStroke); + + // Restore as not all paths set + CGContextSetLineWidth(gc, 1.0f); +} + +//-------------------------------------------------------------------------------------------------- + void SurfaceImpl::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) { if (gc) { CGContextBeginPath(gc); @@ -671,6 +721,22 @@ void SurfaceImpl::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired //-------------------------------------------------------------------------------------------------- +void SurfaceImpl::RectangleDraw(PRectangle rc, FillStroke fillStroke) { + if (!gc) + return; + CGContextBeginPath(gc); + SetFillStroke(fillStroke); + + CGContextAddRect(gc, CGRectFromPRectangleInset(rc, fillStroke.stroke.width)); + + CGContextDrawPath(gc, kCGPathFillStroke); + + // Restore as not all paths set + CGContextSetLineWidth(gc, 1.0f); +} + +//-------------------------------------------------------------------------------------------------- + void SurfaceImpl::FillRectangle(PRectangle rc, ColourDesired back) { if (gc) { FillColour(back); @@ -813,6 +879,70 @@ void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesi CGContextDrawPath(gc, kCGPathFillStroke); } +void SurfaceImpl::RoundedRectangle(PRectangle rc, FillStroke fillStroke) { + // This is only called from the margin marker drawing code for SC_MARK_ROUNDRECT + // The Win32 version does + // ::RoundRect(hdc, rc.left + 1, rc.top, rc.right - 1, rc.bottom, 8, 8 ); + // which is a rectangle with rounded corners each having a radius of 4 pixels. + // It would be almost as good just cutting off the corners with lines at + // 45 degrees as is done on GTK+. + + // Create a rectangle with semicircles at the corners + const int MAX_RADIUS = 4; + const int radius = std::min(MAX_RADIUS, static_cast<int>(std::min(rc.Height()/2, rc.Width()/2))); + + // Points go clockwise, starting from just below the top left + // Corners are kept together, so we can easily create arcs to connect them + CGPoint corners[4][3] = { + { + { rc.left, rc.top + radius }, + { rc.left, rc.top }, + { rc.left + radius, rc.top }, + }, + { + { rc.right - radius - 1, rc.top }, + { rc.right - 1, rc.top }, + { rc.right - 1, rc.top + radius }, + }, + { + { rc.right - 1, rc.bottom - radius - 1 }, + { rc.right - 1, rc.bottom - 1 }, + { rc.right - radius - 1, rc.bottom - 1 }, + }, + { + { rc.left + radius, rc.bottom - 1 }, + { rc.left, rc.bottom - 1 }, + { rc.left, rc.bottom - radius - 1 }, + }, + }; + + // Align the points in the middle of the pixels + for (int i = 0; i < 4; ++ i) { + for (int j = 0; j < 3; ++ j) { + corners[i][j].x += 0.5; + corners[i][j].y += 0.5; + } + } + + SetFillStroke(fillStroke); + + // Move to the last point to begin the path + CGContextBeginPath(gc); + CGContextMoveToPoint(gc, corners[3][2].x, corners[3][2].y); + + for (int i = 0; i < 4; ++ i) { + CGContextAddLineToPoint(gc, corners[i][0].x, corners[i][0].y); + CGContextAddArcToPoint(gc, corners[i][1].x, corners[i][1].y, corners[i][2].x, corners[i][2].y, radius); + } + + // Close the path to enclose it for stroking and for filling, then draw it + CGContextClosePath(gc); + CGContextDrawPath(gc, kCGPathFillStroke); + + // Restore as not all paths set + CGContextSetLineWidth(gc, 1.0f); +} + // DrawChamferedRectangle is a helper function for AlphaRectangle that either fills or strokes a // rectangle with its corners chamfered at 45 degrees. static void DrawChamferedRectangle(CGContextRef gc, PRectangle rc, int cornerSize, CGPathDrawingMode mode) { @@ -1073,6 +1203,16 @@ void SurfaceImpl::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) CGContextDrawPath(gc, kCGPathFillStroke); } +void SurfaceImpl::Ellipse(PRectangle rc, FillStroke fillStroke) { + const CGRect ellipseRect = CGRectFromPRectangleInset(rc, fillStroke.stroke.width / 2.0f); + SetFillStroke(fillStroke); + CGContextBeginPath(gc); + CGContextAddEllipseInRect(gc, ellipseRect); + CGContextDrawPath(gc, kCGPathFillStroke); + // Restore as not all paths set + 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 b1079a31a..b71689007 100755 --- a/gtk/PlatGTK.cxx +++ b/gtk/PlatGTK.cxx @@ -162,17 +162,21 @@ public: void MoveTo(int x_, int y_) override; void LineTo(int x_, int y_) override; void Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesired back) override; + void Polygon(const Point *pts, size_t npts, FillStroke fillStroke) override; void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) override; + void RectangleDraw(PRectangle rc, FillStroke fillStroke) override; void FillRectangle(PRectangle rc, ColourDesired back) override; void FillRectangle(PRectangle rc, Fill fill) override; void FillRectangle(PRectangle rc, Surface &surfacePattern) override; void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) override; + void RoundedRectangle(PRectangle rc, FillStroke fillStroke) override; void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, ColourDesired outline, int alphaOutline, int flags) override; void AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) override; void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override; 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 Copy(PRectangle rc, Point from, Surface &surfaceSource) override; std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override; @@ -494,6 +498,20 @@ void SurfaceImpl::Polygon(Point *pts, size_t npts, ColourDesired fore, cairo_stroke(context); } +void SurfaceImpl::Polygon(const Point *pts, size_t npts, FillStroke fillStroke) { + PLATFORM_ASSERT(context); + PenColourAlpha(fillStroke.fill.colour); + cairo_move_to(context, pts[0].x, pts[0].y); + for (size_t i = 1; i < npts; i++) { + cairo_line_to(context, pts[i].x, pts[i].y); + } + cairo_close_path(context); + cairo_fill_preserve(context); + PenColourAlpha(fillStroke.stroke.colour); + cairo_set_line_width(context, fillStroke.stroke.width); + cairo_stroke(context); +} + void SurfaceImpl::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) { if (context) { cairo_rectangle(context, rc.left + 0.5, rc.top + 0.5, @@ -505,6 +523,17 @@ void SurfaceImpl::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired } } +void SurfaceImpl::RectangleDraw(PRectangle rc, FillStroke fillStroke) { + if (context) { + CairoRectangle(rc.Inset(fillStroke.stroke.width / 2)); + 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::FillRectangle(PRectangle rc, ColourDesired back) { PenColour(back); if (context && (rc.left < maxCoordinate)) { // Protect against out of range @@ -553,6 +582,25 @@ void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesi } } +void SurfaceImpl::RoundedRectangle(PRectangle rc, FillStroke fillStroke) { + if (((rc.right - rc.left) > 4) && ((rc.bottom - rc.top) > 4)) { + // Approximate a round rect with some cut off corners + Point pts[] = { + Point(rc.left + 2, rc.top), + Point(rc.right - 2, rc.top), + Point(rc.right, rc.top + 2), + Point(rc.right, rc.bottom - 2), + Point(rc.right - 2, rc.bottom), + Point(rc.left + 2, rc.bottom), + Point(rc.left, rc.bottom - 2), + Point(rc.left, rc.top + 2), + }; + Polygon(pts, std::size(pts), fillStroke); + } else { + RectangleDraw(rc, fillStroke); + } +} + static void PathRoundRectangle(cairo_t *context, double left, double top, double width, double height, double radius) noexcept { constexpr double degrees = kPi / 180.0; @@ -680,6 +728,17 @@ void SurfaceImpl::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) cairo_stroke(context); } +void SurfaceImpl::Ellipse(PRectangle rc, FillStroke fillStroke) { + PLATFORM_ASSERT(context); + PenColourAlpha(fillStroke.fill.colour); + cairo_arc(context, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2, + (std::min(rc.Width(), rc.Height()) - fillStroke.stroke.width) / 2, 0, 2*kPi); + 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 a8553e658..6c7f789ce 100644 --- a/qt/ScintillaEditBase/PlatQt.cpp +++ b/qt/ScintillaEditBase/PlatQt.cpp @@ -230,11 +230,24 @@ void SurfaceImpl::PenColour(ColourDesired fore) GetPainter()->setPen(penOutline); } +void SurfaceImpl::PenColourWidth(ColourAlpha fore, XYPOSITION strokeWidth) { + QPen penOutline(QColorFromColourAlpha(fore)); + penOutline.setCapStyle(Qt::FlatCap); + penOutline.setJoinStyle(Qt::MiterJoin); + penOutline.setWidthF(strokeWidth); + GetPainter()->setPen(penOutline); +} + void SurfaceImpl::BrushColour(ColourDesired back) { GetPainter()->setBrush(QBrush(QColorFromCA(back))); } +void SurfaceImpl::BrushColour(ColourAlpha back) +{ + GetPainter()->setBrush(QBrush(QColorFromColourAlpha(back))); +} + void SurfaceImpl::SetCodec(const Font *font) { const FontAndCharacterSet *pfacs = AsFontAndCharacterSet(font); @@ -304,6 +317,17 @@ void SurfaceImpl::Polygon(Point *pts, GetPainter()->drawPolygon(&qpts[0], static_cast<int>(npts)); } +void SurfaceImpl::Polygon(const Point *pts, size_t npts, FillStroke fillStroke) +{ + PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width); + BrushColour(fillStroke.fill.colour); + + std::vector<QPointF> qpts; + std::transform(pts, pts + npts, std::back_inserter(qpts), QPointFFromPoint); + + GetPainter()->drawPolygon(&qpts[0], static_cast<int>(npts)); +} + void SurfaceImpl::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) @@ -314,6 +338,14 @@ void SurfaceImpl::RectangleDraw(PRectangle rc, GetPainter()->drawRect(rect); } +void SurfaceImpl::RectangleDraw(PRectangle rc, FillStroke fillStroke) +{ + PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width); + BrushColour(fillStroke.fill.colour); + const QRectF rect = QRectFFromPRect(rc.Inset(fillStroke.stroke.width / 2)); + GetPainter()->drawRect(rect); +} + void SurfaceImpl::FillRectangle(PRectangle rc, ColourDesired back) { GetPainter()->fillRect(QRectFFromPRect(rc), QColorFromCA(back)); @@ -352,6 +384,13 @@ void SurfaceImpl::RoundedRectangle(PRectangle rc, GetPainter()->drawRoundedRect(QRectFFromPRect(RectangleInset(rc, 0.5f)), 3.0f, 3.0f); } +void SurfaceImpl::RoundedRectangle(PRectangle rc, FillStroke fillStroke) +{ + PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width); + BrushColour(fillStroke.fill.colour); + GetPainter()->drawRoundedRect(QRectFFromPRect(rc), 3.0f, 3.0f); +} + void SurfaceImpl::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, @@ -469,6 +508,14 @@ void SurfaceImpl::Ellipse(PRectangle rc, GetPainter()->drawEllipse(QRectFFromPRect(rc)); } +void SurfaceImpl::Ellipse(PRectangle rc, FillStroke fillStroke) +{ + PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width); + BrushColour(fillStroke.fill.colour); + const QRectF rect = QRectFFromPRect(rc.Inset(fillStroke.stroke.width / 2)); + GetPainter()->drawEllipse(rect); +} + 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 257dbda56..12f3e7726 100644 --- a/qt/ScintillaEditBase/PlatQt.h +++ b/qt/ScintillaEditBase/PlatQt.h @@ -62,6 +62,11 @@ inline Point PointFromQPoint(QPoint qp) return Point(qp.x(), qp.y()); } +inline QPointF QPointFFromPoint(Point qp) +{ + return QPointF(qp.x, qp.y); +} + constexpr PRectangle RectangleInset(PRectangle rc, XYPOSITION delta) noexcept { return PRectangle(rc.left + delta, rc.top + delta, rc.right - delta, rc.bottom - delta); } @@ -93,6 +98,7 @@ public: int Supports(int feature) noexcept override; bool Initialised() override; void PenColour(ColourDesired fore) override; + void PenColourWidth(ColourAlpha fore, XYPOSITION strokeWidth); int LogPixelsY() override; int PixelDivisions() override; int DeviceHeightFont(int points) override; @@ -100,13 +106,16 @@ public: void LineTo(int x_, int y_) override; void Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesired back) override; + void Polygon(const Point *pts, size_t npts, FillStroke fillStroke) override; void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) override; + void RectangleDraw(PRectangle rc, FillStroke fillStroke) override; void FillRectangle(PRectangle rc, ColourDesired back) override; void FillRectangle(PRectangle rc, Fill fill) override; void FillRectangle(PRectangle rc, Surface &surfacePattern) override; void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) override; + void RoundedRectangle(PRectangle rc, FillStroke fillStroke) override; void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, ColourDesired outline, int alphaOutline, int flags) override; void AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) override; @@ -115,6 +124,7 @@ public: const unsigned char *pixelsImage) override; void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) override; + void Ellipse(PRectangle rc, FillStroke fillStroke) override; void Copy(PRectangle rc, Point from, Surface &surfaceSource) override; std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override; @@ -155,6 +165,7 @@ public: void SetBidiR2L(bool bidiR2L_) override; void BrushColour(ColourDesired back); + void BrushColour(ColourAlpha back); void SetCodec(const Font *font); void SetFont(const Font *font); diff --git a/src/Platform.h b/src/Platform.h index 1b9f2a6d1..6848e4d4f 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -189,11 +189,14 @@ public: virtual void MoveTo(int x_, int y_)=0; virtual void LineTo(int x_, int y_)=0; virtual void Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesired back)=0; + virtual void Polygon(const Point *pts, size_t npts, FillStroke fillStroke)=0; virtual void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back)=0; + virtual void RectangleDraw(PRectangle rc, FillStroke fillStroke)=0; virtual void FillRectangle(PRectangle rc, ColourDesired back)=0; virtual void FillRectangle(PRectangle rc, Fill fill)=0; virtual void FillRectangle(PRectangle rc, Surface &surfacePattern)=0; virtual void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back)=0; + virtual void RoundedRectangle(PRectangle rc, FillStroke fillStroke)=0; virtual void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, ColourDesired outline, int alphaOutline, int flags)=0; virtual void AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke)=0; @@ -201,6 +204,7 @@ public: virtual void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options)=0; 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 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 ae819bb69..dacea8522 100644 --- a/win32/PlatWin.cxx +++ b/win32/PlatWin.cxx @@ -459,7 +459,9 @@ class SurfaceGDI : public Surface { int codePage = 0; - void BrushColour(ColourDesired back) noexcept; + void PenColour(ColourAlpha fore, XYPOSITION widthStroke) noexcept; + + void BrushColour(ColourAlpha back) noexcept; void SetFont(const Font *font_) noexcept; void Clear() noexcept; @@ -487,17 +489,21 @@ public: void MoveTo(int x_, int y_) override; void LineTo(int x_, int y_) override; void Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesired back) override; + void Polygon(const Point *pts, size_t npts, FillStroke fillStroke) override; void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) override; + void RectangleDraw(PRectangle rc, FillStroke fillStroke) override; void FillRectangle(PRectangle rc, ColourDesired back) override; void FillRectangle(PRectangle rc, Fill fill) override; void FillRectangle(PRectangle rc, Surface &surfacePattern) override; void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) override; + void RoundedRectangle(PRectangle rc, FillStroke fillStroke) override; void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, ColourDesired outline, int alphaOutline, int flags) override; void AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) override; void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override; 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 Copy(PRectangle rc, Point from, Surface &surfaceSource) override; std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override; @@ -625,14 +631,36 @@ void SurfaceGDI::PenColour(ColourDesired fore) { penOld = SelectPen(hdc, pen); } -void SurfaceGDI::BrushColour(ColourDesired back) noexcept { +void SurfaceGDI::PenColour(ColourAlpha fore, XYPOSITION widthStroke) noexcept { + if (pen) { + ::SelectObject(hdc, penOld); + ::DeleteObject(pen); + pen = {}; + penOld = {}; + } + const DWORD penWidth = std::lround(widthStroke); + const COLORREF penColour = fore.GetColour().AsInteger(); + if (widthStroke > 1) { + const LOGBRUSH brushParameters{ BS_SOLID, penColour, 0 }; + pen = ::ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_ROUND | PS_JOIN_MITER, + penWidth, + &brushParameters, + 0, + nullptr); + } else { + pen = ::CreatePen(PS_INSIDEFRAME, penWidth, penColour); + } + penOld = SelectPen(hdc, pen); +} + +void SurfaceGDI::BrushColour(ColourAlpha back) noexcept { if (brush) { ::SelectObject(hdc, brushOld); ::DeleteObject(brush); brush = {}; brushOld = {}; } - brush = ::CreateSolidBrush(back.AsInteger()); + brush = ::CreateSolidBrush(back.GetColour().AsInteger()); brushOld = SelectBrush(hdc, brush); } @@ -675,6 +703,14 @@ void SurfaceGDI::Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesi ::Polygon(hdc, outline.data(), static_cast<int>(npts)); } +void SurfaceGDI::Polygon(const Point *pts, size_t npts, FillStroke fillStroke) { + PenColour(fillStroke.stroke.colour.GetColour(), fillStroke.stroke.width); + BrushColour(fillStroke.fill.colour.GetColour()); + std::vector<POINT> outline; + std::transform(pts, pts + npts, std::back_inserter(outline), POINTFromPoint); + ::Polygon(hdc, outline.data(), static_cast<int>(npts)); +} + void SurfaceGDI::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) { PenColour(fore); BrushColour(back); @@ -682,6 +718,11 @@ void SurfaceGDI::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired ::Rectangle(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom); } +void SurfaceGDI::RectangleDraw(PRectangle rc, FillStroke fillStroke) { + FillRectangle(rc, fillStroke.stroke.colour); + FillRectangle(rc.Inset(fillStroke.stroke.width), fillStroke.fill.colour); +} + void SurfaceGDI::FillRectangle(PRectangle rc, ColourDesired back) { // Using ExtTextOut rather than a FillRect ensures that no dithering occurs. // There is no need to allocate a brush either. @@ -698,9 +739,7 @@ void SurfaceGDI::FillRectangle(PRectangle rc, Fill fill) { ::SetBkColor(hdc, fill.colour.GetColour().AsInteger()); ::ExtTextOut(hdc, rcw.left, rcw.top, ETO_OPAQUE, &rcw, TEXT(""), 0, nullptr); } else { - const ColourDesired fillOpaque = fill.colour.GetColour(); - const int alpha = fill.colour.GetAlpha(); - AlphaRectangle(rc, 0, fillOpaque, alpha, fillOpaque, alpha, 0); + AlphaRectangle(rc, 0, FillStroke(fill.colour)); } } @@ -726,6 +765,16 @@ void SurfaceGDI::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesir 8, 8); } +void SurfaceGDI::RoundedRectangle(PRectangle rc, FillStroke fillStroke) { + PenColour(fillStroke.stroke.colour, fillStroke.stroke.width); + BrushColour(fillStroke.fill.colour); + const RECT rcw = RectFromPRectangle(rc); + ::RoundRect(hdc, + rcw.left + 1, rcw.top, + rcw.right - 1, rcw.bottom, + 8, 8); +} + namespace { constexpr DWORD dwordFromBGRA(byte b, byte g, byte r, byte a) noexcept { @@ -1020,6 +1069,13 @@ void SurfaceGDI::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) ::Ellipse(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom); } +void SurfaceGDI::Ellipse(PRectangle rc, FillStroke fillStroke) { + PenColour(fillStroke.stroke.colour, fillStroke.stroke.width); + BrushColour(fillStroke.fill.colour); + const RECT rcw = RectFromPRectangle(rc); + ::Ellipse(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom); +} + void SurfaceGDI::Copy(PRectangle rc, Point from, Surface &surfaceSource) { ::BitBlt(hdc, static_cast<int>(rc.left), static_cast<int>(rc.top), @@ -1355,18 +1411,23 @@ public: int DeviceHeightFont(int points) override; void MoveTo(int x_, int y_) override; void LineTo(int x_, int y_) override; + ID2D1PathGeometry *Geometry(const Point *pts, size_t npts, D2D1_FIGURE_BEGIN figureBegin) noexcept; void Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesired back) override; + void Polygon(const Point *pts, size_t npts, FillStroke fillStroke) override; void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) override; + void RectangleDraw(PRectangle rc, FillStroke fillStroke) override; void FillRectangle(PRectangle rc, ColourDesired back) override; void FillRectangle(PRectangle rc, Fill fill) override; void FillRectangle(PRectangle rc, Surface &surfacePattern) override; void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) override; + void RoundedRectangle(PRectangle rc, FillStroke fillStroke) override; void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, ColourDesired outline, int alphaOutline, int flags) override; void AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) override; void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override; 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 Copy(PRectangle rc, Point from, Surface &surfaceSource) override; std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override; @@ -1610,6 +1671,26 @@ void SurfaceD2D::LineTo(int x_, int y_) { } } +ID2D1PathGeometry *SurfaceD2D::Geometry(const Point *pts, size_t npts, D2D1_FIGURE_BEGIN figureBegin) noexcept { + ID2D1PathGeometry *geometry = nullptr; + HRESULT hr = pD2DFactory->CreatePathGeometry(&geometry); + if (SUCCEEDED(hr) && geometry) { + ID2D1GeometrySink *sink = nullptr; + hr = geometry->Open(&sink); + if (SUCCEEDED(hr) && sink) { + sink->BeginFigure(D2D1::Point2F(pts[0].x, pts[0].y), figureBegin); + for (size_t i = 1; i < npts; i++) { + sink->AddLine(D2D1::Point2F(pts[i].x, pts[i].y)); + } + sink->EndFigure((figureBegin == D2D1_FIGURE_BEGIN_FILLED) ? + D2D1_FIGURE_END_CLOSED : D2D1_FIGURE_END_OPEN); + sink->Close(); + ReleaseUnknown(sink); + } + } + return geometry; +} + void SurfaceD2D::Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesired back) { PLATFORM_ASSERT(pRenderTarget && (npts > 2)); if (pRenderTarget) { @@ -1639,6 +1720,21 @@ void SurfaceD2D::Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesi } } +void SurfaceD2D::Polygon(const Point *pts, size_t npts, FillStroke fillStroke) { + PLATFORM_ASSERT(pRenderTarget && (npts > 2)); + if (pRenderTarget) { + ID2D1PathGeometry *geometry = Geometry(pts, npts, D2D1_FIGURE_BEGIN_FILLED); + PLATFORM_ASSERT(geometry); + if (geometry) { + D2DPenColourAlpha(fillStroke.fill.colour); + pRenderTarget->FillGeometry(geometry, pBrush); + D2DPenColourAlpha(fillStroke.stroke.colour); + pRenderTarget->DrawGeometry(geometry, pBrush, fillStroke.stroke.width); + ReleaseUnknown(geometry); + } + } +} + void SurfaceD2D::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) { if (pRenderTarget) { const D2D1_RECT_F rectangle1 = D2D1::RectF(std::round(rc.left) + 0.5f, rc.top+0.5f, std::round(rc.right) - 0.5f, rc.bottom-0.5f); @@ -1649,6 +1745,20 @@ void SurfaceD2D::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired } } +void SurfaceD2D::RectangleDraw(PRectangle rc, FillStroke fillStroke) { + if (!pRenderTarget) + return; + const D2D1_RECT_F rect = RectangleFromPRectangle(rc); + const D2D1_RECT_F rectFill = RectangleInset(rect, fillStroke.stroke.width); + const float halfStroke = fillStroke.stroke.width / 2.0f; + const D2D1_RECT_F rectOutline = RectangleInset(rect, halfStroke); + + D2DPenColourAlpha(fillStroke.fill.colour); + pRenderTarget->FillRectangle(&rectFill, pBrush); + D2DPenColourAlpha(fillStroke.stroke.colour); + pRenderTarget->DrawRectangle(&rectOutline, pBrush, fillStroke.stroke.width); +} + void SurfaceD2D::FillRectangle(PRectangle rc, ColourDesired back) { if (pRenderTarget) { D2DPenColour(back); @@ -1703,6 +1813,22 @@ void SurfaceD2D::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesir } } +void SurfaceD2D::RoundedRectangle(PRectangle rc, FillStroke fillStroke) { + if (pRenderTarget) { + D2D1_ROUNDED_RECT roundedRectFill = { + D2D1::RectF(rc.left+1.0f, rc.top+1.0f, rc.right-1.0f, rc.bottom-1.0f), + 4, 4}; + D2DPenColourAlpha(fillStroke.fill.colour); + pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush); + + D2D1_ROUNDED_RECT roundedRect = { + D2D1::RectF(rc.left + 0.5f, rc.top+0.5f, rc.right - 0.5f, rc.bottom-0.5f), + 4, 4}; + D2DPenColourAlpha(fillStroke.stroke.colour); + pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush, fillStroke.stroke.width); + } +} + void SurfaceD2D::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, ColourDesired outline, int alphaOutline, int /* flags*/ ) { if (pRenderTarget) { @@ -1837,6 +1963,24 @@ void SurfaceD2D::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) } } +void SurfaceD2D::Ellipse(PRectangle rc, FillStroke fillStroke) { + if (!pRenderTarget) + return; + const D2D1_POINT_2F centre = D2D1::Point2F((rc.left + rc.right) / 2.0f, (rc.top + rc.bottom) / 2.0f); + + const FLOAT radiusFill = rc.Width() / 2.0f - fillStroke.stroke.width; + const D2D1_ELLIPSE ellipseFill = { centre, radiusFill, radiusFill }; + + D2DPenColourAlpha(fillStroke.fill.colour); + pRenderTarget->FillEllipse(ellipseFill, pBrush); + + const FLOAT radiusOutline = rc.Width() / 2.0f - fillStroke.stroke.width / 2.0f; + const D2D1_ELLIPSE ellipseOutline = { centre, radiusOutline, radiusOutline }; + + D2DPenColourAlpha(fillStroke.stroke.colour); + pRenderTarget->DrawEllipse(ellipseOutline, pBrush, fillStroke.stroke.width); +} + void SurfaceD2D::Copy(PRectangle rc, Point from, Surface &surfaceSource) { SurfaceD2D &surfOther = dynamic_cast<SurfaceD2D &>(surfaceSource); ID2D1Bitmap *pBitmap = nullptr; |