| // Copyright 2015 The Chromium 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:math' as math; |
| |
| import 'package:flutter/material.dart'; |
| |
| class LauncherData { |
| const LauncherData({ this.url, this.title }); |
| final String url; |
| final String title; |
| } |
| |
| final List<LauncherData> _kLauncherData = <LauncherData>[ |
| const LauncherData( |
| url: 'mojo:noodles_view', |
| title: 'Noodles' |
| ), |
| const LauncherData( |
| url: 'mojo:shapes_view', |
| title: 'Shapes' |
| ), |
| new LauncherData( |
| url: Uri.base.resolve('../../../examples/stocks/build/app.flx').toString(), |
| title: 'Stocks' |
| ), |
| ]; |
| |
| const Size _kInitialWindowSize = const Size(200.0, 200.0); |
| const double _kWindowPadding = 10.0; |
| |
| enum WindowSide { |
| topCenter, |
| topRight, |
| bottomRight, |
| } |
| |
| class WindowDecoration extends StatelessWidget { |
| WindowDecoration({ |
| Key key, |
| this.side, |
| this.color, |
| this.onTap, |
| this.onPanUpdate |
| }) : super(key: key); |
| |
| final WindowSide side; |
| final Color color; |
| final GestureTapCallback onTap; |
| final GestureDragUpdateCallback onPanUpdate; |
| |
| @override |
| Widget build(BuildContext context) { |
| double top, right, bottom, left, width, height; |
| |
| height = _kWindowPadding * 2.0; |
| |
| if (side == WindowSide.topCenter || side == WindowSide.topRight) |
| top = 0.0; |
| |
| if (side == WindowSide.topRight || side == WindowSide.bottomRight) { |
| right = 0.0; |
| width = _kWindowPadding * 2.0; |
| } |
| |
| if (side == WindowSide.topCenter) { |
| left = _kWindowPadding; |
| right = _kWindowPadding; |
| } |
| |
| if (side == WindowSide.bottomRight) |
| bottom = 0.0; |
| |
| return new Positioned( |
| top: top, |
| right: right, |
| bottom: bottom, |
| left: left, |
| width: width, |
| height: height, |
| child: new GestureDetector( |
| onTap: onTap, |
| onPanUpdate: onPanUpdate, |
| child: new Container( |
| decoration: new BoxDecoration( |
| backgroundColor: color |
| ) |
| ) |
| ) |
| ); |
| } |
| } |
| |
| class Window extends StatefulWidget { |
| Window({ Key key, this.child, this.onClose }) : super(key: key); |
| |
| final ChildViewConnection child; |
| final ValueChanged<ChildViewConnection> onClose; |
| |
| @override |
| _WindowState createState() => new _WindowState(); |
| } |
| |
| class _WindowState extends State<Window> { |
| Offset _offset = Offset.zero; |
| Size _size = _kInitialWindowSize; |
| |
| void _handleResizerDrag(DragUpdateDetails details) { |
| setState(() { |
| _size = new Size( |
| math.max(0.0, _size.width + details.delta.dx), |
| math.max(0.0, _size.height + details.delta.dy) |
| ); |
| }); |
| } |
| |
| void _handleRepositionDrag(DragUpdateDetails details) { |
| setState(() { |
| _offset += details.delta; |
| }); |
| } |
| |
| void _handleClose() { |
| config.onClose(config.child); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return new Positioned( |
| left: _offset.dx, |
| top: _offset.dy, |
| width: _size.width + _kWindowPadding * 2.0, |
| height: _size.height + _kWindowPadding * 2.0, |
| child: new Stack( |
| children: <Widget>[ |
| new WindowDecoration( |
| side: WindowSide.topCenter, |
| onPanUpdate: _handleRepositionDrag, |
| color: Colors.green[200] |
| ), |
| new WindowDecoration( |
| side: WindowSide.topRight, |
| onTap: _handleClose, |
| color: Colors.red[200] |
| ), |
| new WindowDecoration( |
| side: WindowSide.bottomRight, |
| onPanUpdate: _handleResizerDrag, |
| color: Colors.blue[200] |
| ), |
| new Container( |
| padding: const EdgeInsets.all(_kWindowPadding), |
| child: new Material( |
| elevation: 8, |
| child: new ChildView(child: config.child) |
| ) |
| ) |
| ] |
| ) |
| ); |
| } |
| } |
| |
| class LauncherItem extends StatelessWidget { |
| LauncherItem({ |
| Key key, |
| this.url, |
| this.child, |
| this.onLaunch |
| }) : super(key: key); |
| |
| final String url; |
| final Widget child; |
| final ValueChanged<ChildViewConnection> onLaunch; |
| |
| @override |
| Widget build(BuildContext context) { |
| return new RaisedButton( |
| onPressed: () { onLaunch(new ChildViewConnection(url: url)); }, |
| child: child |
| ); |
| } |
| } |
| |
| class Launcher extends StatelessWidget { |
| Launcher({ Key key, this.items }) : super(key: key); |
| |
| final List<Widget> items; |
| |
| @override |
| Widget build(BuildContext context) { |
| return new ButtonBar( |
| alignment: MainAxisAlignment.center, |
| children: items |
| ); |
| } |
| } |
| |
| class WindowManager extends StatefulWidget { |
| @override |
| _WindowManagerState createState() => new _WindowManagerState(); |
| } |
| |
| class _WindowManagerState extends State<WindowManager> { |
| List<ChildViewConnection> _windows = <ChildViewConnection>[]; |
| |
| void _handleLaunch(ChildViewConnection child) { |
| setState(() { |
| _windows.add(child); |
| }); |
| } |
| |
| void _handleClose(ChildViewConnection child) { |
| setState(() { |
| _windows.remove(child); |
| }); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return new Material( |
| child: new Stack( |
| children: <Widget>[ |
| new Positioned( |
| left: 0.0, |
| right: 0.0, |
| bottom: 0.0, |
| child: new Launcher(items: _kLauncherData.map((LauncherData data) { |
| return new LauncherItem( |
| url: data.url, |
| onLaunch: _handleLaunch, |
| child: new Text(data.title) |
| ); |
| }).toList()) |
| ) |
| ]..addAll(_windows.map((ChildViewConnection child) { |
| return new Window( |
| key: new ObjectKey(child), |
| onClose: _handleClose, |
| child: child |
| ); |
| })) |
| ) |
| ); |
| } |
| } |
| |
| void main() { |
| runApp(new MaterialApp( |
| title: 'Mozart', |
| home: new WindowManager() |
| )); |
| } |