-/*\r
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- * http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- *\r
- */\r
-\r
-/*\r
- * Copyright (C) 2017 The Android Open Source Project\r
- *\r
- * Modified by joogab yun(joogab.yun@samsung.com)\r
- */\r
-\r
-// CLASS HEADER\r
-#include "focus-finder-impl.h"\r
-\r
-// INTERNAL INCLUDES\r
-#include <dali/devel-api/actors/actor-devel.h>\r
-\r
-// EXTERNAL INCLUDES\r
-#include <dali/integration-api/adaptor-framework/adaptor.h>\r
-#include <dali/integration-api/adaptor-framework/scene-holder.h>\r
-#include <dali/integration-api/debug.h>\r
-#include <dali/public-api/actors/layer.h>\r
-#include <math.h>\r
-\r
-namespace\r
-{\r
-static int MajorAxisDistanceRaw(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)\r
-{\r
- switch(direction)\r
- {\r
- case Dali::Toolkit::Control::KeyboardFocus::LEFT:\r
- {\r
- return source.left - dest.right;\r
- }\r
- case Dali::Toolkit::Control::KeyboardFocus::RIGHT:\r
- {\r
- return dest.left - source.right;\r
- }\r
- case Dali::Toolkit::Control::KeyboardFocus::UP:\r
- {\r
- return source.top - dest.bottom;\r
- }\r
- case Dali::Toolkit::Control::KeyboardFocus::DOWN:\r
- {\r
- return dest.top - source.bottom;\r
- }\r
- default:\r
- {\r
- return 0;\r
- }\r
- }\r
-}\r
-\r
-/**\r
- * @return The distance from the edge furthest in the given direction\r
- * of source to the edge nearest in the given direction of dest.\r
- * If the dest is not in the direction from source, return 0.\r
- */\r
-static int MajorAxisDistance(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)\r
-{\r
- return std::max(0, MajorAxisDistanceRaw(direction, source, dest));\r
-}\r
-\r
-static int MajorAxisDistanceToFarEdgeRaw(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)\r
-{\r
- switch(direction)\r
- {\r
- case Dali::Toolkit::Control::KeyboardFocus::LEFT:\r
- {\r
- return source.left - dest.left;\r
- }\r
- case Dali::Toolkit::Control::KeyboardFocus::RIGHT:\r
- {\r
- return dest.right - source.right;\r
- }\r
- case Dali::Toolkit::Control::KeyboardFocus::UP:\r
- {\r
- return source.top - dest.top;\r
- }\r
- case Dali::Toolkit::Control::KeyboardFocus::DOWN:\r
- {\r
- return dest.bottom - source.bottom;\r
- }\r
- default:\r
- {\r
- return 0;\r
- }\r
- }\r
-}\r
-\r
-/**\r
- * @return The distance along the major axis w.r.t the direction from the\r
- * edge of source to the far edge of dest.\r
- * If the dest is not in the direction from source, return 1\r
- */\r
-static int MajorAxisDistanceToFarEdge(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)\r
-{\r
- return std::max(1, MajorAxisDistanceToFarEdgeRaw(direction, source, dest));\r
-}\r
-\r
-/**\r
- * Find the distance on the minor axis w.r.t the direction to the nearest\r
- * edge of the destination rectangle.\r
- * @param direction the direction (up, down, left, right)\r
- * @param source The source rect.\r
- * @param dest The destination rect.\r
- * @return The distance.\r
- */\r
-static int MinorAxisDistance(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> dest)\r
-{\r
- switch(direction)\r
- {\r
- case Dali::Toolkit::Control::KeyboardFocus::LEFT:\r
- case Dali::Toolkit::Control::KeyboardFocus::RIGHT:\r
- {\r
- // the distance between the center verticals\r
- return std::abs(\r
- (((source.top + source.bottom) * 0.5f) -\r
- (((dest.top + dest.bottom) * 0.5f))));\r
- }\r
- case Dali::Toolkit::Control::KeyboardFocus::UP:\r
- case Dali::Toolkit::Control::KeyboardFocus::DOWN:\r
- {\r
- // the distance between the center horizontals\r
- return std::abs(\r
- (((source.left + source.right) * 0.5f) -\r
- (((dest.left + dest.right) * 0.5f))));\r
- }\r
- default:\r
- {\r
- return 0;\r
- }\r
- }\r
-}\r
-\r
-/**\r
- * Calculate distance given major and minor axis distances.\r
- * @param majorAxisDistance The majorAxisDistance\r
- * @param minorAxisDistance The minorAxisDistance\r
- * @return The distance\r
- */\r
-static int GetWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance)\r
-{\r
- return 13 * majorAxisDistance * majorAxisDistance + minorAxisDistance * minorAxisDistance;\r
-}\r
-\r
-/**\r
- * Convert x,y,width,height coordinates into left, right, bottom, top coordinates.\r
- * @param[in,out] rect The rect\r
- */\r
-static void ConvertCoordinate(Dali::Rect<float>& rect)\r
-{\r
- // convert x, y, width, height -> left, right, bottom, top\r
- float left = rect.x;\r
- float right = rect.x + rect.width;\r
- float bottom = rect.y + rect.height;\r
- float top = rect.y;\r
-\r
- rect.left = left;\r
- rect.right = right;\r
- rect.bottom = bottom;\r
- rect.top = top;\r
-}\r
-\r
-/**\r
- * Is destRect a candidate for the next focus given the direction?\r
- * @param srcRect The source rect.\r
- * @param destRect The dest rect.\r
- * @param direction The direction (up, down, left, right)\r
- * @return Whether destRect is a candidate.\r
- */\r
-static bool IsCandidate(Dali::Rect<float> srcRect, Dali::Rect<float> destRect, Dali::Toolkit::Control::KeyboardFocus::Direction direction)\r
-{\r
- switch(direction)\r
- {\r
- case Dali::Toolkit::Control::KeyboardFocus::LEFT:\r
- {\r
- return (srcRect.right > destRect.right || srcRect.left >= destRect.right) && srcRect.left > destRect.left;\r
- }\r
- case Dali::Toolkit::Control::KeyboardFocus::RIGHT:\r
- {\r
- return (srcRect.left < destRect.left || srcRect.right <= destRect.left) && srcRect.right < destRect.right;\r
- }\r
- case Dali::Toolkit::Control::KeyboardFocus::UP:\r
- {\r
- return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom) && srcRect.top > destRect.top;\r
- }\r
- case Dali::Toolkit::Control::KeyboardFocus::DOWN:\r
- {\r
- return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top) && srcRect.bottom < destRect.bottom;\r
- }\r
- default:\r
- {\r
- return false;\r
- }\r
- }\r
- return false;\r
-}\r
-\r
-/**\r
- * Is dest in a given direction from src?\r
- * @param direction the direction (up, down, left, right)\r
- * @param src The source rect\r
- * @param dest The dest rect\r
- */\r
-static bool IsToDirectionOf(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> src, Dali::Rect<float> dest)\r
-{\r
- switch(direction)\r
- {\r
- case Dali::Toolkit::Control::KeyboardFocus::LEFT:\r
- {\r
- return src.left >= dest.right;\r
- }\r
- case Dali::Toolkit::Control::KeyboardFocus::RIGHT:\r
- {\r
- return src.right <= dest.left;\r
- }\r
- case Dali::Toolkit::Control::KeyboardFocus::UP:\r
- {\r
- return src.top >= dest.bottom;\r
- }\r
- case Dali::Toolkit::Control::KeyboardFocus::DOWN:\r
- {\r
- return src.bottom <= dest.top;\r
- }\r
- default:\r
- {\r
- return false;\r
- }\r
- }\r
-}\r
-\r
-/**\r
- * Do the given direction's axis of rect1 and rect2 overlap?\r
- * @param direction the direction (up, down, left, right)\r
- * @param rect1 The first rect\r
- * @param rect2 The second rect\r
- * @return whether the beams overlap\r
- */\r
-static bool BeamsOverlap(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> rect1, Dali::Rect<float> rect2)\r
-{\r
- switch(direction)\r
- {\r
- case Dali::Toolkit::Control::KeyboardFocus::LEFT:\r
- case Dali::Toolkit::Control::KeyboardFocus::RIGHT:\r
- {\r
- return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom);\r
- }\r
- case Dali::Toolkit::Control::KeyboardFocus::UP:\r
- case Dali::Toolkit::Control::KeyboardFocus::DOWN:\r
- {\r
- return (rect2.right >= rect1.left) && (rect2.left <= rect1.right);\r
- }\r
- default:\r
- {\r
- return false;\r
- }\r
- }\r
-}\r
-\r
-/**\r
- * One rectangle may be another candidate than another by virtue of being exclusively in the beam of the source rect.\r
- * @param direction The direction (up, down, left, right)\r
- * @param source The source rect\r
- * @param rect1 The first rect\r
- * @param rect2 The second rect\r
- * @return Whether rect1 is a better candidate than rect2 by virtue of it being in src's beam\r
- */\r
-static bool BeamBeats(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect<float> source, Dali::Rect<float> rect1, Dali::Rect<float> rect2)\r
-{\r
- const bool rect1InSrcBeam = BeamsOverlap(direction, source, rect1);\r
- const bool rect2InSrcBeam = BeamsOverlap(direction, source, rect2);\r
- // if rect1 isn't exclusively in the src beam, it doesn't win\r
- if(rect2InSrcBeam || !rect1InSrcBeam)\r
- {\r
- return false;\r
- }\r
- // we know rect1 is in the beam, and rect2 is not\r
- // if rect1 is to the direction of, and rect2 is not, rect1 wins.\r
- // for example, for direction left, if rect1 is to the left of the source\r
- // and rect2 is below, then we always prefer the in beam rect1, since rect2\r
- // could be reached by going down.\r
- if(!IsToDirectionOf(direction, source, rect2))\r
- {\r
- return true;\r
- }\r
- // for horizontal directions, being exclusively in beam always wins\r
- if((direction == Dali::Toolkit::Control::KeyboardFocus::LEFT || direction == Dali::Toolkit::Control::KeyboardFocus::RIGHT))\r
- {\r
- return true;\r
- }\r
- // for vertical directions, beams only beat up to a point:\r
- // now, as long as rect2 isn't completely closer, rect1 wins\r
- // e.g for direction down, completely closer means for rect2's top\r
- // edge to be closer to the source's top edge than rect1's bottom edge.\r
- return (MajorAxisDistance(direction, source, rect1) < MajorAxisDistanceToFarEdge(direction, source, rect2));\r
-}\r
-\r
-} // unnamed namespace\r
-\r
-namespace Dali\r
-{\r
-namespace Toolkit\r
-{\r
-namespace Internal\r
-{\r
-FocusFinder::FocusFinder()\r
-{\r
-}\r
-\r
-FocusFinder::~FocusFinder()\r
-{\r
-}\r
-\r
-Actor FocusFinder::GetNearestFocusableActor(Actor& focusedActor, Toolkit::Control::KeyboardFocus::Direction direction)\r
-{\r
- Actor nearestActor;\r
- if(!focusedActor)\r
- {\r
- return nearestActor;\r
- }\r
-\r
- Rect<float> focusedRect = DevelActor::CalculateScreenExtents(focusedActor);\r
-\r
- // initialize the best candidate to something impossible\r
- // (so the first plausible actor will become the best choice)\r
- Rect<float> bestCandidateRect = focusedRect;\r
- switch(direction)\r
- {\r
- case Toolkit::Control::KeyboardFocus::LEFT:\r
- {\r
- bestCandidateRect.x += 1;\r
- break;\r
- }\r
- case Toolkit::Control::KeyboardFocus::RIGHT:\r
- {\r
- bestCandidateRect.x -= 1;\r
- break;\r
- }\r
- case Toolkit::Control::KeyboardFocus::UP:\r
- {\r
- bestCandidateRect.y += 1;\r
- break;\r
- }\r
- case Toolkit::Control::KeyboardFocus::DOWN:\r
- {\r
- bestCandidateRect.y -= 1;\r
- break;\r
- }\r
- default:\r
- {\r
- break;\r
- }\r
- }\r
-\r
- ConvertCoordinate(bestCandidateRect);\r
-\r
- ConvertCoordinate(focusedRect);\r
-\r
- Integration::SceneHolder window = Integration::SceneHolder::Get(focusedActor);\r
- if(window)\r
- {\r
- Actor rootActor = window.GetRootLayer();\r
- nearestActor = FindNextFocus(rootActor, focusedActor, focusedRect, bestCandidateRect, direction);\r
- }\r
- return nearestActor;\r
-}\r
-\r
-Actor FocusFinder::FindNextFocus(Actor& actor, Actor& focusedActor, Rect<float>& focusedRect, Rect<float>& bestCandidateRect, Toolkit::Control::KeyboardFocus::Direction direction)\r
-{\r
- Actor nearestActor;\r
- if(actor)\r
- {\r
- // Recursively children\r
- const auto childCount = actor.GetChildCount();\r
- for(auto i = 0u; i < childCount; ++i)\r
- {\r
- Dali::Actor child = actor.GetChildAt(i);\r
- if(child && child != focusedActor && child.GetProperty<bool>(Actor::Property::KEYBOARD_FOCUSABLE))\r
- {\r
- Rect<float> candidateRect = DevelActor::CalculateScreenExtents(child);\r
-\r
- // convert x, y, width, height -> left, right, bottom, top\r
- ConvertCoordinate(candidateRect);\r
-\r
- if(IsBetterCandidate(direction, focusedRect, candidateRect, bestCandidateRect))\r
- {\r
- bestCandidateRect = candidateRect;\r
- nearestActor = child;\r
- }\r
- }\r
- Actor nextActor = FindNextFocus(child, focusedActor, focusedRect, bestCandidateRect, direction);\r
- if(nextActor)\r
- {\r
- nearestActor = nextActor;\r
- }\r
- }\r
- }\r
- return nearestActor;\r
-}\r
-\r
-bool FocusFinder::IsBetterCandidate(Toolkit::Control::KeyboardFocus::Direction direction, Rect<float>& focusedRect, Rect<float>& candidateRect, Rect<float>& bestCandidateRect) const\r
-{\r
- // to be a better candidate, need to at least be a candidate in the first place\r
- if(!IsCandidate(focusedRect, candidateRect, direction))\r
- {\r
- return false;\r
- }\r
- // we know that candidateRect is a candidate.. if bestCandidateRect is not a candidate,\r
- // candidateRect is better\r
- if(!IsCandidate(focusedRect, bestCandidateRect, direction))\r
- {\r
- return true;\r
- }\r
- // if candidateRect is better by beam, it wins\r
- if(BeamBeats(direction, focusedRect, candidateRect, bestCandidateRect))\r
- {\r
- return true;\r
- }\r
- // if bestCandidateRect is better, then candidateRect cant' be :)\r
- if(BeamBeats(direction, focusedRect, bestCandidateRect, candidateRect))\r
- {\r
- return false;\r
- }\r
-\r
- // otherwise, do fudge-tastic comparison of the major and minor axis\r
- return (GetWeightedDistanceFor(\r
- MajorAxisDistance(direction, focusedRect, candidateRect),\r
- MinorAxisDistance(direction, focusedRect, candidateRect)) < GetWeightedDistanceFor(MajorAxisDistance(direction, focusedRect, bestCandidateRect),\r
- MinorAxisDistance(direction, focusedRect, bestCandidateRect)));\r
-}\r
-\r
-} // namespace Internal\r
-\r
-} // namespace Toolkit\r
-\r
-} // namespace Dali\r