From 59896e66b0da0ea472cbbcd87384e467fb4e293f Mon Sep 17 00:00:00 2001 From: Ben Hagen Date: Sun, 12 Nov 2023 15:20:12 +0100 Subject: [PATCH 1/6] Add BlockingToolbar --- macos/Classes/BlockingToolbar.swift | 52 +++++++++++++++++++ .../MainFlutterWindowManipulator.swift | 7 +-- 2 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 macos/Classes/BlockingToolbar.swift diff --git a/macos/Classes/BlockingToolbar.swift b/macos/Classes/BlockingToolbar.swift new file mode 100644 index 0000000..ca9e43d --- /dev/null +++ b/macos/Classes/BlockingToolbar.swift @@ -0,0 +1,52 @@ +// +// BlockingToolbar.swift +// macos_window_utils +// +// Created by Ben Hagen on 12.11.2023. +// + +import Foundation +import FlutterMacOS + +class BlockingToolbar: NSToolbar, NSToolbarDelegate { + let flutterView: FlutterViewController + + init(flutterView: FlutterViewController) { + self.flutterView = flutterView + super.init(identifier: "BlockingToolbar") + } + + func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { + [.flexibleSpace, NSToolbarItem.Identifier("BlockingItem")] + } + + func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { + toolbarDefaultItemIdentifiers(toolbar) + } + + func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? { + if itemIdentifier == NSToolbarItem.Identifier("BlockingItem") { + let item = NSToolbarItem(itemIdentifier: itemIdentifier) + let view = ForwardingView() + view.flutterView = flutterView + view.widthAnchor.constraint(lessThanOrEqualToConstant: 100000).isActive = true + view.widthAnchor.constraint(greaterThanOrEqualToConstant: 1).isActive = true + item.view = view + return item + } + return nil + } +} + + +class ForwardingView: NSView { + var flutterView: FlutterViewController? + + override func mouseDown(with event: NSEvent) { + flutterView!.mouseDown(with: event) + } + + override func mouseUp(with event: NSEvent) { + flutterView!.mouseUp(with: event) + } +} diff --git a/macos/Classes/MainFlutterWindowManipulator.swift b/macos/Classes/MainFlutterWindowManipulator.swift index 09de9a8..e93fd23 100644 --- a/macos/Classes/MainFlutterWindowManipulator.swift +++ b/macos/Classes/MainFlutterWindowManipulator.swift @@ -402,9 +402,10 @@ public class MainFlutterWindowManipulator { } if #available(macOS 10.13, *) { - let newToolbar = NSToolbar() - - self.mainFlutterWindow!.toolbar = newToolbar + let customToolbar = BlockingToolbar(flutterView: (self.mainFlutterWindow?.contentViewController as! MacOSWindowUtilsViewController).flutterViewController) + customToolbar.showsBaselineSeparator = false + customToolbar.delegate = customToolbar + self.mainFlutterWindow!.toolbar = customToolbar } } From 0ddc12601526e1bd2e799ee888591b89f28adf27 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:46:23 +0100 Subject: [PATCH 2/6] automatic changes --- example/macos/Podfile.lock | 2 +- example/macos/Runner.xcodeproj/project.pbxproj | 2 +- .../Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index df1a456..9bf4952 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -25,4 +25,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 500e4707112a5f11963bc198135953cdebb6d50c -COCOAPODS: 1.12.1 +COCOAPODS: 1.15.2 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 4afa19b..12c4c31 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -202,7 +202,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 83d8872..5b055a3 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Date: Thu, 31 Oct 2024 12:49:35 +0100 Subject: [PATCH 3/6] add option to choose between available toolbars, alongside a debug option to visualize the blocking area --- .../command_list_provider.dart | 31 +++++++++++++ lib/toolbars/toolbars.dart | 43 +++++++++++++++++++ lib/window_manipulator.dart | 35 ++++++++++++++- macos/Classes/BlockingToolbar.swift | 9 +++- macos/Classes/MacOSWindowUtilsPlugin.swift | 4 +- .../MainFlutterWindowManipulator.swift | 17 +++++++- macos/Classes/colorFromRGBAString.swift | 30 +++++++++++++ 7 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 lib/toolbars/toolbars.dart create mode 100644 macos/Classes/colorFromRGBAString.swift diff --git a/example/lib/main_area/window_manipulator_demo/command_list_provider/command_list_provider.dart b/example/lib/main_area/window_manipulator_demo/command_list_provider/command_list_provider.dart index 8413223..6877502 100644 --- a/example/lib/main_area/window_manipulator_demo/command_list_provider/command_list_provider.dart +++ b/example/lib/main_area/window_manipulator_demo/command_list_provider/command_list_provider.dart @@ -3,10 +3,12 @@ import 'dart:ui'; import 'package:example/main_area/window_manipulator_demo/command_list_provider/command_list_provider_constants.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:macos_window_utils/macos/ns_window_button_type.dart'; import 'package:macos_window_utils/macos/ns_window_level.dart'; import 'package:macos_window_utils/macos/ns_window_style_mask.dart'; import 'package:macos_window_utils/macos_window_utils.dart'; +import 'package:macos_window_utils/toolbars/toolbars.dart'; import '../command_list/command.dart'; @@ -202,6 +204,35 @@ class CommandListProvider { name: 'addToolbar()', function: () => WindowManipulator.addToolbar(), ), + Command( + name: 'addToolbar(toolbar: const BlockingToolbar())', + description: 'Adds a blocking toolbar to the window.\n\nBlocking ' + 'toolbars contain an area that stops double clicks from zooming the' + 'window, thus allowing for the placement of buttons that can be ' + 'clicked repeatedly.\n\nSetting the `blockingAreaDebugColor` to an ' + 'easily visible color can be useful for debugging purposes:\n\n' + '![image](https://github.com/user-attachments/assets/984c4dc7-f3ea-4b38-ba65-9e611982d32c)' + 'You may wish to hide the native title to extend the blocking ' + 'area:\n\n' + '![image](https://github.com/user-attachments/assets/62e16d4a-1e4d-4c4d-9f1b-f731d08e0b1c)', + function: () => + WindowManipulator.addToolbar(toolbar: const BlockingToolbar()), + ), + Command( + name: + 'addToolbar(toolbar: const BlockingToolbar(blockingAreaDebugColor: Colors.red))', + description: 'Adds a blocking toolbar to the window.\n\nBlocking ' + 'toolbars contain an area that stops double clicks from zooming the' + 'window, thus allowing for the placement of buttons that can be ' + 'clicked repeatedly.\n\nSetting the `blockingAreaDebugColor` to an ' + 'easily visible color can be useful for debugging purposes:\n\n' + '![image](https://github.com/user-attachments/assets/984c4dc7-f3ea-4b38-ba65-9e611982d32c)' + 'You may wish to hide the native title to extend the blocking ' + 'area:\n\n' + '![image](https://github.com/user-attachments/assets/62e16d4a-1e4d-4c4d-9f1b-f731d08e0b1c)', + function: () => WindowManipulator.addToolbar( + toolbar: const BlockingToolbar(blockingAreaDebugColor: Colors.red)), + ), Command( name: 'removeToolbar()', function: () => WindowManipulator.removeToolbar(), diff --git a/lib/toolbars/toolbars.dart b/lib/toolbars/toolbars.dart new file mode 100644 index 0000000..83d79a5 --- /dev/null +++ b/lib/toolbars/toolbars.dart @@ -0,0 +1,43 @@ +import 'dart:ui'; + +abstract class Toolbar { + const Toolbar(); + + String getName(); + + Map getArguments(); +} + +class DefaultToolbar extends Toolbar { + const DefaultToolbar(); + + @override + String getName() { + return "DefaultToolbar"; + } + + @override + Map getArguments() { + return {}; + } +} + +class BlockingToolbar extends Toolbar { + final Color? blockingAreaDebugColor; + + const BlockingToolbar({this.blockingAreaDebugColor}); + + @override + String getName() { + return "BlockingToolbar"; + } + + @override + Map getArguments() { + return { + "blockingAreaDebugColor": blockingAreaDebugColor == null + ? "" + : "${blockingAreaDebugColor!.red},${blockingAreaDebugColor!.green},${blockingAreaDebugColor!.blue},${blockingAreaDebugColor!.alpha}", + }; + } +} diff --git a/lib/window_manipulator.dart b/lib/window_manipulator.dart index 4ddb6a1..43b0183 100644 --- a/lib/window_manipulator.dart +++ b/lib/window_manipulator.dart @@ -12,6 +12,7 @@ import 'package:macos_window_utils/macos/visual_effect_view_properties.dart'; import 'package:macos_window_utils/macos/ns_visual_effect_view_material.dart'; import 'ns_window_delegate_handler/ns_window_delegate_handler.dart'; +import 'toolbars/toolbars.dart'; /// Class that provides methods to manipulate the application's window. class WindowManipulator { @@ -370,9 +371,39 @@ class WindowManipulator { } /// Adds a toolbar to the window. - static Future addToolbar() async { + /// + /// By default, the added toolbar is a [DefaultToolbar]. + /// + /// A [BlockingToolbar] can be added like this: + /// + /// ```dart + /// WindowManipulator.addToolbar( + /// toolbar: const BlockingToolbar(blockingAreaDebugColor: Colors.red)), + /// ); + /// ``` + /// + /// Blocking toolbars contain an area that stops double clicks from zooming the + /// window, thus allowing for the placement of buttons that can be clicked + /// repeatedly. + /// + /// Setting the `blockingAreaDebugColor` to an easily visible color can be + /// useful for debugging purposes: + /// + /// ![image](https://github.com/user-attachments/assets/984c4dc7-f3ea-4b38-ba65-9e611982d32c) + /// + /// You may wish to hide the native title to extend the blocking area: + /// + /// ![image](https://github.com/user-attachments/assets/62e16d4a-1e4d-4c4d-9f1b-f731d08e0b1c) + static Future addToolbar( + {Toolbar toolbar = const DefaultToolbar()}) async { await _completer.future; - await _windowManipulatorMethodChannel.invokeMethod('addToolbar'); + await _windowManipulatorMethodChannel.invokeMethod( + 'addToolbar', + { + 'toolbarName': toolbar.getName(), + 'toolbarArguments': toolbar.getArguments(), + }, + ); } /// Removes the window's toolbar. diff --git a/macos/Classes/BlockingToolbar.swift b/macos/Classes/BlockingToolbar.swift index ca9e43d..63a287e 100644 --- a/macos/Classes/BlockingToolbar.swift +++ b/macos/Classes/BlockingToolbar.swift @@ -10,9 +10,12 @@ import FlutterMacOS class BlockingToolbar: NSToolbar, NSToolbarDelegate { let flutterView: FlutterViewController + + let blockingAreaDebugColor: NSColor? - init(flutterView: FlutterViewController) { + init(flutterView: FlutterViewController, blockingAreaDebugColor: NSColor?) { self.flutterView = flutterView + self.blockingAreaDebugColor = blockingAreaDebugColor super.init(identifier: "BlockingToolbar") } @@ -32,6 +35,10 @@ class BlockingToolbar: NSToolbar, NSToolbarDelegate { view.widthAnchor.constraint(lessThanOrEqualToConstant: 100000).isActive = true view.widthAnchor.constraint(greaterThanOrEqualToConstant: 1).isActive = true item.view = view + if (blockingAreaDebugColor != nil) { + view.wantsLayer = true + view.layer?.backgroundColor = blockingAreaDebugColor!.cgColor + } return item } return nil diff --git a/macos/Classes/MacOSWindowUtilsPlugin.swift b/macos/Classes/MacOSWindowUtilsPlugin.swift index caaba09..087ddd7 100644 --- a/macos/Classes/MacOSWindowUtilsPlugin.swift +++ b/macos/Classes/MacOSWindowUtilsPlugin.swift @@ -283,7 +283,9 @@ public class MacOSWindowUtilsPlugin: NSObject, FlutterPlugin { break case "addToolbar": - MainFlutterWindowManipulator.addToolbar() + let toolbarName = args["toolbarName"] as! String + let toolbarArguments = args["toolbarArguments"] as! [String: String] + MainFlutterWindowManipulator.addToolbar(toolbarName: toolbarName, toolbarArguments: toolbarArguments) result(true) break diff --git a/macos/Classes/MainFlutterWindowManipulator.swift b/macos/Classes/MainFlutterWindowManipulator.swift index e93fd23..d5a8487 100644 --- a/macos/Classes/MainFlutterWindowManipulator.swift +++ b/macos/Classes/MainFlutterWindowManipulator.swift @@ -396,16 +396,29 @@ public class MainFlutterWindowManipulator { macOSWindowUtilsViewController.removeVisualEffectSubview(subviewId) } - public static func addToolbar() { + public static func addToolbar(toolbarName: String, toolbarArguments: [String: String]) { if (self.mainFlutterWindow == nil) { start(mainFlutterWindow: nil) } if #available(macOS 10.13, *) { - let customToolbar = BlockingToolbar(flutterView: (self.mainFlutterWindow?.contentViewController as! MacOSWindowUtilsViewController).flutterViewController) + switch (toolbarName) { + case "DefaultToolbar": + let newToolbar = NSToolbar() + + self.mainFlutterWindow!.toolbar = newToolbar + + case "BlockingToolbar": + let blockingAreaDebugColor = NSColor.colorFromRGBAString(toolbarArguments["blockingAreaDebugColor"]!) + + let customToolbar = BlockingToolbar(flutterView: (self.mainFlutterWindow?.contentViewController as! MacOSWindowUtilsViewController).flutterViewController, blockingAreaDebugColor: blockingAreaDebugColor) customToolbar.showsBaselineSeparator = false customToolbar.delegate = customToolbar self.mainFlutterWindow!.toolbar = customToolbar + + default: + print("Unknown toolbar name: \(toolbarName)") + } } } diff --git a/macos/Classes/colorFromRGBAString.swift b/macos/Classes/colorFromRGBAString.swift new file mode 100644 index 0000000..dc136f0 --- /dev/null +++ b/macos/Classes/colorFromRGBAString.swift @@ -0,0 +1,30 @@ +// +// colorFromRGBAString.swift +// macos_window_utils +// +// Created by Adrian Samoticha on 31.10.24. +// + +import Foundation + +import Cocoa + +extension NSColor { + // Function to convert an RGBA string like "255,0,0,128" into NSColor + class func colorFromRGBAString(_ rgbaString: String) -> NSColor? { + let components = rgbaString.split(separator: ",").map { String($0) } + + guard components.count == 4, + let red = Double(components[0]), + let green = Double(components[1]), + let blue = Double(components[2]), + let alpha = Double(components[3]) else { + return nil // Return nil if input is invalid + } + + return NSColor(red: CGFloat(red / 255.0), + green: CGFloat(green / 255.0), + blue: CGFloat(blue / 255.0), + alpha: CGFloat(alpha / 255.0)) + } +} From 72985490f3c544c8564effd1b068c13becabcaab Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:53:21 +0100 Subject: [PATCH 4/6] bump version to 1.5.0 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 8c49d70..b099137 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: macos_window_utils description: macos_window_utils is a Flutter package that provides a set of methods for modifying the NSWindow of a Flutter application on macOS. -version: 1.4.0 +version: 1.5.0 repository: https://github.com/Adrian-Samoticha/macos_window_utils.dart environment: From b00f755fc09b83eb1b2049916ce6cf4b5d11baec Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:53:48 +0100 Subject: [PATCH 5/6] add changelog entry for version 1.5.0 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cddd764..9b64da6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.5.0 + +- Add support for a blocking toolbar, that is, a special kind of toolbar that blocks double clicks from the user and allows for UI elements to be placed inside a blocked area (thanks to @cbenhagen and @Andre-lbc). + ## 1.4.0 - Add methods to retrieve or manipulate the window’s size and position. From c32950ba1d3850f46ea28b92aa00e0802a3050b6 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:58:22 +0100 Subject: [PATCH 6/6] bump version to 1.6.0 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 279ce33..99b2245 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: macos_window_utils description: macos_window_utils is a Flutter package that provides a set of methods for modifying the NSWindow of a Flutter application on macOS. -version: 1.5.0 +version: 1.6.0 repository: https://github.com/Adrian-Samoticha/macos_window_utils.dart environment: