Revert of Output closure-compiled JavaScript files (patchset #10 id:180001 of https...
[chromium-blink-merge.git] / ui / views / bubble / bubble_frame_view.cc
blob599d5965f2880ab57ca9218144a0cf0877aa48cd
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.
5 #include "ui/views/bubble/bubble_frame_view.h"
7 #include <algorithm>
9 #include "ui/base/hit_test.h"
10 #include "ui/base/resource/resource_bundle.h"
11 #include "ui/gfx/path.h"
12 #include "ui/gfx/screen.h"
13 #include "ui/gfx/skia_util.h"
14 #include "ui/native_theme/native_theme.h"
15 #include "ui/resources/grit/ui_resources.h"
16 #include "ui/views/bubble/bubble_border.h"
17 #include "ui/views/controls/button/label_button.h"
18 #include "ui/views/controls/image_view.h"
19 #include "ui/views/widget/widget.h"
20 #include "ui/views/widget/widget_delegate.h"
21 #include "ui/views/window/client_view.h"
23 namespace {
25 // Insets for the title bar views in pixels.
26 const int kTitleTopInset = 12;
27 const int kTitleLeftInset = 19;
28 const int kTitleBottomInset = 12;
29 const int kTitleRightInset = 7;
31 // The horizontal padding between the title and the icon.
32 const int kTitleHorizontalPadding = 5;
34 // Get the |vertical| or horizontal amount that |available_bounds| overflows
35 // |window_bounds|.
36 int GetOffScreenLength(const gfx::Rect& available_bounds,
37 const gfx::Rect& window_bounds,
38 bool vertical) {
39 if (available_bounds.IsEmpty() || available_bounds.Contains(window_bounds))
40 return 0;
42 // window_bounds
43 // +---------------------------------+
44 // | top |
45 // | +------------------+ |
46 // | left | available_bounds | right |
47 // | +------------------+ |
48 // | bottom |
49 // +---------------------------------+
50 if (vertical)
51 return std::max(0, available_bounds.y() - window_bounds.y()) +
52 std::max(0, window_bounds.bottom() - available_bounds.bottom());
53 return std::max(0, available_bounds.x() - window_bounds.x()) +
54 std::max(0, window_bounds.right() - available_bounds.right());
57 } // namespace
59 namespace views {
61 // static
62 const char BubbleFrameView::kViewClassName[] = "BubbleFrameView";
64 BubbleFrameView::BubbleFrameView(const gfx::Insets& content_margins)
65 : bubble_border_(nullptr),
66 content_margins_(content_margins),
67 title_icon_(new views::ImageView()),
68 title_(nullptr),
69 close_(nullptr),
70 titlebar_extra_view_(nullptr) {
71 AddChildView(title_icon_);
73 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
74 title_ = new Label(base::string16(),
75 rb.GetFontList(ui::ResourceBundle::MediumFont));
76 title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
77 title_->set_collapse_when_hidden(true);
78 title_->SetVisible(false);
79 AddChildView(title_);
81 close_ = CreateCloseButton(this);
82 close_->SetVisible(false);
83 AddChildView(close_);
86 BubbleFrameView::~BubbleFrameView() {}
88 // static
89 gfx::Insets BubbleFrameView::GetTitleInsets() {
90 return gfx::Insets(
91 kTitleTopInset, kTitleLeftInset, kTitleBottomInset, kTitleRightInset);
94 // static
95 LabelButton* BubbleFrameView::CreateCloseButton(ButtonListener* listener) {
96 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
97 LabelButton* close = new LabelButton(listener, base::string16());
98 close->SetImage(CustomButton::STATE_NORMAL,
99 *rb.GetImageNamed(IDR_CLOSE_DIALOG).ToImageSkia());
100 close->SetImage(CustomButton::STATE_HOVERED,
101 *rb.GetImageNamed(IDR_CLOSE_DIALOG_H).ToImageSkia());
102 close->SetImage(CustomButton::STATE_PRESSED,
103 *rb.GetImageNamed(IDR_CLOSE_DIALOG_P).ToImageSkia());
104 close->SetBorder(nullptr);
105 close->SetSize(close->GetPreferredSize());
106 return close;
109 gfx::Rect BubbleFrameView::GetBoundsForClientView() const {
110 gfx::Rect client_bounds = GetLocalBounds();
111 client_bounds.Inset(GetInsets());
112 client_bounds.Inset(bubble_border_->GetInsets());
113 return client_bounds;
116 gfx::Rect BubbleFrameView::GetWindowBoundsForClientBounds(
117 const gfx::Rect& client_bounds) const {
118 return const_cast<BubbleFrameView*>(this)->GetUpdatedWindowBounds(
119 gfx::Rect(), client_bounds.size(), false);
122 int BubbleFrameView::NonClientHitTest(const gfx::Point& point) {
123 if (!bounds().Contains(point))
124 return HTNOWHERE;
125 if (close_->visible() && close_->GetMirroredBounds().Contains(point))
126 return HTCLOSE;
128 // Allow dialogs to show the system menu and be dragged.
129 if (GetWidget()->widget_delegate()->AsDialogDelegate()) {
130 gfx::Rect sys_rect(0, 0, title_->x(), title_->y());
131 sys_rect.set_origin(gfx::Point(GetMirroredXForRect(sys_rect), 0));
132 if (sys_rect.Contains(point))
133 return HTSYSMENU;
134 if (point.y() < title_->bounds().bottom())
135 return HTCAPTION;
138 return GetWidget()->client_view()->NonClientHitTest(point);
141 void BubbleFrameView::GetWindowMask(const gfx::Size& size,
142 gfx::Path* window_mask) {
143 // NOTE: this only provides implementations for the types used by dialogs.
144 if ((bubble_border_->arrow() != BubbleBorder::NONE &&
145 bubble_border_->arrow() != BubbleBorder::FLOAT) ||
146 (bubble_border_->shadow() != BubbleBorder::SMALL_SHADOW &&
147 bubble_border_->shadow() != BubbleBorder::NO_SHADOW_OPAQUE_BORDER))
148 return;
150 // Use a window mask roughly matching the border in the image assets.
151 static const int kBorderStrokeSize = 1;
152 static const SkScalar kCornerRadius = SkIntToScalar(6);
153 const gfx::Insets border_insets = bubble_border_->GetInsets();
154 SkRect rect = { SkIntToScalar(border_insets.left() - kBorderStrokeSize),
155 SkIntToScalar(border_insets.top() - kBorderStrokeSize),
156 SkIntToScalar(size.width() - border_insets.right() +
157 kBorderStrokeSize),
158 SkIntToScalar(size.height() - border_insets.bottom() +
159 kBorderStrokeSize) };
160 if (bubble_border_->shadow() == BubbleBorder::NO_SHADOW_OPAQUE_BORDER) {
161 window_mask->addRoundRect(rect, kCornerRadius, kCornerRadius);
162 } else {
163 static const int kBottomBorderShadowSize = 2;
164 rect.fBottom += SkIntToScalar(kBottomBorderShadowSize);
165 window_mask->addRect(rect);
169 void BubbleFrameView::ResetWindowControls() {
170 close_->SetVisible(GetWidget()->widget_delegate()->ShouldShowCloseButton());
173 void BubbleFrameView::UpdateWindowIcon() {
174 gfx::ImageSkia image;
175 if (GetWidget()->widget_delegate()->ShouldShowWindowIcon())
176 image = GetWidget()->widget_delegate()->GetWindowIcon();
177 title_icon_->SetImage(&image);
181 void BubbleFrameView::UpdateWindowTitle() {
182 title_->SetText(GetWidget()->widget_delegate()->GetWindowTitle());
183 title_->SetVisible(GetWidget()->widget_delegate()->ShouldShowWindowTitle());
186 void BubbleFrameView::SizeConstraintsChanged() {}
188 void BubbleFrameView::SetTitleFontList(const gfx::FontList& font_list) {
189 title_->SetFontList(font_list);
192 gfx::Insets BubbleFrameView::GetInsets() const {
193 gfx::Insets insets = content_margins_;
195 const int icon_height = title_icon_->GetPreferredSize().height();
196 const int label_height = title_->GetPreferredSize().height();
197 const bool has_title = icon_height > 0 || label_height > 0;
198 const int title_padding = has_title ? kTitleTopInset + kTitleBottomInset : 0;
199 const int title_height = std::max(icon_height, label_height) + title_padding;
200 const int close_height = close_->visible() ? close_->height() : 0;
201 insets += gfx::Insets(std::max(title_height, close_height), 0, 0, 0);
202 return insets;
205 gfx::Size BubbleFrameView::GetPreferredSize() const {
206 return GetSizeForClientSize(GetWidget()->client_view()->GetPreferredSize());
209 gfx::Size BubbleFrameView::GetMinimumSize() const {
210 return GetSizeForClientSize(GetWidget()->client_view()->GetMinimumSize());
213 void BubbleFrameView::Layout() {
214 gfx::Rect bounds(GetContentsBounds());
215 bounds.Inset(GetTitleInsets());
216 if (bounds.IsEmpty())
217 return;
219 // The close button top inset is actually smaller than the title top inset.
220 close_->SetPosition(gfx::Point(bounds.right() - close_->width(),
221 bounds.y() - 5));
223 gfx::Size title_icon_size(title_icon_->GetPreferredSize());
224 gfx::Size title_label_size(title_->GetPreferredSize());
225 int padding = 0;
226 if (title_icon_size.width() > 0 && title_label_size.width() > 0)
227 padding = kTitleHorizontalPadding;
228 const int title_height = std::max(title_icon_size.height(),
229 title_label_size.height());
231 const int title_icon_width = std::max(0, close_->x() - bounds.x());
232 title_icon_size.SetToMin(gfx::Size(title_icon_width, title_height));
233 gfx::Rect title_icon_bounds(
234 bounds.x(), bounds.y(), title_icon_size.width(), title_height);
235 title_icon_->SetBoundsRect(title_icon_bounds);
237 const int title_label_x = title_icon_->bounds().right() + padding;
238 const int title_label_width = std::max(0, close_->x() - title_label_x);
239 title_label_size.SetToMin(gfx::Size(title_label_width,
240 title_label_size.height()));
241 gfx::Rect title_label_bounds(
242 title_label_x, bounds.y(), title_label_size.width(), title_height);
243 title_->SetBoundsRect(title_label_bounds);
245 bounds.set_width(
246 title_icon_size.width() + title_label_size.width() + padding);
247 bounds.set_height(title_height);
249 if (titlebar_extra_view_) {
250 const int extra_width = close_->x() - bounds.right();
251 gfx::Size size = titlebar_extra_view_->GetPreferredSize();
252 size.SetToMin(gfx::Size(std::max(0, extra_width), size.height()));
253 gfx::Rect titlebar_extra_view_bounds(
254 close_->x() - size.width(),
255 bounds.y(),
256 size.width(),
257 bounds.height());
258 titlebar_extra_view_bounds.Subtract(bounds);
259 titlebar_extra_view_->SetBoundsRect(titlebar_extra_view_bounds);
263 const char* BubbleFrameView::GetClassName() const {
264 return kViewClassName;
267 void BubbleFrameView::ChildPreferredSizeChanged(View* child) {
268 if (child == titlebar_extra_view_ || child == title_)
269 Layout();
272 void BubbleFrameView::OnThemeChanged() {
273 UpdateWindowTitle();
274 ResetWindowControls();
275 UpdateWindowIcon();
278 void BubbleFrameView::OnNativeThemeChanged(const ui::NativeTheme* theme) {
279 if (bubble_border_ && bubble_border_->use_theme_background_color()) {
280 bubble_border_->set_background_color(GetNativeTheme()->
281 GetSystemColor(ui::NativeTheme::kColorId_DialogBackground));
282 SchedulePaint();
286 void BubbleFrameView::ButtonPressed(Button* sender, const ui::Event& event) {
287 if (sender == close_)
288 GetWidget()->Close();
291 void BubbleFrameView::SetBubbleBorder(scoped_ptr<BubbleBorder> border) {
292 bubble_border_ = border.get();
293 SetBorder(border.Pass());
295 // Update the background, which relies on the border.
296 set_background(new views::BubbleBackground(bubble_border_));
299 void BubbleFrameView::SetTitlebarExtraView(View* view) {
300 DCHECK(view);
301 DCHECK(!titlebar_extra_view_);
302 AddChildView(view);
303 titlebar_extra_view_ = view;
306 gfx::Rect BubbleFrameView::GetUpdatedWindowBounds(const gfx::Rect& anchor_rect,
307 gfx::Size client_size,
308 bool adjust_if_offscreen) {
309 gfx::Size size(GetSizeForClientSize(client_size));
311 const BubbleBorder::Arrow arrow = bubble_border_->arrow();
312 if (adjust_if_offscreen && BubbleBorder::has_arrow(arrow)) {
313 // Try to mirror the anchoring if the bubble does not fit on the screen.
314 if (!bubble_border_->is_arrow_at_center(arrow)) {
315 MirrorArrowIfOffScreen(true, anchor_rect, size);
316 MirrorArrowIfOffScreen(false, anchor_rect, size);
317 } else {
318 const bool mirror_vertical = BubbleBorder::is_arrow_on_horizontal(arrow);
319 MirrorArrowIfOffScreen(mirror_vertical, anchor_rect, size);
320 OffsetArrowIfOffScreen(anchor_rect, size);
324 // Calculate the bounds with the arrow in its updated location and offset.
325 return bubble_border_->GetBounds(anchor_rect, size);
328 gfx::Rect BubbleFrameView::GetAvailableScreenBounds(const gfx::Rect& rect) {
329 // The bubble attempts to fit within the current screen bounds.
330 // TODO(scottmg): Native is wrong. http://crbug.com/133312
331 return gfx::Screen::GetNativeScreen()->GetDisplayNearestPoint(
332 rect.CenterPoint()).work_area();
335 bool BubbleFrameView::IsCloseButtonVisible() const {
336 return close_->visible();
339 gfx::Rect BubbleFrameView::GetCloseButtonBounds() const {
340 return close_->bounds();
343 void BubbleFrameView::MirrorArrowIfOffScreen(
344 bool vertical,
345 const gfx::Rect& anchor_rect,
346 const gfx::Size& client_size) {
347 // Check if the bounds don't fit on screen.
348 gfx::Rect available_bounds(GetAvailableScreenBounds(anchor_rect));
349 gfx::Rect window_bounds(bubble_border_->GetBounds(anchor_rect, client_size));
350 if (GetOffScreenLength(available_bounds, window_bounds, vertical) > 0) {
351 BubbleBorder::Arrow arrow = bubble_border()->arrow();
352 // Mirror the arrow and get the new bounds.
353 bubble_border_->set_arrow(
354 vertical ? BubbleBorder::vertical_mirror(arrow) :
355 BubbleBorder::horizontal_mirror(arrow));
356 gfx::Rect mirror_bounds =
357 bubble_border_->GetBounds(anchor_rect, client_size);
358 // Restore the original arrow if mirroring doesn't show more of the bubble.
359 // Otherwise it should invoke parent's Layout() to layout the content based
360 // on the new bubble border.
361 if (GetOffScreenLength(available_bounds, mirror_bounds, vertical) >=
362 GetOffScreenLength(available_bounds, window_bounds, vertical))
363 bubble_border_->set_arrow(arrow);
364 else if (parent())
365 parent()->Layout();
369 void BubbleFrameView::OffsetArrowIfOffScreen(const gfx::Rect& anchor_rect,
370 const gfx::Size& client_size) {
371 BubbleBorder::Arrow arrow = bubble_border()->arrow();
372 DCHECK(BubbleBorder::is_arrow_at_center(arrow));
374 // Get the desired bubble bounds without adjustment.
375 bubble_border_->set_arrow_offset(0);
376 gfx::Rect window_bounds(bubble_border_->GetBounds(anchor_rect, client_size));
378 gfx::Rect available_bounds(GetAvailableScreenBounds(anchor_rect));
379 if (available_bounds.IsEmpty() || available_bounds.Contains(window_bounds))
380 return;
382 // Calculate off-screen adjustment.
383 const bool is_horizontal = BubbleBorder::is_arrow_on_horizontal(arrow);
384 int offscreen_adjust = 0;
385 if (is_horizontal) {
386 if (window_bounds.x() < available_bounds.x())
387 offscreen_adjust = available_bounds.x() - window_bounds.x();
388 else if (window_bounds.right() > available_bounds.right())
389 offscreen_adjust = available_bounds.right() - window_bounds.right();
390 } else {
391 if (window_bounds.y() < available_bounds.y())
392 offscreen_adjust = available_bounds.y() - window_bounds.y();
393 else if (window_bounds.bottom() > available_bounds.bottom())
394 offscreen_adjust = available_bounds.bottom() - window_bounds.bottom();
397 // For center arrows, arrows are moved in the opposite direction of
398 // |offscreen_adjust|, e.g. positive |offscreen_adjust| means bubble
399 // window needs to be moved to the right and that means we need to move arrow
400 // to the left, and that means negative offset.
401 bubble_border_->set_arrow_offset(
402 bubble_border_->GetArrowOffset(window_bounds.size()) - offscreen_adjust);
403 if (offscreen_adjust)
404 SchedulePaint();
407 gfx::Size BubbleFrameView::GetSizeForClientSize(
408 const gfx::Size& client_size) const {
409 // Accommodate the width of the title bar elements.
410 int title_bar_width = GetInsets().width() + border()->GetInsets().width();
411 gfx::Size title_icon_size = title_icon_->GetPreferredSize();
412 gfx::Size title_label_size = title_->GetPreferredSize();
413 if (title_icon_size.width() > 0 || title_label_size.width() > 0)
414 title_bar_width += kTitleLeftInset;
415 if (title_icon_size.width() > 0 && title_label_size.width() > 0)
416 title_bar_width += kTitleHorizontalPadding;
417 title_bar_width += title_icon_size.width();
418 title_bar_width += title_label_size.width();
419 if (close_->visible())
420 title_bar_width += close_->width() + 1;
421 if (titlebar_extra_view_ != NULL)
422 title_bar_width += titlebar_extra_view_->GetPreferredSize().width();
423 gfx::Size size(client_size);
424 size.SetToMax(gfx::Size(title_bar_width, 0));
425 const gfx::Insets insets(GetInsets());
426 size.Enlarge(insets.width(), insets.height());
427 return size;
430 } // namespace views