From: Joogab Yun Date: Fri, 16 Apr 2021 07:19:35 +0000 (+0900) Subject: Implement FocusFinder X-Git-Tag: dali_2.0.28~6^2 X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=commitdiff_plain;h=d239b109fc055baea71d712bf469fc9434c3e022 Implement FocusFinder The class used for finding the next focusable actor in a given direction from a actor that currently has focus. Change-Id: If5d5eed2599f3d95a0757b855e420e4edd186756 --- diff --git a/automated-tests/src/dali-toolkit/utc-Dali-KeyboardFocusManager.cpp b/automated-tests/src/dali-toolkit/utc-Dali-KeyboardFocusManager.cpp index 720f596..be3cf44 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-KeyboardFocusManager.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-KeyboardFocusManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. + * Copyright (c) 2021 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1616,4 +1616,172 @@ int UtcDaliKeyboardFocusManagerFocusPerWindow(void) END_TEST; } +int UtcDaliKeyboardFocusManagerWithoutFocusablePropertiesMoveFocus(void) +{ + ToolkitTestApplication application; + + tet_infoline(" UtcDaliKeyboardFocusManagerWithoutFocusablePropertiesMoveFocus"); + + // Register Type + TypeInfo type; + type = TypeRegistry::Get().GetTypeInfo( "KeyboardFocusManager" ); + DALI_TEST_CHECK( type ); + BaseHandle handle = type.CreateInstance(); + DALI_TEST_CHECK( handle ); + + KeyboardFocusManager manager = KeyboardFocusManager::Get(); + DALI_TEST_CHECK(manager); + + bool focusChangedSignalVerified = false; + FocusChangedCallback focusChangedCallback(focusChangedSignalVerified); + manager.FocusChangedSignal().Connect( &focusChangedCallback, &FocusChangedCallback::Callback ); + + PushButton button1 = PushButton::New(); + PushButton button2 = PushButton::New(); + PushButton button3 = PushButton::New(); + PushButton button4 = PushButton::New(); + PushButton button5 = PushButton::New(); + + button1.SetProperty( Actor::Property::SIZE, Vector2( 50, 50 ) ); + button2.SetProperty( Actor::Property::SIZE, Vector2( 50, 50 ) ); + button3.SetProperty( Actor::Property::SIZE, Vector2( 50, 50 ) ); + button4.SetProperty( Actor::Property::SIZE, Vector2( 50, 50 ) ); + button5.SetProperty( Actor::Property::SIZE, Vector2( 50, 50 ) ); + + button1.SetProperty( Actor::Property::KEYBOARD_FOCUSABLE,true); + button2.SetProperty( Actor::Property::KEYBOARD_FOCUSABLE,true); + button3.SetProperty( Actor::Property::KEYBOARD_FOCUSABLE,true); + button4.SetProperty( Actor::Property::KEYBOARD_FOCUSABLE,true); + button5.SetProperty( Actor::Property::KEYBOARD_FOCUSABLE,true); + + application.GetScene().Add(button1); + application.GetScene().Add(button2); + application.GetScene().Add(button3); + application.GetScene().Add(button4); + application.GetScene().Add(button5); + + // set position + // button1 -- button2 + // | | + // | button5| + // button3 -- button4 + button1.SetProperty( Actor::Property::POSITION, Vector2(0.0f, 0.0f)); + button2.SetProperty( Actor::Property::POSITION, Vector2(100.0f, 0.0f)); + button3.SetProperty( Actor::Property::POSITION, Vector2(0.0f, 100.0f)); + button4.SetProperty( Actor::Property::POSITION, Vector2(100.0f, 100.0f)); + button5.SetProperty( Actor::Property::POSITION, Vector2(60.0f, 60.0f)); + + // flush the queue and render once + application.SendNotification(); + application.Render(); + + // Set the focus to the button1 + // [button1] -- button2 + // | | + // | button5| + // button3 -- button4 + DALI_TEST_CHECK(manager.SetCurrentFocusActor(button1) == true); + DALI_TEST_CHECK(manager.GetCurrentFocusActor() == button1); + DALI_TEST_CHECK(focusChangedCallback.mSignalVerified); + DALI_TEST_CHECK(focusChangedCallback.mOriginalFocusedActor == Actor()); + DALI_TEST_CHECK(focusChangedCallback.mCurrentFocusedActor == button1); + focusChangedCallback.Reset(); + + // without set the navigation properties, but we can focus move + + // Move the focus towards right + // button1 -- [button2] + // | | + // | button5| + // button3 -- button4 + DALI_TEST_CHECK(manager.MoveFocus(Control::KeyboardFocus::RIGHT) == true); + + // Confirm whether focus is moved to button2 + DALI_TEST_EQUALS(button2.GetProperty(DevelControl::Property::STATE), (int)DevelControl::FOCUSED, TEST_LOCATION ); + DALI_TEST_CHECK(focusChangedCallback.mSignalVerified); + DALI_TEST_CHECK(focusChangedCallback.mOriginalFocusedActor == button1); + DALI_TEST_CHECK(focusChangedCallback.mCurrentFocusedActor == button2); + focusChangedCallback.Reset(); + + // Move the focus towards down + // button1 -- button2 + // | | + // | [button5]| + // button3 -- button4 + DALI_TEST_CHECK(manager.MoveFocus(Control::KeyboardFocus::DOWN) == true); + + // Confirm whether focus is moved to button5 + DALI_TEST_EQUALS(button5.GetProperty(DevelControl::Property::STATE), (int)DevelControl::FOCUSED, TEST_LOCATION ); + DALI_TEST_CHECK(focusChangedCallback.mSignalVerified); + DALI_TEST_CHECK(focusChangedCallback.mOriginalFocusedActor == button2); + DALI_TEST_CHECK(focusChangedCallback.mCurrentFocusedActor == button5); + focusChangedCallback.Reset(); + + // Move the focus towards right + // button1 -- button2 + // | | + // | button5| + // button3 -- [button4] + DALI_TEST_CHECK(manager.MoveFocus(Control::KeyboardFocus::RIGHT) == true); + + // Confirm whether focus is moved to button4 + DALI_TEST_EQUALS(button4.GetProperty(DevelControl::Property::STATE), (int)DevelControl::FOCUSED, TEST_LOCATION ); + DALI_TEST_CHECK(focusChangedCallback.mSignalVerified); + DALI_TEST_CHECK(focusChangedCallback.mOriginalFocusedActor == button5); + DALI_TEST_CHECK(focusChangedCallback.mCurrentFocusedActor == button4); + focusChangedCallback.Reset(); + + // Move the focus towards left + // button1 -- button2 + // | | + // | [button5]| + // button3 -- button4 + DALI_TEST_CHECK(manager.MoveFocus(Control::KeyboardFocus::LEFT) == true); + + // Confirm whether focus is moved to button5 + DALI_TEST_EQUALS(button5.GetProperty(DevelControl::Property::STATE), (int)DevelControl::FOCUSED, TEST_LOCATION ); + DALI_TEST_CHECK(focusChangedCallback.mSignalVerified); + DALI_TEST_CHECK(focusChangedCallback.mOriginalFocusedActor == button4); + DALI_TEST_CHECK(focusChangedCallback.mCurrentFocusedActor == button5); + focusChangedCallback.Reset(); + + // Move the focus towards left + // button1 -- button2 + // | | + // | button5| + //[button3] -- button4 + DALI_TEST_CHECK(manager.MoveFocus(Control::KeyboardFocus::LEFT) == true); + + // Confirm whether focus is moved to button3 + DALI_TEST_EQUALS(button3.GetProperty(DevelControl::Property::STATE), (int)DevelControl::FOCUSED, TEST_LOCATION ); + DALI_TEST_CHECK(focusChangedCallback.mSignalVerified); + DALI_TEST_CHECK(focusChangedCallback.mOriginalFocusedActor == button5); + DALI_TEST_CHECK(focusChangedCallback.mCurrentFocusedActor == button3); + focusChangedCallback.Reset(); + + // Move the focus towards up + //[button1]-- button2 + // | | + // | button5| + // button3 -- button4 + DALI_TEST_CHECK(manager.MoveFocus(Control::KeyboardFocus::UP) == true); + + // Confirm whether focus is moved to button1 + DALI_TEST_EQUALS(button1.GetProperty(DevelControl::Property::STATE), (int)DevelControl::FOCUSED, TEST_LOCATION ); + DALI_TEST_CHECK(focusChangedCallback.mSignalVerified); + DALI_TEST_CHECK(focusChangedCallback.mOriginalFocusedActor == button3); + DALI_TEST_CHECK(focusChangedCallback.mCurrentFocusedActor == button1); + focusChangedCallback.Reset(); + + + // Move the focus towards left. The focus move will fail as no way to move it upwards + DALI_TEST_CHECK(manager.MoveFocus(Control::KeyboardFocus::LEFT) == false); + + // Move the focus toward page up/down. The focus move will fail as invalid direction. + DALI_TEST_CHECK(manager.MoveFocus(Control::KeyboardFocus::PAGE_UP) == false); + DALI_TEST_CHECK(manager.MoveFocus(Control::KeyboardFocus::PAGE_DOWN) == false); + focusChangedCallback.Reset(); + + END_TEST; +} diff --git a/dali-toolkit/devel-api/file.list b/dali-toolkit/devel-api/file.list index f583d3c..e94f60b 100755 --- a/dali-toolkit/devel-api/file.list +++ b/dali-toolkit/devel-api/file.list @@ -46,6 +46,7 @@ SET( devel_api_src_files ${devel_api_src_dir}/controls/web-view/web-view.cpp ${devel_api_src_dir}/focus-manager/keyinput-focus-manager.cpp ${devel_api_src_dir}/focus-manager/keyboard-focus-manager-devel.cpp + ${devel_api_src_dir}/focus-manager/focus-finder.cpp ${devel_api_src_dir}/image-loader/async-image-loader-devel.cpp ${devel_api_src_dir}/image-loader/atlas-upload-observer.cpp ${devel_api_src_dir}/image-loader/image-atlas.cpp @@ -188,6 +189,7 @@ SET( devel_api_shadow_view_header_files SET( devel_api_focus_manager_header_files ${devel_api_src_dir}/focus-manager/keyinput-focus-manager.h ${devel_api_src_dir}/focus-manager/keyboard-focus-manager-devel.h + ${devel_api_src_dir}/focus-manager/focus-finder.h ) SET( devel_api_image_loader_header_files diff --git a/dali-toolkit/devel-api/focus-manager/focus-finder.cpp b/dali-toolkit/devel-api/focus-manager/focus-finder.cpp new file mode 100644 index 0000000..d12ed11 --- /dev/null +++ b/dali-toolkit/devel-api/focus-manager/focus-finder.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// CLASS HEADER +#include "focus-finder.h" + +// EXTERNAL INCLUDES +#include + +// INTERNAL INCLUDES +#include + +namespace Dali +{ +namespace Toolkit +{ +FocusFinder::FocusFinder() +{ +} + +FocusFinder::~FocusFinder() +{ +} + +FocusFinder FocusFinder::Get() +{ + FocusFinder finder; + + // Check whether the focus finder is already created + SingletonService singletonService(SingletonService::Get()); + if(singletonService) + { + Dali::BaseHandle handle = singletonService.GetSingleton(typeid(FocusFinder)); + if(handle) + { + // If so, downcast the handle of singleton to focus finder + finder = FocusFinder(dynamic_cast(handle.GetObjectPtr())); + } + + if(!finder) + { + // If not, create the focus finder and register it as a singleton + finder = FocusFinder(new Internal::FocusFinder()); + singletonService.Register(typeid(finder), finder); + } + } + + return finder; +} + +FocusFinder::FocusFinder(Internal::FocusFinder* impl) +: BaseHandle(impl) +{ +} + +Actor FocusFinder::GetNearestFocusableActor(Actor focusedActor, Toolkit::Control::KeyboardFocus::Direction direction) +{ + return GetImpl(*this).GetNearestFocusableActor(focusedActor, direction); +} + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/devel-api/focus-manager/focus-finder.h b/dali-toolkit/devel-api/focus-manager/focus-finder.h new file mode 100644 index 0000000..8012675 --- /dev/null +++ b/dali-toolkit/devel-api/focus-manager/focus-finder.h @@ -0,0 +1,77 @@ +#ifndef DALI_TOOLKIT_FOCUS_FINDER_H +#define DALI_TOOLKIT_FOCUS_FINDER_H + +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// INTERNAL INCLUDES +#include + +namespace Dali +{ +namespace Toolkit +{ +namespace Internal DALI_INTERNAL +{ +class FocusFinder; +} + +/** + * FocusFinder + * This class used for finding the next focusable actor in a given direction + * from a actor that currently has focus. + */ +class DALI_TOOLKIT_API FocusFinder : public BaseHandle +{ +public: + /** + * Create a FocusFinder handle; this can be initialised with FocusFinder::Get() + * Calling member functions with an uninitialised handle is not allowed. + */ + FocusFinder(); + + /** + * @brief Destructor + * + * This is non-virtual since derived Handle types must not contain data or virtual methods. + */ + ~FocusFinder(); + + /** + * @brief Get the singleton of FocusFinder object. + * @return A handle to the FocusFinder control. + */ + static FocusFinder Get(); + + /** + * Get the nearest focusable actor. + * @param [in] focusedActor The current focused actor. + * @param [in] direction The direction. + * @return The nearest focusable actor, or null if none exists. + */ + Actor GetNearestFocusableActor(Actor focusedActor, Toolkit::Control::KeyboardFocus::Direction direction); + +private: + explicit DALI_INTERNAL FocusFinder(Internal::FocusFinder* impl); + +}; // class FocusFinder + +} // namespace Toolkit + +} // namespace Dali + +#endif // DALI_TOOLKIT_FOCUS_FINDER_H diff --git a/dali-toolkit/internal/file.list b/dali-toolkit/internal/file.list index 395dfc8..fc8edaf 100644 --- a/dali-toolkit/internal/file.list +++ b/dali-toolkit/internal/file.list @@ -114,6 +114,7 @@ SET( toolkit_src_files ${toolkit_src_dir}/focus-manager/keyboard-focus-manager-impl.cpp ${toolkit_src_dir}/focus-manager/keyinput-focus-manager-impl.cpp + ${toolkit_src_dir}/focus-manager/focus-finder-impl.cpp ${toolkit_src_dir}/helpers/color-conversion.cpp ${toolkit_src_dir}/helpers/property-helper.cpp ${toolkit_src_dir}/filters/blur-two-pass-filter.cpp diff --git a/dali-toolkit/internal/focus-manager/focus-finder-impl.cpp b/dali-toolkit/internal/focus-manager/focus-finder-impl.cpp new file mode 100644 index 0000000..93f1d65 --- /dev/null +++ b/dali-toolkit/internal/focus-manager/focus-finder-impl.cpp @@ -0,0 +1,449 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Modified by joogab yun(joogab.yun@samsung.com) + */ + +// CLASS HEADER +#include "focus-finder-impl.h" + +// INTERNAL INCLUDES +#include + +// EXTERNAL INCLUDES +#include +#include +#include +#include +#include + +namespace +{ +static int MajorAxisDistanceRaw(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect source, Dali::Rect dest) +{ + switch(direction) + { + case Dali::Toolkit::Control::KeyboardFocus::LEFT: + { + return source.left - dest.right; + } + case Dali::Toolkit::Control::KeyboardFocus::RIGHT: + { + return dest.left - source.right; + } + case Dali::Toolkit::Control::KeyboardFocus::UP: + { + return source.top - dest.bottom; + } + case Dali::Toolkit::Control::KeyboardFocus::DOWN: + { + return dest.top - source.bottom; + } + default: + { + return 0; + } + } +} + +/** + * @return The distance from the edge furthest in the given direction + * of source to the edge nearest in the given direction of dest. + * If the dest is not in the direction from source, return 0. + */ +static int MajorAxisDistance(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect source, Dali::Rect dest) +{ + return std::max(0, MajorAxisDistanceRaw(direction, source, dest)); +} + +static int MajorAxisDistanceToFarEdgeRaw(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect source, Dali::Rect dest) +{ + switch(direction) + { + case Dali::Toolkit::Control::KeyboardFocus::LEFT: + { + return source.left - dest.left; + } + case Dali::Toolkit::Control::KeyboardFocus::RIGHT: + { + return dest.right - source.right; + } + case Dali::Toolkit::Control::KeyboardFocus::UP: + { + return source.top - dest.top; + } + case Dali::Toolkit::Control::KeyboardFocus::DOWN: + { + return dest.bottom - source.bottom; + } + default: + { + return 0; + } + } +} + +/** + * @return The distance along the major axis w.r.t the direction from the + * edge of source to the far edge of dest. + * If the dest is not in the direction from source, return 1 + */ +static int MajorAxisDistanceToFarEdge(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect source, Dali::Rect dest) +{ + return std::max(1, MajorAxisDistanceToFarEdgeRaw(direction, source, dest)); +} + +/** + * Find the distance on the minor axis w.r.t the direction to the nearest + * edge of the destination rectangle. + * @param direction the direction (up, down, left, right) + * @param source The source rect. + * @param dest The destination rect. + * @return The distance. + */ +static int MinorAxisDistance(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect source, Dali::Rect dest) +{ + switch(direction) + { + case Dali::Toolkit::Control::KeyboardFocus::LEFT: + case Dali::Toolkit::Control::KeyboardFocus::RIGHT: + { + // the distance between the center verticals + return std::abs( + (((source.top + source.bottom) * 0.5f) - + (((dest.top + dest.bottom) * 0.5f)))); + } + case Dali::Toolkit::Control::KeyboardFocus::UP: + case Dali::Toolkit::Control::KeyboardFocus::DOWN: + { + // the distance between the center horizontals + return std::abs( + (((source.left + source.right) * 0.5f) - + (((dest.left + dest.right) * 0.5f)))); + } + default: + { + return 0; + } + } +} + +/** + * Calculate distance given major and minor axis distances. + * @param majorAxisDistance The majorAxisDistance + * @param minorAxisDistance The minorAxisDistance + * @return The distance + */ +static int GetWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) +{ + return 13 * majorAxisDistance * majorAxisDistance + minorAxisDistance * minorAxisDistance; +} + +/** + * Convert x,y,width,height coordinates into left, right, bottom, top coordinates. + * @param[in,out] rect The rect + */ +static void ConvertCoordinate(Dali::Rect& rect) +{ + // convert x, y, width, height -> left, right, bottom, top + float left = rect.x; + float right = rect.x + rect.width; + float bottom = rect.y + rect.height; + float top = rect.y; + + rect.left = left; + rect.right = right; + rect.bottom = bottom; + rect.top = top; +} + +/** + * Is destRect a candidate for the next focus given the direction? + * @param srcRect The source rect. + * @param destRect The dest rect. + * @param direction The direction (up, down, left, right) + * @return Whether destRect is a candidate. + */ +static bool IsCandidate(Dali::Rect srcRect, Dali::Rect destRect, Dali::Toolkit::Control::KeyboardFocus::Direction direction) +{ + switch(direction) + { + case Dali::Toolkit::Control::KeyboardFocus::LEFT: + { + return (srcRect.right > destRect.right || srcRect.left >= destRect.right) && srcRect.left > destRect.left; + } + case Dali::Toolkit::Control::KeyboardFocus::RIGHT: + { + return (srcRect.left < destRect.left || srcRect.right <= destRect.left) && srcRect.right < destRect.right; + } + case Dali::Toolkit::Control::KeyboardFocus::UP: + { + return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom) && srcRect.top > destRect.top; + } + case Dali::Toolkit::Control::KeyboardFocus::DOWN: + { + return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top) && srcRect.bottom < destRect.bottom; + } + default: + { + return false; + } + } + return false; +} + +/** + * Is dest in a given direction from src? + * @param direction the direction (up, down, left, right) + * @param src The source rect + * @param dest The dest rect + */ +static bool IsToDirectionOf(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect src, Dali::Rect dest) +{ + switch(direction) + { + case Dali::Toolkit::Control::KeyboardFocus::LEFT: + { + return src.left >= dest.right; + } + case Dali::Toolkit::Control::KeyboardFocus::RIGHT: + { + return src.right <= dest.left; + } + case Dali::Toolkit::Control::KeyboardFocus::UP: + { + return src.top >= dest.bottom; + } + case Dali::Toolkit::Control::KeyboardFocus::DOWN: + { + return src.bottom <= dest.top; + } + default: + { + return false; + } + } +} + +/** + * Do the given direction's axis of rect1 and rect2 overlap? + * @param direction the direction (up, down, left, right) + * @param rect1 The first rect + * @param rect2 The second rect + * @return whether the beams overlap + */ +static bool BeamsOverlap(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect rect1, Dali::Rect rect2) +{ + switch(direction) + { + case Dali::Toolkit::Control::KeyboardFocus::LEFT: + case Dali::Toolkit::Control::KeyboardFocus::RIGHT: + { + return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom); + } + case Dali::Toolkit::Control::KeyboardFocus::UP: + case Dali::Toolkit::Control::KeyboardFocus::DOWN: + { + return (rect2.right >= rect1.left) && (rect2.left <= rect1.right); + } + default: + { + return false; + } + } +} + +/** + * One rectangle may be another candidate than another by virtue of being exclusively in the beam of the source rect. + * @param direction The direction (up, down, left, right) + * @param source The source rect + * @param rect1 The first rect + * @param rect2 The second rect + * @return Whether rect1 is a better candidate than rect2 by virtue of it being in src's beam + */ +static bool BeamBeats(Dali::Toolkit::Control::KeyboardFocus::Direction direction, Dali::Rect source, Dali::Rect rect1, Dali::Rect rect2) +{ + const bool rect1InSrcBeam = BeamsOverlap(direction, source, rect1); + const bool rect2InSrcBeam = BeamsOverlap(direction, source, rect2); + // if rect1 isn't exclusively in the src beam, it doesn't win + if(rect2InSrcBeam || !rect1InSrcBeam) + { + return false; + } + // we know rect1 is in the beam, and rect2 is not + // if rect1 is to the direction of, and rect2 is not, rect1 wins. + // for example, for direction left, if rect1 is to the left of the source + // and rect2 is below, then we always prefer the in beam rect1, since rect2 + // could be reached by going down. + if(!IsToDirectionOf(direction, source, rect2)) + { + return true; + } + // for horizontal directions, being exclusively in beam always wins + if((direction == Dali::Toolkit::Control::KeyboardFocus::LEFT || direction == Dali::Toolkit::Control::KeyboardFocus::RIGHT)) + { + return true; + } + // for vertical directions, beams only beat up to a point: + // now, as long as rect2 isn't completely closer, rect1 wins + // e.g for direction down, completely closer means for rect2's top + // edge to be closer to the source's top edge than rect1's bottom edge. + return (MajorAxisDistance(direction, source, rect1) < MajorAxisDistanceToFarEdge(direction, source, rect2)); +} + +} // unnamed namespace + +namespace Dali +{ +namespace Toolkit +{ +namespace Internal +{ +FocusFinder::FocusFinder() +{ +} + +FocusFinder::~FocusFinder() +{ +} + +Actor FocusFinder::GetNearestFocusableActor(Actor& focusedActor, Toolkit::Control::KeyboardFocus::Direction direction) +{ + Actor nearestActor; + if(!focusedActor) + { + return nearestActor; + } + + Rect focusedRect = DevelActor::CalculateScreenExtents(focusedActor); + + // initialize the best candidate to something impossible + // (so the first plausible actor will become the best choice) + Rect bestCandidateRect = focusedRect; + switch(direction) + { + case Toolkit::Control::KeyboardFocus::LEFT: + { + bestCandidateRect.x += 1; + break; + } + case Toolkit::Control::KeyboardFocus::RIGHT: + { + bestCandidateRect.x -= 1; + break; + } + case Toolkit::Control::KeyboardFocus::UP: + { + bestCandidateRect.y += 1; + break; + } + case Toolkit::Control::KeyboardFocus::DOWN: + { + bestCandidateRect.y -= 1; + break; + } + default: + { + break; + } + } + + ConvertCoordinate(bestCandidateRect); + + ConvertCoordinate(focusedRect); + + Integration::SceneHolder window = Integration::SceneHolder::Get(focusedActor); + if(window) + { + Actor rootActor = window.GetRootLayer(); + nearestActor = FindNextFocus(rootActor, focusedActor, focusedRect, bestCandidateRect, direction); + } + return nearestActor; +} + +Actor FocusFinder::FindNextFocus(Actor& actor, Actor& focusedActor, Rect& focusedRect, Rect& bestCandidateRect, Toolkit::Control::KeyboardFocus::Direction direction) +{ + Actor nearestActor; + if(actor) + { + // Recursively children + const auto childCount = actor.GetChildCount(); + for(auto i = 0u; i < childCount; ++i) + { + Dali::Actor child = actor.GetChildAt(i); + if(child && child != focusedActor && child.GetProperty(Actor::Property::KEYBOARD_FOCUSABLE)) + { + Rect candidateRect = DevelActor::CalculateScreenExtents(child); + + // convert x, y, width, height -> left, right, bottom, top + ConvertCoordinate(candidateRect); + + if(IsBetterCandidate(direction, focusedRect, candidateRect, bestCandidateRect)) + { + bestCandidateRect = candidateRect; + nearestActor = child; + } + } + Actor nextActor = FindNextFocus(child, focusedActor, focusedRect, bestCandidateRect, direction); + if(nextActor) + { + nearestActor = nextActor; + } + } + } + return nearestActor; +} + +bool FocusFinder::IsBetterCandidate(Toolkit::Control::KeyboardFocus::Direction direction, Rect& focusedRect, Rect& candidateRect, Rect& bestCandidateRect) const +{ + // to be a better candidate, need to at least be a candidate in the first place + if(!IsCandidate(focusedRect, candidateRect, direction)) + { + return false; + } + // we know that candidateRect is a candidate.. if bestCandidateRect is not a candidate, + // candidateRect is better + if(!IsCandidate(focusedRect, bestCandidateRect, direction)) + { + return true; + } + // if candidateRect is better by beam, it wins + if(BeamBeats(direction, focusedRect, candidateRect, bestCandidateRect)) + { + return true; + } + // if bestCandidateRect is better, then candidateRect cant' be :) + if(BeamBeats(direction, focusedRect, bestCandidateRect, candidateRect)) + { + return false; + } + + // otherwise, do fudge-tastic comparison of the major and minor axis + return (GetWeightedDistanceFor( + MajorAxisDistance(direction, focusedRect, candidateRect), + MinorAxisDistance(direction, focusedRect, candidateRect)) < GetWeightedDistanceFor(MajorAxisDistance(direction, focusedRect, bestCandidateRect), + MinorAxisDistance(direction, focusedRect, bestCandidateRect))); +} + +} // namespace Internal + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/internal/focus-manager/focus-finder-impl.h b/dali-toolkit/internal/focus-manager/focus-finder-impl.h new file mode 100644 index 0000000..012b1aa --- /dev/null +++ b/dali-toolkit/internal/focus-manager/focus-finder-impl.h @@ -0,0 +1,117 @@ +#ifndef DALI_TOOLKIT_INTERNAL_FOCUS_FINDER_H +#define DALI_TOOLKIT_INTERNAL_FOCUS_FINDER_H + +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// EXTERNAL INCLUDES +#include + +// INTERNAL INCLUDES +#include + +namespace Dali +{ +namespace Integration +{ +class SceneHolder; + +} // namespace Integration + +namespace Toolkit +{ +namespace Internal +{ +class FocusFinder; + +/** + * @copydoc Toolkit::FocusFinder + */ +class FocusFinder : public Dali::BaseObject +{ +public: + /** + * Construct a new FocusFinder. + */ + FocusFinder(); + + /** + * @copydoc Toolkit::GetNearestFocusableActor + */ + Actor GetNearestFocusableActor(Actor& focusedActor, Toolkit::Control::KeyboardFocus::Direction direction); + +protected: + /** + * Destructor + */ + virtual ~FocusFinder(); + +private: + /** + * Find the next actor to take focus in root's descendants, starting from the actor. + * @param[in] actor The root actor. + * @param[in] focusedActor The current focused actor. + * @param[in] focusedRect The rect of current focused actor. + * @param[in] bestCandidateRect The current best candidate. + * @param[in] direction The direction. + * @return nearest Actor. + */ + Actor FindNextFocus(Actor& actor, Actor& focusedActor, Rect& focusedRect, Rect& bestCandidateRect, Toolkit::Control::KeyboardFocus::Direction direction); + + /** + * Is rect1 a better candidate than rect2 for a focus search in a particular + * direction from a source rect? This is the core routine that determines + * the order of focus searching. + * @param direction The direction (up, down, left, right) + * @param candidateRect The candidate rectangle + * @param bestCandidateRect The current best candidate. + * @return Whether the candidate is the new best. + */ + bool IsBetterCandidate(Toolkit::Control::KeyboardFocus::Direction direction, Rect& focusedRect, Rect& candidateRect, Rect& bestCandidateRect) const; + +private: + // Undefined + FocusFinder(const FocusFinder&); + + FocusFinder& operator=(const FocusFinder& rhs); +}; + +} // namespace Internal + +inline Internal::FocusFinder& GetImpl(Dali::Toolkit::FocusFinder& obj) +{ + DALI_ASSERT_ALWAYS(obj); + + Dali::BaseObject& handle = obj.GetBaseObject(); + + return static_cast(handle); +} + +inline const Internal::FocusFinder& GetImpl(const Dali::Toolkit::FocusFinder& obj) +{ + DALI_ASSERT_ALWAYS(obj); + + const Dali::BaseObject& handle = obj.GetBaseObject(); + + return static_cast(handle); +} + +} // namespace Toolkit + +} // namespace Dali + +#endif // DALI_TOOLKIT_INTERNAL_FOCUS_FINDER_H diff --git a/dali-toolkit/internal/focus-manager/keyboard-focus-manager-impl.cpp b/dali-toolkit/internal/focus-manager/keyboard-focus-manager-impl.cpp index 0d7bbfd..b06edea 100644 --- a/dali-toolkit/internal/focus-manager/keyboard-focus-manager-impl.cpp +++ b/dali-toolkit/internal/focus-manager/keyboard-focus-manager-impl.cpp @@ -36,6 +36,7 @@ // INTERNAL INCLUDES #include #include +#include #include #include #include @@ -495,6 +496,11 @@ bool KeyboardFocusManager::MoveFocus(Toolkit::Control::KeyboardFocus::Direction nextFocusableActor = mPreFocusChangeSignal.Emit(currentFocusActor, Actor(), direction); mIsWaitingKeyboardFocusChangeCommit = false; } + else + { + // We should find it among the actors nearby. + nextFocusableActor = Toolkit::FocusFinder::Get().GetNearestFocusableActor(currentFocusActor, direction); + } } if(nextFocusableActor && nextFocusableActor.GetProperty(Actor::Property::KEYBOARD_FOCUSABLE))