Building a macOS-like Desktop Environment with Flutter 3.x

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;

Tags: Flutter desktop-app window-management getx state-management

Posted on Wed, 02 Sep 2026 16:39:16 +0000 by ravensshade