[NUI] Particle System binding and implementation of C# side
authorAdam Bialogonski <adam.b@samsung.com>
Fri, 4 Aug 2023 14:57:10 +0000 (15:57 +0100)
committerbshsqa <32317749+bshsqa@users.noreply.github.com>
Thu, 14 Sep 2023 10:40:33 +0000 (19:40 +0900)
ParticleSystem provides 4 classes:
ParticleEmitter - emitter of particles
ParticleSource - source of particles (spawning new particles)
ParticleModifier - controller of particles behaviour
Particle - wrapper over particle data

src/Tizen.NUI/src/internal/Interop/Interop.Particle.cs [new file with mode: 0644]
src/Tizen.NUI/src/internal/Interop/Interop.ParticleEmitter.cs [new file with mode: 0644]
src/Tizen.NUI/src/internal/Interop/Interop.ParticleModifier.cs [new file with mode: 0644]
src/Tizen.NUI/src/internal/Interop/Interop.ParticleSource.cs [new file with mode: 0644]
src/Tizen.NUI/src/public/ParticleSystem/Particle.cs [new file with mode: 0644]
src/Tizen.NUI/src/public/ParticleSystem/ParticleEmitter.cs [new file with mode: 0644]
src/Tizen.NUI/src/public/ParticleSystem/ParticleModifier.cs [new file with mode: 0644]
src/Tizen.NUI/src/public/ParticleSystem/ParticleSource.cs [new file with mode: 0644]
test/Tizen.NUI.ParticleSystem.Sample/ParticleSystemSample.cs [new file with mode: 0644]
test/Tizen.NUI.ParticleSystem.Sample/Tizen.NUI.ParticleSystem.Sample.csproj [new file with mode: 0644]
test/Tizen.NUI.ParticleSystem.Sample/res/image/blue-part2.png [new file with mode: 0644]

diff --git a/src/Tizen.NUI/src/internal/Interop/Interop.Particle.cs b/src/Tizen.NUI/src/internal/Interop/Interop.Particle.cs
new file mode 100644 (file)
index 0000000..6c8817f
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright(c) 2023 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.
+ *
+ */
+
+using System;
+using global::System.Runtime.InteropServices;
+
+namespace Tizen.NUI.ParticleSystem
+{
+    internal static partial class Interop
+    {
+        internal static partial class Particle
+        {
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_Particle_ReadFloat")]
+            internal static extern float ReadFloat(global::System.Runtime.InteropServices.HandleRef jarg1, uint streamIndex, uint particleIndex);
+            
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_Particle_ReadVector2")]
+            internal static extern global::System.IntPtr ReadVector2(global::System.Runtime.InteropServices.HandleRef jarg1, uint streamIndex, uint particleIndex);
+            
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_Particle_ReadVector3")]
+            internal static extern global::System.IntPtr ReadVector3(global::System.Runtime.InteropServices.HandleRef jarg1, uint streamIndex, uint particleIndex);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_Particle_ReadVector4")]
+            internal static extern global::System.IntPtr ReadVector4(global::System.Runtime.InteropServices.HandleRef jarg1, uint streamIndex, uint particleIndex);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_Particle_WriteFloat")]
+            internal static extern void WriteFloat(global::System.Runtime.InteropServices.HandleRef jarg1, uint streamIndex, uint particleIndex, float value);
+            
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_Particle_WriteVector2")]
+            internal static extern void WriteVector2(global::System.Runtime.InteropServices.HandleRef jarg1, uint streamIndex, uint particleIndex, HandleRef value);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_Particle_WriteVector3")]
+            internal static extern void WriteVector3(global::System.Runtime.InteropServices.HandleRef jarg1, uint streamIndex, uint particleIndex, HandleRef value);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_Particle_WriteVector4")]
+            internal static extern void WriteVector4(global::System.Runtime.InteropServices.HandleRef jarg1, uint streamIndex, uint particleIndex, HandleRef value);
+        }
+    }
+}  
+
diff --git a/src/Tizen.NUI/src/internal/Interop/Interop.ParticleEmitter.cs b/src/Tizen.NUI/src/internal/Interop/Interop.ParticleEmitter.cs
new file mode 100644 (file)
index 0000000..c463e81
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Copyright(c) 2023 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.
+ *
+ */
+
+using global::System.Runtime.InteropServices;
+
+namespace Tizen.NUI.ParticleSystem
+{
+    internal static partial class Interop
+    {
+        internal static partial class ParticleEmitter
+        {
+            [DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_New_SWIG_0")]
+            internal static extern global::System.IntPtr New(HandleRef view);
+            
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_delete_ParticleEmitter")]
+            internal static extern void DeleteParticleEmitter(global::System.Runtime.InteropServices.HandleRef jarg1);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_Assign")]
+            internal static extern global::System.IntPtr Assign(global::System.Runtime.InteropServices.HandleRef jarg1, global::System.Runtime.InteropServices.HandleRef jarg2);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_DownCast")]
+            internal static extern global::System.IntPtr DownCast(global::System.Runtime.InteropServices.HandleRef jarg1);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_SetSource")]
+            internal static extern void SetSource(global::System.Runtime.InteropServices.HandleRef jarg1, global::System.Runtime.InteropServices.HandleRef jarg2);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_SetDomain")]
+            internal static extern void SetDomain(global::System.Runtime.InteropServices.HandleRef jarg1, global::System.Runtime.InteropServices.HandleRef jarg2);
+  
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_SetRenderer")]
+            internal static extern void SetRenderer(global::System.Runtime.InteropServices.HandleRef jarg1, global::System.Runtime.InteropServices.HandleRef jarg2);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_AddModifier")]
+            internal static extern void AddModifier(global::System.Runtime.InteropServices.HandleRef jarg1, global::System.Runtime.InteropServices.HandleRef jarg2);
+            
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_SetParticleCount")]
+            internal static extern void SetParticleCount(global::System.Runtime.InteropServices.HandleRef jarg1, uint count);
+            
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_GetParticleCount")]
+            internal static extern uint GetParticleCount(global::System.Runtime.InteropServices.HandleRef jarg1);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_SetEmissionRate")]
+            internal static extern void SetEmissionRate(global::System.Runtime.InteropServices.HandleRef jarg1, uint count);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_SetInitialParticleCount")]
+            internal static extern void SetInitialParticleCount(global::System.Runtime.InteropServices.HandleRef jarg1, uint count);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_SetActiveParticlesLimit")]
+            internal static extern void SetActiveParticlesLimit(global::System.Runtime.InteropServices.HandleRef jarg1, uint count);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_GetActiveParticlesLimit")]
+            internal static extern uint GetActiveParticlesLimit(global::System.Runtime.InteropServices.HandleRef jarg1);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_Start")]
+            internal static extern void Start(global::System.Runtime.InteropServices.HandleRef jarg1);
+            
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_Stop")]
+            internal static extern void Stop(global::System.Runtime.InteropServices.HandleRef jarg1);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_RemoveModifierAt")]
+            internal static extern void RemoveModifierAt(global::System.Runtime.InteropServices.HandleRef jarg1, uint index);
+            
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_GetModifierAt")]
+            internal static extern global::System.IntPtr GetModifierAt(global::System.Runtime.InteropServices.HandleRef jarg1, uint index);
+            
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_GetParticleList")]
+            internal static extern global::System.IntPtr GetParticleList(global::System.Runtime.InteropServices.HandleRef jarg1);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_GetSource")]
+            internal static extern global::System.IntPtr GetSource(global::System.Runtime.InteropServices.HandleRef jarg1);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_GetDomain")]
+            internal static extern global::System.IntPtr GetDomain(global::System.Runtime.InteropServices.HandleRef jarg1);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_GetRenderer")]
+            internal static extern global::System.IntPtr GetRenderer(global::System.Runtime.InteropServices.HandleRef jarg1);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_GetEmissionRate")]
+            internal static extern uint GetEmissionRate(global::System.Runtime.InteropServices.HandleRef jarg1);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_GetInitialParticleCount")]
+            internal static extern uint GetInitialParticleCount(global::System.Runtime.InteropServices.HandleRef jarg1);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_GetActiveParticleLimit")]
+            internal static extern uint GetActiveParticleLimit(global::System.Runtime.InteropServices.HandleRef jarg1);
+            
+            // ParticleRenderer
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleRenderer_SetTexture")]
+            internal static extern void SetTexture(global::System.Runtime.InteropServices.HandleRef jarg1, global::System.Runtime.InteropServices.HandleRef jarg2);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleRenderer_SetBlendingMode")]
+            internal static extern void SetBlendingMode(global::System.Runtime.InteropServices.HandleRef jarg1, ParticleBlendingMode mode);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleRenderer_GetBlendingMode")]
+            internal static extern int GetBlendingMode(global::System.Runtime.InteropServices.HandleRef jarg1);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleEmitter_NewParticle")]
+            internal static extern int NewParticle(global::System.IntPtr emitter, float lifetime);
+
+            // ParticleList
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleList_AddLocalStream")]
+            internal unsafe static extern uint AddLocalStream(global::System.Runtime.InteropServices.HandleRef jarg1, uint streamType, void* defaultValue, uint typeSize );
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleList_GetDefaultStreamIndex")]
+            internal unsafe static extern int GetDefaultStreamIndex(global::System.Runtime.InteropServices.HandleRef jarg1, uint builtInStream );
+
+        }
+    }
+}  
+
diff --git a/src/Tizen.NUI/src/internal/Interop/Interop.ParticleModifier.cs b/src/Tizen.NUI/src/internal/Interop/Interop.ParticleModifier.cs
new file mode 100644 (file)
index 0000000..d110894
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright(c) 2023 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.
+ *
+ */
+
+using global::System.Runtime.InteropServices;
+using System.Reflection;
+using System;
+
+namespace Tizen.NUI.ParticleSystem
+{
+    internal static partial class Interop
+    {
+        internal static partial class ParticleModifier
+        {
+            internal delegate void ParticleModifierUpdateInvokerType(IntPtr ptr, IntPtr particleListPtr, uint first, uint count);
+            
+            [DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleModifier_New_SWIG_0")]
+            public static extern global::System.IntPtr New(ParticleModifierUpdateInvokerType updateInvoker, out IntPtr basePtr);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Tizen.NUI/src/internal/Interop/Interop.ParticleSource.cs b/src/Tizen.NUI/src/internal/Interop/Interop.ParticleSource.cs
new file mode 100644 (file)
index 0000000..10c0ca2
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright(c) 2023 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.
+ *
+ */
+
+using global::System.Runtime.InteropServices;
+using System.Reflection;
+using System;
+
+namespace Tizen.NUI.ParticleSystem
+{
+    internal static partial class Interop
+    {
+        internal static partial class ParticleSource
+        {
+            internal delegate void ParticleSourceInitInvokerType(IntPtr ptr);
+            internal delegate uint ParticleSourceUpdateInvokerType(IntPtr ptr, uint count);
+            
+            [DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ParticleSource_New_SWIG_0")]
+            internal static extern global::System.IntPtr New(ParticleSourceInitInvokerType initInvoker, ParticleSourceUpdateInvokerType updateInvoker, out IntPtr refObject);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Tizen.NUI/src/public/ParticleSystem/Particle.cs b/src/Tizen.NUI/src/public/ParticleSystem/Particle.cs
new file mode 100644 (file)
index 0000000..5315592
--- /dev/null
@@ -0,0 +1,500 @@
+/*
+ * Copyright(c) 2023 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.
+ *
+ */
+
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System;
+using System.Runtime.InteropServices;
+using global::System.Runtime.InteropServices;
+using System.ComponentModel;
+using System.Reflection;
+
+namespace Tizen.NUI.ParticleSystem
+{
+    using Tizen.NUI.BaseComponents;
+
+    /// <summary>
+    /// Declares types of default streams 
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public enum ParticleStream
+    {
+        Position      = 1 << 0, // Vector3, Position of particle
+        Rotation      = 1 << 1, // Vector4, Rotation of particle (quaternion)
+        Scale         = 1 << 2, // Vector3, Scale of particle
+        Size          = 1 << 3, // Vector3, size of particle
+        Color         = 1 << 4, // Vector4/Color - Color of particle, (RGBA)
+        Opacity       = 1 << 5, // float, opacity (0.0-1.0)
+        Velocity      = 1 << 6, // Vector3, vector of velocity
+        Lifetime      = 1 << 7, // float, remaining lifetime
+        Lifetime_Base = 1 << 8, // float, initial lifetime
+    }
+
+    /// <summary>
+    /// Particle class provides interface to particle data streams
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class Particle
+    {
+        /// <summary>
+        /// StreamView provides functionality allowing particle
+        /// data manipulation (read/write).
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public class StreamView
+        {
+            internal StreamView(HandleRef list, uint particleIndex, uint streamIndex)
+            {
+                mParticleIndex = particleIndex;
+                mStreamIndex = (int)streamIndex;
+                mEmitterRef = list;
+            }
+            
+            internal StreamView(HandleRef list, uint particleIndex, ParticleStream builtInStream)
+            {
+                mEmitterRef = list;
+                mParticleIndex = particleIndex;
+                mStreamIndex = Interop.ParticleEmitter.GetDefaultStreamIndex(mEmitterRef, (uint)(builtInStream));
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            }
+            
+            private struct Value
+            {
+                internal float valueFloat;
+                internal Vector2 valueVector2;
+                internal Vector3 valueVector3;
+                internal Vector4 valueVector4;
+            }
+
+            internal StreamView(float f)
+            {
+                value.valueFloat = f;
+                type = typeof(float);
+            }
+            
+            internal StreamView(Vector2 f)
+            {
+                value.valueVector2 = f;
+                type = typeof(Vector2);
+            }
+            
+            internal StreamView(Vector3 f)
+            {
+                value.valueVector3 = f;
+                type = typeof(Vector3);
+            }
+            
+            internal StreamView(Vector4 f)
+            {
+                value.valueVector4 = f;
+                type = typeof(Vector4);
+            }
+            
+            /// <summary>
+            /// Conversion operator to float value
+            /// </summary>
+            /// <param name="sv">StreamView object</param>
+            /// <returns>Converted value</returns>
+            [EditorBrowsable(EditorBrowsableState.Never)]
+            public static implicit operator float(StreamView sv)
+            {
+                var ret = sv.mStreamIndex >= 0 ? Interop.Particle.ReadFloat(sv.mEmitterRef, (uint)sv.mStreamIndex, sv.mParticleIndex) : 0.0f;
+                if (NDalicPINVOKE.SWIGPendingException.Pending)
+                    throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return ret;
+            }
+
+            /// <summary>
+            /// Conversion operator to Vector2 value
+            /// </summary>
+            /// <param name="sv">StreamView object</param>
+            /// <returns>Converted value</returns>
+            [EditorBrowsable(EditorBrowsableState.Never)]
+            public static implicit operator Vector2(StreamView sv) {
+                var ret = sv.mStreamIndex >= 0 ? new Vector2(Interop.Particle.ReadVector2(sv.mEmitterRef, (uint)sv.mStreamIndex, sv.mParticleIndex), true) : Vector2.Zero;
+                if (NDalicPINVOKE.SWIGPendingException.Pending)
+                    throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return ret;
+            }
+            
+            /// <summary>
+            /// Conversion operator to Vector3 value
+            /// </summary>
+            /// <param name="sv">StreamView object</param>
+            /// <returns>Converted value</returns>
+            [EditorBrowsable(EditorBrowsableState.Never)]
+            public static implicit operator Vector3(StreamView sv) {
+                var ret = sv.mStreamIndex >= 0 ? new Vector3(Interop.Particle.ReadVector3(sv.mEmitterRef, (uint)sv.mStreamIndex, sv.mParticleIndex), true) : Vector3.Zero;
+                if (NDalicPINVOKE.SWIGPendingException.Pending)
+                    throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return ret;
+            }
+
+            /// <summary>
+            /// Conversion operator to Vecto4 value
+            /// </summary>
+            /// <param name="sv">StreamView object</param>
+            /// <returns>Converted value</returns>
+            [EditorBrowsable(EditorBrowsableState.Never)]
+            public static implicit operator Vector4(StreamView sv) {
+                
+                var ret = sv.mStreamIndex >= 0 ? new Vector4(Interop.Particle.ReadVector4(sv.mEmitterRef, (uint)sv.mStreamIndex, sv.mParticleIndex), true) : Vector4.Zero;
+                if (NDalicPINVOKE.SWIGPendingException.Pending)
+                    throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return ret;
+            }
+
+            private int mStreamIndex;
+            private uint mParticleIndex;
+            private HandleRef mEmitterRef;
+            private Value value;
+            private System.Type type;
+        }
+        
+        /// <summary>
+        /// Create an initialized Particle.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        internal Particle(HandleRef emitter, uint index)
+        {
+            mIndex = index;
+            mEmitterRef = emitter;
+        }
+        
+        /// <summary>
+        /// Returns value from specified data stream (default/custom)
+        /// </summary>
+        /// <param name="streamIndex">Index of stream to get value from</param>
+        /// <returns>StreamView object</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public StreamView GetStreamValue(uint streamIndex)
+        {
+            return new StreamView(mEmitterRef, mIndex, streamIndex);
+        }
+
+        /// <summary>
+        /// Returns value from specified default streamIndex
+        /// </summary>
+        /// <param name="streamIndex">Index of stream to get value from</param>
+        /// <returns>StreamView object</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public StreamView GetStreamValue(ParticleStream streamIndex)
+        {
+            return new StreamView(mEmitterRef, mIndex, streamIndex);
+        }
+
+        /// <summary>
+        /// Sets value on the specified data stream
+        /// </summary>
+        /// <param name="value">Value to set</param>
+        /// <param name="streamIndex">Index of stream to get value from</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void SetStreamValue(float value, uint streamIndex)
+        {
+            Interop.Particle.WriteFloat(mEmitterRef, streamIndex, mIndex, value);
+            if (NDalicPINVOKE.SWIGPendingException.Pending)
+                throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+        
+        /// <summary>
+        /// Sets value on the specified data stream
+        /// </summary>
+        /// <param name="value">Value to set</param>
+        /// <param name="streamIndex">Index of stream to get value from</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void SetStreamValue(Vector2 value, uint streamIndex)
+        {
+            Interop.Particle.WriteVector2(mEmitterRef, streamIndex, mIndex, value.SwigCPtr);
+            if (NDalicPINVOKE.SWIGPendingException.Pending)
+                throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+
+        /// <summary>
+        /// Sets value on the specified data stream
+        /// </summary>
+        /// <param name="value">Value to set</param>
+        /// <param name="streamIndex">Index of stream to get value from</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void SetStreamValue(Vector3 value, uint streamIndex)
+        {
+            Interop.Particle.WriteVector3(mEmitterRef, streamIndex, mIndex, value.SwigCPtr);
+            if (NDalicPINVOKE.SWIGPendingException.Pending)
+                throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+        
+        /// <summary>
+        /// Sets value on the specified data stream
+        /// </summary>
+        /// <param name="value">Value to set</param>
+        /// <param name="streamIndex">Index of stream to get value from</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void SetStreamValue(Vector4 value, uint streamIndex)
+        {
+            Interop.Particle.WriteVector4(mEmitterRef, streamIndex, mIndex, value.SwigCPtr);
+            if (NDalicPINVOKE.SWIGPendingException.Pending)
+                throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+        
+        /// <summary>
+        /// Sets value on the specified data stream
+        /// </summary>
+        /// <param name="value">Value to set</param>
+        /// <param name="particleStream">Stream to get value from</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void SetStreamValue(float value, ParticleStream particleStream)
+        {
+            uint streamIndex = (uint)(Interop.ParticleEmitter.GetDefaultStreamIndex(mEmitterRef, (uint)(particleStream)));
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            
+            Interop.Particle.WriteFloat(mEmitterRef, streamIndex, mIndex, value);
+            if (NDalicPINVOKE.SWIGPendingException.Pending)
+                throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+        
+        /// <summary>
+        /// Sets value on the specified data stream
+        /// </summary>
+        /// <param name="value">Value to set</param>
+        /// <param name="particleStream">Stream to get value from</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void SetStreamValue(Vector2 value, ParticleStream particleStream)
+        {
+            uint streamIndex = (uint)(Interop.ParticleEmitter.GetDefaultStreamIndex(mEmitterRef, (uint)(particleStream)));
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            
+            Interop.Particle.WriteVector2(mEmitterRef, streamIndex, mIndex, value.SwigCPtr);
+            if (NDalicPINVOKE.SWIGPendingException.Pending)
+                throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+
+        /// <summary>
+        /// Sets value on the specified data stream
+        /// </summary>
+        /// <param name="value">Value to set</param>
+        /// <param name="particleStream">Stream to get value from</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void SetStreamValue(Vector3 value, ParticleStream particleStream)
+        {
+            uint streamIndex = (uint)(Interop.ParticleEmitter.GetDefaultStreamIndex(mEmitterRef, (uint)(particleStream)));
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            
+            Interop.Particle.WriteVector3(mEmitterRef, streamIndex, mIndex, value.SwigCPtr);
+            if (NDalicPINVOKE.SWIGPendingException.Pending)
+                throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+        
+        /// <summary>
+        /// Sets value on the specified data stream
+        /// </summary>
+        /// <param name="value">Value to set</param>
+        /// <param name="particleStream">Stream to get value from</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void SetStreamValue(Vector4 value, ParticleStream particleStream)
+        {
+            uint streamIndex = (uint)(Interop.ParticleEmitter.GetDefaultStreamIndex(mEmitterRef, (uint)(particleStream)));
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            
+            Interop.Particle.WriteVector4(mEmitterRef, streamIndex, mIndex, value.SwigCPtr);
+            if (NDalicPINVOKE.SWIGPendingException.Pending)
+                throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+        
+
+        /// <summary>
+        /// Returns index of one of default streams.
+        /// </summary>
+        /// <param name="streamBit">Stream to get index</param>
+        /// <returns>Index of stream within emitter</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        private uint GetStreamIndex(ParticleStream streamBit)
+        {
+            uint streamIndex = (uint)(Interop.ParticleEmitter.GetDefaultStreamIndex(mEmitterRef, (uint)(streamBit)));
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            return streamIndex;
+        }
+        
+        /// <summary>
+        /// Position of the Particle.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Vector3 Position
+        {
+            get
+            {
+                var streamIndex = GetStreamIndex(ParticleStream.Position);
+                var ret = new Vector3(Interop.Particle.ReadVector3(mEmitterRef, (uint)streamIndex, mIndex), true);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return ret;
+            }
+            set
+            {
+                SetStreamValue( value, ParticleStream.Position);
+            }
+        }
+
+        /// <summary>
+        /// Color of the Particle.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Vector4 Color
+        {
+            get
+            {
+                var streamIndex = GetStreamIndex(ParticleStream.Color);
+                var ret = new Vector4(Interop.Particle.ReadVector4(mEmitterRef, (uint)streamIndex, mIndex), true);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return ret;
+            }
+            set
+            {
+                SetStreamValue( value, ParticleStream.Color);
+            }
+        }
+        
+        /// <summary>
+        /// Velocity of the Particle.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Vector3 Velocity
+        {
+            get
+            {
+                var streamIndex = GetStreamIndex(ParticleStream.Velocity);
+                var ret = new Vector3(Interop.Particle.ReadVector3(mEmitterRef, (uint)streamIndex, mIndex), true);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return ret;
+            }
+            set
+            {
+                SetStreamValue( value, ParticleStream.Velocity);
+            }
+        }
+        
+        /// <summary>
+        /// Scale of the Particle.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Vector3 Scale
+        {
+            get
+            {
+                var streamIndex = GetStreamIndex(ParticleStream.Scale);
+                var ret = new Vector3(Interop.Particle.ReadVector3(mEmitterRef, (uint)streamIndex, mIndex), true);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return ret;
+            }
+            set
+            {
+                SetStreamValue( value, ParticleStream.Scale);
+            }
+        }
+        
+        /// <summary>
+        /// Rotation of the Particle.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Vector4 Rotation
+        {
+            get
+            {
+                var streamIndex = GetStreamIndex(ParticleStream.Rotation);
+                var ret = new Vector4(Interop.Particle.ReadVector4(mEmitterRef, (uint)streamIndex, mIndex), true);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return ret;
+            }
+            set
+            {
+                SetStreamValue( value, ParticleStream.Rotation);
+            }
+        }
+        
+        /// <summary>
+        /// Opacity of the Particle.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public float Opacity
+        {
+            get
+            {
+                var streamIndex = GetStreamIndex(ParticleStream.Opacity);
+                var ret = Interop.Particle.ReadFloat(mEmitterRef, (uint)streamIndex, mIndex);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return ret;
+            }
+            set
+            {
+                SetStreamValue( value, ParticleStream.Opacity);
+            }
+        }
+        
+        /// <summary>
+        /// Lifetime of the Particle.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public float Lifetime
+        {
+            get
+            {
+                var streamIndex = GetStreamIndex(ParticleStream.Lifetime);
+                var ret = Interop.Particle.ReadFloat(mEmitterRef, (uint)streamIndex, mIndex);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return ret;
+            }
+            set
+            {
+                SetStreamValue( value, ParticleStream.Lifetime);
+            }
+        }
+        
+        /// <summary>
+        /// Initial lifetime of the Particle.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public float LifetimeBase
+        {
+            get
+            {
+                var streamIndex = GetStreamIndex(ParticleStream.Lifetime_Base);
+                var ret = Interop.Particle.ReadFloat(mEmitterRef, (uint)streamIndex, mIndex);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return ret;
+            }
+            set
+            {
+                SetStreamValue( value, ParticleStream.Lifetime_Base);
+            }
+        }
+
+        /// <summary>
+        /// Index of the Particle.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        internal uint Index
+        {
+            get => mIndex;
+            set
+            {
+                mIndex = value;
+            }
+        }
+
+        private uint mIndex;
+        private readonly HandleRef mEmitterRef;
+    }
+
+}
\ No newline at end of file
diff --git a/src/Tizen.NUI/src/public/ParticleSystem/ParticleEmitter.cs b/src/Tizen.NUI/src/public/ParticleSystem/ParticleEmitter.cs
new file mode 100644 (file)
index 0000000..498f7f8
--- /dev/null
@@ -0,0 +1,575 @@
+/*
+ * Copyright(c) 2023 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.
+ *
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.ComponentModel;
+
+namespace Tizen.NUI.ParticleSystem
+{
+    using Tizen.NUI.BaseComponents;
+
+    /// <summary>
+    /// Enum defining blending options when rendering the particles.
+    /// </summary>
+    public enum ParticleBlendingMode
+    {
+        Additive = 0,
+        Screen = 1,
+        Default = Additive
+    }
+    
+    /// <summary>
+    /// Internal class defining data types stored in the data streams
+    /// </summary>
+    internal enum StreamType
+    {
+        Float = 0,
+        FloatVector2 = 1,
+        FloatVector3 = 2,
+        FloatVector4 = 3,
+        Integer = 4,
+        IntVector2 = 5,
+        IntVector3 = 6,
+        IntVector4 = 7,
+    }
+    
+    /// <summary>
+    /// Class ParticleEmitter creates a single emitter attached to a specified
+    /// View. ParticleEmitter is responsible for spawning and updating particles.
+    ///
+    /// Emitter must contain:
+    /// ParticleSource - responsible for spawning new particles
+    /// ParticleModifier(s) - responsible for updating particles in the system
+    ///
+    /// ParticleSource and ParticleModifier callback interfaces should not be accessing
+    /// Event side (NUI) objects. Both callbacks are executed on Update thread.
+    /// </summary>
+    public class ParticleEmitter : BaseHandle
+    {
+        internal ParticleEmitter(global::System.IntPtr cPtr, bool cMemoryOwn) : base(cPtr, cMemoryOwn)
+        {
+        }
+
+        /// <summary>
+        /// Create an initialized ParticleEmitter.
+        /// </summary>
+        /// <param name="view">View to attach the particle emitter.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ParticleEmitter(View view) : this(Interop.ParticleEmitter.New(view.SwigCPtr), true)
+        {
+            mProxy = new ParticleEmitterProxy(this);
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+        
+        /// <summary>
+        /// Copy constructor.
+        /// </summary>
+        /// <param name="particleEmitter">Source object to copy.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ParticleEmitter( ParticleEmitter particleEmitter) : this(Interop. ParticleEmitter.New( ParticleEmitter.getCPtr(particleEmitter)), true)
+        {
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+
+        /// <summary>
+        /// Assignment operator.
+        /// </summary>
+        /// <param name="particleEmitter">Source object to be assigned.</param>
+        /// <returns>Reference to this.</returns>
+        internal ParticleEmitter Assign( ParticleEmitter particleEmitter)
+        {
+            ParticleEmitter ret = new ParticleEmitter(Interop.ParticleEmitter.Assign(SwigCPtr, ParticleEmitter.getCPtr(particleEmitter)), false);
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            return ret;
+        }
+        
+        /// <summary>
+        /// Raises the window to the top of the window stack.
+        /// </summary>
+        /// <param name="particleEmitter">Source object to copy.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void SetSource<T>(ParticleSource<T> source) where T : ParticleSourceInterface, new()
+        {
+            // update interface
+            source.SetEmitter(this);
+            
+            // Set native source
+            Interop.ParticleEmitter.SetSource(SwigCPtr, source.SwigCPtr);
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+        
+        /// <summary>
+        /// Maximum particle count
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public uint ParticleCount
+        {
+            get
+            {
+                var value = Interop.ParticleEmitter.GetParticleCount(SwigCPtr);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+
+                return value;
+            }
+            set
+            {
+                Interop.ParticleEmitter.SetParticleCount(SwigCPtr, value);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            }
+        }
+
+        /// <summary>
+        /// Rate of emission per second
+        /// </summary>
+        /// <remarks>
+        /// EmissionRate defines number of particles emitted per second.
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public uint EmissionRate
+        {
+            get
+            {
+                var value = Interop.ParticleEmitter.GetEmissionRate(SwigCPtr);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return value;
+            }
+            set
+            {
+                Interop.ParticleEmitter.SetEmissionRate(SwigCPtr, value);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            }
+        }
+        
+        /// <summary>
+        /// Initial particle count
+        /// </summary>
+        /// <remarks>
+        /// Initial number of particles to be emitted immediately after emitter starts. It allows
+        /// initial burst emission. By default it's set to 0.
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public uint InitialParticleCount
+        {
+            get
+            {
+                var value = Interop.ParticleEmitter.GetInitialParticleCount(SwigCPtr);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return value;
+            }
+            set 
+            {
+                Interop.ParticleEmitter.SetInitialParticleCount(SwigCPtr, value);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve(); 
+            }
+        }
+        
+        /// <summary>
+        /// Limit of active particles in the system
+        /// </summary>
+        /// <remarks>
+        /// Active particles in the system can be limited without changing <see cref="ParticleCount"/>.
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public uint ActiveParticleLimit
+        {
+            get{
+                var value = Interop.ParticleEmitter.GetActiveParticlesLimit(SwigCPtr);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return value;
+            }
+            set 
+            {
+                Interop.ParticleEmitter.SetActiveParticlesLimit(SwigCPtr, value);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            }
+            
+        }
+
+        /// <summary>
+        /// Gets/sets blending mode for particle renderer
+        /// </summary>
+        /// <remarks>
+        /// Currently two blending modes are supported: Additive and Screen (advanced blending mode).
+        /// <see cref="ParticleBlendingMode"/>
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ParticleBlendingMode RendererBlendingMode
+        {
+            get
+            {                
+                var value = Interop.ParticleEmitter.GetBlendingMode(SwigCPtr);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+                return (ParticleBlendingMode)value;
+            }
+            set
+            {
+                Interop.ParticleEmitter.SetBlendingMode(SwigCPtr, value);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            }
+        }
+        
+        /// <summary>
+        /// Gets/sets texture to be used by the renderer
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Texture RendererTexture
+        {
+            set
+            {
+                Interop.ParticleEmitter.SetTexture(SwigCPtr, value.SwigCPtr);
+                if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            }
+        }
+        
+        /// <summary>
+        /// Adds ParticleModifier to the stack
+        /// </summary>
+        /// <remarks>
+        /// ParticleEmitter implements a stack of modifiers which are responsible for
+        /// updating particles in the system. The stack is processed such as result of
+        /// previous modifier is an input for next modifier.
+        /// </remarks>
+        /// <param name="modifier">Valid modifier object</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void AddModifier<T>(ParticleModifier<T> modifier) where T : ParticleModifierInterface, new()
+        {
+            // update interface
+            modifier.SetEmitter(this);
+            
+            Interop.ParticleEmitter.AddModifier(SwigCPtr, modifier.SwigCPtr);
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+
+        /// <summary>
+        /// Returns associated ParticleSource object
+        /// </summary>
+        /// <returns>Valid ParticleSource object or null</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ParticleSource<T> GetSource<T>() where T : ParticleSourceInterface, new()
+        {
+            IntPtr cPtr = Interop.ParticleEmitter.GetSource(SwigCPtr);
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            ParticleSource<T> ret = (cPtr == IntPtr.Zero) ? null : Registry.GetManagedBaseHandleFromNativePtr(cPtr) as ParticleSource<T>;
+            return ret;
+        }
+
+        /// <summary>
+        /// Returns modifier at specified index
+        /// </summary>
+        /// <param name="index">Index within modifier stack</param>
+        /// <returns>Valid ParticleModifier object or null</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ParticleModifier<ParticleModifierInterface> GetModifierAt(uint index)
+        {
+            IntPtr cPtr = Interop.ParticleEmitter.GetModifierAt(SwigCPtr, index);
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            ParticleModifier<ParticleModifierInterface> ret = (cPtr == IntPtr.Zero) ? null : Registry.GetManagedBaseHandleFromNativePtr(cPtr) as ParticleModifier<ParticleModifierInterface>;
+            return ret;
+        }
+
+        /// <summary>
+        /// Starts emission of particles.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void Start()
+        {
+            Interop.ParticleEmitter.Start(SwigCPtr);
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+
+        /// <summary>
+        /// Stops emission of particles.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void Stop()
+        {
+            Interop.ParticleEmitter.Stop(SwigCPtr);
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+        
+        /// <summary>
+        /// Adds local (not used by shader) data stream to the particle emitter
+        /// </summary>
+        /// <remarks>
+        /// Adds new stream of float type.
+        /// </remarks>
+        /// <param name="defaultValue">Default value to fill the stream with</param>
+        /// <returns>Index of newly created data stream</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public unsafe uint AddLocalStreamFloat(float defaultValue)
+        {
+            var result = Interop.ParticleEmitter.AddLocalStream(SwigCPtr, (uint)StreamType.Float, &defaultValue, sizeof(float));
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            return result;
+        }
+        
+        /// <summary>
+        /// Adds local (not used by shader) data stream to the particle emitter
+        /// </summary>
+        /// <remarks>
+        /// Adds new stream of Vector2 type.
+        /// </remarks>
+        /// <param name="defaultValue">Default value to fill the stream with</param>
+        /// <returns>Index of newly created data stream</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public unsafe uint AddLocalStreamVector2(Vector2 defaultValue)
+        {
+            var result = Interop.ParticleEmitter.AddLocalStream(SwigCPtr, (uint)StreamType.FloatVector2, (void*)defaultValue.SwigCPtr.Handle, sizeof(float)*2);
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            return result;
+        }
+        
+        /// <summary>
+        /// Adds local (not used by shader) data stream to the particle emitter
+        /// </summary>
+        /// <remarks>
+        /// Adds new stream of Vector3 type.
+        /// </remarks>
+        /// <param name="defaultValue">Default value to fill the stream with</param>
+        /// <returns>Index of newly created data stream</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public unsafe uint AddLocalStreamVector3(Vector3 defaultValue)
+        {
+            var result = Interop.ParticleEmitter.AddLocalStream(SwigCPtr, (uint)StreamType.FloatVector3, (void*)defaultValue.SwigCPtr.Handle, sizeof(float)*3);
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            return result;
+        }
+        
+        /// <summary>
+        /// Adds local (not used by shader) data stream to the particle emitter
+        /// </summary>
+        /// <remarks>
+        /// Adds new stream of Vector4 type.
+        /// </remarks>
+        /// <param name="defaultValue">Default value to fill the stream with</param>
+        /// <returns>Index of newly created data stream</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public unsafe uint AddLocalStreamVector4(Vector4 defaultValue)
+        {
+            var result = Interop.ParticleEmitter.AddLocalStream(SwigCPtr, (uint)StreamType.FloatVector4, (void*)defaultValue.SwigCPtr.Handle, sizeof(float)*4);
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            return result;
+        }
+
+        // Internal proxy object to be used on the update thread
+        internal ParticleEmitterProxy EmitterProxy => mProxy;
+        private ParticleEmitterProxy mProxy = null;
+    }
+    
+    /// <summary>
+    /// This class provides functionality that can be used inside the Source/Modifier callbacks.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class ParticleEmitterProxy
+    {
+        internal ParticleEmitterProxy(ParticleEmitter emitter)
+        {
+            mEmitterBasePtr = emitter.SwigCPtr.Handle;
+            mEmitter = emitter;
+        }
+        
+        /// <summary>
+        /// Creates new particle
+        /// </summary>
+        /// <remarks>
+        /// Function may fail and return null if current number of particles exceeds limits of emitter.
+        /// Particle is valid only inside the callback and must not be stored and used anywhere else. Otherwise
+        /// the behaviour is undefined.
+        /// </remarks>
+        /// <param name="lifetime">Lifetime of the particle in seconds</param>
+        /// <returns>New Particle object or null</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Particle NewParticle( float lifetime )
+        {
+            var result = Interop.ParticleEmitter.NewParticle(mEmitterBasePtr, lifetime);
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            if (result >= 0)
+            {
+                // TODO: new particles should be coming form cached queue
+                return new Particle(mEmitter.SwigCPtr, (uint)result);
+            }
+            return null;
+        }
+        
+        /// <summary>
+        /// Acquires list of Particles of specified length
+        /// </summary>
+        /// <remarks>
+        /// The function should be use internally only. Native side passes list of indices of particles (int[]).
+        /// Before calling the callback the indices must be marshalled and converted into the Particle objects.
+        ///
+        /// Internal Particle cache is used to speed up acquiring new Particle.
+        /// </remarks>
+        /// <param name="nativePtr">Native pointer to the list of indices (int32)</param>
+        /// <param name="count">Number of elements</param>
+        /// <returns>List of Particle objects</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        internal List<Particle> AcquireParticleList(IntPtr nativePtr, uint count)
+        {
+            // Populate enough particles into cache
+            while(mParticleCache.Count < count)
+            {
+                mParticleCache.Push(new Particle(mEmitter.SwigCPtr, 0));
+            }
+            
+            List<Particle> retval = new List<Particle>();
+            for (var i = 0; i < count; ++i)
+            {
+                var particleIndex = Marshal.ReadInt32(nativePtr, i * 4);
+                Particle p = mParticleCache.Pop();
+                p.Index = (uint)particleIndex;
+                retval.Add(p);
+            }
+
+            return retval;
+        }
+
+        /// <summary>
+        /// Releases list of Particles back into the pool
+        /// </summary>
+        /// <remarks>
+        /// Acquired particles come from internal pool and must be returned so then they can
+        /// be recycled.
+        /// </remarks>
+        /// <param name="particles">List of particles to be returned</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        internal void ReleaseParticleList(List<Particle> particles)
+        {
+            // return particles back into the pull
+            for(var i = 0; i < particles.Count; ++i)
+            {
+                mParticleCache.Push(particles[i]);
+            }
+            
+            // clear the list (probably not needed?)
+            particles.Clear();
+        }
+        
+        /// <summary>
+        /// Adds local particle data stream of float values
+        /// </summary>
+        /// <param name="defaultValue">Default value</param>
+        /// <returns>Index of new stream</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public uint AddLocalStreamFloat(float defaultValue)
+        {
+            return mEmitter.AddLocalStreamFloat(defaultValue);
+        }
+        
+        /// <summary>
+        /// Adds local particle data stream of Vector2 values
+        /// </summary>
+        /// <param name="defaultValue">Default value</param>
+        /// <returns>Index of new stream</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public uint AddLocalStreamVector2(Vector2 defaultValue)
+        {
+            return mEmitter.AddLocalStreamVector2(defaultValue);
+        }
+        
+        /// <summary>
+        /// Adds local particle data stream of Vector3 values
+        /// </summary>
+        /// <param name="defaultValue">Default value</param>
+        /// <returns>Index of new stream</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public uint AddLocalStreamVector3(Vector3 defaultValue)
+        {
+            return mEmitter.AddLocalStreamVector3(defaultValue);
+        }
+        
+        /// <summary>
+        /// Adds local particle data stream of Vector4 values
+        /// </summary>
+        /// <param name="defaultValue">Default value</param>
+        /// <returns>Index of new stream</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public uint AddLocalStreamVector4(Vector4 defaultValue)
+        {
+            return mEmitter.AddLocalStreamVector4(defaultValue);
+        }
+        
+        // Stack of cached particles
+        private Stack<Particle> mParticleCache = new Stack<Particle>();
+        
+        private IntPtr mEmitterBasePtr;
+        private ParticleEmitter mEmitter;
+
+    }
+
+    /// <summary>
+    /// Register binding Source/Modifier interface to the pointer of a native counterpart 
+    /// </summary>
+    /// <typeparam name="T">Class type of objects to be stored in the register</typeparam>
+    internal class ParticleInterfaceRegister<T> where T : class
+    {
+        internal void Register(IntPtr cPtr, T iface)
+        {
+            lock (mBasePtr)
+            {
+                mBasePtr.Add(cPtr);
+                mInterfaces.Add(iface);
+            }
+        }
+
+        internal bool Remove(IntPtr cPtr)
+        {
+            lock (mBasePtr)
+            {
+                var result = mBasePtr.FindIndex(0, x => x == cPtr);
+                if (result >= 0)
+                {
+                    mBasePtr.RemoveAt(result);
+                    mInterfaces.RemoveAt(result);
+                }
+
+                return result >= 0;
+            }
+        }
+
+        internal bool Remove(T iface)
+        {
+            lock (mBasePtr)
+            {
+                var result = mInterfaces.FindIndex(0, x => x.Equals(iface));
+                if (result >= 0)
+                {
+                    mBasePtr.RemoveAt(result);
+                    mInterfaces.RemoveAt(result);
+                }
+
+                return result >= 0;
+            }
+        }
+        
+        internal T Get(IntPtr cPtr)
+        {
+            var result = mBasePtr.FindIndex(0, x => x == cPtr );
+            if (result >= 0)
+            {
+                return mInterfaces[result];
+            }
+
+            return null;
+        }
+        
+        private List<IntPtr> mBasePtr = new List<IntPtr>();
+        private List<T> mInterfaces = new List<T>();
+    }
+}
\ No newline at end of file
diff --git a/src/Tizen.NUI/src/public/ParticleSystem/ParticleModifier.cs b/src/Tizen.NUI/src/public/ParticleSystem/ParticleModifier.cs
new file mode 100644 (file)
index 0000000..b19ed39
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Copyright(c) 2023 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.
+ *
+ */
+
+using System.Reflection;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Tizen.NUI.ParticleSystem
+{
+    /// <summary>
+    /// ParticleModifierInterface provides callbacks in order to define
+    /// how particles in the system should be modified 
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class ParticleModifierInterface
+    {
+        /// <summary>
+        /// Constructor
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ParticleModifierInterface()
+        {
+        }
+
+        /// <summary>
+        /// Second constructor
+        /// </summary>
+        /// <remarks>
+        /// Second constructor should be overriden by the implementation.
+        /// It allows passing variable number of arguments for processing.
+        /// It is called immediately following the class constructor.
+        /// </remarks>
+        /// <param name="list">List of arguments</param>
+        [EditorBrowsable(EditorBrowsableState.Never)] 
+        public virtual void Construct(params object[] list)
+        {
+        }
+
+        /// <summary>
+        /// Updates the ParticleModifier.
+        /// </summary>
+        /// <remarks>
+        /// This callback is responsible for updating particles in the system.
+        /// 
+        /// This callback runs on the Update thread! It should avoid using NUI objects.
+        /// 
+        /// </remarks>
+        /// <param name="emitterProxy">Proxy to the ParticleEmitter object</param>
+        /// <param name="particleList">List of particles to be updated by the modifier</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void Update(ParticleEmitterProxy emitterProxy, List<Particle> particleList)
+        {
+        }
+
+        /// <summary>
+        /// ParticleEmitter proxy that can be accessed by the user implementation
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ParticleEmitterProxy Emitter;
+    }
+    
+    
+    /// <summary>
+    /// Class represents particle modifier
+    /// </summary>
+    /// <remarks>
+    /// ParticleModifier modifies existing particles in the system.
+    /// Modifiers can be stacked (more than one can be added to the ParticleEmitter).
+    /// Output of one modifier becomes input for next modifier.
+    ///
+    /// Modifier calls into the implementation of <see cref="ParticleModifierInterface"> class.
+    /// 
+    /// </remarks>
+    /// <typeparam name="T">Class of interface that derives from <see cref="ParticleModifierInterface"/></typeparam>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public partial class ParticleModifier<T> : BaseHandle where T : ParticleModifierInterface, new()
+    {
+        // static cache for modifiers (binding between native side and interfaces)
+        static ParticleInterfaceRegister<ParticleModifierInterface> gModifierInterfaceRegister = new ParticleInterfaceRegister<ParticleModifierInterface>();
+        
+        /// <summary>
+        /// Destructor
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        ~ParticleModifier()
+        {
+            gModifierInterfaceRegister.Remove(mInterface);
+        }
+
+        /// <summary>
+        /// Invoker for ParticleModifierInterface.Update()
+        /// </summary>
+        /// <param name="cPtr">Native pointer of ParticleModifier base object</param>
+        /// <param name="listPtr">C-style array of integers</param>
+        /// <param name="first">First particle to be modified (now always 0)</param>
+        /// <param name="count">Number of particles to be modified</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        static void OnUpdateInvoker(IntPtr cPtr, IntPtr listPtr, uint first, uint count)
+        {
+            if (count > 0)
+            {
+                T modifier = (cPtr == IntPtr.Zero) ? null : gModifierInterfaceRegister.Get(cPtr) as T;
+                var list = modifier?.Emitter.AcquireParticleList(listPtr, count);
+                modifier?.Update(modifier.Emitter, list);
+                modifier?.Emitter.ReleaseParticleList(list);
+            }
+        }
+
+
+        internal ParticleModifier(global::System.IntPtr cPtr, bool cMemoryOwn) : base(cPtr, cMemoryOwn)
+        {
+        } 
+        
+        
+        /// <summary>
+        /// Constructor of ParticleModifier
+        /// </summary>
+        /// <remarks>
+        /// ParticleModifier is a generic type that will call back into the given ParticleModifierInterface
+        /// instance.
+        /// The instance of T (derived from ParticleModifierInterface) is created internally and own by the
+        /// ParticleModifier.
+        /// The constructor takes variable number of arguments which is processed when called ParticleModifierInterface.Construct()
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ParticleModifier(params object[] list) : this(Interop.ParticleModifier.New(mOnUpdateInvoker, out gRefObjectPtr), true)
+        {
+            // Create interface on the C# side (no direct connection with C++)
+            mInterface = new T();
+            mInterface.Construct(list);
+            
+            // Register interface using base ptr
+            gModifierInterfaceRegister.Register(gRefObjectPtr, mInterface);
+            
+            // Initialise native side for this interface
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        internal void SetEmitter(ParticleEmitter emitter)
+        {
+            mInterface.Emitter = new ParticleEmitterProxy(emitter);
+        }
+        
+        private static Interop.ParticleModifier.ParticleModifierUpdateInvokerType mOnUpdateInvoker = OnUpdateInvoker;
+        private ParticleModifierInterface mInterface = null;
+        private static IntPtr gRefObjectPtr;
+    }
+}
\ No newline at end of file
diff --git a/src/Tizen.NUI/src/public/ParticleSystem/ParticleSource.cs b/src/Tizen.NUI/src/public/ParticleSystem/ParticleSource.cs
new file mode 100644 (file)
index 0000000..0fab75f
--- /dev/null
@@ -0,0 +1,206 @@
+/*
+ * Copyright(c) 2023 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.
+ *
+ */
+
+using System;
+using System.ComponentModel;
+
+namespace Tizen.NUI.ParticleSystem
+{
+    using Tizen.NUI.BaseComponents;
+
+    /// <summary>
+    /// ParticleSourceInterface provides callbacks in order to define
+    /// how new particles are emitted.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class ParticleSourceInterface
+    {
+        /// <summary>
+        /// Constructor
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ParticleSourceInterface()
+        {
+        }
+
+        /// <summary>
+        /// Second constructor
+        /// </summary>
+        /// <remarks>
+        /// Second constructor should be overriden by the implementation.
+        /// It allows passing variable number of arguments for processing.
+        /// It is called immediately following the class constructor.
+        /// </remarks>
+        /// <param name="list">List of arguments</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void Construct(params object[] list)
+        {
+        }
+
+        /// <summary>
+        /// Updates the ParticleSource.
+        /// </summary>
+        /// <remarks>
+        /// This callback is responsible for spawning new particles. To spawn new particle,
+        /// emitter.NewParticle() must be call. Number of particles to emit is given as 'count'.
+        ///
+        /// This callback runs on the Update thread! It should avoid using NUI objects.
+        /// 
+        /// </remarks>
+        /// <param name="emitterProxy">Proxy to the ParticleEmitter object</param>
+        /// <param name="count">Number of particles emitter expects to be spawned during call</param>
+        /// <returns>Number of spawned particles</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual uint Update(ParticleEmitterProxy emitterProxy, uint count)
+        {
+            return 0;
+        }
+
+        /// <summary>
+        /// Initializes ParticleSource  
+        /// </summary>
+        /// <remarks>
+        /// This callback should be overriden in order to initialise the ParticleSource.
+        /// It is called after ParticleEmitter.SetSource() and runs on the Event thread.
+        /// It is the only place where ParticleSource may use NUI objects.
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void Init()
+        {
+            
+        }
+
+        /// <summary>
+        /// ParticleEmitter proxy that can be accessed by the user implementation
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ParticleEmitterProxy Emitter;
+    }
+    
+    /// <summary>
+    /// Class represents the particle source
+    /// </summary>
+    /// <remarks>
+    /// ParticleSource is responsible for emission of particles.
+    /// It calls the implementation of <see cref="ParticleSourceInterface"/> class.
+    /// The callback runs on update thread.
+    /// </remarks>
+    /// <typeparam name="T">Class of interface that derives from <see cref="ParticleSourceInterface"/></typeparam>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public partial class ParticleSource<T> : BaseHandle where T : ParticleSourceInterface, new()
+    {
+        // static cache for sources (binding between native side and interfaces)
+        private static ParticleInterfaceRegister<ParticleSourceInterface> gSourceInterfaceRegister = new ParticleInterfaceRegister<ParticleSourceInterface>();
+
+        /// <summary>
+        /// Destructor
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        ~ParticleSource()
+        {
+            gSourceInterfaceRegister.Remove(mInterface);
+        }
+        
+        /// <summary>
+        /// Invoker for ParticleSourceInterface.Init()
+        /// </summary>
+        /// <remarks>
+        /// Function is called from the native side
+        /// </remarks>
+        /// <param name="cPtr">Native pointer of ParticleSource base object</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        static void OnInitInvoker(IntPtr cPtr)
+        {
+            // This function runs on Event thread
+            T source = gSourceInterfaceRegister.Get(cPtr) as T;
+            source?.Init();
+        }
+        
+        /// <summary>
+        /// Invoker for ParticleSourceInterface.Update()
+        /// </summary>
+        /// <remarks>
+        /// Function is called from the native side
+        /// </remarks>
+        /// <param name="cPtr">Native pointer of ParticleSource base object</param>
+        /// <param name="count">Number of particles to emit</param>
+        /// <returns>Number of emitted particles</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        static uint OnUpdateInvoker(IntPtr cPtr, uint count)
+        {
+            T source = gSourceInterfaceRegister.Get(cPtr) as T;
+            var retval= source?.Update(source.Emitter, count);
+            return retval.HasValue ? retval.Value : 0;
+        }
+        
+        internal ParticleSource(global::System.IntPtr cPtr, bool cMemoryOwn) : base(cPtr, cMemoryOwn)
+        {
+        }
+
+        /// <summary>
+        /// Constructor of ParticleSource
+        /// </summary>
+        /// <remarks>
+        /// ParticleSource is a generic type that will call back into the given ParticleSourceInterface
+        /// instance.
+        /// The instance of T (derived from ParticleSourceInterface) is created internally and own by the
+        /// ParticleSource.
+        /// The constructor takes variable number of arguments which is processed when called ParticleSourceInterface.Construct()
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ParticleSource(params object[] list) : this(Interop.ParticleSource.New( mOnInitInvoker, mOnUpdateInvoker, out gRefObjectPtr ), true)
+        {
+            // Create interface on the C# side (no direct connection with C++)
+            mInterface = new T();
+            mInterface.Construct(list);
+            
+            // Register interface using base ptr
+            gSourceInterfaceRegister.Register(gRefObjectPtr, mInterface);
+            
+            // Initialise native side for this interface
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+        
+        /// <summary>
+        /// Returns associated source callback interface
+        /// </summary>
+        /// <returns>Source callback interface</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        private T GetCallbackInterface()
+        {
+            return mInterface as T;
+        }
+
+        /// <summary>
+        /// Returns associated source callback interface
+        /// </summary>
+        /// <returns>Source callback interface</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public T Callback => GetCallbackInterface();
+
+        internal void SetEmitter(ParticleEmitter emitter)
+        {
+            mInterface.Emitter = new ParticleEmitterProxy(emitter);
+        }
+        
+        // private fields
+        private static Interop.ParticleSource.ParticleSourceInitInvokerType mOnInitInvoker = OnInitInvoker;
+        private static Interop.ParticleSource.ParticleSourceUpdateInvokerType mOnUpdateInvoker = OnUpdateInvoker;
+        private ParticleSourceInterface mInterface = null;
+        private static IntPtr gRefObjectPtr;
+    }
+}
\ No newline at end of file
diff --git a/test/Tizen.NUI.ParticleSystem.Sample/ParticleSystemSample.cs b/test/Tizen.NUI.ParticleSystem.Sample/ParticleSystemSample.cs
new file mode 100644 (file)
index 0000000..550dc6c
--- /dev/null
@@ -0,0 +1,200 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using Tizen.NUI.ParticleSystem;
+
+namespace Tizen.NUI.ParticleSystem.Sample
+{
+    // SparkleEffectSource spawns particle from the middle of the
+    // window.
+    class SparkleEffectSource : ParticleSourceInterface
+    {
+        public override void Construct(params object[] list)
+        {
+            base.Construct(list);
+            mRadius = new Vector2(list[0] as Vector2);
+        }
+        
+        public override uint Update(ParticleEmitterProxy emitterProxy, uint count)
+        {
+            if(mStreamBasePos == 0 || mStreamBaseAngle == 0) // streams must exist 
+            {
+                return 0u;
+            }
+
+            while(count > 0)
+            {
+                // Create new particle (lifetime 5 seconds of each)
+                var particle = emitterProxy.NewParticle(5.0f);
+                if(particle == null)
+                {
+                    return 0u;
+                }
+                
+                UpdateParticle(ref particle);
+                
+                count--;
+            }
+            return 0;
+        }
+        
+        public override void Init()
+        {
+            // Add local stream of Vector3 type
+            mStreamBasePos = Emitter.AddLocalStreamVector3(Vector3.Zero);
+            
+            // Add local stream of float type
+            mStreamBaseAngle = Emitter.AddLocalStreamFloat(0.0f);
+        }
+
+        void UpdateParticle(ref Particle p)
+        {
+            float posRadians   = ((mRandom.Next() % 360) * (float)Math.PI) / 180.0f;
+            p.Position = new Vector3(mRadius.X * (float)Math.Sin(posRadians), mRadius.Y * (float)Math.Cos(posRadians), 0.0f);
+            p.SetStreamValue(p.Position, mStreamBasePos);
+            p.Color = Vector4.One;
+            p.SetStreamValue(mAngle, mStreamBaseAngle);
+            mAngle = ((mAngle+5)%360);
+            float rad   = ((mRandom.Next() % 360) * (float)Math.PI) / 180.0f;
+            float speed = ((mRandom.Next() % 5) + 5);
+            p.Velocity = new Vector3((float)Math.Sin(rad) * speed, (float)Math.Cos(rad) * speed, 0);
+
+            // Random initial scale
+            float initialScale = (float)(mRandom.Next() % 32) + 32;
+            p.Scale = new Vector3(initialScale, initialScale, 1.0f);
+        }
+        
+        private static float mAngle = 0;
+        private Random mRandom = new Random();
+        public uint mStreamBasePos = 0;
+        public uint mStreamBaseAngle = 0;
+        private Vector2 mRadius;
+    }
+
+    // SparkleEffectModifier spawns particle from the middle of the
+    // window.
+    class SparkleEffectModifier : ParticleModifierInterface
+    {
+        public override void Construct(params object[] list)
+        {
+            base.Construct(list);
+            mSource = list[0] as SparkleEffectSource;
+        }
+
+        public override void Update(ParticleEmitterProxy proxy, List<Particle> particles)
+        {
+            if (particles.Count == 0)
+            {
+                return;
+            }
+
+            if (mStreamBasePos == 0)
+            {
+                mStreamBasePos = mSource.mStreamBasePos;
+            }
+
+            if (mStreamBaseAngle == 0)
+            {
+                mStreamBaseAngle = mSource.mStreamBaseAngle;
+            }
+
+            if (mStreamBasePos == 0)
+            {
+                return;
+            }
+
+            for (uint i = 0; i < particles.Count; ++i)
+            {
+                var p = particles[(int)i];
+                
+                float angle = p.GetStreamValue(mStreamBaseAngle);
+                Vector3 basePos = p.GetStreamValue(mStreamBaseAngle);
+                float radians  = (float)((angle * Math.PI)/180.0f);
+                float lifetime = p.Lifetime;
+                Vector3 pos = p.Position;
+                var vel = p.Velocity;
+                p.Position = new Vector3(pos.X + vel.X * (float)Math.Cos(radians),pos.Y + vel.Y * (float)Math.Sin(radians),
+                     pos.Z);
+
+                p.Velocity = (vel * 0.990f);
+                float normalizedTime = (lifetime / p.LifetimeBase);
+
+                var color = new Vector4( 1, 1, 1, normalizedTime);
+                p.Color = color;
+                p.Scale = new Vector3(64.0f*(normalizedTime * normalizedTime * normalizedTime * normalizedTime), 64.0f*(normalizedTime * normalizedTime * normalizedTime * normalizedTime), 1.0f);
+                 
+            }
+        }
+
+        private uint mStreamBasePos = 0;
+        private uint mStreamBaseAngle = 0;
+        private SparkleEffectSource mSource;
+
+    }
+    
+    class ParticleSystemSample : NUIApplication
+    {
+        static string IMAGE_DIR = Tizen.Applications.Application.Current.DirectoryInfo.Resource + "image/";
+
+        private Window mWindow;
+
+        private ParticleEmitter mEmitter;
+        private ParticleSource<SparkleEffectSource> mSource;
+        private ParticleModifier<SparkleEffectModifier> mModifier;
+
+        public void Activate()
+        {
+            mWindow = Window.Instance;
+            mWindow.BackgroundColor = Color.Black;
+
+            var view = new View();
+            view.BackgroundColor = Color.Wheat;
+            view.Size = new Size(1, 1);
+            view.PivotPoint = new Position(0, 0);
+            view.Position2D = new Position2D(mWindow.Size.Width/2, mWindow.Size.Height/2);
+            mWindow.Add(view);
+            // Attach emitter to view
+            mEmitter = new ParticleEmitter(view)
+            {
+                ParticleCount = 10000,
+                EmissionRate = 500,
+                InitialParticleCount = 0,
+                RendererBlendingMode = ParticleBlendingMode.Screen
+            };
+            
+            mSource = new ParticleSource<SparkleEffectSource>(new Vector2(50, 50));
+            mModifier = new ParticleModifier<SparkleEffectModifier>(mSource.Callback);
+            
+            mEmitter.SetSource(mSource);
+            mEmitter.AddModifier(mModifier);
+            
+            // Load texture
+            var pixelBuffer = ImageLoader.LoadImageFromFile(IMAGE_DIR + "/blue-part2.png");
+            Texture tex = new Texture(TextureType.TEXTURE_2D, PixelFormat.RGBA8888, pixelBuffer.GetWidth(),
+                pixelBuffer.GetHeight());
+            tex.Upload(pixelBuffer.CreatePixelData());
+            
+            mEmitter.RendererTexture = tex;
+            mEmitter.Start();
+        }
+        
+        protected override void OnCreate()
+        {
+            // Up call to the Base class first
+            base.OnCreate();
+            Activate();
+        }
+        
+        /// <summary>
+        /// The main entry point for the application.
+        /// </summary>
+        [STAThread] // Forces app to use one thread to access NUI
+        static void Main(string[] args)
+        {
+            ParticleSystemSample example = new ParticleSystemSample();
+            example.Run(args);
+        }
+    }
+}
diff --git a/test/Tizen.NUI.ParticleSystem.Sample/Tizen.NUI.ParticleSystem.Sample.csproj b/test/Tizen.NUI.ParticleSystem.Sample/Tizen.NUI.ParticleSystem.Sample.csproj
new file mode 100644 (file)
index 0000000..8dcd2b3
--- /dev/null
@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net6.0</TargetFramework>
+  </PropertyGroup>
+
+  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+
+  <ItemGroup>
+        <ProjectReference Include="../../src/Tizen/Tizen.csproj" />
+        <ProjectReference Include="../../src/Tizen.NUI/Tizen.NUI.csproj" />
+  </ItemGroup>
+  <ItemGroup>
+      <None Update="res\image\*.png">
+          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      </None>
+  </ItemGroup>  
+  <PropertyGroup>
+        <NeedInjection>True</NeedInjection>
+  </PropertyGroup>
+</Project>
diff --git a/test/Tizen.NUI.ParticleSystem.Sample/res/image/blue-part2.png b/test/Tizen.NUI.ParticleSystem.Sample/res/image/blue-part2.png
new file mode 100644 (file)
index 0000000..3f41b43
Binary files /dev/null and b/test/Tizen.NUI.ParticleSystem.Sample/res/image/blue-part2.png differ