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:
Hidenori Matsubayashi
2023-08-23 18:44:46 +00:00
committed by GitHub
parent dd7d8cb2c5
commit 23d92c6c04
34 changed files with 3301 additions and 2 deletions
@@ -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;
}
}