aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretLayer.cs
blob: 105f38d16cca690a22134889d9835b6b71736db3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// 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.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;

using Tango.Scripting.Editors.Rendering;
using Tango.Scripting.Editors.Utils;

namespace Tango.Scripting.Editors.Editing
{
	sealed class CaretLayer : Layer
	{
		bool isVisible;
		Rect caretRectangle;
		
		DispatcherTimer caretBlinkTimer = new DispatcherTimer();
		bool blink;
		
		public CaretLayer(TextView textView) : base(textView, KnownLayer.Caret)
		{
			this.IsHitTestVisible = false;
			caretBlinkTimer.Tick += new EventHandler(caretBlinkTimer_Tick);
		}

		void caretBlinkTimer_Tick(object sender, EventArgs e)
		{
			blink = !blink;
			InvalidateVisual();
		}
		
		public void Show(Rect caretRectangle)
		{
			this.caretRectangle = caretRectangle;
			this.isVisible = true;
			StartBlinkAnimation();
			InvalidateVisual();
		}
		
		public void Hide()
		{
			if (isVisible) {
				isVisible = false;
				StopBlinkAnimation();
				InvalidateVisual();
			}
		}
		
		void StartBlinkAnimation()
		{
			TimeSpan blinkTime = Win32.CaretBlinkTime;
			blink = true; // the caret should visible initially
			// This is important if blinking is disabled (system reports a negative blinkTime)
			if (blinkTime.TotalMilliseconds > 0) {
				caretBlinkTimer.Interval = blinkTime;
				caretBlinkTimer.Start();
			}
		}
		
		void StopBlinkAnimation()
		{
			caretBlinkTimer.Stop();
		}
		
		internal Brush CaretBrush;
		
		protected override void OnRender(DrawingContext drawingContext)
		{
			base.OnRender(drawingContext);
			if (isVisible && blink) {
				Brush caretBrush = this.CaretBrush;
				if (caretBrush == null)
					caretBrush = (Brush)textView.GetValue(TextBlock.ForegroundProperty);
				Rect r = new Rect(caretRectangle.X - textView.HorizontalOffset,
				                  caretRectangle.Y - textView.VerticalOffset,
				                  caretRectangle.Width,
				                  caretRectangle.Height);
				drawingContext.DrawRectangle(caretBrush, null, PixelSnapHelpers.Round(r, PixelSnapHelpers.GetPixelSize(this)));
			}
		}
	}
}
an class="n">emSize, Foreground ); return new Size(text.Width, 0); } /// <inheritdoc/> protected override void OnRender(DrawingContext drawingContext) { TextView textView = this.TextView; Size renderSize = this.RenderSize; if (textView != null && textView.VisualLinesValid) { var foreground = Foreground; foreach (VisualLine line in textView.VisualLines) { int lineNumber = line.FirstDocumentLine.LineNumber; FormattedText text = TextFormatterFactory.CreateFormattedText( this, lineNumber.ToString(CultureInfo.CurrentCulture), typeface, emSize, foreground ); double y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop); drawingContext.DrawText(text, new Point((renderSize.Width / 2) - text.Width + 5, y - textView.VerticalOffset)); } } } /// <inheritdoc/> protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView) { if (oldTextView != null) { oldTextView.VisualLinesChanged -= TextViewVisualLinesChanged; } base.OnTextViewChanged(oldTextView, newTextView); if (newTextView != null) { newTextView.VisualLinesChanged += TextViewVisualLinesChanged; // find the text area belonging to the new text view textArea = newTextView.Services.GetService(typeof(TextArea)) as TextArea; } else { textArea = null; } InvalidateVisual(); } /// <inheritdoc/> protected override void OnDocumentChanged(TextDocument oldDocument, TextDocument newDocument) { if (oldDocument != null) { PropertyChangedEventManager.RemoveListener(oldDocument, this, "LineCount"); } base.OnDocumentChanged(oldDocument, newDocument); if (newDocument != null) { PropertyChangedEventManager.AddListener(newDocument, this, "LineCount"); } OnDocumentLineCountChanged(); } /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/> protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) { if (managerType == typeof(PropertyChangedEventManager)) { OnDocumentLineCountChanged(); return true; } return false; } bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) { return ReceiveWeakEvent(managerType, sender, e); } int maxLineNumberLength = 1; void OnDocumentLineCountChanged() { int documentLineCount = Document != null ? Document.LineCount : 1; int newLength = documentLineCount.ToString(CultureInfo.CurrentCulture).Length; // The margin looks too small when there is only one digit, so always reserve space for // at least two digits if (newLength < 2) newLength = 2; if (newLength != maxLineNumberLength) { maxLineNumberLength = newLength; InvalidateMeasure(); } } void TextViewVisualLinesChanged(object sender, EventArgs e) { InvalidateVisual(); } AnchorSegment selectionStart; bool selecting; /// <inheritdoc/> protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { base.OnMouseLeftButtonDown(e); if (!e.Handled && TextView != null && textArea != null) { e.Handled = true; textArea.Focus(); SimpleSegment currentSeg = GetTextLineSegment(e); if (currentSeg == SimpleSegment.Invalid) return; textArea.Caret.Offset = currentSeg.Offset + currentSeg.Length; if (CaptureMouse()) { selecting = true; selectionStart = new AnchorSegment(Document, currentSeg.Offset, currentSeg.Length); if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) { SimpleSelection simpleSelection = textArea.Selection as SimpleSelection; if (simpleSelection != null) selectionStart = new AnchorSegment(Document, simpleSelection.SurroundingSegment); } textArea.Selection = Selection.Create(textArea, selectionStart); if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) { ExtendSelection(currentSeg); } } } } SimpleSegment GetTextLineSegment(MouseEventArgs e) { Point pos = e.GetPosition(TextView); pos.X = 0; pos.Y += TextView.VerticalOffset; VisualLine vl = TextView.GetVisualLineFromVisualTop(pos.Y); if (vl == null) return SimpleSegment.Invalid; TextLine tl = vl.GetTextLineByVisualYPosition(pos.Y); int visualStartColumn = vl.GetTextLineVisualStartColumn(tl); int visualEndColumn = visualStartColumn + tl.Length; int relStart = vl.FirstDocumentLine.Offset; int startOffset = vl.GetRelativeOffset(visualStartColumn) + relStart; int endOffset = vl.GetRelativeOffset(visualEndColumn) + relStart; if (endOffset == vl.LastDocumentLine.Offset + vl.LastDocumentLine.Length) endOffset += vl.LastDocumentLine.DelimiterLength; return new SimpleSegment(startOffset, endOffset - startOffset); } void ExtendSelection(SimpleSegment currentSeg) { if (currentSeg.Offset < selectionStart.Offset) { textArea.Caret.Offset = currentSeg.Offset; textArea.Selection = Selection.Create(textArea, currentSeg.Offset, selectionStart.Offset + selectionStart.Length); } else { textArea.Caret.Offset = currentSeg.Offset + currentSeg.Length; textArea.Selection = Selection.Create(textArea, selectionStart.Offset, currentSeg.Offset + currentSeg.Length); } } /// <inheritdoc/> protected override void OnMouseMove(MouseEventArgs e) { if (selecting && textArea != null && TextView != null) { e.Handled = true; SimpleSegment currentSeg = GetTextLineSegment(e); if (currentSeg == SimpleSegment.Invalid) return; ExtendSelection(currentSeg); } base.OnMouseMove(e); } /// <inheritdoc/> protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) { if (selecting) { selecting = false; selectionStart = null; ReleaseMouseCapture(); e.Handled = true; } base.OnMouseLeftButtonUp(e); } /// <inheritdoc/> protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) { // accept clicks even when clicking on the background return new PointHitTestResult(this, hitTestParameters.HitPoint); } } }