From 24358ad21ed08bf174b8030b428a9f309dac1cc9 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 7 Nov 2009 21:48:53 +0000 Subject: [PATCH] Improved AvalonEdit pixel snapping. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5234 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Src/BracketHighlightRenderer.cs | 1 + .../AvalonEdit.AddIn/Src/IconBarMargin.cs | 4 +- .../AvalonEdit.AddIn/Src/TextMarkerService.cs | 1 + .../Project/Configuration/AssemblyInfo.cs | 4 -- .../Editing/CaretLayer.cs | 2 +- .../Editing/SelectionLayer.cs | 1 + .../Folding/FoldingMargin.cs | 17 ++--- .../Folding/FoldingMarginMarker.cs | 6 +- .../Rendering/BackgroundGeometryBuilder.cs | 28 +++++++- .../Snippets/SnippetReplaceableTextElement.cs | 1 + .../Utils/PixelSnapHelpers.cs | 66 ++++++++++--------- .../Services/ProjectService/ProjectService.cs | 1 + 12 files changed, 83 insertions(+), 49 deletions(-) diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/BracketHighlightRenderer.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/BracketHighlightRenderer.cs index 4f2e98ad75..c05e82bd66 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/BracketHighlightRenderer.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/BracketHighlightRenderer.cs @@ -59,6 +59,7 @@ namespace ICSharpCode.AvalonEdit.AddIn BackgroundGeometryBuilder builder = new BackgroundGeometryBuilder(); builder.CornerRadius = 1; + builder.AlignToWholePixels = true; builder.AddSegment(textView, new TextSegment() { StartOffset = result.OpeningBracketOffset, Length = result.OpeningBracketLength }); builder.CloseFigure(); // prevent connecting the two segments diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/IconBarMargin.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/IconBarMargin.cs index 3551eb726a..d13f9929ca 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/IconBarMargin.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/IconBarMargin.cs @@ -13,6 +13,7 @@ using System.Windows.Media; using ICSharpCode.AvalonEdit.Editing; using ICSharpCode.AvalonEdit.Rendering; +using ICSharpCode.AvalonEdit.Utils; using ICSharpCode.SharpDevelop.Bookmarks; using ICSharpCode.SharpDevelop.Editor; @@ -85,11 +86,12 @@ namespace ICSharpCode.AvalonEdit.AddIn if (!bookmarkDict.ContainsKey(line)) bookmarkDict.Add(line, bm); } + Size pixelSize = PixelSnapHelpers.GetPixelSize(this); foreach (VisualLine line in textView.VisualLines) { int lineNumber = line.FirstDocumentLine.LineNumber; IBookmark bm; if (bookmarkDict.TryGetValue(lineNumber, out bm)) { - Rect rect = new Rect(0, line.VisualTop - textView.VerticalOffset, 16, 16); + Rect rect = new Rect(0, PixelSnapHelpers.Round(line.VisualTop - textView.VerticalOffset, pixelSize.Height), 16, 16); drawingContext.DrawImage((bm.Image ?? BookmarkBase.DefaultBookmarkImage).ImageSource, rect); } } diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs index 1633965bd3..4afa155734 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs @@ -155,6 +155,7 @@ namespace ICSharpCode.AvalonEdit.AddIn foreach (TextMarker marker in markers.FindOverlappingSegments(viewStart, viewEnd - viewStart)) { if (marker.BackgroundColor != null) { BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); + geoBuilder.AlignToWholePixels = true; geoBuilder.CornerRadius = 3; geoBuilder.AddSegment(textView, marker); Geometry geometry = geoBuilder.CreateGeometry(); diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Configuration/AssemblyInfo.cs b/src/AddIns/Misc/ResourceToolkit/Project/Configuration/AssemblyInfo.cs index 477a4f7072..ac1cc5095f 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Configuration/AssemblyInfo.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Configuration/AssemblyInfo.cs @@ -24,7 +24,3 @@ using System.Security.Permissions; #endif [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] - -[assembly: SecurityPermission(SecurityAction.RequestMinimum, Execution=true)] -[assembly: FileIOPermission(SecurityAction.RequestMinimum, AllLocalFiles=FileIOPermissionAccess.AllAccess)] -[assembly: UIPermission(SecurityAction.RequestMinimum, Clipboard=UIPermissionClipboard.OwnClipboard, Window=UIPermissionWindow.AllWindows)] diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretLayer.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretLayer.cs index 984f31ca22..8b04ac8669 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretLayer.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretLayer.cs @@ -79,7 +79,7 @@ namespace ICSharpCode.AvalonEdit.Editing caretRectangle.Y - textView.VerticalOffset, caretRectangle.Width, caretRectangle.Height); - drawingContext.DrawRectangle(caretBrush, null, PixelSnapHelpers.Round(r)); + drawingContext.DrawRectangle(caretBrush, null, PixelSnapHelpers.Round(r, PixelSnapHelpers.GetPixelSize(this))); } } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SelectionLayer.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SelectionLayer.cs index 7ab3c2c0df..3132988a9d 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SelectionLayer.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SelectionLayer.cs @@ -42,6 +42,7 @@ namespace ICSharpCode.AvalonEdit.Editing base.OnRender(drawingContext); BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); + geoBuilder.AlignToMiddleOfPixels = true; geoBuilder.CornerRadius = textArea.SelectionCornerRadius; foreach (var segment in textArea.Selection.Segments) { geoBuilder.AddSegment(textView, segment); diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Folding/FoldingMargin.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Folding/FoldingMargin.cs index e041441574..af94b5c357 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Folding/FoldingMargin.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Folding/FoldingMargin.cs @@ -38,19 +38,20 @@ namespace ICSharpCode.AvalonEdit.Folding m.Measure(availableSize); } double width = SizeFactor * (double)GetValue(TextBlock.FontSizeProperty); - return new Size(PixelSnapHelpers.RoundToOdd(width), 0); + return new Size(PixelSnapHelpers.RoundToOdd(width, PixelSnapHelpers.GetPixelSize(this).Width), 0); } /// protected override Size ArrangeOverride(Size finalSize) { + Size pixelSize = PixelSnapHelpers.GetPixelSize(this); foreach (FoldingMarginMarker m in markers) { int visualColumn = m.VisualLine.GetVisualColumn(m.FoldingSection.StartOffset - m.VisualLine.FirstDocumentLine.Offset); TextLine textLine = m.VisualLine.GetTextLine(visualColumn); double yPos = m.VisualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineTop) - TextView.VerticalOffset; yPos += (textLine.Height - m.DesiredSize.Height) / 2; double xPos = (finalSize.Width - m.DesiredSize.Width) / 2; - m.Arrange(new Rect(PixelSnapHelpers.Round(new Point(xPos, yPos)), m.DesiredSize)); + m.Arrange(new Rect(PixelSnapHelpers.Round(new Point(xPos, yPos), pixelSize), m.DesiredSize)); } return base.ArrangeOverride(finalSize); } @@ -211,19 +212,19 @@ namespace ICSharpCode.AvalonEdit.Folding // Because we are using PenLineCap.Flat (the default), for vertical lines, // Y coordinates must be on pixel boundaries, whereas the X coordinate must be in the // middle of a pixel. (and the other way round for horizontal lines) - Size pixelSize = PixelSnapHelpers.GetPixelSize(); - double markerXPos = PixelSnapHelpers.PixelAlign(RenderSize.Width / 2); + Size pixelSize = PixelSnapHelpers.GetPixelSize(this); + double markerXPos = PixelSnapHelpers.PixelAlign(RenderSize.Width / 2, pixelSize.Width); double startY = 0; Pen currentPen = colors[0]; int tlNumber = 0; foreach (VisualLine vl in TextView.VisualLines) { foreach (TextLine tl in vl.TextLines) { if (endMarker[tlNumber] != null) { - double visualPos = GetVisualPos(vl, tl); + double visualPos = GetVisualPos(vl, tl, pixelSize.Height); drawingContext.DrawLine(endMarker[tlNumber], new Point(markerXPos - pixelSize.Width / 2, visualPos), new Point(RenderSize.Width, visualPos)); } if (colors[tlNumber + 1] != currentPen) { - double visualPos = GetVisualPos(vl, tl); + double visualPos = GetVisualPos(vl, tl, pixelSize.Height); if (currentPen != null) { drawingContext.DrawLine(currentPen, new Point(markerXPos, startY + pixelSize.Height / 2), new Point(markerXPos, visualPos - pixelSize.Height / 2)); } @@ -238,10 +239,10 @@ namespace ICSharpCode.AvalonEdit.Folding } } - double GetVisualPos(VisualLine vl, TextLine tl) + double GetVisualPos(VisualLine vl, TextLine tl, double pixelHeight) { double pos = vl.GetTextLineVisualYPosition(tl, VisualYPosition.LineTop) + tl.Height / 2 - TextView.VerticalOffset; - return PixelSnapHelpers.PixelAlign(pos); + return PixelSnapHelpers.PixelAlign(pos, pixelHeight); } int GetTextLineIndexFromOffset(List textLines, int offset) diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Folding/FoldingMarginMarker.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Folding/FoldingMarginMarker.cs index 1154a495e0..f75019890f 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Folding/FoldingMarginMarker.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Folding/FoldingMarginMarker.cs @@ -51,7 +51,7 @@ namespace ICSharpCode.AvalonEdit.Folding protected override Size MeasureCore(Size availableSize) { double size = MarginSizeFactor * FoldingMargin.SizeFactor * (double)GetValue(TextBlock.FontSizeProperty); - size = PixelSnapHelpers.RoundToOdd(size); + size = PixelSnapHelpers.RoundToOdd(size, PixelSnapHelpers.GetPixelSize(this).Width); return new Size(size, size); } @@ -60,7 +60,7 @@ namespace ICSharpCode.AvalonEdit.Folding Pen blackPen = new Pen(Brushes.Black, 1); blackPen.StartLineCap = PenLineCap.Square; blackPen.EndLineCap = PenLineCap.Square; - Size pixelSize = PixelSnapHelpers.GetPixelSize(); + Size pixelSize = PixelSnapHelpers.GetPixelSize(this); Rect rect = new Rect(pixelSize.Width / 2, pixelSize.Height / 2, this.RenderSize.Width - pixelSize.Width, @@ -70,7 +70,7 @@ namespace ICSharpCode.AvalonEdit.Folding rect); double middleX = rect.Left + rect.Width / 2; double middleY = rect.Top + rect.Height / 2; - double space = PixelSnapHelpers.Round(rect.Width / 8) + pixelSize.Width; + double space = PixelSnapHelpers.Round(rect.Width / 8, pixelSize.Width) + pixelSize.Width; drawingContext.DrawLine(blackPen, new Point(rect.Left + space, middleY), new Point(rect.Right - space, middleY)); diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs index 4a9bcdc215..5fe332a39f 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs @@ -32,6 +32,16 @@ namespace ICSharpCode.AvalonEdit.Rendering set { cornerRadius = value; } } + /// + /// Gets/Sets whether to align the geometry to whole pixels. + /// + public bool AlignToWholePixels { get; set; } + + /// + /// Gets/Sets whether to align the geometry to the middle of pixels. + /// + public bool AlignToMiddleOfPixels { get; set; } + /// /// Creates a new BackgroundGeometryBuilder instance. /// @@ -44,9 +54,23 @@ namespace ICSharpCode.AvalonEdit.Rendering /// public void AddSegment(TextView textView, ISegment segment) { + if (textView == null) + throw new ArgumentNullException("textView"); + Size pixelSize = PixelSnapHelpers.GetPixelSize(textView); foreach (Rect r in GetRectsForSegment(textView, segment)) { - Rect roundedRect = PixelSnapHelpers.PixelAlign(r); - AddRectangle(roundedRect.Left, roundedRect.Top, roundedRect.Right, roundedRect.Bottom); + if (AlignToWholePixels) { + AddRectangle(PixelSnapHelpers.Round(r.Left, pixelSize.Width), + PixelSnapHelpers.Round(r.Top, pixelSize.Height), + PixelSnapHelpers.Round(r.Right, pixelSize.Width), + PixelSnapHelpers.Round(r.Bottom, pixelSize.Height)); + } else if (AlignToMiddleOfPixels) { + AddRectangle(PixelSnapHelpers.PixelAlign(r.Left, pixelSize.Width), + PixelSnapHelpers.PixelAlign(r.Top, pixelSize.Height), + PixelSnapHelpers.PixelAlign(r.Right, pixelSize.Width), + PixelSnapHelpers.PixelAlign(r.Bottom, pixelSize.Height)); + } else { + AddRectangle(r.Left, r.Top, r.Right, r.Bottom); + } } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetReplaceableTextElement.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetReplaceableTextElement.cs index 75a42eee8b..132298779d 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetReplaceableTextElement.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetReplaceableTextElement.cs @@ -193,6 +193,7 @@ namespace ICSharpCode.AvalonEdit.Snippets ISegment s = element.Segment; if (s != null) { BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); + geoBuilder.AlignToMiddleOfPixels = true; if (Layer == KnownLayer.Background) { geoBuilder.AddSegment(textView, s); drawingContext.DrawGeometry(backgroundBrush, null, geoBuilder.CreateGeometry()); diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/PixelSnapHelpers.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/PixelSnapHelpers.cs index a5bb9ee48a..07e208fe3b 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/PixelSnapHelpers.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/PixelSnapHelpers.cs @@ -7,77 +7,83 @@ using System; using System.Windows; +using System.Windows.Media; namespace ICSharpCode.AvalonEdit.Utils { - static class PixelSnapHelpers + /// + /// Contains static helper methods for aligning stuff on a whole number of + /// + public static class PixelSnapHelpers { - public static Size GetPixelSize() + /// + /// Gets the pixel size on the screen containing visual. + /// This method does not take transforms on visual into account. + /// + public static Size GetPixelSize(Visual visual) { - return new Size(1, 1); + Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice; + return new Size(matrix.M11, matrix.M22); } /// /// Aligns val on the next middle of a pixel. /// - public static double PixelAlign(double val) + /// The value that should be aligned + /// The size of one pixel + public static double PixelAlign(double val, double pixelSize) { // 0 -> 0.5 // 0.1 -> 0.5 // 0.5 -> 0.5 // 0.9 -> 0.5 // 1 -> 1.5 - return Math.Round(val + 0.5) - 0.5; + return pixelSize * (Math.Round((val / pixelSize) + 0.5) - 0.5); } /// /// Aligns the borders of rect on the middles of pixels. /// - public static Rect PixelAlign(Rect rect) + public static Rect PixelAlign(Rect rect, Size pixelSize) { - rect.X = PixelAlign(rect.X); - rect.Y = PixelAlign(rect.Y); - rect.Width = Round(rect.Width); - rect.Height = Round(rect.Height); + rect.X = PixelAlign(rect.X, pixelSize.Width); + rect.Y = PixelAlign(rect.Y, pixelSize.Height); + rect.Width = Round(rect.Width, pixelSize.Width); + rect.Height = Round(rect.Height, pixelSize.Height); return rect; } - public static Point Round(Point val) - { - return new Point(Round(val.X), Round(val.Y)); - } - - public static Rect Round(Rect rect) + /// + /// Rounds val to whole number of pixels. + /// + public static Point Round(Point val, Size pixelSize) { - return new Rect(Round(rect.X), Round(rect.Y), Round(rect.Width), Round(rect.Height)); + return new Point(Round(val.X, pixelSize.Width), Round(val.Y, pixelSize.Height)); } /// - /// Rounds val to a whole number of pixels. + /// Rounds val to whole number of pixels. /// - public static double Round(double val) + public static Rect Round(Rect rect, Size pixelSize) { - return Math.Round(val); + return new Rect(Round(rect.X, pixelSize.Width), Round(rect.Y, pixelSize.Height), + Round(rect.Width, pixelSize.Width), Round(rect.Height, pixelSize.Height)); } /// - /// Rounds val to an even number of pixels. + /// Rounds val to a whole number of pixels. /// - public static double RoundToEven(double val) + public static double Round(double val, double pixelSize) { - // 0 -> 0 - // 1 -> 2 - // 2 -> 2 - // 3 -> 4 - return Math.Round(val / 2) * 2; + return pixelSize * Math.Round(val / pixelSize); } /// - /// Rounds val to an odd number of pixels. + /// Rounds val to an whole odd number of pixels. /// - public static double RoundToOdd(double val) + public static double RoundToOdd(double val, double pixelSize) { - return RoundToEven(val - 1) + 1; + return Round(val - pixelSize, pixelSize * 2) + pixelSize; } } } diff --git a/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs b/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs index 75f3e0f052..2da0863138 100644 --- a/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs +++ b/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs @@ -30,6 +30,7 @@ namespace ICSharpCode.SharpDevelop.Project /// /// Gets/Sets the active project. + /// Returns null if no project is active. /// The getter is thread-safe; the setter may only be called on the main thread. /// public static IProject CurrentProject {