1// Scintilla source code edit control
2/** @file Indicator.cxx
3 ** Defines the style of indicators which are text decorations such as underlining.
4 **/
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.
7
8#include <cmath>
9
10#include <stdexcept>
11#include <string_view>
12#include <vector>
13#include <map>
14#include <optional>
15#include <algorithm>
16#include <memory>
17
18#include "ScintillaTypes.h"
19
20#include "Debugging.h"
21#include "Geometry.h"
22#include "Platform.h"
23
24#include "Indicator.h"
25#include "XPM.h"
26
27using namespace Scintilla;
28using namespace Scintilla::Internal;
29
30void 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));
34 }
35 if (state == State::hover) {
36 sacDraw = sacHover;
37 }
38
39 const int pixelDivisions = surface->PixelDivisions();
40
41 const XYPOSITION halfWidth = strokeWidth / 2.0f;
42
43 const PRectangle rcAligned(PixelAlignOutside(rc, pixelDivisions));
44 PRectangle rcFullHeightAligned = PixelAlignOutside(rcLine, pixelDivisions);
45 rcFullHeightAligned.left = rcAligned.left;
46 rcFullHeightAligned.right = rcAligned.right;
47
48 const XYPOSITION ymid = PixelAlign(rc.Centre().y, pixelDivisions);
49
50 // This is a reasonable clip for indicators beneath text like underlines
51 PRectangle rcClip = rcAligned;
52 rcClip.bottom = rcFullHeightAligned.bottom;
53
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;
60 XYPOSITION y = 0;
61 std::vector<Point> pts;
62 const XYPOSITION pitch = 1 + strokeWidth;
63 pts.emplace_back(x, top + y);
64 while (x < xLast) {
65 x += pitch;
66 y = pitch - y;
67 pts.emplace_back(x, top + y);
68 }
69 surface->PolyLine(pts.data(), std::size(pts), Stroke(sacDraw.fore, strokeWidth));
70 surface->PopClip();
71 }
72 break;
73
74 case IndicatorStyle::SquigglePixmap: {
75 const PRectangle rcSquiggle = PixelAlign(rc, 1);
76
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++) {
83 if (x%2) {
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));
88 } else {
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));
92 }
93 }
94 surface->DrawRGBAImage(rcSquiggle, image.GetWidth(), image.GetHeight(), image.Pixels());
95 }
96 break;
97
98 case IndicatorStyle::SquiggleLow: {
99 std::vector<Point> pts;
100 const XYPOSITION top = rcAligned.top + halfWidth;
101 int y = 0;
102 XYPOSITION x = std::round(rcAligned.left) + halfWidth;
103 pts.emplace_back(x, top + y);
104 const XYPOSITION pitch = 2 + strokeWidth;
105 x += pitch;
106 while (x < rcAligned.right) {
107 pts.emplace_back(x - 1, top + y);
108 y = 1 - y;
109 pts.emplace_back(x, top + y);
110 x += pitch;
111 }
112 pts.emplace_back(rcAligned.right, top + y);
113 surface->PolyLine(pts.data(), std::size(pts), Stroke(sacDraw.fore, strokeWidth));
114 }
115 break;
116
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);
127 x++;
128 x += pitch;
129 }
130 surface->PopClip();
131 }
132 break;
133
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));
143 x += pitch;
144 }
145 surface->PopClip();
146 }
147 break;
148
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);
154 }
155 break;
156
157 case IndicatorStyle::Hidden:
158 case IndicatorStyle::TextFore:
159 // Draw nothing
160 break;
161
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));
167 }
168 break;
169
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));
178 }
179 break;
180
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));
193 break;
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));
198 break;
199 default:
200 break;
201 }
202 surface->GradientRectangle(rcBox, stops, options);
203 }
204 break;
205
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));
217 }
218 }
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));
223 }
224 }
225 surface->DrawRGBAImage(rcBox, image.GetWidth(), image.GetHeight(), image.Pixels());
226 }
227 break;
228
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);
236 x += 3 + widthDash;
237 }
238 }
239 break;
240
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);
248 x += widthDot * 2;
249 }
250 }
251 break;
252
253 case IndicatorStyle::CompositionThick: {
254 const PRectangle rcComposition(rc.left+1, rcLine.bottom-2, rc.right-1, rcLine.bottom);
255 surface->FillRectangle(rcComposition, sacDraw.fore);
256 }
257 break;
258
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);
262 }
263 break;
264
265 case IndicatorStyle::Point:
266 case IndicatorStyle::PointCharacter:
267 if (rcCharacter.Width() >= 0.1) {
268 const XYPOSITION pixelHeight = std::floor(rc.Height() - 1.0f); // 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::floor(rc.top + 1.0f) + 0.5f;
273 const Point pts[] = {
274 Point(ix - pixelHeight, iy + pixelHeight), // Left
275 Point(ix + pixelHeight, iy + pixelHeight), // Right
276 Point(ix, iy) // Top
277 };
278 surface->Polygon(pts, std::size(pts), FillStroke(sacDraw.fore));
279 }
280 break;
281
282 default:
283 // Either IndicatorStyle::Plain or unknown
284 surface->FillRectangle(PRectangle(rcAligned.left, ymid,
285 rcAligned.right, ymid + std::round(strokeWidth)), sacDraw.fore);
286 }
287}
288
289void Indicator::SetFlags(IndicFlag attributes_) noexcept {
290 attributes = attributes_;
291}
292