2022-09-16 14:43:28 +03:00
import ' dart:convert ' ;
2023-07-08 22:53:07 +03:00
import ' dart:async ' ;
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 ' ;
2024-05-07 11:18:48 +03:00
import ' package:flutter_hbb/common/widgets/audio_input.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 ' ;
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-06-11 11:32:22 +03:00
class ToolbarState {
2022-11-10 09:32:22 +03:00
late RxBool _pin ;
2024-06-13 19:28:59 +03:00
bool isShowInited = false ;
RxBool show = false . obs ;
2023-06-11 11:32:22 +03:00
ToolbarState ( ) {
2024-06-13 19:28:59 +03:00
_pin = RxBool ( false ) ;
2024-05-18 18:13:54 +03:00
final s = bind . getLocalFlutterOption ( k: kOptionRemoteMenubarState ) ;
2022-11-10 16:25:12 +03:00
if ( s . isEmpty ) {
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 ) ;
2024-06-13 19:28:59 +03:00
if ( m ! = null ) {
_pin = RxBool ( m [ ' pin ' ] ? ? false ) ;
2022-11-10 09:32:22 +03:00
}
} 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
}
}
bool get pin = > _pin . value ;
2024-06-13 19:28:59 +03:00
switchShow ( SessionID sessionId ) async {
bind . sessionToggleOption (
sessionId: sessionId , value: kOptionCollapseToolbar ) ;
2022-11-10 09:32:22 +03:00
show . value = ! show . value ;
}
2024-06-13 19:28:59 +03:00
initShow ( SessionID sessionId ) async {
if ( ! isShowInited ) {
show . value = ! ( await bind . sessionGetToggleOption (
sessionId: sessionId , arg: kOptionCollapseToolbar ) ? ?
false ) ;
isShowInited = true ;
2022-11-10 09:32:22 +03:00
}
}
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 {
2023-08-10 17:27:35 +03:00
bind . setLocalFlutterOption (
2024-05-18 18:13:54 +03:00
k: kOptionRemoteMenubarState , v: jsonEncode ( { ' pin ' : _pin . value } ) ) ;
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 ;
2023-09-10 09:14:57 +03:00
static Color inactiveColor = Colors . grey [ 800 ] ! ;
2023-09-04 09:17:54 +03:00
static Color hoverInactiveColor = Colors . grey [ 850 ] ! ;
2023-02-15 13:40:17 +03:00
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 ;
2023-10-31 09:27:27 +03:00
static const double buttonHMargin = 2 ;
2023-02-23 09:30:29 +03:00
static const double buttonVMargin = 6 ;
static const double iconRadius = 8 ;
2023-03-12 05:48:54 +03:00
static const double elevation = 3 ;
2023-07-08 22:53:07 +03:00
2024-03-24 06:23:06 +03:00
static double dividerSpaceToAction = isWindows ? 8 : 14 ;
2023-07-09 07:48:28 +03:00
2024-03-24 06:23:06 +03:00
static double menuBorderRadius = isWindows ? 5.0 : 7.0 ;
static EdgeInsets menuPadding = isWindows
2023-07-09 09:06:19 +03:00
? EdgeInsets . fromLTRB ( 4 , 12 , 4 , 12 )
: EdgeInsets . fromLTRB ( 6 , 14 , 6 , 14 ) ;
2023-07-09 07:48:28 +03:00
static const double menuButtonBorderRadius = 3.0 ;
2023-07-09 09:16:52 +03:00
2023-12-05 06:34:54 +03:00
static Color borderColor ( BuildContext context ) = >
MyTheme . color ( context ) . border3 ? ? MyTheme . border ;
static Color ? dividerColor ( BuildContext context ) = >
MyTheme . color ( context ) . divider ;
static MenuStyle defaultMenuStyle ( BuildContext context ) = > MenuStyle (
side: MaterialStateProperty . all ( BorderSide (
width: 1 ,
color: borderColor ( context ) ,
) ) ,
shape: MaterialStatePropertyAll ( RoundedRectangleBorder (
borderRadius:
BorderRadius . circular ( _ToolbarTheme . menuBorderRadius ) ) ) ,
padding: MaterialStateProperty . all ( _ToolbarTheme . menuPadding ) ,
) ;
2023-07-09 09:16:52 +03:00
static final defaultMenuButtonStyle = ButtonStyle (
backgroundColor: MaterialStatePropertyAll ( Colors . transparent ) ,
padding: MaterialStatePropertyAll ( EdgeInsets . zero ) ,
overlayColor: MaterialStatePropertyAll ( Colors . transparent ) ,
) ;
2023-10-31 11:52:18 +03:00
2023-12-05 06:34:54 +03:00
static Widget borderWrapper (
BuildContext context , Widget child , BorderRadius borderRadius ) {
2023-10-31 11:52:18 +03:00
return Container (
decoration: BoxDecoration (
border: Border . all (
2023-12-05 06:34:54 +03:00
color: borderColor ( context ) ,
2023-10-31 11:52:18 +03:00
width: 1 ,
) ,
2023-10-31 14:15:13 +03:00
borderRadius: borderRadius ,
2023-10-31 11:52:18 +03:00
) ,
child: child ,
) ;
}
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 ;
2024-06-13 16:04:00 +03:00
final Function ( int , Function ( bool ) ) onEnterOrLeaveImageSetter ;
final Function ( int ) onEnterOrLeaveImageCleaner ;
2023-10-08 16:44:54 +03:00
final Function ( VoidCallback ) setRemoteState ;
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 ,
2023-10-08 16:44:54 +03:00
required this . setRemoteState ,
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 ;
2022-09-13 16:59:06 +03:00
void _setFullscreen ( bool v ) {
2022-11-01 12:01:43 +03:00
stateGlobal . setFullscreen ( v ) ;
2023-10-17 09:42:35 +03:00
// stateGlobal.fullscreen is RxBool now, no need to call setState.
// 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 ;
2023-07-27 15:45:29 +03:00
void _minimize ( ) async = >
await WindowController . fromWindowId ( windowId ) . minimize ( ) ;
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 ( ) ;
2024-07-24 09:00:49 +03:00
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) async {
2023-05-17 11:55:35 +03:00
_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 ,
) ;
2024-06-13 16:04:00 +03:00
widget . onEnterOrLeaveImageSetter ( identityHashCode ( this ) , ( 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 ( ) ;
2024-06-13 16:04:00 +03:00
widget . onEnterOrLeaveImageCleaner ( identityHashCode ( this ) ) ;
2022-09-14 05:10:55 +03:00
}
2022-08-26 18:28:08 +03:00
@ override
Widget build ( BuildContext context ) {
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
}
2023-10-31 14:15:13 +03:00
final borderRadius = BorderRadius . vertical (
bottom: Radius . circular ( 5 ) ,
) ;
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-10-31 14:15:13 +03:00
borderRadius: borderRadius ,
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 ,
2024-06-13 19:28:59 +03:00
toolbarState: widget . state ,
2023-07-27 17:19:38 +03:00
setFullscreen: _setFullscreen ,
setMinimize: _minimize ,
2023-10-31 14:15:13 +03:00
borderRadius: borderRadius ,
) ,
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 = [ ] ;
2024-08-29 18:38:08 +03:00
toolbarItems . add ( _PinMenu ( state: widget . state ) ) ;
2022-08-26 18:28:08 +03:00
if ( ! isWebDesktop ) {
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-10-08 16:44:54 +03:00
toolbarItems . add ( Obx ( ( ) {
2023-11-14 07:11:38 +03:00
if ( PrivacyModeState . find ( widget . id ) . isEmpty & &
2024-06-07 07:59:42 +03:00
pi . displaysCount . value > 1 ) {
2023-10-08 16:44:54 +03:00
return _MonitorMenu (
id: widget . id ,
ffi: widget . ffi ,
setRemoteState: widget . setRemoteState ) ;
} else {
return Offstage ( ) ;
}
} ) ) ;
2023-03-15 19:57:52 +03:00
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
}
2024-03-28 06:38:11 +03:00
if ( ! isWeb ) toolbarItems . add ( _RecordMenu ( ) ) ;
2023-03-15 20:31:53 +03:00
toolbarItems . add ( _CloseMenu ( id: widget . id , ffi: widget . ffi ) ) ;
2023-10-31 14:15:13 +03:00
final toolbarBorderRadius = BorderRadius . all ( Radius . circular ( 4.0 ) ) ;
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-10-31 14:15:13 +03:00
borderRadius: toolbarBorderRadius ,
2023-03-12 05:48:54 +03:00
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-10-31 14:15:13 +03:00
child: _ToolbarTheme . borderWrapper (
2023-12-05 06:34:54 +03:00
context ,
2023-10-31 14:15:13 +03:00
Row (
children: [
SizedBox ( width: _ToolbarTheme . buttonHMargin * 2 ) ,
. . . toolbarItems ,
SizedBox ( width: _ToolbarTheme . buttonHMargin * 2 )
] ,
) ,
toolbarBorderRadius ) ,
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-07-09 07:48:28 +03:00
shape: MaterialStatePropertyAll ( RoundedRectangleBorder (
2023-07-09 09:06:19 +03:00
borderRadius:
BorderRadius . circular ( _ToolbarTheme . menuButtonBorderRadius ) ) ) ,
2023-03-01 18:35:51 +03:00
) ,
) ,
2023-07-08 22:53:07 +03:00
dividerTheme: DividerThemeData (
space: _ToolbarTheme . dividerSpaceToAction ,
2023-12-05 06:34:54 +03:00
color: _ToolbarTheme . dividerColor ( context ) ,
2023-07-08 22:53:07 +03:00
) ,
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-09-10 13:31:16 +03:00
color:
state . pin ? _ToolbarTheme . blueColor : _ToolbarTheme . inactiveColor ,
hoverColor: state . pin
? _ToolbarTheme . hoverBlueColor
: _ToolbarTheme . hoverInactiveColor ,
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 _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 ( ) ;
2023-09-10 13:31:16 +03:00
return Obx ( ( ) = > _IconMenuButton (
assetName: ' assets/actions_mobile.svg ' ,
tooltip: ' Mobile Actions ' ,
2024-06-23 06:06:47 +03:00
onPressed: ( ) = > ffi . dialogManager . setMobileActionsOverlayVisible (
! ffi . dialogManager . mobileActionsOverlayVisible . value ) ,
2023-09-10 13:31:16 +03:00
color: ffi . dialogManager . mobileActionsOverlayVisible . isTrue
? _ToolbarTheme . blueColor
: _ToolbarTheme . inactiveColor ,
hoverColor: ffi . dialogManager . mobileActionsOverlayVisible . isTrue
? _ToolbarTheme . hoverBlueColor
: _ToolbarTheme . hoverInactiveColor ,
) ) ;
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 ;
2023-10-08 16:44:54 +03:00
final Function ( VoidCallback ) setRemoteState ;
const _MonitorMenu ( {
Key ? key ,
required this . id ,
required this . ffi ,
required this . setRemoteState ,
} ) : super ( key: key ) ;
bool get showMonitorsToolbar = >
2023-10-09 12:22:22 +03:00
bind . mainGetUserDefaultOption ( key: kKeyShowMonitorsToolbar ) = = ' Y ' ;
2022-08-26 18:28:08 +03:00
2023-10-16 02:26:55 +03:00
bool get supportIndividualWindows = >
2024-05-28 11:42:30 +03:00
! isWeb & & ffi . ffiModel . pi . isSupportMultiDisplay ;
2023-10-16 02:26:55 +03:00
2023-02-23 09:30:29 +03:00
@ override
2023-10-31 09:27:27 +03:00
Widget build ( BuildContext context ) = > showMonitorsToolbar
2024-05-28 11:42:30 +03:00
? buildMultiMonitorMenu ( context )
: Obx ( ( ) = > buildMonitorMenu ( context ) ) ;
2023-10-08 16:44:54 +03:00
2024-05-28 11:42:30 +03:00
Widget buildMonitorMenu ( BuildContext context ) {
2023-10-31 09:27:27 +03:00
final width = SimpleWrapper < double > ( 0 ) ;
2023-10-31 10:14:43 +03:00
final monitorsIcon =
globalMonitorsWidget ( width , Colors . white , Colors . black38 ) ;
2023-02-23 09:30:29 +03:00
return _IconSubmenuButton (
2023-03-10 09:16:18 +03:00
tooltip: ' Select Monitor ' ,
2023-10-31 09:27:27 +03:00
icon: monitorsIcon ,
2023-02-23 09:30:29 +03:00
ffi: ffi ,
2023-10-31 09:27:27 +03:00
width: width . value ,
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 ) ) ) ,
2024-05-28 11:42:30 +03:00
menuChildrenGetter: ( ) = > [ buildMonitorSubmenuWidget ( context ) ] ) ;
2023-10-08 16:44:54 +03:00
}
2024-05-28 11:42:30 +03:00
Widget buildMultiMonitorMenu ( BuildContext context ) {
return Row ( children: buildMonitorList ( context , true ) ) ;
2023-10-08 16:44:54 +03:00
}
2024-05-28 11:42:30 +03:00
Widget buildMonitorSubmenuWidget ( BuildContext context ) {
2023-10-09 12:22:22 +03:00
return Column (
mainAxisSize: MainAxisSize . min ,
children: [
2024-05-28 11:42:30 +03:00
Row ( children: buildMonitorList ( context , false ) ) ,
2024-06-13 13:03:41 +03:00
supportIndividualWindows ? Divider ( ) : Offstage ( ) ,
supportIndividualWindows ? chooseDisplayBehavior ( ) : Offstage ( ) ,
2023-10-09 12:22:22 +03:00
] ,
) ;
}
Widget chooseDisplayBehavior ( ) {
final value =
bind . sessionGetDisplaysAsIndividualWindows ( sessionId: ffi . sessionId ) = =
' Y ' ;
return CkbMenuButton (
value: value ,
onChanged: ( value ) async {
if ( value = = null ) return ;
await bind . sessionSetDisplaysAsIndividualWindows (
2024-06-10 18:01:55 +03:00
sessionId: ffi . sessionId , value: value ? ' Y ' : ' N ' ) ;
2023-10-09 12:22:22 +03:00
} ,
ffi: ffi ,
child: Text ( translate ( ' Show displays as individual windows ' ) ) ) ;
}
2023-10-30 15:22:44 +03:00
buildOneMonitorButton ( i , curDisplay ) = > Text (
' ${ i + 1 } ' ,
style: TextStyle (
color: i = = curDisplay
? _ToolbarTheme . blueColor
: _ToolbarTheme . inactiveColor ,
fontSize: 12 ,
fontWeight: FontWeight . bold ,
) ,
) ;
2024-05-28 11:42:30 +03:00
List < Widget > buildMonitorList ( BuildContext context , bool isMulti ) {
2023-10-08 16:44:54 +03:00
final List < Widget > monitorList = [ ] ;
final pi = ffi . ffiModel . pi ;
buildMonitorButton ( int i ) = > Obx ( ( ) {
RxInt display = CurrentDisplayState . find ( id ) ;
2023-10-31 09:27:27 +03:00
final isAllMonitors = i = = kAllDisplayValue ;
final width = SimpleWrapper < double > ( 0 ) ;
Widget ? monitorsIcon ;
if ( isAllMonitors ) {
2023-10-31 10:14:43 +03:00
monitorsIcon = globalMonitorsWidget (
width , Colors . white , _ToolbarTheme . blueColor ) ;
2023-10-31 09:27:27 +03:00
}
2023-10-08 16:44:54 +03:00
return _IconMenuButton (
2023-10-30 15:22:44 +03:00
tooltip: isMulti
? ' '
2023-10-31 09:27:27 +03:00
: isAllMonitors
2023-10-30 15:22:44 +03:00
? ' all monitors '
: ' # ${ i + 1 } monitor ' ,
2023-10-08 16:44:54 +03:00
hMargin: isMulti ? null : 6 ,
vMargin: isMulti ? null : 12 ,
topLevel: false ,
color: i = = display . value
? _ToolbarTheme . blueColor
: _ToolbarTheme . inactiveColor ,
hoverColor: i = = display . value
? _ToolbarTheme . hoverBlueColor
: _ToolbarTheme . hoverInactiveColor ,
2023-10-31 09:27:27 +03:00
width: isAllMonitors ? width . value : null ,
icon: isAllMonitors
? monitorsIcon
: Container (
alignment: AlignmentDirectional . center ,
constraints:
const BoxConstraints ( minHeight: _ToolbarTheme . height ) ,
child: Stack (
alignment: Alignment . center ,
children: [
SvgPicture . asset (
" assets/screen.svg " ,
colorFilter:
ColorFilter . mode ( Colors . white , BlendMode . srcIn ) ,
) ,
Obx ( ( ) = > buildOneMonitorButton ( i , display . value ) ) ,
] ,
) ,
2023-10-08 16:44:54 +03:00
) ,
2023-12-02 16:23:19 +03:00
onPressed: ( ) = > onPressed ( i , pi , isMulti ) ,
2023-10-08 16:44:54 +03:00
) ;
} ) ;
for ( int i = 0 ; i < pi . displays . length ; i + + ) {
monitorList . add ( buildMonitorButton ( i ) ) ;
}
2024-06-13 13:03:41 +03:00
if ( supportIndividualWindows & & pi . displays . length > 1 ) {
2023-10-08 16:44:54 +03:00
monitorList . add ( buildMonitorButton ( kAllDisplayValue ) ) ;
}
return monitorList ;
2023-02-23 09:30:29 +03:00
}
2023-02-21 13:43:43 +03:00
2023-10-31 10:14:43 +03:00
globalMonitorsWidget (
SimpleWrapper < double > width , Color activeTextColor , Color activeBgColor ) {
2023-10-31 09:27:27 +03:00
getMonitors ( ) {
final pi = ffi . ffiModel . pi ;
2023-10-30 15:22:44 +03:00
RxInt display = CurrentDisplayState . find ( id ) ;
final rect = ffi . ffiModel . globalDisplaysRect ( ) ;
if ( rect = = null ) {
return Offstage ( ) ;
}
2023-10-31 09:27:27 +03:00
2023-10-31 10:14:43 +03:00
final scale = _ToolbarTheme . buttonSize / rect . height * 0.75 ;
2023-10-31 09:27:27 +03:00
final startY = ( _ToolbarTheme . buttonSize - rect . height * scale ) * 0.5 ;
final startX = startY ;
2023-10-30 15:22:44 +03:00
final children = < Widget > [ ] ;
for ( var i = 0 ; i < pi . displays . length ; i + + ) {
final d = pi . displays [ i ] ;
2024-02-27 17:28:23 +03:00
double s = d . scale ;
int dWidth = d . width . toDouble ( ) ~ / s ;
int dHeight = d . height . toDouble ( ) ~ / s ;
final fontSize = ( dWidth * scale < dHeight * scale
? dWidth * scale
: dHeight * scale ) *
2023-10-31 09:27:27 +03:00
0.65 ;
2023-10-30 15:22:44 +03:00
children . add ( Positioned (
2023-10-31 09:27:27 +03:00
left: ( d . x - rect . left ) * scale + startX ,
top: ( d . y - rect . top ) * scale + startY ,
2024-02-27 17:28:23 +03:00
width: dWidth * scale ,
height: dHeight * scale ,
2023-10-31 09:27:27 +03:00
child: Container (
decoration: BoxDecoration (
border: Border . all (
color: Colors . grey ,
width: 1.0 ,
2023-10-30 15:22:44 +03:00
) ,
2023-10-31 10:14:43 +03:00
color: display . value = = i ? activeBgColor : Colors . white ,
2023-03-15 19:57:52 +03:00
) ,
2023-10-31 09:27:27 +03:00
child: Center (
child: Text (
' ${ i + 1 } ' ,
style: TextStyle (
color: display . value = = i
2023-10-31 10:14:43 +03:00
? activeTextColor
2023-10-31 09:27:27 +03:00
: _ToolbarTheme . inactiveColor ,
fontSize: fontSize ,
fontWeight: FontWeight . bold ,
) ,
) ) ,
2023-10-30 15:22:44 +03:00
) ,
) ) ;
}
2023-10-31 09:27:27 +03:00
width . value = rect . width * scale + startX * 2 ;
return SizedBox (
width: width . value ,
height: rect . height * scale + startY * 2 ,
2023-10-30 15:22:44 +03:00
child: Stack (
children: children ,
) ,
) ;
2023-10-31 09:27:27 +03:00
}
2022-09-05 17:18:29 +03:00
2023-10-31 09:27:27 +03:00
return Stack (
alignment: Alignment . center ,
children: [
SizedBox ( height: _ToolbarTheme . buttonSize ) ,
getMonitors ( ) ,
] ,
) ;
}
2023-10-30 15:22:44 +03:00
2023-12-02 16:23:19 +03:00
onPressed ( int i , PeerInfo pi , bool isMulti ) {
if ( ! isMulti ) {
// If show monitors in toolbar(`buildMultiMonitorMenu()`), then the menu will dismiss automatically.
_menuDismissCallback ( ffi ) ;
}
2023-10-08 16:44:54 +03:00
RxInt display = CurrentDisplayState . find ( id ) ;
if ( display . value ! = i ) {
2024-05-28 11:42:30 +03:00
final isChooseDisplayToOpenInNewWindow = pi . isSupportMultiDisplay & &
bind . sessionGetDisplaysAsIndividualWindows (
sessionId: ffi . sessionId ) = =
' Y ' ;
if ( isChooseDisplayToOpenInNewWindow ) {
2023-10-16 19:30:34 +03:00
openMonitorInNewTabOrWindow ( i , ffi . id , pi ) ;
2023-10-08 16:44:54 +03:00
} else {
2023-12-02 17:01:05 +03:00
openMonitorInTheSameTab ( i , ffi , pi , updateCursorPos: ! isMulti ) ;
2023-10-08 16:44:54 +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
}
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 ,
2024-04-05 04:28:35 +03:00
menuChildrenGetter: ( ) = > toolbarControls ( context , id , ffi ) . map ( ( e ) {
if ( e . divider ) {
return Divider ( ) ;
} else {
return MenuButton (
child: e . child ,
onPressed: e . onPressed ,
ffi: ffi ,
trailingIcon: e . trailingIcon ) ;
}
} ) . 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-10-17 09:29:14 +03:00
bool get isFullscreen = > stateGlobal . fullscreen . isTrue ;
2023-02-23 09:30:29 +03:00
int get windowId = > stateGlobal . windowId ;
2023-07-27 17:19:38 +03:00
adjustWindow ( BuildContext context ) {
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-07-27 17:19:38 +03:00
onPressed: ( ) = > doAdjustWindow ( context ) ,
2023-05-18 11:17:51 +03:00
ffi: ffi ) ,
2023-04-12 04:41:13 +03:00
Divider ( ) ,
] ,
) ;
} ) ;
2023-02-23 09:30:29 +03:00
}
2023-07-27 17:19:38 +03:00
doAdjustWindow ( BuildContext context ) async {
2023-05-18 11:17:51 +03:00
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 ( ) ;
2023-07-27 17:19:38 +03:00
final mediaSize = MediaQueryData . fromView ( View . of ( context ) ) . size ;
2023-02-23 09:30:29 +03:00
// 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-09-17 14:49:02 +03:00
stateGlobal . setMaximized ( false ) ;
2023-02-23 09:30:29 +03:00
}
}
2023-05-18 11:17:51 +03:00
updateScreen ( ) async {
2024-03-28 06:38:11 +03:00
final String info =
isWeb ? screenInfo : await _getScreenInfoDesktop ( ) ? ? ' ' ;
if ( info . isEmpty ) {
2023-02-23 09:30:29 +03:00
_screen = null ;
} else {
2024-03-28 06:38:11 +03:00
final screenMap = jsonDecode ( info ) ;
2023-02-23 09:30:29 +03:00
_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 ' ] ) ;
}
}
2024-03-28 06:38:11 +03:00
_getScreenInfoDesktop ( ) async {
final v = await rustDeskWinManager . call (
WindowType . Main , kWindowGetWindowInfo , ' ' ) ;
return v . result ;
}
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 ;
}
2024-03-28 06:38:11 +03:00
if ( ! isWeb ) {
final remoteCount = RemoteCountState . find ( ) . value ;
if ( remoteCount ! = 1 ) {
return false ;
}
2023-02-23 09:30:29 +03:00
}
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 ) ,
) ;
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
Widget build ( BuildContext context ) {
_screenAdjustor . updateScreen ( ) ;
2024-04-05 04:28:35 +03:00
menuChildrenGetter ( ) {
final menuChildren = < Widget > [
_screenAdjustor . adjustWindow ( context ) ,
viewStyle ( ) ,
scrollStyle ( ) ,
imageQuality ( ) ,
codec ( ) ,
_ResolutionsMenu (
id: widget . id ,
ffi: widget . ffi ,
screenAdjustor: _screenAdjustor ,
) ,
2024-07-15 13:53:14 +03:00
if ( showVirtualDisplayMenu ( ffi ) )
_SubmenuButton (
2024-04-19 06:31:52 +03:00
ffi: widget . ffi ,
2024-07-15 13:53:14 +03:00
menuChildren: getVirtualDisplayMenuChildren ( ffi , id , null ) ,
child: Text ( translate ( " Virtual display " ) ) ,
2024-04-19 06:31:52 +03:00
) ,
2024-04-25 08:26:02 +03:00
cursorToggles ( ) ,
Divider ( ) ,
2024-04-05 04:28:35 +03:00
toggles ( ) ,
] ;
// privacy mode
if ( ffiModel . keyboard & & pi . features . privacyMode ) {
final privacyModeState = PrivacyModeState . find ( id ) ;
final privacyModeList =
toolbarPrivacyMode ( privacyModeState , context , id , ffi ) ;
if ( privacyModeList . length = = 1 ) {
menuChildren . add ( CkbMenuButton (
value: privacyModeList [ 0 ] . value ,
onChanged: privacyModeList [ 0 ] . onChanged ,
child: privacyModeList [ 0 ] . child ,
ffi: ffi ) ) ;
} else if ( privacyModeList . length > 1 ) {
menuChildren . addAll ( [
Divider ( ) ,
_SubmenuButton (
ffi: widget . ffi ,
child: Text ( translate ( ' Privacy mode ' ) ) ,
menuChildren: privacyModeList
. map ( ( e ) = > CkbMenuButton (
value: e . value ,
onChanged: e . onChanged ,
child: e . child ,
ffi: ffi ) )
. toList ( ) ) ,
] ) ;
}
2023-11-14 07:11:38 +03:00
}
2024-04-05 04:28:35 +03:00
menuChildren . add ( widget . pluginItem ) ;
return menuChildren ;
2023-11-14 07:11:38 +03:00
}
return _IconSubmenuButton (
tooltip: ' Display Settings ' ,
svg: " assets/display.svg " ,
ffi: widget . ffi ,
color: _ToolbarTheme . blueColor ,
hoverColor: _ToolbarTheme . hoverBlueColor ,
2024-04-05 04:28:35 +03:00
menuChildrenGetter: menuChildrenGetter ,
2023-11-14 07:11:38 +03:00
) ;
2023-05-18 11:17:51 +03:00
}
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
}
2024-04-25 08:26:02 +03:00
cursorToggles ( ) {
return futureBuilder (
future: toolbarCursor ( context , id , ffi ) ,
hasData: ( data ) {
final v = data as List < TToggleMenu > ;
if ( v . isEmpty ) return Offstage ( ) ;
2024-05-22 10:07:47 +03:00
return Column ( children: [
Divider ( ) ,
. . . v
. map ( ( e ) = > CkbMenuButton (
value: e . value ,
onChanged: e . onChanged ,
child: e . child ,
ffi: ffi ) )
. toList ( ) ,
] ) ;
2024-04-25 08:26:02 +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 =
2023-10-08 16:44:54 +03:00
TextEditingController ( text: rect ? . width . toInt ( ) . toString ( ) ? ? ' ' ) ;
2023-05-19 12:34:39 +03:00
late final TextEditingController _customHeight =
2023-10-08 16:44:54 +03:00
TextEditingController ( text: rect ? . height . toInt ( ) . toString ( ) ? ? ' ' ) ;
2023-05-19 12:34:39 +03:00
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 ;
2024-04-01 12:20:19 +03:00
Rect ? get rect = > scaledRect ( ) ;
2023-05-18 11:17:51 +03:00
List < Resolution > get resolutions = > pi . resolutions ;
2023-10-02 18:52:40 +03:00
bool get isWayland = > bind . mainCurrentIsWayland ( ) ;
2023-05-18 11:17:51 +03:00
@ override
void initState ( ) {
super . initState ( ) ;
2024-07-24 09:00:49 +03:00
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
_getLocalResolutionWayland ( ) ;
} ) ;
2023-05-18 11:17:51 +03:00
}
2024-04-01 12:20:19 +03:00
Rect ? scaledRect ( ) {
final scale = pi . scaleOfDisplay ( pi . currentDisplay ) ;
final rect = ffiModel . rect ;
if ( rect = = null ) {
return null ;
}
return Rect . fromLTWH (
rect . left ,
rect . top ,
rect . width / scale ,
rect . height / scale ,
) ;
}
2023-05-18 11:17:51 +03:00
@ override
Widget build ( BuildContext context ) {
2023-10-08 16:44:54 +03:00
final isVirtualDisplay = ffiModel . isVirtualDisplayResolution ;
2023-11-01 10:33:21 +03:00
final visible = ffiModel . keyboard & &
( isVirtualDisplay | | resolutions . length > 1 ) & &
pi . currentDisplay ! = kAllDisplayValue ;
2023-05-18 11:17:51 +03:00
if ( ! visible ) return Offstage ( ) ;
2023-05-18 16:25:48 +03:00
final showOriginalBtn =
2023-10-08 16:44:54 +03:00
ffiModel . isOriginalResolutionSet & & ! ffiModel . isOriginalResolution ;
2023-05-18 16:25:48 +03:00
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 > [
2023-07-27 17:19:38 +03:00
_OriginalResolutionMenuButton ( context , showOriginalBtn ) ,
_FitLocalResolutionMenuButton ( context , showFitLocalBtn ) ,
_customResolutionMenuButton ( context , 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-10-08 16:44:54 +03:00
if ( pi . currentDisplay = = kAllDisplayValue ) {
return ;
}
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 {
2024-02-27 17:28:23 +03:00
_groupValue =
2024-04-01 12:20:19 +03:00
' ${ ( rect ? . width ? ? 0 ) . toInt ( ) } x ${ ( rect ? . height ? ? 0 ) . toInt ( ) } ' ;
2023-05-19 12:34:39 +03:00
}
}
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-10-02 18:52:40 +03:00
Future < void > _getLocalResolutionWayland ( ) async {
if ( ! isWayland ) return _getLocalResolution ( ) ;
final window = await window_size . getWindowInfo ( ) ;
final screen = window . screen ;
if ( screen ! = null ) {
setState ( ( ) {
_localResolution = Resolution (
screen . frame . width . toInt ( ) ,
screen . frame . height . toInt ( ) ,
) ;
} ) ;
}
}
2023-05-18 11:17:51 +03:00
_getLocalResolution ( ) {
_localResolution = null ;
2023-10-08 16:44:54 +03:00
final String mainDisplay = bind . mainGetMainDisplay ( ) ;
if ( mainDisplay . isNotEmpty ) {
2023-05-18 11:17:51 +03:00
try {
2023-10-08 16:44:54 +03:00
final display = json . decode ( mainDisplay ) ;
2023-05-18 11:17:51 +03:00
if ( display [ ' w ' ] ! = null & & display [ ' h ' ] ! = null ) {
_localResolution = Resolution ( display [ ' w ' ] , display [ ' h ' ] ) ;
2024-03-28 06:38:11 +03:00
if ( isWeb ) {
if ( display [ ' scaleFactor ' ] ! = null ) {
_localResolution = Resolution (
( display [ ' w ' ] / display [ ' scaleFactor ' ] ) . toInt ( ) ,
( display [ ' h ' ] / display [ ' scaleFactor ' ] ) . toInt ( ) ,
) ;
}
}
2023-05-18 11:17:51 +03:00
}
} catch ( e ) {
2023-10-08 16:44:54 +03:00
debugPrint ( ' Failed to decode $ mainDisplay , $ e ' ) ;
2023-05-18 11:17:51 +03:00
}
}
}
2024-02-10 19:15:11 +03:00
// This widget has been unmounted, so the State no longer has a context
_onChanged ( String ? value ) async {
2023-10-08 16:44:54 +03:00
if ( pi . currentDisplay = = kAllDisplayValue ) {
return ;
}
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-10-08 16:44:54 +03:00
if ( w ! = rect ? . width . toInt ( ) | | h ! = rect ? . height . toInt ( ) ) {
2024-02-10 19:15:11 +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
2024-02-10 19:15:11 +03:00
_changeResolution ( int w , int h ) async {
2023-10-08 16:44:54 +03:00
if ( pi . currentDisplay = = kAllDisplayValue ) {
return ;
}
2023-05-18 18:45:38 +03:00
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 {
2023-10-08 16:44:54 +03:00
final rect = ffiModel . rect ;
if ( rect = = null ) {
return ;
}
if ( w = = rect . width . toInt ( ) & & h = = rect . height . toInt ( ) ) {
2023-05-18 18:45:38 +03:00
if ( await widget . screenAdjustor . isWindowCanBeAdjusted ( ) ) {
2023-07-27 17:19:38 +03:00
widget . screenAdjustor . doAdjustWindow ( context ) ;
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-07-27 17:19:38 +03:00
Widget _OriginalResolutionMenuButton (
BuildContext context , bool showOriginalBtn ) {
2023-10-08 16:44:54 +03:00
final display = pi . tryGetDisplayIfNotAllDisplay ( ) ;
if ( display = = null ) {
return Offstage ( ) ;
}
2024-02-27 17:28:23 +03:00
if ( ! resolutions . any ( ( e ) = >
e . width = = display . originalWidth & &
e . height = = display . originalHeight ) ) {
return Offstage ( ) ;
}
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 (
2024-02-10 19:15:11 +03:00
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-07-27 17:19:38 +03:00
Widget _FitLocalResolutionMenuButton (
BuildContext context , 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 ) {
2024-02-10 19:15:11 +03:00
_changeResolution ( resolution . width , resolution . height ) ;
2023-05-18 18:45:38 +03:00
}
} ,
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-07-27 17:19:38 +03:00
Widget _customResolutionMenuButton ( BuildContext context , isVirtualDisplay ) {
2023-05-19 15:48:47 +03:00
return Offstage (
offstage: ! isVirtualDisplay ,
child: RdoMenuButton (
value: _kCustomResolutionValue ,
groupValue: _groupValue ,
2024-02-10 19:15:11 +03:00
onChanged: ( String ? value ) = > _onChanged ( value ) ,
2023-05-19 15:48:47 +03:00
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 ,
2024-02-10 19:15:11 +03:00
onChanged: ( String ? value ) = > _onChanged ( value ) ,
2023-05-18 11:17:51 +03:00
ffi: widget . ffi ,
child: Text ( ' ${ e . width } x ${ e . height } ' ) ) )
. toList ( ) ;
Resolution ? _getBestFitResolution ( ) {
if ( _localResolution = = null ) {
return null ;
}
2023-10-08 16:44:54 +03:00
if ( ffiModel . isVirtualDisplayResolution ) {
2023-05-18 16:25:48 +03:00
return _localResolution ! ;
}
2023-05-18 11:17:51 +03:00
for ( final r in resolutions ) {
2023-10-18 17:39:28 +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-10-18 17:39:28 +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 ;
}
2023-10-08 16:44:54 +03:00
return bestFitResolution . width = = rect ? . width . toInt ( ) & &
bestFitResolution . height = = rect ? . height . toInt ( ) ;
2023-05-18 11:17:51 +03:00
}
}
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 ( ) ;
2024-04-06 06:37:31 +03:00
toolbarToggles ( ) = > toolbarKeyboardToggles ( ffi )
2023-12-11 07:56:26 +03:00
. map ( ( e ) = > CkbMenuButton (
value: e . value , onChanged: e . onChanged , child: e . child , ffi: ffi ) )
. toList ( ) ;
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 ,
2024-04-05 04:28:35 +03:00
menuChildrenGetter: ( ) = > [
2024-04-06 06:37:31 +03:00
keyboardMode ( ) ,
2024-04-05 04:28:35 +03:00
localKeyboardType ( ) ,
inputSource ( ) ,
Divider ( ) ,
viewMode ( ) ,
Divider ( ) ,
2024-04-06 06:37:31 +03:00
. . . toolbarToggles ( ) ,
2024-06-27 08:28:05 +03:00
. . . mobileActions ( ) ,
2024-04-05 04:28:35 +03:00
] ) ;
2023-02-23 09:30:29 +03:00
}
2022-09-03 13:19:50 +03:00
2024-04-06 06:37:31 +03:00
keyboardMode ( ) {
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 ) ? ?
2023-10-25 04:20:51 +03:00
kKeyLegacyMode ;
2023-02-23 09:30:29 +03:00
} ( ) , hasData: ( data ) {
final groupValue = data as String ;
2023-09-10 09:14:57 +03:00
List < InputModeMenu > modes = [
2023-10-25 04:20:51 +03:00
InputModeMenu ( key: kKeyLegacyMode , menu: ' Legacy mode ' ) ,
InputModeMenu ( key: kKeyMapMode , menu: ' Map mode ' ) ,
InputModeMenu ( 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-11-29 16:31:27 +03:00
await ffi . inputModel . updateKeyboardMode ( ) ;
2023-02-23 09:30:29 +03:00
}
2024-04-06 06:37:31 +03:00
// If use flutter to grab keys, we can only use one mode.
// Map mode and Legacy mode, at least one of them is supported.
String ? modeOnly ;
2024-08-26 19:00:33 +03:00
// Keep both map and legacy mode on web at the moment.
// TODO: Remove legacy mode after web supports translate mode on web.
if ( isInputSourceFlutter & & isDesktop ) {
2024-04-06 06:37:31 +03:00
if ( bind . sessionIsKeyboardModeSupported (
sessionId: ffi . sessionId , mode: kKeyMapMode ) ) {
modeOnly = kKeyMapMode ;
} else if ( bind . sessionIsKeyboardModeSupported (
sessionId: ffi . sessionId , mode: kKeyLegacyMode ) ) {
modeOnly = kKeyLegacyMode ;
}
}
2023-09-10 09:14:57 +03:00
for ( InputModeMenu 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 ;
}
2023-10-25 04:20:51 +03:00
if ( pi . isWayland & & mode . key ! = kKeyMapMode ) {
2023-03-28 07:10:58 +03:00
continue ;
2023-02-23 09:30:29 +03:00
}
2023-03-28 07:10:58 +03:00
var text = translate ( mode . menu ) ;
2023-10-25 04:20:51 +03:00
if ( mode . key = = kKeyTranslateMode ) {
2023-03-28 07:10:58 +03:00
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
2023-11-28 18:57:48 +03:00
inputSource ( ) {
final supportedInputSource = bind . mainSupportedInputSource ( ) ;
if ( supportedInputSource . isEmpty ) return Offstage ( ) ;
late final List < dynamic > supportedInputSourceList ;
try {
supportedInputSourceList = jsonDecode ( supportedInputSource ) ;
} catch ( e ) {
debugPrint ( ' Failed to decode $ supportedInputSource , $ e ' ) ;
return ;
}
if ( supportedInputSourceList . length < 2 ) return Offstage ( ) ;
final inputSource = stateGlobal . getInputSource ( ) ;
final enabled = ! ffi . ffiModel . viewOnly ;
2023-11-29 09:42:44 +03:00
final children = < Widget > [ Divider ( ) ] ;
children . addAll ( supportedInputSourceList . map ( ( e ) {
final d = e as List < dynamic > ;
return RdoMenuButton < String > (
child: Text ( translate ( d [ 1 ] as String ) ) ,
value: d [ 0 ] as String ,
groupValue: inputSource ,
onChanged: enabled
? ( v ) async {
if ( v ! = null ) {
await stateGlobal . setInputSource ( ffi . sessionId , v ) ;
await ffi . ffiModel . checkDesktopKeyboardMode ( ) ;
2023-11-29 16:31:27 +03:00
await ffi . inputModel . updateKeyboardMode ( ) ;
2023-11-29 06:24:03 +03:00
}
2023-11-29 09:42:44 +03:00
}
: null ,
ffi: ffi ,
) ;
} ) ) ;
return Column ( children: children ) ;
2023-11-28 18:57:48 +03:00
}
2023-09-10 09:14:57 +03:00
viewMode ( ) {
2023-03-17 06:27:22 +03:00
final ffiModel = ffi . ffiModel ;
2023-10-08 16:44:54 +03:00
final enabled = versionCmp ( 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 (
2024-05-18 18:13:54 +03:00
sessionId: ffi . sessionId , value: kOptionToggleViewOnly ) ;
2024-09-10 18:54:59 +03:00
final viewOnly = await bind . sessionGetToggleOption (
sessionId: ffi . sessionId , arg: kOptionToggleViewOnly ) ;
ffiModel . setViewOnly ( id , viewOnly ? ? value ) ;
2023-03-17 06:27:22 +03:00
}
: null ,
ffi: ffi ,
child: Text ( translate ( ' View Mode ' ) ) ) ;
}
2024-06-27 08:28:05 +03:00
mobileActions ( ) {
if ( pi . platform ! = kPeerPlatformAndroid ) return [ ] ;
2024-06-29 06:50:40 +03:00
final enabled = versionCmp ( pi . version , ' 1.2.7 ' ) > = 0 ;
2024-06-27 08:28:05 +03:00
if ( ! enabled ) return [ ] ;
return [
Divider ( ) ,
MenuButton (
child: Text ( translate ( ' Back ' ) ) ,
onPressed: ( ) = > ffi . inputModel . onMobileBack ( ) ,
ffi: ffi ) ,
MenuButton (
child: Text ( translate ( ' Home ' ) ) ,
onPressed: ( ) = > ffi . inputModel . onMobileHome ( ) ,
ffi: ffi ) ,
MenuButton (
child: Text ( translate ( ' Apps ' ) ) ,
onPressed: ( ) = > ffi . inputModel . onMobileApps ( ) ,
ffi: ffi ) ,
MenuButton (
child: Text ( translate ( ' Volume up ' ) ) ,
onPressed: ( ) = > ffi . inputModel . onMobileVolumeUp ( ) ,
ffi: ffi ) ,
MenuButton (
child: Text ( translate ( ' Volume down ' ) ) ,
onPressed: ( ) = > ffi . inputModel . onMobileVolumeDown ( ) ,
ffi: ffi ) ,
MenuButton (
child: Text ( translate ( ' Power ' ) ) ,
onPressed: ( ) = > ffi . inputModel . onMobilePower ( ) ,
ffi: ffi ) ,
] ;
}
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 ,
2024-04-05 04:28:35 +03:00
menuChildrenGetter: ( ) = > [ textChat ( ) , voiceCall ( ) ] ) ;
2023-02-23 09:30:29 +03:00
}
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-10 11:02:47 +03:00
widget . ffi . chatModel . changeCurrentKey (
2023-07-09 14:14:57 +03:00
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 ) {
2024-05-07 11:18:48 +03:00
menuChildrenGetter ( ) {
2024-07-13 23:07:02 +03:00
final audioInput = AudioInput (
builder: ( devices , currentDevice , setDevice ) {
return Column (
children: devices
. map ( ( d ) = > RdoMenuButton < String > (
child: Container (
child: Text (
d ,
overflow: TextOverflow . ellipsis ,
) ,
constraints: BoxConstraints ( maxWidth: 250 ) ,
2024-05-07 11:18:48 +03:00
) ,
2024-07-13 23:07:02 +03:00
value: d ,
groupValue: currentDevice ,
onChanged: ( v ) {
if ( v ! = null ) setDevice ( v ) ;
} ,
ffi: ffi ,
) )
. toList ( ) ,
) ;
} ,
isCm: false ,
isVoiceCall: true ,
) ;
2024-05-07 11:18:48 +03:00
return [
audioInput ,
Divider ( ) ,
MenuButton (
child: Text ( translate ( ' End call ' ) ) ,
onPressed: ( ) = > bind . sessionCloseVoiceCall ( sessionId: ffi . sessionId ) ,
ffi: ffi ,
) ,
] ;
}
2023-02-23 09:30:29 +03:00
return Obx (
( ) {
switch ( ffi . chatModel . voiceCallStatus . value ) {
case VoiceCallStatus . waitingForResponse:
2024-05-07 11:18:48 +03:00
return buildCallWaiting ( context ) ;
2023-02-23 09:30:29 +03:00
case VoiceCallStatus . connected:
2024-05-07 11:18:48 +03:00
return _IconSubmenuButton (
tooltip: ' Voice call ' ,
svg: ' assets/voice_call.svg ' ,
color: _ToolbarTheme . blueColor ,
hoverColor: _ToolbarTheme . hoverBlueColor ,
menuChildrenGetter: menuChildrenGetter ,
ffi: ffi ,
) ;
2023-02-23 09:30:29 +03:00
default :
return Offstage ( ) ;
2022-09-08 10:35:19 +03:00
}
} ,
) ;
2023-02-23 09:30:29 +03:00
}
2022-09-08 10:35:19 +03:00
2024-05-07 11:18:48 +03:00
Widget buildCallWaiting ( BuildContext context ) {
return _IconMenuButton (
assetName: " assets/call_wait.svg " ,
tooltip: " Waiting " ,
onPressed: ( ) = > bind . sessionCloseVoiceCall ( sessionId: ffi . sessionId ) ,
color: _ToolbarTheme . redColor ,
hoverColor: _ToolbarTheme . hoverRedColor ,
) ;
}
}
2024-05-18 18:13:54 +03:00
2023-02-23 09:30:29 +03:00
class _RecordMenu extends StatelessWidget {
2023-08-07 08:31:11 +03:00
const _RecordMenu ( { Key ? key } ) : super ( key: key ) ;
2023-02-23 09:30:29 +03:00
@ override
Widget build ( BuildContext context ) {
2023-08-07 08:31:11 +03:00
var ffi = Provider . of < FfiModel > ( context ) ;
2023-04-12 04:41:13 +03:00
var recordingModel = Provider . of < RecordingModel > ( context ) ;
final visible =
2023-10-18 17:39:28 +03:00
( recordingModel . start | | ffi . permissions [ ' recording ' ] ! = false ) & &
ffi . pi . currentDisplay ! = kAllDisplayValue ;
2023-02-23 09:30:29 +03:00
if ( ! visible ) return Offstage ( ) ;
2023-08-07 08:31:11 +03:00
return _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-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-10-20 04:15:53 +03:00
onPressed: ( ) = > closeConnection ( id: id ) ,
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-08-10 16:01:24 +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-10-31 09:27:27 +03:00
final double ? width ;
2023-02-23 09:30:29 +03:00
const _IconMenuButton ( {
Key ? key ,
this . assetName ,
this . icon ,
2023-08-10 16:01:24 +03:00
required 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-10-31 09:27:27 +03:00
this . width ,
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 ! ,
2023-07-27 15:45:29 +03:00
colorFilter: ColorFilter . mode ( Colors . white , BlendMode . srcIn ) ,
2023-06-11 11:32:22 +03:00
width: _ToolbarTheme . buttonSize ,
height: _ToolbarTheme . buttonSize ,
2023-02-23 09:30:29 +03:00
) ;
2023-07-27 15:45:29 +03:00
var button = SizedBox (
2023-10-31 09:27:27 +03:00
width: widget . width ? ? _ToolbarTheme . buttonSize ,
2023-06-11 11:32:22 +03:00
height: _ToolbarTheme . buttonSize ,
2023-02-23 09:30:29 +03:00
child: MenuItemButton (
2023-09-10 13:31:16 +03:00
style: ButtonStyle (
backgroundColor: MaterialStatePropertyAll ( Colors . transparent ) ,
padding: MaterialStatePropertyAll ( EdgeInsets . zero ) ,
overlayColor: MaterialStatePropertyAll ( Colors . transparent ) ) ,
onHover: ( value ) = > setState ( ( ) {
hover = value ;
} ) ,
onPressed: widget . onPressed ,
child: Tooltip (
message: translate ( widget . tooltip ) ,
child: Material (
type: MaterialType . transparency ,
child: Ink (
decoration: BoxDecoration (
borderRadius:
BorderRadius . circular ( _ToolbarTheme . iconRadius ) ,
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-08-11 10:53:47 +03:00
button = Tooltip (
message: widget . tooltip ,
child: button ,
) ;
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 ;
2024-04-05 04:28:35 +03:00
final List < Widget > Function ( ) menuChildrenGetter ;
2023-02-23 09:30:29 +03:00
final MenuStyle ? menuStyle ;
2024-05-07 11:18:48 +03:00
final FFI ? ffi ;
2023-10-31 09:27:27 +03:00
final double ? width ;
2023-02-23 09:30:29 +03:00
2023-10-31 09:27:27 +03:00
_IconSubmenuButton ( {
Key ? key ,
this . svg ,
this . icon ,
required this . tooltip ,
required this . color ,
required this . hoverColor ,
2024-04-05 04:28:35 +03:00
required this . menuChildrenGetter ,
2024-05-07 11:18:48 +03:00
this . ffi ,
2023-10-31 09:27:27 +03:00
this . menuStyle ,
this . width ,
} ) : super ( key: key ) ;
2023-02-23 09:30:29 +03:00
@ 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 ! ,
2023-07-27 17:19:38 +03:00
colorFilter: ColorFilter . mode ( Colors . white , BlendMode . srcIn ) ,
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-10-31 09:27:27 +03:00
width: widget . width ? ? _ToolbarTheme . buttonSize ,
2023-06-11 11:32:22 +03:00
height: _ToolbarTheme . buttonSize ,
2023-03-10 09:16:18 +03:00
child: SubmenuButton (
2023-12-05 06:34:54 +03:00
menuStyle:
widget . menuStyle ? ? _ToolbarTheme . defaultMenuStyle ( context ) ,
2023-07-09 09:16:52 +03:00
style: _ToolbarTheme . defaultMenuButtonStyle ,
2023-03-10 09:16:18 +03:00
onHover: ( value ) = > setState ( ( ) {
hover = value ;
} ) ,
2023-09-10 09:14:57 +03:00
child: Tooltip (
message: translate ( widget . tooltip ) ,
child: Material (
2023-08-10 16:01:24 +03:00
type: MaterialType . transparency ,
child: Ink (
2023-09-10 09:14:57 +03:00
decoration: BoxDecoration (
borderRadius:
BorderRadius . circular ( _ToolbarTheme . iconRadius ) ,
2023-08-10 16:01:24 +03:00
color: hover ? widget . hoverColor : widget . color ,
2023-09-10 09:14:57 +03:00
) ,
child: icon ) ) ) ,
2024-04-05 04:28:35 +03:00
menuChildren: widget
. menuChildrenGetter ( )
2023-03-10 09:16:18 +03:00
. 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-12-05 06:34:54 +03:00
menuStyle: _ToolbarTheme . defaultMenuStyle ( context ) ,
2023-02-24 11:20:00 +03:00
) ;
}
}
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 ;
2024-05-07 11:18:48 +03:00
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 ,
2024-05-07 11:18:48 +03:00
this . ffi } )
2023-02-23 09:30:29 +03:00
: super ( key: key ) ;
@ override
Widget build ( BuildContext context ) {
return MenuItemButton (
key: key ,
onPressed: onPressed ! = null
? ( ) {
2024-05-07 11:18:48 +03:00
if ( ffi ! = null ) {
_menuDismissCallback ( ffi ! ) ;
}
2023-02-23 09:30:29 +03:00
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 ;
2024-05-07 11:18:48 +03:00
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 ,
2024-05-07 11:18:48 +03:00
this . ffi } )
2023-02-23 09:30:29 +03:00
: super ( key: key ) ;
@ override
Widget build ( BuildContext context ) {
return CheckboxMenuButton (
key: key ,
value: value ,
child: child ,
onChanged: onChanged ! = null
? ( bool ? value ) {
2024-05-07 11:18:48 +03:00
if ( ffi ! = null ) {
_menuDismissCallback ( ffi ! ) ;
}
2023-02-23 09:30:29 +03:00
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 ;
2024-05-07 11:18:48 +03:00
final FFI ? ffi ;
2023-05-19 12:34:39 +03:00
const RdoMenuButton ( {
Key ? key ,
required this . value ,
required this . groupValue ,
required this . child ,
2024-05-07 11:18:48 +03:00
this . ffi ,
2023-05-19 12:34:39 +03:00
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 ) {
2024-05-07 11:18:48 +03:00
if ( ffi ! = null ) {
_menuDismissCallback ( ffi ! ) ;
}
2023-02-23 09:30:29 +03:00
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 ;
2024-06-13 19:28:59 +03:00
final ToolbarState toolbarState ;
2023-10-31 14:15:13 +03:00
final BorderRadius borderRadius ;
2023-07-27 17:19:38 +03:00
final Function ( bool ) setFullscreen ;
final Function ( ) setMinimize ;
2022-12-07 10:13:24 +03:00
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 ,
2024-06-13 19:28:59 +03:00
required this . toolbarState ,
2023-07-27 17:19:38 +03:00
required this . setFullscreen ,
required this . setMinimize ,
2023-10-31 14:15:13 +03:00
required this . borderRadius ,
2022-12-07 10:13:24 +03:00
} ) : 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 ;
2024-06-13 19:28:59 +03:00
RxBool get show = > widget . toolbarState . show ;
2023-05-17 11:55:35 +03:00
@ override
initState ( ) {
super . initState ( ) ;
final confLeft = double . tryParse (
2024-05-18 18:13:54 +03:00
bind . mainGetLocalOption ( key: kOptionRemoteMenubarDragLeft ) ) ;
2023-05-17 11:55:35 +03:00
if ( confLeft = = null ) {
bind . mainSetLocalOption (
2024-05-18 18:13:54 +03:00
key: kOptionRemoteMenubarDragLeft , value: left . toString ( ) ) ;
2023-05-17 11:55:35 +03:00
} else {
left = confLeft ;
}
final confRight = double . tryParse (
2024-05-18 18:13:54 +03:00
bind . mainGetLocalOption ( key: kOptionRemoteMenubarDragRight ) ) ;
2023-05-17 11:55:35 +03:00
if ( confRight = = null ) {
bind . mainSetLocalOption (
2024-05-18 18:13:54 +03:00
key: kOptionRemoteMenubarDragRight , value: right . toString ( ) ) ;
2023-05-17 11:55:35 +03:00
} 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 ) {
2023-07-27 17:19:38 +03:00
final mediaSize = MediaQueryData . fromView ( View . of ( context ) ) . size ;
2022-12-07 10:13:24 +03:00
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 ) ,
) ;
2023-07-27 17:19:38 +03:00
final isFullscreen = stateGlobal . fullscreen ;
2023-07-30 14:12:51 +03:00
const double iconSize = 20 ;
2022-12-07 10:13:24 +03:00
final child = Row (
mainAxisSize: MainAxisSize . min ,
children: [
_buildDraggable ( context ) ,
2023-10-17 09:42:35 +03:00
Obx ( ( ) = > TextButton (
onPressed: ( ) {
widget . setFullscreen ( ! isFullscreen . value ) ;
} ,
child: Tooltip (
message: translate (
isFullscreen . isTrue ? ' Exit Fullscreen ' : ' Fullscreen ' ) ,
child: Icon (
isFullscreen . isTrue
? Icons . fullscreen_exit
: Icons . fullscreen ,
size: iconSize ,
) ,
2023-07-30 14:12:51 +03:00
) ,
2023-10-17 09:42:35 +03:00
) ) ,
2024-06-07 07:59:42 +03:00
if ( ! isMacOS )
Obx ( ( ) = > Offstage (
offstage: isFullscreen . isFalse ,
child: TextButton (
onPressed: ( ) = > widget . setMinimize ( ) ,
child: Tooltip (
message: translate ( ' Minimize ' ) ,
child: Icon (
Icons . remove ,
size: iconSize ,
) ,
2023-10-17 09:42:35 +03:00
) ,
) ,
2024-06-07 07:59:42 +03:00
) ) ,
2022-12-07 10:13:24 +03:00
TextButton (
onPressed: ( ) = > setState ( ( ) {
2024-06-13 19:28:59 +03:00
widget . toolbarState . switchShow ( widget . sessionId ) ;
2022-12-07 10:13:24 +03:00
} ) ,
2023-07-30 14:12:51 +03:00
child: Obx ( ( ( ) = > Tooltip (
2024-06-13 19:28:59 +03:00
message:
translate ( show . isTrue ? ' Hide Toolbar ' : ' Show Toolbar ' ) ,
2023-07-30 14:12:51 +03:00
child: Icon (
2024-06-13 19:28:59 +03:00
show . isTrue ? Icons . expand_less : Icons . expand_more ,
2023-07-30 14:12:51 +03:00
size: iconSize ,
) ,
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-10-31 14:15:13 +03:00
border: Border . all (
2023-12-05 06:34:54 +03:00
color: _ToolbarTheme . borderColor ( context ) ,
2023-10-31 14:15:13 +03:00
width: 1 ,
2023-02-14 15:57:33 +03:00
) ,
2023-10-31 14:15:13 +03:00
borderRadius: widget . borderRadius ,
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
2023-09-10 09:14:57 +03:00
class InputModeMenu {
2023-02-03 05:41:47 +03:00
final String key ;
final String menu ;
2023-09-10 09:14:57 +03:00
InputModeMenu ( { required this . key , required this . menu } ) ;
2023-02-03 05:41:47 +03:00
}
2023-02-23 09:30:29 +03:00
_menuDismissCallback ( FFI ffi ) = > ffi . inputModel . refreshMousePos ( ) ;
2024-05-07 11:18:48 +03:00
Widget _buildPointerTrackWidget ( Widget child , FFI ? ffi ) {
2023-02-23 09:30:29 +03:00
return Listener (
2024-05-07 11:18:48 +03:00
onPointerHover: ( PointerHoverEvent e ) = > {
if ( ffi ! = null ) { ffi . inputModel . lastMousePos = e . position }
} ,
2023-02-23 09:30:29 +03:00
child: MouseRegion (
child: child ,
) ,
) ;
}