From 240ffe768718b2de1354a38bc65f20d962983d23 Mon Sep 17 00:00:00 2001 From: Aleksandr Neychev Date: Sat, 8 Aug 2026 22:13:32 +0500 Subject: [PATCH] fixed bug - change tooltip size when change font size --- CursorLang/Interop/PopupWindowNative.cs | 14 ++++ CursorLang/Views/LayoutPopupWindow.xaml.cs | 76 +++++++++++++--------- 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/CursorLang/Interop/PopupWindowNative.cs b/CursorLang/Interop/PopupWindowNative.cs index 5cf69d0..955b350 100644 --- a/CursorLang/Interop/PopupWindowNative.cs +++ b/CursorLang/Interop/PopupWindowNative.cs @@ -101,6 +101,20 @@ internal static class PopupWindowNative SetWindowPos(hWnd, IntPtr.Zero, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } + /// + /// Задаёт положение и размер окна в физических пикселях. + /// + /// + /// Размер выставляется именно так, а не через Width/Height: при первом показе + /// окно ещё подчиняется системному минимальному размеру окна (SM_CXMIN×SM_CYMIN) + /// и подсказка выходит заметно крупнее текста. К моменту вызова окно уже + /// показано и стало popup-окном, на которое это ограничение не действует. + /// + internal static void SetBounds(IntPtr hWnd, int x, int y, int width, int height) + { + SetWindowPos(hWnd, IntPtr.Zero, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE); + } + /// Масштаб монитора, на котором находится точка (1.0 при 96 DPI). internal static double GetScaleAt(Point point) => GetScaleOf(MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST)); diff --git a/CursorLang/Views/LayoutPopupWindow.xaml.cs b/CursorLang/Views/LayoutPopupWindow.xaml.cs index 2f5b764..9ab30ec 100644 --- a/CursorLang/Views/LayoutPopupWindow.xaml.cs +++ b/CursorLang/Views/LayoutPopupWindow.xaml.cs @@ -8,12 +8,13 @@ namespace CursorLang.Views; /// /// Всплывающая подсказка с коротким именем раскладки. -/// Отвечает только за показ и место на экране: когда её убрать, +/// Отвечает только за показ, размер и место на экране: когда её убрать, /// решает . /// public partial class LayoutPopupWindow : Window { private readonly AppSettings _settings; + private Size _contentSize; public LayoutPopupWindow(LayoutPopupViewModel viewModel, AppSettings settings) { @@ -28,37 +29,40 @@ public partial class LayoutPopupWindow : Window /// public void ShowPopup() { - ResizeToContent(); + MeasureContent(); - // Для уже созданного окна двигаем до показа; при самом первом вызове - // хэндла ещё нет, и позиционирование выполнит OnSourceInitialized - MoveToTargetPosition(); + // Уже созданное окно ставим на место до показа, чтобы оно не мелькнуло + // на старой позиции; при самом первом вызове хэндла ещё нет + ApplyBounds(); Show(); + + // Повторяем после показа: до него окно подчиняется системному минимальному + // размеру окна и получается заметно крупнее текста + ApplyBounds(); } - // Хэндл создан, но окно ещё не отрисовано: здесь и настраиваем стили, - // и ставим окно на место, чтобы первый показ прошёл уже на нужном мониторе protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); PopupWindowNative.MakePassive(new WindowInteropHelper(this).Handle); - MoveToTargetPosition(); } - // Размер считаем сами, а не через SizeToContent: тот вычисляет его при создании - // окна, когда содержимое ещё не измерено, и первый показ выходит шире текста. - // Рамок у окна нет, поэтому его размер равен размеру содержимого - private void ResizeToContent() + // Размер содержимого нужен до показа: от него зависит и размер окна, + // и позиция для углов. Рамок у окна нет, поэтому размер окна равен размеру + // содержимого + private void MeasureContent() { var content = (FrameworkElement)Content; content.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); - Width = content.DesiredSize.Width; - Height = content.DesiredSize.Height; + _contentSize = content.DesiredSize; } - private void MoveToTargetPosition() + // Всё считается в физических пикселях: у мониторов разный масштаб, а + // Window.Left/Top/Width/Height пересчитываются по DPI того монитора, + // где окно сейчас, и на соседнем мониторе дают промах + private void ApplyBounds() { IntPtr handle = new WindowInteropHelper(this).Handle; if (handle == IntPtr.Zero) @@ -66,37 +70,43 @@ public partial class LayoutPopupWindow : Window return; } - (int x, int y) = _settings.PlacementMode == PopupPlacementMode.AtCursor - ? GetCursorPosition() - : GetScreenPosition(); - - PopupWindowNative.MoveTo(handle, x, y); + if (_settings.PlacementMode == PopupPlacementMode.AtCursor) + { + ApplyBoundsAtCursor(handle); + } + else + { + ApplyBoundsOnScreen(handle); + } } - // Отступы и размеры заданы в единицах WPF, а окно двигаем в пикселях, - // поэтому всё пересчитывается по масштабу нужного монитора - private (int X, int Y) GetCursorPosition() + private void ApplyBoundsAtCursor(IntPtr handle) { PopupWindowNative.Point cursor = PopupWindowNative.GetCursorPosition(); double scale = PopupWindowNative.GetScaleAt(cursor); - int offset = (int)Math.Round(_settings.CursorOffset * scale); + + int width = ToPixels(_contentSize.Width, scale); + int height = ToPixels(_contentSize.Height, scale); + int offset = ToPixels(_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); + int x = toRight ? cursor.X + offset : cursor.X - offset - width; + int y = toBottom ? cursor.Y + offset : cursor.Y - offset - height; + + PopupWindowNative.SetBounds(handle, x, y, width, height); } - private (int X, int Y) GetScreenPosition() + private void ApplyBoundsOnScreen(IntPtr handle) { (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 + int width = ToPixels(_contentSize.Width, scale); + int height = ToPixels(_contentSize.Height, scale); + int margin = ToPixels(_settings.ScreenMargin, scale); + + (int x, int y) = _settings.ScreenPosition switch { ScreenPosition.TopLeft => (work.Left + margin, work.Top + margin), ScreenPosition.TopRight => (work.Right - margin - width, work.Top + margin), @@ -105,6 +115,8 @@ public partial class LayoutPopupWindow : Window _ => (work.Left + ((work.Right - work.Left - width) / 2), work.Top + ((work.Bottom - work.Top - height) / 2)), }; + + PopupWindowNative.SetBounds(handle, x, y, width, height); } private static int ToPixels(double wpfUnits, double scale) => (int)Math.Round(wpfUnits * scale);