native style

Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
21pages 2022-08-20 19:57:16 +08:00
parent 4faf0a3d35
commit a10487c840
10 changed files with 603 additions and 376 deletions

BIN
flutter/assets/tabbar.ttf Normal file

Binary file not shown.

View File

@ -8,7 +8,7 @@ import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:get/instance_manager.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:window_manager/window_manager.dart';
@ -38,6 +38,88 @@ final iconAudio = MemoryImage(Uint8List.fromList(base64Decode(
final iconFile = MemoryImage(Uint8List.fromList(base64Decode(
'iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAUVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////8IN+deAAAAGnRSTlMAH+CAESEN8jyZkcIb5N/ONy3vmHhmiGjUm7UwS+YAAAHZSURBVGje7dnbboMwDIBhBwgQoFAO7Ta//4NOqCAXYZQstatq4r+r5ubrgQSpg8iyC4ZURa+PlIpQYGiwrzyeHtYZjAL8T05O4H8BbbKvFgRa4NoBU8pXeYEkDDgaaLQBcwJrmeErJQB/7wes3QBWGnCIX0+AQycL1PO6BMwPa0nA4ZxbgTvOjUYMGPHRnZkQAY4mxPZBjmy53E7ukSkFKYB/D4XsWZQx64sCeYebOogGsoOBYvv6/UCb8F0IOBZ0TlP6lEYdANY350AJqB9/qPVuOI5evw4A1hgLigAlepnyxW80bcCcwN++A2s82Vcu02ta+ceq9BoL5KGTTRwQPlpqA3gCnwWU2kCDgeWRQPj2jAPCDxgCMjhI6uZnToDpvd/BJeFrJQB/fsAa02gCt3mi1wNuy8GgBNDZlysBNNSrADVSjcJl6vCpUn6jOdx0kz0q6PMhQRa4465SFKhx35cgUCBTwj2/NHwZAb71qR8GEP2H1XcmAtBPTEO67GP6FUUAIKGABbDLQ0EArhN2sAIGesRO+iyy+RMAjckVTlMCKFVAbh/4Af9OPgG61SkDVco3BQGT3GXaDAnTIAcYZDuBTwGsAGDxuBFeAQqIqwoFMlAVLrHr/wId5MPt0nilGgAAAABJRU5ErkJggg==')));
class IconFont {
static const _family = 'iconfont';
IconFont._();
static const IconData max = IconData(0xe606, fontFamily: _family);
static const IconData restore = IconData(0xe607, fontFamily: _family);
static const IconData close = IconData(0xe668, fontFamily: _family);
static const IconData min = IconData(0xe609, fontFamily: _family);
static const IconData add = IconData(0xe664, fontFamily: _family);
static const IconData menu = IconData(0xe628, fontFamily: _family);
}
class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
const ColorThemeExtension({
required this.bg,
required this.grayBg,
required this.text,
required this.lightText,
required this.lighterText,
required this.border,
});
final Color? bg;
final Color? grayBg;
final Color? text;
final Color? lightText;
final Color? lighterText;
final Color? border;
static const light = ColorThemeExtension(
bg: Color(0xFFFFFFFF),
grayBg: Color(0xFFEEEEEE),
text: Color(0xFF222222),
lightText: Color(0xFF666666),
lighterText: Color(0xFF888888),
border: Color(0xFFCCCCCC),
);
static const dark = ColorThemeExtension(
bg: Color(0xFF252525),
grayBg: Color(0xFF141414),
text: Color(0xFFFFFFFF),
lightText: Color(0xFF999999),
lighterText: Color(0xFF777777),
border: Color(0xFF555555),
);
@override
ThemeExtension<ColorThemeExtension> copyWith(
{Color? bg,
Color? grayBg,
Color? text,
Color? lightText,
Color? lighterText,
Color? border}) {
return ColorThemeExtension(
bg: bg ?? this.bg,
grayBg: grayBg ?? this.grayBg,
text: text ?? this.text,
lightText: lightText ?? this.lightText,
lighterText: lighterText ?? this.lighterText,
border: border ?? this.border,
);
}
@override
ThemeExtension<ColorThemeExtension> lerp(
ThemeExtension<ColorThemeExtension>? other, double t) {
if (other is! ColorThemeExtension) {
return this;
}
return ColorThemeExtension(
bg: Color.lerp(bg, other.bg, t),
grayBg: Color.lerp(grayBg, other.grayBg, t),
text: Color.lerp(text, other.text, t),
lightText: Color.lerp(lightText, other.lightText, t),
lighterText: Color.lerp(lighterText, other.lighterText, t),
border: Color.lerp(border, other.border, t),
);
}
}
class MyTheme {
MyTheme._();
@ -52,20 +134,37 @@ class MyTheme {
static const Color darkGray = Color(0xFFB9BABC);
static const Color cmIdColor = Color(0xFF21790B);
static const Color dark = Colors.black87;
static const Color disabledTextLight = Color(0xFF888888);
static const Color disabledTextDark = Color(0xFF777777);
static ThemeData lightTheme = ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
tabBarTheme: TabBarTheme(labelColor: Colors.black87),
tabBarTheme: TabBarTheme(
labelColor: Colors.black87,
),
// backgroundColor: Color(0xFFFFFFFF),
).copyWith(
extensions: <ThemeExtension<dynamic>>[
ColorThemeExtension.light,
],
);
static ThemeData darkTheme = ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
tabBarTheme: TabBarTheme(labelColor: Colors.white70));
tabBarTheme: TabBarTheme(
labelColor: Colors.white70,
),
// backgroundColor: Color(0xFF252525)
).copyWith(
extensions: <ThemeExtension<dynamic>>[
ColorThemeExtension.dark,
],
);
static ColorThemeExtension color(BuildContext context) {
return Theme.of(context).extension<ColorThemeExtension>()!;
}
}
bool isDarkTheme() {

View File

@ -1,4 +1,4 @@
const double kDesktopRemoteTabBarHeight = 48.0;
const double kDesktopRemoteTabBarHeight = 28.0;
const String kAppTypeMain = "main";
const String kAppTypeDesktopRemote = "remote";
const String kAppTypeDesktopFileTransfer = "file transfer";

View File

@ -48,15 +48,16 @@ class _DesktopHomePageState extends State<DesktopHomePage>
@override
Widget build(BuildContext context) {
super.build(context);
return Row(
children: [
Flexible(
child: buildServerInfo(context),
flex: 1,
buildServerInfo(context),
VerticalDivider(
width: 1,
thickness: 1,
),
Flexible(
Expanded(
child: buildServerBoard(context),
flex: 4,
),
],
);
@ -66,6 +67,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
return ChangeNotifierProvider.value(
value: gFFI.serverModel,
child: Container(
width: 200,
color: MyTheme.color(context).bg,
child: Column(
children: [
buildTip(context),
@ -78,44 +81,48 @@ class _DesktopHomePageState extends State<DesktopHomePage>
}
buildServerBoard(BuildContext context) {
return Column(
children: [
Expanded(child: ConnectionPage()),
],
return Container(
color: MyTheme.color(context).grayBg,
child: ConnectionPage(),
);
}
buildIDBoard(BuildContext context) {
final model = gFFI.serverModel;
return Container(
margin: EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0),
margin: EdgeInsets.symmetric(horizontal: 16),
height: 52,
child: Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Container(
width: 3,
height: 70,
width: 2,
decoration: BoxDecoration(color: MyTheme.accent),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
padding: const EdgeInsets.only(left: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
Container(
height: 15,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
translate("ID"),
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.w500),
fontSize: 14,
color: MyTheme.color(context).lightText),
),
buildPopupMenu(context)
],
),
GestureDetector(
),
Flexible(
child: GestureDetector(
onDoubleTap: () {
Clipboard.setData(
ClipboardData(text: model.serverId.text));
@ -124,7 +131,15 @@ class _DesktopHomePageState extends State<DesktopHomePage>
child: TextFormField(
controller: model.serverId,
readOnly: true,
)),
decoration: InputDecoration(
border: InputBorder.none,
),
style: TextStyle(
fontSize: 22,
),
).marginOnly(bottom: 5),
),
)
],
),
),
@ -136,7 +151,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Widget buildPopupMenu(BuildContext context) {
var position;
return GestureDetector(
RxBool hover = false.obs;
return InkWell(
onTapDown: (detail) {
final x = detail.globalPosition.dx;
final y = detail.globalPosition.dy;
@ -221,31 +237,57 @@ class _DesktopHomePageState extends State<DesktopHomePage>
onSelectMenu(v);
}
},
child: Icon(Icons.more_vert_outlined));
child: Obx(
() => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(90),
boxShadow: [
BoxShadow(
color: hover.value
? MyTheme.color(context).grayBg!
: MyTheme.color(context).bg!,
spreadRadius: 2)
],
),
child: Center(
child: Icon(
Icons.more_vert_outlined,
size: 20,
color: hover.value
? MyTheme.color(context).text
: MyTheme.color(context).lightText,
),
),
),
),
onHover: (value) => hover.value = value,
);
}
buildPasswordBoard(BuildContext context) {
final model = gFFI.serverModel;
RxBool refreshHover = false.obs;
return Container(
margin: EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0),
margin: EdgeInsets.symmetric(vertical: 12, horizontal: 16.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Container(
width: 3,
height: 70,
width: 2,
height: 52,
decoration: BoxDecoration(color: MyTheme.accent),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
padding: const EdgeInsets.only(left: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
translate("Password"),
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
style: TextStyle(
fontSize: 14, color: MyTheme.color(context).lightText),
),
Row(
children: [
@ -262,12 +304,25 @@ class _DesktopHomePageState extends State<DesktopHomePage>
child: TextFormField(
controller: model.serverPasswd,
readOnly: true,
decoration: InputDecoration(
border: InputBorder.none,
),
style: TextStyle(fontSize: 15),
),
),
),
IconButton(
icon: Icon(Icons.refresh),
onPressed: () => bind.mainUpdateTemporaryPassword(),
InkWell(
child: Obx(
() => Icon(
Icons.refresh,
color: refreshHover.value
? MyTheme.color(context).text
: Color(0xFFDDDDDD),
size: 22,
).marginOnly(right: 5),
),
onTap: () => bind.mainUpdateTemporaryPassword(),
onHover: (value) => refreshHover.value = value,
),
FutureBuilder<Widget>(
future: buildPasswordPopupMenu(context),
@ -282,7 +337,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
}
})
],
),
).marginOnly(bottom: 20),
],
),
),
@ -294,7 +349,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Future<Widget> buildPasswordPopupMenu(BuildContext context) async {
var position;
return GestureDetector(
RxBool editHover = false.obs;
return InkWell(
onTapDown: (detail) {
final x = detail.globalPosition.dx;
final y = detail.globalPosition.dy;
@ -365,7 +421,12 @@ class _DesktopHomePageState extends State<DesktopHomePage>
setPasswordDialog();
}
},
child: Icon(Icons.edit));
onHover: (value) => editHover.value = value,
child: Obx(() => Icon(Icons.edit,
size: 22,
color: editHover.value
? MyTheme.color(context).text
: Color(0xFFDDDDDD))));
}
buildTip(BuildContext context) {
@ -377,7 +438,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
children: [
Text(
translate("Your Desktop"),
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
style: TextStyle(fontWeight: FontWeight.normal, fontSize: 19),
),
SizedBox(
height: 8.0,
@ -385,7 +446,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Text(
translate("desk_tip"),
overflow: TextOverflow.clip,
style: TextStyle(fontSize: 14),
style: TextStyle(
fontSize: 12, color: MyTheme.color(context).lighterText),
)
],
),
@ -394,13 +456,17 @@ class _DesktopHomePageState extends State<DesktopHomePage>
buildControlPanel(BuildContext context) {
return Container(
width: 320,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10), color: MyTheme.white),
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(translate("Control Remote Desktop")),
Text(
translate("Control Remote Desktop"),
style: TextStyle(fontWeight: FontWeight.normal, fontSize: 19),
),
Form(
child: Column(
children: [
@ -409,6 +475,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r"[0-9]"))
],
style: TextStyle(fontSize: 22, fontWeight: FontWeight.w400),
)
],
))

View File

@ -71,6 +71,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: Row(
children: <Widget>[
Container(
@ -88,6 +89,8 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
),
const VerticalDivider(thickness: 1, width: 1),
Expanded(
child: Container(
color: MyTheme.color(context).grayBg,
child: PageView(
controller: controller,
children: [
@ -99,6 +102,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
_About(),
],
),
),
)
],
),
@ -269,8 +273,8 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
AbsorbPointer(
absorbing: locked,
child: Column(children: [
permissions(),
password(),
permissions(context),
password(context),
whitelist(),
]),
),
@ -280,24 +284,26 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
).marginOnly(bottom: _kListViewBottomMargin);
}
Widget permissions() {
Widget permissions(context) {
bool enabled = !locked;
return _Card(title: 'Permissions', children: [
_OptionCheckBox('Enable Keyboard/Mouse', 'enable-keyboard',
_OptionCheckBox(context, 'Enable Keyboard/Mouse', 'enable-keyboard',
enabled: enabled),
_OptionCheckBox('Enable Clipboard', 'enable-clipboard', enabled: enabled),
_OptionCheckBox('Enable File Transfer', 'enable-file-transfer',
_OptionCheckBox(context, 'Enable Clipboard', 'enable-clipboard',
enabled: enabled),
_OptionCheckBox('Enable Audio', 'enable-audio', enabled: enabled),
_OptionCheckBox('Enable Remote Restart', 'enable-remote-restart',
_OptionCheckBox(context, 'Enable File Transfer', 'enable-file-transfer',
enabled: enabled),
_OptionCheckBox('Enable remote configuration modification',
_OptionCheckBox(context, 'Enable Audio', 'enable-audio',
enabled: enabled),
_OptionCheckBox(context, 'Enable Remote Restart', 'enable-remote-restart',
enabled: enabled),
_OptionCheckBox(context, 'Enable remote configuration modification',
'allow-remote-config-modification',
enabled: enabled),
]);
}
Widget password() {
Widget password(BuildContext context) {
return ChangeNotifierProvider.value(
value: gFFI.serverModel,
child: Consumer<ServerModel>(builder: ((context, model, child) {
@ -316,6 +322,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
String currentValue = values[keys.indexOf(model.verificationMethod)];
List<Widget> radios = values
.map((value) => _Radio<String>(
context,
value: value,
groupValue: currentValue,
label: value,
@ -343,7 +350,8 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
Text(
value,
style: TextStyle(
color: _disabledTextColor(onChanged != null)),
color: _disabledTextColor(
context, onChanged != null)),
),
],
).paddingSymmetric(horizontal: 10),
@ -408,7 +416,7 @@ class _ConnectionState extends State<_Connection>
_Button('ID/Relay Server', changeServer, enabled: enabled),
]),
_Card(title: 'Service', children: [
_OptionCheckBox('Enable Service', 'stop-service',
_OptionCheckBox(context, 'Enable Service', 'stop-service',
reverse: true, enabled: enabled),
// TODO: Not implemented
// _option_check('Always connected via relay', 'allow-always-relay', enabled: enabled),
@ -416,10 +424,11 @@ class _ConnectionState extends State<_Connection>
// reverse: true, enabled: enabled),
]),
_Card(title: 'TCP Tunneling', children: [
_OptionCheckBox('Enable TCP Tunneling', 'enable-tunnel',
_OptionCheckBox(
context, 'Enable TCP Tunneling', 'enable-tunnel',
enabled: enabled),
]),
direct_ip(),
direct_ip(context),
_Card(title: 'Proxy', children: [
_Button('Socks5 Proxy', changeSocks5Proxy, enabled: enabled),
]),
@ -430,12 +439,12 @@ class _ConnectionState extends State<_Connection>
]).marginOnly(bottom: _kListViewBottomMargin);
}
Widget direct_ip() {
Widget direct_ip(BuildContext context) {
TextEditingController controller = TextEditingController();
var update = () => setState(() {});
RxBool apply_enabled = false.obs;
return _Card(title: 'Direct IP Access', children: [
_OptionCheckBox('Enable Direct IP Access', 'direct-server',
_OptionCheckBox(context, 'Enable Direct IP Access', 'direct-server',
update: update, enabled: !locked),
_futureBuilder(
future: () async {
@ -509,7 +518,7 @@ class _DisplayState extends State<_Display> with AutomaticKeepAliveClientMixin {
return ListView(
children: [
_Card(title: 'Adaptive Bitrate', children: [
_OptionCheckBox('Adaptive Bitrate', 'enable-abr'),
_OptionCheckBox(context, 'Adaptive Bitrate', 'enable-abr'),
]),
hwcodec(),
],
@ -523,7 +532,8 @@ class _DisplayState extends State<_Display> with AutomaticKeepAliveClientMixin {
return Offstage(
offstage: !(data as bool),
child: _Card(title: 'Hardware Codec', children: [
_OptionCheckBox('Enable hardware codec', 'enable-hwcodec'),
_OptionCheckBox(
context, 'Enable hardware codec', 'enable-hwcodec'),
]),
);
});
@ -592,6 +602,7 @@ class _AudioState extends State<_Audio> with AutomaticKeepAliveClientMixin {
);
deviceWidget.addAll([
_Radio<_AudioInputType>(
context,
value: _AudioInputType.Specify,
groupValue: groupValue,
label: 'Specify device',
@ -606,6 +617,7 @@ class _AudioState extends State<_Audio> with AutomaticKeepAliveClientMixin {
}
return Column(children: [
_Radio<_AudioInputType>(
context,
value: _AudioInputType.Mute,
groupValue: groupValue,
label: 'Mute',
@ -615,6 +627,7 @@ class _AudioState extends State<_Audio> with AutomaticKeepAliveClientMixin {
},
),
_Radio(
context,
value: _AudioInputType.Standard,
groupValue: groupValue,
label: 'Use standard device',
@ -743,15 +756,11 @@ Widget _Card({required String title, required List<Widget> children}) {
);
}
Color? _disabledTextColor(bool enabled) {
return enabled
? null
: isDarkTheme()
? MyTheme.disabledTextDark
: MyTheme.disabledTextLight;
Color? _disabledTextColor(BuildContext context, bool enabled) {
return enabled ? null : MyTheme.color(context).lighterText;
}
Widget _OptionCheckBox(String label, String key,
Widget _OptionCheckBox(BuildContext context, String label, String key,
{Function()? update = null, bool reverse = false, bool enabled = true}) {
return _futureBuilder(
future: bind.mainGetOption(key: key),
@ -778,7 +787,7 @@ Widget _OptionCheckBox(String label, String key,
Expanded(
child: Text(
translate(label),
style: TextStyle(color: _disabledTextColor(enabled)),
style: TextStyle(color: _disabledTextColor(context, enabled)),
))
],
),
@ -790,7 +799,7 @@ Widget _OptionCheckBox(String label, String key,
});
}
Widget _Radio<T>(
Widget _Radio<T>(BuildContext context,
{required T value,
required T groupValue,
required String label,
@ -811,7 +820,7 @@ Widget _Radio<T>(
child: Text(translate(label),
style: TextStyle(
fontSize: _kContentFontSize,
color: _disabledTextColor(enabled)))
color: _disabledTextColor(context, enabled)))
.marginOnly(left: 5),
),
],

View File

@ -30,7 +30,11 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
return Container(
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: Column(
children: [
DesktopTabBar(
@ -55,6 +59,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
))),
],
),
),
);
}

View File

@ -98,6 +98,7 @@ class _FileManagerPageState extends State<FileManagerPage>
return false;
},
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: Row(
children: [
Flexible(flex: 3, child: body(isLocal: true)),

View File

@ -181,10 +181,11 @@ class _RemotePageState extends State<RemotePage>
_ffi.inputKey(label, down: down, press: press ?? false);
}
Widget buildBody(FfiModel ffiModel) {
Widget buildBody(BuildContext context, FfiModel ffiModel) {
final hasDisplays = ffiModel.pi.displays.length > 0;
final keyboard = ffiModel.permissions['keyboard'] != false;
return Scaffold(
backgroundColor: MyTheme.color(context).bg,
// resizeToAvoidBottomInset: true,
floatingActionButton: _showBar
? null
@ -229,7 +230,8 @@ class _RemotePageState extends State<RemotePage>
ChangeNotifierProvider.value(value: _ffi.canvasModel),
],
child: Consumer<FfiModel>(
builder: (context, ffiModel, _child) => buildBody(ffiModel))));
builder: (context, ffiModel, _child) =>
buildBody(context, ffiModel))));
}
Widget getRawPointerAndKeyBody(Widget child) {

View File

@ -13,7 +13,7 @@ import 'package:scroll_pos/scroll_pos.dart';
const double _kTabBarHeight = kDesktopRemoteTabBarHeight;
const double _kIconSize = 18;
const double _kDividerIndent = 10;
const double _kAddIconSize = _kTabBarHeight - 15;
const double _kActionIconSize = 12;
final _tabBarKey = GlobalKey();
void closeTab(String? id) {
@ -79,6 +79,10 @@ class DesktopTabBar extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
height: _kTabBarHeight,
child: Column(
children: [
Container(
height: _kTabBarHeight - 1,
child: Row(
children: [
Expanded(
@ -87,9 +91,19 @@ class DesktopTabBar extends StatelessWidget {
Offstage(
offstage: !mainTab,
child: Row(children: [
Image.asset('assets/logo.ico'),
Text("RustDesk").paddingOnly(left: 5),
]).paddingSymmetric(horizontal: 12, vertical: 5),
Image.asset(
'assets/logo.ico',
width: 20,
height: 20,
),
Text(
"RustDesk",
style: TextStyle(fontSize: 13),
).marginOnly(left: 2),
]).marginOnly(
left: 5,
right: 10,
),
),
Expanded(
child: GestureDetector(
@ -121,23 +135,27 @@ class DesktopTabBar extends StatelessWidget {
),
Offstage(
offstage: onAddSetting == null,
child: Tooltip(
message: translate("Settings"),
child: InkWell(
child: Icon(
Icons.menu,
color: _theme.unSelectedIconColor,
),
child: _ActionIcon(
message: 'Settings',
icon: IconFont.menu,
theme: _theme,
onTap: () => onAddSetting?.call(),
).paddingOnly(right: 10),
is_close: false,
),
),
WindowActionPanel(
mainTab: mainTab,
color: _theme.unSelectedIconColor,
theme: _theme,
)
],
),
),
Divider(
height: 1,
thickness: 1,
),
],
),
);
}
@ -156,23 +174,20 @@ class DesktopTabBar extends StatelessWidget {
class WindowActionPanel extends StatelessWidget {
final bool mainTab;
final Color color;
final _Theme theme;
const WindowActionPanel(
{Key? key, required this.mainTab, required this.color})
{Key? key, required this.mainTab, required this.theme})
: super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [
Tooltip(
message: translate("Minimize"),
child: InkWell(
child: Icon(
Icons.minimize,
color: color,
).paddingSymmetric(horizontal: 5),
_ActionIcon(
message: 'Minimize',
icon: IconFont.min,
theme: theme,
onTap: () {
if (mainTab) {
windowManager.minimize();
@ -180,45 +195,50 @@ class WindowActionPanel extends StatelessWidget {
WindowController.fromWindowId(windowId!).minimize();
}
},
is_close: false,
),
),
Tooltip(
message: translate("Maximize"),
child: InkWell(
child: Icon(
Icons.rectangle_outlined,
color: color,
size: 20,
).paddingSymmetric(horizontal: 5),
onTap: () {
FutureBuilder(builder: (context, snapshot) {
RxBool is_maximized = false.obs;
if (mainTab) {
windowManager.isMaximized().then((maximized) {
if (maximized) {
windowManager.unmaximize();
} else {
windowManager.maximize();
}
is_maximized.value = maximized;
});
} else {
final wc = WindowController.fromWindowId(windowId!);
wc.isMaximized().then((maximized) {
if (maximized) {
is_maximized.value = maximized;
});
}
return Obx(
() => _ActionIcon(
message: is_maximized.value ? "Restore" : "Maximize",
icon: is_maximized.value ? IconFont.restore : IconFont.max,
theme: theme,
onTap: () {
if (mainTab) {
if (is_maximized.value) {
windowManager.unmaximize();
} else {
windowManager.maximize();
}
} else {
final wc = WindowController.fromWindowId(windowId!);
if (is_maximized.value) {
wc.unmaximize();
} else {
wc.maximize();
}
});
}
is_maximized.value = !is_maximized.value;
},
is_close: false,
),
),
Tooltip(
message: translate("Close"),
child: InkWell(
child: Icon(
Icons.close,
color: color,
).paddingSymmetric(horizontal: 5),
);
}),
_ActionIcon(
message: 'Close',
icon: IconFont.close,
theme: theme,
onTap: () {
if (mainTab) {
windowManager.close();
@ -226,15 +246,16 @@ class WindowActionPanel extends StatelessWidget {
WindowController.fromWindowId(windowId!).close();
}
},
is_close: true,
),
)
],
);
}
}
// ignore: must_be_immutable
class _ListView extends StatelessWidget {
late Rx<PageController> controller;
final Rx<PageController> controller;
final ScrollPosController scrollController;
final RxList<TabInfo> tabInfos;
final Rx<int> selected;
@ -327,9 +348,7 @@ class _Tab extends StatelessWidget {
Widget build(BuildContext context) {
bool is_selected = index == selected;
bool show_divider = index != selected - 1 && index != selected;
return Stack(
children: [
Ink(
return Ink(
child: InkWell(
onHover: (hover) => _hover.value = hover,
onTap: () => onSelected(),
@ -383,18 +402,6 @@ class _Tab extends StatelessWidget {
],
),
),
),
Positioned(
height: 2,
left: 0,
right: 0,
bottom: 0,
child: Center(
child: Container(
color:
is_selected ? theme.indicatorColor : Colors.transparent),
))
],
);
}
}
@ -409,19 +416,13 @@ class _AddButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Ink(
height: _kTabBarHeight,
child: InkWell(
customBorder: const CircleBorder(),
return _ActionIcon(
message: 'New Connection',
icon: IconFont.add,
theme: theme,
onTap: () =>
rustDeskWinManager.call(WindowType.Main, "main_window_on_top", ""),
child: Icon(
Icons.add_sharp,
size: _kAddIconSize,
color: theme.unSelectedIconColor,
),
),
);
is_close: false);
}
}
@ -460,6 +461,46 @@ class _CloseButton extends StatelessWidget {
}
}
class _ActionIcon extends StatelessWidget {
final String message;
final IconData icon;
final _Theme theme;
final Function() onTap;
final bool is_close;
const _ActionIcon({
Key? key,
required this.message,
required this.icon,
required this.theme,
required this.onTap,
required this.is_close,
}) : super(key: key);
@override
Widget build(BuildContext context) {
RxBool hover = false.obs;
return Obx(() => Tooltip(
message: translate(message),
child: InkWell(
hoverColor: is_close ? Colors.red : theme.hoverColor,
onHover: (value) => hover.value = value,
child: Container(
height: _kTabBarHeight - 1,
width: _kTabBarHeight - 1,
child: Icon(
icon,
color: hover.value && is_close
? Colors.white
: theme.unSelectedIconColor,
size: _kActionIconSize,
),
),
onTap: onTap,
),
));
}
}
class _Theme {
late Color unSelectedtabIconColor;
late Color selectedtabIconColor;
@ -468,7 +509,7 @@ class _Theme {
late Color selectedIconColor;
late Color unSelectedIconColor;
late Color dividerColor;
late Color indicatorColor;
late Color hoverColor;
_Theme.light() {
unSelectedtabIconColor = Color.fromARGB(255, 162, 203, 241);
@ -478,7 +519,7 @@ class _Theme {
selectedIconColor = Color.fromARGB(255, 26, 26, 26);
unSelectedIconColor = Color.fromARGB(255, 96, 96, 96);
dividerColor = Color.fromARGB(255, 238, 238, 238);
indicatorColor = MyTheme.accent;
hoverColor = Colors.grey.withOpacity(0.2);
}
_Theme.dark() {
@ -489,6 +530,6 @@ class _Theme {
selectedIconColor = Color.fromARGB(255, 215, 215, 215);
unSelectedIconColor = Color.fromARGB(255, 255, 255, 255);
dividerColor = Color.fromARGB(255, 64, 64, 64);
indicatorColor = MyTheme.accent;
hoverColor = Colors.black26;
}
}

View File

@ -108,6 +108,9 @@ flutter:
- family: GestureIcons
fonts:
- asset: assets/gestures.ttf
- family: IconFont
fonts:
- asset: assets/tabbar.ttf
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.