Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(299)

Side by Side Diff: sky/engine/core/page/TouchAdjustment.cpp

Issue 774053003: Remove TouchAdjustment. (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « sky/engine/core/page/TouchAdjustment.h ('k') | sky/engine/platform/PlatformGestureEvent.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 #include "sky/engine/config.h"
21
22 #include "sky/engine/core/page/TouchAdjustment.h"
23
24 #include "sky/engine/core/dom/ContainerNode.h"
25 #include "sky/engine/core/dom/Node.h"
26 #include "sky/engine/core/dom/NodeRenderStyle.h"
27 #include "sky/engine/core/dom/Text.h"
28 #include "sky/engine/core/editing/Editor.h"
29 #include "sky/engine/core/frame/FrameView.h"
30 #include "sky/engine/core/frame/LocalFrame.h"
31 #include "sky/engine/core/rendering/RenderBox.h"
32 #include "sky/engine/core/rendering/RenderObject.h"
33 #include "sky/engine/core/rendering/RenderText.h"
34 #include "sky/engine/core/rendering/style/RenderStyle.h"
35 #include "sky/engine/platform/geometry/FloatPoint.h"
36 #include "sky/engine/platform/geometry/FloatQuad.h"
37 #include "sky/engine/platform/geometry/IntSize.h"
38 #include "sky/engine/platform/text/TextBreakIterator.h"
39
40 namespace blink {
41
42 namespace TouchAdjustment {
43
44 const float zeroTolerance = 1e-6f;
45
46 // Class for remembering absolute quads of a target node and what node they repr esent.
47 class SubtargetGeometry {
48 ALLOW_ONLY_INLINE_ALLOCATION();
49 public:
50 SubtargetGeometry(Node* node, const FloatQuad& quad)
51 : m_node(node)
52 , m_quad(quad)
53 { }
54
55 Node* node() const { return m_node; }
56 FloatQuad quad() const { return m_quad; }
57 IntRect boundingBox() const { return m_quad.enclosingBoundingBox(); }
58
59 private:
60 RawPtr<Node> m_node;
61 FloatQuad m_quad;
62 };
63
64 }
65
66 }
67
68 WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(blink::TouchAdjustment::Subta rgetGeometry)
69
70 namespace blink {
71
72 namespace TouchAdjustment {
73
74 typedef Vector<SubtargetGeometry> SubtargetGeometryList;
75 typedef bool (*NodeFilter)(Node*);
76 typedef void (*AppendSubtargetsForNode)(Node*, SubtargetGeometryList&);
77 typedef float (*DistanceFunction)(const IntPoint&, const IntRect&, const Subtarg etGeometry&);
78
79 // Takes non-const Node* because isContentEditable is a non-const function.
80 bool nodeRespondsToTapGesture(Node* node)
81 {
82 if (node->willRespondToMouseClickEvents() || node->willRespondToMouseMoveEve nts())
83 return true;
84 if (node->isElementNode()) {
85 Element* element = toElement(node);
86 // Tapping on a text field or other focusable item should trigger adjust ment, except
87 // that iframe elements are hard-coded to support focus but the effect i s often invisible
88 // so they should be excluded.
89 if (element->isMouseFocusable())
90 return true;
91 }
92 if (RenderStyle* renderStyle = node->renderStyle()) {
93 if (renderStyle->affectedByActive() || renderStyle->affectedByHover())
94 return true;
95 }
96 return false;
97 }
98
99 static inline void appendQuadsToSubtargetList(Vector<FloatQuad>& quads, Node* no de, SubtargetGeometryList& subtargets)
100 {
101 Vector<FloatQuad>::const_iterator it = quads.begin();
102 const Vector<FloatQuad>::const_iterator end = quads.end();
103 for (; it != end; ++it)
104 subtargets.append(SubtargetGeometry(node, *it));
105 }
106
107 static inline void appendBasicSubtargetsForNode(Node* node, SubtargetGeometryLis t& subtargets)
108 {
109 // Node guaranteed to have renderer due to check in node filter.
110 ASSERT(node->renderer());
111
112 Vector<FloatQuad> quads;
113 node->renderer()->absoluteQuads(quads);
114
115 appendQuadsToSubtargetList(quads, node, subtargets);
116 }
117
118 // Compiles a list of subtargets of all the relevant target nodes.
119 void compileSubtargetList(const Vector<RefPtr<Node> >& intersectedNodes, Subtarg etGeometryList& subtargets, NodeFilter nodeFilter, AppendSubtargetsForNode appen dSubtargetsForNode)
120 {
121 // Find candidates responding to tap gesture events in O(n) time.
122 HashMap<RawPtr<Node>, RawPtr<Node> > responderMap;
123 HashSet<RawPtr<Node> > ancestorsToRespondersSet;
124 Vector<RawPtr<Node> > candidates;
125 HashSet<RawPtr<Node> > editableAncestors;
126
127 // A node matching the NodeFilter is called a responder. Candidate nodes mus t either be a
128 // responder or have an ancestor that is a responder.
129 // This iteration tests all ancestors at most once by caching earlier result s.
130 for (unsigned i = 0; i < intersectedNodes.size(); ++i) {
131 Node* node = intersectedNodes[i].get();
132 Vector<RawPtr<Node> > visitedNodes;
133 Node* respondingNode = 0;
134 for (Node* visitedNode = node; visitedNode; visitedNode = visitedNode->p arentOrShadowHostNode()) {
135 // Check if we already have a result for a common ancestor from anot her candidate.
136 respondingNode = responderMap.get(visitedNode);
137 if (respondingNode)
138 break;
139 visitedNodes.append(visitedNode);
140 // Check if the node filter applies, which would mean we have found a responding node.
141 if (nodeFilter(visitedNode)) {
142 respondingNode = visitedNode;
143 // Continue the iteration to collect the ancestors of the respon der, which we will need later.
144 for (visitedNode = visitedNode->parentOrShadowHostNode(); visite dNode; visitedNode = visitedNode->parentOrShadowHostNode()) {
145 HashSet<RawPtr<Node> >::AddResult addResult = ancestorsToRes pondersSet.add(visitedNode);
146 if (!addResult.isNewEntry)
147 break;
148 }
149 break;
150 }
151 }
152 // Insert the detected responder for all the visited nodes.
153 for (unsigned j = 0; j < visitedNodes.size(); j++)
154 responderMap.add(visitedNodes[j], respondingNode);
155
156 if (respondingNode)
157 candidates.append(node);
158 }
159
160 // We compile the list of component absolute quads instead of using the boun ding rect
161 // to be able to perform better hit-testing on inline links on line-breaks.
162 for (unsigned i = 0; i < candidates.size(); i++) {
163 Node* candidate = candidates[i];
164 // Skip nodes who's responders are ancestors of other responders. This g ives preference to
165 // the inner-most event-handlers. So that a link is always preferred eve n when contained
166 // in an element that monitors all click-events.
167 Node* respondingNode = responderMap.get(candidate);
168 ASSERT(respondingNode);
169 if (ancestorsToRespondersSet.contains(respondingNode))
170 continue;
171 // Consolidate bounds for editable content.
172 if (editableAncestors.contains(candidate))
173 continue;
174 if (candidate->isContentEditable()) {
175 Node* replacement = candidate;
176 Node* parent = candidate->parentOrShadowHostNode();
177 while (parent && parent->isContentEditable()) {
178 replacement = parent;
179 if (editableAncestors.contains(replacement)) {
180 replacement = 0;
181 break;
182 }
183 editableAncestors.add(replacement);
184 parent = parent->parentOrShadowHostNode();
185 }
186 candidate = replacement;
187 }
188 if (candidate)
189 appendSubtargetsForNode(candidate, subtargets);
190 }
191 }
192
193 // Uses a hybrid of distance to adjust and intersect ratio, normalizing each sco re between 0 and 1
194 // and combining them. The distance to adjust works best for disambiguating clic ks on targets such
195 // as links, where the width may be significantly larger than the touch width. U sing area of overlap
196 // in such cases can lead to a bias towards shorter links. Conversely, percentag e of overlap can
197 // provide strong confidence in tapping on a small target, where the overlap is often quite high,
198 // and works well for tightly packed controls.
199 float hybridDistanceFunction(const IntPoint& touchHotspot, const IntRect& touchR ect, const SubtargetGeometry& subtarget)
200 {
201 IntRect rect = subtarget.boundingBox();
202
203 // Convert from frame coordinates to window coordinates.
204 rect = subtarget.node()->document().view()->contentsToWindow(rect);
205
206 float radiusSquared = 0.25f * (touchRect.size().diagonalLengthSquared());
207 float distanceToAdjustScore = rect.distanceSquaredToPoint(touchHotspot) / ra diusSquared;
208
209 int maxOverlapWidth = std::min(touchRect.width(), rect.width());
210 int maxOverlapHeight = std::min(touchRect.height(), rect.height());
211 float maxOverlapArea = std::max(maxOverlapWidth * maxOverlapHeight, 1);
212 rect.intersect(touchRect);
213 float intersectArea = rect.size().area();
214 float intersectionScore = 1 - intersectArea / maxOverlapArea;
215
216 float hybridScore = intersectionScore + distanceToAdjustScore;
217
218 return hybridScore;
219 }
220
221 FloatPoint contentsToWindow(FrameView *view, FloatPoint pt)
222 {
223 int x = static_cast<int>(pt.x() + 0.5f);
224 int y = static_cast<int>(pt.y() + 0.5f);
225 IntPoint adjusted = view->contentsToWindow(IntPoint(x, y));
226 return FloatPoint(adjusted.x(), adjusted.y());
227 }
228
229 // Adjusts 'point' to the nearest point inside rect, and leaves it unchanged if already inside.
230 void adjustPointToRect(FloatPoint& point, const FloatRect& rect)
231 {
232 if (point.x() < rect.x())
233 point.setX(rect.x());
234 else if (point.x() > rect.maxX())
235 point.setX(rect.maxX());
236
237 if (point.y() < rect.y())
238 point.setY(rect.y());
239 else if (point.y() > rect.maxY())
240 point.setY(rect.maxY());
241 }
242
243 bool snapTo(const SubtargetGeometry& geom, const IntPoint& touchPoint, const Int Rect& touchArea, IntPoint& adjustedPoint)
244 {
245 FrameView* view = geom.node()->document().view();
246 FloatQuad quad = geom.quad();
247
248 if (quad.isRectilinear()) {
249 IntRect contentBounds = geom.boundingBox();
250 // Convert from frame coordinates to window coordinates.
251 IntRect bounds = view->contentsToWindow(contentBounds);
252 if (bounds.contains(touchPoint)) {
253 adjustedPoint = touchPoint;
254 return true;
255 }
256 if (bounds.intersects(touchArea)) {
257 bounds.intersect(touchArea);
258 adjustedPoint = bounds.center();
259 return true;
260 }
261 return false;
262 }
263
264 // The following code tries to adjust the point to place inside a both the t ouchArea and the non-rectilinear quad.
265 // FIXME: This will return the point inside the touch area that is the close st to the quad center, but does not
266 // guarantee that the point will be inside the quad. Corner-cases exist wher e the quad will intersect but this
267 // will fail to adjust the point to somewhere in the intersection.
268
269 // Convert quad from content to window coordinates.
270 FloatPoint p1 = contentsToWindow(view, quad.p1());
271 FloatPoint p2 = contentsToWindow(view, quad.p2());
272 FloatPoint p3 = contentsToWindow(view, quad.p3());
273 FloatPoint p4 = contentsToWindow(view, quad.p4());
274 quad = FloatQuad(p1, p2, p3, p4);
275
276 if (quad.containsPoint(touchPoint)) {
277 adjustedPoint = touchPoint;
278 return true;
279 }
280
281 // Pull point towards the center of the element.
282 FloatPoint center = quad.center();
283
284 adjustPointToRect(center, touchArea);
285 adjustedPoint = roundedIntPoint(center);
286
287 return quad.containsPoint(adjustedPoint);
288 }
289
290 // A generic function for finding the target node with the lowest distance metri c. A distance metric here is the result
291 // of a distance-like function, that computes how well the touch hits the node.
292 // Distance functions could for instance be distance squared or area of intersec tion.
293 bool findNodeWithLowestDistanceMetric(Node*& targetNode, IntPoint& targetPoint, IntRect& targetArea, const IntPoint& touchHotspot, const IntRect& touchArea, Sub targetGeometryList& subtargets, DistanceFunction distanceFunction)
294 {
295 targetNode = 0;
296 float bestDistanceMetric = std::numeric_limits<float>::infinity();
297 SubtargetGeometryList::const_iterator it = subtargets.begin();
298 const SubtargetGeometryList::const_iterator end = subtargets.end();
299 IntPoint adjustedPoint;
300
301 for (; it != end; ++it) {
302 Node* node = it->node();
303 float distanceMetric = distanceFunction(touchHotspot, touchArea, *it);
304 if (distanceMetric < bestDistanceMetric) {
305 if (snapTo(*it, touchHotspot, touchArea, adjustedPoint)) {
306 targetPoint = adjustedPoint;
307 targetArea = it->boundingBox();
308 targetNode = node;
309 bestDistanceMetric = distanceMetric;
310 }
311 } else if (distanceMetric - bestDistanceMetric < zeroTolerance) {
312 if (snapTo(*it, touchHotspot, touchArea, adjustedPoint)) {
313 if (node->isDescendantOf(targetNode)) {
314 // Try to always return the inner-most element.
315 targetPoint = adjustedPoint;
316 targetNode = node;
317 targetArea = it->boundingBox();
318 }
319 }
320 }
321 }
322
323 if (targetNode) {
324 targetArea = targetNode->document().view()->contentsToWindow(targetArea) ;
325 }
326 return (targetNode);
327 }
328
329 } // namespace TouchAdjustment
330
331 bool findBestClickableCandidate(Node*& targetNode, IntPoint& targetPoint, const IntPoint& touchHotspot, const IntRect& touchArea, const Vector<RefPtr<Node> >& n odes)
332 {
333 IntRect targetArea;
334 TouchAdjustment::SubtargetGeometryList subtargets;
335 TouchAdjustment::compileSubtargetList(nodes, subtargets, TouchAdjustment::no deRespondsToTapGesture, TouchAdjustment::appendBasicSubtargetsForNode);
336 return TouchAdjustment::findNodeWithLowestDistanceMetric(targetNode, targetP oint, targetArea, touchHotspot, touchArea, subtargets, TouchAdjustment::hybridDi stanceFunction);
337 }
338
339 } // namespace blink
OLDNEW
« no previous file with comments | « sky/engine/core/page/TouchAdjustment.h ('k') | sky/engine/platform/PlatformGestureEvent.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698