Skip to content

Commit fcf1f96

Browse files
committed
feature: implement createRegularWindow and add it to the reference app
1 parent e30bf85 commit fcf1f96

File tree

24 files changed

+2064
-2
lines changed

24 files changed

+2064
-2
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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: "4f182c3ce1f3a60a007b034cd5d9f2122ca2e5f1"
8+
channel: "[user-branch]"
9+
10+
project_type: app
11+
12+
# Tracks metadata for the flutter migrate command
13+
migration:
14+
platforms:
15+
- platform: root
16+
create_revision: 4f182c3ce1f3a60a007b034cd5d9f2122ca2e5f1
17+
base_revision: 4f182c3ce1f3a60a007b034cd5d9f2122ca2e5f1
18+
- platform: windows
19+
create_revision: 4f182c3ce1f3a60a007b034cd5d9f2122ca2e5f1
20+
base_revision: 4f182c3ce1f3a60a007b034cd5d9f2122ca2e5f1
21+
22+
# User provided section
23+
24+
# List of Local paths (relative to this file) that should be
25+
# ignored by the migrate tool.
26+
#
27+
# Files that are not part of the templates will be ignored by default.
28+
unmanaged_files:
29+
- 'lib/main.dart'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# multi_window_ref_app
2+
3+
A reference application demonstrating multi-window support for Flutter using a
4+
rich semantics windowing API. At the moment, only the Windows platform is
5+
supported.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# This file configures the analyzer, which statically analyzes Dart code to
2+
# check for errors, warnings, and lints.
3+
#
4+
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5+
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6+
# invoked from the command line by running `flutter analyze`.
7+
8+
# The following line activates a set of recommended lints for Flutter apps,
9+
# packages, and plugins designed to encourage good coding practices.
10+
include: package:flutter_lints/flutter.yaml
11+
12+
linter:
13+
# The lint rules applied to this project can be customized in the
14+
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
15+
# included above or to enable additional rules. A list of all available lints
16+
# and their documentation is published at https://dart.dev/lints.
17+
#
18+
# Instead of disabling a lint rule for the entire project in the
19+
# section below, it can also be suppressed for a single line of code
20+
# or a specific dart file by using the `// ignore: name_of_lint` and
21+
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
22+
# producing the lint.
23+
rules:
24+
# avoid_print: false # Uncomment to disable the `avoid_print` rule
25+
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
26+
27+
# Additional information about this file can be found at
28+
# https://dart.dev/guides/language/analysis-options
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import 'package:flutter/material.dart';
2+
3+
import 'regular_window.dart';
4+
import 'window_settings.dart';
5+
import 'window_settings_dialog.dart';
6+
7+
class MainWindow extends StatefulWidget {
8+
const MainWindow({super.key});
9+
10+
@override
11+
State<MainWindow> createState() => _MainWindowState();
12+
}
13+
14+
class _MainWindowState extends State<MainWindow> {
15+
int selectedRowIndex = -1;
16+
final List<Window> _managedWindows = <Window>[];
17+
18+
@override
19+
Widget build(BuildContext context) {
20+
List<Window> getWindowsInTree(List<Window> windows) {
21+
return windows
22+
.expand((window) => [window, ...getWindowsInTree(window.children)])
23+
.toList();
24+
}
25+
26+
final List<Window> windows =
27+
getWindowsInTree(MultiWindowAppContext.of(context)!.windows);
28+
29+
final widget = Scaffold(
30+
appBar: AppBar(
31+
title: const Text('Multi Window Reference App'),
32+
),
33+
body: Row(
34+
crossAxisAlignment: CrossAxisAlignment.start,
35+
children: [
36+
Expanded(
37+
flex: 60,
38+
child: SingleChildScrollView(
39+
scrollDirection: Axis.vertical,
40+
child: _ActiveWindowsTable(
41+
windows: windows,
42+
selectedRowIndex: selectedRowIndex,
43+
onSelectedRowIndexChanged: (int index) =>
44+
setState(() => selectedRowIndex = index),
45+
),
46+
),
47+
),
48+
Expanded(
49+
flex: 40,
50+
child: Column(
51+
crossAxisAlignment: CrossAxisAlignment.stretch,
52+
children: [
53+
_WindowCreatorCard(
54+
selectedWindow: selectedRowIndex < 0 ||
55+
selectedRowIndex >= windows.length
56+
? null
57+
: windows[selectedRowIndex],
58+
onDialogOpened: (window) => _managedWindows.add(window),
59+
onDialogClosed: (window) => _managedWindows.remove(window)),
60+
],
61+
),
62+
),
63+
],
64+
),
65+
);
66+
67+
final window = WindowContext.of(context)!.window;
68+
final List<Widget> childViews = <Widget>[];
69+
for (final Window childWindow in window.children) {
70+
if (!_shouldRenderWindow(childWindow)) {
71+
continue;
72+
}
73+
74+
childViews.add(View(
75+
view: childWindow.view,
76+
child: WindowContext(
77+
window: childWindow,
78+
child: childWindow.builder(context),
79+
),
80+
));
81+
}
82+
83+
return ViewAnchor(view: ViewCollection(views: childViews), child: widget);
84+
}
85+
86+
bool _shouldRenderWindow(Window window) {
87+
return !_managedWindows.contains(window);
88+
}
89+
}
90+
91+
class _ActiveWindowsTable extends StatelessWidget {
92+
const _ActiveWindowsTable(
93+
{required this.windows,
94+
required this.selectedRowIndex,
95+
required this.onSelectedRowIndexChanged});
96+
97+
final List<Window> windows;
98+
final int selectedRowIndex;
99+
final void Function(int) onSelectedRowIndexChanged;
100+
101+
@override
102+
Widget build(BuildContext context) {
103+
return DataTable(
104+
showBottomBorder: true,
105+
onSelectAll: (selected) {
106+
onSelectedRowIndexChanged(-1);
107+
},
108+
columns: const [
109+
DataColumn(
110+
label: SizedBox(
111+
width: 20,
112+
child: Text(
113+
'ID',
114+
style: TextStyle(
115+
fontSize: 16,
116+
),
117+
),
118+
),
119+
),
120+
DataColumn(
121+
label: SizedBox(
122+
width: 120,
123+
child: Text(
124+
'Type',
125+
style: TextStyle(
126+
fontSize: 16,
127+
),
128+
),
129+
),
130+
),
131+
DataColumn(
132+
label: SizedBox(
133+
width: 20,
134+
child: Text(''),
135+
),
136+
numeric: true),
137+
],
138+
rows: windows.asMap().entries.map<DataRow>((indexedEntry) {
139+
final index = indexedEntry.key;
140+
final Window entry = indexedEntry.value;
141+
final window = entry;
142+
final viewId = window.view.viewId;
143+
final archetype = window.archetype;
144+
final isSelected = selectedRowIndex == index;
145+
146+
return DataRow(
147+
color: WidgetStateColor.resolveWith((states) {
148+
if (states.contains(WidgetState.selected)) {
149+
return Theme.of(context).colorScheme.primary.withOpacity(0.08);
150+
}
151+
return Colors.transparent;
152+
}),
153+
selected: isSelected,
154+
onSelectChanged: (selected) {
155+
if (selected != null) {
156+
onSelectedRowIndexChanged(selected ? index : -1);
157+
}
158+
},
159+
cells: [
160+
DataCell(
161+
Text('$viewId'),
162+
),
163+
DataCell(
164+
Text(archetype.toString().replaceFirst('WindowArchetype.', '')),
165+
),
166+
DataCell(
167+
IconButton(
168+
icon: const Icon(Icons.delete_outlined),
169+
onPressed: () {
170+
destroyWindow(context, window);
171+
},
172+
),
173+
),
174+
],
175+
);
176+
}).toList(),
177+
);
178+
}
179+
}
180+
181+
class _WindowCreatorCard extends StatefulWidget {
182+
const _WindowCreatorCard(
183+
{required this.selectedWindow,
184+
required this.onDialogOpened,
185+
required this.onDialogClosed});
186+
187+
final Window? selectedWindow;
188+
final void Function(Window) onDialogOpened;
189+
final void Function(Window) onDialogClosed;
190+
191+
@override
192+
State<StatefulWidget> createState() => _WindowCreatorCardState();
193+
}
194+
195+
class _WindowCreatorCardState extends State<_WindowCreatorCard> {
196+
WindowSettings _settings = WindowSettings();
197+
198+
@override
199+
Widget build(BuildContext context) {
200+
return Card.outlined(
201+
margin: const EdgeInsets.symmetric(horizontal: 25),
202+
child: Padding(
203+
padding: const EdgeInsets.fromLTRB(25, 0, 25, 5),
204+
child: Column(
205+
crossAxisAlignment: CrossAxisAlignment.center,
206+
children: [
207+
const Padding(
208+
padding: EdgeInsets.only(top: 10, bottom: 10),
209+
child: Text(
210+
'New Window',
211+
style: TextStyle(
212+
fontWeight: FontWeight.bold,
213+
fontSize: 16.0,
214+
),
215+
),
216+
),
217+
Column(
218+
crossAxisAlignment: CrossAxisAlignment.stretch,
219+
children: [
220+
OutlinedButton(
221+
onPressed: () async {
222+
await createRegular(
223+
context: context,
224+
size: _settings.regularSize,
225+
builder: (BuildContext context) {
226+
return const MaterialApp(home: RegularWindowContent());
227+
});
228+
},
229+
child: const Text('Regular'),
230+
),
231+
const SizedBox(height: 8),
232+
Container(
233+
alignment: Alignment.bottomRight,
234+
child: TextButton(
235+
child: const Text('SETTINGS'),
236+
onPressed: () {
237+
windowSettingsDialog(context, _settings,
238+
widget.onDialogOpened, widget.onDialogClosed)
239+
.then(
240+
(WindowSettings? settings) {
241+
if (settings != null) {
242+
_settings = settings;
243+
}
244+
},
245+
);
246+
},
247+
),
248+
),
249+
const SizedBox(width: 8),
250+
],
251+
),
252+
],
253+
),
254+
),
255+
);
256+
}
257+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import 'package:flutter/material.dart';
2+
3+
class RegularWindowContent extends StatelessWidget {
4+
const RegularWindowContent({super.key});
5+
6+
@override
7+
Widget build(BuildContext context) {
8+
final Window window = WindowContext.of(context)!.window;
9+
final widget = Scaffold(
10+
appBar: AppBar(title: Text('${window.archetype}')),
11+
body: Center(
12+
child: Column(
13+
mainAxisAlignment: MainAxisAlignment.center,
14+
crossAxisAlignment: CrossAxisAlignment.center,
15+
children: [
16+
ElevatedButton(
17+
onPressed: () async {
18+
await createRegular(
19+
context: context,
20+
size: const Size(400, 300),
21+
builder: (BuildContext context) =>
22+
const MaterialApp(home: RegularWindowContent()));
23+
},
24+
child: const Text('Create Regular Window'),
25+
),
26+
const SizedBox(height: 20),
27+
Text(
28+
'View #${window.view.viewId}\n'
29+
'Parent View: ${window.parent?.view.viewId}\n'
30+
'Logical Size: ${window.size.width}\u00D7${window.size.height}\n'
31+
'DPR: ${MediaQuery.of(context).devicePixelRatio}',
32+
textAlign: TextAlign.center,
33+
),
34+
],
35+
),
36+
),
37+
);
38+
39+
final List<Widget> childViews = window.children.map((childWindow) {
40+
return View(
41+
view: childWindow.view,
42+
child: WindowContext(
43+
window: childWindow,
44+
child: childWindow.builder(context),
45+
),
46+
);
47+
}).toList();
48+
49+
return ViewAnchor(view: ViewCollection(views: childViews), child: widget);
50+
}
51+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'package:flutter/material.dart';
2+
3+
class WindowSettings {
4+
WindowSettings(
5+
{this.regularSize = const Size(400, 300),
6+
this.floatingRegularSize = const Size(300, 300),
7+
this.dialogSize = const Size(300, 250),
8+
this.satelliteSize = const Size(150, 300),
9+
this.popupSize = const Size(200, 200),
10+
this.tipSize = const Size(140, 140),
11+
this.anchorToWindow = false,
12+
this.anchorRect = const Rect.fromLTWH(0, 0, 1000, 1000)});
13+
final Size regularSize;
14+
final Size floatingRegularSize;
15+
final Size dialogSize;
16+
final Size satelliteSize;
17+
final Size popupSize;
18+
final Size tipSize;
19+
final Rect anchorRect;
20+
final bool anchorToWindow;
21+
}

0 commit comments

Comments
 (0)