1 // Scintilla source code edit control
2 /** @file LineMarker.cxx
3 ** Defines the look of a line marker in the margin.
5 // Copyright 1998-2011 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
13 #include <string_view>
21 #include "ScintillaTypes.h"
23 #include "Debugging.h"
29 #include "LineMarker.h"
30 #include "UniConversion.h"
32 using namespace Scintilla
;
33 using namespace Scintilla::Internal
;
35 LineMarker::LineMarker(const LineMarker
&other
) {
36 // Defined to avoid pxpm and image being blindly copied, not as a complete copy constructor.
37 markType
= other
.markType
;
40 backSelected
= other
.backSelected
;
41 strokeWidth
= other
.strokeWidth
;
45 pxpm
= std::make_unique
<XPM
>(*other
.pxpm
);
49 image
= std::make_unique
<RGBAImage
>(*other
.image
);
52 customDraw
= other
.customDraw
;
55 LineMarker
&LineMarker::operator=(const LineMarker
&other
) {
56 // Defined to avoid pxpm and image being blindly copied, not as a complete assignment operator.
58 markType
= other
.markType
;
61 backSelected
= other
.backSelected
;
62 strokeWidth
= other
.strokeWidth
;
66 pxpm
= std::make_unique
<XPM
>(*other
.pxpm
);
70 image
= std::make_unique
<RGBAImage
>(*other
.image
);
73 customDraw
= other
.customDraw
;
78 ColourRGBA
LineMarker::BackWithAlpha() const noexcept
{
79 return ColourRGBA(back
, static_cast<int>(alpha
));
82 void LineMarker::SetXPM(const char *textForm
) {
83 pxpm
= std::make_unique
<XPM
>(textForm
);
84 markType
= MarkerSymbol::Pixmap
;
87 void LineMarker::SetXPM(const char *const *linesForm
) {
88 pxpm
= std::make_unique
<XPM
>(linesForm
);
89 markType
= MarkerSymbol::Pixmap
;
92 void LineMarker::SetRGBAImage(Point sizeRGBAImage
, float scale
, const unsigned char *pixelsRGBAImage
) {
93 image
= std::make_unique
<RGBAImage
>(static_cast<int>(sizeRGBAImage
.x
), static_cast<int>(sizeRGBAImage
.y
), scale
, pixelsRGBAImage
);
94 markType
= MarkerSymbol::RgbaImage
;
99 enum class Expansion
{ Minus
, Plus
};
100 enum class Shape
{ Square
, Circle
};
102 void DrawSymbol(Surface
*surface
, Shape shape
, Expansion expansion
, PRectangle rcSymbol
, XYPOSITION widthStroke
,
103 ColourRGBA colourFill
, ColourRGBA colourFrame
, ColourRGBA colourFrameRight
, ColourRGBA colourExpansion
) {
105 const FillStroke
fillStroke(colourFill
, colourFrame
, widthStroke
);
106 const PRectangle rcSymbolLeft
= Side(rcSymbol
, Edge::left
, (rcSymbol
.Width() + widthStroke
) / 2.0f
);
107 surface
->SetClip(rcSymbolLeft
);
108 if (shape
== Shape::Square
) {
110 surface
->RectangleDraw(rcSymbol
, fillStroke
);
112 surface
->Ellipse(rcSymbol
, fillStroke
);
116 const FillStroke
fillStrokeRight(colourFill
, colourFrameRight
, widthStroke
);
117 const PRectangle rcSymbolRight
= Side(rcSymbol
, Edge::right
, (rcSymbol
.Width() - widthStroke
) / 2.0f
);
118 surface
->SetClip(rcSymbolRight
);
119 if (shape
== Shape::Square
) {
120 surface
->RectangleDraw(rcSymbol
, fillStrokeRight
);
122 surface
->Ellipse(rcSymbol
, fillStrokeRight
);
126 const PRectangle rcPlusMinus
= rcSymbol
.Inset(widthStroke
+ 1.0f
);
127 const XYPOSITION armWidth
= (rcPlusMinus
.Width() - widthStroke
) / 2.0f
;
128 const XYPOSITION top
= rcPlusMinus
.top
+ armWidth
;
129 const PRectangle rcH
= PRectangle(
130 rcPlusMinus
.left
, top
,
131 rcPlusMinus
.right
, top
+ widthStroke
);
132 surface
->FillRectangle(rcH
, colourExpansion
);
133 if (expansion
== Expansion::Plus
) {
134 const XYPOSITION left
= rcPlusMinus
.left
+ armWidth
;
135 const PRectangle rcV
= PRectangle(
136 left
, rcPlusMinus
.top
,
137 left
+ widthStroke
, rcPlusMinus
.bottom
);
138 surface
->FillRectangle(rcV
, colourExpansion
);
142 void DrawTail(Surface
*surface
, XYPOSITION leftLine
, XYPOSITION rightTail
, XYPOSITION centreY
, XYPOSITION widthSymbolStroke
, ColourRGBA fill
) {
143 const XYPOSITION slopeLength
= 2.0f
+ widthSymbolStroke
;
144 const XYPOSITION strokeTop
= centreY
+ slopeLength
;
145 const XYPOSITION halfWidth
= widthSymbolStroke
/ 2.0f
;
146 const XYPOSITION strokeMiddle
= strokeTop
+ halfWidth
;
147 const Point lines
[] = {
149 Point(rightTail
, strokeMiddle
),
150 Point(leftLine
+ halfWidth
+ slopeLength
, strokeMiddle
),
152 Point(leftLine
+ widthSymbolStroke
/ 2.0f
, centreY
+ halfWidth
),
154 surface
->PolyLine(lines
, std::size(lines
), Stroke(fill
, widthSymbolStroke
));
159 void LineMarker::DrawFoldingMark(Surface
*surface
, const PRectangle
&rcWhole
, FoldPart part
) const {
160 // Assume: edges of rcWhole are integers.
161 // Code can only really handle integer strokeWidth.
163 ColourRGBA colourHead
= back
;
164 ColourRGBA colourBody
= back
;
165 ColourRGBA colourTail
= back
;
169 case FoldPart::headWithTail
:
170 colourHead
= backSelected
;
171 colourTail
= backSelected
;
174 colourHead
= backSelected
;
175 colourBody
= backSelected
;
178 colourBody
= backSelected
;
179 colourTail
= backSelected
;
182 // LineMarker::undefined
186 const int pixelDivisions
= surface
->PixelDivisions();
188 // Folding symbols should have equal height and width to be either a circle or square.
189 // So find the minimum of width and height.
190 const XYPOSITION minDimension
= std::floor(std::min(rcWhole
.Width(), rcWhole
.Height() - 2)) - 1;
192 // If strokeWidth would take up too much of area reduce to reasonable width.
193 const XYPOSITION widthStroke
= PixelAlignFloor(std::min(strokeWidth
, minDimension
/ 5.0f
), pixelDivisions
);
195 // To centre +/-, odd strokeWidth -> odd symbol width, even -> even
196 const XYPOSITION widthSymbol
=
197 ((std::lround(minDimension
* pixelDivisions
) % 2) == (std::lround(widthStroke
* pixelDivisions
) % 2)) ?
198 minDimension
: minDimension
- 1.0f
/ pixelDivisions
;
200 const Point centre
= PixelAlign(rcWhole
.Centre(), pixelDivisions
);
202 // Folder symbols and lines follow some rules to join up, fit the pixel grid,
203 // and avoid over-painting.
205 const XYPOSITION halfSymbol
= std::round(widthSymbol
/ 2);
206 const Point
topLeft(centre
.x
- halfSymbol
, centre
.y
- halfSymbol
);
207 const PRectangle
rcSymbol(
208 topLeft
.x
, topLeft
.y
,
209 topLeft
.x
+ widthSymbol
, topLeft
.y
+ widthSymbol
);
210 const XYPOSITION leftLine
= rcSymbol
.Centre().x
- widthStroke
/ 2.0f
;
211 const XYPOSITION rightLine
= leftLine
+ widthStroke
;
213 // This is the vertical line through the whole area which is subdivided
214 // when there is a symbol on the line or the colour changes for highlighting.
215 const PRectangle
rcVLine(leftLine
, rcWhole
.top
, rightLine
, rcWhole
.bottom
);
217 // Portions of rcVLine above and below the symbol.
218 const PRectangle rcAboveSymbol
= Clamp(rcVLine
, Edge::bottom
, rcSymbol
.top
);
219 const PRectangle rcBelowSymbol
= Clamp(rcVLine
, Edge::top
, rcSymbol
.bottom
);
221 // Projection to right.
222 const PRectangle
rcStick(
223 rcVLine
.right
, centre
.y
+ 1.0f
- widthStroke
,
224 rcWhole
.right
- 1, centre
.y
+ 1.0f
);
228 case MarkerSymbol::VLine
:
229 surface
->FillRectangle(rcVLine
, colourBody
);
232 case MarkerSymbol::LCorner
:
233 surface
->FillRectangle(Clamp(rcVLine
, Edge::bottom
, centre
.y
+ 1.0f
), colourTail
);
234 surface
->FillRectangle(rcStick
, colourTail
);
237 case MarkerSymbol::TCorner
:
238 surface
->FillRectangle(Clamp(rcVLine
, Edge::bottom
, centre
.y
+ 1.0f
), colourBody
);
239 surface
->FillRectangle(Clamp(rcVLine
, Edge::top
, centre
.y
+ 1.0f
), colourHead
);
240 surface
->FillRectangle(rcStick
, colourTail
);
243 // CORNERCURVE cases divide slightly lower than CORNER to accommodate the curve
244 case MarkerSymbol::LCornerCurve
:
245 surface
->FillRectangle(Clamp(rcVLine
, Edge::bottom
, centre
.y
), colourTail
);
246 DrawTail(surface
, leftLine
, rcWhole
.right
- 1.0f
, centre
.y
- widthStroke
,
247 widthStroke
, colourTail
);
250 case MarkerSymbol::TCornerCurve
:
251 surface
->FillRectangle(Clamp(rcVLine
, Edge::bottom
, centre
.y
), colourBody
);
252 surface
->FillRectangle(Clamp(rcVLine
, Edge::top
, centre
.y
), colourHead
);
253 DrawTail(surface
, leftLine
, rcWhole
.right
- 1.0f
, centre
.y
- widthStroke
,
254 widthStroke
, colourTail
);
257 case MarkerSymbol::BoxPlus
:
258 DrawSymbol(surface
, Shape::Square
, Expansion::Plus
, rcSymbol
, widthStroke
,
259 fore
, colourHead
, colourHead
, colourTail
);
262 case MarkerSymbol::BoxPlusConnected
: {
263 const ColourRGBA colourBelow
= (part
== FoldPart::headWithTail
) ? colourTail
: colourBody
;
264 surface
->FillRectangle(rcBelowSymbol
, colourBelow
);
265 surface
->FillRectangle(rcAboveSymbol
, colourBody
);
267 const ColourRGBA colourRight
= (part
== FoldPart::body
) ? colourTail
: colourHead
;
268 DrawSymbol(surface
, Shape::Square
, Expansion::Plus
, rcSymbol
, widthStroke
,
269 fore
, colourHead
, colourRight
, colourTail
);
273 case MarkerSymbol::BoxMinus
:
274 surface
->FillRectangle(rcBelowSymbol
, colourHead
);
275 DrawSymbol(surface
, Shape::Square
, Expansion::Minus
, rcSymbol
, widthStroke
,
276 fore
, colourHead
, colourHead
, colourTail
);
279 case MarkerSymbol::BoxMinusConnected
: {
280 surface
->FillRectangle(rcBelowSymbol
, colourHead
);
281 surface
->FillRectangle(rcAboveSymbol
, colourBody
);
283 const ColourRGBA colourRight
= (part
== FoldPart::body
) ? colourTail
: colourHead
;
284 DrawSymbol(surface
, Shape::Square
, Expansion::Minus
, rcSymbol
, widthStroke
,
285 fore
, colourHead
, colourRight
, colourTail
);
289 case MarkerSymbol::CirclePlus
:
290 DrawSymbol(surface
, Shape::Circle
, Expansion::Plus
, rcSymbol
, widthStroke
,
291 fore
, colourHead
, colourHead
, colourTail
);
294 case MarkerSymbol::CirclePlusConnected
: {
295 const ColourRGBA colourBelow
= (part
== FoldPart::headWithTail
) ? colourTail
: colourBody
;
296 surface
->FillRectangle(rcBelowSymbol
, colourBelow
);
297 surface
->FillRectangle(rcAboveSymbol
, colourBody
);
299 const ColourRGBA colourRight
= (part
== FoldPart::body
) ? colourTail
: colourHead
;
300 DrawSymbol(surface
, Shape::Circle
, Expansion::Plus
, rcSymbol
, widthStroke
,
301 fore
, colourHead
, colourRight
, colourTail
);
305 case MarkerSymbol::CircleMinus
:
306 surface
->FillRectangle(rcBelowSymbol
, colourHead
);
307 DrawSymbol(surface
, Shape::Circle
, Expansion::Minus
, rcSymbol
, widthStroke
,
308 fore
, colourHead
, colourHead
, colourTail
);
311 case MarkerSymbol::CircleMinusConnected
: {
312 surface
->FillRectangle(rcBelowSymbol
, colourHead
);
313 surface
->FillRectangle(rcAboveSymbol
, colourBody
);
314 const ColourRGBA colourRight
= (part
== FoldPart::body
) ? colourTail
: colourHead
;
315 DrawSymbol(surface
, Shape::Circle
, Expansion::Minus
, rcSymbol
, widthStroke
,
316 fore
, colourHead
, colourRight
, colourTail
);
326 void LineMarker::AlignedPolygon(Surface
*surface
, const Point
*pts
, size_t npts
) const {
327 const XYPOSITION move
= strokeWidth
/ 2.0;
328 std::vector
<Point
> points
;
329 std::transform(pts
, pts
+ npts
, std::back_inserter(points
), [=](Point pt
) noexcept
->Point
{
330 return Point(pt
.x
+ move
, pt
.y
+ move
);
332 surface
->Polygon(points
.data(), std::size(points
), FillStroke(back
, fore
, strokeWidth
));
335 void LineMarker::Draw(Surface
*surface
, const PRectangle
&rcWhole
, const Font
*fontForCharacter
, FoldPart part
, MarginType marginStyle
) const {
336 // This is to satisfy the changed API - eventually the stroke width will be exposed to clients
339 customDraw(surface
, rcWhole
, fontForCharacter
, static_cast<int>(part
), marginStyle
, this);
343 if ((markType
== MarkerSymbol::Pixmap
) && (pxpm
)) {
344 pxpm
->Draw(surface
, rcWhole
);
347 if ((markType
== MarkerSymbol::RgbaImage
) && (image
)) {
348 // Make rectangle just large enough to fit image centred on centre of rcWhole
350 rcImage
.top
= ((rcWhole
.top
+ rcWhole
.bottom
) - image
->GetScaledHeight()) / 2;
351 rcImage
.bottom
= rcImage
.top
+ image
->GetScaledHeight();
352 rcImage
.left
= ((rcWhole
.left
+ rcWhole
.right
) - image
->GetScaledWidth()) / 2;
353 rcImage
.right
= rcImage
.left
+ image
->GetScaledWidth();
354 surface
->DrawRGBAImage(rcImage
, image
->GetWidth(), image
->GetHeight(), image
->Pixels());
358 if ((markType
>= MarkerSymbol::VLine
) && markType
<= (MarkerSymbol::CircleMinusConnected
)) {
359 DrawFoldingMark(surface
, rcWhole
, part
);
363 // Restrict most shapes a bit
364 const PRectangle
rc(rcWhole
.left
, rcWhole
.top
+ 1, rcWhole
.right
, rcWhole
.bottom
- 1);
365 // Ensure does not go beyond edge
366 const XYPOSITION minDim
= std::min(rcWhole
.Width(), rcWhole
.Height() - 2) - 1;
368 const Point centre
= rcWhole
.Centre();
369 XYPOSITION centreX
= std::floor(centre
.x
);
370 const XYPOSITION centreY
= std::floor(centre
.y
);
371 const XYPOSITION dimOn2
= std::floor(minDim
/ 2);
372 const XYPOSITION dimOn4
= std::floor(minDim
/ 4);
373 const XYPOSITION armSize
= dimOn2
- 2;
374 if (marginStyle
== MarginType::Number
|| marginStyle
== MarginType::Text
|| marginStyle
== MarginType::RText
) {
375 // On textual margins move marker to the left to try to avoid overlapping the text
376 centreX
= rcWhole
.left
+ dimOn2
+ 1;
380 case MarkerSymbol::RoundRect
: {
381 PRectangle rcRounded
= rc
;
382 rcRounded
.left
= rc
.left
+ 1;
383 rcRounded
.right
= rc
.right
- 1;
384 surface
->RoundedRectangle(rcRounded
, FillStroke(back
, fore
, strokeWidth
));
388 case MarkerSymbol::Circle
: {
389 const PRectangle rcCircle
= PRectangle(
394 surface
->Ellipse(rcCircle
, FillStroke(back
, fore
, strokeWidth
));
398 case MarkerSymbol::Arrow
: {
399 const Point pts
[] = {
400 Point(centreX
- dimOn4
, centreY
- dimOn2
),
401 Point(centreX
- dimOn4
, centreY
+ dimOn2
),
402 Point(centreX
+ dimOn2
- dimOn4
, centreY
),
404 AlignedPolygon(surface
, pts
, std::size(pts
));
408 case MarkerSymbol::ArrowDown
: {
409 const Point pts
[] = {
410 Point(centreX
- dimOn2
, centreY
- dimOn4
),
411 Point(centreX
+ dimOn2
, centreY
- dimOn4
),
412 Point(centreX
, centreY
+ dimOn2
- dimOn4
),
414 AlignedPolygon(surface
, pts
, std::size(pts
));
418 case MarkerSymbol::Plus
: {
419 const Point pts
[] = {
420 Point(centreX
- armSize
, centreY
- 1),
421 Point(centreX
- 1, centreY
- 1),
422 Point(centreX
- 1, centreY
- armSize
),
423 Point(centreX
+ 1, centreY
- armSize
),
424 Point(centreX
+ 1, centreY
- 1),
425 Point(centreX
+ armSize
, centreY
- 1),
426 Point(centreX
+ armSize
, centreY
+ 1),
427 Point(centreX
+ 1, centreY
+ 1),
428 Point(centreX
+ 1, centreY
+ armSize
),
429 Point(centreX
- 1, centreY
+ armSize
),
430 Point(centreX
- 1, centreY
+ 1),
431 Point(centreX
- armSize
, centreY
+ 1),
433 AlignedPolygon(surface
, pts
, std::size(pts
));
437 case MarkerSymbol::Minus
: {
438 const Point pts
[] = {
439 Point(centreX
- armSize
, centreY
- 1),
440 Point(centreX
+ armSize
, centreY
- 1),
441 Point(centreX
+ armSize
, centreY
+ 1),
442 Point(centreX
- armSize
, centreY
+ 1),
444 AlignedPolygon(surface
, pts
, std::size(pts
));
448 case MarkerSymbol::SmallRect
: {
450 rcSmall
.left
= rc
.left
+ 1;
451 rcSmall
.top
= rc
.top
+ 2;
452 rcSmall
.right
= rc
.right
- 1;
453 rcSmall
.bottom
= rc
.bottom
- 2;
454 surface
->RectangleDraw(rcSmall
, FillStroke(back
, fore
, strokeWidth
));
458 case MarkerSymbol::Empty
:
459 case MarkerSymbol::Background
:
460 case MarkerSymbol::Underline
:
461 case MarkerSymbol::Available
:
462 // An invisible marker so don't draw anything
465 case MarkerSymbol::DotDotDot
: {
466 XYPOSITION right
= static_cast<XYPOSITION
>(centreX
- 6);
467 for (int b
= 0; b
< 3; b
++) {
468 const PRectangle
rcBlob(right
, rc
.bottom
- 4, right
+ 2, rc
.bottom
- 2);
469 surface
->FillRectangle(rcBlob
, fore
);
475 case MarkerSymbol::Arrows
: {
476 XYPOSITION right
= centreX
- 4.0f
+ strokeWidth
/ 2.0f
;
477 const XYPOSITION midY
= centreY
+ strokeWidth
/ 2.0f
;
478 const XYPOSITION armLength
= std::round(dimOn2
- strokeWidth
);
479 for (int b
= 0; b
< 3; b
++) {
480 const Point pts
[] = {
481 Point(right
- armLength
, midY
- armLength
),
483 Point(right
- armLength
, midY
+ armLength
)
485 surface
->PolyLine(pts
, std::size(pts
), Stroke(fore
, strokeWidth
));
486 right
+= strokeWidth
+ 3.0f
;
491 case MarkerSymbol::ShortArrow
: {
492 const Point pts
[] = {
493 Point(centreX
, centreY
+ dimOn2
),
494 Point(centreX
+ dimOn2
, centreY
),
495 Point(centreX
, centreY
- dimOn2
),
496 Point(centreX
, centreY
- dimOn4
),
497 Point(centreX
- dimOn4
, centreY
- dimOn4
),
498 Point(centreX
- dimOn4
, centreY
+ dimOn4
),
499 Point(centreX
, centreY
+ dimOn4
),
500 Point(centreX
, centreY
+ dimOn2
),
502 AlignedPolygon(surface
, pts
, std::size(pts
));
506 case MarkerSymbol::FullRect
:
507 surface
->FillRectangle(rcWhole
, back
);
510 case MarkerSymbol::LeftRect
: {
511 PRectangle rcLeft
= rcWhole
;
512 rcLeft
.right
= rcLeft
.left
+ 4;
513 surface
->FillRectangle(rcLeft
, back
);
517 case MarkerSymbol::Bar
: {
518 PRectangle rcBar
= rcWhole
;
519 const XYPOSITION widthBar
= std::floor(rcWhole
.Width() / 3.0);
520 rcBar
.left
= centreX
- std::floor(widthBar
/ 2.0);
521 rcBar
.right
= rcBar
.left
+ widthBar
;
522 surface
->SetClip(rcWhole
);
524 case LineMarker::FoldPart::headWithTail
:
525 surface
->RectangleDraw(rcBar
, FillStroke(back
, fore
, strokeWidth
));
527 case LineMarker::FoldPart::head
:
529 surface
->RectangleDraw(rcBar
, FillStroke(back
, fore
, strokeWidth
));
531 case LineMarker::FoldPart::tail
:
533 surface
->RectangleDraw(rcBar
, FillStroke(back
, fore
, strokeWidth
));
535 case LineMarker::FoldPart::body
:
538 surface
->RectangleDraw(rcBar
, FillStroke(back
, fore
, strokeWidth
));
548 case MarkerSymbol::Bookmark
: {
549 const XYPOSITION halfHeight
= std::floor(minDim
/ 3);
550 const Point pts
[] = {
551 Point(rcWhole
.left
, centreY
- halfHeight
),
552 Point(rcWhole
.right
- strokeWidth
- 2, centreY
- halfHeight
),
553 Point(rcWhole
.right
- strokeWidth
- 2 - halfHeight
, centreY
),
554 Point(rcWhole
.right
- strokeWidth
- 2, centreY
+ halfHeight
),
555 Point(rcWhole
.left
, centreY
+ halfHeight
),
557 AlignedPolygon(surface
, pts
, std::size(pts
));
561 case MarkerSymbol::VerticalBookmark
: {
562 const XYPOSITION halfWidth
= std::floor(minDim
/ 3);
563 const Point pts
[] = {
564 Point(centreX
- halfWidth
, centreY
- dimOn2
),
565 Point(centreX
+ halfWidth
, centreY
- dimOn2
),
566 Point(centreX
+ halfWidth
, centreY
+ dimOn2
),
567 Point(centreX
, centreY
+ dimOn2
- halfWidth
),
568 Point(centreX
- halfWidth
, centreY
+ dimOn2
),
570 AlignedPolygon(surface
, pts
, std::size(pts
));
575 if (markType
>= MarkerSymbol::Character
) {
576 char character
[UTF8MaxBytes
+ 1] {};
577 const int uch
= static_cast<int>(markType
) - static_cast<int>(MarkerSymbol::Character
);
578 UTF8FromUTF32Character(uch
, character
);
579 const XYPOSITION width
= surface
->WidthTextUTF8(fontForCharacter
, character
);
580 PRectangle rcText
= rc
;
581 rcText
.left
+= (rc
.Width() - width
) / 2;
582 rcText
.right
= rcText
.left
+ width
;
583 surface
->DrawTextNoClipUTF8(rcText
, fontForCharacter
, rcText
.bottom
- 2,
584 character
, fore
, back
);
586 // treat as MarkerSymbol::FullRect
587 surface
->FillRectangle(rcWhole
, back
);