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 | |