fixed bug - change tooltip size when change font size

This commit is contained in:
2026-08-08 22:13:32 +05:00
parent 1fdfe77d78
commit 240ffe7687
2 changed files with 58 additions and 32 deletions
+14
View File
@@ -101,6 +101,20 @@ internal static class PopupWindowNative
SetWindowPos(hWnd, IntPtr.Zero, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); SetWindowPos(hWnd, IntPtr.Zero, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
} }
/// <summary>
/// Задаёт положение и размер окна в физических пикселях.
/// </summary>
/// <remarks>
/// Размер выставляется именно так, а не через Width/Height: при первом показе
/// окно ещё подчиняется системному минимальному размеру окна (SM_CXMIN×SM_CYMIN)
/// и подсказка выходит заметно крупнее текста. К моменту вызова окно уже
/// показано и стало popup-окном, на которое это ограничение не действует.
/// </remarks>
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);
}
/// <summary>Масштаб монитора, на котором находится точка (1.0 при 96 DPI).</summary> /// <summary>Масштаб монитора, на котором находится точка (1.0 при 96 DPI).</summary>
internal static double GetScaleAt(Point point) => internal static double GetScaleAt(Point point) =>
GetScaleOf(MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST)); GetScaleOf(MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST));
+44 -32
View File
@@ -8,12 +8,13 @@ namespace CursorLang.Views;
/// <summary> /// <summary>
/// Всплывающая подсказка с коротким именем раскладки. /// Всплывающая подсказка с коротким именем раскладки.
/// Отвечает только за показ и место на экране: когда её убрать, /// Отвечает только за показ, размер и место на экране: когда её убрать,
/// решает <see cref="Services.LayoutPopupService"/>. /// решает <see cref="Services.LayoutPopupService"/>.
/// </summary> /// </summary>
public partial class LayoutPopupWindow : Window public partial class LayoutPopupWindow : Window
{ {
private readonly AppSettings _settings; private readonly AppSettings _settings;
private Size _contentSize;
public LayoutPopupWindow(LayoutPopupViewModel viewModel, AppSettings settings) public LayoutPopupWindow(LayoutPopupViewModel viewModel, AppSettings settings)
{ {
@@ -28,37 +29,40 @@ public partial class LayoutPopupWindow : Window
/// </summary> /// </summary>
public void ShowPopup() public void ShowPopup()
{ {
ResizeToContent(); MeasureContent();
// Для уже созданного окна двигаем до показа; при самом первом вызове // Уже созданное окно ставим на место до показа, чтобы оно не мелькнуло
// хэндла ещё нет, и позиционирование выполнит OnSourceInitialized // на старой позиции; при самом первом вызове хэндла ещё нет
MoveToTargetPosition(); ApplyBounds();
Show(); Show();
// Повторяем после показа: до него окно подчиняется системному минимальному
// размеру окна и получается заметно крупнее текста
ApplyBounds();
} }
// Хэндл создан, но окно ещё не отрисовано: здесь и настраиваем стили,
// и ставим окно на место, чтобы первый показ прошёл уже на нужном мониторе
protected override void OnSourceInitialized(EventArgs e) protected override void OnSourceInitialized(EventArgs e)
{ {
base.OnSourceInitialized(e); base.OnSourceInitialized(e);
PopupWindowNative.MakePassive(new WindowInteropHelper(this).Handle); PopupWindowNative.MakePassive(new WindowInteropHelper(this).Handle);
MoveToTargetPosition();
} }
// Размер считаем сами, а не через SizeToContent: тот вычисляет его при создании // Размер содержимого нужен до показа: от него зависит и размер окна,
// окна, когда содержимое ещё не измерено, и первый показ выходит шире текста. // и позиция для углов. Рамок у окна нет, поэтому размер окна равен размеру
// Рамок у окна нет, поэтому его размер равен размеру содержимого // содержимого
private void ResizeToContent() private void MeasureContent()
{ {
var content = (FrameworkElement)Content; var content = (FrameworkElement)Content;
content.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); content.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
Width = content.DesiredSize.Width; _contentSize = content.DesiredSize;
Height = content.DesiredSize.Height;
} }
private void MoveToTargetPosition() // Всё считается в физических пикселях: у мониторов разный масштаб, а
// Window.Left/Top/Width/Height пересчитываются по DPI того монитора,
// где окно сейчас, и на соседнем мониторе дают промах
private void ApplyBounds()
{ {
IntPtr handle = new WindowInteropHelper(this).Handle; IntPtr handle = new WindowInteropHelper(this).Handle;
if (handle == IntPtr.Zero) if (handle == IntPtr.Zero)
@@ -66,37 +70,43 @@ public partial class LayoutPopupWindow : Window
return; return;
} }
(int x, int y) = _settings.PlacementMode == PopupPlacementMode.AtCursor if (_settings.PlacementMode == PopupPlacementMode.AtCursor)
? GetCursorPosition() {
: GetScreenPosition(); ApplyBoundsAtCursor(handle);
}
PopupWindowNative.MoveTo(handle, x, y); else
{
ApplyBoundsOnScreen(handle);
}
} }
// Отступы и размеры заданы в единицах WPF, а окно двигаем в пикселях, private void ApplyBoundsAtCursor(IntPtr handle)
// поэтому всё пересчитывается по масштабу нужного монитора
private (int X, int Y) GetCursorPosition()
{ {
PopupWindowNative.Point cursor = PopupWindowNative.GetCursorPosition(); PopupWindowNative.Point cursor = PopupWindowNative.GetCursorPosition();
double scale = PopupWindowNative.GetScaleAt(cursor); 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 toRight = _settings.CursorCorner is CursorCorner.BottomRight or CursorCorner.TopRight;
bool toBottom = _settings.CursorCorner is CursorCorner.BottomRight or CursorCorner.BottomLeft; bool toBottom = _settings.CursorCorner is CursorCorner.BottomRight or CursorCorner.BottomLeft;
int x = toRight ? cursor.X + offset : cursor.X - offset - ToPixels(Width, scale); int x = toRight ? cursor.X + offset : cursor.X - offset - width;
int y = toBottom ? cursor.Y + offset : cursor.Y - offset - ToPixels(Height, scale); int y = toBottom ? cursor.Y + offset : cursor.Y - offset - height;
return (x, y);
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(); (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.TopLeft => (work.Left + margin, work.Top + margin),
ScreenPosition.TopRight => (work.Right - margin - width, 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.Left + ((work.Right - work.Left - width) / 2),
work.Top + ((work.Bottom - work.Top - height) / 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); private static int ToPixels(double wpfUnits, double scale) => (int)Math.Round(wpfUnits * scale);