| OLD | NEW |
| (Empty) |
| 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 "ui/views/bubble/bubble_frame_view.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 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/widget/widget.h" | |
| 19 #include "ui/views/widget/widget_delegate.h" | |
| 20 #include "ui/views/window/client_view.h" | |
| 21 | |
| 22 namespace { | |
| 23 | |
| 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; | |
| 29 | |
| 30 // Get the |vertical| or horizontal amount that |available_bounds| overflows | |
| 31 // |window_bounds|. | |
| 32 int GetOffScreenLength(const gfx::Rect& available_bounds, | |
| 33 const gfx::Rect& window_bounds, | |
| 34 bool vertical) { | |
| 35 if (available_bounds.IsEmpty() || available_bounds.Contains(window_bounds)) | |
| 36 return 0; | |
| 37 | |
| 38 // window_bounds | |
| 39 // +---------------------------------+ | |
| 40 // | top | | |
| 41 // | +------------------+ | | |
| 42 // | left | available_bounds | right | | |
| 43 // | +------------------+ | | |
| 44 // | bottom | | |
| 45 // +---------------------------------+ | |
| 46 if (vertical) | |
| 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()); | |
| 51 } | |
| 52 | |
| 53 } // namespace | |
| 54 | |
| 55 namespace views { | |
| 56 | |
| 57 // static | |
| 58 const char BubbleFrameView::kViewClassName[] = "BubbleFrameView"; | |
| 59 | |
| 60 BubbleFrameView::BubbleFrameView(const gfx::Insets& content_margins) | |
| 61 : bubble_border_(NULL), | |
| 62 content_margins_(content_margins), | |
| 63 title_(NULL), | |
| 64 close_(NULL), | |
| 65 titlebar_extra_view_(NULL) { | |
| 66 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
| 67 title_ = new Label(base::string16(), | |
| 68 rb.GetFontList(ui::ResourceBundle::MediumFont)); | |
| 69 title_->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
| 70 AddChildView(title_); | |
| 71 | |
| 72 close_ = CreateCloseButton(this); | |
| 73 close_->SetVisible(false); | |
| 74 AddChildView(close_); | |
| 75 } | |
| 76 | |
| 77 BubbleFrameView::~BubbleFrameView() {} | |
| 78 | |
| 79 // static | |
| 80 gfx::Insets BubbleFrameView::GetTitleInsets() { | |
| 81 return gfx::Insets( | |
| 82 kTitleTopInset, kTitleLeftInset, kTitleBottomInset, kTitleRightInset); | |
| 83 } | |
| 84 | |
| 85 // static | |
| 86 LabelButton* BubbleFrameView::CreateCloseButton(ButtonListener* listener) { | |
| 87 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
| 88 LabelButton* close = new LabelButton(listener, base::string16()); | |
| 89 close->SetImage(CustomButton::STATE_NORMAL, | |
| 90 *rb.GetImageNamed(IDR_CLOSE_DIALOG).ToImageSkia()); | |
| 91 close->SetImage(CustomButton::STATE_HOVERED, | |
| 92 *rb.GetImageNamed(IDR_CLOSE_DIALOG_H).ToImageSkia()); | |
| 93 close->SetImage(CustomButton::STATE_PRESSED, | |
| 94 *rb.GetImageNamed(IDR_CLOSE_DIALOG_P).ToImageSkia()); | |
| 95 close->SetBorder(scoped_ptr<Border>()); | |
| 96 close->SetSize(close->GetPreferredSize()); | |
| 97 return close; | |
| 98 } | |
| 99 | |
| 100 gfx::Rect BubbleFrameView::GetBoundsForClientView() const { | |
| 101 gfx::Rect client_bounds = GetLocalBounds(); | |
| 102 client_bounds.Inset(GetInsets()); | |
| 103 client_bounds.Inset(bubble_border_->GetInsets()); | |
| 104 return client_bounds; | |
| 105 } | |
| 106 | |
| 107 gfx::Rect BubbleFrameView::GetWindowBoundsForClientBounds( | |
| 108 const gfx::Rect& client_bounds) const { | |
| 109 return const_cast<BubbleFrameView*>(this)->GetUpdatedWindowBounds( | |
| 110 gfx::Rect(), client_bounds.size(), false); | |
| 111 } | |
| 112 | |
| 113 int BubbleFrameView::NonClientHitTest(const gfx::Point& point) { | |
| 114 if (!bounds().Contains(point)) | |
| 115 return HTNOWHERE; | |
| 116 if (close_->visible() && close_->GetMirroredBounds().Contains(point)) | |
| 117 return HTCLOSE; | |
| 118 | |
| 119 // Allow dialogs to show the system menu and be dragged. | |
| 120 if (GetWidget()->widget_delegate()->AsDialogDelegate()) { | |
| 121 gfx::Rect sys_rect(0, 0, title_->x(), title_->y()); | |
| 122 sys_rect.set_origin(gfx::Point(GetMirroredXForRect(sys_rect), 0)); | |
| 123 if (sys_rect.Contains(point)) | |
| 124 return HTSYSMENU; | |
| 125 if (point.y() < title_->bounds().bottom()) | |
| 126 return HTCAPTION; | |
| 127 } | |
| 128 | |
| 129 return GetWidget()->client_view()->NonClientHitTest(point); | |
| 130 } | |
| 131 | |
| 132 void BubbleFrameView::GetWindowMask(const gfx::Size& size, | |
| 133 gfx::Path* window_mask) { | |
| 134 // NOTE: this only provides implementations for the types used by dialogs. | |
| 135 if ((bubble_border_->arrow() != BubbleBorder::NONE && | |
| 136 bubble_border_->arrow() != BubbleBorder::FLOAT) || | |
| 137 (bubble_border_->shadow() != BubbleBorder::SMALL_SHADOW && | |
| 138 bubble_border_->shadow() != BubbleBorder::NO_SHADOW_OPAQUE_BORDER)) | |
| 139 return; | |
| 140 | |
| 141 // Use a window mask roughly matching the border in the image assets. | |
| 142 static const int kBorderStrokeSize = 1; | |
| 143 static const SkScalar kCornerRadius = SkIntToScalar(6); | |
| 144 const gfx::Insets border_insets = bubble_border_->GetInsets(); | |
| 145 SkRect rect = { SkIntToScalar(border_insets.left() - kBorderStrokeSize), | |
| 146 SkIntToScalar(border_insets.top() - kBorderStrokeSize), | |
| 147 SkIntToScalar(size.width() - border_insets.right() + | |
| 148 kBorderStrokeSize), | |
| 149 SkIntToScalar(size.height() - border_insets.bottom() + | |
| 150 kBorderStrokeSize) }; | |
| 151 if (bubble_border_->shadow() == BubbleBorder::NO_SHADOW_OPAQUE_BORDER) { | |
| 152 window_mask->addRoundRect(rect, kCornerRadius, kCornerRadius); | |
| 153 } else { | |
| 154 static const int kBottomBorderShadowSize = 2; | |
| 155 rect.fBottom += SkIntToScalar(kBottomBorderShadowSize); | |
| 156 window_mask->addRect(rect); | |
| 157 } | |
| 158 } | |
| 159 | |
| 160 void BubbleFrameView::ResetWindowControls() { | |
| 161 close_->SetVisible(GetWidget()->widget_delegate()->ShouldShowCloseButton()); | |
| 162 } | |
| 163 | |
| 164 void BubbleFrameView::UpdateWindowIcon() {} | |
| 165 | |
| 166 void BubbleFrameView::UpdateWindowTitle() { | |
| 167 title_->SetText(GetWidget()->widget_delegate()->ShouldShowWindowTitle() ? | |
| 168 GetWidget()->widget_delegate()->GetWindowTitle() : base::string16()); | |
| 169 // Update the close button visibility too, otherwise it's not intialized. | |
| 170 ResetWindowControls(); | |
| 171 } | |
| 172 | |
| 173 void BubbleFrameView::SizeConstraintsChanged() {} | |
| 174 | |
| 175 void BubbleFrameView::SetTitleFontList(const gfx::FontList& font_list) { | |
| 176 title_->SetFontList(font_list); | |
| 177 } | |
| 178 | |
| 179 gfx::Insets BubbleFrameView::GetInsets() const { | |
| 180 gfx::Insets insets = content_margins_; | |
| 181 const int title_height = title_->text().empty() ? 0 : | |
| 182 title_->GetPreferredSize().height() + kTitleTopInset + kTitleBottomInset; | |
| 183 const int close_height = close_->visible() ? close_->height() : 0; | |
| 184 insets += gfx::Insets(std::max(title_height, close_height), 0, 0, 0); | |
| 185 return insets; | |
| 186 } | |
| 187 | |
| 188 gfx::Size BubbleFrameView::GetPreferredSize() const { | |
| 189 return GetSizeForClientSize(GetWidget()->client_view()->GetPreferredSize()); | |
| 190 } | |
| 191 | |
| 192 gfx::Size BubbleFrameView::GetMinimumSize() const { | |
| 193 return GetSizeForClientSize(GetWidget()->client_view()->GetMinimumSize()); | |
| 194 } | |
| 195 | |
| 196 void BubbleFrameView::Layout() { | |
| 197 gfx::Rect bounds(GetContentsBounds()); | |
| 198 bounds.Inset(GetTitleInsets()); | |
| 199 if (bounds.IsEmpty()) | |
| 200 return; | |
| 201 | |
| 202 // The close button top inset is actually smaller than the title top inset. | |
| 203 close_->SetPosition(gfx::Point(bounds.right() - close_->width(), | |
| 204 bounds.y() - 5)); | |
| 205 | |
| 206 gfx::Size title_size(title_->GetPreferredSize()); | |
| 207 const int title_width = std::max(0, close_->x() - bounds.x()); | |
| 208 title_size.SetToMin(gfx::Size(title_width, title_size.height())); | |
| 209 bounds.set_size(title_size); | |
| 210 title_->SetBoundsRect(bounds); | |
| 211 | |
| 212 if (titlebar_extra_view_) { | |
| 213 const int extra_width = close_->x() - title_->bounds().right(); | |
| 214 gfx::Size size = titlebar_extra_view_->GetPreferredSize(); | |
| 215 size.SetToMin(gfx::Size(std::max(0, extra_width), size.height())); | |
| 216 gfx::Rect titlebar_extra_view_bounds( | |
| 217 close_->x() - size.width(), | |
| 218 bounds.y(), | |
| 219 size.width(), | |
| 220 bounds.height()); | |
| 221 titlebar_extra_view_bounds.Subtract(bounds); | |
| 222 titlebar_extra_view_->SetBoundsRect(titlebar_extra_view_bounds); | |
| 223 } | |
| 224 } | |
| 225 | |
| 226 const char* BubbleFrameView::GetClassName() const { | |
| 227 return kViewClassName; | |
| 228 } | |
| 229 | |
| 230 void BubbleFrameView::ChildPreferredSizeChanged(View* child) { | |
| 231 if (child == titlebar_extra_view_ || child == title_) | |
| 232 Layout(); | |
| 233 } | |
| 234 | |
| 235 void BubbleFrameView::OnThemeChanged() { | |
| 236 UpdateWindowTitle(); | |
| 237 ResetWindowControls(); | |
| 238 UpdateWindowIcon(); | |
| 239 } | |
| 240 | |
| 241 void BubbleFrameView::OnNativeThemeChanged(const ui::NativeTheme* theme) { | |
| 242 if (bubble_border_ && bubble_border_->use_theme_background_color()) { | |
| 243 bubble_border_->set_background_color(GetNativeTheme()-> | |
| 244 GetSystemColor(ui::NativeTheme::kColorId_DialogBackground)); | |
| 245 SchedulePaint(); | |
| 246 } | |
| 247 } | |
| 248 | |
| 249 void BubbleFrameView::ButtonPressed(Button* sender, const ui::Event& event) { | |
| 250 if (sender == close_) | |
| 251 GetWidget()->Close(); | |
| 252 } | |
| 253 | |
| 254 void BubbleFrameView::SetBubbleBorder(scoped_ptr<BubbleBorder> border) { | |
| 255 bubble_border_ = border.get(); | |
| 256 SetBorder(border.Pass()); | |
| 257 | |
| 258 // Update the background, which relies on the border. | |
| 259 set_background(new views::BubbleBackground(bubble_border_)); | |
| 260 } | |
| 261 | |
| 262 void BubbleFrameView::SetTitlebarExtraView(View* view) { | |
| 263 DCHECK(view); | |
| 264 DCHECK(!titlebar_extra_view_); | |
| 265 AddChildView(view); | |
| 266 titlebar_extra_view_ = view; | |
| 267 } | |
| 268 | |
| 269 gfx::Rect BubbleFrameView::GetUpdatedWindowBounds(const gfx::Rect& anchor_rect, | |
| 270 gfx::Size client_size, | |
| 271 bool adjust_if_offscreen) { | |
| 272 gfx::Size size(GetSizeForClientSize(client_size)); | |
| 273 | |
| 274 const BubbleBorder::Arrow arrow = bubble_border_->arrow(); | |
| 275 if (adjust_if_offscreen && BubbleBorder::has_arrow(arrow)) { | |
| 276 // Try to mirror the anchoring if the bubble does not fit on the screen. | |
| 277 if (!bubble_border_->is_arrow_at_center(arrow)) { | |
| 278 MirrorArrowIfOffScreen(true, anchor_rect, size); | |
| 279 MirrorArrowIfOffScreen(false, anchor_rect, size); | |
| 280 } else { | |
| 281 const bool mirror_vertical = BubbleBorder::is_arrow_on_horizontal(arrow); | |
| 282 MirrorArrowIfOffScreen(mirror_vertical, anchor_rect, size); | |
| 283 OffsetArrowIfOffScreen(anchor_rect, size); | |
| 284 } | |
| 285 } | |
| 286 | |
| 287 // Calculate the bounds with the arrow in its updated location and offset. | |
| 288 return bubble_border_->GetBounds(anchor_rect, size); | |
| 289 } | |
| 290 | |
| 291 gfx::Rect BubbleFrameView::GetAvailableScreenBounds(const gfx::Rect& rect) { | |
| 292 // The bubble attempts to fit within the current screen bounds. | |
| 293 // TODO(scottmg): Native is wrong. http://crbug.com/133312 | |
| 294 return gfx::Screen::GetNativeScreen()->GetDisplayNearestPoint( | |
| 295 rect.CenterPoint()).work_area(); | |
| 296 } | |
| 297 | |
| 298 bool BubbleFrameView::IsCloseButtonVisible() const { | |
| 299 return close_->visible(); | |
| 300 } | |
| 301 | |
| 302 gfx::Rect BubbleFrameView::GetCloseButtonBounds() const { | |
| 303 return close_->bounds(); | |
| 304 } | |
| 305 | |
| 306 void BubbleFrameView::MirrorArrowIfOffScreen( | |
| 307 bool vertical, | |
| 308 const gfx::Rect& anchor_rect, | |
| 309 const gfx::Size& client_size) { | |
| 310 // Check if the bounds don't fit on screen. | |
| 311 gfx::Rect available_bounds(GetAvailableScreenBounds(anchor_rect)); | |
| 312 gfx::Rect window_bounds(bubble_border_->GetBounds(anchor_rect, client_size)); | |
| 313 if (GetOffScreenLength(available_bounds, window_bounds, vertical) > 0) { | |
| 314 BubbleBorder::Arrow arrow = bubble_border()->arrow(); | |
| 315 // Mirror the arrow and get the new bounds. | |
| 316 bubble_border_->set_arrow( | |
| 317 vertical ? BubbleBorder::vertical_mirror(arrow) : | |
| 318 BubbleBorder::horizontal_mirror(arrow)); | |
| 319 gfx::Rect mirror_bounds = | |
| 320 bubble_border_->GetBounds(anchor_rect, client_size); | |
| 321 // Restore the original arrow if mirroring doesn't show more of the bubble. | |
| 322 // Otherwise it should invoke parent's Layout() to layout the content based | |
| 323 // on the new bubble border. | |
| 324 if (GetOffScreenLength(available_bounds, mirror_bounds, vertical) >= | |
| 325 GetOffScreenLength(available_bounds, window_bounds, vertical)) | |
| 326 bubble_border_->set_arrow(arrow); | |
| 327 else if (parent()) | |
| 328 parent()->Layout(); | |
| 329 } | |
| 330 } | |
| 331 | |
| 332 void BubbleFrameView::OffsetArrowIfOffScreen(const gfx::Rect& anchor_rect, | |
| 333 const gfx::Size& client_size) { | |
| 334 BubbleBorder::Arrow arrow = bubble_border()->arrow(); | |
| 335 DCHECK(BubbleBorder::is_arrow_at_center(arrow)); | |
| 336 | |
| 337 // Get the desired bubble bounds without adjustment. | |
| 338 bubble_border_->set_arrow_offset(0); | |
| 339 gfx::Rect window_bounds(bubble_border_->GetBounds(anchor_rect, client_size)); | |
| 340 | |
| 341 gfx::Rect available_bounds(GetAvailableScreenBounds(anchor_rect)); | |
| 342 if (available_bounds.IsEmpty() || available_bounds.Contains(window_bounds)) | |
| 343 return; | |
| 344 | |
| 345 // Calculate off-screen adjustment. | |
| 346 const bool is_horizontal = BubbleBorder::is_arrow_on_horizontal(arrow); | |
| 347 int offscreen_adjust = 0; | |
| 348 if (is_horizontal) { | |
| 349 if (window_bounds.x() < available_bounds.x()) | |
| 350 offscreen_adjust = available_bounds.x() - window_bounds.x(); | |
| 351 else if (window_bounds.right() > available_bounds.right()) | |
| 352 offscreen_adjust = available_bounds.right() - window_bounds.right(); | |
| 353 } else { | |
| 354 if (window_bounds.y() < available_bounds.y()) | |
| 355 offscreen_adjust = available_bounds.y() - window_bounds.y(); | |
| 356 else if (window_bounds.bottom() > available_bounds.bottom()) | |
| 357 offscreen_adjust = available_bounds.bottom() - window_bounds.bottom(); | |
| 358 } | |
| 359 | |
| 360 // For center arrows, arrows are moved in the opposite direction of | |
| 361 // |offscreen_adjust|, e.g. positive |offscreen_adjust| means bubble | |
| 362 // window needs to be moved to the right and that means we need to move arrow | |
| 363 // to the left, and that means negative offset. | |
| 364 bubble_border_->set_arrow_offset( | |
| 365 bubble_border_->GetArrowOffset(window_bounds.size()) - offscreen_adjust); | |
| 366 if (offscreen_adjust) | |
| 367 SchedulePaint(); | |
| 368 } | |
| 369 | |
| 370 gfx::Size BubbleFrameView::GetSizeForClientSize( | |
| 371 const gfx::Size& client_size) const { | |
| 372 // Accommodate the width of the title bar elements. | |
| 373 int title_bar_width = GetInsets().width() + border()->GetInsets().width(); | |
| 374 if (!title_->text().empty()) | |
| 375 title_bar_width += kTitleLeftInset + title_->GetPreferredSize().width(); | |
| 376 if (close_->visible()) | |
| 377 title_bar_width += close_->width() + 1; | |
| 378 if (titlebar_extra_view_ != NULL) | |
| 379 title_bar_width += titlebar_extra_view_->GetPreferredSize().width(); | |
| 380 gfx::Size size(client_size); | |
| 381 size.SetToMax(gfx::Size(title_bar_width, 0)); | |
| 382 const gfx::Insets insets(GetInsets()); | |
| 383 size.Enlarge(insets.width(), insets.height()); | |
| 384 return size; | |
| 385 } | |
| 386 | |
| 387 } // namespace views | |
| OLD | NEW |