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