Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(883)

Side by Side Diff: ash/wm/window_maximize.cc

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

Powered by Google App Engine
This is Rietveld 408576698