2022-09-16 14:43:28 +03:00
import ' dart:convert ' ;
2022-10-09 14:27:30 +03:00
import ' dart:ui ' as ui ;
2022-09-08 18:25:19 +03:00
2022-08-26 18:28:08 +03:00
import ' package:flutter/material.dart ' ;
import ' package:flutter/services.dart ' ;
2023-04-12 04:41:13 +03:00
import ' package:flutter_hbb/common/widgets/toolbar.dart ' ;
2022-08-26 18:28:08 +03:00
import ' package:flutter_hbb/models/chat_model.dart ' ;
2022-11-01 12:01:43 +03:00
import ' package:flutter_hbb/models/state_model.dart ' ;
2022-11-24 06:19:16 +03:00
import ' package:flutter_hbb/consts.dart ' ;
2022-12-01 08:52:12 +03:00
import ' package:flutter_hbb/utils/multi_window_manager.dart ' ;
2023-05-10 13:58:45 +03:00
import ' package:flutter_hbb/plugin/widgets/desc_ui.dart ' ;
2023-04-21 16:40:34 +03:00
import ' package:flutter_hbb/plugin/common.dart ' ;
2023-01-31 17:49:17 +03:00
import ' package:flutter_svg/flutter_svg.dart ' ;
2022-08-26 18:28:08 +03:00
import ' package:get/get.dart ' ;
2022-09-15 12:31:28 +03:00
import ' package:provider/provider.dart ' ;
2022-12-07 10:13:24 +03:00
import ' package:debounce_throttle/debounce_throttle.dart ' ;
2022-10-08 12:27:30 +03:00
import ' package:desktop_multi_window/desktop_multi_window.dart ' ;
2022-10-09 14:27:30 +03:00
import ' package:window_size/window_size.dart ' as window_size ;
2022-08-26 18:28:08 +03:00
import ' ../../common.dart ' ;
2023-03-24 10:21:14 +03:00
import ' ../../common/widgets/dialog.dart ' ;
2022-08-26 18:28:08 +03:00
import ' ../../models/model.dart ' ;
import ' ../../models/platform_model.dart ' ;
2022-08-29 13:48:12 +03:00
import ' ../../common/shared_state.dart ' ;
2022-08-26 18:28:08 +03:00
import ' ./popup_menu.dart ' ;
2022-12-27 11:45:13 +03:00
import ' ./kb_layout_type_chooser.dart ' ;
2022-08-26 18:28:08 +03:00
2023-02-25 17:59:59 +03:00
const _kKeyLegacyMode = ' legacy ' ;
const _kKeyMapMode = ' map ' ;
const _kKeyTranslateMode = ' translate ' ;
2023-06-11 11:32:22 +03:00
class ToolbarState {
2022-11-17 13:52:27 +03:00
final kStoreKey = ' remoteMenubarState ' ;
2022-11-10 09:32:22 +03:00
late RxBool show ;
late RxBool _pin ;
2023-06-11 11:32:22 +03:00
ToolbarState ( ) {
2022-11-10 16:25:12 +03:00
final s = bind . getLocalFlutterConfig ( k: kStoreKey ) ;
if ( s . isEmpty ) {
2022-11-10 09:32:22 +03:00
_initSet ( false , false ) ;
2022-11-10 16:25:12 +03:00
return ;
2022-11-10 09:32:22 +03:00
}
2022-11-10 16:25:12 +03:00
2022-11-10 09:32:22 +03:00
try {
2022-11-10 16:25:12 +03:00
final m = jsonDecode ( s ) ;
2022-11-10 09:32:22 +03:00
if ( m = = null ) {
_initSet ( false , false ) ;
} else {
_initSet ( m [ ' pin ' ] ? ? false , m [ ' pin ' ] ? ? false ) ;
}
} catch ( e ) {
2023-06-11 11:32:22 +03:00
debugPrint ( ' Failed to decode toolbar state ${ e . toString ( ) } ' ) ;
2022-11-10 09:32:22 +03:00
_initSet ( false , false ) ;
}
}
_initSet ( bool s , bool p ) {
2022-11-16 13:07:58 +03:00
// Show remubar when connection is established.
2023-06-11 10:28:41 +03:00
show =
RxBool ( bind . mainGetUserDefaultOption ( key: ' collapse_toolbar ' ) ! = ' Y ' ) ;
2022-11-10 09:32:22 +03:00
_pin = RxBool ( p ) ;
}
bool get pin = > _pin . value ;
switchShow ( ) async {
show . value = ! show . value ;
}
setShow ( bool v ) async {
if ( show . value ! = v ) {
show . value = v ;
}
}
switchPin ( ) async {
_pin . value = ! _pin . value ;
// Save everytime changed, as this func will not be called frequently
2022-11-24 06:19:16 +03:00
await _savePin ( ) ;
2022-11-10 09:32:22 +03:00
}
setPin ( bool v ) async {
if ( _pin . value ! = v ) {
_pin . value = v ;
// Save everytime changed, as this func will not be called frequently
2022-11-24 06:19:16 +03:00
await _savePin ( ) ;
2022-11-10 09:32:22 +03:00
}
}
2022-11-24 06:19:16 +03:00
_savePin ( ) async {
2022-11-10 16:25:12 +03:00
bind . setLocalFlutterConfig (
k: kStoreKey , v: jsonEncode ( { ' pin ' : _pin . value } ) ) ;
2022-11-10 09:32:22 +03:00
}
2022-11-24 06:19:16 +03:00
save ( ) async {
await _savePin ( ) ;
}
2022-11-10 09:32:22 +03:00
}
2023-06-11 11:32:22 +03:00
class _ToolbarTheme {
2023-02-15 13:40:17 +03:00
static const Color blueColor = MyTheme . button ;
static const Color hoverBlueColor = MyTheme . accent ;
static const Color redColor = Colors . redAccent ;
static const Color hoverRedColor = Colors . red ;
2022-08-29 13:48:12 +03:00
// kMinInteractiveDimension
2022-09-23 07:20:40 +03:00
static const double height = 20.0 ;
2022-08-29 13:48:12 +03:00
static const double dividerHeight = 12.0 ;
2023-02-23 09:30:29 +03:00
static const double buttonSize = 32 ;
static const double buttonHMargin = 3 ;
static const double buttonVMargin = 6 ;
static const double iconRadius = 8 ;
2023-03-12 05:48:54 +03:00
static const double elevation = 3 ;
2022-08-26 18:28:08 +03:00
}
2023-02-06 06:27:20 +03:00
typedef DismissFunc = void Function ( ) ;
class RemoteMenuEntry {
static MenuEntryRadios < String > viewStyle (
String remoteId ,
FFI ffi ,
EdgeInsets padding , {
DismissFunc ? dismissFunc ,
DismissCallback ? dismissCallback ,
RxString ? rxViewStyle ,
} ) {
return MenuEntryRadios < String > (
text: translate ( ' Ratio ' ) ,
optionsGetter: ( ) = > [
MenuEntryRadioOption (
text: translate ( ' Scale original ' ) ,
value: kRemoteViewStyleOriginal ,
dismissOnClicked: true ,
dismissCallback: dismissCallback ,
) ,
MenuEntryRadioOption (
text: translate ( ' Scale adaptive ' ) ,
value: kRemoteViewStyleAdaptive ,
dismissOnClicked: true ,
dismissCallback: dismissCallback ,
) ,
] ,
curOptionGetter: ( ) async {
// null means peer id is not found, which there's no need to care about
2023-06-06 02:39:44 +03:00
final viewStyle =
await bind . sessionGetViewStyle ( sessionId: ffi . sessionId ) ? ? ' ' ;
2023-02-06 06:27:20 +03:00
if ( rxViewStyle ! = null ) {
rxViewStyle . value = viewStyle ;
}
return viewStyle ;
} ,
optionSetter: ( String oldValue , String newValue ) async {
2023-06-06 02:39:44 +03:00
await bind . sessionSetViewStyle (
sessionId: ffi . sessionId , value: newValue ) ;
2023-02-06 06:27:20 +03:00
if ( rxViewStyle ! = null ) {
rxViewStyle . value = newValue ;
}
ffi . canvasModel . updateViewStyle ( ) ;
if ( dismissFunc ! = null ) {
dismissFunc ( ) ;
}
} ,
padding: padding ,
dismissOnClicked: true ,
dismissCallback: dismissCallback ,
) ;
}
static MenuEntrySwitch2 < String > showRemoteCursor (
String remoteId ,
2023-06-06 02:39:44 +03:00
SessionID sessionId ,
2023-02-06 06:27:20 +03:00
EdgeInsets padding , {
DismissFunc ? dismissFunc ,
DismissCallback ? dismissCallback ,
} ) {
final state = ShowRemoteCursorState . find ( remoteId ) ;
final optKey = ' show-remote-cursor ' ;
return MenuEntrySwitch2 < String > (
switchType: SwitchType . scheckbox ,
text: translate ( ' Show remote cursor ' ) ,
getter: ( ) {
return state ;
} ,
setter: ( bool v ) async {
2023-06-06 02:39:44 +03:00
await bind . sessionToggleOption ( sessionId: sessionId , value: optKey ) ;
2023-02-06 06:27:20 +03:00
state . value =
2023-06-06 02:39:44 +03:00
bind . sessionGetToggleOptionSync ( sessionId: sessionId , arg: optKey ) ;
2023-02-06 06:27:20 +03:00
if ( dismissFunc ! = null ) {
dismissFunc ( ) ;
}
} ,
padding: padding ,
dismissOnClicked: true ,
dismissCallback: dismissCallback ,
) ;
}
static MenuEntrySwitch < String > disableClipboard (
2023-06-06 02:39:44 +03:00
SessionID sessionId ,
2023-02-06 06:27:20 +03:00
EdgeInsets ? padding , {
DismissFunc ? dismissFunc ,
DismissCallback ? dismissCallback ,
} ) {
return createSwitchMenuEntry (
2023-06-06 02:39:44 +03:00
sessionId ,
2023-02-06 06:27:20 +03:00
' Disable clipboard ' ,
' disable-clipboard ' ,
padding ,
true ,
dismissCallback: dismissCallback ,
) ;
}
static MenuEntrySwitch < String > createSwitchMenuEntry (
2023-06-06 02:39:44 +03:00
SessionID sessionId ,
2023-02-06 06:27:20 +03:00
String text ,
String option ,
EdgeInsets ? padding ,
bool dismissOnClicked , {
DismissFunc ? dismissFunc ,
DismissCallback ? dismissCallback ,
} ) {
return MenuEntrySwitch < String > (
switchType: SwitchType . scheckbox ,
text: translate ( text ) ,
getter: ( ) async {
2023-06-06 02:39:44 +03:00
return bind . sessionGetToggleOptionSync (
sessionId: sessionId , arg: option ) ;
2023-02-06 06:27:20 +03:00
} ,
setter: ( bool v ) async {
2023-06-06 02:39:44 +03:00
await bind . sessionToggleOption ( sessionId: sessionId , value: option ) ;
2023-02-06 06:27:20 +03:00
if ( dismissFunc ! = null ) {
dismissFunc ( ) ;
}
} ,
padding: padding ,
dismissOnClicked: dismissOnClicked ,
dismissCallback: dismissCallback ,
) ;
}
static MenuEntryButton < String > insertLock (
2023-06-06 02:39:44 +03:00
SessionID sessionId ,
2023-02-06 06:27:20 +03:00
EdgeInsets ? padding , {
DismissFunc ? dismissFunc ,
DismissCallback ? dismissCallback ,
} ) {
return MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Text (
translate ( ' Insert Lock ' ) ,
style: style ,
) ,
proc: ( ) {
2023-06-06 02:39:44 +03:00
bind . sessionLockScreen ( sessionId: sessionId ) ;
2023-02-06 06:27:20 +03:00
if ( dismissFunc ! = null ) {
dismissFunc ( ) ;
}
} ,
padding: padding ,
dismissOnClicked: true ,
dismissCallback: dismissCallback ,
) ;
}
static insertCtrlAltDel (
2023-06-06 02:39:44 +03:00
SessionID sessionId ,
2023-02-06 06:27:20 +03:00
EdgeInsets ? padding , {
DismissFunc ? dismissFunc ,
DismissCallback ? dismissCallback ,
} ) {
return MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Text (
' ${ translate ( " Insert " ) } Ctrl + Alt + Del ' ,
style: style ,
) ,
proc: ( ) {
2023-06-06 02:39:44 +03:00
bind . sessionCtrlAltDel ( sessionId: sessionId ) ;
2023-02-06 06:27:20 +03:00
if ( dismissFunc ! = null ) {
dismissFunc ( ) ;
}
} ,
padding: padding ,
dismissOnClicked: true ,
dismissCallback: dismissCallback ,
) ;
}
}
2023-06-11 11:32:22 +03:00
class RemoteToolbar extends StatefulWidget {
2022-08-26 18:28:08 +03:00
final String id ;
final FFI ffi ;
2023-06-11 11:32:22 +03:00
final ToolbarState state ;
2022-09-14 05:10:55 +03:00
final Function ( Function ( bool ) ) onEnterOrLeaveImageSetter ;
final Function ( ) onEnterOrLeaveImageCleaner ;
2022-08-26 18:28:08 +03:00
2023-06-11 11:32:22 +03:00
RemoteToolbar ( {
2022-08-26 18:28:08 +03:00
Key ? key ,
required this . id ,
required this . ffi ,
2022-11-10 09:32:22 +03:00
required this . state ,
2022-09-14 05:10:55 +03:00
required this . onEnterOrLeaveImageSetter ,
required this . onEnterOrLeaveImageCleaner ,
2022-08-26 18:28:08 +03:00
} ) : super ( key: key ) ;
@ override
2023-06-11 11:32:22 +03:00
State < RemoteToolbar > createState ( ) = > _RemoteToolbarState ( ) ;
2022-08-26 18:28:08 +03:00
}
2023-06-11 11:32:22 +03:00
class _RemoteToolbarState extends State < RemoteToolbar > {
2022-12-07 10:13:24 +03:00
late Debouncer < int > _debouncerHide ;
2022-09-13 16:59:06 +03:00
bool _isCursorOverImage = false ;
2022-12-07 10:13:24 +03:00
final _fractionX = 0.5 . obs ;
final _dragging = false . obs ;
2022-08-26 18:28:08 +03:00
2022-11-01 12:01:43 +03:00
int get windowId = > stateGlobal . windowId ;
bool get isFullscreen = > stateGlobal . fullscreen ;
2022-09-13 16:59:06 +03:00
void _setFullscreen ( bool v ) {
2022-11-01 12:01:43 +03:00
stateGlobal . setFullscreen ( v ) ;
setState ( ( ) { } ) ;
2022-08-26 18:28:08 +03:00
}
2022-11-10 09:32:22 +03:00
RxBool get show = > widget . state . show ;
bool get pin = > widget . state . pin ;
2022-11-03 16:58:25 +03:00
2023-03-28 05:36:59 +03:00
PeerInfo get pi = > widget . ffi . ffiModel . pi ;
FfiModel get ffiModel = > widget . ffi . ffiModel ;
2023-04-01 10:51:42 +03:00
triggerAutoHide ( ) = > _debouncerHide . value = _debouncerHide . value + 1 ;
2022-09-13 16:59:06 +03:00
@ override
2022-09-14 05:10:55 +03:00
initState ( ) {
2022-09-13 16:59:06 +03:00
super . initState ( ) ;
2023-05-17 11:55:35 +03:00
Future . delayed ( Duration . zero , ( ) async {
_fractionX . value = double . tryParse ( await bind . sessionGetOption (
2023-06-06 02:39:44 +03:00
sessionId: widget . ffi . sessionId ,
arg: ' remote-menubar-drag-x ' ) ? ?
2023-05-17 11:55:35 +03:00
' 0.5 ' ) ? ?
0.5 ;
} ) ;
2022-12-07 10:13:24 +03:00
_debouncerHide = Debouncer < int > (
Duration ( milliseconds: 5000 ) ,
onChanged: _debouncerHideProc ,
initialValue: 0 ,
) ;
2022-09-14 05:10:55 +03:00
widget . onEnterOrLeaveImageSetter ( ( enter ) {
2022-09-13 16:59:06 +03:00
if ( enter ) {
2023-04-01 10:51:42 +03:00
triggerAutoHide ( ) ;
2022-09-13 16:59:06 +03:00
_isCursorOverImage = true ;
} else {
_isCursorOverImage = false ;
}
} ) ;
2022-12-07 10:13:24 +03:00
}
2022-09-13 16:59:06 +03:00
2022-12-07 10:13:24 +03:00
_debouncerHideProc ( int v ) {
if ( ! pin & & show . isTrue & & _isCursorOverImage & & _dragging . isFalse ) {
show . value = false ;
}
2022-09-13 16:59:06 +03:00
}
2022-09-14 05:10:55 +03:00
@ override
dispose ( ) {
super . dispose ( ) ;
widget . onEnterOrLeaveImageCleaner ( ) ;
}
2022-08-26 18:28:08 +03:00
@ override
Widget build ( BuildContext context ) {
2022-12-23 18:10:34 +03:00
// No need to use future builder here.
2022-08-26 18:28:08 +03:00
return Align (
alignment: Alignment . topCenter ,
2022-12-07 10:13:24 +03:00
child: Obx ( ( ) = > show . value
2023-03-15 20:31:53 +03:00
? _buildToolbar ( context )
2022-12-07 10:13:24 +03:00
: _buildDraggableShowHide ( context ) ) ,
2022-08-26 18:28:08 +03:00
) ;
}
2022-12-07 10:13:24 +03:00
Widget _buildDraggableShowHide ( BuildContext context ) {
return Obx ( ( ) {
if ( show . isTrue & & _dragging . isFalse ) {
2023-04-01 10:51:42 +03:00
triggerAutoHide ( ) ;
2022-12-07 10:13:24 +03:00
}
return Align (
alignment: FractionalOffset ( _fractionX . value , 0 ) ,
child: Offstage (
offstage: _dragging . isTrue ,
2023-03-12 05:48:54 +03:00
child: Material (
2023-06-11 11:32:22 +03:00
elevation: _ToolbarTheme . elevation ,
2023-03-15 11:57:24 +03:00
shadowColor: MyTheme . color ( context ) . shadow ,
2023-03-12 05:48:54 +03:00
child: _DraggableShowHide (
2023-06-06 02:39:44 +03:00
sessionId: widget . ffi . sessionId ,
2023-03-12 05:48:54 +03:00
dragging: _dragging ,
fractionX: _fractionX ,
show: show ,
) ,
2022-12-07 10:13:24 +03:00
) ,
) ,
) ;
} ) ;
2022-08-26 18:28:08 +03:00
}
2023-03-15 20:31:53 +03:00
Widget _buildToolbar ( BuildContext context ) {
final List < Widget > toolbarItems = [ ] ;
2022-08-26 18:28:08 +03:00
if ( ! isWebDesktop ) {
2023-03-15 20:31:53 +03:00
toolbarItems . add ( _PinMenu ( state: widget . state ) ) ;
toolbarItems . add (
2023-02-23 09:30:29 +03:00
_FullscreenMenu ( state: widget . state , setFullscreen: _setFullscreen ) ) ;
2023-03-15 20:31:53 +03:00
toolbarItems . add ( _MobileActionMenu ( ffi: widget . ffi ) ) ;
2022-08-26 18:28:08 +03:00
}
2023-03-15 19:57:52 +03:00
2023-03-28 05:36:59 +03:00
if ( PrivacyModeState . find ( widget . id ) . isFalse & & pi . displays . length > 1 ) {
2023-03-15 20:31:53 +03:00
toolbarItems . add (
2023-03-17 06:27:22 +03:00
bind . mainGetUserDefaultOption ( key: ' show_monitors_toolbar ' ) = = ' Y '
2023-03-15 19:57:52 +03:00
? _MultiMonitorMenu ( id: widget . id , ffi: widget . ffi )
: _MonitorMenu ( id: widget . id , ffi: widget . ffi ) ,
) ;
}
2023-03-15 20:31:53 +03:00
toolbarItems
2023-02-23 09:30:29 +03:00
. add ( _ControlMenu ( id: widget . id , ffi: widget . ffi , state: widget . state ) ) ;
2023-03-15 20:31:53 +03:00
toolbarItems . add ( _DisplayMenu (
2023-02-23 09:30:29 +03:00
id: widget . id ,
ffi: widget . ffi ,
state: widget . state ,
setFullscreen: _setFullscreen ,
) ) ;
2023-03-17 06:27:22 +03:00
toolbarItems . add ( _KeyboardMenu ( id: widget . id , ffi: widget . ffi ) ) ;
2022-08-26 18:28:08 +03:00
if ( ! isWeb ) {
2023-03-15 20:31:53 +03:00
toolbarItems . add ( _ChatMenu ( id: widget . id , ffi: widget . ffi ) ) ;
toolbarItems . add ( _VoiceCallMenu ( id: widget . id , ffi: widget . ffi ) ) ;
2022-08-26 18:28:08 +03:00
}
2023-06-29 16:26:25 +03:00
toolbarItems . add ( _RecordMenu ( ffi: widget . ffi ) ) ;
2023-03-15 20:31:53 +03:00
toolbarItems . add ( _CloseMenu ( id: widget . id , ffi: widget . ffi ) ) ;
2023-02-23 09:30:29 +03:00
return Column (
mainAxisSize: MainAxisSize . min ,
children: [
2023-03-12 05:48:54 +03:00
Material (
2023-06-11 11:32:22 +03:00
elevation: _ToolbarTheme . elevation ,
2023-03-15 11:57:24 +03:00
shadowColor: MyTheme . color ( context ) . shadow ,
2023-03-12 05:48:54 +03:00
borderRadius: BorderRadius . all ( Radius . circular ( 4.0 ) ) ,
color: Theme . of ( context )
. menuBarTheme
. style
? . backgroundColor
? . resolve ( MaterialState . values . toSet ( ) ) ,
2023-02-23 09:30:29 +03:00
child: SingleChildScrollView (
2023-03-01 18:35:51 +03:00
scrollDirection: Axis . horizontal ,
child: Theme (
data: themeData ( ) ,
2023-03-10 08:54:23 +03:00
child: Row (
2023-03-01 18:35:51 +03:00
children: [
2023-06-11 11:32:22 +03:00
SizedBox ( width: _ToolbarTheme . buttonHMargin * 2 ) ,
2023-03-15 20:31:53 +03:00
. . . toolbarItems ,
2023-06-11 11:32:22 +03:00
SizedBox ( width: _ToolbarTheme . buttonHMargin * 2 )
2023-03-01 18:35:51 +03:00
] ,
) ,
) ,
) ,
2023-02-23 09:30:29 +03:00
) ,
_buildDraggableShowHide ( context ) ,
] ,
2023-02-14 15:57:33 +03:00
) ;
2022-08-26 18:28:08 +03:00
}
2023-02-23 16:31:00 +03:00
ThemeData themeData ( ) {
return Theme . of ( context ) . copyWith (
menuButtonTheme: MenuButtonThemeData (
2023-03-01 18:35:51 +03:00
style: ButtonStyle (
2023-04-12 04:41:13 +03:00
minimumSize: MaterialStatePropertyAll ( Size ( 64 , 32 ) ) ,
2023-03-01 18:35:51 +03:00
textStyle: MaterialStatePropertyAll (
TextStyle ( fontWeight: FontWeight . normal ) ,
) ,
) ,
) ,
2023-02-23 16:31:00 +03:00
dividerTheme: DividerThemeData ( space: 4 ) ,
2023-03-10 08:54:23 +03:00
menuBarTheme: MenuBarThemeData (
2023-03-11 14:29:26 +03:00
style: MenuStyle (
padding: MaterialStatePropertyAll ( EdgeInsets . zero ) ,
elevation: MaterialStatePropertyAll ( 0 ) ,
shape: MaterialStatePropertyAll ( BeveledRectangleBorder ( ) ) ,
) . copyWith (
backgroundColor:
Theme . of ( context ) . menuBarTheme . style ? . backgroundColor ) ) ,
2023-02-23 16:31:00 +03:00
) ;
}
2023-02-23 09:30:29 +03:00
}
class _PinMenu extends StatelessWidget {
2023-06-11 11:32:22 +03:00
final ToolbarState state ;
2023-02-23 09:30:29 +03:00
const _PinMenu ( { Key ? key , required this . state } ) : super ( key: key ) ;
2022-08-26 18:28:08 +03:00
2023-02-23 09:30:29 +03:00
@ override
Widget build ( BuildContext context ) {
2023-02-14 15:57:33 +03:00
return Obx (
2023-02-23 09:30:29 +03:00
( ) = > _IconMenuButton (
assetName: state . pin ? " assets/pinned.svg " : " assets/unpinned.svg " ,
2023-06-11 11:32:22 +03:00
tooltip: state . pin ? ' Unpin Toolbar ' : ' Pin Toolbar ' ,
2023-02-23 09:30:29 +03:00
onPressed: state . switchPin ,
2023-06-11 11:32:22 +03:00
color: state . pin ? _ToolbarTheme . blueColor : Colors . grey [ 800 ] ! ,
2023-02-23 09:30:29 +03:00
hoverColor:
2023-06-11 11:32:22 +03:00
state . pin ? _ToolbarTheme . hoverBlueColor : Colors . grey [ 850 ] ! ,
2023-02-14 15:57:33 +03:00
) ,
) ;
2022-09-13 16:59:06 +03:00
}
2023-02-23 09:30:29 +03:00
}
2022-09-13 16:59:06 +03:00
2023-02-23 09:30:29 +03:00
class _FullscreenMenu extends StatelessWidget {
2023-06-11 11:32:22 +03:00
final ToolbarState state ;
2023-02-23 09:30:29 +03:00
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 ) ,
2023-06-11 11:32:22 +03:00
color: _ToolbarTheme . blueColor ,
hoverColor: _ToolbarTheme . hoverBlueColor ,
2022-08-26 18:28:08 +03:00
) ;
}
2023-02-23 09:30:29 +03:00
}
2022-08-26 18:28:08 +03:00
2023-02-23 09:30:29 +03:00
class _MobileActionMenu extends StatelessWidget {
final FFI ffi ;
const _MobileActionMenu ( { Key ? key , required this . ffi } ) : super ( key: key ) ;
2022-08-26 18:28:08 +03:00
2023-02-23 09:30:29 +03:00
@ override
Widget build ( BuildContext context ) {
if ( ! ffi . ffiModel . isPeerAndroid ) return Offstage ( ) ;
return _IconMenuButton (
assetName: ' assets/actions_mobile.svg ' ,
tooltip: ' Mobile Actions ' ,
onPressed: ( ) = > ffi . dialogManager . toggleMobileActionsOverlay ( ffi: ffi ) ,
2023-06-11 11:32:22 +03:00
color: _ToolbarTheme . blueColor ,
hoverColor: _ToolbarTheme . hoverBlueColor ,
2022-08-26 18:28:08 +03:00
) ;
}
2023-02-23 09:30:29 +03:00
}
2022-08-26 18:28:08 +03:00
2023-02-23 09:30:29 +03:00
class _MonitorMenu extends StatelessWidget {
final String id ;
final FFI ffi ;
const _MonitorMenu ( { Key ? key , required this . id , required this . ffi } )
: super ( key: key ) ;
2022-08-26 18:28:08 +03:00
2023-02-23 09:30:29 +03:00
@ override
Widget build ( BuildContext context ) {
return _IconSubmenuButton (
2023-03-10 09:16:18 +03:00
tooltip: ' Select Monitor ' ,
2023-02-23 09:30:29 +03:00
icon: icon ( ) ,
ffi: ffi ,
2023-06-11 11:32:22 +03:00
color: _ToolbarTheme . blueColor ,
hoverColor: _ToolbarTheme . hoverBlueColor ,
2023-02-23 09:30:29 +03:00
menuStyle: MenuStyle (
padding:
MaterialStatePropertyAll ( EdgeInsets . symmetric ( horizontal: 6 ) ) ) ,
menuChildren: [ Row ( children: displays ( context ) ) ] ) ;
}
2023-02-21 13:43:43 +03:00
2023-02-23 09:30:29 +03:00
icon ( ) {
final pi = ffi . ffiModel . pi ;
return Stack (
alignment: Alignment . center ,
children: [
SvgPicture . asset (
2023-03-15 19:57:52 +03:00
" assets/screen.svg " ,
2023-02-23 09:30:29 +03:00
color: Colors . white ,
) ,
2023-03-15 19:57:52 +03:00
Obx ( ( ) {
RxInt display = CurrentDisplayState . find ( id ) ;
return Text (
' ${ display . value + 1 } / ${ pi . displays . length } ' ,
style: const TextStyle (
2023-06-11 11:32:22 +03:00
color: _ToolbarTheme . blueColor ,
2023-03-15 19:57:52 +03:00
fontSize: 8 ,
fontWeight: FontWeight . bold ,
) ,
) ;
} ) ,
2023-02-23 09:30:29 +03:00
] ,
2022-09-05 17:18:29 +03:00
) ;
}
2023-02-23 09:30:29 +03:00
List < Widget > displays ( BuildContext context ) {
final List < Widget > rowChildren = [ ] ;
final pi = ffi . ffiModel . pi ;
for ( int i = 0 ; i < pi . displays . length ; i + + ) {
rowChildren . add ( _IconMenuButton (
2023-03-10 08:54:23 +03:00
topLevel: false ,
2023-06-11 11:32:22 +03:00
color: _ToolbarTheme . blueColor ,
hoverColor: _ToolbarTheme . hoverBlueColor ,
2023-02-23 09:30:29 +03:00
tooltip: " " ,
hMargin: 6 ,
vMargin: 12 ,
icon: Container (
alignment: AlignmentDirectional . center ,
2023-06-11 11:32:22 +03:00
constraints: const BoxConstraints ( minHeight: _ToolbarTheme . height ) ,
2023-02-23 09:30:29 +03:00
child: Stack (
alignment: Alignment . center ,
children: [
SvgPicture . asset (
2023-03-15 19:57:52 +03:00
" assets/screen.svg " ,
2023-02-23 09:30:29 +03:00
color: Colors . white ,
) ,
2023-03-15 19:57:52 +03:00
Text (
( i + 1 ) . toString ( ) ,
style: TextStyle (
2023-06-11 11:32:22 +03:00
color: _ToolbarTheme . blueColor ,
2023-03-15 19:57:52 +03:00
fontSize: 12 ,
fontWeight: FontWeight . bold ,
2023-02-23 09:30:29 +03:00
) ,
2023-03-15 19:57:52 +03:00
) ,
2023-02-23 09:30:29 +03:00
] ,
2023-02-14 15:57:33 +03:00
) ,
2023-02-23 09:30:29 +03:00
) ,
onPressed: ( ) {
_menuDismissCallback ( ffi ) ;
RxInt display = CurrentDisplayState . find ( id ) ;
if ( display . value ! = i ) {
2023-06-06 02:39:44 +03:00
bind . sessionSwitchDisplay ( sessionId: ffi . sessionId , value: i ) ;
2023-02-23 09:30:29 +03:00
}
} ,
) ) ;
}
return rowChildren ;
2022-09-15 12:31:28 +03:00
}
2023-02-23 09:30:29 +03:00
}
2022-09-15 12:31:28 +03:00
2023-02-23 09:30:29 +03:00
class _ControlMenu extends StatelessWidget {
final String id ;
final FFI ffi ;
2023-06-11 11:32:22 +03:00
final ToolbarState state ;
2023-02-23 09:30:29 +03:00
_ControlMenu (
{ Key ? key , required this . id , required this . ffi , required this . state } )
: super ( key: key ) ;
@ override
Widget build ( BuildContext context ) {
return _IconSubmenuButton (
2023-03-10 09:16:18 +03:00
tooltip: ' Control Actions ' ,
2023-02-23 09:30:29 +03:00
svg: " assets/actions.svg " ,
2023-06-11 11:32:22 +03:00
color: _ToolbarTheme . blueColor ,
hoverColor: _ToolbarTheme . hoverBlueColor ,
2023-02-23 09:30:29 +03:00
ffi: ffi ,
2023-04-12 04:41:13 +03:00
menuChildren: toolbarControls ( context , id , ffi ) . map ( ( e ) {
if ( e . divider ) {
return Divider ( ) ;
2023-02-23 09:30:29 +03:00
} else {
2023-04-20 15:57:47 +03:00
return MenuButton (
2023-04-12 04:41:13 +03:00
child: e . child ,
onPressed: e . onPressed ,
ffi: ffi ,
trailingIcon: e . trailingIcon ) ;
2023-02-08 16:29:51 +03:00
}
2023-04-12 04:41:13 +03:00
} ) . toList ( ) ) ;
2023-02-23 09:30:29 +03:00
}
}
2022-10-09 14:27:30 +03:00
2023-05-18 11:17:51 +03:00
class ScreenAdjustor {
2023-02-23 09:30:29 +03:00
final String id ;
final FFI ffi ;
2023-05-18 11:17:51 +03:00
final VoidCallback cbExitFullscreen ;
2023-02-23 09:30:29 +03:00
window_size . Screen ? _screen ;
2023-05-18 11:17:51 +03:00
ScreenAdjustor ( {
required this . id ,
required this . ffi ,
required this . cbExitFullscreen ,
} ) ;
2023-02-23 09:30:29 +03:00
2023-05-18 11:17:51 +03:00
bool get isFullscreen = > stateGlobal . fullscreen ;
2023-02-23 09:30:29 +03:00
int get windowId = > stateGlobal . windowId ;
adjustWindow ( ) {
2023-04-12 04:41:13 +03:00
return futureBuilder (
2023-05-18 11:17:51 +03:00
future: isWindowCanBeAdjusted ( ) ,
2023-04-12 04:41:13 +03:00
hasData: ( data ) {
final visible = data as bool ;
if ( ! visible ) return Offstage ( ) ;
return Column (
children: [
2023-04-20 15:57:47 +03:00
MenuButton (
2023-04-12 04:41:13 +03:00
child: Text ( translate ( ' Adjust Window ' ) ) ,
2023-05-18 11:17:51 +03:00
onPressed: doAdjustWindow ,
ffi: ffi ) ,
2023-04-12 04:41:13 +03:00
Divider ( ) ,
] ,
) ;
} ) ;
2023-02-23 09:30:29 +03:00
}
2023-05-18 11:17:51 +03:00
doAdjustWindow ( ) async {
await updateScreen ( ) ;
2023-02-23 09:30:29 +03:00
if ( _screen ! = null ) {
2023-05-18 11:17:51 +03:00
cbExitFullscreen ( ) ;
2023-02-23 09:30:29 +03:00
double scale = _screen ! . scaleFactor ;
final wndRect = await WindowController . fromWindowId ( windowId ) . getFrame ( ) ;
final mediaSize = MediaQueryData . fromWindow ( ui . window ) . 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 ;
2023-05-18 11:17:51 +03:00
final canvasModel = ffi . canvasModel ;
2023-02-23 09:30:29 +03:00
final width = ( canvasModel . getDisplayWidth ( ) * canvasModel . scale +
2023-03-01 20:00:56 +03:00
CanvasModel . leftToEdge +
CanvasModel . rightToEdge ) *
2023-02-23 09:30:29 +03:00
scale +
magicWidth ;
final height = ( canvasModel . getDisplayHeight ( ) * canvasModel . scale +
2023-03-01 20:00:56 +03:00
CanvasModel . topToEdge +
CanvasModel . bottomToEdge ) *
2023-02-23 09:30:29 +03:00
scale +
magicHeight ;
double left = wndRect . left + ( wndRect . width - width ) / 2 ;
double top = wndRect . top + ( wndRect . height - height ) / 2 ;
Rect frameRect = _screen ! . frame ;
if ( ! isFullscreen ) {
frameRect = _screen ! . visibleFrame ;
}
if ( left < frameRect . left ) {
left = frameRect . left ;
}
if ( top < frameRect . top ) {
top = frameRect . top ;
}
if ( ( left + width ) > frameRect . right ) {
left = frameRect . right - width ;
}
if ( ( top + height ) > frameRect . bottom ) {
top = frameRect . bottom - height ;
}
await WindowController . fromWindowId ( windowId )
. setFrame ( Rect . fromLTWH ( left , top , width , height ) ) ;
}
}
2023-05-18 11:17:51 +03:00
updateScreen ( ) async {
2023-02-23 09:30:29 +03:00
final v = await rustDeskWinManager . call (
WindowType . Main , kWindowGetWindowInfo , ' ' ) ;
final String valueStr = v ;
if ( valueStr . isEmpty ) {
_screen = null ;
} else {
final screenMap = jsonDecode ( valueStr ) ;
_screen = window_size . Screen (
Rect . fromLTRB ( screenMap [ ' frame ' ] [ ' l ' ] , screenMap [ ' frame ' ] [ ' t ' ] ,
screenMap [ ' frame ' ] [ ' r ' ] , screenMap [ ' frame ' ] [ ' b ' ] ) ,
Rect . fromLTRB (
screenMap [ ' visibleFrame ' ] [ ' l ' ] ,
screenMap [ ' visibleFrame ' ] [ ' t ' ] ,
screenMap [ ' visibleFrame ' ] [ ' r ' ] ,
screenMap [ ' visibleFrame ' ] [ ' b ' ] ) ,
screenMap [ ' scaleFactor ' ] ) ;
}
}
2023-05-18 11:17:51 +03:00
Future < bool > isWindowCanBeAdjusted ( ) async {
2023-06-06 02:39:44 +03:00
final viewStyle =
await bind . sessionGetViewStyle ( sessionId: ffi . sessionId ) ? ? ' ' ;
2023-04-12 04:41:13 +03:00
if ( viewStyle ! = kRemoteViewStyleOriginal ) {
2023-02-23 09:30:29 +03:00
return false ;
}
final remoteCount = RemoteCountState . find ( ) . value ;
if ( remoteCount ! = 1 ) {
return false ;
}
if ( _screen = = null ) {
return false ;
}
final scale = kIgnoreDpi ? 1.0 : _screen ! . scaleFactor ;
double selfWidth = _screen ! . visibleFrame . width ;
double selfHeight = _screen ! . visibleFrame . height ;
if ( isFullscreen ) {
selfWidth = _screen ! . frame . width ;
selfHeight = _screen ! . frame . height ;
}
2023-05-18 11:17:51 +03:00
final canvasModel = ffi . canvasModel ;
2022-10-09 14:27:30 +03:00
final displayWidth = canvasModel . getDisplayWidth ( ) ;
final displayHeight = canvasModel . getDisplayHeight ( ) ;
2023-03-01 20:00:56 +03:00
final requiredWidth =
CanvasModel . leftToEdge + displayWidth + CanvasModel . rightToEdge ;
final requiredHeight =
CanvasModel . topToEdge + displayHeight + CanvasModel . bottomToEdge ;
2022-10-09 14:27:30 +03:00
return selfWidth > ( requiredWidth * scale ) & &
selfHeight > ( requiredHeight * scale ) ;
2022-10-08 12:27:30 +03:00
}
2023-05-18 11:17:51 +03:00
}
class _DisplayMenu extends StatefulWidget {
final String id ;
final FFI ffi ;
2023-06-11 11:32:22 +03:00
final ToolbarState state ;
2023-05-18 11:17:51 +03:00
final Function ( bool ) setFullscreen ;
final Widget pluginItem ;
_DisplayMenu (
{ Key ? key ,
required this . id ,
required this . ffi ,
required this . state ,
required this . setFullscreen } )
: pluginItem = LocationItem . createLocationItem (
id ,
ffi ,
kLocationClientRemoteToolbarDisplay ,
true ,
) ,
super ( key: key ) ;
@ override
State < _DisplayMenu > createState ( ) = > _DisplayMenuState ( ) ;
}
class _DisplayMenuState extends State < _DisplayMenu > {
late final ScreenAdjustor _screenAdjustor = ScreenAdjustor (
id: widget . id ,
ffi: widget . ffi ,
cbExitFullscreen: ( ) = > widget . setFullscreen ( false ) ,
) ;
bool get isFullscreen = > stateGlobal . fullscreen ;
int get windowId = > stateGlobal . windowId ;
Map < String , bool > get perms = > widget . ffi . ffiModel . permissions ;
PeerInfo get pi = > widget . ffi . ffiModel . pi ;
FfiModel get ffiModel = > widget . ffi . ffiModel ;
FFI get ffi = > widget . ffi ;
String get id = > widget . id ;
@ override
void initState ( ) {
super . initState ( ) ;
}
@ override
Widget build ( BuildContext context ) {
_screenAdjustor . updateScreen ( ) ;
return _IconSubmenuButton (
tooltip: ' Display Settings ' ,
svg: " assets/display.svg " ,
ffi: widget . ffi ,
2023-06-11 11:32:22 +03:00
color: _ToolbarTheme . blueColor ,
hoverColor: _ToolbarTheme . hoverBlueColor ,
2023-05-18 11:17:51 +03:00
menuChildren: [
_screenAdjustor . adjustWindow ( ) ,
viewStyle ( ) ,
scrollStyle ( ) ,
imageQuality ( ) ,
codec ( ) ,
_ResolutionsMenu (
id: widget . id ,
ffi: widget . ffi ,
screenAdjustor: _screenAdjustor ,
) ,
Divider ( ) ,
toggles ( ) ,
widget . pluginItem ,
] ) ;
}
2022-10-08 12:27:30 +03:00
2023-02-23 09:30:29 +03:00
viewStyle ( ) {
2023-04-12 04:41:13 +03:00
return futureBuilder (
future: toolbarViewStyle ( context , widget . id , widget . ffi ) ,
hasData: ( data ) {
final v = data as List < TRadioMenu < String > > ;
return Column ( children: [
. . . v
2023-04-20 15:57:47 +03:00
. map ( ( e ) = > RdoMenuButton < String > (
2023-04-12 04:41:13 +03:00
value: e . value ,
groupValue: e . groupValue ,
onChanged: e . onChanged ,
child: e . child ,
ffi: ffi ) )
. toList ( ) ,
Divider ( ) ,
] ) ;
} ) ;
2023-02-23 09:30:29 +03:00
}
scrollStyle ( ) {
return futureBuilder ( future: ( ) async {
2023-06-06 02:39:44 +03:00
final viewStyle =
await bind . sessionGetViewStyle ( sessionId: ffi . sessionId ) ? ? ' ' ;
2023-04-12 04:41:13 +03:00
final visible = viewStyle = = kRemoteViewStyleOriginal ;
2023-06-06 02:39:44 +03:00
final scrollStyle =
await bind . sessionGetScrollStyle ( sessionId: ffi . sessionId ) ? ? ' ' ;
2023-04-12 04:41:13 +03:00
return { ' visible ' : visible , ' scrollStyle ' : scrollStyle } ;
2023-02-23 09:30:29 +03:00
} ( ) , hasData: ( data ) {
2023-04-12 04:41:13 +03:00
final visible = data [ ' visible ' ] as bool ;
if ( ! visible ) return Offstage ( ) ;
final groupValue = data [ ' scrollStyle ' ] as String ;
2023-02-23 09:30:29 +03:00
onChange ( String ? value ) async {
if ( value = = null ) return ;
2023-06-06 02:39:44 +03:00
await bind . sessionSetScrollStyle (
sessionId: ffi . sessionId , value: value ) ;
2023-02-23 09:30:29 +03:00
widget . ffi . canvasModel . updateScrollStyle ( ) ;
}
final enabled = widget . ffi . canvasModel . imageOverflow . value ;
return Column ( children: [
2023-04-20 15:57:47 +03:00
RdoMenuButton < String > (
2023-02-23 09:30:29 +03:00
child: Text ( translate ( ' ScrollAuto ' ) ) ,
value: kRemoteScrollStyleAuto ,
groupValue: groupValue ,
onChanged: enabled ? ( value ) = > onChange ( value ) : null ,
ffi: widget . ffi ,
) ,
2023-04-20 15:57:47 +03:00
RdoMenuButton < String > (
2023-02-23 09:30:29 +03:00
child: Text ( translate ( ' Scrollbar ' ) ) ,
value: kRemoteScrollStyleBar ,
groupValue: groupValue ,
onChanged: enabled ? ( value ) = > onChange ( value ) : null ,
ffi: widget . ffi ,
) ,
Divider ( ) ,
] ) ;
} ) ;
}
imageQuality ( ) {
2023-04-12 04:41:13 +03:00
return futureBuilder (
future: toolbarImageQuality ( context , widget . id , widget . ffi ) ,
hasData: ( data ) {
final v = data as List < TRadioMenu < String > > ;
return _SubmenuButton (
2023-02-23 09:30:29 +03:00
ffi: widget . ffi ,
2023-04-12 04:41:13 +03:00
child: Text ( translate ( ' Image Quality ' ) ) ,
menuChildren: v
2023-04-20 15:57:47 +03:00
. map ( ( e ) = > RdoMenuButton < String > (
2023-04-12 04:41:13 +03:00
value: e . value ,
groupValue: e . groupValue ,
onChanged: e . onChanged ,
child: e . child ,
ffi: ffi ) )
. toList ( ) ,
) ;
} ) ;
2023-02-23 09:30:29 +03:00
}
2022-10-08 12:27:30 +03:00
2023-02-23 09:30:29 +03:00
codec ( ) {
2023-04-12 04:41:13 +03:00
return futureBuilder (
future: toolbarCodec ( context , id , ffi ) ,
hasData: ( data ) {
final v = data as List < TRadioMenu < String > > ;
if ( v . isEmpty ) return Offstage ( ) ;
2022-09-16 14:43:28 +03:00
2023-04-12 04:41:13 +03:00
return _SubmenuButton (
2023-02-23 09:30:29 +03:00
ffi: widget . ffi ,
2023-04-12 04:41:13 +03:00
child: Text ( translate ( ' Codec ' ) ) ,
menuChildren: v
2023-04-20 15:57:47 +03:00
. map ( ( e ) = > RdoMenuButton (
2023-04-12 04:41:13 +03:00
value: e . value ,
groupValue: e . groupValue ,
onChanged: e . onChanged ,
child: e . child ,
ffi: ffi ) )
. toList ( ) ) ;
} ) ;
2023-02-23 09:30:29 +03:00
}
2023-04-12 04:41:13 +03:00
toggles ( ) {
return futureBuilder (
future: toolbarDisplayToggle ( context , id , ffi ) ,
hasData: ( data ) {
final v = data as List < TToggleMenu > ;
if ( v . isEmpty ) return Offstage ( ) ;
return Column (
children: v
2023-04-20 15:57:47 +03:00
. map ( ( e ) = > CkbMenuButton (
2023-04-12 04:41:13 +03:00
value: e . value ,
onChanged: e . onChanged ,
child: e . child ,
ffi: ffi ) )
. toList ( ) ) ;
} ) ;
2023-03-01 20:00:56 +03:00
}
2022-08-26 18:28:08 +03:00
}
2023-05-18 11:17:51 +03:00
class _ResolutionsMenu extends StatefulWidget {
final String id ;
final FFI ffi ;
final ScreenAdjustor screenAdjustor ;
_ResolutionsMenu ( {
Key ? key ,
required this . id ,
required this . ffi ,
required this . screenAdjustor ,
} ) : super ( key: key ) ;
@ override
State < _ResolutionsMenu > createState ( ) = > _ResolutionsMenuState ( ) ;
}
2023-06-05 13:01:43 +03:00
const double _kCustomResolutionEditingWidth = 42 ;
2023-05-19 12:34:39 +03:00
const _kCustomResolutionValue = ' custom ' ;
2023-05-18 11:17:51 +03:00
class _ResolutionsMenuState extends State < _ResolutionsMenu > {
String _groupValue = ' ' ;
Resolution ? _localResolution ;
2023-05-19 12:34:39 +03:00
late final TextEditingController _customWidth =
TextEditingController ( text: display . width . toString ( ) ) ;
late final TextEditingController _customHeight =
TextEditingController ( text: display . height . toString ( ) ) ;
2023-06-06 02:39:44 +03:00
FFI get ffi = > widget . ffi ;
2023-05-18 11:17:51 +03:00
PeerInfo get pi = > widget . ffi . ffiModel . pi ;
FfiModel get ffiModel = > widget . ffi . ffiModel ;
Display get display = > ffiModel . display ;
List < Resolution > get resolutions = > pi . resolutions ;
@ override
void initState ( ) {
super . initState ( ) ;
}
@ override
Widget build ( BuildContext context ) {
2023-05-18 16:25:48 +03:00
final isVirtualDisplay = display . isVirtualDisplayResolution ;
2023-05-19 12:34:39 +03:00
final visible =
ffiModel . keyboard & & ( isVirtualDisplay | | resolutions . length > 1 ) ;
2023-05-18 11:17:51 +03:00
if ( ! visible ) return Offstage ( ) ;
_getLocalResolution ( ) ;
2023-05-18 16:25:48 +03:00
final showOriginalBtn =
display . isOriginalResolutionSet & & ! display . isOriginalResolution ;
final showFitLocalBtn = ! _isRemoteResolutionFitLocal ( ) ;
2023-05-19 12:34:39 +03:00
_setGroupValue ( ) ;
2023-05-18 11:17:51 +03:00
return _SubmenuButton (
2023-05-18 18:57:48 +03:00
ffi: widget . ffi ,
menuChildren: < Widget > [
_OriginalResolutionMenuButton ( showOriginalBtn ) ,
_FitLocalResolutionMenuButton ( showFitLocalBtn ) ,
2023-05-19 12:34:39 +03:00
_customResolutionMenuButton ( isVirtualDisplay ) ,
2023-05-18 18:57:48 +03:00
_menuDivider ( showOriginalBtn , showFitLocalBtn , isVirtualDisplay ) ,
] +
_supportedResolutionMenuButtons ( ) ,
child: Text ( translate ( " Resolution " ) ) ,
) ;
2023-05-18 11:17:51 +03:00
}
2023-05-19 12:34:39 +03:00
_setGroupValue ( ) {
2023-05-19 15:48:47 +03:00
final lastGroupValue =
stateGlobal . getLastResolutionGroupValue ( widget . id , pi . currentDisplay ) ;
if ( lastGroupValue = = _kCustomResolutionValue ) {
2023-05-19 12:34:39 +03:00
_groupValue = _kCustomResolutionValue ;
} else {
_groupValue = ' ${ display . width } x ${ display . height } ' ;
}
}
2023-05-18 18:45:38 +03:00
_menuDivider (
bool showOriginalBtn , bool showFitLocalBtn , bool isVirtualDisplay ) {
return Offstage (
2023-05-19 15:48:47 +03:00
offstage: ! ( showOriginalBtn | | showFitLocalBtn | | isVirtualDisplay ) ,
2023-05-18 18:45:38 +03:00
child: Divider ( ) ,
) ;
}
2023-05-18 11:17:51 +03:00
_getLocalResolution ( ) {
_localResolution = null ;
final String currentDisplay = bind . mainGetCurrentDisplay ( ) ;
if ( currentDisplay . isNotEmpty ) {
try {
final display = json . decode ( currentDisplay ) ;
if ( display [ ' w ' ] ! = null & & display [ ' h ' ] ! = null ) {
_localResolution = Resolution ( display [ ' w ' ] , display [ ' h ' ] ) ;
}
} catch ( e ) {
debugPrint ( ' Failed to decode $ currentDisplay , $ e ' ) ;
}
}
}
_onChanged ( String ? value ) async {
2023-05-19 15:48:47 +03:00
stateGlobal . setLastResolutionGroupValue (
widget . id , pi . currentDisplay , value ) ;
2023-05-18 11:17:51 +03:00
if ( value = = null ) return ;
2023-05-19 12:34:39 +03:00
int ? w ;
int ? h ;
if ( value = = _kCustomResolutionValue ) {
w = int . tryParse ( _customWidth . text ) ;
h = int . tryParse ( _customHeight . text ) ;
} else {
final list = value . split ( ' x ' ) ;
if ( list . length = = 2 ) {
w = int . tryParse ( list [ 0 ] ) ;
h = int . tryParse ( list [ 1 ] ) ;
}
}
if ( w ! = null & & h ! = null ) {
2023-05-19 15:48:47 +03:00
if ( w ! = display . width | | h ! = display . height ) {
2023-05-18 18:45:38 +03:00
await _changeResolution ( w , h ) ;
2023-05-18 11:17:51 +03:00
}
}
2023-05-18 18:45:38 +03:00
}
2023-05-18 11:17:51 +03:00
2023-05-18 18:45:38 +03:00
_changeResolution ( int w , int h ) async {
await bind . sessionChangeResolution (
2023-06-06 02:39:44 +03:00
sessionId: ffi . sessionId ,
2023-06-05 13:01:43 +03:00
display: pi . currentDisplay ,
2023-05-18 18:45:38 +03:00
width: w ,
height: h ,
) ;
Future . delayed ( Duration ( seconds: 3 ) , ( ) async {
final display = ffiModel . display ;
if ( w = = display . width & & h = = display . height ) {
if ( await widget . screenAdjustor . isWindowCanBeAdjusted ( ) ) {
widget . screenAdjustor . doAdjustWindow ( ) ;
2023-05-18 11:17:51 +03:00
}
2023-05-18 18:45:38 +03:00
}
} ) ;
2023-05-18 11:17:51 +03:00
}
2023-05-18 16:25:48 +03:00
Widget _OriginalResolutionMenuButton ( bool showOriginalBtn ) {
2023-05-18 11:17:51 +03:00
return Offstage (
2023-05-18 16:25:48 +03:00
offstage: ! showOriginalBtn ,
2023-05-18 18:45:38 +03:00
child: MenuButton (
onPressed: ( ) = >
_changeResolution ( display . originalWidth , display . originalHeight ) ,
2023-05-18 11:17:51 +03:00
ffi: widget . ffi ,
child: Text (
2023-05-18 18:57:48 +03:00
' ${ translate ( ' resolution_original_tip ' ) } ${ display . originalWidth } x ${ display . originalHeight } ' ) ,
2023-05-18 11:17:51 +03:00
) ,
) ;
}
2023-05-18 16:25:48 +03:00
Widget _FitLocalResolutionMenuButton ( bool showFitLocalBtn ) {
2023-05-18 11:17:51 +03:00
return Offstage (
2023-05-18 16:25:48 +03:00
offstage: ! showFitLocalBtn ,
2023-05-18 18:45:38 +03:00
child: MenuButton (
onPressed: ( ) {
final resolution = _getBestFitResolution ( ) ;
if ( resolution ! = null ) {
_changeResolution ( resolution . width , resolution . height ) ;
}
} ,
2023-05-18 11:17:51 +03:00
ffi: widget . ffi ,
child: Text (
2023-05-18 18:57:48 +03:00
' ${ translate ( ' resolution_fit_local_tip ' ) } ${ _localResolution ? . width ? ? 0 } x ${ _localResolution ? . height ? ? 0 } ' ) ,
2023-05-18 11:17:51 +03:00
) ,
) ;
}
2023-05-19 12:34:39 +03:00
Widget _customResolutionMenuButton ( isVirtualDisplay ) {
2023-05-19 15:48:47 +03:00
return Offstage (
offstage: ! isVirtualDisplay ,
child: RdoMenuButton (
value: _kCustomResolutionValue ,
groupValue: _groupValue ,
onChanged: _onChanged ,
ffi: widget . ffi ,
child: Row (
children: [
Text ( ' ${ translate ( ' resolution_custom_tip ' ) } ' ) ,
SizedBox (
2023-06-05 13:01:43 +03:00
width: _kCustomResolutionEditingWidth ,
2023-05-19 15:48:47 +03:00
child: _resolutionInput ( _customWidth ) ,
) ,
Text ( ' x ' ) ,
SizedBox (
2023-06-05 13:01:43 +03:00
width: _kCustomResolutionEditingWidth ,
2023-05-19 15:48:47 +03:00
child: _resolutionInput ( _customHeight ) ,
) ,
] ,
) ,
2023-05-19 12:34:39 +03:00
) ,
) ;
}
TextField _resolutionInput ( TextEditingController controller ) {
return TextField (
decoration: InputDecoration (
border: InputBorder . none ,
isDense: true ,
contentPadding: EdgeInsets . fromLTRB ( 3 , 3 , 3 , 3 ) ,
) ,
keyboardType: TextInputType . number ,
inputFormatters: < TextInputFormatter > [
FilteringTextInputFormatter . digitsOnly ,
LengthLimitingTextInputFormatter ( 4 ) ,
FilteringTextInputFormatter . allow ( RegExp ( r'[0-9]' ) ) ,
] ,
controller: controller ,
) ;
}
2023-05-18 11:17:51 +03:00
List < Widget > _supportedResolutionMenuButtons ( ) = > resolutions
. map ( ( e ) = > RdoMenuButton (
value: ' ${ e . width } x ${ e . height } ' ,
groupValue: _groupValue ,
onChanged: _onChanged ,
ffi: widget . ffi ,
child: Text ( ' ${ e . width } x ${ e . height } ' ) ) )
. toList ( ) ;
Resolution ? _getBestFitResolution ( ) {
if ( _localResolution = = null ) {
return null ;
}
2023-05-18 16:25:48 +03:00
if ( display . isVirtualDisplayResolution ) {
return _localResolution ! ;
}
2023-05-18 11:17:51 +03:00
for ( final r in resolutions ) {
2023-06-29 16:26:25 +03:00
if ( r . width = = _localResolution ! . width & &
r . height = = _localResolution ! . height ) {
2023-06-16 07:48:25 +03:00
return r ;
2023-05-18 11:17:51 +03:00
}
}
2023-06-29 16:26:25 +03:00
2023-06-16 07:48:25 +03:00
return null ;
2023-05-18 11:17:51 +03:00
}
bool _isRemoteResolutionFitLocal ( ) {
if ( _localResolution = = null ) {
return true ;
}
final bestFitResolution = _getBestFitResolution ( ) ;
if ( bestFitResolution = = null ) {
return true ;
}
return bestFitResolution . width = = display . width & &
bestFitResolution . height = = display . height ;
}
}
2023-02-23 09:30:29 +03:00
class _KeyboardMenu extends StatelessWidget {
final String id ;
final FFI ffi ;
_KeyboardMenu ( {
Key ? key ,
required this . id ,
required this . ffi ,
} ) : super ( key: key ) ;
PeerInfo get pi = > ffi . ffiModel . pi ;
@ override
Widget build ( BuildContext context ) {
2023-03-01 20:00:56 +03:00
var ffiModel = Provider . of < FfiModel > ( context ) ;
2023-03-28 05:36:59 +03:00
if ( ! ffiModel . keyboard ) return Offstage ( ) ;
2023-03-28 07:10:58 +03:00
String ? modeOnly ;
2023-02-23 09:30:29 +03:00
if ( stateGlobal . grabKeyboard ) {
2023-06-06 02:39:44 +03:00
if ( bind . sessionIsKeyboardModeSupported (
sessionId: ffi . sessionId , mode: _kKeyMapMode ) ) {
bind . sessionSetKeyboardMode (
sessionId: ffi . sessionId , value: _kKeyMapMode ) ;
2023-03-28 07:10:58 +03:00
modeOnly = _kKeyMapMode ;
2023-02-25 18:11:28 +03:00
} else if ( bind . sessionIsKeyboardModeSupported (
2023-06-06 02:39:44 +03:00
sessionId: ffi . sessionId , mode: _kKeyLegacyMode ) ) {
bind . sessionSetKeyboardMode (
sessionId: ffi . sessionId , value: _kKeyLegacyMode ) ;
2023-03-28 07:10:58 +03:00
modeOnly = _kKeyLegacyMode ;
2023-02-25 17:59:59 +03:00
}
2022-09-03 13:19:50 +03:00
}
2023-02-23 09:30:29 +03:00
return _IconSubmenuButton (
2023-03-10 09:16:18 +03:00
tooltip: ' Keyboard Settings ' ,
2023-02-23 09:30:29 +03:00
svg: " assets/keyboard.svg " ,
ffi: ffi ,
2023-06-11 11:32:22 +03:00
color: _ToolbarTheme . blueColor ,
hoverColor: _ToolbarTheme . hoverBlueColor ,
2023-03-17 06:27:22 +03:00
menuChildren: [
2023-03-28 07:10:58 +03:00
mode ( modeOnly ) ,
2023-03-17 06:27:22 +03:00
localKeyboardType ( ) ,
Divider ( ) ,
view_mode ( ) ,
] ) ;
2023-02-23 09:30:29 +03:00
}
2022-09-03 13:19:50 +03:00
2023-03-28 07:10:58 +03:00
mode ( String ? modeOnly ) {
2023-02-23 09:30:29 +03:00
return futureBuilder ( future: ( ) async {
2023-06-06 02:39:44 +03:00
return await bind . sessionGetKeyboardMode ( sessionId: ffi . sessionId ) ? ?
_kKeyLegacyMode ;
2023-02-23 09:30:29 +03:00
} ( ) , hasData: ( data ) {
final groupValue = data as String ;
List < KeyboardModeMenu > modes = [
2023-02-25 17:59:59 +03:00
KeyboardModeMenu ( key: _kKeyLegacyMode , menu: ' Legacy mode ' ) ,
KeyboardModeMenu ( key: _kKeyMapMode , menu: ' Map mode ' ) ,
KeyboardModeMenu ( key: _kKeyTranslateMode , menu: ' Translate mode ' ) ,
2023-02-23 09:30:29 +03:00
] ;
2023-04-20 15:57:47 +03:00
List < RdoMenuButton > list = [ ] ;
2023-03-17 06:27:22 +03:00
final enabled = ! ffi . ffiModel . viewOnly ;
2023-02-23 09:30:29 +03:00
onChanged ( String ? value ) async {
if ( value = = null ) return ;
2023-06-06 02:39:44 +03:00
await bind . sessionSetKeyboardMode (
sessionId: ffi . sessionId , value: value ) ;
2023-02-23 09:30:29 +03:00
}
for ( KeyboardModeMenu mode in modes ) {
2023-03-28 07:10:58 +03:00
if ( modeOnly ! = null & & mode . key ! = modeOnly ) {
continue ;
} else if ( ! bind . sessionIsKeyboardModeSupported (
2023-06-06 02:39:44 +03:00
sessionId: ffi . sessionId , mode: mode . key ) ) {
2023-03-28 07:10:58 +03:00
continue ;
}
if ( pi . is_wayland & & mode . key ! = _kKeyMapMode ) {
continue ;
2023-02-23 09:30:29 +03:00
}
2023-03-28 07:10:58 +03:00
var text = translate ( mode . menu ) ;
if ( mode . key = = _kKeyTranslateMode ) {
text = ' $ text beta ' ;
}
2023-04-20 15:57:47 +03:00
list . add ( RdoMenuButton < String > (
2023-03-28 07:10:58 +03:00
child: Text ( text ) ,
value: mode . key ,
groupValue: groupValue ,
onChanged: enabled ? onChanged : null ,
ffi: ffi ,
) ) ;
2023-02-23 09:30:29 +03:00
}
return Column ( children: list ) ;
} ) ;
}
localKeyboardType ( ) {
final localPlatform = getLocalPlatformForKBLayoutType ( pi . platform ) ;
final visible = localPlatform ! = ' ' ;
if ( ! visible ) return Offstage ( ) ;
2023-03-17 06:27:22 +03:00
final enabled = ! ffi . ffiModel . viewOnly ;
2023-02-23 09:30:29 +03:00
return Column (
children: [
Divider ( ) ,
2023-04-20 15:57:47 +03:00
MenuButton (
2023-02-23 09:30:29 +03:00
child: Text (
' ${ translate ( ' Local keyboard type ' ) } : ${ KBLayoutType . value } ' ) ,
trailingIcon: const Icon ( Icons . settings ) ,
ffi: ffi ,
2023-03-17 06:27:22 +03:00
onPressed: enabled
? ( ) = > showKBLayoutTypeChooser ( localPlatform , ffi . dialogManager )
: null ,
2023-02-23 09:30:29 +03:00
)
2022-09-03 13:19:50 +03:00
] ,
) ;
2023-02-23 09:30:29 +03:00
}
2023-03-17 06:27:22 +03:00
view_mode ( ) {
final ffiModel = ffi . ffiModel ;
2023-03-28 05:36:59 +03:00
final enabled = version_cmp ( pi . version , ' 1.2.0 ' ) > = 0 & & ffiModel . keyboard ;
2023-04-20 15:57:47 +03:00
return CkbMenuButton (
2023-03-17 06:27:22 +03:00
value: ffiModel . viewOnly ,
onChanged: enabled
? ( value ) async {
if ( value = = null ) return ;
2023-06-06 02:39:44 +03:00
await bind . sessionToggleOption (
sessionId: ffi . sessionId , value: ' view-only ' ) ;
2023-03-17 06:27:22 +03:00
ffiModel . setViewOnly ( id , value ) ;
}
: null ,
ffi: ffi ,
child: Text ( translate ( ' View Mode ' ) ) ) ;
}
2022-08-26 18:28:08 +03:00
}
2022-09-08 10:35:19 +03:00
2023-02-23 09:30:29 +03:00
class _ChatMenu extends StatefulWidget {
final String id ;
final FFI ffi ;
_ChatMenu ( {
Key ? key ,
required this . id ,
required this . ffi ,
} ) : super ( key: key ) ;
2022-09-08 10:35:19 +03:00
2023-02-23 09:30:29 +03:00
@ override
State < _ChatMenu > createState ( ) = > _ChatMenuState ( ) ;
}
class _ChatMenuState extends State < _ChatMenu > {
// Using in StatelessWidget got `Looking up a deactivated widget's ancestor is unsafe`.
final chatButtonKey = GlobalKey ( ) ;
@ override
Widget build ( BuildContext context ) {
return _IconSubmenuButton (
2023-03-10 09:16:18 +03:00
tooltip: ' Chat ' ,
2023-02-23 09:30:29 +03:00
key: chatButtonKey ,
svg: ' assets/chat.svg ' ,
ffi: widget . ffi ,
2023-06-11 11:32:22 +03:00
color: _ToolbarTheme . blueColor ,
hoverColor: _ToolbarTheme . hoverBlueColor ,
2023-02-23 09:30:29 +03:00
menuChildren: [ textChat ( ) , voiceCall ( ) ] ) ;
}
textChat ( ) {
2023-04-20 15:57:47 +03:00
return MenuButton (
2023-02-23 09:30:29 +03:00
child: Text ( translate ( ' Text chat ' ) ) ,
ffi: widget . ffi ,
onPressed: ( ) {
RenderBox ? renderBox =
chatButtonKey . currentContext ? . findRenderObject ( ) as RenderBox ? ;
Offset ? initPos ;
if ( renderBox ! = null ) {
final pos = renderBox . localToGlobal ( Offset . zero ) ;
2023-06-11 11:32:22 +03:00
initPos = Offset ( pos . dx , pos . dy + _ToolbarTheme . dividerHeight ) ;
2022-09-08 10:35:19 +03:00
}
2023-02-23 09:30:29 +03:00
2023-07-09 14:14:57 +03:00
widget . ffi . chatModel . changeCurrentID (
MessageKey ( widget . ffi . id , ChatModel . clientModeID ) ) ;
2023-02-23 09:30:29 +03:00
widget . ffi . chatModel . toggleChatOverlay ( chatInitPos: initPos ) ;
} ) ;
}
voiceCall ( ) {
2023-04-20 15:57:47 +03:00
return MenuButton (
2023-02-23 09:30:29 +03:00
child: Text ( translate ( ' Voice call ' ) ) ,
ffi: widget . ffi ,
2023-06-06 02:39:44 +03:00
onPressed: ( ) = >
bind . sessionRequestVoiceCall ( sessionId: widget . ffi . sessionId ) ,
2023-02-23 09:30:29 +03:00
) ;
}
}
class _VoiceCallMenu extends StatelessWidget {
final String id ;
final FFI ffi ;
_VoiceCallMenu ( {
Key ? key ,
required this . id ,
required this . ffi ,
} ) : super ( key: key ) ;
@ override
Widget build ( BuildContext context ) {
return Obx (
( ) {
final String tooltip ;
final String icon ;
switch ( ffi . chatModel . voiceCallStatus . value ) {
case VoiceCallStatus . waitingForResponse:
tooltip = " Waiting " ;
icon = " assets/call_wait.svg " ;
break ;
case VoiceCallStatus . connected:
tooltip = " Disconnect " ;
icon = " assets/call_end.svg " ;
break ;
default :
return Offstage ( ) ;
2022-09-08 10:35:19 +03:00
}
2023-02-23 09:30:29 +03:00
return _IconMenuButton (
assetName: icon ,
tooltip: tooltip ,
2023-06-06 02:39:44 +03:00
onPressed: ( ) = >
bind . sessionCloseVoiceCall ( sessionId: ffi . sessionId ) ,
2023-06-11 11:32:22 +03:00
color: _ToolbarTheme . redColor ,
hoverColor: _ToolbarTheme . hoverRedColor ) ;
2022-09-08 10:35:19 +03:00
} ,
) ;
2023-02-23 09:30:29 +03:00
}
}
2022-09-08 10:35:19 +03:00
2023-02-23 09:30:29 +03:00
class _RecordMenu extends StatelessWidget {
2023-06-29 16:26:25 +03:00
final FFI ffi ;
const _RecordMenu ( { Key ? key , required this . ffi } ) : super ( key: key ) ;
2023-02-23 09:30:29 +03:00
@ override
Widget build ( BuildContext context ) {
2023-06-29 16:26:25 +03:00
var ffiModel = Provider . of < FfiModel > ( context ) ;
2023-04-12 04:41:13 +03:00
var recordingModel = Provider . of < RecordingModel > ( context ) ;
final visible =
2023-06-29 16:26:25 +03:00
recordingModel . start | | ffiModel . permissions [ ' recording ' ] ! = false ;
2023-02-23 09:30:29 +03:00
if ( ! visible ) return Offstage ( ) ;
2023-06-29 16:26:25 +03:00
final menuButton = _IconMenuButton (
2023-04-12 04:41:13 +03:00
assetName: ' assets/rec.svg ' ,
tooltip: recordingModel . start
? ' Stop session recording '
: ' Start session recording ' ,
onPressed: ( ) = > recordingModel . toggle ( ) ,
color: recordingModel . start
2023-06-11 11:32:22 +03:00
? _ToolbarTheme . redColor
: _ToolbarTheme . blueColor ,
2023-04-12 04:41:13 +03:00
hoverColor: recordingModel . start
2023-06-11 11:32:22 +03:00
? _ToolbarTheme . hoverRedColor
: _ToolbarTheme . hoverBlueColor ,
2023-01-17 08:28:33 +03:00
) ;
2023-06-29 16:26:25 +03:00
return ChangeNotifierProvider . value (
value: ffi . qualityMonitorModel ,
child: Consumer < QualityMonitorModel > (
builder: ( context , model , child ) = > Offstage (
// If already started, AV1->Hidden/Stop, Other->Start, same as actual
offstage: model . data . codecFormat = = ' AV1 ' ,
child: menuButton ,
) ) ) ;
2023-02-23 09:30:29 +03:00
}
2023-01-17 08:28:33 +03:00
}
2023-02-23 09:30:29 +03:00
class _CloseMenu extends StatelessWidget {
final String id ;
final FFI ffi ;
const _CloseMenu ( { Key ? key , required this . id , required this . ffi } )
: super ( key: key ) ;
2023-01-17 08:28:33 +03:00
2023-02-23 09:30:29 +03:00
@ override
Widget build ( BuildContext context ) {
return _IconMenuButton (
assetName: ' assets/close.svg ' ,
tooltip: ' Close ' ,
2023-06-06 02:39:44 +03:00
onPressed: ( ) = > clientClose ( ffi . sessionId , ffi . dialogManager ) ,
2023-06-11 11:32:22 +03:00
color: _ToolbarTheme . redColor ,
hoverColor: _ToolbarTheme . hoverRedColor ,
2023-02-23 09:30:29 +03:00
) ;
}
}
class _IconMenuButton extends StatefulWidget {
final String ? assetName ;
final Widget ? icon ;
2023-03-10 09:16:18 +03:00
final String ? tooltip ;
2023-02-23 09:30:29 +03:00
final Color color ;
final Color hoverColor ;
final VoidCallback ? onPressed ;
final double ? hMargin ;
final double ? vMargin ;
2023-03-10 08:54:23 +03:00
final bool topLevel ;
2023-02-23 09:30:29 +03:00
const _IconMenuButton ( {
Key ? key ,
this . assetName ,
this . icon ,
2023-03-10 09:16:18 +03:00
this . tooltip ,
2023-02-23 09:30:29 +03:00
required this . color ,
required this . hoverColor ,
required this . onPressed ,
this . hMargin ,
this . vMargin ,
2023-03-10 08:54:23 +03:00
this . topLevel = true ,
2023-02-23 09:30:29 +03:00
} ) : super ( key: key ) ;
@ override
State < _IconMenuButton > createState ( ) = > _IconMenuButtonState ( ) ;
}
class _IconMenuButtonState extends State < _IconMenuButton > {
bool hover = false ;
@ override
Widget build ( BuildContext context ) {
assert ( widget . assetName ! = null | | widget . icon ! = null ) ;
final icon = widget . icon ? ?
SvgPicture . asset (
widget . assetName ! ,
color: Colors . white ,
2023-06-11 11:32:22 +03:00
width: _ToolbarTheme . buttonSize ,
height: _ToolbarTheme . buttonSize ,
2023-02-23 09:30:29 +03:00
) ;
2023-03-10 08:54:23 +03:00
final button = SizedBox (
2023-06-11 11:32:22 +03:00
width: _ToolbarTheme . buttonSize ,
height: _ToolbarTheme . buttonSize ,
2023-02-23 09:30:29 +03:00
child: MenuItemButton (
style: ButtonStyle (
2023-03-11 14:29:26 +03:00
backgroundColor: MaterialStatePropertyAll ( Colors . transparent ) ,
2023-02-23 09:30:29 +03:00
padding: MaterialStatePropertyAll ( EdgeInsets . zero ) ,
overlayColor: MaterialStatePropertyAll ( Colors . transparent ) ) ,
onHover: ( value ) = > setState ( ( ) {
hover = value ;
} ) ,
onPressed: widget . onPressed ,
2023-03-10 09:16:18 +03:00
child: Material (
type: MaterialType . transparency ,
child: Ink (
decoration: BoxDecoration (
2023-06-11 11:32:22 +03:00
borderRadius: BorderRadius . circular ( _ToolbarTheme . iconRadius ) ,
2023-03-10 09:16:18 +03:00
color: hover ? widget . hoverColor : widget . color ,
) ,
child: icon ) ) ,
2023-02-23 09:30:29 +03:00
) ,
) . marginSymmetric (
2023-06-11 11:32:22 +03:00
horizontal: widget . hMargin ? ? _ToolbarTheme . buttonHMargin ,
vertical: widget . vMargin ? ? _ToolbarTheme . buttonVMargin ) ;
2023-03-10 08:54:23 +03:00
if ( widget . topLevel ) {
return MenuBar ( children: [ button ] ) ;
} else {
return button ;
}
2023-02-23 09:30:29 +03:00
}
}
class _IconSubmenuButton extends StatefulWidget {
2023-03-10 09:16:18 +03:00
final String tooltip ;
2023-02-23 09:30:29 +03:00
final String ? svg ;
final Widget ? icon ;
final Color color ;
final Color hoverColor ;
final List < Widget > menuChildren ;
final MenuStyle ? menuStyle ;
final FFI ffi ;
_IconSubmenuButton (
{ Key ? key ,
this . svg ,
this . icon ,
2023-03-10 09:16:18 +03:00
required this . tooltip ,
2023-02-23 09:30:29 +03:00
required this . color ,
required this . hoverColor ,
required this . menuChildren ,
required this . ffi ,
this . menuStyle } )
: super ( key: key ) ;
@ override
State < _IconSubmenuButton > createState ( ) = > _IconSubmenuButtonState ( ) ;
}
class _IconSubmenuButtonState extends State < _IconSubmenuButton > {
bool hover = false ;
@ override
Widget build ( BuildContext context ) {
assert ( widget . svg ! = null | | widget . icon ! = null ) ;
final icon = widget . icon ? ?
SvgPicture . asset (
widget . svg ! ,
color: Colors . white ,
2023-06-11 11:32:22 +03:00
width: _ToolbarTheme . buttonSize ,
height: _ToolbarTheme . buttonSize ,
2023-02-23 09:30:29 +03:00
) ;
2023-03-10 08:54:23 +03:00
final button = SizedBox (
2023-06-11 11:32:22 +03:00
width: _ToolbarTheme . buttonSize ,
height: _ToolbarTheme . buttonSize ,
2023-03-10 09:16:18 +03:00
child: SubmenuButton (
menuStyle: widget . menuStyle ,
style: ButtonStyle (
2023-03-11 14:29:26 +03:00
backgroundColor: MaterialStatePropertyAll ( Colors . transparent ) ,
2023-03-10 09:16:18 +03:00
padding: MaterialStatePropertyAll ( EdgeInsets . zero ) ,
overlayColor: MaterialStatePropertyAll ( Colors . transparent ) ) ,
onHover: ( value ) = > setState ( ( ) {
hover = value ;
} ) ,
child: Material (
type: MaterialType . transparency ,
child: Ink (
decoration: BoxDecoration (
borderRadius:
2023-06-11 11:32:22 +03:00
BorderRadius . circular ( _ToolbarTheme . iconRadius ) ,
2023-03-10 09:16:18 +03:00
color: hover ? widget . hoverColor : widget . color ,
) ,
child: icon ) ) ,
menuChildren: widget . menuChildren
. map ( ( e ) = > _buildPointerTrackWidget ( e , widget . ffi ) )
. toList ( ) ) ) ;
return MenuBar ( children: [
button . marginSymmetric (
2023-06-11 11:32:22 +03:00
horizontal: _ToolbarTheme . buttonHMargin ,
vertical: _ToolbarTheme . buttonVMargin )
2023-03-10 09:16:18 +03:00
] ) ;
2023-02-23 09:30:29 +03:00
}
}
2023-02-24 11:20:00 +03:00
class _SubmenuButton extends StatelessWidget {
final List < Widget > menuChildren ;
final Widget ? child ;
final FFI ffi ;
const _SubmenuButton ( {
Key ? key ,
required this . menuChildren ,
required this . child ,
required this . ffi ,
} ) : super ( key: key ) ;
@ override
Widget build ( BuildContext context ) {
return SubmenuButton (
key: key ,
child: child ,
menuChildren:
menuChildren . map ( ( e ) = > _buildPointerTrackWidget ( e , ffi ) ) . toList ( ) ,
) ;
}
}
2023-04-20 15:57:47 +03:00
class MenuButton extends StatelessWidget {
2023-02-23 09:30:29 +03:00
final VoidCallback ? onPressed ;
final Widget ? trailingIcon ;
final Widget ? child ;
final FFI ffi ;
2023-04-20 15:57:47 +03:00
MenuButton (
2023-02-23 09:30:29 +03:00
{ Key ? key ,
this . onPressed ,
this . trailingIcon ,
required this . child ,
required this . ffi } )
: super ( key: key ) ;
@ override
Widget build ( BuildContext context ) {
return MenuItemButton (
key: key ,
onPressed: onPressed ! = null
? ( ) {
_menuDismissCallback ( ffi ) ;
onPressed ? . call ( ) ;
}
: null ,
trailingIcon: trailingIcon ,
child: child ) ;
}
}
2023-04-20 15:57:47 +03:00
class CkbMenuButton extends StatelessWidget {
2023-02-23 09:30:29 +03:00
final bool ? value ;
final ValueChanged < bool ? > ? onChanged ;
final Widget ? child ;
final FFI ffi ;
2023-04-20 15:57:47 +03:00
const CkbMenuButton (
2023-02-23 09:30:29 +03:00
{ Key ? key ,
required this . value ,
required this . onChanged ,
required this . child ,
required this . ffi } )
: super ( key: key ) ;
@ override
Widget build ( BuildContext context ) {
return CheckboxMenuButton (
key: key ,
value: value ,
child: child ,
onChanged: onChanged ! = null
? ( bool ? value ) {
_menuDismissCallback ( ffi ) ;
onChanged ? . call ( value ) ;
}
: null ,
2022-09-08 10:35:19 +03:00
) ;
2023-02-23 09:30:29 +03:00
}
}
2023-04-20 15:57:47 +03:00
class RdoMenuButton < T > extends StatelessWidget {
2023-02-23 09:30:29 +03:00
final T value ;
final T ? groupValue ;
final ValueChanged < T ? > ? onChanged ;
final Widget ? child ;
final FFI ffi ;
2023-05-19 12:34:39 +03:00
const RdoMenuButton ( {
Key ? key ,
required this . value ,
required this . groupValue ,
required this . child ,
required this . ffi ,
this . onChanged ,
} ) : super ( key: key ) ;
2023-02-23 09:30:29 +03:00
@ override
Widget build ( BuildContext context ) {
return RadioMenuButton (
value: value ,
groupValue: groupValue ,
child: child ,
onChanged: onChanged ! = null
? ( T ? value ) {
_menuDismissCallback ( ffi ) ;
onChanged ? . call ( value ) ;
}
: null ,
) ;
}
2022-09-08 10:35:19 +03:00
}
2022-12-07 10:13:24 +03:00
class _DraggableShowHide extends StatefulWidget {
2023-06-06 02:39:44 +03:00
final SessionID sessionId ;
2022-12-07 10:13:24 +03:00
final RxDouble fractionX ;
final RxBool dragging ;
final RxBool show ;
const _DraggableShowHide ( {
Key ? key ,
2023-06-06 02:39:44 +03:00
required this . sessionId ,
2022-12-07 10:13:24 +03:00
required this . fractionX ,
required this . dragging ,
required this . show ,
} ) : super ( key: key ) ;
@ override
2023-01-04 11:41:05 +03:00
State < _DraggableShowHide > createState ( ) = > _DraggableShowHideState ( ) ;
2022-12-07 10:13:24 +03:00
}
2023-01-04 11:41:05 +03:00
class _DraggableShowHideState extends State < _DraggableShowHide > {
2022-12-07 10:13:24 +03:00
Offset position = Offset . zero ;
Size size = Size . zero ;
2023-05-17 11:55:35 +03:00
double left = 0.0 ;
double right = 1.0 ;
@ override
initState ( ) {
super . initState ( ) ;
final confLeft = double . tryParse (
bind . mainGetLocalOption ( key: ' remote-menubar-drag-left ' ) ) ;
if ( confLeft = = null ) {
bind . mainSetLocalOption (
key: ' remote-menubar-drag-left ' , value: left . toString ( ) ) ;
} else {
left = confLeft ;
}
final confRight = double . tryParse (
bind . mainGetLocalOption ( key: ' remote-menubar-drag-right ' ) ) ;
if ( confRight = = null ) {
bind . mainSetLocalOption (
key: ' remote-menubar-drag-right ' , value: right . toString ( ) ) ;
} else {
right = confRight ;
}
}
2022-12-07 10:13:24 +03:00
Widget _buildDraggable ( BuildContext context ) {
return Draggable (
axis: Axis . horizontal ,
child: Icon (
Icons . drag_indicator ,
2023-01-03 18:42:40 +03:00
size: 20 ,
2023-03-11 14:53:19 +03:00
color: MyTheme . color ( context ) . drag_indicator ,
2022-12-07 10:13:24 +03:00
) ,
feedback: widget ,
onDragStarted: ( ( ) {
final RenderObject ? renderObj = context . findRenderObject ( ) ;
if ( renderObj ! = null ) {
final RenderBox renderBox = renderObj as RenderBox ;
size = renderBox . size ;
position = renderBox . localToGlobal ( Offset . zero ) ;
}
widget . dragging . value = true ;
} ) ,
onDragEnd: ( details ) {
final mediaSize = MediaQueryData . fromWindow ( ui . window ) . size ;
widget . fractionX . value + =
( details . offset . dx - position . dx ) / ( mediaSize . width - size . width ) ;
2023-05-17 11:55:35 +03:00
if ( widget . fractionX . value < left ) {
widget . fractionX . value = left ;
2022-12-07 10:13:24 +03:00
}
2023-05-17 11:55:35 +03:00
if ( widget . fractionX . value > right ) {
widget . fractionX . value = right ;
2022-12-07 10:13:24 +03:00
}
2023-05-17 11:55:35 +03:00
bind . sessionPeerOption (
2023-06-06 02:39:44 +03:00
sessionId: widget . sessionId ,
2023-05-17 11:55:35 +03:00
name: ' remote-menubar-drag-x ' ,
value: widget . fractionX . value . toString ( ) ,
) ;
2022-12-07 10:13:24 +03:00
widget . dragging . value = false ;
} ,
) ;
}
@ override
Widget build ( BuildContext context ) {
final ButtonStyle buttonStyle = ButtonStyle (
minimumSize: MaterialStateProperty . all ( const Size ( 0 , 0 ) ) ,
padding: MaterialStateProperty . all ( EdgeInsets . zero ) ,
) ;
final child = Row (
mainAxisSize: MainAxisSize . min ,
children: [
_buildDraggable ( context ) ,
TextButton (
onPressed: ( ) = > setState ( ( ) {
widget . show . value = ! widget . show . value ;
} ) ,
child: Obx ( ( ( ) = > Icon (
widget . show . isTrue ? Icons . expand_less : Icons . expand_more ,
2023-01-03 18:42:40 +03:00
size: 20 ,
2022-12-07 10:13:24 +03:00
) ) ) ,
) ,
] ,
) ;
return TextButtonTheme (
data: TextButtonThemeData ( style: buttonStyle ) ,
child: Container (
decoration: BoxDecoration (
2023-03-11 14:53:19 +03:00
color: Theme . of ( context )
. menuBarTheme
. style
? . backgroundColor
? . resolve ( MaterialState . values . toSet ( ) ) ,
2023-02-14 15:57:33 +03:00
borderRadius: BorderRadius . vertical (
bottom: Radius . circular ( 5 ) ,
) ,
2022-12-07 10:13:24 +03:00
) ,
child: SizedBox (
2023-01-03 18:42:40 +03:00
height: 20 ,
2022-12-07 10:13:24 +03:00
child: child ,
) ,
) ,
) ;
}
}
2023-02-03 05:41:47 +03:00
class KeyboardModeMenu {
final String key ;
final String menu ;
KeyboardModeMenu ( { required this . key , required this . menu } ) ;
}
2023-02-23 09:30:29 +03:00
_menuDismissCallback ( FFI ffi ) = > ffi . inputModel . refreshMousePos ( ) ;
Widget _buildPointerTrackWidget ( Widget child , FFI ffi ) {
return Listener (
onPointerHover: ( PointerHoverEvent e ) = >
ffi . inputModel . lastMousePos = e . position ,
child: MouseRegion (
child: child ,
) ,
) ;
}
2023-03-15 19:57:52 +03:00
class _MultiMonitorMenu extends StatelessWidget {
final String id ;
final FFI ffi ;
const _MultiMonitorMenu ( {
2023-03-23 20:54:49 +03:00
Key ? key ,
2023-03-15 19:57:52 +03:00
required this . id ,
required this . ffi ,
2023-03-23 20:54:49 +03:00
} ) : super ( key: key ) ;
2023-03-15 19:57:52 +03:00
@ override
Widget build ( BuildContext context ) {
final List < Widget > rowChildren = [ ] ;
final pi = ffi . ffiModel . pi ;
for ( int i = 0 ; i < pi . displays . length ; i + + ) {
rowChildren . add (
Obx ( ( ) {
RxInt display = CurrentDisplayState . find ( id ) ;
return _IconMenuButton (
topLevel: false ,
color: i = = display . value
2023-06-11 11:32:22 +03:00
? _ToolbarTheme . blueColor
2023-03-15 19:57:52 +03:00
: Colors . grey [ 800 ] ! ,
hoverColor: i = = display . value
2023-06-11 11:32:22 +03:00
? _ToolbarTheme . hoverBlueColor
2023-03-15 19:57:52 +03:00
: Colors . grey [ 850 ] ! ,
icon: Container (
alignment: AlignmentDirectional . center ,
constraints:
2023-06-11 11:32:22 +03:00
const BoxConstraints ( minHeight: _ToolbarTheme . height ) ,
2023-03-15 19:57:52 +03:00
child: Stack (
alignment: Alignment . center ,
children: [
SvgPicture . asset (
" assets/screen.svg " ,
color: Colors . white ,
) ,
Obx (
( ) = > Text (
( i + 1 ) . toString ( ) ,
style: TextStyle (
color: i = = display . value
2023-06-11 11:32:22 +03:00
? _ToolbarTheme . blueColor
2023-03-15 19:57:52 +03:00
: Colors . grey [ 800 ] ! ,
fontSize: 12 ,
fontWeight: FontWeight . bold ,
) ,
) ,
) ,
] ,
) ,
) ,
onPressed: ( ) {
if ( display . value ! = i ) {
2023-06-06 02:39:44 +03:00
bind . sessionSwitchDisplay ( sessionId: ffi . sessionId , value: i ) ;
2023-03-15 19:57:52 +03:00
}
} ,
) ;
} ) ,
) ;
}
return Row ( children: rowChildren ) ;
}
}