location_bar_layout.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
1// Copyright (c) 2012 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.
4
5#include "chrome/browser/ui/views/location_bar/location_bar_layout.h"
6
7#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
8#include "ui/gfx/rect.h"
9#include "ui/views/view.h"
10
11namespace {
12
13enum DecorationType {
14  // Decoration is always visible.
15  NORMAL = 0,
16  // If there is not enough available space in the location bar, the decoration
17  // will reduce its width either to its minimal width or to zero (making it
18  // invisible), whichever fits.  |LocationBarDecoration::max_fraction| must be
19  // 0.
20  AUTO_COLLAPSE,
21  // Decoration is a separator, only visible if it's not leading, or not
22  // trailing, or not next to another separator.
23  SEPARATOR,
24};
25
26}  // namespace
27
28
29// Description of a decoration to be added inside the location bar, either to
30// the left or to the right.
31struct LocationBarDecoration {
32  LocationBarDecoration(DecorationType type,
33                        int y,
34                        int height,
35                        double max_fraction,
36                        int edge_item_padding,
37                        int item_padding,
38                        int builtin_padding,
39                        views::View* view);
40
41  // The type of decoration.
42  DecorationType type;
43
44  // The y position of the view inside its parent.
45  int y;
46
47  // If 0, will use the preferred height of the view.
48  int height;
49
50  // Used for resizeable decorations, indicates the maximum fraction of the
51  // location bar that can be taken by this decoration, 0 for non-resizable
52  // decorations. If non-zero, |type| must not be AUTO_COLLAPSE.
53  double max_fraction;
54
55  // Padding to use if the decoration is the first element next to the edge.
56  int edge_item_padding;
57
58  // Padding to use if the decoration follows another decoration.
59  int item_padding;
60
61  // Padding built into the decoration and that should be removed, on
62  // both sides, during layout.
63  int builtin_padding;
64
65  views::View* view;
66
67  // The width computed by the layout process.
68  double computed_width;
69};
70
71LocationBarDecoration::LocationBarDecoration(DecorationType type,
72                                             int y,
73                                             int height,
74                                             double max_fraction,
75                                             int edge_item_padding,
76                                             int item_padding,
77                                             int builtin_padding,
78                                             views::View* view)
79    : type(type),
80      y(y),
81      height(height),
82      max_fraction(max_fraction),
83      edge_item_padding(edge_item_padding),
84      item_padding(item_padding),
85      builtin_padding(builtin_padding),
86      view(view),
87      computed_width(0) {
88  if (type == NORMAL) {
89    DCHECK_GE(max_fraction, 0.0);
90  } else {
91    DCHECK_EQ(0.0, max_fraction);
92  }
93}
94
95
96// LocationBarLayout ---------------------------------------------------------
97
98LocationBarLayout::LocationBarLayout(Position position,
99                                     int item_edit_padding,
100                                     int edge_edit_padding)
101    : position_(position),
102      item_edit_padding_(item_edit_padding),
103      edge_edit_padding_(edge_edit_padding) {}
104
105
106LocationBarLayout::~LocationBarLayout() {
107}
108
109void LocationBarLayout::AddDecoration(int y,
110                                      int height,
111                                      bool auto_collapse,
112                                      double max_fraction,
113                                      int edge_item_padding,
114                                      int item_padding,
115                                      int builtin_padding,
116                                      views::View* view) {
117  decorations_.push_back(new LocationBarDecoration(
118      auto_collapse ? AUTO_COLLAPSE : NORMAL, y, height, max_fraction,
119      edge_item_padding, item_padding, builtin_padding, view));
120}
121
122void LocationBarLayout::AddDecoration(int y,
123                                      int height,
124                                      int builtin_padding,
125                                      views::View* view) {
126  decorations_.push_back(new LocationBarDecoration(
127      NORMAL, y, height, 0, LocationBarView::GetEdgeItemPadding(),
128      LocationBarView::GetItemPadding(), builtin_padding, view));
129}
130
131void LocationBarLayout::AddSeparator(int y,
132                                     int height,
133                                     int padding_from_previous_item,
134                                     views::View* separator) {
135  // Edge item padding won't apply since a separator won't be by the edge, so
136  // use 0 for more accurate evaluation of |entry_width| in LayoutPass1().
137  decorations_.push_back(new LocationBarDecoration(
138      SEPARATOR, y, height, 0, 0, padding_from_previous_item, 0, separator));
139}
140
141void LocationBarLayout::LayoutPass1(int* entry_width) {
142  bool first_item = true;
143  bool at_least_one_visible = false;
144  for (Decorations::iterator it(decorations_.begin()); it != decorations_.end();
145       ++it) {
146    // Autocollapsing decorations are ignored in this pass.
147    if ((*it)->type != AUTO_COLLAPSE) {
148      at_least_one_visible = true;
149      *entry_width -= -2 * (*it)->builtin_padding +
150          (first_item ? (*it)->edge_item_padding : (*it)->item_padding);
151    }
152    first_item = false;
153    // Resizing decorations are ignored in this pass.
154    if (((*it)->type != AUTO_COLLAPSE) && ((*it)->max_fraction == 0.0)) {
155      (*it)->computed_width = (*it)->view->GetPreferredSize().width();
156      *entry_width -= (*it)->computed_width;
157    }
158  }
159  *entry_width -= at_least_one_visible ? item_edit_padding_ :
160      edge_edit_padding_;
161}
162
163void LocationBarLayout::LayoutPass2(int *entry_width) {
164  for (Decorations::iterator it(decorations_.begin()); it != decorations_.end();
165       ++it) {
166    if ((*it)->max_fraction > 0.0) {
167      int max_width = static_cast<int>(*entry_width * (*it)->max_fraction);
168      (*it)->computed_width = std::min((*it)->view->GetPreferredSize().width(),
169          std::max((*it)->view->GetMinimumSize().width(), max_width));
170      *entry_width -= (*it)->computed_width;
171    }
172  }
173}
174
175void LocationBarLayout::LayoutPass3(gfx::Rect* bounds, int* available_width) {
176  SetVisibilityForDecorations(available_width);
177  HideUnneededSeparators(available_width);
178  SetBoundsForDecorations(bounds);
179}
180
181void LocationBarLayout::SetVisibilityForDecorations(int* available_width) {
182  bool first_visible = true;
183  for (Decorations::iterator it(decorations_.begin()); it != decorations_.end();
184       ++it) {
185    // Collapse decorations if needed.
186    if ((*it)->type == AUTO_COLLAPSE) {
187      int padding = -2 * (*it)->builtin_padding +
188          (first_visible ? (*it)->edge_item_padding : (*it)->item_padding);
189      // Try preferred size, if it fails try minimum size, if it fails
190      // collapse.
191      (*it)->computed_width = (*it)->view->GetPreferredSize().width();
192      if ((*it)->computed_width + padding > *available_width)
193        (*it)->computed_width = (*it)->view->GetMinimumSize().width();
194      if ((*it)->computed_width + padding > *available_width) {
195        (*it)->computed_width = 0;
196        (*it)->view->SetVisible(false);
197      } else {
198        (*it)->view->SetVisible(true);
199        (*available_width) -= (*it)->computed_width + padding;
200      }
201    } else {
202      (*it)->view->SetVisible(true);
203    }
204
205    if ((*it)->view->visible())
206      first_visible = false;
207  }
208}
209
210void LocationBarLayout::HideUnneededSeparators(int* available_width) {
211  // Initialize |trailing_separator| to first decoration so that any leading
212  // separator will be hidden.
213  Decorations::iterator trailing_separator = decorations_.begin();
214  for (Decorations::iterator it(decorations_.begin()); it != decorations_.end();
215       ++it) {
216    if ((*it)->type == SEPARATOR) {
217      if (trailing_separator != decorations_.end()) {
218        (*it)->view->SetVisible(false);
219        // Add back what was subtracted when setting this separator visible in
220        // LayoutPass1().
221        (*available_width) += (*it)->item_padding + (*it)->computed_width;
222      } else {
223        trailing_separator = it;
224      }
225    } else if ((*it)->view->visible()) {
226      trailing_separator = decorations_.end();
227    }
228  }
229  // If there's a trailing separator, hide it.
230  if (trailing_separator != decorations_.end()) {
231    (*trailing_separator)->view->SetVisible(false);
232    // Add back what was subtracted when setting this separator visible in
233    // LayoutPass1().
234    (*available_width) += (*trailing_separator)->item_padding +
235                          (*trailing_separator)->computed_width;
236  }
237}
238
239void LocationBarLayout::SetBoundsForDecorations(gfx::Rect* bounds) {
240  bool first_visible = true;
241  for (Decorations::iterator it(decorations_.begin()); it != decorations_.end();
242       ++it) {
243    LocationBarDecoration* curr = *it;
244    if (!curr->view->visible())
245      continue;
246    int padding = -curr->builtin_padding +
247        (first_visible ? curr->edge_item_padding : curr->item_padding);
248    first_visible = false;
249    int x = (position_ == LEFT_EDGE) ? (bounds->x() + padding) :
250        (bounds->right() - padding - curr->computed_width);
251    int height = curr->height == 0 ?
252        curr->view->GetPreferredSize().height() : curr->height;
253    curr->view->SetBounds(x, curr->y, curr->computed_width, height);
254    bounds->set_width(bounds->width() - padding - curr->computed_width +
255                      curr->builtin_padding);
256    if (position_ == LEFT_EDGE) {
257      bounds->set_x(
258          bounds->x() + padding + curr->computed_width - curr->builtin_padding);
259    }
260  }
261  int final_padding = first_visible ? edge_edit_padding_ : item_edit_padding_;
262  bounds->set_width(bounds->width() - final_padding);
263  if (position_ == LEFT_EDGE)
264    bounds->set_x(bounds->x() + final_padding);
265}
266