Skip to content

Commit 0e617fe

Browse files
Controlling application window (#39)
* Page size and Window size separate * Fix tests * Fix window size update * Window size returns 0,0 for web app * getWindowMediaData() * Window data on load/resize * Set window size and position * Fix tests * More window_* props * window fullScreen, alwaysOnTop, preventClose * window.destroy() * window.center() * WindowMediaData cleanup * Fix window center and full screen
1 parent f743acb commit 0e617fe

18 files changed

+759
-93
lines changed

client/lib/actions.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import 'dart:ui';
22

3+
import 'package:flet_view/models/window_media_data.dart';
4+
35
import 'protocol/add_page_controls_payload.dart';
46
import 'protocol/app_become_inactive_payload.dart';
57
import 'protocol/append_control_props_request.dart';
@@ -23,8 +25,14 @@ class PageReconnectingAction {
2325
}
2426

2527
class PageSizeChangeAction {
26-
final Size newSize;
27-
PageSizeChangeAction(this.newSize);
28+
final Size newPageSize;
29+
PageSizeChangeAction(this.newPageSize);
30+
}
31+
32+
class WindowEventAction {
33+
final String eventName;
34+
final WindowMediaData wmd;
35+
WindowEventAction(this.eventName, this.wmd);
2836
}
2937

3038
class PageBrightnessChangeAction {

client/lib/controls/create_control.dart

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import 'card.dart';
2-
import 'list_tile.dart';
3-
import 'navigation_rail.dart';
4-
51
import 'package:flutter/material.dart';
62
import 'package:flutter_redux/flutter_redux.dart';
73

client/lib/controls/page.dart

Lines changed: 152 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import 'package:flet_view/utils/user_fonts.dart';
21
import 'package:flutter/material.dart';
32
import 'package:flutter_redux/flutter_redux.dart';
43

@@ -13,12 +12,14 @@ import '../utils/desktop.dart';
1312
import '../utils/edge_insets.dart';
1413
import '../utils/theme.dart';
1514
import '../utils/uri.dart';
15+
import '../utils/user_fonts.dart';
1616
import '../widgets/page_media.dart';
17+
import '../widgets/window_media.dart';
1718
import 'app_bar.dart';
1819
import 'create_control.dart';
1920
import 'scrollable_control.dart';
2021

21-
class PageControl extends StatelessWidget {
22+
class PageControl extends StatefulWidget {
2223
final Control? parent;
2324
final Control control;
2425
final List<Control> children;
@@ -27,34 +28,42 @@ class PageControl extends StatelessWidget {
2728
{Key? key, this.parent, required this.control, required this.children})
2829
: super(key: key);
2930

31+
@override
32+
State<PageControl> createState() => _PageControlState();
33+
}
34+
35+
class _PageControlState extends State<PageControl> {
36+
String? _windowCenter;
37+
3038
@override
3139
Widget build(BuildContext context) {
32-
debugPrint("Page build: ${control.id}");
40+
debugPrint("Page build: ${widget.control.id}");
3341

34-
bool disabled = control.isDisabled;
42+
bool disabled = widget.control.isDisabled;
3543

36-
final spacing = control.attrDouble("spacing", 10)!;
44+
final spacing = widget.control.attrDouble("spacing", 10)!;
3745
final mainAlignment = parseMainAxisAlignment(
38-
control, "verticalAlignment", MainAxisAlignment.start);
46+
widget.control, "verticalAlignment", MainAxisAlignment.start);
3947
final crossAlignment = parseCrossAxisAlignment(
40-
control, "horizontalAlignment", CrossAxisAlignment.start);
48+
widget.control, "horizontalAlignment", CrossAxisAlignment.start);
4149

4250
ScrollMode scrollMode = ScrollMode.values.firstWhere(
4351
(m) =>
4452
m.name.toLowerCase() ==
45-
control.attrString("scroll", "")!.toLowerCase(),
53+
widget.control.attrString("scroll", "")!.toLowerCase(),
4654
orElse: () => ScrollMode.none);
4755

48-
final autoScroll = control.attrBool("autoScroll", false)!;
49-
final textDirection =
50-
control.attrBool("rtl", false)! ? TextDirection.rtl : TextDirection.ltr;
56+
final autoScroll = widget.control.attrBool("autoScroll", false)!;
57+
final textDirection = widget.control.attrBool("rtl", false)!
58+
? TextDirection.rtl
59+
: TextDirection.ltr;
5160

5261
Control? offstage;
5362
Control? appBar;
5463
List<Widget> controls = [];
5564
bool firstControl = true;
5665

57-
for (var ctrl in children.where((c) => c.isVisible)) {
66+
for (var ctrl in widget.children.where((c) => c.isVisible)) {
5867
// offstage control
5968
if (ctrl.type == ControlType.offstage) {
6069
offstage = ctrl;
@@ -74,11 +83,11 @@ class PageControl extends StatelessWidget {
7483
firstControl = false;
7584

7685
// displayed control
77-
controls.add(createControl(control, ctrl.id, disabled));
86+
controls.add(createControl(widget.control, ctrl.id, disabled));
7887
}
7988

8089
// theme
81-
var lightTheme = parseTheme(control, "theme") ??
90+
var lightTheme = parseTheme(widget.control, "theme") ??
8291
ThemeData(
8392
colorSchemeSeed: Colors.blue,
8493
brightness: Brightness.light,
@@ -88,7 +97,7 @@ class PageControl extends StatelessWidget {
8897
// : null,
8998
visualDensity: VisualDensity.adaptivePlatformDensity);
9099

91-
var darkTheme = parseTheme(control, "darkTheme") ??
100+
var darkTheme = parseTheme(widget.control, "darkTheme") ??
92101
ThemeData(
93102
colorSchemeSeed: Colors.blue,
94103
brightness: Brightness.dark,
@@ -98,14 +107,130 @@ class PageControl extends StatelessWidget {
98107
var themeMode = ThemeMode.values.firstWhere(
99108
(t) =>
100109
t.name.toLowerCase() ==
101-
control.attrString("themeMode", "")!.toLowerCase(),
110+
widget.control.attrString("themeMode", "")!.toLowerCase(),
102111
orElse: () => ThemeMode.system);
103112

104113
debugPrint("Page theme: $themeMode");
105114

106-
String title = control.attrString("title", "")!;
115+
// window title
116+
String title = widget.control.attrString("title", "")!;
107117
setWindowTitle(title);
108118

119+
// window params
120+
var windowCenter = widget.control.attrString("windowCenter");
121+
var fullScreen = widget.control.attrBool("windowFullScreen");
122+
123+
// window size
124+
var windowWidth = widget.control.attrDouble("windowWidth");
125+
var windowHeight = widget.control.attrDouble("windowHeight");
126+
if ((windowWidth != null || windowHeight != null) && fullScreen != true) {
127+
debugPrint("setWindowSize: $windowWidth, $windowHeight");
128+
setWindowSize(windowWidth, windowHeight);
129+
}
130+
131+
// window min size
132+
var windowMinWidth = widget.control.attrDouble("windowMinWidth");
133+
var windowMinHeight = widget.control.attrDouble("windowMinHeight");
134+
if (windowMinWidth != null || windowMinHeight != null) {
135+
debugPrint("setWindowMinSize: $windowMinWidth, $windowMinHeight");
136+
setWindowMinSize(windowMinWidth, windowMinHeight);
137+
}
138+
139+
// window max size
140+
var windowMaxWidth = widget.control.attrDouble("windowMaxWidth");
141+
var windowMaxHeight = widget.control.attrDouble("windowMaxHeight");
142+
if (windowMaxWidth != null || windowMaxHeight != null) {
143+
debugPrint("setWindowMaxSize: $windowMaxWidth, $windowMaxHeight");
144+
setWindowMaxSize(windowMaxWidth, windowMaxHeight);
145+
}
146+
147+
// window position
148+
var windowTop = widget.control.attrDouble("windowTop");
149+
var windowLeft = widget.control.attrDouble("windowLeft");
150+
if ((windowTop != null || windowLeft != null) &&
151+
fullScreen != true &&
152+
(windowCenter == null || windowCenter == "")) {
153+
debugPrint("setWindowPosition: $windowTop, $windowLeft");
154+
setWindowPosition(windowTop, windowLeft);
155+
}
156+
157+
// window opacity
158+
var opacity = widget.control.attrDouble("windowOpacity");
159+
if (opacity != null) {
160+
setWindowOpacity(opacity);
161+
}
162+
163+
// window minimizable
164+
var minimizable = widget.control.attrBool("windowMinimizable");
165+
if (minimizable != null) {
166+
setWindowMinimizability(minimizable);
167+
}
168+
169+
// window minimize
170+
var minimized = widget.control.attrBool("windowMinimized");
171+
if (minimized == true) {
172+
minimizeWindow();
173+
} else if (minimized == false) {
174+
restoreWindow();
175+
}
176+
177+
// window maximize
178+
var maximized = widget.control.attrBool("windowMaximized");
179+
if (maximized == true) {
180+
maximizeWindow();
181+
} else if (maximized == false) {
182+
unmaximizeWindow();
183+
}
184+
185+
// window resizable
186+
var resizable = widget.control.attrBool("windowResizable");
187+
if (resizable != null) {
188+
setWindowResizability(resizable);
189+
}
190+
191+
// window movable
192+
var movable = widget.control.attrBool("windowMovable");
193+
if (movable != null) {
194+
setWindowMovability(movable);
195+
}
196+
197+
// window fullScreen
198+
if (fullScreen != null) {
199+
setWindowFullScreen(fullScreen);
200+
}
201+
202+
// window alwaysOnTop
203+
var alwaysOnTop = widget.control.attrBool("windowAlwaysOnTop");
204+
if (alwaysOnTop != null) {
205+
setWindowAlwaysOnTop(alwaysOnTop);
206+
}
207+
208+
// window preventClose
209+
var preventClose = widget.control.attrBool("windowPreventClose");
210+
if (preventClose != null) {
211+
setWindowPreventClose(preventClose);
212+
}
213+
214+
// window focus
215+
var focused = widget.control.attrBool("windowFocused");
216+
if (focused == true) {
217+
focusWindow();
218+
} else if (focused == false) {
219+
blurWindow();
220+
}
221+
222+
// window center
223+
if (windowCenter != _windowCenter) {
224+
centerWindow();
225+
_windowCenter = windowCenter;
226+
}
227+
228+
// window destroy
229+
var destroy = widget.control.attrBool("windowDestroy");
230+
if (destroy == true) {
231+
destroyWindow();
232+
}
233+
109234
List<String> childIds = [];
110235
if (offstage != null) {
111236
childIds.add(offstage.id);
@@ -119,7 +244,7 @@ class PageControl extends StatelessWidget {
119244
converter: (store) => store.state.pageUri,
120245
builder: (context, pageUri) {
121246
// load custom fonts
122-
parseFonts(control, "fonts").forEach((fontFamily, fontUrl) {
247+
parseFonts(widget.control, "fonts").forEach((fontFamily, fontUrl) {
123248
var fontUri = Uri.parse(fontUrl);
124249
if (!fontUri.hasAuthority) {
125250
fontUri = getAssetUri(pageUri!, fontUrl);
@@ -156,6 +281,11 @@ class PageControl extends StatelessWidget {
156281
.toList()
157282
: [];
158283

284+
List<Widget> mediaWidgets = [const PageMedia()];
285+
if (isDesktop()) {
286+
mediaWidgets.add(const WindowMedia());
287+
}
288+
159289
List<Control> fab = offstage != null
160290
? childrenViews.controlViews.first.children
161291
.where((c) =>
@@ -183,7 +313,7 @@ class PageControl extends StatelessWidget {
183313
child: Scaffold(
184314
appBar: appBarView != null
185315
? AppBarControl(
186-
parent: control,
316+
parent: widget.control,
187317
control: appBarView.control,
188318
children: appBarView.children,
189319
parentDisabled: disabled,
@@ -195,12 +325,12 @@ class PageControl extends StatelessWidget {
195325
SizedBox.expand(
196326
child: Container(
197327
padding: parseEdgeInsets(
198-
control, "padding") ??
328+
widget.control, "padding") ??
199329
const EdgeInsets.all(10),
200330
decoration: BoxDecoration(
201331
color: HexColor.fromString(
202332
theme,
203-
control.attrString(
333+
widget.control.attrString(
204334
"bgcolor", "")!)),
205335
child: scrollMode != ScrollMode.none
206336
? ScrollableControl(
@@ -211,7 +341,7 @@ class PageControl extends StatelessWidget {
211341
)
212342
: column)),
213343
...offstageWidgets,
214-
const PageMedia()
344+
...mediaWidgets
215345
]),
216346
floatingActionButton: fab.isNotEmpty
217347
? createControl(
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
class WindowMediaData {
2+
bool? isMaximized;
3+
bool? isMinimized;
4+
bool? isMinimizable;
5+
bool? isFullScreen;
6+
bool? isResizable;
7+
bool? isMovable;
8+
bool? isClosable;
9+
bool? isAlwaysOnTop;
10+
bool? isFocused;
11+
bool? isTitleBarHidden;
12+
bool? isPreventClose;
13+
double? width;
14+
double? height;
15+
double? top;
16+
double? left;
17+
double? opacity;
18+
}
Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,34 @@
11
class RegisterWebClientRequest {
22
final String pageName;
33
final String? pageHash;
4-
final String? winWidth;
5-
final String? winHeight;
4+
final String? pageWidth;
5+
final String? pageHeight;
6+
final String? windowWidth;
7+
final String? windowHeight;
8+
final String? windowTop;
9+
final String? windowLeft;
610
final String? sessionId;
711

812
RegisterWebClientRequest(
913
{required this.pageName,
1014
this.pageHash,
11-
this.winWidth,
12-
this.winHeight,
15+
this.pageWidth,
16+
this.pageHeight,
17+
this.windowWidth,
18+
this.windowHeight,
19+
this.windowTop,
20+
this.windowLeft,
1321
this.sessionId});
1422

1523
Map<String, dynamic> toJson() => <String, dynamic>{
1624
'pageName': pageName,
1725
'pageHash': pageHash,
18-
'winWidth': winWidth,
19-
'winHeight': winHeight,
26+
'pageWidth': pageWidth,
27+
'pageHeight': pageHeight,
28+
'windowWidth': windowWidth,
29+
'windowHeight': windowHeight,
30+
'windowTop': windowTop,
31+
'windowLeft': windowLeft,
2032
'sessionId': sessionId
2133
};
2234
}

0 commit comments

Comments
 (0)