using System.Windows; using System.Windows.Interop; using CursorLang.Interop; using CursorLang.Models; using CursorLang.ViewModels; namespace CursorLang.Views; /// /// Всплывающая подсказка с коротким именем раскладки. /// Отвечает только за показ и место на экране: когда её убрать, /// решает . /// public partial class LayoutPopupWindow : Window { private readonly AppSettings _settings; public LayoutPopupWindow(LayoutPopupViewModel viewModel, AppSettings settings) { InitializeComponent(); DataContext = viewModel; _settings = settings; } /// /// Показывает подсказку в месте, заданном настройками. /// public void ShowPopup() { ResizeToContent(); // Для уже созданного окна двигаем до показа; при самом первом вызове // хэндла ещё нет, и позиционирование выполнит OnSourceInitialized MoveToTargetPosition(); Show(); } // Хэндл создан, но окно ещё не отрисовано: здесь и настраиваем стили, // и ставим окно на место, чтобы первый показ прошёл уже на нужном мониторе protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); PopupWindowNative.MakePassive(new WindowInteropHelper(this).Handle); MoveToTargetPosition(); } // Размер считаем сами, а не через SizeToContent: тот вычисляет его при создании // окна, когда содержимое ещё не измерено, и первый показ выходит шире текста. // Рамок у окна нет, поэтому его размер равен размеру содержимого private void ResizeToContent() { var content = (FrameworkElement)Content; content.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); Width = content.DesiredSize.Width; Height = content.DesiredSize.Height; } private void MoveToTargetPosition() { IntPtr handle = new WindowInteropHelper(this).Handle; if (handle == IntPtr.Zero) { return; } (int x, int y) = _settings.PlacementMode == PopupPlacementMode.AtCursor ? GetCursorPosition() : GetScreenPosition(); PopupWindowNative.MoveTo(handle, x, y); } // Отступы и размеры заданы в единицах WPF, а окно двигаем в пикселях, // поэтому всё пересчитывается по масштабу нужного монитора private (int X, int Y) GetCursorPosition() { PopupWindowNative.Point cursor = PopupWindowNative.GetCursorPosition(); double scale = PopupWindowNative.GetScaleAt(cursor); int offset = (int)Math.Round(_settings.CursorOffset * scale); bool toRight = _settings.CursorCorner is CursorCorner.BottomRight or CursorCorner.TopRight; bool toBottom = _settings.CursorCorner is CursorCorner.BottomRight or CursorCorner.BottomLeft; int x = toRight ? cursor.X + offset : cursor.X - offset - ToPixels(Width, scale); int y = toBottom ? cursor.Y + offset : cursor.Y - offset - ToPixels(Height, scale); return (x, y); } private (int X, int Y) GetScreenPosition() { (PopupWindowNative.Rect work, double scale) = PopupWindowNative.GetActiveMonitorWorkArea(); int margin = (int)Math.Round(_settings.ScreenMargin * scale); int width = ToPixels(Width, scale); int height = ToPixels(Height, scale); return _settings.ScreenPosition switch { ScreenPosition.TopLeft => (work.Left + margin, work.Top + margin), ScreenPosition.TopRight => (work.Right - margin - width, work.Top + margin), ScreenPosition.BottomLeft => (work.Left + margin, work.Bottom - margin - height), ScreenPosition.BottomRight => (work.Right - margin - width, work.Bottom - margin - height), _ => (work.Left + ((work.Right - work.Left - width) / 2), work.Top + ((work.Bottom - work.Top - height) / 2)), }; } private static int ToPixels(double wpfUnits, double scale) => (int)Math.Round(wpfUnits * scale); }