Skip to content

Commit f91e40a

Browse files
committed
dartui
1 parent 4321990 commit f91e40a

File tree

4 files changed

+200
-119
lines changed

4 files changed

+200
-119
lines changed

mwp/lib/auxiliary_windows.dart

+6-10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:ui';
2+
13
import 'package:flutter/material.dart';
24
import 'package:mwp/src/context_menu.dart';
35
import 'package:mwp/src/tooltip.dart';
@@ -22,16 +24,6 @@ class _AuxiliaryWindowAppState extends State<AuxiliaryWindowApp> {
2224

2325
@override
2426
Widget build(BuildContext context) {
25-
final List<Widget> entries = List.filled(
26-
10,
27-
ListTile(
28-
onTap: () {
29-
print('Tap');
30-
},
31-
title: const Text('Hello'),
32-
),
33-
);
34-
3527
return Scaffold(
3628
appBar: AppBar(
3729
title: const Text('🇩🇪 Guten Tag! 🇩🇪'),
@@ -98,6 +90,10 @@ class _AuxiliaryWindowAppState extends State<AuxiliaryWindowApp> {
9890
'Rinderkennzeichnungsfleischetikettierungsüberwachungsaufgabenübertragungsgesetz',
9991
child: ElevatedButton(
10092
onPressed: () {
93+
for (Display d in PlatformDispatcher.instance.displays) {
94+
print('Display: ${d.size} - ${d.devicePixelRatio}');
95+
}
96+
print('current: ${View.of(context).devicePixelRatio}, ${View.of(context).physicalConstraints}');
10197
setState(() {
10298
showExp = !showExp;
10399
});

mwp/lib/src/dart_ui.dart

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// ignore_for_file: public_member_api_docs
6+
7+
import 'dart:ui';
8+
9+
import 'package:flutter/services.dart';
10+
import 'package:meta/meta.dart';
11+
12+
extension WindowApi on PlatformDispatcher {
13+
Iterable<FlutterWindow> get windows => _WindowManager.instance.windows;
14+
Future<FlutterWindow> createWindow(FlutterWindowRequest request) {
15+
return _WindowManager.instance.createWindow(request);
16+
}
17+
VoidCallback? get onWindowsChanged => _WindowManager.instance.onWindowsChanged;
18+
set onWindowsChanged(VoidCallback? value) {
19+
_WindowManager.instance.onWindowsChanged = value;
20+
}
21+
}
22+
23+
// This should live on the PlatformDispatcher.
24+
class _WindowManager {
25+
_WindowManager._() {
26+
_windowChannel.setMethodCallHandler(_handleChannelInvocation);
27+
}
28+
29+
static _WindowManager get instance => _instance;
30+
static final _WindowManager _instance = _WindowManager._();
31+
32+
Iterable<FlutterWindow> get windows => _windows.values;
33+
final Map<int, FlutterWindow> _windows = <int, FlutterWindow>{};
34+
35+
Future<FlutterWindow> createWindow(FlutterWindowRequest request) async {
36+
final int? windowId = await _windowChannel.invokeMethod<int>('create', request._encode());
37+
if (windowId == null) {
38+
throw Exception('Unable to create window');
39+
}
40+
final FlutterWindow? window = _windows[windowId];
41+
if (window == null) {
42+
throw Exception('Unable to create window');
43+
}
44+
return window;
45+
}
46+
47+
VoidCallback? onWindowsChanged;
48+
49+
static const MethodChannel _windowChannel = MethodChannel('flutter/window');
50+
51+
Future<Object?> _handleChannelInvocation(MethodCall call) async {
52+
bool invokeCallback = true;
53+
switch (call.method) {
54+
case 'update':
55+
final Map<String, Object> args = call.arguments as Map<String, Object>;
56+
final int windowId = args['id']! as int;
57+
final _FlutterWindowConfiguration config = _FlutterWindowConfiguration.decode(args['config']!);
58+
final FlutterView view = ServicesBinding.instance.platformDispatcher.view(id: config.viewId)!;
59+
final FlutterWindow? window = _windows[windowId];
60+
if (window != null) {
61+
window._update(config, view);
62+
} else {
63+
_windows[windowId] = FlutterWindow._(windowId, config, view);
64+
}
65+
case 'delete':
66+
invokeCallback = _windows.remove(call.arguments) != null;
67+
}
68+
if (invokeCallback) {
69+
onWindowsChanged?.call();
70+
}
71+
return null;
72+
}
73+
}
74+
75+
class FlutterWindow {
76+
FlutterWindow._(this._id, this._config, this._view);
77+
78+
final int _id;
79+
_FlutterWindowConfiguration _config;
80+
FlutterView _view;
81+
82+
void _update(_FlutterWindowConfiguration config, FlutterView view) {
83+
_view = view;
84+
_config = config;
85+
}
86+
87+
FlutterView get view => _view;
88+
Size get contentSize => _config.contentSize;
89+
90+
Future<void> requestUpdate(FlutterWindowRequest request) async {
91+
await _WindowManager._windowChannel.invokeMethod('update', <String, Object?>{'id': _id, 'request': request._encode()});
92+
}
93+
94+
Future<void> close() async {
95+
await _WindowManager._windowChannel.invokeMethod('close', _id);
96+
}
97+
}
98+
99+
// Everything is in physical pixel!
100+
@immutable
101+
class FlutterWindowRequest {
102+
const FlutterWindowRequest({this.contentOffset, this.contentConstraints, this.allowPointerEvents});
103+
104+
final RelativeOffset? contentOffset;
105+
final ViewConstraints? contentConstraints;
106+
final bool? allowPointerEvents;
107+
108+
Object? _encode() {
109+
return <String, Object?>{
110+
'offset': contentOffset?._encode(),
111+
'constraint_min_width': contentConstraints?.minWidth,
112+
'constraint_max_width': contentConstraints?.maxWidth,
113+
'constraint_min_height': contentConstraints?.minHeight,
114+
'constraint_max_height': contentConstraints?.maxHeight,
115+
'pointerEvents': allowPointerEvents,
116+
};
117+
}
118+
}
119+
120+
@immutable
121+
class _FlutterWindowConfiguration {
122+
const _FlutterWindowConfiguration({
123+
required this.viewId,
124+
required this.contentSize,
125+
required this.contentOffset,
126+
});
127+
128+
factory _FlutterWindowConfiguration.decode(Object encoded) {
129+
final Map<String, Object?> decoded = encoded as Map<String, Object?>;
130+
return _FlutterWindowConfiguration(
131+
viewId: decoded['viewId']! as int,
132+
contentSize: Size(decoded['width']! as double, decoded['height']! as double),
133+
contentOffset: RelativeOffset._decode(decoded['offset']!),
134+
);
135+
}
136+
137+
final int viewId;
138+
final Size contentSize;
139+
final RelativeOffset contentOffset;
140+
}
141+
142+
@immutable
143+
class RelativeOffset {
144+
const RelativeOffset.display(Display this.display, this.offset) : view = null;
145+
const RelativeOffset.view(FlutterView this.view, this.offset) : display = null;
146+
147+
factory RelativeOffset._decode(Object encoded) {
148+
final Map<String, Object?> decoded = encoded as Map<String, Object?>;
149+
final Offset offset = Offset(decoded['offset_x']! as double, decoded['offset_y']! as double);
150+
switch (decoded['type']) {
151+
case 'display':
152+
return RelativeOffset.display(
153+
ServicesBinding.instance.platformDispatcher.displays.firstWhere((Display d) => d.id == decoded['id']),
154+
offset,
155+
);
156+
case 'view':
157+
return RelativeOffset.view(
158+
ServicesBinding.instance.platformDispatcher.view(id: decoded['id']! as int)!,
159+
offset,
160+
);
161+
default:
162+
throw Exception();
163+
}
164+
}
165+
166+
final FlutterView? view;
167+
final Display? display;
168+
final Offset offset;
169+
170+
double get devicePixelRatio => view?.devicePixelRatio ?? display?.devicePixelRatio ?? 1.0;
171+
172+
Object _encode() {
173+
return <String, Object?>{
174+
'type': view == null ? 'display' : 'view',
175+
'id': view?.viewId ?? display?.id,
176+
'offset_x': offset.dx,
177+
'offset_y': offset.dy,
178+
};
179+
}
180+
}

mwp/lib/src/window.dart

+12-41
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import 'dart:ui';
22

3-
import 'package:flutter/services.dart';
43
import 'package:flutter/widgets.dart';
54

5+
import 'dart_ui.dart';
6+
67
class WindowController {
78
WindowController({
89
this.viewAnchor,
@@ -27,16 +28,11 @@ class Window extends StatefulWidget {
2728
State<Window> createState() => _WindowState();
2829
}
2930

30-
class _WindowState extends State<Window> with WidgetsBindingObserver {
31-
FlutterView? _view;
31+
class _WindowState extends State<Window> {
32+
FlutterWindow? _window;
3233

3334
static const Widget _placeholder = ViewCollection(views: <Widget>[]);
3435

35-
@override
36-
void didChangeMetrics() {
37-
print(WidgetsBinding.instance.platformDispatcher.views);
38-
}
39-
4036
@override
4137
void initState() {
4238
super.initState();
@@ -45,56 +41,31 @@ class _WindowState extends State<Window> with WidgetsBindingObserver {
4541

4642
@override
4743
void dispose() {
48-
_disposeWindow();
44+
_window?.close();
45+
_window = null;
4946
super.dispose();
5047
}
5148

5249
Future<void> _createWindow() async {
53-
final Map<String, Object?> windowSpec = {
54-
if (widget.controller?.offset != null) ...{
55-
'offsetX': widget.controller!.offset!.dx,
56-
'offsetY' : widget.controller!.offset!.dy,
57-
},
58-
if (widget.controller?.viewAnchor != null)
59-
'viewAnchor': widget.controller!.viewAnchor!.viewId,
60-
if (widget.controller?.size != null) ...{
61-
'height': widget.controller!.size!.height,
62-
'width': widget.controller!.size!.width,
63-
},
64-
'pointerEvents': widget.controller?.pointerEvents ?? true,
65-
};
66-
print(windowSpec);
67-
68-
int? viewId = await _windowChannel.invokeMethod('create', windowSpec);
69-
print('Received: $viewId');
70-
if (viewId == null) {
50+
final FlutterWindow window = await WidgetsBinding.instance.platformDispatcher.createWindow(widget.controller?._request);
51+
if (!mounted) {
52+
window.close();
7153
return;
7254
}
73-
// TODO: deal with unmounted.
7455
setState(() {
75-
_view = WidgetsBinding.instance.platformDispatcher.view(id: viewId);
56+
_window = window;
7657
});
7758
}
7859

79-
// Method must remain callable even if unmounted.
80-
void _disposeWindow() async {
81-
if (_view != null) {
82-
_windowChannel.invokeMethod('dispose', _view!.viewId);
83-
_view = null;
84-
}
85-
}
86-
8760
@override
8861
Widget build(BuildContext context) {
89-
return _view == null
62+
return _window == null
9063
? _placeholder
9164
: View(
92-
view: _view!,
65+
view: _window!.view,
9366
child: widget.child,
9467
);
9568
}
9669
}
9770

98-
const MethodChannel _windowChannel = OptionalMethodChannel('flutter/window');
99-
10071
// Thought: Should the window be attached to the controller?

0 commit comments

Comments
 (0)