Merge pull request #5166 from dignow/feat/minimize_on_fullscreen

add minimize button on fullscreen toolbar
This commit is contained in:
RustDesk 2023-07-30 19:23:26 +08:00 committed by GitHub
commit 2ab513893d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 95 additions and 58 deletions

View File

@ -1398,6 +1398,17 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId}) async {
isMaximized = await wc.isMaximized(); isMaximized = await wc.isMaximized();
break; break;
} }
if (Platform.isWindows) {
const kMinOffset = -10000;
const kMaxOffset = 10000;
if (position.dx < kMinOffset ||
position.dy < kMinOffset ||
position.dx > kMaxOffset ||
position.dy > kMaxOffset) {
debugPrint("Invalid position: $position, ignore saving position");
return;
}
}
final pos = LastWindowPosition( final pos = LastWindowPosition(
sz.width, sz.height, position.dx, position.dy, isMaximized); sz.width, sz.height, position.dx, position.dy, isMaximized);

View File

@ -4,7 +4,6 @@ import 'dart:ui' as ui;
import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
@ -172,7 +171,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
connectionType.secure.value == ConnectionType.strSecure; connectionType.secure.value == ConnectionType.strSecure;
bool direct = bool direct =
connectionType.direct.value == ConnectionType.strDirect; connectionType.direct.value == ConnectionType.strDirect;
var msgConn; String msgConn;
if (secure && direct) { if (secure && direct) {
msgConn = translate("Direct and encrypted connection"); msgConn = translate("Direct and encrypted connection");
} else if (secure && !direct) { } else if (secure && !direct) {

View File

@ -1,5 +1,4 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:ui' as ui;
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
@ -360,6 +359,9 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
triggerAutoHide() => _debouncerHide.value = _debouncerHide.value + 1; triggerAutoHide() => _debouncerHide.value = _debouncerHide.value + 1;
void _minimize() async =>
await WindowController.fromWindowId(windowId).minimize();
@override @override
initState() { initState() {
super.initState(); super.initState();
@ -429,6 +431,8 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
dragging: _dragging, dragging: _dragging,
fractionX: _fractionX, fractionX: _fractionX,
show: show, show: show,
setFullscreen: _setFullscreen,
setMinimize: _minimize,
), ),
), ),
), ),
@ -440,8 +444,6 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
final List<Widget> toolbarItems = []; final List<Widget> toolbarItems = [];
if (!isWebDesktop) { if (!isWebDesktop) {
toolbarItems.add(_PinMenu(state: widget.state)); toolbarItems.add(_PinMenu(state: widget.state));
toolbarItems.add(
_FullscreenMenu(state: widget.state, setFullscreen: _setFullscreen));
toolbarItems.add(_MobileActionMenu(ffi: widget.ffi)); toolbarItems.add(_MobileActionMenu(ffi: widget.ffi));
} }
@ -549,27 +551,6 @@ class _PinMenu extends StatelessWidget {
} }
} }
class _FullscreenMenu extends StatelessWidget {
final ToolbarState state;
final Function(bool) setFullscreen;
bool get isFullscreen => stateGlobal.fullscreen;
const _FullscreenMenu(
{Key? key, required this.state, required this.setFullscreen})
: super(key: key);
@override
Widget build(BuildContext context) {
return _IconMenuButton(
assetName:
isFullscreen ? "assets/fullscreen_exit.svg" : "assets/fullscreen.svg",
tooltip: isFullscreen ? 'Exit Fullscreen' : 'Fullscreen',
onPressed: () => setFullscreen(!isFullscreen),
color: _ToolbarTheme.blueColor,
hoverColor: _ToolbarTheme.hoverBlueColor,
);
}
}
class _MobileActionMenu extends StatelessWidget { class _MobileActionMenu extends StatelessWidget {
final FFI ffi; final FFI ffi;
const _MobileActionMenu({Key? key, required this.ffi}) : super(key: key); const _MobileActionMenu({Key? key, required this.ffi}) : super(key: key);
@ -614,7 +595,7 @@ class _MonitorMenu extends StatelessWidget {
children: [ children: [
SvgPicture.asset( SvgPicture.asset(
"assets/screen.svg", "assets/screen.svg",
color: Colors.white, colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
), ),
Obx(() { Obx(() {
RxInt display = CurrentDisplayState.find(id); RxInt display = CurrentDisplayState.find(id);
@ -650,7 +631,7 @@ class _MonitorMenu extends StatelessWidget {
children: [ children: [
SvgPicture.asset( SvgPicture.asset(
"assets/screen.svg", "assets/screen.svg",
color: Colors.white, colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
), ),
Text( Text(
(i + 1).toString(), (i + 1).toString(),
@ -721,7 +702,7 @@ class ScreenAdjustor {
bool get isFullscreen => stateGlobal.fullscreen; bool get isFullscreen => stateGlobal.fullscreen;
int get windowId => stateGlobal.windowId; int get windowId => stateGlobal.windowId;
adjustWindow() { adjustWindow(BuildContext context) {
return futureBuilder( return futureBuilder(
future: isWindowCanBeAdjusted(), future: isWindowCanBeAdjusted(),
hasData: (data) { hasData: (data) {
@ -731,7 +712,7 @@ class ScreenAdjustor {
children: [ children: [
MenuButton( MenuButton(
child: Text(translate('Adjust Window')), child: Text(translate('Adjust Window')),
onPressed: doAdjustWindow, onPressed: () => doAdjustWindow(context),
ffi: ffi), ffi: ffi),
Divider(), Divider(),
], ],
@ -739,20 +720,19 @@ class ScreenAdjustor {
}); });
} }
doAdjustWindow() async { doAdjustWindow(BuildContext context) async {
await updateScreen(); await updateScreen();
if (_screen != null) { if (_screen != null) {
cbExitFullscreen(); cbExitFullscreen();
double scale = _screen!.scaleFactor; double scale = _screen!.scaleFactor;
final wndRect = await WindowController.fromWindowId(windowId).getFrame(); final wndRect = await WindowController.fromWindowId(windowId).getFrame();
final mediaSize = MediaQueryData.fromWindow(ui.window).size; final mediaSize = MediaQueryData.fromView(View.of(context)).size;
// On windows, wndRect is equal to GetWindowRect and mediaSize is equal to GetClientRect. // On windows, wndRect is equal to GetWindowRect and mediaSize is equal to GetClientRect.
// https://stackoverflow.com/a/7561083 // https://stackoverflow.com/a/7561083
double magicWidth = double magicWidth =
wndRect.right - wndRect.left - mediaSize.width * scale; wndRect.right - wndRect.left - mediaSize.width * scale;
double magicHeight = double magicHeight =
wndRect.bottom - wndRect.top - mediaSize.height * scale; wndRect.bottom - wndRect.top - mediaSize.height * scale;
final canvasModel = ffi.canvasModel; final canvasModel = ffi.canvasModel;
final width = (canvasModel.getDisplayWidth() * canvasModel.scale + final width = (canvasModel.getDisplayWidth() * canvasModel.scale +
CanvasModel.leftToEdge + CanvasModel.leftToEdge +
@ -895,7 +875,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
color: _ToolbarTheme.blueColor, color: _ToolbarTheme.blueColor,
hoverColor: _ToolbarTheme.hoverBlueColor, hoverColor: _ToolbarTheme.hoverBlueColor,
menuChildren: [ menuChildren: [
_screenAdjustor.adjustWindow(), _screenAdjustor.adjustWindow(context),
viewStyle(), viewStyle(),
scrollStyle(), scrollStyle(),
imageQuality(), imageQuality(),
@ -1082,9 +1062,9 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
return _SubmenuButton( return _SubmenuButton(
ffi: widget.ffi, ffi: widget.ffi,
menuChildren: <Widget>[ menuChildren: <Widget>[
_OriginalResolutionMenuButton(showOriginalBtn), _OriginalResolutionMenuButton(context, showOriginalBtn),
_FitLocalResolutionMenuButton(showFitLocalBtn), _FitLocalResolutionMenuButton(context, showFitLocalBtn),
_customResolutionMenuButton(isVirtualDisplay), _customResolutionMenuButton(context, isVirtualDisplay),
_menuDivider(showOriginalBtn, showFitLocalBtn, isVirtualDisplay), _menuDivider(showOriginalBtn, showFitLocalBtn, isVirtualDisplay),
] + ] +
_supportedResolutionMenuButtons(), _supportedResolutionMenuButtons(),
@ -1125,7 +1105,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
} }
} }
_onChanged(String? value) async { _onChanged(BuildContext context, String? value) async {
stateGlobal.setLastResolutionGroupValue( stateGlobal.setLastResolutionGroupValue(
widget.id, pi.currentDisplay, value); widget.id, pi.currentDisplay, value);
if (value == null) return; if (value == null) return;
@ -1145,12 +1125,12 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
if (w != null && h != null) { if (w != null && h != null) {
if (w != display.width || h != display.height) { if (w != display.width || h != display.height) {
await _changeResolution(w, h); await _changeResolution(context, w, h);
} }
} }
} }
_changeResolution(int w, int h) async { _changeResolution(BuildContext context, int w, int h) async {
await bind.sessionChangeResolution( await bind.sessionChangeResolution(
sessionId: ffi.sessionId, sessionId: ffi.sessionId,
display: pi.currentDisplay, display: pi.currentDisplay,
@ -1161,18 +1141,19 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
final display = ffiModel.display; final display = ffiModel.display;
if (w == display.width && h == display.height) { if (w == display.width && h == display.height) {
if (await widget.screenAdjustor.isWindowCanBeAdjusted()) { if (await widget.screenAdjustor.isWindowCanBeAdjusted()) {
widget.screenAdjustor.doAdjustWindow(); widget.screenAdjustor.doAdjustWindow(context);
} }
} }
}); });
} }
Widget _OriginalResolutionMenuButton(bool showOriginalBtn) { Widget _OriginalResolutionMenuButton(
BuildContext context, bool showOriginalBtn) {
return Offstage( return Offstage(
offstage: !showOriginalBtn, offstage: !showOriginalBtn,
child: MenuButton( child: MenuButton(
onPressed: () => onPressed: () => _changeResolution(
_changeResolution(display.originalWidth, display.originalHeight), context, display.originalWidth, display.originalHeight),
ffi: widget.ffi, ffi: widget.ffi,
child: Text( child: Text(
'${translate('resolution_original_tip')} ${display.originalWidth}x${display.originalHeight}'), '${translate('resolution_original_tip')} ${display.originalWidth}x${display.originalHeight}'),
@ -1180,14 +1161,15 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
); );
} }
Widget _FitLocalResolutionMenuButton(bool showFitLocalBtn) { Widget _FitLocalResolutionMenuButton(
BuildContext context, bool showFitLocalBtn) {
return Offstage( return Offstage(
offstage: !showFitLocalBtn, offstage: !showFitLocalBtn,
child: MenuButton( child: MenuButton(
onPressed: () { onPressed: () {
final resolution = _getBestFitResolution(); final resolution = _getBestFitResolution();
if (resolution != null) { if (resolution != null) {
_changeResolution(resolution.width, resolution.height); _changeResolution(context, resolution.width, resolution.height);
} }
}, },
ffi: widget.ffi, ffi: widget.ffi,
@ -1197,13 +1179,13 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
); );
} }
Widget _customResolutionMenuButton(isVirtualDisplay) { Widget _customResolutionMenuButton(BuildContext context, isVirtualDisplay) {
return Offstage( return Offstage(
offstage: !isVirtualDisplay, offstage: !isVirtualDisplay,
child: RdoMenuButton( child: RdoMenuButton(
value: _kCustomResolutionValue, value: _kCustomResolutionValue,
groupValue: _groupValue, groupValue: _groupValue,
onChanged: _onChanged, onChanged: (String? value) => _onChanged(context, value),
ffi: widget.ffi, ffi: widget.ffi,
child: Row( child: Row(
children: [ children: [
@ -1244,7 +1226,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
.map((e) => RdoMenuButton( .map((e) => RdoMenuButton(
value: '${e.width}x${e.height}', value: '${e.width}x${e.height}',
groupValue: _groupValue, groupValue: _groupValue,
onChanged: _onChanged, onChanged: (String? value) => _onChanged(context, value),
ffi: widget.ffi, ffi: widget.ffi,
child: Text('${e.width}x${e.height}'))) child: Text('${e.width}x${e.height}')))
.toList(); .toList();
@ -1597,11 +1579,11 @@ class _IconMenuButtonState extends State<_IconMenuButton> {
final icon = widget.icon ?? final icon = widget.icon ??
SvgPicture.asset( SvgPicture.asset(
widget.assetName!, widget.assetName!,
color: Colors.white, colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
width: _ToolbarTheme.buttonSize, width: _ToolbarTheme.buttonSize,
height: _ToolbarTheme.buttonSize, height: _ToolbarTheme.buttonSize,
); );
final button = SizedBox( var button = SizedBox(
width: _ToolbarTheme.buttonSize, width: _ToolbarTheme.buttonSize,
height: _ToolbarTheme.buttonSize, height: _ToolbarTheme.buttonSize,
child: MenuItemButton( child: MenuItemButton(
@ -1625,6 +1607,12 @@ class _IconMenuButtonState extends State<_IconMenuButton> {
).marginSymmetric( ).marginSymmetric(
horizontal: widget.hMargin ?? _ToolbarTheme.buttonHMargin, horizontal: widget.hMargin ?? _ToolbarTheme.buttonHMargin,
vertical: widget.vMargin ?? _ToolbarTheme.buttonVMargin); vertical: widget.vMargin ?? _ToolbarTheme.buttonVMargin);
if (widget.tooltip != null) {
button = Tooltip(
message: widget.tooltip!,
child: button,
);
}
if (widget.topLevel) { if (widget.topLevel) {
return MenuBar(children: [button]); return MenuBar(children: [button]);
} else { } else {
@ -1668,7 +1656,7 @@ class _IconSubmenuButtonState extends State<_IconSubmenuButton> {
final icon = widget.icon ?? final icon = widget.icon ??
SvgPicture.asset( SvgPicture.asset(
widget.svg!, widget.svg!,
color: Colors.white, colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
width: _ToolbarTheme.buttonSize, width: _ToolbarTheme.buttonSize,
height: _ToolbarTheme.buttonSize, height: _ToolbarTheme.buttonSize,
); );
@ -1817,12 +1805,18 @@ class _DraggableShowHide extends StatefulWidget {
final RxDouble fractionX; final RxDouble fractionX;
final RxBool dragging; final RxBool dragging;
final RxBool show; final RxBool show;
final Function(bool) setFullscreen;
final Function() setMinimize;
const _DraggableShowHide({ const _DraggableShowHide({
Key? key, Key? key,
required this.sessionId, required this.sessionId,
required this.fractionX, required this.fractionX,
required this.dragging, required this.dragging,
required this.show, required this.show,
required this.setFullscreen,
required this.setMinimize,
}) : super(key: key); }) : super(key: key);
@override @override
@ -1876,7 +1870,7 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
widget.dragging.value = true; widget.dragging.value = true;
}), }),
onDragEnd: (details) { onDragEnd: (details) {
final mediaSize = MediaQueryData.fromWindow(ui.window).size; final mediaSize = MediaQueryData.fromView(View.of(context)).size;
widget.fractionX.value += widget.fractionX.value +=
(details.offset.dx - position.dx) / (mediaSize.width - size.width); (details.offset.dx - position.dx) / (mediaSize.width - size.width);
if (widget.fractionX.value < left) { if (widget.fractionX.value < left) {
@ -1901,17 +1895,49 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
minimumSize: MaterialStateProperty.all(const Size(0, 0)), minimumSize: MaterialStateProperty.all(const Size(0, 0)),
padding: MaterialStateProperty.all(EdgeInsets.zero), padding: MaterialStateProperty.all(EdgeInsets.zero),
); );
final isFullscreen = stateGlobal.fullscreen;
const double iconSize = 20;
final child = Row( final child = Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
_buildDraggable(context), _buildDraggable(context),
TextButton(
onPressed: () {
widget.setFullscreen(!isFullscreen);
setState(() {});
},
child: Tooltip(
message: translate(isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'),
child: Icon(
isFullscreen ? Icons.fullscreen_exit : Icons.fullscreen,
size: iconSize,
),
),
),
Offstage(
offstage: !isFullscreen,
child: TextButton(
onPressed: () => widget.setMinimize(),
child: Tooltip(
message: translate('Minimize'),
child: Icon(
Icons.remove,
size: iconSize,
),
),
),
),
TextButton( TextButton(
onPressed: () => setState(() { onPressed: () => setState(() {
widget.show.value = !widget.show.value; widget.show.value = !widget.show.value;
}), }),
child: Obx((() => Icon( child: Obx((() => Tooltip(
widget.show.isTrue ? Icons.expand_less : Icons.expand_more, message: translate(
size: 20, widget.show.isTrue ? 'Hide Toolbar' : 'Show Toolbar'),
child: Icon(
widget.show.isTrue ? Icons.expand_less : Icons.expand_more,
size: iconSize,
),
))), ))),
), ),
], ],
@ -1993,7 +2019,8 @@ class _MultiMonitorMenu extends StatelessWidget {
children: [ children: [
SvgPicture.asset( SvgPicture.asset(
"assets/screen.svg", "assets/screen.svg",
color: Colors.white, colorFilter:
ColorFilter.mode(Colors.white, BlendMode.srcIn),
), ),
Obx( Obx(
() => Text( () => Text(

View File

@ -65,7 +65,7 @@ class StateGlobal {
? kMaximizeEdgeSize ? kMaximizeEdgeSize
: kWindowEdgeSize; : kWindowEdgeSize;
print( print(
"fullscreen: ${fullscreen}, resizeEdgeSize: ${_resizeEdgeSize.value}"); "fullscreen: $fullscreen, resizeEdgeSize: ${_resizeEdgeSize.value}");
_windowBorderWidth.value = fullscreen ? 0 : kWindowBorderWidth; _windowBorderWidth.value = fullscreen ? 0 : kWindowBorderWidth;
WindowController.fromWindowId(windowId) WindowController.fromWindowId(windowId)
.setFullscreen(_fullscreen) .setFullscreen(_fullscreen)