Commit f30398a4 authored by Adam Barth's avatar Adam Barth

Initial work to add Mozart child views

This patch sketches out the basic widgets for creating Mozart child
views. We're still missing some of the machinery, so we can't quite
create child views yet, but once we supply the ViewHost and teach the
compositor how to actually display the child scenes, we'll have
something that works.
parent 421cd7c2
......@@ -10,6 +10,7 @@ export 'src/rendering/basic_types.dart';
export 'src/rendering/binding.dart';
export 'src/rendering/block.dart';
export 'src/rendering/box.dart';
export 'src/rendering/child_view.dart';
export 'src/rendering/custom_layout.dart';
export 'src/rendering/debug.dart';
export 'src/rendering/editable_line.dart';
......
// 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';
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';
import 'package:mojo/mojo/service_provider.mojom.dart' as mojom;
import 'box.dart';
import 'object.dart';
// TODO(abarth): Populate the view host.
final mojom.ViewHost _viewHost = null;
class ChildViewConnection {
ChildViewConnection({ this.url }) {
mojom.ServiceProviderProxy incomingServices = new mojom.ServiceProviderProxy.unbound();
mojom.ServiceProviderStub outgoingServices = new mojom.ServiceProviderStub.unbound();
_connection = new ApplicationConnection(outgoingServices, incomingServices);
_unresolvedViewToken = _connectToView(incomingServices, outgoingServices);
}
final String url;
ApplicationConnection get connection => _connection;
ApplicationConnection _connection;
Future<mojom.ViewToken> _unresolvedViewToken;
mojom.ViewToken _viewToken;
Future<mojom.ViewToken> _connectToView(
mojom.ServiceProviderProxy incomingServices,
mojom.ServiceProviderStub outgoingServices
) async {
assert(_viewToken == null);
mojom.ViewProviderProxy viewProvider = new mojom.ViewProviderProxy.unbound();
shell.connectToService(url, proxy);
mojom.ViewToken viewToken = (await viewProvider.ptr.createView(incomingServices, outgoingServices)).viewToken;
viewProvider.close();
assert(_viewToken == null);
_viewToken = viewToken;
assert(_viewKey == null);
if (_attached)
_addChildToViewHost();
return viewToken;
}
static int _nextViewKey = 1;
int _viewKey;
void _addChildToViewHost() {
assert(_attached);
assert(_viewToken != null);
assert(_viewKey == null);
_viewKey = _nextViewKey++;
_viewHost.addChild(_viewKey, _viewToken);
}
void _removeChildFromViewHost() {
assert(!_attached);
assert(_viewToken != null);
assert(_viewKey != null);
_viewHost.removeChild(_viewKey);
_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;
if (_viewToken != null && _viewKey == null)
_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);
int width = (size.width * scale).round();
int height = (size.height * scale).round();
// TODO(abarth): Ideally we would propagate our actually constraints to be
// 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;
return (await _viewHost.layoutChild(_viewKey, layoutParams)).info;
}
String toString() {
return '$runtimeType(url: $url)';
}
}
class RenderChildView extends RenderBox {
RenderChildView({
ChildViewConnection child,
double scale
}) : _child = child, _scale = scale;
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 if (_child._viewToken != null) {
// We've already connected to the view, so we're ready to invalidate our
// layout immediately.
markNeedsLayout();
} else {
// Otherwise, we're still in the process of connecting, so we need to
// repaint now (to remove any old child view), and we need to watch for
// the view token resolving before attempting layout.
markNeedsPaint();
_child._unresolvedViewToken.then(_handleViewTokenResolved);
}
}
double get scale => _scale;
double _scale;
void set scale (double value) {
if (value == _scale)
return;
_scale = value;
if (_child != null)
markNeedsLayout();
}
void attach() {
super.attach();
_child?._attach();
}
void detach() {
_child?._detach();
super.detach();
}
bool get alwaysNeedsCompositing => true;
bool get sizedByParent => true;
void performResize() {
size = constraints.biggest;
}
void performLayout() {
if (_child != null)
_child._layout(size: size, scale: scale).then(_handleLayoutInfoChanged);
}
mojom.ViewLayoutInfo _layoutInfo;
void _handleLayoutInfoChanged(mojom.ViewLayoutInfo layoutInfo) {
_layoutInfo = layoutInfo;
markNeedsPaint();
}
void _handleViewTokenResolved(mojom.ViewToken viewToken) {
// The _viewToken might not match viewToken if _child changed between the
// time we started waiting for the future and the time it resolved.
if (attached && _child?._viewToken == viewToken)
markNeedsLayout();
}
void paint(PaintingContext context, Offset offset) {
assert(needsCompositing);
if (_layoutInfo != null)
context.pushChildScene(offset, _layoutInfo);
}
void debugDescribeSettings(List<String> settings) {
super.debugDescribeSettings(settings);
settings.add('child: $child');
settings.add('scale: $scale');
}
}
......@@ -5,6 +5,7 @@
import 'dart:ui' as ui;
import 'package:vector_math/vector_math_64.dart';
import 'package:mojo_services/mojo/ui/layouts.mojom.dart' as mojom;
import 'basic_types.dart';
import 'debug.dart';
......@@ -107,6 +108,30 @@ class PictureLayer extends Layer {
}
}
class ChildSceneLayer extends Layer {
ChildSceneLayer({ this.offset, this.layoutInfo });
Offset offset;
mojom.ViewLayoutInfo layoutInfo;
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
builder.addChildScene(
offset + layerOffset,
layoutInfo.size.width,
layoutInfo.size.height,
layoutInfo.sceneToken.value
);
}
void debugDescribeSettings(List<String> settings) {
super.debugDescribeSettings(settings);
settings.add('offset: $offset');
settings.add('physicalWidth: ${layoutInfo.size.width}');
settings.add('physicalHeight: ${layoutInfo.size.height}');
settings.add('sceneToken.value: ${layoutInfo.sceneToken.value}');
}
}
/// A layer that indicates to the compositor that it should display
/// certain performance statistics within it.
class PerformanceOverlayLayer extends Layer {
......
......@@ -9,6 +9,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:mojo_services/mojo/ui/layouts.mojom.dart' as mojom;
import 'package:vector_math/vector_math_64.dart';
import 'debug.dart';
......@@ -189,12 +190,16 @@ class PaintingContext {
/// compositor.
void pushPerformanceOverlay(Offset offset, int optionsMask, int rasterizerThreshold, Size size) {
_stopRecordingIfNeeded();
PerformanceOverlayLayer performanceOverlayLayer = new PerformanceOverlayLayer(
_appendLayer(new PerformanceOverlayLayer(
overlayRect: new Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height),
optionsMask: optionsMask,
rasterizerThreshold: rasterizerThreshold
);
_appendLayer(performanceOverlayLayer);
));
}
void pushChildScene(Offset offset, mojom.ViewLayoutInfo layoutInfo) {
_stopRecordingIfNeeded();
_appendLayer(new ChildSceneLayer(offset: offset, layoutInfo: layoutInfo));
}
/// Push a rectangular clip rect.
......
// 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 'package:flutter/rendering.dart';
import 'media_query.dart';
import 'framework.dart';
export 'package:flutter/rendering.dart' show ChildViewConnection;
class ChildView extends StatelessComponent {
ChildView({ Key key, this.child }) : super(key: key);
final ChildViewConnection child;
Widget build(BuildContext context) {
return new _ChildViewWidget(
child: child,
scale: MediaQuery.of(context).devicePixelRatio
);
}
}
class _ChildViewWidget extends LeafRenderObjectWidget {
_ChildViewWidget({
ChildViewConnection child,
this.scale
}) : child = child, super(key: new GlobalObjectKey(child));
final ChildViewConnection child;
final double scale;
RenderChildView createRenderObject() => new RenderChildView(child: child, scale: scale);
void updateRenderObject(RenderChildView renderObject, _ChildViewWidget oldWidget) {
renderObject
..child = child
..scale = scale;
}
}
......@@ -8,6 +8,7 @@ library widgets;
export 'src/widgets/asset_vendor.dart';
export 'src/widgets/basic.dart';
export 'src/widgets/binding.dart';
export 'src/widgets/child_view.dart';
export 'src/widgets/dismissable.dart';
export 'src/widgets/drag_target.dart';
export 'src/widgets/editable.dart';
......@@ -35,9 +36,9 @@ export 'src/widgets/placeholder.dart';
export 'src/widgets/raw_keyboard_listener.dart';
export 'src/widgets/routes.dart';
export 'src/widgets/scroll_behavior.dart';
export 'src/widgets/scrollable.dart';
export 'src/widgets/scrollable_grid.dart';
export 'src/widgets/scrollable_list.dart';
export 'src/widgets/scrollable.dart';
export 'src/widgets/semantics_debugger.dart';
export 'src/widgets/status_transitions.dart';
export 'src/widgets/title.dart';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment