| // 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. | |
| // TODO(mattkae): remove invalid_use_of_internal_member ignore comment when this API is stable. | |
| // See: https://github.com/flutter/flutter/issues/177586 | |
| // TODO(mattkae): refactor this example for better widget position tracking | |
| // This positioning logic is simpler than you might want in production. See https://github.com/flutter/flutter/issues/178829. | |
| // ignore_for_file: invalid_use_of_internal_member | |
| // ignore_for_file: implementation_imports | |
| import 'package:flutter/material.dart'; | |
| import 'package:flutter/src/widgets/_window.dart'; | |
| import 'package:flutter/src/widgets/_window_positioner.dart'; | |
| void main() { | |
| try { | |
| runWidget( | |
| Window( | |
| controller: WindowController( | |
| size: const Size(800, 600), | |
| constraints: const BoxConstraints(minWidth: 640, minHeight: 480), | |
| title: 'Example Window', | |
| ), | |
| child: const MaterialApp(home: MyApp()), | |
| ), | |
| ); | |
| } on UnsupportedError catch (e) { | |
| // TODO(mattkae): Remove this catch block when Windows tooltips are supported in tests. | |
| // For now, we need to catch the error so that the API smoke tests pass. | |
| runApp( | |
| MaterialApp( | |
| home: Scaffold(body: Center(child: Text(e.message ?? 'Unsupported'))), | |
| ), | |
| ); | |
| } | |
| } | |
| class MyApp extends StatefulWidget { | |
| const MyApp({super.key}); | |
| @override | |
| State<MyApp> createState() { | |
| return _MyAppState(); | |
| } | |
| } | |
| class _CallbackPopupDelegate extends PopupWindowControllerDelegate { | |
| _CallbackPopupDelegate({required this.onDestroyCallback}); | |
| final VoidCallback onDestroyCallback; | |
| @override | |
| void onWindowDestroyed() { | |
| onDestroyCallback(); | |
| } | |
| } | |
| class _MyAppState extends State<MyApp> { | |
| final GlobalKey _key = GlobalKey(); | |
| PopupWindowController? _popupController; | |
| @override | |
| Widget build(BuildContext context) { | |
| final List<Widget> children = <Widget>[ | |
| ElevatedButton( | |
| key: _key, | |
| onPressed: () { | |
| setState(() { | |
| _popupController ??= PopupWindowController( | |
| parent: WindowScope.of(context), | |
| anchorRect: _getAnchorRect()!, | |
| positioner: const WindowPositioner( | |
| parentAnchor: .right, | |
| childAnchor: .left, | |
| ), | |
| delegate: _CallbackPopupDelegate( | |
| onDestroyCallback: () { | |
| setState(() { | |
| _popupController = null; | |
| }); | |
| }, | |
| ), | |
| ); | |
| }); | |
| }, | |
| child: const Text('Show Popup'), | |
| ), | |
| ]; | |
| if (_popupController != null) { | |
| children.add( | |
| PopupWindow( | |
| controller: _popupController!, | |
| child: Container( | |
| padding: const .all(8), | |
| color: Colors.black, | |
| child: Column( | |
| mainAxisSize: .min, | |
| children: <Widget>[ | |
| const Text( | |
| 'This is a popup', | |
| style: TextStyle(color: Colors.white), | |
| ), | |
| const SizedBox(height: 8), | |
| ElevatedButton( | |
| onPressed: () { | |
| setState(() { | |
| _popupController?.destroy(); | |
| }); | |
| }, | |
| child: const Text('Close'), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| return Scaffold( | |
| body: Center( | |
| child: Row(mainAxisSize: .min, children: children), | |
| ), | |
| ); | |
| } | |
| Rect? _getAnchorRect() { | |
| final RenderBox? renderBox = | |
| _key.currentContext?.findRenderObject() as RenderBox?; | |
| if (renderBox != null) { | |
| final Offset position = renderBox.localToGlobal(Offset.zero); | |
| final Size size = renderBox.size; | |
| return position & size; // creates a Rect | |
| } | |
| return null; | |
| } | |
| } |