macosui / macos_window_utils.dart

macos_window_utils is a Flutter package that provides a set of methods for modifying the NSWindow of a Flutter application on macOS.
https://pub.dev/packages/macos_window_utils
MIT License
52 stars 9 forks source link

Consider offering a widget for window configuration #29

Open matthew-carroll opened 1 year ago

matthew-carroll commented 1 year ago

As far as I can tell, based on the README documentation, all window configurations go through imperative static methods.

Consider offering a widget for most/all of these configurations. You don't need to remove the imperative configurations, but it might be a lot more natural for Flutter developers to consume these options as a widget.

Widget build(BuildContext context) {
  return MacWindow(
    initialSize: const Size(1000, 650),
    titlebarStyle: const MacWindowTitleBarStyle(
      transparent: true,
      hideTitle: true,
    ),
    fullSizeContentView: true,
    child: MaterialApp(
      home: MyHomePage(),
    ),
  );
}

By offering these capabilities as a widget, you don't need for developers to explicitly initialize the Flutter binding. You don't need developers to create a place for asynchronous execution. You don't need developers to leave the widget tree.

Adrian-Samoticha commented 1 year ago

I can see how that would make things a little bit easier in some cases but at the same time, it kind of makes it look as if the MacWindow was a widget that could be placed anywhere in the app, perhaps even at multiple places, which is not the case.

Additionally, an app might listen to window events through an NSWindowDelegate and change the properties of the window dynamically during runtime. In such a case, you wouldn’t want to have a widget in your widget tree that could reset the window’s properties when a rebuild is triggered.

matthew-carroll commented 1 year ago

it kind of makes it look as if the MacWindow was a widget that could be placed anywhere in the app, perhaps even at multiple places, which is not the case

This isn't really different than WidgetsApp, MaterialApp or CupertinoApp. Widgets can be placed in multiple locations in the tree, but sometimes doing that will cause undesired results. That hasn't been a problem for Flutter developers before. I certainly don't think that's a good tradeoff to keep people from using a simpler, declarative, more easily discoverable API...

Additionally, an app might listen to window events through an NSWindowDelegate and change the properties of the window dynamically during runtime. In such a case, you wouldn’t want to have a widget in your widget tree that could reset the window’s properties when a rebuild is triggered.

In those cases, developers can choose not to use the widget. But why prevent all other use-cases from gaining the clarity and simplicity?

Adrian-Samoticha commented 1 year ago

I am fairly certain that it’s possible to have multiple MaterialApps in a single app and I am not aware of any unintended consequences myself. In fact, there can be valid reasons to do so and I have done so myself in my not-yet-finished flutter_manual_widget_tester project.

MaterialApp very much acts like any other widget. Its most common use case may be to act as the root for all other widgets, but if you position it elsewhere it very much respects that. This wouldn’t be the case for the proposed MacWindow.

In those cases, developers can choose not to use the widget. But why prevent all other use-cases from gaining the clarity and simplicity?

If a developer decides to react to, say, full-screen events by dynamically removing the title bar when the app enters fullscreen, but they only realized that they needed to do so after already having decided to use the newly proposed widget, they would now need to migrate away from the widget to the lower-level method that already existed beforehand. This would add significant refactoring overhead, especially if they never bothered to familiarize themselves with the lower-level API.

I don’t think running methods from your main function, or even from within build methods is too unheard-of in the Flutter world, although admittedly, bitsdojo_window has a doWhenWindowReady function which helps hide the WidgetsFlutterBinding.ensureInitialized(); call, as well as any asynchronous calls.
Maybe that’s the route we could go here?

matthew-carroll commented 1 year ago

Ok, I think we're getting a bit abstract and hypothetical, so I'll return this point to what I can say about myself, with certainty.

I would much prefer to establish window configurations from a top-level widget, when possible. I'm not worried about accidentally repeating the widget elsewhere - I can follow documentation that tells me not to. My current use-cases don't need the delegate. Maybe some of them will at some point, but none of them need it now, and some of them may never need it.

There could be any number of API decisions related to this capability. I'm happy to get into the details. But those details don't matter very much if this request is perceived as low-value. So I'd like to first make clear the value to me, and the fact that I would prefer a widget option for cases where it's appropriate.

Adrian-Samoticha commented 1 year ago

Ok, I think we're getting a bit abstract and hypothetical, so I'll return this point to what I can say about myself, with certainty.

My point wasn’t hypothetical at all — what I described is exactly what happened when developing the macos_ui example app.

Honestly, I am not very convinced of the idea for the reasons I mentioned above, however, if you wish to configure your window without leaving the widget tree, there are other ways to do so. You could wrap your app in a Builder widget and have its builder method configure the window, or write a small stateful widget that runs a given function within its initState method. You could then provide it with a function that styles your window.

This way, you could use the already-present API, while still remaining inside your widget tree.