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 <[email protected]> 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 <algorithm> 15 #include <memory> 16 17 #include "Platform.h" 18 19 #include "Scintilla.h" 20 #include "IntegerRectangle.h" 21 #include "Indicator.h" 22 #include "XPM.h" 23 24 using namespace Scintilla; 25 26 static PRectangle PixelGridAlign(const PRectangle &rc) noexcept { 27 // Move left and right side to nearest pixel to avoid blurry visuals 28 return PRectangle(std::round(rc.left), std::floor(rc.top), 29 std::round(rc.right), std::floor(rc.bottom)); 30 } 31 32 void Indicator::Draw(Surface *surface, const PRectangle &rc, const PRectangle &rcLine, const PRectangle &rcCharacter, State state, int value) const { 33 StyleAndColour sacDraw = sacNormal; 34 if (Flags() & SC_INDICFLAG_VALUEFORE) { 35 sacDraw.fore = ColourDesired(value & SC_INDICVALUEMASK); 36 } 37 if (state == State::hover) { 38 sacDraw = sacHover; 39 } 40 const IntegerRectangle irc(rc); 41 surface->PenColour(sacDraw.fore); 42 const int ymid = (irc.bottom + irc.top) / 2; 43 44 switch (sacDraw.style) { 45 case INDIC_SQUIGGLE: { 46 const IntegerRectangle ircSquiggle(PixelGridAlign(rc)); 47 int x = ircSquiggle.left; 48 const int xLast = ircSquiggle.right; 49 int y = 0; 50 surface->MoveTo(x, irc.top + y); 51 while (x < xLast) { 52 if ((x + 2) > xLast) { 53 y = 1; 54 x = xLast; 55 } else { 56 x += 2; 57 y = 2 - y; 58 } 59 surface->LineTo(x, irc.top + y); 60 } 61 } 62 break; 63 64 case INDIC_SQUIGGLEPIXMAP: { 65 const PRectangle rcSquiggle = PixelGridAlign(rc); 66 67 const int width = std::min(4000, static_cast<int>(rcSquiggle.Width())); 68 RGBAImage image(width, 3, 1.0, nullptr); 69 enum { alphaFull = 0xff, alphaSide = 0x2f, alphaSide2=0x5f }; 70 for (int x = 0; x < width; x++) { 71 if (x%2) { 72 // Two halfway columns have a full pixel in middle flanked by light pixels 73 image.SetPixel(x, 0, sacDraw.fore, alphaSide); 74 image.SetPixel(x, 1, sacDraw.fore, alphaFull); 75 image.SetPixel(x, 2, sacDraw.fore, alphaSide); 76 } else { 77 // Extreme columns have a full pixel at bottom or top and a mid-tone pixel in centre 78 image.SetPixel(x, (x % 4) ? 0 : 2, sacDraw.fore, alphaFull); 79 image.SetPixel(x, 1, sacDraw.fore, alphaSide2); 80 } 81 } 82 surface->DrawRGBAImage(rcSquiggle, image.GetWidth(), image.GetHeight(), image.Pixels()); 83 } 84 break; 85 86 case INDIC_SQUIGGLELOW: { 87 surface->MoveTo(irc.left, irc.top); 88 int x = irc.left + 3; 89 int y = 0; 90 while (x < rc.right) { 91 surface->LineTo(x - 1, irc.top + y); 92 y = 1 - y; 93 surface->LineTo(x, irc.top + y); 94 x += 3; 95 } 96 surface->LineTo(irc.right, irc.top + y); // Finish the line 97 } 98 break; 99 100 case INDIC_TT: { 101 surface->MoveTo(irc.left, ymid); 102 int x = irc.left + 5; 103 while (x < rc.right) { 104 surface->LineTo(x, ymid); 105 surface->MoveTo(x-3, ymid); 106 surface->LineTo(x-3, ymid+2); 107 x++; 108 surface->MoveTo(x, ymid); 109 x += 5; 110 } 111 surface->LineTo(irc.right, ymid); // Finish the line 112 if (x - 3 <= rc.right) { 113 surface->MoveTo(x-3, ymid); 114 surface->LineTo(x-3, ymid+2); 115 } 116 } 117 break; 118 119 case INDIC_DIAGONAL: { 120 int x = irc.left; 121 while (x < rc.right) { 122 surface->MoveTo(x, irc.top + 2); 123 int endX = x+3; 124 int endY = irc.top - 1; 125 if (endX > rc.right) { 126 endY += endX - irc.right; 127 endX = irc.right; 128 } 129 surface->LineTo(endX, endY); 130 x += 4; 131 } 132 } 133 break; 134 135 case INDIC_STRIKE: { 136 surface->MoveTo(irc.left, irc.top - 4); 137 surface->LineTo(irc.right, irc.top - 4); 138 } 139 break; 140 141 case INDIC_HIDDEN: 142 case INDIC_TEXTFORE: 143 // Draw nothing 144 break; 145 146 case INDIC_BOX: { 147 surface->MoveTo(irc.left, ymid + 1); 148 surface->LineTo(irc.right, ymid + 1); 149 const int lineTop = static_cast<int>(rcLine.top) + 1; 150 surface->LineTo(irc.right, lineTop); 151 surface->LineTo(irc.left, lineTop); 152 surface->LineTo(irc.left, ymid + 1); 153 } 154 break; 155 156 case INDIC_ROUNDBOX: 157 case INDIC_STRAIGHTBOX: 158 case INDIC_FULLBOX: { 159 PRectangle rcBox = rcLine; 160 if (sacDraw.style != INDIC_FULLBOX) 161 rcBox.top = rcLine.top + 1; 162 rcBox.left = rc.left; 163 rcBox.right = rc.right; 164 surface->AlphaRectangle(rcBox, (sacDraw.style == INDIC_ROUNDBOX) ? 1 : 0, 165 sacDraw.fore, fillAlpha, sacDraw.fore, outlineAlpha, 0); 166 } 167 break; 168 169 case INDIC_GRADIENT: 170 case INDIC_GRADIENTCENTRE: { 171 PRectangle rcBox = rc; 172 rcBox.top = rcLine.top + 1; 173 rcBox.bottom = rcLine.bottom; 174 const Surface::GradientOptions options = Surface::GradientOptions::topToBottom; 175 const ColourAlpha start(sacDraw.fore, fillAlpha); 176 const ColourAlpha end(sacDraw.fore, 0); 177 std::vector<ColourStop> stops; 178 switch (sacDraw.style) { 179 case INDIC_GRADIENT: 180 stops.push_back(ColourStop(0.0, start)); 181 stops.push_back(ColourStop(1.0, end)); 182 break; 183 case INDIC_GRADIENTCENTRE: 184 stops.push_back(ColourStop(0.0, end)); 185 stops.push_back(ColourStop(0.5, start)); 186 stops.push_back(ColourStop(1.0, end)); 187 break; 188 } 189 surface->GradientRectangle(rcBox, stops, options); 190 } 191 break; 192 193 case INDIC_DOTBOX: { 194 PRectangle rcBox = PixelGridAlign(rc); 195 rcBox.top = rcLine.top + 1; 196 rcBox.bottom = rcLine.bottom; 197 const IntegerRectangle ircBox(rcBox); 198 // Cap width at 4000 to avoid large allocations when mistakes made 199 const int width = std::min(ircBox.Width(), 4000); 200 RGBAImage image(width, ircBox.Height(), 1.0, nullptr); 201 // Draw horizontal lines top and bottom 202 for (int x=0; x<width; x++) { 203 for (int y = 0; y<ircBox.Height(); y += ircBox.Height() - 1) { 204 image.SetPixel(x, y, sacDraw.fore, ((x + y) % 2) ? outlineAlpha : fillAlpha); 205 } 206 } 207 // Draw vertical lines left and right 208 for (int y = 1; y<ircBox.Height(); y++) { 209 for (int x=0; x<width; x += width-1) { 210 image.SetPixel(x, y, sacDraw.fore, ((x + y) % 2) ? outlineAlpha : fillAlpha); 211 } 212 } 213 surface->DrawRGBAImage(rcBox, image.GetWidth(), image.GetHeight(), image.Pixels()); 214 } 215 break; 216 217 case INDIC_DASH: { 218 int x = irc.left; 219 while (x < rc.right) { 220 surface->MoveTo(x, ymid); 221 surface->LineTo(std::min(x + 4, irc.right), ymid); 222 x += 7; 223 } 224 } 225 break; 226 227 case INDIC_DOTS: { 228 int x = irc.left; 229 while (x < irc.right) { 230 const PRectangle rcDot = PRectangle::FromInts(x, ymid, x + 1, ymid + 1); 231 surface->FillRectangle(rcDot, sacDraw.fore); 232 x += 2; 233 } 234 } 235 break; 236 237 case INDIC_COMPOSITIONTHICK: { 238 const PRectangle rcComposition(rc.left+1, rcLine.bottom-2, rc.right-1, rcLine.bottom); 239 surface->FillRectangle(rcComposition, sacDraw.fore); 240 } 241 break; 242 243 case INDIC_COMPOSITIONTHIN: { 244 const PRectangle rcComposition(rc.left+1, rcLine.bottom-2, rc.right-1, rcLine.bottom-1); 245 surface->FillRectangle(rcComposition, sacDraw.fore); 246 } 247 break; 248 249 case INDIC_POINT: 250 case INDIC_POINTCHARACTER: 251 if (rcCharacter.Width() >= 0.1) { 252 const XYPOSITION pixelHeight = std::floor(rc.Height() - 1.0f); // 1 pixel onto next line if multiphase 253 const XYPOSITION x = (sacDraw.style == INDIC_POINT) ? (rcCharacter.left) : ((rcCharacter.right + rcCharacter.left) / 2); 254 const XYPOSITION ix = std::round(x); 255 const XYPOSITION iy = std::floor(rc.top + 1.0f); 256 Point pts[] = { 257 Point(ix - pixelHeight, iy + pixelHeight), // Left 258 Point(ix + pixelHeight, iy + pixelHeight), // Right 259 Point(ix, iy) // Top 260 }; 261 surface->Polygon(pts, std::size(pts), sacDraw.fore, sacDraw.fore); 262 } 263 break; 264 265 default: 266 // Either INDIC_PLAIN or unknown 267 surface->MoveTo(irc.left, ymid); 268 surface->LineTo(irc.right, ymid); 269 } 270 } 271 272 void Indicator::SetFlags(int attributes_) noexcept { 273 attributes = attributes_; 274 } 275