From: Joogab Yun Date: Fri, 16 Apr 2021 07:19:35 +0000 (+0900) Subject: [Tizen] 1. Implement FocusFinder X-Git-Tag: accepted/tizen/6.0/unified/20211216.155530~4^2 X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=commitdiff_plain;h=c426dd18a9eff6de4c2d80e91230400673f1136b [Tizen] 1. Implement FocusFinder The class used for finding the next focusable actor in a given direction from a actor that currently has focus. 2. Change FocusFinder to a method 3. Add Tab key focus movement. If the focus group is not changed, Move the focus towards right, "Shift-Tap" key moves the focus towards left. Still if the focus is not moved, Move the focus towards down, "Shift-Tap" key moves the focus towards up. 4. Users can implement focus movement by SetCustomAlgorithm. So User implemented focus movement by SetCustomAlgorithm(AutoFocusAlgorithm.Instance); And if user do SetCustomAlgorithm(null), the user expects the focus not to move. this is for backward-compatibility. Add EnableDefaultAlgorithm() and IsDefaultAlgorithmEnabled() for enable/disable the default algorithm 5. Fixed bug 6. Fixed IsCandidate bug 7. Check if actor can be focused. 8. If parent is hidden, the child cannot be focused. Change-Id: Ieebaafc31fe5e943a63b8e2a079335eb578ab0cd --- diff --git a/automated-tests/src/dali-toolkit/utc-Dali-KeyboardFocusManager.cpp b/automated-tests/src/dali-toolkit/utc-Dali-KeyboardFocusManager.cpp index 720f596..1063d67 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,203 @@ 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); + button5.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(40.0f, 40.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 + // enable the default algorithm + Dali::Toolkit::DevelKeyboardFocusManager::EnableDefaultAlgorithm(manager, true); + DALI_TEST_CHECK( Dali::Toolkit::DevelKeyboardFocusManager::IsDefaultAlgorithmEnabled(manager) ); + + // 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 right + // button1 -- button2 + // | | + // | [button5]| + // button3 -- button4 + DALI_TEST_CHECK(manager.MoveFocus(Control::KeyboardFocus::RIGHT) == 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 == button3); + 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 6b04c3e..4e002be 100755 --- a/dali-toolkit/devel-api/file.list +++ b/dali-toolkit/devel-api/file.list @@ -45,6 +45,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 @@ -179,6 +180,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..2679dda --- /dev/null +++ b/dali-toolkit/devel-api/focus-manager/focus-finder.cpp @@ -0,0 +1,443 @@ +/* + * 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.h" + +// EXTERNAL INCLUDES +#include +#include +#include + +namespace Dali +{ +namespace Toolkit +{ +namespace FocusFinder +{ +namespace +{ +static constexpr float FULLY_TRANSPARENT(0.01f); ///< Alpha values must rise above this, before an object is considered to be visible. + +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 - source.top) * 0.5f) - + (dest.top + (dest.bottom - dest.top) * 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 - source.left) * 0.5f) - + (dest.left + (dest.right - dest.left) * 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)); +} + +bool IsBetterCandidate(Toolkit::Control::KeyboardFocus::Direction direction, Rect& focusedRect, Rect& candidateRect, Rect& bestCandidateRect) +{ + // 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))); +} + +bool IsFocusable(Actor& actor) +{ + return (actor.GetProperty(Actor::Property::KEYBOARD_FOCUSABLE) && + actor.GetProperty(Actor::Property::VISIBLE) && + actor.GetProperty(Actor::Property::WORLD_COLOR).a > FULLY_TRANSPARENT); +} + +Actor FindNextFocus(Actor& actor, Actor& focusedActor, Rect& focusedRect, Rect& bestCandidateRect, Toolkit::Control::KeyboardFocus::Direction direction) +{ + Actor nearestActor; + if(actor && actor.GetProperty(Actor::Property::VISIBLE)) + { + // Recursively children + const auto childCount = actor.GetChildCount(); + for(auto i = 0u; i < childCount; ++i) + { + Dali::Actor child = actor.GetChildAt(i); + if(child && child != focusedActor && IsFocusable(child)) + { + 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; +} + +} // unnamed namespace + +Actor 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; +} + +} // namespace FocusFinder + +} // 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..47f313d --- /dev/null +++ b/dali-toolkit/devel-api/focus-manager/focus-finder.h @@ -0,0 +1,44 @@ +#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 FocusFinder +{ +/** + * Get the nearest focusable actor. + * @param [in] focusedActor The current focused actor. + * @param [in] direction The direction. + * @return The nearest focusable actor, or an empty handle if none exists. + */ +DALI_TOOLKIT_API Actor GetNearestFocusableActor(Actor focusedActor, Toolkit::Control::KeyboardFocus::Direction direction); + +} // namespace FocusFinder + +} // namespace Toolkit + +} // namespace Dali + +#endif // DALI_TOOLKIT_FOCUS_FINDER_H diff --git a/dali-toolkit/devel-api/focus-manager/keyboard-focus-manager-devel.cpp b/dali-toolkit/devel-api/focus-manager/keyboard-focus-manager-devel.cpp index faa7fc1..f320269 100644 --- a/dali-toolkit/devel-api/focus-manager/keyboard-focus-manager-devel.cpp +++ b/dali-toolkit/devel-api/focus-manager/keyboard-focus-manager-devel.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. @@ -40,6 +40,16 @@ bool IsFocusIndicatorEnabled(KeyboardFocusManager keyboardFocusManager) return GetImpl(keyboardFocusManager).IsFocusIndicatorEnabled(); } +void EnableDefaultAlgorithm(KeyboardFocusManager keyboardFocusManager, bool enable) +{ + GetImpl(keyboardFocusManager).EnableDefaultAlgorithm(enable); +} + +bool IsDefaultAlgorithmEnabled(KeyboardFocusManager keyboardFocusManager) +{ + return GetImpl(keyboardFocusManager).IsDefaultAlgorithmEnabled(); +} + } // namespace DevelKeyboardFocusManager } // namespace Toolkit diff --git a/dali-toolkit/devel-api/focus-manager/keyboard-focus-manager-devel.h b/dali-toolkit/devel-api/focus-manager/keyboard-focus-manager-devel.h index afe3e43..192c296 100644 --- a/dali-toolkit/devel-api/focus-manager/keyboard-focus-manager-devel.h +++ b/dali-toolkit/devel-api/focus-manager/keyboard-focus-manager-devel.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_KEYBOARD_FOCUS_MANAGER_DEVEL_H /* - * 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. @@ -84,6 +84,22 @@ DALI_TOOLKIT_API void EnableFocusIndicator(KeyboardFocusManager keyboardFocusMan */ DALI_TOOLKIT_API bool IsFocusIndicatorEnabled(KeyboardFocusManager keyboardFocusManager); +/** + * @brief Decide using default focus algorithm or not + * + * @param[in] keyboardFocusManager The instance of KeyboardFocusManager + * @param[in] enable Whether using default focus algorithm or not + */ +DALI_TOOLKIT_API void EnableDefaultAlgorithm(KeyboardFocusManager keyboardFocusManager, bool enable); + +/** + * @brief Check default focus algorithm is enabled or not + * + * @param[in] keyboardFocusManager The instance of KeyboardFocusManager + * @return True when default focus algorithm is enabled + */ +DALI_TOOLKIT_API bool IsDefaultAlgorithmEnabled(KeyboardFocusManager keyboardFocusManager); + } // namespace DevelKeyboardFocusManager } // namespace Toolkit 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 5092cfe..631e35c 100644 --- a/dali-toolkit/internal/focus-manager/keyboard-focus-manager-impl.cpp +++ b/dali-toolkit/internal/focus-manager/keyboard-focus-manager-impl.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -130,7 +131,8 @@ KeyboardFocusManager::KeyboardFocusManager() mAlwaysShowIndicator( ALWAYS_SHOW ), mFocusGroupLoopEnabled( false ), mIsWaitingKeyboardFocusChangeCommit( false ), - mClearFocusOnTouch( true ) + mClearFocusOnTouch( true ), + mEnableDefaultAlgorithm(false) { // TODO: Get FocusIndicatorEnable constant from stylesheet to set mIsFocusIndicatorShown. @@ -500,6 +502,11 @@ bool KeyboardFocusManager::MoveFocus(Toolkit::Control::KeyboardFocus::Direction nextFocusableActor = mPreFocusChangeSignal.Emit( currentFocusActor, Actor(), direction ); mIsWaitingKeyboardFocusChangeCommit = false; } + else if(mEnableDefaultAlgorithm) + { + // We should find it among the actors nearby. + nextFocusableActor = Toolkit::FocusFinder::GetNearestFocusableActor(currentFocusActor, direction); + } } if( nextFocusableActor && nextFocusableActor.GetProperty< bool >( Actor::Property::KEYBOARD_FOCUSABLE ) ) @@ -879,7 +886,15 @@ void KeyboardFocusManager::OnKeyEvent(const KeyEvent& event) { // "Tab" key changes the focus group in the forward direction and // "Shift-Tab" key changes it in the backward direction. - DoMoveFocusToNextFocusGroup(!event.IsShiftModifier()); + if(!DoMoveFocusToNextFocusGroup(!event.IsShiftModifier())) + { + // If the focus group is not changed, Move the focus towards right, "Shift-Tap" key moves the focus towards left. + if(!MoveFocus(event.IsShiftModifier() ? Toolkit::Control::KeyboardFocus::LEFT : Toolkit::Control::KeyboardFocus::RIGHT)) + { + // If the focus is not moved, Move the focus towards down, "Shift-Tap" key moves the focus towards up. + MoveFocus(event.IsShiftModifier() ? Toolkit::Control::KeyboardFocus::UP : Toolkit::Control::KeyboardFocus::DOWN); + } + } } isFocusStartableKey = true; @@ -1077,6 +1092,16 @@ bool KeyboardFocusManager::IsFocusIndicatorEnabled() const return ( mEnableFocusIndicator == ENABLE ); } +void KeyboardFocusManager::EnableDefaultAlgorithm(bool enable) +{ + mEnableDefaultAlgorithm = enable; +} + +bool KeyboardFocusManager::IsDefaultAlgorithmEnabled() const +{ + return mEnableDefaultAlgorithm; +} + } // namespace Internal } // namespace Toolkit diff --git a/dali-toolkit/internal/focus-manager/keyboard-focus-manager-impl.h b/dali-toolkit/internal/focus-manager/keyboard-focus-manager-impl.h index 4ab0ea3..28052e3 100644 --- a/dali-toolkit/internal/focus-manager/keyboard-focus-manager-impl.h +++ b/dali-toolkit/internal/focus-manager/keyboard-focus-manager-impl.h @@ -157,6 +157,16 @@ public: */ bool IsFocusIndicatorEnabled() const; + /** + * @copydoc Toolkit::DevelKeyboardFocusManager::EnableDefaultAlgorithm + */ + void EnableDefaultAlgorithm(bool enable); + + /** + * @copydoc Toolkit::DevelKeyboardFocusManager::IsDefaultAlgorithmEnabled + */ + bool IsDefaultAlgorithmEnabled() const; + public: /** @@ -342,6 +352,8 @@ private: bool mIsWaitingKeyboardFocusChangeCommit:1; /// A flag to indicate PreFocusChangeSignal emitted but the proposed focus actor is not commited by the application yet. bool mClearFocusOnTouch:1; ///< Whether clear focus on touch. + + bool mEnableDefaultAlgorithm : 1; ///< Whether use default algorithm focus }; } // namespace Internal