Skip to content

Commit 97c11cf

Browse files
authored
Implement Regular Windows for the win32 framework + add an example application for regular windows (#173715)
## What's new? - Implement and test `_window_win32.dart`, the Win32 implementation of `_window.dart` 🎉 - Wrote a bunch of tests in `window_win32_test.dart` which uses a mock API barrier to mock the native Win32 layer - Update `BaseWindowController` to extend `ChangeNotifier` and use it to inform the `WindowScope` of when things change - Made `RegularWindowController.empty()` a non-internal constructor, as it is needed by implementations - Added a _large_ example application for windowing where users can test out different windowing concepts and see how they work! To test, simply run the tests or try out the example app! ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing.
1 parent a2bd004 commit 97c11cf

56 files changed

Lines changed: 4154 additions & 50 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.ci.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6987,6 +6987,20 @@ targets:
69876987
["devicelab", "hostonly", "windows", "arm64"]
69886988
task_name: windows_startup_test
69896989

6990+
- name: Windows windowing_test
6991+
recipe: devicelab/devicelab_drone
6992+
presubmit: true
6993+
bringup: true
6994+
timeout: 60
6995+
properties:
6996+
dependencies: >-
6997+
[
6998+
{"dependency": "vs_build", "version": "version:vs2019"}
6999+
]
7000+
tags: >
7001+
["devicelab", "hostonly", "windows"]
7002+
task_name: windowing_test_windows
7003+
69907004
- name: Windows flutter_tool_startup__windows
69917005
recipe: devicelab/devicelab_drone
69927006
presubmit: false

TESTOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@
304304
/dev/devicelab/bin/tasks/windows_desktop_impeller.dart @jonahwilliams @flutter/engine
305305
/dev/devicelab/bin/tasks/windows_home_scroll_perf__timeline_summary.dart @jonahwilliams @flutter/engine
306306
/dev/devicelab/bin/tasks/windows_startup_test.dart @loic-sharma @flutter/desktop
307+
/dev/devicelab/bin/tasks/windowing_test_windows.dart @mattkae @flutter/desktop
307308
/dev/devicelab/bin/tasks/mac_desktop_impeller.dart @jonahwilliams @flutter/engine
308309
/dev/devicelab/bin/tasks/linux_desktop_impeller.dart @jonahwilliams @flutter/engine
309310
/dev/devicelab/bin/tasks/linux_feature_flags_test.dart @loic-sharma @flutter/desktop
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
import 'package:flutter_devicelab/framework/devices.dart';
6+
import 'package:flutter_devicelab/framework/framework.dart';
7+
import 'package:flutter_devicelab/tasks/integration_tests.dart';
8+
9+
Future<void> main() async {
10+
deviceOperatingSystem = DeviceOperatingSystem.windows;
11+
await task(createWindowingDriverTest());
12+
}

dev/devicelab/lib/tasks/integration_tests.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,16 @@ TaskFunction createWindowsStartupDriverTest({String? deviceIdOverride}) {
221221
).call;
222222
}
223223

224+
TaskFunction createWindowingDriverTest() {
225+
return () async {
226+
await flutter('config', options: const <String>['--enable-windowing']);
227+
return DriverTest(
228+
'${flutterDirectory.path}/dev/integration_tests/windowing_test',
229+
'lib/main.dart',
230+
).call();
231+
};
232+
}
233+
224234
TaskFunction createWideGamutTest() {
225235
return IntegrationTest(
226236
'${flutterDirectory.path}/dev/integration_tests/wide_gamut_test',
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Miscellaneous
2+
*.class
3+
*.log
4+
*.pyc
5+
*.swp
6+
.DS_Store
7+
.atom/
8+
.build/
9+
.buildlog/
10+
.history
11+
.svn/
12+
.swiftpm/
13+
migrate_working_dir/
14+
15+
# IntelliJ related
16+
*.iml
17+
*.ipr
18+
*.iws
19+
.idea/
20+
21+
# The .vscode folder contains launch configuration and tasks you configure in
22+
# VS Code which you may wish to be included in version control, so this line
23+
# is commented out by default.
24+
#.vscode/
25+
26+
# Flutter/Dart/Pub related
27+
**/doc/api/
28+
**/ios/Flutter/.last_build_id
29+
.dart_tool/
30+
.flutter-plugins-dependencies
31+
.pub-cache/
32+
.pub/
33+
/build/
34+
/coverage/
35+
36+
# Symbolication related
37+
app.*.symbols
38+
39+
# Obfuscation related
40+
app.*.map.json
41+
42+
# Android Studio will place build artifacts here
43+
/android/app/debug
44+
/android/app/profile
45+
/android/app/release
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# This file tracks properties of this Flutter project.
2+
# Used by Flutter tool to assess capabilities and perform upgrades etc.
3+
#
4+
# This file should be version controlled and should not be manually edited.
5+
6+
version:
7+
revision: "e27f0a6943e07b6d3420fb185432705b87101fa8"
8+
channel: "[user-branch]"
9+
10+
project_type: app
11+
12+
# Tracks metadata for the flutter migrate command
13+
migration:
14+
platforms:
15+
- platform: windows
16+
create_revision: e27f0a6943e07b6d3420fb185432705b87101fa8
17+
base_revision: e27f0a6943e07b6d3420fb185432705b87101fa8
18+
19+
# User provided section
20+
21+
# List of Local paths (relative to this file) that should be
22+
# ignored by the migrate tool.
23+
#
24+
# Files that are not part of the templates will be ignored by default.
25+
unmanaged_files:
26+
- 'lib/main.dart'
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# windowing_test
2+
3+
Integration tests for windowing.
4+
5+
## Run
6+
From this directory, run:
7+
8+
```sh
9+
flutter drive -t .\lib\main.dart --driver .\test_driver\windowing_test.dart
10+
```
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
include: package:flutter_lints/flutter.yaml
2+
3+
linter:
4+
rules:
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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: invalid_use_of_internal_member
6+
7+
import 'dart:async';
8+
import 'dart:convert';
9+
import 'dart:io';
10+
11+
import 'package:flutter/material.dart';
12+
import 'package:flutter/src/widgets/_window.dart';
13+
import 'package:flutter_driver/driver_extension.dart';
14+
15+
class _MainRegularWindowControllerDelegate
16+
extends RegularWindowControllerDelegate {
17+
@override
18+
void onWindowDestroyed() {
19+
super.onWindowDestroyed();
20+
21+
exit(0);
22+
}
23+
}
24+
25+
late final RegularWindowController controller;
26+
27+
void main() {
28+
final Completer<void> windowCreated = Completer();
29+
enableFlutterDriverExtension(
30+
handler: (String? message) async {
31+
await windowCreated.future;
32+
if (message == null) {
33+
return '';
34+
}
35+
36+
final jsonMap = jsonDecode(message);
37+
if (!jsonMap.containsKey('type')) {
38+
throw ArgumentError('Message must contain a "type" field.');
39+
}
40+
41+
if (jsonMap['type'] == 'get_size') {
42+
return jsonEncode({
43+
'width': controller.contentSize.width,
44+
'height': controller.contentSize.height,
45+
});
46+
} else if (jsonMap['type'] == 'set_size') {
47+
final Size size = Size(
48+
jsonMap['width'].toDouble(),
49+
jsonMap['height'].toDouble(),
50+
);
51+
controller.setSize(size);
52+
} else if (jsonMap['type'] == 'set_constraints') {
53+
final BoxConstraints constraints = BoxConstraints(
54+
minWidth: jsonMap['min_width'].toDouble(),
55+
minHeight: jsonMap['min_height'].toDouble(),
56+
maxWidth: jsonMap['max_width'].toDouble(),
57+
maxHeight: jsonMap['max_height'].toDouble(),
58+
);
59+
controller.setConstraints(constraints);
60+
} else if (jsonMap['type'] == 'set_fullscreen') {
61+
controller.setFullscreen(true);
62+
} else if (jsonMap['type'] == 'unset_fullscreen') {
63+
controller.setFullscreen(false);
64+
} else if (jsonMap['type'] == 'get_fullscreen') {
65+
return jsonEncode({'isFullscreen': controller.isFullscreen});
66+
} else if (jsonMap['type'] == 'set_maximized') {
67+
controller.setMaximized(true);
68+
} else if (jsonMap['type'] == 'unset_maximized') {
69+
controller.setMaximized(false);
70+
} else if (jsonMap['type'] == 'get_maximized') {
71+
return jsonEncode({'isMaximized': controller.isMaximized});
72+
} else if (jsonMap['type'] == 'set_minimized') {
73+
controller.setMinimized(true);
74+
} else if (jsonMap['type'] == 'unset_minimized') {
75+
controller.setMinimized(false);
76+
} else if (jsonMap['type'] == 'get_minimized') {
77+
return jsonEncode({'isMinimized': controller.isMinimized});
78+
} else if (jsonMap['type'] == 'set_title') {
79+
controller.setTitle(jsonMap['title']);
80+
} else if (jsonMap['type'] == 'get_title') {
81+
return jsonEncode({'title': controller.title});
82+
} else if (jsonMap['type'] == 'set_activated') {
83+
controller.activate();
84+
} else if (jsonMap['type'] == 'get_activated') {
85+
return jsonEncode({'isActivated': controller.isActivated});
86+
} else {
87+
throw ArgumentError('Unknown message type: ${jsonMap['type']}');
88+
}
89+
90+
return '';
91+
},
92+
);
93+
controller = RegularWindowController(
94+
preferredSize: Size(640, 480),
95+
title: 'Integration Test',
96+
delegate: _MainRegularWindowControllerDelegate(),
97+
);
98+
windowCreated.complete();
99+
100+
runWidget(RegularWindow(controller: controller, child: MyApp()));
101+
}
102+
103+
class MyApp extends StatelessWidget {
104+
const MyApp({super.key});
105+
106+
@override
107+
Widget build(BuildContext context) {
108+
return MaterialApp(
109+
title: 'Flutter Demo',
110+
theme: ThemeData(
111+
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
112+
),
113+
home: const MyHomePage(title: 'Flutter Demo Home Page'),
114+
);
115+
}
116+
}
117+
118+
class MyHomePage extends StatefulWidget {
119+
const MyHomePage({super.key, required this.title});
120+
121+
final String title;
122+
123+
@override
124+
State<MyHomePage> createState() => _MyHomePageState();
125+
}
126+
127+
class _MyHomePageState extends State<MyHomePage> {
128+
int _counter = 0;
129+
130+
void _incrementCounter() {
131+
setState(() {
132+
_counter++;
133+
});
134+
}
135+
136+
@override
137+
Widget build(BuildContext context) {
138+
return Scaffold(
139+
appBar: AppBar(
140+
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
141+
title: Text(widget.title),
142+
),
143+
body: Center(
144+
child: Column(
145+
mainAxisAlignment: MainAxisAlignment.center,
146+
children: <Widget>[
147+
const Text('You have pushed the button this many times:'),
148+
Text(
149+
'$_counter',
150+
style: Theme.of(context).textTheme.headlineMedium,
151+
),
152+
],
153+
),
154+
),
155+
floatingActionButton: FloatingActionButton(
156+
onPressed: _incrementCounter,
157+
tooltip: 'Increment',
158+
child: const Icon(Icons.add),
159+
),
160+
);
161+
}
162+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: windowing_test
2+
description: Flutter windowing integration tests.
3+
4+
environment:
5+
sdk: ^3.8.0-0
6+
7+
dependencies:
8+
flutter:
9+
sdk: flutter
10+
flutter_driver:
11+
sdk: flutter
12+
integration_test:
13+
sdk: flutter
14+
test: any
15+
16+
path: any
17+
18+
dev_dependencies:
19+
flutter_test:
20+
sdk: flutter
21+
22+
# PUBSPEC CHECKSUM: 77jv2r

0 commit comments

Comments
 (0)