| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/wm/workspace/snap_sizer.h" |
| |
| #include <cmath> |
| |
| #include "ash/ash_switches.h" |
| #include "ash/screen_ash.h" |
| #include "ash/wm/window_resizer.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/window_util.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_delegate.h" |
| #include "ui/gfx/screen.h" |
| |
| namespace ash { |
| namespace internal { |
| |
| namespace { |
| |
| // A list of ideal window widths in DIP which will be used to populate the |
| // |usable_width_| list. |
| const int kIdealWidth[] = { 1280, 1024, 768, 640 }; |
| |
| // Windows are initially snapped to the size in |usable_width_| at index 0. |
| // The index into |usable_width_| is changed if any of the following happen: |
| // . The user stops moving the mouse for |kDelayBeforeIncreaseMS| and then |
| // moves the mouse again. |
| // . The mouse moves |kPixelsBeforeAdjust| horizontal pixels. |
| // . The mouse is against the edge of the screen and the mouse is moved |
| // |kMovesBeforeAdjust| times. |
| const int kDelayBeforeIncreaseMS = 500; |
| const int kMovesBeforeAdjust = 25; |
| const int kPixelsBeforeAdjust = 100; |
| |
| // The maximum fraction of the screen width that a snapped window is allowed |
| // to take up. |
| const int kMaximumScreenPercent = 90; |
| |
| // The width that a window should be snapped to if resizing is disabled in the |
| // SnapSizer for devices with small screen resolutions. |
| const int kDefaultWidthSmallScreen = 1024; |
| |
| // Returns the minimum width that |window| can be snapped to. The returned width |
| // may not be in the width list generated by BuildIdealWidthList(). |
| int GetMinWidth(aura::Window* window) { |
| return window->delegate() ? window->delegate()->GetMinimumSize().width() : 0; |
| } |
| |
| // Returns the maximum width that |window| can be snapped to. The returned width |
| // may not be in the width list generated by BuildIdealWidthList(). |
| // The aura::WindowDelegate's max size is ignored because |
| // ash::wm::CanSnapWindow() returns false when a max size is specified. |
| int GetMaxWidth(aura::Window* window) { |
| gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(window)); |
| return std::max(work_area.width() * kMaximumScreenPercent / 100, |
| GetMinWidth(window)); |
| } |
| |
| // Returns the width that |window| should be snapped to if resizing is disabled |
| // in the SnapSizer. |
| int GetDefaultWidth(aura::Window* window) { |
| gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(window)); |
| |
| int width = 0; |
| if (ash::switches::UseAlternateFrameCaptionButtonStyle()) { |
| // Only the 'half of screen' width is supported when using the alternate |
| // visual style for the frame caption buttons (minimize, maximize, restore, |
| // and close). |
| width = work_area.width() / 2; |
| } else { |
| width = std::max(kDefaultWidthSmallScreen, work_area.width() / 2); |
| } |
| |
| width = std::min(width, GetMaxWidth(window)); |
| return std::max(width, GetMinWidth(window)); |
| } |
| |
| // Creates the list of possible width for the current screen configuration: |
| // Returns a list with items from |kIdealWidth| which fit on the screen and |
| // supplement it with the 'half of screen' size. Furthermore, add an entry for |
| // 90% of the screen size if it is smaller than the biggest value in the |
| // |kIdealWidth| list (to get a step between the values). |
| std::vector<int> BuildIdealWidthList(aura::Window* window) { |
| if (ash::switches::UseAlternateFrameCaptionButtonStyle()) { |
| // Only the 'half of screen' width is supported when using the alternate |
| // visual style for the frame caption buttons (minimize, maximize, |
| // restore, and close). |
| return std::vector<int>(1u, GetDefaultWidth(window)); |
| } |
| |
| int minimum_width = GetMinWidth(window); |
| int maximum_width = GetMaxWidth(window); |
| |
| gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(window)); |
| int half_width = work_area.width() / 2; |
| if (half_width < minimum_width || half_width > maximum_width) |
| half_width = 0; |
| |
| std::vector<int> ideal_width_list; |
| for (size_t i = 0; i < arraysize(kIdealWidth); i++) { |
| if (kIdealWidth[i] >= minimum_width && kIdealWidth[i] <= maximum_width) { |
| if (i && !ideal_width_list.size() && maximum_width != kIdealWidth[i]) |
| ideal_width_list.push_back(maximum_width); |
| if (half_width > kIdealWidth[i]) |
| ideal_width_list.push_back(half_width); |
| if (half_width >= kIdealWidth[i]) |
| half_width = 0; |
| ideal_width_list.push_back(kIdealWidth[i]); |
| } |
| } |
| if (half_width) |
| ideal_width_list.push_back(half_width); |
| if (ideal_width_list.empty()) { |
| if (minimum_width > 0) |
| ideal_width_list.push_back(minimum_width); |
| else |
| ideal_width_list.push_back(maximum_width); |
| } |
| |
| return ideal_width_list; |
| } |
| |
| // Changes |window|'s bounds to |snap_bounds| while preserving the restore |
| // bounds. |
| void SnapWindowToBounds(wm::WindowState* window_state, |
| SnapSizer::Edge edge, |
| const gfx::Rect& snap_bounds) { |
| if (edge == SnapSizer::LEFT_EDGE) { |
| window_state->SnapLeft(snap_bounds); |
| } else { |
| window_state->SnapRight(snap_bounds); |
| } |
| } |
| |
| } // namespace |
| |
| SnapSizer::SnapSizer(wm::WindowState* window_state, |
| const gfx::Point& start, |
| Edge edge, |
| InputType input_type) |
| : window_state_(window_state), |
| edge_(edge), |
| time_last_update_(base::TimeTicks::Now()), |
| size_index_(0), |
| end_of_sequence_(false), |
| resize_disabled_(false), |
| num_moves_since_adjust_(0), |
| last_adjust_x_(start.x()), |
| last_update_x_(start.x()), |
| start_x_(start.x()), |
| input_type_(input_type), |
| usable_width_(BuildIdealWidthList(window_state->window())) { |
| DCHECK(!usable_width_.empty()); |
| target_bounds_ = GetTargetBounds(); |
| } |
| |
| SnapSizer::~SnapSizer() { |
| } |
| |
| void SnapSizer::SnapWindow(wm::WindowState* window_state, |
| SnapSizer::Edge edge) { |
| if (!window_state->CanSnap()) |
| return; |
| internal::SnapSizer sizer(window_state, gfx::Point(), edge, |
| internal::SnapSizer::OTHER_INPUT); |
| SnapWindowToBounds(window_state, edge, |
| sizer.GetSnapBounds(window_state->window()->bounds())); |
| } |
| |
| void SnapSizer::SnapWindowToTargetBounds() { |
| SnapWindowToBounds(window_state_, edge_, target_bounds()); |
| } |
| |
| void SnapSizer::Update(const gfx::Point& location) { |
| // See description above for details on this behavior. |
| num_moves_since_adjust_++; |
| if ((base::TimeTicks::Now() - time_last_update_).InMilliseconds() > |
| kDelayBeforeIncreaseMS) { |
| ChangeBounds(location.x(), |
| CalculateIncrement(location.x(), last_update_x_)); |
| } else { |
| bool along_edge = AlongEdge(location.x()); |
| int pixels_before_adjust = kPixelsBeforeAdjust; |
| if (input_type_ == TOUCH_MAXIMIZE_BUTTON_INPUT) { |
| const gfx::Rect& workspace_bounds = |
| window_state_->window()->parent()->bounds(); |
| if (start_x_ > location.x()) { |
| pixels_before_adjust = |
| std::min(pixels_before_adjust, start_x_ / 10); |
| } else { |
| pixels_before_adjust = |
| std::min(pixels_before_adjust, |
| (workspace_bounds.width() - start_x_) / 10); |
| } |
| } |
| if (std::abs(location.x() - last_adjust_x_) >= pixels_before_adjust || |
| (along_edge && num_moves_since_adjust_ >= kMovesBeforeAdjust)) { |
| ChangeBounds(location.x(), |
| CalculateIncrement(location.x(), last_adjust_x_)); |
| } |
| } |
| last_update_x_ = location.x(); |
| time_last_update_ = base::TimeTicks::Now(); |
| } |
| |
| gfx::Rect SnapSizer::GetSnapBounds(const gfx::Rect& bounds) { |
| int current = 0; |
| if (!resize_disabled_) { |
| for (current = usable_width_.size() - 1; current >= 0; current--) { |
| gfx::Rect target = GetTargetBoundsForSize(current); |
| if (target == bounds) { |
| ++current; |
| break; |
| } |
| } |
| } |
| if (current < 0) |
| current = 0; |
| return GetTargetBoundsForSize(current % usable_width_.size()); |
| } |
| |
| void SnapSizer::SelectDefaultSizeAndDisableResize() { |
| resize_disabled_ = true; |
| size_index_ = 0; |
| end_of_sequence_ = false; |
| target_bounds_ = GetTargetBounds(); |
| } |
| |
| gfx::Rect SnapSizer::GetTargetBoundsForSize(size_t size_index) const { |
| gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent( |
| window_state_->window())); |
| int y = work_area.y(); |
| int max_y = work_area.bottom(); |
| int width = 0; |
| if (resize_disabled_) { |
| width = GetDefaultWidth(window_state_->window()); |
| } else { |
| DCHECK(size_index < usable_width_.size()); |
| width = usable_width_[size_index]; |
| } |
| |
| if (edge_ == LEFT_EDGE) { |
| int x = work_area.x(); |
| int mid_x = x + width; |
| return gfx::Rect(x, y, mid_x - x, max_y - y); |
| } |
| int max_x = work_area.right(); |
| int x = max_x - width; |
| return gfx::Rect(x , y, max_x - x, max_y - y); |
| } |
| |
| int SnapSizer::CalculateIncrement(int x, int reference_x) const { |
| if (AlongEdge(x)) |
| return 1; |
| if (x == reference_x) |
| return 0; |
| if (edge_ == LEFT_EDGE) { |
| if (x < reference_x) |
| return 1; |
| return -1; |
| } |
| // edge_ == RIGHT_EDGE. |
| if (x > reference_x) |
| return 1; |
| return -1; |
| } |
| |
| void SnapSizer::ChangeBounds(int x, int delta) { |
| end_of_sequence_ = |
| delta > 0 && size_index_ == static_cast<int>(usable_width_.size()) - 1; |
| int index = std::min(static_cast<int>(usable_width_.size()) - 1, |
| std::max(size_index_ + delta, 0)); |
| if (index != size_index_) { |
| size_index_ = index; |
| target_bounds_ = GetTargetBounds(); |
| } |
| num_moves_since_adjust_ = 0; |
| last_adjust_x_ = x; |
| } |
| |
| gfx::Rect SnapSizer::GetTargetBounds() const { |
| return GetTargetBoundsForSize(size_index_); |
| } |
| |
| bool SnapSizer::AlongEdge(int x) const { |
| gfx::Rect area(ScreenAsh::GetDisplayWorkAreaBoundsInParent( |
| window_state_->window())); |
| return (x <= area.x()) || (x >= area.right() - 1); |
| } |
| |
| } // namespace internal |
| } // namespace ash |