blob: 23ca75e9c224bc8040a7022e452b7cdde194d18a [file] [log] [blame]
// Copyright 2014 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/browser_window_layout.h"
#include <math.h>
#include <string.h>
#include "base/logging.h"
#include "base/mac/mac_util.h"
#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
namespace chrome {
// The height of the tab strip.
const CGFloat kTabStripHeight = 37;
} // namespace chrome
namespace {
// Insets for the location bar, used when the full toolbar is hidden.
// TODO(viettrungluu): We can argue about the "correct" insetting; I like the
// following best, though arguably 0 inset is better/more correct.
const CGFloat kLocBarLeftRightInset = 1;
const CGFloat kLocBarTopInset = 0;
const CGFloat kLocBarBottomInset = 1;
// Space between the incognito badge and the right edge of the window.
const CGFloat kAvatarRightOffset = 4;
// Space between the location bar and the right edge of the window, when there
// are no extension buttons present.
// When there is a fullscreen button to the right of the new style profile
// button, we align the profile button with the location bar (although it won't
// be aligned when there are extension buttons).
const CGFloat kLocationBarRightOffset = 35;
} // namespace
@interface BrowserWindowLayout ()
// Computes the y offset to use when laying out the tab strip in fullscreen
// mode.
- (void)computeFullscreenYOffset;
// Computes the layout of the tab strip.
- (void)computeTabStripLayout;
// Computes the layout of the subviews of the content view.
- (void)computeContentViewLayout;
// Computes the height of the backing bar for the views in the omnibox area in
// fullscreen mode.
- (CGFloat)fullscreenBackingBarHeight;
@end
@implementation BrowserWindowLayout
- (instancetype)init {
if ((self = [super init])) {
parameters_.isOSYosemiteOrLater = base::mac::IsOSYosemiteOrLater();
}
return self;
}
- (chrome::LayoutOutput)computeLayout {
memset(&output_, 0, sizeof(chrome::LayoutOutput));
[self computeFullscreenYOffset];
[self computeTabStripLayout];
[self computeContentViewLayout];
return output_;
}
- (void)setContentViewSize:(NSSize)size {
parameters_.contentViewSize = size;
}
- (void)setWindowSize:(NSSize)size {
parameters_.windowSize = size;
}
- (void)setInAnyFullscreen:(BOOL)inAnyFullscreen {
parameters_.inAnyFullscreen = inAnyFullscreen;
}
- (void)setFullscreenSlidingStyle:(fullscreen_mac::SlidingStyle)slidingStyle {
parameters_.slidingStyle = slidingStyle;
}
- (void)setFullscreenMenubarOffset:(CGFloat)menubarOffset {
parameters_.menubarOffset = menubarOffset;
}
- (void)setFullscreenToolbarFraction:(CGFloat)toolbarFraction {
parameters_.toolbarFraction = toolbarFraction;
}
- (void)setHasTabStrip:(BOOL)hasTabStrip {
parameters_.hasTabStrip = hasTabStrip;
}
- (void)setFullscreenButtonFrame:(NSRect)frame {
parameters_.fullscreenButtonFrame = frame;
}
- (void)setShouldShowAvatar:(BOOL)shouldShowAvatar {
parameters_.shouldShowAvatar = shouldShowAvatar;
}
- (void)setShouldUseNewAvatar:(BOOL)shouldUseNewAvatar {
parameters_.shouldUseNewAvatar = shouldUseNewAvatar;
}
- (void)setAvatarSize:(NSSize)avatarSize {
parameters_.avatarSize = avatarSize;
}
- (void)setAvatarLineWidth:(CGFloat)avatarLineWidth {
parameters_.avatarLineWidth = avatarLineWidth;
}
- (void)setHasToolbar:(BOOL)hasToolbar {
parameters_.hasToolbar = hasToolbar;
}
- (void)setHasLocationBar:(BOOL)hasLocationBar {
parameters_.hasLocationBar = hasLocationBar;
}
- (void)setToolbarHeight:(CGFloat)toolbarHeight {
parameters_.toolbarHeight = toolbarHeight;
}
- (void)setBookmarkBarHidden:(BOOL)bookmarkBarHidden {
parameters_.bookmarkBarHidden = bookmarkBarHidden;
}
- (void)setPlaceBookmarkBarBelowInfoBar:(BOOL)placeBookmarkBarBelowInfoBar {
parameters_.placeBookmarkBarBelowInfoBar = placeBookmarkBarBelowInfoBar;
}
- (void)setBookmarkBarHeight:(CGFloat)bookmarkBarHeight {
parameters_.bookmarkBarHeight = bookmarkBarHeight;
}
- (void)setInfoBarHeight:(CGFloat)infoBarHeight {
parameters_.infoBarHeight = infoBarHeight;
}
- (void)setPageInfoBubblePointY:(CGFloat)pageInfoBubblePointY {
parameters_.pageInfoBubblePointY = pageInfoBubblePointY;
}
- (void)setHasDownloadShelf:(BOOL)hasDownloadShelf {
parameters_.hasDownloadShelf = hasDownloadShelf;
}
- (void)setDownloadShelfHeight:(CGFloat)downloadShelfHeight {
parameters_.downloadShelfHeight = downloadShelfHeight;
}
- (void)computeFullscreenYOffset {
CGFloat yOffset = 0;
if (parameters_.inAnyFullscreen) {
yOffset += parameters_.menubarOffset;
switch (parameters_.slidingStyle) {
case fullscreen_mac::OMNIBOX_TABS_PRESENT:
break;
case fullscreen_mac::OMNIBOX_TABS_NONE:
case fullscreen_mac::OMNIBOX_TABS_HIDDEN:
// In presentation mode, |yOffset| accounts for the sliding position of
// the floating bar and the extra offset needed to dodge the menu bar.
yOffset += std::floor((1 - parameters_.toolbarFraction) *
[self fullscreenBackingBarHeight]);
break;
}
}
fullscreenYOffset_ = yOffset;
}
- (void)computeTabStripLayout {
if (!parameters_.hasTabStrip) {
maxY_ = parameters_.contentViewSize.height + fullscreenYOffset_;
return;
}
// Temporary variable to hold the output.
chrome::TabStripLayout layout = {};
// Lay out the tab strip.
maxY_ = parameters_.windowSize.height + fullscreenYOffset_;
CGFloat width = parameters_.contentViewSize.width;
layout.frame = NSMakeRect(
0, maxY_ - chrome::kTabStripHeight, width, chrome::kTabStripHeight);
maxY_ = NSMinY(layout.frame);
// In Yosemite, there is no longer an exit fullscreen button in the top-right
// corner of the OSX Menu Bar. Instead, a Cocoa application's toolbar is
// expected to have traffic lights. Chrome doesn't use an NSToolbar, so it
// needs to manually add the traffic lights to the tab strip.
layout.addCustomWindowControls =
parameters_.inAnyFullscreen && parameters_.isOSYosemiteOrLater;
// Set left indentation based on fullscreen mode status.
if (!parameters_.inAnyFullscreen || layout.addCustomWindowControls)
layout.leftIndent = [TabStripController defaultLeftIndentForControls];
// Lay out the icognito/avatar badge because calculating the indentation on
// the right depends on it.
if (parameters_.shouldShowAvatar) {
CGFloat badgeXOffset = -kAvatarRightOffset;
CGFloat badgeYOffset = 0;
CGFloat buttonHeight = parameters_.avatarSize.height;
if (parameters_.shouldUseNewAvatar) {
// The fullscreen icon (if present) is displayed to the right of the
// new style profile button.
if (!NSIsEmptyRect(parameters_.fullscreenButtonFrame))
badgeXOffset = -kLocationBarRightOffset;
// Center the button, but make sure that it's pixel aligned on non-retina
// displays. Use trunc() instead of round() to mimic the behavior of
// autoresizesSubviews.
badgeYOffset = trunc((chrome::kTabStripHeight - buttonHeight) / 2);
} else {
// Actually place the badge *above* |maxY|, by +2 to miss the divider.
badgeYOffset = 2 * parameters_.avatarLineWidth;
}
NSSize size = NSMakeSize(parameters_.avatarSize.width,
std::min(buttonHeight, chrome::kTabStripHeight));
NSPoint origin =
NSMakePoint(width - parameters_.avatarSize.width + badgeXOffset,
maxY_ + badgeYOffset);
layout.avatarFrame =
NSMakeRect(origin.x, origin.y, size.width, size.height);
}
// Calculate the right indentation.
// On 10.7 Lion to 10.9 Mavericks, there will be a fullscreen button when not
// in fullscreen mode.
// There may also be a profile button, which can be on the right of the
// fullscreen button (old style), or to its left (new style).
// The right indentation is calculated to prevent the tab strip from
// overlapping these buttons.
CGFloat maxX = width;
if (!NSIsEmptyRect(parameters_.fullscreenButtonFrame)) {
maxX = NSMinX(parameters_.fullscreenButtonFrame);
}
if (parameters_.shouldShowAvatar) {
maxX = std::min(maxX, NSMinX(layout.avatarFrame));
}
layout.rightIndent = width - maxX;
output_.tabStripLayout = layout;
}
- (void)computeContentViewLayout {
chrome::LayoutParameters parameters = parameters_;
CGFloat maxY = maxY_;
// Sanity-check |maxY|.
DCHECK_GE(maxY, 0);
DCHECK_LE(maxY, parameters_.contentViewSize.height + fullscreenYOffset_);
CGFloat width = parameters_.contentViewSize.width;
// Lay out the toolbar.
if (parameters.hasToolbar) {
output_.toolbarFrame = NSMakeRect(
0, maxY - parameters_.toolbarHeight, width, parameters_.toolbarHeight);
maxY = NSMinY(output_.toolbarFrame);
} else if (parameters_.hasLocationBar) {
CGFloat toolbarX = kLocBarLeftRightInset;
CGFloat toolbarY = maxY - parameters_.toolbarHeight - kLocBarTopInset;
CGFloat toolbarWidth = width - 2 * kLocBarLeftRightInset;
output_.toolbarFrame =
NSMakeRect(toolbarX, toolbarY, toolbarWidth, parameters_.toolbarHeight);
maxY = NSMinY(output_.toolbarFrame) - kLocBarBottomInset;
}
// Lay out the bookmark bar, if it's above the info bar.
if (!parameters.bookmarkBarHidden &&
!parameters.placeBookmarkBarBelowInfoBar) {
output_.bookmarkFrame = NSMakeRect(0,
maxY - parameters.bookmarkBarHeight,
width,
parameters.bookmarkBarHeight);
maxY = NSMinY(output_.bookmarkFrame);
}
// Lay out the backing bar in fullscreen mode.
if (parameters_.inAnyFullscreen) {
output_.fullscreenBackingBarFrame =
NSMakeRect(0, maxY, width, [self fullscreenBackingBarHeight]);
}
// Place the find bar immediately below the toolbar/attached bookmark bar.
output_.findBarMaxY = maxY;
output_.fullscreenExitButtonMaxY = maxY;
if (parameters_.inAnyFullscreen &&
(parameters_.slidingStyle == fullscreen_mac::OMNIBOX_TABS_HIDDEN ||
parameters_.slidingStyle == fullscreen_mac::OMNIBOX_TABS_NONE)) {
// If in presentation mode, reset |maxY| to top of screen, so that the
// floating bar slides over the things which appear to be in the content
// area.
maxY = parameters_.windowSize.height;
}
// Lay out the info bar. It is never hidden.
if (parameters_.infoBarHeight != 0) {
CGFloat infoBarMaxY = maxY;
CGFloat infoBarMinY = maxY - parameters_.infoBarHeight;
// If there's a toolbar, then the frame needs to be high enough to
// accomodate the top arrow, which might stretch all the way to the page
// info bubble icon.
if (parameters_.hasToolbar) {
infoBarMaxY =
NSMinY(output_.toolbarFrame) + parameters.pageInfoBubblePointY;
}
output_.infoBarFrame =
NSMakeRect(0, infoBarMinY, width, infoBarMaxY - infoBarMinY);
output_.infoBarMaxTopArrowHeight =
NSHeight(output_.infoBarFrame) - parameters_.infoBarHeight;
maxY = NSMinY(output_.infoBarFrame);
} else {
// The info bar has 0 height, but tests still expect it in the right
// location.
output_.infoBarFrame = NSMakeRect(0, maxY, width, 0);
}
// Lay out the bookmark bar when it is below the info bar.
if (!parameters.bookmarkBarHidden &&
parameters.placeBookmarkBarBelowInfoBar) {
output_.bookmarkFrame = NSMakeRect(0,
maxY - parameters.bookmarkBarHeight,
width,
parameters.bookmarkBarHeight);
maxY = NSMinY(output_.bookmarkFrame);
}
// Layout the download shelf at the bottom of the content view.
CGFloat minY = 0;
if (parameters.hasDownloadShelf) {
output_.downloadShelfFrame =
NSMakeRect(0, 0, width, parameters.downloadShelfHeight);
minY = NSMaxY(output_.downloadShelfFrame);
}
if (parameters_.inAnyFullscreen &&
parameters_.slidingStyle == fullscreen_mac::OMNIBOX_TABS_PRESENT) {
// If in Canonical Fullscreen, content should be shifted down by an amount
// equal to all the widgets and views at the top of the window. It should
// not be further shifted by the appearance/disappearance of the AppKit
// menu bar.
maxY = parameters_.windowSize.height;
maxY -= NSHeight(output_.toolbarFrame) +
NSHeight(output_.tabStripLayout.frame) +
NSHeight(output_.bookmarkFrame) + parameters.infoBarHeight;
}
// All the remaining space becomes the frame of the content area.
output_.contentAreaFrame = NSMakeRect(0, minY, width, maxY - minY);
}
- (CGFloat)fullscreenBackingBarHeight {
if (!parameters_.inAnyFullscreen)
return 0;
CGFloat totalHeight = 0;
if (parameters_.hasTabStrip)
totalHeight += chrome::kTabStripHeight;
if (parameters_.hasToolbar) {
totalHeight += parameters_.toolbarHeight;
} else if (parameters_.hasLocationBar) {
totalHeight +=
parameters_.toolbarHeight + kLocBarTopInset + kLocBarBottomInset;
}
if (!parameters_.bookmarkBarHidden &&
!parameters_.placeBookmarkBarBelowInfoBar)
totalHeight += parameters_.bookmarkBarHeight;
return totalHeight;
}
@end
@implementation BrowserWindowLayout (ExposedForTesting)
- (void)setOSYosemiteOrLater:(BOOL)osYosemiteOrLater {
parameters_.isOSYosemiteOrLater = osYosemiteOrLater;
}
@end