Update Russian translation (#3918)
[geany-mirror.git] / scintilla / src / LineMarker.cxx
blobc13b2495d9e19b46f2d044d8c27db778494a639c
1 // Scintilla source code edit control
2 /** @file LineMarker.cxx
3 ** Defines the look of a line marker in the margin.
4 **/
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.
8 #include <cstring>
9 #include <cmath>
11 #include <stdexcept>
12 #include <string>
13 #include <string_view>
14 #include <vector>
15 #include <map>
16 #include <optional>
17 #include <algorithm>
18 #include <iterator>
19 #include <memory>
21 #include "ScintillaTypes.h"
23 #include "Debugging.h"
24 #include "Geometry.h"
26 #include "Platform.h"
28 #include "XPM.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;
38 fore = other.fore;
39 back = other.back;
40 backSelected = other.backSelected;
41 strokeWidth = other.strokeWidth;
42 layer = other.layer;
43 alpha = other.alpha;
44 if (other.pxpm)
45 pxpm = std::make_unique<XPM>(*other.pxpm);
46 else
47 pxpm = nullptr;
48 if (other.image)
49 image = std::make_unique<RGBAImage>(*other.image);
50 else
51 image = nullptr;
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.
57 if (this != &other) {
58 markType = other.markType;
59 fore = other.fore;
60 back = other.back;
61 backSelected = other.backSelected;
62 strokeWidth = other.strokeWidth;
63 layer = other.layer;
64 alpha = other.alpha;
65 if (other.pxpm)
66 pxpm = std::make_unique<XPM>(*other.pxpm);
67 else
68 pxpm = nullptr;
69 if (other.image)
70 image = std::make_unique<RGBAImage>(*other.image);
71 else
72 image = nullptr;
73 customDraw = other.customDraw;
75 return *this;
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;
97 namespace {
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) {
109 // Hollowed square
110 surface->RectangleDraw(rcSymbol, fillStroke);
111 } else {
112 surface->Ellipse(rcSymbol, fillStroke);
114 surface->PopClip();
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);
121 } else {
122 surface->Ellipse(rcSymbol, fillStrokeRight);
124 surface->PopClip();
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[] = {
148 // Stick
149 Point(rightTail, strokeMiddle),
150 Point(leftLine + halfWidth + slopeLength, strokeMiddle),
151 // Slope
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;
167 switch (part) {
168 case FoldPart::head:
169 case FoldPart::headWithTail:
170 colourHead = backSelected;
171 colourTail = backSelected;
172 break;
173 case FoldPart::body:
174 colourHead = backSelected;
175 colourBody = backSelected;
176 break;
177 case FoldPart::tail:
178 colourBody = backSelected;
179 colourTail = backSelected;
180 break;
181 default:
182 // LineMarker::undefined
183 break;
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);
226 switch (markType) {
228 case MarkerSymbol::VLine:
229 surface->FillRectangle(rcVLine, colourBody);
230 break;
232 case MarkerSymbol::LCorner:
233 surface->FillRectangle(Clamp(rcVLine, Edge::bottom, centre.y + 1.0f), colourTail);
234 surface->FillRectangle(rcStick, colourTail);
235 break;
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);
241 break;
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);
248 break;
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);
255 break;
257 case MarkerSymbol::BoxPlus:
258 DrawSymbol(surface, Shape::Square, Expansion::Plus, rcSymbol, widthStroke,
259 fore, colourHead, colourHead, colourTail);
260 break;
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);
271 break;
273 case MarkerSymbol::BoxMinus:
274 surface->FillRectangle(rcBelowSymbol, colourHead);
275 DrawSymbol(surface, Shape::Square, Expansion::Minus, rcSymbol, widthStroke,
276 fore, colourHead, colourHead, colourTail);
277 break;
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);
287 break;
289 case MarkerSymbol::CirclePlus:
290 DrawSymbol(surface, Shape::Circle, Expansion::Plus, rcSymbol, widthStroke,
291 fore, colourHead, colourHead, colourTail);
292 break;
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);
303 break;
305 case MarkerSymbol::CircleMinus:
306 surface->FillRectangle(rcBelowSymbol, colourHead);
307 DrawSymbol(surface, Shape::Circle, Expansion::Minus, rcSymbol, widthStroke,
308 fore, colourHead, colourHead, colourTail);
309 break;
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);
318 break;
320 default:
321 break;
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
338 if (customDraw) {
339 customDraw(surface, rcWhole, fontForCharacter, static_cast<int>(part), marginStyle, this);
340 return;
343 if ((markType == MarkerSymbol::Pixmap) && (pxpm)) {
344 pxpm->Draw(surface, rcWhole);
345 return;
347 if ((markType == MarkerSymbol::RgbaImage) && (image)) {
348 // Make rectangle just large enough to fit image centred on centre of rcWhole
349 PRectangle rcImage;
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());
355 return;
358 if ((markType >= MarkerSymbol::VLine) && markType <= (MarkerSymbol::CircleMinusConnected)) {
359 DrawFoldingMark(surface, rcWhole, part);
360 return;
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;
379 switch (markType) {
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));
386 break;
388 case MarkerSymbol::Circle: {
389 const PRectangle rcCircle = PRectangle(
390 centreX - dimOn2,
391 centreY - dimOn2,
392 centreX + dimOn2,
393 centreY + dimOn2);
394 surface->Ellipse(rcCircle, FillStroke(back, fore, strokeWidth));
396 break;
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));
406 break;
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));
416 break;
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));
435 break;
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));
446 break;
448 case MarkerSymbol::SmallRect: {
449 PRectangle rcSmall;
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));
456 break;
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
463 break;
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);
470 right += 5.0f;
473 break;
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),
482 Point(right, midY),
483 Point(right - armLength, midY + armLength)
485 surface->PolyLine(pts, std::size(pts), Stroke(fore, strokeWidth));
486 right += strokeWidth + 3.0f;
489 break;
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));
504 break;
506 case MarkerSymbol::FullRect:
507 surface->FillRectangle(rcWhole, back);
508 break;
510 case MarkerSymbol::LeftRect: {
511 PRectangle rcLeft = rcWhole;
512 rcLeft.right = rcLeft.left + 4;
513 surface->FillRectangle(rcLeft, back);
515 break;
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);
523 switch (part) {
524 case LineMarker::FoldPart::headWithTail:
525 surface->RectangleDraw(rcBar, FillStroke(back, fore, strokeWidth));
526 break;
527 case LineMarker::FoldPart::head:
528 rcBar.bottom += 5;
529 surface->RectangleDraw(rcBar, FillStroke(back, fore, strokeWidth));
530 break;
531 case LineMarker::FoldPart::tail:
532 rcBar.top -= 5;
533 surface->RectangleDraw(rcBar, FillStroke(back, fore, strokeWidth));
534 break;
535 case LineMarker::FoldPart::body:
536 rcBar.top -= 5;
537 rcBar.bottom += 5;
538 surface->RectangleDraw(rcBar, FillStroke(back, fore, strokeWidth));
539 break;
540 default:
541 break;
543 surface->PopClip();
545 break;
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));
559 break;
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));
572 break;
574 default:
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);
585 } else {
586 // treat as MarkerSymbol::FullRect
587 surface->FillRectangle(rcWhole, back);
589 break;