blob: d97584985a94431180ab00f4f98cb8ab509932ff [file] [log] [blame]
// 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.
// 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.
import 'dart:ui' show Display, FlutterView;
import 'package:flutter/foundation.dart';
import '../foundation/_features.dart';
import '_window_io.dart' if (dart.library.js_interop) '_window_web.dart' as window_impl;
import '_window_positioner.dart';
import 'basic.dart';
import 'binding.dart';
import 'framework.dart';
import 'inherited_model.dart';
import 'transitions.dart';
import 'view.dart';
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.
''';
/// Base class for window controllers.
///
/// A [BaseWindowController] is associated with exactly one root [FlutterView].
///
/// When the window is destroyed for any reason (either by the caller or by the
/// platform), the content of the controller will thereafter be invalid.
///
/// {@template flutter.widgets.windowing.experimental}
/// Do not use this API in production applications or packages published to
/// pub.dev. Flutter will make breaking changes to this API, even in patch
/// versions.
///
/// This API throws an [UnsupportedError] error unless Flutter’s windowing
/// feature is enabled by [isWindowingEnabled].
///
/// See: https://github.com/flutter/flutter/issues/30701.
/// {@endtemplate}
///
/// See also:
///
/// * [RegularWindowController], the controller for regular top-level windows.
@internal
sealed class BaseWindowController extends ChangeNotifier {
/// The current size of the drawable area of the window.
///
/// This might differ from the requested size.
///
/// This might also differ from the actual size of the window if the window has
/// decorations such as title bar, borders, etc.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
Size get contentSize;
/// Destroys this window.
///
/// It is permissible to call this method multiple times.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void destroy();
/// The root view associated to this window, which is unique to each window.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
FlutterView get rootView => _view;
late final FlutterView _view;
/// Sets the view associated with this window.
///
/// {@macro flutter.widgets.windowing.experimental}
@protected
set rootView(FlutterView view) {
_view = view;
}
}
/// Delegate class for regular window controller.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindowController], the controller that creates and manages regular windows.
/// * [RegularWindow], the widget for a regular window.
@internal
mixin class RegularWindowControllerDelegate {
/// Invoked when the user attempts to close the window.
///
/// The default implementation destroys the window. Subclasses
/// can override the behavior to delay or prevent the window from closing.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [onWindowDestroyed], which is invoked after the window is closed.
@internal
void onWindowCloseRequested(RegularWindowController controller) {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
controller.destroy();
}
/// Invoked after the window is closed.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [onWindowCloseRequested], which is invoked when the user attempts to close the window.
@internal
void onWindowDestroyed() {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
}
}
/// A controller for a regular window.
///
/// A regular window is a traditional window that can be resized, minimized,
/// maximized, and closed. Upon construction, the window is created for the
/// platform with the provided properties.
///
/// This class does not interact with the widget tree. Instead, it is typically
/// provided to the [RegularWindow] widget, who does the work of rendering the
/// content inside of this window.
///
/// The user of this class is responsible for managing the lifecycle of the window.
/// When the window is no longer needed, the user should call [destroy] on this
/// controller to release the resources associated with the window.
///
/// {@tool snippet}
/// An example usage might look like:
///
///
/// ```dart
/// // TODO(mattkae): remove invalid_use_of_internal_member ignore comment when this API is stable.
/// // ignore_for_file: invalid_use_of_internal_member
/// import 'package:flutter/widgets.dart';
/// import 'package:flutter/material.dart';
/// import 'package:flutter/src/widgets/_window.dart';
///
/// void main() {
/// runWidget(
/// RegularWindow(
/// controller: RegularWindowController(
/// preferredSize: const Size(800, 600),
/// preferredConstraints: const BoxConstraints(minWidth: 640, minHeight: 480),
/// title: 'Example Window',
/// ),
/// child: MaterialApp(home: Container()),
/// ),
/// );
/// }
/// ```
/// {@end-tool}
///
/// Children of a [RegularWindow] widget can access the [RegularWindowController]
/// via the [WindowScope] inherited widget.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
abstract class RegularWindowController extends BaseWindowController {
/// Creates a [RegularWindowController] with the provided properties.
///
/// Upon construction, the window is created by the platform.
///
/// {@template flutter.widgets.windowing.constraints}
/// The [preferredSize] is the preferred content size of the window.
/// This might not be honored by the platform. This is the size that
/// the platform will try to apply to the window when it is created. In contrast,
/// the [preferredConstraints] field enforces the minimum and maximum size of
/// the window. If the [preferredSize] does not satisfy the [preferredConstraints]
/// or the [preferredSize] is null, then the platform will attempt to use an
/// initial size that does satisfy the [preferredConstraints] instead.
///
/// The [preferredConstraints] are the constraints placed upon the size
/// of the window. This might not be honored by the platform.
/// This field enforces a minimum and maximum size on the window. If the
/// user attempts to resize the window beyond these constraints, the platform
/// will enforce the constraints according to its own policy. For example, the
/// platform might clip the content to fit within the resized window, or it might
/// prevent the window from being resized altogether. If null, the window will
/// be unconstrained.
///
/// If both [preferredSize] and [preferredConstraints] are null,
/// then the platform will use its own default size for the window.
/// {@endtemplate}
///
/// The [title] argument configures the window's initial title.
/// If omitted, some platforms might fall back to the app's name.
///
/// The [delegate] argument can be used to listen to the window's
/// lifecycle. For example, it can be used to save state before
/// a window is closed.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
factory RegularWindowController({
Size? preferredSize,
BoxConstraints? preferredConstraints,
String? title,
RegularWindowControllerDelegate? delegate,
}) {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
if (preferredSize != null && preferredConstraints != null) {
assert(preferredConstraints.isSatisfiedBy(preferredSize));
}
final WindowingOwner owner = WidgetsBinding.instance.windowingOwner;
return owner.createRegularWindowController(
delegate: delegate ?? RegularWindowControllerDelegate(),
preferredSize: preferredSize,
preferredConstraints: preferredConstraints,
title: title,
);
}
/// Creates an empty [RegularWindowController].
///
/// This method is only intended to be used by subclasses of the
/// [RegularWindowController].
///
/// Users who want to instantiate a new [RegularWindowController] should
/// always use the factory method to create a controller that is valid
/// for their particular platform.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
@protected
RegularWindowController.empty();
/// The current title of the window.
///
/// This might differ from the requested title.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
String get title;
/// Whether the window is currently activated.
///
/// If `true` this means that the window is currently focused and
/// can receive user input.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
bool get isActivated;
/// Whether or not the window is currently maximized.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
bool get isMaximized;
/// Whether or not window is currently minimized.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
bool get isMinimized;
/// Whether or not the window is currently in fullscreen mode.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
bool get isFullscreen;
/// Request change to the content size of the window.
///
/// The [size] describes the new requested window size. If the size disagrees
/// with the current constraints placed upon the window, the platform might
/// clamp the size within the constraints.
///
/// The platform is free to ignore this request.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void setSize(Size size);
/// Request change to the constraints of the window.
///
/// The [constraints] describes the new constraints that the window should
/// satisfy. If the constraints disagree with the current size of the window,
/// the platform might resize the window to satisfy the new constraints.
///
/// The platform is free to ignore this request.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void setConstraints(BoxConstraints constraints);
/// Request change for the window title.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void setTitle(String title);
/// Requests that the window be displayed in its current size and position.
///
/// The platform may also give the window input focus and bring it to the
/// top of the window stack. However, this behavior is platform-dependent.
///
/// If the window is minimized, the window returns to the size and position
/// that it had before that state was applied. The window will also be
/// brought to the top of the window stack.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void activate();
/// Requests the window to be maximized.
///
/// This has no effect if the window is currently full screen or minimized,
/// but might affect the window size upon restoring it from minimized or
/// full screen state.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void setMaximized(bool maximized);
/// Requests window to be minimized.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void setMinimized(bool minimized);
/// Request change for the window to enter or exit fullscreen state.
///
/// If [fullscreen] is set to true, the platform will attempt to change
/// the state of the window to fullscreen. If false, the window will
/// return to a previous non-hidden state. Both cases might not be
/// honored by the platform.
///
/// The [display] specifies an optional [Display] on which the window
/// would like to be fullscreened. This might not be honored by the
/// platform. The [display] argument is ignored if [fullscreen] is `false`.
///
/// When [fullscreen] is set to false, it is up to the platform as to
/// which display the window will be restored to. The platform might
/// restore the window to the display on which it was previously fullscreened,
/// or it might restore the window to the display on which it was last
/// active.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void setFullscreen(bool fullscreen, {Display? display});
}
/// Delegate class for dialog window controller.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [DialogWindowController], the controller that creates and manages dialog windows.
/// * [DialogWindow], the widget for a dialog window.
/// * [RegularWindowControllerDelegate], the delegate for regular window controllers.
@internal
mixin class DialogWindowControllerDelegate {
/// Invoked when the user attempts to close the window.
///
/// The default implementation destroys the window. Subclasses
/// can override the behavior to delay or prevent the window from closing.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [onWindowDestroyed], which is invoked after the window is closed.
@internal
void onWindowCloseRequested(DialogWindowController controller) {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
controller.destroy();
}
/// Invoked after the window is closed.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [onWindowCloseRequested], which is invoked when the user attempts to close the window.
@internal
void onWindowDestroyed() {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
}
}
/// A controller for a dialog window.
///
/// Two types of dialogs are supported:
/// * Modal dialogs: created with a non-null parent. These dialogs are modal
/// to the parent, do not have a system menu, and are not selectable from the
/// window switcher.
/// * Modeless dialogs: created with a null parent. These dialogs can be
/// minimized (but not maximized), and have a disabled close button.
///
/// This class does not interact with the widget tree. Instead, it is typically
/// provided to the [DialogWindow] widget, which renders the content inside the
/// dialog window.
///
/// The user of this class is responsible for managing the lifecycle of the window.
/// When the window is no longer needed, the user should call [destroy] on this
/// controller to release the resources associated with the window.
///
/// {@tool snippet}
/// An example usage might look like:
///
/// ```dart
/// // TODO(mattkae): remove invalid_use_of_internal_member ignore comment when this API is stable.
/// // ignore_for_file: invalid_use_of_internal_member
/// import 'package:flutter/widgets.dart';
/// import 'package:flutter/material.dart';
/// import 'package:flutter/src/widgets/_window.dart';
///
/// void main() {
/// runWidget(
/// RegularWindow(
/// controller: RegularWindowController(
/// preferredSize: const Size(800, 600),
/// preferredConstraints: const BoxConstraints(minWidth: 640, minHeight: 480),
/// title: 'Example Window',
/// ),
/// child: const MyApp()
/// )
/// );
/// }
///
/// class MyApp extends StatelessWidget {
/// const MyApp({super.key});
///
/// @override
/// Widget build(BuildContext context) {
/// return MaterialApp(
/// home: DialogWindow(
/// controller: DialogWindowController(
/// preferredSize: const Size(400, 300),
/// parent: WindowScope.of(context),
/// title: 'Example Dialog'
/// ),
/// child: const Text('Hello, World!')
/// )
/// );
/// }
/// }
/// ```
/// {@end-tool}
///
/// Children of a [DialogWindow] widget can access the [DialogWindowController]
/// via the [WindowScope] inherited widget.
///
/// {@macro flutter.widgets.windowing.experimental}
abstract class DialogWindowController extends BaseWindowController {
/// Creates a [DialogWindowController] with the provided properties.
///
/// Upon construction, the window is created by the platform.
///
/// {@macro flutter.widgets.windowing.constraints}
///
/// The [parent] argument specifies the parent window of this dialog.
///
/// If the [parent] is null, then the dialog is modeless. Such dialogs can
/// be minimized but not maximized. They also have a disabled close button.
///
/// If the [parent] is non-null, then the dialog is modal to the parent.
/// Such dialogs do not have a system menu. They are also not selectable
/// from the window switcher and they are closed when the parent is closed.
///
/// The [title] argument configures the window's initial title.
/// If omitted, some platforms might fall back to the app's name.
///
/// The [delegate] argument can be used to listen to the window's
/// lifecycle. For example, it can be used to save state before
/// a window is closed.
///
/// {@macro flutter.widgets.windowing.experimental}
factory DialogWindowController({
Size? preferredSize,
BoxConstraints? preferredConstraints,
BaseWindowController? parent,
String? title,
DialogWindowControllerDelegate? delegate,
}) {
WidgetsFlutterBinding.ensureInitialized();
final WindowingOwner owner = WidgetsBinding.instance.windowingOwner;
return owner.createDialogWindowController(
delegate: delegate ?? DialogWindowControllerDelegate(),
preferredSize: preferredSize,
preferredConstraints: preferredConstraints,
title: title,
parent: parent,
);
}
/// Creates an empty [DialogWindowController].
///
/// This method is only intended to be used by subclasses of the
/// [DialogWindowController].
///
/// Users who want to instantiate a new [DialogWindowController] should
/// always use the factory method to create a controller that is valid
/// for their particular platform.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
@protected
DialogWindowController.empty();
/// The parent controller of this dialog, if any.
///
/// If null, this dialog is modeless.
/// If non-null, this dialog is modal to the parent.
BaseWindowController? get parent;
/// The current title of the window.
///
/// This might differ from the requested title.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
String get title;
/// Whether the window is currently activated.
///
/// If `true` this means that the window is currently focused and
/// can receive user input.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
bool get isActivated;
/// Whether or not window is currently minimized.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
bool get isMinimized;
/// Request change to the content size of the window.
///
/// The [size] describes the new requested window size. If the size disagrees
/// with the current constraints placed upon the window, the platform might
/// clamp the size within the constraints.
///
/// The platform is free to ignore this request.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void setSize(Size size);
/// Request change to the constraints of the window.
///
/// The [constraints] describes the new constraints that the window should
/// satisfy. If the constraints disagree with the current size of the window,
/// the platform might resize the window to satisfy the new constraints.
///
/// The platform is free to ignore this request.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void setConstraints(BoxConstraints constraints);
/// Request change for the window title.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void setTitle(String title);
/// Requests that the window be displayed in its current size and position.
///
/// The platform may also give the window input focus and bring it to the
/// top of the window stack. However, this behavior is platform-dependent.
///
/// If the window is minimized, the window returns to the size and position
/// that it had before that state was applied. The window will also be
/// brought to the top of the window stack.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void activate();
/// Requests window to be minimized.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void setMinimized(bool minimized);
}
/// Delegate class for tooltip window controller.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [TooltipWindowController], the controller that creates and manages tooltip windows.
/// * [TooltipWindow], the widget for a tooltip window.
/// * [RegularWindowControllerDelegate], the delegate for regular window controllers.
mixin class TooltipWindowControllerDelegate {
/// Invoked after the window is closed.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void onWindowDestroyed() {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
}
}
/// A controller for a tooltip window.
///
/// A tooltip window is a small window that displays brief, informative text
/// when a user hovers over or focuses on a UI element. Tooltip windows are
/// typically used to provide additional context or explanations for UI elements
/// without cluttering the main interface. As such, it may not receive input
/// focus from the user. It will however stay open when another window receives
/// input focus.
///
/// This class does not interact with the widget tree. Instead, it is typically
/// provided to the [TooltipWindow] widget, which renders the content inside the
/// tooltip window.
///
/// The user of this class is responsible for managing the lifecycle of the window.
/// When the window is no longer needed, the user should call [destroy] on this
/// controller to release the resources associated with the window.
///
/// {@tool snippet}
/// An example usage of [TooltipWindowController] looks like:
///
/// ** See code in examples/api/lib/widgets/windows/tooltip.0.dart **
/// {@end-tool}
///
/// Children of a [TooltipWindow] widget can access the [TooltipWindowController]
/// via the [WindowScope] inherited widget.
///
/// {@macro flutter.widgets.windowing.experimental}
abstract class TooltipWindowController extends BaseWindowController {
/// Creates a [TooltipWindowController] with the provided properties.
///
/// Upon construction, the window is created by the platform.
///
/// The [parent] argument specifies the parent window of this tooltip.
///
/// The [anchorRect] argument specifies the rectangle in the parent's coordinate
/// space to which the tooltip is anchored.
///
/// The [positioner] argument specifies how the tooltip should be positioned
/// relative to the [anchorRect].
///
/// The [preferredConstraints] are the constraints placed upon the size
/// of the window.
///
/// If [isSizedToContent] is true, the tooltip will size itself to fit its content
/// within the given [preferredConstraints]. If false, the tooltip will use
/// the [preferredConstraints] as strict constraints for its size.
///
/// {@macro flutter.widgets.windowing.constraints}
///
/// The [delegate] argument can be used to listen to the window's
/// lifecycle. For example, it can be used to save state before
/// a window is closed.
///
/// {@macro flutter.widgets.windowing.experimental}
factory TooltipWindowController({
required BaseWindowController parent,
required Rect anchorRect,
required WindowPositioner positioner,
BoxConstraints preferredConstraints = const BoxConstraints(),
bool isSizedToContent = true,
TooltipWindowControllerDelegate? delegate,
}) {
WidgetsFlutterBinding.ensureInitialized();
final WindowingOwner owner = WidgetsBinding.instance.windowingOwner;
final TooltipWindowController controller = owner.createTooltipWindowController(
parent: parent,
preferredConstraints: preferredConstraints,
isSizedToContent: isSizedToContent,
delegate: delegate ?? TooltipWindowControllerDelegate(),
anchorRect: anchorRect,
positioner: positioner,
);
return controller;
}
/// Creates an empty [TooltipWindowController].
///
/// This method is only intended to be used by subclasses of the
/// [TooltipWindowController].
///
/// Users who want to instantiate a new [TooltipWindowController] should
/// always use the factory method to create a controller that is valid
/// for their particular platform.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
@protected
TooltipWindowController.empty();
/// The parent controller of this tooltip.
///
/// The tooltip will be destroyed if its parent is destroyed.
BaseWindowController get parent;
/// Request change to the constraints of the window.
///
/// The [constraints] describes the new constraints that the window should
/// satisfy. If the constraints disagree with the current size of the window,
/// the platform might resize the window to satisfy the new constraints.
///
/// The platform is free to ignore this request.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void setConstraints(BoxConstraints constraints);
/// Updates the position of the tooltip.
///
/// This requests that the tooltip be repositioned according to the new [anchorRect] and/or [positioner].
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void updatePosition({Rect? anchorRect, WindowPositioner? positioner});
}
/// Delegate class for popup window controller.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [PopupWindowController], the controller that creates and manages popup windows.
/// * [PopupWindow], the widget for a popup window.
/// * [RegularWindowControllerDelegate], the delegate for regular window controllers.
mixin class PopupWindowControllerDelegate {
/// Invoked after the window is closed.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void onWindowDestroyed() {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
}
}
/// A controller for a popup window.
///
/// A popup window is a transient window that is used for menus and context
/// menus. Popups may receive input focus. When another window receives input focus,
/// the popup is closed.
///
/// This class does not interact with the widget tree. Instead, it is typically
/// provided to the [PopupWindow] widget, which renders the content inside the
/// popup window.
///
/// The user of this class is responsible for managing the lifecycle of the window.
/// When the window is no longer needed, the user should call [destroy] on this
/// controller to release the resources associated with the window.
///
/// {@tool snippet}
/// An example usage of [PopupWindowController] looks like:
///
/// ** See code in examples/api/lib/widgets/windows/popup.0.dart **
/// {@end-tool}
///
/// Children of a [PopupWindow] widget can access the [PopupWindowController]
/// via the [WindowScope] [InheritedWidget].
///
/// {@macro flutter.widgets.windowing.experimental}
abstract class PopupWindowController extends BaseWindowController {
/// Creates a [PopupWindowController] with the provided properties.
///
/// Upon construction, the window is created by the platform.
///
/// The [parent] argument specifies the parent window of this popup.
///
/// The [anchorRect] argument specifies the rectangle in the parent's coordinate
/// space to which the popup is anchored.
///
/// The [positioner] argument specifies how the popup should be positioned
/// relative to the [anchorRect].
///
/// {@macro flutter.widgets.windowing.constraints}
///
/// The [delegate] argument can be used to listen to the window's
/// lifecycle. For example, it can be used to save state before
/// a window is closed.
///
/// {@macro flutter.widgets.windowing.experimental}
factory PopupWindowController({
required BaseWindowController parent,
required Rect anchorRect,
required WindowPositioner positioner,
BoxConstraints? preferredConstraints,
PopupWindowControllerDelegate? delegate,
}) {
WidgetsFlutterBinding.ensureInitialized();
final WindowingOwner owner = WidgetsBinding.instance.windowingOwner;
return owner.createPopupWindowController(
parent: parent,
preferredConstraints: preferredConstraints ?? const BoxConstraints(),
delegate: delegate ?? PopupWindowControllerDelegate(),
anchorRect: anchorRect,
positioner: positioner,
);
}
/// Creates an empty [TooltipWindowController].
///
/// This method is only intended to be used by subclasses of the
/// [TooltipWindowController].
///
/// Users who want to instantiate a new [TooltipWindowController] should
/// always use the factory method to create a controller that is valid
/// for their particular platform.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
@protected
PopupWindowController.empty();
/// The parent controller of this popup.
///
/// The popup will be destroyed if its parent is destroyed.
BaseWindowController get parent;
/// Whether the window is currently activated.
///
/// If `true` this means that the window is currently focused and
/// can receive user input.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
bool get isActivated;
/// Requests that the window receive focus.
///
/// The platform may also give the window input focus and bring it to the
/// top of the window stack. However, this behavior is platform-dependent.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void activate();
/// Request change to the constraints of the window.
///
/// The [constraints] describes the new constraints that the window should
/// satisfy. If the constraints disagree with the current size of the window,
/// the platform might resize the window to satisfy the new constraints.
///
/// The platform is free to ignore this request.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
void setConstraints(BoxConstraints constraints);
}
/// [WindowingOwner] is responsible for creating and managing window controllers.
///
/// A custom implementation can be provided by setting [WidgetsBinding.windowingOwner].
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
abstract class WindowingOwner {
/// Creates a [RegularWindowController] with the provided properties.
///
/// Most app developers should use [RegularWindowController]'s constructor
/// instead of calling this method directly. This method allows platforms
/// to inject platform-specific logic.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
RegularWindowController createRegularWindowController({
required RegularWindowControllerDelegate delegate,
Size? preferredSize,
BoxConstraints? preferredConstraints,
String? title,
});
/// Creates a [DialogWindowController] with the provided properties.
///
/// Most app developers should use [DialogWindowController]'s constructor
/// instead of calling this method directly. This method allows platforms
/// to inject platform-specific logic.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
DialogWindowController createDialogWindowController({
required DialogWindowControllerDelegate delegate,
Size? preferredSize,
BoxConstraints? preferredConstraints,
BaseWindowController? parent,
String? title,
});
/// Creates a [TooltipWindowController] with the provided properties.
///
/// Most app developers should use [TooltipWindowController]'s constructor
/// instead of calling this method directly. This method allows platforms
/// to inject platform-specific logic.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
TooltipWindowController createTooltipWindowController({
required TooltipWindowControllerDelegate delegate,
required BoxConstraints preferredConstraints,
required bool isSizedToContent,
required Rect anchorRect,
required WindowPositioner positioner,
required BaseWindowController parent,
});
/// Creates a [PopupWindowController] with the provided properties.
///
/// Most app developers should use [PopupWindowController]'s constructor
/// instead of calling this method directly. This method allows platforms
/// to inject platform-specific logic.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
PopupWindowController createPopupWindowController({
required PopupWindowControllerDelegate delegate,
required BoxConstraints preferredConstraints,
required Rect anchorRect,
required WindowPositioner positioner,
required BaseWindowController parent,
});
}
/// Creates default windowing owner for standard desktop embedders.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
WindowingOwner createDefaultWindowingOwner() {
if (!isWindowingEnabled) {
return _WindowingOwnerUnsupported(errorMessage: _kWindowingDisabledErrorMessage);
}
final WindowingOwner? owner = window_impl.createDefaultOwner();
if (owner != null) {
return owner;
}
return _WindowingOwnerUnsupported(errorMessage: 'Windowing is unsupported on this platform.');
}
/// Windowing delegate used on platforms that do not support windowing.
class _WindowingOwnerUnsupported extends WindowingOwner {
_WindowingOwnerUnsupported({required this.errorMessage});
final String errorMessage;
@override
RegularWindowController createRegularWindowController({
required RegularWindowControllerDelegate delegate,
Size? preferredSize,
BoxConstraints? preferredConstraints,
String? title,
}) {
throw UnsupportedError(errorMessage);
}
@override
DialogWindowController createDialogWindowController({
required DialogWindowControllerDelegate delegate,
Size? preferredSize,
BoxConstraints? preferredConstraints,
BaseWindowController? parent,
String? title,
}) {
throw UnsupportedError(errorMessage);
}
@override
TooltipWindowController createTooltipWindowController({
required TooltipWindowControllerDelegate delegate,
required BoxConstraints preferredConstraints,
required bool isSizedToContent,
required Rect anchorRect,
required WindowPositioner positioner,
required BaseWindowController parent,
}) {
throw UnimplementedError(errorMessage);
}
@override
PopupWindowController createPopupWindowController({
required PopupWindowControllerDelegate delegate,
required BoxConstraints preferredConstraints,
required Rect anchorRect,
required WindowPositioner positioner,
required BaseWindowController parent,
}) {
throw UnimplementedError(errorMessage);
}
}
/// The [RegularWindow] widget provides a way to render a regular window in the
/// widget tree.
///
/// The provided [controller] creates the native window that backs
/// the widget. The [child] widget is rendered into this newly created window.
///
/// When a [RegularWindow] widget is removed from the tree, the window that was created
/// by the [controller] remains valid until the caller destroys it by calling
/// [RegularWindowController.destroy].
///
/// Widgets in the same tree as the [child] widget will have access to the
/// [RegularWindowController] via the [WindowScope] widget.
///
/// {@tool snippet}
/// An example usage might look like:
///
/// ```dart
/// // TODO(mattkae): remove invalid_use_of_internal_member ignore comment when this API is stable.
/// // ignore_for_file: invalid_use_of_internal_member
/// import 'package:flutter/widgets.dart';
/// import 'package:flutter/material.dart';
/// import 'package:flutter/src/widgets/_window.dart';
///
/// void main() {
/// runWidget(
/// RegularWindow(
/// controller: RegularWindowController(
/// preferredSize: const Size(800, 600),
/// preferredConstraints: const BoxConstraints(minWidth: 640, minHeight: 480),
/// title: 'Example Window',
/// ),
/// child: MaterialApp(home: Container()),
/// ),
/// );
/// }
/// ```
/// {@end-tool}
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
class RegularWindow extends StatelessWidget {
/// Creates a regular window widget.
///
/// The [controller] creates the native backing window into which the
/// [child] widget is rendered.
///
/// It is up to the caller to destroy the window by calling
/// [RegularWindowController.destroy] when the window is no longer needed.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
RegularWindow({super.key, required this.controller, required this.child}) {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
}
/// Controller for this widget.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
final RegularWindowController controller;
/// The content rendered into this window.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
final Widget child;
/// {@macro flutter.widgets.windowing.experimental}
@internal
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: controller,
builder: (BuildContext context, Widget? widget) => WindowScope(
controller: controller,
child: View(view: controller.rootView, child: child),
),
);
}
}
/// The [DialogWindow] widget provides a way to render a dialog window in the
/// widget tree.
///
/// The provided [controller] creates the native window that backs
/// the widget. The [child] widget is rendered into this newly created window.
///
/// When a [DialogWindow] widget is removed from the tree, the window that was created
/// by the [controller] remains valid until the caller destroys it by calling
/// [DialogWindowController.destroy].
///
/// Widgets in the same tree as the [child] widget will have access to the
/// [DialogWindowController] via the [WindowScope] widget.
///
/// {@tool snippet}
/// An example usage might look like:
///
/// ```dart
/// // TODO(mattkae): remove invalid_use_of_internal_member ignore comment when this API is stable.
/// // ignore_for_file: invalid_use_of_internal_member
/// import 'package:flutter/widgets.dart';
/// import 'package:flutter/material.dart';
/// import 'package:flutter/src/widgets/_window.dart';
///
/// void main() {
/// runWidget(
/// RegularWindow(
/// controller: RegularWindowController(
/// preferredSize: const Size(800, 600),
/// preferredConstraints: const BoxConstraints(minWidth: 640, minHeight: 480),
/// title: 'Example Window',
/// ),
/// child: const MyApp()
/// )
/// );
/// }
///
/// class MyApp extends StatelessWidget {
/// const MyApp({super.key});
///
/// @override
/// Widget build(BuildContext context) {
/// return MaterialApp(
/// home: DialogWindow(
/// controller: DialogWindowController(
/// preferredSize: const Size(400, 300),
/// parent: WindowScope.of(context),
/// title: 'Example Dialog'
/// ),
/// child: const Text('Hello, World!')
/// )
/// );
/// }
/// }
/// ```
/// {@end-tool}
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
class DialogWindow extends StatelessWidget {
/// Creates a dialog window widget.
///
/// The [controller] creates the native backing window into which the
/// [child] widget is rendered.
///
/// It is up to the caller to destroy the window by calling
/// [DialogWindowController.destroy] when the window is no longer needed.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
DialogWindow({super.key, required this.controller, required this.child}) {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
}
/// Controller for this widget.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
final DialogWindowController controller;
/// The content rendered into this window.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
final Widget child;
/// {@macro flutter.widgets.windowing.experimental}
@internal
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: controller,
builder: (BuildContext context, Widget? widget) => WindowScope(
controller: controller,
child: View(view: controller.rootView, child: child),
),
);
}
}
@internal
class TooltipWindow extends StatelessWidget {
/// Creates a tooltip window widget.
///
/// The [controller] creates the native backing window into which the
/// [child] widget is rendered.
///
/// It is up to the caller to destroy the window by calling
/// [TooltipWindowController.destroy] when the window is no longer needed.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
TooltipWindow({super.key, required this.controller, required this.child}) {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
}
/// Controller for this widget.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
final TooltipWindowController controller;
/// The content rendered into this window.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
final Widget child;
/// {@macro flutter.widgets.windowing.experimental}
@internal
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: controller,
builder: (BuildContext context, Widget? widget) => WindowScope(
controller: controller,
child: View(view: controller.rootView, child: child),
),
);
}
}
/// The [PopupWindow] widget provides a way to render a popup window in the
/// widget tree.
///
/// The provided [controller] creates the native window that backs
/// the widget. The [child] widget is rendered into this newly created window.
///
/// When a [PopupWindow] widget is removed from the tree, the window that was created
/// by the [controller] remains valid until the caller destroys it by calling
/// [PopupWindowController.destroy].
///
/// Widgets in the same tree as the [child] widget will have access to the
/// [PopupWindowController] via the [WindowScope] widget.
///
/// {@tool snippet}
/// An example usage of [PopupWindow] looks like:
///
/// ** See code in examples/api/lib/widgets/windows/popup.0.dart **
/// {@end-tool}
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
/// * [PopupWindowController], the controller that creates and manages popup windows.
@internal
class PopupWindow extends StatelessWidget {
/// Creates a popup window widget.
///
/// The [controller] creates the native backing window into which the
/// [child] widget is rendered.
///
/// It is up to the caller to destroy the window by calling
/// [PopupWindowController.destroy] when the window is no longer needed.
///
/// {@macro flutter.widgets.windowing.experimental}
PopupWindow({super.key, required this.controller, required this.child}) {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
}
/// Controller for this widget.
///
/// {@macro flutter.widgets.windowing.experimental}
final PopupWindowController controller;
/// The content rendered into this window.
///
/// {@macro flutter.widgets.windowing.experimental}
final Widget child;
/// {@macro flutter.widgets.windowing.experimental}
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: controller,
builder: (BuildContext context, Widget? widget) => WindowScope(
controller: controller,
child: View(view: controller.rootView, child: child),
),
);
}
}
enum _WindowControllerAspect { contentSize, title, activated, maximized, minimized, fullscreen }
/// Provides descendants with access to the [BaseWindowController] associated with
/// the window that is being rendered.
///
/// Windows created using native APIs do not have a [WindowScope].
/// This includes the initial window created by the native entrypoint
/// that [runApp] attaches to.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindow], the widget to create a regular window.
/// * [DialogWindow], the widget to create a dialog window.
@internal
class WindowScope extends InheritedModel<_WindowControllerAspect> {
/// Creates a new [WindowScope].
///
/// This widget is used by the window widgets to provide
/// widgets in a window's subtree with access to information about
/// the window.
///
/// The [controller] is the controller associated with this window,
/// and the [child] is the widget tree that will have access
/// to this context.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
WindowScope({super.key, required this.controller, required super.child}) {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
}
/// The controller associated with this window.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
final BaseWindowController controller;
/// Returns the [BaseWindowController] for the window that hosts the given context.
///
/// {@template flutter.widgets.windowing.WindowScope.of}
/// If there is no [WindowScope] in scope, this method
/// will throw a [TypeError] exception in release builds, and throws
/// a descriptive [FlutterError] in debug builds.
///
/// Windows creating using native APIs do not have a [WindowScope].
/// This includes the initial window created by the native entrypoint
/// that [runApp] attaches to.
/// {@endtemplate}
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindowController], the controller for regular top-level windows.
/// * [DialogWindowController], the controller for dialog windows.
/// * [RegularWindow], the widget for a regular window.
/// * [DialogWindow], the widget for a dialog window.
/// * [maybeOf], which doesn't throw or assert if it doesn't find a
/// [WindowScope] ancestor. It returns null instead.
@internal
static BaseWindowController of(BuildContext context) {
return _of(context);
}
/// Returns the [BaseWindowController] if one exists, otherwise null.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindowController], the controller for regular top-level windows.
/// * [DialogWindowController], the controller for dialog windows.
/// * [RegularWindow], the widget for a regular window.
/// * [DialogWindow], the widget for a dialog window.
/// * [of], which will throw if it doesn't find a [WindowScope] ancestor,
/// instead of returning null.
@internal
static BaseWindowController? maybeOf(BuildContext context) {
return _maybeOf(context);
}
/// Returns [BaseWindowController.contentSize] of the nearest [WindowScope].
///
/// {@macro flutter.widgets.windowing.windowScope.of}
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [BaseWindowController.contentSize], which returns the current content size of the window.
/// * [of], which returns the [BaseWindowController] associated with the window.
@internal
static Size contentSizeOf(BuildContext context) =>
_of(context, _WindowControllerAspect.contentSize).contentSize;
/// Returns [BaseWindowController.contentSize] of the nearest [WindowScope],
/// or null if not found.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [BaseWindowController.contentSize], which returns the current content size of the window.
/// * [maybeOf], which returns the [BaseWindowController] associated with the window, or null if not found.
@internal
static Size? maybeContentSizeOf(BuildContext context) =>
_maybeOf(context, _WindowControllerAspect.contentSize)?.contentSize;
/// Returns the title of the controller in the nearest [WindowScope].
///
/// {@macro flutter.widgets.windowing.windowScope.of}
///
/// If the window associated with the controller does not support titles,
/// this method will throw an [UnsupportedError].
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindowController.title], which returns the current title of the window.
/// * [of], which returns the [BaseWindowController] associated with the window.
@internal
static String titleOf(BuildContext context) {
final BaseWindowController controller = _of(context, _WindowControllerAspect.title);
return switch (controller) {
RegularWindowController() => controller.title,
DialogWindowController() => controller.title,
TooltipWindowController() => '',
PopupWindowController() => '',
};
}
/// Returns title of the nearest [WindowScope], or null if not found.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindowController.title], which returns the current title of the window.
/// * [maybeOf], which returns the [BaseWindowController] associated with the window, or null if not found.
@internal
static String? maybeTitleOf(BuildContext context) {
final BaseWindowController? controller = _maybeOf(context, _WindowControllerAspect.title);
if (controller == null) {
return null;
}
return switch (controller) {
RegularWindowController() => controller.title,
DialogWindowController() => controller.title,
TooltipWindowController() => '',
PopupWindowController() => '',
};
}
/// Returns the activation status of the nearest [WindowScope].
///
/// {@macro flutter.widgets.windowing.windowScope.of}
///
/// If the window associated with the controller does not support activation,
/// this method will throw an [UnsupportedError].
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindowController.isActivated], which returns the current activation status of the window.
/// * [of], which returns the [BaseWindowController] associated with the window.
@internal
static bool isActivatedOf(BuildContext context) {
final BaseWindowController controller = _of(context, _WindowControllerAspect.activated);
return switch (controller) {
RegularWindowController() => controller.isActivated,
DialogWindowController() => controller.isActivated,
TooltipWindowController() => false,
PopupWindowController() => controller.isActivated,
};
}
/// Returns the activation status of the nearest [WindowScope],
/// or null if not found.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindowController.isActivated], which returns the current activation status of the window.
/// * [maybeOf], which returns the [BaseWindowController] associated with the window, or null if not found.
@internal
static bool? maybeIsActivatedOf(BuildContext context) {
final BaseWindowController? controller = _maybeOf(context, _WindowControllerAspect.activated);
if (controller == null) {
return null;
}
return switch (controller) {
RegularWindowController() => controller.isActivated,
DialogWindowController() => controller.isActivated,
TooltipWindowController() => false,
PopupWindowController() => controller.isActivated,
};
}
/// Returns the minimization status of the nearest [WindowScope].
///
/// {@macro flutter.widgets.windowing.windowScope.of}
///
/// If the window associated with the controller does not support minimization,
/// this method will throw an [UnsupportedError].
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindowController.isMinimized], which returns the current minimized status of the window.
/// * [of], which returns the [BaseWindowController] associated with the window.
@internal
static bool isMinimizedOf(BuildContext context) {
final BaseWindowController controller = _of(context, _WindowControllerAspect.minimized);
return switch (controller) {
RegularWindowController() => controller.isMinimized,
DialogWindowController() => controller.isMinimized,
TooltipWindowController() => false,
PopupWindowController() => false,
};
}
/// Returns the minimization status of the nearest [WindowScope],
/// or null if not found.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindowController.isMinimized], which returns the current minimized status of the window.
/// * [maybeOf], which returns the [BaseWindowController] associated with the window, or null if not found.
@internal
static bool? maybeIsMinimizedOf(BuildContext context) {
final BaseWindowController? controller = _maybeOf(context, _WindowControllerAspect.minimized);
if (controller == null) {
return null;
}
return switch (controller) {
RegularWindowController() => controller.isMinimized,
DialogWindowController() => controller.isMinimized,
TooltipWindowController() => false,
PopupWindowController() => false,
};
}
/// Returns the maximization status of the nearest [WindowScope].
///
/// {@macro flutter.widgets.windowing.windowScope.of}
///
/// If the window associated with the controller does not support maximization,
/// this method will throw an [UnsupportedError].
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindowController.isMaximized], which returns the current maximized status of the window.
/// * [of], which returns the [BaseWindowController] associated with the window.
@internal
static bool isMaximizedOf(BuildContext context) {
final BaseWindowController controller = _of(context, _WindowControllerAspect.maximized);
return switch (controller) {
RegularWindowController() => controller.isMaximized,
DialogWindowController() => false,
TooltipWindowController() => false,
PopupWindowController() => false,
};
}
/// Returns the maximization status of the nearest [WindowScope],
/// or null if not found.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindowController.isMaximized], which returns the current maximized status of the window.
/// * [maybeOf], which returns the [BaseWindowController] associated with the window, or null if not found.
@internal
static bool? maybeIsMaximizedOf(BuildContext context) {
final BaseWindowController? controller = _maybeOf(context, _WindowControllerAspect.maximized);
if (controller == null) {
return null;
}
return switch (controller) {
RegularWindowController() => controller.isMaximized,
DialogWindowController() => false,
TooltipWindowController() => false,
PopupWindowController() => false,
};
}
/// Returns the fullscreen status of the nearest [WindowScope].
///
/// {@macro flutter.widgets.windowing.windowScope.of}
///
/// If the window associated with the controller does not support fullscreen,
/// this method will throw an [UnsupportedError].
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindowController.isFullscreen], which returns the current fullscreen status of the window.
/// * [of], which returns the [BaseWindowController] associated with the window.
@internal
static bool isFullscreenOf(BuildContext context) {
final BaseWindowController controller = _of(context, _WindowControllerAspect.fullscreen);
return switch (controller) {
RegularWindowController() => controller.isFullscreen,
DialogWindowController() => false,
TooltipWindowController() => false,
PopupWindowController() => false,
};
}
/// Returns the fullscreen status of the nearest [WindowScope],
/// or null if not found.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindowController.isFullscreen], which returns the current fullscreen status of the window.
/// * [maybeOf], which returns the [BaseWindowController] associated with the window, or null if not found.
@internal
static bool? maybeIsFullscreenOf(BuildContext context) {
final BaseWindowController? controller = _maybeOf(context, _WindowControllerAspect.fullscreen);
if (controller == null) {
return null;
}
return switch (controller) {
RegularWindowController() => controller.isFullscreen,
DialogWindowController() => false,
TooltipWindowController() => false,
PopupWindowController() => false,
};
}
static BaseWindowController _of(BuildContext context, [_WindowControllerAspect? aspect]) {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
assert(_debugCheckHasWindowController(context));
return InheritedModel.inheritFrom<WindowScope>(context, aspect: aspect)!.controller;
}
static BaseWindowController? _maybeOf(BuildContext context, [_WindowControllerAspect? aspect]) {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
return InheritedModel.inheritFrom<WindowScope>(context, aspect: aspect)?.controller;
}
static bool _debugCheckHasWindowController(BuildContext context) {
assert(() {
if (context.dependOnInheritedWidgetOfExactType<WindowScope>() == null) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('No WindowScope found in context.'),
ErrorDescription(
'${context.widget.runtimeType} widgets require a WindowScope widget ancestor.',
),
context.describeWidget(
'The specific widget that could not find a WindowScope ancestor was',
),
context.describeOwnershipChain('The ownership chain for the affected widget is'),
ErrorHint(
'No WindowScope ancestor could be found starting from the context '
'that was passed to WindowScope.of(). This can happen because the '
'context used is not a descendant of a RegularWindow widget, which introduces '
'a WindowScope.',
),
]);
}
return true;
}());
return true;
}
/// {@macro flutter.widgets.windowing.experimental}
@internal
@override
bool updateShouldNotify(WindowScope oldWidget) => controller != oldWidget.controller;
/// {@macro flutter.widgets.windowing.experimental}
@internal
@override
bool updateShouldNotifyDependent(WindowScope oldWidget, Set<Object> dependencies) {
return dependencies.any(
(Object dependency) =>
dependency is _WindowControllerAspect &&
switch (dependency) {
_WindowControllerAspect.contentSize =>
controller.contentSize != oldWidget.controller.contentSize,
_WindowControllerAspect.title => switch (controller) {
final RegularWindowController regular =>
regular.title != (oldWidget.controller as RegularWindowController).title,
final DialogWindowController dialog =>
dialog.title != (oldWidget.controller as DialogWindowController).title,
TooltipWindowController() => false,
PopupWindowController() => false,
},
_WindowControllerAspect.activated => switch (controller) {
final RegularWindowController regular =>
regular.isActivated !=
(oldWidget.controller as RegularWindowController).isActivated,
final DialogWindowController dialog =>
dialog.isActivated != (oldWidget.controller as DialogWindowController).isActivated,
TooltipWindowController() => false,
final PopupWindowController popup =>
popup.isActivated != (oldWidget.controller as PopupWindowController).isActivated,
},
_WindowControllerAspect.maximized => switch (controller) {
final RegularWindowController regular =>
regular.isMaximized !=
(oldWidget.controller as RegularWindowController).isMaximized,
DialogWindowController() => false,
TooltipWindowController() => false,
PopupWindowController() => false,
},
_WindowControllerAspect.minimized => switch (controller) {
final RegularWindowController regular =>
regular.isMinimized !=
(oldWidget.controller as RegularWindowController).isMinimized,
final DialogWindowController dialog =>
dialog.isMinimized != (oldWidget.controller as DialogWindowController).isMinimized,
TooltipWindowController() => false,
PopupWindowController() => false,
},
_WindowControllerAspect.fullscreen => switch (controller) {
final RegularWindowController regular =>
regular.isFullscreen !=
(oldWidget.controller as RegularWindowController).isFullscreen,
DialogWindowController() => false,
TooltipWindowController() => false,
PopupWindowController() => false,
},
},
);
}
}