Add AccessibilityScrollable property & ScrollToChild action emission 42/316342/1
authorYoungsun Suh <youngsun.suh@samsung.com>
Mon, 19 Aug 2024 08:18:09 +0000 (17:18 +0900)
committerYoungsun Suh <youngsun.suh@samsung.com>
Tue, 20 Aug 2024 01:44:22 +0000 (10:44 +0900)
Change-Id: I62d1a80aefa03f61681d3b87eb986c7c6ecda964

automated-tests/src/dali-toolkit-internal/utc-Dali-Accessibility-Controls-BridgeUp.cpp
dali-toolkit/devel-api/controls/control-accessible.cpp
dali-toolkit/devel-api/controls/control-accessible.h
dali-toolkit/devel-api/controls/control-devel.h
dali-toolkit/internal/controls/control/control-data-impl.cpp
dali-toolkit/internal/controls/control/control-data-impl.h

index 1818713fc0e8b2885307ca492fd68bfe3c91253d..e022359e2e776b7a384a28d0fa4d8686c14fa8df 100644 (file)
@@ -378,6 +378,42 @@ int UtcDaliControlAccessibilityHighlightable(void)
   END_TEST;
 }
 
+int UtcDaliControlAccessibilityScrollable(void)
+{
+  ToolkitTestApplication application;
+  auto                   control = Control::New();
+
+  auto scrollable = control.GetProperty<bool>(DevelControl::Property::ACCESSIBILITY_SCROLLABLE);
+  DALI_TEST_EQUALS(scrollable, false, TEST_LOCATION);
+
+  // negative testcase - trying to set unconvertible value
+  control.SetProperty(DevelControl::Property::ACCESSIBILITY_SCROLLABLE, "deadbeef");
+  scrollable = control.GetProperty<bool>(DevelControl::Property::ACCESSIBILITY_SCROLLABLE);
+  DALI_TEST_EQUALS(scrollable, false, TEST_LOCATION);
+
+  auto accessible = dynamic_cast<DevelControl::ControlAccessible*>(Dali::Accessibility::Accessible::Get(control));
+
+  Dali::Accessibility::TestEnableSC(true);
+
+  DALI_TEST_CHECK(!accessible->IsScrollable());
+
+  control.SetProperty(DevelControl::Property::ACCESSIBILITY_SCROLLABLE, true);
+  DALI_TEST_EQUALS(Property::BOOLEAN, control.GetProperty(DevelControl::Property::ACCESSIBILITY_SCROLLABLE).GetType(), TEST_LOCATION);
+  DALI_TEST_EQUALS(true, control.GetProperty(DevelControl::Property::ACCESSIBILITY_SCROLLABLE).Get<bool>(), TEST_LOCATION);
+
+  DALI_TEST_CHECK(accessible->IsScrollable());
+
+  control.SetProperty(DevelControl::Property::ACCESSIBILITY_SCROLLABLE, false);
+  DALI_TEST_EQUALS(Property::BOOLEAN, control.GetProperty(DevelControl::Property::ACCESSIBILITY_HIGHLIGHTABLE).GetType(), TEST_LOCATION);
+  DALI_TEST_EQUALS(false, control.GetProperty(DevelControl::Property::ACCESSIBILITY_HIGHLIGHTABLE).Get<bool>(), TEST_LOCATION);
+
+  DALI_TEST_CHECK(!accessible->IsScrollable());
+
+  Dali::Accessibility::TestEnableSC(false);
+
+  END_TEST;
+}
+
 int UtcDaliControlAccessibilityHighlightBridgeUp(void)
 {
   ToolkitTestApplication application;
@@ -1077,8 +1113,8 @@ int UtcDaliAccessibilityDoAction(void)
   DALI_TEST_CHECK(!b->DoAction(actions[2])); // increment
   DALI_TEST_CHECK(!b->DoAction(actions[3])); // decrement
 
-  DevelControl::AccessibilityActionSignal(control).Connect([](Dali::Accessibility::ActionType action) {
-    actions_done.push_back(action);
+  DevelControl::AccessibilityActionSignal(control).Connect([](const Dali::Accessibility::ActionInfo& action_info) {
+    actions_done.push_back(action_info.type);
     return true;
   });
   DevelControl::AccessibilityReadingSkippedSignal(control).Connect([]() {
@@ -1250,6 +1286,115 @@ int UtcDaliAccessibilityScrollToChildScrollView(void)
   END_TEST;
 }
 
+int UtcDaliAccessibilityScrollToChildCustomScrollable(void)
+{
+  ToolkitTestApplication application;
+
+  thread_local Dali::Accessibility::ActionInfo action_done;
+  const auto                                   check_scroll_to_child_action_done_and_reset = [&](Actor child) {
+    DALI_TEST_CHECK(action_done.type == Dali::Accessibility::ActionType::SCROLL_TO_CHILD);
+    DALI_TEST_CHECK(action_done.target == child);
+    action_done = Dali::Accessibility::ActionInfo{};
+  };
+
+  Dali::Accessibility::TestEnableSC(true);
+
+  auto scrollable = Control::New();
+  // set control as scrollable
+  scrollable.SetProperty(DevelControl::Property::ACCESSIBILITY_SCROLLABLE, true);
+
+  DevelControl::AccessibilityActionSignal(scrollable).Connect([](const Dali::Accessibility::ActionInfo& action_info) {
+    action_done = action_info;
+    return true;
+  });
+
+  application.GetScene().Add(scrollable);
+
+  PushButton          actorA    = PushButton::New();
+  const Dali::Vector3 positionA = Vector3(100.0f, 400.0f, 0.0f);
+  actorA.SetProperty(Dali::Actor::Property::POSITION, positionA);
+  scrollable.Add(actorA);
+
+  PushButton          actorB    = PushButton::New();
+  const Dali::Vector3 positionB = Vector3(500.0f, 200.0f, 0.0f);
+  actorB.SetProperty(Dali::Actor::Property::POSITION, positionB);
+  scrollable.Add(actorB);
+
+  TableView tableView = TableView::New(2, 2); // 2 by 2 grid.
+  tableView.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 100.0f));
+  scrollable.Add(tableView);
+
+  PushButton actorC = PushButton::New();
+  actorC.SetProperty(Actor::Property::SIZE, Vector2(50.0f, 50.0f));
+  tableView.AddChild(actorC, TableView::CellPosition(0, 0));
+
+  PushButton actorD = PushButton::New();
+  application.GetScene().Add(actorD);
+
+  Wait(application);
+
+  auto* accessibleParent = dynamic_cast<DevelControl::ControlAccessible*>(Dali::Accessibility::Accessible::Get(scrollable));
+  DALI_TEST_CHECK(accessibleParent);
+  auto* accessibleA = dynamic_cast<DevelControl::ControlAccessible*>(Dali::Accessibility::Accessible::Get(actorA));
+  DALI_TEST_CHECK(accessibleA);
+  auto* accessibleB = dynamic_cast<DevelControl::ControlAccessible*>(Dali::Accessibility::Accessible::Get(actorB));
+  DALI_TEST_CHECK(accessibleB);
+  auto* accessibleC = dynamic_cast<DevelControl::ControlAccessible*>(Dali::Accessibility::Accessible::Get(actorC));
+  DALI_TEST_CHECK(accessibleC);
+  auto* accessibleD = dynamic_cast<DevelControl::ControlAccessible*>(Dali::Accessibility::Accessible::Get(actorD));
+  DALI_TEST_CHECK(accessibleD);
+
+  accessibleA->GrabHighlight();
+  Wait(application);
+  check_scroll_to_child_action_done_and_reset(actorA);
+
+  accessibleB->GrabHighlight();
+  Wait(application);
+  check_scroll_to_child_action_done_and_reset(actorB);
+
+  // scrollable is ancestor of actorC
+  // This should work without a crash
+  accessibleC->GrabHighlight();
+  check_scroll_to_child_action_done_and_reset(actorC);
+
+  // Grabbing highlight on a non-child actor to scrollable does not emit SCROLL_TO_CHILD
+  accessibleD->GrabHighlight();
+  DALI_TEST_CHECK(action_done.type == Dali::Accessibility::ActionType::MAX_COUNT);
+  DALI_TEST_CHECK(action_done.target != actorD);
+
+  Dali::Accessibility::TestEnableSC(false);
+  END_TEST;
+}
+
+int UtcDaliAccessibilityScrollToChild(void)
+{
+  ToolkitTestApplication application;
+
+  Dali::Accessibility::TestEnableSC(true);
+
+  auto parent = Control::New();
+
+  auto                child    = Control::New();
+  const Dali::Vector3 position = Vector3(100.0f, 400.0f, 0.0f);
+  child.SetProperty(Dali::Actor::Property::POSITION, position);
+
+  auto* accessibleParent = dynamic_cast<DevelControl::ControlAccessible*>(Dali::Accessibility::Accessible::Get(parent));
+  DALI_TEST_CHECK(accessibleParent);
+
+  // ScrollToChild fails if no ActionSignal is connected
+  DALI_TEST_CHECK(!accessibleParent->ScrollToChild(child));
+
+  DevelControl::AccessibilityActionSignal(parent).Connect([](const Dali::Accessibility::ActionInfo& action_info) {
+    return true;
+  });
+
+  // ScrollToChild succeeds is an ActionSinal is connected
+  DALI_TEST_CHECK(accessibleParent->ScrollToChild(child));
+
+  Dali::Accessibility::TestEnableSC(false);
+  END_TEST;
+}
+
 namespace
 {
 class TestItemFactory : public ItemFactory
index 7ab6a8b45042ebcea44f68b830d2dc5afde44320..fc11d5d3f2e4dc5a9198ee44a4b25c79359932e5 100644 (file)
@@ -41,6 +41,10 @@ namespace Dali::Toolkit::DevelControl
 {
 namespace
 {
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_CONTROL_ACCESSIBLE");
+#endif
+
 constexpr const char* ATTR_IMG_SRC_KEY = "imgSrc";
 
 std::string GetLocaleText(std::string string, const char* domain = "dali-toolkit")
@@ -509,9 +513,23 @@ std::vector<Dali::Accessibility::Relation> ControlAccessible::GetRelationSet()
   return DevelControl::GetAccessibilityRelations(control);
 }
 
+bool ControlAccessible::IsScrollable() const
+{
+  return Self().GetProperty<bool>(Toolkit::DevelControl::Property::ACCESSIBILITY_SCROLLABLE);
+}
+
 bool ControlAccessible::ScrollToChild(Actor child)
 {
-  return false;
+  auto control = Dali::Toolkit::Control::DownCast(Self());
+  bool success = false;
+
+  if(!DevelControl::AccessibilityActionSignal(control).Empty())
+  {
+    success = DevelControl::AccessibilityActionSignal(control).Emit({Accessibility::ActionType::SCROLL_TO_CHILD, child});
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Performed AccessibilityAction: scrollToChild, success : %d\n", success);
+  }
+
+  return success;
 }
 
 Dali::Property::Index ControlAccessible::GetNamePropertyIndex()
index 98eda71e8a883e9ced04bcddbcff696fdfacbfb3..630ead350cb505857e40c4532d879a7e7d700895 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_CONTROL_ACCESSIBLE_H
 
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 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.
@@ -194,6 +194,11 @@ public:
    */
   std::vector<Dali::Accessibility::Relation> GetRelationSet() override;
 
+  /**
+   * @copydoc Dali::Accessibility::Component::IsScrollable()
+   */
+  bool IsScrollable() const override;
+
   /**
    * @copydoc Dali::Accessibility::Accessible::GetStates()
    */
index a1b97c45b67b661a086eb99b59072d0d9d42a175..0edb65d96b5d95187bd52c665c072aea63b5b01f 100644 (file)
@@ -69,7 +69,7 @@ typedef Signal<void(std::string&)> AccessibilityGetDescriptionSignalType;
 typedef Signal<void(std::pair<Dali::Accessibility::GestureInfo, bool>&)> AccessibilityDoGestureSignalType;
 
 /// @brief AccessibilityAction signal type.
-typedef Signal<bool(Dali::Accessibility::ActionType)> AccessibilityActionSignalType;
+typedef Signal<bool(const Dali::Accessibility::ActionInfo&)> AccessibilityActionSignalType;
 
 enum State
 {
@@ -232,6 +232,12 @@ enum
    * @details Name "accessibilityValue", type Property::STRING.
    */
   ACCESSIBILITY_VALUE,
+
+  /**
+   * @brief Indicates the accessibility services treat the controla as scrollable.
+   * @details Name "accessibilityScrollable", type Property::BOOLEAN.
+   */
+  ACCESSIBILITY_SCROLLABLE,
 };
 
 } // namespace Property
index e101639376b1b8e4be87256392b9ade8ed78ac34..a3da1f13ae40b2bd8519c252e1aa912437f7c32c 100644 (file)
@@ -296,7 +296,7 @@ bool PerformAccessibilityAction(Toolkit::Control control, const std::string& act
 
   if(action != ActionType::MAX_COUNT)
   {
-    bool success = DevelControl::AccessibilityActionSignal(control).Emit(action);
+    bool success = DevelControl::AccessibilityActionSignal(control).Emit({action, Dali::Actor{}});
     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Performed AccessibilityAction: %s, success : %d\n", actionName.c_str(), success);
     return success;
   }
@@ -597,7 +597,8 @@ const PropertyRegistration Control::Impl::PROPERTY_23(typeRegistration, "accessi
 const PropertyRegistration Control::Impl::PROPERTY_24(typeRegistration, "clockwiseFocusableActorId",      Toolkit::DevelControl::Property::CLOCKWISE_FOCUSABLE_ACTOR_ID,     Property::INTEGER, &Control::Impl::SetProperty, &Control::Impl::GetProperty);
 const PropertyRegistration Control::Impl::PROPERTY_25(typeRegistration, "counterClockwiseFocusableActorId", Toolkit::DevelControl::Property::COUNTER_CLOCKWISE_FOCUSABLE_ACTOR_ID, Property::INTEGER, &Control::Impl::SetProperty, &Control::Impl::GetProperty);
 const PropertyRegistration Control::Impl::PROPERTY_26(typeRegistration, "automationId",                   Toolkit::DevelControl::Property::AUTOMATION_ID,                    Property::STRING,  &Control::Impl::SetProperty, &Control::Impl::GetProperty);
-const PropertyRegistration Control::Impl::PROPERTY_27(typeRegistration, "accessibilityValue",             Toolkit::DevelControl::Property::ACCESSIBILITY_VALUE,                    Property::STRING,  &Control::Impl::SetProperty, &Control::Impl::GetProperty);
+const PropertyRegistration Control::Impl::PROPERTY_27(typeRegistration, "accessibilityValue",             Toolkit::DevelControl::Property::ACCESSIBILITY_VALUE,              Property::STRING,  &Control::Impl::SetProperty, &Control::Impl::GetProperty);
+const PropertyRegistration Control::Impl::PROPERTY_28(typeRegistration, "accessibilityScrollable",        Toolkit::DevelControl::Property::ACCESSIBILITY_SCROLLABLE,         Property::BOOLEAN, &Control::Impl::SetProperty, &Control::Impl::GetProperty);
 
 // clang-format on
 
@@ -1655,6 +1656,16 @@ void Control::Impl::SetProperty(BaseObject* object, Property::Index index, const
         }
         break;
       }
+
+      case Toolkit::DevelControl::Property::ACCESSIBILITY_SCROLLABLE:
+      {
+        bool isScrollable;
+        if(value.Get(isScrollable))
+        {
+          controlImpl.mImpl->mAccessibilityProps.isScrollable = isScrollable;
+        }
+        break;
+      }
     }
   }
 }
@@ -1835,6 +1846,12 @@ Property::Value Control::Impl::GetProperty(BaseObject* object, Property::Index i
         value = controlImpl.mImpl->mAccessibilityProps.value;
         break;
       }
+
+      case Toolkit::DevelControl::Property::ACCESSIBILITY_SCROLLABLE:
+      {
+        value = controlImpl.mImpl->mAccessibilityProps.isScrollable;
+        break;
+      }
     }
   }
 
index 9e77c46c4098dfece9f58e92fa1839d4fa554a37..9d8c73771e6ec2abccb358776b52b5129cbefb4e 100644 (file)
@@ -627,18 +627,19 @@ public:
 
   struct AccessibilityProps
   {
-    std::string name{};
-    std::string description{};
-    std::string value{};
-    std::string automationId{};
-    Dali::Accessibility::Role role = Dali::Accessibility::Role::UNKNOWN;
+    std::string                                                                       name{};
+    std::string                                                                       description{};
+    std::string                                                                       value{};
+    std::string                                                                       automationId{};
+    Dali::Accessibility::Role                                                         role = Dali::Accessibility::Role::UNKNOWN;
     std::map<Dali::Accessibility::RelationType, std::set<Accessibility::Accessible*>> relations;
-    Property::Map       extraAttributes{};
-    bool isHighlightable = false;
-    bool isHidden        = false;
+    Property::Map                                                                     extraAttributes{};
+    bool                                                                              isHighlightable = false;
+    bool                                                                              isHidden        = false;
+    bool                                                                              isScrollable    = false;
   } mAccessibilityProps;
 
-  bool mAccessibleCreatable     = true;
+  bool mAccessibleCreatable = true;
 
   // Gesture Detection
   PinchGestureDetector     mPinchGestureDetector;
@@ -690,6 +691,7 @@ public:
   static const PropertyRegistration PROPERTY_25;
   static const PropertyRegistration PROPERTY_26;
   static const PropertyRegistration PROPERTY_27;
+  static const PropertyRegistration PROPERTY_28;
 
 private:
   // Accessibility - notification for highlighted object to check if it is showing.
@@ -698,8 +700,7 @@ private:
   Dali::PropertyNotification                  mAccessibilityPositionNotification;
   Dali::Accessibility::ScreenRelativeMoveType mAccessibilityLastScreenRelativeMoveType{Accessibility::ScreenRelativeMoveType::OUTSIDE};
 
-  std::shared_ptr<Toolkit::DevelControl::ControlAccessible>                         mAccessibleObject;
-
+  std::shared_ptr<Toolkit::DevelControl::ControlAccessible> mAccessibleObject;
 };
 
 } // namespace Internal