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);