Commit 404bbbd8 authored by Robert Sesek's avatar Robert Sesek Committed by Commit Bot

mac: Delete some unused Cocoa control types.

Bug: 832676
Change-Id: Ida8c7de8f6245e76d1f18928efa1929e642ed0b9
Reviewed-on: https://chromium-review.googlesource.com/1241714
Commit-Queue: Nico Weber <thakis@chromium.org>
Reviewed-by: default avatarNico Weber <thakis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#593763}
parent 3e60c042
......@@ -117,10 +117,6 @@ jumbo_split_static_library("ui") {
"cocoa/device_chooser_content_view_cocoa.mm",
"cocoa/drag_util.h",
"cocoa/drag_util.mm",
"cocoa/draggable_button.h",
"cocoa/draggable_button.mm",
"cocoa/draggable_button_mixin.h",
"cocoa/draggable_button_mixin.mm",
"cocoa/extensions/extension_keybinding_registry_cocoa.h",
"cocoa/extensions/extension_keybinding_registry_cocoa.mm",
"cocoa/fast_resize_view.h",
......@@ -149,8 +145,6 @@ jumbo_split_static_library("ui") {
"cocoa/fullscreen_placeholder_view.mm",
"cocoa/fullscreen_window.h",
"cocoa/fullscreen_window.mm",
"cocoa/harmony_button.h",
"cocoa/harmony_button.mm",
"cocoa/has_weak_browser_pointer.h",
"cocoa/hover_close_button.h",
"cocoa/hover_close_button.mm",
......@@ -177,8 +171,6 @@ jumbo_split_static_library("ui") {
"cocoa/main_menu_item.h",
"cocoa/menu_button.h",
"cocoa/menu_button.mm",
"cocoa/multi_key_equivalent_button.h",
"cocoa/multi_key_equivalent_button.mm",
"cocoa/nsview_additions.h",
"cocoa/nsview_additions.mm",
"cocoa/omnibox/omnibox_popup_cell.h",
......@@ -2197,8 +2189,6 @@ jumbo_split_static_library("ui") {
"cocoa/last_active_browser_cocoa.h",
"cocoa/main_menu_builder.h",
"cocoa/main_menu_builder.mm",
"cocoa/md_hover_button.h",
"cocoa/md_hover_button.mm",
"cocoa/md_util.h",
"cocoa/md_util.mm",
"cocoa/media_picker/desktop_media_picker_bridge.h",
......
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_COCOA_DRAGGABLE_BUTTON_H_
#define CHROME_BROWSER_UI_COCOA_DRAGGABLE_BUTTON_H_
#import <Cocoa/Cocoa.h>
#import "base/mac/scoped_nsobject.h"
#import "chrome/browser/ui/cocoa/draggable_button_mixin.h"
// Class for buttons that can be drag sources. If the mouse is clicked and moved
// more than a given distance, this class will call |-beginDrag:| instead of
// |-performClick:|. Subclasses should override these two methods.
@interface DraggableButton : NSButton<DraggableButtonMixin> {
@private
base::scoped_nsobject<DraggableButtonImpl> draggableButtonImpl_;
}
@property(readonly, nonatomic) DraggableButtonImpl* draggableButton;
@end
#endif // CHROME_BROWSER_UI_COCOA_DRAGGABLE_BUTTON_H_
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/draggable_button.h"
#include "base/logging.h"
@implementation DraggableButton
- (id)initWithFrame:(NSRect)frame {
if ((self = [super initWithFrame:frame])) {
draggableButtonImpl_.reset(
[[DraggableButtonImpl alloc] initWithButton:self]);
}
return self;
}
- (id)initWithCoder:(NSCoder*)coder {
if ((self = [super initWithCoder:coder])) {
draggableButtonImpl_.reset(
[[DraggableButtonImpl alloc] initWithButton:self]);
}
return self;
}
- (DraggableButtonImpl*)draggableButton {
return draggableButtonImpl_.get();
}
- (void)mouseUp:(NSEvent*)theEvent {
if ([draggableButtonImpl_ mouseUpImpl:theEvent] ==
kDraggableButtonMixinCallSuper) {
[super mouseUp:theEvent];
}
}
- (void)mouseDown:(NSEvent*)theEvent {
// The impl spins an event loop to distinguish clicks from drags,
// which could result in our destruction. Wire ourselves down for
// the duration.
base::scoped_nsobject<DraggableButton> keepAlive([self retain]);
if ([draggableButtonImpl_ mouseDownImpl:theEvent] ==
kDraggableButtonMixinCallSuper) {
// Hack to suppress a crash. See http://crbug.com/509833 for details.
if ([self window] && ![self isHiddenOrHasHiddenAncestor])
[super mouseDown:theEvent];
}
}
- (void)beginDrag:(NSEvent*)dragEvent {
// Must be overridden by subclasses.
NOTREACHED();
}
@end // @interface DraggableButton
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_COCOA_DRAGGABLE_BUTTON_MIXIN_H_
#define CHROME_BROWSER_UI_COCOA_DRAGGABLE_BUTTON_MIXIN_H_
#import <Cocoa/Cocoa.h>
// The design of this class is extraordinarily poor. Apologies to all clients in
// advance. Unfortunately, the lack of multiple inheritance and our desire to
// avoid runtime hacks makes this convoluted dance necessary.
//
// Buttons that want to be draggable should implement the Mixin protocol below
// and keep an instance of the Impl as an ivar. The button should forward mouse
// events to the impl, which will tell the button whether or not to call super
// and let the event be handled normally.
//
// If the impl decides to do work on the event, methods of the mixin protocol
// may be called. Some of the methods declared in that protocol have base
// implementations. If the method is not implemented by the button, that base
// implementation will be called. Otherwise, the button's implementation will
// be called first and the DraggableButtonResult will be used to determine
// whether the base implementation should be called. This requires the client to
// understand what the base does.
enum DraggableButtonResult {
// Return values for Impl methods.
kDraggableButtonImplDidWork,
kDraggableButtonMixinCallSuper,
// Return values for Mixin methods.
kDraggableButtonMixinDidWork,
kDraggableButtonImplUseBase,
};
// Mixin Protocol //////////////////////////////////////////////////////////////
// Buttons that make use of the below impl need to conform to this protocol.
@protocol DraggableButtonMixin
@required
// Called when a drag should start. Implement this to do any pasteboard
// manipulation and begin the drag, usually with
// -dragImage:at:offset:event:. Subclasses must call one of the blocking
// -drag* methods of NSView when implementing this method.
- (void)beginDrag:(NSEvent*)dragEvent;
@optional
// Called if the actsOnMouseDown property is set. Fires the button's action and
// tracks the click.
- (DraggableButtonResult)performMouseDownAction:(NSEvent*)theEvent;
// Implement if you want to do any extra work on mouseUp, after a mouseDown
// action has already fired.
- (DraggableButtonResult)secondaryMouseUpAction:(BOOL)wasInside;
// Resets the draggable state of the button after dragging is finished. This is
// called by DraggableButtonImpl when the beginDrag call returns.
- (DraggableButtonResult)endDrag;
// Decides whether to treat the click as a cue to start dragging, or to instead
// call the mouseDown/mouseUp handler as appropriate. Implement if you want to
// do something tricky when making the decision.
- (DraggableButtonResult)deltaIndicatesDragStartWithXDelta:(float)xDelta
yDelta:(float)yDelta
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis
indicates:(BOOL*)result;
// Decides if there is enough information to stop tracking the mouse.
// It's deltaIndicatesDragStartWithXDelta, however, that decides whether it's a
// drag or not. Implement if you want to do something tricky when making the
// decision.
- (DraggableButtonResult)deltaIndicatesConclusionReachedWithXDelta:(float)xDelta
yDelta:(float)yDelta
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis
indicates:(BOOL*)result;
@end
// Impl Interface //////////////////////////////////////////////////////////////
// Implementation of the drag and drop logic. NSButton Mixin subclasses should
// forward their mouse events to this, which in turn will call out to the mixin
// protocol.
@interface DraggableButtonImpl : NSObject {
@private
// The button for which this class is implementing stuff.
NSButton<DraggableButtonMixin>* button_;
// Is this a draggable type of button?
BOOL draggable_;
// Has the action already fired for this click?
BOOL actionHasFired_;
// Does button action happen on mouse down when possible?
BOOL actsOnMouseDown_;
NSTimeInterval durationMouseWasDown_;
NSTimeInterval whenMouseDown_;
}
@property(nonatomic) NSTimeInterval durationMouseWasDown;
@property(nonatomic) NSTimeInterval whenMouseDown;
// Whether the action has already fired for this click.
@property(nonatomic) BOOL actionHasFired;
// Enable or disable dragability for special buttons like "Other Bookmarks".
@property(nonatomic) BOOL draggable;
// If it has a popup menu, for example, we want to perform the action on mouse
// down, if possible (as long as user still gets chance to drag, if
// appropriate).
@property(nonatomic) BOOL actsOnMouseDown;
// Designated initializer.
- (id)initWithButton:(NSButton<DraggableButtonMixin>*)button;
// NSResponder implementation. NSButton subclasses should invoke these methods
// and only call super if the return value indicates such.
- (DraggableButtonResult)mouseDownImpl:(NSEvent*)event;
- (DraggableButtonResult)mouseUpImpl:(NSEvent*)event;
@end
#endif // CHROME_BROWSER_UI_COCOA_DRAGGABLE_BUTTON_MIXIN_H_
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/draggable_button.h"
#include <cmath>
#include "base/logging.h"
// Code taken from <http://codereview.chromium.org/180036/diff/3001/3004>.
// TODO(viettrungluu): Do we want common, standard code for drag hysteresis?
const CGFloat kWebDragStartHysteresisX = 5.0;
const CGFloat kWebDragStartHysteresisY = 5.0;
const CGFloat kDragExpirationTimeout = 0.45;
// Private /////////////////////////////////////////////////////////////////////
@interface DraggableButtonImpl (Private)
- (BOOL)deltaIndicatesDragStartWithXDelta:(float)xDelta
yDelta:(float)yDelta
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis;
- (BOOL)deltaIndicatesConclusionReachedWithXDelta:(float)xDelta
yDelta:(float)yDelta
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis;
- (void)performMouseDownAction:(NSEvent*)theEvent;
- (void)endDrag;
- (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent
withExpiration:(NSDate*)expiration;
- (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent
withExpiration:(NSDate*)expiration
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis;
@end
// Implementation //////////////////////////////////////////////////////////////
@implementation DraggableButtonImpl
@synthesize draggable = draggable_;
@synthesize actsOnMouseDown = actsOnMouseDown_;
@synthesize durationMouseWasDown = durationMouseWasDown_;
@synthesize actionHasFired = actionHasFired_;
@synthesize whenMouseDown = whenMouseDown_;
- (id)initWithButton:(NSButton<DraggableButtonMixin>*)button {
if ((self = [super init])) {
button_ = button;
draggable_ = YES;
actsOnMouseDown_ = NO;
actionHasFired_ = NO;
}
return self;
}
// NSButton/NSResponder Implementations ////////////////////////////////////////
- (DraggableButtonResult)mouseUpImpl:(NSEvent*)theEvent {
durationMouseWasDown_ = [theEvent timestamp] - whenMouseDown_;
if (actionHasFired_)
return kDraggableButtonImplDidWork;
if (!draggable_)
return kDraggableButtonMixinCallSuper;
// There are non-drag cases where a |-mouseUp:| may happen (e.g. mouse-down,
// cmd-tab to another application, move mouse, mouse-up), so check.
NSPoint viewLocal = [button_ convertPoint:[theEvent locationInWindow]
fromView:[[button_ window] contentView]];
if (NSPointInRect(viewLocal, [button_ bounds]))
[button_ performClick:self];
return kDraggableButtonImplDidWork;
}
// Mimic "begin a click" operation visually. Do NOT follow through with normal
// button event handling.
- (DraggableButtonResult)mouseDownImpl:(NSEvent*)theEvent {
[[NSCursor arrowCursor] set];
whenMouseDown_ = [theEvent timestamp];
actionHasFired_ = NO;
if (draggable_) {
NSDate* date = [NSDate dateWithTimeIntervalSinceNow:kDragExpirationTimeout];
if ([self dragShouldBeginFromMouseDown:theEvent
withExpiration:date]) {
[button_ beginDrag:theEvent];
[self endDrag];
} else {
if (actsOnMouseDown_) {
[self performMouseDownAction:theEvent];
return kDraggableButtonImplDidWork;
} else {
return kDraggableButtonMixinCallSuper;
}
}
} else {
if (actsOnMouseDown_) {
[self performMouseDownAction:theEvent];
return kDraggableButtonImplDidWork;
} else {
return kDraggableButtonMixinCallSuper;
}
}
return kDraggableButtonImplDidWork;
}
// Idempotent Helpers //////////////////////////////////////////////////////////
- (BOOL)deltaIndicatesDragStartWithXDelta:(float)xDelta
yDelta:(float)yDelta
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis {
if ([button_ respondsToSelector:@selector(deltaIndicatesDragStartWithXDelta:
yDelta:
xHysteresis:
yHysteresis:
indicates:)]) {
BOOL indicates = NO;
DraggableButtonResult result = [button_
deltaIndicatesDragStartWithXDelta:xDelta
yDelta:yDelta
xHysteresis:xHysteresis
yHysteresis:yHysteresis
indicates:&indicates];
if (result != kDraggableButtonImplUseBase)
return indicates;
}
return (std::abs(xDelta) >= xHysteresis) || (std::abs(yDelta) >= yHysteresis);
}
- (BOOL)deltaIndicatesConclusionReachedWithXDelta:(float)xDelta
yDelta:(float)yDelta
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis {
if ([button_ respondsToSelector:
@selector(deltaIndicatesConclusionReachedWithXDelta:
yDelta:
xHysteresis:
yHysteresis:
indicates:)]) {
BOOL indicates = NO;
DraggableButtonResult result = [button_
deltaIndicatesConclusionReachedWithXDelta:xDelta
yDelta:yDelta
xHysteresis:xHysteresis
yHysteresis:yHysteresis
indicates:&indicates];
if (result != kDraggableButtonImplUseBase)
return indicates;
}
return (std::abs(xDelta) >= xHysteresis) || (std::abs(yDelta) >= yHysteresis);
}
- (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent
withExpiration:(NSDate*)expiration {
return [self dragShouldBeginFromMouseDown:mouseDownEvent
withExpiration:expiration
xHysteresis:kWebDragStartHysteresisX
yHysteresis:kWebDragStartHysteresisY];
}
// Implementation Details //////////////////////////////////////////////////////
// Determine whether a mouse down should turn into a drag; started as copy of
// NSTableView code.
- (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent
withExpiration:(NSDate*)expiration
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis {
if ([mouseDownEvent type] != NSLeftMouseDown) {
return NO;
}
NSEvent* nextEvent = nil;
NSEvent* firstEvent = nil;
NSEvent* dragEvent = nil;
NSEvent* mouseUp = nil;
BOOL dragIt = NO;
while ((nextEvent = [[button_ window]
nextEventMatchingMask:NSLeftMouseUpMask | NSLeftMouseDraggedMask
untilDate:expiration
inMode:NSEventTrackingRunLoopMode
dequeue:YES]) != nil) {
if (firstEvent == nil) {
firstEvent = nextEvent;
}
if ([nextEvent type] == NSLeftMouseDragged) {
float deltax = [nextEvent locationInWindow].x -
[mouseDownEvent locationInWindow].x;
float deltay = [nextEvent locationInWindow].y -
[mouseDownEvent locationInWindow].y;
dragEvent = nextEvent;
if ([self deltaIndicatesConclusionReachedWithXDelta:deltax
yDelta:deltay
xHysteresis:xHysteresis
yHysteresis:yHysteresis]) {
dragIt = [self deltaIndicatesDragStartWithXDelta:deltax
yDelta:deltay
xHysteresis:xHysteresis
yHysteresis:yHysteresis];
break;
}
} else if ([nextEvent type] == NSLeftMouseUp) {
mouseUp = nextEvent;
break;
}
}
// Since we've been dequeuing the events (If we don't, we'll never see
// the mouse up...), we need to push some of the events back on.
// It makes sense to put the first and last drag events and the mouse
// up if there was one.
if (mouseUp != nil) {
[NSApp postEvent:mouseUp atStart:YES];
}
if (dragEvent != nil) {
[NSApp postEvent:dragEvent atStart:YES];
}
if (firstEvent != mouseUp && firstEvent != dragEvent) {
[NSApp postEvent:firstEvent atStart:YES];
}
return dragIt;
}
- (void)secondaryMouseUpAction:(BOOL)wasInside {
if ([button_ respondsToSelector:_cmd])
[button_ secondaryMouseUpAction:wasInside];
// No actual implementation yet.
}
- (void)performMouseDownAction:(NSEvent*)event {
if ([button_ respondsToSelector:_cmd] &&
[button_ performMouseDownAction:event] != kDraggableButtonImplUseBase) {
return;
}
int eventMask = NSLeftMouseUpMask;
[[button_ target] performSelector:[button_ action] withObject:self];
actionHasFired_ = YES;
while (1) {
event = [[button_ window] nextEventMatchingMask:eventMask];
if (!event)
continue;
NSPoint mouseLoc = [button_ convertPoint:[event locationInWindow]
fromView:nil];
BOOL isInside = [button_ mouse:mouseLoc inRect:[button_ bounds]];
[button_ highlight:isInside];
switch ([event type]) {
case NSLeftMouseUp:
durationMouseWasDown_ = [event timestamp] - whenMouseDown_;
[self secondaryMouseUpAction:isInside];
break;
default:
// Ignore any other kind of event.
break;
}
}
[button_ highlight:NO];
}
- (void)endDrag {
if ([button_ respondsToSelector:_cmd] &&
[button_ endDrag] != kDraggableButtonImplUseBase) {
return;
}
[button_ highlight:NO];
}
@end
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/mac/scoped_nsobject.h"
#import "chrome/browser/ui/cocoa/draggable_button.h"
#import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#import "ui/events/test/cocoa_test_event_utils.h"
@interface TestableDraggableButton : DraggableButton {
NSUInteger dragCount_;
BOOL wasTriggered_;
}
- (void)trigger:(id)sender;
- (BOOL)wasTriggered;
- (NSUInteger)dragCount;
@end
@implementation TestableDraggableButton
- (id)initWithFrame:(NSRect)frame {
if ((self = [super initWithFrame:frame])) {
dragCount_ = 0;
wasTriggered_ = NO;
}
return self;
}
- (void)beginDrag:(NSEvent*)theEvent {
dragCount_++;
}
- (void)trigger:(id)sender {
wasTriggered_ = YES;
}
- (BOOL)wasTriggered {
return wasTriggered_;
}
- (NSUInteger)dragCount {
return dragCount_;
}
@end
class DraggableButtonTest : public CocoaTest {};
// Make sure the basic case of "click" still works.
TEST_F(DraggableButtonTest, DownUp) {
base::scoped_nsobject<TestableDraggableButton> button(
[[TestableDraggableButton alloc]
initWithFrame:NSMakeRect(0, 0, 500, 500)]);
[[test_window() contentView] addSubview:button.get()];
[button setTarget:button];
[button setAction:@selector(trigger:)];
EXPECT_FALSE([button wasTriggered]);
NSEvent* downEvent =
cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(10,10),
NSLeftMouseDown,
0);
NSEvent* upEvent =
cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(10,10),
NSLeftMouseUp,
0);
[NSApp postEvent:upEvent atStart:YES];
[test_window() sendEvent:downEvent];
EXPECT_TRUE([button wasTriggered]); // confirms target/action fired
}
TEST_F(DraggableButtonTest, DraggableHysteresis) {
base::scoped_nsobject<TestableDraggableButton> button(
[[TestableDraggableButton alloc]
initWithFrame:NSMakeRect(0, 0, 500, 500)]);
[[test_window() contentView] addSubview:button.get()];
NSEvent* downEvent =
cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(10,10),
NSLeftMouseDown,
0);
NSEvent* firstMove =
cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(11,11),
NSLeftMouseDragged,
0);
NSEvent* firstUpEvent =
cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(11,11),
NSLeftMouseUp,
0);
NSEvent* secondMove =
cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(100,100),
NSLeftMouseDragged,
0);
NSEvent* secondUpEvent =
cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(100,100),
NSLeftMouseUp,
0);
// If the mouse only moves one pixel in each direction
// it should not cause a drag.
[NSApp postEvent:firstUpEvent atStart:YES];
[NSApp postEvent:firstMove atStart:YES];
[button mouseDown:downEvent];
EXPECT_EQ(0U, [button dragCount]);
// If the mouse moves > 5 pixels in either direciton
// it should cause a drag.
[NSApp postEvent:secondUpEvent atStart:YES];
[NSApp postEvent:secondMove atStart:YES];
[button mouseDown:downEvent];
EXPECT_EQ(1U, [button dragCount]);
}
TEST_F(DraggableButtonTest, ResetState) {
base::scoped_nsobject<TestableDraggableButton> button(
[[TestableDraggableButton alloc]
initWithFrame:NSMakeRect(0, 0, 500, 500)]);
[[test_window() contentView] addSubview:button.get()];
NSEvent* downEvent =
cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(10,10),
NSLeftMouseDown,
0);
NSEvent* moveEvent =
cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(100,100),
NSLeftMouseDragged,
0);
NSEvent* upEvent =
cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(100,100),
NSLeftMouseUp,
0);
// If the mouse moves > 5 pixels in either direciton it should cause a drag.
[NSApp postEvent:upEvent atStart:YES];
[NSApp postEvent:moveEvent atStart:YES];
[button mouseDown:downEvent];
// The button should not be highlighted after the drag finishes.
EXPECT_FALSE([[button cell] isHighlighted]);
EXPECT_EQ(1U, [button dragCount]);
// We should be able to initiate another drag immediately after the first one.
[NSApp postEvent:upEvent atStart:YES];
[NSApp postEvent:moveEvent atStart:YES];
[button mouseDown:downEvent];
EXPECT_EQ(2U, [button dragCount]);
EXPECT_FALSE([[button cell] isHighlighted]);
}
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_COCOA_HARMONY_BUTTON_H_
#define CHROME_BROWSER_UI_COCOA_HARMONY_BUTTON_H_
#import <AppKit/AppKit.h>
#import "ui/base/cocoa/hover_button.h"
// HarmonyButton follows the Harmony design spec. It has slightly rounded
// corners, a border, and a shadow that appears on hover. Its color scheme
// tracks the active theme. Not every part of the spec for Harmony buttons has
// been implemented: it doesn't look different if it's the default button, or
// if it's disabled, for instance. Anyone who needs these things is encouraged
// to find joy in adding support for them.
@interface HarmonyButton : HoverButtonCocoa
+ (instancetype)buttonWithTitle:(NSString*)title
target:(id)target
action:(SEL)action;
@end
#endif // CHROME_BROWSER_UI_COCOA_HARMONY_BUTTON_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/harmony_button.h"
#import "base/mac/scoped_cftyperef.h"
#import "chrome/browser/themes/theme_properties.h"
#import "chrome/browser/ui/cocoa/themed_window.h"
#include "skia/ext/skia_utils_mac.h"
#import "ui/base/cocoa/nsview_additions.h"
#import "ui/base/theme_provider.h"
namespace {
NSColor* GetBackgroundColor(CloseButtonHoverState state, BOOL dark_theme) {
if (dark_theme) {
switch (state) {
case kHoverStateNone:
return [NSColor colorWithCalibratedWhite:1 alpha:0.12];
case kHoverStateMouseOver:
return [NSColor colorWithCalibratedWhite:1 alpha:0.18];
case kHoverStateMouseDown:
return [NSColor colorWithCalibratedWhite:1 alpha:0.22];
}
} else {
switch (state) {
case kHoverStateNone:
return [NSColor colorWithCalibratedWhite:1 alpha:1];
case kHoverStateMouseOver:
return [NSColor colorWithCalibratedWhite:0.98 alpha:1];
case kHoverStateMouseDown:
return [NSColor colorWithCalibratedWhite:0.95 alpha:1];
}
}
}
NSColor* GetBorderColor(BOOL dark_theme) {
return [NSColor colorWithCalibratedWhite:dark_theme ? 1 : 0 alpha:0.3];
}
NSColor* GetShadowColor() {
return NSColor.blackColor;
}
constexpr CGFloat kHarmonyButtonFontSize = 12;
constexpr CGFloat kHarmonyButtonTextAlpha = 0x8A / (CGFloat)0xFF;
constexpr CGSize kHarmonyButtonNormalShadowOffset{0, 0};
constexpr CGSize kHarmonyButtonMouseOverShadowOffset{0, 1};
constexpr CGFloat kHarmonyButtonNormalShadowOpacity = 0;
constexpr CGFloat kHarmonyButtonMouseOverShadowOpacity = 0.1;
constexpr CGFloat kHarmonyButtonNormalShadowRadius = 0;
constexpr CGFloat kHarmonyButtonMouseOverShadowRadius = 2;
constexpr CGFloat kHarmonyButtonCornerRadius = 3;
constexpr CGFloat kHarmonyButtonXPadding = 16;
constexpr CGFloat kHarmonyButtonMinWidth = 64;
constexpr CGFloat kHarmonyButtonHeight = 28;
// The text is a bit too low by default; offset the title rect to center it.
constexpr CGFloat kHarmonyButtonTextYOffset = 1;
constexpr NSTimeInterval kHarmonyButtonTransitionDuration = 0.25;
} // namespace
@interface HarmonyButtonCell : NSButtonCell
@end
@implementation HarmonyButtonCell
- (NSRect)titleRectForBounds:(NSRect)rect {
rect.origin.y -= kHarmonyButtonTextYOffset;
return rect;
}
@end
@implementation HarmonyButton
+ (instancetype)buttonWithTitle:(NSString*)title
target:(id)target
action:(SEL)action {
HarmonyButton* button = [[[self alloc] initWithFrame:NSZeroRect] autorelease];
button.title = title;
button.target = target;
button.action = action;
[button sizeToFit];
return button;
}
+ (Class)cellClass {
return [HarmonyButtonCell class];
}
- (instancetype)initWithFrame:(NSRect)frameRect {
if ((self = [super initWithFrame:frameRect])) {
self.bezelStyle = NSRoundedBezelStyle;
self.bordered = NO;
self.wantsLayer = YES;
CALayer* layer = self.layer;
layer.shadowColor = GetShadowColor().CGColor;
layer.cornerRadius = kHarmonyButtonCornerRadius;
[self updateBorderWidth];
[self updateHoverButtonAppearanceAnimated:NO];
}
return self;
}
- (void)updateBorderWidth {
self.layer.borderWidth = [self cr_lineWidth];
}
- (void)updateHoverButtonAppearanceAnimated:(BOOL)animated {
if (!self.window)
return;
const ui::ThemeProvider* themeProvider = [self.window themeProvider];
const BOOL darkTheme = [self.window hasDarkTheme];
CALayer* layer = self.layer;
if (animated) {
[NSAnimationContext beginGrouping];
NSAnimationContext* context = [NSAnimationContext currentContext];
context.allowsImplicitAnimation = YES;
context.duration = kHarmonyButtonTransitionDuration;
} else {
[layer removeAllAnimations];
}
switch (self.hoverState) {
case kHoverStateNone:
layer.shadowOffset = kHarmonyButtonNormalShadowOffset;
layer.shadowOpacity = kHarmonyButtonNormalShadowOpacity;
layer.shadowRadius = kHarmonyButtonNormalShadowRadius;
break;
case kHoverStateMouseOver:
case kHoverStateMouseDown:
layer.shadowOffset = kHarmonyButtonMouseOverShadowOffset;
layer.shadowOpacity = kHarmonyButtonMouseOverShadowOpacity;
layer.shadowRadius = kHarmonyButtonMouseOverShadowRadius;
break;
}
layer.borderColor =
(themeProvider && themeProvider->ShouldIncreaseContrast())
? CGColorGetConstantColor(darkTheme ? kCGColorWhite : kCGColorBlack)
: GetBorderColor(darkTheme).CGColor;
layer.backgroundColor =
GetBackgroundColor(self.hoverState, darkTheme).CGColor;
if (animated) {
[NSAnimationContext endGrouping];
}
}
// HoverButtonCocoa overrides.
- (void)setHoverState:(CloseButtonHoverState)state {
if (state == hoverState_) {
return;
}
const BOOL animated =
state != kHoverStateMouseDown && hoverState_ != kHoverStateMouseDown;
hoverState_ = state;
[self updateHoverButtonAppearanceAnimated:animated];
}
// NSButton overrides.
- (void)setTitle:(NSString*)title {
NSColor* textColor;
if (const ui::ThemeProvider* themeProvider = [self.window themeProvider]) {
textColor = themeProvider->GetNSColor(ThemeProperties::COLOR_TAB_TEXT);
if (!themeProvider->ShouldIncreaseContrast())
textColor = [textColor colorWithAlphaComponent:kHarmonyButtonTextAlpha];
} else {
textColor = [NSColor controlTextColor];
}
NSFont* font;
if (@available(macOS 10.11, *)) {
font = [NSFont systemFontOfSize:kHarmonyButtonFontSize
weight:NSFontWeightMedium];
} else {
font = [[NSFontManager sharedFontManager]
convertWeight:YES
ofFont:[NSFont systemFontOfSize:kHarmonyButtonFontSize]];
}
base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
[[NSParagraphStyle defaultParagraphStyle] mutableCopy]);
paragraphStyle.get().alignment = NSCenterTextAlignment;
base::scoped_nsobject<NSAttributedString> attributedTitle(
[[NSAttributedString alloc]
initWithString:title
attributes:@{
NSFontAttributeName : font,
NSForegroundColorAttributeName : textColor,
NSParagraphStyleAttributeName : paragraphStyle,
}]);
if (![self.attributedTitle isEqual:attributedTitle])
self.attributedTitle = attributedTitle;
}
// NSControl overrides.
- (void)sizeToFit {
NSSize size = self.attributedTitle.size;
size.width += kHarmonyButtonXPadding * 2;
size = [self backingAlignedRect:NSMakeRect(0, 0, size.width, size.height)
options:NSAlignAllEdgesOutward]
.size;
[self setFrameSize:NSMakeSize(size.width > kHarmonyButtonMinWidth
? size.width
: kHarmonyButtonMinWidth,
kHarmonyButtonHeight)];
}
// NSView overrides.
- (void)layout {
CALayer* layer = self.layer;
layer.shadowPath =
base::ScopedCFTypeRef<CGPathRef>(CGPathCreateWithRoundedRect(
layer.bounds,
MIN(layer.cornerRadius, CGRectGetWidth(layer.bounds) / 2),
MIN(layer.cornerRadius, CGRectGetHeight(layer.bounds) / 2), nullptr));
[self updateHoverButtonAppearanceAnimated:NO];
self.title = self.title; // Match the theme.
[super layout];
}
- (void)drawFocusRingMask {
CGFloat radius = self.layer.cornerRadius;
[[NSBezierPath bezierPathWithRoundedRect:self.bounds
xRadius:radius
yRadius:radius] fill];
}
- (void)viewDidChangeBackingProperties {
[super viewDidChangeBackingProperties];
[self updateBorderWidth];
}
// This is undocumented. Without it, NSView sets `self.layer.masksToBounds =
// YES` whenever the view is resized, clipping the shadow.
- (BOOL)clipsToBounds {
return NO;
}
@end
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/harmony_button.h"
#import "base/mac/scoped_nsobject.h"
#import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
namespace {
class HarmonyButtonTest : public ui::CocoaTest {
public:
HarmonyButtonTest() {
base::scoped_nsobject<HarmonyButton> button(
[[HarmonyButton alloc] initWithFrame:NSMakeRect(0, 0, 20, 20)]);
button_ = button;
[[test_window() contentView] addSubview:button_];
}
protected:
HarmonyButton* button_; // Weak, owned by test_window().
};
TEST_VIEW(HarmonyButtonTest, button_)
// Verify that displaying a button that's smaller than double the corner radius
// doesn't crash. (CGPathCreateWithRoundedRect throws an exception if you pass
// it a radius that's too big).
TEST_F(HarmonyButtonTest, TooSmall) {
CGFloat cornerRadius = button_.layer.cornerRadius;
EXPECT_LT(0, cornerRadius);
[button_ setFrameSize:NSMakeSize(cornerRadius, cornerRadius)];
[button_ layoutSubtreeIfNeeded];
[button_ displayIfNeeded];
}
TEST(HarmonyButtonTestMore, NSViewRespondsToClipsToBounds) {
// HarmonyButton implements an undocumented method to avoid having its shadow
// clipped, so verify that it hasn't disappeared.
EXPECT_TRUE([NSView instancesRespondToSelector:@selector(clipsToBounds)]);
}
} // namespace
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_COCOA_MD_HOVER_BUTTON_H_
#define CHROME_BROWSER_UI_COCOA_MD_HOVER_BUTTON_H_
#import <AppKit/AppKit.h>
#import "chrome/browser/ui/cocoa/themed_window.h"
#import "ui/base/cocoa/hover_button.h"
#include "ui/gfx/vector_icon_types.h"
// MDHoverButton has a gray background with rounded corners. The background is
// only visible on hover and gets darker on click. It's friendly to subviews.
@interface MDHoverButton : HoverButtonCocoa<ThemedWindowDrawing>
// An icon that's displayed in the middle of the button.
@property(nonatomic) const gfx::VectorIcon* icon;
@property(nonatomic) int iconSize;
// If YES, the button doesn't have a hover state. This can be useful if a
// button contains another button, or has an area which shouldn't be sensitive
// to clicks: set hoverSuppressed to YES when the mouse enters that area, and
// NO when the mouse exits.
@property(nonatomic) BOOL hoverSuppressed;
@end
#endif // CHROME_BROWSER_UI_COCOA_MD_HOVER_BUTTON_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/md_hover_button.h"
#import <QuartzCore/QuartzCore.h>
#import "ui/base/cocoa/nsview_additions.h"
#include "ui/base/theme_provider.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/color_utils.h"
#import "ui/gfx/image/image_skia_util_mac.h"
#include "ui/gfx/paint_vector_icon.h"
namespace {
constexpr int kMDHoverButtonDefaultIconSize = 16;
constexpr SkAlpha kMDHoverButtonIconAlpha = 0x8A;
constexpr CGFloat kMDHoverButtonHoverAnimationDuration = 0.25;
NSColor* GetHoveringColor(BOOL dark_theme) {
return [NSColor colorWithWhite:dark_theme ? 1 : 0 alpha:0.08];
}
NSColor* GetActiveColor(BOOL dark_theme) {
return [NSColor colorWithWhite:dark_theme ? 1 : 0 alpha:0.12];
}
} // namespace
@implementation MDHoverButton {
const gfx::VectorIcon* icon_;
}
@synthesize icon = icon_;
@synthesize iconSize = iconSize_;
@synthesize hoverSuppressed = hoverSuppressed_;
- (instancetype)initWithFrame:(NSRect)frameRect {
if ((self = [super initWithFrame:frameRect])) {
iconSize_ = kMDHoverButtonDefaultIconSize;
self.bezelStyle = NSRoundedBezelStyle;
self.bordered = NO;
self.wantsLayer = YES;
self.layer.cornerRadius = 2;
}
return self;
}
- (void)setIcon:(const gfx::VectorIcon*)icon {
icon_ = icon;
[self updateIcon];
}
- (void)setIconSize:(int)iconSize {
iconSize_ = iconSize;
[self updateIcon];
}
- (void)setHoverSuppressed:(BOOL)hoverSuppressed {
hoverSuppressed_ = hoverSuppressed;
[self updateHoverButtonAppearanceAnimated:YES];
}
- (SkColor)iconColor {
const ui::ThemeProvider* provider = [[self window] themeProvider];
if (!provider)
return gfx::kPlaceholderColor;
return SkColorSetA(
[[self window] hasDarkTheme] ? SK_ColorWHITE : SK_ColorBLACK,
provider->ShouldIncreaseContrast() ? 0xFF : kMDHoverButtonIconAlpha);
}
- (void)updateIcon {
if (!icon_ || icon_->is_empty() || iconSize_ == 0) {
self.image = nil;
return;
}
self.image = NSImageFromImageSkia(
gfx::CreateVectorIcon(*icon_, iconSize_, [self iconColor]));
}
- (void)updateHoverButtonAppearanceAnimated:(BOOL)animated {
const BOOL darkTheme = [[self window] hasDarkTheme];
const CGColorRef targetBackgroundColor = [&]() -> CGColorRef {
if (hoverSuppressed_)
return nil;
switch (self.hoverState) {
case kHoverStateMouseDown:
return GetActiveColor(darkTheme).CGColor;
case kHoverStateMouseOver:
return GetHoveringColor(darkTheme).CGColor;
case kHoverStateNone:
return nil;
}
}();
if (CGColorEqualToColor(targetBackgroundColor, self.layer.backgroundColor)) {
return;
}
if (!animated) {
[self.layer removeAnimationForKey:@"hoverButtonAppearance"];
self.layer.backgroundColor = targetBackgroundColor;
return;
}
[NSAnimationContext runAnimationGroup:^(NSAnimationContext* context) {
context.duration = kMDHoverButtonHoverAnimationDuration;
CABasicAnimation* animation =
[CABasicAnimation animationWithKeyPath:@"backgroundColor"];
self.layer.backgroundColor = targetBackgroundColor;
[self.layer addAnimation:animation forKey:@"hoverButtonAppearance"];
}
completionHandler:nil];
}
// HoverButtonCocoa overrides.
- (void)setHoverState:(CloseButtonHoverState)state {
if (state == hoverState_)
return;
const BOOL animated =
state != kHoverStateMouseDown && hoverState_ != kHoverStateMouseDown;
[super setHoverState:state];
[self updateHoverButtonAppearanceAnimated:animated];
}
// NSView overrides.
- (void)drawFocusRingMask {
CGFloat radius = self.layer.cornerRadius;
[[NSBezierPath bezierPathWithRoundedRect:self.bounds
xRadius:radius
yRadius:radius] fill];
}
- (void)viewDidMoveToWindow {
[super viewDidMoveToWindow];
[self updateIcon];
}
// ThemedWindowDrawing implementation
- (void)windowDidChangeTheme {
[self updateIcon];
}
- (void)windowDidChangeActive {
}
@end
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/md_hover_button.h"
#import "base/mac/scoped_nsobject.h"
#include "chrome/app/vector_icons/vector_icons.h"
#import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
namespace {
class MDHoverButtonTest : public ui::CocoaTest {
public:
MDHoverButtonTest() {
base::scoped_nsobject<MDHoverButton> button(
[[MDHoverButton alloc] initWithFrame:NSMakeRect(0, 0, 20, 20)]);
button_ = button;
[[test_window() contentView] addSubview:button_];
}
protected:
MDHoverButton* button_; // Weak, owned by test_window().
};
TEST_VIEW(MDHoverButtonTest, button_)
// Exercise icon and iconSize to make sure they don't crash.
TEST_F(MDHoverButtonTest, TestIcon) {
button_.icon = &kCaretDownIcon;
button_.iconSize = 16;
[button_ displayIfNeeded];
}
} // namespace
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_COCOA_MULTI_KEY_EQUIVALENT_BUTTON_H_
#define CHROME_BROWSER_UI_COCOA_MULTI_KEY_EQUIVALENT_BUTTON_H_
#import <AppKit/AppKit.h>
#include <vector>
struct KeyEquivalentAndModifierMask {
public:
KeyEquivalentAndModifierMask() : charCode(nil), mask(0) {}
NSString* charCode;
NSUInteger mask;
};
// MultiKeyEquivalentButton is an NSButton subclass that is capable of
// responding to additional key equivalents. It will respond to the ordinary
// NSButton key equivalent set by -setKeyEquivalent: and
// -setKeyEquivalentModifierMask:, and it will also respond to any additional
// equivalents provided to it in a KeyEquivalentAndModifierMask structure
// passed to -addKeyEquivalent:.
@interface MultiKeyEquivalentButton : NSButton {
@private
std::vector<KeyEquivalentAndModifierMask> extraKeys_;
}
- (void)addKeyEquivalent:(KeyEquivalentAndModifierMask)key;
@end
#endif // CHROME_BROWSER_UI_COCOA_MULTI_KEY_EQUIVALENT_BUTTON_H_
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#import "chrome/browser/ui/cocoa/multi_key_equivalent_button.h"
@implementation MultiKeyEquivalentButton
- (void)addKeyEquivalent:(KeyEquivalentAndModifierMask)key {
extraKeys_.push_back(key);
}
- (BOOL)performKeyEquivalent:(NSEvent*)event {
NSWindow* modalWindow = [NSApp modalWindow];
NSWindow* window = [self window];
if ([self isEnabled] &&
(!modalWindow || modalWindow == window || [window worksWhenModal])) {
for (size_t index = 0; index < extraKeys_.size(); ++index) {
KeyEquivalentAndModifierMask key = extraKeys_[index];
if (key.charCode &&
[key.charCode isEqualToString:[event charactersIgnoringModifiers]] &&
([event modifierFlags] & key.mask) == key.mask) {
[self performClick:self];
return YES;
}
}
}
return [super performKeyEquivalent:event];
}
@end
......@@ -4135,14 +4135,12 @@ test("unit_tests") {
"../browser/ui/cocoa/constrained_window/constrained_window_button_unittest.mm",
"../browser/ui/cocoa/constrained_window/constrained_window_custom_window_unittest.mm",
"../browser/ui/cocoa/constrained_window/constrained_window_sheet_controller_unittest.mm",
"../browser/ui/cocoa/draggable_button_unittest.mm",
"../browser/ui/cocoa/find_pasteboard_unittest.mm",
"../browser/ui/cocoa/first_run_dialog_controller_unittest.mm",
"../browser/ui/cocoa/floating_bar_backing_view_unittest.mm",
"../browser/ui/cocoa/framed_browser_window_unittest.mm",
"../browser/ui/cocoa/fullscreen/fullscreen_toolbar_controller_unittest.mm",
"../browser/ui/cocoa/fullscreen_window_unittest.mm",
"../browser/ui/cocoa/harmony_button_unittest.mm",
"../browser/ui/cocoa/history_menu_bridge_unittest.mm",
"../browser/ui/cocoa/history_menu_cocoa_controller_unittest.mm",
"../browser/ui/cocoa/history_overlay_controller_unittest.mm",
......@@ -4156,7 +4154,6 @@ test("unit_tests") {
"../browser/ui/cocoa/location_bar/image_decoration_unittest.mm",
"../browser/ui/cocoa/location_bar/zoom_decoration_unittest.mm",
"../browser/ui/cocoa/main_menu_builder_unittest.mm",
"../browser/ui/cocoa/md_hover_button_unittest.mm",
"../browser/ui/cocoa/media_picker/desktop_media_picker_controller_unittest.mm",
"../browser/ui/cocoa/menu_button_unittest.mm",
"../browser/ui/cocoa/notifications/notification_builder_mac_unittest.mm",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment