| 1 | // Scintilla source code edit control |
| 2 | /** @file ViewStyle.cxx |
| 3 | ** Store information on how the document is to be viewed. |
| 4 | **/ |
| 5 | // Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org> |
| 6 | // The License.txt file describes the conditions under which this software may be distributed. |
| 7 | |
| 8 | #include <cstddef> |
| 9 | #include <cassert> |
| 10 | #include <cstring> |
| 11 | #include <cmath> |
| 12 | |
| 13 | #include <stdexcept> |
| 14 | #include <string> |
| 15 | #include <string_view> |
| 16 | #include <vector> |
| 17 | #include <array> |
| 18 | #include <map> |
| 19 | #include <set> |
| 20 | #include <optional> |
| 21 | #include <algorithm> |
| 22 | #include <memory> |
| 23 | #include <numeric> |
| 24 | |
| 25 | #include "ScintillaTypes.h" |
| 26 | |
| 27 | #include "Debugging.h" |
| 28 | #include "Geometry.h" |
| 29 | #include "Platform.h" |
| 30 | |
| 31 | #include "Position.h" |
| 32 | #include "UniqueString.h" |
| 33 | #include "Indicator.h" |
| 34 | #include "XPM.h" |
| 35 | #include "LineMarker.h" |
| 36 | #include "Style.h" |
| 37 | #include "ViewStyle.h" |
| 38 | |
| 39 | using namespace Scintilla; |
| 40 | using namespace Scintilla::Internal; |
| 41 | |
| 42 | MarginStyle::MarginStyle(MarginType style_, int width_, int mask_) noexcept : |
| 43 | style(style_), width(width_), mask(mask_), sensitive(false), cursor(CursorShape::ReverseArrow) { |
| 44 | } |
| 45 | |
| 46 | bool MarginStyle::ShowsFolding() const noexcept { |
| 47 | return (mask & MaskFolders) != 0; |
| 48 | } |
| 49 | |
| 50 | void FontRealised::Realise(Surface &surface, int zoomLevel, Technology technology, const FontSpecification &fs, const char *localeName) { |
| 51 | PLATFORM_ASSERT(fs.fontName); |
| 52 | measurements.sizeZoomed = fs.size + zoomLevel * FontSizeMultiplier; |
| 53 | if (measurements.sizeZoomed <= FontSizeMultiplier) // May fail if sizeZoomed < 1 |
| 54 | measurements.sizeZoomed = FontSizeMultiplier; |
| 55 | |
| 56 | const float deviceHeight = static_cast<float>(surface.DeviceHeightFont(measurements.sizeZoomed)); |
| 57 | const FontParameters fp(fs.fontName, deviceHeight / FontSizeMultiplier, fs.weight, |
| 58 | fs.italic, fs.extraFontFlag, technology, fs.characterSet, localeName); |
| 59 | font = Font::Allocate(fp); |
| 60 | |
| 61 | // floor here is historical as platform layers have tweaked their values to match. |
| 62 | // ceil would likely be better to ensure (nearly) all of the ink of a character is seen |
| 63 | // but that would require platform layer changes. |
| 64 | measurements.ascent = std::floor(surface.Ascent(font.get())); |
| 65 | measurements.descent = std::floor(surface.Descent(font.get())); |
| 66 | |
| 67 | measurements.capitalHeight = surface.Ascent(font.get()) - surface.InternalLeading(font.get()); |
| 68 | measurements.aveCharWidth = surface.AverageCharWidth(font.get()); |
| 69 | measurements.monospaceCharacterWidth = measurements.aveCharWidth; |
| 70 | measurements.spaceWidth = surface.WidthText(font.get(), " " ); |
| 71 | |
| 72 | if (fs.checkMonospaced) { |
| 73 | // "Ay" is normally strongly kerned and "fi" may be a ligature |
| 74 | constexpr std::string_view allASCIIGraphic("Ayfi" |
| 75 | // python: ''.join(chr(ch) for ch in range(32, 127)) |
| 76 | " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" ); |
| 77 | std::array<XYPOSITION, allASCIIGraphic.length()> positions {}; |
| 78 | surface.MeasureWidthsUTF8(font.get(), allASCIIGraphic, positions.data()); |
| 79 | std::adjacent_difference(positions.begin(), positions.end(), positions.begin()); |
| 80 | const XYPOSITION maxWidth = *std::max_element(positions.begin(), positions.end()); |
| 81 | const XYPOSITION minWidth = *std::min_element(positions.begin(), positions.end()); |
| 82 | const XYPOSITION variance = maxWidth - minWidth; |
| 83 | const XYPOSITION scaledVariance = variance / measurements.aveCharWidth; |
| 84 | constexpr XYPOSITION monospaceWidthEpsilon = 0.000001; // May need tweaking if monospace fonts vary more |
| 85 | measurements.monospaceASCII = scaledVariance < monospaceWidthEpsilon; |
| 86 | measurements.monospaceCharacterWidth = minWidth; |
| 87 | } else { |
| 88 | measurements.monospaceASCII = false; |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | ViewStyle::ViewStyle(size_t stylesSize_) : |
| 93 | styles(stylesSize_), |
| 94 | markers(MarkerMax + 1), |
| 95 | indicators(static_cast<size_t>(IndicatorNumbers::Max) + 1) { |
| 96 | |
| 97 | nextExtendedStyle = 256; |
| 98 | ResetDefaultStyle(); |
| 99 | |
| 100 | // There are no image markers by default, so no need for calling CalcLargestMarkerHeight() |
| 101 | largestMarkerHeight = 0; |
| 102 | |
| 103 | indicators[0] = Indicator(IndicatorStyle::Squiggle, ColourRGBA(0, 0x7f, 0)); |
| 104 | indicators[1] = Indicator(IndicatorStyle::TT, ColourRGBA(0, 0, 0xff)); |
| 105 | indicators[2] = Indicator(IndicatorStyle::Plain, ColourRGBA(0xff, 0, 0)); |
| 106 | |
| 107 | technology = Technology::Default; |
| 108 | indicatorsDynamic = false; |
| 109 | indicatorsSetFore = false; |
| 110 | lineHeight = 1; |
| 111 | lineOverlap = 0; |
| 112 | maxAscent = 1; |
| 113 | maxDescent = 1; |
| 114 | aveCharWidth = 8; |
| 115 | spaceWidth = 8; |
| 116 | tabWidth = spaceWidth * 8; |
| 117 | |
| 118 | // Default is for no selection foregrounds |
| 119 | elementColours.erase(Element::SelectionText); |
| 120 | elementColours.erase(Element::SelectionAdditionalText); |
| 121 | elementColours.erase(Element::SelectionSecondaryText); |
| 122 | elementColours.erase(Element::SelectionInactiveText); |
| 123 | // Shades of grey for selection backgrounds |
| 124 | elementBaseColours[Element::SelectionBack] = ColourRGBA(0xc0, 0xc0, 0xc0, 0xff); |
| 125 | elementBaseColours[Element::SelectionAdditionalBack] = ColourRGBA(0xd7, 0xd7, 0xd7, 0xff); |
| 126 | elementBaseColours[Element::SelectionSecondaryBack] = ColourRGBA(0xb0, 0xb0, 0xb0, 0xff); |
| 127 | elementBaseColours[Element::SelectionInactiveBack] = ColourRGBA(0x80, 0x80, 0x80, 0x3f); |
| 128 | elementAllowsTranslucent.insert({ |
| 129 | Element::SelectionText, |
| 130 | Element::SelectionBack, |
| 131 | Element::SelectionAdditionalText, |
| 132 | Element::SelectionAdditionalBack, |
| 133 | Element::SelectionSecondaryText, |
| 134 | Element::SelectionSecondaryBack, |
| 135 | Element::SelectionInactiveText, |
| 136 | Element::SelectionBack, |
| 137 | Element::SelectionInactiveBack, |
| 138 | }); |
| 139 | |
| 140 | foldmarginColour.reset(); |
| 141 | foldmarginHighlightColour.reset(); |
| 142 | |
| 143 | controlCharSymbol = 0; /* Draw the control characters */ |
| 144 | controlCharWidth = 0; |
| 145 | selbar = Platform::Chrome(); |
| 146 | selbarlight = Platform::ChromeHighlight(); |
| 147 | styles[StyleLineNumber].fore = ColourRGBA(0, 0, 0); |
| 148 | styles[StyleLineNumber].back = Platform::Chrome(); |
| 149 | |
| 150 | elementBaseColours[Element::Caret] = ColourRGBA(0, 0, 0); |
| 151 | elementBaseColours[Element::CaretAdditional] = ColourRGBA(0x7f, 0x7f, 0x7f); |
| 152 | elementAllowsTranslucent.insert({ |
| 153 | Element::Caret, |
| 154 | Element::CaretAdditional, |
| 155 | }); |
| 156 | |
| 157 | elementColours.erase(Element::CaretLineBack); |
| 158 | elementAllowsTranslucent.insert(Element::CaretLineBack); |
| 159 | |
| 160 | someStylesProtected = false; |
| 161 | someStylesForceCase = false; |
| 162 | |
| 163 | hotspotUnderline = true; |
| 164 | elementColours.erase(Element::HotSpotActive); |
| 165 | elementAllowsTranslucent.insert(Element::HotSpotActive); |
| 166 | |
| 167 | leftMarginWidth = 1; |
| 168 | rightMarginWidth = 1; |
| 169 | ms.resize(MaxMargin + 1); |
| 170 | ms[0] = MarginStyle(MarginType::Number); |
| 171 | ms[1] = MarginStyle(MarginType::Symbol, 16, ~MaskFolders); |
| 172 | ms[2] = MarginStyle(MarginType::Symbol); |
| 173 | marginInside = true; |
| 174 | CalculateMarginWidthAndMask(); |
| 175 | textStart = marginInside ? fixedColumnWidth : leftMarginWidth; |
| 176 | zoomLevel = 0; |
| 177 | viewWhitespace = WhiteSpace::Invisible; |
| 178 | tabDrawMode = TabDrawMode::LongArrow; |
| 179 | whitespaceSize = 1; |
| 180 | elementColours.erase(Element::WhiteSpace); |
| 181 | elementAllowsTranslucent.insert(Element::WhiteSpace); |
| 182 | |
| 183 | viewIndentationGuides = IndentView::None; |
| 184 | viewEOL = false; |
| 185 | extraFontFlag = FontQuality::QualityDefault; |
| 186 | extraAscent = 0; |
| 187 | extraDescent = 0; |
| 188 | marginStyleOffset = 0; |
| 189 | annotationVisible = AnnotationVisible::Hidden; |
| 190 | annotationStyleOffset = 0; |
| 191 | eolAnnotationVisible = EOLAnnotationVisible::Hidden; |
| 192 | eolAnnotationStyleOffset = 0; |
| 193 | braceHighlightIndicatorSet = false; |
| 194 | braceHighlightIndicator = 0; |
| 195 | braceBadLightIndicatorSet = false; |
| 196 | braceBadLightIndicator = 0; |
| 197 | |
| 198 | edgeState = EdgeVisualStyle::None; |
| 199 | theEdge = EdgeProperties(0, ColourRGBA(0xc0, 0xc0, 0xc0)); |
| 200 | |
| 201 | marginNumberPadding = 3; |
| 202 | ctrlCharPadding = 3; // +3 For a blank on front and rounded edge each side |
| 203 | lastSegItalicsOffset = 2; |
| 204 | |
| 205 | localeName = localeNameDefault; |
| 206 | } |
| 207 | |
| 208 | // Copy constructor only called when printing copies the screen ViewStyle so it can be |
| 209 | // modified for printing styles. |
| 210 | ViewStyle::ViewStyle(const ViewStyle &source) : ViewStyle(source.styles.size()) { |
| 211 | styles = source.styles; |
| 212 | for (Style &style : styles) { |
| 213 | // Can't just copy fontName as its lifetime is relative to its owning ViewStyle |
| 214 | style.fontName = fontNames.Save(style.fontName); |
| 215 | } |
| 216 | nextExtendedStyle = source.nextExtendedStyle; |
| 217 | markers = source.markers; |
| 218 | CalcLargestMarkerHeight(); |
| 219 | |
| 220 | indicators = source.indicators; |
| 221 | |
| 222 | indicatorsDynamic = source.indicatorsDynamic; |
| 223 | indicatorsSetFore = source.indicatorsSetFore; |
| 224 | |
| 225 | selection = source.selection; |
| 226 | |
| 227 | foldmarginColour = source.foldmarginColour; |
| 228 | foldmarginHighlightColour = source.foldmarginHighlightColour; |
| 229 | |
| 230 | hotspotUnderline = source.hotspotUnderline; |
| 231 | |
| 232 | controlCharSymbol = source.controlCharSymbol; |
| 233 | controlCharWidth = source.controlCharWidth; |
| 234 | selbar = source.selbar; |
| 235 | selbarlight = source.selbarlight; |
| 236 | caret = source.caret; |
| 237 | caretLine = source.caretLine; |
| 238 | someStylesProtected = false; |
| 239 | someStylesForceCase = false; |
| 240 | leftMarginWidth = source.leftMarginWidth; |
| 241 | rightMarginWidth = source.rightMarginWidth; |
| 242 | ms = source.ms; |
| 243 | maskInLine = source.maskInLine; |
| 244 | maskDrawInText = source.maskDrawInText; |
| 245 | fixedColumnWidth = source.fixedColumnWidth; |
| 246 | marginInside = source.marginInside; |
| 247 | textStart = source.textStart; |
| 248 | zoomLevel = source.zoomLevel; |
| 249 | viewWhitespace = source.viewWhitespace; |
| 250 | tabDrawMode = source.tabDrawMode; |
| 251 | whitespaceSize = source.whitespaceSize; |
| 252 | viewIndentationGuides = source.viewIndentationGuides; |
| 253 | viewEOL = source.viewEOL; |
| 254 | extraFontFlag = source.extraFontFlag; |
| 255 | extraAscent = source.extraAscent; |
| 256 | extraDescent = source.extraDescent; |
| 257 | marginStyleOffset = source.marginStyleOffset; |
| 258 | annotationVisible = source.annotationVisible; |
| 259 | annotationStyleOffset = source.annotationStyleOffset; |
| 260 | eolAnnotationVisible = source.eolAnnotationVisible; |
| 261 | eolAnnotationStyleOffset = source.eolAnnotationStyleOffset; |
| 262 | braceHighlightIndicatorSet = source.braceHighlightIndicatorSet; |
| 263 | braceHighlightIndicator = source.braceHighlightIndicator; |
| 264 | braceBadLightIndicatorSet = source.braceBadLightIndicatorSet; |
| 265 | braceBadLightIndicator = source.braceBadLightIndicator; |
| 266 | |
| 267 | edgeState = source.edgeState; |
| 268 | theEdge = source.theEdge; |
| 269 | theMultiEdge = source.theMultiEdge; |
| 270 | |
| 271 | marginNumberPadding = source.marginNumberPadding; |
| 272 | ctrlCharPadding = source.ctrlCharPadding; |
| 273 | lastSegItalicsOffset = source.lastSegItalicsOffset; |
| 274 | |
| 275 | wrap = source.wrap; |
| 276 | |
| 277 | localeName = source.localeName; |
| 278 | } |
| 279 | |
| 280 | ViewStyle::~ViewStyle() = default; |
| 281 | |
| 282 | void ViewStyle::CalculateMarginWidthAndMask() noexcept { |
| 283 | fixedColumnWidth = marginInside ? leftMarginWidth : 0; |
| 284 | maskInLine = 0xffffffff; |
| 285 | int maskDefinedMarkers = 0; |
| 286 | for (const MarginStyle &m : ms) { |
| 287 | fixedColumnWidth += m.width; |
| 288 | if (m.width > 0) |
| 289 | maskInLine &= ~m.mask; |
| 290 | maskDefinedMarkers |= m.mask; |
| 291 | } |
| 292 | maskDrawInText = 0; |
| 293 | for (int markBit = 0; markBit < 32; markBit++) { |
| 294 | const int maskBit = 1U << markBit; |
| 295 | switch (markers[markBit].markType) { |
| 296 | case MarkerSymbol::Empty: |
| 297 | maskInLine &= ~maskBit; |
| 298 | break; |
| 299 | case MarkerSymbol::Background: |
| 300 | case MarkerSymbol::Underline: |
| 301 | maskInLine &= ~maskBit; |
| 302 | maskDrawInText |= maskDefinedMarkers & maskBit; |
| 303 | break; |
| 304 | default: // Other marker types do not affect the masks |
| 305 | break; |
| 306 | } |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | void ViewStyle::Refresh(Surface &surface, int tabInChars) { |
| 311 | fonts.clear(); |
| 312 | |
| 313 | selbar = Platform::Chrome(); |
| 314 | selbarlight = Platform::ChromeHighlight(); |
| 315 | |
| 316 | // Apply the extra font flag which controls text drawing quality to each style. |
| 317 | for (Style &style : styles) { |
| 318 | style.extraFontFlag = extraFontFlag; |
| 319 | } |
| 320 | |
| 321 | // Create a FontRealised object for each unique font in the styles. |
| 322 | CreateAndAddFont(styles[StyleDefault]); |
| 323 | for (const Style &style : styles) { |
| 324 | CreateAndAddFont(style); |
| 325 | } |
| 326 | |
| 327 | // Ask platform to allocate each unique font. |
| 328 | for (const std::pair<const FontSpecification, std::unique_ptr<FontRealised>> &font : fonts) { |
| 329 | font.second->Realise(surface, zoomLevel, technology, font.first, localeName.c_str()); |
| 330 | } |
| 331 | |
| 332 | // Set the platform font handle and measurements for each style. |
| 333 | for (Style &style : styles) { |
| 334 | const FontRealised *fr = Find(style); |
| 335 | style.Copy(fr->font, fr->measurements); |
| 336 | } |
| 337 | |
| 338 | indicatorsDynamic = std::any_of(indicators.cbegin(), indicators.cend(), |
| 339 | [](const Indicator &indicator) noexcept { return indicator.IsDynamic(); }); |
| 340 | |
| 341 | indicatorsSetFore = std::any_of(indicators.cbegin(), indicators.cend(), |
| 342 | [](const Indicator &indicator) noexcept { return indicator.OverridesTextFore(); }); |
| 343 | |
| 344 | maxAscent = 1; |
| 345 | maxDescent = 1; |
| 346 | FindMaxAscentDescent(); |
| 347 | // Ensure reasonable values: lines less than 1 pixel high will not work |
| 348 | maxAscent = std::max(1.0, maxAscent + extraAscent); |
| 349 | maxDescent = std::max(0.0, maxDescent + extraDescent); |
| 350 | lineHeight = static_cast<int>(std::lround(maxAscent + maxDescent)); |
| 351 | lineOverlap = lineHeight / 10; |
| 352 | if (lineOverlap < 2) |
| 353 | lineOverlap = 2; |
| 354 | if (lineOverlap > lineHeight) |
| 355 | lineOverlap = lineHeight; |
| 356 | |
| 357 | someStylesProtected = std::any_of(styles.cbegin(), styles.cend(), |
| 358 | [](const Style &style) noexcept { return style.IsProtected(); }); |
| 359 | |
| 360 | someStylesForceCase = std::any_of(styles.cbegin(), styles.cend(), |
| 361 | [](const Style &style) noexcept { return style.caseForce != Style::CaseForce::mixed; }); |
| 362 | |
| 363 | aveCharWidth = styles[StyleDefault].aveCharWidth; |
| 364 | spaceWidth = styles[StyleDefault].spaceWidth; |
| 365 | tabWidth = spaceWidth * tabInChars; |
| 366 | |
| 367 | controlCharWidth = 0.0; |
| 368 | if (controlCharSymbol >= 32) { |
| 369 | const char cc[2] = { static_cast<char>(controlCharSymbol), '\0' }; |
| 370 | controlCharWidth = surface.WidthText(styles[StyleControlChar].font.get(), cc); |
| 371 | } |
| 372 | |
| 373 | CalculateMarginWidthAndMask(); |
| 374 | textStart = marginInside ? fixedColumnWidth : leftMarginWidth; |
| 375 | } |
| 376 | |
| 377 | void ViewStyle::ReleaseAllExtendedStyles() noexcept { |
| 378 | nextExtendedStyle = 256; |
| 379 | } |
| 380 | |
| 381 | int ViewStyle::AllocateExtendedStyles(int numberStyles) { |
| 382 | const int startRange = nextExtendedStyle; |
| 383 | nextExtendedStyle += numberStyles; |
| 384 | EnsureStyle(nextExtendedStyle); |
| 385 | return startRange; |
| 386 | } |
| 387 | |
| 388 | void ViewStyle::EnsureStyle(size_t index) { |
| 389 | if (index >= styles.size()) { |
| 390 | AllocStyles(index+1); |
| 391 | } |
| 392 | } |
| 393 | |
| 394 | void ViewStyle::ResetDefaultStyle() { |
| 395 | styles[StyleDefault] = Style(fontNames.Save(Platform::DefaultFont())); |
| 396 | } |
| 397 | |
| 398 | void ViewStyle::ClearStyles() { |
| 399 | // Reset all styles to be like the default style |
| 400 | for (size_t i=0; i<styles.size(); i++) { |
| 401 | if (i != StyleDefault) { |
| 402 | styles[i] = styles[StyleDefault]; |
| 403 | } |
| 404 | } |
| 405 | styles[StyleLineNumber].back = Platform::Chrome(); |
| 406 | |
| 407 | // Set call tip fore/back to match the values previously set for call tips |
| 408 | styles[StyleCallTip].back = ColourRGBA(0xff, 0xff, 0xff); |
| 409 | styles[StyleCallTip].fore = ColourRGBA(0x80, 0x80, 0x80); |
| 410 | } |
| 411 | |
| 412 | void ViewStyle::SetStyleFontName(int styleIndex, const char *name) { |
| 413 | styles[styleIndex].fontName = fontNames.Save(name); |
| 414 | } |
| 415 | |
| 416 | void ViewStyle::SetFontLocaleName(const char *name) { |
| 417 | localeName = name; |
| 418 | } |
| 419 | |
| 420 | bool ViewStyle::ProtectionActive() const noexcept { |
| 421 | return someStylesProtected; |
| 422 | } |
| 423 | |
| 424 | int ViewStyle::ExternalMarginWidth() const noexcept { |
| 425 | return marginInside ? 0 : fixedColumnWidth; |
| 426 | } |
| 427 | |
| 428 | int ViewStyle::MarginFromLocation(Point pt) const noexcept { |
| 429 | XYPOSITION x = marginInside ? 0 : -fixedColumnWidth; |
| 430 | for (size_t i = 0; i < ms.size(); i++) { |
| 431 | if ((pt.x >= x) && (pt.x < x + ms[i].width)) |
| 432 | return static_cast<int>(i); |
| 433 | x += ms[i].width; |
| 434 | } |
| 435 | return -1; |
| 436 | } |
| 437 | |
| 438 | bool ViewStyle::ValidStyle(size_t styleIndex) const noexcept { |
| 439 | return styleIndex < styles.size(); |
| 440 | } |
| 441 | |
| 442 | void ViewStyle::CalcLargestMarkerHeight() noexcept { |
| 443 | largestMarkerHeight = 0; |
| 444 | for (const LineMarker &marker : markers) { |
| 445 | switch (marker.markType) { |
| 446 | case MarkerSymbol::Pixmap: |
| 447 | if (marker.pxpm && marker.pxpm->GetHeight() > largestMarkerHeight) |
| 448 | largestMarkerHeight = marker.pxpm->GetHeight(); |
| 449 | break; |
| 450 | case MarkerSymbol::RgbaImage: |
| 451 | if (marker.image && marker.image->GetHeight() > largestMarkerHeight) |
| 452 | largestMarkerHeight = marker.image->GetHeight(); |
| 453 | break; |
| 454 | default: // Only images have their own natural heights |
| 455 | break; |
| 456 | } |
| 457 | } |
| 458 | } |
| 459 | |
| 460 | int ViewStyle::GetFrameWidth() const noexcept { |
| 461 | return std::clamp(caretLine.frame, 1, lineHeight / 3); |
| 462 | } |
| 463 | |
| 464 | bool ViewStyle::IsLineFrameOpaque(bool caretActive, bool lineContainsCaret) const { |
| 465 | return caretLine.frame && (caretActive || caretLine.alwaysShow) && |
| 466 | ElementColour(Element::CaretLineBack) && |
| 467 | (caretLine.layer == Layer::Base) && lineContainsCaret; |
| 468 | } |
| 469 | |
| 470 | // See if something overrides the line background colour: Either if caret is on the line |
| 471 | // and background colour is set for that, or if a marker is defined that forces its background |
| 472 | // colour onto the line, or if a marker is defined but has no selection margin in which to |
| 473 | // display itself (as long as it's not an MarkerSymbol::Empty marker). These are checked in order |
| 474 | // with the earlier taking precedence. When multiple markers cause background override, |
| 475 | // the colour for the highest numbered one is used. |
| 476 | std::optional<ColourRGBA> ViewStyle::Background(int marksOfLine, bool caretActive, bool lineContainsCaret) const { |
| 477 | std::optional<ColourRGBA> background; |
| 478 | if (!caretLine.frame && (caretActive || caretLine.alwaysShow) && |
| 479 | (caretLine.layer == Layer::Base) && lineContainsCaret) { |
| 480 | background = ElementColour(Element::CaretLineBack); |
| 481 | } |
| 482 | if (!background && marksOfLine) { |
| 483 | int marks = marksOfLine; |
| 484 | for (int markBit = 0; (markBit < 32) && marks; markBit++) { |
| 485 | if ((marks & 1) && (markers[markBit].markType == MarkerSymbol::Background) && |
| 486 | (markers[markBit].layer == Layer::Base)) { |
| 487 | background = markers[markBit].back; |
| 488 | } |
| 489 | marks >>= 1; |
| 490 | } |
| 491 | } |
| 492 | if (!background && maskInLine) { |
| 493 | int marksMasked = marksOfLine & maskInLine; |
| 494 | if (marksMasked) { |
| 495 | for (int markBit = 0; (markBit < 32) && marksMasked; markBit++) { |
| 496 | if ((marksMasked & 1) && |
| 497 | (markers[markBit].layer == Layer::Base)) { |
| 498 | background = markers[markBit].back; |
| 499 | } |
| 500 | marksMasked >>= 1; |
| 501 | } |
| 502 | } |
| 503 | } |
| 504 | if (background) { |
| 505 | return background->Opaque(); |
| 506 | } else { |
| 507 | return {}; |
| 508 | } |
| 509 | } |
| 510 | |
| 511 | bool ViewStyle::SelectionBackgroundDrawn() const noexcept { |
| 512 | return selection.layer == Layer::Base; |
| 513 | } |
| 514 | |
| 515 | bool ViewStyle::SelectionTextDrawn() const { |
| 516 | return |
| 517 | ElementIsSet(Element::SelectionText) || |
| 518 | ElementIsSet(Element::SelectionAdditionalText) || |
| 519 | ElementIsSet(Element::SelectionSecondaryText) || |
| 520 | ElementIsSet(Element::SelectionInactiveText); |
| 521 | } |
| 522 | |
| 523 | bool ViewStyle::WhitespaceBackgroundDrawn() const { |
| 524 | return (viewWhitespace != WhiteSpace::Invisible) && (ElementIsSet(Element::WhiteSpaceBack)); |
| 525 | } |
| 526 | |
| 527 | bool ViewStyle::WhiteSpaceVisible(bool inIndent) const noexcept { |
| 528 | return (!inIndent && viewWhitespace == WhiteSpace::VisibleAfterIndent) || |
| 529 | (inIndent && viewWhitespace == WhiteSpace::VisibleOnlyInIndent) || |
| 530 | viewWhitespace == WhiteSpace::VisibleAlways; |
| 531 | } |
| 532 | |
| 533 | ColourRGBA ViewStyle::WrapColour() const { |
| 534 | return ElementColour(Element::WhiteSpace).value_or(styles[StyleDefault].fore); |
| 535 | } |
| 536 | |
| 537 | // Insert new edge in sorted order. |
| 538 | void ViewStyle::AddMultiEdge(int column, ColourRGBA colour) { |
| 539 | theMultiEdge.insert( |
| 540 | std::upper_bound(theMultiEdge.begin(), theMultiEdge.end(), column, |
| 541 | [](const EdgeProperties &a, const EdgeProperties &b) noexcept { |
| 542 | return a.column < b.column; |
| 543 | }), |
| 544 | EdgeProperties(column, colour)); |
| 545 | } |
| 546 | |
| 547 | std::optional<ColourRGBA> ViewStyle::ElementColour(Element element) const { |
| 548 | ElementMap::const_iterator search = elementColours.find(element); |
| 549 | if (search != elementColours.end()) { |
| 550 | if (search->second.has_value()) { |
| 551 | return search->second; |
| 552 | } |
| 553 | } |
| 554 | ElementMap::const_iterator searchBase = elementBaseColours.find(element); |
| 555 | if (searchBase != elementBaseColours.end()) { |
| 556 | if (searchBase->second.has_value()) { |
| 557 | return searchBase->second; |
| 558 | } |
| 559 | } |
| 560 | return {}; |
| 561 | } |
| 562 | |
| 563 | bool ViewStyle::ElementAllowsTranslucent(Element element) const { |
| 564 | return elementAllowsTranslucent.count(element) > 0; |
| 565 | } |
| 566 | |
| 567 | bool ViewStyle::ResetElement(Element element) { |
| 568 | ElementMap::const_iterator search = elementColours.find(element); |
| 569 | const bool changed = (search != elementColours.end()) && (search->second.has_value()); |
| 570 | elementColours.erase(element); |
| 571 | return changed; |
| 572 | } |
| 573 | |
| 574 | bool ViewStyle::SetElementColour(Element element, ColourRGBA colour) { |
| 575 | ElementMap::const_iterator search = elementColours.find(element); |
| 576 | const bool changed = |
| 577 | (search == elementColours.end()) || |
| 578 | (search->second.has_value() && !(*search->second == colour)); |
| 579 | elementColours[element] = colour; |
| 580 | return changed; |
| 581 | } |
| 582 | |
| 583 | bool ViewStyle::SetElementColourOptional(Element element, uptr_t wParam, sptr_t lParam) { |
| 584 | if (wParam) { |
| 585 | return SetElementColour(element, ColourRGBA::FromIpRGB(lParam)); |
| 586 | } else { |
| 587 | return ResetElement(element); |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | void ViewStyle::SetElementRGB(Element element, int rgb) { |
| 592 | const ColourRGBA current = ElementColour(element).value_or(ColourRGBA(0, 0, 0, 0)); |
| 593 | elementColours[element] = ColourRGBA(ColourRGBA(rgb), current.GetAlpha()); |
| 594 | } |
| 595 | |
| 596 | void ViewStyle::SetElementAlpha(Element element, int alpha) { |
| 597 | const ColourRGBA current = ElementColour(element).value_or(ColourRGBA(0, 0, 0, 0)); |
| 598 | elementColours[element] = ColourRGBA(current, std::min(alpha, 0xff)); |
| 599 | } |
| 600 | |
| 601 | bool ViewStyle::ElementIsSet(Element element) const { |
| 602 | ElementMap::const_iterator search = elementColours.find(element); |
| 603 | if (search != elementColours.end()) { |
| 604 | return search->second.has_value(); |
| 605 | } |
| 606 | return false; |
| 607 | } |
| 608 | |
| 609 | bool ViewStyle::SetElementBase(Element element, ColourRGBA colour) { |
| 610 | ElementMap::const_iterator search = elementBaseColours.find(element); |
| 611 | const bool changed = |
| 612 | (search == elementBaseColours.end()) || |
| 613 | (search->second.has_value() && !(*search->second == colour)); |
| 614 | elementBaseColours[element] = colour; |
| 615 | return changed; |
| 616 | } |
| 617 | |
| 618 | bool ViewStyle::SetWrapState(Wrap wrapState_) noexcept { |
| 619 | const bool changed = wrap.state != wrapState_; |
| 620 | wrap.state = wrapState_; |
| 621 | return changed; |
| 622 | } |
| 623 | |
| 624 | bool ViewStyle::SetWrapVisualFlags(WrapVisualFlag wrapVisualFlags_) noexcept { |
| 625 | const bool changed = wrap.visualFlags != wrapVisualFlags_; |
| 626 | wrap.visualFlags = wrapVisualFlags_; |
| 627 | return changed; |
| 628 | } |
| 629 | |
| 630 | bool ViewStyle::SetWrapVisualFlagsLocation(WrapVisualLocation wrapVisualFlagsLocation_) noexcept { |
| 631 | const bool changed = wrap.visualFlagsLocation != wrapVisualFlagsLocation_; |
| 632 | wrap.visualFlagsLocation = wrapVisualFlagsLocation_; |
| 633 | return changed; |
| 634 | } |
| 635 | |
| 636 | bool ViewStyle::SetWrapVisualStartIndent(int wrapVisualStartIndent_) noexcept { |
| 637 | const bool changed = wrap.visualStartIndent != wrapVisualStartIndent_; |
| 638 | wrap.visualStartIndent = wrapVisualStartIndent_; |
| 639 | return changed; |
| 640 | } |
| 641 | |
| 642 | bool ViewStyle::SetWrapIndentMode(WrapIndentMode wrapIndentMode_) noexcept { |
| 643 | const bool changed = wrap.indentMode != wrapIndentMode_; |
| 644 | wrap.indentMode = wrapIndentMode_; |
| 645 | return changed; |
| 646 | } |
| 647 | |
| 648 | bool ViewStyle::IsBlockCaretStyle() const noexcept { |
| 649 | return ((caret.style & CaretStyle::InsMask) == CaretStyle::Block) || |
| 650 | FlagSet(caret.style, CaretStyle::OverstrikeBlock) || |
| 651 | FlagSet(caret.style, CaretStyle::Curses); |
| 652 | } |
| 653 | |
| 654 | bool ViewStyle::IsCaretVisible(bool isMainSelection) const noexcept { |
| 655 | return caret.width > 0 && |
| 656 | ((caret.style & CaretStyle::InsMask) != CaretStyle::Invisible || |
| 657 | (FlagSet(caret.style, CaretStyle::Curses) && !isMainSelection)); // only draw additional selections in curses mode |
| 658 | } |
| 659 | |
| 660 | bool ViewStyle::DrawCaretInsideSelection(bool inOverstrike, bool imeCaretBlockOverride) const noexcept { |
| 661 | if (FlagSet(caret.style, CaretStyle::BlockAfter)) |
| 662 | return false; |
| 663 | return ((caret.style & CaretStyle::InsMask) == CaretStyle::Block) || |
| 664 | (inOverstrike && FlagSet(caret.style, CaretStyle::OverstrikeBlock)) || |
| 665 | imeCaretBlockOverride || |
| 666 | FlagSet(caret.style, CaretStyle::Curses); |
| 667 | } |
| 668 | |
| 669 | ViewStyle::CaretShape ViewStyle::CaretShapeForMode(bool inOverstrike, bool isMainSelection) const noexcept { |
| 670 | if (inOverstrike) { |
| 671 | return (FlagSet(caret.style, CaretStyle::OverstrikeBlock)) ? CaretShape::block : CaretShape::bar; |
| 672 | } |
| 673 | |
| 674 | if (FlagSet(caret.style, CaretStyle::Curses) && !isMainSelection) { |
| 675 | return CaretShape::block; |
| 676 | } |
| 677 | |
| 678 | const CaretStyle caretStyle = caret.style & CaretStyle::InsMask; |
| 679 | return (caretStyle <= CaretStyle::Block) ? static_cast<CaretShape>(caretStyle) : CaretShape::line; |
| 680 | } |
| 681 | |
| 682 | void ViewStyle::AllocStyles(size_t sizeNew) { |
| 683 | size_t i=styles.size(); |
| 684 | styles.resize(sizeNew); |
| 685 | if (styles.size() > StyleDefault) { |
| 686 | for (; i<sizeNew; i++) { |
| 687 | if (i != StyleDefault) { |
| 688 | styles[i] = styles[StyleDefault]; |
| 689 | } |
| 690 | } |
| 691 | } |
| 692 | } |
| 693 | |
| 694 | void ViewStyle::CreateAndAddFont(const FontSpecification &fs) { |
| 695 | if (fs.fontName) { |
| 696 | FontMap::iterator it = fonts.find(fs); |
| 697 | if (it == fonts.end()) { |
| 698 | fonts[fs] = std::make_unique<FontRealised>(); |
| 699 | } |
| 700 | } |
| 701 | } |
| 702 | |
| 703 | FontRealised *ViewStyle::Find(const FontSpecification &fs) { |
| 704 | if (!fs.fontName) // Invalid specification so return arbitrary object |
| 705 | return fonts.begin()->second.get(); |
| 706 | FontMap::iterator it = fonts.find(fs); |
| 707 | if (it != fonts.end()) { |
| 708 | // Should always reach here since map was just set for all styles |
| 709 | return it->second.get(); |
| 710 | } |
| 711 | return nullptr; |
| 712 | } |
| 713 | |
| 714 | void ViewStyle::FindMaxAscentDescent() noexcept { |
| 715 | for (const auto &font : fonts) { |
| 716 | if (maxAscent < font.second->measurements.ascent) |
| 717 | maxAscent = font.second->measurements.ascent; |
| 718 | if (maxDescent < font.second->measurements.descent) |
| 719 | maxDescent = font.second->measurements.descent; |
| 720 | } |
| 721 | } |
| 722 | |