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"
9 #include "grit/ui_resources.h"
10 #include "ui/base/hit_test.h"
11 #include "ui/base/resource/resource_bundle.h"
12 #include "ui/gfx/path.h"
13 #include "ui/gfx/screen.h"
14 #include "ui/gfx/skia_util.h"
15 #include "ui/native_theme/native_theme.h"
16 #include "ui/views/bubble/bubble_border.h"
17 #include "ui/views/controls/button/label_button.h"
18 #include "ui/views/widget/widget.h"
19 #include "ui/views/widget/widget_delegate.h"
20 #include "ui/views/window/client_view.h"
24 // Insets for the title bar views in pixels.
25 const int kTitleTopInset
= 12;
26 const int kTitleLeftInset
= 19;
27 const int kTitleBottomInset
= 12;
28 const int kTitleRightInset
= 7;
30 // Get the |vertical| or horizontal amount that |available_bounds| overflows
32 int GetOffScreenLength(const gfx::Rect
& available_bounds
,
33 const gfx::Rect
& window_bounds
,
35 if (available_bounds
.IsEmpty() || available_bounds
.Contains(window_bounds
))
39 // +---------------------------------+
41 // | +------------------+ |
42 // | left | available_bounds | right |
43 // | +------------------+ |
45 // +---------------------------------+
47 return std::max(0, available_bounds
.y() - window_bounds
.y()) +
48 std::max(0, window_bounds
.bottom() - available_bounds
.bottom());
49 return std::max(0, available_bounds
.x() - window_bounds
.x()) +
50 std::max(0, window_bounds
.right() - available_bounds
.right());
58 const char BubbleFrameView::kViewClassName
[] = "BubbleFrameView";
61 gfx::Insets
BubbleFrameView::GetTitleInsets() {
62 return gfx::Insets(kTitleTopInset
, kTitleLeftInset
,
63 kTitleBottomInset
, kTitleRightInset
);
66 BubbleFrameView::BubbleFrameView(const gfx::Insets
& content_margins
)
67 : bubble_border_(NULL
),
68 content_margins_(content_margins
),
71 titlebar_extra_view_(NULL
) {
72 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
73 title_
= new Label(base::string16(),
74 rb
.GetFontList(ui::ResourceBundle::MediumFont
));
75 title_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
78 close_
= new LabelButton(this, base::string16());
79 close_
->SetImage(CustomButton::STATE_NORMAL
,
80 *rb
.GetImageNamed(IDR_CLOSE_DIALOG
).ToImageSkia());
81 close_
->SetImage(CustomButton::STATE_HOVERED
,
82 *rb
.GetImageNamed(IDR_CLOSE_DIALOG_H
).ToImageSkia());
83 close_
->SetImage(CustomButton::STATE_PRESSED
,
84 *rb
.GetImageNamed(IDR_CLOSE_DIALOG_P
).ToImageSkia());
85 close_
->SetBorder(scoped_ptr
<Border
>());
86 close_
->SetSize(close_
->GetPreferredSize());
87 close_
->SetVisible(false);
91 BubbleFrameView::~BubbleFrameView() {}
93 gfx::Rect
BubbleFrameView::GetBoundsForClientView() const {
94 gfx::Rect client_bounds
= GetLocalBounds();
95 client_bounds
.Inset(GetInsets());
96 client_bounds
.Inset(bubble_border_
->GetInsets());
100 gfx::Rect
BubbleFrameView::GetWindowBoundsForClientBounds(
101 const gfx::Rect
& client_bounds
) const {
102 return const_cast<BubbleFrameView
*>(this)->GetUpdatedWindowBounds(
103 gfx::Rect(), client_bounds
.size(), false);
106 int BubbleFrameView::NonClientHitTest(const gfx::Point
& point
) {
107 if (!bounds().Contains(point
))
109 if (close_
->visible() && close_
->GetMirroredBounds().Contains(point
))
112 // Allow dialogs to show the system menu and be dragged.
113 if (GetWidget()->widget_delegate()->AsDialogDelegate()) {
114 gfx::Rect
sys_rect(0, 0, title_
->x(), title_
->y());
115 sys_rect
.set_origin(gfx::Point(GetMirroredXForRect(sys_rect
), 0));
116 if (sys_rect
.Contains(point
))
118 if (point
.y() < title_
->bounds().bottom())
122 return GetWidget()->client_view()->NonClientHitTest(point
);
125 void BubbleFrameView::GetWindowMask(const gfx::Size
& size
,
126 gfx::Path
* window_mask
) {
127 // NOTE: this only provides implementations for the types used by dialogs.
128 if ((bubble_border_
->arrow() != BubbleBorder::NONE
&&
129 bubble_border_
->arrow() != BubbleBorder::FLOAT
) ||
130 (bubble_border_
->shadow() != BubbleBorder::SMALL_SHADOW
&&
131 bubble_border_
->shadow() != BubbleBorder::NO_SHADOW_OPAQUE_BORDER
))
134 // Use a window mask roughly matching the border in the image assets.
135 static const int kBorderStrokeSize
= 1;
136 static const SkScalar kCornerRadius
= SkIntToScalar(6);
137 const gfx::Insets border_insets
= bubble_border_
->GetInsets();
138 SkRect rect
= { SkIntToScalar(border_insets
.left() - kBorderStrokeSize
),
139 SkIntToScalar(border_insets
.top() - kBorderStrokeSize
),
140 SkIntToScalar(size
.width() - border_insets
.right() +
142 SkIntToScalar(size
.height() - border_insets
.bottom() +
143 kBorderStrokeSize
) };
144 if (bubble_border_
->shadow() == BubbleBorder::NO_SHADOW_OPAQUE_BORDER
) {
145 window_mask
->addRoundRect(rect
, kCornerRadius
, kCornerRadius
);
147 static const int kBottomBorderShadowSize
= 2;
148 rect
.fBottom
+= SkIntToScalar(kBottomBorderShadowSize
);
149 window_mask
->addRect(rect
);
153 void BubbleFrameView::ResetWindowControls() {
154 close_
->SetVisible(GetWidget()->widget_delegate()->ShouldShowCloseButton());
157 void BubbleFrameView::UpdateWindowIcon() {}
159 void BubbleFrameView::UpdateWindowTitle() {
160 title_
->SetText(GetWidget()->widget_delegate()->ShouldShowWindowTitle() ?
161 GetWidget()->widget_delegate()->GetWindowTitle() : base::string16());
162 // Update the close button visibility too, otherwise it's not intialized.
163 ResetWindowControls();
166 void BubbleFrameView::SetTitleFontList(const gfx::FontList
& font_list
) {
167 title_
->SetFontList(font_list
);
170 gfx::Insets
BubbleFrameView::GetInsets() const {
171 gfx::Insets insets
= content_margins_
;
172 const int title_height
= title_
->text().empty() ? 0 :
173 title_
->GetPreferredSize().height() + kTitleTopInset
+ kTitleBottomInset
;
174 const int close_height
= close_
->visible() ? close_
->height() : 0;
175 insets
+= gfx::Insets(std::max(title_height
, close_height
), 0, 0, 0);
179 gfx::Size
BubbleFrameView::GetPreferredSize() const {
180 return GetSizeForClientSize(GetWidget()->client_view()->GetPreferredSize());
183 gfx::Size
BubbleFrameView::GetMinimumSize() const {
184 return GetSizeForClientSize(GetWidget()->client_view()->GetMinimumSize());
187 void BubbleFrameView::Layout() {
188 gfx::Rect
bounds(GetContentsBounds());
189 bounds
.Inset(GetTitleInsets());
190 if (bounds
.IsEmpty())
193 // The close button top inset is actually smaller than the title top inset.
194 close_
->SetPosition(gfx::Point(bounds
.right() - close_
->width(),
197 gfx::Size
title_size(title_
->GetPreferredSize());
198 const int title_width
= std::max(0, close_
->x() - bounds
.x());
199 title_size
.SetToMin(gfx::Size(title_width
, title_size
.height()));
200 bounds
.set_size(title_size
);
201 title_
->SetBoundsRect(bounds
);
203 if (titlebar_extra_view_
) {
204 const int extra_width
= close_
->x() - title_
->bounds().right();
205 gfx::Size size
= titlebar_extra_view_
->GetPreferredSize();
206 size
.SetToMin(gfx::Size(std::max(0, extra_width
), size
.height()));
207 gfx::Rect
titlebar_extra_view_bounds(
208 close_
->x() - size
.width(),
212 titlebar_extra_view_bounds
.Subtract(bounds
);
213 titlebar_extra_view_
->SetBoundsRect(titlebar_extra_view_bounds
);
217 const char* BubbleFrameView::GetClassName() const {
218 return kViewClassName
;
221 void BubbleFrameView::ChildPreferredSizeChanged(View
* child
) {
222 if (child
== titlebar_extra_view_
|| child
== title_
)
226 void BubbleFrameView::OnThemeChanged() {
228 ResetWindowControls();
232 void BubbleFrameView::OnNativeThemeChanged(const ui::NativeTheme
* theme
) {
233 if (bubble_border_
&& bubble_border_
->use_theme_background_color()) {
234 bubble_border_
->set_background_color(GetNativeTheme()->
235 GetSystemColor(ui::NativeTheme::kColorId_DialogBackground
));
240 void BubbleFrameView::ButtonPressed(Button
* sender
, const ui::Event
& event
) {
241 if (sender
== close_
)
242 GetWidget()->Close();
245 void BubbleFrameView::SetBubbleBorder(scoped_ptr
<BubbleBorder
> border
) {
246 bubble_border_
= border
.get();
247 SetBorder(border
.PassAs
<Border
>());
249 // Update the background, which relies on the border.
250 set_background(new views::BubbleBackground(bubble_border_
));
253 void BubbleFrameView::SetTitlebarExtraView(View
* view
) {
255 DCHECK(!titlebar_extra_view_
);
257 titlebar_extra_view_
= view
;
260 gfx::Rect
BubbleFrameView::GetUpdatedWindowBounds(const gfx::Rect
& anchor_rect
,
261 gfx::Size client_size
,
262 bool adjust_if_offscreen
) {
263 gfx::Size
size(GetSizeForClientSize(client_size
));
265 const BubbleBorder::Arrow arrow
= bubble_border_
->arrow();
266 if (adjust_if_offscreen
&& BubbleBorder::has_arrow(arrow
)) {
267 // Try to mirror the anchoring if the bubble does not fit on the screen.
268 if (!bubble_border_
->is_arrow_at_center(arrow
)) {
269 MirrorArrowIfOffScreen(true, anchor_rect
, size
);
270 MirrorArrowIfOffScreen(false, anchor_rect
, size
);
272 const bool mirror_vertical
= BubbleBorder::is_arrow_on_horizontal(arrow
);
273 MirrorArrowIfOffScreen(mirror_vertical
, anchor_rect
, size
);
274 OffsetArrowIfOffScreen(anchor_rect
, size
);
278 // Calculate the bounds with the arrow in its updated location and offset.
279 return bubble_border_
->GetBounds(anchor_rect
, size
);
282 gfx::Rect
BubbleFrameView::GetAvailableScreenBounds(const gfx::Rect
& rect
) {
283 // The bubble attempts to fit within the current screen bounds.
284 // TODO(scottmg): Native is wrong. http://crbug.com/133312
285 return gfx::Screen::GetNativeScreen()->GetDisplayNearestPoint(
286 rect
.CenterPoint()).work_area();
289 void BubbleFrameView::MirrorArrowIfOffScreen(
291 const gfx::Rect
& anchor_rect
,
292 const gfx::Size
& client_size
) {
293 // Check if the bounds don't fit on screen.
294 gfx::Rect
available_bounds(GetAvailableScreenBounds(anchor_rect
));
295 gfx::Rect
window_bounds(bubble_border_
->GetBounds(anchor_rect
, client_size
));
296 if (GetOffScreenLength(available_bounds
, window_bounds
, vertical
) > 0) {
297 BubbleBorder::Arrow arrow
= bubble_border()->arrow();
298 // Mirror the arrow and get the new bounds.
299 bubble_border_
->set_arrow(
300 vertical
? BubbleBorder::vertical_mirror(arrow
) :
301 BubbleBorder::horizontal_mirror(arrow
));
302 gfx::Rect mirror_bounds
=
303 bubble_border_
->GetBounds(anchor_rect
, client_size
);
304 // Restore the original arrow if mirroring doesn't show more of the bubble.
305 // Otherwise it should invoke parent's Layout() to layout the content based
306 // on the new bubble border.
307 if (GetOffScreenLength(available_bounds
, mirror_bounds
, vertical
) >=
308 GetOffScreenLength(available_bounds
, window_bounds
, vertical
))
309 bubble_border_
->set_arrow(arrow
);
315 void BubbleFrameView::OffsetArrowIfOffScreen(const gfx::Rect
& anchor_rect
,
316 const gfx::Size
& client_size
) {
317 BubbleBorder::Arrow arrow
= bubble_border()->arrow();
318 DCHECK(BubbleBorder::is_arrow_at_center(arrow
));
320 // Get the desired bubble bounds without adjustment.
321 bubble_border_
->set_arrow_offset(0);
322 gfx::Rect
window_bounds(bubble_border_
->GetBounds(anchor_rect
, client_size
));
324 gfx::Rect
available_bounds(GetAvailableScreenBounds(anchor_rect
));
325 if (available_bounds
.IsEmpty() || available_bounds
.Contains(window_bounds
))
328 // Calculate off-screen adjustment.
329 const bool is_horizontal
= BubbleBorder::is_arrow_on_horizontal(arrow
);
330 int offscreen_adjust
= 0;
332 if (window_bounds
.x() < available_bounds
.x())
333 offscreen_adjust
= available_bounds
.x() - window_bounds
.x();
334 else if (window_bounds
.right() > available_bounds
.right())
335 offscreen_adjust
= available_bounds
.right() - window_bounds
.right();
337 if (window_bounds
.y() < available_bounds
.y())
338 offscreen_adjust
= available_bounds
.y() - window_bounds
.y();
339 else if (window_bounds
.bottom() > available_bounds
.bottom())
340 offscreen_adjust
= available_bounds
.bottom() - window_bounds
.bottom();
343 // For center arrows, arrows are moved in the opposite direction of
344 // |offscreen_adjust|, e.g. positive |offscreen_adjust| means bubble
345 // window needs to be moved to the right and that means we need to move arrow
346 // to the left, and that means negative offset.
347 bubble_border_
->set_arrow_offset(
348 bubble_border_
->GetArrowOffset(window_bounds
.size()) - offscreen_adjust
);
349 if (offscreen_adjust
)
353 gfx::Size
BubbleFrameView::GetSizeForClientSize(
354 const gfx::Size
& client_size
) const {
355 // Accommodate the width of the title bar elements.
356 int title_bar_width
= GetInsets().width() + border()->GetInsets().width();
357 if (!title_
->text().empty())
358 title_bar_width
+= kTitleLeftInset
+ title_
->GetPreferredSize().width();
359 if (close_
->visible())
360 title_bar_width
+= close_
->width() + 1;
361 if (titlebar_extra_view_
!= NULL
)
362 title_bar_width
+= titlebar_extra_view_
->GetPreferredSize().width();
363 gfx::Size
size(client_size
);
364 size
.SetToMax(gfx::Size(title_bar_width
, 0));
365 const gfx::Insets
insets(GetInsets());
366 size
.Enlarge(insets
.width(), insets
.height());