// Scintilla source code edit control /** @file Indicator.cxx ** Defines the style of indicators which are text decorations such as underlining. **/ // Copyright 1998-2001 by Neil Hodgson // The License.txt file describes the conditions under which this software may be distributed. #include #include #include #include #include #include #include #include #include #include "ScintillaTypes.h" #include "Debugging.h" #include "Geometry.h" #include "Platform.h" #include "Indicator.h" #include "XPM.h" using namespace Scintilla; using namespace Scintilla::Internal; void Indicator::Draw(Surface *surface, const PRectangle &rc, const PRectangle &rcLine, const PRectangle &rcCharacter, State state, int value) const { StyleAndColour sacDraw = sacNormal; if (FlagSet(Flags(), IndicFlag::ValueFore)) { sacDraw.fore = ColourRGBA::FromRGB(value & static_cast(IndicValue::Mask)); } if (state == State::hover) { sacDraw = sacHover; } const int pixelDivisions = surface->PixelDivisions(); const XYPOSITION halfWidth = strokeWidth / 2.0f; const PRectangle rcAligned(PixelAlignOutside(rc, pixelDivisions)); PRectangle rcFullHeightAligned = PixelAlignOutside(rcLine, pixelDivisions); rcFullHeightAligned.left = rcAligned.left; rcFullHeightAligned.right = rcAligned.right; const XYPOSITION ymid = PixelAlign(rc.Centre().y, pixelDivisions); // This is a reasonable clip for indicators beneath text like underlines PRectangle rcClip = rcAligned; rcClip.bottom = rcFullHeightAligned.bottom; switch (sacDraw.style) { case IndicatorStyle::Squiggle: { surface->SetClip(rcClip); XYPOSITION x = rcAligned.left + halfWidth; const XYPOSITION top = rcAligned.top + halfWidth; const XYPOSITION xLast = rcAligned.right + halfWidth; XYPOSITION y = 0; std::vector pts; const XYPOSITION pitch = 1 + strokeWidth; pts.emplace_back(x, top + y); while (x < xLast) { x += pitch; y = pitch - y; pts.emplace_back(x, top + y); } surface->PolyLine(pts.data(), std::size(pts), Stroke(sacDraw.fore, strokeWidth)); surface->PopClip(); } break; case IndicatorStyle::SquigglePixmap: { const PRectangle rcSquiggle = PixelAlign(rc, 1); const int width = std::min(4000, static_cast(rcSquiggle.Width())); RGBAImage image(width, 3, 1.0, nullptr); constexpr unsigned int alphaFull = 0xff; constexpr unsigned int alphaSide = 0x2f; constexpr unsigned int alphaSide2 = 0x5f; for (int x = 0; x < width; x++) { if (x%2) { // Two halfway columns have a full pixel in middle flanked by light pixels image.SetPixel(x, 0, ColourRGBA(sacDraw.fore, alphaSide)); image.SetPixel(x, 1, ColourRGBA(sacDraw.fore, alphaFull)); image.SetPixel(x, 2, ColourRGBA(sacDraw.fore, alphaSide)); } else { // Extreme columns have a full pixel at bottom or top and a mid-tone pixel in centre image.SetPixel(x, (x % 4) ? 0 : 2, ColourRGBA(sacDraw.fore, alphaFull)); image.SetPixel(x, 1, ColourRGBA(sacDraw.fore, alphaSide2)); } } surface->DrawRGBAImage(rcSquiggle, image.GetWidth(), image.GetHeight(), image.Pixels()); } break; case IndicatorStyle::SquiggleLow: { std::vector pts; const XYPOSITION top = rcAligned.top + halfWidth; int y = 0; XYPOSITION x = std::round(rcAligned.left) + halfWidth; pts.emplace_back(x, top + y); const XYPOSITION pitch = 2 + strokeWidth; x += pitch; while (x < rcAligned.right) { pts.emplace_back(x - 1, top + y); y = 1 - y; pts.emplace_back(x, top + y); x += pitch; } pts.emplace_back(rcAligned.right, top + y); surface->PolyLine(pts.data(), std::size(pts), Stroke(sacDraw.fore, strokeWidth)); } break; case IndicatorStyle::TT: { surface->SetClip(rcClip); const XYPOSITION yLine = ymid; XYPOSITION x = rcAligned.left + 5.0f; const XYPOSITION pitch = 4 + strokeWidth; while (x < rc.right + pitch) { const PRectangle line(x-pitch, yLine, x, yLine + strokeWidth); surface->FillRectangle(line, sacDraw.fore); const PRectangle tail(x - 2 - strokeWidth, yLine + strokeWidth, x - 2, yLine + strokeWidth * 2); surface->FillRectangle(tail, sacDraw.fore); x++; x += pitch; } surface->PopClip(); } break; case IndicatorStyle::Diagonal: { surface->SetClip(rcClip); XYPOSITION x = rcAligned.left + halfWidth; const XYPOSITION top = rcAligned.top + halfWidth; const XYPOSITION pitch = 3 + strokeWidth; while (x < rc.right) { const XYPOSITION endX = x+3; const XYPOSITION endY = top - 1; surface->LineDraw(Point(x, top + 2), Point(endX, endY), Stroke(sacDraw.fore, strokeWidth)); x += pitch; } surface->PopClip(); } break; case IndicatorStyle::Strike: { const XYPOSITION yStrike = std::round(rcLine.Centre().y); const PRectangle rcStrike( rcAligned.left, yStrike, rcAligned.right, yStrike + strokeWidth); surface->FillRectangle(rcStrike, sacDraw.fore); } break; case IndicatorStyle::Hidden: case IndicatorStyle::TextFore: // Draw nothing break; case IndicatorStyle::Box: { PRectangle rcBox = rcFullHeightAligned; rcBox.top = rcBox.top + 1.0f; rcBox.bottom = ymid + 1.0f; surface->RectangleFrame(rcBox, Stroke(ColourRGBA(sacDraw.fore, outlineAlpha), strokeWidth)); } break; case IndicatorStyle::RoundBox: case IndicatorStyle::StraightBox: case IndicatorStyle::FullBox: { PRectangle rcBox = rcFullHeightAligned; if (sacDraw.style != IndicatorStyle::FullBox) rcBox.top = rcBox.top + 1; surface->AlphaRectangle(rcBox, (sacDraw.style == IndicatorStyle::RoundBox) ? 1.0f : 0.0f, FillStroke(ColourRGBA(sacDraw.fore, fillAlpha), ColourRGBA(sacDraw.fore, outlineAlpha), strokeWidth)); } break; case IndicatorStyle::Gradient: case IndicatorStyle::GradientCentre: { PRectangle rcBox = rcFullHeightAligned; rcBox.top = rcBox.top + 1; const Surface::GradientOptions options = Surface::GradientOptions::topToBottom; const ColourRGBA start(sacDraw.fore, fillAlpha); const ColourRGBA end(sacDraw.fore, 0); std::vector stops; switch (sacDraw.style) { case IndicatorStyle::Gradient: stops.push_back(ColourStop(0.0, start)); stops.push_back(ColourStop(1.0, end)); break; case IndicatorStyle::GradientCentre: stops.push_back(ColourStop(0.0, end)); stops.push_back(ColourStop(0.5, start)); stops.push_back(ColourStop(1.0, end)); break; default: break; } surface->GradientRectangle(rcBox, stops, options); } break; case IndicatorStyle::DotBox: { PRectangle rcBox = rcFullHeightAligned; rcBox.top = rcBox.top + 1; // Cap width at 4000 to avoid large allocations when mistakes made const int width = std::min(static_cast(rcBox.Width()), 4000); const int height = static_cast(rcBox.Height()); RGBAImage image(width, height, 1.0, nullptr); // Draw horizontal lines top and bottom for (int x=0; xDrawRGBAImage(rcBox, image.GetWidth(), image.GetHeight(), image.Pixels()); } break; case IndicatorStyle::Dash: { XYPOSITION x = std::floor(rc.left); const XYPOSITION widthDash = 3 + std::round(strokeWidth); while (x < rc.right) { const PRectangle rcDash = PRectangle(x, ymid, x + widthDash, ymid + std::round(strokeWidth)); surface->FillRectangle(rcDash, sacDraw.fore); x += 3 + widthDash; } } break; case IndicatorStyle::Dots: { const XYPOSITION widthDot = std::round(strokeWidth); XYPOSITION x = std::floor(rc.left); while (x < rc.right) { const PRectangle rcDot = PRectangle(x, ymid, x + widthDot, ymid + widthDot); surface->FillRectangle(rcDot, sacDraw.fore); x += widthDot * 2; } } break; case IndicatorStyle::CompositionThick: { const PRectangle rcComposition(rc.left+1, rcLine.bottom-2, rc.right-1, rcLine.bottom); surface->FillRectangle(rcComposition, ColourRGBA(sacDraw.fore, outlineAlpha)); } break; case IndicatorStyle::CompositionThin: { const PRectangle rcComposition(rc.left+1, rcLine.bottom-2, rc.right-1, rcLine.bottom-1); surface->FillRectangle(rcComposition, sacDraw.fore); } break; case IndicatorStyle::Point: case IndicatorStyle::PointCharacter: if (rcCharacter.Width() >= 0.1) { const XYPOSITION pixelHeight = std::floor(rc.Height()); // 1 pixel onto next line if multiphase const XYPOSITION x = (sacDraw.style == IndicatorStyle::Point) ? (rcCharacter.left) : ((rcCharacter.right + rcCharacter.left) / 2); // 0.5f is to hit midpoint of pixels: const XYPOSITION ix = std::round(x) + 0.5f; const XYPOSITION iy = std::ceil(rc.bottom) + 0.5f; const Point pts[] = { Point(ix - pixelHeight, iy), // Left Point(ix + pixelHeight, iy), // Right Point(ix, iy - pixelHeight) // Top }; surface->Polygon(pts, std::size(pts), FillStroke(sacDraw.fore)); } break; case IndicatorStyle::PointTop: if (rcCharacter.Width() >= 0.1) { const XYPOSITION pixelHeight = std::floor(rc.Height()); // 1 pixel onto previous line if multiphase const XYPOSITION x = rcCharacter.left; // 0.5f is to hit midpoint of pixels: const XYPOSITION ix = std::round(x) + 0.5f; const XYPOSITION iy = std::floor(rcLine.top) - 0.5f; const Point pts[] = { Point(ix - pixelHeight, iy), // Left Point(ix + pixelHeight, iy), // Right Point(ix, iy + pixelHeight) // Bottom }; surface->Polygon(pts, std::size(pts), FillStroke(sacDraw.fore)); } break; default: // Either IndicatorStyle::Plain or unknown surface->FillRectangle(PRectangle(rcAligned.left, ymid, rcAligned.right, ymid + std::round(strokeWidth)), sacDraw.fore); } } void Indicator::SetFlags(IndicFlag attributes_) noexcept { attributes = attributes_; }