Skip to content

Instantly share code, notes, and snippets.

@SQL-MisterMagoo
Last active October 24, 2024 14:18
Show Gist options
  • Save SQL-MisterMagoo/81ae8cf8bc1003c1b946c785c1c09993 to your computer and use it in GitHub Desktop.
Save SQL-MisterMagoo/81ae8cf8bc1003c1b946c785c1c09993 to your computer and use it in GitHub Desktop.
Screen position from Caret Position in IWpfTextView
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

using Microsoft.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Formatting;
using Microsoft.VisualStudio.Utilities;

using Window = System.Windows.Window;

namespace SampleCaretToScreen
{
	/// <summary>
	/// Adornment class that draws a square box in the top right hand corner of the viewport
	/// </summary>
	internal sealed class ViewportAdornment1 : IDisposable
	{
		/// <summary>
		/// The width of the square box.
		/// </summary>
		private const double AdornmentWidth = 30;

		/// <summary>
		/// The height of the square box.
		/// </summary>
		private const double AdornmentHeight = 30;

		/// <summary>
		/// Distance from the viewport top to the top of the square box.
		/// </summary>
		private const double TopMargin = 30;

		/// <summary>
		/// Distance from the viewport right to the right end of the square box.
		/// </summary>
		private const double RightMargin = 30;

		/// <summary>
		/// Text view to add the adornment on.
		/// </summary>
		private readonly IWpfTextView view;

		/// <summary>
		/// Adornment image
		/// </summary>
		private readonly Image image;

		/// <summary>
		/// The layer for the adornment.
		/// </summary>
		private readonly IAdornmentLayer adornmentLayer;
		private readonly TextBlock textBlock;
		private readonly Window testWindow;

		public ViewportAdornment1(IWpfTextView view)
		{
			if (view == null)
			{
				throw new ArgumentNullException("view");
			}

			this.view = view;

			this.adornmentLayer = view.GetAdornmentLayer("ViewportAdornment1");

			this.view.GotAggregateFocus += GotAggregateFocus;
			this.view.ViewportLeftChanged += ViewportLeftChanged;
			this.view.Caret.PositionChanged += CaretPositionChanged;
			this.view.LayoutChanged += LayoutChanged;
			this.view.Closed += ViewClosed;

			textBlock = new TextBlock() 
			{ 
				VerticalAlignment = VerticalAlignment.Center,
				HorizontalAlignment = HorizontalAlignment.Center, 
				FontSize = 28 
			};
			testWindow = new System.Windows.Window()
			{
				Background = new SolidColorBrush( GetThemeBackgroundColor()),
				Foreground = new SolidColorBrush( GetThemeForegroundColor()),
				Top = 0,
				Left = 0,
				WindowStyle = WindowStyle.None,
				Content = textBlock,
				Focusable = false,
				Owner = Application.Current.MainWindow,
			};
			testWindow.Show();
		}

		private void ViewportLeftChanged(object sender, EventArgs e)
		{
			if (GetCaretTextBounds() is TextBounds tb)
				MoveWindow(tb);
			else
				testWindow.Visibility = Visibility.Hidden;
		}

		private void ViewClosed(object sender, EventArgs e)
		{
			testWindow.Close();
		}

		private void GotAggregateFocus(object sender, EventArgs e)
		{
			if (GetCaretTextBounds() is TextBounds tb)
				MoveWindow(tb);
			else
				testWindow.Visibility = Visibility.Hidden;
		}

		private TextBounds? GetCaretTextBounds()
		{
			try
			{
				// get the caret position - will throw if off screen
				return view.TextViewLines.GetCharacterBounds(view.Caret.Position.BufferPosition);
			}
			catch { }
			return default;
		}
		private void MoveWindow(TextBounds caretPosition)
		{
			try
			{
				// get the top-left position of the caret line
				Point screenPosition = view.VisualElement.PointToScreen(new Point(0, caretPosition.Top - view.ViewportTop));
				// get the bottom-right position of the caret line
				Point screenPositionRight = view.VisualElement.PointToScreen(new Point(view.ViewportWidth, caretPosition.Bottom - view.ViewportTop));
				double xScale = testWindow.GetDpiXScale();
				double yScale = testWindow.GetDpiYScale();
				Rect windowPosition = new Rect()
				{
					X = (screenPosition.X) / xScale,
					Y = (screenPositionRight.Y) / yScale,
					Width = (screenPositionRight.X - screenPosition.X) / xScale,
					Height = 2 * (screenPositionRight.Y - screenPosition.Y) / yScale
				};
				testWindow.Visibility = Visibility.Visible;
				testWindow.Left = windowPosition.Left;
				testWindow.Top = windowPosition.Top;
				testWindow.Width = windowPosition.Width;
				testWindow.Height = windowPosition.Height;
				testWindow.Topmost = true;
				textBlock.FontSize = (testWindow.Height - 20)  / 1.5 ;
				textBlock.Text = $"Pos: {windowPosition.Left:F0},{windowPosition.Top:F0} Size: {windowPosition.Width:F0} x {windowPosition.Height:F0}";
			}
			catch
			{
				testWindow.Visibility = Visibility.Hidden;
			}
		}
		private void LayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
		{
			if (GetCaretTextBounds() is TextBounds tb)
				MoveWindow(tb);
			else
				testWindow.Visibility = Visibility.Hidden;
		}
		private void CaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
		{
			if (GetCaretTextBounds() is TextBounds caretPosition)
			{
				MoveWindow(caretPosition);
			}
		}
		public void Dispose()
		{
			testWindow.Close();
		}

		private Color GetThemeBackgroundColor()
		{
			ThreadHelper.ThrowIfNotOnUIThread();
			// Get background color using VSColorTheme.GetThemedColor()
			var backgroundColor = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowBackgroundColorKey);

			return Color.FromArgb(
				backgroundColor.A,
				backgroundColor.R,
				backgroundColor.G,
				backgroundColor.B);
		}
		private Color GetThemeForegroundColor()
		{
			ThreadHelper.ThrowIfNotOnUIThread();
			var foregroundColor = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowTextColorKey);

			return Color.FromArgb(
				foregroundColor.A,
				foregroundColor.R,
				foregroundColor.G,
				foregroundColor.B);
		}
	}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment