using System.Windows; using System.Windows.Interop; using CursorLang.Interop; using CursorLang.Models; using CursorLang.Services; using CursorLang.ViewModels; namespace CursorLang.Views; /// /// The popup with the short name of the layout. /// Responsible only for showing it, its size and its place on screen: when to take it /// down is decided by . /// public partial class LayoutPopupWindow : Window, ILayoutPopupWindow { private readonly AppSettings _settings; private Size _contentSize; public LayoutPopupWindow(LayoutPopupViewModel viewModel, AppSettings settings) { InitializeComponent(); DataContext = viewModel; _settings = settings; // We create the window in advance to set its bounds before the first show: // otherwise it appears at the default size for a moment new WindowInteropHelper(this).EnsureHandle(); } /// /// Shows the popup at the place set by the settings. /// public void ShowPopup() { MeasureContent(); // An already created window is put in place before the show so that it does // not flash in its old position; on the very first call there is no handle yet ApplyBounds(); Show(); // We repeat it after the show: before that the window is subject to the system // minimum window size and comes out noticeably larger than its text ApplyBounds(); } protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); PopupWindowNative.MakePassive(new WindowInteropHelper(this).Handle); } // The content size is needed before the show: both the window size and the corner // position depend on it. The window has no frame, so the window size equals the // content size private void MeasureContent() { var content = (FrameworkElement)Content; content.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); _contentSize = content.DesiredSize; } // Everything is computed in physical pixels: monitors have different scaling, // while Window.Left/Top/Width/Height are converted using the DPI of the monitor // the window is on right now, which misses the target on a neighbouring monitor private void ApplyBounds() { IntPtr handle = new WindowInteropHelper(this).Handle; if (handle == IntPtr.Zero) { return; } if (_settings.PlacementMode == PopupPlacementMode.FixedPoint) { ApplyBoundsOnScreen(handle); } else { ApplyBoundsNearAnchor(handle, GetAnchor()); } } // The anchor point: the caret in the input field or the mouse cursor. The cursor // is a rectangle of zero size, so the corner computation is shared by both private PopupWindowNative.Rect GetAnchor() { if (_settings.PlacementMode == PopupPlacementMode.AtCaret && CaretNative.TryGetCaretRect() is { } caret) { return caret; } return PopupLayout.AsAnchor(PopupWindowNative.GetCursorPosition()); } private void ApplyBoundsNearAnchor(IntPtr handle, PopupWindowNative.Rect anchor) { var anchorPoint = new PopupWindowNative.Point { X = anchor.Left, Y = anchor.Top }; double scale = PopupWindowNative.GetScaleAt(anchorPoint); // Every anchor mode has a side and an offset of its own bool atCaret = _settings.PlacementMode == PopupPlacementMode.AtCaret; AnchorSide side = atCaret ? _settings.CaretSide : _settings.CursorSide; int offset = PopupLayout.ToPixels(atCaret ? _settings.CaretOffset : _settings.CursorOffset, scale); int width = PopupLayout.ToPixels(_contentSize.Width, scale); int height = PopupLayout.ToPixels(_contentSize.Height, scale); PopupWindowNative.Point position = PopupLayout.NearAnchor(anchor, side, offset, width, height); PopupWindowNative.SetBounds(handle, position.X, position.Y, width, height); } private void ApplyBoundsOnScreen(IntPtr handle) { (PopupWindowNative.Rect work, double scale) = PopupWindowNative.GetActiveMonitorWorkArea(); int width = PopupLayout.ToPixels(_contentSize.Width, scale); int height = PopupLayout.ToPixels(_contentSize.Height, scale); int margin = PopupLayout.ToPixels(_settings.ScreenMargin, scale); PopupWindowNative.Point position = PopupLayout.OnScreen(work, _settings.ScreenPosition, margin, width, height); PopupWindowNative.SetBounds(handle, position.X, position.Y, width, height); } }