blob: b78e4a3725ceebdd7a3535a87855aa33fbbad2eb [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, unnecessary_null_comparison
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'focus_state.dart';
import 'fuchsia_views_service.dart';
/// Defines a callback to receive view connected and disconnected event.
typedef FuchsiaViewConnectionCallback = void Function(
FuchsiaViewController connection);
/// Defines a callback to receive view state changed event.
typedef FuchsiaViewConnectionStateCallback = void Function(
FuchsiaViewController connection, bool? newState);
/// Defines a callback to receive pointer events for the embedded view.
typedef FuchsiaPointerEventsCallback = Future<void> Function(
FuchsiaViewController, PointerEvent);
/// A connection to a fuchsia view. It can be used to construct a [FuchsiaView]
/// widget that will display the view's contents on their own layer.
class FuchsiaViewController implements PlatformViewController {
/// The raw value of the [ViewHolderToken] or [ViewportCreationToken] where
/// this view is attached.
@override
final int viewId;
/// 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 view's state changes.
final FuchsiaViewConnectionStateCallback? onViewStateChanged;
/// Callback when pointer events are dispatched on top of child view.
final FuchsiaPointerEventsCallback? onPointerEvent;
// The [bool] that tracks whether the platform view was created or not yet.
// The [FuchsiaViewController] will only create its associated platform view
// once during the controller's lifetime.
bool _viewCreated = false;
// The [Completer] that is completed when the platform view is connected.
Completer _whenConnected = Completer();
/// The future that completes when the platform view is connected.
Future get whenConnected => _whenConnected.future;
/// Returns true when platform view is connected.
bool get connected => _whenConnected.isCompleted;
/// Constructor.
FuchsiaViewController({
required this.viewId,
this.onViewConnected,
this.onViewDisconnected,
this.onViewStateChanged,
this.onPointerEvent,
}) : assert(viewId != null);
/// Connects to the platform view given it's [viewId].
///
/// Called by [FuchsiaView] when the platform view is ready to be initialized
/// and should not be called directly. This MUST NOT be called directly.
Future<void> connect({
bool hitTestable = true,
bool focusable = true,
Rect viewOcclusionHint = Rect.zero,
}) async {
if (_viewCreated) return;
_viewCreated = true; // Only allow this to be called once
// Setup callbacks for receiving view events.
FuchsiaViewsService.instance.register(viewId, (call) async {
switch (call.method) {
case 'View.viewConnected':
_whenConnected.complete();
onViewConnected?.call(this);
break;
case 'View.viewDisconnected':
_whenConnected = Completer();
onViewDisconnected?.call(this);
break;
case 'View.viewStateChanged':
final state =
call.arguments['state'] == 1 || call.arguments['state'] == true;
onViewStateChanged?.call(this, state);
break;
default:
print('Unknown method call from platform view channel: $call');
}
});
// Now send a create message to the platform view.
return FuchsiaViewsService.instance.createView(
viewId,
hitTestable: hitTestable,
focusable: focusable,
viewOcclusionHint: viewOcclusionHint,
);
}
/// Updates properties on the platform view given it's [viewId].
///
/// Called by [FuchsiaView] when the [focusable] or [hitTestable] or
/// [viewOcclusionHint] properties are changed. This MUST NOT be called
/// directly.
Future<void> update({
bool focusable = true,
bool hitTestable = true,
Rect viewOcclusionHint = Rect.zero,
}) async {
return FuchsiaViewsService.instance.updateView(
viewId,
hitTestable: hitTestable,
focusable: focusable,
viewOcclusionHint: viewOcclusionHint,
);
}
/// Requests that focus be transferred to the child view referred to by
/// viewRef. Note that this method should only be used with Scenics legacy
/// GFX API
Future<void> requestFocus(int viewRef) async {
return FocusState.instance.requestFocus(viewRef);
}
/// Requests that focus be transferred to the child view referred to by
/// viewId. Note that this method should only be used with the Flatland API
Future<void> requestFocusById(int viewId) async {
return FocusState.instance.requestFocusById(viewId);
}
/// Dispose relevant resources when the view is take [OffStage] by Flutter.
///
/// There are currently no resources that need to be released when the
/// [PlatformView] is taken [Offstage] because the underlying Fuchsia view
/// owner is still alive and the view can be bought back on stage.
@override
Future<void> dispose() async {}
@override
Future<void> clearFocus() async {}
/// Dispatch pointer events for the child view. This MUST NOT be called
/// directly.
@override
Future<void> dispatchPointerEvent(PointerEvent event) async {
return onPointerEvent?.call(this, event);
}
/// Returns the viewport rect of the child view in parent's coordinates.
///
/// This only works if there is one and only one widget that has the [Key]
/// set to [GlobalObjectKey] using this constroller's instance. [FuchsiaView]
/// set's its [key] to the controller instance passed to it.
///
/// Returns [null] if the child view has not been rendered.
///
/// Note: this viewport is *not* used by the injector, not needed there.
Rect? get viewport {
final key = GlobalObjectKey(this);
RenderBox? box = key.currentContext?.findRenderObject() as RenderBox?;
if (box?.hasSize == true) {
final offset = box!.localToGlobal(Offset.zero);
return offset & box.size;
}
return null;
}
}