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 #ifndef ASH_WM_WORKSPACE_MAGNETISM_MATCHER_H_
6 #define ASH_WM_WORKSPACE_MAGNETISM_MATCHER_H_
7 
8 #include <stdint.h>
9 
10 #include <memory>
11 #include <utility>
12 #include <vector>
13 
14 #include "ash/ash_export.h"
15 #include "base/compiler_specific.h"
16 #include "base/macros.h"
17 #include "base/notreached.h"
18 #include "ui/gfx/geometry/rect.h"
19 
20 namespace ash {
21 
22 enum MagnetismEdge {
23   MAGNETISM_EDGE_TOP = 1 << 0,
24   MAGNETISM_EDGE_LEFT = 1 << 1,
25   MAGNETISM_EDGE_BOTTOM = 1 << 2,
26   MAGNETISM_EDGE_RIGHT = 1 << 3,
27 };
28 
29 const uint32_t kAllMagnetismEdges = MAGNETISM_EDGE_TOP | MAGNETISM_EDGE_LEFT |
30                                     MAGNETISM_EDGE_BOTTOM |
31                                     MAGNETISM_EDGE_RIGHT;
32 
33 // MagnetismEdgeMatcher is used for matching a particular edge of a window. You
34 // shouldn't need to use this directly, instead use MagnetismMatcher which takes
35 // care of all edges.
36 // MagnetismEdgeMatcher maintains a range of the visible portions of the
37 // edge. As ShouldAttach() is invoked the visible range is updated.
38 class MagnetismEdgeMatcher {
39  public:
40   MagnetismEdgeMatcher(const gfx::Rect& bounds, MagnetismEdge edge);
41   ~MagnetismEdgeMatcher();
42 
edge()43   MagnetismEdge edge() const { return edge_; }
bounds()44   const gfx::Rect& bounds() const { return bounds_; }
45 
46   // Returns true if the edge is completely obscured. If true ShouldAttach()
47   // will return false.
is_edge_obscured()48   bool is_edge_obscured() const { return ranges_.empty(); }
49 
50   // Returns true if should attach to the specified bounds.
51   bool ShouldAttach(const gfx::Rect& bounds);
52 
53  private:
54   typedef std::pair<int, int> Range;
55   typedef std::vector<Range> Ranges;
56 
57   // Removes |range| from |ranges_|.
58   void UpdateRanges(const Range& range);
59 
GetPrimaryCoordinate(const gfx::Rect & bounds,MagnetismEdge edge)60   static int GetPrimaryCoordinate(const gfx::Rect& bounds, MagnetismEdge edge) {
61     switch (edge) {
62       case MAGNETISM_EDGE_TOP:
63         return bounds.y();
64       case MAGNETISM_EDGE_LEFT:
65         return bounds.x();
66       case MAGNETISM_EDGE_BOTTOM:
67         return bounds.bottom();
68       case MAGNETISM_EDGE_RIGHT:
69         return bounds.right();
70     }
71     NOTREACHED();
72     return 0;
73   }
74 
FlipEdge(MagnetismEdge edge)75   static MagnetismEdge FlipEdge(MagnetismEdge edge) {
76     switch (edge) {
77       case MAGNETISM_EDGE_TOP:
78         return MAGNETISM_EDGE_BOTTOM;
79       case MAGNETISM_EDGE_BOTTOM:
80         return MAGNETISM_EDGE_TOP;
81       case MAGNETISM_EDGE_LEFT:
82         return MAGNETISM_EDGE_RIGHT;
83       case MAGNETISM_EDGE_RIGHT:
84         return MAGNETISM_EDGE_LEFT;
85     }
86     NOTREACHED();
87     return MAGNETISM_EDGE_LEFT;
88   }
89 
GetPrimaryRange(const gfx::Rect & bounds)90   Range GetPrimaryRange(const gfx::Rect& bounds) const {
91     switch (edge_) {
92       case MAGNETISM_EDGE_TOP:
93       case MAGNETISM_EDGE_BOTTOM:
94         return Range(bounds.y(), bounds.bottom());
95       case MAGNETISM_EDGE_LEFT:
96       case MAGNETISM_EDGE_RIGHT:
97         return Range(bounds.x(), bounds.right());
98     }
99     NOTREACHED();
100     return Range();
101   }
102 
GetSecondaryRange(const gfx::Rect & bounds)103   Range GetSecondaryRange(const gfx::Rect& bounds) const {
104     switch (edge_) {
105       case MAGNETISM_EDGE_TOP:
106       case MAGNETISM_EDGE_BOTTOM:
107         return Range(bounds.x(), bounds.right());
108       case MAGNETISM_EDGE_LEFT:
109       case MAGNETISM_EDGE_RIGHT:
110         return Range(bounds.y(), bounds.bottom());
111     }
112     NOTREACHED();
113     return Range();
114   }
115 
RangesIntersect(const Range & r1,const Range & r2)116   static bool RangesIntersect(const Range& r1, const Range& r2) {
117     return r2.first < r1.second && r2.second > r1.first;
118   }
119 
120   // The bounds of window.
121   const gfx::Rect bounds_;
122 
123   // The edge this matcher checks.
124   const MagnetismEdge edge_;
125 
126   // Visible ranges of the edge. Initialized with GetSecondaryRange() and
127   // updated as ShouldAttach() is invoked. When empty the edge is completely
128   // obscured by other bounds.
129   Ranges ranges_;
130 
131   DISALLOW_COPY_AND_ASSIGN(MagnetismEdgeMatcher);
132 };
133 
134 enum SecondaryMagnetismEdge {
135   SECONDARY_MAGNETISM_EDGE_LEADING,
136   SECONDARY_MAGNETISM_EDGE_TRAILING,
137   SECONDARY_MAGNETISM_EDGE_NONE,
138 };
139 
140 // Used to identify a matched edge. |primary_edge| is relative to the source and
141 // indicates the edge the two are to share. For example, if |primary_edge| is
142 // MAGNETISM_EDGE_RIGHT then the right edge of the source should snap to to the
143 // left edge of the target. |secondary_edge| indicates one of the edges along
144 // the opposite axis should should also be aligned. For example, if
145 // |primary_edge| is MAGNETISM_EDGE_RIGHT and |secondary_edge| is
146 // SECONDARY_MAGNETISM_EDGE_LEADING then the source should snap to the left top
147 // corner of the target.
148 struct MatchedEdge {
149   MagnetismEdge primary_edge;
150   SecondaryMagnetismEdge secondary_edge;
151 };
152 
153 // MagnetismMatcher is used to test if a window should snap to another window.
154 // To use MagnetismMatcher do the following:
155 // . Create it with the bounds of the window being dragged.
156 // . Iterate over the child windows checking if the window being dragged should
157 //   attach to it using ShouldAttach().
158 // . Use AreEdgesObscured() to test if no other windows can match (because all
159 //   edges are completely obscured).
160 class ASH_EXPORT MagnetismMatcher {
161  public:
162   static const int kMagneticDistance;
163 
164   // |edges| is a bitmask of MagnetismEdges to match against.
165   MagnetismMatcher(const gfx::Rect& bounds, uint32_t edges);
166   ~MagnetismMatcher();
167 
168   // Returns true if |bounds| is close enough to the initial bounds that the two
169   // should be attached. If true is returned |edge| is set to indicates how the
170   // two should snap together. See description of MatchedEdge for details.
171   bool ShouldAttach(const gfx::Rect& bounds, MatchedEdge* edge);
172 
173   // Returns true if no other matches are possible.
174   bool AreEdgesObscured() const;
175 
176  private:
177   // Sets |secondary_edge| based on whether the secondary edges should snap.
178   void AttachToSecondaryEdge(const gfx::Rect& bounds,
179                              MagnetismEdge edge,
180                              SecondaryMagnetismEdge* secondary_edge) const;
181 
182   // The edges to match against.
183   const int32_t edges_;
184 
185   std::vector<std::unique_ptr<MagnetismEdgeMatcher>> matchers_;
186 
187   DISALLOW_COPY_AND_ASSIGN(MagnetismMatcher);
188 };
189 
190 }  // namespace ash
191 
192 #endif  // ASH_WM_WORKSPACE_MAGNETISM_MATCHER_H_
193