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 "ash/wm/maximize_bubble_controller.h"
8 #include "ash/shell_delegate.h"
9 #include "ash/shell_window_ids.h"
10 #include "ash/wm/window_animations.h"
11 #include "ash/wm/workspace/frame_maximize_button.h"
12 #include "base/timer.h"
13 #include "grit/ash_resources.h"
14 #include "grit/ash_strings.h"
15 #include "third_party/skia/include/core/SkPath.h"
16 #include "ui/aura/window.h"
17 #include "ui/base/animation/animation.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/gfx/canvas.h"
21 #include "ui/gfx/path.h"
22 #include "ui/gfx/screen.h"
23 #include "ui/views/bubble/bubble_delegate.h"
24 #include "ui/views/bubble/bubble_frame_view.h"
25 #include "ui/views/controls/button/button.h"
26 #include "ui/views/controls/button/image_button.h"
27 #include "ui/views/controls/label.h"
28 #include "ui/views/layout/box_layout.h"
29 #include "ui/views/mouse_watcher.h"
30 #include "ui/views/widget/widget.h"
34 // The spacing between two buttons.
35 const int kLayoutSpacing
= -1;
37 // The background color.
38 const SkColor kBubbleBackgroundColor
= 0xFF141414;
40 // The text color within the bubble.
41 const SkColor kBubbleTextColor
= SK_ColorWHITE
;
43 // The line width of the bubble.
44 const int kLineWidth
= 1;
46 // The spacing for the top and bottom of the info label.
47 const int kLabelSpacing
= 4;
49 // The pixel dimensions of the arrow.
50 const int kArrowHeight
= 10;
51 const int kArrowWidth
= 20;
53 // The animation offset in y for the bubble when appearing.
54 const int kBubbleAnimationOffsetY
= 5;
56 class MaximizeBubbleBorder
: public views::BubbleBorder
{
58 MaximizeBubbleBorder(views::View
* content_view
, views::View
* anchor
);
60 virtual ~MaximizeBubbleBorder() {}
62 // Get the mouse active area of the window.
63 void GetMask(gfx::Path
* mask
);
65 // Overridden from views::BubbleBorder to match the design specs.
66 virtual gfx::Rect
GetBounds(const gfx::Rect
& position_relative_to
,
67 const gfx::Size
& contents_size
) const OVERRIDE
;
69 // Overridden from views::Border.
70 virtual void Paint(const views::View
& view
, gfx::Canvas
* canvas
) OVERRIDE
;
73 // Note: Animations can continue after then main window frame was destroyed.
74 // To avoid this problem, the owning screen metrics get extracted upon
76 gfx::Size anchor_size_
;
77 gfx::Point anchor_screen_origin_
;
78 views::View
* content_view_
;
80 DISALLOW_COPY_AND_ASSIGN(MaximizeBubbleBorder
);
83 MaximizeBubbleBorder::MaximizeBubbleBorder(views::View
* content_view
,
85 : views::BubbleBorder(views::BubbleBorder::TOP_RIGHT
,
86 views::BubbleBorder::NO_SHADOW
,
87 kBubbleBackgroundColor
),
88 anchor_size_(anchor
->size()),
89 anchor_screen_origin_(0, 0),
90 content_view_(content_view
) {
91 views::View::ConvertPointToScreen(anchor
, &anchor_screen_origin_
);
92 set_alignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE
);
95 void MaximizeBubbleBorder::GetMask(gfx::Path
* mask
) {
96 gfx::Insets inset
= GetInsets();
97 // Note: Even though the tip could be added as activatable, it is left out
98 // since it would not change the action behavior in any way plus it makes
99 // more sense to keep the focus on the underlying button for clicks.
100 int left
= inset
.left() - kLineWidth
;
101 int right
= inset
.left() + content_view_
->width() + kLineWidth
;
102 int top
= inset
.top() - kLineWidth
;
103 int bottom
= inset
.top() + content_view_
->height() + kLineWidth
;
104 mask
->moveTo(left
, top
);
105 mask
->lineTo(right
, top
);
106 mask
->lineTo(right
, bottom
);
107 mask
->lineTo(left
, bottom
);
108 mask
->lineTo(left
, top
);
112 gfx::Rect
MaximizeBubbleBorder::GetBounds(
113 const gfx::Rect
& position_relative_to
,
114 const gfx::Size
& contents_size
) const {
115 gfx::Size
border_size(contents_size
);
116 gfx::Insets insets
= GetInsets();
117 border_size
.Enlarge(insets
.width(), insets
.height());
119 // Position the bubble to center the box on the anchor.
120 int x
= (-border_size
.width() + anchor_size_
.width()) / 2;
121 // Position the bubble under the anchor, overlapping the arrow with it.
122 int y
= anchor_size_
.height() - insets
.top();
124 gfx::Point
view_origin(x
+ anchor_screen_origin_
.x(),
125 y
+ anchor_screen_origin_
.y());
127 return gfx::Rect(view_origin
, border_size
);
130 void MaximizeBubbleBorder::Paint(const views::View
& view
, gfx::Canvas
* canvas
) {
131 gfx::Insets inset
= GetInsets();
133 // Draw the border line around everything.
136 canvas
->FillRect(gfx::Rect(inset
.left(),
138 content_view_
->width(),
140 kBubbleBackgroundColor
);
142 canvas
->FillRect(gfx::Rect(inset
.left(),
143 y
+ content_view_
->height(),
144 content_view_
->width(),
146 kBubbleBackgroundColor
);
148 canvas
->FillRect(gfx::Rect(inset
.left() - kLineWidth
,
151 content_view_
->height() + 2 * kLineWidth
),
152 kBubbleBackgroundColor
);
154 canvas
->FillRect(gfx::Rect(inset
.left() + content_view_
->width(),
157 content_view_
->height() + 2 * kLineWidth
),
158 kBubbleBackgroundColor
);
160 // Draw the arrow afterwards covering the border.
163 // The center of the tip should be in the middle of the button.
164 int tip_x
= inset
.left() + content_view_
->width() / 2;
165 int left_base_x
= tip_x
- kArrowWidth
/ 2;
167 int tip_y
= left_base_y
- kArrowHeight
;
168 path
.moveTo(SkIntToScalar(left_base_x
), SkIntToScalar(left_base_y
));
169 path
.lineTo(SkIntToScalar(tip_x
), SkIntToScalar(tip_y
));
170 path
.lineTo(SkIntToScalar(left_base_x
+ kArrowWidth
),
171 SkIntToScalar(left_base_y
));
174 paint
.setStyle(SkPaint::kFill_Style
);
175 paint
.setColor(kBubbleBackgroundColor
);
176 canvas
->DrawPath(path
, paint
);
183 class BubbleContentsButtonRow
;
184 class BubbleContentsView
;
185 class BubbleDialogButton
;
187 // The mouse watcher host which makes sure that the bubble does not get closed
188 // while the mouse cursor is over the maximize button or the balloon content.
189 // Note: This object gets destroyed when the MouseWatcher gets destroyed.
190 class BubbleMouseWatcherHost
: public views::MouseWatcherHost
{
192 explicit BubbleMouseWatcherHost(MaximizeBubbleController::Bubble
* bubble
)
194 virtual ~BubbleMouseWatcherHost() {}
196 // Implementation of MouseWatcherHost.
197 virtual bool Contains(const gfx::Point
& screen_point
,
198 views::MouseWatcherHost::MouseEventType type
) OVERRIDE
;
200 MaximizeBubbleController::Bubble
* bubble_
;
202 DISALLOW_COPY_AND_ASSIGN(BubbleMouseWatcherHost
);
205 // The class which creates and manages the bubble menu element.
206 // It creates a 'bubble border' and the content accordingly.
207 // Note: Since the SnapSizer will show animations on top of the maximize button
208 // this menu gets created as a separate window and the SnapSizer will be
209 // created underneath this window.
210 class MaximizeBubbleController::Bubble
: public views::BubbleDelegateView
,
211 public views::MouseWatcherListener
{
213 explicit Bubble(MaximizeBubbleController
* owner
, int appearance_delay_ms_
);
216 // The window of the menu under which the SnapSizer will get created.
217 aura::Window
* GetBubbleWindow();
219 // Overridden from views::BubbleDelegateView.
220 virtual gfx::Rect
GetAnchorRect() OVERRIDE
;
221 virtual void AnimationProgressed(const ui::Animation
* animation
) OVERRIDE
;
222 virtual bool CanActivate() const OVERRIDE
{ return false; }
224 // Overridden from views::WidgetDelegateView.
225 virtual bool WidgetHasHitTestMask() const OVERRIDE
;
226 virtual void GetWidgetHitTestMask(gfx::Path
* mask
) const OVERRIDE
;
228 // Implementation of MouseWatcherListener.
229 virtual void MouseMovedOutOfHost() OVERRIDE
;
231 // Implementation of MouseWatcherHost.
232 virtual bool Contains(const gfx::Point
& screen_point
,
233 views::MouseWatcherHost::MouseEventType type
);
235 // Overridden from views::View.
236 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
238 // Overridden from views::Widget::Observer.
239 virtual void OnWidgetDestroying(views::Widget
* widget
) OVERRIDE
;
241 // Called from the controller class to indicate that the menu should get
243 virtual void ControllerRequestsCloseAndDelete();
245 // Called from the owning class to change the menu content to the given
246 // |snap_type| so that the user knows what is selected.
247 void SetSnapType(SnapType snap_type
);
249 // Get the owning MaximizeBubbleController. This might return NULL in case
250 // of an asynchronous shutdown.
251 MaximizeBubbleController
* controller() const { return owner_
; }
253 // Added for unit test: Retrieve the button for an action.
254 // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
255 views::CustomButton
* GetButtonForUnitTest(SnapType state
);
258 // True if the shut down has been initiated.
262 MaximizeBubbleController
* owner_
;
264 // The widget which contains our menu and the bubble border.
265 views::Widget
* bubble_widget_
;
267 // The content accessor of the menu.
268 BubbleContentsView
* contents_view_
;
270 // The bubble border.
271 MaximizeBubbleBorder
* bubble_border_
;
273 // The rectangle before the animation starts.
274 gfx::Rect initial_position_
;
276 // The mouse watcher which takes care of out of window hover events.
277 scoped_ptr
<views::MouseWatcher
> mouse_watcher_
;
279 // The fade delay - if 0 it will show / hide immediately.
280 const int appearance_delay_ms_
;
282 DISALLOW_COPY_AND_ASSIGN(Bubble
);
285 // A class that creates all buttons and put them into a view.
286 class BubbleContentsButtonRow
: public views::View
,
287 public views::ButtonListener
{
289 explicit BubbleContentsButtonRow(MaximizeBubbleController::Bubble
* bubble
);
291 virtual ~BubbleContentsButtonRow() {}
293 // Overridden from ButtonListener.
294 virtual void ButtonPressed(views::Button
* sender
,
295 const ui::Event
& event
) OVERRIDE
;
296 // Called from BubbleDialogButton.
297 void ButtonHovered(BubbleDialogButton
* sender
);
299 // Added for unit test: Retrieve the button for an action.
300 // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
301 views::CustomButton
* GetButtonForUnitTest(SnapType state
);
303 MaximizeBubbleController::Bubble
* bubble() { return bubble_
; }
306 // Functions to add the left and right maximize / restore buttons.
307 void AddMaximizeLeftButton();
308 void AddMaximizeRightButton();
309 void AddMinimizeButton();
311 // The owning object which gets notifications.
312 MaximizeBubbleController::Bubble
* bubble_
;
314 // The created buttons for our menu.
315 BubbleDialogButton
* left_button_
;
316 BubbleDialogButton
* minimize_button_
;
317 BubbleDialogButton
* right_button_
;
319 DISALLOW_COPY_AND_ASSIGN(BubbleContentsButtonRow
);
322 // A class which creates the content of the bubble: The buttons, and the label.
323 class BubbleContentsView
: public views::View
{
325 explicit BubbleContentsView(MaximizeBubbleController::Bubble
* bubble
);
327 virtual ~BubbleContentsView() {}
329 // Set the label content to reflect the currently selected |snap_type|.
330 // This function can be executed through the frame maximize button as well as
331 // through hover operations.
332 void SetSnapType(SnapType snap_type
);
334 // Added for unit test: Retrieve the button for an action.
335 // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
336 views::CustomButton
* GetButtonForUnitTest(SnapType state
) {
337 return buttons_view_
->GetButtonForUnitTest(state
);
342 MaximizeBubbleController::Bubble
* bubble_
;
344 // The object which owns all the buttons.
345 BubbleContentsButtonRow
* buttons_view_
;
347 // The label object which shows the user the selected action.
348 views::Label
* label_view_
;
350 DISALLOW_COPY_AND_ASSIGN(BubbleContentsView
);
353 // The image button gets overridden to be able to capture mouse hover events.
354 // The constructor also assigns all button states and
355 class BubbleDialogButton
: public views::ImageButton
{
357 explicit BubbleDialogButton(
358 BubbleContentsButtonRow
* button_row_listener
,
362 virtual ~BubbleDialogButton() {}
364 // CustomButton overrides:
365 virtual void OnMouseCaptureLost() OVERRIDE
;
366 virtual void OnMouseEntered(const ui::MouseEvent
& event
) OVERRIDE
;
367 virtual void OnMouseExited(const ui::MouseEvent
& event
) OVERRIDE
;
368 virtual bool OnMouseDragged(const ui::MouseEvent
& event
) OVERRIDE
;
371 // The creating class which needs to get notified in case of a hover event.
372 BubbleContentsButtonRow
* button_row_
;
374 DISALLOW_COPY_AND_ASSIGN(BubbleDialogButton
);
377 MaximizeBubbleController::Bubble::Bubble(
378 MaximizeBubbleController
* owner
,
379 int appearance_delay_ms
)
380 : views::BubbleDelegateView(owner
->frame_maximize_button(),
381 views::BubbleBorder::TOP_RIGHT
),
382 shutting_down_(false),
384 bubble_widget_(NULL
),
385 contents_view_(NULL
),
386 bubble_border_(NULL
),
387 appearance_delay_ms_(appearance_delay_ms
) {
388 set_margins(gfx::Insets());
390 // The window needs to be owned by the root so that the SnapSizer does not
391 // cover it upon animation.
392 aura::Window
* parent
= Shell::GetContainer(
393 Shell::GetActiveRootWindow(),
394 internal::kShellWindowId_ShelfContainer
);
395 set_parent_window(parent
);
397 set_notify_enter_exit_on_child(true);
398 set_adjust_if_offscreen(false);
399 SetPaintToLayer(true);
400 set_color(kBubbleBackgroundColor
);
401 set_close_on_deactivate(false);
403 views::Background::CreateSolidBackground(kBubbleBackgroundColor
));
405 SetLayoutManager(new views::BoxLayout(
406 views::BoxLayout::kVertical
, 0, 0, kLayoutSpacing
));
408 contents_view_
= new BubbleContentsView(this);
409 AddChildView(contents_view_
);
411 // Note that the returned widget has an observer which points to our
413 bubble_widget_
= views::BubbleDelegateView::CreateBubble(this);
414 bubble_widget_
->set_focus_on_creation(false);
416 SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE
);
417 bubble_widget_
->non_client_view()->frame_view()->set_background(NULL
);
419 bubble_border_
= new MaximizeBubbleBorder(this, anchor_view());
420 GetBubbleFrameView()->SetBubbleBorder(bubble_border_
);
421 GetBubbleFrameView()->set_background(NULL
);
423 // Recalculate size with new border.
426 if (!appearance_delay_ms_
)
431 ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(
432 ash::UMA_MAXIMIZE_BUTTON_SHOW_BUBBLE
);
434 mouse_watcher_
.reset(new views::MouseWatcher(
435 new BubbleMouseWatcherHost(this),
437 mouse_watcher_
->Start();
440 bool BubbleMouseWatcherHost::Contains(
441 const gfx::Point
& screen_point
,
442 views::MouseWatcherHost::MouseEventType type
) {
443 return bubble_
->Contains(screen_point
, type
);
446 aura::Window
* MaximizeBubbleController::Bubble::GetBubbleWindow() {
447 return bubble_widget_
? bubble_widget_
->GetNativeWindow() : NULL
;
450 gfx::Rect
MaximizeBubbleController::Bubble::GetAnchorRect() {
454 gfx::Rect anchor_rect
=
455 owner_
->frame_maximize_button()->GetBoundsInScreen();
459 void MaximizeBubbleController::Bubble::AnimationProgressed(
460 const ui::Animation
* animation
) {
461 // First do everything needed for the fade by calling the base function.
462 BubbleDelegateView::AnimationProgressed(animation
);
463 // When fading in we are done.
466 // Upon fade out an additional shift is required.
467 int shift
= animation
->CurrentValueBetween(kBubbleAnimationOffsetY
, 0);
468 gfx::Rect rect
= initial_position_
;
470 rect
.set_y(rect
.y() + shift
);
471 bubble_widget_
->GetNativeWindow()->SetBounds(rect
);
474 bool MaximizeBubbleController::Bubble::WidgetHasHitTestMask() const {
475 return bubble_border_
!= NULL
;
478 void MaximizeBubbleController::Bubble::GetWidgetHitTestMask(
479 gfx::Path
* mask
) const {
481 DCHECK(bubble_border_
);
482 bubble_border_
->GetMask(mask
);
485 void MaximizeBubbleController::Bubble::MouseMovedOutOfHost() {
486 if (!owner_
|| shutting_down_
)
488 // When we leave the bubble, we might be still be in gesture mode or over
489 // the maximize button. So only close if none of the other cases apply.
490 if (!owner_
->frame_maximize_button()->is_snap_enabled()) {
491 gfx::Point screen_location
= Shell::GetScreen()->GetCursorScreenPoint();
492 if (!owner_
->frame_maximize_button()->GetBoundsInScreen().Contains(
494 owner_
->RequestDestructionThroughOwner();
499 bool MaximizeBubbleController::Bubble::Contains(
500 const gfx::Point
& screen_point
,
501 views::MouseWatcherHost::MouseEventType type
) {
502 if (!owner_
|| shutting_down_
)
505 owner_
->frame_maximize_button()->GetBoundsInScreen().Contains(
507 if (!owner_
->frame_maximize_button()->is_snap_enabled() && inside_button
) {
508 SetSnapType(controller()->maximize_type() == FRAME_STATE_FULL
?
509 SNAP_RESTORE
: SNAP_MAXIMIZE
);
512 // Check if either a gesture is taking place (=> bubble stays no matter what
513 // the mouse does) or the mouse is over the maximize button or the bubble
515 return (owner_
->frame_maximize_button()->is_snap_enabled() ||
517 contents_view_
->GetBoundsInScreen().Contains(screen_point
));
520 gfx::Size
MaximizeBubbleController::Bubble::GetPreferredSize() {
521 return contents_view_
->GetPreferredSize();
524 void MaximizeBubbleController::Bubble::OnWidgetDestroying(
525 views::Widget
* widget
) {
526 if (bubble_widget_
== widget
) {
527 mouse_watcher_
->Stop();
530 // If the bubble destruction was triggered by some other external
531 // influence then ourselves, the owner needs to be informed that the menu
533 shutting_down_
= true;
534 owner_
->RequestDestructionThroughOwner();
538 BubbleDelegateView::OnWidgetDestroying(widget
);
541 void MaximizeBubbleController::Bubble::ControllerRequestsCloseAndDelete() {
542 // This only gets called from the owning base class once it is deleted.
545 shutting_down_
= true;
548 // Close the widget asynchronously after the hide animation is finished.
549 initial_position_
= bubble_widget_
->GetNativeWindow()->bounds();
550 if (!appearance_delay_ms_
)
551 bubble_widget_
->CloseNow();
556 void MaximizeBubbleController::Bubble::SetSnapType(SnapType snap_type
) {
558 contents_view_
->SetSnapType(snap_type
);
561 views::CustomButton
* MaximizeBubbleController::Bubble::GetButtonForUnitTest(
563 return contents_view_
->GetButtonForUnitTest(state
);
566 BubbleContentsButtonRow::BubbleContentsButtonRow(
567 MaximizeBubbleController::Bubble
* bubble
)
570 minimize_button_(NULL
),
571 right_button_(NULL
) {
572 SetLayoutManager(new views::BoxLayout(
573 views::BoxLayout::kHorizontal
, 0, 0, kLayoutSpacing
));
575 views::Background::CreateSolidBackground(kBubbleBackgroundColor
));
577 if (base::i18n::IsRTL()) {
578 AddMaximizeRightButton();
580 AddMaximizeLeftButton();
582 AddMaximizeLeftButton();
584 AddMaximizeRightButton();
588 // Overridden from ButtonListener.
589 void BubbleContentsButtonRow::ButtonPressed(views::Button
* sender
,
590 const ui::Event
& event
) {
591 // While shutting down, the connection to the owner might already be broken.
592 if (!bubble_
->controller())
594 if (sender
== left_button_
)
595 bubble_
->controller()->OnButtonClicked(
596 bubble_
->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT
?
597 SNAP_RESTORE
: SNAP_LEFT
);
598 else if (sender
== minimize_button_
)
599 bubble_
->controller()->OnButtonClicked(SNAP_MINIMIZE
);
600 else if (sender
== right_button_
)
601 bubble_
->controller()->OnButtonClicked(
602 bubble_
->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT
?
603 SNAP_RESTORE
: SNAP_RIGHT
);
605 NOTREACHED() << "Unknown button pressed.";
608 // Called from BubbleDialogButton.
609 void BubbleContentsButtonRow::ButtonHovered(BubbleDialogButton
* sender
) {
610 // While shutting down, the connection to the owner might already be broken.
611 if (!bubble_
->controller())
613 if (sender
== left_button_
)
614 bubble_
->controller()->OnButtonHover(
615 bubble_
->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT
?
616 SNAP_RESTORE
: SNAP_LEFT
);
617 else if (sender
== minimize_button_
)
618 bubble_
->controller()->OnButtonHover(SNAP_MINIMIZE
);
619 else if (sender
== right_button_
)
620 bubble_
->controller()->OnButtonHover(
621 bubble_
->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT
?
622 SNAP_RESTORE
: SNAP_RIGHT
);
624 bubble_
->controller()->OnButtonHover(SNAP_NONE
);
627 views::CustomButton
* BubbleContentsButtonRow::GetButtonForUnitTest(
633 return minimize_button_
;
635 return right_button_
;
642 void BubbleContentsButtonRow::AddMaximizeLeftButton() {
643 if (bubble_
->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT
) {
644 left_button_
= new BubbleDialogButton(
646 IDR_AURA_WINDOW_POSITION_LEFT_RESTORE
,
647 IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_H
,
648 IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_P
);
650 left_button_
= new BubbleDialogButton(
652 IDR_AURA_WINDOW_POSITION_LEFT
,
653 IDR_AURA_WINDOW_POSITION_LEFT_H
,
654 IDR_AURA_WINDOW_POSITION_LEFT_P
);
658 void BubbleContentsButtonRow::AddMaximizeRightButton() {
659 if (bubble_
->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT
) {
660 right_button_
= new BubbleDialogButton(
662 IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE
,
663 IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_H
,
664 IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_P
);
666 right_button_
= new BubbleDialogButton(
668 IDR_AURA_WINDOW_POSITION_RIGHT
,
669 IDR_AURA_WINDOW_POSITION_RIGHT_H
,
670 IDR_AURA_WINDOW_POSITION_RIGHT_P
);
674 void BubbleContentsButtonRow::AddMinimizeButton() {
675 minimize_button_
= new BubbleDialogButton(
677 IDR_AURA_WINDOW_POSITION_MIDDLE
,
678 IDR_AURA_WINDOW_POSITION_MIDDLE_H
,
679 IDR_AURA_WINDOW_POSITION_MIDDLE_P
);
682 BubbleContentsView::BubbleContentsView(
683 MaximizeBubbleController::Bubble
* bubble
)
687 SetLayoutManager(new views::BoxLayout(
688 views::BoxLayout::kVertical
, 0, 0, kLayoutSpacing
));
690 views::Background::CreateSolidBackground(kBubbleBackgroundColor
));
692 buttons_view_
= new BubbleContentsButtonRow(bubble
);
693 AddChildView(buttons_view_
);
695 label_view_
= new views::Label();
696 SetSnapType(SNAP_NONE
);
697 label_view_
->SetBackgroundColor(kBubbleBackgroundColor
);
698 label_view_
->SetEnabledColor(kBubbleTextColor
);
699 label_view_
->set_border(views::Border::CreateEmptyBorder(
700 kLabelSpacing
, 0, kLabelSpacing
, 0));
701 AddChildView(label_view_
);
704 // Set the label content to reflect the currently selected |snap_type|.
705 // This function can be executed through the frame maximize button as well as
706 // through hover operations.
707 void BubbleContentsView::SetSnapType(SnapType snap_type
) {
708 if (!bubble_
->controller())
711 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
715 id
= IDS_ASH_SNAP_WINDOW_LEFT
;
718 id
= IDS_ASH_SNAP_WINDOW_RIGHT
;
721 DCHECK_NE(FRAME_STATE_FULL
, bubble_
->controller()->maximize_type());
722 id
= IDS_ASH_MAXIMIZE_WINDOW
;
725 id
= IDS_ASH_MINIMIZE_WINDOW
;
728 DCHECK_NE(FRAME_STATE_NONE
, bubble_
->controller()->maximize_type());
729 id
= IDS_ASH_RESTORE_WINDOW
;
732 // If nothing is selected, we automatically select the click operation.
733 id
= bubble_
->controller()->maximize_type() == FRAME_STATE_FULL
?
734 IDS_ASH_RESTORE_WINDOW
: IDS_ASH_MAXIMIZE_WINDOW
;
737 label_view_
->SetText(rb
.GetLocalizedString(id
));
740 MaximizeBubbleController::MaximizeBubbleController(
741 FrameMaximizeButton
* frame_maximize_button
,
742 MaximizeBubbleFrameState maximize_type
,
743 int appearance_delay_ms
)
744 : frame_maximize_button_(frame_maximize_button
),
746 maximize_type_(maximize_type
),
747 appearance_delay_ms_(appearance_delay_ms
) {
748 // Create the task which will create the bubble delayed.
749 base::OneShotTimer
<MaximizeBubbleController
>* new_timer
=
750 new base::OneShotTimer
<MaximizeBubbleController
>();
751 // Note: Even if there was no delay time given, we need to have a timer.
754 base::TimeDelta::FromMilliseconds(
755 appearance_delay_ms_
? appearance_delay_ms_
: 10),
757 &MaximizeBubbleController::CreateBubble
);
758 timer_
.reset(new_timer
);
759 if (!appearance_delay_ms_
)
763 MaximizeBubbleController::~MaximizeBubbleController() {
764 // Note: The destructor only gets initiated through the owner.
767 bubble_
->ControllerRequestsCloseAndDelete();
772 void MaximizeBubbleController::SetSnapType(SnapType snap_type
) {
774 bubble_
->SetSnapType(snap_type
);
777 aura::Window
* MaximizeBubbleController::GetBubbleWindow() {
778 return bubble_
? bubble_
->GetBubbleWindow() : NULL
;
781 void MaximizeBubbleController::DelayCreation() {
782 if (timer_
.get() && timer_
->IsRunning())
786 void MaximizeBubbleController::OnButtonClicked(SnapType snap_type
) {
787 frame_maximize_button_
->ExecuteSnapAndCloseMenu(snap_type
);
790 void MaximizeBubbleController::OnButtonHover(SnapType snap_type
) {
791 frame_maximize_button_
->SnapButtonHovered(snap_type
);
794 views::CustomButton
* MaximizeBubbleController::GetButtonForUnitTest(
796 return bubble_
? bubble_
->GetButtonForUnitTest(state
) : NULL
;
799 void MaximizeBubbleController::RequestDestructionThroughOwner() {
800 // Tell the parent to destroy us (if this didn't happen yet).
803 // Informs the owner that the menu is gone and requests |this| destruction.
804 frame_maximize_button_
->DestroyMaximizeMenu();
805 // Note: After this call |this| is destroyed.
809 void MaximizeBubbleController::CreateBubble() {
811 bubble_
= new Bubble(this, appearance_delay_ms_
);
816 BubbleDialogButton::BubbleDialogButton(
817 BubbleContentsButtonRow
* button_row
,
821 : views::ImageButton(button_row
),
822 button_row_(button_row
) {
823 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
824 SetImage(views::CustomButton::STATE_NORMAL
,
825 rb
.GetImageSkiaNamed(normal_image
));
826 SetImage(views::CustomButton::STATE_HOVERED
,
827 rb
.GetImageSkiaNamed(hovered_image
));
828 SetImage(views::CustomButton::STATE_PRESSED
,
829 rb
.GetImageSkiaNamed(pressed_image
));
830 button_row
->AddChildView(this);
833 void BubbleDialogButton::OnMouseCaptureLost() {
834 button_row_
->ButtonHovered(NULL
);
835 views::ImageButton::OnMouseCaptureLost();
838 void BubbleDialogButton::OnMouseEntered(const ui::MouseEvent
& event
) {
839 button_row_
->ButtonHovered(this);
840 views::ImageButton::OnMouseEntered(event
);
843 void BubbleDialogButton::OnMouseExited(const ui::MouseEvent
& event
) {
844 button_row_
->ButtonHovered(NULL
);
845 views::ImageButton::OnMouseExited(event
);
848 bool BubbleDialogButton::OnMouseDragged(const ui::MouseEvent
& event
) {
849 if (!button_row_
->bubble()->controller())
852 // Remove the phantom window when we leave the button.
853 gfx::Point
screen_location(event
.location());
854 View::ConvertPointToScreen(this, &screen_location
);
855 if (!GetBoundsInScreen().Contains(screen_location
))
856 button_row_
->ButtonHovered(NULL
);
858 button_row_
->ButtonHovered(this);
860 // Pass the event on to the normal handler.
861 return views::ImageButton::OnMouseDragged(event
);