Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(481)

Unified Diff: ui/base/cocoa/menu_controller.mm

Issue 2852233002: Mac[Views]: Make native menus more responsive by pumping private runloop modes. (Closed)
Patch Set: respond to comments Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ui/base/cocoa/menu_controller.h ('k') | ui/base/cocoa/menu_controller_unittest.mm » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ui/base/cocoa/menu_controller.mm
diff --git a/ui/base/cocoa/menu_controller.mm b/ui/base/cocoa/menu_controller.mm
index 0c42fe7ba34504e04d011ebb24215a1f75ea9e2d..4b9b8ab0f14b46f2bbad2bd886a4baa24478a7e6 100644
--- a/ui/base/cocoa/menu_controller.mm
+++ b/ui/base/cocoa/menu_controller.mm
@@ -4,8 +4,12 @@
#import "ui/base/cocoa/menu_controller.h"
+#include "base/cancelable_callback.h"
#include "base/logging.h"
+#include "base/mac/bind_objc_block.h"
+#include "base/memory/ptr_util.h"
#include "base/strings/sys_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/accelerators/platform_accelerator_cocoa.h"
#include "ui/base/l10n/l10n_util_mac.h"
@@ -20,15 +24,39 @@ NSString* const kMenuControllerMenuWillOpenNotification =
NSString* const kMenuControllerMenuDidCloseNotification =
@"MenuControllerMenuDidClose";
-@interface MenuController (Private)
-- (void)addSeparatorToMenu:(NSMenu*)menu
- atIndex:(int)index;
+// Internal methods.
+@interface MenuController ()
+// Adds a separator item at the given index. As the separator doesn't need
+// anything from the model, this method doesn't need the model index as the
+// other method below does.
+- (void)addSeparatorToMenu:(NSMenu*)menu atIndex:(int)index;
+
+// Called via a private API hook shortly after the event that selects a menu
+// item arrives.
+- (void)itemWillBeSelected:(NSMenuItem*)sender;
+
+// Called when the user chooses a particular menu item. AppKit sends this only
+// after the menu has fully faded out. |sender| is the menu item chosen.
+- (void)itemSelected:(id)sender;
+
+// Called by the posted task to selected an item during menu fade out.
+// |uiEventFlags| are the ui::EventFlags captured from the triggering NSEvent.
+- (void)itemSelected:(id)sender uiEventFlags:(int)uiEventFlags;
@end
-@implementation MenuController
+@interface ResponsiveNSMenuItem : NSMenuItem
+@end
+
+@implementation MenuController {
+ BOOL useWithPopUpButtonCell_; // If YES, 0th item is blank
+ BOOL isMenuOpen_;
+ BOOL postItemSelectedAsTask_;
+ std::unique_ptr<base::CancelableClosure> postedItemSelectedTask_;
+}
@synthesize model = model_;
@synthesize useWithPopUpButtonCell = useWithPopUpButtonCell_;
+@synthesize postItemSelectedAsTask = postItemSelectedAsTask_;
+ (base::string16)elideMenuTitle:(const base::string16&)title
toWidth:(int)width {
@@ -71,8 +99,6 @@ NSString* const kMenuControllerMenuDidCloseNotification =
}
}
-// Creates a NSMenu from the given model. If the model has submenus, this can
-// be invoked recursively.
- (NSMenu*)menuFromModel:(ui::MenuModel*)model {
NSMenu* menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
@@ -92,17 +118,12 @@ NSString* const kMenuControllerMenuDidCloseNotification =
return -1;
}
-// Adds a separator item at the given index. As the separator doesn't need
-// anything from the model, this method doesn't need the model index as the
-// other method below does.
- (void)addSeparatorToMenu:(NSMenu*)menu
atIndex:(int)index {
NSMenuItem* separator = [NSMenuItem separatorItem];
[menu insertItem:separator atIndex:index];
}
-// Adds an item or a hierarchical menu to the item at the |index|,
-// associated with the entry in the model identified by |modelIndex|.
- (void)addItemToMenu:(NSMenu*)menu
atIndex:(NSInteger)index
fromModel:(ui::MenuModel*)model {
@@ -112,10 +133,10 @@ NSString* const kMenuControllerMenuDidCloseNotification =
label16 = [MenuController elideMenuTitle:label16 toWidth:maxWidth];
NSString* label = l10n_util::FixUpWindowsStyleLabel(label16);
- base::scoped_nsobject<NSMenuItem> item(
- [[NSMenuItem alloc] initWithTitle:label
- action:@selector(itemSelected:)
- keyEquivalent:@""]);
+ base::scoped_nsobject<NSMenuItem> item([[ResponsiveNSMenuItem alloc]
+ initWithTitle:label
+ action:@selector(itemSelected:)
+ keyEquivalent:@""]);
// If the menu item has an icon, set it.
gfx::Image icon;
@@ -156,9 +177,6 @@ NSString* const kMenuControllerMenuDidCloseNotification =
[menu insertItem:item atIndex:index];
}
-// Called before the menu is to be displayed to update the state (enabled,
-// radio, etc) of each item in the menu. Also will update the title if
-// the item is marked as "dynamic".
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
SEL action = [item action];
if (action != @selector(itemSelected:))
@@ -200,18 +218,48 @@ NSString* const kMenuControllerMenuDidCloseNotification =
return NO;
}
-// Called when the user chooses a particular menu item. |sender| is the menu
-// item chosen.
+- (void)itemWillBeSelected:(NSMenuItem*)sender {
+ if (postItemSelectedAsTask_ && [sender action] == @selector(itemSelected:) &&
+ [[sender target]
+ respondsToSelector:@selector(itemSelected:uiEventFlags:)]) {
+ const int uiEventFlags = ui::EventFlagsFromNative([NSApp currentEvent]);
+ postedItemSelectedTask_ =
+ base::MakeUnique<base::CancelableClosure>(base::BindBlock(^{
+ id target = [sender target];
+ if ([target respondsToSelector:@selector(itemSelected:uiEventFlags:)])
+ [target itemSelected:sender uiEventFlags:uiEventFlags];
+ else
+ NOTREACHED();
+ }));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, postedItemSelectedTask_->callback());
+ }
+}
+
- (void)itemSelected:(id)sender {
+ if (postedItemSelectedTask_) {
+ if (!postedItemSelectedTask_->IsCancelled())
+ postedItemSelectedTask_->callback().Run();
+ postedItemSelectedTask_.reset();
+ } else {
+ [self itemSelected:sender
+ uiEventFlags:ui::EventFlagsFromNative([NSApp currentEvent])];
+ }
+}
+
+- (void)itemSelected:(id)sender uiEventFlags:(int)uiEventFlags {
NSInteger modelIndex = [sender tag];
ui::MenuModel* model =
static_cast<ui::MenuModel*>(
[[sender representedObject] pointerValue]);
DCHECK(model);
- if (model) {
- int event_flags = ui::EventFlagsFromNative([NSApp currentEvent]);
- model->ActivatedAt(modelIndex, event_flags);
- }
+ if (model)
+ model->ActivatedAt(modelIndex, uiEventFlags);
+
+ // Cancel any posted task, but don't reset it, so that the correct path is
+ // taken in -itemSelected:.
+ if (postedItemSelectedTask_)
+ postedItemSelectedTask_->Cancel();
}
- (NSMenu*)menu {
@@ -254,3 +302,18 @@ NSString* const kMenuControllerMenuDidCloseNotification =
}
@end
+
+@interface NSMenuItem (Private)
+// Private method which is invoked very soon after the event that activates a
+// menu item is received. AppKit then spends 300ms or so flashing the menu item,
+// and fading out the menu, in private run loop modes.
+- (void)_sendItemSelectedNote;
+@end
+
+@implementation ResponsiveNSMenuItem
+- (void)_sendItemSelectedNote {
+ if ([[self target] respondsToSelector:@selector(itemWillBeSelected:)])
+ [[self target] itemWillBeSelected:self];
+ [super _sendItemSelectedNote];
+}
+@end
« no previous file with comments | « ui/base/cocoa/menu_controller.h ('k') | ui/base/cocoa/menu_controller_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698