Project Overview
This desktop application mimics the macOS interface using modern Flutter capabilities. The implementation leverages Flutter 3.19 with Dart 3.3, incorporating window management and state management solutions.
Core Technologies
- Framework: Flutter 3.19.2 + Dart 3.3.0
- Window Management: window_manager ^0.3.8
- State Management: get ^4.6.6
- Persistent Storage: get_storage ^2.1.1
- Drag-and-Drop Reordering: reorderables ^0.6.0
- Data Visualization: fl_chart ^0.67.0
- System Tray Integration: system_tray ^2.0.3
Key Features
- Multi-level context menus
- Frosted glass visual effects
- Interactive dock with drag-to-reorder capability
- Cascading dock menus
- Customizable themes and backgrounds
- Multi-window routing system
Application Structure
The interface consists of three primary sections: top navigatoin bar, desktop area, and bottom dock menu.
return Scaffold(
key: scaffoldKey,
body: Container(
decoration: applyTheme(),
child: DragToResizeArea(
child: Flex(
direction: Axis.vertical,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
WindowTitlebar(
onMenuPressed: () {
scaffoldKey.currentState?.openEndDrawer();
},
),
Expanded(
child: GestureDetector(
child: Container(
color: Colors.transparent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: GestureDetector(
child: const DesktopArea(),
onSecondaryTapDown: (TapDownDetails details) {
mouseX = details.globalPosition.dx;
mouseY = details.globalPosition.dy;
},
onSecondaryTap: () {
showDesktopIconMenu();
},
),
),
],
),
),
onSecondaryTapDown: (TapDownDetails details) {
mouseX = details.globalPosition.dx;
mouseY = details.globalPosition.dy;
},
onSecondaryTap: () {
showDesktopContextMenu();
},
),
),
settingController.userData['dockStyle'] == 'windows' ?
const WindowsTaskbar()
:
const MacDock()
,
],
),
),
),
endDrawer: Drawer(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0.0)),
width: 300,
child: const PreferencesPanel(),
),
);
Dock Implementation
The dock supports both macOS and Windows 11 visual styles with blur effects.
MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (event) {
setState(() {
activeIndex = index;
});
animationController.forward(from: 0.0);
},
onExit: (event) {
setState(() {
activeIndex = -1;
});
animationController.stop();
},
child: GestureDetector(
onTapDown: (TapDownDetails details) {
clickPositionX = details.globalPosition.dx;
},
onTap: () {
if(item!['submenu'] != null) {
displaySubmenu(item!['submenu']);
}
},
child: ScaleTransition(
alignment: Alignment.bottomCenter,
scale: activeIndex == index ?
animationController.drive(Tween(begin: 1.0, end: 1.5).chain(CurveTween(curve: Curves.easeOutCubic)))
:
Tween(begin: 1.0, end: 1.0).animate(animationController)
,
child: UnconstrainedBox(
child: Stack(
alignment: AlignmentDirectional.topCenter,
children: [
Visibility(
visible: activeIndex == index && !isDragging,
child: Positioned(
top: 0,
child: SizedOverflowBox(
size: Size.zero,
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 1.0),
margin: const EdgeInsets.only(bottom: 20.0),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(3.0),
),
child: Text('${item!['label']}', style: const TextStyle(color: Colors.white, fontSize: 8.0, fontFamily: 'arial')),
),
),
),
),
item!['submenu'] != null ?
renderDockThumbnail(item!['submenu'])
:
SizedBox(
height: 35.0,
width: 35.0,
child: item!['category'] != null && item!['category'] == 'icon' ?
IconTheme(
data: const IconThemeData(color: Colors.white, size: 32.0),
child: item!['graphic'],
)
:
Image.asset('${item!['graphic']}')
,
),
Visibility(
visible: item!['isActive'] != null,
child: Positioned(
bottom: 0,
child: SizedOverflowBox(
size: Size.zero,
child: Container(
margin: const EdgeInsets.only(top: 2.0),
height: 4.0,
width: 4.0,
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: BorderRadius.circular(10.0),
),
),
),
),
),
],
),
),
),
),
)
List dockItems = [
{'label': 'Flutter3.19', 'graphic': 'assets/images/logo.png'},
{'label': 'Safari', 'graphic': 'assets/images/mac/safari.png', 'isActive': true},
{
'label': 'Launchpad',
'graphic': 'assets/images/mac/launchpad.png',
'submenu': [
{'label': 'Podcasts', 'graphic': 'assets/images/mac/podcasts.png'},
{'label': 'Quicktime', 'graphic': 'assets/images/mac/quicktime.png'},
{'label': 'Notes', 'graphic': 'assets/images/mac/notes.png'},
{'label': 'Reminder', 'graphic': 'assets/images/mac/reminders.png'},
{'label': 'Calc', 'graphic': 'assets/images/mac/calculator.png'},
]
},
{'label': 'Appstore', 'graphic': 'assets/images/mac/appstore.png',},
{'label': 'Messages', 'graphic': 'assets/images/mac/messages.png', 'isActive': true},
{'type': 'separator'},
{'label': 'Recycle Bin', 'graphic': 'assets/images/mac/bin.png'},
];
Secondary dock menus utilize showDialog with Positioned widgets for precise placement.
void displaySubmenu(data) {
calculateDockAnchor();
showDialog(
context: context,
barrierColor: Colors.transparent,
builder: (context) {
return Stack(
children: [
Positioned(
top: anchorY - 210,
left: anchorX - 120,
width: 240.0,
height: 210,
child: ClipRRect(
borderRadius: BorderRadius.circular(16.0),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20.0, sigmaY: 20.0),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10.0),
decoration: const BoxDecoration(
backgroundBlendMode: BlendMode.overlay,
color: Colors.white,
),
child: ListView(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 10.0,),
child: Wrap(
runSpacing: 5.0,
spacing: 5.0,
children: List.generate(data.length, (index) {
final item = data[index];
return MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: Column(
children: [
SizedBox(
height: 40.0,
width: 40.0,
child: item!['category'] != null && item!['category'] == 'icon' ?
IconTheme(
data: const IconThemeData(color: Colors.black87, size: 35.0),
child: item!['graphic'],
)
:
Image.asset('${item!['graphic']}')
,
),
SizedBox(
width: 70,
child: Text(item['label'], style: const TextStyle(color: Colors.black87, fontSize: 12.0), maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center,),
)
],
),
onTap: () {
// Handle submenu item selection
},
),
);
}),
),
),
],
),
),
),
),
),
],
);
},
);
}
Desktop Menu System
Vertical arrangement for desktop context menus.
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(10.0),
child: Wrap(
direction: Axis.vertical,
spacing: 5.0,
runSpacing: 5.0,
children: List.generate(menuItems.length, (index) {
final item = menuItems[index];
return MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (event) {
setState(() {
hoverIndex = index;
});
},
onExit: (event) {
setState(() {
hoverIndex = -1;
});
},
child: GestureDetector(
onTapDown: (TapDownDetails details) {
tapX = details.globalPosition.dx;
tapY = details.globalPosition.dy;
},
onTap: () {
if(item!['submenu'] != null) {
showNestedMenu(item!['submenu']);
}else {
openModalWindow(item);
}
},
child: Container(
// Menu item styling
),
),
);
}),
),
);
}
Modal windows are implemented through a custom dialog component.
void openModalWindow(item) async {
if(item!['externalLink'] != null) {
await launchUrl(Uri.parse(item!['externalLink']));
return;
}
Widget windowIcon() {
if(item!['category'] != null && item!['category'] == 'icon') {
return IconTheme(
data: const IconThemeData(size: 16.0),
child: item!['graphic'],
);
}else {
return Image.asset('${item!['graphic']}', height: 16.0, width: 16.0, fit: BoxFit.cover);
}
}
dynamic modalConfig = item!['modalSettings'] ?? {};
navigator?.push(ModalPageRoute(
child: ModalWindow(
title: modalConfig!['header'] ?? Row(
children: [
windowIcon(),
const SizedBox(width: 5.0,),
Text('${item!['label']}',),
],
),
content: modalConfig!['body'] ?? ListView(
padding: const EdgeInsets.all(10.0),
children: [
item!['widgetContent'] ?? const Center(child: Column(children: [Icon(Icons.layers,), Text('Empty~'),],)),
],
),
titlePadding: modalConfig!['headerSpacing'],
backgroundColor: modalConfig!['background'] ?? Colors.white.withOpacity(.85),
overlayColor: modalConfig!['overlay'] ?? Colors.black54,
position: modalConfig!['coordinates'],
width: modalConfig!['width'] ?? 800,
height: modalConfig!['height'] ?? 500,
cornerRadius: modalConfig!['cornerRadius'],
fullScreen: modalConfig!['fullScreen'] ?? false,
resizable: modalConfig!['resizable'] ?? true,
closable: modalConfig!['closable'] ?? true,
customCloseButton: modalConfig!['customCloseBtn'],
closeIcon: modalConfig!['closeSymbol'],
controlColor: modalConfig!['controlColor'],
controlSize: modalConfig!['controlSize'],
movable: modalConfig!['movable'] ?? true,
autoClose: modalConfig!['autoClose'] ?? false,
),
));
}
List menuItems = [
{'label': 'Flutter3.19', 'graphic': 'assets/images/logo.png', 'externalLink': 'https://flutter.dev/'},
{
'label': 'Home', 'graphic': const Icon(Icons.home_outlined), 'category': 'icon',
'widgetContent': const HomePage(),
'modalSettings': {
'fullScreen': true
}
},
{
'label': 'Dashboard', 'graphic': const Icon(Icons.poll_outlined), 'category': 'icon',
'widgetContent': const DashboardView(),
},
{
'label': 'Components',
'graphic': const Icon(Icons.apps),
'category': 'icon',
'submenu': [
{'label': 'Mail', 'graphic': 'assets/images/mac/mail.png'},
{'label': 'Info', 'graphic': 'assets/images/mac/info.png'},
{'label': 'Editor', 'graphic': 'assets/images/mac/scripteditor.png'},
{'label': 'Downloads', 'graphic': const Icon(Icons.download_outlined), 'category': 'icon'},
{'label': 'Bug Reports', 'graphic': const Icon(Icons.bug_report_outlined), 'category': 'icon'},
{'label': 'Calculator', 'graphic': const Icon(Icons.calculate), 'category': 'icon'},
{'label': 'Charts', 'graphic': const Icon(Icons.bar_chart), 'category': 'icon'},
{'label': 'Print', 'graphic': const Icon(Icons.print), 'category': 'icon'},
{'label': 'Messages', 'graphic': const Icon(Icons.campaign), 'category': 'icon'},
{'label': 'Cloud Storage', 'graphic': const Icon(Icons.cloud_outlined), 'category': 'icon'},
{'label': 'Crop Tool', 'graphic': const Icon(Icons.crop_outlined), 'category': 'icon'},
]
},
{
'label': 'Private Zone', 'graphic': const Icon(Icons.camera_outlined), 'category': 'icon',
'widgetContent': const PrivateSection(),
},
{
'label': 'QR Code', 'graphic': const Icon(Icons.qr_code), 'category': 'icon',
'modalSettings': {
'header': const Text('QRcode', style: TextStyle(color: Colors.white60, fontSize: 14.0, fontFamily: 'arial')),
'body': Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset('assets/images/qrcode_white.png', height: 120.0, fit: BoxFit.contain,),
const Spacer(),
const Text('Scan to follow our channel', style: TextStyle(color: Colors.white60, fontSize: 12.0,),),
],
),
),
'background': const Color(0xff07c160),
'controlColor': Colors.white54,
'width': 300,
'height': 220,
'resizable': false,
'closable': true,
'movable': true,
}
},
];
The custom modal window accepts the following configuration parameters:
final Widget? title;
final Widget content;
final EdgeInsetsGeometry? titlePadding;
final Color? backgroundColor;
final Color? overlayColor;
final Offset? position;
final num width;
final num height;
final double? cornerRadius;
final bool fullScreen;
final bool resizable;
final bool closable;
final Widget? customCloseButton;
final IconData? closeIcon;
final Color? controlColor;
final double? controlSize;
final bool movable;
final bool autoClose;