[ATSPI] Check higlight area using PropertyNotification 42/260342/18
authorShinwoo Kim <cinoo.kim@samsung.com>
Wed, 23 Jun 2021 11:29:30 +0000 (20:29 +0900)
committerShinwoo Kim <cinoo.kim@samsung.com>
Wed, 21 Jul 2021 03:39:44 +0000 (12:39 +0900)
Move highlight to next or prev if highlighted object goes out of screen.
This patch is using PropertyNotification only for highlighted object.

Change-Id: Idaea158fa9e9a1eb569ab619210d2aa916f28f22

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

index f8eec35..6d61f56 100644 (file)
@@ -10,6 +10,8 @@ namespace Dali {
         using MethodType = TestDBusWrapper::MethodType;
         using MessagePtr = DBusWrapper::MessagePtr;
 
+        static bool MoveOutedCalled = false;
+
         void TestEnableSC(bool b) {
             static bool firstTime = true;
             if (b && firstTime) {
@@ -71,6 +73,11 @@ namespace Dali {
                 [wr](const MessagePtr &m) -> MessagePtr {
                     return wr->newReplyMessage(m);
                 };
+                wr->testMethods[std::tuple<std::string, std::string, std::string, MethodType>{"/org/a11y/atspi/accessible", "org.a11y.atspi.Event.Object", "MoveOuted", MethodType::Method}] =
+                [wr](const MessagePtr &m) -> MessagePtr {
+                    MoveOutedCalled = true;
+                    return wr->newReplyMessage(m);
+                };
             }
             auto wr = static_cast<TestDBusWrapper*>(DBusWrapper::Installed());
             wr->fromTestChangeProperty("/org/a11y/bus", "org.a11y.Status", "ScreenReaderEnabled", b);
@@ -267,6 +274,15 @@ namespace Dali {
             return std::move(std::get<0>(chs));
         }
 
+        void TestResetMoveOutedCalled ()
+        {
+          MoveOutedCalled = false;
+        }
+
+        bool TestGetMoveOutedCalled ()
+        {
+          return MoveOutedCalled;
+        }
 
         void printTree(const Address &root, size_t depth)
         {
index 8c3d201..4966ff2 100644 (file)
@@ -36,7 +36,8 @@ namespace Dali {
         bool TestDoAction ( const Address &adr, const std::string& name );
         std::string TestGetActionKeyBinding ( const Address &adr, size_t index );
         std::string TestGetActionDescription ( const Address &adr, size_t index );
-
+        void TestResetMoveOutedCalled ();
+        bool TestGetMoveOutedCalled ();
     }
 }
 
index a6b682e..6d47f06 100644 (file)
@@ -1051,3 +1051,63 @@ int UtcDaliAccessibilityScrollToChildNonScrollable(void)
   Dali::Accessibility::TestEnableSC( false );
   END_TEST;
 }
+
+int UtcDaliAccessibilityCheckHighlight(void)
+{
+  ToolkitTestApplication application;
+  Dali::Accessibility::TestEnableSC( true );
+  Dali::Accessibility::TestResetMoveOutedCalled();
+
+  // Make precondition two children exist in parent area
+  PushButton parentButton = PushButton::New();
+  parentButton.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
+  parentButton.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
+  parentButton.SetProperty( Dali::Actor::Property::POSITION, Dali::Vector2(0.0f, 0.0f) );
+  parentButton.SetProperty( Dali::Actor::Property::SIZE, Dali::Vector2(100.0f, 200.0f) );
+  application.GetScene().Add( parentButton );
+
+  PushButton buttonA = PushButton::New();
+  buttonA.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
+  buttonA.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
+  buttonA.SetProperty( Dali::Actor::Property::POSITION, Dali::Vector2(0.0f, 0.0f) );
+  buttonA.SetProperty( Dali::Actor::Property::SIZE, Dali::Vector2(100.0f, 100.0f) );
+  parentButton.Add(buttonA);
+
+  PushButton buttonB = PushButton::New();
+  buttonB.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
+  buttonB.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
+  buttonB.SetProperty( Dali::Actor::Property::POSITION, Dali::Vector2(0.0f, 100.0f) );
+  buttonB.SetProperty( Dali::Actor::Property::SIZE, Dali::Vector2(100.0f, 100.0f) );
+  parentButton.Add(buttonB);
+  Wait(application);
+
+  // Move second child out of parent area
+  auto* accessible = dynamic_cast<DevelControl::AccessibleImpl*>(Dali::Accessibility::Accessible::Get(buttonA));
+  DALI_TEST_CHECK(accessible);
+  accessible->GrabHighlight();
+  Wait(application);
+
+  buttonA.SetProperty( Dali::Actor::Property::POSITION, Dali::Vector2(0.0f, -200.0f) );
+  Wait(application);
+  // Need one more seding notificaiton to get correct updated position
+  application.SendNotification();
+  DALI_TEST_EQUALS( true, Dali::Accessibility::TestGetMoveOutedCalled(), TEST_LOCATION );
+
+  // Reset verdict data
+  Dali::Accessibility::TestResetMoveOutedCalled();
+
+  // Move second child out of parent area
+  accessible = dynamic_cast<DevelControl::AccessibleImpl*>(Dali::Accessibility::Accessible::Get(buttonB));
+  DALI_TEST_CHECK(accessible);
+  accessible->GrabHighlight();
+  Wait(application);
+
+  buttonB.SetProperty( Dali::Actor::Property::POSITION, Dali::Vector2(300.0f, 100.0f) );
+  Wait(application);
+  // Need one more seding notificaiton to get correct updated position
+  application.SendNotification();
+  DALI_TEST_EQUALS( true, Dali::Accessibility::TestGetMoveOutedCalled(), TEST_LOCATION );
+
+  Dali::Accessibility::TestEnableSC( false );
+  END_TEST;
+}
index c42cb8a..774a280 100644 (file)
@@ -328,6 +328,22 @@ void AccessibleImpl::ScrollToSelf()
   }
 }
 
+void AccessibleImpl::RegisterPositionPropertyNotification()
+{
+  auto                     control         = Dali::Toolkit::Control::DownCast(Self());
+  Internal::Control&       internalControl = Toolkit::Internal::GetImplementation(control);
+  Internal::Control::Impl& controlImpl     = Internal::Control::Impl::Get(internalControl);
+  controlImpl.RegisterAccessibilityPositionPropertyNotification();
+}
+
+void AccessibleImpl::UnregisterPositionPropertyNotification()
+{
+  auto                     control         = Dali::Toolkit::Control::DownCast(Self());
+  Internal::Control&       internalControl = Toolkit::Internal::GetImplementation(control);
+  Internal::Control::Impl& controlImpl     = Internal::Control::Impl::Get(internalControl);
+  controlImpl.UnregisterAccessibilityPositionPropertyNotification();
+}
+
 bool AccessibleImpl::GrabHighlight()
 {
   Dali::Actor self = Self();
@@ -360,11 +376,15 @@ bool AccessibleImpl::GrabHighlight()
     SetHighlightActor(highlight);
   }
 
-  highlight.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
-  highlight.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
+  highlight.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
   highlight.SetProperty(Actor::Property::POSITION_Z, 1.0f);
   highlight.SetProperty(Actor::Property::POSITION, Vector2(0.0f, 0.0f));
 
+  // Need to set resize policy again, to update SIZE property which is set by
+  // AccessibleImpl_NUI. The highlight could move from AccessibleImpl_NUI to
+  // AccessibleImpl. In this case, highlight has incorrect size.
+  highlight.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
+
   // Remember the highlight actor, so that when the default is changed with
   // SetHighlightActor(), the currently displayed highlight can still be cleared.
   mCurrentHighlightActor = highlight;
@@ -372,6 +392,7 @@ bool AccessibleImpl::GrabHighlight()
   self.Add(highlight);
   SetCurrentlyHighlightedActor(self);
   EmitHighlighted(true);
+  RegisterPositionPropertyNotification();
 
   return true;
 }
@@ -387,6 +408,7 @@ bool AccessibleImpl::ClearHighlight()
 
   if(GetCurrentlyHighlightedActor() == self)
   {
+    UnregisterPositionPropertyNotification();
     self.Remove(mCurrentHighlightActor.GetHandle());
     mCurrentHighlightActor = {};
     SetCurrentlyHighlightedActor({});
@@ -495,4 +517,14 @@ Dali::Property::Index AccessibleImpl::GetDescriptionPropertyIndex()
   return Dali::Property::INVALID_INDEX;
 }
 
+void AccessibleImpl::SetLastPosition(Vector2 position)
+{
+  mLastPosition = position;
+}
+
+Vector2 AccessibleImpl::GetLastPosition() const
+{
+  return mLastPosition;
+}
+
 } // namespace Dali::Toolkit::DevelControl
index 37e7ad3..c8c1361 100644 (file)
@@ -47,6 +47,7 @@ struct DALI_TOOLKIT_API AccessibleImpl : public virtual Dali::Accessibility::Acc
                                          public virtual Dali::Accessibility::Action
 {
 protected:
+  Vector2                       mLastPosition{0.0f, 0.0f};
   Dali::WeakHandle<Dali::Actor> mSelf;
   Dali::WeakHandle<Dali::Actor> mCurrentHighlightActor;
   bool mIsModal = false;
@@ -65,6 +66,16 @@ protected:
 
   void ScrollToSelf();
 
+  /**
+   * @brief Register property notification to check highlighted object position
+   */
+  void RegisterPositionPropertyNotification();
+
+  /**
+   * @brief Remove property notification added by RegisterPropertyNotification
+   */
+  void UnregisterPositionPropertyNotification();
+
 public:
   AccessibleImpl(Dali::Actor self, Dali::Accessibility::Role role, bool modal = false);
 
@@ -228,6 +239,18 @@ public:
    * @brief Returns the index of the property that represents this actor's description
    */
   virtual Dali::Property::Index GetDescriptionPropertyIndex();
+
+  /**
+   * @brief Sets last object position
+   * @param[in] position Last object position
+   */
+  void SetLastPosition(Vector2 position);
+
+  /**
+   * @brief Gets last object position
+   * @return The Last object position
+   */
+  Vector2 GetLastPosition() const;
 };
 
 } // namespace Dali::Toolkit::DevelControl
index db346bd..8fd35e0 100644 (file)
@@ -410,6 +410,35 @@ void SetVisualsOffScene(const RegisteredVisualContainer& container, Actor parent
   }
 }
 
+Dali::Rect<> GetShowingGeometry(Dali::Rect<> rect, Dali::Toolkit::DevelControl::AccessibleImpl* accessibleImpl)
+{
+  Rect<>  parentRect;
+  Vector2 currentPosition;
+  auto    parent = dynamic_cast<Toolkit::DevelControl::AccessibleImpl*>(accessibleImpl->GetParent());
+
+  while(parent)
+  {
+    parentRect = parent->GetExtents(Dali::Accessibility::CoordinateType::WINDOW);
+
+    currentPosition.x = rect.x;
+    currentPosition.y = rect.y;
+
+    rect.x      = rect.x > parentRect.x ? rect.x : parentRect.x;
+    rect.y      = rect.y > parentRect.y ? rect.y : parentRect.y;
+    rect.width  = currentPosition.x + rect.width < parentRect.x + parentRect.width ? currentPosition.x + rect.width - rect.x : parentRect.x + parentRect.width - rect.x;
+    rect.height = currentPosition.y + rect.height < parentRect.y + parentRect.height ? currentPosition.y + rect.height - rect.y : parentRect.y + parentRect.height - rect.y;
+
+    if(rect.width < 0 || rect.height < 0)
+    {
+      return rect;
+    }
+
+    parent = dynamic_cast<Toolkit::DevelControl::AccessibleImpl*>(parent->GetParent());
+  }
+
+  return rect;
+}
+
 } // unnamed namespace
 
 // clang-format off
@@ -518,6 +547,70 @@ const Control::Impl& Control::Impl::Get(const Internal::Control& internalControl
   return *internalControl.mImpl;
 }
 
+void Control::Impl::CheckHighlightedObjectGeometry(PropertyNotification& propertyNotification)
+{
+  auto accessibleImpl = dynamic_cast<Dali::Toolkit::DevelControl::AccessibleImpl*>(mAccessibilityObject.get());
+  auto lastPosition   = accessibleImpl->GetLastPosition();
+  auto accessibleRect = accessibleImpl->GetExtents(Dali::Accessibility::CoordinateType::WINDOW);
+
+  if(lastPosition.x == accessibleRect.x && lastPosition.y == accessibleRect.y)
+  {
+    return;
+  }
+
+  auto rect = GetShowingGeometry(accessibleRect, accessibleImpl);
+
+  // MoveOuted is sent already, no need to send it again
+  if(mAccessibilityMovedOutOfScreenDirection != Dali::Accessibility::MovedOutOfScreenType::NONE)
+  {
+    if(rect.width > 0 && rect.height > 0)
+    {
+      // flick next does not use MoveOuted - ScrollToSelf makes object show, so reset for sending MoveOuted next
+      mAccessibilityMovedOutOfScreenDirection = Dali::Accessibility::MovedOutOfScreenType::NONE;
+    }
+    return;
+  }
+
+  if(rect.width < 0)
+  {
+    mAccessibilityMovedOutOfScreenDirection = (accessibleRect.x < lastPosition.x) ? Dali::Accessibility::MovedOutOfScreenType::TOP_LEFT : Dali::Accessibility::MovedOutOfScreenType::BOTTOM_RIGHT;
+  }
+
+  if(rect.height < 0)
+  {
+    mAccessibilityMovedOutOfScreenDirection = (accessibleRect.y < lastPosition.y) ? Dali::Accessibility::MovedOutOfScreenType::TOP_LEFT : Dali::Accessibility::MovedOutOfScreenType::BOTTOM_RIGHT;
+  }
+
+  if(mAccessibilityMovedOutOfScreenDirection != Dali::Accessibility::MovedOutOfScreenType::NONE)
+  {
+    mAccessibilityObject.get()->EmitMovedOutOfScreen(mAccessibilityMovedOutOfScreenDirection);
+    accessibleImpl->SetLastPosition(Vector2(0.0f, 0.0f));
+    return;
+  }
+
+  accessibleImpl->SetLastPosition(Vector2(accessibleRect.x, accessibleRect.y));
+}
+
+void Control::Impl::RegisterAccessibilityPositionPropertyNotification()
+{
+  if(mIsAccessibilityPositionPropertyNotificationSet)
+  {
+    return;
+  }
+
+  mAccessibilityMovedOutOfScreenDirection = Dali::Accessibility::MovedOutOfScreenType::NONE;
+  mAccessibilityPositionNotification      = mControlImpl.Self().AddPropertyNotification(Actor::Property::WORLD_POSITION, StepCondition(1.0f, 1.0f));
+  mAccessibilityPositionNotification.SetNotifyMode(PropertyNotification::NOTIFY_ON_CHANGED);
+  mAccessibilityPositionNotification.NotifySignal().Connect(this, &Control::Impl::CheckHighlightedObjectGeometry);
+  mIsAccessibilityPositionPropertyNotificationSet = true;
+}
+
+void Control::Impl::UnregisterAccessibilityPositionPropertyNotification()
+{
+  mControlImpl.Self().RemovePropertyNotification(mAccessibilityPositionNotification);
+  mIsAccessibilityPositionPropertyNotificationSet = false;
+}
+
 // Gesture Detection Methods
 void Control::Impl::PinchDetected(Actor actor, const PinchGesture& pinch)
 {
index e278390..4c480a0 100644 (file)
@@ -468,6 +468,22 @@ private:
    */
   void OnIdleCallback();
 
+  /**
+   * @brief Checks highlighted object geometry if it is showing or not
+   * @param[in] propertyNotification PropertyNotification
+   */
+  void CheckHighlightedObjectGeometry(Dali::PropertyNotification& propertyNotification);
+
+  /**
+   * @brief Register property notification to check highlighted object position
+   */
+  void RegisterAccessibilityPositionPropertyNotification();
+
+  /**
+   * @brief Remove property notification added by RegisterPropertyNotification
+   */
+  void UnregisterAccessibilityPositionPropertyNotification();
+
 public:
   Control&            mControlImpl;
   DevelControl::State mState;
@@ -562,6 +578,12 @@ public:
   static const PropertyRegistration PROPERTY_20;
   static const PropertyRegistration PROPERTY_21;
   static const PropertyRegistration PROPERTY_22;
+
+private:
+  // Accessibility - notification for highlighted object to check if it is showing.
+  bool                                      mIsAccessibilityPositionPropertyNotificationSet{false};
+  Dali::PropertyNotification                mAccessibilityPositionNotification;
+  Dali::Accessibility::MovedOutOfScreenType mAccessibilityMovedOutOfScreenDirection;
 };
 
 } // namespace Internal