From 4d2036512a76c2021b37a3e35901589299ace2eb Mon Sep 17 00:00:00 2001 From: dignow Date: Thu, 27 Jul 2023 20:45:29 +0800 Subject: [PATCH 1/3] add minimize button on fullscreen toolbar Signed-off-by: dignow --- flutter/lib/common.dart | 7 +++ .../lib/desktop/widgets/remote_toolbar.dart | 43 ++++++++++++++++++- flutter/lib/models/state_model.dart | 2 +- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index b069fcee1..9f6bc01f2 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1389,6 +1389,13 @@ Future saveWindowPosition(WindowType type, {int? windowId}) async { isMaximized = await wc.isMaximized(); break; } + if (Platform.isWindows) { + const kMinOffset = -10000; + if (position.dx < kMinOffset || position.dy < kMinOffset) { + debugPrint("Invalid position: $position, ignore saving position"); + return; + } + } final pos = LastWindowPosition( sz.width, sz.height, position.dx, position.dy, isMaximized); diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 8c2f6a0b0..ec96d271e 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -360,6 +360,9 @@ class _RemoteToolbarState extends State { triggerAutoHide() => _debouncerHide.value = _debouncerHide.value + 1; + void _minimize() async => + await WindowController.fromWindowId(windowId).minimize(); + @override initState() { super.initState(); @@ -467,6 +470,12 @@ class _RemoteToolbarState extends State { toolbarItems.add(_VoiceCallMenu(id: widget.id, ffi: widget.ffi)); } toolbarItems.add(_RecordMenu(ffi: widget.ffi)); + if (!isWebDesktop) { + toolbarItems.add(_MinimizeMenu( + state: widget.state, + onPressed: _minimize, + )); + } toolbarItems.add(_CloseMenu(id: widget.id, ffi: widget.ffi)); return Column( mainAxisSize: MainAxisSize.min, @@ -549,6 +558,30 @@ class _PinMenu extends StatelessWidget { } } +class _MinimizeMenu extends StatelessWidget { + final ToolbarState state; + final Function() onPressed; + bool get isFullscreen => stateGlobal.fullscreen; + const _MinimizeMenu({ + Key? key, + required this.state, + required this.onPressed, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Offstage( + offstage: !isFullscreen, + child: _IconMenuButton( + assetName: 'assets/minimize_black_24dp.svg', + tooltip: 'Minimize', + onPressed: onPressed, + color: _ToolbarTheme.blueColor, + hoverColor: _ToolbarTheme.hoverBlueColor, + )); + } +} + class _FullscreenMenu extends StatelessWidget { final ToolbarState state; final Function(bool) setFullscreen; @@ -1597,11 +1630,11 @@ class _IconMenuButtonState extends State<_IconMenuButton> { final icon = widget.icon ?? SvgPicture.asset( widget.assetName!, - color: Colors.white, + colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn), width: _ToolbarTheme.buttonSize, height: _ToolbarTheme.buttonSize, ); - final button = SizedBox( + var button = SizedBox( width: _ToolbarTheme.buttonSize, height: _ToolbarTheme.buttonSize, child: MenuItemButton( @@ -1625,6 +1658,12 @@ class _IconMenuButtonState extends State<_IconMenuButton> { ).marginSymmetric( horizontal: widget.hMargin ?? _ToolbarTheme.buttonHMargin, vertical: widget.vMargin ?? _ToolbarTheme.buttonVMargin); + if (widget.tooltip != null) { + button = Tooltip( + message: widget.tooltip!, + child: button, + ); + } if (widget.topLevel) { return MenuBar(children: [button]); } else { diff --git a/flutter/lib/models/state_model.dart b/flutter/lib/models/state_model.dart index f7b4f8cc2..94f9cc234 100644 --- a/flutter/lib/models/state_model.dart +++ b/flutter/lib/models/state_model.dart @@ -65,7 +65,7 @@ class StateGlobal { ? kMaximizeEdgeSize : kWindowEdgeSize; print( - "fullscreen: ${fullscreen}, resizeEdgeSize: ${_resizeEdgeSize.value}"); + "fullscreen: $fullscreen, resizeEdgeSize: ${_resizeEdgeSize.value}"); _windowBorderWidth.value = fullscreen ? 0 : kWindowBorderWidth; WindowController.fromWindowId(windowId) .setFullscreen(_fullscreen) From 9321a4f486b3e3160786738de051c7c9d0c7f8e0 Mon Sep 17 00:00:00 2001 From: dignow Date: Thu, 27 Jul 2023 22:19:38 +0800 Subject: [PATCH 2/3] refact fullscreen and minimize button Signed-off-by: dignow --- flutter/lib/common.dart | 6 +- .../lib/desktop/pages/remote_tab_page.dart | 3 +- .../lib/desktop/widgets/remote_toolbar.dart | 136 ++++++++---------- 3 files changed, 62 insertions(+), 83 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 9f6bc01f2..bb4c60c37 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1391,7 +1391,11 @@ Future saveWindowPosition(WindowType type, {int? windowId}) async { } if (Platform.isWindows) { const kMinOffset = -10000; - if (position.dx < kMinOffset || position.dy < kMinOffset) { + const kMaxOffset = 10000; + if (position.dx < kMinOffset || + position.dy < kMinOffset || + position.dx > kMaxOffset || + position.dy > kMaxOffset) { debugPrint("Invalid position: $position, ignore saving position"); return; } diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index 9ee04f8b1..1c4c27c48 100644 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ b/flutter/lib/desktop/pages/remote_tab_page.dart @@ -4,7 +4,6 @@ import 'dart:ui' as ui; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/consts.dart'; @@ -172,7 +171,7 @@ class _ConnectionTabPageState extends State { connectionType.secure.value == ConnectionType.strSecure; bool direct = connectionType.direct.value == ConnectionType.strDirect; - var msgConn; + String msgConn; if (secure && direct) { msgConn = translate("Direct and encrypted connection"); } else if (secure && !direct) { diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index ec96d271e..ab74dab59 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:ui' as ui; import 'dart:async'; import 'dart:io'; @@ -432,6 +431,8 @@ class _RemoteToolbarState extends State { dragging: _dragging, fractionX: _fractionX, show: show, + setFullscreen: _setFullscreen, + setMinimize: _minimize, ), ), ), @@ -443,8 +444,6 @@ class _RemoteToolbarState extends State { final List toolbarItems = []; if (!isWebDesktop) { toolbarItems.add(_PinMenu(state: widget.state)); - toolbarItems.add( - _FullscreenMenu(state: widget.state, setFullscreen: _setFullscreen)); toolbarItems.add(_MobileActionMenu(ffi: widget.ffi)); } @@ -470,12 +469,6 @@ class _RemoteToolbarState extends State { toolbarItems.add(_VoiceCallMenu(id: widget.id, ffi: widget.ffi)); } toolbarItems.add(_RecordMenu(ffi: widget.ffi)); - if (!isWebDesktop) { - toolbarItems.add(_MinimizeMenu( - state: widget.state, - onPressed: _minimize, - )); - } toolbarItems.add(_CloseMenu(id: widget.id, ffi: widget.ffi)); return Column( mainAxisSize: MainAxisSize.min, @@ -558,51 +551,6 @@ class _PinMenu extends StatelessWidget { } } -class _MinimizeMenu extends StatelessWidget { - final ToolbarState state; - final Function() onPressed; - bool get isFullscreen => stateGlobal.fullscreen; - const _MinimizeMenu({ - Key? key, - required this.state, - required this.onPressed, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Offstage( - offstage: !isFullscreen, - child: _IconMenuButton( - assetName: 'assets/minimize_black_24dp.svg', - tooltip: 'Minimize', - onPressed: onPressed, - color: _ToolbarTheme.blueColor, - hoverColor: _ToolbarTheme.hoverBlueColor, - )); - } -} - -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 { final FFI ffi; const _MobileActionMenu({Key? key, required this.ffi}) : super(key: key); @@ -647,7 +595,7 @@ class _MonitorMenu extends StatelessWidget { children: [ SvgPicture.asset( "assets/screen.svg", - color: Colors.white, + colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn), ), Obx(() { RxInt display = CurrentDisplayState.find(id); @@ -683,7 +631,7 @@ class _MonitorMenu extends StatelessWidget { children: [ SvgPicture.asset( "assets/screen.svg", - color: Colors.white, + colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn), ), Text( (i + 1).toString(), @@ -754,7 +702,7 @@ class ScreenAdjustor { bool get isFullscreen => stateGlobal.fullscreen; int get windowId => stateGlobal.windowId; - adjustWindow() { + adjustWindow(BuildContext context) { return futureBuilder( future: isWindowCanBeAdjusted(), hasData: (data) { @@ -764,7 +712,7 @@ class ScreenAdjustor { children: [ MenuButton( child: Text(translate('Adjust Window')), - onPressed: doAdjustWindow, + onPressed: () => doAdjustWindow(context), ffi: ffi), Divider(), ], @@ -772,20 +720,19 @@ class ScreenAdjustor { }); } - doAdjustWindow() async { + doAdjustWindow(BuildContext context) async { await updateScreen(); if (_screen != null) { cbExitFullscreen(); double scale = _screen!.scaleFactor; 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. // https://stackoverflow.com/a/7561083 double magicWidth = wndRect.right - wndRect.left - mediaSize.width * scale; double magicHeight = wndRect.bottom - wndRect.top - mediaSize.height * scale; - final canvasModel = ffi.canvasModel; final width = (canvasModel.getDisplayWidth() * canvasModel.scale + CanvasModel.leftToEdge + @@ -928,7 +875,7 @@ class _DisplayMenuState extends State<_DisplayMenu> { color: _ToolbarTheme.blueColor, hoverColor: _ToolbarTheme.hoverBlueColor, menuChildren: [ - _screenAdjustor.adjustWindow(), + _screenAdjustor.adjustWindow(context), viewStyle(), scrollStyle(), imageQuality(), @@ -1115,9 +1062,9 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { return _SubmenuButton( ffi: widget.ffi, menuChildren: [ - _OriginalResolutionMenuButton(showOriginalBtn), - _FitLocalResolutionMenuButton(showFitLocalBtn), - _customResolutionMenuButton(isVirtualDisplay), + _OriginalResolutionMenuButton(context, showOriginalBtn), + _FitLocalResolutionMenuButton(context, showFitLocalBtn), + _customResolutionMenuButton(context, isVirtualDisplay), _menuDivider(showOriginalBtn, showFitLocalBtn, isVirtualDisplay), ] + _supportedResolutionMenuButtons(), @@ -1158,7 +1105,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { } } - _onChanged(String? value) async { + _onChanged(BuildContext context, String? value) async { stateGlobal.setLastResolutionGroupValue( widget.id, pi.currentDisplay, value); if (value == null) return; @@ -1178,12 +1125,12 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { if (w != null && h != null) { 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( sessionId: ffi.sessionId, display: pi.currentDisplay, @@ -1194,18 +1141,19 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { final display = ffiModel.display; if (w == display.width && h == display.height) { if (await widget.screenAdjustor.isWindowCanBeAdjusted()) { - widget.screenAdjustor.doAdjustWindow(); + widget.screenAdjustor.doAdjustWindow(context); } } }); } - Widget _OriginalResolutionMenuButton(bool showOriginalBtn) { + Widget _OriginalResolutionMenuButton( + BuildContext context, bool showOriginalBtn) { return Offstage( offstage: !showOriginalBtn, child: MenuButton( - onPressed: () => - _changeResolution(display.originalWidth, display.originalHeight), + onPressed: () => _changeResolution( + context, display.originalWidth, display.originalHeight), ffi: widget.ffi, child: Text( '${translate('resolution_original_tip')} ${display.originalWidth}x${display.originalHeight}'), @@ -1213,14 +1161,15 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { ); } - Widget _FitLocalResolutionMenuButton(bool showFitLocalBtn) { + Widget _FitLocalResolutionMenuButton( + BuildContext context, bool showFitLocalBtn) { return Offstage( offstage: !showFitLocalBtn, child: MenuButton( onPressed: () { final resolution = _getBestFitResolution(); if (resolution != null) { - _changeResolution(resolution.width, resolution.height); + _changeResolution(context, resolution.width, resolution.height); } }, ffi: widget.ffi, @@ -1230,13 +1179,13 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { ); } - Widget _customResolutionMenuButton(isVirtualDisplay) { + Widget _customResolutionMenuButton(BuildContext context, isVirtualDisplay) { return Offstage( offstage: !isVirtualDisplay, child: RdoMenuButton( value: _kCustomResolutionValue, groupValue: _groupValue, - onChanged: _onChanged, + onChanged: (String? value) => _onChanged(context, value), ffi: widget.ffi, child: Row( children: [ @@ -1277,7 +1226,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { .map((e) => RdoMenuButton( value: '${e.width}x${e.height}', groupValue: _groupValue, - onChanged: _onChanged, + onChanged: (String? value) => _onChanged(context, value), ffi: widget.ffi, child: Text('${e.width}x${e.height}'))) .toList(); @@ -1707,7 +1656,7 @@ class _IconSubmenuButtonState extends State<_IconSubmenuButton> { final icon = widget.icon ?? SvgPicture.asset( widget.svg!, - color: Colors.white, + colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn), width: _ToolbarTheme.buttonSize, height: _ToolbarTheme.buttonSize, ); @@ -1856,12 +1805,18 @@ class _DraggableShowHide extends StatefulWidget { final RxDouble fractionX; final RxBool dragging; final RxBool show; + + final Function(bool) setFullscreen; + final Function() setMinimize; + const _DraggableShowHide({ Key? key, required this.sessionId, required this.fractionX, required this.dragging, required this.show, + required this.setFullscreen, + required this.setMinimize, }) : super(key: key); @override @@ -1915,7 +1870,7 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { widget.dragging.value = true; }), onDragEnd: (details) { - final mediaSize = MediaQueryData.fromWindow(ui.window).size; + final mediaSize = MediaQueryData.fromView(View.of(context)).size; widget.fractionX.value += (details.offset.dx - position.dx) / (mediaSize.width - size.width); if (widget.fractionX.value < left) { @@ -1940,10 +1895,31 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { minimumSize: MaterialStateProperty.all(const Size(0, 0)), padding: MaterialStateProperty.all(EdgeInsets.zero), ); + final isFullscreen = stateGlobal.fullscreen; final child = Row( mainAxisSize: MainAxisSize.min, children: [ _buildDraggable(context), + TextButton( + onPressed: () { + widget.setFullscreen(!isFullscreen); + setState(() {}); + }, + child: Icon( + isFullscreen ? Icons.fullscreen_exit : Icons.fullscreen, + size: 20, + ), + ), + Offstage( + offstage: !isFullscreen, + child: TextButton( + onPressed: () => widget.setMinimize(), + child: Icon( + Icons.minimize, + size: 20, + ), + ), + ), TextButton( onPressed: () => setState(() { widget.show.value = !widget.show.value; @@ -2032,7 +2008,7 @@ class _MultiMonitorMenu extends StatelessWidget { children: [ SvgPicture.asset( "assets/screen.svg", - color: Colors.white, + colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn), ), Obx( () => Text( From a4600dd8a85e4b2dbe9e4b02bc9cba8c7848ee87 Mon Sep 17 00:00:00 2001 From: dignow Date: Sun, 30 Jul 2023 19:12:51 +0800 Subject: [PATCH 3/3] refact, minimize on fullscreen Signed-off-by: dignow --- .../lib/desktop/widgets/remote_toolbar.dart | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index ab74dab59..dcf78b27b 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -1896,6 +1896,7 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { padding: MaterialStateProperty.all(EdgeInsets.zero), ); final isFullscreen = stateGlobal.fullscreen; + const double iconSize = 20; final child = Row( mainAxisSize: MainAxisSize.min, children: [ @@ -1905,18 +1906,24 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { widget.setFullscreen(!isFullscreen); setState(() {}); }, - child: Icon( - isFullscreen ? Icons.fullscreen_exit : Icons.fullscreen, - size: 20, + 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: Icon( - Icons.minimize, - size: 20, + child: Tooltip( + message: translate('Minimize'), + child: Icon( + Icons.remove, + size: iconSize, + ), ), ), ), @@ -1924,9 +1931,13 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { onPressed: () => setState(() { widget.show.value = !widget.show.value; }), - child: Obx((() => Icon( - widget.show.isTrue ? Icons.expand_less : Icons.expand_more, - size: 20, + child: Obx((() => Tooltip( + message: translate( + widget.show.isTrue ? 'Hide Toolbar' : 'Show Toolbar'), + child: Icon( + widget.show.isTrue ? Icons.expand_less : Icons.expand_more, + size: iconSize, + ), ))), ), ], @@ -2008,7 +2019,8 @@ class _MultiMonitorMenu extends StatelessWidget { children: [ SvgPicture.asset( "assets/screen.svg", - colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn), + colorFilter: + ColorFilter.mode(Colors.white, BlendMode.srcIn), ), Obx( () => Text(