Implement FocusFinder
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / focus-manager / focus-finder-impl.cpp
1 /*\r
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.\r
3  *\r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  * http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  *\r
16  */\r
17 \r
18 /*\r
19  * Copyright (C) 2017 The Android Open Source Project\r
20  *\r
21  * Modified by joogab yun(joogab.yun@samsung.com)\r
22  */\r
23 \r
24 // CLASS HEADER\r
25 #include "focus-finder-impl.h"\r
26 \r
27 // INTERNAL INCLUDES\r
28 #include <dali/devel-api/actors/actor-devel.h>\r
29 \r
30 // EXTERNAL INCLUDES\r
31 #include <dali/integration-api/adaptor-framework/adaptor.h>\r
32 #include <dali/integration-api/adaptor-framework/scene-holder.h>\r
33 #include <dali/integration-api/debug.h>\r
34 #include <dali/public-api/actors/layer.h>\r
35 #include <math.h>\r
36 \r
37 namespace\r
38 {\r
39 static int MajorAxisDistanceRaw(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)\r
40 {\r
41   switch(direction)\r
42   {\r
43     case Dali::Toolkit::Control::KeyboardFocus::LEFT:\r
44     {\r
45       return source.left - dest.right;\r
46     }\r
47     case Dali::Toolkit::Control::KeyboardFocus::RIGHT:\r
48     {\r
49       return dest.left - source.right;\r
50     }\r
51     case Dali::Toolkit::Control::KeyboardFocus::UP:\r
52     {\r
53       return source.top - dest.bottom;\r
54     }\r
55     case Dali::Toolkit::Control::KeyboardFocus::DOWN:\r
56     {\r
57       return dest.top - source.bottom;\r
58     }\r
59     default:\r
60     {\r
61       return 0;\r
62     }\r
63   }\r
64 }\r
65 \r
66 /**\r
67  * @return The distance from the edge furthest in the given direction\r
68  *   of source to the edge nearest in the given direction of dest.\r
69  *   If the dest is not in the direction from source, return 0.\r
70  */\r
71 static int MajorAxisDistance(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)\r
72 {\r
73   return std::max(0, MajorAxisDistanceRaw(direction, source, dest));\r
74 }\r
75 \r
76 static int MajorAxisDistanceToFarEdgeRaw(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)\r
77 {\r
78   switch(direction)\r
79   {\r
80     case Dali::Toolkit::Control::KeyboardFocus::LEFT:\r
81     {\r
82       return source.left - dest.left;\r
83     }\r
84     case Dali::Toolkit::Control::KeyboardFocus::RIGHT:\r
85     {\r
86       return dest.right - source.right;\r
87     }\r
88     case Dali::Toolkit::Control::KeyboardFocus::UP:\r
89     {\r
90       return source.top - dest.top;\r
91     }\r
92     case Dali::Toolkit::Control::KeyboardFocus::DOWN:\r
93     {\r
94       return dest.bottom - source.bottom;\r
95     }\r
96     default:\r
97     {\r
98       return 0;\r
99     }\r
100   }\r
101 }\r
102 \r
103 /**\r
104  * @return The distance along the major axis w.r.t the direction from the\r
105  *   edge of source to the far edge of dest.\r
106  *   If the dest is not in the direction from source, return 1\r
107  */\r
108 static int MajorAxisDistanceToFarEdge(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)\r
109 {\r
110   return std::max(1, MajorAxisDistanceToFarEdgeRaw(direction, source, dest));\r
111 }\r
112 \r
113 /**\r
114  * Find the distance on the minor axis w.r.t the direction to the nearest\r
115  * edge of the destination rectangle.\r
116  * @param direction the direction (up, down, left, right)\r
117  * @param source The source rect.\r
118  * @param dest The destination rect.\r
119  * @return The distance.\r
120  */\r
121 static int MinorAxisDistance(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)\r
122 {\r
123   switch(direction)\r
124   {\r
125     case Dali::Toolkit::Control::KeyboardFocus::LEFT:\r
126     case Dali::Toolkit::Control::KeyboardFocus::RIGHT:\r
127     {\r
128       // the distance between the center verticals\r
129       return std::abs(\r
130         (((source.top + source.bottom) * 0.5f) -\r
131          (((dest.top + dest.bottom) * 0.5f))));\r
132     }\r
133     case Dali::Toolkit::Control::KeyboardFocus::UP:\r
134     case Dali::Toolkit::Control::KeyboardFocus::DOWN:\r
135     {\r
136       // the distance between the center horizontals\r
137       return std::abs(\r
138         (((source.left + source.right) * 0.5f) -\r
139          (((dest.left + dest.right) * 0.5f))));\r
140     }\r
141     default:\r
142     {\r
143       return 0;\r
144     }\r
145   }\r
146 }\r
147 \r
148 /**\r
149  * Calculate distance given major and minor axis distances.\r
150  * @param majorAxisDistance The majorAxisDistance\r
151  * @param minorAxisDistance The minorAxisDistance\r
152  * @return The distance\r
153  */\r
154 static int GetWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance)\r
155 {\r
156   return 13 * majorAxisDistance * majorAxisDistance + minorAxisDistance * minorAxisDistance;\r
157 }\r
158 \r
159 /**\r
160  * Convert x,y,width,height coordinates into left, right, bottom, top coordinates.\r
161  * @param[in,out] rect The rect\r
162  */\r
163 static void ConvertCoordinate(Dali::Rect<float>& rect)\r
164 {\r
165   // convert x, y, width, height -> left, right, bottom, top\r
166   float left   = rect.x;\r
167   float right  = rect.x + rect.width;\r
168   float bottom = rect.y + rect.height;\r
169   float top    = rect.y;\r
170 \r
171   rect.left   = left;\r
172   rect.right  = right;\r
173   rect.bottom = bottom;\r
174   rect.top    = top;\r
175 }\r
176 \r
177 /**\r
178  * Is destRect a candidate for the next focus given the direction?\r
179  * @param srcRect The source rect.\r
180  * @param destRect The dest rect.\r
181  * @param direction The direction (up, down, left, right)\r
182  * @return Whether destRect is a candidate.\r
183  */\r
184 static bool IsCandidate(Dali::Rect<float> srcRect, Dali::Rect<float> destRect, Dali::Toolkit::Control::KeyboardFocus::Direction direction)\r
185 {\r
186   switch(direction)\r
187   {\r
188     case Dali::Toolkit::Control::KeyboardFocus::LEFT:\r
189     {\r
190       return (srcRect.right > destRect.right || srcRect.left >= destRect.right) && srcRect.left > destRect.left;\r
191     }\r
192     case Dali::Toolkit::Control::KeyboardFocus::RIGHT:\r
193     {\r
194       return (srcRect.left < destRect.left || srcRect.right <= destRect.left) && srcRect.right < destRect.right;\r
195     }\r
196     case Dali::Toolkit::Control::KeyboardFocus::UP:\r
197     {\r
198       return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom) && srcRect.top > destRect.top;\r
199     }\r
200     case Dali::Toolkit::Control::KeyboardFocus::DOWN:\r
201     {\r
202       return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top) && srcRect.bottom < destRect.bottom;\r
203     }\r
204     default:\r
205     {\r
206       return false;\r
207     }\r
208   }\r
209   return false;\r
210 }\r
211 \r
212 /**\r
213  * Is dest in a given direction from src?\r
214  * @param direction the direction (up, down, left, right)\r
215  * @param src The source rect\r
216  * @param dest The dest rect\r
217  */\r
218 static bool IsToDirectionOf(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> src, Dali::Rect<float> dest)\r
219 {\r
220   switch(direction)\r
221   {\r
222     case Dali::Toolkit::Control::KeyboardFocus::LEFT:\r
223     {\r
224       return src.left >= dest.right;\r
225     }\r
226     case Dali::Toolkit::Control::KeyboardFocus::RIGHT:\r
227     {\r
228       return src.right <= dest.left;\r
229     }\r
230     case Dali::Toolkit::Control::KeyboardFocus::UP:\r
231     {\r
232       return src.top >= dest.bottom;\r
233     }\r
234     case Dali::Toolkit::Control::KeyboardFocus::DOWN:\r
235     {\r
236       return src.bottom <= dest.top;\r
237     }\r
238     default:\r
239     {\r
240       return false;\r
241     }\r
242   }\r
243 }\r
244 \r
245 /**\r
246  * Do the given direction's axis of rect1 and rect2 overlap?\r
247  * @param direction the direction (up, down, left, right)\r
248  * @param rect1 The first rect\r
249  * @param rect2 The second rect\r
250  * @return whether the beams overlap\r
251  */\r
252 static bool BeamsOverlap(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> rect1, Dali::Rect<float> rect2)\r
253 {\r
254   switch(direction)\r
255   {\r
256     case Dali::Toolkit::Control::KeyboardFocus::LEFT:\r
257     case Dali::Toolkit::Control::KeyboardFocus::RIGHT:\r
258     {\r
259       return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom);\r
260     }\r
261     case Dali::Toolkit::Control::KeyboardFocus::UP:\r
262     case Dali::Toolkit::Control::KeyboardFocus::DOWN:\r
263     {\r
264       return (rect2.right >= rect1.left) && (rect2.left <= rect1.right);\r
265     }\r
266     default:\r
267     {\r
268       return false;\r
269     }\r
270   }\r
271 }\r
272 \r
273 /**\r
274  * One rectangle may be another candidate than another by virtue of being exclusively in the beam of the source rect.\r
275  * @param direction The direction (up, down, left, right)\r
276  * @param source The source rect\r
277  * @param rect1 The first rect\r
278  * @param rect2 The second rect\r
279  * @return Whether rect1 is a better candidate than rect2 by virtue of it being in src's beam\r
280  */\r
281 static bool BeamBeats(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> rect1, Dali::Rect<float> rect2)\r
282 {\r
283   const bool rect1InSrcBeam = BeamsOverlap(direction, source, rect1);\r
284   const bool rect2InSrcBeam = BeamsOverlap(direction, source, rect2);\r
285   // if rect1 isn't exclusively in the src beam, it doesn't win\r
286   if(rect2InSrcBeam || !rect1InSrcBeam)\r
287   {\r
288     return false;\r
289   }\r
290   // we know rect1 is in the beam, and rect2 is not\r
291   // if rect1 is to the direction of, and rect2 is not, rect1 wins.\r
292   // for example, for direction left, if rect1 is to the left of the source\r
293   // and rect2 is below, then we always prefer the in beam rect1, since rect2\r
294   // could be reached by going down.\r
295   if(!IsToDirectionOf(direction, source, rect2))\r
296   {\r
297     return true;\r
298   }\r
299   // for horizontal directions, being exclusively in beam always wins\r
300   if((direction == Dali::Toolkit::Control::KeyboardFocus::LEFT || direction == Dali::Toolkit::Control::KeyboardFocus::RIGHT))\r
301   {\r
302     return true;\r
303   }\r
304   // for vertical directions, beams only beat up to a point:\r
305   // now, as long as rect2 isn't completely closer, rect1 wins\r
306   // e.g for direction down, completely closer means for rect2's top\r
307   // edge to be closer to the source's top edge than rect1's bottom edge.\r
308   return (MajorAxisDistance(direction, source, rect1) < MajorAxisDistanceToFarEdge(direction, source, rect2));\r
309 }\r
310 \r
311 } // unnamed namespace\r
312 \r
313 namespace Dali\r
314 {\r
315 namespace Toolkit\r
316 {\r
317 namespace Internal\r
318 {\r
319 FocusFinder::FocusFinder()\r
320 {\r
321 }\r
322 \r
323 FocusFinder::~FocusFinder()\r
324 {\r
325 }\r
326 \r
327 Actor FocusFinder::GetNearestFocusableActor(Actor& focusedActor, Toolkit::Control::KeyboardFocus::Direction direction)\r
328 {\r
329   Actor nearestActor;\r
330   if(!focusedActor)\r
331   {\r
332     return nearestActor;\r
333   }\r
334 \r
335   Rect<float> focusedRect = DevelActor::CalculateScreenExtents(focusedActor);\r
336 \r
337   // initialize the best candidate to something impossible\r
338   // (so the first plausible actor will become the best choice)\r
339   Rect<float> bestCandidateRect = focusedRect;\r
340   switch(direction)\r
341   {\r
342     case Toolkit::Control::KeyboardFocus::LEFT:\r
343     {\r
344       bestCandidateRect.x += 1;\r
345       break;\r
346     }\r
347     case Toolkit::Control::KeyboardFocus::RIGHT:\r
348     {\r
349       bestCandidateRect.x -= 1;\r
350       break;\r
351     }\r
352     case Toolkit::Control::KeyboardFocus::UP:\r
353     {\r
354       bestCandidateRect.y += 1;\r
355       break;\r
356     }\r
357     case Toolkit::Control::KeyboardFocus::DOWN:\r
358     {\r
359       bestCandidateRect.y -= 1;\r
360       break;\r
361     }\r
362     default:\r
363     {\r
364       break;\r
365     }\r
366   }\r
367 \r
368   ConvertCoordinate(bestCandidateRect);\r
369 \r
370   ConvertCoordinate(focusedRect);\r
371 \r
372   Integration::SceneHolder window = Integration::SceneHolder::Get(focusedActor);\r
373   if(window)\r
374   {\r
375     Actor rootActor = window.GetRootLayer();\r
376     nearestActor    = FindNextFocus(rootActor, focusedActor, focusedRect, bestCandidateRect, direction);\r
377   }\r
378   return nearestActor;\r
379 }\r
380 \r
381 Actor FocusFinder::FindNextFocus(Actor& actor, Actor& focusedActor, Rect<float>& focusedRect, Rect<float>& bestCandidateRect, Toolkit::Control::KeyboardFocus::Direction direction)\r
382 {\r
383   Actor nearestActor;\r
384   if(actor)\r
385   {\r
386     // Recursively children\r
387     const auto childCount = actor.GetChildCount();\r
388     for(auto i = 0u; i < childCount; ++i)\r
389     {\r
390       Dali::Actor child = actor.GetChildAt(i);\r
391       if(child && child != focusedActor && child.GetProperty<bool>(Actor::Property::KEYBOARD_FOCUSABLE))\r
392       {\r
393         Rect<float> candidateRect = DevelActor::CalculateScreenExtents(child);\r
394 \r
395         // convert x, y, width, height -> left, right, bottom, top\r
396         ConvertCoordinate(candidateRect);\r
397 \r
398         if(IsBetterCandidate(direction, focusedRect, candidateRect, bestCandidateRect))\r
399         {\r
400           bestCandidateRect = candidateRect;\r
401           nearestActor      = child;\r
402         }\r
403       }\r
404       Actor nextActor = FindNextFocus(child, focusedActor, focusedRect, bestCandidateRect, direction);\r
405       if(nextActor)\r
406       {\r
407         nearestActor = nextActor;\r
408       }\r
409     }\r
410   }\r
411   return nearestActor;\r
412 }\r
413 \r
414 bool FocusFinder::IsBetterCandidate(Toolkit::Control::KeyboardFocus::Direction direction, Rect<float>& focusedRect, Rect<float>& candidateRect, Rect<float>& bestCandidateRect) const\r
415 {\r
416   // to be a better candidate, need to at least be a candidate in the first place\r
417   if(!IsCandidate(focusedRect, candidateRect, direction))\r
418   {\r
419     return false;\r
420   }\r
421   // we know that candidateRect is a candidate.. if bestCandidateRect is not a candidate,\r
422   // candidateRect is better\r
423   if(!IsCandidate(focusedRect, bestCandidateRect, direction))\r
424   {\r
425     return true;\r
426   }\r
427   // if candidateRect is better by beam, it wins\r
428   if(BeamBeats(direction, focusedRect, candidateRect, bestCandidateRect))\r
429   {\r
430     return true;\r
431   }\r
432   // if bestCandidateRect is better, then candidateRect cant' be :)\r
433   if(BeamBeats(direction, focusedRect, bestCandidateRect, candidateRect))\r
434   {\r
435     return false;\r
436   }\r
437 \r
438   // otherwise, do fudge-tastic comparison of the major and minor axis\r
439   return (GetWeightedDistanceFor(\r
440             MajorAxisDistance(direction, focusedRect, candidateRect),\r
441             MinorAxisDistance(direction, focusedRect, candidateRect)) < GetWeightedDistanceFor(MajorAxisDistance(direction, focusedRect, bestCandidateRect),\r
442                                                                                                MinorAxisDistance(direction, focusedRect, bestCandidateRect)));\r
443 }\r
444 \r
445 } // namespace Internal\r
446 \r
447 } // namespace Toolkit\r
448 \r
449 } // namespace Dali\r