2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
\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
8 * http://www.apache.org/licenses/LICENSE-2.0
\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
19 * Copyright (C) 2017 The Android Open Source Project
\r
21 * Modified by joogab yun(joogab.yun@samsung.com)
\r
25 #include "focus-finder.h"
\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
36 namespace FocusFinder
\r
40 static constexpr float FULLY_TRANSPARENT(0.01f); ///< Alpha values must rise above this, before an object is considered to be visible.
\r
42 static int MajorAxisDistanceRaw(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)
\r
46 case Dali::Toolkit::Control::KeyboardFocus::LEFT:
\r
48 return source.left - dest.right;
\r
50 case Dali::Toolkit::Control::KeyboardFocus::RIGHT:
\r
52 return dest.left - source.right;
\r
54 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
56 return source.top - dest.bottom;
\r
58 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
60 return dest.top - source.bottom;
\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
74 static int MajorAxisDistance(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)
\r
76 return std::max(0, MajorAxisDistanceRaw(direction, source, dest));
\r
79 static int MajorAxisDistanceToFarEdgeRaw(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)
\r
83 case Dali::Toolkit::Control::KeyboardFocus::LEFT:
\r
85 return source.left - dest.left;
\r
87 case Dali::Toolkit::Control::KeyboardFocus::RIGHT:
\r
89 return dest.right - source.right;
\r
91 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
93 return source.top - dest.top;
\r
95 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
97 return dest.bottom - source.bottom;
\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
111 static int MajorAxisDistanceToFarEdge(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)
\r
113 return std::max(1, MajorAxisDistanceToFarEdgeRaw(direction, source, dest));
\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
124 static int MinorAxisDistance(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)
\r
128 case Dali::Toolkit::Control::KeyboardFocus::LEFT:
\r
129 case Dali::Toolkit::Control::KeyboardFocus::RIGHT:
\r
131 // the distance between the center verticals
\r
133 (((source.top + source.bottom) * 0.5f) -
\r
134 (((dest.top + dest.bottom) * 0.5f))));
\r
136 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
137 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
139 // the distance between the center horizontals
\r
141 (((source.left + source.right) * 0.5f) -
\r
142 (((dest.left + dest.right) * 0.5f))));
\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
157 static int GetWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance)
\r
159 return 13 * majorAxisDistance * majorAxisDistance + minorAxisDistance * minorAxisDistance;
\r
163 * Convert x,y,width,height coordinates into left, right, bottom, top coordinates.
\r
164 * @param[in,out] rect The rect
\r
166 static void ConvertCoordinate(Dali::Rect<float>& rect)
\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
175 rect.right = right;
\r
176 rect.bottom = bottom;
\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
187 static bool IsCandidate(Dali::Rect<float> srcRect, Dali::Rect<float> destRect, Dali::Toolkit::Control::KeyboardFocus::Direction direction)
\r
191 case Dali::Toolkit::Control::KeyboardFocus::LEFT:
\r
193 return (srcRect.right > destRect.right || srcRect.left >= destRect.right) && srcRect.left > destRect.left;
\r
195 case Dali::Toolkit::Control::KeyboardFocus::RIGHT:
\r
197 return (srcRect.left < destRect.left || srcRect.right <= destRect.left) && srcRect.right < destRect.right;
\r
199 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
201 return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom) && srcRect.top > destRect.top;
\r
203 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
205 return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top) && srcRect.bottom < destRect.bottom;
\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
221 static bool IsToDirectionOf(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> src, Dali::Rect<float> dest)
\r
225 case Dali::Toolkit::Control::KeyboardFocus::LEFT:
\r
227 return src.left >= dest.right;
\r
229 case Dali::Toolkit::Control::KeyboardFocus::RIGHT:
\r
231 return src.right <= dest.left;
\r
233 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
235 return src.top >= dest.bottom;
\r
237 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
239 return src.bottom <= dest.top;
\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
255 static bool BeamsOverlap(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> rect1, Dali::Rect<float> rect2)
\r
259 case Dali::Toolkit::Control::KeyboardFocus::LEFT:
\r
260 case Dali::Toolkit::Control::KeyboardFocus::RIGHT:
\r
262 return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom);
\r
264 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
265 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
267 return (rect2.right >= rect1.left) && (rect2.left <= rect1.right);
\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
284 static bool BeamBeats(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> rect1, Dali::Rect<float> rect2)
\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
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
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
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
314 bool IsBetterCandidate(Toolkit::Control::KeyboardFocus::Direction direction, Rect<float>& focusedRect, Rect<float>& candidateRect, Rect<float>& bestCandidateRect)
\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
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
327 // if candidateRect is better by beam, it wins
\r
328 if(BeamBeats(direction, focusedRect, candidateRect, bestCandidateRect))
\r
332 // if bestCandidateRect is better, then candidateRect cant' be :)
\r
333 if(BeamBeats(direction, focusedRect, bestCandidateRect, candidateRect))
\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
345 bool IsFocusable(Actor& actor)
\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
353 Actor FindNextFocus(Actor& actor, Actor& focusedActor, Rect<float>& focusedRect, Rect<float>& bestCandidateRect, Toolkit::Control::KeyboardFocus::Direction direction)
\r
355 Actor nearestActor;
\r
358 // Recursively children
\r
359 const auto childCount = actor.GetChildCount();
\r
360 for(auto i = 0u; i < childCount; ++i)
\r
362 Dali::Actor child = actor.GetChildAt(i);
\r
363 if(child && child != focusedActor && IsFocusable(child))
\r
365 Rect<float> candidateRect = DevelActor::CalculateScreenExtents(child);
\r
367 // convert x, y, width, height -> left, right, bottom, top
\r
368 ConvertCoordinate(candidateRect);
\r
370 if(IsBetterCandidate(direction, focusedRect, candidateRect, bestCandidateRect))
\r
372 bestCandidateRect = candidateRect;
\r
373 nearestActor = child;
\r
376 Actor nextActor = FindNextFocus(child, focusedActor, focusedRect, bestCandidateRect, direction);
\r
379 nearestActor = nextActor;
\r
383 return nearestActor;
\r
386 } // unnamed namespace
\r
388 Actor GetNearestFocusableActor(Actor focusedActor, Toolkit::Control::KeyboardFocus::Direction direction)
\r
390 Actor nearestActor;
\r
393 return nearestActor;
\r
396 Rect<float> focusedRect = DevelActor::CalculateScreenExtents(focusedActor);
\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
403 case Toolkit::Control::KeyboardFocus::LEFT:
\r
405 bestCandidateRect.x += 1;
\r
408 case Toolkit::Control::KeyboardFocus::RIGHT:
\r
410 bestCandidateRect.x -= 1;
\r
413 case Toolkit::Control::KeyboardFocus::UP:
\r
415 bestCandidateRect.y += 1;
\r
418 case Toolkit::Control::KeyboardFocus::DOWN:
\r
420 bestCandidateRect.y -= 1;
\r
429 ConvertCoordinate(bestCandidateRect);
\r
431 ConvertCoordinate(focusedRect);
\r
433 Integration::SceneHolder window = Integration::SceneHolder::Get(focusedActor);
\r
436 Actor rootActor = window.GetRootLayer();
\r
437 nearestActor = FindNextFocus(rootActor, focusedActor, focusedRect, bestCandidateRect, direction);
\r
439 return nearestActor;
\r
442 } // namespace FocusFinder
\r
444 } // namespace Toolkit
\r
446 } // namespace Dali
\r