diff --git a/src/Magpie.App/App.cpp b/src/Magpie.App/App.cpp index b0a4a1a1f..b59f8a02c 100644 --- a/src/Magpie.App/App.cpp +++ b/src/Magpie.App/App.cpp @@ -82,13 +82,8 @@ StartUpOptions App::Initialize(int) { } result.IsError = false; - const RECT& windowRect = settings.WindowRect(); - result.MainWndRect = { - (float)windowRect.left, - (float)windowRect.top, - (float)windowRect.right, - (float)windowRect.bottom - }; + result.MainWindowCenter = settings.MainWindowCenter(); + result.MainWindowSizeInDips = settings.MainWindowSizeInDips(); result.IsWndMaximized= settings.IsWindowMaximized(); result.IsNeedElevated = settings.IsAlwaysRunAsAdmin(); diff --git a/src/Magpie.App/App.idl b/src/Magpie.App/App.idl index d08f97cd4..df6748bc2 100644 --- a/src/Magpie.App/App.idl +++ b/src/Magpie.App/App.idl @@ -43,7 +43,8 @@ namespace Magpie.App { }; struct StartUpOptions { - Windows.Foundation.Rect MainWndRect; + Windows.Foundation.Point MainWindowCenter; + Windows.Foundation.Size MainWindowSizeInDips; Boolean IsError; Boolean IsWndMaximized; Boolean IsNeedElevated; diff --git a/src/Magpie.App/AppSettings.cpp b/src/Magpie.App/AppSettings.cpp index 5161c84af..375aba1af 100644 --- a/src/Magpie.App/AppSettings.cpp +++ b/src/Magpie.App/AppSettings.cpp @@ -13,6 +13,9 @@ #include "JsonHelper.h" #include "ScalingMode.h" #include "LocalizationService.h" +#include + +#pragma comment(lib, "Shcore.lib") using namespace ::Magpie::Core; @@ -449,13 +452,18 @@ void AppSettings::_UpdateWindowPlacement() noexcept { return; } - _windowRect = { - wp.rcNormalPosition.left, - wp.rcNormalPosition.top, - wp.rcNormalPosition.right - wp.rcNormalPosition.left, - wp.rcNormalPosition.bottom - wp.rcNormalPosition.top + _mainWindowCenter = { + (wp.rcNormalPosition.left + wp.rcNormalPosition.right) / 2.0f, + (wp.rcNormalPosition.top + wp.rcNormalPosition.bottom) / 2.0f + }; + + const float dpiFactor = GetDpiForWindow(hwndMain) / float(USER_DEFAULT_SCREEN_DPI); + _mainWindowSizeInDips = { + (wp.rcNormalPosition.right - wp.rcNormalPosition.left) / dpiFactor, + (wp.rcNormalPosition.bottom - wp.rcNormalPosition.top) / dpiFactor, }; - _isWindowMaximized = wp.showCmd == SW_MAXIMIZE; + + _isMainWindowMaximized = wp.showCmd == SW_MAXIMIZE; } bool AppSettings::_Save(const _AppSettingsData& data) noexcept { @@ -484,16 +492,16 @@ bool AppSettings::_Save(const _AppSettingsData& data) noexcept { writer.Key("windowPos"); writer.StartObject(); - writer.Key("x"); - writer.Int(data._windowRect.left); - writer.Key("y"); - writer.Int(data._windowRect.top); + writer.Key("centerX"); + writer.Double(data._mainWindowCenter.X); + writer.Key("centerY"); + writer.Double(data._mainWindowCenter.Y); writer.Key("width"); - writer.Uint((uint32_t)data._windowRect.right); + writer.Double(data._mainWindowSizeInDips.Width); writer.Key("height"); - writer.Uint((uint32_t)data._windowRect.bottom); + writer.Double(data._mainWindowSizeInDips.Height); writer.Key("maximized"); - writer.Bool(data._isWindowMaximized); + writer.Bool(data._isMainWindowMaximized); writer.EndObject(); writer.Key("shortcuts"); @@ -608,25 +616,49 @@ void AppSettings::_LoadSettings(const rapidjson::GenericObjectvalue.IsObject()) { - const auto& windowRectObj = windowPosNode->value.GetObj(); - - int x = 0; - int y = 0; - if (JsonHelper::ReadInt(windowRectObj, "x", x, true) - && JsonHelper::ReadInt(windowRectObj, "y", y, true)) { - _windowRect.left = x; - _windowRect.top = y; - } - - uint32_t width = 0; - uint32_t height = 0; - if (JsonHelper::ReadUInt(windowRectObj, "width", width, true) - && JsonHelper::ReadUInt(windowRectObj, "height", height, true)) { - _windowRect.right = (LONG)width; - _windowRect.bottom = (LONG)height; + const auto& windowPosObj = windowPosNode->value.GetObj(); + + Point center{}; + Size size{}; + if (JsonHelper::ReadFloat(windowPosObj, "centerX", center.X, true) && + JsonHelper::ReadFloat(windowPosObj, "centerY", center.Y, true) && + JsonHelper::ReadFloat(windowPosObj, "width", size.Width, true) && + JsonHelper::ReadFloat(windowPosObj, "height", size.Height, true)) { + _mainWindowCenter = center; + _mainWindowSizeInDips = size; + } else { + // 尽最大努力和旧版本兼容 + int x = 0; + int y = 0; + uint32_t width = 0; + uint32_t height = 0; + if (JsonHelper::ReadInt(windowPosObj, "x", x, true) && + JsonHelper::ReadInt(windowPosObj, "y", y, true) && + JsonHelper::ReadUInt(windowPosObj, "width", width, true) && + JsonHelper::ReadUInt(windowPosObj, "height", height, true)) { + _mainWindowCenter = { + x + width / 2.0f, + y + height / 2.0f + }; + + // 如果窗口位置不存在屏幕则使用主屏幕的缩放,猜错的后果仅是窗口尺寸错误, + // 无论如何原始缩放信息已经丢失。 + const HMONITOR hMon = MonitorFromPoint( + { std::lroundf(_mainWindowCenter.X), std::lroundf(_mainWindowCenter.Y) }, + MONITOR_DEFAULTTOPRIMARY + ); + + UINT dpi = USER_DEFAULT_SCREEN_DPI; + GetDpiForMonitor(hMon, MDT_EFFECTIVE_DPI, &dpi, &dpi); + const float dpiFactor = dpi / float(USER_DEFAULT_SCREEN_DPI); + _mainWindowSizeInDips = { + width / dpiFactor, + height / dpiFactor + }; + } } - JsonHelper::ReadBool(windowRectObj, "maximized", _isWindowMaximized); + JsonHelper::ReadBool(windowPosObj, "maximized", _isMainWindowMaximized); } auto shortcutsNode = root.FindMember("shortcuts"); @@ -742,7 +774,7 @@ bool AppSettings::_LoadProfile( const rapidjson::GenericObject& profileObj, Profile& profile, bool isDefault -) { +) const { if (!isDefault) { if (!JsonHelper::ReadString(profileObj, "name", profile.name, true)) { return false; diff --git a/src/Magpie.App/AppSettings.h b/src/Magpie.App/AppSettings.h index 9582d0532..9eea0cf53 100644 --- a/src/Magpie.App/AppSettings.h +++ b/src/Magpie.App/AppSettings.h @@ -36,8 +36,10 @@ struct _AppSettingsData { // -1 表示使用系统设置 int _language = -1; - // X, Y, 长, 高 - RECT _windowRect{ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT }; + // 保存窗口中心点和 DPI 无关的窗口尺寸 + Point _mainWindowCenter{}; + // 小于零表示默认位置和尺寸 + Size _mainWindowSizeInDips{ -1.0f,-1.0f }; Theme _theme = Theme::System; // 必须在 1~5 之间 @@ -59,7 +61,7 @@ struct _AppSettingsData { bool _isInlineParams = false; bool _isShowTrayIcon = true; bool _isAutoRestore = false; - bool _isWindowMaximized = false; + bool _isMainWindowMaximized = false; bool _isAutoCheckForUpdates = true; bool _isCheckForPreviewUpdates = false; }; @@ -115,12 +117,16 @@ class AppSettings : private _AppSettingsData { _themeChangedEvent.remove(token); } - const RECT& WindowRect() const noexcept { - return _windowRect; + Point MainWindowCenter() const noexcept { + return _mainWindowCenter; + } + + Size MainWindowSizeInDips() const noexcept { + return _mainWindowSizeInDips; } bool IsWindowMaximized() const noexcept { - return _isWindowMaximized; + return _isMainWindowMaximized; } const Shortcut& GetShortcut(ShortcutAction action) const { @@ -363,7 +369,7 @@ class AppSettings : private _AppSettingsData { const rapidjson::GenericObject& profileObj, Profile& profile, bool isDefault = false - ); + ) const; bool _SetDefaultShortcuts(); void _SetDefaultScalingModes(); diff --git a/src/Magpie/MainWindow.cpp b/src/Magpie/MainWindow.cpp index 621a9b51b..344168d1a 100644 --- a/src/Magpie/MainWindow.cpp +++ b/src/Magpie/MainWindow.cpp @@ -4,10 +4,13 @@ #include "Win32Utils.h" #include "ThemeHelper.h" #include "XamlApp.h" +#include + +#pragma comment(lib, "Shcore.lib") namespace Magpie { -bool MainWindow::Create(HINSTANCE hInstance, const RECT& windowRect, bool isMaximized) noexcept { +bool MainWindow::Create(HINSTANCE hInstance, winrt::Point windowCenter, winrt::Size windowSizeInDips, bool isMaximized) noexcept { static const int _ = [](HINSTANCE hInstance) { WNDCLASSEXW wcex{}; wcex.cbSize = sizeof(wcex); @@ -27,7 +30,7 @@ bool MainWindow::Create(HINSTANCE hInstance, const RECT& windowRect, bool isMaxi return 0; }(hInstance); - _CreateWindow(hInstance, windowRect); + const auto& [posToSet, sizeToSet] = _CreateWindow(hInstance, windowCenter, windowSizeInDips); if (!_hWnd) { return false; @@ -46,8 +49,10 @@ bool MainWindow::Create(HINSTANCE hInstance, const RECT& windowRect, bool isMaxi // 1. 设置初始 XAML Islands 窗口的尺寸 // 2. 刷新窗口边框 - // 3. 防止窗口显示时背景闪烁: https://stackoverflow.com/questions/69715610/how-to-initialize-the-background-color-of-win32-app-to-something-other-than-whit - SetWindowPos(_hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); + // 3. 无法获知 DPI 的情况下 _CreateWindow 创建的窗口尺寸为零,在这里延后设置窗口位置 + // 4. 防止窗口显示时背景闪烁: https://stackoverflow.com/questions/69715610/how-to-initialize-the-background-color-of-win32-app-to-something-other-than-whit + SetWindowPos(_hWnd, NULL, posToSet.x, posToSet.y, sizeToSet.cx, sizeToSet.cy, + (sizeToSet.cx == 0 ? (SWP_NOMOVE | SWP_NOSIZE) : 0) | SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOCOPYBITS); // Xaml 控件加载完成后显示主窗口 _content.Loaded([this, isMaximized](winrt::IInspectable const&, winrt::RoutedEventArgs const&) { @@ -155,8 +160,8 @@ LRESULT MainWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) noex // 设置窗口最小尺寸 MINMAXINFO* mmi = (MINMAXINFO*)lParam; mmi->ptMinTrackSize = { - std::lround(550 * _currentDpi / double(USER_DEFAULT_SCREEN_DPI)), - std::lround(300 * _currentDpi / double(USER_DEFAULT_SCREEN_DPI)) + std::lroundf(550 * _currentDpi / float(USER_DEFAULT_SCREEN_DPI)), + std::lroundf(300 * _currentDpi / float(USER_DEFAULT_SCREEN_DPI)) }; return 0; } @@ -217,31 +222,113 @@ LRESULT MainWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) noex return base_type::_MessageHandler(msg, wParam, lParam); } -void MainWindow::_CreateWindow(HINSTANCE hInstance, const RECT& windowRect) noexcept { - // 防止窗口启动时不在可见区域,Windows 不会自动处理。 - // 检查两个点的位置是否存在屏幕:窗口的中心点和上边框中心点。前者确保大部分窗口内容可见,后者确保大部分标题栏可见。 - const POINT windowCenter{ - (windowRect.left + windowRect.right) / 2, - (windowRect.top + windowRect.bottom) / 2 - }; - const bool isValidPosition = MonitorFromPoint(windowCenter, MONITOR_DEFAULTTONULL) - && MonitorFromPoint({ windowCenter.x, windowRect.top }, MONITOR_DEFAULTTONULL); +std::pair MainWindow::_CreateWindow(HINSTANCE hInstance, winrt::Point windowCenter, winrt::Size windowSizeInDips) noexcept { + POINT windowPos = { CW_USEDEFAULT,CW_USEDEFAULT }; + SIZE windowSize{}; + + // windowSizeInDips 小于零表示默认位置和尺寸 + if (windowSizeInDips.Width > 0) { + // 检查窗口中心点的 DPI,根据我的测试,创建窗口时 Windows 使用窗口中心点确定 DPI。 + // 如果窗口中心点不在任何屏幕上,则在默认位置启动,让调用者设置窗口尺寸。 + const HMONITOR hMon = MonitorFromPoint( + { std::lroundf(windowCenter.X),std::lroundf(windowCenter.Y) }, + MONITOR_DEFAULTTONULL + ); + if (hMon) { + UINT dpi = USER_DEFAULT_SCREEN_DPI; + GetDpiForMonitor(hMon, MDT_EFFECTIVE_DPI, &dpi, &dpi); + + const float dpiFactor = dpi / float(USER_DEFAULT_SCREEN_DPI); + const winrt::Size windowSizeInPixels = { + windowSizeInDips.Width * dpiFactor, + windowSizeInDips.Height * dpiFactor + }; + + windowSize.cx = std::lroundf(windowSizeInPixels.Width); + windowSize.cy = std::lroundf(windowSizeInPixels.Height); + + MONITORINFO mi{ sizeof(mi) }; + GetMonitorInfo(hMon, &mi); + + // 确保启动位置在屏幕工作区内。不允许启动时跨越多个屏幕。 + if (windowSize.cx <= mi.rcWork.right - mi.rcWork.left && windowSize.cy <= mi.rcWork.bottom - mi.rcWork.top) { + windowPos.x = std::lroundf(windowCenter.X - windowSizeInPixels.Width / 2); + windowPos.x = std::clamp(windowPos.x, mi.rcWork.left, mi.rcWork.right - windowSize.cx); + + windowPos.y = std::lroundf(windowCenter.Y - windowSizeInPixels.Height / 2); + windowPos.y = std::clamp(windowPos.y, mi.rcWork.top, mi.rcWork.bottom - windowSize.cy); + } else { + // 屏幕工作区无法容纳窗口则使用默认窗口尺寸 + windowSize = {}; + windowSizeInDips.Width = -1.0f; + } + } + } // Win11 22H2 中为了使用 Mica 背景需指定 WS_EX_NOREDIRECTIONBITMAP + // windowSize 可能为零,并返回窗口尺寸给调用者 CreateWindowEx( Win32Utils::GetOSVersion().Is22H2OrNewer() ? WS_EX_NOREDIRECTIONBITMAP : 0, CommonSharedConstants::MAIN_WINDOW_CLASS_NAME, L"Magpie", WS_OVERLAPPEDWINDOW, - isValidPosition ? windowRect.left : CW_USEDEFAULT, - isValidPosition ? windowRect.top : CW_USEDEFAULT, - windowRect.right - windowRect.left, - windowRect.bottom - windowRect.top, + windowPos.x, + windowPos.y, + windowSize.cx, + windowSize.cy, NULL, NULL, hInstance, this ); + + if (windowSize.cx == 0) { + const HMONITOR hMon = MonitorFromWindow(_hWnd, MONITOR_DEFAULTTONEAREST); + + MONITORINFO mi{ sizeof(mi) }; + GetMonitorInfo(hMon, &mi); + + const float dpiFactor = _currentDpi / float(USER_DEFAULT_SCREEN_DPI); + const winrt::Size workingAreaSizeInDips = { + (mi.rcWork.right - mi.rcWork.left) / dpiFactor, + (mi.rcWork.bottom - mi.rcWork.top) / dpiFactor + }; + + // 确保启动尺寸小于屏幕工作区 + if (windowSizeInDips.Width <= 0 || + windowSizeInDips.Width > workingAreaSizeInDips.Width || + windowSizeInDips.Height > workingAreaSizeInDips.Height) { + // 默认尺寸 + static constexpr winrt::Size DEFAULT_SIZE{ 980.0f, 690.0f }; + + windowSizeInDips = DEFAULT_SIZE; + + if (windowSizeInDips.Width > workingAreaSizeInDips.Width || + windowSizeInDips.Height > workingAreaSizeInDips.Height) { + // 屏幕太小无法容纳默认尺寸 + windowSizeInDips.Width = workingAreaSizeInDips.Width * 0.8f; + windowSizeInDips.Height = windowSizeInDips.Width * DEFAULT_SIZE.Height / DEFAULT_SIZE.Width; + + if (windowSizeInDips.Height > workingAreaSizeInDips.Height) { + windowSizeInDips.Height = workingAreaSizeInDips.Height * 0.8f; + windowSizeInDips.Width = windowSizeInDips.Height * DEFAULT_SIZE.Width / DEFAULT_SIZE.Height; + } + } + } + + windowSize.cx = std::lroundf(windowSizeInDips.Width * dpiFactor); + windowSize.cy = std::lroundf(windowSizeInDips.Height * dpiFactor); + + // 确保启动位置在屏幕工作区内 + RECT targetRect; + GetWindowRect(_hWnd, &targetRect); + windowPos.x = std::clamp(targetRect.left, mi.rcWork.left, mi.rcWork.right - windowSize.cx); + windowPos.y = std::clamp(targetRect.top, mi.rcWork.top, mi.rcWork.bottom - windowSize.cy); + + return std::make_pair(windowPos, windowSize); + } else { + return {}; + } } void MainWindow::_UpdateTheme() { diff --git a/src/Magpie/MainWindow.h b/src/Magpie/MainWindow.h index 7bd6d125b..cd4dc4010 100644 --- a/src/Magpie/MainWindow.h +++ b/src/Magpie/MainWindow.h @@ -7,7 +7,7 @@ namespace Magpie { class MainWindow : public XamlWindowT { friend class base_type; public: - bool Create(HINSTANCE hInstance, const RECT& windowRect, bool isMaximized) noexcept; + bool Create(HINSTANCE hInstance, winrt::Point windowCenter, winrt::Size windowSizeInDips, bool isMaximized) noexcept; void Show() const noexcept; @@ -15,7 +15,7 @@ class MainWindow : public XamlWindowT LRESULT _MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) noexcept; private: - void _CreateWindow(HINSTANCE hInstance, const RECT& windowRect) noexcept; + std::pair _CreateWindow(HINSTANCE hInstance, winrt::Point windowCenter, winrt::Size windowSizeInDips) noexcept; void _UpdateTheme(); diff --git a/src/Magpie/XamlApp.cpp b/src/Magpie/XamlApp.cpp index fd3484a46..238b1b248 100644 --- a/src/Magpie/XamlApp.cpp +++ b/src/Magpie/XamlApp.cpp @@ -54,12 +54,8 @@ bool XamlApp::Initialize(HINSTANCE hInstance, const wchar_t* arguments) { return false; } - _mainWndRect = { - (int)std::lroundf(options.MainWndRect.X), - (int)std::lroundf(options.MainWndRect.Y), - (int)std::lroundf(options.MainWndRect.X + options.MainWndRect.Width), - (int)std::lroundf(options.MainWndRect.Y + options.MainWndRect.Height) - }; + _mainWindowCenter = options.MainWindowCenter; + _mainWindowSizeInDips = options.MainWindowSizeInDips; _isMainWndMaximized = options.IsWndMaximized; ThemeHelper::Initialize(); @@ -137,7 +133,17 @@ void XamlApp::SaveSettings() { WINDOWPLACEMENT wp{}; wp.length = sizeof(wp); if (GetWindowPlacement(_mainWindow.Handle(), &wp)) { - _mainWndRect = wp.rcNormalPosition; + _mainWindowCenter = { + (wp.rcNormalPosition.left + wp.rcNormalPosition.right) / 2.0f, + (wp.rcNormalPosition.top + wp.rcNormalPosition.bottom) / 2.0f + }; + + const float dpiFactor = GetDpiForWindow(_mainWindow.Handle()) / float(USER_DEFAULT_SCREEN_DPI); + _mainWindowSizeInDips = { + (wp.rcNormalPosition.right - wp.rcNormalPosition.left) / dpiFactor, + (wp.rcNormalPosition.bottom - wp.rcNormalPosition.top) / dpiFactor, + }; + _isMainWndMaximized = wp.showCmd == SW_MAXIMIZE; } else { Logger::Get().Win32Error("GetWindowPlacement 失败"); @@ -209,7 +215,7 @@ void XamlApp::_InitializeLogger() { } bool XamlApp::_CreateMainWindow() { - if (!_mainWindow.Create(_hInst, _mainWndRect, _isMainWndMaximized)) { + if (!_mainWindow.Create(_hInst, _mainWindowCenter, _mainWindowSizeInDips, _isMainWndMaximized)) { return false; } diff --git a/src/Magpie/XamlApp.h b/src/Magpie/XamlApp.h index 54133edcb..ce17690d6 100644 --- a/src/Magpie/XamlApp.h +++ b/src/Magpie/XamlApp.h @@ -51,7 +51,8 @@ class XamlApp { winrt::Magpie::App::App _uwpApp{ nullptr }; MainWindow _mainWindow; - RECT _mainWndRect{}; + winrt::Point _mainWindowCenter{}; + winrt::Size _mainWindowSizeInDips{}; bool _isMainWndMaximized = false; };