From fa87b3e6fd2b73ceaa25098b223eccf3e608c38b Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 2 Dec 2022 21:55:47 +0100 Subject: [PATCH 01/21] example: add menu for macOS actions This commit replaces the unwieldy row containing macOS actions with a button that opens a dialog and shows a sleek menu that features a search function and displays descriptions of actions that warrant one. --- example/lib/main.dart | 375 +++++++++++------- .../action_list/action_list.dart | 46 +++ .../action_list/action_list_item.dart | 78 ++++ .../description_display.dart | 39 ++ .../macos_action_menu/macos_action_menu.dart | 127 ++++++ 5 files changed, 518 insertions(+), 147 deletions(-) create mode 100644 example/lib/widgets/macos_action_menu/action_list/action_list.dart create mode 100644 example/lib/widgets/macos_action_menu/action_list/action_list_item.dart create mode 100644 example/lib/widgets/macos_action_menu/description_display.dart create mode 100644 example/lib/widgets/macos_action_menu/macos_action_menu.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 051038d..fac76ad 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_acrylic/flutter_acrylic.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:flutter_acrylic/widgets/visual_effect_subview_container/visual_effect_subview_container.dart'; +import 'package:flutter_acrylic_example/widgets/macos_action_menu/macos_action_menu.dart'; import 'package:flutter_acrylic_example/widgets/sidebar_frame/sidebar_frame.dart'; Future main() async { @@ -362,160 +363,240 @@ class MyAppBodyState extends State { return const SizedBox(); } - return Column(children: [ - Padding( - padding: const EdgeInsets.only(bottom: 4.0, top: 12.0), - child: Text('macOS actions:', + return Padding( + padding: const EdgeInsets.only(bottom: 8.0, top: 12.0, left: 12.0), + child: Row( + children: [ + Text( + 'macOS actions:', style: TextStyle( fontSize: 16.0, color: brightness.getForegroundColor(context), fontWeight: FontWeight.bold, - )), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - ['Set Document Edited', () => Window.setDocumentEdited()], - ['Set Document Unedited', () => Window.setDocumentUnedited()], - [ - 'Set Represented Filename', - () => Window.setRepresentedFilename('filename') - ], - ['Set Represented Url', () => Window.setRepresentedUrl('url')], - ['Hide Title', () => Window.hideTitle()], - ['Show Title', () => Window.showTitle()], - [ - 'Make Titlebar Transparent', - () => Window.makeTitlebarTransparent() - ], - ['Make Titlebar Opaque', () => Window.makeTitlebarOpaque()], - [ - 'Enable Full Size Content View', - () => Window.enableFullSizeContentView() - ], - [ - 'Disable Full Size Content View', - () => Window.disableFullSizeContentView() - ], - ['Zoom Window', () => Window.zoomWindow()], - ['Unzoom Window', () => Window.unzoomWindow()], - ['Hide Zoom Button', () => Window.hideZoomButton()], - ['Show Zoom Button', () => Window.showZoomButton()], - ['Hide Miniaturize Button', () => Window.hideMiniaturizeButton()], - ['Show Miniaturize Button', () => Window.showMiniaturizeButton()], - ['Hide Close Button', () => Window.hideCloseButton()], - ['Show Close Button', () => Window.showCloseButton()], - ['Enable Zoom Button', () => Window.enableZoomButton()], - ['Disable Zoom Button', () => Window.disableZoomButton()], - [ - 'Enable Miniaturize Button', - () => Window.enableMiniaturizeButton() - ], - [ - 'Disable Miniaturize Button', - () => Window.disableMiniaturizeButton() - ], - ['Enable Close Button', () => Window.enableCloseButton()], - ['Disable Close Button', () => Window.disableCloseButton()], - [ - 'Set Window Alpha Value to 0.5', - () => Window.setWindowAlphaValue(0.5) - ], - [ - 'Set Window Alpha Value to 0.75', - () => Window.setWindowAlphaValue(0.75) - ], - [ - 'Set Window Alpha Value to 1.0', - () => Window.setWindowAlphaValue(1.0) - ], - [ - 'Set Window Background Color to Default Color', - () => Window.setWindowBackgroundColorToDefaultColor() - ], - [ - 'Set Window Background Color to Clear', - () => Window.setWindowBackgroundColorToClear() - ], - [ - 'Set Blur View State to Active', - () { - setState(() { - macOSBlurViewState = MacOSBlurViewState.active; - }); - return Window.setBlurViewState(MacOSBlurViewState.active); - } - ], - [ - 'Set Blur View State to Inactive', - () { - setState(() { - macOSBlurViewState = MacOSBlurViewState.inactive; - }); - return Window.setBlurViewState(MacOSBlurViewState.inactive); - } - ], - [ - 'Set Blur View State to Follows Window Active State', - () { - setState(() { - macOSBlurViewState = - MacOSBlurViewState.followsWindowActiveState; + ), + ), + const SizedBox(width: 16.0), + OutlinedButton( + child: Text('show all actions'), + onPressed: () { + showDialog( + context: context, + builder: (_) { + return Theme( + data: brightness.getIsDark(context) + ? ThemeData.dark() + : ThemeData.light(), + child: LayoutBuilder(builder: (_, constraints) { + return Center( + child: SizedBox( + width: min(512.0, constraints.maxWidth - 32.0), + height: constraints.maxHeight - 32.0, + child: MacOSActionMenu( + items: [ + MacOSActionMenuItem( + name: 'Set Document Edited', + function: () => Window.setDocumentEdited(), + description: + 'This will change the appearance of the close button on the titlebar.', + ), + MacOSActionMenuItem( + name: 'Set Document Unedited', + function: () => + Window.setDocumentUnedited()), + MacOSActionMenuItem( + name: 'Set Represented Filename', + function: () => + Window.setRepresentedFilename( + 'filename')), + MacOSActionMenuItem( + name: 'Set Represented URL', + function: () => + Window.setRepresentedUrl('url')), + MacOSActionMenuItem( + name: 'Hide Title', + function: () => Window.hideTitle()), + MacOSActionMenuItem( + name: 'Show Title', + function: () => Window.showTitle()), + MacOSActionMenuItem( + name: 'Make Titlebar Transparent', + function: () => + Window.makeTitlebarTransparent()), + MacOSActionMenuItem( + name: 'Make Titlebar Opaque', + function: () => + Window.makeTitlebarOpaque()), + MacOSActionMenuItem( + name: 'Enable Full Size Content View', + function: () => + Window.enableFullSizeContentView(), + description: + 'This expands the area that Flutter can draw to to fill the entire window. It is recommended to enable the full-size content view when making the titlebar transparent.'), + MacOSActionMenuItem( + name: 'Disable Full Size Content View', + function: () => + Window.disableFullSizeContentView()), + MacOSActionMenuItem( + name: 'Zoom Window', + function: () => Window.zoomWindow()), + MacOSActionMenuItem( + name: 'Unzoom Window', + function: () => Window.unzoomWindow()), + MacOSActionMenuItem( + name: 'Hide Zoom Button', + function: () => Window.hideZoomButton()), + MacOSActionMenuItem( + name: 'Show Zoom Button', + function: () => Window.showZoomButton()), + MacOSActionMenuItem( + name: 'Hide Miniaturize Button', + function: () => + Window.hideMiniaturizeButton()), + MacOSActionMenuItem( + name: 'Show Miniaturize Button', + function: () => + Window.showMiniaturizeButton()), + MacOSActionMenuItem( + name: 'Hide Close Button', + function: () => Window.hideCloseButton()), + MacOSActionMenuItem( + name: 'Show Close Button', + function: () => Window.showCloseButton()), + MacOSActionMenuItem( + name: 'Enable Zoom Button', + function: () => Window.enableZoomButton()), + MacOSActionMenuItem( + name: 'Disable Zoom Button', + function: () => Window.disableZoomButton()), + MacOSActionMenuItem( + name: 'Enable Miniaturize Button', + function: () => + Window.enableMiniaturizeButton()), + MacOSActionMenuItem( + name: 'Disable Miniaturize Button', + function: () => + Window.disableMiniaturizeButton()), + MacOSActionMenuItem( + name: 'Enable Close Button', + function: () => Window.enableCloseButton()), + MacOSActionMenuItem( + name: 'Disable Close Button', + function: () => + Window.disableCloseButton()), + MacOSActionMenuItem( + name: 'Set Window Alpha Value to 0.5', + function: () => + Window.setWindowAlphaValue(0.5)), + MacOSActionMenuItem( + name: 'Set Window Alpha Value to 0.75', + function: () => + Window.setWindowAlphaValue(0.75)), + MacOSActionMenuItem( + name: 'Set Window Alpha Value to 1.0', + function: () => + Window.setWindowAlphaValue(1.0)), + MacOSActionMenuItem( + name: + 'Set Window Background Color to Default Color', + function: () => Window + .setWindowBackgroundColorToDefaultColor(), + description: + 'Sets the window background color to the default (opaque) window color.'), + MacOSActionMenuItem( + name: + 'Set Window Background Color to Clear', + function: () => Window + .setWindowBackgroundColorToClear()), + MacOSActionMenuItem( + name: 'Set Blur View State to Active', + function: () { + setState(() { + macOSBlurViewState = + MacOSBlurViewState.active; + }); + Window.setBlurViewState( + MacOSBlurViewState.active); + }), + MacOSActionMenuItem( + name: 'Set Blur View State to Inactive', + function: () { + setState(() { + macOSBlurViewState = + MacOSBlurViewState.inactive; + }); + Window.setBlurViewState( + MacOSBlurViewState.inactive); + }), + MacOSActionMenuItem( + name: + 'Set Blur View State to Follows Window Active State', + function: () { + setState(() { + macOSBlurViewState = MacOSBlurViewState + .followsWindowActiveState; + }); + Window.setBlurViewState(MacOSBlurViewState + .followsWindowActiveState); + }), + MacOSActionMenuItem( + name: 'Add Toolbar', + function: () => Window.addToolbar(), + ), + MacOSActionMenuItem( + name: 'Remove Toolbar', + function: () => Window.removeToolbar(), + ), + MacOSActionMenuItem( + name: 'Set Toolbar Style to Automatic', + function: () => Window.setToolbarStyle( + toolbarStyle: + MacOSToolbarStyle.automatic), + description: + 'For this method to have an effect, the window needs to have had a toolbar added beforehand.', + ), + MacOSActionMenuItem( + name: 'Set Toolbar Style to Expanded', + function: () => Window.setToolbarStyle( + toolbarStyle: MacOSToolbarStyle.expanded, + ), + description: + 'For this method to have an effect, the window needs to have had a toolbar added beforehand.', + ), + MacOSActionMenuItem( + name: 'Set Toolbar Style to Preference', + function: () => Window.setToolbarStyle( + toolbarStyle: + MacOSToolbarStyle.preference), + description: + 'For this method to have an effect, the window needs to have had a toolbar added beforehand.', + ), + MacOSActionMenuItem( + name: 'Set Toolbar Style to Unified', + function: () => Window.setToolbarStyle( + toolbarStyle: MacOSToolbarStyle.unified), + description: + 'For this method to have an effect, the window needs to have had a toolbar added beforehand.', + ), + MacOSActionMenuItem( + name: 'Set Toolbar Style to Unified Compact', + function: () => Window.setToolbarStyle( + toolbarStyle: + MacOSToolbarStyle.unifiedCompact), + description: + 'For this method to have an effect, the window needs to have had a toolbar added beforehand.', + ), + ], + ), + ), + ); + }), + ); }); - return Window.setBlurViewState( - MacOSBlurViewState.followsWindowActiveState); - } - ], - [ - 'Add Toolbar', - () => Window.addToolbar(), - ], - [ - 'Remove Toolbar', - () => Window.removeToolbar(), - ], - [ - 'Set Toolbar Style to Automatic', - () => Window.setToolbarStyle( - toolbarStyle: MacOSToolbarStyle.automatic), - ], - [ - 'Set Toolbar Style to Expanded', - () => Window.setToolbarStyle( - toolbarStyle: MacOSToolbarStyle.expanded), - ], - [ - 'Set Toolbar Style to Preference', - () => Window.setToolbarStyle( - toolbarStyle: MacOSToolbarStyle.preference), - ], - [ - 'Set Toolbar Style to Unified', - () => Window.setToolbarStyle( - toolbarStyle: MacOSToolbarStyle.unified), - ], - [ - 'Set Toolbar Style to Unified Compact', - () => Window.setToolbarStyle( - toolbarStyle: MacOSToolbarStyle.unifiedCompact), - ], - ] - .map((e) => MaterialButton( - child: Text( - e[0] as String, - style: TextStyle( - color: brightness.getForegroundColor(context)), - ), - onPressed: e[1] as void Function(), - )) - .toList(), + }, ), - ), + ], ), - ]); + ); } SingleChildScrollView generateEffectMenu(BuildContext context) { diff --git a/example/lib/widgets/macos_action_menu/action_list/action_list.dart b/example/lib/widgets/macos_action_menu/action_list/action_list.dart new file mode 100644 index 0000000..c3e0253 --- /dev/null +++ b/example/lib/widgets/macos_action_menu/action_list/action_list.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; + +import '../macos_action_menu.dart'; +import 'action_list_item.dart'; + +/// A list containing macOS action items. +class ActionList extends StatelessWidget { + const ActionList({ + Key? key, + required this.items, + required this.selectedIndex, + required this.selectItem, + required this.performItemAction, + }) : super(key: key); + + final List items; + final int selectedIndex; + final void Function(int) selectItem; + final void Function(MacOSActionMenuItem) performItemAction; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + ...items + .asMap() + .map(((key, value) { + final item = ActionListItem( + name: value.name, + function: value.function, + isSelected: key == selectedIndex, + select: () => selectItem(key), + perform: () => performItemAction(value), + ); + + return MapEntry(key, item); + })) + .values + .toList(), + // This SizedBox allows for a “scroll past end” effect and is necessary to prevent + // the DescriptionDisplay from covering the ActionList. + const SizedBox(height: 128), + ], + ); + } +} diff --git a/example/lib/widgets/macos_action_menu/action_list/action_list_item.dart b/example/lib/widgets/macos_action_menu/action_list/action_list_item.dart new file mode 100644 index 0000000..3978e2a --- /dev/null +++ b/example/lib/widgets/macos_action_menu/action_list/action_list_item.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; + +/// An item to be displayed in the action list. +class ActionListItem extends StatefulWidget { + const ActionListItem({ + Key? key, + required this.name, + required this.function, + required this.isSelected, + required this.select, + required this.perform, + }) : super(key: key); + + final String name; + final void Function() function; + final bool isSelected; + final void Function() select; + final void Function() perform; + + @override + State createState() => _ActionListItemState(); +} + +class _ActionListItemState extends State { + bool _isBeingHoveredOver = false; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: widget.isSelected ? widget.perform : widget.select, + child: MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (event) => setState(() { + _isBeingHoveredOver = true; + }), + onExit: (event) => setState(() { + _isBeingHoveredOver = false; + }), + child: AnimatedContainer( + duration: const Duration(milliseconds: 50), + decoration: BoxDecoration( + color: Theme.of(context) + .backgroundColor + .withOpacity(_getBackgroundOpacity()), + ), + child: SizedBox( + width: double.infinity, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 8.0, + ), + child: AnimatedOpacity( + duration: const Duration(milliseconds: 100), + opacity: widget.isSelected ? 1.0 : 0.5, + child: Text( + widget.name, + style: TextStyle( + fontWeight: + widget.isSelected ? FontWeight.bold : FontWeight.normal, + ), + ), + ), + ), + ), + ), + ), + ); + } + + double _getBackgroundOpacity() { + if (widget.isSelected) { + return 1.0; + } + + return _isBeingHoveredOver ? 0.5 : 0.0; + } +} diff --git a/example/lib/widgets/macos_action_menu/description_display.dart b/example/lib/widgets/macos_action_menu/description_display.dart new file mode 100644 index 0000000..0e37f1c --- /dev/null +++ b/example/lib/widgets/macos_action_menu/description_display.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; + +/// A widget that displays an action menu item's description at the bottom +/// of the action menu. +class DescriptionDisplay extends StatelessWidget { + const DescriptionDisplay({Key? key, this.description}) : super(key: key); + + final String? description; + + @override + Widget build(BuildContext context) { + if (description == null) { + return const SizedBox(); + } + + return Align( + alignment: Alignment.bottomCenter, + child: Container( + width: double.infinity, + padding: EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: Theme.of(context).backgroundColor, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(5.0), + bottomRight: Radius.circular(5.0), + ), + boxShadow: [ + // This shadows serves as a top-border. + BoxShadow( + color: Theme.of(context).canvasColor, + offset: const Offset(0.0, -0.5), + ), + ], + ), + child: Text(description!), + ), + ); + } +} diff --git a/example/lib/widgets/macos_action_menu/macos_action_menu.dart b/example/lib/widgets/macos_action_menu/macos_action_menu.dart new file mode 100644 index 0000000..be282b8 --- /dev/null +++ b/example/lib/widgets/macos_action_menu/macos_action_menu.dart @@ -0,0 +1,127 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_acrylic/flutter_acrylic.dart'; +import 'package:flutter_acrylic_example/widgets/macos_action_menu/description_display.dart'; + +import 'action_list/action_list.dart'; + +class MacOSActionMenu extends StatefulWidget { + final List items; + + const MacOSActionMenu({Key? key, required this.items}) : super(key: key); + + @override + State createState() => _MacOSActionMenuState(); +} + +class _MacOSActionMenuState extends State { + String _searchString = ''; + int _selectedIndex = 0; + late List _filteredItems; + + int get _legalSelectedIndex => + max(0, min(_filteredItems.length - 1, _selectedIndex)); + + void _updateFilteredItems() { + _filteredItems = widget.items.where((element) { + return element.name + .toLowerCase() + .replaceAll(' ', '') + .contains(_searchString.toLowerCase().replaceAll(' ', '')); + }).toList(); + } + + @override + void initState() { + super.initState(); + + _updateFilteredItems(); + } + + @override + Widget build(BuildContext context) { + return TitlebarSafeArea( + child: Material( + borderRadius: BorderRadius.circular(5.0), + child: Focus( + onKeyEvent: (focusNode, event) { + if (event is KeyDownEvent || event is KeyRepeatEvent) { + if (event.physicalKey == PhysicalKeyboardKey.arrowDown) { + setState(() { + _selectedIndex = _legalSelectedIndex + 1; + }); + return KeyEventResult.handled; + } + + if (event.physicalKey == PhysicalKeyboardKey.arrowUp) { + setState(() { + _selectedIndex = _legalSelectedIndex - 1; + }); + return KeyEventResult.handled; + } + + if (event.physicalKey == PhysicalKeyboardKey.enter) { + _filteredItems[_legalSelectedIndex].function(); + Navigator.of(context).maybePop(); + return KeyEventResult.handled; + } + } + + return KeyEventResult.ignored; + }, + child: Stack( + children: [ + Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + autofocus: true, + decoration: InputDecoration( + labelText: 'Search for a macOS action to perform:', + ), + onChanged: ((value) => setState(() { + _searchString = value; + _updateFilteredItems(); + _selectedIndex = 0; + })), + ), + ), + Expanded( + child: SingleChildScrollView( + child: ActionList( + selectedIndex: _legalSelectedIndex, + items: _filteredItems, + selectItem: (newIndex) => setState(() { + _selectedIndex = newIndex; + }), + performItemAction: (item) { + item.function(); + Navigator.of(context).maybePop(); + }, + ), + ), + ), + ], + ), + DescriptionDisplay( + description: _filteredItems[_legalSelectedIndex].description, + ), + ], + ), + ), + ), + ); + } +} + +class MacOSActionMenuItem { + final String name; + final void Function() function; + final String? description; + + MacOSActionMenuItem( + {required this.name, required this.function, this.description}); +} From 29c78320ce16feb9ef228786227c85115b7d5929 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 3 Dec 2022 10:47:34 +0100 Subject: [PATCH 02/21] example: fix crash when filtered macOS action list is empty --- example/lib/widgets/macos_action_menu/macos_action_menu.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example/lib/widgets/macos_action_menu/macos_action_menu.dart b/example/lib/widgets/macos_action_menu/macos_action_menu.dart index be282b8..5efd92c 100644 --- a/example/lib/widgets/macos_action_menu/macos_action_menu.dart +++ b/example/lib/widgets/macos_action_menu/macos_action_menu.dart @@ -107,7 +107,9 @@ class _MacOSActionMenuState extends State { ], ), DescriptionDisplay( - description: _filteredItems[_legalSelectedIndex].description, + description: _filteredItems.isEmpty + ? null + : _filteredItems[_legalSelectedIndex].description, ), ], ), From 2ad286b539476e00503984ad1af8472d9d854ec2 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 3 Dec 2022 18:22:02 +0100 Subject: [PATCH 03/21] macOS: add macOS actions to address #22 This adds the following actions: * enableShadow * disableShadow * invalidateShadows * addEmptyMaskImage * removeMaskImage --- lib/window.dart | 47 ++++++++++++ macos/Classes/FlutterAcrylicPlugin.swift | 30 ++++++++ .../MainFlutterWindowManipulator.swift | 76 +++++++++++++++++-- 3 files changed, 146 insertions(+), 7 deletions(-) diff --git a/lib/window.dart b/lib/window.dart index db7795f..7e42e11 100644 --- a/lib/window.dart +++ b/lib/window.dart @@ -151,6 +151,21 @@ const _kRemoveToolbar = "RemoveToolbar"; /// (macOS only). const _kSetToolbarStyle = "SetToolbarStyle"; +/// (macOS only) +const _kEnableShadow = "EnableShadow"; + +/// (macOS only) +const _kDisableShadow = "DisableShadow"; + +/// (macOS only) +const _kInvalidateShadows = "InvalidateShadows"; + +/// (macOS only) +const _kAddEmptyMaskImage = "AddEmptyMaskImage"; + +/// (macOS only) +const _kRemoveMaskImage = "RemoveMaskImage"; + final MethodChannel _kChannel = const MethodChannel(_kChannelName); final Completer _kCompleter = new Completer(); @@ -626,4 +641,36 @@ class Window { 'toolbarStyle': toolbarStyle.name, }); } + + /// Enables the window's shadow (macOS only). + static Future enableShadow() async { + await _kCompleter.future; + await _kChannel.invokeMethod(_kEnableShadow, {}); + } + + /// Disables the window's shadow (macOS only). + static Future disableShadow() async { + await _kCompleter.future; + await _kChannel.invokeMethod(_kDisableShadow, {}); + } + + /// Invalidates the window's shadow (macOS only). + static Future invalidateShadows() async { + await _kCompleter.future; + await _kChannel.invokeMethod(_kInvalidateShadows, {}); + } + + /// Adds an empty mask image to the window's view (macOS only). + /// + /// This will effectively disable the `NSVisualEffectView`'s effect. + static Future addEmptyMaskImage() async { + await _kCompleter.future; + await _kChannel.invokeMethod(_kAddEmptyMaskImage, {}); + } + + /// Removes the window's mask image (macOS only). + static Future removeMaskImage() async { + await _kCompleter.future; + await _kChannel.invokeMethod(_kRemoveMaskImage, {}); + } } diff --git a/macos/Classes/FlutterAcrylicPlugin.swift b/macos/Classes/FlutterAcrylicPlugin.swift index 4095e2b..001e091 100644 --- a/macos/Classes/FlutterAcrylicPlugin.swift +++ b/macos/Classes/FlutterAcrylicPlugin.swift @@ -323,6 +323,36 @@ public class FlutterAcrylicPlugin: NSObject, FlutterPlugin { result(true) break + case "EnableShadow": + MainFlutterWindowManipulator.enableShadow() + + result(true) + break + + case "DisableShadow": + MainFlutterWindowManipulator.disableShadow() + + result(true) + break + + case "InvalidateShadows": + MainFlutterWindowManipulator.invalidateShadows() + + result(true) + break + + case "AddEmptyMaskImage": + MainFlutterWindowManipulator.addEmptyMaskImage() + + result(true) + break + + case "RemoveMaskImage": + MainFlutterWindowManipulator.removeMaskImage() + + result(true) + break + default: result(FlutterMethodNotImplemented) break diff --git a/macos/Classes/MainFlutterWindowManipulator.swift b/macos/Classes/MainFlutterWindowManipulator.swift index df3b7b1..299e9f9 100644 --- a/macos/Classes/MainFlutterWindowManipulator.swift +++ b/macos/Classes/MainFlutterWindowManipulator.swift @@ -342,9 +342,9 @@ public class MainFlutterWindowManipulator { return } - self.mainFlutterWindow?.contentView?.superview?.appearance = NSAppearance(named: dark ? .darkAqua : .aqua) + self.mainFlutterWindow!.contentView?.superview?.appearance = NSAppearance(named: dark ? .darkAqua : .aqua) - self.mainFlutterWindow?.invalidateShadow() + self.mainFlutterWindow!.invalidateShadow() } @available(macOS 10.14, *) @@ -354,10 +354,10 @@ public class MainFlutterWindowManipulator { return } - let blurryContainerViewController = self.mainFlutterWindow?.contentViewController as! BlurryContainerViewController; + let blurryContainerViewController = self.mainFlutterWindow!.contentViewController as! BlurryContainerViewController; (blurryContainerViewController.view as! NSVisualEffectView).material = material - self.mainFlutterWindow?.invalidateShadow() + self.mainFlutterWindow!.invalidateShadow() } @available(macOS 10.14, *) @@ -388,19 +388,81 @@ public class MainFlutterWindowManipulator { } public static func addToolbar() { + if (self.mainFlutterWindow == nil) { + printNotStartedWarning() + return + } + if #available(macOS 10.13, *) { let newToolbar = NSToolbar() - self.mainFlutterWindow?.toolbar = newToolbar + self.mainFlutterWindow!.toolbar = newToolbar } } public static func removeToolbar() { - self.mainFlutterWindow?.toolbar = nil + if (self.mainFlutterWindow == nil) { + printNotStartedWarning() + return + } + + self.mainFlutterWindow!.toolbar = nil } @available(macOS 11.0, *) public static func setToolbarStyle(toolbarStyle: NSWindow.ToolbarStyle) { - self.mainFlutterWindow?.toolbarStyle = toolbarStyle + if (self.mainFlutterWindow == nil) { + printNotStartedWarning() + return + } + + self.mainFlutterWindow!.toolbarStyle = toolbarStyle + } + + public static func enableShadow() { + if (self.mainFlutterWindow == nil) { + printNotStartedWarning() + return + } + + self.mainFlutterWindow!.hasShadow = true + } + + public static func disableShadow() { + if (self.mainFlutterWindow == nil) { + printNotStartedWarning() + return + } + + self.mainFlutterWindow!.hasShadow = false + } + + public static func invalidateShadows() { + if (self.mainFlutterWindow == nil) { + printNotStartedWarning() + return + } + + self.mainFlutterWindow!.invalidateShadow() + } + + public static func addEmptyMaskImage() { + if (self.mainFlutterWindow == nil) { + printNotStartedWarning() + return + } + + let blurryContainerViewController = self.mainFlutterWindow!.contentViewController as! BlurryContainerViewController; + (blurryContainerViewController.view as! NSVisualEffectView).maskImage = NSImage() + } + + public static func removeMaskImage() { + if (self.mainFlutterWindow == nil) { + printNotStartedWarning() + return + } + + let blurryContainerViewController = self.mainFlutterWindow!.contentViewController as! BlurryContainerViewController; + (blurryContainerViewController.view as! NSVisualEffectView).maskImage = nil } } From 79facbaa362948cd90301ccdb059fd8324e8e569 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 3 Dec 2022 18:25:48 +0100 Subject: [PATCH 04/21] example: add macOS actions introduced in 2ad286b539476e00503984ad1af8472d9d854ec2 --- example/lib/main.dart | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/example/lib/main.dart b/example/lib/main.dart index fac76ad..bf9b86d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -585,6 +585,26 @@ class MyAppBodyState extends State { description: 'For this method to have an effect, the window needs to have had a toolbar added beforehand.', ), + MacOSActionMenuItem( + name: 'Enable Shadow', + function: () => Window.enableShadow(), + ), + MacOSActionMenuItem( + name: 'Disable Shadow', + function: () => Window.disableShadow(), + ), + MacOSActionMenuItem( + name: 'Invalidate Shadows', + function: () => Window.invalidateShadows(), + ), + MacOSActionMenuItem( + name: 'Add Empty Mask Image', + function: () => Window.addEmptyMaskImage(), + ), + MacOSActionMenuItem( + name: 'Remove Mask Image', + function: () => Window.removeMaskImage(), + ), ], ), ), From 255e282c6e7d162b81f06063a30951bbd349b29e Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 12:13:54 +0100 Subject: [PATCH 05/21] macOS: add ignore/acknowledge mouse events actions These actions make it possible to make parts of the window click-through, which may be useful when making the window fully transparent. --- lib/window.dart | 18 +++++++++++ macos/Classes/FlutterAcrylicPlugin.swift | 12 +++++++ .../MainFlutterWindowManipulator.swift | 32 +++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/lib/window.dart b/lib/window.dart index 7e42e11..81d21a3 100644 --- a/lib/window.dart +++ b/lib/window.dart @@ -166,6 +166,12 @@ const _kAddEmptyMaskImage = "AddEmptyMaskImage"; /// (macOS only) const _kRemoveMaskImage = "RemoveMaskImage"; +/// (macOS only) +const _kIgnoreMouseEvents = "IgnoreMouseEvents"; + +/// (macOS only) +const _kAcknowledgeMouseEvents = "AcknowledgeMouseEvents"; + final MethodChannel _kChannel = const MethodChannel(_kChannelName); final Completer _kCompleter = new Completer(); @@ -673,4 +679,16 @@ class Window { await _kCompleter.future; await _kChannel.invokeMethod(_kRemoveMaskImage, {}); } + + /// Makes the window ignore mouse events (macOS only). + static Future ignoreMouseEvents() async { + await _kCompleter.future; + await _kChannel.invokeMethod(_kIgnoreMouseEvents, {}); + } + + /// Makes the window acknowledge mouse events (macOS only). + static Future acknowledgeMouseEvents() async { + await _kCompleter.future; + await _kChannel.invokeMethod(_kAcknowledgeMouseEvents, {}); + } } diff --git a/macos/Classes/FlutterAcrylicPlugin.swift b/macos/Classes/FlutterAcrylicPlugin.swift index 001e091..8ae9de6 100644 --- a/macos/Classes/FlutterAcrylicPlugin.swift +++ b/macos/Classes/FlutterAcrylicPlugin.swift @@ -353,6 +353,18 @@ public class FlutterAcrylicPlugin: NSObject, FlutterPlugin { result(true) break + case "IgnoreMouseEvents": + MainFlutterWindowManipulator.ignoreMouseEvents() + + result(true) + break + + case "AcknowledgeMouseEvents": + MainFlutterWindowManipulator.acknowledgeMouseEvents() + + result(true) + break + default: result(FlutterMethodNotImplemented) break diff --git a/macos/Classes/MainFlutterWindowManipulator.swift b/macos/Classes/MainFlutterWindowManipulator.swift index 299e9f9..8942da9 100644 --- a/macos/Classes/MainFlutterWindowManipulator.swift +++ b/macos/Classes/MainFlutterWindowManipulator.swift @@ -21,6 +21,20 @@ public class MainFlutterWindowManipulator { makeTitlebarOpaque() disableFullSizeContentView() setWindowBackgroundColorToDefaultColor() + + /*if #available(macOS 11.0, *) { + self.mainFlutterWindow!.subtitle = "subtitle" + } else { + // Fallback on earlier versions + } + //self.mainFlutterWindow!.ignoresMouseEvents = true + if #available(macOS 11.0, *) { + self.mainFlutterWindow!.titlebarSeparatorStyle = NSTitlebarSeparatorStyle.none + } else { + // Fallback on earlier versions + } + + self.mainFlutterWindow!.*/ } public static func hideTitle() { @@ -465,4 +479,22 @@ public class MainFlutterWindowManipulator { let blurryContainerViewController = self.mainFlutterWindow!.contentViewController as! BlurryContainerViewController; (blurryContainerViewController.view as! NSVisualEffectView).maskImage = nil } + + public static func ignoreMouseEvents() { + if (self.mainFlutterWindow == nil) { + printNotStartedWarning() + return + } + + self.mainFlutterWindow!.ignoresMouseEvents = true + } + + public static func acknowledgeMouseEvents() { + if (self.mainFlutterWindow == nil) { + printNotStartedWarning() + return + } + + self.mainFlutterWindow!.ignoresMouseEvents = false + } } From c419fb436873bf71069241ad85de6dc125fda648 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 12:14:23 +0100 Subject: [PATCH 06/21] example: add macOS actions introduced in 255e282c6e7d162b81f06063a30951bbd349b29e --- example/lib/main.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/example/lib/main.dart b/example/lib/main.dart index bf9b86d..e756442 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -605,6 +605,19 @@ class MyAppBodyState extends State { name: 'Remove Mask Image', function: () => Window.removeMaskImage(), ), + MacOSActionMenuItem( + name: 'Ignore Mouse Events', + function: () { + Window.ignoreMouseEvents(); + Timer(const Duration(seconds: 5), + () => Window.acknowledgeMouseEvents()); + }, + ), + MacOSActionMenuItem( + name: 'Acknowledge Mouse Events', + function: () => + Window.acknowledgeMouseEvents(), + ), ], ), ), From 7f33c102fd2d86ae04e434ee938d85ca487acb54 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 13:23:00 +0100 Subject: [PATCH 07/21] macOS: add `makeWindowFullyTransparent` --- lib/window.dart | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/window.dart b/lib/window.dart index 81d21a3..ff3ee0f 100644 --- a/lib/window.dart +++ b/lib/window.dart @@ -680,6 +680,26 @@ class Window { await _kChannel.invokeMethod(_kRemoveMaskImage, {}); } + /// Makes a window fully transparent (with no blur effect) (macOS only). + /// + /// This is a convenience method which executes: + /// ```dart + /// setWindowBackgroundColorToClear(); + /// makeTitlebarTransparent(); + /// addEmptyMaskImage(); + /// disableShadow(); + /// ``` + /// + /// **Warning:** When the window is fully transparent, its highlight effect + /// (the thin white line at the top of the window) is still visible. This is + /// considered a bug and may change in a future version. + static Future makeWindowFullyTransparent() async { + setWindowBackgroundColorToClear(); + makeTitlebarTransparent(); + addEmptyMaskImage(); + disableShadow(); + } + /// Makes the window ignore mouse events (macOS only). static Future ignoreMouseEvents() async { await _kCompleter.future; From 0d4a5fa4a884e52108cff1499a402518d28e56f2 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 13:23:30 +0100 Subject: [PATCH 08/21] example: add macOS action introduced in 7f33c102fd2d86ae04e434ee938d85ca487acb54 --- example/lib/main.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/example/lib/main.dart b/example/lib/main.dart index e756442..38f4972 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -605,6 +605,13 @@ class MyAppBodyState extends State { name: 'Remove Mask Image', function: () => Window.removeMaskImage(), ), + MacOSActionMenuItem( + name: 'Make Window Fully Transparent', + function: () => + Window.makeWindowFullyTransparent(), + description: + 'Makes a window fully transparent (with no blur effect). This is a convenience function which executes:\n\tsetWindowBackgroundColorToClear();\n\tmakeTitlebarTransparent();\n\taddEmptyMaskImage();\n\tdisableShadow();\n**Warning:** When the window is fully transparent, its highlight effect (the thin white line at the top of the window) is still visible. This is considered a bug and may change in a future version.', + ), MacOSActionMenuItem( name: 'Ignore Mouse Events', function: () { From 034a314f2dcb8399600ed88d2cfba2346bbcc652 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 13:29:56 +0100 Subject: [PATCH 09/21] macOS: make `makeWindowFullyTransparent` non-async `makeWindowFullyTransparent` was initially unnecessarily async as to not introduce a breaking change if it ever needed to be async in the future, when a bug associated with it was fixed. However, it is unlikely that fixing that bug would result in the method needing to become async, therefore, this commit removes this method's async property. --- lib/window.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/window.dart b/lib/window.dart index ff3ee0f..742b06e 100644 --- a/lib/window.dart +++ b/lib/window.dart @@ -693,7 +693,7 @@ class Window { /// **Warning:** When the window is fully transparent, its highlight effect /// (the thin white line at the top of the window) is still visible. This is /// considered a bug and may change in a future version. - static Future makeWindowFullyTransparent() async { + static void makeWindowFullyTransparent() { setWindowBackgroundColorToClear(); makeTitlebarTransparent(); addEmptyMaskImage(); From d7bb87e52ad459cf03f431d1d53a2576abe20acf Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 13:48:49 +0100 Subject: [PATCH 10/21] example: add markdown support to macOS action menu --- example/lib/main.dart | 2 +- .../description_display.dart | 10 ++++++-- example/pubspec.lock | 23 ++++++++++++++++++- example/pubspec.yaml | 1 + 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 38f4972..470df03 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -610,7 +610,7 @@ class MyAppBodyState extends State { function: () => Window.makeWindowFullyTransparent(), description: - 'Makes a window fully transparent (with no blur effect). This is a convenience function which executes:\n\tsetWindowBackgroundColorToClear();\n\tmakeTitlebarTransparent();\n\taddEmptyMaskImage();\n\tdisableShadow();\n**Warning:** When the window is fully transparent, its highlight effect (the thin white line at the top of the window) is still visible. This is considered a bug and may change in a future version.', + 'Makes a window fully transparent (with no blur effect). This is a convenience function which executes:\n```dart\nsetWindowBackgroundColorToClear();\nmakeTitlebarTransparent();\naddEmptyMaskImage();\ndisableShadow();\n```\n**Warning:** When the window is fully transparent, its highlight effect (the thin white line at the top of the window) is still visible. This is considered a bug and may change in a future version.', ), MacOSActionMenuItem( name: 'Ignore Mouse Events', diff --git a/example/lib/widgets/macos_action_menu/description_display.dart b/example/lib/widgets/macos_action_menu/description_display.dart index 0e37f1c..7e22b8f 100644 --- a/example/lib/widgets/macos_action_menu/description_display.dart +++ b/example/lib/widgets/macos_action_menu/description_display.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; /// A widget that displays an action menu item's description at the bottom /// of the action menu. @@ -17,7 +18,7 @@ class DescriptionDisplay extends StatelessWidget { alignment: Alignment.bottomCenter, child: Container( width: double.infinity, - padding: EdgeInsets.all(8.0), + constraints: BoxConstraints(maxHeight: 128), decoration: BoxDecoration( color: Theme.of(context).backgroundColor, borderRadius: const BorderRadius.only( @@ -32,7 +33,12 @@ class DescriptionDisplay extends StatelessWidget { ), ], ), - child: Text(description!), + // child: Text(description!), + child: Markdown( + data: description!, + shrinkWrap: true, + padding: EdgeInsets.all(8.0), + ), ), ); } diff --git a/example/pubspec.lock b/example/pubspec.lock index ddc1a1a..ae189ff 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,6 +1,13 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" async: dependency: transitive description: @@ -104,11 +111,25 @@ packages: relative: true source: path version: "1.0.0+2" + flutter_markdown: + dependency: "direct main" + description: + name: flutter_markdown + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.13" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + markdown: + dependency: transitive + description: + name: markdown + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.1" matcher: dependency: transitive description: @@ -207,4 +228,4 @@ packages: version: "2.6.1" sdks: dart: ">=2.17.0 <3.0.0" - flutter: ">=1.22.0" + flutter: ">=3.0.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 736743f..53c7b0a 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: # the parent directory to use the current plugin's version. path: ../ bitsdojo_window: ^0.1.2 + flutter_markdown: ^0.6.13 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. From 9beec0a2df8f95f8203bf60b2d1564dece999bb2 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 13:53:43 +0100 Subject: [PATCH 11/21] macOS: add warning to `addEmptyMaskImage`'s documentation --- lib/window.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/window.dart b/lib/window.dart index 742b06e..1329c29 100644 --- a/lib/window.dart +++ b/lib/window.dart @@ -669,6 +669,11 @@ class Window { /// Adds an empty mask image to the window's view (macOS only). /// /// This will effectively disable the `NSVisualEffectView`'s effect. + /// + /// **Warning:** It is recommended to disable the window's shadow using + /// `Window.disableShadow()` when using this method. Keeping the shadow + /// enabled when using an empty mask image can cause visual artifacts + /// and performance issues. static Future addEmptyMaskImage() async { await _kCompleter.future; await _kChannel.invokeMethod(_kAddEmptyMaskImage, {}); From 9764a15e153b1d1d218d942a9fd6cd16404222b2 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 14:00:27 +0100 Subject: [PATCH 12/21] macOS: improve documentation --- lib/window.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/window.dart b/lib/window.dart index 1329c29..e22ae42 100644 --- a/lib/window.dart +++ b/lib/window.dart @@ -661,6 +661,9 @@ class Window { } /// Invalidates the window's shadow (macOS only). + /// + /// This is a fairly technical method and is included here for + /// completeness' sake. Normally, it should not be necessary to use it. static Future invalidateShadows() async { await _kCompleter.future; await _kChannel.invokeMethod(_kInvalidateShadows, {}); @@ -706,12 +709,20 @@ class Window { } /// Makes the window ignore mouse events (macOS only). + /// + /// This method can be used to make parts of the window click-through, which + /// may be desirable when used in conjunction with + /// `Window.makeWindowFullyTransparent()`. static Future ignoreMouseEvents() async { await _kCompleter.future; await _kChannel.invokeMethod(_kIgnoreMouseEvents, {}); } /// Makes the window acknowledge mouse events (macOS only). + /// + /// This method can be used to make parts of the window click-through, which + /// may be desirable when used in conjunction with + /// `Window.makeWindowFullyTransparent()`. static Future acknowledgeMouseEvents() async { await _kCompleter.future; await _kChannel.invokeMethod(_kAcknowledgeMouseEvents, {}); From f26fc88e2bea938aaa5b0a317f4769adf05980bb Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 14:13:33 +0100 Subject: [PATCH 13/21] example: improve macOS action descriptions --- example/lib/main.dart | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 470df03..cb5dfb2 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -552,7 +552,7 @@ class MyAppBodyState extends State { toolbarStyle: MacOSToolbarStyle.automatic), description: - 'For this method to have an effect, the window needs to have had a toolbar added beforehand.', + 'For this method to have an effect, the window needs to have had a toolbar added beforehand. This can be achieved using the “Add Toolbar” action.', ), MacOSActionMenuItem( name: 'Set Toolbar Style to Expanded', @@ -560,7 +560,7 @@ class MyAppBodyState extends State { toolbarStyle: MacOSToolbarStyle.expanded, ), description: - 'For this method to have an effect, the window needs to have had a toolbar added beforehand.', + 'For this method to have an effect, the window needs to have had a toolbar added beforehand. This can be achieved using the “Add Toolbar” action.', ), MacOSActionMenuItem( name: 'Set Toolbar Style to Preference', @@ -568,14 +568,14 @@ class MyAppBodyState extends State { toolbarStyle: MacOSToolbarStyle.preference), description: - 'For this method to have an effect, the window needs to have had a toolbar added beforehand.', + 'For this method to have an effect, the window needs to have had a toolbar added beforehand. This can be achieved using the “Add Toolbar” action.', ), MacOSActionMenuItem( name: 'Set Toolbar Style to Unified', function: () => Window.setToolbarStyle( toolbarStyle: MacOSToolbarStyle.unified), description: - 'For this method to have an effect, the window needs to have had a toolbar added beforehand.', + 'For this method to have an effect, the window needs to have had a toolbar added beforehand. This can be achieved using the “Add Toolbar” action.', ), MacOSActionMenuItem( name: 'Set Toolbar Style to Unified Compact', @@ -583,7 +583,7 @@ class MyAppBodyState extends State { toolbarStyle: MacOSToolbarStyle.unifiedCompact), description: - 'For this method to have an effect, the window needs to have had a toolbar added beforehand.', + 'For this method to have an effect, the window needs to have had a toolbar added beforehand. This can be achieved using the “Add Toolbar” action.', ), MacOSActionMenuItem( name: 'Enable Shadow', @@ -596,10 +596,14 @@ class MyAppBodyState extends State { MacOSActionMenuItem( name: 'Invalidate Shadows', function: () => Window.invalidateShadows(), + description: + 'This is a fairly technical action and is included here for completeness\' sake. Normally, it should not be necessary to use it.', ), MacOSActionMenuItem( name: 'Add Empty Mask Image', function: () => Window.addEmptyMaskImage(), + description: + 'This will effectively disable the `NSVisualEffectView`\'s effect.\n\n**Warning:** It is recommended to disable the window\'s shadow using `Window.disableShadow()` when using this method. Keeping the shadow enabled when using an empty mask image can cause visual artifacts and performance issues.', ), MacOSActionMenuItem( name: 'Remove Mask Image', @@ -619,11 +623,15 @@ class MyAppBodyState extends State { Timer(const Duration(seconds: 5), () => Window.acknowledgeMouseEvents()); }, + description: + 'This action can be used to make parts of the window click-through, which may be desirable when used in conjunction with `Window.makeWindowFullyTransparent()`.\n\n**Note:** Executing this action will make this widow click-through, thus making it impossible to perform the “Acknowledge Mouse Events” again. For this reason, the example app automatically starts acknowledging mouse events again after five seconds.', ), MacOSActionMenuItem( name: 'Acknowledge Mouse Events', function: () => Window.acknowledgeMouseEvents(), + description: + 'This action is included here for completeness\' sake, however it is technically impossible to run it after performing the “Ignore Mouse Events” action, since the “show all actions” button can then no longer be clicked.', ), ], ), From bd3e836eab0014a58dc52244385ef44034d48c88 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 14:15:56 +0100 Subject: [PATCH 14/21] example: refactor some method names --- example/lib/main.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index cb5dfb2..4e30ec5 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -270,7 +270,7 @@ class MyAppBodyState extends State { ), ), Expanded( - child: generateEffectMenu(context), + child: buildEffectMenu(context), ), Divider( height: 1.0, @@ -278,8 +278,8 @@ class MyAppBodyState extends State { ? Colors.white12 : Colors.black12, ), - generateActionButtonBar(context), - generateMacOSActionButtonBar(context), + buildActionButtonBar(context), + buildMacOSActionMenuOpener(context), ], ), ), @@ -289,7 +289,7 @@ class MyAppBodyState extends State { ); } - ButtonBar generateActionButtonBar(BuildContext context) { + ButtonBar buildActionButtonBar(BuildContext context) { return ButtonBar( alignment: MainAxisAlignment.start, overflowButtonSpacing: 4.0, @@ -358,7 +358,7 @@ class MyAppBodyState extends State { ); } - Widget generateMacOSActionButtonBar(BuildContext context) { + Widget buildMacOSActionMenuOpener(BuildContext context) { if (!Platform.isMacOS) { return const SizedBox(); } @@ -647,7 +647,7 @@ class MyAppBodyState extends State { ); } - SingleChildScrollView generateEffectMenu(BuildContext context) { + SingleChildScrollView buildEffectMenu(BuildContext context) { return SingleChildScrollView( child: Theme( data: brightness.getIsDark(context) From 81d2c2522af105cbb8bdae400d2f5efebe1c1eea Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 14:25:00 +0100 Subject: [PATCH 15/21] example: increase description display height MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Increases the description display's max height. Previously, the height made the warning message of the “Make Window Fully Transparent” action's description disappear. However, the padding looked perfect. This might make a user miss the warning message entirely. Additionally, the `ActionList` can now source the `DescriptionDisplay`'s max height directly from its widget, thus eliminating some code duplication. --- .../widgets/macos_action_menu/action_list/action_list.dart | 3 ++- .../lib/widgets/macos_action_menu/description_display.dart | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/example/lib/widgets/macos_action_menu/action_list/action_list.dart b/example/lib/widgets/macos_action_menu/action_list/action_list.dart index c3e0253..6b4c56a 100644 --- a/example/lib/widgets/macos_action_menu/action_list/action_list.dart +++ b/example/lib/widgets/macos_action_menu/action_list/action_list.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_acrylic_example/widgets/macos_action_menu/description_display.dart'; import '../macos_action_menu.dart'; import 'action_list_item.dart'; @@ -39,7 +40,7 @@ class ActionList extends StatelessWidget { .toList(), // This SizedBox allows for a “scroll past end” effect and is necessary to prevent // the DescriptionDisplay from covering the ActionList. - const SizedBox(height: 128), + const SizedBox(height: DescriptionDisplay.maxHeight), ], ); } diff --git a/example/lib/widgets/macos_action_menu/description_display.dart b/example/lib/widgets/macos_action_menu/description_display.dart index 7e22b8f..826034f 100644 --- a/example/lib/widgets/macos_action_menu/description_display.dart +++ b/example/lib/widgets/macos_action_menu/description_display.dart @@ -4,6 +4,8 @@ import 'package:flutter_markdown/flutter_markdown.dart'; /// A widget that displays an action menu item's description at the bottom /// of the action menu. class DescriptionDisplay extends StatelessWidget { + static const maxHeight = 150.0; + const DescriptionDisplay({Key? key, this.description}) : super(key: key); final String? description; @@ -18,7 +20,7 @@ class DescriptionDisplay extends StatelessWidget { alignment: Alignment.bottomCenter, child: Container( width: double.infinity, - constraints: BoxConstraints(maxHeight: 128), + constraints: BoxConstraints(maxHeight: maxHeight), decoration: BoxDecoration( color: Theme.of(context).backgroundColor, borderRadius: const BorderRadius.only( From 101f92d52322855a16f0ba2c9d8dba11e85a32f6 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 14:30:39 +0100 Subject: [PATCH 16/21] macOS: remove commented out code This code was used for testing purposes and was committed by mistake. --- macos/Classes/MainFlutterWindowManipulator.swift | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/macos/Classes/MainFlutterWindowManipulator.swift b/macos/Classes/MainFlutterWindowManipulator.swift index 8942da9..2314cd7 100644 --- a/macos/Classes/MainFlutterWindowManipulator.swift +++ b/macos/Classes/MainFlutterWindowManipulator.swift @@ -21,20 +21,6 @@ public class MainFlutterWindowManipulator { makeTitlebarOpaque() disableFullSizeContentView() setWindowBackgroundColorToDefaultColor() - - /*if #available(macOS 11.0, *) { - self.mainFlutterWindow!.subtitle = "subtitle" - } else { - // Fallback on earlier versions - } - //self.mainFlutterWindow!.ignoresMouseEvents = true - if #available(macOS 11.0, *) { - self.mainFlutterWindow!.titlebarSeparatorStyle = NSTitlebarSeparatorStyle.none - } else { - // Fallback on earlier versions - } - - self.mainFlutterWindow!.*/ } public static func hideTitle() { From 7238c030cadfd12d4eb75f0ccc475794763c500c Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 14:41:40 +0100 Subject: [PATCH 17/21] macOS: resolve #47 --- lib/window.dart | 11 +++++++++++ macos/Classes/FlutterAcrylicPlugin.swift | 11 +++++++++++ macos/Classes/MainFlutterWindowManipulator.swift | 10 ++++++++++ 3 files changed, 32 insertions(+) diff --git a/lib/window.dart b/lib/window.dart index e22ae42..81e5fb9 100644 --- a/lib/window.dart +++ b/lib/window.dart @@ -172,6 +172,9 @@ const _kIgnoreMouseEvents = "IgnoreMouseEvents"; /// (macOS only) const _kAcknowledgeMouseEvents = "AcknowledgeMouseEvents"; +/// (macOS only) +const _kSetSubtitle = "SetSubtitle"; + final MethodChannel _kChannel = const MethodChannel(_kChannelName); final Completer _kCompleter = new Completer(); @@ -727,4 +730,12 @@ class Window { await _kCompleter.future; await _kChannel.invokeMethod(_kAcknowledgeMouseEvents, {}); } + + /// Sets the subtitle of the window (macOS only). + static Future setSubtitle(String subtitle) async { + await _kCompleter.future; + await _kChannel.invokeMethod(_kSetSubtitle, { + 'subtitle': subtitle, + }); + } } diff --git a/macos/Classes/FlutterAcrylicPlugin.swift b/macos/Classes/FlutterAcrylicPlugin.swift index 8ae9de6..0521c93 100644 --- a/macos/Classes/FlutterAcrylicPlugin.swift +++ b/macos/Classes/FlutterAcrylicPlugin.swift @@ -365,6 +365,17 @@ public class FlutterAcrylicPlugin: NSObject, FlutterPlugin { result(true) break + case "SetSubtitle": + let subtitle = args["subtitle"] as! String + if #available(macOS 11.0, *) { + MainFlutterWindowManipulator.setSubtitle(subtitle) + } else { + FlutterAcrylicPlugin.printUnsupportedMacOSVersionWarning() + } + + result(true) + break + default: result(FlutterMethodNotImplemented) break diff --git a/macos/Classes/MainFlutterWindowManipulator.swift b/macos/Classes/MainFlutterWindowManipulator.swift index 2314cd7..2d45058 100644 --- a/macos/Classes/MainFlutterWindowManipulator.swift +++ b/macos/Classes/MainFlutterWindowManipulator.swift @@ -483,4 +483,14 @@ public class MainFlutterWindowManipulator { self.mainFlutterWindow!.ignoresMouseEvents = false } + + @available(macOS 11.0, *) + public static func setSubtitle(_ subtitle: String) { + if (self.mainFlutterWindow == nil) { + printNotStartedWarning() + return + } + + self.mainFlutterWindow!.subtitle = subtitle + } } From 1d84d7e145906a9e9b7cbfdbdd47a242b19c86b8 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 14:42:11 +0100 Subject: [PATCH 18/21] example: add macOS action introduced in 7238c030cadfd12d4eb75f0ccc475794763c500c --- example/lib/main.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/example/lib/main.dart b/example/lib/main.dart index 4e30ec5..d3271a9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -633,6 +633,17 @@ class MyAppBodyState extends State { description: 'This action is included here for completeness\' sake, however it is technically impossible to run it after performing the “Ignore Mouse Events” action, since the “show all actions” button can then no longer be clicked.', ), + MacOSActionMenuItem( + name: 'Set Subtitle', + function: () => + Window.setSubtitle('subtitle'), + ), + MacOSActionMenuItem( + name: 'Remove Subtitle', + function: () => Window.setSubtitle(''), + description: + 'The action works by setting the subtitle to an empty string using `Window.setSubtitle(\'\')`.', + ), ], ), ), From 344de66f8018f1159b846596ccf6c335abf4e3b0 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 14:45:40 +0100 Subject: [PATCH 19/21] macOS: improve documentation of `setSubtitle` --- lib/window.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/window.dart b/lib/window.dart index 81e5fb9..ed89068 100644 --- a/lib/window.dart +++ b/lib/window.dart @@ -732,6 +732,8 @@ class Window { } /// Sets the subtitle of the window (macOS only). + /// + /// To remove the subtitle, pass an empty string to this method. static Future setSubtitle(String subtitle) async { await _kCompleter.future; await _kChannel.invokeMethod(_kSetSubtitle, { From e4f0a4f19fd1bc500bedaccf832057c67da7750e Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 14:46:52 +0100 Subject: [PATCH 20/21] =?UTF-8?q?example:=20improve=20description=20of=20?= =?UTF-8?q?=E2=80=9CRemove=20Subtitle=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index d3271a9..fc1ecdf 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -642,7 +642,7 @@ class MyAppBodyState extends State { name: 'Remove Subtitle', function: () => Window.setSubtitle(''), description: - 'The action works by setting the subtitle to an empty string using `Window.setSubtitle(\'\')`.', + 'The action works by setting the subtitle to an empty string using `Window.setSubtitle(\'\')`. There is no method called `Window.removeSubtitle()`.', ), ], ), From 283bdf14634c57d7bfc11bf83f63cbb33aad0edc Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 4 Dec 2022 19:37:08 +0100 Subject: [PATCH 21/21] readme: add macOS-specific methods introduced in 2ad286b539476e00503984ad1af8472d9d854ec2, 255e282c6e7d162b81f06063a30951bbd349b29e, 7f33c102fd2d86ae04e434ee938d85ca487acb54, and 7238c030cadfd12d4eb75f0ccc475794763c500c --- README.md | 76 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 884e657..d37d9b4 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,11 @@ _Example app running on Microsoft Windows 11 (pre-compiled release mode x64 exec ## Platforms -|Platform|Status|Maintainer | -|--------|------|------------------------------------------------| -|Windows |✅ |[Hitesh Kumar Saini](https://github.com/alexmercerind) | -|macOS |✅ |[Adrian Samoticha](https://github.com/Adrian-Samoticha) | -|Linux |✅ |[Hitesh Kumar Saini](https://github.com/alexmercerind) | +| Platform | Status | Maintainer | +| -------- | ------ | ------------------------------------------------------- | +| Windows | ✅ | [Hitesh Kumar Saini](https://github.com/alexmercerind) | +| macOS | ✅ | [Adrian Samoticha](https://github.com/Adrian-Samoticha) | +| Linux | ✅ | [Hitesh Kumar Saini](https://github.com/alexmercerind) | ## Docs @@ -57,26 +57,26 @@ await Window.setEffect( | Effect | Description | Windows | macOS | Linux | | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | ----- | -| `WindowEffect.transparent` | Transparent window background. | ✅ | ✅ | ✅ | -| `WindowEffect.disabled` | Default window background. | ✅ | ✅ | ✅ | -| `WindowEffect.solid` | Solid colored window background. | ✅ | ✅ | ✅ | -| `WindowEffect.aero` | Aero glass effect. Windows Vista & Windows 7 like glossy blur effect. | ✅ | ✅ | | -| `WindowEffect.acrylic` | Acrylic is a type of brush that creates a translucent texture. You can apply acrylic to app surfaces to add depth and help establish a visual hierarchy. Works only on Windows 10 version 1803 or higher. | ✅ | ✅ | | -| `WindowEffect.mica` | Mica is an opaque, dynamic material that incorporates theme and desktop wallpaper to paint the background of long-lived windows. Works only on Windows 11 or greater. | ✅ | ✅ | | -| `WindowEffect.tabbed` | Tabbed is a Mica like material that incorporates theme and desktop wallpaper, but is more sensitive to desktop wallpaper color. Works only on later Windows 11 versions (builds higher than 22523). | ✅ | ✅ | | -| `WindowEffect.titlebar` | The material for a window’s titlebar. | | ✅ | | -| `WindowEffect.menu` | The material for menus. | | ✅ | | -| `WindowEffect.popover` | The material for the background of popover windows. | | ✅ | | -| `WindowEffect.sidebar` | The material for the background of window sidebars. | | ✅ | | -| `WindowEffect.headerView` | The material for in-line header or footer views. | | ✅ | | -| `WindowEffect.sheet` | The material for the background of sheet windows. | | ✅ | | -| `WindowEffect.windowBackground` | The material for the background of opaque windows. | | ✅ | | -| `WindowEffect.hudWindow` | The material for the background of heads-up display (HUD) windows. | | ✅ | | -| `WindowEffect.fullScreenUI` | The material for the background of a full-screen modal interface. | | ✅ | | -| `WindowEffect.toolTip` | The material for the background of a tool tip. | | ✅ | | -| `WindowEffect.contentBackground` | The material for the background of opaque content. | | ✅ | | -| `WindowEffect.underWindowBackground` | The material to show under a window's background. | | ✅ | | -| `WindowEffect.underPageBackground` | The material for the area behind the pages of a document. | | ✅ | | +| `WindowEffect.transparent` | Transparent window background. | ✅ | ✅ | ✅ | +| `WindowEffect.disabled` | Default window background. | ✅ | ✅ | ✅ | +| `WindowEffect.solid` | Solid colored window background. | ✅ | ✅ | ✅ | +| `WindowEffect.aero` | Aero glass effect. Windows Vista & Windows 7 like glossy blur effect. | ✅ | ✅ | | +| `WindowEffect.acrylic` | Acrylic is a type of brush that creates a translucent texture. You can apply acrylic to app surfaces to add depth and help establish a visual hierarchy. Works only on Windows 10 version 1803 or higher. | ✅ | ✅ | | +| `WindowEffect.mica` | Mica is an opaque, dynamic material that incorporates theme and desktop wallpaper to paint the background of long-lived windows. Works only on Windows 11 or greater. | ✅ | ✅ | | +| `WindowEffect.tabbed` | Tabbed is a Mica like material that incorporates theme and desktop wallpaper, but is more sensitive to desktop wallpaper color. Works only on later Windows 11 versions (builds higher than 22523). | ✅ | ✅ | | +| `WindowEffect.titlebar` | The material for a window’s titlebar. | | ✅ | | +| `WindowEffect.menu` | The material for menus. | | ✅ | | +| `WindowEffect.popover` | The material for the background of popover windows. | | ✅ | | +| `WindowEffect.sidebar` | The material for the background of window sidebars. | | ✅ | | +| `WindowEffect.headerView` | The material for in-line header or footer views. | | ✅ | | +| `WindowEffect.sheet` | The material for the background of sheet windows. | | ✅ | | +| `WindowEffect.windowBackground` | The material for the background of opaque windows. | | ✅ | | +| `WindowEffect.hudWindow` | The material for the background of heads-up display (HUD) windows. | | ✅ | | +| `WindowEffect.fullScreenUI` | The material for the background of a full-screen modal interface. | | ✅ | | +| `WindowEffect.toolTip` | The material for the background of a tool tip. | | ✅ | | +| `WindowEffect.contentBackground` | The material for the background of opaque content. | | ✅ | | +| `WindowEffect.underWindowBackground` | The material to show under a window's background. | | ✅ | | +| `WindowEffect.underPageBackground` | The material for the area behind the pages of a document. | | ✅ | | _Windows 10 versions higher than 1803 & all Windows 11 versions are supported by the plugin, although not all effects might be available to a particular Windows version. See [pinned issues](https://github.com/alexmercerind/flutter_acrylic/issues) if you encounter some problem or feel free to file one yourself._ @@ -318,6 +318,32 @@ Window.setToolbarStyle(MacOSToolbarStyle.unified); Window.setToolbarStyle(MacOSToolbarStyle.unifiedCompact); ``` +Enable and disable window shadow. + +```dart +Window.enableShadow(); +Window.disableShadow(); +``` + +Make window fully transparent (with no blur effect): + +```dart +Window.makeWindowFullyTransparent(); +``` + +Acknowledge or ignore mouse events: + +```dart +Window.acknowledgeMouseEvents(); +Window.ignoreMouseEvents(); +``` + +Set the window's subtitle: + +```dart +Window.setSubtitle('subtitle'); +``` + More features coming soon. ## Notes