| // 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. |
| |
| import 'dart:convert' show utf8; |
| import 'dart:ffi' hide Size; |
| import 'dart:io'; |
| import 'dart:ui' show Display, FlutterView; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/rendering.dart'; |
| |
| import '../foundation/_features.dart'; |
| import '_window.dart'; |
| import '_window_positioner.dart'; |
| import 'binding.dart'; |
| |
| // Do not import this file in production applications or packages published |
| // to pub.dev. Flutter will make breaking changes to this file, even in patch |
| // versions. |
| // |
| // All APIs in this file must be private or must: |
| // |
| // 1. Have the `@internal` attribute. |
| // 2. Throw an `UnsupportedError` if `isWindowingEnabled` |
| // is `false`. |
| // |
| // See: https://github.com/flutter/flutter/issues/30701. |
| |
| const String _kWindowingDisabledErrorMessage = ''' |
| Windowing APIs are not enabled. |
| |
| Windowing APIs are currently experimental. Do not use windowing APIs in |
| production applications or plugins published to pub.dev. |
| |
| To try experimental windowing APIs: |
| 1. Switch to Flutter's main release channel. |
| 2. Turn on the windowing feature flag. |
| |
| See: https://github.com/flutter/flutter/issues/30701. |
| '''; |
| |
| /// [WindowingOwner] implementation for macOS. |
| /// |
| /// If [Platform.isMacOS] is false, then the constructor will throw an |
| /// [UnsupportedError]. |
| /// |
| /// {@macro flutter.widgets.windowing.experimental} |
| /// |
| /// See also: |
| /// |
| /// * [WindowingOwner], the abstract class that manages native windows. |
| class WindowingOwnerMacOS extends WindowingOwner { |
| /// Creates a new [WindowingOwnerMacOS] instance. |
| /// |
| /// {@macro flutter.widgets.windowing.experimental} |
| /// |
| /// See also: |
| /// |
| /// * [WindowingOwner], the abstract class that manages native windows. |
| @internal |
| WindowingOwnerMacOS() { |
| if (!isWindowingEnabled) { |
| throw UnsupportedError(_kWindowingDisabledErrorMessage); |
| } |
| |
| if (!Platform.isMacOS) { |
| throw UnsupportedError('Only available on the macOS platform'); |
| } |
| |
| assert( |
| WidgetsBinding.instance.platformDispatcher.engineId != null, |
| 'WindowingOwnerMacOS must be created after the engine has been initialized.', |
| ); |
| } |
| |
| @override |
| RegularWindowController createRegularWindowController({ |
| required RegularWindowControllerDelegate delegate, |
| Size? preferredSize, |
| BoxConstraints? preferredConstraints, |
| String? title, |
| }) { |
| final res = RegularWindowControllerMacOS( |
| owner: this, |
| delegate: delegate, |
| preferredSize: preferredSize, |
| title: title, |
| ); |
| _activeControllers.add(res); |
| return res; |
| } |
| |
| @override |
| DialogWindowController createDialogWindowController({ |
| required DialogWindowControllerDelegate delegate, |
| Size? preferredSize, |
| BoxConstraints? preferredConstraints, |
| BaseWindowController? parent, |
| String? title, |
| }) { |
| final res = DialogWindowControllerMacOS( |
| owner: this, |
| delegate: delegate, |
| preferredSize: preferredSize, |
| parent: parent, |
| title: title, |
| ); |
| _activeControllers.add(res); |
| return res; |
| } |
| |
| @internal |
| @override |
| TooltipWindowController createTooltipWindowController({ |
| required TooltipWindowControllerDelegate delegate, |
| required BoxConstraints preferredConstraints, |
| required bool isSizedToContent, |
| required Rect anchorRect, |
| required WindowPositioner positioner, |
| required BaseWindowController parent, |
| }) { |
| final res = TooltipWindowControllerMacOS( |
| owner: this, |
| delegate: delegate, |
| contentSizeConstraints: preferredConstraints, |
| anchorRect: anchorRect, |
| positioner: positioner, |
| parent: parent, |
| ); |
| _activeControllers.add(res); |
| return res; |
| } |
| |
| @internal |
| @override |
| PopupWindowController createPopupWindowController({ |
| required PopupWindowControllerDelegate delegate, |
| required BoxConstraints preferredConstraints, |
| required Rect anchorRect, |
| required WindowPositioner positioner, |
| required BaseWindowController parent, |
| }) { |
| throw UnimplementedError('Popup windows are not yet implemented on MacOS.'); |
| } |
| |
| final List<_WindowControllerMixin> _activeControllers = <_WindowControllerMixin>[]; |
| |
| /// Returns the window handle for the given [view], or null is the window |
| /// handle is not available. |
| /// |
| /// The window handle is a pointer to the NSWindow instance. |
| static Pointer<Void> getWindowHandle(FlutterView view) { |
| return _MacOSPlatformInterface.getWindowHandle( |
| PlatformDispatcher.instance.engineId!, |
| view.viewId, |
| ); |
| } |
| } |
| |
| mixin _WindowControllerMixin { |
| void _initController(WindowingOwnerMacOS owner) { |
| if (!isWindowingEnabled) { |
| throw UnsupportedError(_kWindowingDisabledErrorMessage); |
| } |
| |
| _onShouldClose = NativeCallable<Void Function()>.isolateLocal(_handleOnShouldClose); |
| _onWillClose = NativeCallable<Void Function()>.isolateLocal(_handleOnWillClose); |
| _onResize = NativeCallable<Void Function()>.isolateLocal(_handleOnResize); |
| _onGetWindowPosition = |
| NativeCallable< |
| Pointer<_Rect> Function( |
| Pointer<_Size> childSize, |
| Pointer<_Rect> parentRect, |
| Pointer<_Rect> outputRect, |
| ) |
| >.isolateLocal(_handleOnGetWindowPosition); |
| _owner = owner; |
| _owner._activeControllers.add(this); |
| } |
| |
| void _handleOnShouldClose(); |
| |
| void _handleOnResize(); |
| |
| @mustCallSuper |
| void _handleOnWillClose() { |
| _onWillClose.close(); |
| _onShouldClose.close(); |
| _onResize.close(); |
| _onGetWindowPosition.close(); |
| _destroyed = true; |
| _owner._activeControllers.remove(this); |
| } |
| |
| @mustCallSuper |
| Pointer<_Rect> _handleOnGetWindowPosition( |
| Pointer<_Size> childSize, |
| Pointer<_Rect> parentRect, |
| Pointer<_Rect> outputRect, |
| ) { |
| return Pointer<_Rect>.fromAddress(0); |
| } |
| |
| void _ensureNotDestroyed() { |
| if (_destroyed) { |
| throw StateError('Window has been destroyed.'); |
| } |
| } |
| |
| FlutterView get rootView; |
| |
| /// Returns window handle for the current window. |
| /// The handle is a pointer to NSWindow instance. |
| Pointer<Void> getWindowHandle() { |
| _ensureNotDestroyed(); |
| return WindowingOwnerMacOS.getWindowHandle(rootView); |
| } |
| |
| Size get contentSize { |
| _ensureNotDestroyed(); |
| return _MacOSPlatformInterface.getWindowContentSize(getWindowHandle()); |
| } |
| |
| void destroy() { |
| if (_destroyed) { |
| return; |
| } |
| final Pointer<Void> handle = getWindowHandle(); |
| _MacOSPlatformInterface.destroyWindow(handle); |
| } |
| |
| bool get destroyed => _destroyed; |
| |
| bool _destroyed = false; |
| |
| late final NativeCallable<Void Function()> _onShouldClose; |
| late final NativeCallable<Void Function()> _onWillClose; |
| late final NativeCallable<Void Function()> _onResize; |
| late final NativeCallable< |
| Pointer<_Rect> Function( |
| Pointer<_Size> childSize, |
| Pointer<_Rect> parentRect, |
| Pointer<_Rect> outputRect, |
| ) |
| > |
| _onGetWindowPosition; |
| |
| late final WindowingOwnerMacOS _owner; |
| } |
| |
| /// MacOS specific implementation of [TooltipWindowController]. |
| /// |
| /// /// {@macro flutter.widgets.windowing.experimental} |
| class TooltipWindowControllerMacOS extends TooltipWindowController with _WindowControllerMixin { |
| /// Creates a new tooltip window controller for macOS. |
| TooltipWindowControllerMacOS({ |
| required WindowingOwnerMacOS owner, |
| required TooltipWindowControllerDelegate delegate, |
| required BoxConstraints contentSizeConstraints, |
| required BaseWindowController parent, |
| required Rect anchorRect, |
| required WindowPositioner positioner, |
| }) : _anchorRect = anchorRect, |
| _positioner = positioner, |
| _delegate = delegate, |
| _parent = parent, |
| super.empty() { |
| _initController(owner); |
| |
| final int viewId = _MacOSPlatformInterface.createTooltipWindow( |
| preferredConstraints: contentSizeConstraints, |
| onShouldClose: _onShouldClose.nativeFunction, |
| onWillClose: _onWillClose.nativeFunction, |
| onNotifyListeners: _onResize.nativeFunction, |
| onGetWindowPosition: _onGetWindowPosition.nativeFunction, |
| parentViewId: parent.rootView.viewId, |
| ); |
| |
| final FlutterView flutterView = WidgetsBinding.instance.platformDispatcher.views.firstWhere( |
| (FlutterView view) => view.viewId == viewId, |
| ); |
| rootView = flutterView; |
| } |
| |
| @override |
| void updatePosition({Rect? anchorRect, WindowPositioner? positioner}) { |
| if (anchorRect != null) { |
| _anchorRect = anchorRect; |
| } |
| if (positioner != null) { |
| _positioner = positioner; |
| } |
| _MacOSPlatformInterface.updateWindowPosition(getWindowHandle()); |
| } |
| |
| @override |
| void _handleOnShouldClose() { |
| destroy(); |
| } |
| |
| @override |
| void _handleOnWillClose() { |
| super._handleOnWillClose(); |
| _delegate.onWindowDestroyed(); |
| } |
| |
| @override |
| void _handleOnResize() { |
| notifyListeners(); |
| } |
| |
| @override |
| Pointer<_Rect> _handleOnGetWindowPosition( |
| Pointer<_Size> childSize, |
| Pointer<_Rect> parentRect, |
| Pointer<_Rect> outputRect, |
| ) { |
| super._handleOnGetWindowPosition(childSize, parentRect, outputRect); |
| final Pointer<_Rect> result = _allocator<_Rect>(); |
| final Rect targetRect = _positioner.placeWindow( |
| childSize: childSize.ref.toSize(), |
| anchorRect: _anchorRect.translate(parentRect.ref.left, parentRect.ref.top), |
| parentRect: parentRect.ref.toRect(), |
| displayRect: outputRect.ref.toRect(), |
| ); |
| result.ref.left = targetRect.left; |
| result.ref.top = targetRect.top; |
| result.ref.width = childSize.ref.width; |
| result.ref.height = childSize.ref.height; |
| return result; |
| } |
| |
| @override |
| BaseWindowController get parent => _parent; |
| |
| @override |
| void setConstraints(BoxConstraints constraints) { |
| _ensureNotDestroyed(); |
| _MacOSPlatformInterface.setWindowConstraints(getWindowHandle(), constraints); |
| } |
| |
| final TooltipWindowControllerDelegate _delegate; |
| final BaseWindowController _parent; |
| WindowPositioner _positioner; |
| Rect _anchorRect; |
| } |
| |
| /// Implementation of [RegularWindowController] for the macOS platform. |
| /// |
| /// {@macro flutter.widgets.windowing.experimental} |
| /// |
| /// See also: |
| /// |
| /// * [RegularWindowController], the base class for regular windows. |
| class RegularWindowControllerMacOS extends RegularWindowController with _WindowControllerMixin { |
| /// Creates a new regular window controller for macOS. When this constructor |
| /// completes the FlutterView is created and framework is aware of it. |
| RegularWindowControllerMacOS({ |
| required WindowingOwnerMacOS owner, |
| required RegularWindowControllerDelegate delegate, |
| required Size? preferredSize, |
| BoxConstraints? preferredConstraints, |
| String? title, |
| }) : _delegate = delegate, |
| super.empty() { |
| _initController(owner); |
| |
| final int viewId = _MacOSPlatformInterface.createRegularWindow( |
| preferredSize: preferredSize, |
| preferredConstraints: preferredConstraints, |
| onShouldClose: _onShouldClose.nativeFunction, |
| onWillClose: _onWillClose.nativeFunction, |
| onNotifyListeners: _onResize.nativeFunction, |
| ); |
| final FlutterView flutterView = WidgetsBinding.instance.platformDispatcher.views.firstWhere( |
| (FlutterView view) => view.viewId == viewId, |
| ); |
| rootView = flutterView; |
| if (title != null) { |
| setTitle(title); |
| } |
| } |
| |
| @override |
| void _handleOnShouldClose() { |
| _delegate.onWindowCloseRequested(this); |
| } |
| |
| @override |
| void _handleOnWillClose() { |
| super._handleOnWillClose(); |
| _delegate.onWindowDestroyed(); |
| } |
| |
| @override |
| void _handleOnResize() { |
| notifyListeners(); |
| } |
| |
| @override |
| @internal |
| void setSize(Size size) { |
| _ensureNotDestroyed(); |
| _MacOSPlatformInterface.setWindowContentSize(getWindowHandle(), size); |
| } |
| |
| @override |
| @internal |
| void setConstraints(BoxConstraints constraints) { |
| _ensureNotDestroyed(); |
| _MacOSPlatformInterface.setWindowConstraints(getWindowHandle(), constraints); |
| } |
| |
| @override |
| void setTitle(String title) { |
| _ensureNotDestroyed(); |
| _MacOSPlatformInterface.setWindowTitle(getWindowHandle(), title); |
| notifyListeners(); |
| } |
| |
| @override |
| Size get contentSize { |
| _ensureNotDestroyed(); |
| return _MacOSPlatformInterface.getWindowContentSize(getWindowHandle()); |
| } |
| |
| @override |
| void activate() { |
| _ensureNotDestroyed(); |
| _MacOSPlatformInterface.activate(getWindowHandle()); |
| } |
| |
| @override |
| void setMaximized(bool maximized) { |
| _ensureNotDestroyed(); |
| _MacOSPlatformInterface.setMaximized(getWindowHandle(), maximized); |
| } |
| |
| @override |
| bool get isMaximized { |
| _ensureNotDestroyed(); |
| return _MacOSPlatformInterface.isMaximized(getWindowHandle()); |
| } |
| |
| @override |
| void setMinimized(bool minimized) { |
| _ensureNotDestroyed(); |
| if (minimized) { |
| _MacOSPlatformInterface.minimize(getWindowHandle()); |
| } else { |
| _MacOSPlatformInterface.unminimize(getWindowHandle()); |
| } |
| } |
| |
| @override |
| bool get isMinimized { |
| _ensureNotDestroyed(); |
| return _MacOSPlatformInterface.isMinimized(getWindowHandle()); |
| } |
| |
| @override |
| void setFullscreen(bool fullscreen, {Display? display}) { |
| _ensureNotDestroyed(); |
| _MacOSPlatformInterface.setFullscreen(getWindowHandle(), fullscreen); |
| } |
| |
| @override |
| bool get isFullscreen { |
| _ensureNotDestroyed(); |
| return _MacOSPlatformInterface.isFullscreen(getWindowHandle()); |
| } |
| |
| final RegularWindowControllerDelegate _delegate; |
| |
| @override |
| bool get isActivated => _MacOSPlatformInterface.isActivated(getWindowHandle()); |
| |
| @override |
| String get title => _MacOSPlatformInterface.getTitle(getWindowHandle()); |
| } |
| |
| /// Implementation of [DialogWindowController] for the macOS platform. |
| /// |
| /// {@macro flutter.widgets.windowing.experimental} |
| /// |
| /// See also: |
| /// |
| /// * [DialogWindowController], the base class for dialog windows. |
| class DialogWindowControllerMacOS extends DialogWindowController with _WindowControllerMixin { |
| /// Creates a new regular window controller for macOS. When this constructor |
| /// completes the FlutterView is created and framework is aware of it. |
| DialogWindowControllerMacOS({ |
| required WindowingOwnerMacOS owner, |
| required DialogWindowControllerDelegate delegate, |
| required Size? preferredSize, |
| this.parent, |
| BoxConstraints? preferredConstraints, |
| String? title, |
| }) : _delegate = delegate, |
| super.empty() { |
| _initController(owner); |
| |
| final int viewId = _MacOSPlatformInterface.createDialogWindow( |
| preferredSize: preferredSize, |
| preferredConstraints: preferredConstraints, |
| onShouldClose: _onShouldClose.nativeFunction, |
| onWillClose: _onWillClose.nativeFunction, |
| onNotifyListeners: _onResize.nativeFunction, |
| parentViewId: parent?.rootView.viewId, |
| ); |
| final FlutterView flutterView = WidgetsBinding.instance.platformDispatcher.views.firstWhere( |
| (FlutterView view) => view.viewId == viewId, |
| ); |
| rootView = flutterView; |
| if (title != null) { |
| setTitle(title); |
| } |
| } |
| |
| @override |
| void _handleOnShouldClose() { |
| _delegate.onWindowCloseRequested(this); |
| } |
| |
| @override |
| void _handleOnWillClose() { |
| super._handleOnWillClose(); |
| _delegate.onWindowDestroyed(); |
| } |
| |
| @override |
| void _handleOnResize() { |
| notifyListeners(); |
| } |
| |
| @override |
| @internal |
| void setSize(Size size) { |
| _ensureNotDestroyed(); |
| _MacOSPlatformInterface.setWindowContentSize(getWindowHandle(), size); |
| } |
| |
| @override |
| @internal |
| void setConstraints(BoxConstraints constraints) { |
| _ensureNotDestroyed(); |
| _MacOSPlatformInterface.setWindowConstraints(getWindowHandle(), constraints); |
| } |
| |
| @override |
| void setTitle(String title) { |
| _ensureNotDestroyed(); |
| _MacOSPlatformInterface.setWindowTitle(getWindowHandle(), title); |
| notifyListeners(); |
| } |
| |
| final DialogWindowControllerDelegate _delegate; |
| |
| @override |
| Size get contentSize { |
| _ensureNotDestroyed(); |
| return _MacOSPlatformInterface.getWindowContentSize(getWindowHandle()); |
| } |
| |
| @override |
| void activate() { |
| _ensureNotDestroyed(); |
| _MacOSPlatformInterface.activate(getWindowHandle()); |
| } |
| |
| @override |
| void setMinimized(bool minimized) { |
| _ensureNotDestroyed(); |
| if (minimized) { |
| _MacOSPlatformInterface.minimize(getWindowHandle()); |
| } else { |
| _MacOSPlatformInterface.unminimize(getWindowHandle()); |
| } |
| } |
| |
| @override |
| bool get isMinimized { |
| _ensureNotDestroyed(); |
| return _MacOSPlatformInterface.isMinimized(getWindowHandle()); |
| } |
| |
| @override |
| bool get isActivated => _MacOSPlatformInterface.isActivated(getWindowHandle()); |
| |
| @override |
| String get title => _MacOSPlatformInterface.getTitle(getWindowHandle()); |
| |
| @override |
| final BaseWindowController? parent; |
| } |
| |
| final class _WindowCreationRequest extends Struct { |
| @Bool() |
| external bool hasSize; |
| external _Size contentSize; |
| |
| @Bool() |
| external bool hasConstraints; |
| external _Constraints constraints; |
| |
| @Int64() |
| external int parentViewId; |
| |
| external Pointer<NativeFunction<Void Function()>> onShouldClose; |
| external Pointer<NativeFunction<Void Function()>> onWillClose; |
| external Pointer<NativeFunction<Void Function()>> onNotifyListeners; |
| external Pointer< |
| NativeFunction< |
| Pointer<_Rect> Function( |
| Pointer<_Size> childSize, |
| Pointer<_Rect> parentRect, |
| Pointer<_Rect> outputRect, |
| ) |
| > |
| > |
| onGetWindowPosition; |
| } |
| |
| final class _Size extends Struct { |
| @Double() |
| external double width; |
| |
| @Double() |
| external double height; |
| |
| @override |
| String toString() { |
| return 'Size(width: $width, height: $height)'; |
| } |
| |
| Size toSize() { |
| return Size(width, height); |
| } |
| } |
| |
| final class _Rect extends Struct { |
| @Double() |
| external double left; |
| |
| @Double() |
| external double top; |
| |
| @Double() |
| external double width; |
| |
| @Double() |
| external double height; |
| |
| Rect toRect() { |
| return Rect.fromLTWH(left, top, width, height); |
| } |
| |
| @override |
| String toString() { |
| return 'Rect(left: $left, top: $top, width: $width, height: $height)'; |
| } |
| } |
| |
| final class _Constraints extends Struct { |
| @Double() |
| external double minWidth; |
| |
| @Double() |
| external double minHeight; |
| |
| @Double() |
| external double maxWidth; |
| |
| @Double() |
| external double maxHeight; |
| } |
| |
| class _MacOSPlatformInterface { |
| @Native<Pointer<Void> Function(Int64, Int64)>(symbol: 'InternalFlutter_Window_GetHandle') |
| external static Pointer<Void> getWindowHandle(int engineId, int viewId); |
| |
| @Native<Void Function(Pointer<Void>, Pointer<_Size>)>( |
| symbol: 'InternalFlutter_Window_SetContentSize', |
| ) |
| external static void _setWindowContentSize(Pointer<Void> windowHandle, Pointer<_Size> size); |
| |
| static void setWindowContentSize(Pointer<Void> windowHandle, Size size) { |
| final Pointer<_Size> ffiSize = _allocator<_Size>(); |
| ffiSize.ref |
| ..width = size.width |
| ..height = size.height; |
| _setWindowContentSize(windowHandle, ffiSize); |
| _allocator.free(ffiSize); |
| } |
| |
| @Native<Void Function(Pointer<Void>, Pointer<_Constraints>)>( |
| symbol: 'InternalFlutter_Window_SetConstraints', |
| ) |
| external static void _setWindowConstraints( |
| Pointer<Void> windowHandle, |
| Pointer<_Constraints> size, |
| ); |
| |
| static void setWindowConstraints(Pointer<Void> windowHandle, BoxConstraints constraints) { |
| final Pointer<_Constraints> ffiConstraints = _allocator<_Constraints>(); |
| ffiConstraints.ref |
| ..minWidth = constraints.minWidth |
| ..minHeight = constraints.minHeight |
| ..maxWidth = constraints.maxWidth |
| ..maxHeight = constraints.maxHeight; |
| |
| _setWindowConstraints(windowHandle, ffiConstraints); |
| _allocator.free(ffiConstraints); |
| } |
| |
| @Native<Int64 Function(Int64, Pointer<_WindowCreationRequest>)>( |
| symbol: 'InternalFlutter_WindowController_CreateRegularWindow', |
| ) |
| external static int _createRegularWindow(int engineId, Pointer<_WindowCreationRequest> request); |
| |
| /// Creates a new window and returns the viewId of the created FlutterView. |
| static int createRegularWindow({ |
| required Size? preferredSize, |
| BoxConstraints? preferredConstraints, |
| required Pointer<NativeFunction<Void Function()>> onShouldClose, |
| required Pointer<NativeFunction<Void Function()>> onWillClose, |
| required Pointer<NativeFunction<Void Function()>> onNotifyListeners, |
| }) { |
| final Pointer<_WindowCreationRequest> request = _allocator<_WindowCreationRequest>() |
| ..ref.onShouldClose = onShouldClose |
| ..ref.onWillClose = onWillClose |
| ..ref.onNotifyListeners = onNotifyListeners; |
| |
| if (preferredSize != null) { |
| request.ref |
| ..hasSize = true |
| ..contentSize.width = preferredSize.width |
| ..contentSize.height = preferredSize.height; |
| } |
| |
| if (preferredConstraints != null) { |
| request.ref |
| ..hasConstraints = true |
| ..constraints.minWidth = preferredConstraints.minWidth |
| ..constraints.minHeight = preferredConstraints.minHeight |
| ..constraints.maxWidth = preferredConstraints.maxWidth |
| ..constraints.maxHeight = preferredConstraints.maxHeight; |
| } |
| final int viewId = _createRegularWindow( |
| WidgetsBinding.instance.platformDispatcher.engineId!, |
| request, |
| ); |
| _allocator.free(request); |
| return viewId; |
| } |
| |
| @Native<Int64 Function(Int64, Pointer<_WindowCreationRequest>)>( |
| symbol: 'InternalFlutter_WindowController_CreateDialogWindow', |
| ) |
| external static int _createDialogWindow(int engineId, Pointer<_WindowCreationRequest> request); |
| |
| /// Creates a new window and returns the viewId of the created FlutterView. |
| static int createDialogWindow({ |
| required Size? preferredSize, |
| BoxConstraints? preferredConstraints, |
| int? parentViewId, |
| required Pointer<NativeFunction<Void Function()>> onShouldClose, |
| required Pointer<NativeFunction<Void Function()>> onWillClose, |
| required Pointer<NativeFunction<Void Function()>> onNotifyListeners, |
| }) { |
| final Pointer<_WindowCreationRequest> request = _allocator<_WindowCreationRequest>() |
| ..ref.onShouldClose = onShouldClose |
| ..ref.onWillClose = onWillClose |
| ..ref.onNotifyListeners = onNotifyListeners |
| ..ref.parentViewId = parentViewId ?? 0; |
| |
| if (preferredSize != null) { |
| request.ref |
| ..hasSize = true |
| ..contentSize.width = preferredSize.width |
| ..contentSize.height = preferredSize.height; |
| } |
| |
| if (preferredConstraints != null) { |
| request.ref |
| ..hasConstraints = true |
| ..constraints.minWidth = preferredConstraints.minWidth |
| ..constraints.minHeight = preferredConstraints.minHeight |
| ..constraints.maxWidth = preferredConstraints.maxWidth |
| ..constraints.maxHeight = preferredConstraints.maxHeight; |
| } |
| try { |
| final int viewId = _createDialogWindow( |
| WidgetsBinding.instance.platformDispatcher.engineId!, |
| request, |
| ); |
| return viewId; |
| } finally { |
| _allocator.free(request); |
| } |
| } |
| |
| @Native<Int64 Function(Int64, Pointer<_WindowCreationRequest>)>( |
| symbol: 'InternalFlutter_WindowController_CreateTooltipWindow', |
| ) |
| external static int _createTooltipWindow(int engineId, Pointer<_WindowCreationRequest> request); |
| |
| /// Creates a new window and returns the viewId of the created FlutterView. |
| static int createTooltipWindow({ |
| required BoxConstraints preferredConstraints, |
| required int parentViewId, |
| required Pointer<NativeFunction<Void Function()>> onShouldClose, |
| required Pointer<NativeFunction<Void Function()>> onWillClose, |
| required Pointer<NativeFunction<Void Function()>> onNotifyListeners, |
| required Pointer< |
| NativeFunction< |
| Pointer<_Rect> Function( |
| Pointer<_Size> childSize, |
| Pointer<_Rect> parentRect, |
| Pointer<_Rect> outputRect, |
| ) |
| > |
| > |
| onGetWindowPosition, |
| }) { |
| final Pointer<_WindowCreationRequest> request = _allocator<_WindowCreationRequest>() |
| ..ref.onShouldClose = onShouldClose |
| ..ref.onWillClose = onWillClose |
| ..ref.onNotifyListeners = onNotifyListeners |
| ..ref.onGetWindowPosition = onGetWindowPosition |
| ..ref.parentViewId = parentViewId; |
| |
| request.ref |
| ..hasConstraints = true |
| ..constraints.minWidth = preferredConstraints.minWidth |
| ..constraints.minHeight = preferredConstraints.minHeight |
| ..constraints.maxWidth = preferredConstraints.maxWidth |
| ..constraints.maxHeight = preferredConstraints.maxHeight; |
| |
| final int viewId = _createTooltipWindow(PlatformDispatcher.instance.engineId!, request); |
| _allocator.free(request); |
| return viewId; |
| } |
| |
| @Native<Void Function(Int64, Pointer<Void>)>(symbol: 'InternalFlutter_Window_Destroy') |
| external static void _destroyWindow(int engineId, Pointer<Void> handle); |
| |
| static void destroyWindow(Pointer<Void> windowHandle) { |
| _destroyWindow(WidgetsBinding.instance.platformDispatcher.engineId!, windowHandle); |
| } |
| |
| @Native<_Size Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_GetContentSize') |
| external static _Size _getWindowContentSize(Pointer<Void> windowHandle); |
| |
| static Size getWindowContentSize(Pointer<Void> windowHandle) { |
| final _Size size = _getWindowContentSize(windowHandle); |
| return Size(size.width, size.height); |
| } |
| |
| @Native<Void Function(Pointer<Void>, Pointer<_Utf8>)>(symbol: 'InternalFlutter_Window_SetTitle') |
| external static void _setWindowTitle(Pointer<Void> windowHandle, Pointer<_Utf8> title); |
| |
| static void setWindowTitle(Pointer<Void> windowHandle, String title) { |
| final Pointer<_Utf8> titlePointer = title.toNativeUtf8(); |
| _setWindowTitle(windowHandle, titlePointer); |
| _allocator.free(titlePointer); |
| } |
| |
| @Native<Void Function(Pointer<Void>, Bool)>(symbol: 'InternalFlutter_Window_SetMaximized') |
| external static void setMaximized(Pointer<Void> windowHandle, bool maximized); |
| |
| @Native<Bool Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_IsMaximized') |
| external static bool isMaximized(Pointer<Void> windowHandle); |
| |
| @Native<Void Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_Minimize') |
| external static void minimize(Pointer<Void> windowHandle); |
| |
| @Native<Void Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_Unminimize') |
| external static void unminimize(Pointer<Void> windowHandle); |
| |
| @Native<Bool Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_IsMinimized') |
| external static bool isMinimized(Pointer<Void> windowHandle); |
| |
| @Native<Void Function(Pointer<Void>, Bool)>(symbol: 'InternalFlutter_Window_SetFullScreen') |
| external static void setFullscreen(Pointer<Void> windowHandle, bool fullscreen); |
| |
| @Native<Bool Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_IsFullScreen') |
| external static bool isFullscreen(Pointer<Void> windowHandle); |
| |
| @Native<Void Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_Activate') |
| external static void activate(Pointer<Void> windowHandle); |
| |
| @Native<Pointer<_Utf8> Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_GetTitle') |
| external static Pointer<_Utf8> _getTitle(Pointer<Void> windowHandle); |
| |
| static String getTitle(Pointer<Void> windowHandle) { |
| final Pointer<_Utf8> title = _getTitle(windowHandle); |
| final String result = title.toDartString(); |
| _allocator.free(title); |
| return result; |
| } |
| |
| @Native<Bool Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_IsActivated') |
| external static bool isActivated(Pointer<Void> windowHandle); |
| |
| @Native<Void Function(Pointer<Void>)>(symbol: 'InternalFlutter_Window_UpdatePosition') |
| external static void updateWindowPosition(Pointer<Void> windowHandle); |
| } |
| |
| // FFI utilities. |
| |
| typedef _PosixCallocNative = Pointer<Void> Function(IntPtr num, IntPtr size); |
| |
| @Native<_PosixCallocNative>(symbol: 'calloc') |
| external Pointer<Void> _posixCalloc(int num, int size); |
| |
| typedef _PosixFreeNative = Void Function(Pointer<NativeType>); |
| |
| @Native<Void Function(Pointer<NativeType>)>(symbol: 'free') |
| external void _posixFree(Pointer<NativeType> ptr); |
| |
| final Pointer<NativeFunction<_PosixFreeNative>> _posixFreePointer = |
| Native.addressOf<NativeFunction<_PosixFreeNative>>(_posixFree); |
| |
| const _CallocAllocator _allocator = _CallocAllocator._(); |
| |
| final class _CallocAllocator implements Allocator { |
| const _CallocAllocator._(); |
| |
| /// Allocates [byteCount] bytes of zero-initialized of memory on the native |
| /// heap. |
| @override |
| Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) { |
| final Pointer<T> result = _posixCalloc(byteCount, 1).cast(); |
| |
| if (result.address == 0) { |
| throw ArgumentError('Could not allocate $byteCount bytes.'); |
| } |
| return result; |
| } |
| |
| /// Releases memory allocated on the native heap. |
| @override |
| void free(Pointer<NativeType> pointer) { |
| _posixFree(pointer); |
| } |
| |
| /// Returns a pointer to a native free function. |
| Pointer<NativeFinalizerFunction> get nativeFree => _posixFreePointer; |
| } |
| |
| /// The contents of a native zero-terminated array of UTF-8 code units. |
| /// |
| /// The Utf8 type itself has no functionality, it's only intended to be used |
| /// through a `Pointer<Utf8>` representing the entire array. This pointer is |
| /// the equivalent of a char pointer (`const char*`) in C code. |
| final class _Utf8 extends Opaque {} |
| |
| /// Extension method for converting a`Pointer<Utf8>` to a [String]. |
| extension _Utf8Pointer on Pointer<_Utf8> { |
| /// Converts this UTF-8 encoded string to a Dart string. |
| /// |
| /// Decodes the UTF-8 code units of this zero-terminated byte array as |
| /// Unicode code points and creates a Dart string containing those code |
| /// points. |
| /// |
| /// If [length] is provided, zero-termination is ignored and the result can |
| /// contain NUL characters. |
| /// |
| /// If [length] is not provided, the returned string is the string up til |
| /// but not including the first NUL character. |
| String toDartString({int? length}) { |
| _ensureNotNullptr('toDartString'); |
| final Pointer<Uint8> codeUnits = cast<Uint8>(); |
| if (length != null) { |
| RangeError.checkNotNegative(length, 'length'); |
| } else { |
| length = _length(codeUnits); |
| } |
| return utf8.decode(codeUnits.asTypedList(length)); |
| } |
| |
| static int _length(Pointer<Uint8> codeUnits) { |
| var length = 0; |
| while (codeUnits[length] != 0) { |
| length++; |
| } |
| return length; |
| } |
| |
| void _ensureNotNullptr(String operation) { |
| if (this == nullptr) { |
| throw UnsupportedError("Operation '$operation' not allowed on a 'nullptr'."); |
| } |
| } |
| } |
| |
| /// Extension method for converting a [String] to a `Pointer<Utf8>`. |
| extension _StringUtf8Pointer on String { |
| /// Creates a zero-terminated [_Utf8] code-unit array from this String. |
| /// |
| /// If this [String] contains NUL characters, converting it back to a string |
| /// using [_Utf8Pointer.toDartString] will truncate the result if a length is |
| /// not passed. |
| /// |
| /// Unpaired surrogate code points in this [String] will be encoded as |
| /// replacement characters (U+FFFD, encoded as the bytes 0xEF 0xBF 0xBD) in |
| /// the UTF-8 encoded result. See [Utf8Encoder] for details on encoding. |
| /// |
| /// Returns an [allocator]-allocated pointer to the result. |
| Pointer<_Utf8> toNativeUtf8({Allocator allocator = _allocator}) { |
| final Uint8List units = utf8.encode(this); |
| final Pointer<Uint8> result = allocator<Uint8>(units.length + 1); |
| final Uint8List nativeString = result.asTypedList(units.length + 1); |
| nativeString.setAll(0, units); |
| nativeString[units.length] = 0; |
| return result.cast(); |
| } |
| } |