blob: 77d3eae341712a35723fd3f0846e4f6f897d59ba [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: avoid_as, null_check_always_fails, unnecessary_null_comparison
import 'package:fidl_fuchsia_ui_views/fidl_async.dart';
import 'package:flutter/widgets.dart';
import 'package:fuchsia_scenic/views.dart';
import 'package:zircon/zircon.dart';
import 'fuchsia_view_controller.dart';
import 'pointer_injector.dart';
/// Defines a thin wrapper around [FuchsiaViewController].
///
/// Its primary purpose is to hold on to [ViewHolderToken] for the lifetime of
/// the view controller, since [FuchsiaViewController] is agnostic to all
/// Fuchsia data types. (Eventually, [FuchsiaView] and [FuchsiaViewController]
/// will be moved to Flutter framework, which cannot have Fuchsia data types.)
class FuchsiaViewConnection extends FuchsiaViewController {
/// The Gfx view tree token when the view is attached.
final ViewHolderToken? viewHolderToken;
/// The Flatland token when the view is attached.
final ViewportCreationToken? viewportCreationToken;
/// The handle to the view used for [requestFocus] calls.
final ViewRef? viewRef;
PointerInjector? _pointerInjector;
/// Returns the [PointerInjector] instance used by this connection.
@visibleForTesting
PointerInjector get pointerInjector => _pointerInjector ??=
PointerInjector.fromSvcPath(onError: onPointerInjectionError);
/// Callback when the connection to child's view is connected to view tree.
final FuchsiaViewConnectionCallback? _onViewConnected;
/// Callback when the child's view is disconnected from view tree.
final FuchsiaViewConnectionCallback? _onViewDisconnected;
/// Callback when the child's view render state changes.
final FuchsiaViewConnectionStateCallback? _onViewStateChanged;
/// Set to true if pointer injection into child views should be enabled.
/// This requires the view's [ViewRef] to be set during construction.
final bool usePointerInjection;
final bool useFlatland;
/// Constructor.
FuchsiaViewConnection(
this.viewHolderToken, {
this.viewRef,
FuchsiaViewConnectionCallback? onViewConnected,
FuchsiaViewConnectionCallback? onViewDisconnected,
FuchsiaViewConnectionStateCallback? onViewStateChanged,
this.usePointerInjection = false,
this.useFlatland = false,
}) : assert(viewHolderToken!.value != null && viewHolderToken.value.isValid),
assert(
viewRef?.reference == null || viewRef!.reference.handle!.isValid),
assert(!usePointerInjection || viewRef?.reference != null),
viewportCreationToken = null,
_onViewConnected = onViewConnected,
_onViewDisconnected = onViewDisconnected,
_onViewStateChanged = onViewStateChanged,
super(
viewId: viewHolderToken!.value.handle!.handle,
onViewConnected: _handleViewConnected,
onViewDisconnected: _handleViewDisconnected,
onViewStateChanged: _handleViewStateChanged,
onPointerEvent: _handlePointerEvent,
);
FuchsiaViewConnection.flatland(
this.viewportCreationToken, {
this.viewRef,
FuchsiaViewConnectionCallback? onViewConnected,
FuchsiaViewConnectionCallback? onViewDisconnected,
FuchsiaViewConnectionStateCallback? onViewStateChanged,
this.usePointerInjection = false,
this.useFlatland = true,
}) : assert(viewportCreationToken!.value != null &&
viewportCreationToken.value.isValid),
assert(
viewRef?.reference == null || viewRef!.reference.handle!.isValid),
assert(!usePointerInjection || viewRef?.reference != null),
viewHolderToken = null,
_onViewConnected = onViewConnected,
_onViewDisconnected = onViewDisconnected,
_onViewStateChanged = onViewStateChanged,
super(
viewId: viewportCreationToken!.value.handle!.handle,
onViewConnected: _handleViewConnected,
onViewDisconnected: _handleViewDisconnected,
onViewStateChanged: _handleViewStateChanged,
onPointerEvent: _handlePointerEvent,
);
/// Requests that focus be transferred to the remote Scene represented by
/// this connection. This method is the point at which focus handling for
/// flatland diverges. In Flatland, the Flutter engine holds the ViewRef
/// and does not provide it to dart code, so we must refer to the child
/// view by viewId instead
@override
Future<void> requestFocus([int _ = 0]) async {
assert(viewRef?.reference != null && _ == 0);
if (useFlatland) {
return super.requestFocusById(viewId);
} else {
return super.requestFocus(viewRef!.reference.handle!.handle);
}
}
static void _handleViewStateChanged(
FuchsiaViewController controller, bool? state) async {
FuchsiaViewConnection connection = controller as FuchsiaViewConnection;
connection._onViewStateChanged?.call(controller, state);
}
@visibleForTesting
ViewRef get hostViewRef => ScenicContext.hostViewRef();
static void _handleViewConnected(FuchsiaViewController controller) async {
FuchsiaViewConnection connection = controller as FuchsiaViewConnection;
connection._onViewConnected?.call(controller);
}
static void _handleViewDisconnected(FuchsiaViewController controller) {
FuchsiaViewConnection connection = controller as FuchsiaViewConnection;
connection._onViewDisconnected?.call(controller);
// TODO(fxbug.dev/84035): Dispose eagerly, on widget disconnect lifecycle
// event. Here, injection races with view-disconnect on the server.
if (connection.usePointerInjection) {
connection.pointerInjector.dispose();
}
}
static Future<void> _handlePointerEvent(
FuchsiaViewController controller, PointerEvent pointer) async {
FuchsiaViewConnection connection = controller as FuchsiaViewConnection;
if (!connection.usePointerInjection) {
return;
}
// If we haven't made a pointerInjector for this View yet, or if the old one
// was torn down, we need to create one.
if (!connection.pointerInjector.registered) {
// The only valid pointer event to start an injector with is DOWN event.
// This check affords protection against the first stream if malformed,
// but does not guard against future malformed streams.
if (!(pointer is PointerDownEvent)) {
return;
}
// Create the pointerInjector.
final viewRefDup = ViewRef(
reference:
connection.viewRef!.reference.duplicate(ZX.RIGHT_SAME_RIGHTS));
connection.pointerInjector.register(
hostViewRef: connection.hostViewRef,
viewRef: viewRefDup,
);
}
return connection.pointerInjector.dispatchEvent(pointer: pointer);
}
@visibleForTesting
void onPointerInjectionError() {
// Dispose the current instance of pointer injector. It gets recreated on
// next _handlePointerEvent().
pointerInjector.dispose();
_pointerInjector = null;
}
}