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 // Needs to be included first to get the enum resolved. |
| 6 #include "ash/wm/workspace/frame_maximize_button.h" |
| 7 #include "ash/wm/window_maximize.h" |
| 8 |
| 9 #include "ash/shell.h" |
| 10 #include "ash/shell_window_ids.h" |
| 11 #include "ash/wm/window_animations.h" |
| 12 #include "base/bind.h" |
| 13 #include "base/command_line.h" |
| 14 #include "base/timer.h" |
| 15 #include "grit/ash_strings.h" |
| 16 #include "grit/ui_resources.h" |
| 17 #include "ui/aura/aura_switches.h" |
| 18 #include "ui/base/ui_base_switches.h" |
| 19 #include "ui/aura/event.h" |
| 20 #include "ui/aura/window.h" |
| 21 #include "ui/base/l10n/l10n_util.h" |
| 22 #include "ui/base/resource/resource_bundle.h" |
| 23 #include "ui/gfx/canvas.h" |
| 24 #include "ui/gfx/screen.h" |
| 25 #include "ui/views/bubble/bubble_delegate.h" |
| 26 #include "ui/views/bubble/bubble_frame_view.h" |
| 27 #include "ui/views/controls/button/button.h" |
| 28 #include "ui/views/controls/button/custom_button.h" |
| 29 #include "ui/views/controls/label.h" |
| 30 #include "ui/views/events/event.h" |
| 31 #include "ui/views/layout/box_layout.h" |
| 32 #include "ui/views/radial_menu/radial_menu_views.h" |
| 33 |
| 34 namespace { |
| 35 |
| 36 // The command codes returned from the radial menu. |
| 37 enum RadialMenuCommands { |
| 38 RADIAL_MENU_NONE = 0, |
| 39 RADIAL_MENU_RIGHT, |
| 40 RADIAL_MENU_MINIMIZE, |
| 41 RADIAL_MENU_LEFT |
| 42 }; |
| 43 |
| 44 // Bubble constants |
| 45 const int kMaximumBubbleWidth = 200; |
| 46 const int kArrowOffset = 10; |
| 47 |
| 48 // The spacing between two buttons. |
| 49 const int kLayoutSpacing = 1; |
| 50 |
| 51 const int kAnimationDurationForPopupMS = 200; |
| 52 |
| 53 // The background color |
| 54 const SkColor kBubbleBackgroundColor = 0xf2141414; |
| 55 |
| 56 // The text color within the bubble |
| 57 const SkColor kBubbleTextColor = 0xffffffff; |
| 58 |
| 59 // The line width of the bubble. |
| 60 const int kLineHeight = 1; |
| 61 |
| 62 // The pixel dimensions of the arrow |
| 63 const int kArrowHeight = 10; |
| 64 const int kArrowWidth = 20; |
| 65 |
| 66 // The delay of the bubble appearance. |
| 67 const int kBubbleAppearanceDelay = 200; // msec |
| 68 |
| 69 // The active area behind a segment in a radial menu (in segment widths): |
| 70 // In case of hover the user wants to cancel out when getting reasonably far |
| 71 // away from the menu. |
| 72 const int kHoverRadialMenuExtension = 3; |
| 73 // In case of dragging the menu extends till eternity. |
| 74 const int kDragRadialMenuExtension = 10000; |
| 75 |
| 76 class MaximizeBubbleBorder : public views::BubbleBorder { |
| 77 public: |
| 78 MaximizeBubbleBorder(views::View* owner, |
| 79 views::View* anchor) |
| 80 : views::BubbleBorder(views::BubbleBorder::TOP_RIGHT, |
| 81 views::BubbleBorder::NO_SHADOW), |
| 82 owner_(owner), |
| 83 anchor_(anchor) { |
| 84 set_alignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE); |
| 85 } |
| 86 |
| 87 virtual ~MaximizeBubbleBorder() {} |
| 88 |
| 89 // Overridden from views::BubbleBorder to match the design specs. |
| 90 virtual gfx::Rect GetBounds(const gfx::Rect& position_relative_to, |
| 91 const gfx::Size& contents_size) const OVERRIDE { |
| 92 gfx::Size border_size(contents_size); |
| 93 gfx::Insets insets; |
| 94 GetInsets(&insets); |
| 95 border_size.Enlarge(insets.width(), insets.height()); |
| 96 |
| 97 // Position the bubble to center the box on the anchor. |
| 98 int x = -insets.left() - |
| 99 (border_size.width() - anchor_->width() - kArrowWidth) / 2; |
| 100 // Position the bubble under the anchor, overlapping the arrow with it. |
| 101 int y = anchor_->height() - insets.top(); |
| 102 |
| 103 gfx::Point view_topleft(x, y); |
| 104 views::View::ConvertPointToScreen(anchor_, &view_topleft); |
| 105 |
| 106 return gfx::Rect(view_topleft.x(), view_topleft.y(), |
| 107 border_size.width(), border_size.height()); |
| 108 } |
| 109 |
| 110 // Overridden from views::Border |
| 111 virtual void Paint(const views::View& view, |
| 112 gfx::Canvas* canvas) const OVERRIDE { |
| 113 gfx::Insets inset; |
| 114 GetInsets(&inset); |
| 115 |
| 116 // Draw the border line around everything. |
| 117 int y = inset.top(); |
| 118 // Top |
| 119 canvas->FillRect(gfx::Rect(inset.left(), |
| 120 y - kLineHeight, |
| 121 owner_->width(), |
| 122 kLineHeight), |
| 123 kBubbleBackgroundColor); |
| 124 // Bottom |
| 125 canvas->FillRect(gfx::Rect(inset.left(), |
| 126 y + owner_->height(), |
| 127 owner_->width(), |
| 128 kLineHeight), |
| 129 kBubbleBackgroundColor); |
| 130 // Left |
| 131 canvas->FillRect(gfx::Rect(inset.left() - kLineHeight, |
| 132 y - kLineHeight, |
| 133 kLineHeight, |
| 134 owner_->height() + 2 * kLineHeight), |
| 135 kBubbleBackgroundColor); |
| 136 // Right |
| 137 canvas->FillRect(gfx::Rect(inset.left() + owner_->width(), |
| 138 y - kLineHeight, |
| 139 kLineHeight, |
| 140 owner_->height() + 2 * kLineHeight), |
| 141 kBubbleBackgroundColor); |
| 142 |
| 143 // Draw the arrow afterwards covering the border. |
| 144 SkPath path; |
| 145 path.incReserve(4); |
| 146 // The center of the tip should be in the middle of the button. |
| 147 int tip_x = inset.left() + owner_->width() / 2; |
| 148 int left_base_x = tip_x - kArrowWidth / 2; |
| 149 int left_base_y = y; |
| 150 int tip_y = left_base_y - kArrowHeight; |
| 151 path.moveTo(SkIntToScalar(left_base_x), SkIntToScalar(left_base_y)); |
| 152 path.lineTo(SkIntToScalar(tip_x), SkIntToScalar(tip_y)); |
| 153 path.lineTo(SkIntToScalar(left_base_x + kArrowWidth), |
| 154 SkIntToScalar(left_base_y)); |
| 155 |
| 156 SkPaint paint; |
| 157 paint.setStyle(SkPaint::kFill_Style); |
| 158 paint.setColor(kBubbleBackgroundColor); |
| 159 canvas->DrawPath(path, paint); |
| 160 } |
| 161 |
| 162 private: |
| 163 views::View* owner_; |
| 164 views::View* anchor_; |
| 165 |
| 166 DISALLOW_COPY_AND_ASSIGN(MaximizeBubbleBorder); |
| 167 }; |
| 168 |
| 169 } // namespace |
| 170 |
| 171 namespace ash { |
| 172 |
| 173 // The image button gets overridden to be able to capture mouse hover events. |
| 174 class MaximizeBubble::BubbleMenuButton : public views::ImageButton { |
| 175 public: |
| 176 explicit BubbleMenuButton(MaximizeBubble::BubbleContentsButtonRow* listener); |
| 177 virtual ~BubbleMenuButton() {}; |
| 178 |
| 179 // CustomButton overrides: |
| 180 virtual void OnMouseEntered(const views::MouseEvent& event) OVERRIDE; |
| 181 virtual void OnMouseExited(const views::MouseEvent& event) OVERRIDE; |
| 182 |
| 183 private: |
| 184 // The creating class which needs to get notified in case of a hover event. |
| 185 MaximizeBubble::BubbleContentsButtonRow* owner_; |
| 186 |
| 187 DISALLOW_COPY_AND_ASSIGN(BubbleMenuButton); |
| 188 }; |
| 189 |
| 190 // A class that creates all buttons and put them into a view. |
| 191 class MaximizeBubble::BubbleContentsButtonRow : public views::View, |
| 192 public views::ButtonListener { |
| 193 public: |
| 194 explicit BubbleContentsButtonRow(MaximizeBubble* bubble) |
| 195 : bubble_(bubble), |
| 196 left_button_(NULL), |
| 197 minimize_button_(NULL), |
| 198 right_button_(NULL) { |
| 199 SetLayoutManager(new views::BoxLayout( |
| 200 views::BoxLayout::kHorizontal, 0, 0, kLayoutSpacing)); |
| 201 set_background( |
| 202 views::Background::CreateSolidBackground(kBubbleBackgroundColor)); |
| 203 |
| 204 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| 205 left_button_ = new MaximizeBubble::BubbleMenuButton(this); |
| 206 left_button_->SetImage(views::CustomButton::BS_NORMAL, |
| 207 // TODO(skuhne): Replace images as soon as they come in. |
| 208 rb.GetImageSkiaNamed(IDR_AURA_WINDOW_SNAP_LEFT_P)); |
| 209 left_button_->SetImage(views::CustomButton::BS_HOT, |
| 210 // TODO(skuhne): Replace images as soon as they come in. |
| 211 rb.GetImageSkiaNamed(IDR_AURA_WINDOW_SNAP_LEFT_P)); |
| 212 left_button_->SetImage(views::CustomButton::BS_PUSHED, |
| 213 // TODO(skuhne): Replace images as soon as they come in. |
| 214 rb.GetImageSkiaNamed(IDR_AURA_WINDOW_SNAP_LEFT_P)); |
| 215 AddChildView(left_button_); |
| 216 |
| 217 minimize_button_ = new MaximizeBubble::BubbleMenuButton(this); |
| 218 minimize_button_->SetImage(views::CustomButton::BS_NORMAL, |
| 219 // TODO(skuhne): Replace images as soon as they come in. |
| 220 rb.GetImageSkiaNamed(IDR_AURA_WINDOW_SNAP_P)); |
| 221 minimize_button_->SetImage(views::CustomButton::BS_HOT, |
| 222 // TODO(skuhne): Replace images as soon as they come in. |
| 223 rb.GetImageSkiaNamed(IDR_AURA_WINDOW_SNAP_P)); |
| 224 minimize_button_->SetImage(views::CustomButton::BS_PUSHED, |
| 225 // TODO(skuhne): Replace images as soon as they come in. |
| 226 rb.GetImageSkiaNamed(IDR_AURA_WINDOW_SNAP_P)); |
| 227 AddChildView(minimize_button_); |
| 228 |
| 229 right_button_ = new MaximizeBubble::BubbleMenuButton(this); |
| 230 right_button_->SetImage(views::CustomButton::BS_NORMAL, |
| 231 // TODO(skuhne): Replace images as soon as they come in. |
| 232 rb.GetImageSkiaNamed(IDR_AURA_WINDOW_SNAP_RIGHT_P)); |
| 233 right_button_->SetImage(views::CustomButton::BS_HOT, |
| 234 // TODO(skuhne): Replace images as soon as they come in. |
| 235 rb.GetImageSkiaNamed(IDR_AURA_WINDOW_SNAP_RIGHT_P)); |
| 236 right_button_->SetImage(views::CustomButton::BS_PUSHED, |
| 237 // TODO(skuhne): Replace images as soon as they come in. |
| 238 rb.GetImageSkiaNamed(IDR_AURA_WINDOW_SNAP_RIGHT_P)); |
| 239 AddChildView(right_button_); |
| 240 } |
| 241 |
| 242 virtual ~BubbleContentsButtonRow() {} |
| 243 |
| 244 // Overridden from ButtonListener. |
| 245 virtual void ButtonPressed(views::Button* sender, |
| 246 const views::Event& event) OVERRIDE { |
| 247 if (sender == left_button_) |
| 248 bubble_->OnButtonClicked(FrameMaximizeButton::SNAP_LEFT); |
| 249 if (sender == minimize_button_) |
| 250 bubble_->OnButtonClicked(FrameMaximizeButton::SNAP_MINIMIZE); |
| 251 if (sender == right_button_) |
| 252 bubble_->OnButtonClicked(FrameMaximizeButton::SNAP_RIGHT); |
| 253 } |
| 254 |
| 255 // Called from BubbleMenuButton. |
| 256 void ButtonHovered(MaximizeBubble::BubbleMenuButton* sender) { |
| 257 if (sender == left_button_) |
| 258 bubble_->OnButtonHover(FrameMaximizeButton::SNAP_LEFT); |
| 259 else if (sender == minimize_button_) |
| 260 bubble_->OnButtonHover(FrameMaximizeButton::SNAP_MINIMIZE); |
| 261 else if (sender == right_button_) |
| 262 bubble_->OnButtonHover(FrameMaximizeButton::SNAP_RIGHT); |
| 263 else |
| 264 bubble_->OnButtonHover(FrameMaximizeButton::SNAP_NONE); |
| 265 } |
| 266 |
| 267 private: |
| 268 // The owning object which gets notifications. |
| 269 MaximizeBubble* bubble_; |
| 270 |
| 271 // The created buttons for our menu. |
| 272 MaximizeBubble::BubbleMenuButton* left_button_; |
| 273 MaximizeBubble::BubbleMenuButton* minimize_button_; |
| 274 MaximizeBubble::BubbleMenuButton* right_button_; |
| 275 |
| 276 DISALLOW_COPY_AND_ASSIGN(BubbleContentsButtonRow); |
| 277 }; |
| 278 |
| 279 MaximizeBubble::BubbleMenuButton::BubbleMenuButton( |
| 280 MaximizeBubble::BubbleContentsButtonRow* listener) |
| 281 : views::ImageButton(listener), |
| 282 owner_(listener) {} |
| 283 |
| 284 void MaximizeBubble::BubbleMenuButton::OnMouseEntered( |
| 285 const views::MouseEvent& event) OVERRIDE { |
| 286 owner_->ButtonHovered(this); |
| 287 views::ImageButton::OnMouseEntered(event); |
| 288 } |
| 289 |
| 290 void MaximizeBubble::BubbleMenuButton::OnMouseExited( |
| 291 const views::MouseEvent& event) OVERRIDE { |
| 292 owner_->ButtonHovered(NULL); |
| 293 views::ImageButton::OnMouseExited(event); |
| 294 } |
| 295 |
| 296 // A class which creates the content of the bubble: The buttons, and the label. |
| 297 class MaximizeBubble::BubbleContentsView : public views::View { |
| 298 public: |
| 299 explicit BubbleContentsView(MaximizeBubble* bubble) |
| 300 : bubble_(bubble) { |
| 301 SetLayoutManager(new views::BoxLayout( |
| 302 views::BoxLayout::kVertical, 0, 0, kLayoutSpacing)); |
| 303 set_background( |
| 304 views::Background::CreateSolidBackground(kBubbleBackgroundColor)); |
| 305 |
| 306 buttons_view_ = new BubbleContentsButtonRow(bubble); |
| 307 AddChildView(buttons_view_); |
| 308 |
| 309 label_view_ = new views::Label(); |
| 310 SetMenuState(FrameMaximizeButton::SNAP_NONE); |
| 311 label_view_->SetHorizontalAlignment(views::Label::ALIGN_CENTER); |
| 312 label_view_->SetBackgroundColor(kBubbleBackgroundColor); |
| 313 label_view_->SetEnabledColor(kBubbleTextColor); |
| 314 AddChildView(label_view_); |
| 315 } |
| 316 |
| 317 virtual ~BubbleContentsView() {} |
| 318 |
| 319 // Set the label content to reflect the currently selected |snap_type|. |
| 320 // This function can be executed through the frame maximize button as well as |
| 321 // through hover operations. |
| 322 void SetMenuState(FrameMaximizeButton::SnapType snap_type) { |
| 323 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| 324 switch (snap_type) { |
| 325 case FrameMaximizeButton::SNAP_LEFT: |
| 326 label_view_->SetText(rb.GetLocalizedString(IDS_ASH_SNAP_WINDOW_LEFT)); |
| 327 return; |
| 328 case FrameMaximizeButton::SNAP_RIGHT: |
| 329 label_view_->SetText(rb.GetLocalizedString(IDS_ASH_SNAP_WINDOW_RIGHT)); |
| 330 return; |
| 331 case FrameMaximizeButton::SNAP_MAXIMIZE: |
| 332 DCHECK(!bubble_->is_maximized()); |
| 333 label_view_->SetText(rb.GetLocalizedString(IDS_ASH_MAXIMIZE_WINDOW)); |
| 334 return; |
| 335 case FrameMaximizeButton::SNAP_MINIMIZE: |
| 336 label_view_->SetText(rb.GetLocalizedString(IDS_ASH_MINIMIZE_WINDOW)); |
| 337 return; |
| 338 case FrameMaximizeButton::SNAP_RESTORE: |
| 339 DCHECK(bubble_->is_maximized()); |
| 340 label_view_->SetText(rb.GetLocalizedString(IDS_ASH_RESTORE_WINDOW)); |
| 341 return; |
| 342 default: |
| 343 // If nothing is selected, we automatically select the click operation. |
| 344 label_view_->SetText(rb.GetLocalizedString( |
| 345 bubble_->is_maximized() ? IDS_ASH_RESTORE_WINDOW : |
| 346 IDS_ASH_MAXIMIZE_WINDOW)); |
| 347 return; |
| 348 } |
| 349 } |
| 350 |
| 351 private: |
| 352 // The owning class. |
| 353 MaximizeBubble* bubble_; |
| 354 |
| 355 // The object which owns all the buttons. |
| 356 BubbleContentsButtonRow* buttons_view_; |
| 357 |
| 358 // The label object which shows the user the selected action. |
| 359 views::Label* label_view_; |
| 360 |
| 361 DISALLOW_COPY_AND_ASSIGN(BubbleContentsView); |
| 362 }; |
| 363 |
| 364 // The class which creates and manages the bubble menu element. |
| 365 // It creates a "bubble border" and the content accordingly. |
| 366 // Note: Since the SnapSizer will show animations on top of the maximize button |
| 367 // this menu gets creates as a separate window and the SnapSizer will be |
| 368 // created underneath this window. |
| 369 class MaximizeBubble::Bubble : public views::BubbleDelegateView { |
| 370 public: |
| 371 explicit Bubble(MaximizeBubble* bubble) |
| 372 : views::BubbleDelegateView(bubble->frame_maximize_button(), |
| 373 views::BubbleBorder::TOP_RIGHT), |
| 374 bubble_(bubble), |
| 375 bubble_widget_(NULL), |
| 376 contents_view_(NULL) { |
| 377 set_margins(gfx::Insets()); |
| 378 |
| 379 // The window needs to be owned by the root so that the SnapSizer does not |
| 380 // cover it upon animation. |
| 381 aura::Window* parent = Shell::GetContainer( |
| 382 Shell::GetActiveRootWindow(), |
| 383 internal::kShellWindowId_LauncherContainer); |
| 384 set_parent_window(parent); |
| 385 |
| 386 set_notify_enter_exit_on_child(true); |
| 387 set_try_mirroring_arrow(false); |
| 388 SetPaintToLayer(true); |
| 389 set_color(kBubbleBackgroundColor); |
| 390 set_close_on_deactivate(false); |
| 391 set_background( |
| 392 views::Background::CreateSolidBackground(kBubbleBackgroundColor)); |
| 393 |
| 394 SetLayoutManager(new views::BoxLayout( |
| 395 views::BoxLayout::kVertical, 0, 0, kLayoutSpacing)); |
| 396 |
| 397 contents_view_ = new BubbleContentsView(bubble); |
| 398 AddChildView(contents_view_); |
| 399 |
| 400 // Note that the returned widget has an observer which points to our |
| 401 // functions. |
| 402 bubble_widget_ = views::BubbleDelegateView::CreateBubble(this); |
| 403 |
| 404 SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE); |
| 405 bubble_widget_->non_client_view()->frame_view()->set_background(NULL); |
| 406 |
| 407 MaximizeBubbleBorder* bubble_border = new MaximizeBubbleBorder( |
| 408 this, anchor_view()); |
| 409 GetBubbleFrameView()->SetBubbleBorder(bubble_border); |
| 410 GetBubbleFrameView()->set_background(NULL); |
| 411 |
| 412 // Recalculate size with new border. |
| 413 SizeToContents(); |
| 414 |
| 415 // Setup animation. |
| 416 ash::SetWindowVisibilityAnimationType( |
| 417 bubble_widget_->GetNativeWindow(), |
| 418 ash::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE); |
| 419 ash::SetWindowVisibilityAnimationTransition( |
| 420 bubble_widget_->GetNativeWindow(), |
| 421 ash::ANIMATE_BOTH); |
| 422 ash::SetWindowVisibilityAnimationDuration( |
| 423 bubble_widget_->GetNativeWindow(), |
| 424 base::TimeDelta::FromMilliseconds(kAnimationDurationForPopupMS)); |
| 425 |
| 426 Show(); |
| 427 } |
| 428 |
| 429 virtual ~Bubble() { |
| 430 if (bubble_widget_) { |
| 431 bubble_widget_->RemoveObserver(this); |
| 432 anchor_widget()->RemoveObserver(this); |
| 433 bubble_widget_->Close(); |
| 434 bubble_widget_ = NULL; |
| 435 } |
| 436 } |
| 437 |
| 438 // The window of the menu under which the SnapSizer will get created. |
| 439 aura::Window* GetMenuWindow() { |
| 440 return bubble_widget_ ? bubble_widget_->GetNativeWindow() : NULL; |
| 441 } |
| 442 |
| 443 // Overridden from views::BubbleDelegateView. |
| 444 virtual gfx::Rect GetAnchorRect() const OVERRIDE { |
| 445 gfx::Rect anchor_rect = |
| 446 bubble_->frame_maximize_button()->GetBoundsInScreen(); |
| 447 return anchor_rect; |
| 448 } |
| 449 |
| 450 // Overridden from View |
| 451 virtual void OnMouseExited(const views::MouseEvent& event) OVERRIDE { |
| 452 // When we leave the bubble, we might be still be in gesture mode or over |
| 453 // the maximize button. So only close if none of the other cases apply. |
| 454 if (!bubble_->frame_maximize_button()->is_snap_enabled()) { |
| 455 gfx::Point screen_location = gfx::Screen::GetCursorScreenPoint(); |
| 456 if (!bubble_->frame_maximize_button()->GetBoundsInScreen().Contains( |
| 457 screen_location)) |
| 458 bubble_->Close(); |
| 459 } |
| 460 } |
| 461 |
| 462 virtual void OnClickedOutsideView() OVERRIDE { |
| 463 // Don't destroy the menu when the click happened on the frame maximize |
| 464 // button. |
| 465 if (!bubble_->frame_maximize_button()->is_snap_enabled()) |
| 466 bubble_->Close(); // Will destroy |this|. |
| 467 } |
| 468 |
| 469 // Overridden from views::View. |
| 470 virtual gfx::Size GetPreferredSize() OVERRIDE { |
| 471 return contents_view_->GetPreferredSize(); |
| 472 } |
| 473 |
| 474 // Overridden from views::Widget::Observer. |
| 475 virtual void OnWidgetClosing(views::Widget* widget) OVERRIDE { |
| 476 if (bubble_widget_) { |
| 477 CHECK_EQ(bubble_widget_, widget); |
| 478 bubble_widget_ = NULL; |
| 479 bubble_->Close(); // Will destroy |this|. |
| 480 } |
| 481 } |
| 482 |
| 483 // Called from the owning class to indicate that the menu should get |
| 484 // destroyed. |
| 485 virtual void Close() { |
| 486 if (bubble_widget_) { |
| 487 bubble_widget_->Close(); |
| 488 bubble_widget_ = NULL; |
| 489 } |
| 490 bubble_->Close(); // Will destroy |this|. |
| 491 } |
| 492 |
| 493 // Called from the owning class to change the menu content to the given |
| 494 // |snap_type| so that the user knows what is selected. |
| 495 void SetMenuState(FrameMaximizeButton::SnapType snap_type) { |
| 496 if (contents_view_) |
| 497 contents_view_->SetMenuState(snap_type); |
| 498 } |
| 499 |
| 500 private: |
| 501 // Our owning class. |
| 502 MaximizeBubble* bubble_; |
| 503 |
| 504 // The widget which contains our menu and the bubble border. |
| 505 views::Widget* bubble_widget_; |
| 506 |
| 507 // The content accessor of the menu. |
| 508 BubbleContentsView* contents_view_; |
| 509 |
| 510 DISALLOW_COPY_AND_ASSIGN(Bubble); |
| 511 }; |
| 512 |
| 513 MaximizeBubble::MaximizeBubble(FrameMaximizeButton* frame_maximize_button, |
| 514 bool is_maximized) |
| 515 : frame_maximize_button_(frame_maximize_button), |
| 516 bubble_(NULL), |
| 517 radial_menu_(NULL), |
| 518 current_radial_snap_hover_type_(FrameMaximizeButton::SNAP_NONE), |
| 519 is_maximized_(is_maximized) { |
| 520 // Create the task which will create the bubble delayed. |
| 521 base::OneShotTimer<MaximizeBubble>* new_timer = |
| 522 new base::OneShotTimer<MaximizeBubble>(); |
| 523 new_timer->Start( |
| 524 FROM_HERE, |
| 525 base::TimeDelta::FromMilliseconds(kBubbleAppearanceDelay), |
| 526 this, |
| 527 &MaximizeBubble::DelayedBubbleCreation); |
| 528 timer_.reset(new_timer); |
| 529 } |
| 530 |
| 531 void MaximizeBubble::DelayCreation() { |
| 532 if (timer_.get() && timer_->IsRunning()) |
| 533 timer_->Reset(); |
| 534 } |
| 535 |
| 536 void MaximizeBubble::DelayedBubbleCreation() { |
| 537 if (!bubble_ && !radial_menu_) { |
| 538 if (CommandLine::ForCurrentProcess()->HasSwitch( |
| 539 switches::kEnableTouchRadialMenu)) { |
| 540 std::vector<views::RadialMenuItem*> items; |
| 541 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| 542 DCHECK(RADIAL_MENU_NONE == items.size()); |
| 543 items.push_back(views::RadialMenuItem::CreateRadialMenuItemInstance( |
| 544 views::RadialMenuItem::RADIAL_SEPARATOR, |
| 545 NULL, |
| 546 false)); |
| 547 DCHECK(RADIAL_MENU_RIGHT == items.size()); |
| 548 items.push_back(views::RadialMenuItem::CreateRadialMenuItemInstance( |
| 549 views::RadialMenuItem::RADIAL_BUTTON, |
| 550 // TODO(skuhne): Replace images as soon as they come in. |
| 551 rb.GetImageSkiaNamed(IDR_AURA_WINDOW_SNAP_RIGHT_P), |
| 552 false)); |
| 553 DCHECK(RADIAL_MENU_MINIMIZE == items.size()); |
| 554 items.push_back(views::RadialMenuItem::CreateRadialMenuItemInstance( |
| 555 views::RadialMenuItem::RADIAL_BUTTON, |
| 556 // TODO(skuhne): Replace images as soon as they come in. |
| 557 rb.GetImageSkiaNamed(IDR_AURA_WINDOW_SNAP_P), |
| 558 false)); |
| 559 DCHECK(RADIAL_MENU_LEFT == items.size()); |
| 560 items.push_back(views::RadialMenuItem::CreateRadialMenuItemInstance( |
| 561 views::RadialMenuItem::RADIAL_BUTTON, |
| 562 // TODO(skuhne): Replace images as soon as they come in. |
| 563 rb.GetImageSkiaNamed(IDR_AURA_WINDOW_SNAP_LEFT_P), |
| 564 false)); |
| 565 gfx::Rect rect = frame_maximize_button()->GetBoundsInScreen(); |
| 566 gfx::Point location = rect.CenterPoint(); |
| 567 int radius = (rect.width() < rect.height() ? rect.width() : |
| 568 rect.height()) / 2; |
| 569 // Shrinking the radius slightly to avoid having the radial menu not |
| 570 // covering the entire button. |
| 571 radius -= radius / 10; |
| 572 radial_menu_ = views::RadialMenu::CreateRadialMenuInstance( |
| 573 location, // Location center is always the center of the button. |
| 574 3 * radius, // The outer radius hits the end of the window. |
| 575 radius, // The button itself is minimally covered. |
| 576 0.0, |
| 577 items, |
| 578 kHoverRadialMenuExtension, |
| 579 false); // Do not crop the menu to the screen. |
| 580 // A global event handler gets added to keep track of radial menu related |
| 581 // mouse move updates. This filter only tracks mouse movement when no key |
| 582 // is pressed. |
| 583 ash::Shell::GetInstance()->AddEnvEventFilter(this); |
| 584 } else { |
| 585 bubble_ = new Bubble(this); |
| 586 } |
| 587 } |
| 588 timer_.reset(NULL); |
| 589 } |
| 590 |
| 591 MaximizeBubble::~MaximizeBubble() { |
| 592 timer_.reset(NULL); |
| 593 if (bubble_) { |
| 594 // To avoid recursive destruction we make sure to clear the destroyed |
| 595 // objects pointer before we tell it to destroy itself. |
| 596 Bubble* bubble = bubble_; |
| 597 bubble_ = NULL; |
| 598 bubble->Close(); |
| 599 } |
| 600 if (radial_menu_) { |
| 601 // To avoid recursive destruction we make sure to clear the destroyed |
| 602 // objects pointer before we tell it to destroy itself. |
| 603 ash::Shell::GetInstance()->RemoveEnvEventFilter(this); |
| 604 views::RadialMenu* radial_menu = radial_menu_; |
| 605 radial_menu_ = NULL; |
| 606 radial_menu->CloseAndDelete(false); |
| 607 } |
| 608 } |
| 609 |
| 610 void MaximizeBubble::Close() { |
| 611 timer_.reset(NULL); |
| 612 // Note: If |bubble_| is NULL, we are in process of deleting the menu. |
| 613 if (bubble_ || radial_menu_) |
| 614 frame_maximize_button_->DestroyMaximizeMenu(); |
| 615 } |
| 616 |
| 617 void MaximizeBubble::OnButtonClicked(FrameMaximizeButton::SnapType snap_type) { |
| 618 frame_maximize_button_->SnapButtonClicked(snap_type); |
| 619 // Once it was clicked we can close the menu. |
| 620 Close(); |
| 621 } |
| 622 |
| 623 void MaximizeBubble::OnButtonHover(FrameMaximizeButton::SnapType snap_type) { |
| 624 frame_maximize_button_->SnapButtonHovered(snap_type); |
| 625 } |
| 626 |
| 627 void MaximizeBubble::SetMenuState(FrameMaximizeButton::SnapType snap_type) { |
| 628 if (bubble_) |
| 629 bubble_->SetMenuState(snap_type); |
| 630 } |
| 631 |
| 632 aura::Window* MaximizeBubble::GetMenuWindow() { |
| 633 return bubble_ ? bubble_->GetMenuWindow() : |
| 634 (radial_menu_ ? radial_menu_->GetWindow() : NULL); |
| 635 } |
| 636 |
| 637 // Functions added for the experimental radial menu. |
| 638 |
| 639 bool MaximizeBubble::IsRadialMenu() { |
| 640 return radial_menu_ ? true : false; |
| 641 } |
| 642 |
| 643 bool MaximizeBubble::SnapTypeForLocation( |
| 644 const gfx::Point& location, |
| 645 FrameMaximizeButton::SnapType& type) { |
| 646 int item = -1; |
| 647 if (radial_menu_) { |
| 648 int count = 0; |
| 649 // Depending on the hover / drag operation we use different |
| 650 // 'activateable areas' behind a segment: In case of hover we cancel the |
| 651 // menu when we get a bit away - for drag operations however we do not |
| 652 // cancel the operation at all (at least not from within the menu). |
| 653 int max_activatable_area = frame_maximize_button_->is_snap_enabled() ? |
| 654 kDragRadialMenuExtension : kHoverRadialMenuExtension; |
| 655 radial_menu_->set_pointer_activation_area_extension(max_activatable_area); |
| 656 radial_menu_->HitMenuItemTest(location, &item, &count); |
| 657 } |
| 658 switch (item) { |
| 659 case RADIAL_MENU_RIGHT: |
| 660 type = FrameMaximizeButton::SNAP_RIGHT; |
| 661 return true; |
| 662 case RADIAL_MENU_MINIMIZE: |
| 663 type = FrameMaximizeButton::SNAP_MINIMIZE; |
| 664 return true; |
| 665 case RADIAL_MENU_LEFT: |
| 666 type = FrameMaximizeButton::SNAP_LEFT; |
| 667 return true; |
| 668 default: |
| 669 type = FrameMaximizeButton::SNAP_NONE; |
| 670 return false; |
| 671 } |
| 672 } |
| 673 |
| 674 bool MaximizeBubble::PreHandleKeyEvent(aura::Window* target, |
| 675 aura::KeyEvent* event) { |
| 676 return false; |
| 677 } |
| 678 |
| 679 bool MaximizeBubble::PreHandleMouseEvent(aura::Window* target, |
| 680 aura::MouseEvent* event) { |
| 681 if (event->type() == ui::ET_MOUSE_MOVED && |
| 682 !frame_maximize_button_->is_snap_enabled()) { |
| 683 // If we are neither over our menu, nor over the button itself we close |
| 684 // the menu. |
| 685 gfx::Point pt = gfx::Screen::GetCursorScreenPoint(); |
| 686 if (!SnapTypeForLocation(pt, current_radial_snap_hover_type_) && |
| 687 !frame_maximize_button()->GetBoundsInScreen().Contains(pt)) { |
| 688 OnButtonHover(FrameMaximizeButton::SNAP_NONE); |
| 689 Close(); |
| 690 } else |
| 691 OnButtonHover(current_radial_snap_hover_type_); |
| 692 } |
| 693 |
| 694 if (event->type() == ui::ET_MOUSE_PRESSED && |
| 695 !frame_maximize_button_->is_snap_enabled() && |
| 696 current_radial_snap_hover_type_ != FrameMaximizeButton::SNAP_NONE) { |
| 697 OnButtonClicked(current_radial_snap_hover_type_); |
| 698 // This action was consumed here. |
| 699 return true; |
| 700 } |
| 701 |
| 702 return false; |
| 703 } |
| 704 |
| 705 ui::TouchStatus MaximizeBubble::PreHandleTouchEvent( |
| 706 aura::Window* target, |
| 707 aura::TouchEvent* event) { |
| 708 return ui::TOUCH_STATUS_UNKNOWN; |
| 709 } |
| 710 |
| 711 ui::GestureStatus MaximizeBubble::PreHandleGestureEvent( |
| 712 aura::Window* target, aura::GestureEvent* event) { |
| 713 return ui::GESTURE_STATUS_UNKNOWN; |
| 714 } |
| 715 |
| 716 } // namespace ash |
OLD | NEW |