child_view.dart 7.45 KB
Newer Older
1 2 3 4 5
// Copyright 2016 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:async';
6
import 'dart:ui' as ui;
7 8 9 10 11 12 13

import 'package:flutter/services.dart';
import 'package:mojo_services/mojo/gfx/composition/scene_token.mojom.dart' as mojom;
import 'package:mojo_services/mojo/ui/layouts.mojom.dart' as mojom;
import 'package:mojo_services/mojo/ui/view_provider.mojom.dart' as mojom;
import 'package:mojo_services/mojo/ui/views.mojom.dart' as mojom;
import 'package:mojo/application.dart';
14
import 'package:mojo/core.dart' as core;
15 16 17 18 19
import 'package:mojo/mojo/service_provider.mojom.dart' as mojom;

import 'box.dart';
import 'object.dart';

Adam Barth's avatar
Adam Barth committed
20
mojom.ViewProxy _initViewProxy() {
21
  int viewHandle = ui.MojoServices.takeView();
22 23
  if (viewHandle == core.MojoHandle.INVALID)
    return null;
Adam Barth's avatar
Adam Barth committed
24
  return new mojom.ViewProxy.fromHandle(new core.MojoHandle(viewHandle));
25 26 27 28 29 30 31
}

// TODO(abarth): The view host is a unique resource. We should structure how we
// take the handle from the engine so that multiple libraries can interact with
// the view host safely. Unfortunately, the view host has a global namespace of
// view keys, which means any scheme for sharing the view host also needs to
// provide a mechanism for coordinating about view keys.
Adam Barth's avatar
Adam Barth committed
32 33
final mojom.ViewProxy _viewProxy = _initViewProxy();
final mojom.View _view = _viewProxy?.ptr;
34

35 36 37
/// (mojo-only) A connection with a child view.
///
/// Used with the [ChildView] widget to display a child view.
38
class ChildViewConnection {
39 40
  /// Establishes a connection to the app at the given URL.
  ChildViewConnection({ String url }) {
41 42
    mojom.ViewProviderProxy viewProvider = new mojom.ViewProviderProxy.unbound();
    shell.connectToService(url, viewProvider);
Adam Barth's avatar
Adam Barth committed
43 44 45 46 47
    mojom.ServiceProviderProxy incomingServices = new mojom.ServiceProviderProxy.unbound();
    mojom.ServiceProviderStub outgoingServices = new mojom.ServiceProviderStub.unbound();
    _viewOwner = new mojom.ViewOwnerProxy.unbound();
    viewProvider.ptr.createView(_viewOwner, incomingServices, outgoingServices);
    viewProvider.close();
48 49 50
    _connection = new ApplicationConnection(outgoingServices, incomingServices);
  }

P.Y. Laligand's avatar
P.Y. Laligand committed
51
  /// Wraps an already-established connection to a child app.
52 53 54 55
  ChildViewConnection.fromViewOwner({
    mojom.ViewOwnerProxy viewOwner,
    ApplicationConnection connection
  }) : _connection = connection, _viewOwner = viewOwner;
56

57 58 59 60
  /// The underlying application connection to the child app.
  ///
  /// Useful for requesting services from the child app and for providing
  /// services to the child app.
61 62 63
  ApplicationConnection get connection => _connection;
  ApplicationConnection _connection;

Adam Barth's avatar
Adam Barth committed
64
  mojom.ViewOwnerProxy _viewOwner;
65 66 67 68 69 70

  static int _nextViewKey = 1;
  int _viewKey;

  void _addChildToViewHost() {
    assert(_attached);
Adam Barth's avatar
Adam Barth committed
71
    assert(_viewOwner != null);
72 73
    assert(_viewKey == null);
    _viewKey = _nextViewKey++;
74
    _view?.addChild(_viewKey, _viewOwner.impl);
Adam Barth's avatar
Adam Barth committed
75
    _viewOwner = null;
76 77 78 79
  }

  void _removeChildFromViewHost() {
    assert(!_attached);
Adam Barth's avatar
Adam Barth committed
80
    assert(_viewOwner == null);
81
    assert(_viewKey != null);
Adam Barth's avatar
Adam Barth committed
82
    _viewOwner = new mojom.ViewOwnerProxy.unbound();
83
    _view?.removeChild(_viewKey, _viewOwner);
84 85 86 87 88 89 90 91 92 93 94 95 96
    _viewKey = null;
  }

  // The number of render objects attached to this view. In between frames, we
  // might have more than one connected if we get added to a new render object
  // before we get removed from the old render object. By the time we get around
  // to computing our layout, we must be back to just having one render object.
  int _attachments = 0;
  bool get _attached => _attachments > 0;

  void _attach() {
    assert(_attachments >= 0);
    ++_attachments;
Adam Barth's avatar
Adam Barth committed
97
    if (_viewKey == null)
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
      _addChildToViewHost();
  }

  void _detach() {
    assert(_attached);
    --_attachments;
    scheduleMicrotask(_removeChildFromViewHostIfNeeded);
  }

  void _removeChildFromViewHostIfNeeded() {
    assert(_attachments >= 0);
    if (_attachments == 0)
      _removeChildFromViewHost();
  }

  Future<mojom.ViewLayoutInfo> _layout({ Size size, double scale }) async {
    assert(_attached);
    assert(_attachments == 1);
    assert(_viewKey != null);
117 118
    if (_view == null)
      return new Future<mojom.ViewLayoutInfo>.value(null);
119 120
    int width = (size.width * scale).round();
    int height = (size.height * scale).round();
P.Y. Laligand's avatar
P.Y. Laligand committed
121
    // TODO(abarth): Ideally we would propagate our actual constraints to be
122 123 124 125 126 127 128 129 130 131
    // able to support rich cross-app layout. For now, we give the child tight
    // constraints for simplicity.
    mojom.BoxConstraints childConstraints = new mojom.BoxConstraints()
      ..minWidth = width
      ..maxWidth = width
      ..minHeight = height
      ..maxHeight = height;
    mojom.ViewLayoutParams layoutParams = new mojom.ViewLayoutParams()
      ..constraints = childConstraints
      ..devicePixelRatio = scale;
Adam Barth's avatar
Adam Barth committed
132
    return (await _view.layoutChild(_viewKey, layoutParams)).info;
133 134 135
  }
}

136
/// (mojo-only) A view of a child application.
137 138 139 140
class RenderChildView extends RenderBox {
  RenderChildView({
    ChildViewConnection child,
    double scale
Adam Barth's avatar
Adam Barth committed
141
  }) : _child = child, _scale = scale;
142

143
  /// The child to display.
144 145 146 147 148 149 150 151 152 153 154 155 156 157
  ChildViewConnection get child => _child;
  ChildViewConnection _child;
  void set child (ChildViewConnection value) {
    if (value == _child)
      return;
    if (attached)
      _child?._detach();
    _child = value;
    _layoutInfo = null;
    if (attached)
      _child?._attach();
    if (_child == null) {
      markNeedsPaint();
    } else {
Adam Barth's avatar
Adam Barth committed
158
      markNeedsLayout();
159 160 161
    }
  }

162
  /// The device pixel ratio to provide the child.
163 164 165 166 167 168 169 170 171 172
  double get scale => _scale;
  double _scale;
  void set scale (double value) {
    if (value == _scale)
      return;
    _scale = value;
    if (_child != null)
      markNeedsLayout();
  }

173
  @override
174 175 176 177 178
  void attach() {
    super.attach();
    _child?._attach();
  }

179
  @override
180 181 182 183 184
  void detach() {
    _child?._detach();
    super.detach();
  }

185
  @override
186
  bool get alwaysNeedsCompositing => true;
187 188

  @override
189 190
  bool get sizedByParent => true;

191
  @override
192 193 194 195
  void performResize() {
    size = constraints.biggest;
  }

196 197
  TextPainter _debugErrorMessage;

198
  @override
199
  void performLayout() {
200
    if (_child != null) {
201
      _child._layout(size: size, scale: scale).then(_handleLayoutInfoChanged);
202 203 204 205 206 207 208 209 210 211 212 213 214 215
      assert(() {
        if (_view == null) {
          _debugErrorMessage ??= new TextPainter()
            ..text = new TextSpan(text: 'Child view are supported only when running in Mojo shell.');
          _debugErrorMessage
            ..minWidth = size.width
            ..maxWidth = size.width
            ..minHeight = size.height
            ..maxHeight = size.height
            ..layout();
        }
        return true;
      });
    }
216 217 218 219 220 221 222 223 224
  }

  mojom.ViewLayoutInfo _layoutInfo;

  void _handleLayoutInfoChanged(mojom.ViewLayoutInfo layoutInfo) {
    _layoutInfo = layoutInfo;
    markNeedsPaint();
  }

225
  @override
226 227
  bool hitTestSelf(Point position) => true;

228
  @override
229 230 231
  void paint(PaintingContext context, Offset offset) {
    assert(needsCompositing);
    if (_layoutInfo != null)
Adam Barth's avatar
Adam Barth committed
232
      context.pushChildScene(offset, scale, _layoutInfo);
233 234 235 236 237 238 239
    assert(() {
      if (_view == null) {
        context.canvas.drawRect(offset & size, new Paint()..color = const Color(0xFF0000FF));
        _debugErrorMessage.paint(context.canvas, offset);
      }
      return true;
    });
240 241
  }

242
  @override
243 244 245 246
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('child: $child');
    description.add('scale: $scale');
247 248
  }
}