From 51c28dc006edc2924051e224ce9d3effe1bdad08 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 23 Mar 2012 18:26:35 +0100 Subject: [PATCH] Add EnhancedScrollBar. --- .../Project/Src/Refactoring/IssueManager.cs | 10 +- .../AvalonEdit.AddIn/AvalonEdit.AddIn.csproj | 1 + .../AvalonEdit.AddIn/Src/CodeEditor.cs | 2 + .../AvalonEdit.AddIn/Src/EnhancedScrollBar.cs | 277 ++++++++++++++++++ .../AvalonEdit.AddIn/Src/TextMarkerService.cs | 53 ++-- .../AvalonEdit.AddIn/themes/generic.xaml | 2 +- .../Base/Project/Src/Editor/ITextMarker.cs | 30 +- .../Services/Debugger/BreakpointBookmark.cs | 2 + .../Src/Services/Tasks/ErrorPainter.cs | 2 +- 9 files changed, 345 insertions(+), 34 deletions(-) create mode 100644 src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/EnhancedScrollBar.cs diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/IssueManager.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/IssueManager.cs index 130ab1d8a7..9546c32a4b 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/IssueManager.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/IssueManager.cs @@ -166,12 +166,14 @@ namespace CSharpBinding.Refactoring return; marker = markerService.Create(startOffset, endOffset - startOffset); marker.ToolTip = this.Description; + + Color color = GetColor(this.Severity); + color.A = 186; + marker.MarkerColor = color; + marker.MarkerTypes = TextMarkerTypes.ScrollBarRightTriangle; switch (Provider.DefaultMarker) { case IssueMarker.Underline: - Color underlineColor = GetColor(this.Severity); - underlineColor.A = 186; - marker.MarkerType = TextMarkerType.SquigglyUnderline; - marker.MarkerColor = underlineColor; + marker.MarkerTypes |= TextMarkerTypes.SquigglyUnderline; break; case IssueMarker.GrayOut: marker.ForegroundColor = SystemColors.GrayTextColor; diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj index fad33ab8b8..d0c5359736 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj @@ -98,6 +98,7 @@ + diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs index 5c25dd4acd..cbb134bd4d 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs @@ -213,6 +213,7 @@ namespace ICSharpCode.AvalonEdit.AddIn if (changeWatcher != null) { codeEditorView.TextArea.LeftMargins.Add(new ChangeMarkerMargin(changeWatcher)); } + textView.Services.AddService(typeof(EnhancedScrollBar), new EnhancedScrollBar(codeEditorView, textMarkerService, changeWatcher)); codeEditorView.TextArea.MouseRightButtonDown += TextAreaMouseRightButtonDown; codeEditorView.TextArea.ContextMenuOpening += TextAreaContextMenuOpening; @@ -234,6 +235,7 @@ namespace ICSharpCode.AvalonEdit.AddIn { foreach (var d in textEditor.TextArea.LeftMargins.OfType()) d.Dispose(); + ((EnhancedScrollBar)textEditor.TextArea.GetService(typeof(EnhancedScrollBar))).Dispose(); textEditor.Dispose(); } diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/EnhancedScrollBar.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/EnhancedScrollBar.cs new file mode 100644 index 0000000000..c57e7fadb5 --- /dev/null +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/EnhancedScrollBar.cs @@ -0,0 +1,277 @@ +// 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; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using ICSharpCode.SharpDevelop.Editor; + +namespace ICSharpCode.AvalonEdit.AddIn +{ + /// + /// Scrollbar that shows markers. + /// + public class EnhancedScrollBar : IDisposable + { + readonly TextEditor editor; + readonly TextMarkerService textMarkerService; + readonly IChangeWatcher changeWatcher; + TrackBackground trackBackground; + TrackAdorner trackAdorner; + + public EnhancedScrollBar(TextEditor editor, TextMarkerService textMarkerService, IChangeWatcher changeWatcher) + { + if (editor == null) + throw new ArgumentNullException("editor"); + this.editor = editor; + this.textMarkerService = textMarkerService; + this.changeWatcher = changeWatcher; + + editor.Loaded += editor_Loaded; + if (editor.IsLoaded) { + editor_Loaded(null, null); + } + } + + public void Dispose() + { + editor.Loaded -= editor_Loaded; + if (trackBackground != null) { + trackBackground.Remove(); + trackBackground = null; + } + if (trackAdorner != null) { + trackAdorner.Remove(); + trackAdorner = null; + } + } + + #region Initialize UI + bool isUIInitialized; + + void editor_Loaded(object sender, RoutedEventArgs e) + { + if (isUIInitialized) + return; + isUIInitialized = true; + editor.ApplyTemplate(); + var scrollViewer = (ScrollViewer)editor.Template.FindName("PART_ScrollViewer", editor); + if (scrollViewer == null) + return; + scrollViewer.ApplyTemplate(); + var vScrollBar = (ScrollBar)scrollViewer.Template.FindName("PART_VerticalScrollBar", scrollViewer); + var hScrollBar = (ScrollBar)scrollViewer.Template.FindName("PART_HorizontalScrollBar", scrollViewer); + // make both scrollbars transparent so that they look consistent + MakeThumbTransparent(vScrollBar); + MakeThumbTransparent(hScrollBar); + if (vScrollBar == null) + return; + Track track = (Track)vScrollBar.Template.FindName("PART_Track", vScrollBar); + if (track == null) + return; + Grid grid = VisualTreeHelper.GetParent(track) as Grid; + if (grid != null) { + trackBackground = new TrackBackground(this); + Grid.SetColumn(trackBackground, Grid.GetColumn(track)); + Grid.SetRow(trackBackground, Grid.GetRow(track)); + Grid.SetColumnSpan(trackBackground, Grid.GetColumnSpan(track)); + Grid.SetRowSpan(trackBackground, Grid.GetRowSpan(track)); + Panel.SetZIndex(track, 1); + grid.Children.Add(trackBackground); + } + AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(track); + if (adornerLayer != null) { + trackAdorner = new TrackAdorner(this, adornerLayer, track); + } + } + #endregion + + #region MakeThumbTransparent + List transparentThumbs = new List(); + const double thumbOpacity = 0.7; + static readonly Duration animationDuration = new Duration(TimeSpan.FromSeconds(0.25)); + + void MakeThumbTransparent(ScrollBar scrollBar) + { + if (scrollBar == null) + return; + Track track = (Track)scrollBar.Template.FindName("PART_Track", scrollBar); + if (track == null) + return; + for (int i = 0; i < VisualTreeHelper.GetChildrenCount(track); i++) { + var thumb = VisualTreeHelper.GetChild(track, i) as Thumb; + if (thumb != null) { + thumb.Opacity = thumbOpacity; + thumb.MouseEnter += thumb_MouseEnter; + thumb.MouseLeave += thumb_MouseLeave; + transparentThumbs.Add(thumb); + break; + } + } + } + + void ClearTransparencyFromThumbs() + { + foreach (var thumb in transparentThumbs) { + thumb.MouseEnter -= thumb_MouseEnter; + thumb.MouseLeave -= thumb_MouseLeave; + thumb.ClearValue(Thumb.OpacityProperty); + } + } + + void thumb_MouseEnter(object sender, MouseEventArgs e) + { + var thumb = (Thumb)sender; + thumb.BeginAnimation(Thumb.OpacityProperty, new DoubleAnimation(1, animationDuration, FillBehavior.HoldEnd)); + } + + void thumb_MouseLeave(object sender, MouseEventArgs e) + { + var thumb = (Thumb)sender; + thumb.BeginAnimation(Thumb.OpacityProperty, new DoubleAnimation(thumbOpacity, animationDuration, FillBehavior.HoldEnd)); + } + #endregion + + static Brush GetBrush(Color markerColor) + { + SolidColorBrush brush = new SolidColorBrush(markerColor); + brush.Freeze(); + return brush; + } + + #region TrackBackground + sealed class TrackBackground : UIElement + { + readonly TextEditor editor; + readonly TextMarkerService textMarkerService; + readonly IChangeWatcher changeWatcher; + + public TrackBackground(EnhancedScrollBar enhanchedScrollBar) + { + this.editor = enhanchedScrollBar.editor; + this.textMarkerService = enhanchedScrollBar.textMarkerService; + this.changeWatcher = enhanchedScrollBar.changeWatcher; + + textMarkerService.RedrawRequested += textMarkerService_RedrawRequested; + } + + public void Remove() + { + textMarkerService.RedrawRequested -= textMarkerService_RedrawRequested; + + Grid grid = (Grid)VisualTreeHelper.GetParent(this); + grid.Children.Remove(this); + } + + void textMarkerService_RedrawRequested(object sender, EventArgs e) + { + InvalidateVisual(); + } + + protected override void OnRender(DrawingContext drawingContext) + { + var renderSize = this.RenderSize; + var document = editor.Document; + var textView = editor.TextArea.TextView; + double documentHeight = textView.DocumentHeight; + foreach (var marker in textMarkerService.TextMarkers) { + if ((marker.MarkerTypes & (TextMarkerTypes.LineInScrollBar | TextMarkerTypes.CircleInScrollBar)) == 0) + continue; + var location = document.GetLocation(marker.StartOffset); + double visualTop = textView.GetVisualTopByDocumentLine(location.Line); + double renderPos = visualTop / documentHeight * renderSize.Height; + var brush = GetBrush(marker.MarkerColor); + if ((marker.MarkerTypes & (TextMarkerTypes.LineInScrollBar)) != 0) { + drawingContext.DrawRectangle(brush, null, new Rect(3, renderPos - 1, renderSize.Width - 6, 2)); + } + if ((marker.MarkerTypes & (TextMarkerTypes.CircleInScrollBar)) != 0) { + const double radius = 3; + drawingContext.DrawEllipse(brush, null, new Point(renderSize.Width / 2, renderPos), radius, radius); + } + } + } + } + #endregion + + #region TrackAdorner + sealed class TrackAdorner : Adorner + { + readonly AdornerLayer adornerLayer; + readonly TextEditor editor; + readonly TextMarkerService textMarkerService; + readonly StreamGeometry triangleGeometry; + + public TrackAdorner(EnhancedScrollBar enhanchedScrollBar, AdornerLayer adornerLayer, UIElement adornedElement) + : base(adornedElement) + { + this.adornerLayer = adornerLayer; + this.editor = enhanchedScrollBar.editor; + this.textMarkerService = enhanchedScrollBar.textMarkerService; + + triangleGeometry = new StreamGeometry(); + using (var ctx = triangleGeometry.Open()) { + const double triangleSize = 6.5; + const double right = (triangleSize * 0.866) / 2; + const double left = -right; + ctx.BeginFigure(new Point(left, triangleSize / 2), true, true); + ctx.LineTo(new Point(left, -triangleSize / 2), true, false); + ctx.LineTo(new Point(right, 0), true, false); + } + triangleGeometry.Freeze(); + + adornerLayer.Add(this); + textMarkerService.RedrawRequested += textMarkerService_RedrawRequested; + } + + public void Remove() + { + textMarkerService.RedrawRequested -= textMarkerService_RedrawRequested; + adornerLayer.Remove(this); + } + + void textMarkerService_RedrawRequested(object sender, EventArgs e) + { + InvalidateVisual(); + } + + protected override void OnRender(DrawingContext drawingContext) + { + var renderSize = this.RenderSize; + var document = editor.Document; + var textView = editor.TextArea.TextView; + double documentHeight = textView.DocumentHeight; + foreach (var marker in textMarkerService.TextMarkers) { + if ((marker.MarkerTypes & (TextMarkerTypes.ScrollBarLeftTriangle | TextMarkerTypes.ScrollBarRightTriangle)) == 0) + continue; + var location = document.GetLocation(marker.StartOffset); + double visualTop = textView.GetVisualTopByDocumentLine(location.Line); + double renderPos = visualTop / documentHeight * renderSize.Height; + var brush = GetBrush(marker.MarkerColor); + + var translateTransform = new TranslateTransform(6, renderPos); + translateTransform.Freeze(); + drawingContext.PushTransform(translateTransform); + + if ((marker.MarkerTypes & (TextMarkerTypes.ScrollBarLeftTriangle)) != 0) { + var scaleTransform = new ScaleTransform(-1, 1); + scaleTransform.Freeze(); + drawingContext.PushTransform(scaleTransform); + drawingContext.DrawGeometry(brush, null, triangleGeometry); + drawingContext.Pop(); + } + if ((marker.MarkerTypes & (TextMarkerTypes.ScrollBarRightTriangle)) != 0) { + drawingContext.DrawGeometry(brush, null, triangleGeometry); + } + drawingContext.Pop(); + } + } + } + #endregion + } +} diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs index 42c691d64c..e81846ba08 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs @@ -118,7 +118,11 @@ namespace ICSharpCode.AvalonEdit.AddIn internal void Redraw(ISegment segment) { codeEditor.Redraw(segment, DispatcherPriority.Normal); + if (RedrawRequested != null) + RedrawRequested(this, EventArgs.Empty); } + + public event EventHandler RedrawRequested; #endregion #region DocumentColorizingTransformer @@ -182,30 +186,31 @@ namespace ICSharpCode.AvalonEdit.AddIn drawingContext.DrawGeometry(brush, null, geometry); } } - if (marker.MarkerType != TextMarkerType.None) { + if ((marker.MarkerTypes & (TextMarkerTypes.SquigglyUnderline | TextMarkerTypes.NormalUnderline)) != 0) { foreach (Rect r in BackgroundGeometryBuilder.GetRectsForSegment(textView, marker)) { Point startPoint = r.BottomLeft; Point endPoint = r.BottomRight; Pen usedPen = new Pen(new SolidColorBrush(marker.MarkerColor), 1); usedPen.Freeze(); - switch (marker.MarkerType) { - case TextMarkerType.SquigglyUnderline: - double offset = 2.5; - - int count = Math.Max((int)((endPoint.X - startPoint.X) / offset) + 1, 4); - - StreamGeometry geometry = new StreamGeometry(); - - using (StreamGeometryContext ctx = geometry.Open()) { - ctx.BeginFigure(startPoint, false, false); - ctx.PolyLineTo(CreatePoints(startPoint, endPoint, offset, count).ToArray(), true, false); - } - - geometry.Freeze(); - - drawingContext.DrawGeometry(Brushes.Transparent, usedPen, geometry); - break; + if ((marker.MarkerTypes & TextMarkerTypes.SquigglyUnderline) != 0) { + double offset = 2.5; + + int count = Math.Max((int)((endPoint.X - startPoint.X) / offset) + 1, 4); + + StreamGeometry geometry = new StreamGeometry(); + + using (StreamGeometryContext ctx = geometry.Open()) { + ctx.BeginFigure(startPoint, false, false); + ctx.PolyLineTo(CreatePoints(startPoint, endPoint, offset, count).ToArray(), true, false); + } + + geometry.Freeze(); + + drawingContext.DrawGeometry(Brushes.Transparent, usedPen, geometry); + } + if ((marker.MarkerTypes & TextMarkerTypes.NormalUnderline) != 0) { + drawingContext.DrawLine(usedPen, startPoint, endPoint); } } } @@ -231,7 +236,7 @@ namespace ICSharpCode.AvalonEdit.AddIn this.service = service; this.StartOffset = startOffset; this.Length = length; - this.markerType = TextMarkerType.None; + this.markerTypes = TextMarkerTypes.None; } public event EventHandler Deleted; @@ -282,13 +287,13 @@ namespace ICSharpCode.AvalonEdit.AddIn public object Tag { get; set; } - TextMarkerType markerType; + TextMarkerTypes markerTypes; - public TextMarkerType MarkerType { - get { return markerType; } + public TextMarkerTypes MarkerTypes { + get { return markerTypes; } set { - if (markerType != value) { - markerType = value; + if (markerTypes != value) { + markerTypes = value; Redraw(); } } diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/themes/generic.xaml b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/themes/generic.xaml index 9ea4a275ea..d16f35dace 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/themes/generic.xaml +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/themes/generic.xaml @@ -27,7 +27,7 @@ BorderThickness="{TemplateBinding BorderThickness}" TextOptions.TextFormattingMode="{Binding CurrentZoom, ElementName=PART_ScrollViewer, Converter={x:Static local:ZoomLevelToTextFormattingModeConverter.Instance}}"> - diff --git a/src/Main/Base/Project/Src/Editor/ITextMarker.cs b/src/Main/Base/Project/Src/Editor/ITextMarker.cs index 286b2c9b5e..5f308934b6 100644 --- a/src/Main/Base/Project/Src/Editor/ITextMarker.cs +++ b/src/Main/Base/Project/Src/Editor/ITextMarker.cs @@ -55,7 +55,7 @@ namespace ICSharpCode.SharpDevelop.Editor /// /// Gets/Sets the type of the marker. Use TextMarkerType.None for normal markers. /// - TextMarkerType MarkerType { get; set; } + TextMarkerTypes MarkerTypes { get; set; } /// /// Gets/Sets the color of the marker. @@ -73,16 +73,38 @@ namespace ICSharpCode.SharpDevelop.Editor object ToolTip { get; set; } } - public enum TextMarkerType + [Flags] + public enum TextMarkerTypes { /// /// Use no marker /// - None, + None = 0x0000, /// /// Use squiggly underline marker /// - SquigglyUnderline + SquigglyUnderline = 0x001, + /// + /// Normal underline. + /// + NormalUnderline = 0x002, + + /// + /// Horizontal line in the scroll bar. + /// + LineInScrollBar = 0x0100, + /// + /// Small triangle in the scroll bar, pointing to the right. + /// + ScrollBarRightTriangle = 0x0400, + /// + /// Small triangle in the scroll bar, pointing to the left. + /// + ScrollBarLeftTriangle = 0x0800, + /// + /// Small circle in the scroll bar. + /// + CircleInScrollBar = 0x1000 } public interface ITextMarkerService diff --git a/src/Main/Base/Project/Src/Services/Debugger/BreakpointBookmark.cs b/src/Main/Base/Project/Src/Services/Debugger/BreakpointBookmark.cs index a6237510fe..1e6df4f14e 100644 --- a/src/Main/Base/Project/Src/Services/Debugger/BreakpointBookmark.cs +++ b/src/Main/Base/Project/Src/Services/Debugger/BreakpointBookmark.cs @@ -113,6 +113,8 @@ namespace ICSharpCode.SharpDevelop.Debugging ITextMarker marker = markerService.Create(line.Offset, line.Length); marker.BackgroundColor = Color.FromRgb(180, 38, 38); marker.ForegroundColor = Colors.White; + marker.MarkerColor = Color.FromRgb(180, 38, 38); + marker.MarkerTypes = TextMarkerTypes.CircleInScrollBar; return marker; } diff --git a/src/Main/Base/Project/Src/Services/Tasks/ErrorPainter.cs b/src/Main/Base/Project/Src/Services/Tasks/ErrorPainter.cs index 8c082cf997..8b0f1e0deb 100644 --- a/src/Main/Base/Project/Src/Services/Tasks/ErrorPainter.cs +++ b/src/Main/Base/Project/Src/Services/Tasks/ErrorPainter.cs @@ -160,7 +160,7 @@ namespace ICSharpCode.SharpDevelop } marker.MarkerColor = markerColor; - marker.MarkerType = TextMarkerType.SquigglyUnderline; + marker.MarkerTypes = TextMarkerTypes.SquigglyUnderline | TextMarkerTypes.LineInScrollBar; marker.ToolTip = task.Description;