From 0ae6c8f9f7ad9fc4698a76877a4790d517ee384a Mon Sep 17 00:00:00 2001 From: Martin Garstenauer Date: Sat, 27 Apr 2024 15:47:25 +0200 Subject: [PATCH 1/2] Add custom, clickable margin in demo --- src/AvaloniaEdit.Demo/CustomMargin.cs | 109 +++++++++++++++++++++++ src/AvaloniaEdit.Demo/MainWindow.xaml.cs | 3 + 2 files changed, 112 insertions(+) create mode 100644 src/AvaloniaEdit.Demo/CustomMargin.cs diff --git a/src/AvaloniaEdit.Demo/CustomMargin.cs b/src/AvaloniaEdit.Demo/CustomMargin.cs new file mode 100644 index 00000000..9550ff5a --- /dev/null +++ b/src/AvaloniaEdit.Demo/CustomMargin.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using Avalonia; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Media.Immutable; +using AvaloniaEdit.Editing; +using AvaloniaEdit.Rendering; + +#nullable enable + +namespace AvaloniaEdit.Demo +{ + /// + /// A custom, clickable margin. + /// + internal sealed class CustomMargin : AbstractMargin + { + private readonly IBrush _backgroundBrush = new ImmutableSolidColorBrush(Colors.DarkGray, 0.5f); + private readonly IBrush _pointerOverBrush = new ImmutableSolidColorBrush(new Color(192, 80, 80, 80)); + private readonly IPen _pointerOverPen = new ImmutablePen(new ImmutableSolidColorBrush(new Color(192, 37, 37, 37)), 1); + private readonly IBrush _markerBrush = new ImmutableSolidColorBrush(new Color(192, 196, 65, 74)); + private readonly IPen _markerPen = new ImmutablePen(new ImmutableSolidColorBrush(new Color(192, 186, 17, 28)), 1); + + private readonly List _markedDocumentLines = []; + private int _pointerOverLine = -1; + + public CustomMargin() + { + Cursor = new Cursor(StandardCursorType.Arrow); + } + + protected override void OnTextViewChanged(TextView? oldTextView, TextView? newTextView) + { + if (oldTextView != null) + oldTextView.VisualLinesChanged -= OnVisualLinesChanged; + if (newTextView != null) + newTextView.VisualLinesChanged += OnVisualLinesChanged; + + base.OnTextViewChanged(oldTextView, newTextView); + } + + private void OnVisualLinesChanged(object? sender, EventArgs eventArgs) + { + InvalidateVisual(); + } + + protected override Size MeasureOverride(Size availableSize) + { + return new Size(20, 0); + } + + private int GetLineNumber(PointerEventArgs e) + { + double visualY = e.GetPosition(TextView).Y + TextView.VerticalOffset; + VisualLine visualLine = TextView.GetVisualLineFromVisualTop(visualY); + return visualLine.FirstDocumentLine.LineNumber; + } + + protected override void OnPointerMoved(PointerEventArgs e) + { + _pointerOverLine = GetLineNumber(e); + InvalidateVisual(); + + base.OnPointerMoved(e); + } + + protected override void OnPointerExited(PointerEventArgs e) + { + _pointerOverLine = -1; + + base.OnPointerExited(e); + } + + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + int line = _pointerOverLine = GetLineNumber(e); + + if (!_markedDocumentLines.Remove(line)) + _markedDocumentLines.Add(line); + + _markedDocumentLines.Sort(); + InvalidateVisual(); + e.Handled = true; + + base.OnPointerPressed(e); + } + + public override void Render(DrawingContext context) + { + context.DrawRectangle(_backgroundBrush, null, Bounds); + + if (TextView?.VisualLinesValid == true) + { + foreach (var visualLine in TextView.VisualLines) + { + double y = TextView.VerticalOffset + visualLine.VisualTop + visualLine.Height / 2; + + if (_pointerOverLine == visualLine.FirstDocumentLine.LineNumber) + context.DrawEllipse(_pointerOverBrush, _pointerOverPen, new Point(10, y), 8, 8); + else if (_markedDocumentLines.Contains(visualLine.FirstDocumentLine.LineNumber)) + context.DrawEllipse(_markerBrush, _markerPen, new Point(10, y), 8, 8); + } + } + + base.Render(context); + } + } +} diff --git a/src/AvaloniaEdit.Demo/MainWindow.xaml.cs b/src/AvaloniaEdit.Demo/MainWindow.xaml.cs index c165ded1..bb00314e 100644 --- a/src/AvaloniaEdit.Demo/MainWindow.xaml.cs +++ b/src/AvaloniaEdit.Demo/MainWindow.xaml.cs @@ -105,6 +105,9 @@ public MainWindow() if (i.Delta.Y > 0) _textEditor.FontSize++; else _textEditor.FontSize = _textEditor.FontSize > 1 ? _textEditor.FontSize - 1 : 1; }, RoutingStrategies.Bubble, true); + + // Add a custom margin at the left of the text area, which can be clicked. + _textEditor.TextArea.LeftMargins.Insert(0, new CustomMargin()); } private void Caret_PositionChanged(object sender, EventArgs e) From c0234e93ca85e91829542c4709476b98fceeae7a Mon Sep 17 00:00:00 2001 From: Martin Garstenauer Date: Fri, 31 May 2024 12:47:55 +0200 Subject: [PATCH 2/2] Change color to match Visual Studio breakpoint margin --- src/AvaloniaEdit.Demo/CustomMargin.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/AvaloniaEdit.Demo/CustomMargin.cs b/src/AvaloniaEdit.Demo/CustomMargin.cs index 9550ff5a..0a6b1c00 100644 --- a/src/AvaloniaEdit.Demo/CustomMargin.cs +++ b/src/AvaloniaEdit.Demo/CustomMargin.cs @@ -12,15 +12,16 @@ namespace AvaloniaEdit.Demo { /// - /// A custom, clickable margin. + /// A custom margin that draws a red dot when clicked. (Similar to the breakpoint margin the + /// Visual Studio editor.) /// internal sealed class CustomMargin : AbstractMargin { - private readonly IBrush _backgroundBrush = new ImmutableSolidColorBrush(Colors.DarkGray, 0.5f); + private readonly IBrush _backgroundBrush = new ImmutableSolidColorBrush(new Color(255, 51, 51, 51)); private readonly IBrush _pointerOverBrush = new ImmutableSolidColorBrush(new Color(192, 80, 80, 80)); private readonly IPen _pointerOverPen = new ImmutablePen(new ImmutableSolidColorBrush(new Color(192, 37, 37, 37)), 1); - private readonly IBrush _markerBrush = new ImmutableSolidColorBrush(new Color(192, 196, 65, 74)); - private readonly IPen _markerPen = new ImmutablePen(new ImmutableSolidColorBrush(new Color(192, 186, 17, 28)), 1); + private readonly IBrush _markerBrush = new ImmutableSolidColorBrush(new Color(255, 195, 81, 92)); + private readonly IPen _markerPen = new ImmutablePen(new ImmutableSolidColorBrush(new Color(255, 240, 92, 104)), 1); private readonly List _markedDocumentLines = []; private int _pointerOverLine = -1;