packages: add flutter_elinux package for platform views (#206)
https://github.com/sony/flutter-embedded-linux/issues/41 Signed-off-by: Hidenori Matsubayashi <[email protected]>
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
// Copyright 2023 Sony Group Corporation. All rights reserved.
|
||||
// Copyright 2014 The Flutter 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/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
import '../services/platform_views.dart';
|
||||
|
||||
/// See: [_PlatformViewState] in `src/rendering/platform_view.dart`
|
||||
enum _PlatformViewState {
|
||||
uninitialized,
|
||||
resizing,
|
||||
ready,
|
||||
}
|
||||
|
||||
/// See: [RenderAndroidView] in `src/rendering/platform_view.dart`
|
||||
class RenderELinuxView extends PlatformViewRenderBox {
|
||||
/// Creates a render object for an ELinux view.
|
||||
RenderELinuxView({
|
||||
required ELinuxViewController viewController,
|
||||
required PlatformViewHitTestBehavior hitTestBehavior,
|
||||
required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
|
||||
Clip clipBehavior = Clip.hardEdge,
|
||||
}) : _viewController = viewController,
|
||||
_clipBehavior = clipBehavior,
|
||||
super(
|
||||
controller: viewController,
|
||||
hitTestBehavior: hitTestBehavior,
|
||||
gestureRecognizers: gestureRecognizers) {
|
||||
_viewController.pointTransformer = (Offset offset) => globalToLocal(offset);
|
||||
updateGestureRecognizers(gestureRecognizers);
|
||||
_viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
|
||||
this.hitTestBehavior = hitTestBehavior;
|
||||
_setOffset();
|
||||
}
|
||||
|
||||
_PlatformViewState _state = _PlatformViewState.uninitialized;
|
||||
|
||||
Size? _currentTextureSize;
|
||||
|
||||
bool _isDisposed = false;
|
||||
|
||||
/// The ELinux view controller for the ELinux view associated with this render object.
|
||||
@override
|
||||
ELinuxViewController get controller => _viewController;
|
||||
|
||||
ELinuxViewController _viewController;
|
||||
|
||||
/// Sets a new ELinux view controller.
|
||||
@override
|
||||
set controller(ELinuxViewController controller) {
|
||||
assert(!_isDisposed);
|
||||
if (_viewController == controller) {
|
||||
return;
|
||||
}
|
||||
_viewController.removeOnPlatformViewCreatedListener(_onPlatformViewCreated);
|
||||
super.controller = controller;
|
||||
_viewController = controller;
|
||||
_viewController.pointTransformer = (Offset offset) => globalToLocal(offset);
|
||||
_sizePlatformView();
|
||||
if (_viewController.isCreated) {
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
_viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
|
||||
}
|
||||
|
||||
/// {@macro flutter.material.Material.clipBehavior}
|
||||
///
|
||||
/// Defaults to [Clip.hardEdge], and must not be null.
|
||||
Clip get clipBehavior => _clipBehavior;
|
||||
Clip _clipBehavior = Clip.hardEdge;
|
||||
set clipBehavior(Clip value) {
|
||||
if (value != _clipBehavior) {
|
||||
_clipBehavior = value;
|
||||
markNeedsPaint();
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void _onPlatformViewCreated(int id) {
|
||||
assert(!_isDisposed);
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
@override
|
||||
bool get sizedByParent => true;
|
||||
|
||||
@override
|
||||
bool get alwaysNeedsCompositing => true;
|
||||
|
||||
@override
|
||||
bool get isRepaintBoundary => true;
|
||||
|
||||
@override
|
||||
Size computeDryLayout(BoxConstraints constraints) {
|
||||
return constraints.biggest;
|
||||
}
|
||||
|
||||
@override
|
||||
void performResize() {
|
||||
super.performResize();
|
||||
_sizePlatformView();
|
||||
}
|
||||
|
||||
Future<void> _sizePlatformView() async {
|
||||
// ELinux virtual displays cannot have a zero size.
|
||||
// Trying to size it to 0 crashes the app, which was happening when starting the app
|
||||
// with a locked screen (see: https://github.com/flutter/flutter/issues/20456).
|
||||
if (_state == _PlatformViewState.resizing || size.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
_state = _PlatformViewState.resizing;
|
||||
markNeedsPaint();
|
||||
|
||||
Size targetSize;
|
||||
do {
|
||||
targetSize = size;
|
||||
_currentTextureSize = await _viewController.setSize(targetSize);
|
||||
if (_isDisposed) {
|
||||
return;
|
||||
}
|
||||
// We've resized the platform view to targetSize, but it is possible that
|
||||
// while we were resizing the render object's size was changed again.
|
||||
// In that case we will resize the platform view again.
|
||||
} while (size != targetSize);
|
||||
|
||||
_state = _PlatformViewState.ready;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
// Sets the offset of the underlying platform view on the platform side.
|
||||
//
|
||||
// This allows the ELinux native view to draw the a11y highlights in the same
|
||||
// location on the screen as the platform view widget in the Flutter framework.
|
||||
//
|
||||
// It also allows platform code to obtain the correct position of the ELinux
|
||||
// native view on the screen.
|
||||
void _setOffset() {
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
||||
if (!_isDisposed) {
|
||||
if (attached) {
|
||||
await _viewController.setOffset(localToGlobal(Offset.zero));
|
||||
}
|
||||
// Schedule a new post frame callback.
|
||||
_setOffset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
if (_viewController.textureId == null || _currentTextureSize == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// As resizing the ELinux view happens asynchronously we don't know exactly when is a
|
||||
// texture frame with the new size is ready for consumption.
|
||||
// TextureLayer is unaware of the texture frame's size and always maps it to the
|
||||
// specified rect. If the rect we provide has a different size from the current texture frame's
|
||||
// size the texture frame will be scaled.
|
||||
// To prevent unwanted scaling artifacts while resizing, clip the texture.
|
||||
// This guarantees that the size of the texture frame we're painting is always
|
||||
// _currentELinuxTextureSize.
|
||||
final bool isTextureLargerThanWidget =
|
||||
_currentTextureSize!.width > size.width ||
|
||||
_currentTextureSize!.height > size.height;
|
||||
if (isTextureLargerThanWidget && clipBehavior != Clip.none) {
|
||||
_clipRectLayer.layer = context.pushClipRect(
|
||||
true,
|
||||
offset,
|
||||
offset & size,
|
||||
_paintTexture,
|
||||
clipBehavior: clipBehavior,
|
||||
oldLayer: _clipRectLayer.layer,
|
||||
);
|
||||
return;
|
||||
}
|
||||
_clipRectLayer.layer = null;
|
||||
_paintTexture(context, offset);
|
||||
}
|
||||
|
||||
final LayerHandle<ClipRectLayer> _clipRectLayer =
|
||||
LayerHandle<ClipRectLayer>();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_isDisposed = true;
|
||||
_clipRectLayer.layer = null;
|
||||
_viewController.removeOnPlatformViewCreatedListener(_onPlatformViewCreated);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _paintTexture(PaintingContext context, Offset offset) {
|
||||
if (_currentTextureSize == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.addLayer(TextureLayer(
|
||||
rect: offset & _currentTextureSize!,
|
||||
textureId: _viewController.textureId!,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
// Don't call the super implementation since `platformViewId` should
|
||||
// be set only when the platform view is created, but the concept of
|
||||
// a "created" platform view belongs to this subclass.
|
||||
config.isSemanticBoundary = true;
|
||||
|
||||
if (_viewController.isCreated) {
|
||||
config.platformViewId = _viewController.viewId;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,301 @@
|
||||
// Copyright 2023 Sony Group Corporation. All rights reserved.
|
||||
// Copyright 2014 The Flutter 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/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../rendering/platform_view.dart';
|
||||
import '../services/platform_views.dart';
|
||||
|
||||
/// See: [AndroidView] in `src/widgets/platform_view.dart`
|
||||
class ELinuxView extends StatefulWidget {
|
||||
const ELinuxView({
|
||||
super.key,
|
||||
required this.viewType,
|
||||
this.onPlatformViewCreated,
|
||||
this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
|
||||
this.layoutDirection,
|
||||
this.gestureRecognizers,
|
||||
this.creationParams,
|
||||
this.creationParamsCodec,
|
||||
this.clipBehavior = Clip.hardEdge,
|
||||
}) : assert(creationParams == null || creationParamsCodec != null);
|
||||
|
||||
final String viewType;
|
||||
final PlatformViewCreatedCallback? onPlatformViewCreated;
|
||||
final PlatformViewHitTestBehavior hitTestBehavior;
|
||||
final TextDirection? layoutDirection;
|
||||
final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
|
||||
final dynamic creationParams;
|
||||
final MessageCodec<dynamic>? creationParamsCodec;
|
||||
final Clip clipBehavior;
|
||||
|
||||
@override
|
||||
State<ELinuxView> createState() => _ELinuxViewState();
|
||||
}
|
||||
|
||||
/// See: [_AndroidViewState] in `src/widgets/platform_view.dart`
|
||||
class _ELinuxViewState extends State<ELinuxView> {
|
||||
int? _id;
|
||||
late ELinuxViewController _controller;
|
||||
TextDirection? _layoutDirection;
|
||||
bool _initialized = false;
|
||||
FocusNode? _focusNode;
|
||||
|
||||
static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
|
||||
<Factory<OneSequenceGestureRecognizer>>{};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Focus(
|
||||
focusNode: _focusNode,
|
||||
onFocusChange: _onFocusChange,
|
||||
child: _ELinuxPlatformView(
|
||||
controller: _controller,
|
||||
hitTestBehavior: widget.hitTestBehavior,
|
||||
gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _initializeOnce() {
|
||||
if (_initialized) {
|
||||
return;
|
||||
}
|
||||
_initialized = true;
|
||||
_createNewELinuxView();
|
||||
_focusNode = FocusNode(debugLabel: 'ELinuxView(id: $_id)');
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final TextDirection newLayoutDirection = _findLayoutDirection();
|
||||
final bool didChangeLayoutDirection =
|
||||
_layoutDirection != newLayoutDirection;
|
||||
_layoutDirection = newLayoutDirection;
|
||||
|
||||
_initializeOnce();
|
||||
if (didChangeLayoutDirection) {
|
||||
// The native view will update asynchronously, in the meantime we don't want
|
||||
// to block the framework. (so this is intentionally not awaiting).
|
||||
_controller.setLayoutDirection(_layoutDirection!);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(ELinuxView oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
final TextDirection newLayoutDirection = _findLayoutDirection();
|
||||
final bool didChangeLayoutDirection =
|
||||
_layoutDirection != newLayoutDirection;
|
||||
_layoutDirection = newLayoutDirection;
|
||||
|
||||
if (widget.viewType != oldWidget.viewType) {
|
||||
//_controller.disposePostFrame();
|
||||
_controller.dispose();
|
||||
_createNewELinuxView();
|
||||
return;
|
||||
}
|
||||
|
||||
if (didChangeLayoutDirection) {
|
||||
_controller.setLayoutDirection(_layoutDirection!);
|
||||
}
|
||||
}
|
||||
|
||||
TextDirection _findLayoutDirection() {
|
||||
assert(
|
||||
widget.layoutDirection != null || debugCheckHasDirectionality(context));
|
||||
return widget.layoutDirection ?? Directionality.of(context);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_focusNode?.dispose();
|
||||
_focusNode = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _createNewELinuxView() {
|
||||
_id = platformViewsRegistry.getNextPlatformViewId();
|
||||
_controller = PlatformViewsServiceELinux.initELinuxView(
|
||||
id: _id!,
|
||||
viewType: widget.viewType,
|
||||
layoutDirection: _layoutDirection!,
|
||||
creationParams: widget.creationParams,
|
||||
creationParamsCodec: widget.creationParamsCodec,
|
||||
onFocus: () {
|
||||
_focusNode!.requestFocus();
|
||||
},
|
||||
);
|
||||
if (widget.onPlatformViewCreated != null) {
|
||||
_controller
|
||||
.addOnPlatformViewCreatedListener(widget.onPlatformViewCreated!);
|
||||
}
|
||||
}
|
||||
|
||||
void _onFocusChange(bool isFocused) {
|
||||
if (!_controller.isCreated) {
|
||||
return;
|
||||
}
|
||||
if (!isFocused) {
|
||||
_controller.clearFocus().catchError((dynamic e) {
|
||||
if (e is MissingPluginException) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
SystemChannels.textInput.invokeMethod<void>(
|
||||
'TextInput.setPlatformViewClient',
|
||||
<String, dynamic>{'platformViewId': _id},
|
||||
).catchError((dynamic e) {
|
||||
if (e is MissingPluginException) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// See: [_AndroidPlatformView] in `src/widgets/platform_view.dart`
|
||||
class _ELinuxPlatformView extends LeafRenderObjectWidget {
|
||||
const _ELinuxPlatformView({
|
||||
required this.controller,
|
||||
required this.hitTestBehavior,
|
||||
required this.gestureRecognizers,
|
||||
this.clipBehavior = Clip.hardEdge,
|
||||
});
|
||||
|
||||
final ELinuxViewController controller;
|
||||
final PlatformViewHitTestBehavior hitTestBehavior;
|
||||
final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
|
||||
final Clip clipBehavior;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) => RenderELinuxView(
|
||||
viewController: controller,
|
||||
hitTestBehavior: hitTestBehavior,
|
||||
gestureRecognizers: gestureRecognizers,
|
||||
clipBehavior: clipBehavior,
|
||||
);
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, RenderELinuxView renderObject) {
|
||||
renderObject.controller = controller;
|
||||
renderObject.hitTestBehavior = hitTestBehavior;
|
||||
renderObject.updateGestureRecognizers(gestureRecognizers);
|
||||
renderObject.clipBehavior = clipBehavior;
|
||||
}
|
||||
}
|
||||
|
||||
/// See: [AndroidViewSurface] in `src/widgets/platform_view.dart`
|
||||
class ELinuxViewSurface extends StatefulWidget {
|
||||
const ELinuxViewSurface({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.hitTestBehavior,
|
||||
required this.gestureRecognizers,
|
||||
});
|
||||
|
||||
final ELinuxViewController controller;
|
||||
final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
|
||||
final PlatformViewHitTestBehavior hitTestBehavior;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _ELinuxViewSurfaceState();
|
||||
}
|
||||
}
|
||||
|
||||
/// See: [AndroidViewSurfaceState] in `src/widgets/platform_view.dart`
|
||||
class _ELinuxViewSurfaceState extends State<ELinuxViewSurface> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (!widget.controller.isCreated) {
|
||||
widget.controller
|
||||
.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.controller
|
||||
.removeOnPlatformViewCreatedListener(_onPlatformViewCreated);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.controller.requiresViewComposition) {
|
||||
return _PlatformLayerBasedELinuxViewSurface(
|
||||
controller: widget.controller,
|
||||
hitTestBehavior: widget.hitTestBehavior,
|
||||
gestureRecognizers: widget.gestureRecognizers,
|
||||
);
|
||||
} else {
|
||||
return _TextureBasedELinuxViewSurface(
|
||||
controller: widget.controller,
|
||||
hitTestBehavior: widget.hitTestBehavior,
|
||||
gestureRecognizers: widget.gestureRecognizers,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onPlatformViewCreated(int _) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
/// See: [_TextureBasedAndroidViewSurface] in `src/widgets/platform_view.dart`
|
||||
class _TextureBasedELinuxViewSurface extends PlatformViewSurface {
|
||||
const _TextureBasedELinuxViewSurface({
|
||||
required ELinuxViewController super.controller,
|
||||
required super.hitTestBehavior,
|
||||
required super.gestureRecognizers,
|
||||
});
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
final ELinuxViewController viewController =
|
||||
controller as ELinuxViewController;
|
||||
// Use GL texture based composition.
|
||||
// App should use GL texture unless they require to embed a SurfaceView.
|
||||
final RenderELinuxView renderBox = RenderELinuxView(
|
||||
viewController: viewController,
|
||||
gestureRecognizers: gestureRecognizers,
|
||||
hitTestBehavior: hitTestBehavior,
|
||||
);
|
||||
viewController.pointTransformer =
|
||||
(Offset position) => renderBox.globalToLocal(position);
|
||||
return renderBox;
|
||||
}
|
||||
}
|
||||
|
||||
/// See: [_PlatformLayerBasedAndroidViewSurface] in `src/widgets/platform_view.dart`
|
||||
class _PlatformLayerBasedELinuxViewSurface extends PlatformViewSurface {
|
||||
const _PlatformLayerBasedELinuxViewSurface({
|
||||
required ELinuxViewController super.controller,
|
||||
required super.hitTestBehavior,
|
||||
required super.gestureRecognizers,
|
||||
});
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
final ELinuxViewController viewController =
|
||||
controller as ELinuxViewController;
|
||||
final PlatformViewRenderBox renderBox =
|
||||
super.createRenderObject(context) as PlatformViewRenderBox;
|
||||
viewController.pointTransformer =
|
||||
(Offset position) => renderBox.globalToLocal(position);
|
||||
return renderBox;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user