From 21bb45a47647d7afd35261d0c9d8c2723bd09a20 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Mon, 25 Nov 2024 13:53:59 -0500 Subject: [PATCH 01/23] wip: refactor of the foundational code --- packages/flutter/lib/src/widgets/window.dart | 556 ++++++++----------- 1 file changed, 222 insertions(+), 334 deletions(-) diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index 5488615f5d078..0af8843923fe8 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -3,10 +3,8 @@ // found in the LICENSE file. import 'dart:ui' show FlutterView; -import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; /// Defines the type of a [Window] @@ -30,407 +28,297 @@ enum WindowArchetype { tip, } -class WindowChangedEvent { - WindowChangedEvent({this.size}); - Size? size; -} +/// Controller used with the [RegularWindow] widget. This controller +/// provides access to modify and destroy the window, in addition to +/// listening to changes on the window. +class RegularWindowController with ChangeNotifier { + late int _viewId; -class _WindowMetadata { - _WindowMetadata( - {required this.flView, - required this.archetype, - required this.size, - this.parent}); + /// The ID of the view used for this window, which is unique to each window. + int get viewId => _viewId; - final FlutterView flView; - final WindowArchetype archetype; - final Size size; - final Window? parent; -} + late Size _size; -/// Defines a [Window] created by the application. To use [Window]s, you must wrap -/// your application in the [MultiWindowApp] widget. New [Window]s are created via -/// global functions like [createRegular] and [createPopup]. -abstract class Window { - /// [view] the underlying [FlutterView] - /// [builder] render function containing the content of this [Window] - /// [size] initial [Size] of the [Window] - /// [parent] the parent of this window, if any - Window( - {required this.view, - required this.builder, - required this.size, - this.parent}); + /// The current size of the window. This may differ from the requested size. + Size get size => _size; - /// The underlying [FlutterView] associated with this [Window] - final FlutterView view; - - /// The render function containing the content of this [Window] - final Widget Function(BuildContext context) builder; + /// Modifies this window with the provided properties. + Future modify({Size? size}) { + throw UnimplementedError(); + } - /// The current [Size] of the [Window] - Size size; + /// Destroys this window. + Future destroy() { + return destroyWindow(viewId); + } +} - /// The parent of this window, which may or may not exist. - final Window? parent; +class RegularWindow extends StatefulWidget { + RegularWindow( + {required Size preferredSize, + this.onDestroyed, + this.controller, + required this.child}) + : _future = createRegular(size: preferredSize); - /// A list of child [Window]s associated with this window - final List children = []; + final RegularWindowController? controller; + void Function()? onDestroyed; + final Future _future; + final Widget child; - UniqueKey _key = UniqueKey(); + @override + State createState() => _RegularWindowState(); +} - final StreamController _onDestroyedController = - StreamController.broadcast(); - final StreamController _onWindowChangedController = - StreamController.broadcast(); +class _RegularWindowState extends State { + _WindowListener? _listener; - Stream get destroyedStream { - return _onDestroyedController.stream; + @override + void initState() { + super.initState(); + final _WindowingAppContext? windowingAppContext = + _WindowingAppContext.of(context); + widget._future.then((RegularWindowMetadata metadata) async { + assert(windowingAppContext != null); + _listener = _WindowListener( + viewId: metadata.view.viewId, + onChanged: (_WindowChangeProperties properties) { + if (widget.controller == null) { + return; + } + + if (properties.size != null) { + widget.controller!._size = properties.size!; + } + }, + onDestroyed: widget.onDestroyed); + windowingAppContext!.windowingApp._registerListener(_listener!); + }); } - Stream get changedStream { - return _onWindowChangedController.stream; + @override + void dispose() { + super.dispose(); + final _WindowingAppContext? windowingAppContext = + _WindowingAppContext.of(context); + if (_listener != null) { + assert(windowingAppContext != null); + windowingAppContext!.windowingApp._unregisterListener(_listener!); + } } - WindowArchetype get archetype; + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: widget._future, + builder: (BuildContext context, + AsyncSnapshot metadata) { + if (!metadata.hasData) { + return Container(); + } + + return View( + view: metadata.data!.view, + child: WindowContext( + viewId: metadata.data!.view.viewId, child: widget.child)); + }); + } } -/// Describes a top level window that is created with [createRegular]. -class RegularWindow extends Window { - /// [view] the underlying [FlutterView] - /// [builder] render function containing the content of this [Window] - /// [size] initial [Size] of the [Window] - RegularWindow( - {required super.view, required super.builder, required super.size}); +/// Provides descendents with access to the [Window] in which they are rendered +class WindowContext extends InheritedWidget { + /// [window] the [Window] + const WindowContext({super.key, required this.viewId, required super.child}); + + /// The view ID in this context + final int viewId; + + /// Returns the [WindowContext] if any + static WindowContext? of(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType(); + } @override - WindowArchetype get archetype { - return WindowArchetype.regular; + bool updateShouldNotify(WindowContext oldWidget) { + return viewId != oldWidget.viewId; } } -/// Creates a new regular [Window]. +class RegularWindowMetadata { + RegularWindowMetadata({required this.view, required this.size}); + + final FlutterView view; + final Size size; +} + +class _WindowMetadata { + _WindowMetadata( + {required this.flView, + required this.archetype, + required this.size, + this.parent}); + + final FlutterView flView; + final WindowArchetype archetype; + final Size size; + final int? parent; +} + +/// Creates a regular window for the platform and returns the metadata associated +/// with the new window. Users should prefer using the [RegularWindow] +/// widget instead of this method. /// -/// [context] the current [BuildContext], which must include a [MultiWindowAppContext] /// [size] the size of the new [Window] in pixels -/// [builder] a builder function that returns the contents of the new [Window] -Future createRegular( - {required BuildContext context, - required Size size, - required WidgetBuilder builder}) async { - final MultiWindowAppContext? multiViewAppContext = - MultiWindowAppContext.of(context); - if (multiViewAppContext == null) { - throw Exception( - 'Cannot create a window: your application does not use MultiViewApp. Try wrapping your toplevel application in a MultiViewApp widget'); - } +Future createRegular({required Size size}) async { + final _WindowMetadata metadata = + await _createWindow(viewBuilder: (MethodChannel channel) async { + return await channel.invokeMethod('createWindow', { + 'size': [size.width.toInt(), size.height.toInt()], + }) as Map; + }); + return RegularWindowMetadata(view: metadata.flView, size: metadata.size); +} - return multiViewAppContext.windowController - .createRegular(size: size, builder: builder); +Future<_WindowMetadata> _createWindow( + {required Future> Function(MethodChannel channel) + viewBuilder}) async { + final Map creationData = + await viewBuilder(SystemChannels.windowing); + final int viewId = creationData['viewId']! as int; + final WindowArchetype archetype = + WindowArchetype.values[creationData['archetype']! as int]; + final List size = creationData['size']! as List; + final int? parentViewId = creationData['parentViewId'] as int?; + + final FlutterView flView = + WidgetsBinding.instance.platformDispatcher.views.firstWhere( + (FlutterView view) => view.viewId == viewId, + orElse: () { + throw Exception('No matching view found for viewId: $viewId'); + }, + ); + + return _WindowMetadata( + flView: flView, + archetype: archetype, + size: Size((size[0]! as int).toDouble(), (size[1]! as int).toDouble()), + parent: parentViewId); } -/// Destroys the provided [Window] +/// Destroys the window associated with the provided view ID. /// -/// [context] the current [BuildContext], which must include a [MultiWindowAppContext] -/// [window] the [Window] to be destroyed -Future destroyWindow(BuildContext context, Window window) async { - final MultiWindowAppContext? multiViewAppContext = - MultiWindowAppContext.of(context); - if (multiViewAppContext == null) { - throw Exception( - 'Cannot create a window: your application does not use MultiViewApp. Try wrapping your toplevel application in a MultiViewApp widget'); +/// [viewId] the view id of the window that should be destroyed +Future destroyWindow(int viewId) async { + try { + await SystemChannels.windowing + .invokeMethod('destroyWindow', {'viewId': viewId}); + } on PlatformException catch (e) { + throw ArgumentError( + 'Unable to delete window with view_id=$viewId. Does the window exist? Error: $e'); } - - return multiViewAppContext.windowController.destroyWindow(window); } -/// Declares that an application will create multiple [Window]s. -/// The current [Window] can be looked up with [WindowContext.of]. -class MultiWindowApp extends StatefulWidget { - /// [initialWindows] A list of [Function]s to create [Window]s that will be run as soon as the app starts. - const MultiWindowApp({super.key, this.initialWindows}); - - /// A list of [Function]s to create [Window]s that will be run as soon as the app starts. - final List Function(BuildContext)>? initialWindows; +class _WindowChangeProperties { + _WindowChangeProperties({this.size, this.parentViewId}); - @override - State createState() => WindowController(); + Size? size; + int? parentViewId; } -/// Provides methods to create, update, and delete [Window]s. It is preferred that -/// you use the global functions like [createRegular] and [destroyWindow] over -/// accessing the [WindowController] directly. -class WindowController extends State { - List _windows = []; +class _WindowListener { + _WindowListener( + {required this.viewId, + required this.onChanged, + required this.onDestroyed}); - @override - void initState() { - super.initState(); + int viewId; + void Function(_WindowChangeProperties) onChanged; + void Function()? onDestroyed; +} + +/// Declares that an application will create multiple [Window]s. +/// The current [Window] can be looked up with [WindowContext.of]. +class WindowingApp extends StatelessWidget { + WindowingApp({super.key, required this.children}) { SystemChannels.windowing.setMethodCallHandler(_methodCallHandler); } + final List children; + final List<_WindowListener> _listeners = []; + Future _methodCallHandler(MethodCall call) async { final Map arguments = call.arguments as Map; switch (call.method) { + case 'onWindowCreated': + final int viewId = arguments['viewId']! as int; + int? parentViewId; + if (arguments['parentViewId'] != null) { + parentViewId = arguments['parentViewId']! as int; + } + + final _WindowChangeProperties properties = + _WindowChangeProperties(parentViewId: parentViewId); + for (final _WindowListener listener in _listeners) { + if (listener.viewId == viewId) { + listener.onChanged(properties); + } + } case 'onWindowChanged': final int viewId = arguments['viewId']! as int; - final Window? window = _findWindow(viewId); - assert(window != null); Size? size; if (arguments['size'] != null) { final List sizeRaw = arguments['size']! as List; size = Size( (sizeRaw[0]! as int).toDouble(), (sizeRaw[1]! as int).toDouble()); } - _changed(window!, size); + + final _WindowChangeProperties properties = + _WindowChangeProperties(size: size); + for (final _WindowListener listener in _listeners) { + if (listener.viewId == viewId) { + listener.onChanged(properties); + } + } case 'onWindowDestroyed': final int viewId = arguments['viewId']! as int; - _remove(viewId); - } - } - - Future<_WindowMetadata> _createWindow( - {required Future> Function(MethodChannel channel) - viewBuilder, - required WidgetBuilder builder}) async { - final Map creationData = - await viewBuilder(SystemChannels.windowing); - final int viewId = creationData['viewId']! as int; - final WindowArchetype archetype = - WindowArchetype.values[creationData['archetype']! as int]; - final List size = creationData['size']! as List; - final int? parentViewId = creationData['parentViewId'] as int?; - - final FlutterView flView = - WidgetsBinding.instance.platformDispatcher.views.firstWhere( - (FlutterView view) => view.viewId == viewId, - orElse: () { - throw Exception('No matching view found for viewId: $viewId'); - }, - ); - - Window? parent; - if (parentViewId != null) { - parent = _findWindow(parentViewId); - assert(parent != null, - 'No matching window found for parentViewId: $parentViewId'); - } - - return _WindowMetadata( - flView: flView, - archetype: archetype, - size: Size((size[0]! as int).toDouble(), (size[1]! as int).toDouble()), - parent: parent); - } - - /// Creates a new regular [Window] - /// - /// [size] the size of the new [Window] in pixels - /// [builder] a builder function that returns the contents of the new [Window] - Future createRegular( - {required Size size, required WidgetBuilder builder}) async { - final _WindowMetadata metadata = await _createWindow( - viewBuilder: (MethodChannel channel) async { - return await channel.invokeMethod('createWindow', { - 'size': [size.width.toInt(), size.height.toInt()], - }) as Map; - }, - builder: builder); - final RegularWindow window = RegularWindow( - view: metadata.flView, builder: builder, size: metadata.size); - _add(window); - return window; - } - - /// Destroys the provided [Window] - /// - /// [window] the [Window] to be destroyed - Future destroyWindow(Window window) async { - try { - await SystemChannels.windowing.invokeMethod( - 'destroyWindow', {'viewId': window.view.viewId}); - _remove(window.view.viewId); - } on PlatformException catch (e) { - throw ArgumentError( - 'Unable to delete window with view_id=${window.view.viewId}. Does the window exist? Error: $e'); - } - } - - void _add(Window window) { - final List copy = List.from(_windows); - if (window.parent != null) { - window.parent!.children.add(window); - Window rootWindow = window; - while (rootWindow.parent != null) { - rootWindow = rootWindow.parent!; - } - rootWindow._key = UniqueKey(); - } else { - copy.add(window); - } - - setState(() { - _windows = copy; - }); - } - - Window? _findWindow(int viewId) { - Window? find(int viewId, Window window) { - if (window.view.viewId == viewId) { - return window; - } - - for (final Window other in window.children) { - final Window? result = find(viewId, other); - if (result != null) { - return result; + for (final _WindowListener listener in _listeners) { + if (listener.viewId == viewId) { + listener.onDestroyed?.call(); + } } - } - - return null; - } - - for (final Window other in _windows) { - final Window? result = find(viewId, other); - if (result != null) { - return result; - } - } - - return null; - } - - void _remove(int viewId) { - final List copy = List.from(_windows); - - final Window? toDelete = _findWindow(viewId); - if (toDelete == null) { - return; - } - - if (toDelete.parent == null) { - copy.remove(toDelete); - } else { - toDelete.parent!.children.remove(toDelete); - } - - toDelete._onDestroyedController.add(null); - - setState(() { - _windows = copy; - }); - } - - void _changed(Window window, Size? size) { - if (size != null) { - window.size = size; - window._onWindowChangedController.add(WindowChangedEvent(size: size)); } } - @override - Widget build(BuildContext context) { - return MultiWindowAppContext( - windows: _windows, - windowController: this, - child: _MultiWindowAppView( - initialWindows: widget.initialWindows, windows: _windows)); + void _registerListener(_WindowListener listener) { + _listeners.add(listener); } -} -/// Provides access to the list of [Window]s. -/// Users may provide the identifier of a [View] to look up a particular -/// [Window] if any exists. -/// -/// This class also provides access to the [WindowController] which is -/// used internally to provide access to create, update, and delete methods -/// on the windowing system. -class MultiWindowAppContext extends InheritedWidget { - /// [windows] a list of [Window]s - /// [windowController] the [WindowController] active in this context - const MultiWindowAppContext( - {super.key, - required super.child, - required this.windows, - required this.windowController}); - - /// The list of Windows - final List windows; - - /// The [WindowController] active in this context - final WindowController windowController; - - /// Returns the [MultiWindowAppContext] if any - static MultiWindowAppContext? of(BuildContext context) { - return context.dependOnInheritedWidgetOfExactType(); - } - - @override - bool updateShouldNotify(MultiWindowAppContext oldWidget) { - return windows != oldWidget.windows || - windowController != oldWidget.windowController; - } -} - -class _MultiWindowAppView extends StatefulWidget { - const _MultiWindowAppView( - {required this.initialWindows, required this.windows}); - - final List Function(BuildContext)>? initialWindows; - final List windows; - - @override - State createState() => _MultiWindowAppViewState(); -} - -class _MultiWindowAppViewState extends State<_MultiWindowAppView> { - @override - void initState() { - super.initState(); - SchedulerBinding.instance.addPostFrameCallback((_) async { - if (widget.initialWindows != null) { - for (final Future Function(BuildContext) window - in widget.initialWindows!) { - await window(context); - } - } - }); - } - - Widget buildView(BuildContext context, Window window) { - return View( - key: window._key, - view: window.view, - child: WindowContext(window: window, child: window.builder(context))); + void _unregisterListener(_WindowListener listener) { + _listeners.remove(listener); } @override Widget build(BuildContext context) { - final List views = []; - for (final Window window in widget.windows) { - views.add(buildView(context, window)); - } - return ViewCollection(views: views); + return _WindowingAppContext( + windowingApp: this, child: ViewCollection(views: children)); } } -/// Provides descendents with access to the [Window] in which they are rendered -class WindowContext extends InheritedWidget { - /// [window] the [Window] - const WindowContext({super.key, required this.window, required super.child}); +class _WindowingAppContext extends InheritedWidget { + const _WindowingAppContext( + {super.key, required super.child, required this.windowingApp}); - /// The [Window] in this context - final Window window; + final WindowingApp windowingApp; - /// Returns the [WindowContext] if any - static WindowContext? of(BuildContext context) { - return context.dependOnInheritedWidgetOfExactType(); + /// Returns the [MultiWindowAppContext] if any + static _WindowingAppContext? of(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType<_WindowingAppContext>(); } @override - bool updateShouldNotify(WindowContext oldWidget) { - return window != oldWidget.window; + bool updateShouldNotify(_WindowingAppContext oldWidget) { + return windowingApp != oldWidget.windowingApp; } } From 05b10525391dcf5fd6cb48be64c9379020c14a79 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Mon, 25 Nov 2024 15:52:45 -0500 Subject: [PATCH 02/23] wip: refactored the mutlti window reference app to use new API --- .../lib/app/main_window.dart | 134 ++++++++---------- .../lib/app/regular_window.dart | 51 ------- .../lib/app/regular_window_content.dart | 54 +++++++ .../lib/app/window_metadata_content.dart | 30 ++++ .../lib/app/window_settings_dialog.dart | 14 +- examples/multi_window_ref_app/lib/main.dart | 12 +- packages/flutter/lib/src/widgets/window.dart | 24 +++- 7 files changed, 179 insertions(+), 140 deletions(-) delete mode 100644 examples/multi_window_ref_app/lib/app/regular_window.dart create mode 100644 examples/multi_window_ref_app/lib/app/regular_window_content.dart create mode 100644 examples/multi_window_ref_app/lib/app/window_metadata_content.dart diff --git a/examples/multi_window_ref_app/lib/app/main_window.dart b/examples/multi_window_ref_app/lib/app/main_window.dart index 19c4f1d63ed44..2f1f96c6e82c0 100644 --- a/examples/multi_window_ref_app/lib/app/main_window.dart +++ b/examples/multi_window_ref_app/lib/app/main_window.dart @@ -1,9 +1,43 @@ import 'package:flutter/material.dart'; +import 'package:multi_window_ref_app/app/window_metadata_content.dart'; -import 'regular_window.dart'; +import 'regular_window_content.dart'; import 'window_settings.dart'; import 'window_settings_dialog.dart'; +class _WindowManagerModel extends ChangeNotifier { + final List _windows = []; + List get windows => _windows; + int? _selectedViewId; + WindowMetadata? get selected { + if (_selectedViewId == null) { + return null; + } + + for (final WindowMetadata window in _windows) { + if (window.view.viewId == _selectedViewId) { + return window; + } + } + + return null; + } + + void add(WindowMetadata window) { + _windows.add(window); + notifyListeners(); + } + + void remove(WindowMetadata window) { + _windows.remove(window); + notifyListeners(); + } + + void select(int? viewId) { + _selectedViewId = viewId; + } +} + class MainWindow extends StatefulWidget { const MainWindow({super.key}); @@ -12,20 +46,10 @@ class MainWindow extends StatefulWidget { } class _MainWindowState extends State { - int selectedRowIndex = -1; - final List _managedWindows = []; + final _WindowManagerModel _windowManagerModel = _WindowManagerModel(); @override Widget build(BuildContext context) { - List getWindowsInTree(List windows) { - return windows - .expand((window) => [window, ...getWindowsInTree(window.children)]) - .toList(); - } - - final List windows = - getWindowsInTree(MultiWindowAppContext.of(context)!.windows); - final widget = Scaffold( appBar: AppBar( title: const Text('Multi Window Reference App'), @@ -37,12 +61,8 @@ class _MainWindowState extends State { flex: 60, child: SingleChildScrollView( scrollDirection: Axis.vertical, - child: _ActiveWindowsTable( - windows: windows, - selectedRowIndex: selectedRowIndex, - onSelectedRowIndexChanged: (int index) => - setState(() => selectedRowIndex = index), - ), + child: + _ActiveWindowsTable(windowManagerModel: _windowManagerModel), ), ), Expanded( @@ -50,13 +70,12 @@ class _MainWindowState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - _WindowCreatorCard( - selectedWindow: selectedRowIndex < 0 || - selectedRowIndex >= windows.length - ? null - : windows[selectedRowIndex], - onDialogOpened: (window) => _managedWindows.add(window), - onDialogClosed: (window) => _managedWindows.remove(window)), + ListenableBuilder( + listenable: _windowManagerModel, + builder: (BuildContext context, Widget? child) { + return _WindowCreatorCard( + selectedWindow: _windowManagerModel.selected); + }) ], ), ), @@ -64,46 +83,26 @@ class _MainWindowState extends State { ), ); - final window = WindowContext.of(context)!.window; final List childViews = []; - for (final Window childWindow in window.children) { - if (!_shouldRenderWindow(childWindow)) { - continue; - } - - childViews.add(View( - view: childWindow.view, - child: WindowContext( - window: childWindow, - child: childWindow.builder(context), - ), - )); + for (final WindowMetadata childWindow in _windowManagerModel.windows) { + childViews.add(WindowMetadataContent(childWindow: childWindow)); } return ViewAnchor(view: ViewCollection(views: childViews), child: widget); } - - bool _shouldRenderWindow(Window window) { - return !_managedWindows.contains(window); - } } class _ActiveWindowsTable extends StatelessWidget { - const _ActiveWindowsTable( - {required this.windows, - required this.selectedRowIndex, - required this.onSelectedRowIndexChanged}); + const _ActiveWindowsTable({required this.windowManagerModel}); - final List windows; - final int selectedRowIndex; - final void Function(int) onSelectedRowIndexChanged; + final _WindowManagerModel windowManagerModel; @override Widget build(BuildContext context) { return DataTable( showBottomBorder: true, onSelectAll: (selected) { - onSelectedRowIndexChanged(-1); + windowManagerModel.select(null); }, columns: const [ DataColumn( @@ -135,13 +134,16 @@ class _ActiveWindowsTable extends StatelessWidget { ), numeric: true), ], - rows: windows.asMap().entries.map((indexedEntry) { + rows: windowManagerModel.windows + .asMap() + .entries + .map((indexedEntry) { final index = indexedEntry.key; - final Window entry = indexedEntry.value; + final WindowMetadata entry = indexedEntry.value; final window = entry; final viewId = window.view.viewId; - final archetype = window.archetype; - final isSelected = selectedRowIndex == index; + final archetype = window.type; + final isSelected = viewId == windowManagerModel._selectedViewId; return DataRow( color: WidgetStateColor.resolveWith((states) { @@ -153,7 +155,7 @@ class _ActiveWindowsTable extends StatelessWidget { selected: isSelected, onSelectChanged: (selected) { if (selected != null) { - onSelectedRowIndexChanged(selected ? index : -1); + windowManagerModel.select(selected ? viewId : null); } }, cells: [ @@ -167,7 +169,7 @@ class _ActiveWindowsTable extends StatelessWidget { IconButton( icon: const Icon(Icons.delete_outlined), onPressed: () { - destroyWindow(context, window); + destroyWindow(viewId); }, ), ), @@ -179,14 +181,9 @@ class _ActiveWindowsTable extends StatelessWidget { } class _WindowCreatorCard extends StatefulWidget { - const _WindowCreatorCard( - {required this.selectedWindow, - required this.onDialogOpened, - required this.onDialogClosed}); + const _WindowCreatorCard({required this.selectedWindow}); - final Window? selectedWindow; - final void Function(Window) onDialogOpened; - final void Function(Window) onDialogClosed; + final WindowMetadata? selectedWindow; @override State createState() => _WindowCreatorCardState(); @@ -219,12 +216,7 @@ class _WindowCreatorCardState extends State<_WindowCreatorCard> { children: [ OutlinedButton( onPressed: () async { - await createRegular( - context: context, - size: _settings.regularSize, - builder: (BuildContext context) { - return const MaterialApp(home: RegularWindowContent()); - }); + await createRegular(size: _settings.regularSize); }, child: const Text('Regular'), ), @@ -234,9 +226,7 @@ class _WindowCreatorCardState extends State<_WindowCreatorCard> { child: TextButton( child: const Text('SETTINGS'), onPressed: () { - windowSettingsDialog(context, _settings, - widget.onDialogOpened, widget.onDialogClosed) - .then( + windowSettingsDialog(context, _settings).then( (WindowSettings? settings) { if (settings != null) { _settings = settings; diff --git a/examples/multi_window_ref_app/lib/app/regular_window.dart b/examples/multi_window_ref_app/lib/app/regular_window.dart deleted file mode 100644 index e564b8fe9f351..0000000000000 --- a/examples/multi_window_ref_app/lib/app/regular_window.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:flutter/material.dart'; - -class RegularWindowContent extends StatelessWidget { - const RegularWindowContent({super.key}); - - @override - Widget build(BuildContext context) { - final Window window = WindowContext.of(context)!.window; - final widget = Scaffold( - appBar: AppBar(title: Text('${window.archetype}')), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () async { - await createRegular( - context: context, - size: const Size(400, 300), - builder: (BuildContext context) => - const MaterialApp(home: RegularWindowContent())); - }, - child: const Text('Create Regular Window'), - ), - const SizedBox(height: 20), - Text( - 'View #${window.view.viewId}\n' - 'Parent View: ${window.parent?.view.viewId}\n' - 'Logical Size: ${window.size.width}\u00D7${window.size.height}\n' - 'DPR: ${MediaQuery.of(context).devicePixelRatio}', - textAlign: TextAlign.center, - ), - ], - ), - ), - ); - - final List childViews = window.children.map((childWindow) { - return View( - view: childWindow.view, - child: WindowContext( - window: childWindow, - child: childWindow.builder(context), - ), - ); - }).toList(); - - return ViewAnchor(view: ViewCollection(views: childViews), child: widget); - } -} diff --git a/examples/multi_window_ref_app/lib/app/regular_window_content.dart b/examples/multi_window_ref_app/lib/app/regular_window_content.dart new file mode 100644 index 0000000000000..493206a27ddc6 --- /dev/null +++ b/examples/multi_window_ref_app/lib/app/regular_window_content.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:multi_window_ref_app/app/window_metadata_content.dart'; + +class RegularWindowContent extends StatefulWidget { + const RegularWindowContent({super.key, required this.window}); + + final RegularWindowMetadata window; + + @override + State createState() => _RegularWindowContentState(); +} + +class _RegularWindowContentState extends State { + List children = []; + + @override + Widget build(BuildContext context) { + final child = Scaffold( + appBar: AppBar(title: Text('${widget.window.type}')), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () async { + final WindowMetadata next = + await createRegular(size: const Size(400, 300)); + setState(() { + children = [...children, next]; + }); + }, + child: const Text('Create Regular Window'), + ), + const SizedBox(height: 20), + Text( + 'View #${widget.window.view.viewId}\n' + 'Parent View: ${widget.window.parentViewId}\n' + 'Logical Size: ${widget.window.size.width}\u00D7${widget.window.size.height}\n' + 'DPR: ${MediaQuery.of(context).devicePixelRatio}', + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + + final List childViews = children.map((WindowMetadata childWindow) { + return WindowMetadataContent(childWindow: childWindow); + }).toList(); + + return ViewAnchor(view: ViewCollection(views: childViews), child: child); + } +} diff --git a/examples/multi_window_ref_app/lib/app/window_metadata_content.dart b/examples/multi_window_ref_app/lib/app/window_metadata_content.dart new file mode 100644 index 0000000000000..73d7458e2f30e --- /dev/null +++ b/examples/multi_window_ref_app/lib/app/window_metadata_content.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:multi_window_ref_app/app/regular_window_content.dart'; + +class WindowMetadataContent extends StatelessWidget { + WindowMetadataContent({required this.childWindow}); + + WindowMetadata childWindow; + + @override + Widget build(BuildContext context) { + Widget child; + switch (childWindow.type) { + case WindowArchetype.regular: + child = + RegularWindowContent(window: childWindow as RegularWindowMetadata); + break; + default: + throw UnimplementedError( + "The provided window type does not have an implementation"); + } + + return View( + view: childWindow.view, + child: WindowContext( + viewId: childWindow.view.viewId, + child: child, + ), + ); + } +} diff --git a/examples/multi_window_ref_app/lib/app/window_settings_dialog.dart b/examples/multi_window_ref_app/lib/app/window_settings_dialog.dart index a2ee837be705c..0b573e413f186 100644 --- a/examples/multi_window_ref_app/lib/app/window_settings_dialog.dart +++ b/examples/multi_window_ref_app/lib/app/window_settings_dialog.dart @@ -2,11 +2,7 @@ import 'package:flutter/material.dart'; import 'package:multi_window_ref_app/app/window_settings.dart'; Future windowSettingsDialog( - BuildContext context, - WindowSettings settings, - void Function(Window) onWindowOpened, - void Function(Window) onWindowClosed -) async { + BuildContext context, WindowSettings settings) async { return await showDialog( barrierDismissible: true, context: context, @@ -306,7 +302,8 @@ Future windowSettingsDialog( alignment: Alignment.centerLeft, child: CheckboxListTile( title: const Text('Anchor to Window'), - subtitle: const Text("Use the parent's window frame as the anchor rectangle"), + subtitle: const Text( + "Use the parent's window frame as the anchor rectangle"), contentPadding: EdgeInsets.zero, value: anchorToWindow, onChanged: (bool? value) { @@ -325,7 +322,10 @@ Future windowSettingsDialog( style: TextStyle( color: anchorToWindow ? Theme.of(context).disabledColor - : Theme.of(context).textTheme.bodyMedium?.color, + : Theme.of(context) + .textTheme + .bodyMedium + ?.color, ), ), ), diff --git a/examples/multi_window_ref_app/lib/main.dart b/examples/multi_window_ref_app/lib/main.dart index bff2d5b87e619..6fd044eef6d86 100644 --- a/examples/multi_window_ref_app/lib/main.dart +++ b/examples/multi_window_ref_app/lib/main.dart @@ -1,14 +1,10 @@ import 'package:flutter/material.dart'; - import 'app/main_window.dart'; void main() { - runWidget(MultiWindowApp(initialWindows: [ - (BuildContext context) => createRegular( - context: context, - size: const Size(800, 600), - builder: (context) { - return const MaterialApp(home: MainWindow()); - }) + runWidget(WindowingApp(children: [ + RegularWindow( + preferredSize: const Size(800, 600), + child: const MaterialApp(home: MainWindow())) ])); } diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index 0af8843923fe8..fc0642a174176 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -42,6 +42,11 @@ class RegularWindowController with ChangeNotifier { /// The current size of the window. This may differ from the requested size. Size get size => _size; + late int? _parentViewId; + + /// The ID of the parent in which this rendered, if any. + int? get parentViewId => _parentViewId; + /// Modifies this window with the provided properties. Future modify({Size? size}) { throw UnimplementedError(); @@ -90,6 +95,10 @@ class _RegularWindowState extends State { if (properties.size != null) { widget.controller!._size = properties.size!; } + + if (properties.parentViewId != null) { + widget.controller!._parentViewId = properties.parentViewId; + } }, onDestroyed: widget.onDestroyed); windowingAppContext!.windowingApp._registerListener(_listener!); @@ -144,11 +153,22 @@ class WindowContext extends InheritedWidget { } } -class RegularWindowMetadata { - RegularWindowMetadata({required this.view, required this.size}); +abstract class WindowMetadata { + WindowMetadata({required this.view, required this.size, this.parentViewId}); final FlutterView view; final Size size; + final int? parentViewId; + + WindowArchetype get type; +} + +class RegularWindowMetadata extends WindowMetadata { + RegularWindowMetadata( + {required super.view, required super.size, super.parentViewId}); + + @override + WindowArchetype get type => WindowArchetype.regular; } class _WindowMetadata { From 120592756ece36ea62ce44f4165989c161d04c00 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Mon, 25 Nov 2024 16:38:03 -0500 Subject: [PATCH 03/23] wip: able to create windows in the reference application again --- .../lib/app/main_window.dart | 33 +++++++++--- packages/flutter/lib/src/widgets/window.dart | 53 +++++++++++-------- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/examples/multi_window_ref_app/lib/app/main_window.dart b/examples/multi_window_ref_app/lib/app/main_window.dart index 2f1f96c6e82c0..42b84ef8f5234 100644 --- a/examples/multi_window_ref_app/lib/app/main_window.dart +++ b/examples/multi_window_ref_app/lib/app/main_window.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:multi_window_ref_app/app/window_metadata_content.dart'; -import 'regular_window_content.dart'; import 'window_settings.dart'; import 'window_settings_dialog.dart'; @@ -74,7 +73,8 @@ class _MainWindowState extends State { listenable: _windowManagerModel, builder: (BuildContext context, Widget? child) { return _WindowCreatorCard( - selectedWindow: _windowManagerModel.selected); + selectedWindow: _windowManagerModel.selected, + windowManagerModel: _windowManagerModel); }) ], ), @@ -83,12 +83,29 @@ class _MainWindowState extends State { ), ); + return ViewAnchor( + view: ListenableBuilder( + listenable: _windowManagerModel, + builder: (BuildContext context, Widget? widget) { + return _ViewCollection(windowManagerModel: _windowManagerModel); + }), + child: widget); + } +} + +class _ViewCollection extends StatelessWidget { + _ViewCollection({required this.windowManagerModel}); + + _WindowManagerModel windowManagerModel; + + @override + Widget build(BuildContext context) { final List childViews = []; - for (final WindowMetadata childWindow in _windowManagerModel.windows) { + for (final WindowMetadata childWindow in windowManagerModel.windows) { childViews.add(WindowMetadataContent(childWindow: childWindow)); } - return ViewAnchor(view: ViewCollection(views: childViews), child: widget); + return ViewCollection(views: childViews); } } @@ -181,9 +198,11 @@ class _ActiveWindowsTable extends StatelessWidget { } class _WindowCreatorCard extends StatefulWidget { - const _WindowCreatorCard({required this.selectedWindow}); + const _WindowCreatorCard( + {required this.selectedWindow, required this.windowManagerModel}); final WindowMetadata? selectedWindow; + final _WindowManagerModel windowManagerModel; @override State createState() => _WindowCreatorCardState(); @@ -216,7 +235,9 @@ class _WindowCreatorCardState extends State<_WindowCreatorCard> { children: [ OutlinedButton( onPressed: () async { - await createRegular(size: _settings.regularSize); + final WindowMetadata metadata = + await createRegular(size: _settings.regularSize); + widget.windowManagerModel.add(metadata); }, child: const Text('Regular'), ), diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index fc0642a174176..daeeb68e2af5a 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -5,6 +5,7 @@ import 'dart:ui' show FlutterView; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; /// Defines the type of a [Window] @@ -81,27 +82,34 @@ class _RegularWindowState extends State { @override void initState() { super.initState(); - final _WindowingAppContext? windowingAppContext = - _WindowingAppContext.of(context); widget._future.then((RegularWindowMetadata metadata) async { - assert(windowingAppContext != null); - _listener = _WindowListener( - viewId: metadata.view.viewId, - onChanged: (_WindowChangeProperties properties) { - if (widget.controller == null) { - return; - } - - if (properties.size != null) { - widget.controller!._size = properties.size!; - } - - if (properties.parentViewId != null) { - widget.controller!._parentViewId = properties.parentViewId; - } - }, - onDestroyed: widget.onDestroyed); - windowingAppContext!.windowingApp._registerListener(_listener!); + if (widget.controller != null) { + widget.controller!._parentViewId = metadata.parentViewId; + widget.controller!._size = metadata.size; + } + + SchedulerBinding.instance.addPostFrameCallback((_) async { + final _WindowingAppContext? windowingAppContext = + _WindowingAppContext.of(context); + assert(windowingAppContext != null); + _listener = _WindowListener( + viewId: metadata.view.viewId, + onChanged: (_WindowChangeProperties properties) { + if (widget.controller == null) { + return; + } + + if (properties.size != null) { + widget.controller!._size = properties.size!; + } + + if (properties.parentViewId != null) { + widget.controller!._parentViewId = properties.parentViewId; + } + }, + onDestroyed: widget.onDestroyed); + windowingAppContext!.windowingApp._registerListener(_listener!); + }); }); } @@ -123,7 +131,8 @@ class _RegularWindowState extends State { builder: (BuildContext context, AsyncSnapshot metadata) { if (!metadata.hasData) { - return Container(); + final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); + return binding.wrapWithDefaultView(Container()); } return View( @@ -202,6 +211,7 @@ Future createRegular({required Size size}) async { Future<_WindowMetadata> _createWindow( {required Future> Function(MethodChannel channel) viewBuilder}) async { + WidgetsFlutterBinding.ensureInitialized(); final Map creationData = await viewBuilder(SystemChannels.windowing); final int viewId = creationData['viewId']! as int; @@ -260,6 +270,7 @@ class _WindowListener { /// The current [Window] can be looked up with [WindowContext.of]. class WindowingApp extends StatelessWidget { WindowingApp({super.key, required this.children}) { + WidgetsFlutterBinding.ensureInitialized(); SystemChannels.windowing.setMethodCallHandler(_methodCallHandler); } From 1866aba84c313872fd046553f18a9a5a53c93626 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Wed, 4 Dec 2024 16:13:58 -0500 Subject: [PATCH 04/23] Selection and rerenders are functioning appropriately --- .../lib/app/main_window.dart | 188 ++++++++++-------- .../lib/app/regular_window_content.dart | 33 +-- .../lib/app/window_metadata_content.dart | 23 +-- packages/flutter/lib/src/widgets/window.dart | 78 +++++--- 4 files changed, 177 insertions(+), 145 deletions(-) diff --git a/examples/multi_window_ref_app/lib/app/main_window.dart b/examples/multi_window_ref_app/lib/app/main_window.dart index 42b84ef8f5234..ca93869d6e680 100644 --- a/examples/multi_window_ref_app/lib/app/main_window.dart +++ b/examples/multi_window_ref_app/lib/app/main_window.dart @@ -5,16 +5,16 @@ import 'window_settings.dart'; import 'window_settings_dialog.dart'; class _WindowManagerModel extends ChangeNotifier { - final List _windows = []; - List get windows => _windows; + final List _windows = []; + List get windows => _windows; int? _selectedViewId; - WindowMetadata? get selected { + WindowController? get selected { if (_selectedViewId == null) { return null; } - for (final WindowMetadata window in _windows) { - if (window.view.viewId == _selectedViewId) { + for (final WindowController window in _windows) { + if (window.view?.viewId == _selectedViewId) { return window; } } @@ -22,18 +22,19 @@ class _WindowManagerModel extends ChangeNotifier { return null; } - void add(WindowMetadata window) { + void add(WindowController window) { _windows.add(window); notifyListeners(); } - void remove(WindowMetadata window) { + void remove(WindowController window) { _windows.remove(window); notifyListeners(); } void select(int? viewId) { _selectedViewId = viewId; + notifyListeners(); } } @@ -101,8 +102,8 @@ class _ViewCollection extends StatelessWidget { @override Widget build(BuildContext context) { final List childViews = []; - for (final WindowMetadata childWindow in windowManagerModel.windows) { - childViews.add(WindowMetadataContent(childWindow: childWindow)); + for (final WindowController childWindow in windowManagerModel.windows) { + childViews.add(WindowMetadataContent(controller: childWindow)); } return ViewCollection(views: childViews); @@ -116,84 +117,97 @@ class _ActiveWindowsTable extends StatelessWidget { @override Widget build(BuildContext context) { - return DataTable( - showBottomBorder: true, - onSelectAll: (selected) { - windowManagerModel.select(null); - }, - columns: const [ - DataColumn( - label: SizedBox( - width: 20, - child: Text( - 'ID', - style: TextStyle( - fontSize: 16, + return ListenableBuilder( + listenable: windowManagerModel, + builder: (BuildContext context, Widget? widget) { + return DataTable( + showBottomBorder: true, + onSelectAll: (selected) { + windowManagerModel.select(null); + }, + columns: const [ + DataColumn( + label: SizedBox( + width: 20, + child: Text( + 'ID', + style: TextStyle( + fontSize: 16, + ), + ), + ), ), - ), - ), - ), - DataColumn( - label: SizedBox( - width: 120, - child: Text( - 'Type', - style: TextStyle( - fontSize: 16, + DataColumn( + label: SizedBox( + width: 120, + child: Text( + 'Type', + style: TextStyle( + fontSize: 16, + ), + ), + ), ), - ), - ), - ), - DataColumn( - label: SizedBox( - width: 20, - child: Text(''), - ), - numeric: true), - ], - rows: windowManagerModel.windows - .asMap() - .entries - .map((indexedEntry) { - final index = indexedEntry.key; - final WindowMetadata entry = indexedEntry.value; - final window = entry; - final viewId = window.view.viewId; - final archetype = window.type; - final isSelected = viewId == windowManagerModel._selectedViewId; - - return DataRow( - color: WidgetStateColor.resolveWith((states) { - if (states.contains(WidgetState.selected)) { - return Theme.of(context).colorScheme.primary.withOpacity(0.08); - } - return Colors.transparent; - }), - selected: isSelected, - onSelectChanged: (selected) { - if (selected != null) { - windowManagerModel.select(selected ? viewId : null); - } - }, - cells: [ - DataCell( - Text('$viewId'), - ), - DataCell( - Text(archetype.toString().replaceFirst('WindowArchetype.', '')), - ), - DataCell( - IconButton( - icon: const Icon(Icons.delete_outlined), - onPressed: () { - destroyWindow(viewId); + DataColumn( + label: SizedBox( + width: 20, + child: Text(''), + ), + numeric: true), + ], + rows: windowManagerModel.windows + .map((WindowController controller) { + return DataRow( + color: WidgetStateColor.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return Theme.of(context) + .colorScheme + .primary + .withOpacity(0.08); + } + return Colors.transparent; + }), + selected: controller.view?.viewId == + windowManagerModel._selectedViewId, + onSelectChanged: (selected) { + if (selected != null) { + windowManagerModel + .select(selected ? controller.view?.viewId : null); + } }, - ), - ), - ], - ); - }).toList(), - ); + cells: [ + DataCell( + ListenableBuilder( + listenable: controller, + builder: (BuildContext context, Widget? _) => Text( + controller.view != null + ? '${controller.view?.viewId}' + : 'Loading...')), + ), + DataCell( + ListenableBuilder( + listenable: controller, + builder: (BuildContext context, Widget? _) => Text( + controller.type + .toString() + .replaceFirst('WindowArchetype.', ''))), + ), + DataCell( + ListenableBuilder( + listenable: controller, + builder: (BuildContext context, Widget? _) => + IconButton( + icon: const Icon(Icons.delete_outlined), + onPressed: () async { + await controller.destroy(); + }, + )), + ), + ], + ); + }).toList(), + ); + }); } } @@ -201,7 +215,7 @@ class _WindowCreatorCard extends StatefulWidget { const _WindowCreatorCard( {required this.selectedWindow, required this.windowManagerModel}); - final WindowMetadata? selectedWindow; + final WindowController? selectedWindow; final _WindowManagerModel windowManagerModel; @override @@ -235,9 +249,7 @@ class _WindowCreatorCardState extends State<_WindowCreatorCard> { children: [ OutlinedButton( onPressed: () async { - final WindowMetadata metadata = - await createRegular(size: _settings.regularSize); - widget.windowManagerModel.add(metadata); + widget.windowManagerModel.add(RegularWindowController()); }, child: const Text('Regular'), ), diff --git a/examples/multi_window_ref_app/lib/app/regular_window_content.dart b/examples/multi_window_ref_app/lib/app/regular_window_content.dart index 493206a27ddc6..f1ecb295b7e31 100644 --- a/examples/multi_window_ref_app/lib/app/regular_window_content.dart +++ b/examples/multi_window_ref_app/lib/app/regular_window_content.dart @@ -4,14 +4,14 @@ import 'package:multi_window_ref_app/app/window_metadata_content.dart'; class RegularWindowContent extends StatefulWidget { const RegularWindowContent({super.key, required this.window}); - final RegularWindowMetadata window; + final RegularWindowController window; @override State createState() => _RegularWindowContentState(); } class _RegularWindowContentState extends State { - List children = []; + List children = []; @override Widget build(BuildContext context) { @@ -23,30 +23,33 @@ class _RegularWindowContentState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ ElevatedButton( - onPressed: () async { - final WindowMetadata next = - await createRegular(size: const Size(400, 300)); + onPressed: () { setState(() { - children = [...children, next]; + children = [...children, RegularWindowController()]; }); }, child: const Text('Create Regular Window'), ), const SizedBox(height: 20), - Text( - 'View #${widget.window.view.viewId}\n' - 'Parent View: ${widget.window.parentViewId}\n' - 'Logical Size: ${widget.window.size.width}\u00D7${widget.window.size.height}\n' - 'DPR: ${MediaQuery.of(context).devicePixelRatio}', - textAlign: TextAlign.center, - ), + ListenableBuilder( + listenable: widget.window, + builder: (BuildContext context, Widget? _) { + return Text( + 'View #${widget.window.view?.viewId ?? "Unknown"}\n' + 'Parent View: ${widget.window.parentViewId}\n' + 'Logical Size: ${widget.window.size?.width ?? "?"}\u00D7${widget.window.size?.height ?? "?"}\n' + 'DPR: ${MediaQuery.of(context).devicePixelRatio}', + textAlign: TextAlign.center, + ); + }) ], ), ), ); - final List childViews = children.map((WindowMetadata childWindow) { - return WindowMetadataContent(childWindow: childWindow); + final List childViews = + children.map((WindowController childWindow) { + return WindowMetadataContent(controller: childWindow); }).toList(); return ViewAnchor(view: ViewCollection(views: childViews), child: child); diff --git a/examples/multi_window_ref_app/lib/app/window_metadata_content.dart b/examples/multi_window_ref_app/lib/app/window_metadata_content.dart index 73d7458e2f30e..9fe41a24830e4 100644 --- a/examples/multi_window_ref_app/lib/app/window_metadata_content.dart +++ b/examples/multi_window_ref_app/lib/app/window_metadata_content.dart @@ -2,29 +2,22 @@ import 'package:flutter/material.dart'; import 'package:multi_window_ref_app/app/regular_window_content.dart'; class WindowMetadataContent extends StatelessWidget { - WindowMetadataContent({required this.childWindow}); + WindowMetadataContent({required this.controller}); - WindowMetadata childWindow; + WindowController controller; @override Widget build(BuildContext context) { - Widget child; - switch (childWindow.type) { + switch (controller.type) { case WindowArchetype.regular: - child = - RegularWindowContent(window: childWindow as RegularWindowMetadata); - break; + return RegularWindow( + preferredSize: Size(400, 400), + controller: controller as RegularWindowController, + child: RegularWindowContent( + window: controller as RegularWindowController)); default: throw UnimplementedError( "The provided window type does not have an implementation"); } - - return View( - view: childWindow.view, - child: WindowContext( - viewId: childWindow.view.viewId, - child: child, - ), - ); } } diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index daeeb68e2af5a..dcb45dbf0d87b 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -32,30 +32,57 @@ enum WindowArchetype { /// Controller used with the [RegularWindow] widget. This controller /// provides access to modify and destroy the window, in addition to /// listening to changes on the window. -class RegularWindowController with ChangeNotifier { - late int _viewId; +abstract class WindowController with ChangeNotifier { + FlutterView? _view; /// The ID of the view used for this window, which is unique to each window. - int get viewId => _viewId; + FlutterView? get view => _view; + set view(FlutterView? value) { + _view = value; + notifyListeners(); + } - late Size _size; + Size? _size; /// The current size of the window. This may differ from the requested size. - Size get size => _size; + Size? get size => _size; + set size(Size? value) { + _size = value; + notifyListeners(); + } - late int? _parentViewId; + int? _parentViewId; /// The ID of the parent in which this rendered, if any. int? get parentViewId => _parentViewId; + set parentViewId(int? value) { + _parentViewId = value; + notifyListeners(); + } + + /// The archetype of the window. + WindowArchetype get type; /// Modifies this window with the provided properties. - Future modify({Size? size}) { - throw UnimplementedError(); - } + Future modify({Size? size}); /// Destroys this window. - Future destroy() { - return destroyWindow(viewId); + Future destroy() async { + if (view == null) { + return; + } + + return destroyWindow(view!.viewId); + } +} + +class RegularWindowController extends WindowController { + @override + WindowArchetype get type => WindowArchetype.regular; + + @override + Future modify({Size? size}) { + throw UnimplementedError(); } } @@ -65,12 +92,12 @@ class RegularWindow extends StatefulWidget { this.onDestroyed, this.controller, required this.child}) - : _future = createRegular(size: preferredSize); + : _preferredSize = preferredSize; final RegularWindowController? controller; void Function()? onDestroyed; - final Future _future; final Widget child; + final Size _preferredSize; @override State createState() => _RegularWindowState(); @@ -78,14 +105,17 @@ class RegularWindow extends StatefulWidget { class _RegularWindowState extends State { _WindowListener? _listener; + Future? _future; @override void initState() { super.initState(); - widget._future.then((RegularWindowMetadata metadata) async { + _future = createRegular(size: widget._preferredSize); + _future!.then((RegularWindowMetadata metadata) async { if (widget.controller != null) { - widget.controller!._parentViewId = metadata.parentViewId; - widget.controller!._size = metadata.size; + widget.controller!.view = metadata.view; + widget.controller!.parentViewId = metadata.parentViewId; + widget.controller!.size = metadata.size; } SchedulerBinding.instance.addPostFrameCallback((_) async { @@ -100,11 +130,11 @@ class _RegularWindowState extends State { } if (properties.size != null) { - widget.controller!._size = properties.size!; + widget.controller!.size = properties.size; } if (properties.parentViewId != null) { - widget.controller!._parentViewId = properties.parentViewId; + widget.controller!.parentViewId = properties.parentViewId; } }, onDestroyed: widget.onDestroyed); @@ -127,12 +157,11 @@ class _RegularWindowState extends State { @override Widget build(BuildContext context) { return FutureBuilder( - future: widget._future, + future: _future, builder: (BuildContext context, AsyncSnapshot metadata) { if (!metadata.hasData) { - final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); - return binding.wrapWithDefaultView(Container()); + return const ViewCollection(views: []); } return View( @@ -162,22 +191,17 @@ class WindowContext extends InheritedWidget { } } -abstract class WindowMetadata { +class WindowMetadata { WindowMetadata({required this.view, required this.size, this.parentViewId}); final FlutterView view; final Size size; final int? parentViewId; - - WindowArchetype get type; } class RegularWindowMetadata extends WindowMetadata { RegularWindowMetadata( {required super.view, required super.size, super.parentViewId}); - - @override - WindowArchetype get type => WindowArchetype.regular; } class _WindowMetadata { From ab185bbe9f201ac031cae4d814fb5036d2d754b2 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Wed, 4 Dec 2024 16:45:34 -0500 Subject: [PATCH 05/23] Able to remove an application, albeit with some bugs + refactored how settings are donw --- .../lib/app/main_window.dart | 53 +- .../lib/app/regular_window_content.dart | 22 +- .../lib/app/window_controller_render.dart | 31 ++ .../lib/app/window_metadata_content.dart | 23 - .../lib/app/window_settings.dart | 28 +- .../lib/app/window_settings_dialog.dart | 492 ++---------------- packages/flutter/lib/src/widgets/window.dart | 10 +- 7 files changed, 146 insertions(+), 513 deletions(-) create mode 100644 examples/multi_window_ref_app/lib/app/window_controller_render.dart delete mode 100644 examples/multi_window_ref_app/lib/app/window_metadata_content.dart diff --git a/examples/multi_window_ref_app/lib/app/main_window.dart b/examples/multi_window_ref_app/lib/app/main_window.dart index ca93869d6e680..3fd863b8b1b00 100644 --- a/examples/multi_window_ref_app/lib/app/main_window.dart +++ b/examples/multi_window_ref_app/lib/app/main_window.dart @@ -1,5 +1,7 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; -import 'package:multi_window_ref_app/app/window_metadata_content.dart'; +import 'package:multi_window_ref_app/app/window_controller_render.dart'; import 'window_settings.dart'; import 'window_settings_dialog.dart'; @@ -47,6 +49,7 @@ class MainWindow extends StatefulWidget { class _MainWindowState extends State { final _WindowManagerModel _windowManagerModel = _WindowManagerModel(); + final WindowSettings _settings = WindowSettings(); @override Widget build(BuildContext context) { @@ -75,7 +78,8 @@ class _MainWindowState extends State { builder: (BuildContext context, Widget? child) { return _WindowCreatorCard( selectedWindow: _windowManagerModel.selected, - windowManagerModel: _windowManagerModel); + windowManagerModel: _windowManagerModel, + windowSettings: _settings); }) ], ), @@ -88,22 +92,31 @@ class _MainWindowState extends State { view: ListenableBuilder( listenable: _windowManagerModel, builder: (BuildContext context, Widget? widget) { - return _ViewCollection(windowManagerModel: _windowManagerModel); + return _ViewCollection( + windowManagerModel: _windowManagerModel, + windowSettings: _settings); }), child: widget); } } class _ViewCollection extends StatelessWidget { - _ViewCollection({required this.windowManagerModel}); + _ViewCollection( + {required this.windowManagerModel, required this.windowSettings}); - _WindowManagerModel windowManagerModel; + final _WindowManagerModel windowManagerModel; + final WindowSettings windowSettings; @override Widget build(BuildContext context) { final List childViews = []; - for (final WindowController childWindow in windowManagerModel.windows) { - childViews.add(WindowMetadataContent(controller: childWindow)); + for (final WindowController controller in windowManagerModel.windows) { + childViews.add(WindowControllerRender( + controller: controller, + onDestroyed: () { + windowManagerModel.remove(controller); + }, + windowSettings: windowSettings)); } return ViewCollection(views: childViews); @@ -211,19 +224,15 @@ class _ActiveWindowsTable extends StatelessWidget { } } -class _WindowCreatorCard extends StatefulWidget { - const _WindowCreatorCard( - {required this.selectedWindow, required this.windowManagerModel}); +class _WindowCreatorCard extends StatelessWidget { + _WindowCreatorCard( + {required this.selectedWindow, + required this.windowManagerModel, + required this.windowSettings}); final WindowController? selectedWindow; final _WindowManagerModel windowManagerModel; - - @override - State createState() => _WindowCreatorCardState(); -} - -class _WindowCreatorCardState extends State<_WindowCreatorCard> { - WindowSettings _settings = WindowSettings(); + final WindowSettings windowSettings; @override Widget build(BuildContext context) { @@ -249,7 +258,7 @@ class _WindowCreatorCardState extends State<_WindowCreatorCard> { children: [ OutlinedButton( onPressed: () async { - widget.windowManagerModel.add(RegularWindowController()); + windowManagerModel.add(RegularWindowController()); }, child: const Text('Regular'), ), @@ -259,13 +268,7 @@ class _WindowCreatorCardState extends State<_WindowCreatorCard> { child: TextButton( child: const Text('SETTINGS'), onPressed: () { - windowSettingsDialog(context, _settings).then( - (WindowSettings? settings) { - if (settings != null) { - _settings = settings; - } - }, - ); + windowSettingsDialog(context, windowSettings); }, ), ), diff --git a/examples/multi_window_ref_app/lib/app/regular_window_content.dart b/examples/multi_window_ref_app/lib/app/regular_window_content.dart index f1ecb295b7e31..e6f414e89a3fb 100644 --- a/examples/multi_window_ref_app/lib/app/regular_window_content.dart +++ b/examples/multi_window_ref_app/lib/app/regular_window_content.dart @@ -1,17 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:multi_window_ref_app/app/window_metadata_content.dart'; +import 'package:multi_window_ref_app/app/window_controller_render.dart'; +import 'package:multi_window_ref_app/app/window_settings.dart'; class RegularWindowContent extends StatefulWidget { - const RegularWindowContent({super.key, required this.window}); + const RegularWindowContent({super.key, required this.window, required this.windowSettings}); final RegularWindowController window; + final WindowSettings windowSettings; @override State createState() => _RegularWindowContentState(); } class _RegularWindowContentState extends State { - List children = []; + List childControllers = []; @override Widget build(BuildContext context) { @@ -25,7 +27,10 @@ class _RegularWindowContentState extends State { ElevatedButton( onPressed: () { setState(() { - children = [...children, RegularWindowController()]; + childControllers = [ + ...childControllers, + RegularWindowController() + ]; }); }, child: const Text('Create Regular Window'), @@ -48,8 +53,13 @@ class _RegularWindowContentState extends State { ); final List childViews = - children.map((WindowController childWindow) { - return WindowMetadataContent(controller: childWindow); + childControllers.map((WindowController controller) { + return WindowControllerRender( + controller: controller, + windowSettings: widget.windowSettings, + onDestroyed: () { + childControllers.remove(controller); + }); }).toList(); return ViewAnchor(view: ViewCollection(views: childViews), child: child); diff --git a/examples/multi_window_ref_app/lib/app/window_controller_render.dart b/examples/multi_window_ref_app/lib/app/window_controller_render.dart new file mode 100644 index 0000000000000..fd00ec5f9a419 --- /dev/null +++ b/examples/multi_window_ref_app/lib/app/window_controller_render.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:multi_window_ref_app/app/regular_window_content.dart'; +import 'package:multi_window_ref_app/app/window_settings.dart'; + +class WindowControllerRender extends StatelessWidget { + WindowControllerRender( + {required this.controller, + required this.onDestroyed, + required this.windowSettings}); + + final WindowController controller; + final VoidCallback onDestroyed; + final WindowSettings windowSettings; + + @override + Widget build(BuildContext context) { + switch (controller.type) { + case WindowArchetype.regular: + return RegularWindow( + onDestroyed: onDestroyed, + preferredSize: windowSettings.regularSize, + controller: controller as RegularWindowController, + child: RegularWindowContent( + window: controller as RegularWindowController, + windowSettings: windowSettings)); + default: + throw UnimplementedError( + "The provided window type does not have an implementation"); + } + } +} diff --git a/examples/multi_window_ref_app/lib/app/window_metadata_content.dart b/examples/multi_window_ref_app/lib/app/window_metadata_content.dart deleted file mode 100644 index 9fe41a24830e4..0000000000000 --- a/examples/multi_window_ref_app/lib/app/window_metadata_content.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:multi_window_ref_app/app/regular_window_content.dart'; - -class WindowMetadataContent extends StatelessWidget { - WindowMetadataContent({required this.controller}); - - WindowController controller; - - @override - Widget build(BuildContext context) { - switch (controller.type) { - case WindowArchetype.regular: - return RegularWindow( - preferredSize: Size(400, 400), - controller: controller as RegularWindowController, - child: RegularWindowContent( - window: controller as RegularWindowController)); - default: - throw UnimplementedError( - "The provided window type does not have an implementation"); - } - } -} diff --git a/examples/multi_window_ref_app/lib/app/window_settings.dart b/examples/multi_window_ref_app/lib/app/window_settings.dart index f93ab4e624db8..8cb0d5ba13498 100644 --- a/examples/multi_window_ref_app/lib/app/window_settings.dart +++ b/examples/multi_window_ref_app/lib/app/window_settings.dart @@ -1,21 +1,13 @@ import 'package:flutter/material.dart'; -class WindowSettings { - WindowSettings( - {this.regularSize = const Size(400, 300), - this.floatingRegularSize = const Size(300, 300), - this.dialogSize = const Size(300, 250), - this.satelliteSize = const Size(150, 300), - this.popupSize = const Size(200, 200), - this.tipSize = const Size(140, 140), - this.anchorToWindow = false, - this.anchorRect = const Rect.fromLTWH(0, 0, 1000, 1000)}); - final Size regularSize; - final Size floatingRegularSize; - final Size dialogSize; - final Size satelliteSize; - final Size popupSize; - final Size tipSize; - final Rect anchorRect; - final bool anchorToWindow; +class WindowSettings extends ChangeNotifier { + WindowSettings({Size regularSize = const Size(400, 300)}) + : _regularSize = regularSize; + + Size _regularSize; + Size get regularSize => _regularSize; + set regularSize(Size value) { + _regularSize = value; + notifyListeners(); + } } diff --git a/examples/multi_window_ref_app/lib/app/window_settings_dialog.dart b/examples/multi_window_ref_app/lib/app/window_settings_dialog.dart index 0b573e413f186..7c0da255ccd05 100644 --- a/examples/multi_window_ref_app/lib/app/window_settings_dialog.dart +++ b/examples/multi_window_ref_app/lib/app/window_settings_dialog.dart @@ -1,380 +1,45 @@ import 'package:flutter/material.dart'; import 'package:multi_window_ref_app/app/window_settings.dart'; -Future windowSettingsDialog( +Future windowSettingsDialog( BuildContext context, WindowSettings settings) async { return await showDialog( barrierDismissible: true, context: context, builder: (BuildContext ctx) { - Size regularSize = settings.regularSize; - Size floatingRegularSize = settings.floatingRegularSize; - Size dialogSize = settings.dialogSize; - Size satelliteSize = settings.satelliteSize; - Size popupSize = settings.popupSize; - Size tipSize = settings.tipSize; - Rect anchorRect = settings.anchorRect; - bool anchorToWindow = settings.anchorToWindow; - - return StatefulBuilder( - builder: (BuildContext ctx, StateSetter setState) { - return SimpleDialog( - contentPadding: const EdgeInsets.all(4), - titlePadding: const EdgeInsets.fromLTRB(24, 10, 24, 0), - title: const Center( - child: Text('Window Settings'), - ), - children: [ - SizedBox( - width: 600, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - children: [ - Expanded( - child: ListTile( - title: const Text('Regular'), - subtitle: Row( - children: [ - Expanded( - child: TextFormField( - initialValue: regularSize.width.toString(), - decoration: const InputDecoration( - labelText: 'Initial width', - ), - onChanged: (String value) => setState( - () => regularSize = Size( - double.tryParse(value) ?? 0, - regularSize.height), - ), - ), - ), - const SizedBox( - width: 20, - ), - Expanded( - child: TextFormField( - initialValue: regularSize.height.toString(), - decoration: const InputDecoration( - labelText: 'Initial height', - ), - onChanged: (String value) => setState( - () => regularSize = Size( - regularSize.width, - double.tryParse(value) ?? 0), - ), - ), - ), - ], - ), - ), - ), - const SizedBox( - width: 10, - ), - Expanded( - child: ListTile( - title: const Text('Floating Regular'), - subtitle: Row( - children: [ - Expanded( - child: TextFormField( - initialValue: - floatingRegularSize.width.toString(), - decoration: const InputDecoration( - labelText: 'Initial width', - ), - onChanged: (String value) => setState( - () => floatingRegularSize = Size( - double.tryParse(value) ?? 0, - floatingRegularSize.height), - ), - ), - ), - const SizedBox( - width: 20, - ), - Expanded( - child: TextFormField( - initialValue: - floatingRegularSize.height.toString(), - decoration: const InputDecoration( - labelText: 'Initial height', - ), - onChanged: (String value) => setState( - () => floatingRegularSize = Size( - regularSize.width, - double.tryParse(value) ?? 0), - ), - ), - ), - ], - ), - ), - ), - ], - ), - const SizedBox( - height: 2, - ), - Row( - children: [ - Expanded( - child: ListTile( - title: const Text('Dialog'), - subtitle: Row( - children: [ - Expanded( - child: TextFormField( - initialValue: dialogSize.width.toString(), - decoration: const InputDecoration( - labelText: 'Initial width', - ), - onChanged: (String value) => setState( - () => dialogSize = Size( - double.tryParse(value) ?? 0, - dialogSize.height), - ), - ), - ), - const SizedBox( - width: 20, - ), - Expanded( - child: TextFormField( - initialValue: dialogSize.height.toString(), - decoration: const InputDecoration( - labelText: 'Initial height', - ), - onChanged: (String value) => setState( - () => dialogSize = Size(dialogSize.width, - double.tryParse(value) ?? 0), - ), - ), - ), - ], - ), - ), - ), - const SizedBox( - width: 10, - ), - Expanded( - child: ListTile( - title: const Text('Satellite'), - subtitle: Row( - children: [ - Expanded( - child: TextFormField( - initialValue: - satelliteSize.width.toString(), - decoration: const InputDecoration( - labelText: 'Initial width', - ), - onChanged: (String value) => setState( - () => satelliteSize = Size( - double.tryParse(value) ?? 0, - satelliteSize.height), - ), - ), - ), - const SizedBox( - width: 20, - ), - Expanded( - child: TextFormField( - initialValue: - satelliteSize.height.toString(), - decoration: const InputDecoration( - labelText: 'Initial height', - ), - onChanged: (String value) => setState( - () => satelliteSize = Size( - satelliteSize.width, - double.tryParse(value) ?? 0), - ), - ), - ), - ], - ), - ), - ), - ], - ), - const SizedBox( - height: 2, - ), - Row( - children: [ - Expanded( - child: ListTile( - title: const Text('Popup'), - subtitle: Row( - children: [ - Expanded( - child: TextFormField( - initialValue: popupSize.width.toString(), - decoration: const InputDecoration( - labelText: 'Initial width', - ), - onChanged: (String value) => setState( - () => popupSize = Size( - double.tryParse(value) ?? 0, - popupSize.height), - ), - ), - ), - const SizedBox( - width: 20, - ), - Expanded( - child: TextFormField( - initialValue: popupSize.height.toString(), - decoration: const InputDecoration( - labelText: 'Initial height', - ), - onChanged: (String value) => setState( - () => popupSize = Size(popupSize.width, - double.tryParse(value) ?? 0), - ), - ), - ), - ], - ), - ), - ), - const SizedBox( - width: 10, - ), - Expanded( - child: ListTile( - title: const Text('Tip'), - subtitle: Row( - children: [ - Expanded( - child: TextFormField( - initialValue: tipSize.width.toString(), - decoration: const InputDecoration( - labelText: 'Initial width', - ), - onChanged: (String value) => setState( - () => tipSize = Size( - double.tryParse(value) ?? 0, - tipSize.height), - ), - ), - ), - const SizedBox( - width: 20, - ), - Expanded( - child: TextFormField( - initialValue: tipSize.height.toString(), - decoration: const InputDecoration( - labelText: 'Initial height', - ), - onChanged: (String value) => setState( - () => tipSize = Size(tipSize.width, - double.tryParse(value) ?? 0), - ), - ), - ), - ], - ), - ), - ), - ], - ), - const SizedBox( - height: 2, - ), - const Divider(), - Row( + return ListenableBuilder( + listenable: settings, + builder: (BuildContext ctx, Widget? _) { + return SimpleDialog( + contentPadding: const EdgeInsets.all(4), + titlePadding: const EdgeInsets.fromLTRB(24, 10, 24, 0), + title: const Center( + child: Text('Window Settings'), + ), + children: [ + SizedBox( + width: 600, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - Expanded( - child: ListTile( - title: const Text('Anchoring'), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Align( - alignment: Alignment.centerLeft, - child: CheckboxListTile( - title: const Text('Anchor to Window'), - subtitle: const Text( - "Use the parent's window frame as the anchor rectangle"), - contentPadding: EdgeInsets.zero, - value: anchorToWindow, - onChanged: (bool? value) { - setState(() { - anchorToWindow = value ?? false; - }); - }, - controlAffinity: - ListTileControlAffinity.leading, - ), - ), - Align( - alignment: Alignment.centerLeft, - child: Text( - "View Anchor Rectangle (values will be clamped to the size of the parent view)", - style: TextStyle( - color: anchorToWindow - ? Theme.of(context).disabledColor - : Theme.of(context) - .textTheme - .bodyMedium - ?.color, - ), - ), - ), - Row( + Row( + children: [ + Expanded( + child: ListTile( + title: const Text('Regular'), + subtitle: Row( children: [ Expanded( child: TextFormField( - enabled: !anchorToWindow, - initialValue: - anchorRect.left.toString(), - decoration: const InputDecoration( - labelText: 'Left', - ), - onChanged: anchorToWindow - ? null - : (String value) => setState( - () => anchorRect = - Rect.fromLTWH( - double.tryParse( - value) ?? - 0, - anchorRect.top, - anchorRect.width, - anchorRect.height), - ), - ), - ), - const SizedBox( - width: 20, - ), - Expanded( - child: TextFormField( - enabled: !anchorToWindow, - initialValue: anchorRect.top.toString(), + initialValue: settings.regularSize.width + .toString(), decoration: const InputDecoration( - labelText: 'Top', + labelText: 'Initial width', ), - onChanged: anchorToWindow - ? null - : (String value) => setState( - () => anchorRect = - Rect.fromLTWH( - anchorRect.left, - double.tryParse( - value) ?? - 0, - anchorRect.width, - anchorRect.height), - ), + onChanged: (String value) => + settings.regularSize = Size( + double.tryParse(value) ?? 0, + settings.regularSize.height), ), ), const SizedBox( @@ -382,89 +47,44 @@ Future windowSettingsDialog( ), Expanded( child: TextFormField( - enabled: !anchorToWindow, - initialValue: - anchorRect.width.toString(), + initialValue: settings + .regularSize.height + .toString(), decoration: const InputDecoration( - labelText: 'Width', + labelText: 'Initial height', ), - onChanged: anchorToWindow - ? null - : (String value) => setState( - () => anchorRect = - Rect.fromLTWH( - anchorRect.left, - anchorRect.top, - double.tryParse( - value) ?? - 0, - anchorRect.height), - ), - ), - ), - const SizedBox( - width: 20, - ), - Expanded( - child: TextFormField( - enabled: !anchorToWindow, - initialValue: - anchorRect.height.toString(), - decoration: const InputDecoration( - labelText: 'Height', - ), - onChanged: anchorToWindow - ? null - : (String value) => setState( - () => anchorRect = - Rect.fromLTWH( - anchorRect.left, - anchorRect.top, - anchorRect.width, - double.tryParse( - value) ?? - 0), - ), + onChanged: (String value) => + settings.regularSize = Size( + settings.regularSize.width, + double.tryParse(value) ?? 0), ), ), ], ), - ], + ), + ), + const SizedBox( + width: 10, ), - ), + ], ), ], ), - const SizedBox( - height: 2, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: TextButton( + onPressed: () { + Navigator.of(context, rootNavigator: true).pop(); + }, + child: const Text('Apply'), ), - ], - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: TextButton( - onPressed: () { - Navigator.of(context, rootNavigator: true) - .pop(WindowSettings( - regularSize: regularSize, - floatingRegularSize: floatingRegularSize, - dialogSize: dialogSize, - satelliteSize: satelliteSize, - popupSize: popupSize, - tipSize: tipSize, - anchorRect: anchorRect, - anchorToWindow: anchorToWindow, - )); - }, - child: const Text('Apply'), - ), - ), - const SizedBox( - height: 2, - ), - ], - ); - }); + ), + const SizedBox( + height: 2, + ), + ], + ); + }); }); } diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index dcb45dbf0d87b..abc0390d6e2fc 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -106,6 +106,7 @@ class RegularWindow extends StatefulWidget { class _RegularWindowState extends State { _WindowListener? _listener; Future? _future; + WindowingApp? _app; @override void initState() { @@ -138,7 +139,8 @@ class _RegularWindowState extends State { } }, onDestroyed: widget.onDestroyed); - windowingAppContext!.windowingApp._registerListener(_listener!); + _app = windowingAppContext!.windowingApp; + _app!._registerListener(_listener!); }); }); } @@ -146,11 +148,9 @@ class _RegularWindowState extends State { @override void dispose() { super.dispose(); - final _WindowingAppContext? windowingAppContext = - _WindowingAppContext.of(context); if (_listener != null) { - assert(windowingAppContext != null); - windowingAppContext!.windowingApp._unregisterListener(_listener!); + assert(_app != null); + _app!._unregisterListener(_listener!); } } From 018edc31e9c7a2d1071c8999e4d654c7f827c0ed Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Wed, 4 Dec 2024 18:59:05 -0500 Subject: [PATCH 06/23] Able to add and remove windows --- .../lib/app/main_window.dart | 86 +++++++++---------- .../lib/app/window_controller_render.dart | 5 +- packages/flutter/lib/src/widgets/window.dart | 3 + 3 files changed, 46 insertions(+), 48 deletions(-) diff --git a/examples/multi_window_ref_app/lib/app/main_window.dart b/examples/multi_window_ref_app/lib/app/main_window.dart index 3fd863b8b1b00..07da1296f3e15 100644 --- a/examples/multi_window_ref_app/lib/app/main_window.dart +++ b/examples/multi_window_ref_app/lib/app/main_window.dart @@ -1,35 +1,40 @@ -import 'dart:ui'; - import 'package:flutter/material.dart'; import 'package:multi_window_ref_app/app/window_controller_render.dart'; import 'window_settings.dart'; import 'window_settings_dialog.dart'; +class _KeyedWindowController { + _KeyedWindowController({required this.controller}); + + final WindowController controller; + final UniqueKey key = UniqueKey(); +} + class _WindowManagerModel extends ChangeNotifier { - final List _windows = []; - List get windows => _windows; + final List<_KeyedWindowController> _windows = <_KeyedWindowController>[]; + List<_KeyedWindowController> get windows => _windows; int? _selectedViewId; WindowController? get selected { if (_selectedViewId == null) { return null; } - for (final WindowController window in _windows) { - if (window.view?.viewId == _selectedViewId) { - return window; + for (final _KeyedWindowController controller in _windows) { + if (controller.controller.view?.viewId == _selectedViewId) { + return controller.controller; } } return null; } - void add(WindowController window) { + void add(_KeyedWindowController window) { _windows.add(window); notifyListeners(); } - void remove(WindowController window) { + void remove(_KeyedWindowController window) { _windows.remove(window); notifyListeners(); } @@ -92,37 +97,22 @@ class _MainWindowState extends State { view: ListenableBuilder( listenable: _windowManagerModel, builder: (BuildContext context, Widget? widget) { - return _ViewCollection( - windowManagerModel: _windowManagerModel, - windowSettings: _settings); + return ViewCollection( + views: _windowManagerModel.windows + .map((_KeyedWindowController controller) { + return WindowControllerRender( + key: controller.key, + controller: controller.controller, + onDestroyed: () { + _windowManagerModel.remove(controller); + }, + windowSettings: _settings); + }).toList()); }), child: widget); } } -class _ViewCollection extends StatelessWidget { - _ViewCollection( - {required this.windowManagerModel, required this.windowSettings}); - - final _WindowManagerModel windowManagerModel; - final WindowSettings windowSettings; - - @override - Widget build(BuildContext context) { - final List childViews = []; - for (final WindowController controller in windowManagerModel.windows) { - childViews.add(WindowControllerRender( - controller: controller, - onDestroyed: () { - windowManagerModel.remove(controller); - }, - windowSettings: windowSettings)); - } - - return ViewCollection(views: childViews); - } -} - class _ActiveWindowsTable extends StatelessWidget { const _ActiveWindowsTable({required this.windowManagerModel}); @@ -169,8 +159,9 @@ class _ActiveWindowsTable extends StatelessWidget { numeric: true), ], rows: windowManagerModel.windows - .map((WindowController controller) { + .map((_KeyedWindowController controller) { return DataRow( + key: controller.key, color: WidgetStateColor.resolveWith((states) { if (states.contains(WidgetState.selected)) { return Theme.of(context) @@ -180,39 +171,39 @@ class _ActiveWindowsTable extends StatelessWidget { } return Colors.transparent; }), - selected: controller.view?.viewId == + selected: controller.controller.view?.viewId == windowManagerModel._selectedViewId, onSelectChanged: (selected) { if (selected != null) { - windowManagerModel - .select(selected ? controller.view?.viewId : null); + windowManagerModel.select( + selected ? controller.controller.view?.viewId : null); } }, cells: [ DataCell( ListenableBuilder( - listenable: controller, + listenable: controller.controller, builder: (BuildContext context, Widget? _) => Text( - controller.view != null - ? '${controller.view?.viewId}' + controller.controller.view != null + ? '${controller.controller.view?.viewId}' : 'Loading...')), ), DataCell( ListenableBuilder( - listenable: controller, + listenable: controller.controller, builder: (BuildContext context, Widget? _) => Text( - controller.type + controller.controller.type .toString() .replaceFirst('WindowArchetype.', ''))), ), DataCell( ListenableBuilder( - listenable: controller, + listenable: controller.controller, builder: (BuildContext context, Widget? _) => IconButton( icon: const Icon(Icons.delete_outlined), onPressed: () async { - await controller.destroy(); + await controller.controller.destroy(); }, )), ), @@ -258,7 +249,8 @@ class _WindowCreatorCard extends StatelessWidget { children: [ OutlinedButton( onPressed: () async { - windowManagerModel.add(RegularWindowController()); + windowManagerModel.add(_KeyedWindowController( + controller: RegularWindowController())); }, child: const Text('Regular'), ), diff --git a/examples/multi_window_ref_app/lib/app/window_controller_render.dart b/examples/multi_window_ref_app/lib/app/window_controller_render.dart index fd00ec5f9a419..2f8a1a03f034c 100644 --- a/examples/multi_window_ref_app/lib/app/window_controller_render.dart +++ b/examples/multi_window_ref_app/lib/app/window_controller_render.dart @@ -6,17 +6,20 @@ class WindowControllerRender extends StatelessWidget { WindowControllerRender( {required this.controller, required this.onDestroyed, - required this.windowSettings}); + required this.windowSettings, + this.key}); final WindowController controller; final VoidCallback onDestroyed; final WindowSettings windowSettings; + final Key? key; @override Widget build(BuildContext context) { switch (controller.type) { case WindowArchetype.regular: return RegularWindow( + key: key, onDestroyed: onDestroyed, preferredSize: windowSettings.regularSize, controller: controller as RegularWindowController, diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index abc0390d6e2fc..4c67394ae76ab 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -91,11 +91,13 @@ class RegularWindow extends StatefulWidget { {required Size preferredSize, this.onDestroyed, this.controller, + this.key, required this.child}) : _preferredSize = preferredSize; final RegularWindowController? controller; void Function()? onDestroyed; + final Key? key; final Widget child; final Size _preferredSize; @@ -157,6 +159,7 @@ class _RegularWindowState extends State { @override Widget build(BuildContext context) { return FutureBuilder( + key: widget.key, future: _future, builder: (BuildContext context, AsyncSnapshot metadata) { From e085b3239059389b2c3af897711c32b6ce7922c1 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Wed, 4 Dec 2024 20:41:41 -0500 Subject: [PATCH 07/23] All windows are now showing in the list --- .../lib/app/main_window.dart | 100 ++++++------------ .../lib/app/regular_window_content.dart | 66 ++++++------ .../lib/app/window_controller_render.dart | 14 ++- .../lib/app/window_manager_model.dart | 46 ++++++++ examples/multi_window_ref_app/lib/main.dart | 4 +- 5 files changed, 129 insertions(+), 101 deletions(-) create mode 100644 examples/multi_window_ref_app/lib/app/window_manager_model.dart diff --git a/examples/multi_window_ref_app/lib/app/main_window.dart b/examples/multi_window_ref_app/lib/app/main_window.dart index 07da1296f3e15..a76a9730bb571 100644 --- a/examples/multi_window_ref_app/lib/app/main_window.dart +++ b/examples/multi_window_ref_app/lib/app/main_window.dart @@ -3,62 +3,25 @@ import 'package:multi_window_ref_app/app/window_controller_render.dart'; import 'window_settings.dart'; import 'window_settings_dialog.dart'; - -class _KeyedWindowController { - _KeyedWindowController({required this.controller}); - - final WindowController controller; - final UniqueKey key = UniqueKey(); -} - -class _WindowManagerModel extends ChangeNotifier { - final List<_KeyedWindowController> _windows = <_KeyedWindowController>[]; - List<_KeyedWindowController> get windows => _windows; - int? _selectedViewId; - WindowController? get selected { - if (_selectedViewId == null) { - return null; - } - - for (final _KeyedWindowController controller in _windows) { - if (controller.controller.view?.viewId == _selectedViewId) { - return controller.controller; - } - } - - return null; - } - - void add(_KeyedWindowController window) { - _windows.add(window); - notifyListeners(); - } - - void remove(_KeyedWindowController window) { - _windows.remove(window); - notifyListeners(); - } - - void select(int? viewId) { - _selectedViewId = viewId; - notifyListeners(); - } -} +import 'window_manager_model.dart'; class MainWindow extends StatefulWidget { - const MainWindow({super.key}); + MainWindow({super.key, required WindowController mainController}) + : _mainKeyedController = KeyedWindowController(controller: mainController); + + final KeyedWindowController _mainKeyedController; @override State createState() => _MainWindowState(); } class _MainWindowState extends State { - final _WindowManagerModel _windowManagerModel = _WindowManagerModel(); + final WindowManagerModel _windowManagerModel = WindowManagerModel(); final WindowSettings _settings = WindowSettings(); @override Widget build(BuildContext context) { - final widget = Scaffold( + final child = Scaffold( appBar: AppBar( title: const Text('Multi Window Reference App'), ), @@ -70,7 +33,9 @@ class _MainWindowState extends State { child: SingleChildScrollView( scrollDirection: Axis.vertical, child: - _ActiveWindowsTable(windowManagerModel: _windowManagerModel), + _ActiveWindowsTable( + mainController: widget._mainKeyedController, + windowManagerModel: _windowManagerModel), ), ), Expanded( @@ -97,26 +62,32 @@ class _MainWindowState extends State { view: ListenableBuilder( listenable: _windowManagerModel, builder: (BuildContext context, Widget? widget) { - return ViewCollection( - views: _windowManagerModel.windows - .map((_KeyedWindowController controller) { - return WindowControllerRender( - key: controller.key, - controller: controller.controller, - onDestroyed: () { - _windowManagerModel.remove(controller); - }, - windowSettings: _settings); - }).toList()); + final List childViews = []; + for (final KeyedWindowController controller + in _windowManagerModel.windows) { + if (controller.parent == null) { + childViews.add(WindowControllerRender( + controller: controller.controller, + key: controller.key, + windowSettings: _settings, + windowManagerModel: _windowManagerModel, + onDestroyed: () { + _windowManagerModel.remove(controller); + })); + } + } + + return ViewCollection(views: childViews); }), - child: widget); + child: child); } } class _ActiveWindowsTable extends StatelessWidget { - const _ActiveWindowsTable({required this.windowManagerModel}); + const _ActiveWindowsTable({required this.mainController, required this.windowManagerModel}); - final _WindowManagerModel windowManagerModel; + final KeyedWindowController mainController; + final WindowManagerModel windowManagerModel; @override Widget build(BuildContext context) { @@ -158,8 +129,8 @@ class _ActiveWindowsTable extends StatelessWidget { ), numeric: true), ], - rows: windowManagerModel.windows - .map((_KeyedWindowController controller) { + rows: ([mainController] + windowManagerModel.windows) + .map((KeyedWindowController controller) { return DataRow( key: controller.key, color: WidgetStateColor.resolveWith((states) { @@ -171,8 +142,7 @@ class _ActiveWindowsTable extends StatelessWidget { } return Colors.transparent; }), - selected: controller.controller.view?.viewId == - windowManagerModel._selectedViewId, + selected: controller.controller == windowManagerModel.selected, onSelectChanged: (selected) { if (selected != null) { windowManagerModel.select( @@ -222,7 +192,7 @@ class _WindowCreatorCard extends StatelessWidget { required this.windowSettings}); final WindowController? selectedWindow; - final _WindowManagerModel windowManagerModel; + final WindowManagerModel windowManagerModel; final WindowSettings windowSettings; @override @@ -249,7 +219,7 @@ class _WindowCreatorCard extends StatelessWidget { children: [ OutlinedButton( onPressed: () async { - windowManagerModel.add(_KeyedWindowController( + windowManagerModel.add(KeyedWindowController( controller: RegularWindowController())); }, child: const Text('Regular'), diff --git a/examples/multi_window_ref_app/lib/app/regular_window_content.dart b/examples/multi_window_ref_app/lib/app/regular_window_content.dart index e6f414e89a3fb..e2cf74329f3b2 100644 --- a/examples/multi_window_ref_app/lib/app/regular_window_content.dart +++ b/examples/multi_window_ref_app/lib/app/regular_window_content.dart @@ -1,24 +1,23 @@ import 'package:flutter/material.dart'; import 'package:multi_window_ref_app/app/window_controller_render.dart'; +import 'package:multi_window_ref_app/app/window_manager_model.dart'; import 'package:multi_window_ref_app/app/window_settings.dart'; -class RegularWindowContent extends StatefulWidget { - const RegularWindowContent({super.key, required this.window, required this.windowSettings}); +class RegularWindowContent extends StatelessWidget { + const RegularWindowContent( + {super.key, + required this.window, + required this.windowSettings, + required this.windowManagerModel}); final RegularWindowController window; final WindowSettings windowSettings; - - @override - State createState() => _RegularWindowContentState(); -} - -class _RegularWindowContentState extends State { - List childControllers = []; + final WindowManagerModel windowManagerModel; @override Widget build(BuildContext context) { final child = Scaffold( - appBar: AppBar(title: Text('${widget.window.type}')), + appBar: AppBar(title: Text('${window.type}')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -26,23 +25,19 @@ class _RegularWindowContentState extends State { children: [ ElevatedButton( onPressed: () { - setState(() { - childControllers = [ - ...childControllers, - RegularWindowController() - ]; - }); + windowManagerModel.add(KeyedWindowController( + controller: RegularWindowController())); }, child: const Text('Create Regular Window'), ), const SizedBox(height: 20), ListenableBuilder( - listenable: widget.window, + listenable: window, builder: (BuildContext context, Widget? _) { return Text( - 'View #${widget.window.view?.viewId ?? "Unknown"}\n' - 'Parent View: ${widget.window.parentViewId}\n' - 'Logical Size: ${widget.window.size?.width ?? "?"}\u00D7${widget.window.size?.height ?? "?"}\n' + 'View #${window.view?.viewId ?? "Unknown"}\n' + 'Parent View: ${window.parentViewId}\n' + 'Logical Size: ${window.size?.width ?? "?"}\u00D7${window.size?.height ?? "?"}\n' 'DPR: ${MediaQuery.of(context).devicePixelRatio}', textAlign: TextAlign.center, ); @@ -52,16 +47,27 @@ class _RegularWindowContentState extends State { ), ); - final List childViews = - childControllers.map((WindowController controller) { - return WindowControllerRender( - controller: controller, - windowSettings: widget.windowSettings, - onDestroyed: () { - childControllers.remove(controller); - }); - }).toList(); + return ViewAnchor( + view: ListenableBuilder( + listenable: windowManagerModel, + builder: (BuildContext context, Widget? _) { + final List childViews = []; + for (final KeyedWindowController controller + in windowManagerModel.windows) { + if (controller.parent == window) { + childViews.add(WindowControllerRender( + controller: controller.controller, + key: controller.key, + windowSettings: windowSettings, + windowManagerModel: windowManagerModel, + onDestroyed: () { + windowManagerModel.remove(controller); + })); + } + } - return ViewAnchor(view: ViewCollection(views: childViews), child: child); + return ViewCollection(views: childViews); + }), + child: child); } } diff --git a/examples/multi_window_ref_app/lib/app/window_controller_render.dart b/examples/multi_window_ref_app/lib/app/window_controller_render.dart index 2f8a1a03f034c..13b97fea0896c 100644 --- a/examples/multi_window_ref_app/lib/app/window_controller_render.dart +++ b/examples/multi_window_ref_app/lib/app/window_controller_render.dart @@ -1,18 +1,21 @@ import 'package:flutter/material.dart'; -import 'package:multi_window_ref_app/app/regular_window_content.dart'; -import 'package:multi_window_ref_app/app/window_settings.dart'; +import 'regular_window_content.dart'; +import 'window_manager_model.dart'; +import 'window_settings.dart'; class WindowControllerRender extends StatelessWidget { WindowControllerRender( {required this.controller, required this.onDestroyed, required this.windowSettings, - this.key}); + required this.windowManagerModel, + required this.key}); final WindowController controller; final VoidCallback onDestroyed; final WindowSettings windowSettings; - final Key? key; + final WindowManagerModel windowManagerModel; + final Key key; @override Widget build(BuildContext context) { @@ -25,7 +28,8 @@ class WindowControllerRender extends StatelessWidget { controller: controller as RegularWindowController, child: RegularWindowContent( window: controller as RegularWindowController, - windowSettings: windowSettings)); + windowSettings: windowSettings, + windowManagerModel: windowManagerModel)); default: throw UnimplementedError( "The provided window type does not have an implementation"); diff --git a/examples/multi_window_ref_app/lib/app/window_manager_model.dart b/examples/multi_window_ref_app/lib/app/window_manager_model.dart new file mode 100644 index 0000000000000..d641f05b690df --- /dev/null +++ b/examples/multi_window_ref_app/lib/app/window_manager_model.dart @@ -0,0 +1,46 @@ +import 'package:flutter/widgets.dart'; + +class KeyedWindowController { + KeyedWindowController({this.parent, required this.controller}); + + final WindowController? parent; + final WindowController controller; + final UniqueKey key = UniqueKey(); +} + +/// Manages a flat list of all of the [WindowController]s that have been +/// created by the application as well as which controller is currently +/// selected by the UI. +class WindowManagerModel extends ChangeNotifier { + final List _windows = []; + List get windows => _windows; + int? _selectedViewId; + WindowController? get selected { + if (_selectedViewId == null) { + return null; + } + + for (final KeyedWindowController controller in _windows) { + if (controller.controller.view?.viewId == _selectedViewId) { + return controller.controller; + } + } + + return null; + } + + void add(KeyedWindowController window) { + _windows.add(window); + notifyListeners(); + } + + void remove(KeyedWindowController window) { + _windows.remove(window); + notifyListeners(); + } + + void select(int? viewId) { + _selectedViewId = viewId; + notifyListeners(); + } +} diff --git a/examples/multi_window_ref_app/lib/main.dart b/examples/multi_window_ref_app/lib/main.dart index 6fd044eef6d86..48148c7b6b6dc 100644 --- a/examples/multi_window_ref_app/lib/main.dart +++ b/examples/multi_window_ref_app/lib/main.dart @@ -2,9 +2,11 @@ import 'package:flutter/material.dart'; import 'app/main_window.dart'; void main() { + final RegularWindowController controller = RegularWindowController(); runWidget(WindowingApp(children: [ RegularWindow( + controller: controller, preferredSize: const Size(800, 600), - child: const MaterialApp(home: MainWindow())) + child: MaterialApp(home: MainWindow(mainController: controller))) ])); } From 68d219ba7dd4a946849f042065eae322287e3ef3 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Wed, 4 Dec 2024 21:02:42 -0500 Subject: [PATCH 08/23] Added the ability to listen for errors and respond to them accordingly --- .../lib/app/main_window.dart | 27 +++++----- .../lib/app/regular_window_content.dart | 14 ++--- .../lib/app/window_controller_render.dart | 3 ++ packages/flutter/lib/src/widgets/window.dart | 51 +++++++++++-------- 4 files changed, 53 insertions(+), 42 deletions(-) diff --git a/examples/multi_window_ref_app/lib/app/main_window.dart b/examples/multi_window_ref_app/lib/app/main_window.dart index a76a9730bb571..f862ab215ed32 100644 --- a/examples/multi_window_ref_app/lib/app/main_window.dart +++ b/examples/multi_window_ref_app/lib/app/main_window.dart @@ -7,7 +7,8 @@ import 'window_manager_model.dart'; class MainWindow extends StatefulWidget { MainWindow({super.key, required WindowController mainController}) - : _mainKeyedController = KeyedWindowController(controller: mainController); + : _mainKeyedController = + KeyedWindowController(controller: mainController); final KeyedWindowController _mainKeyedController; @@ -32,10 +33,9 @@ class _MainWindowState extends State { flex: 60, child: SingleChildScrollView( scrollDirection: Axis.vertical, - child: - _ActiveWindowsTable( - mainController: widget._mainKeyedController, - windowManagerModel: _windowManagerModel), + child: _ActiveWindowsTable( + mainController: widget._mainKeyedController, + windowManagerModel: _windowManagerModel), ), ), Expanded( @@ -67,13 +67,13 @@ class _MainWindowState extends State { in _windowManagerModel.windows) { if (controller.parent == null) { childViews.add(WindowControllerRender( - controller: controller.controller, - key: controller.key, - windowSettings: _settings, - windowManagerModel: _windowManagerModel, - onDestroyed: () { - _windowManagerModel.remove(controller); - })); + controller: controller.controller, + key: controller.key, + windowSettings: _settings, + windowManagerModel: _windowManagerModel, + onDestroyed: () => _windowManagerModel.remove(controller), + onError: () => _windowManagerModel.remove(controller), + )); } } @@ -84,7 +84,8 @@ class _MainWindowState extends State { } class _ActiveWindowsTable extends StatelessWidget { - const _ActiveWindowsTable({required this.mainController, required this.windowManagerModel}); + const _ActiveWindowsTable( + {required this.mainController, required this.windowManagerModel}); final KeyedWindowController mainController; final WindowManagerModel windowManagerModel; diff --git a/examples/multi_window_ref_app/lib/app/regular_window_content.dart b/examples/multi_window_ref_app/lib/app/regular_window_content.dart index e2cf74329f3b2..394c9b5cdc0fe 100644 --- a/examples/multi_window_ref_app/lib/app/regular_window_content.dart +++ b/examples/multi_window_ref_app/lib/app/regular_window_content.dart @@ -56,13 +56,13 @@ class RegularWindowContent extends StatelessWidget { in windowManagerModel.windows) { if (controller.parent == window) { childViews.add(WindowControllerRender( - controller: controller.controller, - key: controller.key, - windowSettings: windowSettings, - windowManagerModel: windowManagerModel, - onDestroyed: () { - windowManagerModel.remove(controller); - })); + controller: controller.controller, + key: controller.key, + windowSettings: windowSettings, + windowManagerModel: windowManagerModel, + onDestroyed: () => windowManagerModel.remove(controller), + onError: () => windowManagerModel.remove(controller), + )); } } diff --git a/examples/multi_window_ref_app/lib/app/window_controller_render.dart b/examples/multi_window_ref_app/lib/app/window_controller_render.dart index 13b97fea0896c..b7b82b6b3d0a1 100644 --- a/examples/multi_window_ref_app/lib/app/window_controller_render.dart +++ b/examples/multi_window_ref_app/lib/app/window_controller_render.dart @@ -7,12 +7,14 @@ class WindowControllerRender extends StatelessWidget { WindowControllerRender( {required this.controller, required this.onDestroyed, + required this.onError, required this.windowSettings, required this.windowManagerModel, required this.key}); final WindowController controller; final VoidCallback onDestroyed; + final VoidCallback onError; final WindowSettings windowSettings; final WindowManagerModel windowManagerModel; final Key key; @@ -24,6 +26,7 @@ class WindowControllerRender extends StatelessWidget { return RegularWindow( key: key, onDestroyed: onDestroyed, + onError: (String? reason) => onError(), preferredSize: windowSettings.regularSize, controller: controller as RegularWindowController, child: RegularWindowContent( diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index 4c67394ae76ab..e95cb6539607f 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -8,24 +8,24 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; -/// Defines the type of a [Window] +/// Defines the type of the Window enum WindowArchetype { - /// Defines a standard [Window] + /// Defines a traditional window regular, - /// Defines a [Window] that is on a layer above [regular] [Window]s and is not dockable + /// Defines a window that is on a layer above [regular] windows and is not dockable floatingRegular, - /// Defines a dialog [Window] + /// Defines a dialog window dialog, - /// Defines a satellite [Window] + /// Defines a satellite window satellite, - /// Defines a popup [Window] + /// Defines a popup window popup, - /// Defines a tooltip + /// Defines a tooltip window tip, } @@ -76,6 +76,8 @@ abstract class WindowController with ChangeNotifier { } } +/// Provided to [RegularWindow]. Allows the user to listen on changes +/// to a regular window and modify the window. class RegularWindowController extends WindowController { @override WindowArchetype get type => WindowArchetype.regular; @@ -86,10 +88,12 @@ class RegularWindowController extends WindowController { } } +/// A regular window. class RegularWindow extends StatefulWidget { RegularWindow( {required Size preferredSize, this.onDestroyed, + this.onError, this.controller, this.key, required this.child}) @@ -97,6 +101,7 @@ class RegularWindow extends StatefulWidget { final RegularWindowController? controller; void Function()? onDestroyed; + void Function(String?)? onError; final Key? key; final Widget child; final Size _preferredSize; @@ -107,14 +112,14 @@ class RegularWindow extends StatefulWidget { class _RegularWindowState extends State { _WindowListener? _listener; - Future? _future; + Future<_RegularWindowMetadata>? _future; WindowingApp? _app; @override void initState() { super.initState(); _future = createRegular(size: widget._preferredSize); - _future!.then((RegularWindowMetadata metadata) async { + _future!.then((_RegularWindowMetadata metadata) async { if (widget.controller != null) { widget.controller!.view = metadata.view; widget.controller!.parentViewId = metadata.parentViewId; @@ -144,6 +149,8 @@ class _RegularWindowState extends State { _app = windowingAppContext!.windowingApp; _app!._registerListener(_listener!); }); + }).catchError((Object? error) { + widget.onError?.call(error.toString()); }); } @@ -158,11 +165,11 @@ class _RegularWindowState extends State { @override Widget build(BuildContext context) { - return FutureBuilder( + return FutureBuilder<_RegularWindowMetadata>( key: widget.key, future: _future, builder: (BuildContext context, - AsyncSnapshot metadata) { + AsyncSnapshot<_RegularWindowMetadata> metadata) { if (!metadata.hasData) { return const ViewCollection(views: []); } @@ -194,21 +201,21 @@ class WindowContext extends InheritedWidget { } } -class WindowMetadata { - WindowMetadata({required this.view, required this.size, this.parentViewId}); +class _WindowMetadata { + _WindowMetadata({required this.view, required this.size, this.parentViewId}); final FlutterView view; final Size size; final int? parentViewId; } -class RegularWindowMetadata extends WindowMetadata { - RegularWindowMetadata( +class _RegularWindowMetadata extends _WindowMetadata { + _RegularWindowMetadata( {required super.view, required super.size, super.parentViewId}); } -class _WindowMetadata { - _WindowMetadata( +class _WindowCreationResult { + _WindowCreationResult( {required this.flView, required this.archetype, required this.size, @@ -225,17 +232,17 @@ class _WindowMetadata { /// widget instead of this method. /// /// [size] the size of the new [Window] in pixels -Future createRegular({required Size size}) async { - final _WindowMetadata metadata = +Future<_RegularWindowMetadata> createRegular({required Size size}) async { + final _WindowCreationResult metadata = await _createWindow(viewBuilder: (MethodChannel channel) async { return await channel.invokeMethod('createWindow', { 'size': [size.width.toInt(), size.height.toInt()], }) as Map; }); - return RegularWindowMetadata(view: metadata.flView, size: metadata.size); + return _RegularWindowMetadata(view: metadata.flView, size: metadata.size); } -Future<_WindowMetadata> _createWindow( +Future<_WindowCreationResult> _createWindow( {required Future> Function(MethodChannel channel) viewBuilder}) async { WidgetsFlutterBinding.ensureInitialized(); @@ -255,7 +262,7 @@ Future<_WindowMetadata> _createWindow( }, ); - return _WindowMetadata( + return _WindowCreationResult( flView: flView, archetype: archetype, size: Size((size[0]! as int).toDouble(), (size[1]! as int).toDouble()), From 1a6aed7b3bdde2581f1fe8ad110f4aef34fbebda Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Thu, 5 Dec 2024 16:28:16 -0500 Subject: [PATCH 09/23] Refactored window.dart to be more concise for the initial PR --- packages/flutter/lib/src/widgets/window.dart | 78 +++++++++++--------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index e95cb6539607f..90d79c0d6af1c 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -11,22 +11,7 @@ import 'package:flutter/services.dart'; /// Defines the type of the Window enum WindowArchetype { /// Defines a traditional window - regular, - - /// Defines a window that is on a layer above [regular] windows and is not dockable - floatingRegular, - - /// Defines a dialog window - dialog, - - /// Defines a satellite window - satellite, - - /// Defines a popup window - popup, - - /// Defines a tooltip window - tip, + regular } /// Controller used with the [RegularWindow] widget. This controller @@ -88,24 +73,34 @@ class RegularWindowController extends WindowController { } } -/// A regular window. +/// A widget that creates a regular window. This content of this window is +/// rendered into a [View], meaning that this widget must be rendered into +/// either a [ViewAnchor] or a [ViewCollection]. class RegularWindow extends StatefulWidget { - RegularWindow( - {required Size preferredSize, + /// Creates a regular window widget + const RegularWindow( + {this.controller, this.onDestroyed, this.onError, - this.controller, - this.key, + super.key, + required Size preferredSize, required this.child}) : _preferredSize = preferredSize; + /// Controller for this widget. final RegularWindowController? controller; - void Function()? onDestroyed; - void Function(String?)? onError; - final Key? key; - final Widget child; + + /// Called when the window backing this widget is destroyed. + final void Function()? onDestroyed; + + /// Called when an error is encountered during the creation of this widget. + final void Function(String?)? onError; + final Size _preferredSize; + /// The content rendered into this window. + final Widget child; + @override State createState() => _RegularWindowState(); } @@ -113,7 +108,7 @@ class RegularWindow extends StatefulWidget { class _RegularWindowState extends State { _WindowListener? _listener; Future<_RegularWindowMetadata>? _future; - WindowingApp? _app; + _WindowingAppState? _app; @override void initState() { @@ -300,17 +295,30 @@ class _WindowListener { void Function()? onDestroyed; } -/// Declares that an application will create multiple [Window]s. -/// The current [Window] can be looked up with [WindowContext.of]. -class WindowingApp extends StatelessWidget { - WindowingApp({super.key, required this.children}) { +/// Declares that an application will create multiple windows. +class WindowingApp extends StatefulWidget { + /// Creates a new windowing app with the provided child windows. + const WindowingApp({super.key, required this.children}); + + /// A list of initial windows to render. These windows will be placed inside + /// of a [ViewCollection]. + final List children; + + @override + State createState() => _WindowingAppState(); +} + +class _WindowingAppState extends State +{ + final List<_WindowListener> _listeners = <_WindowListener>[]; + + @override + void initState() { + super.initState(); WidgetsFlutterBinding.ensureInitialized(); SystemChannels.windowing.setMethodCallHandler(_methodCallHandler); } - final List children; - final List<_WindowListener> _listeners = []; - Future _methodCallHandler(MethodCall call) async { final Map arguments = call.arguments as Map; @@ -367,7 +375,7 @@ class WindowingApp extends StatelessWidget { @override Widget build(BuildContext context) { return _WindowingAppContext( - windowingApp: this, child: ViewCollection(views: children)); + windowingApp: this, child: ViewCollection(views: widget.children)); } } @@ -375,7 +383,7 @@ class _WindowingAppContext extends InheritedWidget { const _WindowingAppContext( {super.key, required super.child, required this.windowingApp}); - final WindowingApp windowingApp; + final _WindowingAppState windowingApp; /// Returns the [MultiWindowAppContext] if any static _WindowingAppContext? of(BuildContext context) { From d3c2fb6591e416dc324ee6aef03360f6c9760b2d Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Thu, 5 Dec 2024 16:39:45 -0500 Subject: [PATCH 10/23] Making the WindowMetadata public and only refresing the relevant fields when settings change --- .../lib/app/window_settings_dialog.dart | 86 +++++++++---------- packages/flutter/lib/src/widgets/window.dart | 32 ++++--- 2 files changed, 64 insertions(+), 54 deletions(-) diff --git a/examples/multi_window_ref_app/lib/app/window_settings_dialog.dart b/examples/multi_window_ref_app/lib/app/window_settings_dialog.dart index 7c0da255ccd05..2d8fe433b133f 100644 --- a/examples/multi_window_ref_app/lib/app/window_settings_dialog.dart +++ b/examples/multi_window_ref_app/lib/app/window_settings_dialog.dart @@ -7,27 +7,27 @@ Future windowSettingsDialog( barrierDismissible: true, context: context, builder: (BuildContext ctx) { - return ListenableBuilder( - listenable: settings, - builder: (BuildContext ctx, Widget? _) { - return SimpleDialog( - contentPadding: const EdgeInsets.all(4), - titlePadding: const EdgeInsets.fromLTRB(24, 10, 24, 0), - title: const Center( - child: Text('Window Settings'), - ), + return SimpleDialog( + contentPadding: const EdgeInsets.all(4), + titlePadding: const EdgeInsets.fromLTRB(24, 10, 24, 0), + title: const Center( + child: Text('Window Settings'), + ), + children: [ + SizedBox( + width: 600, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - SizedBox( - width: 600, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - children: [ - Expanded( - child: ListTile( - title: const Text('Regular'), - subtitle: Row( + Row( + children: [ + Expanded( + child: ListTile( + title: const Text('Regular'), + subtitle: ListenableBuilder( + listenable: settings, + builder: (BuildContext ctx, Widget? _) { + return Row( children: [ Expanded( child: TextFormField( @@ -60,31 +60,31 @@ Future windowSettingsDialog( ), ), ], - ), - ), - ), - const SizedBox( - width: 10, - ), - ], + ); + }), ), - ], - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: TextButton( - onPressed: () { - Navigator.of(context, rootNavigator: true).pop(); - }, - child: const Text('Apply'), - ), - ), - const SizedBox( - height: 2, + ), + const SizedBox( + width: 10, + ), + ], ), ], - ); - }); + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: TextButton( + onPressed: () { + Navigator.of(context, rootNavigator: true).pop(); + }, + child: const Text('Apply'), + ), + ), + const SizedBox( + height: 2, + ), + ], + ); }); } diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index 90d79c0d6af1c..ba026087434b0 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -107,14 +107,14 @@ class RegularWindow extends StatefulWidget { class _RegularWindowState extends State { _WindowListener? _listener; - Future<_RegularWindowMetadata>? _future; + Future? _future; _WindowingAppState? _app; @override void initState() { super.initState(); _future = createRegular(size: widget._preferredSize); - _future!.then((_RegularWindowMetadata metadata) async { + _future!.then((RegularWindowMetadata metadata) async { if (widget.controller != null) { widget.controller!.view = metadata.view; widget.controller!.parentViewId = metadata.parentViewId; @@ -160,11 +160,11 @@ class _RegularWindowState extends State { @override Widget build(BuildContext context) { - return FutureBuilder<_RegularWindowMetadata>( + return FutureBuilder( key: widget.key, future: _future, builder: (BuildContext context, - AsyncSnapshot<_RegularWindowMetadata> metadata) { + AsyncSnapshot metadata) { if (!metadata.hasData) { return const ViewCollection(views: []); } @@ -196,17 +196,27 @@ class WindowContext extends InheritedWidget { } } -class _WindowMetadata { - _WindowMetadata({required this.view, required this.size, this.parentViewId}); +/// Base class for window creation metadata. +abstract class WindowMetadata { + /// Creates generic window metadata. + WindowMetadata({required this.view, required this.size, this.parentViewId}); + /// The view associated with the window. final FlutterView view; + + /// The size of the created window. final Size size; + + /// The parent view of the window, if any. final int? parentViewId; } -class _RegularWindowMetadata extends _WindowMetadata { - _RegularWindowMetadata( - {required super.view, required super.size, super.parentViewId}); +/// Data object returned by [createRegular]. +class RegularWindowMetadata extends WindowMetadata { + /// Creates metadata for a regular window. This should only be initialized + /// by [createRegular]. + RegularWindowMetadata( + {required super.view, required super.size}); } class _WindowCreationResult { @@ -227,14 +237,14 @@ class _WindowCreationResult { /// widget instead of this method. /// /// [size] the size of the new [Window] in pixels -Future<_RegularWindowMetadata> createRegular({required Size size}) async { +Future createRegular({required Size size}) async { final _WindowCreationResult metadata = await _createWindow(viewBuilder: (MethodChannel channel) async { return await channel.invokeMethod('createWindow', { 'size': [size.width.toInt(), size.height.toInt()], }) as Map; }); - return _RegularWindowMetadata(view: metadata.flView, size: metadata.size); + return RegularWindowMetadata(view: metadata.flView, size: metadata.size); } Future<_WindowCreationResult> _createWindow( From d6322feca61f3273b9a62425f0b17702c8ee8547 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Thu, 5 Dec 2024 15:43:46 -0500 Subject: [PATCH 11/23] RegularWindowMetadata and WindowMetadata are useless --- packages/flutter/lib/src/widgets/window.dart | 67 ++++++++------------ 1 file changed, 25 insertions(+), 42 deletions(-) diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index ba026087434b0..796fac246058e 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -107,17 +107,17 @@ class RegularWindow extends StatefulWidget { class _RegularWindowState extends State { _WindowListener? _listener; - Future? _future; + Future? _future; _WindowingAppState? _app; @override void initState() { super.initState(); _future = createRegular(size: widget._preferredSize); - _future!.then((RegularWindowMetadata metadata) async { + _future!.then((WindowCreationResult metadata) async { if (widget.controller != null) { - widget.controller!.view = metadata.view; - widget.controller!.parentViewId = metadata.parentViewId; + widget.controller!.view = metadata.flView; + widget.controller!.parentViewId = metadata.parent; widget.controller!.size = metadata.size; } @@ -126,7 +126,7 @@ class _RegularWindowState extends State { _WindowingAppContext.of(context); assert(windowingAppContext != null); _listener = _WindowListener( - viewId: metadata.view.viewId, + viewId: metadata.flView.viewId, onChanged: (_WindowChangeProperties properties) { if (widget.controller == null) { return; @@ -160,19 +160,19 @@ class _RegularWindowState extends State { @override Widget build(BuildContext context) { - return FutureBuilder( + return FutureBuilder( key: widget.key, future: _future, builder: (BuildContext context, - AsyncSnapshot metadata) { + AsyncSnapshot metadata) { if (!metadata.hasData) { return const ViewCollection(views: []); } return View( - view: metadata.data!.view, + view: metadata.data!.flView, child: WindowContext( - viewId: metadata.data!.view.viewId, child: widget.child)); + viewId: metadata.data!.flView.viewId, child: widget.child)); }); } } @@ -196,39 +196,25 @@ class WindowContext extends InheritedWidget { } } -/// Base class for window creation metadata. -abstract class WindowMetadata { - /// Creates generic window metadata. - WindowMetadata({required this.view, required this.size, this.parentViewId}); - - /// The view associated with the window. - final FlutterView view; - - /// The size of the created window. - final Size size; - - /// The parent view of the window, if any. - final int? parentViewId; -} - -/// Data object returned by [createRegular]. -class RegularWindowMetadata extends WindowMetadata { - /// Creates metadata for a regular window. This should only be initialized - /// by [createRegular]. - RegularWindowMetadata( - {required super.view, required super.size}); -} - -class _WindowCreationResult { - _WindowCreationResult( +/// The raw data returned as a result of creating a window. +class WindowCreationResult { + /// Creates a new window. + WindowCreationResult( {required this.flView, required this.archetype, required this.size, this.parent}); + /// The view associated with the window. final FlutterView flView; + + /// The archetype of the window. final WindowArchetype archetype; + + /// The initial size of the window. final Size size; + + /// The id of the window's parent, if any. final int? parent; } @@ -237,17 +223,15 @@ class _WindowCreationResult { /// widget instead of this method. /// /// [size] the size of the new [Window] in pixels -Future createRegular({required Size size}) async { - final _WindowCreationResult metadata = - await _createWindow(viewBuilder: (MethodChannel channel) async { +Future createRegular({required Size size}) { + return _createWindow(viewBuilder: (MethodChannel channel) async { return await channel.invokeMethod('createWindow', { 'size': [size.width.toInt(), size.height.toInt()], }) as Map; }); - return RegularWindowMetadata(view: metadata.flView, size: metadata.size); } -Future<_WindowCreationResult> _createWindow( +Future _createWindow( {required Future> Function(MethodChannel channel) viewBuilder}) async { WidgetsFlutterBinding.ensureInitialized(); @@ -267,7 +251,7 @@ Future<_WindowCreationResult> _createWindow( }, ); - return _WindowCreationResult( + return WindowCreationResult( flView: flView, archetype: archetype, size: Size((size[0]! as int).toDouble(), (size[1]! as int).toDouble()), @@ -318,8 +302,7 @@ class WindowingApp extends StatefulWidget { State createState() => _WindowingAppState(); } -class _WindowingAppState extends State -{ +class _WindowingAppState extends State { final List<_WindowListener> _listeners = <_WindowListener>[]; @override From 06b3cdc134411e3e75b07a52caa36c602d736ad6 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Thu, 5 Dec 2024 16:02:14 -0500 Subject: [PATCH 12/23] The future should be set in setState --- packages/flutter/lib/src/widgets/window.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index 796fac246058e..85ba206aae9f7 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -113,8 +113,11 @@ class _RegularWindowState extends State { @override void initState() { super.initState(); - _future = createRegular(size: widget._preferredSize); - _future!.then((WindowCreationResult metadata) async { + final Future createRegularFuture = + createRegular(size: widget._preferredSize); + setState(() => _future = createRegularFuture); + + createRegularFuture.then((WindowCreationResult metadata) async { if (widget.controller != null) { widget.controller!.view = metadata.flView; widget.controller!.parentViewId = metadata.parent; From 79101645fae402cd20f9b0a54a4035aefe794e23 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Thu, 5 Dec 2024 16:05:53 -0500 Subject: [PATCH 13/23] Properly handling the event where we need to destroy the widget during a dispose --- packages/flutter/lib/src/widgets/window.dart | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index 85ba206aae9f7..ec289f0b798f5 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -109,6 +109,8 @@ class _RegularWindowState extends State { _WindowListener? _listener; Future? _future; _WindowingAppState? _app; + int? _viewId; + bool _hasBeenDestroyed = false; @override void initState() { @@ -118,6 +120,7 @@ class _RegularWindowState extends State { setState(() => _future = createRegularFuture); createRegularFuture.then((WindowCreationResult metadata) async { + _viewId = metadata.flView.viewId; if (widget.controller != null) { widget.controller!.view = metadata.flView; widget.controller!.parentViewId = metadata.parent; @@ -143,7 +146,10 @@ class _RegularWindowState extends State { widget.controller!.parentViewId = properties.parentViewId; } }, - onDestroyed: widget.onDestroyed); + onDestroyed: () { + widget.onDestroyed?.call(); + _hasBeenDestroyed = true; + }); _app = windowingAppContext!.windowingApp; _app!._registerListener(_listener!); }); @@ -159,6 +165,12 @@ class _RegularWindowState extends State { assert(_app != null); _app!._unregisterListener(_listener!); } + + // In the event that we're being disposed before we've been destroyed + // we need to destroy ther window on our way out. + if (!_hasBeenDestroyed && _viewId != null) { + destroyWindow(_viewId!); + } } @override From 56676f6df47e304b757942a80670d76df77b418b Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Thu, 5 Dec 2024 16:16:03 -0500 Subject: [PATCH 14/23] Fix issue where setState returned a future --- packages/flutter/lib/src/widgets/window.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index ec289f0b798f5..56d1b0974716a 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -117,7 +117,9 @@ class _RegularWindowState extends State { super.initState(); final Future createRegularFuture = createRegular(size: widget._preferredSize); - setState(() => _future = createRegularFuture); + setState(() { + _future = createRegularFuture; + }); createRegularFuture.then((WindowCreationResult metadata) async { _viewId = metadata.flView.viewId; From a9bf4786a1a9d2358a1e50b43c4e3d463cc283e4 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Thu, 5 Dec 2024 16:18:21 -0500 Subject: [PATCH 15/23] setState is ssynchronous, whoops! --- packages/flutter/lib/src/widgets/window.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index 56d1b0974716a..b712f0477039a 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -115,13 +115,11 @@ class _RegularWindowState extends State { @override void initState() { super.initState(); - final Future createRegularFuture = - createRegular(size: widget._preferredSize); setState(() { - _future = createRegularFuture; - }); + _future = createRegular(size: widget._preferredSize); + }); - createRegularFuture.then((WindowCreationResult metadata) async { + _future!.then((WindowCreationResult metadata) async { _viewId = metadata.flView.viewId; if (widget.controller != null) { widget.controller!.view = metadata.flView; From 47d5911f5ad3b9a04e7afe2d6b770187cf86e0ff Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Thu, 5 Dec 2024 16:35:19 -0500 Subject: [PATCH 16/23] Fixed issue where we might accidentally dispose of a window twice --- packages/flutter/lib/src/widgets/window.dart | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index b712f0477039a..9e4a67c0056b8 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -115,11 +115,13 @@ class _RegularWindowState extends State { @override void initState() { super.initState(); + final Future createRegularFuture = + createRegular(size: widget._preferredSize); setState(() { - _future = createRegular(size: widget._preferredSize); + _future = createRegularFuture; }); - _future!.then((WindowCreationResult metadata) async { + createRegularFuture.then((WindowCreationResult metadata) async { _viewId = metadata.flView.viewId; if (widget.controller != null) { widget.controller!.view = metadata.flView; @@ -159,8 +161,7 @@ class _RegularWindowState extends State { } @override - void dispose() { - super.dispose(); + Future dispose() async { if (_listener != null) { assert(_app != null); _app!._unregisterListener(_listener!); @@ -169,8 +170,14 @@ class _RegularWindowState extends State { // In the event that we're being disposed before we've been destroyed // we need to destroy ther window on our way out. if (!_hasBeenDestroyed && _viewId != null) { - destroyWindow(_viewId!); + // In the event of an argument error, we do nothing. We assume that + // the window has been successfully destroyed somehow else. + try { + await destroyWindow(_viewId!); + } on ArgumentError {} } + + super.dispose(); } @override From 7121af2e7194bd437a96cd15d4477ecdecca6e6e Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 6 Dec 2024 08:36:14 -0500 Subject: [PATCH 17/23] It does not make sense for the modify method to be an override since different window types will have different values --- packages/flutter/lib/src/widgets/window.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index b712f0477039a..c5f2fa773f248 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -48,9 +48,6 @@ abstract class WindowController with ChangeNotifier { /// The archetype of the window. WindowArchetype get type; - /// Modifies this window with the provided properties. - Future modify({Size? size}); - /// Destroys this window. Future destroy() async { if (view == null) { @@ -67,7 +64,7 @@ class RegularWindowController extends WindowController { @override WindowArchetype get type => WindowArchetype.regular; - @override + /// Modify the properties of the window. Future modify({Size? size}) { throw UnimplementedError(); } From 986800e3defe70cfa2bdc2580779b86a093cda46 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Mon, 9 Dec 2024 08:31:57 -0500 Subject: [PATCH 18/23] bugfix: able to select the initial wndow in the table --- .../lib/app/main_window.dart | 48 +++++++++---------- .../lib/app/window_manager_model.dart | 4 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/examples/multi_window_ref_app/lib/app/main_window.dart b/examples/multi_window_ref_app/lib/app/main_window.dart index f862ab215ed32..eda909b182fb9 100644 --- a/examples/multi_window_ref_app/lib/app/main_window.dart +++ b/examples/multi_window_ref_app/lib/app/main_window.dart @@ -6,20 +6,19 @@ import 'window_settings_dialog.dart'; import 'window_manager_model.dart'; class MainWindow extends StatefulWidget { - MainWindow({super.key, required WindowController mainController}) - : _mainKeyedController = - KeyedWindowController(controller: mainController); + MainWindow({super.key, required WindowController mainController}) { + _windowManagerModel.add( + KeyedWindowController(isMainWindow: true, controller: mainController)); + } - final KeyedWindowController _mainKeyedController; + final WindowManagerModel _windowManagerModel = WindowManagerModel(); + final WindowSettings _settings = WindowSettings(); @override State createState() => _MainWindowState(); } class _MainWindowState extends State { - final WindowManagerModel _windowManagerModel = WindowManagerModel(); - final WindowSettings _settings = WindowSettings(); - @override Widget build(BuildContext context) { final child = Scaffold( @@ -34,8 +33,7 @@ class _MainWindowState extends State { child: SingleChildScrollView( scrollDirection: Axis.vertical, child: _ActiveWindowsTable( - mainController: widget._mainKeyedController, - windowManagerModel: _windowManagerModel), + windowManagerModel: widget._windowManagerModel), ), ), Expanded( @@ -44,12 +42,12 @@ class _MainWindowState extends State { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ListenableBuilder( - listenable: _windowManagerModel, + listenable: widget._windowManagerModel, builder: (BuildContext context, Widget? child) { return _WindowCreatorCard( - selectedWindow: _windowManagerModel.selected, - windowManagerModel: _windowManagerModel, - windowSettings: _settings); + selectedWindow: widget._windowManagerModel.selected, + windowManagerModel: widget._windowManagerModel, + windowSettings: widget._settings); }) ], ), @@ -60,19 +58,21 @@ class _MainWindowState extends State { return ViewAnchor( view: ListenableBuilder( - listenable: _windowManagerModel, - builder: (BuildContext context, Widget? widget) { + listenable: widget._windowManagerModel, + builder: (BuildContext context, Widget? _) { final List childViews = []; for (final KeyedWindowController controller - in _windowManagerModel.windows) { - if (controller.parent == null) { + in widget._windowManagerModel.windows) { + if (controller.parent == null && !controller.isMainWindow) { childViews.add(WindowControllerRender( controller: controller.controller, key: controller.key, - windowSettings: _settings, - windowManagerModel: _windowManagerModel, - onDestroyed: () => _windowManagerModel.remove(controller), - onError: () => _windowManagerModel.remove(controller), + windowSettings: widget._settings, + windowManagerModel: widget._windowManagerModel, + onDestroyed: () => + widget._windowManagerModel.remove(controller), + onError: () => + widget._windowManagerModel.remove(controller), )); } } @@ -84,10 +84,8 @@ class _MainWindowState extends State { } class _ActiveWindowsTable extends StatelessWidget { - const _ActiveWindowsTable( - {required this.mainController, required this.windowManagerModel}); + const _ActiveWindowsTable({required this.windowManagerModel}); - final KeyedWindowController mainController; final WindowManagerModel windowManagerModel; @override @@ -130,7 +128,7 @@ class _ActiveWindowsTable extends StatelessWidget { ), numeric: true), ], - rows: ([mainController] + windowManagerModel.windows) + rows: (windowManagerModel.windows) .map((KeyedWindowController controller) { return DataRow( key: controller.key, diff --git a/examples/multi_window_ref_app/lib/app/window_manager_model.dart b/examples/multi_window_ref_app/lib/app/window_manager_model.dart index d641f05b690df..b21f5097c4218 100644 --- a/examples/multi_window_ref_app/lib/app/window_manager_model.dart +++ b/examples/multi_window_ref_app/lib/app/window_manager_model.dart @@ -1,10 +1,12 @@ import 'package:flutter/widgets.dart'; class KeyedWindowController { - KeyedWindowController({this.parent, required this.controller}); + KeyedWindowController( + {this.parent, this.isMainWindow = false, required this.controller}); final WindowController? parent; final WindowController controller; + final bool isMainWindow; final UniqueKey key = UniqueKey(); } From cb32700bc5a89c657badf3fe82b6021bf8f297ec Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Mon, 9 Dec 2024 08:52:21 -0500 Subject: [PATCH 19/23] Reimplement the rotating cube in the demo app --- .../lib/app/regular_window_content.dart | 181 ++++++++++++++---- 1 file changed, 147 insertions(+), 34 deletions(-) diff --git a/examples/multi_window_ref_app/lib/app/regular_window_content.dart b/examples/multi_window_ref_app/lib/app/regular_window_content.dart index 394c9b5cdc0fe..42e3673b35cc8 100644 --- a/examples/multi_window_ref_app/lib/app/regular_window_content.dart +++ b/examples/multi_window_ref_app/lib/app/regular_window_content.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:multi_window_ref_app/app/window_controller_render.dart'; import 'package:multi_window_ref_app/app/window_manager_model.dart'; import 'package:multi_window_ref_app/app/window_settings.dart'; +import 'dart:math'; +import 'package:vector_math/vector_math_64.dart'; -class RegularWindowContent extends StatelessWidget { +class RegularWindowContent extends StatefulWidget { const RegularWindowContent( {super.key, required this.window, @@ -14,54 +16,111 @@ class RegularWindowContent extends StatelessWidget { final WindowSettings windowSettings; final WindowManagerModel windowManagerModel; + @override + State createState() => _RegularWindowContentState(); +} + +class _RegularWindowContentState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _animation; + late final Color cubeColor; + + @override + void initState() { + super.initState(); + _animation = AnimationController( + vsync: this, + lowerBound: 0, + upperBound: 2 * pi, + duration: const Duration(seconds: 15), + )..repeat(); + cubeColor = _generateRandomDarkColor(); + } + + @override + void dispose() { + _animation.dispose(); + super.dispose(); + } + + Color _generateRandomDarkColor() { + final random = Random(); + const int lowerBound = 32; + const int span = 160; + int red = lowerBound + random.nextInt(span); + int green = lowerBound + random.nextInt(span); + int blue = lowerBound + random.nextInt(span); + return Color.fromARGB(255, red, green, blue); + } + @override Widget build(BuildContext context) { final child = Scaffold( - appBar: AppBar(title: Text('${window.type}')), + appBar: AppBar(title: Text('${widget.window.type}')), body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () { - windowManagerModel.add(KeyedWindowController( - controller: RegularWindowController())); - }, - child: const Text('Create Regular Window'), - ), - const SizedBox(height: 20), - ListenableBuilder( - listenable: window, - builder: (BuildContext context, Widget? _) { - return Text( - 'View #${window.view?.viewId ?? "Unknown"}\n' - 'Parent View: ${window.parentViewId}\n' - 'Logical Size: ${window.size?.width ?? "?"}\u00D7${window.size?.height ?? "?"}\n' - 'DPR: ${MediaQuery.of(context).devicePixelRatio}', - textAlign: TextAlign.center, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AnimatedBuilder( + animation: _animation, + builder: (context, child) { + return CustomPaint( + size: const Size(200, 200), + painter: _RotatedWireCube( + angle: _animation.value, color: cubeColor), ); - }) - ], - ), - ), + }, + ), + ], + ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + widget.windowManagerModel.add(KeyedWindowController( + controller: RegularWindowController())); + }, + child: const Text('Create Regular Window'), + ), + const SizedBox(height: 20), + ListenableBuilder( + listenable: widget.window, + builder: (BuildContext context, Widget? _) { + return Text( + 'View #${widget.window.view?.viewId ?? "Unknown"}\n' + 'Parent View: ${widget.window.parentViewId}\n' + 'Logical Size: ${widget.window.size?.width ?? "?"}\u00D7${widget.window.size?.height ?? "?"}\n' + 'DPR: ${MediaQuery.of(context).devicePixelRatio}', + textAlign: TextAlign.center, + ); + }) + ], + ), + ], + )), ); return ViewAnchor( view: ListenableBuilder( - listenable: windowManagerModel, + listenable: widget.windowManagerModel, builder: (BuildContext context, Widget? _) { final List childViews = []; for (final KeyedWindowController controller - in windowManagerModel.windows) { - if (controller.parent == window) { + in widget.windowManagerModel.windows) { + if (controller.parent == widget.window) { childViews.add(WindowControllerRender( controller: controller.controller, key: controller.key, - windowSettings: windowSettings, - windowManagerModel: windowManagerModel, - onDestroyed: () => windowManagerModel.remove(controller), - onError: () => windowManagerModel.remove(controller), + windowSettings: widget.windowSettings, + windowManagerModel: widget.windowManagerModel, + onDestroyed: () => + widget.windowManagerModel.remove(controller), + onError: () => widget.windowManagerModel.remove(controller), )); } } @@ -71,3 +130,57 @@ class RegularWindowContent extends StatelessWidget { child: child); } } + +class _RotatedWireCube extends CustomPainter { + static List vertices = [ + Vector3(-0.5, -0.5, -0.5), + Vector3(0.5, -0.5, -0.5), + Vector3(0.5, 0.5, -0.5), + Vector3(-0.5, 0.5, -0.5), + Vector3(-0.5, -0.5, 0.5), + Vector3(0.5, -0.5, 0.5), + Vector3(0.5, 0.5, 0.5), + Vector3(-0.5, 0.5, 0.5), + ]; + + static const List> edges = [ + [0, 1], [1, 2], [2, 3], [3, 0], // Front face + [4, 5], [5, 6], [6, 7], [7, 4], // Back face + [0, 4], [1, 5], [2, 6], [3, 7], // Connecting front and back + ]; + + final double angle; + final Color color; + + _RotatedWireCube({required this.angle, required this.color}); + + Offset scaleAndCenter(Vector3 point, double size, Offset center) { + final scale = size / 2; + return Offset(center.dx + point.x * scale, center.dy - point.y * scale); + } + + @override + void paint(Canvas canvas, Size size) { + final rotatedVertices = vertices + .map((vertex) => Matrix4.rotationX(angle).transformed3(vertex)) + .map((vertex) => Matrix4.rotationY(angle).transformed3(vertex)) + .map((vertex) => Matrix4.rotationZ(angle).transformed3(vertex)) + .toList(); + + final center = Offset(size.width / 2, size.height / 2); + + final paint = Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = 2; + + for (var edge in edges) { + final p1 = scaleAndCenter(rotatedVertices[edge[0]], size.width, center); + final p2 = scaleAndCenter(rotatedVertices[edge[1]], size.width, center); + canvas.drawLine(p1, p2, paint); + } + } + + @override + bool shouldRepaint(_RotatedWireCube oldDelegate) => true; +} From e4ba96396afa8a5fd7300cd28903c413b7dcf1e1 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Mon, 9 Dec 2024 13:55:03 -0500 Subject: [PATCH 20/23] PR feedback --- .../lib/app/main_window.dart | 4 +-- .../lib/app/window_controller_render.dart | 5 ++-- packages/flutter/lib/src/widgets/window.dart | 28 +++++++++---------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/examples/multi_window_ref_app/lib/app/main_window.dart b/examples/multi_window_ref_app/lib/app/main_window.dart index eda909b182fb9..cef69dbe13d0b 100644 --- a/examples/multi_window_ref_app/lib/app/main_window.dart +++ b/examples/multi_window_ref_app/lib/app/main_window.dart @@ -137,7 +137,7 @@ class _ActiveWindowsTable extends StatelessWidget { return Theme.of(context) .colorScheme .primary - .withOpacity(0.08); + .withAlpha(20); } return Colors.transparent; }), @@ -185,7 +185,7 @@ class _ActiveWindowsTable extends StatelessWidget { } class _WindowCreatorCard extends StatelessWidget { - _WindowCreatorCard( + const _WindowCreatorCard( {required this.selectedWindow, required this.windowManagerModel, required this.windowSettings}); diff --git a/examples/multi_window_ref_app/lib/app/window_controller_render.dart b/examples/multi_window_ref_app/lib/app/window_controller_render.dart index b7b82b6b3d0a1..fb9eb6d5e825d 100644 --- a/examples/multi_window_ref_app/lib/app/window_controller_render.dart +++ b/examples/multi_window_ref_app/lib/app/window_controller_render.dart @@ -4,20 +4,19 @@ import 'window_manager_model.dart'; import 'window_settings.dart'; class WindowControllerRender extends StatelessWidget { - WindowControllerRender( + const WindowControllerRender( {required this.controller, required this.onDestroyed, required this.onError, required this.windowSettings, required this.windowManagerModel, - required this.key}); + required super.key}); final WindowController controller; final VoidCallback onDestroyed; final VoidCallback onError; final WindowSettings windowSettings; final WindowManagerModel windowManagerModel; - final Key key; @override Widget build(BuildContext context) { diff --git a/packages/flutter/lib/src/widgets/window.dart b/packages/flutter/lib/src/widgets/window.dart index 02a66dceaac4c..d368f484ce7b1 100644 --- a/packages/flutter/lib/src/widgets/window.dart +++ b/packages/flutter/lib/src/widgets/window.dart @@ -80,9 +80,8 @@ class RegularWindow extends StatefulWidget { this.onDestroyed, this.onError, super.key, - required Size preferredSize, - required this.child}) - : _preferredSize = preferredSize; + required this.preferredSize, + required this.child}); /// Controller for this widget. final RegularWindowController? controller; @@ -93,7 +92,8 @@ class RegularWindow extends StatefulWidget { /// Called when an error is encountered during the creation of this widget. final void Function(String?)? onError; - final Size _preferredSize; + /// Preferred size of the window. + final Size preferredSize; /// The content rendered into this window. final Widget child; @@ -113,15 +113,15 @@ class _RegularWindowState extends State { void initState() { super.initState(); final Future createRegularFuture = - createRegular(size: widget._preferredSize); + createRegular(size: widget.preferredSize); setState(() { _future = createRegularFuture; }); createRegularFuture.then((WindowCreationResult metadata) async { - _viewId = metadata.flView.viewId; + _viewId = metadata.view.viewId; if (widget.controller != null) { - widget.controller!.view = metadata.flView; + widget.controller!.view = metadata.view; widget.controller!.parentViewId = metadata.parent; widget.controller!.size = metadata.size; } @@ -131,7 +131,7 @@ class _RegularWindowState extends State { _WindowingAppContext.of(context); assert(windowingAppContext != null); _listener = _WindowListener( - viewId: metadata.flView.viewId, + viewId: metadata.view.viewId, onChanged: (_WindowChangeProperties properties) { if (widget.controller == null) { return; @@ -165,7 +165,7 @@ class _RegularWindowState extends State { } // In the event that we're being disposed before we've been destroyed - // we need to destroy ther window on our way out. + // we need to destroy the window on our way out. if (!_hasBeenDestroyed && _viewId != null) { // In the event of an argument error, we do nothing. We assume that // the window has been successfully destroyed somehow else. @@ -189,9 +189,9 @@ class _RegularWindowState extends State { } return View( - view: metadata.data!.flView, + view: metadata.data!.view, child: WindowContext( - viewId: metadata.data!.flView.viewId, child: widget.child)); + viewId: metadata.data!.view.viewId, child: widget.child)); }); } } @@ -219,13 +219,13 @@ class WindowContext extends InheritedWidget { class WindowCreationResult { /// Creates a new window. WindowCreationResult( - {required this.flView, + {required this.view, required this.archetype, required this.size, this.parent}); /// The view associated with the window. - final FlutterView flView; + final FlutterView view; /// The archetype of the window. final WindowArchetype archetype; @@ -271,7 +271,7 @@ Future _createWindow( ); return WindowCreationResult( - flView: flView, + view: flView, archetype: archetype, size: Size((size[0]! as int).toDouble(), (size[1]! as int).toDouble()), parent: parentViewId); From 589621ba6b67794965e3dcc87f2691b7a471efed Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Mon, 9 Dec 2024 14:07:35 -0500 Subject: [PATCH 21/23] Rendering correct text --- .../lib/app/regular_window_content.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/multi_window_ref_app/lib/app/regular_window_content.dart b/examples/multi_window_ref_app/lib/app/regular_window_content.dart index 42e3673b35cc8..45089e878f0a8 100644 --- a/examples/multi_window_ref_app/lib/app/regular_window_content.dart +++ b/examples/multi_window_ref_app/lib/app/regular_window_content.dart @@ -55,6 +55,8 @@ class _RegularWindowContentState extends State @override Widget build(BuildContext context) { + final dpr = MediaQuery.of(context).devicePixelRatio; + final child = Scaffold( appBar: AppBar(title: Text('${widget.window.type}')), body: Center( @@ -94,8 +96,9 @@ class _RegularWindowContentState extends State return Text( 'View #${widget.window.view?.viewId ?? "Unknown"}\n' 'Parent View: ${widget.window.parentViewId}\n' - 'Logical Size: ${widget.window.size?.width ?? "?"}\u00D7${widget.window.size?.height ?? "?"}\n' - 'DPR: ${MediaQuery.of(context).devicePixelRatio}', + 'View Size: ${(widget.window.view!.physicalSize.width / dpr).toStringAsFixed(1)}\u00D7${(widget.window.view!.physicalSize.height / dpr).toStringAsFixed(1)}\n' + 'Window Size: ${widget.window.size?.width}\u00D7${widget.window.size?.height}\n' + 'Device Pixel Ratio: $dpr', textAlign: TextAlign.center, ); }) From 539333bfab40c7cb30e11b2b7aa389031df82278 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Mon, 9 Dec 2024 17:01:24 -0500 Subject: [PATCH 22/23] Reverting back to the old runner code --- .../windows/CMakeLists.txt | 31 +-- .../windows/flutter/CMakeLists.txt | 12 +- .../windows/runner/CMakeLists.txt | 3 +- .../windows/runner/Runner.rc | 14 +- .../windows/runner/flutter_window.cpp | 65 +++++ .../windows/runner/flutter_window.h | 37 +++ .../windows/runner/main.cpp | 23 +- .../windows/runner/resource.h | 4 + .../windows/runner/resources/app_icon.ico | Bin 33772 -> 0 bytes .../windows/runner/utils.cpp | 7 +- .../windows/runner/utils.h | 4 + .../windows/runner/win32_window.cpp | 249 ++++++++++++++++++ .../windows/runner/win32_window.h | 102 +++++++ 13 files changed, 500 insertions(+), 51 deletions(-) create mode 100644 examples/multi_window_ref_app/windows/runner/flutter_window.cpp create mode 100644 examples/multi_window_ref_app/windows/runner/flutter_window.h delete mode 100644 examples/multi_window_ref_app/windows/runner/resources/app_icon.ico create mode 100644 examples/multi_window_ref_app/windows/runner/win32_window.cpp create mode 100644 examples/multi_window_ref_app/windows/runner/win32_window.h diff --git a/examples/multi_window_ref_app/windows/CMakeLists.txt b/examples/multi_window_ref_app/windows/CMakeLists.txt index 4450980427578..8d3590ee41cb0 100644 --- a/examples/multi_window_ref_app/windows/CMakeLists.txt +++ b/examples/multi_window_ref_app/windows/CMakeLists.txt @@ -1,16 +1,13 @@ -# Project-level configuration. cmake_minimum_required(VERSION 3.14) -project(multi_window_ref_app LANGUAGES CXX) +project(dartpad_curve2_d_0 LANGUAGES CXX) -# The name of the executable created for the application. Change this to change -# the on-disk name of your application. -set(BINARY_NAME "multi_window_ref_app") +set(BINARY_NAME "dartpad_curve2_d_0") -# Explicitly opt in to modern CMake behaviors to avoid warnings with recent -# versions of CMake. cmake_policy(VERSION 3.14...3.25) -# Define build configuration option. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Configure build options. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" @@ -23,7 +20,7 @@ else() "Debug" "Profile" "Release") endif() endif() -# Define settings for the Profile build mode. + set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") @@ -33,10 +30,6 @@ set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. -# -# Be cautious about adding new options here, as plugins use this function by -# default. In most cases, you should add new options to specific targets instead -# of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") @@ -45,14 +38,14 @@ function(APPLY_STANDARD_SETTINGS TARGET) target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() -# Flutter library and tool build rules. set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) -# Application build; see runner/CMakeLists.txt. +# Application build add_subdirectory("runner") - # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) @@ -87,12 +80,6 @@ if(PLUGIN_BUNDLED_LIBRARIES) COMPONENT Runtime) endif() -# Copy the native assets provided by the build.dart from all packages. -set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") -install(DIRECTORY "${NATIVE_ASSETS_DIR}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") diff --git a/examples/multi_window_ref_app/windows/flutter/CMakeLists.txt b/examples/multi_window_ref_app/windows/flutter/CMakeLists.txt index 4bff75b03b7aa..903f4899d6fce 100644 --- a/examples/multi_window_ref_app/windows/flutter/CMakeLists.txt +++ b/examples/multi_window_ref_app/windows/flutter/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.14) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") -set(CMAKE_CXX_STANDARD 20) # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) @@ -46,10 +45,6 @@ list(APPEND CPP_WRAPPER_SOURCES_CORE "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_WINDOWING - "windowing.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_WINDOWING PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) @@ -57,16 +52,12 @@ list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" - "flutter_window_controller.cc" - "flutter_win32_window.cc" - "win32_window.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_WINDOWING} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) @@ -83,7 +74,6 @@ add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_WINDOWING} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) @@ -101,7 +91,7 @@ set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_WINDOWING} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env diff --git a/examples/multi_window_ref_app/windows/runner/CMakeLists.txt b/examples/multi_window_ref_app/windows/runner/CMakeLists.txt index ae89fc0a64873..17411a8ab8eb7 100644 --- a/examples/multi_window_ref_app/windows/runner/CMakeLists.txt +++ b/examples/multi_window_ref_app/windows/runner/CMakeLists.txt @@ -7,8 +7,10 @@ project(runner LANGUAGES CXX) # # 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" @@ -31,7 +33,6 @@ 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. diff --git a/examples/multi_window_ref_app/windows/runner/Runner.rc b/examples/multi_window_ref_app/windows/runner/Runner.rc index ebdce3fdd6e39..34a087abcbc73 100644 --- a/examples/multi_window_ref_app/windows/runner/Runner.rc +++ b/examples/multi_window_ref_app/windows/runner/Runner.rc @@ -52,7 +52,7 @@ END // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -IDI_APP_ICON ICON "resources\\app_icon.ico" +// IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// @@ -89,13 +89,13 @@ BEGIN BEGIN BLOCK "040904e4" BEGIN - VALUE "CompanyName", "com.example" "\0" - VALUE "FileDescription", "multi_window_ref_app" "\0" + VALUE "CompanyName", "The Flutter Authors" "\0" + VALUE "FileDescription", "A sample application demonstrating Flutter APIs." "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "multi_window_ref_app" "\0" - VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0" - VALUE "OriginalFilename", "multi_window_ref_app.exe" "\0" - VALUE "ProductName", "multi_window_ref_app" "\0" + VALUE "InternalName", "Flutter API Sample" "\0" + VALUE "LegalCopyright", "Copyright 2014 The Flutter Authors. All rights reserved." "\0" + VALUE "OriginalFilename", "flutter_api_samples.exe" "\0" + VALUE "ProductName", "Flutter API Sample" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END diff --git a/examples/multi_window_ref_app/windows/runner/flutter_window.cpp b/examples/multi_window_ref_app/windows/runner/flutter_window.cpp new file mode 100644 index 0000000000000..9cbd3109c3fee --- /dev/null +++ b/examples/multi_window_ref_app/windows/runner/flutter_window.cpp @@ -0,0 +1,65 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter_window.h" + +#include + +#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( + 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()); + 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 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/examples/multi_window_ref_app/windows/runner/flutter_window.h b/examples/multi_window_ref_app/windows/runner/flutter_window.h new file mode 100644 index 0000000000000..bbc5836c018a2 --- /dev/null +++ b/examples/multi_window_ref_app/windows/runner/flutter_window.h @@ -0,0 +1,37 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#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_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/examples/multi_window_ref_app/windows/runner/main.cpp b/examples/multi_window_ref_app/windows/runner/main.cpp index 6c975ca798ddd..d1f23e5b8047f 100644 --- a/examples/multi_window_ref_app/windows/runner/main.cpp +++ b/examples/multi_window_ref_app/windows/runner/main.cpp @@ -1,9 +1,12 @@ -#include -#include +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + #include -#include +#include #include +#include "flutter_window.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, @@ -20,14 +23,18 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, flutter::DartProject project(L"data"); - auto command_line_arguments{GetCommandLineArguments()}; + std::vector command_line_arguments = + GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); - auto const engine{std::make_shared(project)}; - RegisterPlugins(engine.get()); - flutter::FlutterWindowController::GetInstance().SetEngine(engine); - engine->Run(); + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"flutter_api_samples", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); ::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) { diff --git a/examples/multi_window_ref_app/windows/runner/resource.h b/examples/multi_window_ref_app/windows/runner/resource.h index 66a65d1e4a79f..c245ff19cb580 100644 --- a/examples/multi_window_ref_app/windows/runner/resource.h +++ b/examples/multi_window_ref_app/windows/runner/resource.h @@ -1,3 +1,7 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc diff --git a/examples/multi_window_ref_app/windows/runner/resources/app_icon.ico b/examples/multi_window_ref_app/windows/runner/resources/app_icon.ico deleted file mode 100644 index c04e20caf6370ebb9253ad831cc31de4a9c965f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33772 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 diff --git a/examples/multi_window_ref_app/windows/runner/utils.cpp b/examples/multi_window_ref_app/windows/runner/utils.cpp index 3a0b46511a71b..6abcd65042070 100644 --- a/examples/multi_window_ref_app/windows/runner/utils.cpp +++ b/examples/multi_window_ref_app/windows/runner/utils.cpp @@ -1,3 +1,7 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + #include "utils.h" #include @@ -49,7 +53,6 @@ std::string Utf8FromUtf16(const wchar_t* utf16_string) { 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; @@ -57,7 +60,7 @@ std::string Utf8FromUtf16(const wchar_t* utf16_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); + -1, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } diff --git a/examples/multi_window_ref_app/windows/runner/utils.h b/examples/multi_window_ref_app/windows/runner/utils.h index 3879d54755798..54414c989ba71 100644 --- a/examples/multi_window_ref_app/windows/runner/utils.h +++ b/examples/multi_window_ref_app/windows/runner/utils.h @@ -1,3 +1,7 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ diff --git a/examples/multi_window_ref_app/windows/runner/win32_window.cpp b/examples/multi_window_ref_app/windows/runner/win32_window.cpp new file mode 100644 index 0000000000000..8fef19b0fbfd4 --- /dev/null +++ b/examples/multi_window_ref_app/windows/runner/win32_window.cpp @@ -0,0 +1,249 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// 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(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 + +// 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::CreateAndShow(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(origin.x), + static_cast(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 | WS_VISIBLE, + 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; + } + + return OnCreate(); +} + +// 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); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(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(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; + } + + 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( + 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. +} diff --git a/examples/multi_window_ref_app/windows/runner/win32_window.h b/examples/multi_window_ref_app/windows/runner/win32_window.h new file mode 100644 index 0000000000000..33786ecd6f0b9 --- /dev/null +++ b/examples/multi_window_ref_app/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// 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 and shows a win32 window with |title| and position and size 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); + + // 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; + + 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_ From aba015df63024ac3baee13a6f7d5105f17476afc Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Tue, 10 Dec 2024 14:51:44 -0500 Subject: [PATCH 23/23] Update runner code to work with the latest & greatest --- .../windows/CMakeLists.txt | 31 +++++++++++++------ .../windows/flutter/CMakeLists.txt | 9 +++++- .../windows/runner/CMakeLists.txt | 3 +- .../windows/runner/main.cpp | 23 +++++--------- 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/examples/multi_window_ref_app/windows/CMakeLists.txt b/examples/multi_window_ref_app/windows/CMakeLists.txt index 8d3590ee41cb0..4450980427578 100644 --- a/examples/multi_window_ref_app/windows/CMakeLists.txt +++ b/examples/multi_window_ref_app/windows/CMakeLists.txt @@ -1,13 +1,16 @@ +# Project-level configuration. cmake_minimum_required(VERSION 3.14) -project(dartpad_curve2_d_0 LANGUAGES CXX) +project(multi_window_ref_app LANGUAGES CXX) -set(BINARY_NAME "dartpad_curve2_d_0") +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "multi_window_ref_app") +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. cmake_policy(VERSION 3.14...3.25) -set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") - -# Configure build options. +# Define build configuration option. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" @@ -20,7 +23,7 @@ else() "Debug" "Profile" "Release") endif() endif() - +# Define settings for the Profile build mode. set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") @@ -30,6 +33,10 @@ set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") @@ -38,14 +45,14 @@ function(APPLY_STANDARD_SETTINGS TARGET) target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") - # Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) -# Application build +# Application build; see runner/CMakeLists.txt. add_subdirectory("runner") + # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) @@ -80,6 +87,12 @@ if(PLUGIN_BUNDLED_LIBRARIES) COMPONENT Runtime) endif() +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") diff --git a/examples/multi_window_ref_app/windows/flutter/CMakeLists.txt b/examples/multi_window_ref_app/windows/flutter/CMakeLists.txt index 903f4899d6fce..2eea2a3218180 100644 --- a/examples/multi_window_ref_app/windows/flutter/CMakeLists.txt +++ b/examples/multi_window_ref_app/windows/flutter/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.14) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") +set(CMAKE_CXX_STANDARD 20) # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) @@ -52,12 +53,16 @@ list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" + "flutter_window_controller.cc" + "flutter_win32_window.cc" + "win32_window.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_WINDOWING} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) @@ -74,6 +79,7 @@ add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_WINDOWING} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) @@ -91,7 +97,7 @@ set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_WINDOWING} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env @@ -104,6 +110,7 @@ add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_WINDOWING} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) diff --git a/examples/multi_window_ref_app/windows/runner/CMakeLists.txt b/examples/multi_window_ref_app/windows/runner/CMakeLists.txt index 17411a8ab8eb7..ae89fc0a64873 100644 --- a/examples/multi_window_ref_app/windows/runner/CMakeLists.txt +++ b/examples/multi_window_ref_app/windows/runner/CMakeLists.txt @@ -7,10 +7,8 @@ project(runner LANGUAGES CXX) # # 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" @@ -33,6 +31,7 @@ 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. diff --git a/examples/multi_window_ref_app/windows/runner/main.cpp b/examples/multi_window_ref_app/windows/runner/main.cpp index d1f23e5b8047f..6c975ca798ddd 100644 --- a/examples/multi_window_ref_app/windows/runner/main.cpp +++ b/examples/multi_window_ref_app/windows/runner/main.cpp @@ -1,12 +1,9 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - +#include +#include #include -#include +#include #include -#include "flutter_window.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, @@ -23,18 +20,14 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, flutter::DartProject project(L"data"); - std::vector command_line_arguments = - GetCommandLineArguments(); + auto 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.CreateAndShow(L"flutter_api_samples", origin, size)) { - return EXIT_FAILURE; - } - window.SetQuitOnClose(true); + auto const engine{std::make_shared(project)}; + RegisterPlugins(engine.get()); + flutter::FlutterWindowController::GetInstance().SetEngine(engine); + engine->Run(); ::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) {