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