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-impl.h"
\r
27 // INTERNAL INCLUDES
\r
28 #include <dali/devel-api/actors/actor-devel.h>
\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
39 static int MajorAxisDistanceRaw(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)
\r
43 case Dali::Toolkit::Control::KeyboardFocus::LEFT:
\r
45 return source.left - dest.right;
\r
47 case Dali::Toolkit::Control::KeyboardFocus::RIGHT:
\r
49 return dest.left - source.right;
\r
51 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
53 return source.top - dest.bottom;
\r
55 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
57 return dest.top - source.bottom;
\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
71 static int MajorAxisDistance(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)
\r
73 return std::max(0, MajorAxisDistanceRaw(direction, source, dest));
\r
76 static int MajorAxisDistanceToFarEdgeRaw(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)
\r
80 case Dali::Toolkit::Control::KeyboardFocus::LEFT:
\r
82 return source.left - dest.left;
\r
84 case Dali::Toolkit::Control::KeyboardFocus::RIGHT:
\r
86 return dest.right - source.right;
\r
88 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
90 return source.top - dest.top;
\r
92 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
94 return dest.bottom - source.bottom;
\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
108 static int MajorAxisDistanceToFarEdge(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)
\r
110 return std::max(1, MajorAxisDistanceToFarEdgeRaw(direction, source, dest));
\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
121 static int MinorAxisDistance(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)
\r
125 case Dali::Toolkit::Control::KeyboardFocus::LEFT:
\r
126 case Dali::Toolkit::Control::KeyboardFocus::RIGHT:
\r
128 // the distance between the center verticals
\r
130 (((source.top + source.bottom) * 0.5f) -
\r
131 (((dest.top + dest.bottom) * 0.5f))));
\r
133 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
134 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
136 // the distance between the center horizontals
\r
138 (((source.left + source.right) * 0.5f) -
\r
139 (((dest.left + dest.right) * 0.5f))));
\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
154 static int GetWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance)
\r
156 return 13 * majorAxisDistance * majorAxisDistance + minorAxisDistance * minorAxisDistance;
\r
160 * Convert x,y,width,height coordinates into left, right, bottom, top coordinates.
\r
161 * @param[in,out] rect The rect
\r
163 static void ConvertCoordinate(Dali::Rect<float>& rect)
\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
172 rect.right = right;
\r
173 rect.bottom = bottom;
\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
184 static bool IsCandidate(Dali::Rect<float> srcRect, Dali::Rect<float> destRect, Dali::Toolkit::Control::KeyboardFocus::Direction direction)
\r
188 case Dali::Toolkit::Control::KeyboardFocus::LEFT:
\r
190 return (srcRect.right > destRect.right || srcRect.left >= destRect.right) && srcRect.left > destRect.left;
\r
192 case Dali::Toolkit::Control::KeyboardFocus::RIGHT:
\r
194 return (srcRect.left < destRect.left || srcRect.right <= destRect.left) && srcRect.right < destRect.right;
\r
196 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
198 return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom) && srcRect.top > destRect.top;
\r
200 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
202 return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top) && srcRect.bottom < destRect.bottom;
\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
218 static bool IsToDirectionOf(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> src, Dali::Rect<float> dest)
\r
222 case Dali::Toolkit::Control::KeyboardFocus::LEFT:
\r
224 return src.left >= dest.right;
\r
226 case Dali::Toolkit::Control::KeyboardFocus::RIGHT:
\r
228 return src.right <= dest.left;
\r
230 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
232 return src.top >= dest.bottom;
\r
234 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
236 return src.bottom <= dest.top;
\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
252 static bool BeamsOverlap(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> rect1, Dali::Rect<float> rect2)
\r
256 case Dali::Toolkit::Control::KeyboardFocus::LEFT:
\r
257 case Dali::Toolkit::Control::KeyboardFocus::RIGHT:
\r
259 return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom);
\r
261 case Dali::Toolkit::Control::KeyboardFocus::UP:
\r
262 case Dali::Toolkit::Control::KeyboardFocus::DOWN:
\r
264 return (rect2.right >= rect1.left) && (rect2.left <= rect1.right);
\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
281 static bool BeamBeats(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> rect1, Dali::Rect<float> rect2)
\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
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
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
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
311 } // unnamed namespace
\r
319 FocusFinder::FocusFinder()
\r
323 FocusFinder::~FocusFinder()
\r
327 Actor FocusFinder::GetNearestFocusableActor(Actor& focusedActor, Toolkit::Control::KeyboardFocus::Direction direction)
\r
329 Actor nearestActor;
\r
332 return nearestActor;
\r
335 Rect<float> focusedRect = DevelActor::CalculateScreenExtents(focusedActor);
\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
342 case Toolkit::Control::KeyboardFocus::LEFT:
\r
344 bestCandidateRect.x += 1;
\r
347 case Toolkit::Control::KeyboardFocus::RIGHT:
\r
349 bestCandidateRect.x -= 1;
\r
352 case Toolkit::Control::KeyboardFocus::UP:
\r
354 bestCandidateRect.y += 1;
\r
357 case Toolkit::Control::KeyboardFocus::DOWN:
\r
359 bestCandidateRect.y -= 1;
\r
368 ConvertCoordinate(bestCandidateRect);
\r
370 ConvertCoordinate(focusedRect);
\r
372 Integration::SceneHolder window = Integration::SceneHolder::Get(focusedActor);
\r
375 Actor rootActor = window.GetRootLayer();
\r
376 nearestActor = FindNextFocus(rootActor, focusedActor, focusedRect, bestCandidateRect, direction);
\r
378 return nearestActor;
\r
381 Actor FocusFinder::FindNextFocus(Actor& actor, Actor& focusedActor, Rect<float>& focusedRect, Rect<float>& bestCandidateRect, Toolkit::Control::KeyboardFocus::Direction direction)
\r
383 Actor nearestActor;
\r
386 // Recursively children
\r
387 const auto childCount = actor.GetChildCount();
\r
388 for(auto i = 0u; i < childCount; ++i)
\r
390 Dali::Actor child = actor.GetChildAt(i);
\r
391 if(child && child != focusedActor && child.GetProperty<bool>(Actor::Property::KEYBOARD_FOCUSABLE))
\r
393 Rect<float> candidateRect = DevelActor::CalculateScreenExtents(child);
\r
395 // convert x, y, width, height -> left, right, bottom, top
\r
396 ConvertCoordinate(candidateRect);
\r
398 if(IsBetterCandidate(direction, focusedRect, candidateRect, bestCandidateRect))
\r
400 bestCandidateRect = candidateRect;
\r
401 nearestActor = child;
\r
404 Actor nextActor = FindNextFocus(child, focusedActor, focusedRect, bestCandidateRect, direction);
\r
407 nearestActor = nextActor;
\r
411 return nearestActor;
\r
414 bool FocusFinder::IsBetterCandidate(Toolkit::Control::KeyboardFocus::Direction direction, Rect<float>& focusedRect, Rect<float>& candidateRect, Rect<float>& bestCandidateRect) const
\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
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
427 // if candidateRect is better by beam, it wins
\r
428 if(BeamBeats(direction, focusedRect, candidateRect, bestCandidateRect))
\r
432 // if bestCandidateRect is better, then candidateRect cant' be :)
\r
433 if(BeamBeats(direction, focusedRect, bestCandidateRect, candidateRect))
\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
445 } // namespace Internal
\r
447 } // namespace Toolkit
\r
449 } // namespace Dali
\r