1 // Scintilla source code edit control
2 /** @file Indicator.cxx
3 ** Defines the style of indicators which are text decorations such as underlining.
5 // Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
11 #include <string_view>
18 #include "ScintillaTypes.h"
20 #include "Debugging.h"
24 #include "Indicator.h"
27 using namespace Scintilla
;
28 using namespace Scintilla::Internal
;
30 void Indicator::Draw(Surface
*surface
, const PRectangle
&rc
, const PRectangle
&rcLine
, const PRectangle
&rcCharacter
, State state
, int value
) const {
31 StyleAndColour sacDraw
= sacNormal
;
32 if (FlagSet(Flags(), IndicFlag::ValueFore
)) {
33 sacDraw
.fore
= ColourRGBA::FromRGB(value
& static_cast<int>(IndicValue::Mask
));
35 if (state
== State::hover
) {
39 const int pixelDivisions
= surface
->PixelDivisions();
41 const XYPOSITION halfWidth
= strokeWidth
/ 2.0f
;
43 const PRectangle
rcAligned(PixelAlignOutside(rc
, pixelDivisions
));
44 PRectangle rcFullHeightAligned
= PixelAlignOutside(rcLine
, pixelDivisions
);
45 rcFullHeightAligned
.left
= rcAligned
.left
;
46 rcFullHeightAligned
.right
= rcAligned
.right
;
48 const XYPOSITION ymid
= PixelAlign(rc
.Centre().y
, pixelDivisions
);
50 // This is a reasonable clip for indicators beneath text like underlines
51 PRectangle rcClip
= rcAligned
;
52 rcClip
.bottom
= rcFullHeightAligned
.bottom
;
54 switch (sacDraw
.style
) {
55 case IndicatorStyle::Squiggle
: {
56 surface
->SetClip(rcClip
);
57 XYPOSITION x
= rcAligned
.left
+ halfWidth
;
58 const XYPOSITION top
= rcAligned
.top
+ halfWidth
;
59 const XYPOSITION xLast
= rcAligned
.right
+ halfWidth
;
61 std::vector
<Point
> pts
;
62 const XYPOSITION pitch
= 1 + strokeWidth
;
63 pts
.emplace_back(x
, top
+ y
);
67 pts
.emplace_back(x
, top
+ y
);
69 surface
->PolyLine(pts
.data(), std::size(pts
), Stroke(sacDraw
.fore
, strokeWidth
));
74 case IndicatorStyle::SquigglePixmap
: {
75 const PRectangle rcSquiggle
= PixelAlign(rc
, 1);
77 const int width
= std::min(4000, static_cast<int>(rcSquiggle
.Width()));
78 RGBAImage
image(width
, 3, 1.0, nullptr);
79 constexpr unsigned int alphaFull
= 0xff;
80 constexpr unsigned int alphaSide
= 0x2f;
81 constexpr unsigned int alphaSide2
= 0x5f;
82 for (int x
= 0; x
< width
; x
++) {
84 // Two halfway columns have a full pixel in middle flanked by light pixels
85 image
.SetPixel(x
, 0, ColourRGBA(sacDraw
.fore
, alphaSide
));
86 image
.SetPixel(x
, 1, ColourRGBA(sacDraw
.fore
, alphaFull
));
87 image
.SetPixel(x
, 2, ColourRGBA(sacDraw
.fore
, alphaSide
));
89 // Extreme columns have a full pixel at bottom or top and a mid-tone pixel in centre
90 image
.SetPixel(x
, (x
% 4) ? 0 : 2, ColourRGBA(sacDraw
.fore
, alphaFull
));
91 image
.SetPixel(x
, 1, ColourRGBA(sacDraw
.fore
, alphaSide2
));
94 surface
->DrawRGBAImage(rcSquiggle
, image
.GetWidth(), image
.GetHeight(), image
.Pixels());
98 case IndicatorStyle::SquiggleLow
: {
99 std::vector
<Point
> pts
;
100 const XYPOSITION top
= rcAligned
.top
+ halfWidth
;
102 XYPOSITION x
= std::round(rcAligned
.left
) + halfWidth
;
103 pts
.emplace_back(x
, top
+ y
);
104 const XYPOSITION pitch
= 2 + strokeWidth
;
106 while (x
< rcAligned
.right
) {
107 pts
.emplace_back(x
- 1, top
+ y
);
109 pts
.emplace_back(x
, top
+ y
);
112 pts
.emplace_back(rcAligned
.right
, top
+ y
);
113 surface
->PolyLine(pts
.data(), std::size(pts
), Stroke(sacDraw
.fore
, strokeWidth
));
117 case IndicatorStyle::TT
: {
118 surface
->SetClip(rcClip
);
119 const XYPOSITION yLine
= ymid
;
120 XYPOSITION x
= rcAligned
.left
+ 5.0f
;
121 const XYPOSITION pitch
= 4 + strokeWidth
;
122 while (x
< rc
.right
+ pitch
) {
123 const PRectangle
line(x
-pitch
, yLine
, x
, yLine
+ strokeWidth
);
124 surface
->FillRectangle(line
, sacDraw
.fore
);
125 const PRectangle
tail(x
- 2 - strokeWidth
, yLine
+ strokeWidth
, x
- 2, yLine
+ strokeWidth
* 2);
126 surface
->FillRectangle(tail
, sacDraw
.fore
);
134 case IndicatorStyle::Diagonal
: {
135 surface
->SetClip(rcClip
);
136 XYPOSITION x
= rcAligned
.left
+ halfWidth
;
137 const XYPOSITION top
= rcAligned
.top
+ halfWidth
;
138 const XYPOSITION pitch
= 3 + strokeWidth
;
139 while (x
< rc
.right
) {
140 const XYPOSITION endX
= x
+3;
141 const XYPOSITION endY
= top
- 1;
142 surface
->LineDraw(Point(x
, top
+ 2), Point(endX
, endY
), Stroke(sacDraw
.fore
, strokeWidth
));
149 case IndicatorStyle::Strike
: {
150 const XYPOSITION yStrike
= std::round(rcLine
.Centre().y
);
151 const PRectangle
rcStrike(
152 rcAligned
.left
, yStrike
, rcAligned
.right
, yStrike
+ strokeWidth
);
153 surface
->FillRectangle(rcStrike
, sacDraw
.fore
);
157 case IndicatorStyle::Hidden
:
158 case IndicatorStyle::TextFore
:
162 case IndicatorStyle::Box
: {
163 PRectangle rcBox
= rcFullHeightAligned
;
164 rcBox
.top
= rcBox
.top
+ 1.0f
;
165 rcBox
.bottom
= ymid
+ 1.0f
;
166 surface
->RectangleFrame(rcBox
, Stroke(ColourRGBA(sacDraw
.fore
, outlineAlpha
), strokeWidth
));
170 case IndicatorStyle::RoundBox
:
171 case IndicatorStyle::StraightBox
:
172 case IndicatorStyle::FullBox
: {
173 PRectangle rcBox
= rcFullHeightAligned
;
174 if (sacDraw
.style
!= IndicatorStyle::FullBox
)
175 rcBox
.top
= rcBox
.top
+ 1;
176 surface
->AlphaRectangle(rcBox
, (sacDraw
.style
== IndicatorStyle::RoundBox
) ? 1.0f
: 0.0f
,
177 FillStroke(ColourRGBA(sacDraw
.fore
, fillAlpha
), ColourRGBA(sacDraw
.fore
, outlineAlpha
), strokeWidth
));
181 case IndicatorStyle::Gradient
:
182 case IndicatorStyle::GradientCentre
: {
183 PRectangle rcBox
= rcFullHeightAligned
;
184 rcBox
.top
= rcBox
.top
+ 1;
185 const Surface::GradientOptions options
= Surface::GradientOptions::topToBottom
;
186 const ColourRGBA
start(sacDraw
.fore
, fillAlpha
);
187 const ColourRGBA
end(sacDraw
.fore
, 0);
188 std::vector
<ColourStop
> stops
;
189 switch (sacDraw
.style
) {
190 case IndicatorStyle::Gradient
:
191 stops
.push_back(ColourStop(0.0, start
));
192 stops
.push_back(ColourStop(1.0, end
));
194 case IndicatorStyle::GradientCentre
:
195 stops
.push_back(ColourStop(0.0, end
));
196 stops
.push_back(ColourStop(0.5, start
));
197 stops
.push_back(ColourStop(1.0, end
));
202 surface
->GradientRectangle(rcBox
, stops
, options
);
206 case IndicatorStyle::DotBox
: {
207 PRectangle rcBox
= rcFullHeightAligned
;
208 rcBox
.top
= rcBox
.top
+ 1;
209 // Cap width at 4000 to avoid large allocations when mistakes made
210 const int width
= std::min(static_cast<int>(rcBox
.Width()), 4000);
211 const int height
= static_cast<int>(rcBox
.Height());
212 RGBAImage
image(width
, height
, 1.0, nullptr);
213 // Draw horizontal lines top and bottom
214 for (int x
=0; x
<width
; x
++) {
215 for (int y
= 0; y
< height
; y
+= height
- 1) {
216 image
.SetPixel(x
, y
, ColourRGBA(sacDraw
.fore
, ((x
+ y
) % 2) ? outlineAlpha
: fillAlpha
));
219 // Draw vertical lines left and right
220 for (int y
= 1; y
<height
; y
++) {
221 for (int x
=0; x
<width
; x
+= width
-1) {
222 image
.SetPixel(x
, y
, ColourRGBA(sacDraw
.fore
, ((x
+ y
) % 2) ? outlineAlpha
: fillAlpha
));
225 surface
->DrawRGBAImage(rcBox
, image
.GetWidth(), image
.GetHeight(), image
.Pixels());
229 case IndicatorStyle::Dash
: {
230 XYPOSITION x
= std::floor(rc
.left
);
231 const XYPOSITION widthDash
= 3 + std::round(strokeWidth
);
232 while (x
< rc
.right
) {
233 const PRectangle rcDash
= PRectangle(x
, ymid
,
234 x
+ widthDash
, ymid
+ std::round(strokeWidth
));
235 surface
->FillRectangle(rcDash
, sacDraw
.fore
);
241 case IndicatorStyle::Dots
: {
242 const XYPOSITION widthDot
= std::round(strokeWidth
);
243 XYPOSITION x
= std::floor(rc
.left
);
244 while (x
< rc
.right
) {
245 const PRectangle rcDot
= PRectangle(x
, ymid
,
246 x
+ widthDot
, ymid
+ widthDot
);
247 surface
->FillRectangle(rcDot
, sacDraw
.fore
);
253 case IndicatorStyle::CompositionThick
: {
254 const PRectangle
rcComposition(rc
.left
+1, rcLine
.bottom
-2, rc
.right
-1, rcLine
.bottom
);
255 surface
->FillRectangle(rcComposition
, ColourRGBA(sacDraw
.fore
, outlineAlpha
));
259 case IndicatorStyle::CompositionThin
: {
260 const PRectangle
rcComposition(rc
.left
+1, rcLine
.bottom
-2, rc
.right
-1, rcLine
.bottom
-1);
261 surface
->FillRectangle(rcComposition
, sacDraw
.fore
);
265 case IndicatorStyle::Point
:
266 case IndicatorStyle::PointCharacter
:
267 if (rcCharacter
.Width() >= 0.1) {
268 const XYPOSITION pixelHeight
= std::floor(rc
.Height()); // 1 pixel onto next line if multiphase
269 const XYPOSITION x
= (sacDraw
.style
== IndicatorStyle::Point
) ? (rcCharacter
.left
) : ((rcCharacter
.right
+ rcCharacter
.left
) / 2);
270 // 0.5f is to hit midpoint of pixels:
271 const XYPOSITION ix
= std::round(x
) + 0.5f
;
272 const XYPOSITION iy
= std::ceil(rc
.bottom
) + 0.5f
;
273 const Point pts
[] = {
274 Point(ix
- pixelHeight
, iy
), // Left
275 Point(ix
+ pixelHeight
, iy
), // Right
276 Point(ix
, iy
- pixelHeight
) // Top
278 surface
->Polygon(pts
, std::size(pts
), FillStroke(sacDraw
.fore
));
282 case IndicatorStyle::PointTop
:
283 if (rcCharacter
.Width() >= 0.1) {
284 const XYPOSITION pixelHeight
= std::floor(rc
.Height()); // 1 pixel onto previous line if multiphase
285 const XYPOSITION x
= rcCharacter
.left
;
286 // 0.5f is to hit midpoint of pixels:
287 const XYPOSITION ix
= std::round(x
) + 0.5f
;
288 const XYPOSITION iy
= std::floor(rcLine
.top
) - 0.5f
;
289 const Point pts
[] = {
290 Point(ix
- pixelHeight
, iy
), // Left
291 Point(ix
+ pixelHeight
, iy
), // Right
292 Point(ix
, iy
+ pixelHeight
) // Bottom
294 surface
->Polygon(pts
, std::size(pts
), FillStroke(sacDraw
.fore
));
299 // Either IndicatorStyle::Plain or unknown
300 surface
->FillRectangle(PRectangle(rcAligned
.left
, ymid
,
301 rcAligned
.right
, ymid
+ std::round(strokeWidth
)), sacDraw
.fore
);
305 void Indicator::SetFlags(IndicFlag attributes_
) noexcept
{
306 attributes
= attributes_
;