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