1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "ash/wm/workspace/frame_maximize_button.h" 6 7 #include "ash/launcher/launcher.h" 8 #include "ash/screen_ash.h" 9 #include "ash/shelf/shelf_widget.h" 10 #include "ash/shell.h" 11 #include "ash/shell_delegate.h" 12 #include "ash/touch/touch_uma.h" 13 #include "ash/wm/maximize_bubble_controller.h" 14 #include "ash/wm/property_util.h" 15 #include "ash/wm/window_animations.h" 16 #include "ash/wm/window_properties.h" 17 #include "ash/wm/window_util.h" 18 #include "ash/wm/workspace/phantom_window_controller.h" 19 #include "ash/wm/workspace/snap_sizer.h" 20 #include "grit/ash_strings.h" 21 #include "ui/aura/window.h" 22 #include "ui/base/events/event.h" 23 #include "ui/base/events/event_handler.h" 24 #include "ui/base/l10n/l10n_util.h" 25 #include "ui/base/resource/resource_bundle.h" 26 #include "ui/gfx/image/image.h" 27 #include "ui/gfx/screen.h" 28 #include "ui/views/widget/widget.h" 29 #include "ui/views/window/non_client_view.h" 30 31 using ash::internal::SnapSizer; 32 33 namespace ash { 34 35 namespace { 36 37 // Delay before forcing an update of the snap location. 38 const int kUpdateDelayMS = 400; 39 40 // The delay of the bubble appearance. 41 const int kBubbleAppearanceDelayMS = 500; 42 43 // The minimum sanp size in percent of the screen width. 44 const int kMinSnapSizePercent = 50; 45 } 46 47 // EscapeEventFilter is installed on the RootWindow to track when the escape key 48 // is pressed. We use an EventFilter for this as the FrameMaximizeButton 49 // normally does not get focus. 50 class FrameMaximizeButton::EscapeEventFilter : public ui::EventHandler { 51 public: 52 explicit EscapeEventFilter(FrameMaximizeButton* button); 53 virtual ~EscapeEventFilter(); 54 55 // EventFilter overrides: 56 virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE; 57 58 private: 59 FrameMaximizeButton* button_; 60 61 DISALLOW_COPY_AND_ASSIGN(EscapeEventFilter); 62 }; 63 64 FrameMaximizeButton::EscapeEventFilter::EscapeEventFilter( 65 FrameMaximizeButton* button) 66 : button_(button) { 67 Shell::GetInstance()->AddPreTargetHandler(this); 68 } 69 70 FrameMaximizeButton::EscapeEventFilter::~EscapeEventFilter() { 71 Shell::GetInstance()->RemovePreTargetHandler(this); 72 } 73 74 void FrameMaximizeButton::EscapeEventFilter::OnKeyEvent( 75 ui::KeyEvent* event) { 76 if (event->type() == ui::ET_KEY_PRESSED && 77 event->key_code() == ui::VKEY_ESCAPE) { 78 button_->Cancel(false); 79 } 80 } 81 82 // FrameMaximizeButton --------------------------------------------------------- 83 84 FrameMaximizeButton::FrameMaximizeButton(views::ButtonListener* listener, 85 views::NonClientFrameView* frame) 86 : ImageButton(listener), 87 frame_(frame), 88 is_snap_enabled_(false), 89 exceeded_drag_threshold_(false), 90 widget_(NULL), 91 press_is_gesture_(false), 92 snap_type_(SNAP_NONE), 93 bubble_appearance_delay_ms_(kBubbleAppearanceDelayMS) { 94 // TODO(sky): nuke this. It's temporary while we don't have good images. 95 SetImageAlignment(ALIGN_LEFT, ALIGN_BOTTOM); 96 97 if (ash::Shell::IsForcedMaximizeMode()) 98 views::View::SetVisible(false); 99 } 100 101 FrameMaximizeButton::~FrameMaximizeButton() { 102 // Before the window gets destroyed, the maximizer dialog needs to be shut 103 // down since it would otherwise call into a deleted object. 104 maximizer_.reset(); 105 if (widget_) 106 OnWindowDestroying(widget_->GetNativeWindow()); 107 } 108 109 void FrameMaximizeButton::SnapButtonHovered(SnapType type) { 110 // Make sure to only show hover operations when no button is pressed and 111 // a similar snap operation in progress does not get re-applied. 112 if (is_snap_enabled_ || (type == snap_type_ && snap_sizer_)) 113 return; 114 // Prime the mouse location with the center of the (local) button. 115 press_location_ = gfx::Point(width() / 2, height() / 2); 116 // Then get an adjusted mouse position to initiate the effect. 117 gfx::Point location = press_location_; 118 switch (type) { 119 case SNAP_LEFT: 120 location.set_x(location.x() - width()); 121 break; 122 case SNAP_RIGHT: 123 location.set_x(location.x() + width()); 124 break; 125 case SNAP_MINIMIZE: 126 location.set_y(location.y() + height()); 127 break; 128 case SNAP_RESTORE: 129 // Simulate a mouse button move over the according button. 130 if (GetMaximizeBubbleFrameState() == FRAME_STATE_SNAP_LEFT) 131 location.set_x(location.x() - width()); 132 else if (GetMaximizeBubbleFrameState() == FRAME_STATE_SNAP_RIGHT) 133 location.set_x(location.x() + width()); 134 break; 135 case SNAP_MAXIMIZE: 136 break; 137 case SNAP_NONE: 138 Cancel(true); 139 return; 140 default: 141 // We should not come here. 142 NOTREACHED(); 143 } 144 // Note: There is no hover with touch - we can therefore pass false for touch 145 // operations. 146 UpdateSnap(location, true, false); 147 } 148 149 void FrameMaximizeButton::ExecuteSnapAndCloseMenu(SnapType snap_type) { 150 // We can come here with no snap type set in case that the mouse opened the 151 // maximize button and a touch event "touched" a button. 152 if (snap_type_ == SNAP_NONE) 153 SnapButtonHovered(snap_type); 154 155 Cancel(true); 156 // Tell our menu to close. 157 maximizer_.reset(); 158 snap_type_ = snap_type; 159 // Since Snap might destroy |this|, but the snap_sizer needs to be destroyed, 160 // The ownership of the snap_sizer is taken now. 161 scoped_ptr<SnapSizer> snap_sizer(snap_sizer_.release()); 162 Snap(*snap_sizer.get()); 163 } 164 165 void FrameMaximizeButton::DestroyMaximizeMenu() { 166 Cancel(false); 167 } 168 169 void FrameMaximizeButton::OnWindowBoundsChanged( 170 aura::Window* window, 171 const gfx::Rect& old_bounds, 172 const gfx::Rect& new_bounds) { 173 Cancel(false); 174 } 175 176 void FrameMaximizeButton::OnWindowPropertyChanged(aura::Window* window, 177 const void* key, 178 intptr_t old) { 179 // Changing the window position is managed status should not Cancel. 180 // Note that this case might happen when a non user managed window 181 // transitions from maximized to L/R maximized. 182 if (key != ash::internal::kWindowPositionManagedKey) 183 Cancel(false); 184 } 185 186 void FrameMaximizeButton::OnWindowDestroying(aura::Window* window) { 187 maximizer_.reset(); 188 if (widget_) { 189 CHECK_EQ(widget_->GetNativeWindow(), window); 190 widget_->GetNativeWindow()->RemoveObserver(this); 191 widget_->RemoveObserver(this); 192 widget_ = NULL; 193 } 194 } 195 196 void FrameMaximizeButton::OnWidgetActivationChanged(views::Widget* widget, 197 bool active) { 198 // Upon losing focus, the control bubble should hide. 199 if (!active && maximizer_) 200 maximizer_.reset(); 201 } 202 203 bool FrameMaximizeButton::OnMousePressed(const ui::MouseEvent& event) { 204 // If we are already in a mouse click / drag operation, a second button down 205 // call will cancel (this addresses crbug.com/143755). 206 if (is_snap_enabled_) { 207 Cancel(false); 208 } else { 209 is_snap_enabled_ = event.IsOnlyLeftMouseButton(); 210 if (is_snap_enabled_) 211 ProcessStartEvent(event); 212 } 213 ImageButton::OnMousePressed(event); 214 return true; 215 } 216 217 void FrameMaximizeButton::OnMouseEntered(const ui::MouseEvent& event) { 218 ImageButton::OnMouseEntered(event); 219 if (!maximizer_) { 220 DCHECK(GetWidget()); 221 if (!widget_) { 222 widget_ = frame_->GetWidget(); 223 widget_->GetNativeWindow()->AddObserver(this); 224 widget_->AddObserver(this); 225 } 226 maximizer_.reset(new MaximizeBubbleController( 227 this, 228 GetMaximizeBubbleFrameState(), 229 bubble_appearance_delay_ms_)); 230 } 231 } 232 233 void FrameMaximizeButton::OnMouseExited(const ui::MouseEvent& event) { 234 ImageButton::OnMouseExited(event); 235 // Remove the bubble menu when the button is not pressed and the mouse is not 236 // within the bubble. 237 if (!is_snap_enabled_ && maximizer_) { 238 if (maximizer_->GetBubbleWindow()) { 239 gfx::Point screen_location = Shell::GetScreen()->GetCursorScreenPoint(); 240 if (!maximizer_->GetBubbleWindow()->GetBoundsInScreen().Contains( 241 screen_location)) { 242 maximizer_.reset(); 243 // Make sure that all remaining snap hover states get removed. 244 SnapButtonHovered(SNAP_NONE); 245 } 246 } else { 247 // The maximize dialog does not show up immediately after creating the 248 // |mazimizer_|. Destroy the dialog therefore before it shows up. 249 maximizer_.reset(); 250 } 251 } 252 } 253 254 bool FrameMaximizeButton::OnMouseDragged(const ui::MouseEvent& event) { 255 if (is_snap_enabled_) 256 ProcessUpdateEvent(event); 257 return ImageButton::OnMouseDragged(event); 258 } 259 260 void FrameMaximizeButton::OnMouseReleased(const ui::MouseEvent& event) { 261 maximizer_.reset(); 262 bool snap_was_enabled = is_snap_enabled_; 263 if (!ProcessEndEvent(event) && snap_was_enabled) 264 ImageButton::OnMouseReleased(event); 265 // At this point |this| might be already destroyed. 266 } 267 268 void FrameMaximizeButton::OnMouseCaptureLost() { 269 Cancel(false); 270 ImageButton::OnMouseCaptureLost(); 271 } 272 273 void FrameMaximizeButton::OnGestureEvent(ui::GestureEvent* event) { 274 if (event->type() == ui::ET_GESTURE_TAP_DOWN) { 275 is_snap_enabled_ = true; 276 ProcessStartEvent(*event); 277 event->SetHandled(); 278 return; 279 } 280 281 if (event->type() == ui::ET_GESTURE_TAP || 282 (event->type() == ui::ET_GESTURE_SCROLL_END && is_snap_enabled_) || 283 event->type() == ui::ET_SCROLL_FLING_START) { 284 // The position of the event may have changed from the previous event (both 285 // for TAP and SCROLL_END). So it is necessary to update the snap-state for 286 // the current event. 287 ProcessUpdateEvent(*event); 288 if (event->type() == ui::ET_GESTURE_TAP) { 289 snap_type_ = SnapTypeForLocation(event->location()); 290 TouchUMA::GetInstance()->RecordGestureAction( 291 TouchUMA::GESTURE_FRAMEMAXIMIZE_TAP); 292 } 293 ProcessEndEvent(*event); 294 event->SetHandled(); 295 return; 296 } 297 298 if (is_snap_enabled_) { 299 if (event->type() == ui::ET_GESTURE_END && 300 event->details().touch_points() == 1) { 301 // The position of the event may have changed from the previous event. So 302 // it is necessary to update the snap-state for the current event. 303 ProcessUpdateEvent(*event); 304 snap_type_ = SnapTypeForLocation(event->location()); 305 ProcessEndEvent(*event); 306 event->SetHandled(); 307 return; 308 } 309 310 if (event->type() == ui::ET_GESTURE_SCROLL_UPDATE || 311 event->type() == ui::ET_GESTURE_SCROLL_BEGIN) { 312 ProcessUpdateEvent(*event); 313 event->SetHandled(); 314 return; 315 } 316 } 317 318 ImageButton::OnGestureEvent(event); 319 } 320 321 void FrameMaximizeButton::SetVisible(bool visible) { 322 // In the enforced maximized mode we do not allow to be made visible. 323 if (ash::Shell::IsForcedMaximizeMode()) 324 return; 325 326 views::View::SetVisible(visible); 327 } 328 329 void FrameMaximizeButton::ProcessStartEvent(const ui::LocatedEvent& event) { 330 DCHECK(is_snap_enabled_); 331 // Prepare the help menu. 332 if (!maximizer_) { 333 maximizer_.reset(new MaximizeBubbleController( 334 this, 335 GetMaximizeBubbleFrameState(), 336 bubble_appearance_delay_ms_)); 337 } else { 338 // If the menu did not show up yet, we delay it even a bit more. 339 maximizer_->DelayCreation(); 340 } 341 snap_sizer_.reset(NULL); 342 InstallEventFilter(); 343 snap_type_ = SNAP_NONE; 344 press_location_ = event.location(); 345 press_is_gesture_ = event.IsGestureEvent(); 346 exceeded_drag_threshold_ = false; 347 update_timer_.Start( 348 FROM_HERE, 349 base::TimeDelta::FromMilliseconds(kUpdateDelayMS), 350 this, 351 &FrameMaximizeButton::UpdateSnapFromEventLocation); 352 } 353 354 void FrameMaximizeButton::ProcessUpdateEvent(const ui::LocatedEvent& event) { 355 DCHECK(is_snap_enabled_); 356 if (!exceeded_drag_threshold_) { 357 exceeded_drag_threshold_ = views::View::ExceededDragThreshold( 358 event.location() - press_location_); 359 } 360 if (exceeded_drag_threshold_) 361 UpdateSnap(event.location(), false, event.IsGestureEvent()); 362 } 363 364 bool FrameMaximizeButton::ProcessEndEvent(const ui::LocatedEvent& event) { 365 update_timer_.Stop(); 366 UninstallEventFilter(); 367 bool should_snap = is_snap_enabled_; 368 is_snap_enabled_ = false; 369 370 // Remove our help bubble. 371 maximizer_.reset(); 372 373 if (!should_snap || snap_type_ == SNAP_NONE) 374 return false; 375 376 SetState(views::CustomButton::STATE_NORMAL); 377 // SetState will not call SchedulePaint() if state was already set to 378 // STATE_NORMAL during a drag. 379 SchedulePaint(); 380 phantom_window_.reset(); 381 // Since Snap might destroy |this|, but the snap_sizer needs to be destroyed, 382 // The ownership of the snap_sizer is taken now. 383 scoped_ptr<SnapSizer> snap_sizer(snap_sizer_.release()); 384 Snap(*snap_sizer.get()); 385 return true; 386 } 387 388 void FrameMaximizeButton::Cancel(bool keep_menu_open) { 389 if (!keep_menu_open) { 390 maximizer_.reset(); 391 UninstallEventFilter(); 392 is_snap_enabled_ = false; 393 snap_sizer_.reset(); 394 } 395 phantom_window_.reset(); 396 snap_type_ = SNAP_NONE; 397 update_timer_.Stop(); 398 SchedulePaint(); 399 } 400 401 void FrameMaximizeButton::InstallEventFilter() { 402 if (escape_event_filter_) 403 return; 404 405 escape_event_filter_.reset(new EscapeEventFilter(this)); 406 } 407 408 void FrameMaximizeButton::UninstallEventFilter() { 409 escape_event_filter_.reset(NULL); 410 } 411 412 void FrameMaximizeButton::UpdateSnapFromEventLocation() { 413 // If the drag threshold has been exceeded the snap location is up to date. 414 if (exceeded_drag_threshold_) 415 return; 416 exceeded_drag_threshold_ = true; 417 UpdateSnap(press_location_, false, press_is_gesture_); 418 } 419 420 void FrameMaximizeButton::UpdateSnap(const gfx::Point& location, 421 bool select_default, 422 bool is_touch) { 423 SnapType type = SnapTypeForLocation(location); 424 if (type == snap_type_) { 425 if (snap_sizer_) { 426 snap_sizer_->Update(LocationForSnapSizer(location)); 427 phantom_window_->Show(ScreenAsh::ConvertRectToScreen( 428 frame_->GetWidget()->GetNativeView()->parent(), 429 snap_sizer_->target_bounds())); 430 } 431 return; 432 } 433 434 snap_type_ = type; 435 snap_sizer_.reset(); 436 SchedulePaint(); 437 438 if (snap_type_ == SNAP_NONE) { 439 phantom_window_.reset(); 440 return; 441 } 442 443 if (snap_type_ == SNAP_LEFT || snap_type_ == SNAP_RIGHT) { 444 SnapSizer::Edge snap_edge = snap_type_ == SNAP_LEFT ? 445 SnapSizer::LEFT_EDGE : SnapSizer::RIGHT_EDGE; 446 SnapSizer::InputType input_type = 447 is_touch ? SnapSizer::TOUCH_MAXIMIZE_BUTTON_INPUT : 448 SnapSizer::OTHER_INPUT; 449 snap_sizer_.reset(new SnapSizer(frame_->GetWidget()->GetNativeWindow(), 450 LocationForSnapSizer(location), 451 snap_edge, 452 input_type)); 453 if (select_default) 454 snap_sizer_->SelectDefaultSizeAndDisableResize(); 455 } 456 if (!phantom_window_) { 457 phantom_window_.reset(new internal::PhantomWindowController( 458 frame_->GetWidget()->GetNativeWindow())); 459 } 460 if (maximizer_) { 461 phantom_window_->set_phantom_below_window(maximizer_->GetBubbleWindow()); 462 maximizer_->SetSnapType(snap_type_); 463 } 464 phantom_window_->Show( 465 ScreenBoundsForType(snap_type_, *snap_sizer_.get())); 466 } 467 468 SnapType FrameMaximizeButton::SnapTypeForLocation( 469 const gfx::Point& location) const { 470 MaximizeBubbleFrameState maximize_type = GetMaximizeBubbleFrameState(); 471 gfx::Vector2d delta(location - press_location_); 472 if (!views::View::ExceededDragThreshold(delta)) 473 return maximize_type != FRAME_STATE_FULL ? SNAP_MAXIMIZE : SNAP_RESTORE; 474 if (delta.x() < 0 && delta.y() > delta.x() && delta.y() < -delta.x()) 475 return maximize_type == FRAME_STATE_SNAP_LEFT ? SNAP_RESTORE : SNAP_LEFT; 476 if (delta.x() > 0 && delta.y() > -delta.x() && delta.y() < delta.x()) 477 return maximize_type == FRAME_STATE_SNAP_RIGHT ? SNAP_RESTORE : SNAP_RIGHT; 478 if (delta.y() > 0) 479 return SNAP_MINIMIZE; 480 return maximize_type != FRAME_STATE_FULL ? SNAP_MAXIMIZE : SNAP_RESTORE; 481 } 482 483 gfx::Rect FrameMaximizeButton::ScreenBoundsForType( 484 SnapType type, 485 const SnapSizer& snap_sizer) const { 486 aura::Window* window = frame_->GetWidget()->GetNativeWindow(); 487 switch (type) { 488 case SNAP_LEFT: 489 case SNAP_RIGHT: 490 return ScreenAsh::ConvertRectToScreen( 491 frame_->GetWidget()->GetNativeView()->parent(), 492 snap_sizer.target_bounds()); 493 case SNAP_MAXIMIZE: 494 return ScreenAsh::ConvertRectToScreen( 495 window->parent(), 496 ScreenAsh::GetMaximizedWindowBoundsInParent(window)); 497 case SNAP_MINIMIZE: { 498 gfx::Rect rect = GetMinimizeAnimationTargetBoundsInScreen(window); 499 if (!rect.IsEmpty()) { 500 // PhantomWindowController insets slightly, outset it so the phantom 501 // doesn't appear inset. 502 rect.Inset(-8, -8); 503 } 504 return rect; 505 } 506 case SNAP_RESTORE: { 507 const gfx::Rect* restore = GetRestoreBoundsInScreen(window); 508 return restore ? 509 *restore : frame_->GetWidget()->GetWindowBoundsInScreen(); 510 } 511 case SNAP_NONE: 512 NOTREACHED(); 513 } 514 return gfx::Rect(); 515 } 516 517 gfx::Point FrameMaximizeButton::LocationForSnapSizer( 518 const gfx::Point& location) const { 519 gfx::Point result(location); 520 views::View::ConvertPointToScreen(this, &result); 521 return result; 522 } 523 524 void FrameMaximizeButton::Snap(const SnapSizer& snap_sizer) { 525 ash::Shell* shell = ash::Shell::GetInstance(); 526 views::Widget* widget = frame_->GetWidget(); 527 switch (snap_type_) { 528 case SNAP_LEFT: 529 case SNAP_RIGHT: { 530 shell->delegate()->RecordUserMetricsAction( 531 snap_type_ == SNAP_LEFT ? 532 ash::UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_LEFT : 533 ash::UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_RIGHT); 534 // Get the bounds in screen coordinates for restore purposes. 535 gfx::Rect restore = widget->GetWindowBoundsInScreen(); 536 if (widget->IsMaximized() || widget->IsFullscreen()) { 537 aura::Window* window = widget->GetNativeWindow(); 538 // In case of maximized we have a restore boundary. 539 DCHECK(ash::GetRestoreBoundsInScreen(window)); 540 // If it was maximized we need to recover the old restore set. 541 restore = *ash::GetRestoreBoundsInScreen(window); 542 543 // The auto position manager will kick in when this is the only window. 544 // To avoid interference with it we tell it temporarily to not change 545 // the coordinates of this window. 546 bool is_managed = ash::wm::IsWindowPositionManaged(window); 547 if (is_managed) 548 ash::wm::SetWindowPositionManaged(window, false); 549 550 // Set the restore size we want to restore to. 551 ash::SetRestoreBoundsInScreen(window, 552 ScreenBoundsForType(snap_type_, 553 snap_sizer)); 554 widget->Restore(); 555 556 // After the window is where we want it to be we allow the window to be 557 // auto managed again. 558 if (is_managed) 559 ash::wm::SetWindowPositionManaged(window, true); 560 } else { 561 // Others might also have set up a restore rectangle already. If so, 562 // we should not overwrite the restore rectangle. 563 bool restore_set = 564 GetRestoreBoundsInScreen(widget->GetNativeWindow()) != NULL; 565 widget->SetBounds(ScreenBoundsForType(snap_type_, snap_sizer)); 566 if (restore_set) 567 break; 568 } 569 // Remember the widow's bounds for restoration. 570 ash::SetRestoreBoundsInScreen(widget->GetNativeWindow(), restore); 571 break; 572 } 573 case SNAP_MAXIMIZE: 574 widget->Maximize(); 575 shell->delegate()->RecordUserMetricsAction( 576 ash::UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE); 577 break; 578 case SNAP_MINIMIZE: 579 widget->Minimize(); 580 shell->delegate()->RecordUserMetricsAction( 581 ash::UMA_WINDOW_MAXIMIZE_BUTTON_MINIMIZE); 582 break; 583 case SNAP_RESTORE: 584 widget->Restore(); 585 shell->delegate()->RecordUserMetricsAction( 586 ash::UMA_WINDOW_MAXIMIZE_BUTTON_RESTORE); 587 break; 588 case SNAP_NONE: 589 NOTREACHED(); 590 } 591 } 592 593 MaximizeBubbleFrameState 594 FrameMaximizeButton::GetMaximizeBubbleFrameState() const { 595 // When there are no restore bounds, we are in normal mode. 596 if (!ash::GetRestoreBoundsInScreen( 597 frame_->GetWidget()->GetNativeWindow())) 598 return FRAME_STATE_NONE; 599 // The normal maximized test can be used. 600 if (frame_->GetWidget()->IsMaximized()) 601 return FRAME_STATE_FULL; 602 // For Left/right maximize we need to check the dimensions. 603 gfx::Rect bounds = frame_->GetWidget()->GetWindowBoundsInScreen(); 604 gfx::Rect screen = Shell::GetScreen()->GetDisplayNearestWindow( 605 frame_->GetWidget()->GetNativeView()).work_area(); 606 if (bounds.width() < (screen.width() * kMinSnapSizePercent) / 100) 607 return FRAME_STATE_NONE; 608 // We might still have a horizontally filled window at this point which we 609 // treat as no special state. 610 if (bounds.y() != screen.y() || bounds.height() != screen.height()) 611 return FRAME_STATE_NONE; 612 613 // We have to be in a maximize mode at this point. 614 if (bounds.x() == screen.x()) 615 return FRAME_STATE_SNAP_LEFT; 616 if (bounds.right() == screen.right()) 617 return FRAME_STATE_SNAP_RIGHT; 618 // If we come here, it is likely caused by the fact that the 619 // "VerticalResizeDoubleClick" stored a restore rectangle. In that case 620 // we allow all maximize operations (and keep the restore rectangle). 621 return FRAME_STATE_NONE; 622 } 623 624 } // namespace ash 625