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
39using namespace Scintilla;
40using namespace Scintilla::Internal;
41
42MarginStyle::MarginStyle(MarginType style_, int width_, int mask_) noexcept :
43 style(style_), width(width_), mask(mask_), sensitive(false), cursor(CursorShape::ReverseArrow) {
44}
45
46bool MarginStyle::ShowsFolding() const noexcept {
47 return (mask & MaskFolders) != 0;
48}
49
50void 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
92ViewStyle::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.
210ViewStyle::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
280ViewStyle::~ViewStyle() = default;
281
282void 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
310void 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
377void ViewStyle::ReleaseAllExtendedStyles() noexcept {
378 nextExtendedStyle = 256;
379}
380
381int ViewStyle::AllocateExtendedStyles(int numberStyles) {
382 const int startRange = nextExtendedStyle;
383 nextExtendedStyle += numberStyles;
384 EnsureStyle(nextExtendedStyle);
385 return startRange;
386}
387
388void ViewStyle::EnsureStyle(size_t index) {
389 if (index >= styles.size()) {
390 AllocStyles(index+1);
391 }
392}
393
394void ViewStyle::ResetDefaultStyle() {
395 styles[StyleDefault] = Style(fontNames.Save(Platform::DefaultFont()));
396}
397
398void 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
412void ViewStyle::SetStyleFontName(int styleIndex, const char *name) {
413 styles[styleIndex].fontName = fontNames.Save(name);
414}
415
416void ViewStyle::SetFontLocaleName(const char *name) {
417 localeName = name;
418}
419
420bool ViewStyle::ProtectionActive() const noexcept {
421 return someStylesProtected;
422}
423
424int ViewStyle::ExternalMarginWidth() const noexcept {
425 return marginInside ? 0 : fixedColumnWidth;
426}
427
428int 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
438bool ViewStyle::ValidStyle(size_t styleIndex) const noexcept {
439 return styleIndex < styles.size();
440}
441
442void 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
460int ViewStyle::GetFrameWidth() const noexcept {
461 return std::clamp(caretLine.frame, 1, lineHeight / 3);
462}
463
464bool 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.
476std::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
511bool ViewStyle::SelectionBackgroundDrawn() const noexcept {
512 return selection.layer == Layer::Base;
513}
514
515bool ViewStyle::SelectionTextDrawn() const {
516 return
517 ElementIsSet(Element::SelectionText) ||
518 ElementIsSet(Element::SelectionAdditionalText) ||
519 ElementIsSet(Element::SelectionSecondaryText) ||
520 ElementIsSet(Element::SelectionInactiveText);
521}
522
523bool ViewStyle::WhitespaceBackgroundDrawn() const {
524 return (viewWhitespace != WhiteSpace::Invisible) && (ElementIsSet(Element::WhiteSpaceBack));
525}
526
527bool ViewStyle::WhiteSpaceVisible(bool inIndent) const noexcept {
528 return (!inIndent && viewWhitespace == WhiteSpace::VisibleAfterIndent) ||
529 (inIndent && viewWhitespace == WhiteSpace::VisibleOnlyInIndent) ||
530 viewWhitespace == WhiteSpace::VisibleAlways;
531}
532
533ColourRGBA ViewStyle::WrapColour() const {
534 return ElementColour(Element::WhiteSpace).value_or(styles[StyleDefault].fore);
535}
536
537// Insert new edge in sorted order.
538void 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
547std::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
563bool ViewStyle::ElementAllowsTranslucent(Element element) const {
564 return elementAllowsTranslucent.count(element) > 0;
565}
566
567bool 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
574bool 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
583bool 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
591void 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
596void 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
601bool 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
609bool 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
618bool ViewStyle::SetWrapState(Wrap wrapState_) noexcept {
619 const bool changed = wrap.state != wrapState_;
620 wrap.state = wrapState_;
621 return changed;
622}
623
624bool ViewStyle::SetWrapVisualFlags(WrapVisualFlag wrapVisualFlags_) noexcept {
625 const bool changed = wrap.visualFlags != wrapVisualFlags_;
626 wrap.visualFlags = wrapVisualFlags_;
627 return changed;
628}
629
630bool ViewStyle::SetWrapVisualFlagsLocation(WrapVisualLocation wrapVisualFlagsLocation_) noexcept {
631 const bool changed = wrap.visualFlagsLocation != wrapVisualFlagsLocation_;
632 wrap.visualFlagsLocation = wrapVisualFlagsLocation_;
633 return changed;
634}
635
636bool ViewStyle::SetWrapVisualStartIndent(int wrapVisualStartIndent_) noexcept {
637 const bool changed = wrap.visualStartIndent != wrapVisualStartIndent_;
638 wrap.visualStartIndent = wrapVisualStartIndent_;
639 return changed;
640}
641
642bool ViewStyle::SetWrapIndentMode(WrapIndentMode wrapIndentMode_) noexcept {
643 const bool changed = wrap.indentMode != wrapIndentMode_;
644 wrap.indentMode = wrapIndentMode_;
645 return changed;
646}
647
648bool 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
654bool 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
660bool 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
669ViewStyle::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
682void 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
694void 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
703FontRealised *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
714void 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