[NUI] Add a protected virtual bool HitTest(Touch touch) method.
authorjoogab.yun <joogab.yun@samsung.com>
Sat, 12 Feb 2022 00:55:52 +0000 (09:55 +0900)
committerdongsug-song <35130733+dongsug-song@users.noreply.github.com>
Tue, 15 Mar 2022 08:03:22 +0000 (17:03 +0900)
In the case of TouchEvent or Gesture, there is no way to propagate the event to the view below that is not related.

So, in the hit-test process, called the virtual HitTest method to check whether it was hit or not.

User can override whether hit or not in HitTest.

If it returns false, it means that it will not be hit, and the hit-test continues to the next view.
If it returns true, it means that it was hit, and the touch/gesture event is called from the view as before.

for example)
Override HitTest as below.
HitTest() is called when the View is hit.
If returned false, This means it will not be hit. Then the hit-test goes to the next view.

class MyView : View
{
  protected override bool HitTest(Touch touch)
  {
    return false;
  }
}

class Sample
{
   public void Create()
   {
     View view = new MyView()
     {
       Size = new Size(100, 100),
     }
     view.TouchEvent += (s, e) =>
     {
       return false;
     }
   }
}

src/Tizen.NUI/src/internal/Interop/Interop.ActorSignal.cs
src/Tizen.NUI/src/public/BaseComponents/View.cs
src/Tizen.NUI/src/public/BaseComponents/ViewEvent.cs
src/Tizen.NUI/src/public/BaseComponents/ViewInternal.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/EventPropagationSample.cs [new file with mode: 0755]
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/HitTestSample.cs [new file with mode: 0644]

index 7b06082..e89fe0c 100755 (executable)
@@ -43,6 +43,9 @@ namespace Tizen.NUI
             [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_delete_ActorSignal")]
             public static extern void DeleteActorSignal(global::System.Runtime.InteropServices.HandleRef jarg1);
 
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_Actor_HitTestResultSignal")]
+            public static extern global::System.IntPtr ActorHitTestResultSignal(global::System.Runtime.InteropServices.HandleRef jarg1);
+
             [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_Actor_InterceptTouchSignal")]
             public static extern global::System.IntPtr ActorInterceptTouchSignal(global::System.Runtime.InteropServices.HandleRef jarg1);
 
index 185f04f..7061abd 100755 (executable)
@@ -142,10 +142,15 @@ namespace Tizen.NUI.BaseComponents
             using ViewSignal signal = new ViewSignal(Interop.ActorSignal.ActorOnSceneSignal(SwigCPtr), false);
             signal?.Connect(onWindowSendEventCallback);
 
+            hitTestResultDataCallback = OnHitTestResult;
+            using TouchDataSignal touchDataSignal = new TouchDataSignal(Interop.ActorSignal.ActorHitTestResultSignal(SwigCPtr), false);
+            touchDataSignal?.Connect(hitTestResultDataCallback);
+
             if (!shown)
             {
                 SetVisible(false);
             }
+
         }
 
         internal View(ViewImpl implementation, bool shown = true) : this(Interop.View.NewViewInternal(ViewImpl.getCPtr(implementation)), true)
@@ -3151,7 +3156,20 @@ namespace Tizen.NUI.BaseComponents
             }
         }
 
-
+        /// <summary>
+        /// Called when the view is hit through TouchEvent or GestureEvent.
+        /// If it returns true, it means that it was hit, and the touch/gesture event is called from the view.
+        /// If it returns false, it means that it will not be hit, and the hit-test continues to the next view.
+        /// User can override whether hit or not in HitTest.
+        /// You can get the coordinates relative to tthe top-left of the hit view by touch.GetLocalPosition(0).
+        /// or you can get the coordinates relative to the top-left of the screen by touch.GetScreenPosition(0).
+        /// </summary>
+        // <param name="touch"><see cref="Tizen.NUI.Touch"/>The touch data</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual bool HitTest(Touch touch)
+        {
+            return true;
+        }
 
 
     }
index 77617bb..0f9d355 100755 (executable)
@@ -65,6 +65,7 @@ namespace Tizen.NUI.BaseComponents
             _ = data;
             NUIApplication.GetDefaultWindow()?.SendViewAdded(this);
         }
+        private TouchDataCallbackType hitTestResultDataCallback;
 
         [UnmanagedFunctionPointer(CallingConvention.StdCall)]
         private delegate void OffWindowEventCallbackType(IntPtr control);
@@ -915,6 +916,14 @@ namespace Tizen.NUI.BaseComponents
             }
         }
 
+        // Callback for View HitTestResultSignal
+        private bool OnHitTestResult(IntPtr view, IntPtr touchData)
+        {
+            TouchEventArgs e = new TouchEventArgs();
+            e.Touch = Tizen.NUI.Touch.GetTouchFromPtr(touchData);
+            return HitTest(e.Touch);
+        }
+
         // Callback for View TouchSignal
         private bool OnInterceptTouch(IntPtr view, IntPtr touchData)
         {
index ce23074..4f61176 100755 (executable)
@@ -1334,6 +1334,16 @@ namespace Tizen.NUI.BaseComponents
                 hoverEventCallback = null;
             }
 
+            if (hitTestResultDataCallback != null)
+            {
+                NUILog.Debug($"[Dispose] hitTestResultDataCallback");
+
+                using TouchDataSignal signal = new TouchDataSignal(Interop.ActorSignal.ActorHitTestResultSignal(GetBaseHandleCPtrHandleRef), false);
+                signal?.Disconnect(hitTestResultDataCallback);
+                hitTestResultDataCallback = null;
+            }
+
+
             if (interceptTouchDataCallback != null)
             {
                 NUILog.Debug($"[Dispose] interceptTouchDataCallback");
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/EventPropagationSample.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/EventPropagationSample.cs
new file mode 100755 (executable)
index 0000000..9b9a1fa
--- /dev/null
@@ -0,0 +1,180 @@
+
+using System;
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+
+namespace Tizen.NUI.Samples
+{
+  public class EventPropagationSample : IExample
+  {
+
+    class MyView : View
+    {
+        protected override bool HitTest(Touch touch)
+        {
+            Tizen.Log.Error("NUI", $"override HitTest\n");
+            // If false is returned, the hit-test is continued.
+            // If true is returned, the current View is hit. Event propagation starts from the current View.
+            return false;
+        }
+    }
+
+    class LogOutput : ScrollableBase
+    {
+        public LogOutput()
+        {
+            WidthSpecification = LayoutParamPolicies.MatchParent;
+            HeightSpecification = LayoutParamPolicies.MatchParent;
+            HideScrollbar = false;
+
+            ContentContainer.Layout = new LinearLayout
+            {
+                LinearOrientation = LinearLayout.Orientation.Vertical,
+                VerticalAlignment = VerticalAlignment.Top,
+            };
+        }
+
+        public void AddLog(string log)
+        {
+            Console.WriteLine($"{log}");
+            var txt = new TextLabel
+            {
+                Text = log
+            };
+
+            ContentContainer.Add(txt);
+            if (ContentContainer.Children.Count > 30)
+            {
+                var remove = ContentContainer.Children.GetRange(0, 10);
+                foreach (var child in remove)
+                {
+                    ContentContainer.Remove(child);
+                }
+            }
+            ElmSharp.EcoreMainloop.Post(() =>
+            {
+                ScrollTo((ContentContainer.Children.Count) * (txt.NaturalSize.Height), true);
+            });
+        }
+        public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+        {
+            return null;
+        }
+    }
+
+    public View TargetView;
+    TapGestureDetector tap;
+
+    public void Activate()
+    {
+      Window window = NUIApplication.GetDefaultWindow();
+      TargetView = new View()
+      {
+        WidthResizePolicy = ResizePolicyType.FillToParent,
+        HeightResizePolicy = ResizePolicyType.FillToParent,
+        Layout = new LinearLayout()
+        {
+            LinearOrientation = LinearLayout.Orientation.Vertical,
+        },
+      };
+      window.Add(TargetView);
+
+      var log = new LogOutput();
+
+      TargetView.TouchEvent += (s, e) =>
+      {
+          if (e.Touch.GetState(0) == PointStateType.Down)
+          {
+              log.AddLog($"TouchEvent on Layout");
+          }
+          return false;
+      };
+
+      var margin = new View
+      {
+          SizeHeight = 50,
+          WidthResizePolicy = ResizePolicyType.FillToParent
+      };
+      TargetView.Add(margin);
+      var container = new View
+      {
+          WidthResizePolicy = ResizePolicyType.FillToParent,
+          SizeHeight = 400,
+          BackgroundColor = Color.Gray,
+          Layout = new AbsoluteLayout(),
+      };
+      container.TouchEvent += (s, e) =>
+      {
+          if (e.Touch.GetState(0) == PointStateType.Down)
+          {
+              log.AddLog($"TouchEvent on Gray");
+          }
+          return false;
+      };
+      TargetView.Add(container);
+
+      var layer = new Layer();
+      window.AddLayer(layer);
+      var overlayContainer = new MyView
+      {
+          WidthResizePolicy = ResizePolicyType.FillToParent,
+          SizeHeight = 150,
+          BackgroundColor = Color.Red,
+          Layout = new AbsoluteLayout(),
+      };
+
+      overlayContainer.TouchEvent += (s, e) =>
+      {
+          if (e.Touch.GetState(0) == PointStateType.Down)
+          {
+              log.AddLog($"TouchEvent on Red");
+          }
+          return false;
+      };
+
+      var overlayContainer2 = new View
+      {
+          WidthResizePolicy = ResizePolicyType.FillToParent,
+          SizeHeight = 200,
+          BackgroundColor = Color.Yellow,
+          Layout = new AbsoluteLayout(),
+      };
+      tap = new TapGestureDetector();
+      tap.Detected += (s, e) =>
+      {
+          if (e.TapGesture.State == Gesture.StateType.Started)
+          {
+              log.AddLog($"Gesture Detected on Yellow");
+          }
+      };
+      tap.Attach(overlayContainer2);
+
+      overlayContainer2.TouchEvent += (s, e) =>
+      {
+          if (e.Touch.GetState(0) == PointStateType.Down)
+          {
+              log.AddLog($"TouchEvent on Yellow");
+          }
+          return false;
+      };
+
+      layer.Add(overlayContainer);
+      layer.Add(overlayContainer2);
+      overlayContainer2.Position = new Position(0, 150);
+
+      TargetView.RemovedFromWindow += (s, e) =>
+      {
+          window.RemoveLayer(layer);
+      };
+
+      TargetView.Add(log);
+    }
+
+    public void Deactivate()
+    {
+    }
+
+
+  }
+}
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/HitTestSample.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/HitTestSample.cs
new file mode 100644 (file)
index 0000000..bae95ee
--- /dev/null
@@ -0,0 +1,193 @@
+
+using System;
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+
+namespace Tizen.NUI.Samples
+{
+  public class HitTestSample : IExample
+  {
+    class LogOutput : ScrollableBase
+    {
+        public LogOutput()
+        {
+            WidthSpecification = LayoutParamPolicies.MatchParent;
+            HeightSpecification = LayoutParamPolicies.MatchParent;
+            HideScrollbar = false;
+
+            ContentContainer.Layout = new LinearLayout
+            {
+                LinearOrientation = LinearLayout.Orientation.Vertical,
+                VerticalAlignment = VerticalAlignment.Top,
+            };
+        }
+
+        public void AddLog(string log)
+        {
+            Console.WriteLine($"{log}");
+            var txt = new TextLabel
+            {
+                Text = log
+            };
+
+            ContentContainer.Add(txt);
+            if (ContentContainer.Children.Count > 30)
+            {
+                var remove = ContentContainer.Children.GetRange(0, 10);
+                foreach (var child in remove)
+                {
+                    ContentContainer.Remove(child);
+                }
+            }
+            ElmSharp.EcoreMainloop.Post(() =>
+            {
+                ScrollTo((ContentContainer.Children.Count) * (txt.NaturalSize.Height), true);
+            });
+        }
+        public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+        {
+            return null;
+        }
+    }
+
+    class MyView : View
+    {
+        protected override bool HitTest(Touch touch)
+        {
+            if(hitTest == true)
+            {
+                log.AddLog($"BlueView hittest hitted");
+            }
+            else
+            {
+                log.AddLog($"BlueView hittest passed");
+            }
+            // If false is returned, the hit-test is continued.
+            // If true is returned, the current View is hit. Event propagation starts from the current View.
+            return hitTest;
+        }
+    }
+
+    View rootView;
+    static bool hitTest = true;
+    static LogOutput log = new LogOutput();
+    public void Activate()
+    {
+        Window window = NUIApplication.GetDefaultWindow();
+
+        rootView = new View()
+        {
+            WidthResizePolicy = ResizePolicyType.FillToParent,
+            HeightResizePolicy = ResizePolicyType.FillToParent,
+            Layout = new LinearLayout()
+            {
+                LinearOrientation = LinearLayout.Orientation.Vertical,
+            },
+        };
+
+        View container = new View()
+        {
+            WidthResizePolicy = ResizePolicyType.FillToParent,
+            SizeHeight = 400,
+            Layout = new AbsoluteLayout(),
+        };
+
+        View blueView = new MyView()
+        {
+            Size = new Size(200, 100),
+            Position = new Position(75, 125),
+            BackgroundColor = Color.Blue,
+            GrabTouchAfterLeave = true,
+            LeaveRequired = true,
+        };
+
+        blueView.TouchEvent += (s, e) =>
+        {
+            // if (e.Touch.GetState(0) == PointStateType.Down)
+            {
+                log.AddLog($"TouchEvent on BlueView {e.Touch.GetState(0)}");
+            }
+            return false;
+        };
+
+        // TapGestureDetector blueTap = new TapGestureDetector();
+        // blueTap.Detected += (s, e) =>
+        // {
+        //     if (e.TapGesture.State == Gesture.StateType.Started)
+        //     {
+        //         log.AddLog($"Gesture Detected on blueView");
+        //     }
+        // };
+        // blueTap.Attach(blueView);
+
+
+        View greenView = new View()
+        {
+            Size = new Size(300, 200),
+            Position = new Position(50, 100),
+            BackgroundColor = Color.Green,
+            GrabTouchAfterLeave = true,
+        };
+        greenView.TouchEvent += (s, e) =>
+        {
+            // if (e.Touch.GetState(0) == PointStateType.Down)
+            {
+                log.AddLog($"TouchEvent on greenView {e.Touch.GetState(0)}");
+            }
+            return false;
+        };
+
+        // TapGestureDetector greenTap = new TapGestureDetector();
+        // greenTap.Detected += (s, e) =>
+        // {
+        //     if (e.TapGesture.State == Gesture.StateType.Started)
+        //     {
+
+        //         log.AddLog($"Gesture Detected on greenView");
+        //     }
+        // };
+        // greenTap.Attach(greenView);
+
+
+
+        Button hitTestPass = new Button()
+        {
+            Text = "Blue HitTest Pass",
+        };
+        hitTestPass.Clicked += (o, e) => {
+            hitTest = false;
+        };
+
+        Button hitTestHit = new Button()
+        {
+            Text = "Blue HitTest Hit",
+            Position = new Position(350, 0),
+        };
+        hitTestHit.Clicked += (o, e) => {
+            hitTest = true;
+        };
+
+        container.Add(greenView);
+        container.Add(blueView);
+        container.Add(hitTestPass);
+        container.Add(hitTestHit);
+
+        rootView.Add(container);
+        rootView.Add(log);
+        window.Add(rootView);
+
+    }
+
+    public void Deactivate()
+    {
+        if (rootView != null)
+        {
+            NUIApplication.GetDefaultWindow().Remove(rootView);
+            rootView.Dispose();
+        }
+    }
+
+
+  }
+}