-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve native event handling in the toolbar area. #53
Comments
This looks great. Do you think it’d be possible to implement this into macos_window_utils in such a way that users wouldn’t need to update their Right now, macos_window_utils is usable without touching any Swift code (unless you’re targeting older macOS versions, see #44). It’d be great if your proposal could be implemented in such a way that this continues to be the case. |
I think so. I am not so familiar with So, maybe we can edit MacOSWindowUtilsPlugin.swift, adding these changes into its
Then it is just a matter of organizing the new methods/classes/variables according to the project structure. |
If I understand correctly, your |
Yes, they can be added to any toolbar (as far as I tested). I would also like to share the solutions to some problems I had after upgrading to Sequoia. The problem is that right clicking on the toolbar area is no longer passed to flutter because it now opens a native menu (i.e.: it consumes the event and does not open my menu implementation on flutter side). To fix this issue, add the following changes to the code above: MainFlutterWindow.swift
On method channel setup: //... Same as before
case "initialize":
// Clean up necessary for flutter hot restart
self.toolbarPassthroughViews.removeAll()
self.toolbarPassthroughContainer = nil
+ // Disable toolbar customizations and remove native context menu (right click)
+ if let toolbar = self.toolbar {
+ toolbar.allowsUserCustomization = false
+ toolbar.allowsExtensionItems = false
+ if #available(macOS 15.0, *) {
+ toolbar.allowsDisplayModeCustomization = false
+ }
+ }
#if DEBUG
let args = call.arguments as? [String: Any]
let showDebugLayers = args?["showDebugLayers"] as? Bool
self.hasToolbarDebugLayers = showDebugLayers ?? false
#endif
// Get the count of accessory view controllers
let accessoryCount = self.titlebarAccessoryViewControllers.count
// Iterate through the indices in reverse order to avoid index shifting
for index in stride(from: accessoryCount - 1, through: 0, by: -1) {
self.removeTitlebarAccessoryViewController(at: index)
}
result(nil)
//... Same as before
On PassthroughView: class PassthroughView: NSView {
var flutterViewController: FlutterViewController?
required init(frame: CGRect, flutterViewController: FlutterViewController) {
super.init(frame: frame)
self.flutterViewController = flutterViewController
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func mouseDown(with event: NSEvent) {
flutterViewController!.mouseDown(with: event)
}
override func mouseUp(with event: NSEvent) {
flutterViewController!.mouseUp(with: event)
}
override var mouseDownCanMoveWindow: Bool {
return false
}
+ override func rightMouseUp(with event: NSEvent) {
+ flutterViewController!.rightMouseUp(with: event)
+ }
+
+ override func rightMouseDown(with event: NSEvent) {
+ flutterViewController!.rightMouseDown(with: event)
+ }
} |
@Andre-lbc Excuse the delay, I was a little busy lately. You’re right, right clicking on a toolbar in macOS Sequoia seems to open a context menu that allows the user to choose whether toolbar items appear as icons or text. I’d argue that is an issue in and of itself and needs to be fixed as well, so thanks for the heads up. |
The issue:
Interacting (double-clicking or dragging) with areas in the Flutter app where the native macOS toolbar would typically be triggers native actions like maximizing or moving the window. While this is expected behavior on "empty" areas, it's undesirable when interacting with interactive widgets, such as buttons and draggable interfaces.
The solution:
I've developed a solution that involves creating and managing invisible native macOS UI elements (
NSViews
) in Swift. These elements intercept the native toolbar events, preventing them from being processed natively and instead passing them to the Flutter engine for handling within the app.I've implemented this fix in my app project, and I'd like to share it upstream for further discussion (e.g.: better naming, code improvements, etc) and potential integration into the package.
Preview:
With debug layers
Screen.Recording.2024-10-31.at.09.42.51.mp4
Overlay legend:
Without debug overlays
Screen.Recording.2024-10-31.at.09.48.03.mp4
Key code points:
MacosToolbarPassthrough
: A widget that watches its child and creates an invisible "passthrough" equivalent on the native side. Mouse events on this "passthrough" item do not trigger native events like expanding or dragging the window. Most layout changes in the child automatically trigger an update on the native equivalent, but you can also manually trigger an update usingrequestUpdate
from the widget's state (MacosToolbarPassthroughState
).Most simple UI (e.g.: static size and fixed positions) may only need to use one or multiple
MacosToolbarPassthrough
. More complex use cases may also require the addition of aMacosToolbarPassthroughScope
.MacosToolbarPassthroughScope
(optional): Flutter is optimized to avoid unnecessarily drawing UI elements, so detecting some changes in UI can be expensive or cumbersome. Even worse, sometimes the implementation may not be able to detect these changes at all (such as in some scrolling widgets or widgets that either move or change size in uncommon ways), resulting in flutter widgets being out of sync relative to their native counterpart.Wrapping multiple
MacosToolbarPassthrough
under aMacosToolbarPassthroughScope
allows you to both:1- Trigger a reevaluation on all scoped items when any of them internally requests a reevaluation.
2- Manually trigger these reevaluation events if needed (using
notifyChangesOf
).When a scoped item detects a change that would normally trigger a reevaluation of their position and size, they instead notify their parent scope, which then notifies all their scoped items to reevaluate and update their positions and sizes.
NSTitlebarAccessoryViewController
instead ofNSToolbar
because the later has a padding that I could not remove and also was much more difficult to work with (e.g.: position items).Code:
MainFlutterWindow.swift
macos_toolbar_passthrough.dart
main.dart
This builds upon improvements made in #43
The text was updated successfully, but these errors were encountered: