1 // Copyright 2013 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/caption_buttons/maximize_bubble_controller.h"
8 #include "ash/shell_delegate.h"
9 #include "ash/shell_window_ids.h"
10 #include "ash/wm/caption_buttons/frame_maximize_button.h"
11 #include "ash/wm/window_animations.h"
12 #include "base/timer/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/l10n/l10n_util.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/gfx/animation/animation.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 Bubble(MaximizeBubbleController
* owner
,
214 int appearance_delay_ms
,
215 SnapType initial_snap_type
);
218 // The window of the menu under which the SnapSizer will get created.
219 aura::Window
* GetBubbleWindow();
221 // Overridden from views::BubbleDelegateView.
222 virtual gfx::Rect
GetAnchorRect() OVERRIDE
;
223 virtual void AnimationProgressed(const gfx::Animation
* animation
) OVERRIDE
;
224 virtual bool CanActivate() const OVERRIDE
{ return false; }
226 // Overridden from views::WidgetDelegateView.
227 virtual bool WidgetHasHitTestMask() const OVERRIDE
;
228 virtual void GetWidgetHitTestMask(gfx::Path
* mask
) const OVERRIDE
;
230 // Implementation of MouseWatcherListener.
231 virtual void MouseMovedOutOfHost() OVERRIDE
;
233 // Implementation of MouseWatcherHost.
234 virtual bool Contains(const gfx::Point
& screen_point
,
235 views::MouseWatcherHost::MouseEventType type
);
237 // Overridden from views::View.
238 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
240 // Overridden from views::Widget::Observer.
241 virtual void OnWidgetDestroying(views::Widget
* widget
) OVERRIDE
;
243 // Called from the controller class to indicate that the menu should get
245 virtual void ControllerRequestsCloseAndDelete();
247 // Called from the owning class to change the menu content to the given
248 // |snap_type| so that the user knows what is selected.
249 void SetSnapType(SnapType snap_type
);
251 // Get the owning MaximizeBubbleController. This might return NULL in case
252 // of an asynchronous shutdown.
253 MaximizeBubbleController
* controller() const { return owner_
; }
255 // Added for unit test: Retrieve the button for an action.
256 // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
257 views::CustomButton
* GetButtonForUnitTest(SnapType state
);
260 // True if the shut down has been initiated.
264 MaximizeBubbleController
* owner_
;
266 // The widget which contains our menu and the bubble border.
267 views::Widget
* bubble_widget_
;
269 // The content accessor of the menu.
270 BubbleContentsView
* contents_view_
;
272 // The bubble border.
273 MaximizeBubbleBorder
* bubble_border_
;
275 // The rectangle before the animation starts.
276 gfx::Rect initial_position_
;
278 // The mouse watcher which takes care of out of window hover events.
279 scoped_ptr
<views::MouseWatcher
> mouse_watcher_
;
281 // The fade delay - if 0 it will show / hide immediately.
282 const int appearance_delay_ms_
;
284 DISALLOW_COPY_AND_ASSIGN(Bubble
);
287 // A class that creates all buttons and put them into a view.
288 class BubbleContentsButtonRow
: public views::View
,
289 public views::ButtonListener
{
291 explicit BubbleContentsButtonRow(MaximizeBubbleController::Bubble
* bubble
);
293 virtual ~BubbleContentsButtonRow() {}
295 // Overridden from ButtonListener.
296 virtual void ButtonPressed(views::Button
* sender
,
297 const ui::Event
& event
) OVERRIDE
;
298 // Called from BubbleDialogButton.
299 void ButtonHovered(BubbleDialogButton
* sender
);
301 // Added for unit test: Retrieve the button for an action.
302 // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
303 views::CustomButton
* GetButtonForUnitTest(SnapType state
);
305 MaximizeBubbleController::Bubble
* bubble() { return bubble_
; }
308 // Functions to add the left and right maximize / restore buttons.
309 void AddMaximizeLeftButton();
310 void AddMaximizeRightButton();
311 void AddMinimizeButton();
313 // The owning object which gets notifications.
314 MaximizeBubbleController::Bubble
* bubble_
;
316 // The created buttons for our menu.
317 BubbleDialogButton
* left_button_
;
318 BubbleDialogButton
* minimize_button_
;
319 BubbleDialogButton
* right_button_
;
321 DISALLOW_COPY_AND_ASSIGN(BubbleContentsButtonRow
);
324 // A class which creates the content of the bubble: The buttons, and the label.
325 class BubbleContentsView
: public views::View
{
327 BubbleContentsView(MaximizeBubbleController::Bubble
* bubble
,
328 SnapType initial_snap_type
);
330 virtual ~BubbleContentsView() {}
332 // Set the label content to reflect the currently selected |snap_type|.
333 // This function can be executed through the frame maximize button as well as
334 // through hover operations.
335 void SetSnapType(SnapType snap_type
);
337 // Added for unit test: Retrieve the button for an action.
338 // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
339 views::CustomButton
* GetButtonForUnitTest(SnapType state
) {
340 return buttons_view_
->GetButtonForUnitTest(state
);
345 MaximizeBubbleController::Bubble
* bubble_
;
347 // The object which owns all the buttons.
348 BubbleContentsButtonRow
* buttons_view_
;
350 // The label object which shows the user the selected action.
351 views::Label
* label_view_
;
353 DISALLOW_COPY_AND_ASSIGN(BubbleContentsView
);
356 // The image button gets overridden to be able to capture mouse hover events.
357 // The constructor also assigns all button states and
358 class BubbleDialogButton
: public views::ImageButton
{
360 explicit BubbleDialogButton(
361 BubbleContentsButtonRow
* button_row_listener
,
365 virtual ~BubbleDialogButton() {}
367 // CustomButton overrides:
368 virtual void OnMouseCaptureLost() OVERRIDE
;
369 virtual void OnMouseEntered(const ui::MouseEvent
& event
) OVERRIDE
;
370 virtual void OnMouseExited(const ui::MouseEvent
& event
) OVERRIDE
;
371 virtual bool OnMouseDragged(const ui::MouseEvent
& event
) OVERRIDE
;
374 // The creating class which needs to get notified in case of a hover event.
375 BubbleContentsButtonRow
* button_row_
;
377 DISALLOW_COPY_AND_ASSIGN(BubbleDialogButton
);
380 MaximizeBubbleController::Bubble::Bubble(
381 MaximizeBubbleController
* owner
,
382 int appearance_delay_ms
,
383 SnapType initial_snap_type
)
384 : views::BubbleDelegateView(owner
->frame_maximize_button(),
385 views::BubbleBorder::TOP_RIGHT
),
386 shutting_down_(false),
388 bubble_widget_(NULL
),
389 contents_view_(NULL
),
390 bubble_border_(NULL
),
391 appearance_delay_ms_(appearance_delay_ms
) {
392 set_margins(gfx::Insets());
394 // The window needs to be owned by the root so that the SnapSizer does not
395 // cover it upon animation.
396 aura::Window
* parent
= Shell::GetContainer(
397 Shell::GetTargetRootWindow(),
398 internal::kShellWindowId_ShelfContainer
);
399 set_parent_window(parent
);
401 set_notify_enter_exit_on_child(true);
402 set_adjust_if_offscreen(false);
403 SetPaintToLayer(true);
404 set_color(kBubbleBackgroundColor
);
405 set_close_on_deactivate(false);
407 views::Background::CreateSolidBackground(kBubbleBackgroundColor
));
409 SetLayoutManager(new views::BoxLayout(
410 views::BoxLayout::kVertical
, 0, 0, kLayoutSpacing
));
412 contents_view_
= new BubbleContentsView(this, initial_snap_type
);
413 AddChildView(contents_view_
);
415 // Note that the returned widget has an observer which points to our
417 bubble_widget_
= views::BubbleDelegateView::CreateBubble(this);
418 bubble_widget_
->set_focus_on_creation(false);
420 SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE
);
421 bubble_widget_
->non_client_view()->frame_view()->set_background(NULL
);
423 bubble_border_
= new MaximizeBubbleBorder(this, GetAnchorView());
424 GetBubbleFrameView()->SetBubbleBorder(bubble_border_
);
425 GetBubbleFrameView()->set_background(NULL
);
427 // Recalculate size with new border.
430 if (!appearance_delay_ms_
)
435 ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(
436 ash::UMA_WINDOW_MAXIMIZE_BUTTON_SHOW_BUBBLE
);
438 mouse_watcher_
.reset(new views::MouseWatcher(
439 new BubbleMouseWatcherHost(this),
441 mouse_watcher_
->Start();
444 bool BubbleMouseWatcherHost::Contains(
445 const gfx::Point
& screen_point
,
446 views::MouseWatcherHost::MouseEventType type
) {
447 return bubble_
->Contains(screen_point
, type
);
450 aura::Window
* MaximizeBubbleController::Bubble::GetBubbleWindow() {
451 return bubble_widget_
? bubble_widget_
->GetNativeWindow() : NULL
;
454 gfx::Rect
MaximizeBubbleController::Bubble::GetAnchorRect() {
458 gfx::Rect anchor_rect
=
459 owner_
->frame_maximize_button()->GetBoundsInScreen();
463 void MaximizeBubbleController::Bubble::AnimationProgressed(
464 const gfx::Animation
* animation
) {
465 // First do everything needed for the fade by calling the base function.
466 BubbleDelegateView::AnimationProgressed(animation
);
467 // When fading in we are done.
470 // Upon fade out an additional shift is required.
471 int shift
= animation
->CurrentValueBetween(kBubbleAnimationOffsetY
, 0);
472 gfx::Rect rect
= initial_position_
;
474 rect
.set_y(rect
.y() + shift
);
475 bubble_widget_
->GetNativeWindow()->SetBounds(rect
);
478 bool MaximizeBubbleController::Bubble::WidgetHasHitTestMask() const {
479 return bubble_border_
!= NULL
;
482 void MaximizeBubbleController::Bubble::GetWidgetHitTestMask(
483 gfx::Path
* mask
) const {
485 DCHECK(bubble_border_
);
486 bubble_border_
->GetMask(mask
);
489 void MaximizeBubbleController::Bubble::MouseMovedOutOfHost() {
490 if (!owner_
|| shutting_down_
)
492 // When we leave the bubble, we might be still be in gesture mode or over
493 // the maximize button. So only close if none of the other cases apply.
494 if (!owner_
->frame_maximize_button()->is_snap_enabled()) {
495 gfx::Point screen_location
= Shell::GetScreen()->GetCursorScreenPoint();
496 if (!owner_
->frame_maximize_button()->GetBoundsInScreen().Contains(
498 owner_
->RequestDestructionThroughOwner();
503 bool MaximizeBubbleController::Bubble::Contains(
504 const gfx::Point
& screen_point
,
505 views::MouseWatcherHost::MouseEventType type
) {
506 if (!owner_
|| shutting_down_
)
509 owner_
->frame_maximize_button()->GetBoundsInScreen().Contains(
511 if (!owner_
->frame_maximize_button()->is_snap_enabled() && inside_button
) {
512 SetSnapType(controller()->maximize_type() == FRAME_STATE_FULL
?
513 SNAP_RESTORE
: SNAP_MAXIMIZE
);
516 // Check if either a gesture is taking place (=> bubble stays no matter what
517 // the mouse does) or the mouse is over the maximize button or the bubble
519 return (owner_
->frame_maximize_button()->is_snap_enabled() ||
521 contents_view_
->GetBoundsInScreen().Contains(screen_point
));
524 gfx::Size
MaximizeBubbleController::Bubble::GetPreferredSize() {
525 return contents_view_
->GetPreferredSize();
528 void MaximizeBubbleController::Bubble::OnWidgetDestroying(
529 views::Widget
* widget
) {
530 if (bubble_widget_
== widget
) {
531 mouse_watcher_
->Stop();
534 // If the bubble destruction was triggered by some other external
535 // influence then ourselves, the owner needs to be informed that the menu
537 shutting_down_
= true;
538 owner_
->RequestDestructionThroughOwner();
542 BubbleDelegateView::OnWidgetDestroying(widget
);
545 void MaximizeBubbleController::Bubble::ControllerRequestsCloseAndDelete() {
546 // This only gets called from the owning base class once it is deleted.
549 shutting_down_
= true;
552 // Close the widget asynchronously after the hide animation is finished.
553 initial_position_
= bubble_widget_
->GetNativeWindow()->bounds();
554 if (!appearance_delay_ms_
)
555 bubble_widget_
->CloseNow();
560 void MaximizeBubbleController::Bubble::SetSnapType(SnapType snap_type
) {
562 contents_view_
->SetSnapType(snap_type
);
565 views::CustomButton
* MaximizeBubbleController::Bubble::GetButtonForUnitTest(
567 return contents_view_
->GetButtonForUnitTest(state
);
570 BubbleContentsButtonRow::BubbleContentsButtonRow(
571 MaximizeBubbleController::Bubble
* bubble
)
574 minimize_button_(NULL
),
575 right_button_(NULL
) {
576 SetLayoutManager(new views::BoxLayout(
577 views::BoxLayout::kHorizontal
, 0, 0, kLayoutSpacing
));
579 views::Background::CreateSolidBackground(kBubbleBackgroundColor
));
581 if (base::i18n::IsRTL()) {
582 AddMaximizeRightButton();
584 AddMaximizeLeftButton();
586 AddMaximizeLeftButton();
588 AddMaximizeRightButton();
592 // Overridden from ButtonListener.
593 void BubbleContentsButtonRow::ButtonPressed(views::Button
* sender
,
594 const ui::Event
& event
) {
595 // While shutting down, the connection to the owner might already be broken.
596 if (!bubble_
->controller())
598 if (sender
== left_button_
)
599 bubble_
->controller()->OnButtonClicked(
600 bubble_
->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT
?
601 SNAP_RESTORE
: SNAP_LEFT
);
602 else if (sender
== minimize_button_
)
603 bubble_
->controller()->OnButtonClicked(SNAP_MINIMIZE
);
604 else if (sender
== right_button_
)
605 bubble_
->controller()->OnButtonClicked(
606 bubble_
->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT
?
607 SNAP_RESTORE
: SNAP_RIGHT
);
609 NOTREACHED() << "Unknown button pressed.";
612 // Called from BubbleDialogButton.
613 void BubbleContentsButtonRow::ButtonHovered(BubbleDialogButton
* sender
) {
614 // While shutting down, the connection to the owner might already be broken.
615 if (!bubble_
->controller())
617 if (sender
== left_button_
)
618 bubble_
->controller()->OnButtonHover(
619 bubble_
->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT
?
620 SNAP_RESTORE
: SNAP_LEFT
);
621 else if (sender
== minimize_button_
)
622 bubble_
->controller()->OnButtonHover(SNAP_MINIMIZE
);
623 else if (sender
== right_button_
)
624 bubble_
->controller()->OnButtonHover(
625 bubble_
->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT
?
626 SNAP_RESTORE
: SNAP_RIGHT
);
628 bubble_
->controller()->OnButtonHover(SNAP_NONE
);
631 views::CustomButton
* BubbleContentsButtonRow::GetButtonForUnitTest(
637 return minimize_button_
;
639 return right_button_
;
646 void BubbleContentsButtonRow::AddMaximizeLeftButton() {
647 if (bubble_
->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT
) {
648 left_button_
= new BubbleDialogButton(
650 IDR_AURA_WINDOW_POSITION_LEFT_RESTORE
,
651 IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_H
,
652 IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_P
);
654 left_button_
= new BubbleDialogButton(
656 IDR_AURA_WINDOW_POSITION_LEFT
,
657 IDR_AURA_WINDOW_POSITION_LEFT_H
,
658 IDR_AURA_WINDOW_POSITION_LEFT_P
);
662 void BubbleContentsButtonRow::AddMaximizeRightButton() {
663 if (bubble_
->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT
) {
664 right_button_
= new BubbleDialogButton(
666 IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE
,
667 IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_H
,
668 IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_P
);
670 right_button_
= new BubbleDialogButton(
672 IDR_AURA_WINDOW_POSITION_RIGHT
,
673 IDR_AURA_WINDOW_POSITION_RIGHT_H
,
674 IDR_AURA_WINDOW_POSITION_RIGHT_P
);
678 void BubbleContentsButtonRow::AddMinimizeButton() {
679 minimize_button_
= new BubbleDialogButton(
681 IDR_AURA_WINDOW_POSITION_MIDDLE
,
682 IDR_AURA_WINDOW_POSITION_MIDDLE_H
,
683 IDR_AURA_WINDOW_POSITION_MIDDLE_P
);
686 BubbleContentsView::BubbleContentsView(
687 MaximizeBubbleController::Bubble
* bubble
,
688 SnapType initial_snap_type
)
692 SetLayoutManager(new views::BoxLayout(
693 views::BoxLayout::kVertical
, 0, 0, kLayoutSpacing
));
695 views::Background::CreateSolidBackground(kBubbleBackgroundColor
));
697 buttons_view_
= new BubbleContentsButtonRow(bubble
);
698 AddChildView(buttons_view_
);
700 label_view_
= new views::Label();
701 SetSnapType(initial_snap_type
);
702 label_view_
->SetBackgroundColor(kBubbleBackgroundColor
);
703 label_view_
->SetEnabledColor(kBubbleTextColor
);
704 label_view_
->set_border(views::Border::CreateEmptyBorder(
705 kLabelSpacing
, 0, kLabelSpacing
, 0));
706 AddChildView(label_view_
);
709 // Set the label content to reflect the currently selected |snap_type|.
710 // This function can be executed through the frame maximize button as well as
711 // through hover operations.
712 void BubbleContentsView::SetSnapType(SnapType snap_type
) {
713 if (!bubble_
->controller())
716 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
720 id
= IDS_ASH_SNAP_WINDOW_LEFT
;
723 id
= IDS_ASH_SNAP_WINDOW_RIGHT
;
726 DCHECK_NE(FRAME_STATE_FULL
, bubble_
->controller()->maximize_type());
727 id
= IDS_ASH_MAXIMIZE_WINDOW
;
730 id
= IDS_ASH_MINIMIZE_WINDOW
;
733 DCHECK_NE(FRAME_STATE_NONE
, bubble_
->controller()->maximize_type());
734 id
= IDS_ASH_RESTORE_WINDOW
;
737 // If nothing is selected, we automatically select the click operation.
738 id
= bubble_
->controller()->maximize_type() == FRAME_STATE_FULL
?
739 IDS_ASH_RESTORE_WINDOW
: IDS_ASH_MAXIMIZE_WINDOW
;
742 label_view_
->SetText(rb
.GetLocalizedString(id
));
745 MaximizeBubbleController::MaximizeBubbleController(
746 FrameMaximizeButton
* frame_maximize_button
,
747 MaximizeBubbleFrameState maximize_type
,
748 int appearance_delay_ms
)
749 : frame_maximize_button_(frame_maximize_button
),
751 maximize_type_(maximize_type
),
752 snap_type_for_creation_(SNAP_NONE
),
753 appearance_delay_ms_(appearance_delay_ms
) {
754 // Create the task which will create the bubble delayed.
755 base::OneShotTimer
<MaximizeBubbleController
>* new_timer
=
756 new base::OneShotTimer
<MaximizeBubbleController
>();
757 // Note: Even if there was no delay time given, we need to have a timer.
760 base::TimeDelta::FromMilliseconds(
761 appearance_delay_ms_
? appearance_delay_ms_
: 10),
763 &MaximizeBubbleController::CreateBubble
);
764 timer_
.reset(new_timer
);
765 if (!appearance_delay_ms_
)
769 MaximizeBubbleController::~MaximizeBubbleController() {
770 // Note: The destructor only gets initiated through the owner.
773 bubble_
->ControllerRequestsCloseAndDelete();
778 void MaximizeBubbleController::SetSnapType(SnapType snap_type
) {
780 bubble_
->SetSnapType(snap_type
);
782 // The bubble has not been created yet. This can occur if bubble creation is
784 snap_type_for_creation_
= snap_type
;
788 aura::Window
* MaximizeBubbleController::GetBubbleWindow() {
789 return bubble_
? bubble_
->GetBubbleWindow() : NULL
;
792 void MaximizeBubbleController::DelayCreation() {
793 if (timer_
.get() && timer_
->IsRunning())
797 void MaximizeBubbleController::OnButtonClicked(SnapType snap_type
) {
798 frame_maximize_button_
->ExecuteSnapAndCloseMenu(snap_type
);
801 void MaximizeBubbleController::OnButtonHover(SnapType snap_type
) {
802 frame_maximize_button_
->SnapButtonHovered(snap_type
);
805 views::CustomButton
* MaximizeBubbleController::GetButtonForUnitTest(
807 return bubble_
? bubble_
->GetButtonForUnitTest(state
) : NULL
;
810 void MaximizeBubbleController::RequestDestructionThroughOwner() {
811 // Tell the parent to destroy us (if this didn't happen yet).
814 // Informs the owner that the menu is gone and requests |this| destruction.
815 frame_maximize_button_
->DestroyMaximizeMenu();
816 // Note: After this call |this| is destroyed.
820 void MaximizeBubbleController::CreateBubble() {
822 bubble_
= new Bubble(this, appearance_delay_ms_
, snap_type_for_creation_
);
827 BubbleDialogButton::BubbleDialogButton(
828 BubbleContentsButtonRow
* button_row
,
832 : views::ImageButton(button_row
),
833 button_row_(button_row
) {
834 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
835 SetImage(views::CustomButton::STATE_NORMAL
,
836 rb
.GetImageSkiaNamed(normal_image
));
837 SetImage(views::CustomButton::STATE_HOVERED
,
838 rb
.GetImageSkiaNamed(hovered_image
));
839 SetImage(views::CustomButton::STATE_PRESSED
,
840 rb
.GetImageSkiaNamed(pressed_image
));
841 button_row
->AddChildView(this);
844 void BubbleDialogButton::OnMouseCaptureLost() {
845 button_row_
->ButtonHovered(NULL
);
846 views::ImageButton::OnMouseCaptureLost();
849 void BubbleDialogButton::OnMouseEntered(const ui::MouseEvent
& event
) {
850 button_row_
->ButtonHovered(this);
851 views::ImageButton::OnMouseEntered(event
);
854 void BubbleDialogButton::OnMouseExited(const ui::MouseEvent
& event
) {
855 button_row_
->ButtonHovered(NULL
);
856 views::ImageButton::OnMouseExited(event
);
859 bool BubbleDialogButton::OnMouseDragged(const ui::MouseEvent
& event
) {
860 if (!button_row_
->bubble()->controller())
863 // Remove the phantom window when we leave the button.
864 gfx::Point
screen_location(event
.location());
865 View::ConvertPointToScreen(this, &screen_location
);
866 if (!GetBoundsInScreen().Contains(screen_location
))
867 button_row_
->ButtonHovered(NULL
);
869 button_row_
->ButtonHovered(this);
871 // Pass the event on to the normal handler.
872 return views::ImageButton::OnMouseDragged(event
);