diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index eb22d4cc4..903eb0e11 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1398,6 +1398,17 @@ Future saveWindowPosition(WindowType type, {int? windowId}) async { isMaximized = await wc.isMaximized(); 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( sz.width, sz.height, position.dx, position.dy, isMaximized); diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index af6e00f4a..2e553e724 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 8c2f6a0b0..dcf78b27b 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'; @@ -360,6 +359,9 @@ class _RemoteToolbarState extends State { triggerAutoHide() => _debouncerHide.value = _debouncerHide.value + 1; + void _minimize() async => + await WindowController.fromWindowId(windowId).minimize(); + @override initState() { super.initState(); @@ -429,6 +431,8 @@ class _RemoteToolbarState extends State { dragging: _dragging, fractionX: _fractionX, show: show, + setFullscreen: _setFullscreen, + setMinimize: _minimize, ), ), ), @@ -440,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)); } @@ -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 { final FFI ffi; const _MobileActionMenu({Key? key, required this.ffi}) : super(key: key); @@ -614,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); @@ -650,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(), @@ -721,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) { @@ -731,7 +712,7 @@ class ScreenAdjustor { children: [ MenuButton( child: Text(translate('Adjust Window')), - onPressed: doAdjustWindow, + onPressed: () => doAdjustWindow(context), ffi: ffi), Divider(), ], @@ -739,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 + @@ -895,7 +875,7 @@ class _DisplayMenuState extends State<_DisplayMenu> { color: _ToolbarTheme.blueColor, hoverColor: _ToolbarTheme.hoverBlueColor, menuChildren: [ - _screenAdjustor.adjustWindow(), + _screenAdjustor.adjustWindow(context), viewStyle(), scrollStyle(), imageQuality(), @@ -1082,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(), @@ -1125,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; @@ -1145,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, @@ -1161,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}'), @@ -1180,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, @@ -1197,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: [ @@ -1244,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(); @@ -1597,11 +1579,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 +1607,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 { @@ -1668,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, ); @@ -1817,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 @@ -1876,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) { @@ -1901,17 +1895,49 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { minimumSize: MaterialStateProperty.all(const Size(0, 0)), padding: MaterialStateProperty.all(EdgeInsets.zero), ); + final isFullscreen = stateGlobal.fullscreen; + const double iconSize = 20; final child = Row( mainAxisSize: MainAxisSize.min, children: [ _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( 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, + ), ))), ), ], @@ -1993,7 +2019,8 @@ class _MultiMonitorMenu extends StatelessWidget { children: [ SvgPicture.asset( "assets/screen.svg", - color: Colors.white, + colorFilter: + ColorFilter.mode(Colors.white, BlendMode.srcIn), ), Obx( () => Text( 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)