diff --git a/CHANGELOG.md b/CHANGELOG.md index c5f52a2..5f6e77c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ -## 1.5.0 +## 1.6.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.5.0 - Add the following window methods: - `preventWindowClosure` - `allowWindowClosure` 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 52c75b5..644460a 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/example/macos/Podfile.lock b/example/macos/Podfile.lock index 0b4c57d..9bf4952 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -25,4 +25,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 500e4707112a5f11963bc198135953cdebb6d50c -COCOAPODS: 1.13.0 +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 @@ 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 9058ac1..1cff071 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 new file mode 100644 index 0000000..63a287e --- /dev/null +++ b/macos/Classes/BlockingToolbar.swift @@ -0,0 +1,59 @@ +// +// BlockingToolbar.swift +// macos_window_utils +// +// Created by Ben Hagen on 12.11.2023. +// + +import Foundation +import FlutterMacOS + +class BlockingToolbar: NSToolbar, NSToolbarDelegate { + let flutterView: FlutterViewController + + let blockingAreaDebugColor: NSColor? + + init(flutterView: FlutterViewController, blockingAreaDebugColor: NSColor?) { + self.flutterView = flutterView + self.blockingAreaDebugColor = blockingAreaDebugColor + 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 + if (blockingAreaDebugColor != nil) { + view.wantsLayer = true + view.layer?.backgroundColor = blockingAreaDebugColor!.cgColor + } + 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/MacOSWindowUtilsPlugin.swift b/macos/Classes/MacOSWindowUtilsPlugin.swift index 373d04b..554ec6f 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 b7cf018..5ee24bb 100644 --- a/macos/Classes/MainFlutterWindowManipulator.swift +++ b/macos/Classes/MainFlutterWindowManipulator.swift @@ -396,15 +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, *) { + 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)) + } +} 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: