From e17d6e0482ddc1f86b617dfd749c61afabd4962d Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 4 Feb 2012 12:26:28 +0100 Subject: [PATCH] AvalonEdit: Fixed caret rectangle calculation when there are inline UI elements that extend below the baseline. Add VisualYPosition.TextBottom and VisualYPosition.BaseLine options. Fixed VisualYPosition.TextTop calculation. --- .../ICSharpCode.AvalonEdit/Editing/Caret.cs | 2 +- .../Rendering/TextView.cs | 62 +++++++++++++++---- .../Rendering/VisualLine.cs | 7 ++- .../Rendering/VisualYPosition.cs | 20 ++++-- 4 files changed, 72 insertions(+), 19 deletions(-) diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs index 468f24735b..5c6999c1fd 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs @@ -351,7 +351,7 @@ namespace ICSharpCode.AvalonEdit.Editing TextLine textLine = visualLine.GetTextLine(position.VisualColumn); double xPos = visualLine.GetTextLineVisualXPosition(textLine, position.VisualColumn); double lineTop = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextTop); - double lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineBottom); + double lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextBottom); return new Rect(xPos, lineTop, diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs index a03d344547..920f9f8958 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs @@ -115,7 +115,8 @@ namespace ICSharpCode.AvalonEdit.Rendering if (newValue != null) { TextDocumentWeakEventManager.Changing.AddListener(newValue, this); formatter = TextFormatterFactory.Create(this); - heightTree = new HeightTree(newValue, DefaultLineHeight); // measuring DefaultLineHeight depends on formatter + InvalidateDefaultTextMetrics(); // measuring DefaultLineHeight depends on formatter + heightTree = new HeightTree(newValue, DefaultLineHeight); cachedElements = new TextViewCachedElements(); } InvalidateMeasure(DispatcherPriority.Normal); @@ -1369,29 +1370,64 @@ namespace ICSharpCode.AvalonEdit.Rendering OnScrollChange(); } + bool defaultTextMetricsValid; double wideSpaceWidth; // Width of an 'x'. Used as basis for the tab width, and for scrolling. double defaultLineHeight; // Height of a line containing 'x'. Used for scrolling. + double defaultBaseline; // Baseline of a line containing 'x'. Used for TextTop/TextBottom calculation. - internal double WideSpaceWidth { + /// + /// Gets the width of a 'wide space' (the space width used for calculating the tab size). + /// + /// + /// This is the width of an 'x' in the current font. + /// We do not measure the width of an actual space as that would lead to tiny tabs in + /// some proportional fonts. + /// For monospaced fonts, this property will return the expected value, as 'x' and ' ' have the same width. + /// + public double WideSpaceWidth { get { - if (wideSpaceWidth == 0) { - MeasureWideSpaceWidthAndDefaultLineHeight(); - } + CalculateDefaultTextMetrics(); return wideSpaceWidth; } } - double DefaultLineHeight { + /// + /// Gets the default line height. This is the height of an empty line or a line containing regular text. + /// Lines that include formatted text or custom UI elements may have a different line height. + /// + public double DefaultLineHeight { get { - if (defaultLineHeight == 0) { - MeasureWideSpaceWidthAndDefaultLineHeight(); - } + CalculateDefaultTextMetrics(); return defaultLineHeight; } } - void MeasureWideSpaceWidthAndDefaultLineHeight() + /// + /// Gets the default baseline position. This is the difference between + /// and for a line containing regular text. + /// Lines that include formatted text or custom UI elements may have a different baseline. + /// + public double DefaultBaseline { + get { + CalculateDefaultTextMetrics(); + return defaultBaseline; + } + } + + void InvalidateDefaultTextMetrics() { + defaultTextMetricsValid = false; + if (heightTree != null) { + // calculate immediately so that height tree gets updated + CalculateDefaultTextMetrics(); + } + } + + void CalculateDefaultTextMetrics() + { + if (defaultTextMetricsValid) + return; + defaultTextMetricsValid = true; if (formatter != null) { var textRunProperties = CreateGlobalTextRunProperties(); using (var line = formatter.FormatLine( @@ -1401,10 +1437,12 @@ namespace ICSharpCode.AvalonEdit.Rendering null)) { wideSpaceWidth = Math.Max(1, line.WidthIncludingTrailingWhitespace); + defaultBaseline = Math.Max(1, line.Baseline); defaultLineHeight = Math.Max(1, line.Height); } } else { wideSpaceWidth = FontSize / 2; + defaultBaseline = FontSize; defaultLineHeight = FontSize + 3; } // Update heightTree.DefaultLineHeight, if a document is loaded. @@ -1831,7 +1869,7 @@ namespace ICSharpCode.AvalonEdit.Rendering // changing text formatter requires recreating the cached elements RecreateCachedElements(); // and we need to re-measure the font metrics: - MeasureWideSpaceWidthAndDefaultLineHeight(); + InvalidateDefaultTextMetrics(); } else if (e.Property == Control.ForegroundProperty || e.Property == TextView.NonPrintableCharacterBrushProperty) { @@ -1848,7 +1886,7 @@ namespace ICSharpCode.AvalonEdit.Rendering // changing font properties requires recreating cached elements RecreateCachedElements(); // and we need to re-measure the font metrics: - MeasureWideSpaceWidthAndDefaultLineHeight(); + InvalidateDefaultTextMetrics(); Redraw(); } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs index 7c6ddaa2cb..cf3e080980 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs @@ -1,6 +1,7 @@ // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) +using System.Windows.Controls; using System.Windows.Media; using ICSharpCode.AvalonEdit.Utils; using System; @@ -268,7 +269,11 @@ namespace ICSharpCode.AvalonEdit.Rendering case VisualYPosition.LineBottom: return pos + tl.Height; case VisualYPosition.TextTop: - return pos + tl.Height - textView.FontSize; + return pos + tl.Baseline - textView.DefaultBaseline; + case VisualYPosition.TextBottom: + return pos + tl.Baseline - textView.DefaultBaseline + textView.DefaultLineHeight; + case VisualYPosition.Baseline: + return pos + tl.Baseline; default: throw new ArgumentException("Invalid yPositionMode:" + yPositionMode); } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualYPosition.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualYPosition.cs index 4eee266364..536b5fe832 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualYPosition.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualYPosition.cs @@ -15,18 +15,28 @@ namespace ICSharpCode.AvalonEdit.Rendering /// LineTop, /// - /// Returns the top of the text. If the line contains inline UI elements larger than the text, TextTop - /// will be below LineTop. + /// Returns the top of the text. + /// If the line contains inline UI elements larger than the text, TextTop may be below LineTop. + /// For a line containing regular text (all in the editor's main font), this will be equal to LineTop. /// TextTop, /// - /// Returns the bottom of the TextLine. This is the same as the bottom of the text (the text is always - /// aligned at the bottom border). + /// Returns the bottom of the TextLine. /// LineBottom, /// /// The middle between LineTop and LineBottom. /// - LineMiddle + LineMiddle, + /// + /// Returns the bottom of the text. + /// If the line contains inline UI elements larger than the text, TextBottom might be above LineBottom. + /// For a line containing regular text (all in the editor's main font), this will be equal to LineBottom. + /// + TextBottom, + /// + /// Returns the baseline of the text. + /// + Baseline } }