diff --git a/frontend/appflowy_flutter/windows/runner/flutter_window.cpp b/frontend/appflowy_flutter/windows/runner/flutter_window.cpp index 8e9deabc69cee..955ee3038f988 100644 --- a/frontend/appflowy_flutter/windows/runner/flutter_window.cpp +++ b/frontend/appflowy_flutter/windows/runner/flutter_window.cpp @@ -17,7 +17,7 @@ bool FlutterWindow::OnCreate() { RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface -// creation / destruction in the startup path. + // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. @@ -26,6 +26,16 @@ bool FlutterWindow::OnCreate() { } RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + return true; } diff --git a/frontend/appflowy_flutter/windows/runner/main.cpp b/frontend/appflowy_flutter/windows/runner/main.cpp index 8ac91fd693fe8..b1fff72b84efe 100644 --- a/frontend/appflowy_flutter/windows/runner/main.cpp +++ b/frontend/appflowy_flutter/windows/runner/main.cpp @@ -47,9 +47,12 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); - if (!window.CreateAndShow(L"AppFlowy", origin, size)) { + + if (!window.Create(L"AppFlowy", origin, size)) { return EXIT_FAILURE; } + + window.Show(); window.SetQuitOnClose(true); ::MSG msg; diff --git a/frontend/appflowy_flutter/windows/runner/win32_window.cpp b/frontend/appflowy_flutter/windows/runner/win32_window.cpp index a46adb6af51c3..2f78196d35324 100644 --- a/frontend/appflowy_flutter/windows/runner/win32_window.cpp +++ b/frontend/appflowy_flutter/windows/runner/win32_window.cpp @@ -1,60 +1,70 @@ #include "win32_window.h" +#include #include #include "resource.h" #include "app_links/app_links_plugin_c_api.h" -namespace -{ +namespace { - constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif - // The number of Win32Window objects that currently exist. - static int g_active_window_count = 0; +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; - using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; - // Scale helper to convert logical scaler values to physical using passed in - // scale factor - int Scale(int source, double scale_factor) - { - return static_cast(source * scale_factor); - } +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; - // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. - // This API is only needed for PerMonitor V1 awareness mode. - void EnableFullDpiSupportIfAvailable(HWND hwnd) - { - HMODULE user32_module = LoadLibraryA("User32.dll"); - if (!user32_module) - { - return; - } - auto enable_non_client_dpi_scaling = - reinterpret_cast( - GetProcAddress(user32_module, "EnableNonClientDpiScaling")); - if (enable_non_client_dpi_scaling != nullptr) - { - enable_non_client_dpi_scaling(hwnd); - FreeLibrary(user32_module); - } +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} -} // namespace +} // namespace // Manages the Win32Window's window class registration. -class WindowClassRegistrar -{ -public: +class WindowClassRegistrar { + public: ~WindowClassRegistrar() = default; - // Returns the singleton registar instance. - static WindowClassRegistrar *GetInstance() - { - if (!instance_) - { + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; @@ -62,26 +72,24 @@ class WindowClassRegistrar // Returns the name of the window class, registering the class if it hasn't // previously been registered. - const wchar_t *GetWindowClass(); + const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); -private: + private: WindowClassRegistrar() = default; - static WindowClassRegistrar *instance_; + static WindowClassRegistrar* instance_; bool class_registered_ = false; }; -WindowClassRegistrar *WindowClassRegistrar::instance_ = nullptr; +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; -const wchar_t *WindowClassRegistrar::GetWindowClass() -{ - if (!class_registered_) - { +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; @@ -100,35 +108,31 @@ const wchar_t *WindowClassRegistrar::GetWindowClass() return kWindowClassName; } -void WindowClassRegistrar::UnregisterWindowClass() -{ +void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } -Win32Window::Win32Window() -{ +Win32Window::Win32Window() { ++g_active_window_count; } -Win32Window::~Win32Window() -{ +Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } -bool Win32Window::CreateAndShow(const std::wstring &title, - const Point &origin, - const Size &size) -{ +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + if (SendAppLinkToInstance(title)) { return false; } - Destroy(); - - const wchar_t *window_class = + const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), @@ -138,74 +142,38 @@ bool Win32Window::CreateAndShow(const std::wstring &title, double scale_factor = dpi / 96.0; HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); - if (!window) - { + if (!window) { return false; } + UpdateTheme(window); + return OnCreate(); } -bool Win32Window::SendAppLinkToInstance(const std::wstring &title) -{ - // Find our exact window - HWND hwnd = ::FindWindow(kWindowClassName, title.c_str()); - - if (hwnd) - { - // Dispatch new link to current window - SendAppLink(hwnd); - - // (Optional) Restore our window to front in same state - WINDOWPLACEMENT place = {sizeof(WINDOWPLACEMENT)}; - GetWindowPlacement(hwnd, &place); - - switch (place.showCmd) - { - case SW_SHOWMAXIMIZED: - ShowWindow(hwnd, SW_SHOWMAXIMIZED); - break; - case SW_SHOWMINIMIZED: - ShowWindow(hwnd, SW_RESTORE); - break; - default: - ShowWindow(hwnd, SW_NORMAL); - break; - } - - SetWindowPos(0, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE); - SetForegroundWindow(hwnd); - - // Window has been found, don't create another one. - return true; - } - - return false; +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, - LPARAM const lparam) noexcept -{ - if (message == WM_NCCREATE) - { - auto window_struct = reinterpret_cast(lparam); + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); - auto that = static_cast(window_struct->lpCreateParams); + auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; - } - else if (Win32Window *that = GetThisFromHandle(window)) - { + } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } @@ -216,76 +184,68 @@ LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, - LPARAM const lparam) noexcept -{ - switch (message) - { - case WM_DESTROY: - window_handle_ = nullptr; - Destroy(); - if (quit_on_close_) - { - PostQuitMessage(0); + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; } - return 0; - - case WM_DPICHANGED: - { - auto newRectSize = reinterpret_cast(lparam); - LONG newWidth = newRectSize->right - newRectSize->left; - LONG newHeight = newRectSize->bottom - newRectSize->top; - - SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, - newHeight, SWP_NOZORDER | SWP_NOACTIVATE); - - return 0; - } - case WM_SIZE: - { - RECT rect = GetClientArea(); - if (child_content_ != nullptr) - { - // Size and position the child window. - MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, - rect.bottom - rect.top, TRUE); + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; } - return 0; - } - case WM_ACTIVATE: - if (child_content_ != nullptr) - { - SetFocus(child_content_); - } - return 0; + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } -void Win32Window::Destroy() -{ +void Win32Window::Destroy() { OnDestroy(); - if (window_handle_) - { + if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } - if (g_active_window_count == 0) - { + if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } -Win32Window *Win32Window::GetThisFromHandle(HWND const window) noexcept -{ - return reinterpret_cast( +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } -void Win32Window::SetChildContent(HWND content) -{ +void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); @@ -296,30 +256,77 @@ void Win32Window::SetChildContent(HWND content) SetFocus(child_content_); } -RECT Win32Window::GetClientArea() -{ +RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } -HWND Win32Window::GetHandle() -{ +HWND Win32Window::GetHandle() { return window_handle_; } -void Win32Window::SetQuitOnClose(bool quit_on_close) -{ +void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } -bool Win32Window::OnCreate() -{ +bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } -void Win32Window::OnDestroy() -{ +void Win32Window::OnDestroy() { // No-op; provided for subclasses. } + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} + +bool Win32Window::SendAppLinkToInstance(const std::wstring &title) +{ + // Find our exact window + HWND hwnd = ::FindWindow(kWindowClassName, title.c_str()); + + if (hwnd) + { + // Dispatch new link to current window + SendAppLink(hwnd); + + // (Optional) Restore our window to front in same state + WINDOWPLACEMENT place = {sizeof(WINDOWPLACEMENT)}; + GetWindowPlacement(hwnd, &place); + + switch (place.showCmd) + { + case SW_SHOWMAXIMIZED: + ShowWindow(hwnd, SW_SHOWMAXIMIZED); + break; + case SW_SHOWMINIMIZED: + ShowWindow(hwnd, SW_RESTORE); + break; + default: + ShowWindow(hwnd, SW_NORMAL); + break; + } + + SetWindowPos(0, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE); + SetForegroundWindow(hwnd); + + // Window has been found, don't create another one. + return true; + } + + return false; +} \ No newline at end of file diff --git a/frontend/appflowy_flutter/windows/runner/win32_window.h b/frontend/appflowy_flutter/windows/runner/win32_window.h index 4d717b053df05..fae0d8a74153c 100644 --- a/frontend/appflowy_flutter/windows/runner/win32_window.h +++ b/frontend/appflowy_flutter/windows/runner/win32_window.h @@ -10,18 +10,15 @@ // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling -class Win32Window -{ -public: - struct Point - { +class Win32Window { + public: + struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; - struct Size - { + struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) @@ -31,19 +28,16 @@ class Win32Window Win32Window(); virtual ~Win32Window(); - // Creates and shows a win32 window with |title| and position and size using + // Creates a win32 window with |title| that is positioned and sized using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a - // consistent size to will treat the width height passed in to this function - // as logical pixels and scale to appropriate for the default monitor. Returns - // true if the window was created successfully. - bool CreateAndShow(const std::wstring &title, - const Point &origin, - const Size &size); + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); - // Dispatches link if any. - // This method enables our app to be with a single instance too. - bool SendAppLinkToInstance(const std::wstring &title); + // Show the current window. Returns true if the window was successfully shown. + bool Show(); // Release OS resources associated with window. void Destroy(); @@ -61,7 +55,11 @@ class Win32Window // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); -protected: + // Dispatches link if any. + // This method enables our app to be with a single instance too. + bool SendAppLinkToInstance(const std::wstring &title); + + protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. @@ -77,13 +75,13 @@ class Win32Window // Called when Destroy is called. virtual void OnDestroy(); -private: + private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically - // responsponds to changes in DPI. All other messages are handled by + // responds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, @@ -91,7 +89,10 @@ class Win32Window LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| - static Win32Window *GetThisFromHandle(HWND const window) noexcept; + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); bool quit_on_close_ = false; @@ -102,4 +103,4 @@ class Win32Window HWND child_content_ = nullptr; }; -#endif // RUNNER_WIN32_WINDOW_H_ +#endif // RUNNER_WIN32_WINDOW_H_