1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/browser_window_layout.h"
10 #include "base/logging.h"
11 #include "base/mac/mac_util.h"
12 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
16 // The height of the tab strip.
17 const CGFloat kTabStripHeight = 37;
23 // Insets for the location bar, used when the full toolbar is hidden.
24 // TODO(viettrungluu): We can argue about the "correct" insetting; I like the
25 // following best, though arguably 0 inset is better/more correct.
26 const CGFloat kLocBarLeftRightInset = 1;
27 const CGFloat kLocBarTopInset = 0;
28 const CGFloat kLocBarBottomInset = 1;
30 // Space between the incognito badge and the right edge of the window.
31 const CGFloat kAvatarRightOffset = 4;
33 // Space between the location bar and the right edge of the window, when there
34 // are no extension buttons present.
35 // When there is a fullscreen button to the right of the new style profile
36 // button, we align the profile button with the location bar (although it won't
37 // be aligned when there are extension buttons).
38 const CGFloat kLocationBarRightOffset = 35;
42 @interface BrowserWindowLayout ()
44 // Computes the y offset to use when laying out the tab strip in fullscreen
46 - (void)computeFullscreenYOffset;
48 // Computes the layout of the tab strip.
49 - (void)computeTabStripLayout;
51 // Computes the layout of the subviews of the content view.
52 - (void)computeContentViewLayout;
54 // Computes the height of the backing bar for the views in the omnibox area in
56 - (CGFloat)fullscreenBackingBarHeight;
60 @implementation BrowserWindowLayout
62 - (instancetype)init {
63 if ((self = [super init])) {
64 parameters_.isOSYosemiteOrLater = base::mac::IsOSYosemiteOrLater();
69 - (chrome::LayoutOutput)computeLayout {
70 memset(&output_, 0, sizeof(chrome::LayoutOutput));
72 [self computeFullscreenYOffset];
73 [self computeTabStripLayout];
74 [self computeContentViewLayout];
79 - (void)setContentViewSize:(NSSize)size {
80 parameters_.contentViewSize = size;
83 - (void)setWindowSize:(NSSize)size {
84 parameters_.windowSize = size;
87 - (void)setInAnyFullscreen:(BOOL)inAnyFullscreen {
88 parameters_.inAnyFullscreen = inAnyFullscreen;
91 - (void)setFullscreenSlidingStyle:(fullscreen_mac::SlidingStyle)slidingStyle {
92 parameters_.slidingStyle = slidingStyle;
95 - (void)setFullscreenMenubarOffset:(CGFloat)menubarOffset {
96 parameters_.menubarOffset = menubarOffset;
99 - (void)setFullscreenToolbarFraction:(CGFloat)toolbarFraction {
100 parameters_.toolbarFraction = toolbarFraction;
103 - (void)setHasTabStrip:(BOOL)hasTabStrip {
104 parameters_.hasTabStrip = hasTabStrip;
107 - (void)setFullscreenButtonFrame:(NSRect)frame {
108 parameters_.fullscreenButtonFrame = frame;
111 - (void)setShouldShowAvatar:(BOOL)shouldShowAvatar {
112 parameters_.shouldShowAvatar = shouldShowAvatar;
115 - (void)setShouldUseNewAvatar:(BOOL)shouldUseNewAvatar {
116 parameters_.shouldUseNewAvatar = shouldUseNewAvatar;
119 - (void)setAvatarSize:(NSSize)avatarSize {
120 parameters_.avatarSize = avatarSize;
123 - (void)setAvatarLineWidth:(CGFloat)avatarLineWidth {
124 parameters_.avatarLineWidth = avatarLineWidth;
127 - (void)setHasToolbar:(BOOL)hasToolbar {
128 parameters_.hasToolbar = hasToolbar;
131 - (void)setHasLocationBar:(BOOL)hasLocationBar {
132 parameters_.hasLocationBar = hasLocationBar;
135 - (void)setToolbarHeight:(CGFloat)toolbarHeight {
136 parameters_.toolbarHeight = toolbarHeight;
139 - (void)setBookmarkBarHidden:(BOOL)bookmarkBarHidden {
140 parameters_.bookmarkBarHidden = bookmarkBarHidden;
143 - (void)setPlaceBookmarkBarBelowInfoBar:(BOOL)placeBookmarkBarBelowInfoBar {
144 parameters_.placeBookmarkBarBelowInfoBar = placeBookmarkBarBelowInfoBar;
147 - (void)setBookmarkBarHeight:(CGFloat)bookmarkBarHeight {
148 parameters_.bookmarkBarHeight = bookmarkBarHeight;
151 - (void)setInfoBarHeight:(CGFloat)infoBarHeight {
152 parameters_.infoBarHeight = infoBarHeight;
155 - (void)setPageInfoBubblePointY:(CGFloat)pageInfoBubblePointY {
156 parameters_.pageInfoBubblePointY = pageInfoBubblePointY;
159 - (void)setHasDownloadShelf:(BOOL)hasDownloadShelf {
160 parameters_.hasDownloadShelf = hasDownloadShelf;
163 - (void)setDownloadShelfHeight:(CGFloat)downloadShelfHeight {
164 parameters_.downloadShelfHeight = downloadShelfHeight;
167 - (void)computeFullscreenYOffset {
169 if (parameters_.inAnyFullscreen) {
170 yOffset += parameters_.menubarOffset;
171 switch (parameters_.slidingStyle) {
172 case fullscreen_mac::OMNIBOX_TABS_PRESENT:
174 case fullscreen_mac::OMNIBOX_TABS_HIDDEN:
175 // In presentation mode, |yOffset| accounts for the sliding position of
176 // the floating bar and the extra offset needed to dodge the menu bar.
177 yOffset += std::floor((1 - parameters_.toolbarFraction) *
178 [self fullscreenBackingBarHeight]);
182 fullscreenYOffset_ = yOffset;
185 - (void)computeTabStripLayout {
186 if (!parameters_.hasTabStrip) {
187 maxY_ = parameters_.contentViewSize.height + fullscreenYOffset_;
191 // Temporary variable to hold the output.
192 chrome::TabStripLayout layout = {};
194 // Lay out the tab strip.
195 maxY_ = parameters_.windowSize.height + fullscreenYOffset_;
196 CGFloat width = parameters_.contentViewSize.width;
197 layout.frame = NSMakeRect(
198 0, maxY_ - chrome::kTabStripHeight, width, chrome::kTabStripHeight);
199 maxY_ = NSMinY(layout.frame);
201 // In Yosemite, there is no longer an exit fullscreen button in the top-right
202 // corner of the OSX Menu Bar. Instead, a Cocoa application's toolbar is
203 // expected to have traffic lights. Chrome doesn't use an NSToolbar, so it
204 // needs to manually add the traffic lights to the tab strip.
205 layout.addCustomWindowControls =
206 parameters_.inAnyFullscreen && parameters_.isOSYosemiteOrLater;
208 // Set left indentation based on fullscreen mode status.
209 if (!parameters_.inAnyFullscreen || layout.addCustomWindowControls)
210 layout.leftIndent = [TabStripController defaultLeftIndentForControls];
212 // Lay out the icognito/avatar badge because calculating the indentation on
213 // the right depends on it.
214 if (parameters_.shouldShowAvatar) {
215 CGFloat badgeXOffset = -kAvatarRightOffset;
216 CGFloat badgeYOffset = 0;
217 CGFloat buttonHeight = parameters_.avatarSize.height;
219 if (parameters_.shouldUseNewAvatar) {
220 // The fullscreen icon (if present) is displayed to the right of the
221 // new style profile button.
222 if (!NSIsEmptyRect(parameters_.fullscreenButtonFrame))
223 badgeXOffset = -kLocationBarRightOffset;
225 // Center the button, but make sure that it's pixel aligned on non-retina
226 // displays. Use trunc() instead of round() to mimic the behavior of
227 // autoresizesSubviews.
228 badgeYOffset = trunc((chrome::kTabStripHeight - buttonHeight) / 2);
230 // Actually place the badge *above* |maxY|, by +2 to miss the divider.
231 badgeYOffset = 2 * parameters_.avatarLineWidth;
234 NSSize size = NSMakeSize(parameters_.avatarSize.width,
235 std::min(buttonHeight, chrome::kTabStripHeight));
237 NSMakePoint(width - parameters_.avatarSize.width + badgeXOffset,
238 maxY_ + badgeYOffset);
240 NSMakeRect(origin.x, origin.y, size.width, size.height);
243 // Calculate the right indentation.
244 // On 10.7 Lion to 10.9 Mavericks, there will be a fullscreen button when not
245 // in fullscreen mode.
246 // There may also be a profile button, which can be on the right of the
247 // fullscreen button (old style), or to its left (new style).
248 // The right indentation is calculated to prevent the tab strip from
249 // overlapping these buttons.
250 CGFloat maxX = width;
251 if (!NSIsEmptyRect(parameters_.fullscreenButtonFrame)) {
252 maxX = NSMinX(parameters_.fullscreenButtonFrame);
254 if (parameters_.shouldShowAvatar) {
255 maxX = std::min(maxX, NSMinX(layout.avatarFrame));
257 layout.rightIndent = width - maxX;
259 output_.tabStripLayout = layout;
262 - (void)computeContentViewLayout {
263 chrome::LayoutParameters parameters = parameters_;
264 CGFloat maxY = maxY_;
266 // Sanity-check |maxY|.
268 DCHECK_LE(maxY, parameters_.contentViewSize.height + fullscreenYOffset_);
270 CGFloat width = parameters_.contentViewSize.width;
272 // Lay out the toolbar.
273 if (parameters.hasToolbar) {
274 output_.toolbarFrame = NSMakeRect(
275 0, maxY - parameters_.toolbarHeight, width, parameters_.toolbarHeight);
276 maxY = NSMinY(output_.toolbarFrame);
277 } else if (parameters_.hasLocationBar) {
278 CGFloat toolbarX = kLocBarLeftRightInset;
279 CGFloat toolbarY = maxY - parameters_.toolbarHeight - kLocBarTopInset;
280 CGFloat toolbarWidth = width - 2 * kLocBarLeftRightInset;
281 output_.toolbarFrame =
282 NSMakeRect(toolbarX, toolbarY, toolbarWidth, parameters_.toolbarHeight);
283 maxY = NSMinY(output_.toolbarFrame) - kLocBarBottomInset;
286 // Lay out the bookmark bar, if it's above the info bar.
287 if (!parameters.bookmarkBarHidden &&
288 !parameters.placeBookmarkBarBelowInfoBar) {
289 output_.bookmarkFrame = NSMakeRect(0,
290 maxY - parameters.bookmarkBarHeight,
292 parameters.bookmarkBarHeight);
293 maxY = NSMinY(output_.bookmarkFrame);
296 // Lay out the backing bar in fullscreen mode.
297 if (parameters_.inAnyFullscreen) {
298 output_.fullscreenBackingBarFrame =
299 NSMakeRect(0, maxY, width, [self fullscreenBackingBarHeight]);
302 // Place the find bar immediately below the toolbar/attached bookmark bar.
303 output_.findBarMaxY = maxY;
304 output_.fullscreenExitButtonMaxY = maxY;
306 if (parameters_.inAnyFullscreen &&
307 parameters_.slidingStyle == fullscreen_mac::OMNIBOX_TABS_HIDDEN) {
308 // If in presentation mode, reset |maxY| to top of screen, so that the
309 // floating bar slides over the things which appear to be in the content
311 maxY = parameters_.windowSize.height;
314 // Lay out the info bar. It is never hidden.
315 if (parameters_.infoBarHeight != 0) {
316 CGFloat infoBarMaxY = maxY;
317 CGFloat infoBarMinY = maxY - parameters_.infoBarHeight;
319 // If there's a toolbar, then the frame needs to be high enough to
320 // accomodate the top arrow, which might stretch all the way to the page
322 if (parameters_.hasToolbar) {
324 NSMinY(output_.toolbarFrame) + parameters.pageInfoBubblePointY;
327 output_.infoBarFrame =
328 NSMakeRect(0, infoBarMinY, width, infoBarMaxY - infoBarMinY);
329 output_.infoBarMaxTopArrowHeight =
330 NSHeight(output_.infoBarFrame) - parameters_.infoBarHeight;
331 maxY = NSMinY(output_.infoBarFrame);
333 // The info bar has 0 height, but tests still expect it in the right
335 output_.infoBarFrame = NSMakeRect(0, maxY, width, 0);
338 // Lay out the bookmark bar when it is below the info bar.
339 if (!parameters.bookmarkBarHidden &&
340 parameters.placeBookmarkBarBelowInfoBar) {
341 output_.bookmarkFrame = NSMakeRect(0,
342 maxY - parameters.bookmarkBarHeight,
344 parameters.bookmarkBarHeight);
345 maxY = NSMinY(output_.bookmarkFrame);
348 // Layout the download shelf at the bottom of the content view.
350 if (parameters.hasDownloadShelf) {
351 output_.downloadShelfFrame =
352 NSMakeRect(0, 0, width, parameters.downloadShelfHeight);
353 minY = NSMaxY(output_.downloadShelfFrame);
356 if (parameters_.inAnyFullscreen &&
357 parameters_.slidingStyle == fullscreen_mac::OMNIBOX_TABS_PRESENT) {
358 // If in Canonical Fullscreen, content should be shifted down by an amount
359 // equal to all the widgets and views at the top of the window. It should
360 // not be further shifted by the appearance/disappearance of the AppKit
362 maxY = parameters_.windowSize.height;
363 maxY -= NSHeight(output_.toolbarFrame) +
364 NSHeight(output_.tabStripLayout.frame) +
365 NSHeight(output_.bookmarkFrame) + parameters.infoBarHeight;
368 // All the remaining space becomes the frame of the content area.
369 output_.contentAreaFrame = NSMakeRect(0, minY, width, maxY - minY);
372 - (CGFloat)fullscreenBackingBarHeight {
373 if (!parameters_.inAnyFullscreen)
376 CGFloat totalHeight = 0;
377 if (parameters_.hasTabStrip)
378 totalHeight += chrome::kTabStripHeight;
380 if (parameters_.hasToolbar) {
381 totalHeight += parameters_.toolbarHeight;
382 } else if (parameters_.hasLocationBar) {
384 parameters_.toolbarHeight + kLocBarTopInset + kLocBarBottomInset;
387 if (!parameters_.bookmarkBarHidden &&
388 !parameters_.placeBookmarkBarBelowInfoBar)
389 totalHeight += parameters_.bookmarkBarHeight;
396 @implementation BrowserWindowLayout (ExposedForTesting)
398 - (void)setOSYosemiteOrLater:(BOOL)osYosemiteOrLater {
399 parameters_.isOSYosemiteOrLater = osYosemiteOrLater;