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/magnetism_matcher.h"
6
7#include <algorithm>
8#include <cmath>
9
10namespace ash {
11namespace {
12
13// Returns true if |a| is close enough to |b| that the two edges snap.
14bool IsCloseEnough(int a, int b) {
15  return abs(a - b) <= MagnetismMatcher::kMagneticDistance;
16}
17
18// Returns true if the specified SecondaryMagnetismEdge can be matched with a
19// primary edge of |primary|. |edges| is a bitmask of the allowed
20// MagnetismEdges.
21bool CanMatchSecondaryEdge(MagnetismEdge primary,
22                           SecondaryMagnetismEdge secondary,
23                           uint32 edges) {
24  // Convert |secondary| to a MagnetismEdge so we can compare it to |edges|.
25  MagnetismEdge secondary_as_magnetism_edge = MAGNETISM_EDGE_TOP;
26  switch (primary) {
27    case MAGNETISM_EDGE_TOP:
28    case MAGNETISM_EDGE_BOTTOM:
29      if (secondary == SECONDARY_MAGNETISM_EDGE_LEADING)
30        secondary_as_magnetism_edge = MAGNETISM_EDGE_LEFT;
31      else if (secondary == SECONDARY_MAGNETISM_EDGE_TRAILING)
32        secondary_as_magnetism_edge = MAGNETISM_EDGE_RIGHT;
33      else
34        NOTREACHED();
35      break;
36    case MAGNETISM_EDGE_LEFT:
37    case MAGNETISM_EDGE_RIGHT:
38      if (secondary == SECONDARY_MAGNETISM_EDGE_LEADING)
39        secondary_as_magnetism_edge = MAGNETISM_EDGE_TOP;
40      else if (secondary == SECONDARY_MAGNETISM_EDGE_TRAILING)
41        secondary_as_magnetism_edge = MAGNETISM_EDGE_BOTTOM;
42      else
43        NOTREACHED();
44      break;
45  }
46  return (edges & secondary_as_magnetism_edge) != 0;
47}
48
49}  // namespace
50
51// MagnetismEdgeMatcher --------------------------------------------------------
52
53MagnetismEdgeMatcher::MagnetismEdgeMatcher(const gfx::Rect& bounds,
54                                           MagnetismEdge edge)
55    : bounds_(bounds),
56      edge_(edge) {
57  ranges_.push_back(GetSecondaryRange(bounds_));
58}
59
60MagnetismEdgeMatcher::~MagnetismEdgeMatcher() {
61}
62
63bool MagnetismEdgeMatcher::ShouldAttach(const gfx::Rect& bounds) {
64  if (is_edge_obscured())
65    return false;
66
67  if (IsCloseEnough(GetPrimaryCoordinate(bounds_, edge_),
68                    GetPrimaryCoordinate(bounds, FlipEdge(edge_)))) {
69    const Range range(GetSecondaryRange(bounds));
70    Ranges::const_iterator i =
71        std::lower_bound(ranges_.begin(), ranges_.end(), range);
72    // Close enough, but only attach if some portion of the edge is visible.
73    if ((i != ranges_.begin() && RangesIntersect(*(i - 1), range)) ||
74        (i != ranges_.end() && RangesIntersect(*i, range))) {
75      return true;
76    }
77  }
78  // NOTE: this checks against the current bounds, we may want to allow some
79  // flexibility here.
80  const Range primary_range(GetPrimaryRange(bounds));
81  if (primary_range.first <= GetPrimaryCoordinate(bounds_, edge_) &&
82      primary_range.second >= GetPrimaryCoordinate(bounds_, edge_)) {
83    UpdateRanges(GetSecondaryRange(bounds));
84  }
85  return false;
86}
87
88void MagnetismEdgeMatcher::UpdateRanges(const Range& range) {
89  Ranges::const_iterator it =
90      std::lower_bound(ranges_.begin(), ranges_.end(), range);
91  if (it != ranges_.begin() && RangesIntersect(*(it - 1), range))
92    --it;
93  if (it == ranges_.end())
94    return;
95
96  for (size_t i = it - ranges_.begin();
97       i < ranges_.size() && RangesIntersect(ranges_[i], range); ) {
98    if (range.first <= ranges_[i].first &&
99        range.second >= ranges_[i].second) {
100      ranges_.erase(ranges_.begin() + i);
101    } else if (range.first < ranges_[i].first) {
102      DCHECK_GT(range.second, ranges_[i].first);
103      ranges_[i] = Range(range.second, ranges_[i].second);
104      ++i;
105    } else {
106      Range existing(ranges_[i]);
107      ranges_[i].second = range.first;
108      ++i;
109      if (existing.second > range.second) {
110        ranges_.insert(ranges_.begin() + i,
111                       Range(range.second, existing.second));
112        ++i;
113      }
114    }
115  }
116}
117
118// MagnetismMatcher ------------------------------------------------------------
119
120// static
121const int MagnetismMatcher::kMagneticDistance = 8;
122
123MagnetismMatcher::MagnetismMatcher(const gfx::Rect& bounds, uint32 edges)
124    : edges_(edges) {
125  if (edges & MAGNETISM_EDGE_TOP)
126    matchers_.push_back(new MagnetismEdgeMatcher(bounds, MAGNETISM_EDGE_TOP));
127  if (edges & MAGNETISM_EDGE_LEFT)
128    matchers_.push_back(new MagnetismEdgeMatcher(bounds, MAGNETISM_EDGE_LEFT));
129  if (edges & MAGNETISM_EDGE_BOTTOM) {
130    matchers_.push_back(new MagnetismEdgeMatcher(bounds,
131                                                 MAGNETISM_EDGE_BOTTOM));
132  }
133  if (edges & MAGNETISM_EDGE_RIGHT)
134    matchers_.push_back(new MagnetismEdgeMatcher(bounds, MAGNETISM_EDGE_RIGHT));
135}
136
137MagnetismMatcher::~MagnetismMatcher() {
138}
139
140bool MagnetismMatcher::ShouldAttach(const gfx::Rect& bounds,
141                                    MatchedEdge* edge) {
142  for (size_t i = 0; i < matchers_.size(); ++i) {
143    if (matchers_[i]->ShouldAttach(bounds)) {
144      edge->primary_edge = matchers_[i]->edge();
145      AttachToSecondaryEdge(bounds, edge->primary_edge,
146                            &(edge->secondary_edge));
147      return true;
148    }
149  }
150  return false;
151}
152
153bool MagnetismMatcher::AreEdgesObscured() const {
154  for (size_t i = 0; i < matchers_.size(); ++i) {
155    if (!matchers_[i]->is_edge_obscured())
156      return false;
157  }
158  return true;
159}
160
161void MagnetismMatcher::AttachToSecondaryEdge(
162    const gfx::Rect& bounds,
163    MagnetismEdge edge,
164    SecondaryMagnetismEdge* secondary_edge) const {
165  const gfx::Rect& src_bounds(matchers_[0]->bounds());
166  if (edge == MAGNETISM_EDGE_LEFT || edge == MAGNETISM_EDGE_RIGHT) {
167    if (CanMatchSecondaryEdge(edge, SECONDARY_MAGNETISM_EDGE_LEADING, edges_) &&
168        IsCloseEnough(bounds.y(), src_bounds.y())) {
169      *secondary_edge = SECONDARY_MAGNETISM_EDGE_LEADING;
170    } else if (CanMatchSecondaryEdge(edge, SECONDARY_MAGNETISM_EDGE_TRAILING,
171                                     edges_) &&
172               IsCloseEnough(bounds.bottom(), src_bounds.bottom())) {
173      *secondary_edge = SECONDARY_MAGNETISM_EDGE_TRAILING;
174    } else {
175      *secondary_edge = SECONDARY_MAGNETISM_EDGE_NONE;
176    }
177  } else {
178    if (CanMatchSecondaryEdge(edge, SECONDARY_MAGNETISM_EDGE_LEADING, edges_) &&
179        IsCloseEnough(bounds.x(), src_bounds.x())) {
180      *secondary_edge = SECONDARY_MAGNETISM_EDGE_LEADING;
181    } else if (CanMatchSecondaryEdge(edge, SECONDARY_MAGNETISM_EDGE_TRAILING,
182                                     edges_) &&
183               IsCloseEnough(bounds.right(), src_bounds.right())) {
184      *secondary_edge = SECONDARY_MAGNETISM_EDGE_TRAILING;
185    } else {
186      *secondary_edge = SECONDARY_MAGNETISM_EDGE_NONE;
187    }
188  }
189}
190
191}  // namespace ash
192