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
132 return std::abs((source.top + (source.bottom - source.top) * 0.5f) -
\r
133 (dest.top + (dest.bottom - dest.top) * 0.5f));
\r
135 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
136 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
138 // the distance between the center horizontals
\r
139 return std::abs((source.left + (source.right - source.left) * 0.5f) -
\r
140 (dest.left + (dest.right - dest.left) * 0.5f));
\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
155 static int GetWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance)
\r
157 return 13 * majorAxisDistance * majorAxisDistance + minorAxisDistance * minorAxisDistance;
\r
161 * Convert x,y,width,height coordinates into left, right, bottom, top coordinates.
\r
162 * @param[in,out] rect The rect
\r
164 static void ConvertCoordinate(Dali::Rect<float>& rect)
\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
173 rect.right = right;
\r
174 rect.bottom = bottom;
\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
185 static bool IsCandidate(Dali::Rect<float> srcRect, Dali::Rect<float> destRect, Dali::Toolkit::Control::KeyboardFocus::Direction direction)
\r
189 case Dali::Toolkit::Control::KeyboardFocus::LEFT:
\r
191 return (srcRect.right > destRect.right || srcRect.left >= destRect.right) && srcRect.left > destRect.left;
\r
193 case Dali::Toolkit::Control::KeyboardFocus::RIGHT:
\r
195 return (srcRect.left < destRect.left || srcRect.right <= destRect.left) && srcRect.right < destRect.right;
\r
197 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
199 return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom) && srcRect.top > destRect.top;
\r
201 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
203 return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top) && srcRect.bottom < destRect.bottom;
\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
219 static bool IsToDirectionOf(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> src, Dali::Rect<float> dest)
\r
223 case Dali::Toolkit::Control::KeyboardFocus::LEFT:
\r
225 return src.left >= dest.right;
\r
227 case Dali::Toolkit::Control::KeyboardFocus::RIGHT:
\r
229 return src.right <= dest.left;
\r
231 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
233 return src.top >= dest.bottom;
\r
235 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
237 return src.bottom <= dest.top;
\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
253 static bool BeamsOverlap(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> rect1, Dali::Rect<float> rect2)
\r
257 case Dali::Toolkit::Control::KeyboardFocus::LEFT:
\r
258 case Dali::Toolkit::Control::KeyboardFocus::RIGHT:
\r
260 return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom);
\r
262 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
263 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
265 return (rect2.right >= rect1.left) && (rect2.left <= rect1.right);
\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
282 static bool BeamBeats(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> rect1, Dali::Rect<float> rect2)
\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
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
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
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
312 bool IsBetterCandidate(Toolkit::Control::KeyboardFocus::Direction direction, Rect<float>& focusedRect, Rect<float>& candidateRect, Rect<float>& bestCandidateRect)
\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
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
325 // if candidateRect is better by beam, it wins
\r
326 if(BeamBeats(direction, focusedRect, candidateRect, bestCandidateRect))
\r
330 // if bestCandidateRect is better, then candidateRect cant' be :)
\r
331 if(BeamBeats(direction, focusedRect, bestCandidateRect, candidateRect))
\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
343 bool IsFocusable(Actor& actor)
\r
345 return (actor.GetProperty<bool>(Actor::Property::KEYBOARD_FOCUSABLE) &&
\r
346 actor.GetProperty<bool>(DevelActor::Property::USER_INTERACTION_ENABLED) &&
\r
347 actor.GetProperty<bool>(Actor::Property::VISIBLE) &&
\r
348 actor.GetProperty<Vector4>(Actor::Property::WORLD_COLOR).a > FULLY_TRANSPARENT);
\r
351 Actor FindNextFocus(Actor& actor, Actor& focusedActor, Rect<float>& focusedRect, Rect<float>& bestCandidateRect, Toolkit::Control::KeyboardFocus::Direction direction)
\r
353 Actor nearestActor;
\r
354 if(actor && actor.GetProperty<bool>(Actor::Property::VISIBLE) && actor.GetProperty<bool>(DevelActor::Property::KEYBOARD_FOCUSABLE_CHILDREN))
\r
356 // Recursively children
\r
357 const auto childCount = actor.GetChildCount();
\r
358 for(auto i = childCount; i > 0u; --i)
\r
360 Dali::Actor child = actor.GetChildAt(i-1);
\r
361 if(child && child != focusedActor && IsFocusable(child))
\r
363 Rect<float> candidateRect = DevelActor::CalculateScreenExtents(child);
\r
365 // convert x, y, width, height -> left, right, bottom, top
\r
366 ConvertCoordinate(candidateRect);
\r
368 if(IsBetterCandidate(direction, focusedRect, candidateRect, bestCandidateRect))
\r
370 bestCandidateRect = candidateRect;
\r
371 nearestActor = child;
\r
374 Actor nextActor = FindNextFocus(child, focusedActor, focusedRect, bestCandidateRect, direction);
\r
377 nearestActor = nextActor;
\r
381 return nearestActor;
\r
384 } // unnamed namespace
\r
386 Actor GetNearestFocusableActor(Actor rootActor, Actor focusedActor, Toolkit::Control::KeyboardFocus::Direction direction)
\r
388 Actor nearestActor;
\r
391 return nearestActor;
\r
394 Rect<float> focusedRect;
\r
397 // If there is no currently focused actor, it is searched based on the upper left corner of the current window.
\r
398 Rect<float> rootRect = DevelActor::CalculateScreenExtents(rootActor);
\r
399 focusedRect = Rect<float>(rootRect.x, rootRect.y, 0.f, 0.f);
\r
403 focusedRect = DevelActor::CalculateScreenExtents(focusedActor);
\r
407 // initialize the best candidate to something impossible
\r
408 // (so the first plausible actor will become the best choice)
\r
409 Rect<float> bestCandidateRect = focusedRect;
\r
412 case Toolkit::Control::KeyboardFocus::LEFT:
\r
414 bestCandidateRect.x += 1;
\r
417 case Toolkit::Control::KeyboardFocus::RIGHT:
\r
419 bestCandidateRect.x -= 1;
\r
422 case Toolkit::Control::KeyboardFocus::UP:
\r
424 bestCandidateRect.y += 1;
\r
427 case Toolkit::Control::KeyboardFocus::DOWN:
\r
429 bestCandidateRect.y -= 1;
\r
438 ConvertCoordinate(bestCandidateRect);
\r
440 ConvertCoordinate(focusedRect);
\r
441 nearestActor = FindNextFocus(rootActor, focusedActor, focusedRect, bestCandidateRect, direction);
\r
442 return nearestActor;
\r
445 } // namespace FocusFinder
\r
447 } // namespace Toolkit
\r
449 } // namespace Dali
\r