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