diff options
| author | uvok | 2025-07-21 15:23:19 +0200 | 
|---|---|---|
| committer | uvok | 2025-07-21 15:23:19 +0200 | 
| commit | 9532e812f84a089cbf7fe8f35b3fa119fa17d728 (patch) | |
| tree | 95c0ca0a6b5bd83dcb86175356d554b8ad89c91b /windows/runner | |
Add q&d flutter app
Diffstat (limited to 'windows/runner')
| -rw-r--r-- | windows/runner/CMakeLists.txt | 40 | ||||
| -rw-r--r-- | windows/runner/Runner.rc | 121 | ||||
| -rw-r--r-- | windows/runner/flutter_window.cpp | 71 | ||||
| -rw-r--r-- | windows/runner/flutter_window.h | 33 | ||||
| -rw-r--r-- | windows/runner/main.cpp | 43 | ||||
| -rw-r--r-- | windows/runner/resource.h | 16 | ||||
| -rw-r--r-- | windows/runner/resources/app_icon.ico | bin | 0 -> 33772 bytes | |||
| -rw-r--r-- | windows/runner/runner.exe.manifest | 14 | ||||
| -rw-r--r-- | windows/runner/utils.cpp | 65 | ||||
| -rw-r--r-- | windows/runner/utils.h | 19 | ||||
| -rw-r--r-- | windows/runner/win32_window.cpp | 288 | ||||
| -rw-r--r-- | windows/runner/win32_window.h | 102 | 
12 files changed, 812 insertions, 0 deletions
| diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 +  "flutter_window.cpp" +  "main.cpp" +  "utils.cpp" +  "win32_window.cpp" +  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +  "Runner.rc" +  "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 0000000..fab392b --- /dev/null +++ b/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN +    "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN +    "#include ""winres.h""\r\n" +    "\0" +END + +3 TEXTINCLUDE +BEGIN +    "\r\n" +    "\0" +END + +#endif    // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON            ICON                    "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN +    BLOCK "StringFileInfo" +    BEGIN +        BLOCK "040904e4" +        BEGIN +            VALUE "CompanyName", "de.uvok" "\0" +            VALUE "FileDescription", "badge" "\0" +            VALUE "FileVersion", VERSION_AS_STRING "\0" +            VALUE "InternalName", "badge" "\0" +            VALUE "LegalCopyright", "Copyright (C) 2025 de.uvok. All rights reserved." "\0" +            VALUE "OriginalFilename", "badge.exe" "\0" +            VALUE "ProductName", "badge" "\0" +            VALUE "ProductVersion", VERSION_AS_STRING "\0" +        END +    END +    BLOCK "VarFileInfo" +    BEGIN +        VALUE "Translation", 0x409, 1252 +    END +END + +#endif    // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif    // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include <optional> + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) +    : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { +  if (!Win32Window::OnCreate()) { +    return false; +  } + +  RECT frame = GetClientArea(); + +  // The size here must match the window dimensions to avoid unnecessary surface +  // creation / destruction in the startup path. +  flutter_controller_ = std::make_unique<flutter::FlutterViewController>( +      frame.right - frame.left, frame.bottom - frame.top, project_); +  // Ensure that basic setup of the controller was successful. +  if (!flutter_controller_->engine() || !flutter_controller_->view()) { +    return false; +  } +  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; +} + +void FlutterWindow::OnDestroy() { +  if (flutter_controller_) { +    flutter_controller_ = nullptr; +  } + +  Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, +                              WPARAM const wparam, +                              LPARAM const lparam) noexcept { +  // Give Flutter, including plugins, an opportunity to handle window messages. +  if (flutter_controller_) { +    std::optional<LRESULT> result = +        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, +                                                      lparam); +    if (result) { +      return *result; +    } +  } + +  switch (message) { +    case WM_FONTCHANGE: +      flutter_controller_->engine()->ReloadSystemFonts(); +      break; +  } + +  return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include <flutter/dart_project.h> +#include <flutter/flutter_view_controller.h> + +#include <memory> + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: +  // Creates a new FlutterWindow hosting a Flutter view running |project|. +  explicit FlutterWindow(const flutter::DartProject& project); +  virtual ~FlutterWindow(); + + protected: +  // Win32Window: +  bool OnCreate() override; +  void OnDestroy() override; +  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, +                         LPARAM const lparam) noexcept override; + + private: +  // The project to run. +  flutter::DartProject project_; + +  // The Flutter instance hosted by this window. +  std::unique_ptr<flutter::FlutterViewController> flutter_controller_; +}; + +#endif  // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 0000000..1212c6f --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include <flutter/dart_project.h> +#include <flutter/flutter_view_controller.h> +#include <windows.h> + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, +                      _In_ wchar_t *command_line, _In_ int show_command) { +  // Attach to console when present (e.g., 'flutter run') or create a +  // new console when running with a debugger. +  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { +    CreateAndAttachConsole(); +  } + +  // Initialize COM, so that it is available for use in the library and/or +  // plugins. +  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + +  flutter::DartProject project(L"data"); + +  std::vector<std::string> command_line_arguments = +      GetCommandLineArguments(); + +  project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + +  FlutterWindow window(project); +  Win32Window::Point origin(10, 10); +  Win32Window::Size size(1280, 720); +  if (!window.Create(L"badge", origin, size)) { +    return EXIT_FAILURE; +  } +  window.SetQuitOnClose(true); + +  ::MSG msg; +  while (::GetMessage(&msg, nullptr, 0, 0)) { +    ::TranslateMessage(&msg); +    ::DispatchMessage(&msg); +  } + +  ::CoUninitialize(); +  return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON                    101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE        102 +#define _APS_NEXT_COMMAND_VALUE         40001 +#define _APS_NEXT_CONTROL_VALUE         1001 +#define _APS_NEXT_SYMED_VALUE           101 +#endif +#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.icoBinary files differ new file mode 100644 index 0000000..c04e20c --- /dev/null +++ b/windows/runner/resources/app_icon.ico diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +  <application xmlns="urn:schemas-microsoft-com:asm.v3"> +    <windowsSettings> +      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> +    </windowsSettings> +  </application> +  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> +    <application> +      <!-- Windows 10 and Windows 11 --> +      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> +    </application> +  </compatibility> +</assembly> diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include <flutter_windows.h> +#include <io.h> +#include <stdio.h> +#include <windows.h> + +#include <iostream> + +void CreateAndAttachConsole() { +  if (::AllocConsole()) { +    FILE *unused; +    if (freopen_s(&unused, "CONOUT$", "w", stdout)) { +      _dup2(_fileno(stdout), 1); +    } +    if (freopen_s(&unused, "CONOUT$", "w", stderr)) { +      _dup2(_fileno(stdout), 2); +    } +    std::ios::sync_with_stdio(); +    FlutterDesktopResyncOutputStreams(); +  } +} + +std::vector<std::string> GetCommandLineArguments() { +  // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. +  int argc; +  wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); +  if (argv == nullptr) { +    return std::vector<std::string>(); +  } + +  std::vector<std::string> command_line_arguments; + +  // Skip the first argument as it's the binary name. +  for (int i = 1; i < argc; i++) { +    command_line_arguments.push_back(Utf8FromUtf16(argv[i])); +  } + +  ::LocalFree(argv); + +  return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { +  if (utf16_string == nullptr) { +    return std::string(); +  } +  unsigned int target_length = ::WideCharToMultiByte( +      CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, +      -1, nullptr, 0, nullptr, nullptr) +    -1; // remove the trailing null character +  int input_length = (int)wcslen(utf16_string); +  std::string utf8_string; +  if (target_length == 0 || target_length > utf8_string.max_size()) { +    return utf8_string; +  } +  utf8_string.resize(target_length); +  int converted_length = ::WideCharToMultiByte( +      CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, +      input_length, utf8_string.data(), target_length, nullptr, nullptr); +  if (converted_length == 0) { +    return std::string(); +  } +  return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include <string> +#include <vector> + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector<std::string>, +// encoded in UTF-8. Returns an empty std::vector<std::string> on failure. +std::vector<std::string> GetCommandLineArguments(); + +#endif  // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include <dwmapi.h> +#include <flutter_windows.h> + +#include "resource.h" + +namespace { + +/// 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 + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// 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"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +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<int>(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<EnableNonClientDpiScaling*>( +          GetProcAddress(user32_module, "EnableNonClientDpiScaling")); +  if (enable_non_client_dpi_scaling != nullptr) { +    enable_non_client_dpi_scaling(hwnd); +  } +  FreeLibrary(user32_module); +} + +}  // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: +  ~WindowClassRegistrar() = default; + +  // Returns the singleton registrar instance. +  static WindowClassRegistrar* GetInstance() { +    if (!instance_) { +      instance_ = new WindowClassRegistrar(); +    } +    return instance_; +  } + +  // Returns the name of the window class, registering the class if it hasn't +  // previously been registered. +  const wchar_t* GetWindowClass(); + +  // Unregisters the window class. Should only be called if there are no +  // instances of the window. +  void UnregisterWindowClass(); + + private: +  WindowClassRegistrar() = default; + +  static WindowClassRegistrar* instance_; + +  bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { +  if (!class_registered_) { +    WNDCLASS window_class{}; +    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); +    window_class.lpszClassName = kWindowClassName; +    window_class.style = CS_HREDRAW | CS_VREDRAW; +    window_class.cbClsExtra = 0; +    window_class.cbWndExtra = 0; +    window_class.hInstance = GetModuleHandle(nullptr); +    window_class.hIcon = +        LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); +    window_class.hbrBackground = 0; +    window_class.lpszMenuName = nullptr; +    window_class.lpfnWndProc = Win32Window::WndProc; +    RegisterClass(&window_class); +    class_registered_ = true; +  } +  return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { +  UnregisterClass(kWindowClassName, nullptr); +  class_registered_ = false; +} + +Win32Window::Win32Window() { +  ++g_active_window_count; +} + +Win32Window::~Win32Window() { +  --g_active_window_count; +  Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, +                         const Point& origin, +                         const Size& size) { +  Destroy(); + +  const wchar_t* window_class = +      WindowClassRegistrar::GetInstance()->GetWindowClass(); + +  const POINT target_point = {static_cast<LONG>(origin.x), +                              static_cast<LONG>(origin.y)}; +  HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); +  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); +  double scale_factor = dpi / 96.0; + +  HWND window = CreateWindow( +      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) { +    return false; +  } + +  UpdateTheme(window); + +  return OnCreate(); +} + +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<CREATESTRUCT*>(lparam); +    SetWindowLongPtr(window, GWLP_USERDATA, +                     reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams)); + +    auto that = static_cast<Win32Window*>(window_struct->lpCreateParams); +    EnableFullDpiSupportIfAvailable(window); +    that->window_handle_ = window; +  } else if (Win32Window* that = GetThisFromHandle(window)) { +    return that->MessageHandler(window, message, wparam, lparam); +  } + +  return DefWindowProc(window, message, wparam, lparam); +} + +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); +      } +      return 0; + +    case WM_DPICHANGED: { +      auto newRectSize = reinterpret_cast<RECT*>(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); +      } +      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() { +  OnDestroy(); + +  if (window_handle_) { +    DestroyWindow(window_handle_); +    window_handle_ = nullptr; +  } +  if (g_active_window_count == 0) { +    WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); +  } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { +  return reinterpret_cast<Win32Window*>( +      GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { +  child_content_ = content; +  SetParent(content, window_handle_); +  RECT frame = GetClientArea(); + +  MoveWindow(content, frame.left, frame.top, frame.right - frame.left, +             frame.bottom - frame.top, true); + +  SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { +  RECT frame; +  GetClientRect(window_handle_, &frame); +  return frame; +} + +HWND Win32Window::GetHandle() { +  return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { +  quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { +  // No-op; provided for subclasses. +  return true; +} + +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)); +  } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include <windows.h> + +#include <functional> +#include <memory> +#include <string> + +// 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 { +    unsigned int x; +    unsigned int y; +    Point(unsigned int x, unsigned int y) : x(x), y(y) {} +  }; + +  struct Size { +    unsigned int width; +    unsigned int height; +    Size(unsigned int width, unsigned int height) +        : width(width), height(height) {} +  }; + +  Win32Window(); +  virtual ~Win32Window(); + +  // 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 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); + +  // Show the current window. Returns true if the window was successfully shown. +  bool Show(); + +  // Release OS resources associated with window. +  void Destroy(); + +  // Inserts |content| into the window tree. +  void SetChildContent(HWND content); + +  // Returns the backing Window handle to enable clients to set icon and other +  // window properties. Returns nullptr if the window has been destroyed. +  HWND GetHandle(); + +  // If true, closing this window will quit the application. +  void SetQuitOnClose(bool quit_on_close); + +  // Return a RECT representing the bounds of the current client area. +  RECT GetClientArea(); + + 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. +  virtual LRESULT MessageHandler(HWND window, +                                 UINT const message, +                                 WPARAM const wparam, +                                 LPARAM const lparam) noexcept; + +  // Called when CreateAndShow is called, allowing subclass window-related +  // setup. Subclasses should return false if setup fails. +  virtual bool OnCreate(); + +  // Called when Destroy is called. +  virtual void OnDestroy(); + + 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 +  // responds to changes in DPI. All other messages are handled by +  // MessageHandler. +  static LRESULT CALLBACK WndProc(HWND const window, +                                  UINT const message, +                                  WPARAM const wparam, +                                  LPARAM const lparam) noexcept; + +  // Retrieves a class instance pointer for |window| +  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; + +  // window handle for top level window. +  HWND window_handle_ = nullptr; + +  // window handle for hosted content. +  HWND child_content_ = nullptr; +}; + +#endif  // RUNNER_WIN32_WINDOW_H_ | 
