-
Notifications
You must be signed in to change notification settings - Fork 28.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Multiwindow Preview based on FFI #165072
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's tricky to review this because of the older PRs that this is branched off of, but it looks great from what I see.
- Easiest case for app developers: I'm writing a cross-platform desktop app and I don't care about the details and just want to create a couple of windows and control their size/title/state.
- Use RegularWindowController and its
.modify
method.
- Use RegularWindowController and its
- Still doable: I'm writing say a Linux-only app, and I want to use something that's only available in the Linux windowing API.
- Use LinuxWindowController.
And it seems like we as framework/engine developers are able to modify Linux/Windows/MacOSWindowControllers independently if we want to add some new platform API.
Those were the main things I was looking for and they all seem to work the way I hoped they would 👍
|
||
@Native<Void Function(Pointer<Void>, Int64)>(symbol: 'flutter_set_window_state') | ||
external static void _setWindowState(Pointer<Void> windowHandle, int state); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if in the future Linux adds some new window method that we want to support, we can just add it here without messing with RegularWindowController, WindowController, etc. right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's exactly right. We can add platform specific functionality independently, and then when if we later decide that something is generic enough to support on all platform add it to RegularWindowController
.
} | ||
|
||
FLUTTER_EXPORT | ||
void flutter_set_window_state(HWND hwnd, int64_t state) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to implement this in C++ instead of Dart? Did you consider FFI'ing straight to win32? Something like this: https://github.com/halildurmus/win32/blob/27496b847b9ce831db798203238e4aa847e87d54/packages/win32/lib/src/win32/user32.g.dart#L9200-L9214
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For trivial things I already call win32 API directly, for example _setWindowTitle
calls SetWindowTextW
, _destroyWindow
calls DestroyWindow
. Here i went this way because it seemed simpler (needs less declarations and constants), in reality there is nothing preventing us from calling win32 api directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a completely reasonable answer, but I suspect that more lines of code is a good trade-off for moving more of the logic to Dart.
Customers know how to debug Dart code. However, they might be significantly less comfortable debugging C++ code. The more logic is in the Dart layer, the more accessible that logic is to our customers.
Of course, this is a balancing act. If there are areas where Dart FFI to win32 is significantly less readable/maintainable, we should stick to C++.
|
||
/// Creates the [WindowingOwner] instance available via [windowingOwner]. | ||
/// Can be overriden in subclasses to create embedder-specific [WindowingOwner] | ||
/// implementation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it hard to switch an app to a WidgetsBinding
subclass? What would an app entrypoint look like if I need to support a third-party embedder? I imagine you won't be able to use runApp
anymore?
Spitballing: should we instead have a model where you set your desired windowing owner if you have a third-party embedder?
In today's world, something like:
void main() {
if (onPlatformFoo) {
WidgetsBinding.instance.defaultWindowingOwner = WindowingOwnerFoo();
}
runApp(...);
}
Of course the third-party platform could provide a helper to do all the necessary registration.
If the "custom platforms set stuff" model works, we could extend this to allow embedders to inject Dart code that's invoked at Isolate start up, before main executes. For example, today we allow embedders to inject a Dart plugin registrant which the engine invokes before invoking main. This would allow embedders to register their custom Dart implementations automatically without the user needing to add code to their main
function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, WidgetsBinding.platformMenuDelegate
appears to be useful prior art. It's a member on the widgets binding that a third-party platform can override to implement their own functionality. See: https://api.flutter.dev/flutter/widgets/PlatformMenuDelegate-class.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was honestly only a first draft. If widget binding subclass is too much friction we can just make the owner field non-final.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was honestly only a first draft.
Yup, and it's a great first draft! Sorry for nitpicking so early in the process haha
If widget binding subclass is too much friction we can just make the owner field non-final.
That sounds good to me.
This is initial prototype for multi-window implementation based on FFI. Contains basic implementation for macOS, Windows and Linux platforms.
The purpose of this PR is to gather feedback on the approach and design. It definitely needs polish and work.
The PR is a bit noisy right now since it contains code from other PRs that are not yet merged.
Interesting bits are inside
packages/flutter/lib/src/widgets/window.dart
packages/flutter/lib/src/widgets/window_win32.dart
packages/flutter/lib/src/widgets/window_macos.dart
packages/flutter/lib/src/widgets/window_linux.dart
engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm
engine/src/flutter/shell/platform/linux/fl_windowing.cc
engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc
Basic usage
Creating a Window
This will synchronously create new platform window and the corresponding
FlutterView
. When the controller constructor returns theFlutterView
can immediately be placed to widget hierarchy:RegularWindowController
is a factory and returned instance is a platform specific subclass, so it can be casted to access platform specific features.for example
or
(This being synchronous and FFI based we are actually able to route windows messages to Dart where they can be handled if needed, allowing for example for a custom
NC_HITTEST
implementation that can do actual Flutter hittesting to determine draggable window area).Out of tree embedders
This approach scales to out of tree embedders as well. Out of tree embedder would subclass
WidgetBinding
and override thecreateWindowingOwner
method. That is entry point for windowing implementation responsible for creating platform specific window controllers.The out of tree embedders can then provide
RegularWindowControllerHarmony
for example, which must implement the common functionality required byRegularWindowController
, but can also provide custom, platform specific functionality.One benefit of this approach is that the platform interface is defined in Dart, rather than being an informal platform channel protocol, so when changes are made to common interface (
RegularWindowController
) that out of tree embedder does not expect, it will fail at compile time rather than runtime.The out of tree embedder code can be provided as package.
Pre-launch Checklist
///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.