diff --git a/example/lib/main.dart b/example/lib/main.dart index 504cdd5f..fa520981 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:polyvox_filament/animations/animation_data.dart'; @@ -14,7 +15,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:polyvox_filament/widgets/filament_gesture_detector.dart'; import 'package:polyvox_filament/widgets/filament_widget.dart'; -void main() { +void main() async { runApp(const MyApp()); } @@ -30,8 +31,10 @@ class _MyAppState extends State with SingleTickerProviderStateMixin { Widget build(BuildContext context) { return MaterialApp( // showPerformanceOverlay: true, - color: Colors.white, - home: Scaffold(backgroundColor: Colors.white, body: ExampleWidget())); + home: Scaffold( + body: + ExampleWidget() + )); } } diff --git a/ios/src/PolyvoxFilamentFFIApi.cpp b/ios/src/PolyvoxFilamentFFIApi.cpp index ba56561b..65ab44a4 100644 --- a/ios/src/PolyvoxFilamentFFIApi.cpp +++ b/ios/src/PolyvoxFilamentFFIApi.cpp @@ -77,7 +77,9 @@ public: void doRender() { render(_viewer, 0, nullptr, nullptr, nullptr); - _renderCallback(_renderCallbackOwner); + if(_renderCallback) { + _renderCallback(_renderCallbackOwner); + } } void setFrameIntervalInMilliseconds(float frameIntervalInMilliseconds) { diff --git a/lib/filament_controller.dart b/lib/filament_controller.dart index cb01b93d..b2bd4390 100644 --- a/lib/filament_controller.dart +++ b/lib/filament_controller.dart @@ -19,6 +19,14 @@ class TextureDetails { } abstract class FilamentController { + + /// + /// Whether a Flutter Texture widget should be inserted into the widget hierarchy. + /// This will be false on certain platforms where we use a transparent window underlay. + /// Used internally by [FilamentWidget]; you probably don't need to access this property directly. + /// + bool get requiresTextureWidget; + /// /// The Flutter texture ID and dimensions for current texture in use. /// This is only used by [FilamentWidget]; you shouldn't need to access directly yourself. diff --git a/lib/filament_controller_ffi.dart b/lib/filament_controller_ffi.dart index 4a76758b..0e37ced3 100644 --- a/lib/filament_controller_ffi.dart +++ b/lib/filament_controller_ffi.dart @@ -4,16 +4,21 @@ import 'dart:io'; import 'dart:ui' as ui; import 'package:flutter/services.dart'; import 'package:ffi/ffi.dart'; -import 'package:flutter/widgets.dart'; + import 'package:polyvox_filament/filament_controller.dart'; import 'package:polyvox_filament/animations/animation_data.dart'; import 'package:polyvox_filament/generated_bindings.dart'; +// ignore: constant_identifier_names const FilamentEntity _FILAMENT_ASSET_ERROR = 0; class FilamentControllerFFI extends FilamentController { - late MethodChannel _channel = MethodChannel("app.polyvox.filament/event"); + + final _channel = const MethodChannel("app.polyvox.filament/event"); + + @override + bool get requiresTextureWidget => !Platform.isWindows; double _pixelRatio = 1.0; @@ -25,9 +30,11 @@ class FilamentControllerFFI extends FilamentController { final String? uberArchivePath; + @override Stream get hasViewer => _hasViewerController.stream; final _hasViewerController = StreamController(); + @override Stream get pickResult => _pickResultController.stream; final _pickResultController = StreamController.broadcast(); @@ -48,7 +55,7 @@ class FilamentControllerFFI extends FilamentController { _resizeTimer?.cancel(); _resizingWidth = call.arguments[0]; _resizingHeight = call.arguments[1]; - _resizeTimer = Timer(Duration(milliseconds: 500), () async { + _resizeTimer = Timer(const Duration(milliseconds: 500), () async { await resize(_resizingWidth!, _resizingHeight!); }); @@ -160,7 +167,7 @@ class FilamentControllerFFI extends FilamentController { print("Using flutterTextureId $flutterTextureId, surface $surfaceAddress and nativeTexture $nativeTexture"); - if (Platform.isWindows) { + if (Platform.isWindows && requiresTextureWidget) { _driver = Pointer.fromAddress( await _channel.invokeMethod("getDriverPlatform")); } @@ -275,6 +282,10 @@ class FilamentControllerFFI extends FilamentController { @override Future resize(int width, int height, {double scaleFactor = 1.0}) async { + + if(Platform.isWindows) { + return; + } // we defer to the FilamentWidget to ensure that every call to [resize] is synchronized // so this exception should never be thrown (right?) if (textureDetails.value == null) { diff --git a/lib/widgets/filament_widget.dart b/lib/widgets/filament_widget.dart index 9191592f..4073dfc5 100644 --- a/lib/widgets/filament_widget.dart +++ b/lib/widgets/filament_widget.dart @@ -42,7 +42,7 @@ class _RenderResizeObserver extends RenderProxyBox { void performLayout() async { super.performLayout(); if (size.width != _oldSize.width || size.height != _oldSize.height) { - onLayoutChangedCallback(size); + onLayoutChangedCallback(size); _oldSize = Size(size.width, size.height); } } @@ -87,7 +87,7 @@ class _FilamentWidgetState extends State { return ResizeObserver( onResized: (newSize) { - if(!Platform.isWindows) { + if (!Platform.isWindows) { return; } WidgetsBinding.instance.addPostFrameCallback((timeStamp) { @@ -124,10 +124,10 @@ class _SizedFilamentWidget extends StatefulWidget { } class _SizedFilamentWidgetState extends State<_SizedFilamentWidget> { + String? _error; late final AppLifecycleListener _appLifecycleListener; - AppLifecycleState? _lastState; @override void initState() { @@ -136,9 +136,6 @@ class _SizedFilamentWidgetState extends State<_SizedFilamentWidget> { ); WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { - if (!kReleaseMode) { - await Future.delayed(Duration(seconds: 2)); - } try { await widget.controller.createViewer(widget.width, widget.height); } catch (err) { @@ -171,7 +168,7 @@ class _SizedFilamentWidgetState extends State<_SizedFilamentWidget> { var width = size.width.ceil(); var height = size.height.ceil(); while (_resizing) { - await Future.delayed(Duration(milliseconds: 20)); + await Future.delayed(const Duration(milliseconds: 20)); } _resizing = true; await widget.controller.resize(width, height); @@ -237,7 +234,6 @@ class _SizedFilamentWidgetState extends State<_SizedFilamentWidget> { await widget.controller.setRendering(_wasRenderingOnInactive); break; } - _lastState = state; } @override @@ -252,31 +248,55 @@ class _SizedFilamentWidgetState extends State<_SizedFilamentWidget> { ])); } - return ListenableBuilder(listenable: widget.controller.textureDetails, builder: (BuildContext ctx, Widget? wdgt) { - - if (widget.controller.textureDetails.value == null) { - return Stack(children: [ - Positioned.fill(child: widget.initial ?? Container(color: Colors.red)) - ]); - } - // see [FilamentControllerFFI.resize] for an explanation of how we deal with resizing - var texture = Texture( - key: ObjectKey("texture_${widget.controller.textureDetails.value!.textureId}"), - textureId: widget.controller.textureDetails.value!.textureId, - filterQuality: FilterQuality.none, - freeze: false, - ); - + if (!widget.controller.requiresTextureWidget) { return Stack(children: [ - Positioned.fill( - child: Platform.isLinux || Platform.isWindows - ? Transform( - alignment: Alignment.center, - transform: Matrix4.rotationX( - pi), // TODO - this rotation is due to OpenGL texture coordinate working in a different space from Flutter, can we move this to the C++ side somewhere? - child: texture) - : texture) + Positioned.fill(child: CustomPaint(painter: TransparencyPainter())) ]); - }); + } + + return ListenableBuilder( + listenable: widget.controller.textureDetails, + builder: (BuildContext ctx, Widget? wdgt) { + if (widget.controller.textureDetails.value == null) { + return Stack(children: [ + Positioned.fill( + child: widget.initial ?? Container(color: Colors.red)) + ]); + } + // see [FilamentControllerFFI.resize] for an explanation of how we deal with resizing + var texture = Texture( + key: ObjectKey( + "texture_${widget.controller.textureDetails.value!.textureId}"), + textureId: widget.controller.textureDetails.value!.textureId, + filterQuality: FilterQuality.none, + freeze: false, + ); + + return Stack(children: [ + Positioned.fill( + child: Platform.isLinux || Platform.isWindows + ? Transform( + alignment: Alignment.center, + transform: Matrix4.rotationX( + pi), // TODO - this rotation is due to OpenGL texture coordinate working in a different space from Flutter, can we move this to the C++ side somewhere? + child: texture) + : texture) + ]); + }); } } + +class TransparencyPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + canvas.drawRect( + Rect.fromLTWH(0, 0, size.width, size.height), + Paint() + ..blendMode = BlendMode.clear + ..color = const Color(0x00000000), + ); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 64c81fbe..0ae3874b 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -23,21 +23,19 @@ list(APPEND PLUGIN_SOURCES ) set(USE_ANGLE FALSE) - -if(!USE_ANGLE) set(WGL_USE_BACKING_WINDOW TRUE) -endif() if(USE_ANGLE) add_compile_definitions(USE_ANGLE) list(APPEND PLUGIN_SOURCES "flutter_angle_texture.cpp" "egl_context.cpp" ) else() - list(APPEND PLUGIN_SOURCES "wgl_context.cpp") - if(WGL_USE_BACKING_WINDOW) - list(APPEND PLUGIN_SOURCES "utils.cc" "backing_window.cpp") - else() - list(APPEND PLUGIN_SOURCES "opengl_texture_buffer.cpp") - endif() + add_compile_definitions(WGL_USE_BACKING_WINDOW) + list(APPEND PLUGIN_SOURCES "wgl_context.cpp" "opengl_texture_buffer.cpp" "backing_window.cpp") + # if(WGL_USE_BACKING_WINDOW) + # list(APPEND PLUGIN_SOURCES ) + # else() + # list(APPEND PLUGIN_SOURCES ) + # endif() endif() # Define the plugin library target. Its name must not be changed (see comment diff --git a/windows/backing_window.cpp b/windows/backing_window.cpp index f630b3cb..b7a814c0 100644 --- a/windows/backing_window.cpp +++ b/windows/backing_window.cpp @@ -1,5 +1,18 @@ #include "backing_window.h" +#include +#include +#include +#include + +#include +#include +#include +#include + +#pragma comment(lib, "dwmapi.lib") +#pragma comment(lib, "comctl32.lib") + namespace polyvox_filament { static constexpr auto kClassName = L"FLUTTER_FILAMENT_WINDOW"; @@ -9,6 +22,116 @@ static WPARAM last_wm_size_wparam_ = SIZE_RESTORED; uint64_t last_thread_time_ = 0; static constexpr auto kNativeViewPositionAndShowDelay = 300; +typedef enum _WINDOWCOMPOSITIONATTRIB { + WCA_UNDEFINED = 0, + WCA_NCRENDERING_ENABLED = 1, + WCA_NCRENDERING_POLICY = 2, + WCA_TRANSITIONS_FORCEDISABLED = 3, + WCA_ALLOW_NCPAINT = 4, + WCA_CAPTION_BUTTON_BOUNDS = 5, + WCA_NONCLIENT_RTL_LAYOUT = 6, + WCA_FORCE_ICONIC_REPRESENTATION = 7, + WCA_EXTENDED_FRAME_BOUNDS = 8, + WCA_HAS_ICONIC_BITMAP = 9, + WCA_THEME_ATTRIBUTES = 10, + WCA_NCRENDERING_EXILED = 11, + WCA_NCADORNMENTINFO = 12, + WCA_EXCLUDED_FROM_LIVEPREVIEW = 13, + WCA_VIDEO_OVERLAY_ACTIVE = 14, + WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15, + WCA_DISALLOW_PEEK = 16, + WCA_CLOAK = 17, + WCA_CLOAKED = 18, + WCA_ACCENT_POLICY = 19, + WCA_FREEZE_REPRESENTATION = 20, + WCA_EVER_UNCLOAKED = 21, + WCA_VISUAL_OWNER = 22, + WCA_HOLOGRAPHIC = 23, + WCA_EXCLUDED_FROM_DDA = 24, + WCA_PASSIVEUPDATEMODE = 25, + WCA_USEDARKMODECOLORS = 26, + WCA_LAST = 27 +} WINDOWCOMPOSITIONATTRIB; + +typedef struct _WINDOWCOMPOSITIONATTRIBDATA { + WINDOWCOMPOSITIONATTRIB Attrib; + PVOID pvData; + SIZE_T cbData; +} WINDOWCOMPOSITIONATTRIBDATA; + +typedef enum _ACCENT_STATE { + ACCENT_DISABLED = 0, + ACCENT_ENABLE_GRADIENT = 1, + ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, + ACCENT_ENABLE_BLURBEHIND = 3, + ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, + ACCENT_ENABLE_HOSTBACKDROP = 5, + ACCENT_INVALID_STATE = 6 +} ACCENT_STATE; + +typedef struct _ACCENT_POLICY { + ACCENT_STATE AccentState; + DWORD AccentFlags; + DWORD GradientColor; + DWORD AnimationId; +} ACCENT_POLICY; + +typedef BOOL(WINAPI* _GetWindowCompositionAttribute)( + HWND, WINDOWCOMPOSITIONATTRIBDATA*); +typedef BOOL(WINAPI* _SetWindowCompositionAttribute)( + HWND, WINDOWCOMPOSITIONATTRIBDATA*); + +static _SetWindowCompositionAttribute g_set_window_composition_attribute = NULL; +static bool g_set_window_composition_attribute_initialized = false; + +typedef LONG NTSTATUS, *PNTSTATUS; +#define STATUS_SUCCESS (0x00000000) + +typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); + +RTL_OSVERSIONINFOW GetWindowsVersion() { + HMODULE hmodule = ::GetModuleHandleW(L"ntdll.dll"); + if (hmodule) { + RtlGetVersionPtr rtl_get_version_ptr = + (RtlGetVersionPtr)::GetProcAddress(hmodule, "RtlGetVersion"); + if (rtl_get_version_ptr != nullptr) { + RTL_OSVERSIONINFOW rovi = {0}; + rovi.dwOSVersionInfoSize = sizeof(rovi); + if (STATUS_SUCCESS == rtl_get_version_ptr(&rovi)) { + return rovi; + } + } + } + RTL_OSVERSIONINFOW rovi = {0}; + return rovi; +} + +void SetWindowComposition(HWND window, int32_t accent_state, + int32_t gradient_color) { + // TODO: Look for a better available API. + if (GetWindowsVersion().dwBuildNumber >= 18362) { + if (!g_set_window_composition_attribute_initialized) { + auto user32 = ::GetModuleHandleA("user32.dll"); + if (user32) { + g_set_window_composition_attribute = + reinterpret_cast<_SetWindowCompositionAttribute>( + ::GetProcAddress(user32, "SetWindowCompositionAttribute")); + if (g_set_window_composition_attribute) { + g_set_window_composition_attribute_initialized = true; + } + } + } + ACCENT_POLICY accent = {static_cast(accent_state), 2, + static_cast(gradient_color), 0}; + WINDOWCOMPOSITIONATTRIBDATA data; + data.Attrib = WCA_ACCENT_POLICY; + data.pvData = &accent; + data.cbData = sizeof(accent); + g_set_window_composition_attribute(window, &data); + } +} + + LRESULT NativeViewSubclassProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam, UINT_PTR subclass_id, DWORD_PTR ref_data) noexcept { @@ -17,11 +140,12 @@ LRESULT NativeViewSubclassProc(HWND window, UINT message, WPARAM wparam, // Prevent erasing of |window| when it is unfocused and minimized or // moved out of screen etc. return 1; + break; } case WM_SIZE: { // Prevent unnecessary maxmize, minimize or restore messages for |window|. - // Since it is |SetParent|'ed into native view container. return 1; + break; } default: break; @@ -32,6 +156,8 @@ LRESULT NativeViewSubclassProc(HWND window, UINT message, WPARAM wparam, LRESULT CALLBACK FilamentWindowProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { + // std::cout << "FILAMENT WINDOW EVENT " << message << std::endl; + switch (message) { case WM_MOUSEMOVE: { std::cout << "FILAMENT MOUSE MOVE" << std::endl; @@ -56,14 +182,14 @@ LRESULT CALLBACK FilamentWindowProc(HWND const window, UINT const message, std::cout << "FILAMENT ERASE BKGND" << std::endl; // Prevent erasing of |window| when it is unfocused and minimized or // moved out of screen etc. - return 1; + break; } case WM_SIZE: case WM_MOVE: case WM_MOVING: case WM_ACTIVATE: case WM_WINDOWPOSCHANGED: { - std::cout << "FILAMENT POS CHANGED" << std::endl; + // std::cout << "FILAMENT POS CHANGED" << std::endl; // NativeViewCore::GetInstance()->SetHitTestBehavior(0); auto user_data = ::GetWindowLongPtr(window, GWLP_USERDATA); if (user_data) { @@ -83,168 +209,178 @@ LRESULT CALLBACK FilamentWindowProc(HWND const window, UINT const message, return ::DefWindowProc(window, message, wparam, lparam); } +BackingWindow::BackingWindow(flutter::PluginRegistrarWindows *pluginRegistrar, + int initialWidth, int initialHeight) { + // a Flutter application actually has two windows - the innner window contains the FlutterView. + // although we will use the outer window for various events, we always position things relative to the inner window. + _flutterViewWindow = pluginRegistrar->GetView()->GetNativeWindow(); + _flutterRootWindow = ::GetAncestor(_flutterViewWindow, GA_ROOT); -class BackingWindow { + RECT flutterChildRect; + ::GetWindowRect(_flutterViewWindow, &flutterChildRect); + // ::GetClientRect(flutterWindow, &flutterChildRect); - BackingWindow::BackingWindow( - flutter::PluginRegistrarWindows *pluginRegistrar, - int initialWidth, - int initialHeight) { - // get the root Flutter window - HWND flutterWindow = pluginRegistrar->GetView()->GetNativeWindow(); - _flutterRootWindow = ::GetAncestor(flutterWindow, GA_ROOT); + std::cout << "child rect " << flutterChildRect.left << " " << flutterChildRect.top << " " << flutterChildRect.right << " " << flutterChildRect.bottom << std::endl; - // set composition to allow transparency - flutternativeview::SetWindowComposition(_flutterRootWindow, 6, 0); + // set composition to allow transparency + SetWindowComposition(_flutterRootWindow, 6, 0); - // register a top-level WindowProcDelegate to handle window events - pluginRegistrar->RegisterTopLevelWindowProcDelegate([=](HWND hwnd, - UINT message, - WPARAM wparam, - LPARAM lparam) { - switch (message) { - case WM_ACTIVATE: { - std::cout << "WM_ACTIVATE" << std::endl; - RECT window_rect; - ::GetWindowRect(_flutterRootWindow, &window_rect); - // Position |native_view| such that it's z order is behind |window_| & - // redraw aswell. - ::SetWindowPos(_windowHandle, _flutterRootWindow, window_rect.left, - window_rect.top, window_rect.right - window_rect.left, - window_rect.bottom - window_rect.top, SWP_NOACTIVATE); - break; - } - case WM_SIZE: { - std::cout << "WM_SIZE" << std::endl; - - // Handle Windows's minimize & maximize animations properly. - // Since |SetWindowPos| & other Win32 APIs on |native_view_container_| - // do not re-produce the same DWM animations like actual user - // interractions on the |window_| do (though both windows are overlapped - // tightly but maximize and minimze animations can't be mimiced for the - // both of them at the same time), the best solution is to make the - // |window_| opaque & hide |native_view_container_| & alter it's position. - // After that, finally make |native_view_container_| visible again & - // |window_| transparent again. This approach is not perfect, but it's the - // best we can do. The minimize & maximize animations on the |window_| - // look good with just a slight glitch on the visible native views. In - // future, maybe replacing the |NativeView| widget (Flutter-side) with - // equivalent window screenshot will result in a totally seamless - // experience. - if (wparam != SIZE_RESTORED || last_wm_size_wparam_ == SIZE_MINIMIZED || - last_wm_size_wparam_ == SIZE_MAXIMIZED || - was_window_hidden_due_to_minimize_) { - was_window_hidden_due_to_minimize_ = false; - // Minimize condition is handled separately inside |WM_WINDOWPOSCHANGED| - // case, since we don't want to cause unnecessary redraws (& show/hide) - // when user is resizing the window by dragging the window border. - SetWindowComposition(_flutterRootWindow, 0, 0); - ::ShowWindow(_windowHandle, SW_HIDE); - last_thread_time_ = - std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - std::thread( - [=](uint64_t time) { - if (time < last_thread_time_) { - return; - } - std::this_thread::sleep_for( - std::chrono::milliseconds(kNativeViewPositionAndShowDelay)); - SetWindowComposition(_flutterRootWindow, 6, 0); - // Handling SIZE_MINIMIZED separately. - if (wparam != SIZE_MINIMIZED) { - ::ShowWindow(_windowHandle, SW_SHOWNOACTIVATE); - } - }, - last_thread_time_) - .detach(); - } - last_wm_size_wparam_ = wparam; - break; - } - // Keep |native_view_container_| behind the |window_|. - case WM_MOVE: - case WM_MOVING: - case WM_WINDOWPOSCHANGED: { - std::cout << "FLUTTER WINDOWPOSCHANGED"<< std::endl; - RECT window_rect; - ::GetWindowRect(_flutterRootWindow, &window_rect); - if (window_rect.right - window_rect.left > 0 && - window_rect.bottom - window_rect.top > 0) { - ::SetWindowPos(_windowHandle, _flutterRootWindow, window_rect.left, - window_rect.top, window_rect.right - window_rect.left, - window_rect.bottom - window_rect.top, SWP_NOACTIVATE); - // |window_| is minimized. - if (window_rect.left < 0 && window_rect.top < 0 && - window_rect.right < 0 && window_rect.bottom < 0) { - // Hide |native_view_container_| to prevent showing - // |native_view_container_| before |window_| placement - // i.e when restoring window after clicking the taskbar icon. - SetWindowComposition(_flutterRootWindow, 0, 0); - ::ShowWindow(_windowHandle, SW_HIDE); - was_window_hidden_due_to_minimize_ = true; - } - } - break; - } - case WM_CLOSE: { - // close - break; - } - default: - break; - } - return NULL; - }); - - // create the HWND for Filament - auto window_class = WNDCLASSEX{}; - ::SecureZeroMemory(&window_class, sizeof(window_class)); - window_class.cbSize = sizeof(window_class); - window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.lpfnWndProc = FilamentWindowProc; - window_class.hInstance = 0; - window_class.lpszClassName = kClassName; - window_class.hCursor = ::LoadCursorW(nullptr, IDC_ARROW); - window_class.hbrBackground = ::CreateSolidBrush(RGB(0, 255, 0)); - ::RegisterClassExW(&window_class); - _windowHandle = - ::CreateWindow(kClassName, kWindowName, WS_OVERLAPPEDWINDOW, 0, 0, initialWidth, initialHeight, - nullptr, nullptr, GetModuleHandle(nullptr), nullptr); - - // Disable DWM animations - auto disable_window_transitions = TRUE; - DwmSetWindowAttribute(_windowHandle, DWMWA_TRANSITIONS_FORCEDISABLED, - &disable_window_transitions, - sizeof(disable_window_transitions)); - - ::SetWindowSubclass(_windowHandle, NativeViewSubclassProc, 69420, - NULL); // what does this do? - - auto style = ::GetWindowLongPtr(_windowHandle, GWL_STYLE); - style &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | - WS_EX_APPWINDOW); - ::SetWindowLongPtr(_windowHandle, GWL_STYLE, style); - - RECT flutterWindowRect; - ::GetClientRect(_flutterRootWindow, &flutterWindowRect); - - ::SetWindowLongPtr(_windowHandle, GWLP_USERDATA, - reinterpret_cast(_flutterRootWindow)); - - ::SetWindowPos(_windowHandle, _flutterRootWindow, flutterWindowRect.left, - flutterWindowRect.top, initialWidth, initialHeight, SWP_SHOWWINDOW); - // flutterWindowRect.right - flutterWindowRect.left, - // flutterWindowRect.bottom - flutterWindowRect.top, SWP_SHOWWINDOW); - ::ShowWindow(_windowHandle, SW_SHOW); - ::ShowWindow(_flutterRootWindow, SW_SHOW); - ::SetFocus(_flutterRootWindow); - } + // register a top-level WindowProcDelegate to handle window events + pluginRegistrar->RegisterTopLevelWindowProcDelegate([=](HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + // std::cout << "TOP LEVEL EVENT " << message << std::endl; + switch (message) { + case WM_MOUSEMOVE: { + // std::cout << "FLUTTER MOUSE MOVE" << std::endl; + break; + } + case WM_ACTIVATE: { + std::cout << "WM_ACTIVATE" << std::endl; + RECT rootWindowRect; + ::GetWindowRect(_flutterViewWindow, &rootWindowRect); + // Position |native_view| such that it's z order is behind |window_| & + // redraw aswell. + ::SetWindowPos(_windowHandle, _flutterRootWindow, rootWindowRect.left, + rootWindowRect.top, rootWindowRect.right - rootWindowRect.left, + rootWindowRect.bottom - rootWindowRect.top, SWP_NOACTIVATE); + break; } + case WM_SIZE: { + std::cout << "WM_SIZE" << std::endl; - BackingWindow::GetHandle() { - return _windowHandle; + // Handle Windows's minimize & maximize animations properly. + // Since |SetWindowPos| & other Win32 APIs on |native_view_container_| + // do not re-produce the same DWM animations like actual user + // interractions on the |window_| do (though both windows are overlapped + // tightly but maximize and minimze animations can't be mimiced for the + // both of them at the same time), the best solution is to make the + // |window_| opaque & hide |native_view_container_| & alter it's position. + // After that, finally make |native_view_container_| visible again & + // |window_| transparent again. This approach is not perfect, but it's the + // best we can do. The minimize & maximize animations on the |window_| + // look good with just a slight glitch on the visible native views. In + // future, maybe replacing the |NativeView| widget (Flutter-side) with + // equivalent window screenshot will result in a totally seamless + // experience. + if (wparam != SIZE_RESTORED || last_wm_size_wparam_ == SIZE_MINIMIZED || + last_wm_size_wparam_ == SIZE_MAXIMIZED || + was_window_hidden_due_to_minimize_) { + was_window_hidden_due_to_minimize_ = false; + // Minimize condition is handled separately inside |WM_WINDOWPOSCHANGED| + // case, since we don't want to cause unnecessary redraws (& show/hide) + // when user is resizing the window by dragging the window border. + SetWindowComposition(_flutterRootWindow, 0, 0); + ::ShowWindow(_windowHandle, SW_HIDE); + last_thread_time_ = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + std::thread( + [=](uint64_t time) { + if (time < last_thread_time_) { + return; + } + std::this_thread::sleep_for( + std::chrono::milliseconds(kNativeViewPositionAndShowDelay)); + SetWindowComposition(_flutterRootWindow, 6, 0); + // Handling SIZE_MINIMIZED separately. + if (wparam != SIZE_MINIMIZED) { + ::ShowWindow(_windowHandle, SW_SHOWNOACTIVATE); + } + }, + last_thread_time_) + .detach(); + } + last_wm_size_wparam_ = wparam; + break; } + // Keep |native_view_container_| behind the |window_|. + case WM_MOVE: + case WM_MOVING: + case WM_WINDOWPOSCHANGED: { + RECT rootWindowRect; + ::GetWindowRect(_flutterViewWindow, &rootWindowRect); + // std::cout << "FLUTTER WINDOWPOSCHANGED TO " << rootWindowRect.left << " " << rootWindowRect.top << " " << rootWindowRect.right << " " << rootWindowRect.bottom << std::endl; + if (rootWindowRect.right - rootWindowRect.left > 0 && + rootWindowRect.bottom - rootWindowRect.top > 0) { + ::SetWindowPos(_windowHandle, _flutterRootWindow, rootWindowRect.left, + rootWindowRect.top, rootWindowRect.right - rootWindowRect.left, + rootWindowRect.bottom - rootWindowRect.top, SWP_NOACTIVATE); + // |window_| is minimized. + if (rootWindowRect.left < 0 && rootWindowRect.top < 0 && + rootWindowRect.right < 0 && rootWindowRect.bottom < 0) { + // Hide |native_view_container_| to prevent showing + // |native_view_container_| before |window_| placement + // i.e when restoring window after clicking the taskbar icon. + SetWindowComposition(_flutterRootWindow, 0, 0); + ::ShowWindow(_windowHandle, SW_HIDE); + was_window_hidden_due_to_minimize_ = true; + } + } + break; + } + case WM_CLOSE: { + // close + break; + } + default: + break; + } + return std::nullopt; + }); + // create the HWND for Filament + auto window_class = WNDCLASSEX{}; + ::SecureZeroMemory(&window_class, sizeof(window_class)); + window_class.cbSize = sizeof(window_class); + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.lpfnWndProc = FilamentWindowProc; + window_class.hInstance = 0; + window_class.lpszClassName = kClassName; + window_class.hCursor = ::LoadCursorW(nullptr, IDC_ARROW); + window_class.hbrBackground = ::CreateSolidBrush(0); + ::RegisterClassExW(&window_class); + _windowHandle = ::CreateWindow(kClassName, kWindowName, WS_OVERLAPPEDWINDOW, + 0, 0, initialWidth, initialHeight, nullptr, + nullptr, GetModuleHandle(nullptr), nullptr); + + // Disable DWM animations + auto disable_window_transitions = TRUE; + DwmSetWindowAttribute(_windowHandle, DWMWA_TRANSITIONS_FORCEDISABLED, + &disable_window_transitions, + sizeof(disable_window_transitions)); + + ::SetWindowSubclass(_windowHandle, NativeViewSubclassProc, 69420, + NULL); // what does this do? + + auto style = ::GetWindowLongPtr(_windowHandle, GWL_STYLE); + style &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | + WS_EX_APPWINDOW); + ::SetWindowLongPtr(_windowHandle, GWL_STYLE, style); + + RECT flutterViewRect; + ::GetClientRect(_flutterViewWindow, &flutterViewRect); + + ::SetWindowLongPtr(_windowHandle, GWLP_USERDATA, + reinterpret_cast(_flutterRootWindow)); + + ::SetWindowPos(_windowHandle, _flutterRootWindow, flutterViewRect.left, + flutterViewRect.top, flutterViewRect.right - flutterViewRect.left, flutterViewRect.bottom - flutterViewRect.top, + SWP_NOACTIVATE); + + // remove taskbar entry for the window we created + ITaskbarList3* taskbar = nullptr; + ::CoCreateInstance(CLSID_TaskbarList, 0, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&taskbar)); + taskbar->DeleteTab(_windowHandle); + taskbar->Release(); + + ::ShowWindow(_windowHandle, SW_SHOW); + ::ShowWindow(_flutterRootWindow, SW_SHOW); + ::SetFocus(_flutterRootWindow); } + +HWND BackingWindow::GetHandle() { return _windowHandle; } +} // namespace polyvox_filament diff --git a/windows/backing_window.h b/windows/backing_window.h index bb7a7bef..090f0727 100644 --- a/windows/backing_window.h +++ b/windows/backing_window.h @@ -17,7 +17,8 @@ class BackingWindow { private: HWND _windowHandle; HWND _flutterRootWindow; -} + HWND _flutterViewWindow; +}; } #endif \ No newline at end of file diff --git a/windows/polyvox_filament_plugin.cpp b/windows/polyvox_filament_plugin.cpp index c3fab1b2..9473fb45 100644 --- a/windows/polyvox_filament_plugin.cpp +++ b/windows/polyvox_filament_plugin.cpp @@ -36,8 +36,6 @@ #include #include -#include "utils.h" - #include "flutter_render_context.h" #if USE_ANGLE @@ -181,7 +179,7 @@ void PolyvoxFilamentPlugin::CreateTexture( _context = std::make_unique(_pluginRegistrar, _textureRegistrar); #endif } - //_context->CreateTexture(width, height, std::move(result)); + _context->CreateTexture(width, height, std::move(result)); } void PolyvoxFilamentPlugin::DestroyTexture( @@ -228,14 +226,15 @@ void PolyvoxFilamentPlugin::HandleMethodCall( } else if (methodCall.method_name() == "destroyTexture") { DestroyTexture(methodCall, std::move(result)); } else if (methodCall.method_name() == "getRenderCallback") { + flutter::EncodableList resultList; #if !ANGLE && WGL_USE_BACKING_WINDOW - result->Success(nullptr); + resultList.push_back(flutter::EncodableValue((int64_t)nullptr)); + resultList.push_back(flutter::EncodableValue((int64_t)nullptr)); #else - flutter::EncodableList resultList; resultList.push_back(flutter::EncodableValue((int64_t)&render_callback)); resultList.push_back(flutter::EncodableValue((int64_t)this)); - result->Success(resultList); #endif + result->Success(resultList); } else if (methodCall.method_name() == "getDriverPlatform") { #ifdef USE_ANGLE result->Success(flutter::EncodableValue((int64_t)_platform)); diff --git a/windows/utils.cc b/windows/utils.cc deleted file mode 100644 index 16f7aab2..00000000 --- a/windows/utils.cc +++ /dev/null @@ -1,130 +0,0 @@ -// This file is a part of flutter_native_view -// (https://github.com/alexmercerind/flutter_native_view). -// -// Copyright (c) 2022, Hitesh Kumar Saini . -// All rights reserved. -// Use of this source code is governed by MIT license that can be found in the -// LICENSE file. - -#include "utils.h" -#include - -#pragma comment(lib, "dwmapi.lib") -#pragma comment(lib, "comctl32.lib") - -namespace flutternativeview { - -typedef enum _WINDOWCOMPOSITIONATTRIB { - WCA_UNDEFINED = 0, - WCA_NCRENDERING_ENABLED = 1, - WCA_NCRENDERING_POLICY = 2, - WCA_TRANSITIONS_FORCEDISABLED = 3, - WCA_ALLOW_NCPAINT = 4, - WCA_CAPTION_BUTTON_BOUNDS = 5, - WCA_NONCLIENT_RTL_LAYOUT = 6, - WCA_FORCE_ICONIC_REPRESENTATION = 7, - WCA_EXTENDED_FRAME_BOUNDS = 8, - WCA_HAS_ICONIC_BITMAP = 9, - WCA_THEME_ATTRIBUTES = 10, - WCA_NCRENDERING_EXILED = 11, - WCA_NCADORNMENTINFO = 12, - WCA_EXCLUDED_FROM_LIVEPREVIEW = 13, - WCA_VIDEO_OVERLAY_ACTIVE = 14, - WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15, - WCA_DISALLOW_PEEK = 16, - WCA_CLOAK = 17, - WCA_CLOAKED = 18, - WCA_ACCENT_POLICY = 19, - WCA_FREEZE_REPRESENTATION = 20, - WCA_EVER_UNCLOAKED = 21, - WCA_VISUAL_OWNER = 22, - WCA_HOLOGRAPHIC = 23, - WCA_EXCLUDED_FROM_DDA = 24, - WCA_PASSIVEUPDATEMODE = 25, - WCA_USEDARKMODECOLORS = 26, - WCA_LAST = 27 -} WINDOWCOMPOSITIONATTRIB; - -typedef struct _WINDOWCOMPOSITIONATTRIBDATA { - WINDOWCOMPOSITIONATTRIB Attrib; - PVOID pvData; - SIZE_T cbData; -} WINDOWCOMPOSITIONATTRIBDATA; - -typedef enum _ACCENT_STATE { - ACCENT_DISABLED = 0, - ACCENT_ENABLE_GRADIENT = 1, - ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, - ACCENT_ENABLE_BLURBEHIND = 3, - ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, - ACCENT_ENABLE_HOSTBACKDROP = 5, - ACCENT_INVALID_STATE = 6 -} ACCENT_STATE; - -typedef struct _ACCENT_POLICY { - ACCENT_STATE AccentState; - DWORD AccentFlags; - DWORD GradientColor; - DWORD AnimationId; -} ACCENT_POLICY; - -typedef BOOL(WINAPI* _GetWindowCompositionAttribute)( - HWND, WINDOWCOMPOSITIONATTRIBDATA*); -typedef BOOL(WINAPI* _SetWindowCompositionAttribute)( - HWND, WINDOWCOMPOSITIONATTRIBDATA*); - -static _SetWindowCompositionAttribute g_set_window_composition_attribute = NULL; -static bool g_set_window_composition_attribute_initialized = false; - -typedef LONG NTSTATUS, *PNTSTATUS; -#define STATUS_SUCCESS (0x00000000) - -typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); - -RTL_OSVERSIONINFOW GetWindowsVersion() { - HMODULE hmodule = ::GetModuleHandleW(L"ntdll.dll"); - if (hmodule) { - RtlGetVersionPtr rtl_get_version_ptr = - (RtlGetVersionPtr)::GetProcAddress(hmodule, "RtlGetVersion"); - if (rtl_get_version_ptr != nullptr) { - RTL_OSVERSIONINFOW rovi = {0}; - rovi.dwOSVersionInfoSize = sizeof(rovi); - if (STATUS_SUCCESS == rtl_get_version_ptr(&rovi)) { - return rovi; - } - } - } - RTL_OSVERSIONINFOW rovi = {0}; - return rovi; -} - -void SetWindowComposition(HWND window, int32_t accent_state, - int32_t gradient_color) { - // TODO: Look for a better available API. - if (GetWindowsVersion().dwBuildNumber >= 18362) { - std::cout << "got win ver" << std::endl; - if (!g_set_window_composition_attribute_initialized) { - std::cout << "not init" << std::endl; - auto user32 = ::GetModuleHandleA("user32.dll"); - if (user32) { - std::cout << "user32" << std::endl; - g_set_window_composition_attribute = - reinterpret_cast<_SetWindowCompositionAttribute>( - ::GetProcAddress(user32, "SetWindowCompositionAttribute")); - if (g_set_window_composition_attribute) { - std::cout << "set" << std::endl; - g_set_window_composition_attribute_initialized = true; - } - } - } - ACCENT_POLICY accent = {static_cast(accent_state), 2, - static_cast(gradient_color), 0}; - WINDOWCOMPOSITIONATTRIBDATA data; - data.Attrib = WCA_ACCENT_POLICY; - data.pvData = &accent; - data.cbData = sizeof(accent); - g_set_window_composition_attribute(window, &data); - } -} - -} // namespace flutternativeview diff --git a/windows/utils.h b/windows/utils.h deleted file mode 100644 index 36fb05d9..00000000 --- a/windows/utils.h +++ /dev/null @@ -1,20 +0,0 @@ -// This file is a part of flutter_native_view -// (https://github.com/alexmercerind/flutter_native_view). -// -// Copyright (c) 2022, Hitesh Kumar Saini . -// All rights reserved. -// Use of this source code is governed by MIT license that can be found in the -// LICENSE file. - -#include - -#include - -namespace flutternativeview { - -RTL_OSVERSIONINFOW GetWindowsVersion(); - -void SetWindowComposition(HWND window, int32_t accent_state, - int32_t gradient_color); - -} // namespace flutternativeview diff --git a/windows/wgl_context.cpp b/windows/wgl_context.cpp index e6394c34..fd3a1ff7 100644 --- a/windows/wgl_context.cpp +++ b/windows/wgl_context.cpp @@ -9,7 +9,8 @@ namespace polyvox_filament { WGLContext::WGLContext(flutter::PluginRegistrarWindows *pluginRegistrar, - flutter::TextureRegistrar *textureRegistrar) { + flutter::TextureRegistrar *textureRegistrar) + : _pluginRegistrar(pluginRegistrar), _textureRegistrar(textureRegistrar) { auto hwnd = pluginRegistrar->GetView()->GetNativeWindow(); @@ -99,27 +100,24 @@ void WGLContext::CreateTexture( uint32_t width, uint32_t height, std::unique_ptr> result) { -#ifdef WGL_USE_BACKING_WINDOW - _backingWindow = std::make_unique() - : std::vector resultList; +#if WGL_USE_BACKING_WINDOW + _backingWindow = std::make_unique( + _pluginRegistrar, static_cast(width), static_cast(height)); + std::vector resultList; resultList.push_back(flutter::EncodableValue((int64_t) nullptr)); resultList.push_back( flutter::EncodableValue((int64_t)_backingWindow->GetHandle())); resultList.push_back(flutter::EncodableValue((int64_t) nullptr)); result->Success(resultList); -} -else { - result->Error("FOO", "ERROR", nullptr); -} #else if (_active.get()) { result->Error("ERROR", "Texture already exists. You must call destroyTexture before " "attempting to create a new one."); } else { - _active = std::make_unique(_pluginRegistrar, _textureRegistrar, - std::move(result), width, height, - _context); + _active = std::make_unique( + _pluginRegistrar, _textureRegistrar, std::move(result), width, height, + _context); if (_active->flutterTextureId != -1) { std::vector resultList; diff --git a/windows/wgl_context.h b/windows/wgl_context.h index 5d676ec0..59450ff9 100644 --- a/windows/wgl_context.h +++ b/windows/wgl_context.h @@ -15,6 +15,8 @@ namespace polyvox_filament { void CreateTexture(uint32_t width, uint32_t height, std::unique_ptr> result); void* GetSharedContext(); private: + flutter::PluginRegistrarWindows* _pluginRegistrar = nullptr; + flutter::TextureRegistrar* _textureRegistrar = nullptr; HGLRC _context = NULL; #if WGL_USE_BACKING_WINDOW std::unique_ptr _backingWindow = nullptr;