diff --git a/packaging/csapi-tizenfx.spec b/packaging/csapi-tizenfx.spec
index 5b73c8feab0..69df670fdcb 100644
--- a/packaging/csapi-tizenfx.spec
+++ b/packaging/csapi-tizenfx.spec
@@ -1,7 +1,7 @@
# Auto-generated from csapi-tizenfx.spec.in by makespec.sh
%define TIZEN_NET_API_VERSION 13
-%define TIZEN_NET_RPM_VERSION 13.0.0.999+nui22354
+%define TIZEN_NET_RPM_VERSION 13.0.0.999+nui22400
%define TIZEN_NET_NUGET_VERSION 13.0.0.99999
%define DOTNET_ASSEMBLY_PATH /usr/share/dotnet.tizen/framework
diff --git a/packaging/makerid.py b/packaging/makerid.py
index bded6387644..f078f47011c 100755
--- a/packaging/makerid.py
+++ b/packaging/makerid.py
@@ -5,6 +5,9 @@
runtime_dir = os.path.join(scrpit_dir, "../pkg/Tizen.NET/runtime.json")
spec_dir = os.path.join(scrpit_dir, "csapi-tizenfx.spec")
+def parse_rid_as_int(rid):
+ return [int(part) for part in rid.split(".")]
+
with open(runtime_dir) as json_file:
json_data = json.load(json_file)
json_string = json_data["runtimes"]
@@ -23,7 +26,7 @@
rid_list.append(key)
rid_list = list(set(rid_list))
- rid_list.sort(reverse=True)
+ rid_list = sorted(rid_list, key=parse_rid_as_int, reverse=True)
f = open(spec_dir,'r')
origin_data = f.read()
diff --git a/packaging/version.txt b/packaging/version.txt
index 0a25b298a6f..c40b29e89e4 100755
--- a/packaging/version.txt
+++ b/packaging/version.txt
@@ -6,4 +6,4 @@ RPM_VERSION=13.0.0.999
NUGET_VERSION=13.0.0.99999
# RPM Version Suffix
-RPM_VERSION_SUFFIX=nui22354
+RPM_VERSION_SUFFIX=nui22400
diff --git a/src/Tizen.AIAvatar/Tizen.AIAvatar.csproj b/src/Tizen.AIAvatar/Tizen.AIAvatar.csproj
index 68314141a71..63f5918ca9d 100644
--- a/src/Tizen.AIAvatar/Tizen.AIAvatar.csproj
+++ b/src/Tizen.AIAvatar/Tizen.AIAvatar.csproj
@@ -7,7 +7,6 @@
-
@@ -16,7 +15,6 @@
-
diff --git a/src/Tizen.AIAvatar/src/AIServices/BaseAIService.cs b/src/Tizen.AIAvatar/src/AIServices/BaseAIService.cs
new file mode 100644
index 00000000000..c9a18196cf9
--- /dev/null
+++ b/src/Tizen.AIAvatar/src/AIServices/BaseAIService.cs
@@ -0,0 +1,82 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.AIAvatar
+{
+ ///
+ /// Abstract base class for AI services, providing common functionalities.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public abstract class BaseAIService : IAIService, IDisposable
+ {
+ ///
+ /// Gets the name of the AI service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public abstract string ServiceName { get; }
+
+ ///
+ /// Gets the capabilities of the AI service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public abstract ServiceCapabilities Capabilities { get; }
+
+ ///
+ /// Gets the service client manager responsible for managing client operations.
+ ///
+ protected ServiceClientManager ClientManager { get; }
+
+ ///
+ /// Gets the configuration settings for the AI service.
+ ///
+ protected AIServiceConfiguration Configuration { get; }
+
+ ///
+ /// Initializes a new instance of the class with the specified configuration.
+ ///
+ /// The configuration settings for the AI service.
+ protected BaseAIService(AIServiceConfiguration config)
+ {
+ Configuration = config;
+ ClientManager = new ServiceClientManager(config);
+ }
+
+ ///
+ /// Releases all resources used by the AI service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Releases all resources used by the AI service.
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing && ClientManager != null)
+ {
+ ClientManager.Dispose();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Tizen.AIAvatar/src/AIServices/Core/AIServiceData.cs b/src/Tizen.AIAvatar/src/AIServices/Core/AIServiceData.cs
new file mode 100644
index 00000000000..c75e9d49026
--- /dev/null
+++ b/src/Tizen.AIAvatar/src/AIServices/Core/AIServiceData.cs
@@ -0,0 +1,185 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+
+
+namespace Tizen.AIAvatar
+{
+ ///
+ /// Provides data for LLM response events.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class llmResponseEventArgs : EventArgs
+ {
+ ///
+ /// Gets or sets the task ID associated with the LLM response.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int TaskID { get; set; }
+
+ ///
+ /// Gets or sets the response text from the LLM.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Text { get; set; }
+
+ ///
+ /// Gets or sets the error message, if any, from the LLM response.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Error { get; set; }
+ }
+
+ ///
+ /// Provides data for TTS streaming events.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class ttsStreamingEventArgs : EventArgs
+ {
+ ///
+ /// Gets or sets the audio data of the current chunk.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public byte[] AudioData { get; set; }
+
+ ///
+ /// Gets or sets the sample rate of the audio data.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int SampleRate { get; set; }
+
+ ///
+ /// Gets or sets the text being converted to speech.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Text { get; set; }
+
+ ///
+ /// Gets or sets the voice used for the TTS conversion.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Voice { get; set; }
+
+ ///
+ /// Gets or sets the size of the current audio data chunk in bytes.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int AudioBytes { get; set; }
+
+ ///
+ /// Gets or sets the total number of bytes for the audio data.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int TotalBytes { get; set; }
+
+ ///
+ /// Gets or sets the number of bytes processed so far.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int ProcessedBytes { get; set; }
+
+ ///
+ /// Gets or sets the progress percentage of the TTS processing.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public double ProgressPercentage { get; set; }
+
+ ///
+ /// Gets or sets the error message, if any, during the TTS process.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Error { get; set; }
+ }
+
+ ///
+ /// Provides data for STT streaming events.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class sttStreamingEventArgs : EventArgs
+ {
+ ///
+ /// Gets or sets a value indicating whether the text is interim.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool Interim { get; set; }
+
+ ///
+ /// Gets or sets the transcribed text from the STT process.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Text { get; set; }
+
+ ///
+ /// Gets or sets the error message, if any, during the STT process.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Error { get; set; }
+ }
+
+ ///
+ /// Represents service endpoints for AI services.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class ServiceEndpoints
+ {
+ ///
+ /// Gets or sets the endpoint for the LLM service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string LLMEndpoint { get; set; }
+
+ ///
+ /// Gets or sets the endpoint for the Text-to-Speech service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string TextToSpeechEndpoint { get; set; }
+
+ ///
+ /// Gets or sets the endpoint for the Speech-to-Text service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string SpeechToTextEndpoint { get; set; }
+ }
+
+ ///
+ /// Represents the configuration for an AI service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public abstract class AIServiceConfiguration
+ {
+ ///
+ /// Gets or sets the API key for the AI service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string ApiKey { get; set; }
+
+ ///
+ /// Gets or sets the service endpoints.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ServiceEndpoints Endpoints { get; set; }
+
+ ///
+ /// Gets or sets additional settings for the AI service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public Dictionary AdditionalSettings { get; set; } = new();
+ }
+}
diff --git a/src/Tizen.AIAvatar/src/Multimedia/Audio/AudioOptions.cs b/src/Tizen.AIAvatar/src/AIServices/Core/IAIService.cs
similarity index 50%
rename from src/Tizen.AIAvatar/src/Multimedia/Audio/AudioOptions.cs
rename to src/Tizen.AIAvatar/src/AIServices/Core/IAIService.cs
index 2ac9305159e..d82f4aac12a 100644
--- a/src/Tizen.AIAvatar/src/Multimedia/Audio/AudioOptions.cs
+++ b/src/Tizen.AIAvatar/src/AIServices/Core/IAIService.cs
@@ -15,51 +15,65 @@
*
*/
+using System;
using System.ComponentModel;
-using Tizen.Multimedia;
namespace Tizen.AIAvatar
{
///
- /// Provides the ability to audio
+ /// Defines the capabilities that an AI service can support.
///
+ [Flags]
[EditorBrowsable(EditorBrowsableState.Never)]
- internal class AudioOptions
+ public enum ServiceCapabilities
{
- private int sampleRate;
- private AudioChannel channel;
- private AudioSampleType sampleType;
+ ///
+ /// No capabilities.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ None = 0,
+
+ ///
+ /// Capability for Text-to-Speech service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ TextToSpeech = 1,
+
+ ///
+ /// Capability for Speech-to-Text service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ SpeechToText = 2,
///
- /// Initializes a new instance of the AudioOptions class with the specified sample rate, channel, and sampleType.
+ /// Capability for Large Language Model service.
///
- /// the audio sample rate (8000 ~ 192000Hz)
- /// the audio channel type.
- /// the audio sample type.
[EditorBrowsable(EditorBrowsableState.Never)]
- public AudioOptions(int sampleRate, AudioChannel channel, AudioSampleType sampleType)
- {
- this.sampleRate = sampleRate;
- this.channel = channel;
- this.sampleType = sampleType;
- }
+ LargeLanguageModel = 4,
///
- /// The audio sample rate (8000 ~ 192000Hz)
+ /// Capability for Vision-related service.
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public int SampleRate { get => sampleRate; set => sampleRate = value; }
+ Vision = 8
+ }
+ ///
+ /// Represents a generic AI service interface.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public interface IAIService
+ {
///
- /// The audio channel type
+ /// Gets the name of the AI service.
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public AudioChannel Channel { get => channel; set => channel = value; }
+ string ServiceName { get; }
///
- /// The audio sample type
+ /// Gets the capabilities of the AI service.
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public AudioSampleType SampleType { get => sampleType; set => sampleType = value; }
+ ServiceCapabilities Capabilities { get; }
}
}
diff --git a/src/Tizen.AIAvatar/src/AIServices/Core/IAIServiceFeatures.cs b/src/Tizen.AIAvatar/src/AIServices/Core/IAIServiceFeatures.cs
new file mode 100644
index 00000000000..6df878534ce
--- /dev/null
+++ b/src/Tizen.AIAvatar/src/AIServices/Core/IAIServiceFeatures.cs
@@ -0,0 +1,128 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+using System.IO;
+using System.Threading.Tasks;
+
+
+namespace Tizen.AIAvatar
+{
+ ///
+ /// Interface for a Text-to-Speech service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public interface ITextToSpeechService
+ {
+ ///
+ /// Occurs when the Text-to-Speech service starts processing.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ event EventHandler OnTtsStart;
+
+ ///
+ /// Occurs when the Text-to-Speech service is receiving data.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ event EventHandler OnTtsReceiving;
+
+ ///
+ /// Occurs when the Text-to-Speech service finishes processing.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ event EventHandler OnTtsFinish;
+
+ ///
+ /// Converts the given text to speech asynchronously and returns the audio data.
+ ///
+ /// The text to be converted to speech.
+ /// Optional parameter to specify the voice type.
+ /// Optional parameters for customizing speech output.
+ /// A task representing the asynchronous operation, with a byte array of the generated audio data.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Task TextToSpeechAsync(
+ string text,
+ string voice = null,
+ Dictionary options = null);
+
+ ///
+ /// Streams the given text as speech asynchronously.
+ ///
+ /// The text to be converted to speech.
+ /// Optional parameter to specify the voice type.
+ /// Optional parameters for customizing speech output.
+ /// A task representing the asynchronous operation.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Task TextToSpeechStreamAsync(
+ string text,
+ string voice = null,
+ Dictionary options = null);
+ }
+
+ ///
+ /// Interface for a Speech-to-Text service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public interface ISpeechToTextService
+ {
+ ///
+ /// Converts the given audio data to text asynchronously.
+ ///
+ /// The audio data to be converted to text.
+ /// Optional parameters for customizing the transcription.
+ /// A task representing the asynchronous operation, with the resulting transcribed text.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Task SpeechToTextAsync(
+ byte[] audioData,
+ Dictionary options = null);
+
+ ///
+ /// Streams the given audio data to text asynchronously.
+ ///
+ /// The audio stream to be converted to text.
+ /// Optional parameters for customizing the transcription.
+ /// A task representing the asynchronous operation.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Task StreamSpeechToTextAsync(
+ Stream audioStream,
+ Dictionary options = null);
+ }
+
+ ///
+ /// Interface for a Large Language Model (LLM) service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public interface ILLMService
+ {
+ ///
+ /// Occurs when a response is generated by the LLM service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ event EventHandler ResponseHandler;
+
+ ///
+ /// Generates a response to the given message asynchronously.
+ ///
+ /// The input message to be processed by the LLM.
+ /// Optional parameters for customizing the LLM output.
+ /// A task representing the asynchronous operation.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Task GenerateTextAsync(string message, Dictionary options = null);
+ }
+}
diff --git a/src/Tizen.AIAvatar/src/AIServices/Core/Prompt.cs b/src/Tizen.AIAvatar/src/AIServices/Core/Prompt.cs
new file mode 100644
index 00000000000..a3707d4ce5d
--- /dev/null
+++ b/src/Tizen.AIAvatar/src/AIServices/Core/Prompt.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+
+namespace Tizen.AIAvatar
+{
+ internal class Message
+ {
+ public string role { get; set; }
+ public string content { get; set; }
+ }
+
+ internal class Prompt
+ {
+ public string model { get; set; }
+ public List messages { get; set; }
+ public double temperature { get; set; }
+ public int seed { get; set; }
+ }
+}
diff --git a/src/Tizen.AIAvatar/src/AIServices/Core/RestClient/IRestClient.cs b/src/Tizen.AIAvatar/src/AIServices/Core/RestClient/IRestClient.cs
new file mode 100644
index 00000000000..f79415ccc6e
--- /dev/null
+++ b/src/Tizen.AIAvatar/src/AIServices/Core/RestClient/IRestClient.cs
@@ -0,0 +1,78 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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;
+using System.Net;
+using System.Threading.Tasks;
+
+namespace Tizen.AIAvatar
+{
+
+ ///
+ /// Represents the response from a REST API call, containing information such as status, content, and potential error messages.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class RestResponse
+ {
+ ///
+ /// Indicates whether the request was successful.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsSuccessful { get; set; }
+
+ ///
+ /// The HTTP status code returned by the server, providing information about the response status.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public HttpStatusCode StatusCode { get; set; }
+
+ ///
+ /// The content of the response as a string, typically containing the body of the HTTP response.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Content { get; set; }
+
+ ///
+ /// The raw bytes of the response, which can be useful for handling non-text data or binary responses.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public byte[] RawBytes { get; set; }
+
+ ///
+ /// The error message if the request failed, providing details about what went wrong.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string ErrorMessage { get; set; }
+ }
+
+ ///
+ /// Interface for making REST API calls, providing functionality to execute HTTP requests and receive responses.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public interface IRestClient : IDisposable
+ {
+ ///
+ /// Executes a REST request asynchronously and returns the response from the server.
+ ///
+ /// The REST request to execute, containing information such as endpoint, headers, and parameters.
+ /// A task representing the asynchronous operation, which returns the REST response containing status, content, and any errors.
+ /// Thrown when the request parameter is null.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Task ExecuteAsync(RestRequest request);
+ }
+}
\ No newline at end of file
diff --git a/src/Tizen.AIAvatar/src/AIServices/Core/RestClient/RestClient.cs b/src/Tizen.AIAvatar/src/AIServices/Core/RestClient/RestClient.cs
new file mode 100644
index 00000000000..d7cf0560636
--- /dev/null
+++ b/src/Tizen.AIAvatar/src/AIServices/Core/RestClient/RestClient.cs
@@ -0,0 +1,97 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+
+namespace Tizen.AIAvatar
+{
+ ///
+ /// A class that provides methods to execute REST requests and handle responses.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class RestClient : IRestClient
+ {
+ private readonly HttpClient _httpClient;
+
+ ///
+ /// Initializes a new instance of the RestClient class with the specified HttpClient.
+ ///
+ /// The HttpClient instance to use for sending HTTP requests.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RestClient(HttpClient httpClient)
+ {
+ _httpClient = httpClient;
+ }
+
+ ///
+ /// Executes a REST request asynchronously and returns the response.
+ ///
+ /// The RestRequest object containing details of the request.
+ /// A RestResponse object representing the result of the request.
+ /// Thrown when an error occurs during the request execution.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public async Task ExecuteAsync(RestRequest request)
+ {
+ using var httpRequest = request?.CreateRequestMessage(_httpClient.BaseAddress);
+
+ try
+ {
+ using var httpResponse = await _httpClient.SendAsync(httpRequest);
+ var response = new RestResponse
+ {
+ StatusCode = httpResponse.StatusCode,
+ IsSuccessful = httpResponse.IsSuccessStatusCode
+ };
+
+ if (httpResponse.Content != null)
+ {
+ response.RawBytes = await httpResponse.Content.ReadAsByteArrayAsync();
+ response.Content = await httpResponse.Content.ReadAsStringAsync();
+ }
+
+ if (!httpResponse.IsSuccessStatusCode)
+
+ {
+ response.ErrorMessage = $"HTTP {(int)httpResponse.StatusCode} - {httpResponse.ReasonPhrase}";
+ }
+
+ return response;
+ }
+ catch (Exception ex)
+ {
+ return new RestResponse
+ {
+ IsSuccessful = false,
+ ErrorMessage = ex.Message
+ };
+ }
+ }
+
+ ///
+ /// Disposes of the resources used by the RestClient.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Dispose()
+ {
+ _httpClient?.Dispose();
+ }
+ }
+}
diff --git a/src/Tizen.AIAvatar/src/AIServices/Core/RestClient/RestRequest.cs b/src/Tizen.AIAvatar/src/AIServices/Core/RestClient/RestRequest.cs
new file mode 100644
index 00000000000..4d6ca754319
--- /dev/null
+++ b/src/Tizen.AIAvatar/src/AIServices/Core/RestClient/RestRequest.cs
@@ -0,0 +1,219 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.Text.Json;
+using System.Text;
+using System;
+using System.Net.Http;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using System.ComponentModel;
+
+namespace Tizen.AIAvatar
+{
+ ///
+ /// An enumeration representing various HTTP methods.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public enum Method
+ {
+ ///
+ /// Represents the HTTP GET method.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Get,
+
+ ///
+ /// Represents the HTTP POST method.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Post,
+
+ ///
+ /// Represents the HTTP PUT method.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Put,
+
+ ///
+ /// Represents the HTTP DELETE method.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Delete,
+
+ ///
+ /// Represents the HTTP PATCH method.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Patch
+ }
+
+ ///
+ /// Represents a RESTful API request.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class RestRequest
+ {
+ ///
+ /// Initializes a new instance of the RestRequest class with the specified HTTP method.
+ ///
+ /// The HTTP method to use for the request.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RestRequest(Method method)
+ {
+ Resource = string.Empty;
+ Method = method;
+ _headers = new Dictionary();
+ }
+
+ ///
+ /// Initializes a new instance of the RestRequest class with the specified resource and HTTP method.
+ ///
+ /// The resource URI for the request.
+ /// The HTTP method to use for the request.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RestRequest(string resource, Method method)
+ {
+ Resource = resource;
+ Method = method;
+ _headers = new Dictionary();
+ }
+
+ ///
+ /// Adds a header to the request.
+ ///
+ /// The name of the header.
+ /// The value of the header.
+ /// The current instance of RestRequest.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RestRequest AddHeader(string name, string value)
+ {
+ _headers[name] = value;
+ return this;
+ }
+
+ ///
+ /// Adds a JSON body to the request.
+ ///
+ /// The object to serialize as JSON and include in the request body.
+ /// The current instance of RestRequest.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RestRequest AddJsonBody(object body)
+ {
+ _body = body;
+ _jsonStringBody = null; // Clear json string if object body is set
+ if (!_headers.ContainsKey("Content-Type"))
+ {
+ _headers["Content-Type"] = "application/json";
+ }
+ return this;
+ }
+
+ ///
+ /// Adds a JSON string body to the request.
+ ///
+ /// The JSON string to include in the request body.
+ /// The current instance of RestRequest.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RestRequest AddJsonStringBody(string jsonString)
+ {
+ _jsonStringBody = jsonString;
+ _body = null; // Clear object body if json string is set
+ if (!_headers.ContainsKey("Content-Type"))
+ {
+ _headers["Content-Type"] = "application/json";
+ }
+ return this;
+ }
+
+ ///
+ /// The resource URI for the request.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Resource { get; }
+
+ ///
+ /// The HTTP method to use for the request.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public Method Method { get; }
+
+ ///
+ /// Creates an HttpRequestMessage based on the current RestRequest instance.
+ ///
+ /// The base address to use when constructing the request URI.
+ /// A new HttpRequestMessage representing the current RestRequest.
+ internal HttpRequestMessage CreateRequestMessage(Uri baseAddress)
+ {
+ // If Resource is empty, use only the baseAddress
+ var requestUri = string.IsNullOrEmpty(Resource) ? baseAddress : new Uri(baseAddress, Resource);
+
+ var request = new HttpRequestMessage(GetHttpMethod(), requestUri);
+
+ foreach (var header in _headers)
+ {
+ request.Headers.TryAddWithoutValidation(header.Key, header.Value);
+ }
+
+ if (_jsonStringBody != null)
+ {
+ request.Content = new StringContent(_jsonStringBody, Encoding.UTF8, "application/json");
+ }
+ else if (_body != null)
+ {
+ var jsonBody = JsonSerializer.Serialize(_body, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
+ request.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
+ }
+
+ return request;
+ }
+
+ ///
+ /// Gets the appropriate HttpMethod for the specified Method enumeration value.
+ ///
+ /// The corresponding HttpMethod.
+ private HttpMethod GetHttpMethod()
+ {
+ return Method switch
+ {
+ Method.Get => HttpMethod.Get,
+ Method.Post => HttpMethod.Post,
+ Method.Put => HttpMethod.Put,
+ Method.Delete => HttpMethod.Delete,
+ Method.Patch => HttpMethod.Patch,
+ _ => throw new ArgumentException($"Unsupported HTTP method: {Method}")
+ };
+ }
+
+
+
+ ///
+ /// A dictionary containing the headers for the request.
+ ///
+ private readonly Dictionary _headers;
+
+ ///
+ /// The object to serialize as JSON and include in the request body.
+ ///
+ private object _body;
+
+ ///
+ /// The JSON string to include in the request body.
+ ///
+ private string _jsonStringBody;
+ }
+}
\ No newline at end of file
diff --git a/src/Tizen.AIAvatar/src/AIServices/Core/RestClient/ServiceClientManager.cs b/src/Tizen.AIAvatar/src/AIServices/Core/RestClient/ServiceClientManager.cs
new file mode 100644
index 00000000000..1aaf70e79fa
--- /dev/null
+++ b/src/Tizen.AIAvatar/src/AIServices/Core/RestClient/ServiceClientManager.cs
@@ -0,0 +1,77 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+using System.Net.Http;
+
+namespace Tizen.AIAvatar
+{
+ ///
+ /// Manages REST clients for different endpoints based on the AI service configuration.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class ServiceClientManager : IDisposable
+ {
+ private readonly Dictionary _clients = new();
+ private readonly AIServiceConfiguration _config;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The AI service configuration.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ServiceClientManager(AIServiceConfiguration config)
+ {
+ _config = config;
+ }
+
+ ///
+ /// Retrieves or creates a REST client for the specified endpoint.
+ ///
+ /// The base URI of the endpoint.
+ /// The REST client for the specified endpoint.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public IRestClient GetClient(string endpoint)
+ {
+ if (!_clients.ContainsKey(endpoint))
+ {
+ var client = new RestClient(new HttpClient
+ {
+ BaseAddress = new Uri(endpoint)
+ });
+ _clients[endpoint] = client;
+ }
+ return _clients[endpoint];
+ }
+
+ ///
+ /// Releases all resources used by the .
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Dispose()
+ {
+ foreach (var client in _clients.Values)
+ {
+ client?.Dispose();
+ }
+ _clients.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Tizen.AIAvatar/src/AIServices/OpenAIService.cs b/src/Tizen.AIAvatar/src/AIServices/OpenAIService.cs
new file mode 100644
index 00000000000..f6b231f9698
--- /dev/null
+++ b/src/Tizen.AIAvatar/src/AIServices/OpenAIService.cs
@@ -0,0 +1,280 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace Tizen.AIAvatar
+{
+ ///
+ /// Configuration class for OpenAI services.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class OpenAIConfiguration : AIServiceConfiguration
+ {
+ ///
+ /// Initializes a new instance of the class with default endpoint settings.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public OpenAIConfiguration()
+ {
+ Endpoints = new ServiceEndpoints
+ {
+ LLMEndpoint = "https://api.openai.com/v1/chat/",
+ TextToSpeechEndpoint = "https://api.openai.com/v1/audio/"
+ };
+ }
+
+ ///
+ /// Gets or sets the default model to use for LLM services.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Model { get; set; } = "gpt-3.5-turbo";
+
+ ///
+ /// Gets or sets the OpenAI organization ID for accessing specific organizational resources.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Organization { get; set; }
+ }
+
+ ///
+ /// OpenAI Service implementation that provides Text-to-Speech and LLM services.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class OpenAIService : BaseAIService, ITextToSpeechService, ILLMService
+ {
+ private readonly OpenAIConfiguration config;
+
+ ///
+ /// Occurs when a response is generated by the LLM service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler ResponseHandler;
+
+ ///
+ /// Occurs when the Text-to-Speech service starts processing.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler OnTtsStart;
+
+ ///
+ /// Occurs when the Text-to-Speech service is receiving data.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler OnTtsReceiving;
+
+ ///
+ /// Occurs when the Text-to-Speech service finishes processing.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler OnTtsFinish;
+
+ ///
+ /// Gets the name of the AI service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string ServiceName => "OpenAI";
+
+ ///
+ /// Gets the capabilities supported by this AI service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override ServiceCapabilities Capabilities =>
+ ServiceCapabilities.TextToSpeech | ServiceCapabilities.LargeLanguageModel;
+
+ ///
+ /// Initializes a new instance of the class with the specified configuration.
+ ///
+ /// The configuration for the OpenAI Service.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public OpenAIService(OpenAIConfiguration config) : base(config)
+ {
+ this.config = config;
+ }
+
+ ///
+ /// Generates a response from the LLM service asynchronously.
+ ///
+ /// The input message to be processed by the LLM.
+ /// Optional parameters for customizing the LLM output.
+ /// A task representing the asynchronous operation.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public async Task GenerateTextAsync(string message, Dictionary options = null)
+ {
+ var client = ClientManager.GetClient(config.Endpoints.LLMEndpoint);
+ var messages = new List
+ {
+ new { role = "user", content = message }
+ };
+
+ var request = new RestRequest("completions", Method.Post)
+ .AddHeader("Authorization", $"Bearer {config.ApiKey}")
+ .AddJsonBody(new
+ {
+ model = config.Model,
+ messages = messages,
+ temperature = options?.GetValueOrDefault("temperature", 0.7),
+ max_tokens = options?.GetValueOrDefault("max_tokens", 1000)
+ });
+
+ var response = await client.ExecuteAsync(request).ConfigureAwait(false);
+
+ if (!response.IsSuccessful)
+ {
+ ResponseHandler?.Invoke(this, new llmResponseEventArgs { Error = response.ErrorMessage });
+ return;
+ }
+
+ var jsonResponse = JsonSerializer.Deserialize(response.Content);
+ string content = jsonResponse
+ .GetProperty("choices")[0]
+ .GetProperty("message")
+ .GetProperty("content")
+ .GetString();
+
+ ResponseHandler?.Invoke(this, new llmResponseEventArgs { Text = content });
+ }
+
+ ///
+ /// Converts the given text to speech asynchronously and returns the audio data.
+ ///
+ /// The text to be converted to speech.
+ /// Optional parameter to specify the voice type.
+ /// Optional parameters for customizing speech output.
+ /// A task representing the asynchronous operation, with a byte array of the generated audio data.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public async Task TextToSpeechAsync(
+ string text,
+ string voice = null,
+ Dictionary options = null)
+ {
+ var client = ClientManager.GetClient(config.Endpoints.TextToSpeechEndpoint);
+
+ var request = new RestRequest("speech", Method.Post)
+ .AddHeader("Authorization", $"Bearer {config.ApiKey}")
+ .AddJsonBody(new
+ {
+ model = "tts-1",
+ input = text,
+ voice = voice ?? "alloy",
+ response_format = "mp3"
+ });
+
+ var response = await client.ExecuteAsync(request);
+ if (!response.IsSuccessful)
+ throw new Exception($"OpenAI TTS Error: {response.ErrorMessage}");
+
+ return response.RawBytes;
+ }
+
+ ///
+ /// Streams the given text as speech asynchronously.
+ ///
+ /// The text to be converted to speech.
+ /// Optional parameter to specify the voice type.
+ /// Optional parameters for customizing speech output.
+ /// A task representing the asynchronous operation.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public async Task TextToSpeechStreamAsync(string text, string voice = null, Dictionary options = null)
+ {
+ const int SAMPLE_RATE = 24000;
+ const int FRAME_DURATION_MS = 160;
+ const int TAIL_DURATION_MS = 15;
+ const int BYTES_PER_SAMPLE = 2;
+ const int CHUNK_SIZE = (SAMPLE_RATE * FRAME_DURATION_MS * BYTES_PER_SAMPLE) / 1000;
+ const int TAIL_CHUNK_SIZE = (SAMPLE_RATE * TAIL_DURATION_MS * BYTES_PER_SAMPLE) / 1000;
+
+ var client = ClientManager.GetClient(config.Endpoints.TextToSpeechEndpoint);
+ var request = new RestRequest("speech", Method.Post)
+ .AddHeader("Authorization", $"Bearer {config.ApiKey}")
+ .AddJsonBody(new
+ {
+ model = "tts-1",
+ input = text,
+ voice = voice ?? "alloy",
+ response_format = "pcm"
+ });
+
+ try
+ {
+ OnTtsStart?.Invoke(this, new ttsStreamingEventArgs
+ {
+ Text = text,
+ Voice = voice ?? "alloy",
+ SampleRate = SAMPLE_RATE,
+ TotalBytes = 0,
+ AudioData = Array.Empty()
+ });
+
+ var response = await client.ExecuteAsync(request).ConfigureAwait(false);
+ if (!response.IsSuccessful)
+ {
+ throw new Exception($"OpenAI TTS Error: {response.ErrorMessage}");
+ }
+
+ var audioData = response.RawBytes;
+ var totalBytes = audioData.Length;
+ var bytesProcessed = 0;
+
+ // Process the audio data in 160ms chunks
+ while (bytesProcessed < totalBytes)
+ {
+ var remainingBytes = totalBytes - bytesProcessed;
+ var currentChunkSize = Math.Min(CHUNK_SIZE + TAIL_CHUNK_SIZE, remainingBytes);
+ var chunk = new byte[currentChunkSize];
+ Array.Copy(audioData, bytesProcessed, chunk, 0, currentChunkSize);
+ bytesProcessed += Math.Min(CHUNK_SIZE, remainingBytes);
+
+ OnTtsReceiving?.Invoke(this, new ttsStreamingEventArgs
+ {
+ Text = text,
+ Voice = voice ?? "alloy",
+ TotalBytes = totalBytes,
+ ProcessedBytes = bytesProcessed,
+ ProgressPercentage = (double)bytesProcessed / totalBytes * 100,
+ AudioData = chunk
+ });
+ }
+
+ OnTtsFinish?.Invoke(this, new ttsStreamingEventArgs
+ {
+ Text = text,
+ Voice = voice ?? "alloy",
+ TotalBytes = totalBytes,
+ ProcessedBytes = totalBytes,
+ ProgressPercentage = 100,
+ AudioData = Array.Empty()
+ });
+ }
+ catch (Exception ex)
+ {
+ OnTtsFinish?.Invoke(this, new ttsStreamingEventArgs
+ {
+ Text = text,
+ Voice = voice ?? "alloy",
+ Error = ex.Message,
+ AudioData = Array.Empty()
+ });
+ throw;
+ }
+ }
+ }
+}
diff --git a/src/Tizen.AIAvatar/src/AIServices/SamsungAIService.cs b/src/Tizen.AIAvatar/src/AIServices/SamsungAIService.cs
new file mode 100644
index 00000000000..e032d61ebaa
--- /dev/null
+++ b/src/Tizen.AIAvatar/src/AIServices/SamsungAIService.cs
@@ -0,0 +1,444 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Tizen.Uix.Tts;
+
+
+namespace Tizen.AIAvatar
+{
+ ///
+ /// Configuration class for Samsung AI services.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class SamsungAIConfiguration : AIServiceConfiguration
+ {
+ ///
+ /// Initializes a new instance of the class with default endpoint settings.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public SamsungAIConfiguration()
+ {
+ Endpoints = new ServiceEndpoints
+ {
+ LLMEndpoint = "https://playground-api.sec.samsung.net/api/v1/chat/completions",
+ TextToSpeechEndpoint = "OnDevice"
+ };
+ }
+
+ ///
+ /// Gets or sets the default model to use for LLM services.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Model { get; set; } = "chat-65b-32k-1.1.2";
+
+ ///
+ /// Gets or sets the small model version for LLM services.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string SmallModel { get; set; } = "Gauss.L-1B-0.1.1";
+ }
+
+ ///
+ /// Samsung AI Service implementation that provides Text-to-Speech and LLM services.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class SamsungAIService : BaseAIService, ITextToSpeechService, ILLMService
+ {
+ private readonly SamsungAIConfiguration config;
+ private readonly TtsClient ttsHandle;
+ private TaskCompletionSource ttsCompletionSource;
+
+ // Audio-related constants
+ private const float audioLengthFactor = 0.16f;
+ private const float audioTailLengthFactor = 0.015f;
+ private const float audioBufferMultiflier = 2f;
+
+
+ private int audioLength;
+ private int desiredBufferLength;
+ private int audioTailLength;
+ private byte[] recordedBuffer;
+ private byte[] audioMainBuffer;
+ private List audioSyncBuffer;
+ private float desiredBufferDuration = audioLengthFactor + audioTailLengthFactor;
+
+ ///
+ /// Occurs when a response is generated by the LLM service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler ResponseHandler;
+
+ ///
+ /// Occurs when the Text-to-Speech service starts processing.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler OnTtsStart;
+
+ ///
+ /// Occurs when the Text-to-Speech service is receiving data.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler OnTtsReceiving;
+
+ ///
+ /// Occurs when the Text-to-Speech service finishes processing.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler OnTtsFinish;
+
+ ///
+ /// Gets the name of the AI service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string ServiceName => "SamsungResearch";
+
+ ///
+ /// Gets the capabilities supported by this AI service.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override ServiceCapabilities Capabilities =>
+ ServiceCapabilities.TextToSpeech | ServiceCapabilities.LargeLanguageModel;
+
+ ///
+ /// Initializes a new instance of the class with the specified configuration.
+ ///
+ /// The configuration for the Samsung AI Service.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public SamsungAIService(SamsungAIConfiguration config) : base(config)
+ {
+ this.config = config;
+
+ try
+ {
+ ttsHandle = new TtsClient();
+ ttsHandle.SynthesizedPcm += TtsSynthesizedPCM;
+ ttsHandle.PlayingMode = PlayingMode.ByClient;
+
+ ttsHandle.Prepare();
+
+ GetSupportedVoices();
+ }
+ catch (Exception e)
+ {
+ throw new Exception($"[ERROR] Failed to prepare TTS {e.Message}");
+ }
+ }
+
+ ///
+ /// Generates a response from the LLM service asynchronously.
+ ///
+ /// The input message to be processed by the LLM.
+ /// Optional parameters for customizing the LLM output.
+ /// A task representing the asynchronous operation.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public async Task GenerateTextAsync(string message, Dictionary options = null)
+ {
+ var client = ClientManager.GetClient(config.Endpoints.LLMEndpoint);
+
+ var request = new RestRequest(Method.Post)
+ .AddHeader("Authorization", $"Bearer {config.ApiKey}");
+
+ int taskID = (int)(options?.GetValueOrDefault("TaskID", 0) ?? 0);
+
+ if (options != null && options.TryGetValue("promptFilePath", out var jsonFilePathObj) && jsonFilePathObj is string jsonFilePath)
+ {
+ // Read JSON file content
+ if (File.Exists(jsonFilePath))
+ {
+ try
+ {
+ var jsonContent = await File.ReadAllTextAsync(jsonFilePath).ConfigureAwait(false);
+ Prompt prompt = JsonSerializer.Deserialize(jsonContent);
+ var msg = prompt.messages.Last();
+ msg.content = String.Format(msg.content, message);
+
+ request.AddJsonBody(prompt);
+ }
+ catch (Exception ex)
+ {
+ ResponseHandler?.Invoke(this, new llmResponseEventArgs { TaskID = taskID, Error = ex.Message });
+ return;
+ }
+ }
+ else
+ {
+ ResponseHandler?.Invoke(this, new llmResponseEventArgs { TaskID = taskID, Error = $"File not found: {jsonFilePath}" });
+ return;
+ }
+ }
+ else
+ {
+
+ var messages = new List
+ {
+ new { role = "user", content = message }
+ };
+ // Add the default body if no JSON file is provided
+ request.AddJsonBody(new
+ {
+ model = config.Model,
+ messages = messages,
+ temperature = options?.GetValueOrDefault("temperature", 0.5) ?? 0.5,
+ seed = options?.GetValueOrDefault("seed", 0)
+ });
+ }
+
+ var response = await client.ExecuteAsync(request).ConfigureAwait(false);
+ if (!response.IsSuccessful)
+ {
+ ResponseHandler?.Invoke(this, new llmResponseEventArgs { TaskID = taskID, Error = response.ErrorMessage });
+ return;
+ }
+
+ var jsonResponse = JsonSerializer.Deserialize(response.Content);
+ string content = jsonResponse.GetProperty("response").GetProperty("content").GetString();
+
+ ResponseHandler?.Invoke(this, new llmResponseEventArgs { TaskID = taskID, Text = content });
+ }
+
+ ///
+ /// Converts the given text to speech asynchronously and returns the audio data.
+ ///
+ /// The text to be converted to speech.
+ /// Optional parameter to specify the voice type.
+ /// Optional parameters for customizing speech output.
+ /// A task representing the asynchronous operation, with a byte array of the generated audio data.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public async Task TextToSpeechAsync(string text, string voice = null, Dictionary options = null)
+ {
+ audioSyncBuffer = new List();
+
+ await TextToSpeechStreamAsync(text,voice, options).ConfigureAwait(false);
+
+ return audioSyncBuffer.ToArray();
+ }
+
+ ///
+ /// Streams the given text as speech asynchronously.
+ ///
+ /// The text to be converted to speech.
+ /// The voice type to be used for the speech.
+ /// Optional parameters for customizing speech output.
+ /// A task representing the asynchronous operation.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public async Task TextToSpeechStreamAsync(string text, string voice, Dictionary options)
+ {
+ ttsCompletionSource = new TaskCompletionSource();
+ recordedBuffer = Array.Empty();
+
+ //Option 처리
+ SpeedRange speedRange = ttsHandle.GetSpeedRange();
+ int speed = speedRange.Normal;
+ int voiceType = (int)VoiceType.Auto;
+
+ if (options != null)
+ {
+ if (options.ContainsKey("speechRate"))
+ {
+ float speechRate = (float)(options["speechRate"]) / 2.0f;
+ speed = (int)(speedRange.Min + (speedRange.Max - speedRange.Min) * speechRate);
+ }
+
+ if (options.ContainsKey("voiceType"))
+ {
+ voiceType = (int)options["voiceType"];
+ }
+ }
+ /////////
+ try
+ {
+ ttsHandle.AddText(text, voice, voiceType, speed);
+
+ ttsHandle.Play();
+
+ await ttsCompletionSource.Task.ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ OnTtsFinish?.Invoke(this, new ttsStreamingEventArgs
+ {
+ Text = text,
+ Voice = voice,
+ Error = ex.Message,
+ AudioData = Array.Empty()
+ });
+ }
+ }
+
+ ///
+ /// Releases all resources used by the AI service.
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ ttsHandle?.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ ///
+ /// Handles the PCM data synthesized by the TTS service.
+ ///
+ /// The sender of the event.
+ /// The event arguments containing PCM data and other information.
+ private void TtsSynthesizedPCM(object sender, SynthesizedPcmEventArgs e)
+ {
+ try
+ {
+ switch (e.EventType)
+ {
+ case SynthesizedPcmEvent.Start:
+ recordedBuffer = Array.Empty();
+
+ audioLength = (int)(audioLengthFactor * e.SampleRate * audioBufferMultiflier);
+ audioTailLength = (int)(audioTailLengthFactor * e.SampleRate * audioBufferMultiflier);
+ desiredBufferLength = (int)(desiredBufferDuration * e.SampleRate * audioBufferMultiflier);
+
+ audioMainBuffer = new byte[desiredBufferLength];
+
+ OnTtsStart?.Invoke(this, new ttsStreamingEventArgs
+ {
+ SampleRate = e.SampleRate,
+ AudioBytes = audioLength,
+ TotalBytes = 0,
+ AudioData = Array.Empty()
+ });
+
+ Log.Info("Tizen.AIAvatar", $"TTS Start: UtteranceId={e.UtteranceId}, SampleRate={e.SampleRate}");
+
+ break;
+
+ case SynthesizedPcmEvent.Continue:
+
+ audioSyncBuffer?.AddRange(e.Data);
+ recordedBuffer = recordedBuffer.Concat(e.Data).ToArray();
+
+ if (recordedBuffer.Length >= desiredBufferLength)
+ {
+ Buffer.BlockCopy(recordedBuffer, 0, audioMainBuffer, 0, desiredBufferLength);
+ OnTtsReceiving?.Invoke(this, new ttsStreamingEventArgs
+ {
+ AudioData = audioMainBuffer,
+ ProcessedBytes = audioMainBuffer.Length
+ });
+
+
+ int slicedBufferLength = recordedBuffer.Length - audioLength;
+ byte[] slicedBuffer = new byte[slicedBufferLength];
+ Buffer.BlockCopy(recordedBuffer, audioLength, slicedBuffer, 0, slicedBufferLength);
+
+ recordedBuffer = slicedBuffer;
+ }
+
+ break;
+
+ case SynthesizedPcmEvent.Finish:
+ Log.Info("Tizen.AIAvatar", $"TTS Finish: UtteranceId={e.UtteranceId}");
+
+ // Send any remaining audio data
+ if (recordedBuffer.Length > 0)
+ {
+ int minBufferSize = Math.Min(desiredBufferLength, recordedBuffer.Length);
+
+ Array.Clear(audioMainBuffer, 0, desiredBufferLength);
+ Buffer.BlockCopy(recordedBuffer, 0, audioMainBuffer, 0, minBufferSize);
+ OnTtsReceiving?.Invoke(this, new ttsStreamingEventArgs
+ {
+ AudioData = audioMainBuffer,
+ ProcessedBytes = minBufferSize,
+ ProgressPercentage = 100
+ });
+ }
+
+ OnTtsFinish?.Invoke(this, new ttsStreamingEventArgs
+ {
+ AudioData = Array.Empty(),
+ TotalBytes = recordedBuffer.Length,
+ ProgressPercentage = 100
+ });
+
+ ttsCompletionSource?.SetResult(true);
+ break;
+
+ case SynthesizedPcmEvent.Fail:
+ var error = "TTS synthesis failed";
+
+ OnTtsFinish?.Invoke(this, new ttsStreamingEventArgs
+ {
+ Error = error,
+ AudioData = Array.Empty()
+ });
+
+ ttsCompletionSource?.SetException(new Exception(error));
+
+ break;
+
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Tizen.AIAvatar", $"Error in TtsSynthesizedPCM: {ex.Message}");
+ OnTtsFinish?.Invoke(this, new ttsStreamingEventArgs
+ {
+ Error = ex.Message,
+ AudioData = Array.Empty()
+ });
+ ttsCompletionSource?.SetException(ex);
+ }
+ }
+
+ ///
+ /// Retrieves the list of supported voices from the TTS client.
+ ///
+ /// A list of supported voice information.
+ private List GetSupportedVoices()
+ {
+ var voiceInfoList = new List();
+
+ if (ttsHandle == null)
+ {
+ Log.Error("Tizen.AIAvatar", $"ttsHandle is null");
+ return voiceInfoList;
+ }
+
+ var supportedVoices = ttsHandle.GetSupportedVoices();
+ foreach (var supportedVoice in supportedVoices)
+ {
+ Log.Info("Tizen.AIAvatar", $"{supportedVoice.Language} & {supportedVoice.VoiceType} is supported");
+ voiceInfoList.Add(new VoiceInfo() { Language = supportedVoice.Language, Type = (VoiceType)supportedVoice.VoiceType });
+ }
+ return voiceInfoList;
+ }
+
+ ///
+ /// Invokes the response handler event with the given event arguments.
+ ///
+ /// The response event arguments.
+ private void OnResponseHandler(llmResponseEventArgs e)
+ {
+ ResponseHandler?.Invoke(this, e);
+ }
+ }
+}
diff --git a/src/Tizen.AIAvatar/src/Animations/EyeBlinker.cs b/src/Tizen.AIAvatar/src/Animations/EyeBlinker.cs
deleted file mode 100644
index 14319028d91..00000000000
--- a/src/Tizen.AIAvatar/src/Animations/EyeBlinker.cs
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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 Tizen.NUI;
-
-using static Tizen.AIAvatar.AIAvatar;
-
-namespace Tizen.AIAvatar
-{
- internal class EyeBlinker : IBlendShapeModule, IDisposable
- {
- private AvatarMotionState currentMotionState = AvatarMotionState.Unavailable;
-
- private readonly Object motionChangedLock = new Object();
- private event EventHandler motionChanged;
-
- private const int blinkIntervalMinimum = 800;
- private const int blinkIntervalMaximum = 3000;
- private Animation eyeAnimation;
-
- private Timer blinkTimer;
-
- private bool isPlaying = false;
- private const int blinkDuration = 200;
-
- public AvatarMotionState CurrentMotionState
- {
- get
- {
- return currentMotionState;
- }
- set
- {
- if (currentMotionState == value)
- {
- return;
- }
- var preState = currentMotionState;
- currentMotionState = value;
- if (motionChanged != null)
- {
- motionChanged?.Invoke(this, new AvatarMotionChangedEventArgs(preState, currentMotionState));
- }
- }
- }
-
- public event EventHandler MotionStateChanged
- {
- add
- {
- lock (motionChangedLock)
- {
- motionChanged += value;
- }
-
- }
-
- remove
- {
- lock (motionChangedLock)
- {
- if (motionChanged == null)
- {
- Log.Error(LogTag, "Remove StateChanged Failed : motionChanged is null");
- return;
- }
- motionChanged -= value;
- }
- }
- }
-
- public EyeBlinker()
- {
-
- }
-
- public void Dispose()
- {
- DestroyAnimation();
- }
-
- public void Init(Animation eyeAnimation)
- {
- this.eyeAnimation = eyeAnimation;
- }
-
- public void Play()
- {
- //data
- StartEyeBlink();
- }
-
- public void Stop()
- {
- StopEyeBlink();
- }
-
- public void Pause()
- {
- eyeAnimation?.Pause();
- }
-
- public void Destroy()
- {
- DestroyAnimation();
- }
-
- private void StartEyeBlink()
- {
- DestroyBlinkTimer();
-
- blinkTimer = new Timer(blinkDuration);
- if (blinkTimer != null)
- {
- blinkTimer.Tick += OnBlinkTimer;
- blinkTimer?.Start();
- isPlaying = true;
- }
- }
-
- private void PauseEyeBlink()
- {
- blinkTimer?.Stop();
- isPlaying = false;
- }
-
- private void StopEyeBlink()
- {
- blinkTimer?.Stop();
- isPlaying = false;
- }
-
- private void DestroyAnimation()
- {
- DestroyBlinkTimer();
- if (eyeAnimation != null)
- {
- eyeAnimation.Stop();
- eyeAnimation.Dispose();
- eyeAnimation = null;
- }
- isPlaying = false;
- }
-
- private bool OnBlinkTimer(object source, Timer.TickEventArgs e)
- {
- if (eyeAnimation == null)
- {
- Log.Error(LogTag, "eye animation is not ready");
- return false;
- }
- eyeAnimation?.Play();
-
- var random = new Random();
- var fortimerinterval = (uint)random.Next(blinkIntervalMinimum, blinkIntervalMaximum);
- blinkTimer.Interval = fortimerinterval;
- return true;
- }
-
- private void DestroyBlinkTimer()
- {
- if (blinkTimer != null)
- {
- blinkTimer.Tick -= OnBlinkTimer;
- blinkTimer.Stop();
- blinkTimer.Dispose();
- blinkTimer = null;
- }
- }
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Animations/MotionPlayer.cs b/src/Tizen.AIAvatar/src/Animations/MotionPlayer.cs
deleted file mode 100644
index a590ba9deae..00000000000
--- a/src/Tizen.AIAvatar/src/Animations/MotionPlayer.cs
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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 Tizen.NUI;
-using Tizen.NUI.Scene3D;
-using static Tizen.AIAvatar.AIAvatar;
-
-namespace Tizen.AIAvatar
-{
- internal class MotionPlayer
- {
- private Animation motionAnimation;
- private EyeBlinker eyeBlinker;
-
- internal Animation MotionAnimation { get => motionAnimation; private set => motionAnimation = value; }
-
- internal MotionPlayer()
- {
- eyeBlinker = new EyeBlinker();
-
-
- }
-
- internal void PlayAnimation(Animation motionAnimation, int duration = 3000, bool isLooping = false, int loopCount = 1)
- {
- ResetAnimations();
- if (motionAnimation != null)
- {
- MotionAnimation = motionAnimation;
- MotionAnimation?.Play();
- }
- else
- {
- Tizen.Log.Error(LogTag, "motionAnimation is null");
- }
- }
-
- internal void PauseMotionAnimation()
- {
- MotionAnimation?.Pause();
- }
-
- internal void StopMotionAnimation()
- {
- MotionAnimation?.Stop();
- }
-
- internal void SetBlinkAnimation(Animation blinkerAnimation)
- {
- eyeBlinker?.Init(blinkerAnimation);
- }
-
- internal void StartEyeBlink()
- {
- eyeBlinker?.Play();
- }
-
- internal void PauseEyeBlink()
- {
- eyeBlinker?.Pause();
- }
-
-
- internal void StopEyeBlink()
- {
- eyeBlinker?.Stop();
- }
-
- internal void DestroyAnimations()
- {
- eyeBlinker?.Destroy();
- }
-
- private void ResetAnimations()
- {
- if (MotionAnimation != null)
- {
- MotionAnimation.Stop();
- MotionAnimation.Dispose();
- MotionAnimation = null;
- }
- }
-
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Common/Avatar.cs b/src/Tizen.AIAvatar/src/Common/Avatar.cs
deleted file mode 100644
index 8ff4306ebe3..00000000000
--- a/src/Tizen.AIAvatar/src/Common/Avatar.cs
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.ComponentModel;
-using Tizen.NUI.Scene3D;
-using Tizen.NUI;
-
-using static Tizen.AIAvatar.AIAvatar;
-
-namespace Tizen.AIAvatar
-{
- ///
- /// The Avatar class displays 3D avatars and provides easy access to their animations.
- /// This class is a sub-class of the Model class which allows us to easily control the Avatar's animations.
- /// Avatar also supports AR Emoji for humanoid-based 3D models.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public class Avatar : Model
- {
- private AvatarProperties avatarProperties = new DefaultAvatarProperties();
-
- private MotionPlayer motionPlayer;
-
- ///
- /// The AvatarProperties property gets or sets the AvatarProperties object containing various information about the Avatar.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public AvatarProperties Properties
- {
- get => avatarProperties;
- set
- {
- avatarProperties = value;
- }
- }
-
- ///
- /// Create an initialized AvatarModel.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public Avatar() : base()
- {
- InitAvatar();
- }
-
- ///
- /// Create an initialized AREmojiDefaultAvatar.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public Avatar(AvatarInfo avatarInfo) : base(avatarInfo.ResourcePath)
- {
- InitAvatar();
- }
-
- ///
- /// Create an initialized Avatar.
- ///
- /// avatar file url.(e.g. glTF).
- /// The url to derectory containing resources: binary, image etc.
- ///
- /// If resourceDirectoryUrl is empty, the parent directory url of avatarUrl is used for resource url.
- ///
- /// http://tizen.org/privilege/mediastorage for local files in media storage.
- /// http://tizen.org/privilege/externalstorage for local files in external storage.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public Avatar(string avatarUrl, string resourceDirectoryUrl = "") : base(avatarUrl, resourceDirectoryUrl)
- {
- InitAvatar();
- }
-
- ///
- /// Copy constructor.
- ///
- /// Source object to copy.
- [EditorBrowsable(EditorBrowsableState.Never)]
- public Avatar(Avatar avatar) : base(avatar)
- {
- InitAvatar();
- }
-
- #region Manage Animating
-
- ///
- /// Plays the specified avatar animation with an optional duration and loop count.
- ///
- /// The AnimationInfo object containing information about the desired avatar animation.
- /// The duration of the animation in milliseconds (default is 3000).
- /// A boolean indicating whether the animation should be looped or not.
- /// The number of times to repeat the animation if it's set to loop.
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void PlayAnimation(AnimationInfo animationInfo, int duration = 3000, bool isLooping = false, int loopCount = 1)
- {
- if (animationInfo == null)
- {
- Tizen.Log.Error(LogTag, "animationInfo is null");
- return;
- }
- if (animationInfo.MotionData == null)
- {
- Tizen.Log.Error(LogTag, "animationInfo.MotionData is null");
- return;
- }
- motionAnimation = GenerateMotionDataAnimation(animationInfo.MotionData);
- if (motionAnimation != null)
- {
- motionAnimation.Duration = duration;
- motionAnimation.Looping = isLooping;
- motionAnimation.LoopCount = loopCount;
- motionAnimation.BlendPoint = 0.2f;
- motionPlayer.PlayAnimation(motionAnimation);
- }
- else
- {
- Tizen.Log.Error(LogTag, "motionAnimation is null");
- }
- }
-
- Animation motionAnimation;
-
- ///
- /// Plays the specified avatar animation with MotionData and an optional duration and loop count.
- ///
- /// The MotionData object containing information about the desired avatar animation.
- /// The duration of the animation in milliseconds (default is 3000).
- /// A boolean indicating whether the animation should be looped or not.
- /// The number of times to repeat the animation if it's set to loop.
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void PlayAnimation(MotionData motionData, int duration = 3000, bool isLooping = false, int loopCount = 1)
- {
-
- if (motionData == null)
- {
- Tizen.Log.Error(LogTag, "motionData is null");
- return;
- }
- var motionAnimation = GenerateMotionDataAnimation(motionData);
- if (motionAnimation != null)
- {
- motionAnimation.Duration = duration;
- motionAnimation.Looping = isLooping;
- motionAnimation.LoopCount = loopCount;
- motionAnimation.BlendPoint = 0.2f;
- motionPlayer.PlayAnimation(motionAnimation);
- }
- else
- {
- Tizen.Log.Error(LogTag, "motionAnimation is null");
- }
- }
-
- ///
- /// Plays the specified avatar animation based on its index within the available animations and an optional duration and loop count.
- ///
- /// The zero-based index of the desired avatar animation within the list of available animations.
- /// The duration of the animation in milliseconds (default is 3000).
- /// A boolean indicating whether the animation should be looped or not.
- /// The number of times to repeat the animation if it's set to loop.
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void PlayAnimation(int index, int duration = 3000, bool isLooping = false, int loopCount = 1)
- {
- //TODO by index
- //var motionAnimation = GenerateMotionDataAnimation(animationInfoList[index].MotionData);
- /*motionAnimation.Duration = duration;
- motionAnimation.Looping = isLooping;
- motionAnimation.LoopCount = loopCount;
- motionAnimation.BlendPoint = 0.2f;
-
- motionPlayer.PlayAnimation(motionAnimation);*/
- }
-
- ///
- /// Pauses the currently playing avatar animation.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void PauseMotionAnimation()
- {
- motionPlayer.PauseMotionAnimation();
- }
-
- ///
- /// Stops the currently playing avatar animation.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void StopMotionAnimation()
- {
- motionPlayer?.StopMotionAnimation();
- }
-
- ///
- /// Starts the eye blink animation for the current avatar.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void StartEyeBlink()
- {
- motionPlayer?.StartEyeBlink();
- }
-
- ///
- /// Pauses the eye blink animation for the current avatar.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void PauseEyeBlink()
- {
- motionPlayer?.PauseEyeBlink();
- }
-
- ///
- /// Stops the eye blink animation for the current avatar.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void StopEyeBlink()
- {
- motionPlayer?.StopEyeBlink();
- }
- #endregion
-
- private void InitAvatar()
- {
- motionPlayer = new MotionPlayer();
- var eyeMotionData = CreateEyeBlinkMotionData(200);
- if (eyeMotionData == null)
- {
- Tizen.Log.Info(LogTag, "Failed Loading eyeAnimation");
- }
-
- ResourcesLoaded += (s, e) =>
- {
- var eyeAnimation = GenerateMotionDataAnimation(eyeMotionData);
- if (eyeAnimation != null)
- {
- motionPlayer.SetBlinkAnimation(eyeAnimation);
- }
- };
- }
-
- private MotionData CreateEyeBlinkMotionData(int ms)
- {
- var keyFrames = new KeyFrames();
- keyFrames.Add(0.1f, 0.0f);
- keyFrames.Add(0.5f, 1.0f);
- keyFrames.Add(0.9f, 0.0f);
-
- var headBlendShapeEyeLeft = new AvatarBlendShapeIndex(avatarProperties.NodeMapper, NodeType.HeadGeo, avatarProperties.BlendShapeMapper, BlendShapeType.EyeBlinkLeft);
- var headBlendShapeEyeRight = new AvatarBlendShapeIndex(avatarProperties.NodeMapper, NodeType.HeadGeo, avatarProperties.BlendShapeMapper, BlendShapeType.EyeBlinkRight);
- var eyelashBlendShapeEyeLeft = new AvatarBlendShapeIndex(avatarProperties.NodeMapper, NodeType.EyelashGeo, avatarProperties.BlendShapeMapper, BlendShapeType.EyeBlinkLeft);
- var eyelashBlendShapeEyeRight = new AvatarBlendShapeIndex(avatarProperties.NodeMapper, NodeType.EyelashGeo, avatarProperties.BlendShapeMapper, BlendShapeType.EyeBlinkRight);
-
- var motionData = new MotionData(ms);
- motionData.Add(headBlendShapeEyeLeft, new MotionValue(keyFrames));
- motionData.Add(headBlendShapeEyeRight, new MotionValue(keyFrames));
- motionData.Add(eyelashBlendShapeEyeLeft, new MotionValue(keyFrames));
- motionData.Add(eyelashBlendShapeEyeRight, new MotionValue(keyFrames));
-
- return motionData;
- }
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Emotion/EmotionAnalyzer.cs b/src/Tizen.AIAvatar/src/Emotion/EmotionAnalyzer.cs
deleted file mode 100644
index ec74d751e85..00000000000
--- a/src/Tizen.AIAvatar/src/Emotion/EmotionAnalyzer.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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 Newtonsoft.Json;
-using System.Net.Http;
-using System;
-using System.Threading.Tasks;
-
-namespace Tizen.AIAvatar
-{
- internal class EmotionAnalyzer
- {
- private LipSyncController avatarTTS;
- private IRestClient restClient;
- private const string playgroundURL = "https://playground-api.sec.samsung.net";
-
- internal EmotionAnalyzer()
- {
- }
-
- internal void InitAvatarLLM(LipSyncController avatarTTS)
- {
- this.avatarTTS = avatarTTS;
- // Setup RestClinet
- var restClientFactory = new RestClientFactory();
- restClient = restClientFactory.CreateClient(playgroundURL);
- }
-
- internal async Task StartTTSWithLLMAsync(string text, string token)
- {
- var bearerToken = token;
- var jsonData = "{\"messages\": [{\"role\": \"user\", \"content\": \"" + text + "\"}]}";
-
- try
- {
- var postResponse = await restClient.SendRequestAsync(HttpMethod.Post, "/api/v1/chat/completions", bearerToken, jsonData);
- var responseData = JsonConvert.DeserializeObject(postResponse);
- string content = responseData["response"]["content"];
- Log.Info("Tizen.AIAvatar", content);
-
- //TTS 호출
- var voiceInfo = new VoiceInfo()
- {
- Language = "en_US",
- Type = VoiceType.Female,
- };
-
- avatarTTS.PlayTTSAsync(content, voiceInfo, (o, e) =>
- {
-
- });
-
- }
- catch (Exception ex)
- {
- Log.Error("Tizen.AIAvatar", "에러 발생: " + ex.Message);
- }
- }
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Emotion/EmotionController.cs b/src/Tizen.AIAvatar/src/Emotion/EmotionController.cs
deleted file mode 100644
index fd0bd421915..00000000000
--- a/src/Tizen.AIAvatar/src/Emotion/EmotionController.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.ComponentModel;
-
-namespace Tizen.AIAvatar
-{
- ///
- /// Manages facial expression control for avatars based on input text sentiment analysis results.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public class EmotionController
- {
- ///
- /// Initializes a new instance of the class without an avatar reference.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public EmotionController()
- {
-
- }
-
- ///
- /// Initializes the EmotionController by setting up the necessary components for managing facial expressions in the avatar.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void Initialize()
- {
-
- }
- ///
- /// This method analyzes emotion the given text.
- ///
- /// The text to analyze
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void AnalizeEmotion(string text)
- {
-
- }
-
- ///
- /// This method starts playing the emotion facial.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void PlayEmotionFacial()
- {
-
- }
-
- ///
- /// This method pauses the emotion facial.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void PauseEmotionFacial()
- {
-
- }
-
- ///
- /// This method stops the emotion facial.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void StopEmotionFacial()
- {
-
- }
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Internal/AIAvatar.cs b/src/Tizen.AIAvatar/src/Internal/AIAvatar.cs
deleted file mode 100644
index 847866ac90d..00000000000
--- a/src/Tizen.AIAvatar/src/Internal/AIAvatar.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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 Tizen.Multimedia;
-
-namespace Tizen.AIAvatar
-{
- internal static class AIAvatar
- {
- internal const string LogTag = "Tizen.AIAvatar";
- internal static readonly string ApplicationResourcePath = "/usr/apps/org.tizen.default-avatar-resource/shared/res/";
- internal static readonly string EmojiAvatarResourcePath = "/models/EmojiAvatar/";
- internal static readonly string DefaultModelResourcePath = "/models/DefaultAvatar/";
- internal static readonly string DefaultMotionResourcePath = "/animation/motion/";
-
- internal static readonly string VisemeInfo = $"{ApplicationResourcePath}/viseme/emoji_viseme_info.json";
- internal static readonly string DefaultModel = "DefaultAvatar.gltf";
-
- internal static readonly string AREmojiDefaultAvatarPath = $"{ApplicationResourcePath}{DefaultModelResourcePath}{DefaultModel}";
-
- internal static readonly string DefaultLowModelResourcePath = "/models/DefaultAvatar_Low/";
- internal static readonly string ExternalModel = "model_external.gltf";
- internal static readonly string AREmojiDefaultLowAvatarPath = $"{ApplicationResourcePath}{DefaultLowModelResourcePath}{ExternalModel}";
-
- internal static AudioOptions DefaultAudioOptions = new AudioOptions(24000, AudioChannel.Mono, AudioSampleType.S16Le);
- internal static AudioOptions CurrentAudioOptions = DefaultAudioOptions;
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Internal/Utils.cs b/src/Tizen.AIAvatar/src/Internal/Utils.cs
deleted file mode 100644
index e845a02f812..00000000000
--- a/src/Tizen.AIAvatar/src/Internal/Utils.cs
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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 Tizen.Security;
-using Newtonsoft.Json;
-using System.Collections.Generic;
-using static Tizen.AIAvatar.AIAvatar;
-
-namespace Tizen.AIAvatar
-{
- internal class Utils
- {
- internal static void ConvertAudioToFloat(in byte[] audioBytes, out float[] audioFloat)
- {
- audioFloat = new float[audioBytes.Length / 2];
-
- for (int i = 0, j = 0; i < audioBytes.Length; i += 2, j++)
- {
- short sample = BitConverter.ToInt16(audioBytes, i);
- audioFloat[j] = sample / 32768.0f;
- }
- }
-
- internal static byte[] ReadAllBytes(string path)
- {
- try
- {
- var bytes = File.ReadAllBytes(path);
- return bytes;
- }
- catch (Exception)
- {
- return null;
- }
- }
-
- internal static void SaveFile(string path, string filename, byte[] array)
- {
- try
- {
- var file = new FileStream($"{path}/{filename}", FileMode.Create);
- file.Write(array, 0, array.Length);
- file.Close();
- }
- catch (Exception)
- {
- return;
- }
- }
-
- internal static void CheckPrivilege(string privilege)
- {
- var result = PrivacyPrivilegeManager.CheckPermission(privilege);
-
- switch (result)
- {
- case CheckResult.Allow:
- Log.Info(LogTag, $"Privilege \"{privilege}\" : allowed.");
- break;
- case CheckResult.Deny:
- Log.Info(LogTag, $"Privilege \"{privilege}\" : denied.");
- /// Privilege can't be used
- break;
- case CheckResult.Ask:
- /// Request permission to user
- PrivacyPrivilegeManager.RequestPermission(privilege);
- break;
- }
- }
-
- internal static T ConvertJson(string jsonString)
- {
- return JsonConvert.DeserializeObject(jsonString);
- }
-
- internal int FindMaxValue(List list, Converter projection)
- {
- if (list.Count == 0)
- {
- throw new InvalidOperationException("Empty list");
- }
- int maxValue = int.MinValue;
- foreach (T item in list)
- {
- int value = projection(item);
- if (value > maxValue)
- {
- maxValue = value;
- }
- }
- return maxValue;
- }
- }
-}
-
-internal class SystemUtils
-{
- internal static string GetFileName(string path)
- {
- return System.IO.Path.GetFileName(path);
- }
- internal static string GetFileNameWithoutExtension(string path)
- {
- return System.IO.Path.GetFileNameWithoutExtension(path);
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Lipsync/AsyncLipSyncer.cs b/src/Tizen.AIAvatar/src/Lipsync/AsyncLipSyncer.cs
deleted file mode 100644
index 952c47c4980..00000000000
--- a/src/Tizen.AIAvatar/src/Lipsync/AsyncLipSyncer.cs
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.ComponentModel;
-using Tizen.NUI;
-
-using static Tizen.AIAvatar.AIAvatar;
-
-namespace Tizen.AIAvatar
-{
- [EditorBrowsable(EditorBrowsableState.Never)]
- internal class AsyncLipSyncer
- {
- private readonly uint AsyncPlayTime = 160;
- private Queue lipAnimations;
- private Queue lipAudios;
- private Timer asyncVowelTimer;
-
- private bool isAsyncInit = false;
- private bool isFinishAsyncLip = false;
-
- [EditorBrowsable(EditorBrowsableState.Never)]
- internal AsyncLipSyncer()
- {
-
- }
-
- internal int SampleRate { get; set; }
-
- [EditorBrowsable(EditorBrowsableState.Never)]
- internal bool IsAsyncInit { get=>isAsyncInit; set=>isAsyncInit = value; }
-
- [EditorBrowsable(EditorBrowsableState.Never)]
- internal void SetFinishAsyncLip(bool finished)
- {
- isFinishAsyncLip = finished;
- }
-
- [EditorBrowsable(EditorBrowsableState.Never)]
- protected new void OnLipAnimationFinished(object sender, EventArgs e)
- {
- if (!isAsyncInit)
- {
- //TODO : State is Stop
- }
- else
- {
- Tizen.Log.Error(LogTag, "OnLipAnimationFinished---------------c");
- //Async State
- if (isFinishAsyncLip && lipAnimations.Count == 0)
- {
- Tizen.Log.Error(LogTag, "Finish vowel lip sync");
-
-
- //Audio Player is Stop, AudioPlayer.Stop();
- //TODO : State is Stop
- DestroyVowelTimer();
- isAsyncInit = false;
- }
- }
- }
-
- internal void InitAsyncLipsync()
- {
- if (lipAnimations == null)
- {
- lipAnimations = new Queue();
- }
- else
- {
- lipAnimations.Clear();
- }
-
- if (lipAudios == null)
- {
- lipAudios = new Queue();
- }
- else
- {
- lipAudios.Clear();
- }
- }
-
- private void EnqueueVowels(byte[] buffer, bool deleteLast = false)
- {
- /*
- var vowels = PredictVowels(buffer);
- if (vowels != null)
- {
- vowelPools.Enqueue(vowels);
- if (deleteLast)
- {
- var vowelList = new List(vowels);
- vowelList.RemoveAt(vowelList.Count - 1);
- vowels = vowelList.ToArray();
- }
- }*/
- }
-
- internal Animation CreateAsyncLipAnimation(byte[] buffer, int sampleRate)
- {
- EnqueueVowels(buffer, false);
- return null;
- //return CreateLipAnimationByVowelsQueue(sampleRate);
- }
-
- internal void EnqueueAnimation(byte[] recordBuffer, int sampleRate, int audioLength)
- {
- var createdAni = CreateAsyncLipAnimation(recordBuffer, sampleRate);
- if (createdAni != null)
- {
- lipAnimations.Enqueue(createdAni);
- }
-
- //Use Audio Full File
- ///var createdAni = avatarLipSyncer.CreateLipAnimation(recordBuffer, sampleRate);
- //lipAnimations.Enqueue(createdAni);
-
- var currentAudioBuffer = new byte[audioLength];
- Buffer.BlockCopy(recordBuffer, 0, currentAudioBuffer, 0, audioLength);
-
- lipAudios.Enqueue(currentAudioBuffer);
- }
-
- internal bool PlayAsyncLip(int sampleRate, bool isFinishAsyncLip)
- {
- try
- {
- if (lipAudios.Count <= 0 && lipAnimations.Count <= 0)
- {
- Tizen.Log.Info(LogTag, "Return lipaudio 0");
- if (isFinishAsyncLip)
- {
- Tizen.Log.Info(LogTag, "Finish Async lipsync");
- return false;
- }
- return true;
- }
- Tizen.Log.Info(LogTag, "Async timer tick lipAudios : " + lipAudios.Count);
- Tizen.Log.Info(LogTag, "Async timer tick lipAnimations : " + lipAnimations.Count);
-
- var lipAnimation = lipAnimations.Dequeue();
- lipAnimation.Finished += OnLipAnimationFinished;
-
- //ResetLipAnimation(lipAnimation);
- //PlayLipAnimation();
- var audioBuffer = lipAudios.Dequeue();
-
- //Audio Playe rAsync Play
- //AudioPlayer.PlayAsync(audioBuffer, sampleRate);
- return true;
-
- }
- catch (Exception ex)
- {
- Log.Error(LogTag, $"---Log Tick : {ex.StackTrace}");
-
- return false;
- }
- }
-
- internal void StartAsyncLipPlayTimer()
- {
- if (asyncVowelTimer == null)
- {
- Tizen.Log.Info(LogTag, "Start Async");
- asyncVowelTimer = new Timer(AsyncPlayTime);
- asyncVowelTimer.Tick += OnAsyncVowelTick;
- asyncVowelTimer.Start();
- }
- return;
- }
-
- private void DestroyVowelTimer()
- {
- if (asyncVowelTimer != null)
- {
-
- asyncVowelTimer.Tick -= OnAsyncVowelTick;
- asyncVowelTimer.Stop();
- asyncVowelTimer.Dispose();
- asyncVowelTimer = null;
- }
- }
-
- private bool OnAsyncVowelTick(object source, Tizen.NUI.Timer.TickEventArgs e)
- {
- return PlayAsyncLip(SampleRate, isFinishAsyncLip);
- }
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Audio2Vowels.cs b/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Audio2Vowels.cs
new file mode 100644
index 00000000000..c1cddee7027
--- /dev/null
+++ b/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Audio2Vowels.cs
@@ -0,0 +1,108 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.Collections.Generic;
+using System.ComponentModel;
+
+
+namespace Tizen.AIAvatar
+{
+ ///
+ /// The Audio2Vowels class is responsible for predicting vowels from audio data using a pre-trained TensorFlow model.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class Audio2Vowels
+ {
+ ///
+ /// The sample rate of the audio data.
+ ///
+ private int sampleRate;
+
+ ///
+ /// The pre-trained TensorFlow model used for vowel prediction.
+ ///
+ private TFVowel6 model;
+
+ ///
+ /// A dictionary containing vowel classifiers indexed by their respective sample rates.
+ ///
+ private Dictionary classifiers;
+
+ ///
+ /// Initializes a new instance of the Audio2Vowels class with the specified TensorFlow model file path.
+ ///
+ /// The path to the pre-trained TensorFlow model file.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public Audio2Vowels(string tensorflowFilePath)
+ {
+ classifiers = new Dictionary();
+ model = new TFVowel6(new int[4] { 12, 1, 1, 1 }, new int[4] { 7, 1, 1, 1 }, tensorflowFilePath);
+ }
+
+ ///
+ /// Predicts the vowels present in the given audio data.
+ ///
+ /// The audio data to analyze.
+ /// An array of predicted vowels.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string[] PredictVowels(byte[] audioData)
+ {
+ VowelClassifier classifier = GetVowelClassifier(sampleRate);
+ return classifier.Inference(audioData, model);
+ }
+
+ ///
+ /// Sets the sample rate of the audio data.
+ ///
+ /// The sample rate to set.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetSampleRate(int rate)
+ {
+ sampleRate = rate;
+ }
+
+ ///
+ /// Gets the current sample rate of the audio data.
+ ///
+ /// The current sample rate.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int GetSampleRate()
+ {
+ return sampleRate;
+ }
+
+ ///
+ /// Retrieves the vowel classifier corresponding to the specified sample rate.
+ /// If no classifier exists for the given sample rate, a new one is created.
+ ///
+ /// The sample rate for which to retrieve the classifier.
+ /// A VowelClassifier object.
+ private VowelClassifier GetVowelClassifier(int rate)
+ {
+ if (classifiers.ContainsKey(rate))
+ {
+ return classifiers[rate];
+ }
+ else
+ {
+ VowelClassifier classifier = new VowelClassifier(rate);
+ classifiers[rate] = classifier;
+ return classifier;
+ }
+ }
+ }
+}
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/ISingleShotModel.cs b/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/ISingleShotModel.cs
similarity index 100%
rename from src/Tizen.AIAvatar/src/Lipsync/VowelConverter/ISingleShotModel.cs
rename to src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/ISingleShotModel.cs
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Audio2Vowel/inMFCC/Algorithm/CustomMfccExtractor.cs b/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/MFCC/Algorithm/CustomMfccExtractor.cs
similarity index 100%
rename from src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Audio2Vowel/inMFCC/Algorithm/CustomMfccExtractor.cs
rename to src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/MFCC/Algorithm/CustomMfccExtractor.cs
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Audio2Vowel/inMFCC/Algorithm/DCT.cs b/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/MFCC/Algorithm/DCT.cs
similarity index 100%
rename from src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Audio2Vowel/inMFCC/Algorithm/DCT.cs
rename to src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/MFCC/Algorithm/DCT.cs
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Audio2Vowel/inMFCC/Algorithm/FFT.cs b/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/MFCC/Algorithm/FFT.cs
similarity index 100%
rename from src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Audio2Vowel/inMFCC/Algorithm/FFT.cs
rename to src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/MFCC/Algorithm/FFT.cs
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Audio2Vowel/inMFCC/Algorithm/FilterBank.cs b/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/MFCC/Algorithm/FilterBank.cs
similarity index 100%
rename from src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Audio2Vowel/inMFCC/Algorithm/FilterBank.cs
rename to src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/MFCC/Algorithm/FilterBank.cs
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Audio2Vowel/inMFCC/Algorithm/IMFccExtractor.cs b/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/MFCC/Algorithm/IMFccExtractor.cs
similarity index 100%
rename from src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Audio2Vowel/inMFCC/Algorithm/IMFccExtractor.cs
rename to src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/MFCC/Algorithm/IMFccExtractor.cs
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Audio2Vowel/inMFCC/Algorithm/PreEmphasis.cs b/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/MFCC/Algorithm/PreEmphasis.cs
similarity index 100%
rename from src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Audio2Vowel/inMFCC/Algorithm/PreEmphasis.cs
rename to src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/MFCC/Algorithm/PreEmphasis.cs
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Audio2Vowel/inMFCC/Algorithm/Window.cs b/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/MFCC/Algorithm/Window.cs
similarity index 100%
rename from src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Audio2Vowel/inMFCC/Algorithm/Window.cs
rename to src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/MFCC/Algorithm/Window.cs
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/SoftmaxLinqExtension.cs b/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/SoftmaxLinqExtension.cs
similarity index 100%
rename from src/Tizen.AIAvatar/src/Lipsync/VowelConverter/SoftmaxLinqExtension.cs
rename to src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/SoftmaxLinqExtension.cs
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/TFVowel6.cs b/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/TFVowel6.cs
similarity index 96%
rename from src/Tizen.AIAvatar/src/Lipsync/VowelConverter/TFVowel6.cs
rename to src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/TFVowel6.cs
index fcafa674366..7d152b0a45b 100644
--- a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/TFVowel6.cs
+++ b/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/TFVowel6.cs
@@ -29,10 +29,8 @@ internal class TFVowel6 : ISingleShotModel
private TensorsInfo outputInfo;
private TensorsData inputData;
private TensorsData outputData;
-
- private readonly string modelPath = ApplicationResourcePath + "audio2vowel_7.tflite";
-
- internal TFVowel6(int[] inputDimension, int[] outputDimension)
+
+ internal TFVowel6(int[] inputDimension, int[] outputDimension, string modelPath)
{
try
{
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/VowelClassifier.cs b/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/VowelClassifier.cs
similarity index 86%
rename from src/Tizen.AIAvatar/src/Lipsync/VowelConverter/VowelClassifier.cs
rename to src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/VowelClassifier.cs
index 958b6f19647..c9771bb478d 100644
--- a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/VowelClassifier.cs
+++ b/src/Tizen.AIAvatar/src/Lipsync/Audio2Vowels/Core/VowelClassifier.cs
@@ -43,13 +43,13 @@ internal VowelClassifier(int sampleRate, float frameDuration = 0.025f, float hop
mfccExtractor = new CustomMfccExtractor(sampleRate, frameDuration, hopDuration, featureCount);
}
- public float GetStepTime()
+ internal float GetStepTime()
{
return stepTime;
}
- public string[] Inference(byte[] audio, ISingleShotModel model)
+ internal string[] Inference(byte[] audio, ISingleShotModel model)
{
int length = PreprocessInput(audio, out float[] features, out List audioEnergy);
var outputData = InferenceModel(features, length, model);
@@ -58,9 +58,9 @@ public string[] Inference(byte[] audio, ISingleShotModel model)
return result.ToArray();
}
- public int PreprocessInput(byte[] audioByte, out float[] flattenedArray, out List audioEnergy)
+ internal int PreprocessInput(byte[] audioByte, out float[] flattenedArray, out List audioEnergy)
{
- Utils.ConvertAudioToFloat(audioByte, out var audio);
+ ConvertAudioToFloat(audioByte, out var audio);
var mfccFeatures = mfccExtractor.ComputeFrom(audio, sampleRate);
@@ -78,7 +78,7 @@ public int PreprocessInput(byte[] audioByte, out float[] flattenedArray, out Lis
return featureLength;
}
- public List InferenceModel(float[] input, int length, ISingleShotModel model)
+ internal List InferenceModel(float[] input, int length, ISingleShotModel model)
{
byte[] inputBuffer = new byte[4 * 12 * 1 * 1];
byte[] outputBuffer;
@@ -108,6 +108,17 @@ public List InferenceModel(float[] input, int length, ISingleShotModel
return fullOutput;
}
+ private void ConvertAudioToFloat(in byte[] audioBytes, out float[] audioFloat)
+ {
+ audioFloat = new float[audioBytes.Length / 2];
+
+ for (int i = 0, j = 0; i < audioBytes.Length; i += 2, j++)
+ {
+ short sample = BitConverter.ToInt16(audioBytes, i);
+ audioFloat[j] = sample / 32768.0f;
+ }
+ }
+
private List ProcessOutput(List outputData, List audioEnergy)
{
var result = new List();
diff --git a/src/Tizen.AIAvatar/src/Lipsync/LipSyncController.cs b/src/Tizen.AIAvatar/src/Lipsync/LipSyncController.cs
deleted file mode 100644
index 45349f86cb0..00000000000
--- a/src/Tizen.AIAvatar/src/Lipsync/LipSyncController.cs
+++ /dev/null
@@ -1,668 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.Collections.Generic;
-using System.ComponentModel;
-using System;
-using Tizen.Uix.Tts;
-
-using static Tizen.AIAvatar.AIAvatar;
-using Tizen.Multimedia;
-using Tizen.NUI.Scene3D;
-using Tizen.NUI;
-
-namespace Tizen.AIAvatar
-{
- ///
- /// A controller class used to manage lip sync functionality for avatars.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public class LipSyncController : IDisposable
- {
- private TTSController ttsController;
- private AudioRecorder audioRecorder;
- private LipSyncer lipSyncer;
-
- private Avatar avatar;
-
- private event EventHandler ttsReadyFinished;
- private readonly Object ttsReadyFinishedLock = new Object();
-
- ///
- /// Initializes a new instance of the class.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public LipSyncController(Avatar avatar)
- {
- this.avatar = avatar;
-
- audioRecorder = new AudioRecorder();
- lipSyncer = new LipSyncer();
-
- lipSyncer.CreatedKeyFrameAnimation += OnCreatedKeyFrameAnimation;
- }
-
- private Animation CreateLipAnimation(AnimationKeyFrame animationKeyFrames, bool isMic = false)
- {
- var animationTime = (int)(animationKeyFrames.AnimationTime * 1000f);
- var lipAnimation = new Animation(animationTime);
-
- var motionData = new MotionData(animationTime);
- for (var i = 0; i < animationKeyFrames.NodeNames.Length; i++)
- {
- var nodeName = animationKeyFrames.NodeNames[i];
- for (var j = 0; j < animationKeyFrames.BlendShapeCounts[i]; j++)
- {
- var blendShapeIndex = new BlendShapeIndex(new PropertyKey(nodeName), new PropertyKey(j));
- var keyFrameList = animationKeyFrames.GetKeyFrames(nodeName, j);
- if (keyFrameList.Count == 0)
- {
- continue;
- }
-
- var keyFrames = new KeyFrames();
- CreateKeyTimeFrames(ref keyFrames, keyFrameList, isMic);
-
- motionData.Add(blendShapeIndex, new MotionValue(keyFrames));
- lipAnimation = avatar.GenerateMotionDataAnimation(motionData);
- }
- }
- return lipAnimation;
- }
-
- private KeyFrames CreateKeyTimeFrames(ref KeyFrames keyFrames, List keyFrameList, bool isMic = false)
- {
- foreach (var key in keyFrameList)
- {
- keyFrames.Add(key.time, key.value);
- }
- if (!isMic) keyFrames.Add(1.0f, 0.0f);
-
- return keyFrames;
- }
-
- private Animation OnCreatedKeyFrameAnimation(AnimationKeyFrame animationKeyFrame, bool isMic)
- {
- if (animationKeyFrame == null)
- {
- Tizen.Log.Error(LogTag, "animtionKeyFrame is null");
- }
- var lipAnimation = CreateLipAnimation(animationKeyFrame, isMic);
- return lipAnimation;
- }
-
- ///
- /// Occurs when text-to-speech synthesis has finished and the audio data is ready to play.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public event EventHandler TTSReadyFinished
- {
- add
- {
- lock (ttsReadyFinishedLock)
- {
- ttsReadyFinished += value;
- }
-
- }
-
- remove
- {
- lock (ttsReadyFinishedLock)
- {
- if (ttsReadyFinished == null)
- {
- Log.Error(LogTag, "ttsReadyFinished is null");
- return;
- }
- ttsReadyFinished -= value;
- }
- }
- }
-
- ///
- /// Gets the current Text-To-Speech client associated with the Lip Sync Controller.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public TtsClient CurrentTTSClient => ttsController?.TtsHandle;
-
- ///
- /// Initializes the Text-To-Speech system for use with the Lip Sync Controller.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void InitializeTTS()
- {
- if (ttsController == null)
- {
- try
- {
- ttsController = new TTSController();
- //TODO : LipSync Event Connect
- ttsController.PreparedSyncText += OnPreparedSyncText;
- ttsController.StoppedTTS += OnStoppedTTS;
- ttsController.UpdatedBuffer += OnUpdatedBuffer;
- }
- catch (Exception e)
- {
- Log.Error(LogTag, $"error :{e.Message}");
- Log.Error(LogTag, $"{e.StackTrace}");
- }
- }
- }
-
- private void OnUpdatedBuffer(object sender, TTSControllerEventArgs e)
- {
- throw new NotImplementedException();
- if (lipSyncer != null)
- {
- Log.Error(LogTag, "OnTTSBufferChanged");
- /*
- lipSyncer.EnqueueAnimation(recordBuffer, sampleRate, audioLength);
- if (!isAsyncLipStarting)
- {
- lipSyncer.StartAsyncLipPlayTimer();
- isAsyncLipStarting = true;
- }*/
- }
- else
- {
- Log.Error(LogTag, "avatarLipSyncer is null");
- }
- }
-
- private void OnStoppedTTS(object sender, TTSControllerEventArgs e)
- {
- lipSyncer.Stop();
- }
-
- private void OnPreparedSyncText(object sender, TTSControllerEventArgs e)
- {
- var data = e.AudioData;
- var sampleRate = e.SampleRate;
-
- // Play Lipsync Animation by Audio
- lipSyncer.PlayAudio(data, sampleRate);
- }
-
- ///
- /// Deinitializes the Text-To-Speech system for use with the Lip Sync Controller.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void DeInitializeTTS()
- {
- if (ttsController != null)
- {
- try
- {
- ttsController.DeinitTts();
- ttsController = null;
- }
- catch (Exception e)
- {
- Log.Error(LogTag, $"error :{e.Message}");
- Log.Error(LogTag, $"{e.StackTrace}");
- }
- }
- }
-
- ///
- /// Returns a list of supported voices for the Text-To-Speech system.
- ///
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public List GetSupportedVoices()
- {
- return ttsController.GetSupportedVoices();
- }
-
- ///
- /// Plays the given audio with LipSync and returns whether it was successful or not.
- /// If the lip syncer is null, logs an error and returns false.
- /// The lipSyncer plays the audio using the provided byte array and sample rate.
- ///
- /// The audio data to be played in a byte array format.
- /// The sampling rate of the audio data, default value is 24000 Hz.
- public bool PlayLipSync(byte[] audio, int sampleRate = 24000)
- {
- if (lipSyncer == null)
- {
- Log.Error(LogTag, "lipSyncer is null");
- return false;
- }
- lipSyncer.PlayAudio(audio, sampleRate);
-
- return true;
- }
-
- ///
- /// Plays the given audio with LipSync and returns whether it was successful or not.
- /// If the lip syncer is null, logs an error and returns false.
- /// The lipSyncer plays the audio using the provided byte array and sample rate.
- ///
- /// The audio data to be played in a byte array format.
- /// The sampling rate of the audio data, default value is 16000 Hz.
- public bool PlayLipSync(string path, int sampleRate = 24000)
- {
- var audio = Utils.ReadAllBytes(path);
- if (audio == null)
- {
- Log.Error(LogTag, "audio data is null");
- return false;
- }
- lipSyncer.PlayAudio(audio, sampleRate);
-
- return true;
- }
-
- ///
- /// Prepares the Text-To-Speech engine to synthesize speech from the provided text and voice info.
- ///
- /// The text to convert into speech.
- /// The voice info to use for converting the text into speech.
- /// An optional callback that will be invoked when the TTS process is complete.
- /// True if preparation was successful, otherwise False .
- [EditorBrowsable(EditorBrowsableState.Never)]
- public bool PrepareTTS(string text, VoiceInfo voiceInfo, EventHandler ttsReadyFinishedCallback = null)
- {
- if (ttsController == null || ttsController.TtsHandle == null)
- {
- Log.Error(LogTag, "tts is null");
- return false;
- }
-
-
- if (!ttsController.IsSupportedVoice(voiceInfo))
- {
- Log.Info(LogTag, $"{voiceInfo.Language} & {voiceInfo.Type} is not supported");
- return false;
- }
-
- Log.Info(LogTag, "Current TTS State :" + ttsController.TtsHandle.CurrentState);
- if (ttsController.TtsHandle.CurrentState != Tizen.Uix.Tts.State.Ready)
- {
- Log.Info(LogTag, "TTS is not ready");
- return false;
- }
-
- try
- {
- ttsController.AddText(text, voiceInfo);
- ttsController.Prepare(ttsReadyFinishedCallback);
- }
- catch (Exception e)
- {
- Log.Error(LogTag, $"error :{e.Message}");
- Log.Error(LogTag, $"{e.StackTrace}");
- return false;
- }
- return true;
- }
-
- ///
- /// Prepares the Text-To-Speech engine to synthesize speech from the provided text and language code.
- ///
- /// The text to convert into speech.
- /// The two-letter ISO 639-1 language code to use for converting the text into speech (default is Korean - ko_KR).
- /// An optional callback that will be invoked when the TTS process is complete.
- /// True if preparation was successful, otherwise False .
- [EditorBrowsable(EditorBrowsableState.Never)]
- public bool PrepareTTS(string text, string lang = "ko_KR", EventHandler ttsReadyFinishedCallback = null)
- {
- if (ttsController == null || ttsController.TtsHandle == null)
- {
- Log.Error(LogTag, "tts is null");
- return false;
- }
-
- if (!ttsController.IsSupportedVoice(lang))
- {
- Log.Error(LogTag, $"{lang} is not supported");
- return false;
- }
-
- Log.Info(LogTag, "Current TTS State :" + ttsController.TtsHandle.CurrentState);
- if (ttsController.TtsHandle.CurrentState != Tizen.Uix.Tts.State.Ready)
- {
- Log.Error(LogTag, "TTS is not ready");
- return false;
- }
- try
- {
- ttsController.AddText(text, lang);
- ttsController.Prepare(ttsReadyFinishedCallback);
- }
- catch (Exception e)
- {
- Log.Error(LogTag, $"error :{e.Message}");
- Log.Error(LogTag, $"{e.StackTrace}");
- return false;
- }
- return true;
- }
-
- ///
- /// Plays the previously prepared Text-To-Speech audio data.
- ///
- /// True if playback started successfully, otherwise False .
- [EditorBrowsable(EditorBrowsableState.Never)]
- public bool PlayPreparedTTS()
- {
- if (ttsController == null || ttsController.TtsHandle == null)
- {
- Log.Error(LogTag, "tts is null");
- return false;
- }
-
- return ttsController.PlayPreparedText();
- }
-
- ///
- /// Converts the given text into speech using the specified voice info and plays the resulting audio immediately.
- ///
- /// The text to convert into speech.
- /// The voice info to use for converting the text into speech.
- /// True if playback started successfully, otherwise False .
- [EditorBrowsable(EditorBrowsableState.Never)]
- public bool PlayTTS(string text, VoiceInfo voiceInfo)
- {
- if (ttsController == null || ttsController.TtsHandle == null)
- {
- Log.Error(LogTag, "tts is null");
- return false;
- }
-
- if (!ttsController.IsSupportedVoice(voiceInfo))
- {
- Log.Info(LogTag, $"{voiceInfo.Language} & {voiceInfo.Type} is not supported");
- return false;
- }
-
- Log.Info(LogTag, "Current TTS State :" + ttsController.TtsHandle.CurrentState);
- if (ttsController.TtsHandle.CurrentState != Tizen.Uix.Tts.State.Ready)
- {
- Log.Info(LogTag, "TTS is not ready");
- return false;
- }
-
- try
- {
- ttsController.AddText(text, voiceInfo);
- ttsController.Play();
- }
- catch (Exception e)
- {
- Log.Error(LogTag, $"error :{e.Message}");
- Log.Error(LogTag, $"{e.StackTrace}");
- return false;
- }
- return true;
- }
-
- ///
- /// Converts the given text into speech using the specified language code and plays the resulting audio immediately.
- ///
- /// The text to convert into speech.
- /// The two-letter ISO 639-1 language code to use for converting the text into speech (default is Korean - ko_KR).
- /// True if playback started successfully, otherwise False .
- [EditorBrowsable(EditorBrowsableState.Never)]
- public bool PlayTTS(string text, string lang = "ko_KR")
- {
- if (ttsController == null || ttsController.TtsHandle == null)
- {
- Log.Error(LogTag, "tts is null");
- return false;
- }
-
- if (!ttsController.IsSupportedVoice(lang))
- {
- Log.Error(LogTag, $"{lang} is not supported");
- return false;
- }
-
- Log.Info(LogTag, "Current TTS State :" + ttsController.TtsHandle.CurrentState);
- if (ttsController.TtsHandle.CurrentState != Tizen.Uix.Tts.State.Ready)
- {
- Log.Error(LogTag, "TTS is not ready");
- return false;
- }
- try
- {
- ttsController.AddText(text, lang);
- ttsController.Play();
- }
- catch (Exception e)
- {
- Log.Error(LogTag, $"error :{e.Message}");
- Log.Error(LogTag, $"{e.StackTrace}");
- return false;
- }
- return true;
- }
-
- ///
- /// Asynchronously converts the given text into speech using the specified voice info and plays the resulting audio once it's ready.
- ///
- /// The text to convert into speech.
- /// The voice info to use for converting the text into speech.
- /// An optional callback that will be invoked when the TTS process is complete.
- /// True if asynchronous conversion was initiated successfully, otherwise False .
- [EditorBrowsable(EditorBrowsableState.Never)]
- public bool PlayTTSAsync(string text, VoiceInfo voiceInfo, EventHandler ttsReadyFinishedCallback = null)
- {
- if (ttsController == null || ttsController.TtsHandle == null)
- {
- Log.Error(LogTag, "tts is null");
- return false;
- }
-
- if (!ttsController.IsSupportedVoice(voiceInfo))
- {
- Log.Info(LogTag, $"{voiceInfo.Language} & {voiceInfo.Type} is not supported");
- return false;
- }
-
- Log.Info(LogTag, "Current TTS State :" + ttsController.TtsHandle.CurrentState);
- if (ttsController.TtsHandle.CurrentState != Tizen.Uix.Tts.State.Ready)
- {
- Log.Info(LogTag, "TTS is not ready");
- return false;
- }
-
- try
- {
- ttsController.AddText(text, voiceInfo);
- ttsController.PlayAsync(ttsReadyFinishedCallback);
- }
- catch (Exception e)
- {
- Log.Error(LogTag, $"error :{e.Message}");
- Log.Error(LogTag, $"{e.StackTrace}");
- return false;
- }
- return true;
- }
-
- ///
- /// Asynchronously converts the given text into speech using the specified language code and plays the resulting audio once it's ready.
- ///
- /// The text to convert into speech.
- /// The two-letter ISO 639-1 language code to use for converting the text into speech (default is Korean - ko_KR).
- /// An optional callback that will be invoked when the TTS process is complete.
- /// True if asynchronous conversion was initiated successfully, otherwise False .
- [EditorBrowsable(EditorBrowsableState.Never)]
- public bool PlayTTS(string text, string lang = "ko_KR", EventHandler ttsReadyFinishedCallback = null)
- {
- if (ttsController == null || ttsController.TtsHandle == null)
- {
- Log.Error(LogTag, "tts is null");
- return false;
- }
-
- if (!ttsController.IsSupportedVoice(lang))
- {
- Log.Error(LogTag, $"{lang} is not supported");
- return false;
- }
-
- Log.Info(LogTag, "Current TTS State :" + ttsController.TtsHandle.CurrentState);
- if (ttsController.TtsHandle.CurrentState != Tizen.Uix.Tts.State.Ready)
- {
- Log.Error(LogTag, "TTS is not ready");
- return false;
- }
- try
- {
- ttsController.AddText(text, lang);
- ttsController.PlayAsync(ttsReadyFinishedCallback);
- }
- catch (Exception e)
- {
- Log.Error(LogTag, $"error :{e.Message}");
- Log.Error(LogTag, $"{e.StackTrace}");
- return false;
- }
- return true;
- }
-
- ///
- /// Pauses the currently playing Text-To-Speech audio.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void PauseTTS()
- {
- if (ttsController == null || ttsController.TtsHandle == null)
- {
- Log.Error(LogTag, "tts is null");
- return;
- }
-
- try
- {
- Log.Info(LogTag, "Current TTS State :" + ttsController.TtsHandle.CurrentState);
- ttsController?.Pause();
- }
- catch (Exception e)
- {
- Log.Error(LogTag, $"error :{e.Message}");
- Log.Error(LogTag, $"{e.StackTrace}");
- }
- }
-
- ///
- /// Stops the currently playing Text-To-Speech audio.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void StopTTS()
- {
- if (ttsController == null || ttsController.TtsHandle == null)
- {
- Log.Error(LogTag, "tts is null");
- return;
- }
-
- try
- {
- Log.Info(LogTag, "Current TTS State :" + ttsController.TtsHandle.CurrentState);
- ttsController?.Stop();
- }
- catch (Exception e)
- {
- Log.Error(LogTag, $"error :{e.Message}");
- Log.Error(LogTag, $"{e.StackTrace}");
- }
- }
-
- public void InitializeMic()
- {
- if (audioRecorder == null)
- {
- Tizen.Log.Error(LogTag, "audio record is null");
- return;
- }
- audioRecorder.InitializeMic(lipSyncer, 160);
- }
-
- public void DeinitializeMic()
- {
- if (audioRecorder == null)
- {
- Tizen.Log.Error(LogTag, "audio record is null");
- return;
- }
- audioRecorder.StartRecording();
- }
-
- public void StartMic()
- {
- if (audioRecorder == null)
- {
- Tizen.Log.Error(LogTag, "audio record is null");
- return;
- }
- audioRecorder.StartRecording();
- }
-
- public void StopMic()
- {
- if (audioRecorder == null)
- {
- Tizen.Log.Error(LogTag, "audio record is null");
- return;
- }
- audioRecorder.StopRecording();
- }
-
- public void PauseMic()
- {
- if (audioRecorder == null)
- {
- Tizen.Log.Error(LogTag, "audio record is null");
- return;
- }
- audioRecorder.PauseRecording();
- }
-
- public void ResumeMic()
- {
- if(audioRecorder == null)
- {
- Tizen.Log.Error(LogTag, "audio record is null");
- return;
- }
- audioRecorder.ResumeRecording();
- }
-
- ///
- /// Releases unmanaged resources used by this instance.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void Dispose()
- {
- if (ttsController != null)
- {
- ttsController.Dispose();
- ttsController = null;
- }
-
- if (audioRecorder != null)
- {
- audioRecorder.Dispose();
- audioRecorder = null;
- }
- }
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Lipsync/LipSyncer.cs b/src/Tizen.AIAvatar/src/Lipsync/LipSyncer.cs
deleted file mode 100644
index b929ae89aa6..00000000000
--- a/src/Tizen.AIAvatar/src/Lipsync/LipSyncer.cs
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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 Tizen.NUI;
-using Tizen.NUI.Scene3D;
-using static Tizen.AIAvatar.AIAvatar;
-
-namespace Tizen.AIAvatar
-{
- internal class LipSyncer
- {
- private Animation lipAnimation = null;
-
- private VowelConverter vowelConverter;
- private AudioPlayer audioPlayer;
- private event Func TheEvent;
-
- internal Func CreatedKeyFrameAnimation;
-
- //Mic
- private Queue vowelPools = new Queue();
- private string prevVowel = "sil";
-
- internal LipSyncer()
- {
- vowelConverter = new VowelConverter();
- audioPlayer = new AudioPlayer();
- }
-
- internal AudioPlayer AudioPlayer { get { return audioPlayer; } }
-
- public void SetLipAnimation(Animation lipAnimation)
- {
- this.lipAnimation = lipAnimation;
- }
-
- public void PlayAudio(byte[] audio, int sampleRate)
- {
- if (audio != null)
- {
- PlayLipSync(audio, sampleRate);
- }
- }
-
- public void Stop()
- {
- StopLipSync();
- }
-
- public void Pause()
- {
- PauseLipSync();
- }
-
- public void Destroy()
- {
- DestroyLipAnimation();
- }
-
- //TODO : lipAnimation 자체를 Animator안에서 생성하고 관리될 수 있게 수정.
- protected void ResetLipAnimation(Animation lipAnimation)
- {
- DestroyLipAnimation();
- this.lipAnimation = lipAnimation;
- if (this.lipAnimation != null)
- {
- this.lipAnimation.Finished += OnLipAnimationFinished;
- }
- }
-
- protected void PlayLipAnimation()
- {
- if (lipAnimation == null)
- {
- Log.Error(LogTag, "Current Lip Animation is null");
- }
- lipAnimation?.Play();
- }
-
- protected void OnLipAnimationFinished(object sender, EventArgs e)
- {
- }
-
- private void PlayLipSync(byte[] audio)
- {
- if (audio == null)
- {
- Tizen.Log.Error(LogTag, "audi data is null");
- return;
- }
- DestroyLipAnimation();
-
- var lipKeyframes = CreateKeyFrame(audio, CurrentAudioOptions.SampleRate);
- if (lipKeyframes != null)
- {
- var lipAnimation = CreatedKeyFrameAnimation?.Invoke(lipKeyframes, false);
-
- if (lipAnimation != null)
- {
- ResetLipAnimation(lipAnimation);
- PlayLipAnimation();
- audioPlayer.Play(audio);
- }
- else
- {
- Tizen.Log.Error(LogTag, "lipAnimation is null");
- }
- }
- else
- {
- Tizen.Log.Error(LogTag, "lipKeyframes is null");
- }
- }
-
- private void PlayLipSync(byte[] audio, int sampleRate)
- {
- DestroyLipAnimation();
- var lipKeyFrames = CreateKeyFrame(audio, sampleRate);
- if (lipKeyFrames != null)
- {
- var lipAnimation = CreatedKeyFrameAnimation?.Invoke(lipKeyFrames, false);
- if (lipAnimation != null)
- {
- ResetLipAnimation(lipAnimation);
- PlayLipAnimation();
- }
- audioPlayer.Play(audio, sampleRate);
- }
- }
-
- private void PlayLipSync(string path)
- {
- var bytes = Utils.ReadAllBytes(path);
- if (bytes != null)
- {
- PlayLipSync(bytes);
- }
- else
- {
- Log.Error(LogTag, "Failed to load audio file");
- }
- }
-
- private void PauseLipSync()
- {
- PauseLipAnimation();
- audioPlayer.Pause();
- }
-
- private void StopLipSync()
- {
- if (lipAnimation != null)
- {
- DestroyLipAnimation();
-
- var lipAnimation = ResetLipAnimation();
- if (lipAnimation != null)
- {
- ResetLipAnimation(lipAnimation);
- PlayLipAnimation();
- }
- else
- {
- Log.Error(LogTag, "Current Lip Animation is null");
- }
- }
- else
- {
- Log.Error(LogTag, "Current Lip Animation is null");
- }
- audioPlayer.Stop();
- }
-
- private void PauseLipAnimation()
- {
- if (lipAnimation != null)
- {
- lipAnimation?.Pause();
- }
- else
- {
- Log.Error(LogTag, "Current Lip Animation is null");
- }
- }
-
- private void DestroyLipAnimation()
- {
- if (lipAnimation != null)
- {
- lipAnimation.Stop();
- lipAnimation.Dispose();
- lipAnimation = null;
- }
- }
-
- private void EnqueueVowels(byte[] buffer, bool deleteLast = false)
- {
- var vowels = PredictVowels(buffer);
- if (vowels != null)
- {
- vowelPools.Enqueue(vowels);
- if (deleteLast)
- {
- var vowelList = new List(vowels);
- vowelList.RemoveAt(vowelList.Count - 1);
- vowels = vowelList.ToArray();
- }
- }
- }
-
- private Animation CreateLipAnimationByVowelsQueue(int sampleRate = 0)
- {
- if (sampleRate == 0)
- {
- sampleRate = CurrentAudioOptions.SampleRate;
- }
- if (vowelPools.Count > 0)
- {
- AttachPreviousVowel(vowelPools.Dequeue(), out var newVowels);
- Log.Info(LogTag, $"vowelRecognition: {String.Join(", ", newVowels)}");
-
- var lipKeyFrames = vowelConverter?.CreateKeyFrames(newVowels, sampleRate, true);
- var lipAnimation = CreatedKeyFrameAnimation?.Invoke(lipKeyFrames, true);
-
- return lipAnimation;
- }
- return null;
- }
-
- private Animation ResetLipAnimation()
- {
- vowelPools.Clear();
- var newVowels = new string[1];
- newVowels[0] = prevVowel = "sil";
- vowelPools.Enqueue(newVowels);
- return CreateLipAnimationByVowelsQueue();
- }
-
- private string[] PredictVowels(byte[] audioData)
- {
- string[] vowels = vowelConverter?.PredictVowels(audioData);
- return vowels;
- }
-
- private void AttachPreviousVowel(in string[] vowels, out string[] newVowels)
- {
- newVowels = new string[vowels.Length + 1];
- newVowels[0] = prevVowel;
- Array.Copy(vowels, 0, newVowels, 1, vowels.Length);
- prevVowel = vowels[vowels.Length - 1];
- }
-
- private AnimationKeyFrame CreateKeyFrame(byte[] audio, int sampleRate)
- {
- var keyFrames = vowelConverter?.CreateKeyFrames(audio, sampleRate);
- if (keyFrames == null)
- {
- Log.Error(LogTag, $"Failed to initialize KeyFrames");
- }
-
- return keyFrames;
- }
-
-
- internal void OnRecordBufferChanged(byte[] recordBuffer, int sampleRate)
- {
- EnqueueVowels(recordBuffer);
- }
-
- internal void OnRecodingTick()
- {
- var lipAnimation = CreateLipAnimationByVowelsQueue();
- if (lipAnimation != null)
- {
- ResetLipAnimation(lipAnimation);
- PlayLipAnimation();
- }
- else
- {
- Log.Error(LogTag, "Current Lip Animation is null");
- }
- }
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Vowel2Animation/AnimationConverter.cs b/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Vowel2Animation/AnimationConverter.cs
deleted file mode 100644
index 2e2c6a4c06b..00000000000
--- a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Vowel2Animation/AnimationConverter.cs
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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 Newtonsoft.Json;
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace Tizen.AIAvatar
-{
- internal enum Viseme
- {
- sil, AE, Ah, B_M_P, Ch_J, EE, Er, IH, Oh, W_OO, S_Z, F_V, TH,
- T_L_D_N, K_G_H_NG, R
- };
-
- internal class AnimationConverter
- {
- private Dictionary VowelToViseme
- = new Dictionary {
- { "A", "Ah" },
- { "E", "AE" },
- { "I", "EE" },
- { "O", "R" },
- { "ER", "R" },
- { "U", "W_OO" },
- { "HM", "B_M_P" },
- { "sil", "sil" }
- };
-
- private AnimationKeyFrame _animationKeyFrame;
- private BlendShapeInfo _visemBlendShapeInfo;
- private Dictionary _visemeMap;
-
- private bool _isInitialized;
-
- public AnimationConverter() { }
-
- public void InitializeVisemeInfo(string info_path)
- {
- try
- {
- StreamReader v = new StreamReader(info_path);
- var jsonString = v.ReadToEnd();
-
- _visemBlendShapeInfo = JsonConvert.DeserializeObject(jsonString);
- _visemeMap = _visemBlendShapeInfo.GetVisemeMap();
-
- var nodeNames = _visemBlendShapeInfo.GetNodeNames();
- var blendShapeCounts = _visemBlendShapeInfo.GetBlendShapeCounts();
- var blendShapeKeyFormat = _visemBlendShapeInfo.blendShape.keyFormat;
-
- _animationKeyFrame = new AnimationKeyFrame(nodeNames,
- blendShapeCounts,
- blendShapeKeyFormat);
- _isInitialized = true;
- v.Dispose();
- }
- catch (Exception)
- {
- throw new FailedPersonalizeException(info_path);
- }
- }
-
- public AnimationKeyFrame ConvertVowelsToAnimation(string[] vowels,
- float stepDuration)
- {
- if (!_isInitialized) throw new NotInitializedException();
-
- _animationKeyFrame.ClearAnimation();
- ConvertVowelsToVisemes(vowels, out var visemes);
- ConvertVisemesToAnimationKeyFrame(visemes, stepDuration);
-
- return _animationKeyFrame;
- }
-
- public AnimationKeyFrame ConvertVowelsToAnimationMic(string[] vowels,
- float stepDuration)
- {
- if (!_isInitialized) throw new NotInitializedException();
-
- _animationKeyFrame.ClearAnimation();
- ConvertVowelsToVisemes(vowels, out var visemes);
- ConvertVisemesToAnimationKeyFrameMic(visemes, stepDuration);
-
- return _animationKeyFrame;
- }
-
- private void ConvertVowelsToVisemes(in string[] vowels,
- out string[] visemes)
- {
- visemes = new string[vowels.Length];
-
- for (var i = 0; i < vowels.Length; i++)
- {
- if (!VowelToViseme.TryGetValue(vowels[i], out visemes[i]))
- {
- throw new InvalidVowelTypeException(vowels[i]);
- }
- }
- }
-
- private void ConvertVisemesToAnimationKeyFrame(in string[] visemes,
- float stepDuration)
- {
- float animationTime = visemes.Length * stepDuration;
- _animationKeyFrame.SetAnimationTime(animationTime);
-
- for (int i = 0; i < visemes.Length; i++)
- {
- float timeStamp = GetTimeStamp(i, stepDuration) / animationTime;
-
- foreach (var info in _visemeMap[visemes[i]])
- {
- _animationKeyFrame.AddKeyFrame(info.nodeName,
- info.blendIndex,
- timeStamp,
- info.blendValue);
- }
- }
- }
-
- private void ConvertVisemesToAnimationKeyFrameMic(in string[] visemes,
- float stepDuration)
- {
- float animationTime = (visemes.Length - 1) * stepDuration;
- _animationKeyFrame.SetAnimationTime(animationTime);
-
- for (int i = 0; i < visemes.Length; i++)
- {
- float timeStamp = GetTimeStamp(i, stepDuration) / animationTime;
-
- foreach (var info in _visemeMap[visemes[i]])
- {
- _animationKeyFrame.AddKeyFrame(info.nodeName,
- info.blendIndex,
- timeStamp,
- info.blendValue);
- }
- }
- }
-
- private float GetTimeStamp(int idx, float stepDuration)
- {
- if (idx > 0)
- return (idx * stepDuration) - (stepDuration / 2.0f);
- else
- return 0;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Vowel2Animation/DTO/AnimationKeyFrame.cs b/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Vowel2Animation/DTO/AnimationKeyFrame.cs
deleted file mode 100644
index 04f2a6ea832..00000000000
--- a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Vowel2Animation/DTO/AnimationKeyFrame.cs
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.Collections.Generic;
-
-namespace Tizen.AIAvatar
-{
- internal class KeyFrame
- {
- public KeyFrame(float t, float v)
- {
- time = t;
- value = v;
- }
- public float time;
- public float value;
- }
-
- internal class AnimationKeyFrame
- {
- private Dictionary[]> _animationKeyFrames;
-
- public string[] NodeNames { get; private set; }
- public int[] BlendShapeCounts { get; private set; }
- public string BlendShapeKeyFormat { get; private set; }
- public float AnimationTime { get; private set; }
-
- public AnimationKeyFrame(string[] nodeNames,
- int[] blendShapeCounts,
- string blendShapeKeyFormat)
- {
- _animationKeyFrames = new Dictionary[]>();
- NodeNames = nodeNames;
- BlendShapeCounts = blendShapeCounts;
- BlendShapeKeyFormat = blendShapeKeyFormat;
-
- InitializeAnimation(nodeNames, blendShapeCounts);
- }
-
- public void SetAnimationTime(float animationTime)
- {
- AnimationTime = animationTime;
- }
-
- public List GetKeyFrames(string nodeName, int blendIndex)
- {
- return _animationKeyFrames[nodeName][blendIndex];
- }
-
- public void AddKeyFrame(string nodeName, int blendIndex, float time, float value)
- {
- _animationKeyFrames[nodeName][blendIndex].Add(new KeyFrame(time, value));
- }
-
- public void AddKeyFrame(string nodeName, int blendIndex, KeyFrame value)
- {
- _animationKeyFrames[nodeName][blendIndex].Add(value);
- }
-
- public void InitializeAnimation(string[] nodeNames, int[] blendShapeCounts)
- {
- ClearAnimation();
-
- for (int i = 0; i < nodeNames.Length; i++)
- {
- var nodeName = nodeNames[i];
- var blendShapeCount = blendShapeCounts[i];
-
- _animationKeyFrames.Add(nodeName, new List[blendShapeCount]);
-
- for (int j = 0; j < blendShapeCount; j++)
- {
- _animationKeyFrames[nodeName][j] = new List();
- }
- }
- }
-
- public void ClearAnimation()
- {
- foreach (KeyValuePair[]> kvp in _animationKeyFrames)
- {
- foreach (List kf in kvp.Value)
- {
- kf.Clear();
- }
- }
- }
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Vowel2Animation/DTO/BlendShapeInfo.cs b/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Vowel2Animation/DTO/BlendShapeInfo.cs
deleted file mode 100644
index 8fb8163b146..00000000000
--- a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Vowel2Animation/DTO/BlendShapeInfo.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.Collections.Generic;
-
-namespace Tizen.AIAvatar
-{
- internal class BlendShapeInfo
- {
- public BlendShapeModelInfo blendShape;
- public BlendShapeVisemeInfo[] visemes;
-
- public BlendShapeInfo() { }
-
- public string[] GetNodeNames()
- {
- return blendShape.nodeNames;
- }
-
- public int[] GetBlendShapeCounts()
- {
- return blendShape.blendShapeCount;
- }
-
- public Dictionary GetVisemeMap()
- {
- Dictionary visemeMap
- = new Dictionary();
-
- foreach (var visemeInfo in this.visemes)
- {
- visemeMap.Add(visemeInfo.name, visemeInfo.values);
- }
-
- return visemeMap;
- }
- }
-
- internal class BlendShapeModelInfo
- {
- public string keyFormat;
- public string[] nodeNames;
- public int[] blendShapeCount;
-
- public BlendShapeModelInfo(string keyFormat, string[] nodeNames, int[] blendShapeCount)
- {
- this.keyFormat = keyFormat;
- this.nodeNames = nodeNames;
- this.blendShapeCount = blendShapeCount;
- }
- }
-
- internal class BlendShapeVisemeInfo
- {
- public string name;
- public BlendShapeValue[] values;
- }
-
- internal class BlendShapeValue
- {
- public string nodeName;
- public int blendIndex;
- public float blendValue;
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Vowel2Animation/Exception.cs b/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Vowel2Animation/Exception.cs
deleted file mode 100644
index da7b089053b..00000000000
--- a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/Vowel2Animation/Exception.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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;
-
-namespace Tizen.AIAvatar
-{
- internal class InvalidVowelTypeException : Exception
- {
- public InvalidVowelTypeException() { }
-
- public InvalidVowelTypeException(string name)
- : base($"Not supported vowel type, {name}") { }
- }
-
- internal class FailedPersonalizeException : Exception
- {
- public FailedPersonalizeException() { }
-
- public FailedPersonalizeException(string name)
- : base($"Failed to personalize, file_path : {name}") { }
- }
-
- internal class NotInitializedException : Exception
- {
- public NotInitializedException()
- : base($"Animation Converter should be initialized with viseme_info") { }
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/VowelConverter.cs b/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/VowelConverter.cs
deleted file mode 100644
index c18bded2efb..00000000000
--- a/src/Tizen.AIAvatar/src/Lipsync/VowelConverter/VowelConverter.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.Collections.Generic;
-
-using static Tizen.AIAvatar.AIAvatar;
-
-namespace Tizen.AIAvatar
-{
- internal class VowelConverter
- {
- private TFVowel6 vowelClassfierModel = null;
- private VowelClassifier vowelClassifier = null;
- private Dictionary vowelClassifiers;
- private AnimationConverter animationConverter = new AnimationConverter();
-
- internal VowelConverter()
- {
- vowelClassifiers = new Dictionary();
- vowelClassifier = GetVowelClassifier(CurrentAudioOptions.SampleRate);
- vowelClassfierModel = new TFVowel6(new int[4] { 12, 1, 1, 1 }, new int[4] { 7, 1, 1, 1 });
- animationConverter.InitializeVisemeInfo(VisemeInfo);
- }
-
- internal AnimationKeyFrame CreateKeyFrames(string[] vowels, int sampleRate, bool isMic = false)
- {
- vowelClassifier = GetVowelClassifier(sampleRate);
-
- if (vowelClassifier != null)
- {
- if (isMic)
- {
- return animationConverter.ConvertVowelsToAnimationMic(vowels, vowelClassifier.GetStepTime());
- }
- else
- {
- return animationConverter.ConvertVowelsToAnimation(vowels, vowelClassifier.GetStepTime());
- }
- }
- else
- {
- return null;
- }
- }
-
- internal AnimationKeyFrame CreateKeyFrames(byte[] audioData, int sampleRate, bool isMic = false)
- {
- vowelClassifier = GetVowelClassifier(sampleRate);
-
- if (vowelClassifier == null)
- {
- Log.Error(LogTag, "Failed to play Buffer");
- return null;
- }
- var vowels = PredictVowels(audioData);
- Log.Info(LogTag, $"vowelRecognition: {string.Join(", ", vowels)}");
-
- if (isMic) return animationConverter.ConvertVowelsToAnimationMic(vowels, vowelClassifier.GetStepTime());
- else return animationConverter.ConvertVowelsToAnimation(vowels, vowelClassifier.GetStepTime());
- }
-
- internal string[] PredictVowels(byte[] audioData)
- {
- var vowels = vowelClassifier.Inference(audioData, vowelClassfierModel);
- return vowels;
- }
-
- internal VowelClassifier GetVowelClassifier(int sampleRate)
- {
- if (vowelClassifiers.ContainsKey(sampleRate))
- {
- return vowelClassifiers[sampleRate];
- }
- else
- {
- vowelClassifiers[sampleRate] = new VowelClassifier(sampleRate);
- return vowelClassifiers[sampleRate];
- }
- }
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Multimedia/Audio/AudioPlayer.cs b/src/Tizen.AIAvatar/src/Multimedia/Audio/AudioPlayer.cs
index bd04ec08502..8b13be57562 100644
--- a/src/Tizen.AIAvatar/src/Multimedia/Audio/AudioPlayer.cs
+++ b/src/Tizen.AIAvatar/src/Multimedia/Audio/AudioPlayer.cs
@@ -15,124 +15,460 @@
*
*/
-using Tizen.Multimedia;
-using System.IO;
+
using System;
+using System.IO;
+using System.Collections.Generic;
+using Tizen.NUI;
+using Tizen.Multimedia;
using static Tizen.AIAvatar.AIAvatar;
+using System.ComponentModel;
namespace Tizen.AIAvatar
{
- internal class AudioPlayer : IDisposable
+ ///
+ /// Represents an audio player capable of streaming and playing audio with support for audio ducking.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class AudioPlayer : IDisposable
{
- private AudioPlayback audioPlayback;
+
+ private int accLength = 0;
+ private int streamIndex = 0;
+
+ private bool isStreaming = false;
+
private MemoryStream audioStream;
+ private MemoryStream baseAudioStream;
+ private List streamList;
+
+ private AudioDucking audioDucking;
+ private AudioPlayback audioPlayback;
+ private AudioStreamPolicy audioStreamPolicy;
- internal AudioPlayer()
+ private Timer bufferChecker;
+
+ private AudioPlayerState currentAudioPlayerState = AudioPlayerState.Unavailable;
+ internal event EventHandler AudioPlayerStateChanged;
+
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Optional audio options for playback configuration.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AudioPlayer(AudioOptions audioOptions = null)
{
+
+ if (audioOptions == null)
+ CurrentAudioOptions = DefaultAudioOptions;
+ else
+ CurrentAudioOptions = audioOptions;
+
+
+ baseAudioStream = new MemoryStream();
+ streamList = new List();
+
+ audioStreamPolicy = new AudioStreamPolicy(CurrentAudioOptions.StreamType);
+ InitAudio(CurrentAudioOptions.SampleRate);
+
+ bufferChecker = new Timer(100);
+ bufferChecker.Tick += OnBufferChecker;
+
+ audioDucking = new AudioDucking(CurrentAudioOptions.DuckingTargetStreamType);
+ audioDucking.DuckingStateChanged += (sender, arg) =>
+ {
+ if (arg.IsDucked)
+ {
+ CurrentAudioPlayerState = AudioPlayerState.Playing;
+ }
+ };
+
+ AudioPlayerStateChanged += OnStateChanged;
+ }
+
+ ///
+ /// Adds a new audio buffer to the stream list.
+ ///
+ /// The audio buffer to add.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void AddStream(byte[] buffer)
+ {
+ streamList.Add(new MemoryStream(buffer));
}
- internal void PlayAsync(byte[] buffer, int sampleRate = 0)
+ ///
+ /// Determines if the audio player is prepared with a valid stream.
+ ///
+ /// True if a valid stream is available; otherwise, false.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsPrepare()
{
- if (audioPlayback == null)
+ return streamList.Count > 0;
+ }
+
+ ///
+ /// Prepare the audio from the stream asynchronously.
+ ///
+ /// Optional sample rate for the audio playback.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void PrepareStreamAudio(int sampleRate = 0)
+ {
+ InitializeStream();
+
+ if (audioPlayback == null || audioPlayback.SampleRate != sampleRate)
{
- Play(buffer, sampleRate);
+ InitAudio(sampleRate);
}
- else
+
+
+ try
+ {
+ audioDucking.Activate(AIAvatar.CurrentAudioOptions.DuckingDuration, AIAvatar.CurrentAudioOptions.DuckingRatio);
+ }
+ catch (Exception e)
{
- audioPlayback.Write(buffer);
+ Log.Error(LogTag, $"Failed to PlayAsync AudioPlayback. {e.Message}");
+ CurrentAudioPlayerState = AudioPlayerState.Playing;
}
}
- internal void Play(byte[] audioBytes, int sampleRate = 0)
+ ///
+ /// Plays the provided audio buffer.
+ ///
+ /// The audio buffer to play.
+ /// Optional sample rate for the audio playback.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Play(byte[] audioBytes, int sampleRate = 0)
{
+ isStreaming = false;
+
if (audioBytes == null)
{
+ Log.Error(LogTag, $"Play AudioPlayBack null.");
return;
}
+ if (audioPlayback.SampleRate != sampleRate)
+ {
+ InitAudio(sampleRate);
+ }
+
+ InitializeStream();
+ streamList.Add(new MemoryStream(audioBytes));
+
try
{
+ audioDucking.Activate(CurrentAudioOptions.DuckingDuration, CurrentAudioOptions.DuckingRatio);
+ }
+ catch (Exception e)
+ {
+ Log.Error(LogTag, $"Failed to Play AudioPlayback. {e.Message}");
+ CurrentAudioPlayerState = AudioPlayerState.Playing;
+ }
+ }
+
+ ///
+ /// Pauses the current audio playback.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Pause()
+ {
+ CurrentAudioPlayerState = AudioPlayerState.Paused;
+
+ }
+
+ ///
+ /// Stops the current audio playback.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Stop()
+ {
+ CurrentAudioPlayerState = AudioPlayerState.Stopped;
+ }
+
+ ///
+ /// Destroys the audio player and clears all resources.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Destroy()
+ {
+ DestroyAudioPlayback();
+ streamList.Clear();
+ streamList = null;
+ }
+
+ ///
+ /// Releases all resources used by the AudioPlayer.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Releases all resources used by the AudioPlayer.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (bufferChecker != null)
+ {
+ bufferChecker.Stop();
+ bufferChecker.Dispose();
+ bufferChecker = null;
+ }
+
+ if (audioDucking != null)
+ {
+ audioDucking.DuckingStateChanged -= (sender, arg) =>
+ {
+ if (arg.IsDucked)
+ {
+ CurrentAudioPlayerState = AudioPlayerState.Playing;
+ }
+ };
+ audioDucking.Dispose();
+ audioDucking = null;
+ }
+
if (audioPlayback != null)
{
- Destroy();
+ Stop();
+ audioPlayback.BufferAvailable -= OnBufferAvailable;
+ audioPlayback.Dispose();
+ audioPlayback = null;
}
- if (sampleRate == 0)
+
+ if (audioStreamPolicy != null)
{
- sampleRate = CurrentAudioOptions.SampleRate;
+ audioStreamPolicy.Dispose();
+ audioStreamPolicy = null;
+ }
+
+ if (baseAudioStream != null)
+ {
+ baseAudioStream.Dispose();
+ baseAudioStream = null;
+ }
+
+ if (streamList != null)
+ {
+ foreach (var stream in streamList)
+ {
+ stream.Dispose();
+ }
+ streamList.Clear();
+ streamList = null;
}
- audioPlayback = new AudioPlayback(sampleRate, CurrentAudioOptions.Channel, CurrentAudioOptions.SampleType);
}
- catch (Exception e)
+ }
+
+ ///
+ /// Gets or sets the current state of the audio player.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AudioPlayerState CurrentAudioPlayerState
+ {
+ get => currentAudioPlayerState;
+ protected set
{
- Log.Error(LogTag, $"Failed to create AudioPlayback. {e.Message}");
+ if (currentAudioPlayerState == value) return;
+
+ var preState = currentAudioPlayerState;
+ currentAudioPlayerState = value;
+
+ AudioPlayerStateChanged?.Invoke(this, new AudioPlayerChangedEventArgs(preState, currentAudioPlayerState));
}
+ }
- if (audioPlayback != null)
+
+ private bool OnBufferChecker(object source, Timer.TickEventArgs e)
+ {
+ if (isStreaming && streamList.Count == 0)
+ {
+ return true;
+ }
+
+ if (audioStream != null && audioStream.Position == audioStream.Length)
{
- audioPlayback.Prepare();
- audioPlayback.BufferAvailable += (sender, args) =>
+ if (streamIndex >= streamList.Count)
{
- if (audioStream.Position == audioStream.Length)
+ CurrentAudioPlayerState = AudioPlayerState.Finished;
+ Log.Debug(LogTag, $"Complete Play Audio Buffer.");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void OnStateChanged(object sender, AudioPlayerChangedEventArgs state)
+ {
+
+ switch (state.Current)
+ {
+ case AudioPlayerState.Playing:
+ try
{
- return;
+ bufferChecker?.Start();
+ audioPlayback?.Prepare();
+ Log.Debug(LogTag, "Audio is playing.");
}
+ catch (NullReferenceException e)
+ {
+ Log.Error(LogTag, $"NullReferenceException in Playing state: {e.Message}");
+ }
+ catch (Exception e)
+ {
+ Log.Info(LogTag, $"PlayinnState: {e.Message}");
+ }
+
+ break;
+ case AudioPlayerState.Paused:
try
{
- var buffer = new byte[args.Length];
- audioStream.Read(buffer, 0, args.Length);
- audioPlayback.Write(buffer);
+ bufferChecker?.Stop();
+ audioPlayback?.Pause();
+ audioPlayback?.Unprepare();
+
+ if (audioDucking.IsDucked)
+ audioDucking?.Deactivate();
+
+ Log.Debug(LogTag, "Audio is paused.");
+ }
+ catch (NullReferenceException e)
+ {
+ Log.Error(LogTag, $"NullReferenceException in Paused state: {e.Message}");
}
catch (Exception e)
{
- Log.Error(LogTag, $"Failed to write. {e.Message}");
+ Log.Info(LogTag, $"PlayinnState: {e.Message}");
}
- };
+ break;
- audioStream = new MemoryStream(audioBytes);
+ case AudioPlayerState.Stopped:
+ case AudioPlayerState.Finished:
+
+ try
+ {
+ bufferChecker?.Stop();
+ audioPlayback?.Pause();
+ audioPlayback?.Unprepare();
+
+ if (audioDucking.IsDucked)
+ audioDucking?.Deactivate();
+
+ streamIndex = 0;
+ audioStream = baseAudioStream;
+
+ Log.Debug(LogTag, "Audio is stopped.");
+ }
+ catch (NullReferenceException e)
+ {
+ Log.Error(LogTag, $"NullReferenceException in Stopped/Finished state: {e.Message}");
+ }
+ catch (Exception e)
+ {
+ Log.Info(LogTag, $"PlayinnState: {e.Message}");
+ }
+
+ break;
}
+
}
- internal void Pause()
+ private void OnBufferAvailable(object sender, AudioPlaybackBufferAvailableEventArgs args)
{
- if (audioPlayback != null)
+ if (audioStream.Position == audioStream.Length)
{
- audioPlayback.Pause();
+
+ if (streamIndex < streamList.Count)
+ {
+ audioStream = streamList[streamIndex];
+ accLength = (int)audioStream.Length;
+
+ streamIndex++;
+ }
+ else
+ {
+ return;
+ }
}
- else
+
+ try
+ {
+ if (args.Length > 1024)
+ {
+ accLength -= args.Length;
+ int length = args.Length;
+ if (accLength < 0)
+ {
+ length += accLength;
+ }
+
+ var buffer = new byte[length];
+ audioStream.Read(buffer, 0, length);
+ audioPlayback.Write(buffer);
+ }
+ }
+ catch (Exception e)
{
- Log.Error(LogTag, $"audioPlayBack is null");
+ Log.Error(LogTag, $"Failed to write. {e.Message}");
}
}
- internal void Stop()
+ private void InitializeStream()
+ {
+ isStreaming = true;
+ streamIndex = 0;
+ streamList.Clear();
+ }
+
+ private void InitAudio(int sampleRate)
{
if (audioPlayback != null)
{
- audioPlayback.Pause();
- Destroy();
+ DestroyAudioPlayback();
}
- else
+ if (sampleRate == 0)
{
- Log.Error(LogTag, $"audioPlayBack is null");
+ sampleRate = CurrentAudioOptions.SampleRate;
}
- }
- public void Dispose()
- {
- Destroy();
+ try
+ {
+ audioPlayback = new AudioPlayback(sampleRate, CurrentAudioOptions.Channel, CurrentAudioOptions.SampleType);
+ audioPlayback.ApplyStreamPolicy(audioStreamPolicy);
+ audioPlayback.BufferAvailable += OnBufferAvailable;
+
+ audioStream = baseAudioStream;
+ }
+ catch (Exception e)
+ {
+ Log.Error(LogTag, $"Failed to create AudioPlayback. {e.Message}");
+ }
- audioStream?.Flush();
- audioStream?.Dispose();
- audioStream = null;
}
- private void Destroy()
+ private void DestroyAudioPlayback()
{
- audioPlayback?.Unprepare();
- audioPlayback?.Dispose();
+ if (audioPlayback != null)
+ {
+ Stop();
+ audioPlayback.BufferAvailable -= OnBufferAvailable;
+ audioPlayback.Dispose();
+ }
+
audioPlayback = null;
}
+
+
}
}
diff --git a/src/Tizen.AIAvatar/src/Multimedia/Audio/AudioRecorder.cs b/src/Tizen.AIAvatar/src/Multimedia/Audio/AudioRecorder.cs
deleted file mode 100644
index 406ce6d09de..00000000000
--- a/src/Tizen.AIAvatar/src/Multimedia/Audio/AudioRecorder.cs
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * 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.Linq;
-using Tizen.Multimedia;
-using Tizen.NUI;
-using static Tizen.AIAvatar.AIAvatar;
-
-namespace Tizen.AIAvatar
-{
- internal class AudioRecorder : IDisposable
- {
- private const string privilegeForRecording = "http://tizen.org/privilege/recorder";
-
- private AsyncAudioCapture asyncAudioCapture;
-
- private byte[] recordedBuffer;
- private float desiredBufferDuration = 0.16f;
- private int desiredBufferLength;
-
- private Timer audioRecordingTimer;
-
- private Action audioRecdingAction;
- private Action bufferAction;
-
- private static AudioRecorder instance;
-
- internal static AudioRecorder Instance
- {
- get
- {
- if (instance == null)
- {
- instance = new AudioRecorder();
- }
- return instance;
- }
- }
-
- internal event EventHandler BufferChanged;
-
- internal AudioRecorder()
- {
- Utils.CheckPrivilege(privilegeForRecording);
- desiredBufferLength = (int)(CurrentAudioOptions.SampleRate * desiredBufferDuration * 2);
- }
-
- internal void InitializeMic(LipSyncer lipSyncer, uint recordingTime = 160)
- {
- audioRecordingTimer = new Timer(recordingTime);
- if (lipSyncer != null)
- {
- Tizen.Log.Error(LogTag, "LipSyncer of animator is null");
- return;
- }
- this.audioRecdingAction = lipSyncer.OnRecodingTick;
- this.bufferAction = lipSyncer.OnRecordBufferChanged;
-
- BufferChanged += OnRecordBufferChanged;
- audioRecordingTimer.Tick += AudioRecordingTimerTick;
- }
-
-
- internal void DeinitializeMic()
- {
- StopRecording();
- BufferChanged -= OnRecordBufferChanged;
-
- if (audioRecordingTimer != null)
- {
- audioRecordingTimer.Stop();
- audioRecordingTimer.Tick -= AudioRecordingTimerTick;
-
- audioRecordingTimer.Dispose();
- audioRecordingTimer = null;
- }
- audioRecdingAction = null;
- }
-
- internal void StartRecording()
- {
- audioRecordingTimer?.Start();
- asyncAudioCapture = new AsyncAudioCapture(CurrentAudioOptions.SampleRate, CurrentAudioOptions.Channel, CurrentAudioOptions.SampleType);
-
- recordedBuffer = new byte[0];
- asyncAudioCapture.DataAvailable += (s, e) =>
- {
- recordedBuffer = recordedBuffer.Concat(e.Data).ToArray();
- if (recordedBuffer.Length >= desiredBufferLength)
- {
- var recordedBuffer = this.recordedBuffer;
- this.recordedBuffer = Array.Empty();
-
- BufferChanged?.Invoke(this, new RecordBufferChangedEventArgs(recordedBuffer, CurrentAudioOptions.SampleRate));
- }
- };
- asyncAudioCapture.Prepare();
- Log.Info(LogTag, "Start Recording - Preapre AsyncAudioCapture");
- }
-
- internal void StopRecording()
- {
- audioRecordingTimer?.Stop();
- asyncAudioCapture.Dispose();
- }
-
- internal void PauseRecording()
- {
- asyncAudioCapture.Pause();
- }
-
- internal void ResumeRecording()
- {
- asyncAudioCapture.Resume();
- }
-
- private void OnRecordBufferChanged(object sender, RecordBufferChangedEventArgs e)
- {
- bufferAction?.Invoke(e.RecordedBuffer, CurrentAudioOptions.SampleRate);
- }
-
- private bool AudioRecordingTimerTick(object source, Timer.TickEventArgs e)
- {
- Log.Info(LogTag, "TickTimer");
- audioRecdingAction?.Invoke();
- return true;
- }
-
- public void Dispose()
- {
- throw new NotImplementedException();
- }
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Multimedia/Audio/Core/AudioOptions.cs b/src/Tizen.AIAvatar/src/Multimedia/Audio/Core/AudioOptions.cs
new file mode 100644
index 00000000000..63b792db137
--- /dev/null
+++ b/src/Tizen.AIAvatar/src/Multimedia/Audio/Core/AudioOptions.cs
@@ -0,0 +1,123 @@
+/*
+ * 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.ComponentModel;
+using Tizen.Multimedia;
+
+namespace Tizen.AIAvatar
+{
+ ///
+ /// Provides the ability to audio
+ ///
+ // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class AudioOptions
+ {
+ private int sampleRate;
+ private AudioChannel channel;
+ private AudioSampleType sampleType;
+ private AudioStreamType streamType;
+
+ private double duckingRatio;
+ private uint duckingDuration;
+ private AudioStreamType duckingTargetStreamType;
+
+
+ ///
+ /// Initializes a new instance of the AudioOptions class with the specified sample rate, channel, sampleType and streamType.
+ ///
+ /// the audio sample rate (8000 ~ 192000Hz)
+ /// the audio channel type.
+ /// the audio sample type.
+ /// the audio Stream type.
+ // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AudioOptions(int sampleRate, AudioChannel channel, AudioSampleType sampleType, AudioStreamType streamType)
+ {
+ this.sampleRate = sampleRate;
+ this.channel = channel;
+ this.sampleType = sampleType;
+ this.streamType = streamType;
+
+ DuckingOptions(AudioStreamType.Media, 500, 0.2);
+ }
+
+ ///
+ /// Initialize the DuckingOption values of the AudioOptions class using the specified target StreamType, Duration, and Ratio.
+ ///
+ /// the audio Ducking target Stream type.
+ /// the ducking duration.
+ /// the ducking target volume ratio.
+ // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void DuckingOptions(AudioStreamType duckingTargetStreamType, uint duckingDuration, double duckingRatio)
+ {
+ this.duckingTargetStreamType = duckingTargetStreamType;
+ this.duckingDuration = duckingDuration;
+ this.duckingRatio = duckingRatio;
+ }
+
+ ///
+ /// The audio sample rate (8000 ~ 192000Hz)
+ ///
+ // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int SampleRate { get => sampleRate; set => sampleRate = value; }
+
+ ///
+ /// The audio channel type
+ ///
+ // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AudioChannel Channel { get => channel; set => channel = value; }
+
+ ///
+ /// The audio sample type
+ ///
+ // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AudioSampleType SampleType { get => sampleType; set => sampleType = value; }
+
+ ///
+ /// The audio stream type
+ ///
+ // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AudioStreamType StreamType { get => streamType; set => streamType = value; }
+
+ ///
+ /// The audio ducking duration.
+ ///
+ // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public uint DuckingDuration { get => duckingDuration; set => duckingDuration = value; }
+
+ ///
+ /// The audio ducking ratio.
+ ///
+ // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public double DuckingRatio { get => duckingRatio; set => duckingRatio = value; }
+
+ ///
+ /// The audio ducking target stream type.
+ ///
+ // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AudioStreamType DuckingTargetStreamType { get => duckingTargetStreamType; }
+ }
+}
diff --git a/src/Tizen.AIAvatar/src/Multimedia/Audio/Core/AudioPlayerChangedEventArgs.cs b/src/Tizen.AIAvatar/src/Multimedia/Audio/Core/AudioPlayerChangedEventArgs.cs
new file mode 100644
index 00000000000..145f6cf1a73
--- /dev/null
+++ b/src/Tizen.AIAvatar/src/Multimedia/Audio/Core/AudioPlayerChangedEventArgs.cs
@@ -0,0 +1,63 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.AIAvatar
+{
+ ///
+ /// This class provides arguments for handling Audio State change events.
+ /// The previous state of the audio.
+ /// The current state of the audio.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class AudioPlayerChangedEventArgs : EventArgs
+ {
+ ///
+ /// Initializes a new instance of the AudioChangedEventArgs class with the specified previous and current states.
+ ///
+ /// The previous state of the audio.
+ /// The current state of the audio.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AudioPlayerChangedEventArgs(AudioPlayerState previous, AudioPlayerState current)
+ {
+ Previous = previous;
+ Current = current;
+ }
+
+ ///
+ /// The previous state.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AudioPlayerState Previous
+ {
+ get;
+ internal set;
+ }
+
+ ///
+ /// The current state.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AudioPlayerState Current
+ {
+ get;
+ internal set;
+ }
+ }
+}
diff --git a/src/Tizen.AIAvatar/src/Animations/AvatarMotionState.cs b/src/Tizen.AIAvatar/src/Multimedia/Audio/Core/AudioPlayerState.cs
similarity index 88%
rename from src/Tizen.AIAvatar/src/Animations/AvatarMotionState.cs
rename to src/Tizen.AIAvatar/src/Multimedia/Audio/Core/AudioPlayerState.cs
index aa62b0d4940..b4e37f779e1 100644
--- a/src/Tizen.AIAvatar/src/Animations/AvatarMotionState.cs
+++ b/src/Tizen.AIAvatar/src/Multimedia/Audio/Core/AudioPlayerState.cs
@@ -23,10 +23,10 @@ namespace Tizen.AIAvatar
/// Enumeration for the states.
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public enum AvatarMotionState
+ public enum AudioPlayerState
{
///
- /// Created state.
+ /// Fail state.
///
[EditorBrowsable(EditorBrowsableState.Never)]
Failed = -1,
@@ -55,6 +55,12 @@ public enum AvatarMotionState
[EditorBrowsable(EditorBrowsableState.Never)]
Stopped = 5,
+ ///
+ /// Finished state.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Finished = 6,
+
///
/// Unavailable state.
///
diff --git a/src/Tizen.AIAvatar/src/Multimedia/TTS/TTSController.cs b/src/Tizen.AIAvatar/src/Multimedia/TTS/TTSController.cs
deleted file mode 100644
index f6cea3c405f..00000000000
--- a/src/Tizen.AIAvatar/src/Multimedia/TTS/TTSController.cs
+++ /dev/null
@@ -1,532 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.Linq;
-using Tizen.Uix.Tts;
-
-using static Tizen.AIAvatar.AIAvatar;
-
-namespace Tizen.AIAvatar
-{
- internal class TTSControllerEventArgs : EventArgs
- {
- public TTSControllerEventArgs()
- {
- }
-
- public TTSControllerEventArgs(byte[] audioData, int sampleRate)
- {
- //AudioData = audioData;
- AudioData = new byte[audioData.Length];
- Buffer.BlockCopy(audioData, 0, AudioData, 0, audioData.Length);
- SampleRate = sampleRate;
- }
-
- public byte[] AudioData
- {
- get;
- internal set;
- }
-
- public int SampleRate
- {
- get;
- internal set;
- }
- }
-
- internal class TTSController : IDisposable
- {
- private List textList;
- private TtsClient ttsHandle;
- private VoiceInfo voiceInfo;
- private List byteList;
-
- private byte[] recordedBuffer;
- private byte[] audioTailBuffer;
-
- private int sampleRate;
- private float desiredBufferDuration = 0.175f;
- private float audioTailLengthFactor = 0.015f;
- private float audioBufferMultiflier = 2f;
-
- private int desiredBufferLength;
- private int audioTailLength;
-
- private bool isPrepared = false;
- private bool isAsync = false;
-
- private Action bufferChangedAction;
-
- private int audioLength;
- private bool isAsyncLipStarting;
-
- internal event EventHandler PlayReadyCallback;
-
- internal event EventHandler PreparedSyncText;
- internal event EventHandler StoppedTTS;
-
-
- internal event EventHandler UpdatedBuffer;
-
- internal TTSController()
- {
- InitTts();
- }
-
- ~TTSController()
- {
- DeinitTts();
- }
-
- internal TtsClient TtsHandle
- {
- get { return ttsHandle; }
- }
-
- internal VoiceInfo VoiceInfo
- {
- get { return voiceInfo; }
- set
- {
- voiceInfo = value;
- }
- }
-
- internal List GetSupportedVoices()
- {
- var voiceInfoList = new List();
-
- if (ttsHandle == null)
- {
- Log.Error(LogTag, $"ttsHandle is null");
- return voiceInfoList;
- }
-
- var supportedVoices = ttsHandle.GetSupportedVoices();
- foreach (var supportedVoice in supportedVoices)
- {
- Log.Info(LogTag, $"{supportedVoice.Language} & {supportedVoice.VoiceType} is supported");
- voiceInfoList.Add(new VoiceInfo() { Language = supportedVoice.Language, Type = (VoiceType)supportedVoice.VoiceType });
- }
- return voiceInfoList;
- }
-
- internal bool IsSupportedVoice(string lang)
- {
- if (ttsHandle == null)
- {
- Log.Error(LogTag, $"ttsHandle is null");
- return false;
- }
- var supportedVoices = ttsHandle.GetSupportedVoices();
-
- foreach (var supportedVoice in supportedVoices)
- {
- if (supportedVoice.Language.Equals(lang))
- {
- Log.Info(LogTag, $"{lang} is supported");
- return true;
- }
- }
- return false;
- }
-
- internal bool IsSupportedVoice(VoiceInfo voiceInfo)
- {
- if (ttsHandle == null)
- {
- Log.Error(LogTag, $"ttsHandle is null");
- return false;
- }
- var supportedVoices = ttsHandle.GetSupportedVoices();
- foreach (var supportedVoice in supportedVoices)
- {
- if (supportedVoice.Language.Equals(voiceInfo.Language) && ((VoiceType)supportedVoice.VoiceType == voiceInfo.Type))
- {
- Log.Info(LogTag, $"{voiceInfo.Language} & {voiceInfo.Type} is supported");
- return true;
- }
- }
- return false;
- }
-
-
- internal void AddText(string txt, VoiceInfo voiceInfo)
- {
- if (voiceInfo.Language == null)
- {
- Log.Error(LogTag, "VoiceInfo's value is null");
- }
- if (ttsHandle == null)
- {
- Log.Error(LogTag, $"ttsHandle is null");
- return;
- }
- var temp = new UtteranceText();
- temp.Text = txt;
- temp.UttID = ttsHandle.AddText(txt, voiceInfo.Language, (int)voiceInfo.Type, 0);
- try
- {
- textList.Add(temp);
- }
- catch (Exception e)
- {
- Log.Error(LogTag, $"Error AddText" + e.Message);
- }
- }
-
- internal void AddText(string txt, string lang)
- {
- if (ttsHandle == null)
- {
- Log.Error(LogTag, $"ttsHandle is null");
- return;
- }
- var temp = new UtteranceText();
- temp.Text = txt;
- temp.UttID = ttsHandle.AddText(txt, lang, (int)voiceInfo.Type, 0);
- try
- {
- textList.Add(temp);
- }
- catch (Exception e)
- {
- Log.Error(LogTag, $"Error AddText" + e.Message);
- }
- }
-
- internal void Prepare(EventHandler playReadyCallback)
- {
- if (ttsHandle == null)
- {
- Log.Error(LogTag, $"ttsHandle is null");
- return;
- }
- Log.Info(LogTag, "Prepare TTS");
- isPrepared = true;
- isAsync = false;
- PlayReadyCallback = playReadyCallback;
- Play(true);
- }
-
-
- internal bool PlayPreparedText()
- {
- if (byteList != null && byteList.Count != 0)
- {
- Log.Info(LogTag, "PlayPreparedText TTS");
-
- PreparedSyncText?.Invoke(this, new TTSControllerEventArgs(byteList.ToArray(), sampleRate));
- return true;
- }
- return false;
- }
-
- internal void Play(bool isPrepared = false)
- {
- if (ttsHandle == null)
- {
- Log.Error(LogTag, $"ttsHandle is null");
- return;
- }
-
- this.isPrepared = isPrepared;
- isAsync = false;
- ttsHandle.Play();
- }
-
- internal void PlayAsync(EventHandler playReadyCallback)
- {
- if (ttsHandle == null)
- {
- Log.Error(LogTag, $"ttsHandle is null");
- return;
- }
-
- isPrepared = false;
- isAsync = true;
- PlayReadyCallback = playReadyCallback;
- ttsHandle.Play();
- }
-
- public void Pause()
- {
- if (ttsHandle == null)
- {
- Log.Error(LogTag, $"ttsHandle is null");
- return;
- }
- ttsHandle.Pause();
- }
-
- internal void Stop()
- {
- if (ttsHandle == null)
- {
- Log.Error(LogTag, $"ttsHandle is null");
- return;
- }
- ttsHandle.Stop();
- StoppedTTS?.Invoke(this, new TTSControllerEventArgs());
- }
-
- internal void DeinitTts()
- {
- try
- {
- if (ttsHandle != null)
- {
- ttsHandle.Unprepare();
-
- // Unregister Callbacks
- ttsHandle.DefaultVoiceChanged -= TtsDefaultVoiceChangedCallback;
- ttsHandle.EngineChanged -= TtsEngineChangedCallback;
- ttsHandle.ErrorOccurred -= TtsErrorOccuredCallback;
- ttsHandle.StateChanged -= TtsStateChangedCallback;
- ttsHandle.UtteranceCompleted -= TtsUtteranceCompletedCallback;
- ttsHandle.UtteranceStarted -= TtsUtteranceStartedCallback;
-
- ttsHandle.Dispose();
- ttsHandle = null;
- }
-
- if (textList != null)
- {
- textList.Clear();
- textList = null;
- }
-
- if (byteList != null)
- {
- byteList.Clear();
- byteList = null;
- }
- }
- catch (Exception e)
- {
- Log.Error(LogTag, "[ERROR] Fail to unprepare Tts");
- Log.Error(LogTag, e.Message);
- }
- }
-
- private void InitTts()
- {
- try
- {
- ttsHandle = new TtsClient();
-
- // Register Callbacks
- ttsHandle.DefaultVoiceChanged += TtsDefaultVoiceChangedCallback;
- ttsHandle.EngineChanged += TtsEngineChangedCallback;
- ttsHandle.ErrorOccurred += TtsErrorOccuredCallback;
- ttsHandle.StateChanged += TtsStateChangedCallback;
- ttsHandle.UtteranceCompleted += TtsUtteranceCompletedCallback;
- ttsHandle.UtteranceStarted += TtsUtteranceStartedCallback;
-
- ttsHandle.SynthesizedPcm += TtsSyntheiszedPCM;
- ttsHandle.PlayingMode = PlayingMode.ByClient;
-
- ttsHandle.Prepare();
-
- voiceInfo = new VoiceInfo
- {
- Language = ttsHandle.DefaultVoice.Language,
- Type = (VoiceType)ttsHandle.DefaultVoice.VoiceType
- };
-
- textList = new List();
- Log.Info(LogTag, voiceInfo.Language + ", " + voiceInfo.Type.ToString());
-
- }
- catch (Exception e)
- {
- Log.Error(LogTag, "[ERROR] Fail to prepare Tts");
- Log.Error(LogTag, e.Message);
- }
- }
-
- private void TtsSyntheiszedPCM(object sender, SynthesizedPcmEventArgs e)
- {
- var dataSize = e.Data.Length;
- var audio = new byte[dataSize];
- sampleRate = e.SampleRate;
-
- //Marshal.Copy(e.Data, audio, 0, dataSize);
- switch (e.EventType) //START
- {
- case SynthesizedPcmEvent.Start://start
- Tizen.Log.Info(LogTag, "------------------Start : " + e.UtteranceId);
- Tizen.Log.Info(LogTag, "Output audio Size : " + dataSize);
- Tizen.Log.Info(LogTag, "SampleRate" + e.SampleRate);
- if (byteList == null)
- {
- byteList = new List();
- }
- if (recordedBuffer == null)
- {
- recordedBuffer = new byte[0];
- }
- byteList.Clear();
-
- if (isAsync)
- {
- recordedBuffer = Array.Empty();
-
- desiredBufferLength = (int)(e.SampleRate * desiredBufferDuration * audioBufferMultiflier);
- audioTailLength = (int)(sampleRate * audioTailLengthFactor * audioBufferMultiflier);
- audioTailBuffer = new byte[audioTailLength];
- PlayReadyCallback?.Invoke(null, EventArgs.Empty);
- //InitAsyncBuffer();
- }
- break;
- case SynthesizedPcmEvent.Continue://continue
- if (isAsync)
- {
- recordedBuffer = recordedBuffer.Concat(e.Data).ToArray();
- //PlayAsync
- if (recordedBuffer.Length >= desiredBufferLength)
- {
- Tizen.Log.Error(LogTag, "Current recordbuffer length :" + recordedBuffer.Length);
- //UpdateBuffer(recordedBuffer, sampleRate);
-
- Buffer.BlockCopy(recordedBuffer, recordedBuffer.Length - audioTailLength, audioTailBuffer, 0, audioTailLength);
-
- recordedBuffer = Array.Empty();
- recordedBuffer = recordedBuffer.Concat(audioTailBuffer).ToArray();
- Array.Clear(audioTailBuffer, 0, audioTailLength);
- }
- }
- else
- {
- byteList.AddRange(e.Data);
- }
- break;
- case SynthesizedPcmEvent.Finish://finish
- Tizen.Log.Info(LogTag, "------------------Finish : " + e.UtteranceId);
- if (!isAsync)
- {
- if (!isPrepared)
- {
- //Play voice immediately
- PlayPreparedText();
- }
- else
- {
- //Notify finished state
- Log.Info(LogTag, "Notify finished state");
- PlayReadyCallback?.Invoke(null, EventArgs.Empty);
- }
- }
- else
- {//async
- //FinishedSynthesizedPcm?.Invoke(null, EventArgs.Empty);
- //lipSyncer.SetFinishAsyncLip(true);
- }
- break;
- case SynthesizedPcmEvent.Fail: //fail
- break;
-
- }
- }
-
- private void TtsUtteranceStartedCallback(object sender, UtteranceEventArgs e)
- {
- Log.Debug(LogTag, "Utterance start now (" + e.UtteranceId + ")");
- }
-
- private void TtsUtteranceCompletedCallback(object sender, UtteranceEventArgs e)
- {
- Log.Debug(LogTag, "Utterance complete (" + e.UtteranceId + ")");
-
- foreach (UtteranceText item in textList)
- {
- if (item.UttID == e.UtteranceId)
- {
- textList.Remove(item);
- Log.Debug(LogTag, "TextList Count (" + textList.Count.ToString() + ")");
- break;
- }
- }
- }
-
- private void TtsStateChangedCallback(object sender, StateChangedEventArgs e)
- {
- Log.Debug(LogTag, "Current state is changed from (" + e.Previous + ") to (" + e.Current + ")");
- }
-
- private void TtsErrorOccuredCallback(object sender, ErrorOccurredEventArgs e)
- {
- Log.Error(LogTag, "Error is occured (" + e.ErrorMessage + ")");
- }
-
- private void TtsEngineChangedCallback(object sender, EngineChangedEventArgs e)
- {
- Log.Debug(LogTag, "Prefered engine is changed (" + e.EngineId + ") (" + e.VoiceType.Language + ")");
- }
-
- private void TtsDefaultVoiceChangedCallback(object sender, DefaultVoiceChangedEventArgs e)
- {
- Log.Debug(LogTag, "Default voice is changed from (" + e.Previous + ") to (" + e.Current + ")");
- }
-
- private void InitAsyncBuffer()
- {
- /*
- InitedAsyncBuffer?.Invoke(null, EventArgs.Empty);
- if (!lipSyncer.IsAsyncInit)
- {
- audioLength = (int)(sampleRate * 0.16f * 2f);
-
- lipSyncer.InitAsyncLipsync();
- lipSyncer.IsAsyncInit = true;
-
- lipSyncer.SetFinishAsyncLip(false);
- isAsyncLipStarting = false;
- }*/
- }
-
- private void UpdateBuffer(byte[] recordBuffer, int sampleRate)
- {
- UpdatedBuffer?.Invoke(this, new TTSControllerEventArgs(recordBuffer, sampleRate));
- /*
- if (lipSyncer != null)
- {
- Log.Error(LogTag, "OnTTSBufferChanged");
- lipSyncer.EnqueueAnimation(recordBuffer, sampleRate, audioLength);
- if (!isAsyncLipStarting)
- {
- lipSyncer.StartAsyncLipPlayTimer();
- isAsyncLipStarting = true;
- }
- }
- else
- {
- Log.Error(LogTag, "avatarLipSyncer is null");
- }*/
- }
-
- public void Dispose()
- {
- ttsHandle.Stop();
- ttsHandle.Dispose();
- ttsHandle = null;
- }
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Multimedia/TTS/UtteranceText.cs b/src/Tizen.AIAvatar/src/Multimedia/TTS/UtteranceText.cs
deleted file mode 100644
index 2e69e1f97d6..00000000000
--- a/src/Tizen.AIAvatar/src/Multimedia/TTS/UtteranceText.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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;
-
-namespace Tizen.AIAvatar
-{
- internal struct UtteranceText : IEquatable
- {
- private string text;
- private int uttID;
-
- public string Text { get => text; set => text = value; }
- public int UttID { get => uttID; set => uttID = value; }
-
- public override bool Equals(object obj) => obj is VoiceInfo other && this.Equals(other);
-
- public bool Equals(UtteranceText other) => Text == other.Text && UttID == other.UttID;
-
- public static bool operator ==(UtteranceText lhsUtternaceText, UtteranceText rhsUtternaceText) => lhsUtternaceText.Equals(rhsUtternaceText);
-
- public static bool operator !=(UtteranceText lhsUtternaceText, UtteranceText rhsVoiceInfo) => !lhsUtternaceText.Equals(rhsVoiceInfo);
-
- public override int GetHashCode() => (Text, UttID).GetHashCode();
- }
-}
diff --git a/src/Tizen.AIAvatar/src/RestClient/IRestClient.cs b/src/Tizen.AIAvatar/src/RestClient/IRestClient.cs
deleted file mode 100644
index 9a593b135b6..00000000000
--- a/src/Tizen.AIAvatar/src/RestClient/IRestClient.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.Net.Http;
-using System.Threading.Tasks;
-
-namespace Tizen.AIAvatar
-{
-
- internal interface IRestClient
- {
- Task SendRequestAsync(HttpMethod method, string endpoint, string bearerToken = null, string jsonData = null);
- }
-}
diff --git a/src/Tizen.AIAvatar/src/RestClient/RestClient.cs b/src/Tizen.AIAvatar/src/RestClient/RestClient.cs
deleted file mode 100644
index 0ee81ebb73b..00000000000
--- a/src/Tizen.AIAvatar/src/RestClient/RestClient.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.Net.Http.Headers;
-using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
-using System.ComponentModel;
-
-namespace Tizen.AIAvatar
-{
- internal class RestClient : IRestClient, IDisposable
- {
- private readonly HttpClient client;
-
- internal RestClient(HttpClient httpClient)
- {
- client = httpClient;
- }
-
- [EditorBrowsable(EditorBrowsableState.Never)]
- public async Task SendRequestAsync(HttpMethod method, string endpoint, string bearerToken = null, string jsonData = null)
- {
- AddBearerToken(bearerToken);
-
- HttpRequestMessage request = new HttpRequestMessage(method, endpoint);
-
- if (jsonData != null)
- {
- request.Content = new StringContent(jsonData, Encoding.UTF8, "application/json");
- }
-
- HttpResponseMessage response = await client.SendAsync(request);
- request?.Dispose();
- return await HandleResponse(response);
- }
-
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void Dispose()
- {
- client.Dispose();
- }
-
- private void AddBearerToken(string bearerToken)
- {
- if (!string.IsNullOrEmpty(bearerToken))
- {
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
- }
- }
-
- private async Task HandleResponse(HttpResponseMessage response)
- {
- if (response.IsSuccessStatusCode)
- {
- return await response.Content.ReadAsStringAsync();
- }
- else
- {
- throw new HttpRequestException($"HTTP request failed with status code {response.StatusCode}");
- }
- }
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Tracking/TrackingOptions.cs b/src/Tizen.AIAvatar/src/Tracking/TrackingOptions.cs
deleted file mode 100644
index a908e718b73..00000000000
--- a/src/Tizen.AIAvatar/src/Tracking/TrackingOptions.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.
- *
- */
-
-namespace Tizen.AIAvatar
-{
- internal class TrackingOptions
- {
- }
-}
diff --git a/src/Tizen.AIAvatar/src/Tracking/TrackingController.cs b/src/Tizen.AIAvatar/src/Utility/AIAvatar.cs
similarity index 65%
rename from src/Tizen.AIAvatar/src/Tracking/TrackingController.cs
rename to src/Tizen.AIAvatar/src/Utility/AIAvatar.cs
index 4ca0fbf9b03..79079cad032 100644
--- a/src/Tizen.AIAvatar/src/Tracking/TrackingController.cs
+++ b/src/Tizen.AIAvatar/src/Utility/AIAvatar.cs
@@ -15,26 +15,15 @@
*
*/
+using Tizen.Multimedia;
+
namespace Tizen.AIAvatar
{
- internal class TrackingController
+ internal static class AIAvatar
{
- internal TrackingController()
- {
- }
-
- internal void Initialize(TrackingOptions options)
- {
- }
-
- internal void StartMotionTracking()
- {
-
- }
-
- internal void StopMotionTracking()
- {
+ internal const string LogTag = "Tizen.AIAvatar";
- }
+ internal static AudioOptions DefaultAudioOptions = new AudioOptions(24000, AudioChannel.Mono, AudioSampleType.S16Le, AudioStreamType.System);
+ internal static AudioOptions CurrentAudioOptions = DefaultAudioOptions;
}
}
diff --git a/src/Tizen.AIAvatar/test/Test.cs b/src/Tizen.AIAvatar/test/Test.cs
index e8f0e11a86d..5d9c2cc9984 100644
--- a/src/Tizen.AIAvatar/test/Test.cs
+++ b/src/Tizen.AIAvatar/test/Test.cs
@@ -21,19 +21,7 @@ internal class Test
{
public void CreateTest()
{
- var avatar1 = new Avatar();
-
- var avatarInfo = new AvatarInfo("avatarName", "resourcePath");
- var avatar2 = new Avatar(avatarInfo);
-
- var avatar3 = new Avatar("resourcePath");
-
- var avatar4 = new Avatar(avatar1);
-
- avatar1.Dispose();
- avatar2.Dispose();
- avatar3.Dispose();
- avatar4.Dispose();
+
}
}
}
diff --git a/src/Tizen.Applications.Common/Tizen.Applications.RPCPort/Parcel.cs b/src/Tizen.Applications.Common/Tizen.Applications.RPCPort/Parcel.cs
index 15acd882d41..4197083872a 100755
--- a/src/Tizen.Applications.Common/Tizen.Applications.RPCPort/Parcel.cs
+++ b/src/Tizen.Applications.Common/Tizen.Applications.RPCPort/Parcel.cs
@@ -282,6 +282,17 @@ public void WriteByte(byte b)
Interop.LibRPCPort.Parcel.WriteByte(_handle, b);
}
+ ///
+ /// Writes a single signed byte value into the parcel object.
+ ///
+ /// The signed byte value to be written into the parcel object.
+ /// 10
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void WriteSByte(sbyte b)
+ {
+ Interop.LibRPCPort.Parcel.WriteByte(_handle, (byte)b);
+ }
+
///
/// Writes a short value into parcel object.
///
@@ -292,6 +303,18 @@ public void WriteShort(short b)
Interop.LibRPCPort.Parcel.WriteInt16(_handle, b);
}
+ ///
+ /// Writes a unsigned short value into parcel object.
+ ///
+ /// The unsigned short data to write.
+ /// 10
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void WriteUShort(ushort b)
+ {
+ var bytes = BitConverter.GetBytes(b);
+ Write(bytes);
+ }
+
///
/// Writes an integer value into the parcel object.
///
@@ -316,6 +339,18 @@ public void WriteInt(int b)
Interop.LibRPCPort.Parcel.WriteInt32(_handle, b);
}
+ ///
+ /// Writes an unsigned integer value into the parcel object.
+ ///
+ /// The unsigned integer value to write.
+ /// 10
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void WriteUInt(uint b)
+ {
+ var bytes = BitConverter.GetBytes(b);
+ Write(bytes);
+ }
+
///
/// Writes a long value into the parcel object.
///
@@ -326,6 +361,18 @@ public void WriteLong(long b)
Interop.LibRPCPort.Parcel.WriteInt64(_handle, b);
}
+ ///
+ /// Writes an unsigned long value into the parcel object.
+ ///
+ /// The unsigned long data to write.
+ /// 10
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void WriteULong(ulong b)
+ {
+ var bytes = BitConverter.GetBytes(b);
+ Write(bytes);
+ }
+
///
/// Writes a float value into the parcel object.
///
@@ -402,6 +449,18 @@ public byte ReadByte()
return b;
}
+ ///
+ /// Reads a signed byte value from the parcel object.
+ ///
+ /// The byte value.
+ /// 10
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sbyte ReadSByte()
+ {
+ Interop.LibRPCPort.Parcel.ReadByte(_handle, out byte b);
+ return (sbyte)b;
+ }
+
///
/// Reads a short value from the parcel object.
///
@@ -413,6 +472,20 @@ public short ReadShort()
return b;
}
+ ///
+ /// Reads an unsigned short value from the parcel object.
+ ///
+ /// The unsigned short data.
+ /// 10
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ushort ReadUShort()
+ {
+ var bytes = Read(sizeof(ushort));
+ if (BitConverter.IsLittleEndian)
+ Array.Reverse(bytes);
+ return BitConverter.ToUInt16(bytes, 0);
+ }
+
///
/// Reads an integer value from the parcel object.
///
@@ -424,6 +497,20 @@ public int ReadInt()
return b;
}
+ ///
+ /// Reads an unsigned integer value from the parcel object.
+ ///
+ /// The integer data.
+ /// 10
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public uint ReadUInt()
+ {
+ var bytes = Read(sizeof(uint));
+ if (BitConverter.IsLittleEndian)
+ Array.Reverse(bytes);
+ return BitConverter.ToUInt32(bytes, 0);
+ }
+
///
/// Reads a long value from the parcel object.
///
@@ -435,6 +522,20 @@ public long ReadLong()
return b;
}
+ ///
+ /// Reads an unsigned long value from the parcel object.
+ ///
+ /// The unsigned long data.
+ /// 10
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ulong ReadULong()
+ {
+ var bytes = Read(sizeof(ulong));
+ if (BitConverter.IsLittleEndian)
+ Array.Reverse(bytes);
+ return BitConverter.ToUInt64(bytes, 0);
+ }
+
///
/// Reads a float value from the parcel object.
///
diff --git a/src/Tizen.Applications.Common/Tizen.Applications/AppControl.cs b/src/Tizen.Applications.Common/Tizen.Applications/AppControl.cs
index 043fa7edf2c..0cb8093e1b9 100755
--- a/src/Tizen.Applications.Common/Tizen.Applications/AppControl.cs
+++ b/src/Tizen.Applications.Common/Tizen.Applications/AppControl.cs
@@ -932,7 +932,7 @@ public static Task SendLaunchRequestAsync(AppControl launchReq
/// Thrown when the argument is invalid.
/// Thrown when the permission is denied.
/// Thrown when the memory is insufficient.
- /// Thrown when the memory is insufficient.
+ /// Thrown when failed because of an invalid operation.
[EditorBrowsable(EditorBrowsableState.Never)]
public static void SetAutoRestart(AppControl appControl)
{
@@ -967,7 +967,7 @@ public static void SetAutoRestart(AppControl appControl)
///
/// Thrown when the permission is denied.
/// Thrown when the memory is insufficient.
- /// Thrown when the memory is insufficient.
+ /// Thrown when failed because of an invalid operation.
[EditorBrowsable(EditorBrowsableState.Never)]
public static void UnsetAutoRestart()
{
diff --git a/src/Tizen.NUI.AIAvatar/Tizen.NUI.AIAvatar.csproj b/src/Tizen.NUI.AIAvatar/Tizen.NUI.AIAvatar.csproj
new file mode 100644
index 00000000000..0fac1407c14
--- /dev/null
+++ b/src/Tizen.NUI.AIAvatar/Tizen.NUI.AIAvatar.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net6.0
+ 9.0
+ $(NoWarn);0618;CA1054;CA1056
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.AIAvatar/src/Animator/AnimatorBase.cs b/src/Tizen.NUI.AIAvatar/src/Animator/AnimatorBase.cs
new file mode 100644
index 00000000000..a5c2ebbe7ff
--- /dev/null
+++ b/src/Tizen.NUI.AIAvatar/src/Animator/AnimatorBase.cs
@@ -0,0 +1,298 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+using System.Linq;
+
+namespace Tizen.NUI.AIAvatar
+{
+ ///
+ /// The base class for all animator implementations. Provides basic functionality for adding, playing, stopping, pausing, and removing animations.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public abstract class AnimatorBase : IAnimator
+ {
+ private bool disposed = false;
+ private string animationName = "";
+ private AnimatorState currentAnimatorState = AnimatorState.Unavailable;
+
+
+ private Dictionary nameToIndex = new Dictionary();
+ private Dictionary indexToName = new Dictionary();
+
+ ///
+ /// A dictionary to store animations. Each animation has a unique ID of type uint, which can be used as a key to store and retrieve the animations.
+ ///
+ protected Dictionary animations = new Dictionary();
+
+
+ ///
+ /// Event triggered when the state of the animator changes.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler AnimatorStateChanged;
+
+ ///
+ /// Initializes a new instance of the AnimatorBase class.
+ ///
+ protected AnimatorBase() { }
+
+ ///
+ /// Adds an animation to the animator.
+ ///
+ /// The animation to add.
+ /// The name of the animation.
+ /// The index of the added animation.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int Add(Animation animation, string name)
+ {
+ if (animation == null)
+ {
+ throw new ArgumentNullException(nameof(animation));
+ }
+
+ if (!nameToIndex.ContainsKey(name))
+ {
+ uint index = animation.ID;
+ animations.Add(index, animation);
+ nameToIndex.Add(name, index);
+ indexToName.Add(index, name);
+
+ return (int)index;
+ }
+ else
+ {
+ throw new ArgumentException("Duplicate name: " + name);
+ }
+
+
+ }
+
+ ///
+ /// Gets the number of animations currently added to the animator.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int Count
+ {
+ get
+ {
+ return animations.Count;
+ }
+ }
+
+ ///
+ /// Plays an animation by its index.
+ ///
+ /// The index of the animation to play.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public abstract void Play(uint index);
+
+ ///
+ /// Plays an animation by its name.
+ ///
+ /// The name of the animation to play.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Play(string name)
+ {
+ if (nameToIndex.TryGetValue(name, out uint index))
+ {
+ Play(index);
+ }
+ else
+ {
+ throw new ArgumentException($"Animation with name {name} does not exist.");
+ }
+ }
+
+ ///
+ /// Stops all currently playing animations.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public abstract void Stop();
+
+ ///
+ /// Pauses all currently playing animations.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public abstract void Pause();
+
+ ///
+ /// Removes an animation by its index.
+ ///
+ /// The index of the animation to remove.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Remove(uint index)
+ {
+ if (!animations.ContainsKey(index))
+ {
+ throw new ArgumentException($"Animation with index {index} does not exist.");
+ }
+
+ Stop();
+ animations.Remove(index);
+ nameToIndex.Remove(indexToName[index]);
+ indexToName.Remove(index);
+ }
+
+ ///
+ /// Removes an animation by its name.
+ ///
+ /// The name of the animation to remove.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Remove(string name)
+ {
+ uint index = GetIndexByName(name);
+
+ animations.Remove(index);
+ nameToIndex.Remove(name);
+ indexToName.Remove(index);
+ }
+
+ ///
+ /// Gets the index of an animation by its name.
+ ///
+ /// The name of the animation.
+ /// The index of the animation.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public uint GetIndexByName(string name)
+ {
+ if (!nameToIndex.TryGetValue(name, out uint index))
+ {
+ throw new ArgumentException($"Animation with name {name} does not exist.");
+ }
+ return index;
+ }
+
+ ///
+ /// Gets the name of an animation by its index.
+ ///
+ /// The index of the animation.
+ /// The name of the animation.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string GetNameByIndex(uint index)
+ {
+ if (!indexToName.TryGetValue(index, out string name))
+ {
+ throw new ArgumentException($"Animation with index {index} does not exist.");
+ }
+ return name;
+ }
+
+ ///
+ /// Gets the key element at the specified index in the animations dictionary.
+ ///
+ /// The zero-based index of the key element to retrieve.
+ /// The key element at the specified index.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public uint GetKeyElementAt(int index)
+ {
+ return animations.Keys.ElementAt(index);
+ }
+
+ ///
+ /// Gets or sets the current state of the animator.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AnimatorState CurrentAnimatorState
+ {
+ get => currentAnimatorState;
+ protected set
+ {
+ if (currentAnimatorState != AnimatorState.AnimationFinished && currentAnimatorState == value) return;
+
+ var preState = currentAnimatorState;
+ currentAnimatorState = value;
+
+ var message = animationName;
+ AnimatorStateChanged?.Invoke(this, new AnimatorChangedEventArgs(preState, currentAnimatorState, message));
+ animationName = "";
+ }
+ }
+
+ ///
+ /// Changes the state of the animator.
+ ///
+ /// The new state of the animator.
+ /// The name of the animation associated with the state change.
+ protected void ChangeAnimatorState(AnimatorState newState, string animationName)
+ {
+ this.animationName = animationName;
+ CurrentAnimatorState = newState;
+ }
+
+ ///
+ /// Handles the Finished event of an animation.
+ ///
+ /// The object that raised the event.
+ /// The event arguments.
+ protected void OnAnimationFinished(object sender, EventArgs e)
+ {
+ if (!(sender is Animation anim)) return;
+
+ anim.Finished -= OnAnimationFinished;
+ foreach (var animation in animations.Values)
+ {
+ if (animation.State == Animation.States.Playing)
+ {
+ ChangeAnimatorState(AnimatorState.AnimationFinished, indexToName[anim.ID]);
+ return;
+ }
+ }
+
+ ChangeAnimatorState(AnimatorState.Stopped, indexToName[anim.ID]);
+ }
+
+ ///
+ /// Releases all resources used by the AnimatorBase class.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Releases the unmanaged resources used by the AnimatorBase class and optionally releases the managed resources.
+ ///
+ /// True to release both managed and unmanaged resources; false to release only unmanaged resources.
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposed)
+ {
+ if (disposing)
+ {
+ // Managed resources cleanup
+ if (animations != null)
+ {
+ foreach (var animation in animations.Values)
+ {
+ animation.Dispose();
+ }
+ animations.Clear();
+ animations = null;
+ }
+ }
+ // Unmanaged resources cleanup
+
+ disposed = true;
+ }
+ }
+ }
+}
diff --git a/src/Tizen.AIAvatar/src/Animations/AvatarMotionChangedEventArgs.cs b/src/Tizen.NUI.AIAvatar/src/Animator/Core/AnimatorChangedEventArgs.cs
similarity index 75%
rename from src/Tizen.AIAvatar/src/Animations/AvatarMotionChangedEventArgs.cs
rename to src/Tizen.NUI.AIAvatar/src/Animator/Core/AnimatorChangedEventArgs.cs
index 4210248d020..aa2bef2701f 100644
--- a/src/Tizen.AIAvatar/src/Animations/AvatarMotionChangedEventArgs.cs
+++ b/src/Tizen.NUI.AIAvatar/src/Animator/Core/AnimatorChangedEventArgs.cs
@@ -18,7 +18,7 @@
using System;
using System.ComponentModel;
-namespace Tizen.AIAvatar
+namespace Tizen.NUI.AIAvatar
{
///
/// This class provides arguments for handling avatar motion change events.
@@ -26,25 +26,27 @@ namespace Tizen.AIAvatar
/// The current state of the avatar's motion.
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public class AvatarMotionChangedEventArgs : EventArgs
+ public class AnimatorChangedEventArgs : EventArgs
{
///
/// Initializes a new instance of the AvatarMotionChangedEventArgs class with the specified previous and current states.
///
/// The previous state of the avatar's motion.
/// The current state of the avatar's motion.
+ /// The current Animation of the Animation name.
[EditorBrowsable(EditorBrowsableState.Never)]
- public AvatarMotionChangedEventArgs(AvatarMotionState previous, AvatarMotionState current)
+ public AnimatorChangedEventArgs(AnimatorState previous, AnimatorState current, string message = "")
{
Previous = previous;
Current = current;
+ Message = message;
}
///
/// The previous state.
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public AvatarMotionState Previous
+ public AnimatorState Previous
{
get;
internal set;
@@ -54,7 +56,17 @@ public AvatarMotionState Previous
/// The current state.
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public AvatarMotionState Current
+ public AnimatorState Current
+ {
+ get;
+ internal set;
+ }
+
+ ///
+ /// The Message string.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Message
{
get;
internal set;
diff --git a/src/Tizen.NUI.AIAvatar/src/Animator/Core/AnimatorState.cs b/src/Tizen.NUI.AIAvatar/src/Animator/Core/AnimatorState.cs
new file mode 100644
index 00000000000..2f100cfa40a
--- /dev/null
+++ b/src/Tizen.NUI.AIAvatar/src/Animator/Core/AnimatorState.cs
@@ -0,0 +1,70 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+
+namespace Tizen.NUI.AIAvatar
+{
+ ///
+ /// Enumeration for the states.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public enum AnimatorState
+ {
+ ///
+ /// Fail state.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Failed = -1,
+
+ ///
+ /// Ready state.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Ready = 0,
+
+ ///
+ /// Playing state.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Playing = 3,
+
+ ///
+ /// Paused state.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Paused = 4,
+
+ ///
+ /// Stopped state.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Stopped = 5,
+
+ ///
+ /// AnimationFinished state.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ AnimationFinished = 6,
+
+ ///
+ /// Unavailable state.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Unavailable
+ };
+}
diff --git a/src/Tizen.NUI.AIAvatar/src/Animator/Core/IAnimator.cs b/src/Tizen.NUI.AIAvatar/src/Animator/Core/IAnimator.cs
new file mode 100644
index 00000000000..bd364236462
--- /dev/null
+++ b/src/Tizen.NUI.AIAvatar/src/Animator/Core/IAnimator.cs
@@ -0,0 +1,99 @@
+
+using System;
+using System.ComponentModel;
+using Tizen.NUI;
+
+namespace Tizen.NUI.AIAvatar
+{
+ ///
+ /// Represents an animator that can play and manage a collection of animations.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public interface IAnimator : IDisposable
+ {
+ ///
+ /// Adds an animation to the animator with a given name.
+ ///
+ /// The animation to add.
+ /// The name to associate with the animation.
+ /// The index of the added animation.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ int Add(Animation animation, string name);
+
+ ///
+ /// Gets the number of animations currently managed by the animator.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ int Count { get; }
+
+ ///
+ /// Plays the animation at the specified index.
+ ///
+ /// The index of the animation to play.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void Play(uint index);
+
+ ///
+ /// Plays the animation with the specified name.
+ ///
+ /// The name of the animation to play.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void Play(string name);
+
+ ///
+ /// Stops all currently playing animations.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void Stop();
+
+ ///
+ /// Pauses all currently playing animations.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void Pause();
+
+ ///
+ /// Removes the animation at the specified index.
+ ///
+ /// The index of the animation to remove.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void Remove(uint index);
+
+ ///
+ /// Removes the animation with the specified name.
+ ///
+ /// The name of the animation to remove.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void Remove(string name);
+
+ ///
+ /// Gets the index of the animation with the specified name.
+ ///
+ /// The name of the animation to find.
+ /// The index of the animation with the specified name.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ uint GetIndexByName(string name);
+
+ ///
+ /// Gets the name of the animation at the specified index.
+ ///
+ /// The index of the animation to find.
+ /// The name of the animation at the specified index.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ string GetNameByIndex(uint index);
+
+ ///
+ /// Gets the key element at the specified index in the animator's collection.
+ ///
+ /// The index of the key element to retrieve.
+ /// The key element at the specified index.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ uint GetKeyElementAt(int index);
+
+ ///
+ /// Gets the current state of the animator.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ AnimatorState CurrentAnimatorState { get; }
+ }
+}
diff --git a/src/Tizen.NUI.AIAvatar/src/Animator/EmotionAnimator.cs b/src/Tizen.NUI.AIAvatar/src/Animator/EmotionAnimator.cs
new file mode 100644
index 00000000000..41ff5eaba42
--- /dev/null
+++ b/src/Tizen.NUI.AIAvatar/src/Animator/EmotionAnimator.cs
@@ -0,0 +1,164 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+using System.IO;
+using System.Text.Json;
+using Tizen.NUI.Scene3D;
+
+namespace Tizen.NUI.AIAvatar
+{
+ ///
+ /// The EmotionAnimator class extends SerialAnimator and provides functionality to play animations based on emotions.
+ /// It manages emotion configurations, generates expression data animations, and loads necessary resources.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class EmotionAnimator : SerialAnimator
+ {
+
+ private EmotionConfig EmotionConfigData;
+ private Dictionary> expressionDataByCategory;
+ private Dictionary> expressionIdByCategory;
+
+ ///
+ /// Plays a random animation from the specified emotion category.
+ /// If the emotion is not found, plays a random animation from the "normal" category.
+ ///
+ /// The emotion category to play.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new void Play(string emotion)
+ {
+ if (expressionIdByCategory.TryGetValue(emotion.ToLower(), out List expressionList))
+ {
+ int randomIndex = new Random().Next(0, expressionList.Count);
+ base.Play(expressionList[randomIndex]);
+ }
+ else if (expressionIdByCategory.TryGetValue("normal", out List normalList))
+ {
+ int randomIndex = new Random().Next(0, normalList.Count);
+ base.Play(normalList[randomIndex]);
+ }
+ }
+
+ ///
+ /// Generates expression data animations for the given model.
+ /// Throws exceptions if the model is null or its resources are not ready.
+ ///
+ /// The model to generate animations for.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void GenerateExpressionDataAnimation(Model model)
+ {
+ if (model == null)
+ {
+ throw new ArgumentNullException(nameof(model));
+ }
+
+ if (!model.IsResourceReady())
+ {
+ throw new InvalidOperationException("The model resource is not ready.");
+ }
+
+ expressionIdByCategory = new Dictionary>();
+
+ foreach (var category in expressionDataByCategory)
+ {
+ int categoryCount = 0;
+
+ if (!expressionIdByCategory.ContainsKey(category.Key))
+ {
+ expressionIdByCategory[category.Key] = new List();
+ }
+
+ foreach (var motionData in category.Value)
+ {
+ var animation = model.GenerateMotionDataAnimation(motionData);
+ string key = $"{category.Key}_{categoryCount}";
+ uint animationId = (uint)Add(animation, key);
+ expressionIdByCategory[category.Key].Add(animationId);
+ categoryCount++;
+ }
+ }
+ }
+
+ ///
+ /// Loads the emotion configuration data from the specified JSON file and expression data from the given resource path.
+ /// Throws exceptions if there are errors reading the files or if the paths are invalid.
+ ///
+ /// The path to the emotion configuration JSON file.
+ /// The path to the directory containing expression resource files.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void LoadEmotionConfig(in string configPath, in string expressionResourcePath)
+ {
+ try
+ {
+ string json = File.ReadAllText(configPath);
+ EmotionConfigData = JsonSerializer.Deserialize(json);
+ }
+ catch (JsonException ex)
+ {
+ throw new Exception($"Error loading Emotion Config data from {configPath}: {ex}");
+ }
+
+ LoadExpressionData(expressionResourcePath);
+
+ }
+
+ ///
+ /// Loads expression data from the specified resource path.
+ /// Throws exceptions if the resource path is null or empty, or if any expression files are missing.
+ ///
+ /// The path to the directory containing expression resource files.
+ private void LoadExpressionData(string expressionResourcePath)
+ {
+
+ if (string.IsNullOrEmpty(expressionResourcePath))
+ {
+ throw new ArgumentException("expressionResourcePath cannot be null or empty.", nameof(expressionResourcePath));
+ }
+
+
+ expressionDataByCategory = new Dictionary>();
+
+ foreach (Expression expression in EmotionConfigData.expressions)
+ {
+ if (!expressionDataByCategory.ContainsKey(expression.name))
+ {
+ expressionDataByCategory[expression.name] = new List();
+ }
+
+ foreach (string filename in expression.filename)
+ {
+ string expressionFile = global::System.IO.Path.Combine(expressionResourcePath, filename);
+
+ if (!File.Exists(expressionFile))
+ {
+ throw new FileNotFoundException($"Expression file not found: {expressionFile}", expressionFile);
+ }
+
+ string expressionJson = File.ReadAllText(expressionFile);
+
+ FaceAnimationData expressionFaceAnimationData = JsonSerializer.Deserialize(expressionJson);
+ MotionData expressionFaceMotionData = AnimationLoader.Instance.CreateFacialMotionData(expressionFaceAnimationData, EmotionConfigData.ignoreBlendShapes);
+ expressionDataByCategory[expression.name].Add(expressionFaceMotionData);
+ }
+
+ }
+ }
+ }
+}
diff --git a/src/Tizen.NUI.AIAvatar/src/Animator/ParallelAnimator.cs b/src/Tizen.NUI.AIAvatar/src/Animator/ParallelAnimator.cs
new file mode 100644
index 00000000000..033400b493d
--- /dev/null
+++ b/src/Tizen.NUI.AIAvatar/src/Animator/ParallelAnimator.cs
@@ -0,0 +1,174 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+using System.Linq;
+using Tizen.NUI;
+
+namespace Tizen.NUI.AIAvatar
+{
+ ///
+ /// The ParallelAnimator class extends the AnimatorBase class and provides methods to play, stop, and pause animations in parallel.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class ParallelAnimator : AnimatorBase
+ {
+ ///
+ /// Plays the animation at the specified index.
+ ///
+ /// The index of the animation to play.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Play(uint index)
+ {
+ Play(new List { index });
+ }
+
+ ///
+ /// Stops all playing animations.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Stop()
+ {
+ Stop(null);
+ }
+
+ ///
+ /// Pauses all playing animations.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Pause()
+ {
+ Pause(null);
+ }
+
+ ///
+ /// Plays the specified animations in parallel.
+ ///
+ /// A list of indices of the animations to play. If null, all animations will be played.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Play(IEnumerable indexes = null)
+ {
+ string names = string.Join(", ", indexes?.Select(index => GetNameByIndex(animations[index].ID)).Where(name => !string.IsNullOrEmpty(name)) ?? Enumerable.Empty());
+
+ if (indexes == null)
+ {
+ foreach (var animation in animations.Values)
+ {
+ animation.Finished += OnAnimationFinished;
+ animation.Play();
+ }
+ }
+ else
+ {
+ foreach (var index in indexes)
+ {
+ if (animations.TryGetValue(index, out var animation))
+ {
+ animation.Finished += OnAnimationFinished;
+ animation.Play();
+ }
+ }
+ }
+
+ ChangeAnimatorState(AnimatorState.Playing, names);
+ }
+
+ ///
+ /// Stops the specified animations or all playing animations if no indices are provided.
+ ///
+ /// A list of indices of the animations to stop. If null, all playing animations will be stopped.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Stop(IEnumerable indexes = null)
+ {
+ int count = 0;
+ string names = string.Join(", ", indexes?.Select(index => GetNameByIndex(animations[index].ID)).Where(name => !string.IsNullOrEmpty(name)) ?? Enumerable.Empty());
+
+ if (indexes == null)
+ {
+ foreach (var animation in animations.Values)
+ {
+ if (animation.State == Animation.States.Playing)
+ {
+ animation.Finished -= OnAnimationFinished;
+ animation.Stop();
+ count++;
+ }
+ }
+ }
+ else
+ {
+ foreach (var index in indexes)
+ {
+ if (animations.TryGetValue(index, out var animation))
+ {
+ if (animation.State == Animation.States.Playing)
+ {
+ animation.Finished -= OnAnimationFinished;
+ animation.Stop();
+ count++;
+ }
+ }
+ }
+ }
+
+ if(count > 0) ChangeAnimatorState(AnimatorState.Stopped, names);
+ }
+
+ ///
+ /// Pauses the specified animations or all playing animations if no indices are provided.
+ ///
+ /// A list of indices of the animations to pause. If null, all playing animations will be paused.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Pause(IEnumerable indexes = null)
+ {
+ int count = 0;
+ string names = string.Join(", ", indexes?.Select(index => GetNameByIndex(animations[index].ID)).Where(name => !string.IsNullOrEmpty(name)) ?? Enumerable.Empty());
+
+ if (indexes == null)
+ {
+ foreach (var animation in animations.Values)
+ {
+ if (animation.State == Animation.States.Playing)
+ {
+ animation.Finished -= OnAnimationFinished;
+ animation.Pause();
+ count++;
+ }
+ }
+ }
+ else
+ {
+ foreach (var index in indexes)
+ {
+ if (animations.TryGetValue(index, out var animation))
+ {
+ if (animation.State == Animation.States.Playing)
+ {
+ animation.Finished -= OnAnimationFinished;
+ animation.Pause();
+ count++;
+ }
+ }
+ }
+ }
+
+ if(count > 0) ChangeAnimatorState(AnimatorState.Paused, names);
+ }
+ }
+}
diff --git a/src/Tizen.NUI.AIAvatar/src/Animator/SerialAnimator.cs b/src/Tizen.NUI.AIAvatar/src/Animator/SerialAnimator.cs
new file mode 100644
index 00000000000..3762f9f6db9
--- /dev/null
+++ b/src/Tizen.NUI.AIAvatar/src/Animator/SerialAnimator.cs
@@ -0,0 +1,89 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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;
+using Tizen.Applications;
+
+namespace Tizen.NUI.AIAvatar
+{
+ ///
+ /// Represents an animator that plays animations in a serial manner.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class SerialAnimator : AnimatorBase
+ {
+ private uint playIndex;
+
+ private void PlayMainThreadAnimation()
+ {
+ animations[playIndex].Finished += OnAnimationFinished;
+ animations[playIndex].Play();
+
+ ChangeAnimatorState(AnimatorState.Playing, GetNameByIndex(playIndex));
+ }
+
+ ///
+ /// Plays the animation corresponding to the specified index.
+ ///
+ /// The index of the animation to play.
+ /// Thrown when the specified animation index does not exist.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Play(uint index)
+ {
+ Stop();
+
+ if (!animations.ContainsKey(index))
+ {
+ throw new ArgumentException($"Animation with index {index} does not exist.");
+ }
+
+ playIndex = index;
+ CoreApplication.Post(PlayMainThreadAnimation);
+ }
+
+ ///
+ /// Stops the currently playing animation.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Stop()
+ {
+ if (animations.ContainsKey(playIndex))
+ {
+ animations[playIndex].Finished -= OnAnimationFinished;
+ animations[playIndex].Stop();
+
+ ChangeAnimatorState(AnimatorState.Stopped, GetNameByIndex(playIndex));
+ }
+ }
+
+ ///
+ /// Pauses the currently playing animation.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Pause()
+ {
+ if (animations.ContainsKey(playIndex))
+ {
+ animations[playIndex].Finished -= OnAnimationFinished;
+ animations[playIndex].Pause();
+
+ ChangeAnimatorState(AnimatorState.Paused, GetNameByIndex(playIndex));
+ }
+ }
+ }
+}
diff --git a/src/Tizen.NUI.AIAvatar/src/Animator/Utility/AnimationLoader.cs b/src/Tizen.NUI.AIAvatar/src/Animator/Utility/AnimationLoader.cs
new file mode 100644
index 00000000000..8f54fb96325
--- /dev/null
+++ b/src/Tizen.NUI.AIAvatar/src/Animator/Utility/AnimationLoader.cs
@@ -0,0 +1,208 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.Collections.Generic;
+using System.IO;
+using System.Security;
+using System;
+using Tizen.NUI.Scene3D;
+using Tizen.NUI;
+using System.Text.Json;
+using System.Linq;
+using System.Xml.Linq;
+using System.ComponentModel;
+
+namespace Tizen.NUI.AIAvatar
+{
+ ///
+ /// The AnimationLoader class provides methods to load and manage body and face motion data from specified resources.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class AnimationLoader
+ {
+ ///
+ /// A singleton instance of the AnimationLoader class.
+ ///
+ private static readonly Lazy instance = new Lazy(() => new AnimationLoader());
+
+ ///
+ /// Gets the singleton instance of the AnimationLoader class.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static AnimationLoader Instance => instance.Value;
+
+
+ ///
+ /// Loads a single body motion from the specified resource path.
+ ///
+ /// The path to the body motion resource file.
+ /// A boolean indicating whether to use only root translation.
+ /// The scale factor to apply to the motion data.
+ /// A boolean indicating whether to load the data synchronously.
+ /// A MotionInfo object containing the loaded body motion data.
+ /// Thrown if there is an error loading the body motion data.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public MotionInfo LoadBodyMotion(in string resourcePath, bool useRootTranslationOnly, Vector3 scale = null, bool synchronousLoad = false)
+ {
+ try
+ {
+ string fileName = global::System.IO.Path.GetFileNameWithoutExtension(resourcePath);
+ var bodyMotionData = new MotionData();
+ bodyMotionData.LoadMotionCaptureAnimation(resourcePath, useRootTranslationOnly, scale, synchronousLoad);
+ return new MotionInfo(bodyMotionData, fileName);
+ }
+ catch (Exception ex)
+ {
+ throw new Exception($"Error loading body motion data from {resourcePath}: {ex}");
+ }
+ }
+
+ ///
+ /// Loads multiple body motions from the specified directory path.
+ ///
+ /// The path to the directory containing body motion resource files.
+ /// A boolean indicating whether to use only root translation.
+ /// The scale factor to apply to the motion data.
+ /// A boolean indicating whether to load the data synchronously.
+ /// A list of MotionInfo objects containing the loaded body motion data.
+ /// Thrown if there is an error reading the body motion resource directory.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public List LoadBodyMotions(in string bodyMotionDirectoryPath, bool useRootTranslationOnly, Vector3 scale = null, bool synchronousLoad = false)
+ {
+ try
+ {
+ var motionInfoList = new List();
+ var bodyMotionAnimations = Directory.GetFiles(bodyMotionDirectoryPath, "*.bvh");
+
+ foreach (var path in bodyMotionAnimations)
+ {
+ motionInfoList.Add(LoadBodyMotion(path, useRootTranslationOnly, scale, synchronousLoad));
+ }
+ return motionInfoList;
+ }
+ catch (DirectoryNotFoundException ex)
+ {
+ throw new Exception($"Body motion resource directory not found: {ex.Message}");
+ }
+ catch (IOException ex)
+ {
+ throw new Exception($"Error reading body motion resource directory: {ex.Message}");
+ }
+ catch (SecurityException ex)
+ {
+ throw new Exception($"Security error reading body motion resource directory: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Loads a single face motion from the specified resource path.
+ ///
+ /// The path to the face motion resource file.
+ /// A MotionInfo object containing the loaded face motion data.
+ /// Thrown if there is an error loading the face animation data.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public MotionInfo LoadFaceMotion(in string resourcePath)
+ {
+ try
+ {
+ string fileName = global::System.IO.Path.GetFileNameWithoutExtension(resourcePath);
+ string json = File.ReadAllText(resourcePath);
+ var faceAnimationData = JsonSerializer.Deserialize(json);
+ var motionData = CreateFacialMotionData(faceAnimationData);
+ return new MotionInfo(motionData, fileName);
+ }
+ catch (JsonException ex)
+ {
+ throw new Exception($"Error loading face animation data from {resourcePath}: {ex}");
+ }
+ }
+
+ ///
+ /// Loads multiple face motions from the specified directory path.
+ ///
+ /// The path to the directory containing face motion resource files.
+ /// A list of MotionInfo objects containing the loaded face motion data.
+ /// Thrown if there is an error reading the face motion resource directory.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public List LoadFaceMotions(in string faceMotionDirectoryPath)
+ {
+ try
+ {
+ var motionInfoList = new List();
+ var faceMotionAnimations = Directory.GetFiles(faceMotionDirectoryPath, "*.json");
+
+ foreach (var path in faceMotionAnimations)
+ {
+ motionInfoList.Add(LoadFaceMotion(path));
+ }
+ return motionInfoList;
+ }
+ catch (DirectoryNotFoundException ex)
+ {
+ throw new Exception($"Face motion resource directory not found: {ex.Message}");
+ }
+ catch (IOException ex)
+ {
+ throw new Exception($"Error reading face motion resource directory: {ex.Message}");
+ }
+ catch (SecurityException ex)
+ {
+ throw new Exception($"Security error reading face motion resource directory: {ex.Message}");
+ }
+ }
+
+
+ internal MotionData CreateFacialMotionData(FaceAnimationData facialAnimation, List ignoreBlendShapes = null)
+ {
+
+ int frames = facialAnimation.frames;
+
+ if (frames == 0) return null;
+
+ int endTime = facialAnimation.time[frames - 1] + 200;
+ MotionData motionData = new MotionData((int)(endTime * 1.5));
+
+
+ foreach (var blendshape in facialAnimation.blendShapes)
+ {
+ using var modelNodeID = new PropertyKey(blendshape.name);
+ IgnoreBlendShape ignoreBS = ignoreBlendShapes?.FirstOrDefault(x => x.name == blendshape.name);
+
+
+ for (int target = 0; target < blendshape.morphtarget; target++)
+ {
+ if (ignoreBS != null && ignoreBS.morphname.Contains(blendshape.morphname[target])) continue;
+
+ using var keyFrames = new KeyFrames();
+ using var blendShapeID = new PropertyKey(blendshape.morphname[target]);
+ using var blendshapeIndex = new BlendShapeIndex(modelNodeID, blendShapeID);
+
+ for (int frame = 0; frame < frames; frame++)
+ {
+ keyFrames.Add((float)facialAnimation.time[frame] / endTime, blendshape.key[frame][target]);
+ }
+ keyFrames.Add((float)(facialAnimation.time[frames - 1] + 200) / endTime, 0.0f);
+
+ using var motionValue = new MotionValue(keyFrames);
+
+ motionData.Add(blendshapeIndex, motionValue);
+ }
+ }
+
+ return motionData;
+ }
+ }
+}
diff --git a/src/Tizen.NUI.AIAvatar/src/Animator/Utility/FaceAnimationData.cs b/src/Tizen.NUI.AIAvatar/src/Animator/Utility/FaceAnimationData.cs
new file mode 100644
index 00000000000..a6055cd9796
--- /dev/null
+++ b/src/Tizen.NUI.AIAvatar/src/Animator/Utility/FaceAnimationData.cs
@@ -0,0 +1,62 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.Collections.Generic;
+using System.Linq.Expressions;
+
+namespace Tizen.NUI.AIAvatar
+{
+ internal class FaceAnimationData
+ {
+ public string name { get; set; }
+ public string version { get; set; }
+ public List blendShapes { get; set; }
+ public int shapesAmount { get; set; }
+ public List time { get; set; }
+ public int frames { get; set; }
+ }
+ internal class FaceAnimBlendShape
+ {
+ public string name { get; set; }
+ public string fullName { get; set; }
+ public string blendShapeVersion { get; set; }
+ public int morphtarget { get; set; }
+ public List morphname { get; set; }
+ public List> key { get; set; }
+ }
+
+
+ internal class Expression
+ {
+ public string name { get; set; }
+ public List filename { get; set; }
+ }
+
+ internal class IgnoreBlendShape
+ {
+ public string name { get; set; }
+ public List morphname { get; set; }
+ }
+
+
+ internal class EmotionConfig
+ {
+ public List expressions { get; set; }
+ public List ignoreBlendShapes { get; set; }
+ }
+
+
+}
diff --git a/src/Tizen.AIAvatar/src/Common/AvatarInfo.cs b/src/Tizen.NUI.AIAvatar/src/Common/AvatarInfo.cs
similarity index 92%
rename from src/Tizen.AIAvatar/src/Common/AvatarInfo.cs
rename to src/Tizen.NUI.AIAvatar/src/Common/AvatarInfo.cs
index 3a4cf3d6133..74440f9f79e 100644
--- a/src/Tizen.AIAvatar/src/Common/AvatarInfo.cs
+++ b/src/Tizen.NUI.AIAvatar/src/Common/AvatarInfo.cs
@@ -17,8 +17,6 @@
using System.ComponentModel;
-using static Tizen.AIAvatar.AIAvatar;
-
namespace Tizen.AIAvatar
{
///
@@ -76,11 +74,10 @@ public AvatarInfo(string name, string path, AvatarInfoOption info = AvatarInfoOp
}
}
- internal AvatarInfo(string directoryPath)
- {
- string path = ApplicationResourcePath + EmojiAvatarResourcePath;
- Name = directoryPath.Substring(path.Length, directoryPath.Length - path.Length);
- ResourcePath = $"{directoryPath}/{AIAvatar.ExternalModel}";
+ internal AvatarInfo(string avatarPath)
+ {
+ Name = global::System.IO.Path.GetFileNameWithoutExtension(avatarPath);
+ ResourcePath = avatarPath;
}
}
diff --git a/src/Tizen.AIAvatar/src/Common/AvatarProperties.cs b/src/Tizen.NUI.AIAvatar/src/Common/Properties/AvatarProperties.cs
similarity index 98%
rename from src/Tizen.AIAvatar/src/Common/AvatarProperties.cs
rename to src/Tizen.NUI.AIAvatar/src/Common/Properties/AvatarProperties.cs
index 6a3f73ffde9..5a338bd60f0 100644
--- a/src/Tizen.AIAvatar/src/Common/AvatarProperties.cs
+++ b/src/Tizen.NUI.AIAvatar/src/Common/Properties/AvatarProperties.cs
@@ -19,7 +19,7 @@
using System.ComponentModel;
using Tizen.NUI.Scene3D;
-namespace Tizen.AIAvatar
+namespace Tizen.NUI.AIAvatar
{
///
@@ -29,7 +29,7 @@ namespace Tizen.AIAvatar
/// This structure enables users to work with Avatar properties in a more convenient way.
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public class AvatarProperties
+ internal abstract class AvatarProperties
{
private AvatarPropertyMapper jointMapper;
private AvatarPropertyMapper blendShapeMapper;
diff --git a/src/Tizen.AIAvatar/src/Common/AvatarPropertyMapper.cs b/src/Tizen.NUI.AIAvatar/src/Common/Properties/AvatarPropertyMapper.cs
similarity index 94%
rename from src/Tizen.AIAvatar/src/Common/AvatarPropertyMapper.cs
rename to src/Tizen.NUI.AIAvatar/src/Common/Properties/AvatarPropertyMapper.cs
index 8098dd6e92b..ae4d96ce676 100644
--- a/src/Tizen.AIAvatar/src/Common/AvatarPropertyMapper.cs
+++ b/src/Tizen.NUI.AIAvatar/src/Common/Properties/AvatarPropertyMapper.cs
@@ -18,7 +18,7 @@
using System.Collections.Generic;
using System.ComponentModel;
-namespace Tizen.AIAvatar
+namespace Tizen.NUI.AIAvatar
{
///
/// The AvatarPropertyMapper class manages property mapping information for specific attributes of an Avatar model.
@@ -27,7 +27,7 @@ namespace Tizen.AIAvatar
/// This approach provides consistency and convenience when working with Avatar models.
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public class AvatarPropertyMapper
+ internal class AvatarPropertyMapper
{
///
/// Mapper between index and property name
@@ -161,5 +161,15 @@ public string GetPropertyName(uint index)
mapper.TryGetValue(index, out ret);
return ret;
}
+
+ ///
+ /// Clear AvatarPropertyNameMapper.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Clear()
+ {
+ mapper.Clear();
+ customIndexCounter = 0u;
+ }
}
}
diff --git a/src/Tizen.AIAvatar/src/Internal/DefaultAvatarProperties.cs b/src/Tizen.NUI.AIAvatar/src/Common/Properties/EmojiAvatarProperties.cs
similarity index 74%
rename from src/Tizen.AIAvatar/src/Internal/DefaultAvatarProperties.cs
rename to src/Tizen.NUI.AIAvatar/src/Common/Properties/EmojiAvatarProperties.cs
index 57fe550e5dc..bda0975c038 100644
--- a/src/Tizen.AIAvatar/src/Internal/DefaultAvatarProperties.cs
+++ b/src/Tizen.NUI.AIAvatar/src/Common/Properties/EmojiAvatarProperties.cs
@@ -17,15 +17,21 @@
using System.Collections.Generic;
-namespace Tizen.AIAvatar
+namespace Tizen.NUI.AIAvatar
{
- internal class DefaultAvatarProperties : AvatarProperties
+ ///
+ /// Represents properties specific to an Emoji avatar, including mappings for joints, blend shapes, and nodes.
+ ///
+ internal class EmojiAvatarProperties : AvatarProperties
{
private static AvatarPropertyMapper defaultJointMapper = new AvatarPropertyMapper();
private static AvatarPropertyMapper defaultBlendShapeNameMapper = new AvatarPropertyMapper();
private static AvatarPropertyMapper defaultNodeMapper = new AvatarPropertyMapper();
- internal DefaultAvatarProperties() : base(defaultJointMapper, defaultBlendShapeNameMapper, defaultNodeMapper)
+ ///
+ /// Initializes a new instance of the EmojiAvatarProperties class using default mappers for joints, blend shapes, and nodes.
+ ///
+ public EmojiAvatarProperties() : base(defaultJointMapper, defaultBlendShapeNameMapper, defaultNodeMapper)
{
Initialize();
}
@@ -49,8 +55,74 @@ private void Initialize()
}
+ ///
+ /// Reinitializes the property mappings for joints and nodes.
+ ///
+ public void Reinitialize()
+ {
+ BlendShapeMapper.Clear();
+ JointMapper.Clear();
+ NodeMapper.Clear();
+ Initialize();
+ }
+
+ ///
+ /// Updates a blend shape mapping.
+ ///
+ /// The blend shape type identifier.
+ /// The blend shape name.
+ public void UpdateBlendShapeMapping(BlendShapeType key, string value)
+ {
+ var existingIndex = blendShapeList.FindIndex(item => item.Item1 == key);
+ if (existingIndex >= 0)
+ {
+ blendShapeList[existingIndex] = (key, value);
+ }
+ else
+ {
+ throw new KeyNotFoundException("Blend shape type identifier not found.");
+ }
+ }
+
+ ///
+ /// Updates a joint mapping.
+ ///
+ /// The joint type identifier.
+ /// The joint name.
+ public void UpdateJointMapping(JointType key, string value)
+ {
+ var existingIndex = jointList.FindIndex(item => item.Item1 == key);
+ if (existingIndex >= 0)
+ {
+ jointList[existingIndex] = (key, value);
+ }
+ else
+ {
+ throw new KeyNotFoundException("Joint type identifier not found.");
+ }
+ }
+
+ ///
+ /// Updates a node mapping.
+ ///
+ /// The node type identifier.
+ /// The node name.
+ public void UpdateNodeMapping(NodeType key, string value)
+ {
+ var existingIndex = nodeList.FindIndex(item => item.Item1 == key);
+ if (existingIndex >= 0)
+ {
+ nodeList[existingIndex] = (key, value);
+ }
+ else
+ {
+ throw new KeyNotFoundException("Node type identifier not found.");
+ }
+ }
+
+
#region AR Emoji BlendShape name list
- private static readonly List<(BlendShapeType, string)> blendShapeList = new List<(BlendShapeType, string)>()
+ private List<(BlendShapeType, string)> blendShapeList = new List<(BlendShapeType, string)>()
{
(BlendShapeType.EyeBlinkLeft, "EyeBlink_Left"),
(BlendShapeType.EyeSquintLeft, "EyeSquint_Left"),
@@ -119,7 +191,7 @@ private void Initialize()
#endregion
#region AR Emoji Joint name list
- private static readonly List<(JointType, string)> jointList = new List<(JointType, string)>
+ private List<(JointType, string)> jointList = new List<(JointType, string)>
{
(JointType.Head, "head_JNT"),
(JointType.Neck, "neck_JNT"),
@@ -189,7 +261,7 @@ private void Initialize()
#endregion
#region AR Emoji Joint name list
- private static readonly List<(NodeType, string)> nodeList = new List<(NodeType, string)>
+ private List<(NodeType, string)> nodeList = new List<(NodeType, string)>
{
(NodeType.HeadGeo, "head_GEO"),
(NodeType.MouthGeo, "mouth_GEO"),
diff --git a/src/Tizen.AIAvatar/src/Common/BlendShapeType.cs b/src/Tizen.NUI.AIAvatar/src/Common/Properties/EnumType/BlendShapeType.cs
similarity index 99%
rename from src/Tizen.AIAvatar/src/Common/BlendShapeType.cs
rename to src/Tizen.NUI.AIAvatar/src/Common/Properties/EnumType/BlendShapeType.cs
index 78c27f796fb..b25b0582ab7 100644
--- a/src/Tizen.AIAvatar/src/Common/BlendShapeType.cs
+++ b/src/Tizen.NUI.AIAvatar/src/Common/Properties/EnumType/BlendShapeType.cs
@@ -17,7 +17,7 @@
using System.ComponentModel;
-namespace Tizen.AIAvatar
+namespace Tizen.NUI.AIAvatar
{
///
/// The type of predefined blendshape. We can customize each type name by "TODO_mapper"
@@ -25,7 +25,7 @@ namespace Tizen.AIAvatar
/// Contains the BlendShape information of AIAvatar.
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public enum BlendShapeType
+ internal enum BlendShapeType
{
#region Left Eyes
///
diff --git a/src/Tizen.AIAvatar/src/Common/JointType.cs b/src/Tizen.NUI.AIAvatar/src/Common/Properties/EnumType/JointType.cs
similarity index 99%
rename from src/Tizen.AIAvatar/src/Common/JointType.cs
rename to src/Tizen.NUI.AIAvatar/src/Common/Properties/EnumType/JointType.cs
index 78589bf9369..e7d22c5de89 100644
--- a/src/Tizen.AIAvatar/src/Common/JointType.cs
+++ b/src/Tizen.NUI.AIAvatar/src/Common/Properties/EnumType/JointType.cs
@@ -17,7 +17,7 @@
using System.ComponentModel;
-namespace Tizen.AIAvatar
+namespace Tizen.NUI.AIAvatar
{
///
/// The type of predefined skeleton joint. We can customize each type name by "TODO_mapper"
@@ -25,7 +25,7 @@ namespace Tizen.AIAvatar
/// Contains the joint information of AIAvatar.
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public enum JointType
+ internal enum JointType
{
#region Head
///
diff --git a/src/Tizen.AIAvatar/src/Common/NodeType.cs b/src/Tizen.NUI.AIAvatar/src/Common/Properties/EnumType/NodeType.cs
similarity index 96%
rename from src/Tizen.AIAvatar/src/Common/NodeType.cs
rename to src/Tizen.NUI.AIAvatar/src/Common/Properties/EnumType/NodeType.cs
index 784f2061f6d..5710eca691a 100644
--- a/src/Tizen.AIAvatar/src/Common/NodeType.cs
+++ b/src/Tizen.NUI.AIAvatar/src/Common/Properties/EnumType/NodeType.cs
@@ -17,7 +17,7 @@
using System.ComponentModel;
-namespace Tizen.AIAvatar
+namespace Tizen.NUI.AIAvatar
{
///
/// The type of predefined node. We can customize each type name by "TODO_mapper"
@@ -25,7 +25,7 @@ namespace Tizen.AIAvatar
/// Contains the node information of AIAvatar.
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public enum NodeType
+ internal enum NodeType
{
///
/// head geometry
diff --git a/src/Tizen.AIAvatar/src/Internal/AvatarBlendShapeIndex.cs b/src/Tizen.NUI.AIAvatar/src/Common/Properties/MotionIndex/AvatarBlendShapeIndex.cs
similarity index 84%
rename from src/Tizen.AIAvatar/src/Internal/AvatarBlendShapeIndex.cs
rename to src/Tizen.NUI.AIAvatar/src/Common/Properties/MotionIndex/AvatarBlendShapeIndex.cs
index c2185dd6ea5..60d3f4d9ecf 100644
--- a/src/Tizen.AIAvatar/src/Internal/AvatarBlendShapeIndex.cs
+++ b/src/Tizen.NUI.AIAvatar/src/Common/Properties/MotionIndex/AvatarBlendShapeIndex.cs
@@ -19,7 +19,7 @@
using Tizen.NUI;
using Tizen.NUI.Scene3D;
-namespace Tizen.AIAvatar
+namespace Tizen.NUI.AIAvatar
{
///
/// Specialized to control avatar blend shape.
@@ -36,35 +36,35 @@ namespace Tizen.AIAvatar
[EditorBrowsable(EditorBrowsableState.Never)]
internal class AvatarBlendShapeIndex : BlendShapeIndex
{
- internal AvatarPropertyMapper nameMapper = null;
- internal uint blendShapeType;
+ public AvatarPropertyMapper nameMapper = null;
+ public uint blendShapeType;
- internal AvatarBlendShapeIndex(AvatarPropertyMapper mapper) : base()
+ public AvatarBlendShapeIndex(AvatarPropertyMapper mapper) : base()
{
nameMapper = mapper;
}
- internal AvatarBlendShapeIndex(AvatarPropertyMapper mapper, PropertyKey blendShapeId) : base(new PropertyKey(GetPropertyNameFromMapper(mapper, blendShapeId)))
+ public AvatarBlendShapeIndex(AvatarPropertyMapper mapper, PropertyKey blendShapeId) : base(new PropertyKey(GetPropertyNameFromMapper(mapper, blendShapeId)))
{
nameMapper = mapper;
}
- internal AvatarBlendShapeIndex(AvatarPropertyMapper nodeMapper, NodeType nodeType, PropertyKey blendShapeId)
+ public AvatarBlendShapeIndex(AvatarPropertyMapper nodeMapper, NodeType nodeType, PropertyKey blendShapeId)
: base(new PropertyKey(GetPropertyNameFromMapper(nodeMapper, (uint)nodeType)), blendShapeId)
{
}
- internal AvatarBlendShapeIndex(AvatarPropertyMapper blendShapeMapper, BlendShapeType blendShapeType) : this(blendShapeMapper, (uint)blendShapeType)
+ public AvatarBlendShapeIndex(AvatarPropertyMapper blendShapeMapper, BlendShapeType blendShapeType) : this(blendShapeMapper, (uint)blendShapeType)
{
}
- internal AvatarBlendShapeIndex(AvatarPropertyMapper mapper, uint blendShapeType) : base(new PropertyKey(GetPropertyNameFromMapper(mapper, blendShapeType)))
+ public AvatarBlendShapeIndex(AvatarPropertyMapper mapper, uint blendShapeType) : base(new PropertyKey(GetPropertyNameFromMapper(mapper, blendShapeType)))
{
nameMapper = mapper;
this.blendShapeType = blendShapeType;
}
- internal AvatarBlendShapeIndex(AvatarPropertyMapper nodeMapper, NodeType nodeType, AvatarPropertyMapper blendShapeMapper, BlendShapeType blendShapeType)
+ public AvatarBlendShapeIndex(AvatarPropertyMapper nodeMapper, NodeType nodeType, AvatarPropertyMapper blendShapeMapper, BlendShapeType blendShapeType)
: base(new PropertyKey(GetPropertyNameFromMapper(nodeMapper, (uint)nodeType)), new PropertyKey(GetPropertyNameFromMapper(blendShapeMapper, (uint)blendShapeType)))
{
nameMapper = blendShapeMapper;
diff --git a/src/Tizen.AIAvatar/src/Internal/AvatarJointTransformIndex.cs b/src/Tizen.NUI.AIAvatar/src/Common/Properties/MotionIndex/AvatarJointTransformIndex.cs
similarity index 99%
rename from src/Tizen.AIAvatar/src/Internal/AvatarJointTransformIndex.cs
rename to src/Tizen.NUI.AIAvatar/src/Common/Properties/MotionIndex/AvatarJointTransformIndex.cs
index bf21dde3405..80d59fc4c9f 100644
--- a/src/Tizen.AIAvatar/src/Internal/AvatarJointTransformIndex.cs
+++ b/src/Tizen.NUI.AIAvatar/src/Common/Properties/MotionIndex/AvatarJointTransformIndex.cs
@@ -19,7 +19,7 @@
using Tizen.NUI;
using Tizen.NUI.Scene3D;
-namespace Tizen.AIAvatar
+namespace Tizen.NUI.AIAvatar
{
///
/// Specialized to control avatar joint transform.
diff --git a/src/Tizen.AIAvatar/src/Extensions/Interop.SceneView.cs b/src/Tizen.NUI.AIAvatar/src/Extensions/Interop.SceneView.cs
similarity index 100%
rename from src/Tizen.AIAvatar/src/Extensions/Interop.SceneView.cs
rename to src/Tizen.NUI.AIAvatar/src/Extensions/Interop.SceneView.cs
diff --git a/src/Tizen.AIAvatar/src/Extensions/SceneViewExtension.cs b/src/Tizen.NUI.AIAvatar/src/Extensions/SceneViewExtension.cs
similarity index 60%
rename from src/Tizen.AIAvatar/src/Extensions/SceneViewExtension.cs
rename to src/Tizen.NUI.AIAvatar/src/Extensions/SceneViewExtension.cs
index ae8552fe86a..65144d63a35 100644
--- a/src/Tizen.AIAvatar/src/Extensions/SceneViewExtension.cs
+++ b/src/Tizen.NUI.AIAvatar/src/Extensions/SceneViewExtension.cs
@@ -20,9 +20,17 @@
namespace Tizen.AIAvatar
{
+ ///
+ /// Extension methods for SceneView to manipulate alpha mask URL, mask content scale factor, and crop to mask settings.
+ ///
[EditorBrowsable(EditorBrowsableState.Never)]
public static class SceneViewExtension
{
+ ///
+ /// Sets the URL of the alpha mask for the SceneView.
+ ///
+ /// The SceneView instance to apply the property.
+ /// The URL of the alpha mask image.
[EditorBrowsable(EditorBrowsableState.Never)]
public static void SetAlphaMaskUrl(this SceneView sceneView, string url)
{
@@ -31,6 +39,11 @@ public static void SetAlphaMaskUrl(this SceneView sceneView, string url)
setValue.Dispose();
}
+ ///
+ /// Retrieves the URL of the alpha mask for the SceneView.
+ ///
+ /// The SceneView instance to retrieve the property from.
+ /// The URL of the alpha mask image.
[EditorBrowsable(EditorBrowsableState.Never)]
public static string GetAlphaMaskUrl(this SceneView sceneView)
{
@@ -41,6 +54,11 @@ public static string GetAlphaMaskUrl(this SceneView sceneView)
return returnValue;
}
+ ///
+ /// Sets the content scale factor for the mask applied to the SceneView.
+ ///
+ /// The SceneView instance to apply the property.
+ /// The scaling factor for the mask content.
[EditorBrowsable(EditorBrowsableState.Never)]
public static void SetMaskContentScaleFactor(this SceneView sceneView, float factor)
{
@@ -49,6 +67,11 @@ public static void SetMaskContentScaleFactor(this SceneView sceneView, float fac
setValue.Dispose();
}
+ ///
+ /// Retrieves the content scale factor for the mask applied to the SceneView.
+ ///
+ /// The SceneView instance to retrieve the property from.
+ /// The scaling factor for the mask content.
[EditorBrowsable(EditorBrowsableState.Never)]
public static float GetMaskContentScaleFactor(this SceneView sceneView)
{
@@ -59,6 +82,11 @@ public static float GetMaskContentScaleFactor(this SceneView sceneView)
return returnValue;
}
+ ///
+ /// Enables or disables cropping to the mask for the SceneView.
+ ///
+ /// The SceneView instance to apply the property.
+ /// True to enable cropping to the mask, false otherwise.
[EditorBrowsable(EditorBrowsableState.Never)]
public static void EnableCropToMask(this SceneView sceneView, bool enableCropToMask)
{
@@ -67,6 +95,11 @@ public static void EnableCropToMask(this SceneView sceneView, bool enableCropToM
setValue.Dispose();
}
+ ///
+ /// Checks if cropping to the mask is enabled for the SceneView.
+ ///
+ /// The SceneView instance to retrieve the property from.
+ /// True if cropping to the mask is enabled, false otherwise.
[EditorBrowsable(EditorBrowsableState.Never)]
public static bool IsEnabledCropToMask(this SceneView sceneView)
{
diff --git a/src/Tizen.NUI.AIAvatar/src/LipSync/Data/LipData.cs b/src/Tizen.NUI.AIAvatar/src/LipSync/Data/LipData.cs
new file mode 100644
index 00000000000..c788ee819da
--- /dev/null
+++ b/src/Tizen.NUI.AIAvatar/src/LipSync/Data/LipData.cs
@@ -0,0 +1,118 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.Collections.Generic;
+using System.ComponentModel;
+
+namespace Tizen.NUI.AIAvatar
+{
+ internal class KeyFrame
+ {
+ public KeyFrame(float t, float v)
+ {
+ time = t;
+ value = v;
+ }
+ public float time;
+ public float value;
+ }
+
+ internal class LipData
+ {
+ private Dictionary[]> blendShapeKeyFrames;
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string[] NodeNames { get; private set; }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int[] BlendShapeCounts { get; private set; }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string BlendShapeKeyFormat { get; private set; }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public float Duration { get; private set; }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public LipData(string[] nodeNames,
+ int[] blendShapeCounts,
+ string blendShapeKeyFormat)
+ {
+ blendShapeKeyFrames = new Dictionary[]>();
+ NodeNames = nodeNames;
+ BlendShapeCounts = blendShapeCounts;
+ BlendShapeKeyFormat = blendShapeKeyFormat;
+
+ Initialize(nodeNames, blendShapeCounts);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetDuration(float duration)
+ {
+ Duration = duration;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public List GetKeyFrames(string nodeName, int blendShapeId)
+ {
+ return blendShapeKeyFrames[nodeName][blendShapeId];
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void AddKeyFrame(string nodeName, int blendShapeId, float time, float value)
+ {
+ blendShapeKeyFrames[nodeName][blendShapeId].Add(new KeyFrame(time, value));
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void AddKeyFrame(string nodeName, int blendShapeId, KeyFrame value)
+ {
+ blendShapeKeyFrames[nodeName][blendShapeId].Add(value);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Initialize(string[] nodeNames, int[] blendShapeCounts)
+ {
+ Clear();
+
+ for (int i = 0; i < nodeNames.Length; i++)
+ {
+ var nodeName = nodeNames[i];
+ var blendShapeCount = blendShapeCounts[i];
+
+ blendShapeKeyFrames.Add(nodeName, new List[blendShapeCount]);
+
+ for (int j = 0; j < blendShapeCount; j++)
+ {
+ blendShapeKeyFrames[nodeName][j] = new List();
+ }
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Clear()
+ {
+ foreach (KeyValuePair[]> blendShapeData in blendShapeKeyFrames)
+ {
+ foreach (List keyFrames in blendShapeData.Value)
+ {
+ keyFrames.Clear();
+ }
+ }
+ }
+ }
+}
diff --git a/src/Tizen.NUI.AIAvatar/src/LipSync/Data/VisemeData.cs b/src/Tizen.NUI.AIAvatar/src/LipSync/Data/VisemeData.cs
new file mode 100644
index 00000000000..91fbb4ea937
--- /dev/null
+++ b/src/Tizen.NUI.AIAvatar/src/LipSync/Data/VisemeData.cs
@@ -0,0 +1,103 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+using System.Linq;
+using System.Text.Json.Serialization;
+
+namespace Tizen.NUI.AIAvatar
+{
+ internal class VisemeData
+ {
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [JsonPropertyName("visemeParameters")]
+ public VisemeParameters visemeParameters { get; set; }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [JsonPropertyName("visemes")]
+ public List visemes { get; set; }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public VisemeData()
+ {
+ visemeParameters = new VisemeParameters();
+ visemes = new List();
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public Dictionary GetVisemeMap()
+ {
+ Dictionary visemeMap
+ = new Dictionary();
+
+ foreach (var viseme in this.visemes)
+ {
+ if (!visemeMap.ContainsKey(viseme.name))
+ {
+ visemeMap.Add(viseme.name, viseme.values.Select(v => new BlendShapeValue { nodeName = v.nodeName, id = v.id, value = (float)v.value }).ToArray());
+ }
+ }
+
+ return visemeMap;
+ }
+ }
+
+ internal class VisemeParameters
+ {
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [JsonPropertyName("keyFormat")]
+ public string keyFormat { get; set; }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [JsonPropertyName("nodeNames")]
+ public List nodeNames { get; set; }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [JsonPropertyName("blendShapeCount")]
+ public List blendShapeCount { get; set; }
+ }
+
+ internal class Viseme
+ {
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [JsonPropertyName("name")]
+ public string name { get; set; }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [JsonPropertyName("symbol")]
+ public string symbol { get; set; }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [JsonPropertyName("values")]
+ public List values { get; set; }
+ }
+
+
+ internal class BlendShapeValue
+ {
+ [JsonPropertyName("nodeName")]
+ public string nodeName { get; set; }
+
+ [JsonPropertyName("id")]
+ public int id { get; set; }
+
+ [JsonPropertyName("value")]
+ public float value { get; set; }
+ }
+}
diff --git a/src/Tizen.NUI.AIAvatar/src/LipSync/LipSyncTransformer.cs b/src/Tizen.NUI.AIAvatar/src/LipSync/LipSyncTransformer.cs
new file mode 100644
index 00000000000..7194818edd2
--- /dev/null
+++ b/src/Tizen.NUI.AIAvatar/src/LipSync/LipSyncTransformer.cs
@@ -0,0 +1,129 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+using System.IO;
+using System.Text.Json;
+
+namespace Tizen.NUI.AIAvatar
+{
+
+ internal class LipSyncTransformer
+ {
+ private LipData lipData;
+ private VisemeData visemeData;
+ private Dictionary visemeMap;
+
+ private bool isInitialized;
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public LipSyncTransformer() { }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Initialize(string visemeDefinitionPath)
+ {
+ try
+ {
+ using (StreamReader v = new StreamReader(visemeDefinitionPath))
+ {
+ var jsonString = v.ReadToEnd();
+
+ try
+ {
+ visemeData = JsonSerializer.Deserialize(jsonString);
+ visemeMap = visemeData.GetVisemeMap();
+ }catch (JsonException e)
+ {
+ Log.Error("Tizen.AIAvatar", e.Message);
+ }
+ }
+
+ var nodeNames = visemeData.visemeParameters.nodeNames;
+ var blendShapeCounts = visemeData.visemeParameters.blendShapeCount;
+ var blendShapeKeyFormat = visemeData.visemeParameters.keyFormat;
+
+ lipData = new LipData(nodeNames.ToArray(),
+ blendShapeCounts.ToArray(),
+ blendShapeKeyFormat);
+ isInitialized = true;
+ }
+ catch (FileNotFoundException e)
+ {
+ throw new FileNotFoundException($"File not found: {visemeDefinitionPath}", e);
+ }
+ catch (IOException e)
+ {
+ throw new IOException($"IO error occurred while reading file: {visemeDefinitionPath}", e);
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public LipData TransformVowelsToLipData(string[] vowels, float stepTime, bool isStreaming)
+ {
+ if (!isInitialized) throw new InvalidOperationException("LipSyncer is not initialized");
+
+ lipData.Clear();
+ VowelsToAnimationKeyFrame(vowels, stepTime, isStreaming);
+
+ return lipData;
+ }
+
+
+
+ private void VowelsToAnimationKeyFrame(in string[] visemes, float stepTime, bool isStreaming)
+ {
+ int numVisemes = visemes.Length;
+ float animationTime = 0f;
+
+ if (isStreaming)
+ {
+ animationTime = (numVisemes-1) * stepTime;
+ }
+ else
+ {
+ animationTime = numVisemes * stepTime;
+ }
+
+
+ lipData.SetDuration(animationTime);
+
+ for (int i = 0; i < numVisemes; i++)
+ {
+ float timeStamp = GetTimeStamp(i, stepTime) / animationTime;
+
+ foreach (var blendshape in visemeMap[visemes[i]])
+ {
+ lipData.AddKeyFrame(blendshape.nodeName,
+ blendshape.id,
+ timeStamp,
+ blendshape.value);
+ }
+ }
+ }
+
+
+ private float GetTimeStamp(int idx, float stepTime)
+ {
+ if (idx > 0)
+ return (idx * stepTime) - (stepTime / 8.0f);
+ else
+ return (stepTime / 8.0f);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Tizen.NUI.AIAvatar/src/LipSync/LipSyncer.cs b/src/Tizen.NUI.AIAvatar/src/LipSync/LipSyncer.cs
new file mode 100644
index 00000000000..073bc276cc3
--- /dev/null
+++ b/src/Tizen.NUI.AIAvatar/src/LipSync/LipSyncer.cs
@@ -0,0 +1,244 @@
+
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Tizen.NUI.Scene3D;
+
+namespace Tizen.NUI.AIAvatar
+{
+ ///
+ /// LipSyncer class manages the synchronization of an avatar's mouth movements with audio input.
+ /// It handles the creation, enqueuing, playing, pausing, and stopping of animations based on lip-sync data.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class LipSyncer
+ {
+ private Model avatar;
+ private Animation currentAnimation;
+ private Animation silenceAnimation;
+ private Queue queuedAnimations = new Queue();
+ private Timer animationTimer;
+
+ private readonly AlphaFunction alphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseInOut);
+ private readonly LipSyncTransformer lipSyncTransformer = new LipSyncTransformer();
+ private AnimatorState currentAnimatorState = AnimatorState.Unavailable;
+
+ private string prevVowel = "sil";
+
+ ///
+ /// Event triggered when the state of the animator changes.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler AnimatorStateChanged;
+
+ ///
+ /// Initializes a new instance of the LipSyncer class.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public LipSyncer()
+ {
+
+ }
+
+ ///
+ /// Initializes the LipSyncer with the given avatar and viseme definition path.
+ ///
+ /// The avatar to apply lip-sync animations to.
+ /// The path to the viseme definition file.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Initialize(Model avatar, string visemeDefinitionPath)
+ {
+ this.avatar = avatar;
+ lipSyncTransformer.Initialize(visemeDefinitionPath);
+ silenceAnimation = GenerateAnimationFromVowels(new string[] { "sil", "sil" }, 0.5f);
+ }
+
+ ///
+ /// Enqueues a lip-sync animation to be played later.
+ ///
+ /// The animation to enqueue.
+ /// is auto play (default is true).
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Enqueue(Animation lipAnimation, bool isAutoPlay = true)
+ {
+ queuedAnimations.Enqueue(lipAnimation);
+ if (isAutoPlay)
+ {
+ Play();
+ }
+ }
+
+ ///
+ /// Generates an animation from a list of vowels.
+ ///
+ /// The list of vowels to generate the animation from.
+ /// The time interval between each key frame (default is 0.08 seconds).
+ /// Indicates whether the animation is streaming (default is false).
+ /// The generated animation.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public Animation GenerateAnimationFromVowels(string[] vowels, float stepTime = 0.08f, bool isStreaming = false)
+ {
+ if (isStreaming)
+ {
+ var vowelList = new List(vowels);
+ vowelList.Insert(0, prevVowel);
+ prevVowel = vowelList[vowelList.Count - 1];
+ vowels = vowelList.ToArray();
+ }
+
+ var lipData = lipSyncTransformer.TransformVowelsToLipData(vowels, stepTime, isStreaming);
+ using var motionData = GenerateMotionFromLipData(lipData);
+ var animation = avatar.GenerateMotionDataAnimation(motionData);
+
+ return animation;
+ }
+
+
+ ///
+ /// Plays the current animation or dequeues and plays the next animation if available.
+ /// If no animations are queued, plays a silence animation.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Play()
+ {
+ if (animationTimer == null)
+ {
+ PlayNextAnimation(null, null);
+ return;
+ }
+
+ if (animationTimer != null && !animationTimer.IsRunning())
+ {
+ PlayNextAnimation(null, null);
+ }
+ }
+
+ ///
+ /// Pauses the current animation.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Pause()
+ {
+ if (currentAnimation != null)
+ {
+ currentAnimation.Pause();
+ CurrentAnimatorState = AnimatorState.Paused;
+ }
+ }
+
+ ///
+ /// Stops and disposes of the current animation.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Stop()
+ {
+ if (currentAnimation != null)
+ {
+ currentAnimation.Stop();
+ currentAnimation.Dispose();
+ currentAnimation = null;
+
+ queuedAnimations.Clear();
+ animationTimer.Stop();
+ animationTimer.Dispose();
+
+ CurrentAnimatorState = AnimatorState.Stopped;
+ }
+ }
+
+ ///
+ /// Gets or sets the current state of the animator.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AnimatorState CurrentAnimatorState
+ {
+ get => currentAnimatorState;
+ protected set
+ {
+ if (currentAnimatorState != AnimatorState.AnimationFinished && currentAnimatorState == value) return;
+
+ var preState = currentAnimatorState;
+ currentAnimatorState = value;
+
+ AnimatorStateChanged?.Invoke(this, new AnimatorChangedEventArgs(preState, currentAnimatorState));
+ }
+ }
+
+ private bool PlayNextAnimation(object source, Timer.TickEventArgs e)
+ {
+ // 현재 애니메이션을 정지 및 해제
+ currentAnimation?.Stop();
+ currentAnimation?.Dispose();
+ currentAnimation = null;
+
+ if (queuedAnimations.Count > 0)
+ {
+ currentAnimation = queuedAnimations.Dequeue();
+ currentAnimation.Play();
+ CurrentAnimatorState = AnimatorState.Playing;
+
+ // 현재 애니메이션의 Duration을 기준으로 타이머 설정
+ animationTimer = new Timer((uint)currentAnimation.Duration - 1); // 밀리초 단위
+ animationTimer.Tick += PlayNextAnimation;
+ animationTimer.Start();
+ }
+ else
+ {
+ prevVowel = "sil";
+ silenceAnimation.Play();
+ CurrentAnimatorState = AnimatorState.AnimationFinished;
+ }
+
+ return false;
+ }
+
+
+ private void OnAnimationFinished(object sender, EventArgs e)
+ {
+ currentAnimation.Dispose();
+ currentAnimation = null;
+ Play();
+ }
+
+ private MotionData GenerateMotionFromLipData(LipData animationKeyFrames)
+ {
+ int animationTime = (int)(animationKeyFrames.Duration * 1000f);
+
+ var motionData = new MotionData(animationTime);
+ for (var i = 0; i < animationKeyFrames.NodeNames.Length; i++)
+ {
+ string nodeName = animationKeyFrames.NodeNames[i];
+ for (var j = 0; j < animationKeyFrames.BlendShapeCounts[i]; j++)
+ {
+ using var modelNodeID = new PropertyKey(nodeName);
+ using var blendShapeID = new PropertyKey(j);
+ var blendShapeIndex = new BlendShapeIndex(modelNodeID, blendShapeID);
+ var keyFrameList = animationKeyFrames.GetKeyFrames(nodeName, j);
+ if (keyFrameList.Count == 0)
+ {
+ continue;
+ }
+
+ var keyFrames = CreateKeyTimeFrames(keyFrameList);
+
+ motionData.Add(blendShapeIndex, new MotionValue(keyFrames));
+ }
+ }
+
+ return motionData;
+ }
+
+ private KeyFrames CreateKeyTimeFrames(List keyFrameList)
+ {
+ var keyFrames = new KeyFrames();
+
+ foreach (var key in keyFrameList)
+ {
+ keyFrames.Add(key.time, key.value, alphaFunction);
+ }
+
+ return keyFrames;
+ }
+ }
+}
diff --git a/src/Tizen.NUI.AIAvatar/src/Motion/ICustomMotionData.cs b/src/Tizen.NUI.AIAvatar/src/Motion/ICustomMotionData.cs
new file mode 100644
index 00000000000..4b942f83dce
--- /dev/null
+++ b/src/Tizen.NUI.AIAvatar/src/Motion/ICustomMotionData.cs
@@ -0,0 +1,42 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+using Tizen.NUI.Scene3D;
+
+namespace Tizen.NUI.AIAvatar
+{
+ ///
+ /// Represents an interface for custom motion data operations.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public interface ICustomMotionData
+ {
+ ///
+ /// Initializes the instance of the custom motion data.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Initialize();
+
+ ///
+ /// Retrieves motion data based on the specified duration in milliseconds.
+ ///
+ /// The duration in milliseconds for which to retrieve motion data.
+ /// An instance of MotionData representing the motion data for the given duration.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public MotionData GetMotionData(int durationMilliseconds);
+ }
+}
diff --git a/src/Tizen.AIAvatar/src/Animations/AnimationInfo.cs b/src/Tizen.NUI.AIAvatar/src/Motion/MotionInfo.cs
similarity index 82%
rename from src/Tizen.AIAvatar/src/Animations/AnimationInfo.cs
rename to src/Tizen.NUI.AIAvatar/src/Motion/MotionInfo.cs
index c6da89f9e12..a0d76e53679 100644
--- a/src/Tizen.AIAvatar/src/Animations/AnimationInfo.cs
+++ b/src/Tizen.NUI.AIAvatar/src/Motion/MotionInfo.cs
@@ -15,16 +15,17 @@
*
*/
+using System;
using System.ComponentModel;
using Tizen.NUI.Scene3D;
-namespace Tizen.AIAvatar
+namespace Tizen.NUI.AIAvatar
{
///
/// The AnimationInfo class manages animation data for an Avatar, including motion data and names. It is not meant to be directly edited by users or editors.
///
[EditorBrowsable(EditorBrowsableState.Never)]
- public class AnimationInfo
+ public class MotionInfo
{
///
/// Gets the motion data associated with this animation.
@@ -38,14 +39,20 @@ public class AnimationInfo
[EditorBrowsable(EditorBrowsableState.Never)]
public string MotionName { get; private set; }
+
///
/// Initializes a new instance of the AnimationInfo class with the specified motion data and name.
///
- /// TheThe motion data associated with this animation.
+ /// The motion data associated with this animation.
/// The name of this animation.
[EditorBrowsable(EditorBrowsableState.Never)]
- public AnimationInfo(MotionData motionData, string motionName)
+ public MotionInfo(MotionData motionData, string motionName)
{
+ if (motionData == null)
+ {
+ throw new ArgumentNullException(nameof(motionData));
+ }
+
MotionData = motionData;
MotionName = motionName;
}
diff --git a/src/Tizen.NUI.PenWave/Properties/AssemblyInfo.cs b/src/Tizen.NUI.PenWave/Properties/AssemblyInfo.cs
new file mode 100755
index 00000000000..58d444169cb
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/Properties/AssemblyInfo.cs
@@ -0,0 +1,4 @@
+using Tizen.NUI;
+
+// Xamarin.Forms.Loader.dll Xamarin.Forms.Xaml.XamlLoader.Load(object, string), kzu@microsoft.com
+[assembly: XmlnsDefinition("http://tizen.org/Tizen.NUI/2018/XAML", "Tizen.NUI.PenWave")]
diff --git a/src/Tizen.NUI.PenWave/Tizen.NUI.PenWave.csproj b/src/Tizen.NUI.PenWave/Tizen.NUI.PenWave.csproj
new file mode 100755
index 00000000000..3d6d7e346b1
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/Tizen.NUI.PenWave.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net6.0
+ $(NoWarn);CS0618;CA1054;CA1056
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/Tizen.NUI.PenWave.sln b/src/Tizen.NUI.PenWave/Tizen.NUI.PenWave.sln
new file mode 100755
index 00000000000..ce4854b735a
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/Tizen.NUI.PenWave.sln
@@ -0,0 +1,79 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31515.178
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.NUI.PenWave", "Tizen.NUI.PenWave.csproj", "{F6CEE887-775A-4623-8BF8-DCA18C363D62}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.NUI", "..\Tizen.NUI\Tizen.NUI.csproj", "{F9DAA9C3-593D-467E-B02C-FFF51F1BC8CD}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.Applications.Common", "..\Tizen.Applications.Common\Tizen.Applications.Common.csproj", "{CE90CD24-82F7-45A3-96B2-2E3C97D25C30}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.System.SystemSettings", "..\Tizen.System.SystemSettings\Tizen.System.SystemSettings.csproj", "{D726EEB8-6382-4BA3-BE0C-D9E61B5D8374}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen", "..\Tizen\Tizen.csproj", "{6D5FFD69-6DCC-4953-85E9-C23AC18B190E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.System.Information", "..\Tizen.System.Information\Tizen.System.Information.csproj", "{A951EAFE-D191-4F45-9AEF-7D97C382A747}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.Log", "..\Tizen.Log\Tizen.Log.csproj", "{E1E30AEC-AD46-4E53-B9B1-780A68B59963}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.Applications.ComponentBased", "..\Tizen.Applications.ComponentBased\Tizen.Applications.ComponentBased.csproj", "{70341AA2-1324-4215-9DB8-BFB13389D932}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.Applications.ThemeManager", "..\Tizen.Applications.ThemeManager\Tizen.Applications.ThemeManager.csproj", "{F6A776BF-6743-4C1D-A8AF-F3E9F8CEFA0A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.Tracer", "..\Tizen.Tracer\Tizen.Tracer.csproj", "{6DABE78F-1816-4F2E-8966-F909173194C8}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F6CEE887-775A-4623-8BF8-DCA18C363D62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F6CEE887-775A-4623-8BF8-DCA18C363D62}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F6CEE887-775A-4623-8BF8-DCA18C363D62}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F6CEE887-775A-4623-8BF8-DCA18C363D62}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F9DAA9C3-593D-467E-B02C-FFF51F1BC8CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F9DAA9C3-593D-467E-B02C-FFF51F1BC8CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F9DAA9C3-593D-467E-B02C-FFF51F1BC8CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F9DAA9C3-593D-467E-B02C-FFF51F1BC8CD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CE90CD24-82F7-45A3-96B2-2E3C97D25C30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CE90CD24-82F7-45A3-96B2-2E3C97D25C30}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CE90CD24-82F7-45A3-96B2-2E3C97D25C30}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CE90CD24-82F7-45A3-96B2-2E3C97D25C30}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D726EEB8-6382-4BA3-BE0C-D9E61B5D8374}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D726EEB8-6382-4BA3-BE0C-D9E61B5D8374}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D726EEB8-6382-4BA3-BE0C-D9E61B5D8374}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D726EEB8-6382-4BA3-BE0C-D9E61B5D8374}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6D5FFD69-6DCC-4953-85E9-C23AC18B190E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6D5FFD69-6DCC-4953-85E9-C23AC18B190E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6D5FFD69-6DCC-4953-85E9-C23AC18B190E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6D5FFD69-6DCC-4953-85E9-C23AC18B190E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A951EAFE-D191-4F45-9AEF-7D97C382A747}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A951EAFE-D191-4F45-9AEF-7D97C382A747}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A951EAFE-D191-4F45-9AEF-7D97C382A747}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A951EAFE-D191-4F45-9AEF-7D97C382A747}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E1E30AEC-AD46-4E53-B9B1-780A68B59963}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E1E30AEC-AD46-4E53-B9B1-780A68B59963}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E1E30AEC-AD46-4E53-B9B1-780A68B59963}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E1E30AEC-AD46-4E53-B9B1-780A68B59963}.Release|Any CPU.Build.0 = Release|Any CPU
+ {70341AA2-1324-4215-9DB8-BFB13389D932}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {70341AA2-1324-4215-9DB8-BFB13389D932}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {70341AA2-1324-4215-9DB8-BFB13389D932}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {70341AA2-1324-4215-9DB8-BFB13389D932}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F6A776BF-6743-4C1D-A8AF-F3E9F8CEFA0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F6A776BF-6743-4C1D-A8AF-F3E9F8CEFA0A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F6A776BF-6743-4C1D-A8AF-F3E9F8CEFA0A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F6A776BF-6743-4C1D-A8AF-F3E9F8CEFA0A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6DABE78F-1816-4F2E-8966-F909173194C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6DABE78F-1816-4F2E-8966-F909173194C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6DABE78F-1816-4F2E-8966-F909173194C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6DABE78F-1816-4F2E-8966-F909173194C8}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {83C85CFB-3AB8-403A-9F6D-CC2783C6C559}
+ EndGlobalSection
+EndGlobal
diff --git a/src/Tizen.NUI.PenWave/res/fonts/font.ttf b/src/Tizen.NUI.PenWave/res/fonts/font.ttf
new file mode 100644
index 00000000000..b173da27417
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/fonts/font.ttf differ
diff --git a/src/Tizen.NUI.PenWave/res/images/dark/icon_grid.svg b/src/Tizen.NUI.PenWave/res/images/dark/icon_grid.svg
new file mode 100644
index 00000000000..470337d6e3f
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/dark/icon_grid.svg
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/dark/icon_insert.svg b/src/Tizen.NUI.PenWave/res/images/dark/icon_insert.svg
new file mode 100644
index 00000000000..74d7b5b5114
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/dark/icon_insert.svg
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/dark/icon_layers.svg b/src/Tizen.NUI.PenWave/res/images/dark/icon_layers.svg
new file mode 100644
index 00000000000..7a14a487e2e
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/dark/icon_layers.svg
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/dark/icon_more.svg b/src/Tizen.NUI.PenWave/res/images/dark/icon_more.svg
new file mode 100644
index 00000000000..3a2fa621fae
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/dark/icon_more.svg
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/dark/icon_redo.svg b/src/Tizen.NUI.PenWave/res/images/dark/icon_redo.svg
new file mode 100644
index 00000000000..9c11a1d1b49
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/dark/icon_redo.svg
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/dark/icon_rulers.svg b/src/Tizen.NUI.PenWave/res/images/dark/icon_rulers.svg
new file mode 100644
index 00000000000..6a166c9a48d
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/dark/icon_rulers.svg
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/dark/icon_select.svg b/src/Tizen.NUI.PenWave/res/images/dark/icon_select.svg
new file mode 100644
index 00000000000..c8a08a35a98
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/dark/icon_select.svg
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/dark/icon_session.svg b/src/Tizen.NUI.PenWave/res/images/dark/icon_session.svg
new file mode 100644
index 00000000000..93f3e49f78e
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/dark/icon_session.svg
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/dark/icon_template.svg b/src/Tizen.NUI.PenWave/res/images/dark/icon_template.svg
new file mode 100644
index 00000000000..8f2b1320c0a
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/dark/icon_template.svg
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/dark/icon_undo.svg b/src/Tizen.NUI.PenWave/res/images/dark/icon_undo.svg
new file mode 100644
index 00000000000..05c71d400ff
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/dark/icon_undo.svg
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/dark/icon_user - Copy.svg b/src/Tizen.NUI.PenWave/res/images/dark/icon_user - Copy.svg
new file mode 100644
index 00000000000..b4fea72f8d6
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/dark/icon_user - Copy.svg
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/dark/icon_user.svg b/src/Tizen.NUI.PenWave/res/images/dark/icon_user.svg
new file mode 100644
index 00000000000..b4fea72f8d6
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/dark/icon_user.svg
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/dark/slider_handler.png b/src/Tizen.NUI.PenWave/res/images/dark/slider_handler.png
new file mode 100755
index 00000000000..8cc7da3eda8
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/dark/slider_handler.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/canvas_popup_bg.png b/src/Tizen.NUI.PenWave/res/images/light/canvas_popup_bg.png
new file mode 100644
index 00000000000..472d708bf01
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/canvas_popup_bg.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/canvas_thumbnail.png b/src/Tizen.NUI.PenWave/res/images/light/canvas_thumbnail.png
new file mode 100644
index 00000000000..6751a9b0cea
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/canvas_thumbnail.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/chart_popup_bg.png b/src/Tizen.NUI.PenWave/res/images/light/chart_popup_bg.png
new file mode 100644
index 00000000000..2c411aa8d74
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/chart_popup_bg.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/checkbox.png b/src/Tizen.NUI.PenWave/res/images/light/checkbox.png
new file mode 100644
index 00000000000..9c394e68da4
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/checkbox.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/checkbox_selected.png b/src/Tizen.NUI.PenWave/res/images/light/checkbox_selected.png
new file mode 100644
index 00000000000..f3f66bfc24b
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/checkbox_selected.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/color_icon_base.png b/src/Tizen.NUI.PenWave/res/images/light/color_icon_base.png
new file mode 100644
index 00000000000..bd201be5673
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/color_icon_base.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/color_icon_selected.png b/src/Tizen.NUI.PenWave/res/images/light/color_icon_selected.png
new file mode 100644
index 00000000000..971fe87c42f
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/color_icon_selected.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/eraser_popup_bg.png b/src/Tizen.NUI.PenWave/res/images/light/eraser_popup_bg.png
new file mode 100644
index 00000000000..2c411aa8d74
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/eraser_popup_bg.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/floating_icon_bg.png b/src/Tizen.NUI.PenWave/res/images/light/floating_icon_bg.png
new file mode 100644
index 00000000000..2c411aa8d74
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/floating_icon_bg.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/grid_popup_bg.png b/src/Tizen.NUI.PenWave/res/images/light/grid_popup_bg.png
new file mode 100644
index 00000000000..2c411aa8d74
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/grid_popup_bg.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_3d.png b/src/Tizen.NUI.PenWave/res/images/light/icon_3d.png
new file mode 100644
index 00000000000..758a762d067
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_3d.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_add.png b/src/Tizen.NUI.PenWave/res/images/light/icon_add.png
new file mode 100644
index 00000000000..9641fe02756
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_add.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_add_page.png b/src/Tizen.NUI.PenWave/res/images/light/icon_add_page.png
new file mode 100644
index 00000000000..35e0ec3a882
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_add_page.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_ai_prediction.png b/src/Tizen.NUI.PenWave/res/images/light/icon_ai_prediction.png
new file mode 100644
index 00000000000..e8b688dd1f0
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_ai_prediction.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_btn_cancel.png b/src/Tizen.NUI.PenWave/res/images/light/icon_btn_cancel.png
new file mode 100755
index 00000000000..8ce15c3f6e8
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_btn_cancel.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_btn_connect.png b/src/Tizen.NUI.PenWave/res/images/light/icon_btn_connect.png
new file mode 100644
index 00000000000..517829b952c
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_btn_connect.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_btn_ok.png b/src/Tizen.NUI.PenWave/res/images/light/icon_btn_ok.png
new file mode 100644
index 00000000000..519cb4c835a
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_btn_ok.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_btn_share.png b/src/Tizen.NUI.PenWave/res/images/light/icon_btn_share.png
new file mode 100644
index 00000000000..3c7ab660d59
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_btn_share.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_chart.png b/src/Tizen.NUI.PenWave/res/images/light/icon_chart.png
new file mode 100644
index 00000000000..8f0eec0040f
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_chart.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_chart_add.png b/src/Tizen.NUI.PenWave/res/images/light/icon_chart_add.png
new file mode 100644
index 00000000000..e3fe938312b
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_chart_add.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_chart_mode.png b/src/Tizen.NUI.PenWave/res/images/light/icon_chart_mode.png
new file mode 100644
index 00000000000..c651df5fd17
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_chart_mode.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_circle.png b/src/Tizen.NUI.PenWave/res/images/light/icon_circle.png
new file mode 100644
index 00000000000..e1d152709dd
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_circle.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_clear.png b/src/Tizen.NUI.PenWave/res/images/light/icon_clear.png
new file mode 100644
index 00000000000..422794c44b1
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_clear.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_color_palette.png b/src/Tizen.NUI.PenWave/res/images/light/icon_color_palette.png
new file mode 100644
index 00000000000..7008968249d
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_color_palette.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_copy.png b/src/Tizen.NUI.PenWave/res/images/light/icon_copy.png
new file mode 100644
index 00000000000..19f67ad0f7f
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_copy.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_cut.png b/src/Tizen.NUI.PenWave/res/images/light/icon_cut.png
new file mode 100644
index 00000000000..c8b91288fa6
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_cut.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_dashed_line.png b/src/Tizen.NUI.PenWave/res/images/light/icon_dashed_line.png
new file mode 100644
index 00000000000..59f51cb4cbf
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_dashed_line.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_dot-10.png b/src/Tizen.NUI.PenWave/res/images/light/icon_dot-10.png
new file mode 100755
index 00000000000..b11118fcc78
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_dot-10.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_dot-25.png b/src/Tizen.NUI.PenWave/res/images/light/icon_dot-25.png
new file mode 100755
index 00000000000..8bd241e8b62
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_dot-25.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_dot-50.png b/src/Tizen.NUI.PenWave/res/images/light/icon_dot-50.png
new file mode 100755
index 00000000000..7925b8852dd
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_dot-50.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_dot-75.png b/src/Tizen.NUI.PenWave/res/images/light/icon_dot-75.png
new file mode 100644
index 00000000000..5f978b7b835
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_dot-75.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_dot.png b/src/Tizen.NUI.PenWave/res/images/light/icon_dot.png
new file mode 100755
index 00000000000..fdb9c2efeda
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_dot.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_draw_line.png b/src/Tizen.NUI.PenWave/res/images/light/icon_draw_line.png
new file mode 100644
index 00000000000..c3d6e8d8a9b
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_draw_line.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_draw_stroke.png b/src/Tizen.NUI.PenWave/res/images/light/icon_draw_stroke.png
new file mode 100644
index 00000000000..81817c6f09a
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_draw_stroke.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_eraser.png b/src/Tizen.NUI.PenWave/res/images/light/icon_eraser.png
new file mode 100644
index 00000000000..43f1242b81f
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_eraser.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_full_eraser.png b/src/Tizen.NUI.PenWave/res/images/light/icon_full_eraser.png
new file mode 100755
index 00000000000..3878b1db833
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_full_eraser.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_grid.png b/src/Tizen.NUI.PenWave/res/images/light/icon_grid.png
new file mode 100644
index 00000000000..9dbac3adf31
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_grid.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_grid.svg b/src/Tizen.NUI.PenWave/res/images/light/icon_grid.svg
new file mode 100644
index 00000000000..470337d6e3f
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/light/icon_grid.svg
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_highlighter.png b/src/Tizen.NUI.PenWave/res/images/light/icon_highlighter.png
new file mode 100644
index 00000000000..d85d9cc96b3
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_highlighter.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_insert.svg b/src/Tizen.NUI.PenWave/res/images/light/icon_insert.svg
new file mode 100644
index 00000000000..74d7b5b5114
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/light/icon_insert.svg
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_insert_page.png b/src/Tizen.NUI.PenWave/res/images/light/icon_insert_page.png
new file mode 100644
index 00000000000..e735571e66b
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_insert_page.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_large_grid_density.png b/src/Tizen.NUI.PenWave/res/images/light/icon_large_grid_density.png
new file mode 100644
index 00000000000..7c5b931b6c5
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_large_grid_density.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_layers.svg b/src/Tizen.NUI.PenWave/res/images/light/icon_layers.svg
new file mode 100644
index 00000000000..7a14a487e2e
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/light/icon_layers.svg
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_line.png b/src/Tizen.NUI.PenWave/res/images/light/icon_line.png
new file mode 100644
index 00000000000..c3d6e8d8a9b
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_line.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_medium_grid_density.png b/src/Tizen.NUI.PenWave/res/images/light/icon_medium_grid_density.png
new file mode 100644
index 00000000000..53a8e61a833
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_medium_grid_density.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_more.svg b/src/Tizen.NUI.PenWave/res/images/light/icon_more.svg
new file mode 100644
index 00000000000..3a2fa621fae
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/light/icon_more.svg
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_move.png b/src/Tizen.NUI.PenWave/res/images/light/icon_move.png
new file mode 100644
index 00000000000..0e53c2d667b
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_move.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_move_page.png b/src/Tizen.NUI.PenWave/res/images/light/icon_move_page.png
new file mode 100644
index 00000000000..032a9795ec3
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_move_page.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_multidrawing.png b/src/Tizen.NUI.PenWave/res/images/light/icon_multidrawing.png
new file mode 100644
index 00000000000..66014170888
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_multidrawing.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_note.png b/src/Tizen.NUI.PenWave/res/images/light/icon_note.png
new file mode 100644
index 00000000000..3b17737cc69
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_note.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_pages_menu.png b/src/Tizen.NUI.PenWave/res/images/light/icon_pages_menu.png
new file mode 100644
index 00000000000..db3afacd264
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_pages_menu.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_partial_eraser.png b/src/Tizen.NUI.PenWave/res/images/light/icon_partial_eraser.png
new file mode 100644
index 00000000000..2bbacc70ce8
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_partial_eraser.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_paste.png b/src/Tizen.NUI.PenWave/res/images/light/icon_paste.png
new file mode 100644
index 00000000000..cc94e180df0
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_paste.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_pencil.png b/src/Tizen.NUI.PenWave/res/images/light/icon_pencil.png
new file mode 100755
index 00000000000..a4c2a09cd9c
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_pencil.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_picture.png b/src/Tizen.NUI.PenWave/res/images/light/icon_picture.png
new file mode 100644
index 00000000000..8f2e04299a6
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_picture.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_rectangle.png b/src/Tizen.NUI.PenWave/res/images/light/icon_rectangle.png
new file mode 100644
index 00000000000..d22a9a2aff2
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_rectangle.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_redo.png b/src/Tizen.NUI.PenWave/res/images/light/icon_redo.png
new file mode 100644
index 00000000000..db304eef821
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_redo.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_redo.svg b/src/Tizen.NUI.PenWave/res/images/light/icon_redo.svg
new file mode 100644
index 00000000000..9c11a1d1b49
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/light/icon_redo.svg
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_remove.png b/src/Tizen.NUI.PenWave/res/images/light/icon_remove.png
new file mode 100755
index 00000000000..1a60a638531
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_remove.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_remove_page.png b/src/Tizen.NUI.PenWave/res/images/light/icon_remove_page.png
new file mode 100644
index 00000000000..942484c0cae
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_remove_page.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_rotate.png b/src/Tizen.NUI.PenWave/res/images/light/icon_rotate.png
new file mode 100644
index 00000000000..1d281b0700d
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_rotate.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_rulers copy.svg b/src/Tizen.NUI.PenWave/res/images/light/icon_rulers copy.svg
new file mode 100644
index 00000000000..6a166c9a48d
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/light/icon_rulers copy.svg
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_rulers.svg b/src/Tizen.NUI.PenWave/res/images/light/icon_rulers.svg
new file mode 100644
index 00000000000..6a166c9a48d
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/light/icon_rulers.svg
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_save.png b/src/Tizen.NUI.PenWave/res/images/light/icon_save.png
new file mode 100644
index 00000000000..e3fe938312b
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_save.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_save_pdf.png b/src/Tizen.NUI.PenWave/res/images/light/icon_save_pdf.png
new file mode 100644
index 00000000000..1dc44ffa8b9
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_save_pdf.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_scale.png b/src/Tizen.NUI.PenWave/res/images/light/icon_scale.png
new file mode 100644
index 00000000000..bee41ee3e58
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_scale.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_select.png b/src/Tizen.NUI.PenWave/res/images/light/icon_select.png
new file mode 100644
index 00000000000..10442f03bb9
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_select.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_select.svg b/src/Tizen.NUI.PenWave/res/images/light/icon_select.svg
new file mode 100644
index 00000000000..c8a08a35a98
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/light/icon_select.svg
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_select_area.png b/src/Tizen.NUI.PenWave/res/images/light/icon_select_area.png
new file mode 100644
index 00000000000..a7331c76c05
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_select_area.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_selection.png b/src/Tizen.NUI.PenWave/res/images/light/icon_selection.png
new file mode 100755
index 00000000000..9efb0b27d7d
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_selection.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_session.png b/src/Tizen.NUI.PenWave/res/images/light/icon_session.png
new file mode 100644
index 00000000000..123e809210b
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_session.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_session.svg b/src/Tizen.NUI.PenWave/res/images/light/icon_session.svg
new file mode 100644
index 00000000000..93f3e49f78e
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/light/icon_session.svg
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_settings.png b/src/Tizen.NUI.PenWave/res/images/light/icon_settings.png
new file mode 100644
index 00000000000..ad31fe1dd89
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_settings.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_shape.png b/src/Tizen.NUI.PenWave/res/images/light/icon_shape.png
new file mode 100644
index 00000000000..b34d68781c6
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_shape.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_share.png b/src/Tizen.NUI.PenWave/res/images/light/icon_share.png
new file mode 100755
index 00000000000..88348d14a67
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_share.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_sharp_brush.png b/src/Tizen.NUI.PenWave/res/images/light/icon_sharp_brush.png
new file mode 100644
index 00000000000..ff1d156ad90
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_sharp_brush.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_small_grid_density.png b/src/Tizen.NUI.PenWave/res/images/light/icon_small_grid_density.png
new file mode 100644
index 00000000000..5560b64e63c
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_small_grid_density.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_soft_brush.png b/src/Tizen.NUI.PenWave/res/images/light/icon_soft_brush.png
new file mode 100644
index 00000000000..081ffa53b4c
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_soft_brush.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_spray.png b/src/Tizen.NUI.PenWave/res/images/light/icon_spray.png
new file mode 100755
index 00000000000..b7b2aefae0a
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_spray.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_template.svg b/src/Tizen.NUI.PenWave/res/images/light/icon_template.svg
new file mode 100644
index 00000000000..8f2b1320c0a
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/light/icon_template.svg
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_text.png b/src/Tizen.NUI.PenWave/res/images/light/icon_text.png
new file mode 100644
index 00000000000..b410da24bba
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_text.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_turn_left.png b/src/Tizen.NUI.PenWave/res/images/light/icon_turn_left.png
new file mode 100644
index 00000000000..5f9e351bc8e
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_turn_left.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_turn_right.png b/src/Tizen.NUI.PenWave/res/images/light/icon_turn_right.png
new file mode 100644
index 00000000000..d631776c330
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_turn_right.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_undo.png b/src/Tizen.NUI.PenWave/res/images/light/icon_undo.png
new file mode 100644
index 00000000000..373e8391437
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_undo.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_undo.svg b/src/Tizen.NUI.PenWave/res/images/light/icon_undo.svg
new file mode 100644
index 00000000000..05c71d400ff
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/light/icon_undo.svg
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_user - Copy.svg b/src/Tizen.NUI.PenWave/res/images/light/icon_user - Copy.svg
new file mode 100644
index 00000000000..b4fea72f8d6
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/light/icon_user - Copy.svg
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_user.svg b/src/Tizen.NUI.PenWave/res/images/light/icon_user.svg
new file mode 100644
index 00000000000..b4fea72f8d6
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/res/images/light/icon_user.svg
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_varstroke_dec.png b/src/Tizen.NUI.PenWave/res/images/light/icon_varstroke_dec.png
new file mode 100644
index 00000000000..5c771573c73
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_varstroke_dec.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/icon_varstroke_inc.png b/src/Tizen.NUI.PenWave/res/images/light/icon_varstroke_inc.png
new file mode 100644
index 00000000000..af126d9b1ad
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/icon_varstroke_inc.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/menu_bg.png b/src/Tizen.NUI.PenWave/res/images/light/menu_bg.png
new file mode 100644
index 00000000000..6d9962fda71
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/menu_bg.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/picker_popup_bg.png b/src/Tizen.NUI.PenWave/res/images/light/picker_popup_bg.png
new file mode 100644
index 00000000000..2c411aa8d74
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/picker_popup_bg.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/session_bg.png b/src/Tizen.NUI.PenWave/res/images/light/session_bg.png
new file mode 100644
index 00000000000..f8087282ff1
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/session_bg.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/slider_handler.png b/src/Tizen.NUI.PenWave/res/images/light/slider_handler.png
new file mode 100755
index 00000000000..8cc7da3eda8
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/slider_handler.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/slider_normal.png b/src/Tizen.NUI.PenWave/res/images/light/slider_normal.png
new file mode 100644
index 00000000000..9c394e68da4
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/slider_normal.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/slider_pressed.png b/src/Tizen.NUI.PenWave/res/images/light/slider_pressed.png
new file mode 100644
index 00000000000..67dea85dfbe
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/slider_pressed.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/thumb_background.png b/src/Tizen.NUI.PenWave/res/images/light/thumb_background.png
new file mode 100644
index 00000000000..d372235ed31
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/thumb_background.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/thumb_selected_background.png b/src/Tizen.NUI.PenWave/res/images/light/thumb_selected_background.png
new file mode 100644
index 00000000000..6751a9b0cea
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/thumb_selected_background.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/toolbox_bg.png b/src/Tizen.NUI.PenWave/res/images/light/toolbox_bg.png
new file mode 100644
index 00000000000..1556bb7f0b1
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/toolbox_bg.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/warning_bg.png b/src/Tizen.NUI.PenWave/res/images/light/warning_bg.png
new file mode 100644
index 00000000000..159c4e8b090
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/warning_bg.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/light/zoom_popup_bg.png b/src/Tizen.NUI.PenWave/res/images/light/zoom_popup_bg.png
new file mode 100644
index 00000000000..5962ee7fd0c
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/light/zoom_popup_bg.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/textures/brush_acrylic.png b/src/Tizen.NUI.PenWave/res/images/textures/brush_acrylic.png
new file mode 100644
index 00000000000..2dcb4c088b8
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/textures/brush_acrylic.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/textures/brush_sponge.png b/src/Tizen.NUI.PenWave/res/images/textures/brush_sponge.png
new file mode 100644
index 00000000000..904bb9cb86b
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/textures/brush_sponge.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/textures/dot_brush.png b/src/Tizen.NUI.PenWave/res/images/textures/dot_brush.png
new file mode 100644
index 00000000000..53596ecb1c0
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/textures/dot_brush.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/textures/eraser_texture.png b/src/Tizen.NUI.PenWave/res/images/textures/eraser_texture.png
new file mode 100755
index 00000000000..dd76d8125f4
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/textures/eraser_texture.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/textures/highlighter.png b/src/Tizen.NUI.PenWave/res/images/textures/highlighter.png
new file mode 100644
index 00000000000..5997b641f3b
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/textures/highlighter.png differ
diff --git a/src/Tizen.NUI.PenWave/res/images/textures/soft_brush.png b/src/Tizen.NUI.PenWave/res/images/textures/soft_brush.png
new file mode 100644
index 00000000000..125d986fe0e
Binary files /dev/null and b/src/Tizen.NUI.PenWave/res/images/textures/soft_brush.png differ
diff --git a/src/Tizen.NUI.PenWave/src/Interop/Interop.PenWaveRenderer.cs b/src/Tizen.NUI.PenWave/src/Interop/Interop.PenWaveRenderer.cs
new file mode 100644
index 00000000000..77ed3f8d02d
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/Interop/Interop.PenWaveRenderer.cs
@@ -0,0 +1,377 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.Runtime.InteropServices;
+
+namespace Tizen.NUI.PenWave
+{
+ internal static partial class Interop
+ {
+ internal static partial class PenWaveRenderer
+ {
+ private const string Lib = "libhand-drawing-engine.so";
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "InitializeGL")]
+ public static extern void InitializeGL();
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "RenderFrameGL")]
+ public static extern int RenderFrameGL();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "RenderFullyReDraw")]
+ public static extern void RenderFullyReDraw();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "TerminateGL")]
+ public static extern void TerminateGL();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "UpdateGLWindowSize")]
+ public static extern void UpdateGLWindowSize(int w, int h);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "UpdateGLWindowOrientation")]
+ public static extern void UpdateGLWindowOrientation(int angle);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "BeginShapeDraw")]
+ public static extern uint BeginShapeDraw(float x, float y, uint time = 1);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "DrawShape")]
+ public static extern int DrawShape(uint shapeID, float x, float y, uint time = 1);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "EndShapeDraw")]
+ public static extern int EndShapeDraw(uint shapeID, uint time = 1);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "BeginLineDraw")]
+ public static extern uint BeginLineDraw(float x, float y, uint time = 1);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "DrawLine")]
+ public static extern int DrawLine(uint lineID, float x, float y, uint time = 1);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "EndLineDraw")]
+ public static extern int EndLineDraw(uint lineID, uint time = 1);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "StopErasing")]
+ public static extern void StopErasing();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "EraseShape")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool EraseShape(int x, int y, float radius, [MarshalAs(UnmanagedType.I1)] bool partial);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "AddRectanglePath")]
+ public static extern uint AddRectanglePath(float xStart, float yStart, float x, float y, bool finished);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "AddArcPath")]
+ public static extern uint AddArcPath(float xCenter, float yCenter, float radius, float x, float y, bool finished);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "ResizeShapePath")]
+ public static extern int ResizeShapePath(uint shapeID, float x, float y);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "FinishShapePath")]
+ public static extern int FinishShapePath(uint shapeID);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "CanvasZoomBegin")]
+ public static extern bool CanvasZoomBegin();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "CanvasZoom")]
+ public static extern bool CanvasZoom(float x, float y, float zoom, float dx, float dy);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "CanvasZoomEnd")]
+ public static extern bool CanvasZoomEnd();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "CanvasGetZoomValue")]
+ public static extern int CanvasGetZoomValue();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "CanvasSetZoomValue")]
+ public static extern void CanvasSetZoomValue(float zoomValue);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "CanvasZoomToValue")]
+ public static extern void CanvasSetZoomValue(int x, int y, float zoomValue);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "CanvasMoveBegin")]
+ public static extern bool CanvasMoveBegin();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "CanvasMove")]
+ public static extern bool CanvasMove(int x, int y);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "CanvasMoveEnd")]
+ public static extern bool CanvasMoveEnd();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "CanvasSetColor")]
+ public static extern void CanvasSetColor([MarshalAs(UnmanagedType.LPStr)] string hexColor, float a);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "CanvasSetSize")]
+ public static extern void CanvasSetSize(int width, int height);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetResourcePath")]
+ public static extern void SetResourcePath([MarshalAs(UnmanagedType.LPStr)] string resourcePath);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetFontPath")]
+ public static extern void SetFontPath([MarshalAs(UnmanagedType.LPStr)] string fontPath);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetTexturePaths")]
+ public static extern void SetTexturePaths([MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStr)] string[] texturePaths, int textureCount);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetStrokeSize")]
+ public static extern void SetStrokeSize(float val);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetStrokeColor")]
+ public static extern void SetStrokeColor([MarshalAs(UnmanagedType.LPStr)] string hexColor, float a);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetStrokeType")]
+ public static extern void SetStrokeType(int brushId);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetBrushTexture")]
+ public static extern void SetBrushTexture(int textureId);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetBrushDistance")]
+ public static extern void SetBrushDistance(float distance);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetDashArray")]
+ public static extern void SetDashArray([MarshalAs(UnmanagedType.LPStr)] string dashArray);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetLineAngle")]
+ public static extern void SetLineAngle(float angle);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "GetBrushSize")]
+ public static extern float GetBrushSize();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "GetBrushType")]
+ public static extern int GetBrushType();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "GetBrushTexture")]
+ public static extern int GetBrushTexture();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "GetBrushDistance")]
+ public static extern float GetBrushDistance();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "GetLineAngle")]
+ public static extern float GetLineAngle();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "GetShapeCount")]
+ public static extern int GetShapeCount();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "CreateCanvas")]
+ public static extern uint CreateCanvas(int canvasWidth, int canvasHeight);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "CreateCanvasWithBackgroundImage")]
+ public static extern uint CreateCanvasWithBackgroundImage([MarshalAs(UnmanagedType.LPStr)] string path);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetCurrentCanvas")]
+ public static extern void SetCurrentCanvas(uint canvasID);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "ClearCurrentCanvas")]
+ public static extern void ClearCurrentCanvas();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "StartSelectingArea")]
+ public static extern void StartSelectingArea(float x, float y);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "ResizeSelectedArea")]
+ public static extern void ResizeSelectedArea(float x, float y);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "InsideSelectedArea")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool InsideSelectedArea(float x, float y);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "GetSelectionDimensions")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool GetSelectionDimensions(ref float x, ref float y, ref float width, ref float height);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "TouchedDrawable")]
+ public static extern int TouchedDrawable(float x, float y);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SelectDrawable")]
+ public static extern int SelectDrawable(float x, float y);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SelectDrawables")]
+ public static extern int SelectDrawables();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "DragSelectedDrawables")]
+ public static extern void DragSelectedDrawables(float x, float y);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "EndDraging")]
+ public static extern void EndDraging();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "StartRotating")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool StartRotating(float x, float y);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "RotateSelected")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool RotateSelected(float x, float y);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "EndRotating")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool EndRotating(float x, float y);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "StartSelectionScale")]
+ public static extern void StartSelectionScale(bool anchorLeft, bool anchorRight, bool anchorTop, bool anchorBottom, ref float anchorX, ref float anchorY);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "ScaleSelection")]
+ public static extern void ScaleSelection(float scaleFactorX, float scaleFactorY);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "EndSelectionScale")]
+ public static extern void EndSelectionScale(float scaleFactorX, float scaleFactorY);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "DropSelectedDrawables")]
+ public static extern void DropSelectedDrawables();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SelectedAreaZoom")]
+ public static extern void SelectedAreaZoom(float x, float y, float zoom);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SaveCanvas")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool SaveCanvas(uint canvasID, [MarshalAs(UnmanagedType.LPStr)] string path);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "LoadCanvas")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool LoadCanvas(uint canvasID, [MarshalAs(UnmanagedType.LPStr)] string path);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "TakeScreenshot")]
+ public static extern void TakeScreenShot(uint canvasID, [MarshalAs(UnmanagedType.LPStr)] string path, int x, int y, int width, int height, [MarshalAs(UnmanagedType.FunctionPtr)] Tizen.NUI.PenWave.PenWaveRenderer.ScreenShotCallback thumbSaved);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "ToggleGrid")]
+ public static extern void ToggleGrid(int densityType);
+
+ //CHART
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "ToggleChartGrid")]
+ public static extern void ToggleChartGrid(int densityType);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "GetChartGridDensity")]
+ public static extern int GetChartGridDensity();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "AddChart")]
+ public static extern void AddChart(int chartType, [MarshalAs(UnmanagedType.LPStr)] string path);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "ChangeMode")]
+ public static extern void ChangeMode(int mode);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "ChartPosition")]
+ public static extern void ChartPosition(ref float x, ref float y);
+ //
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "Dump")]
+ public static extern void Dump();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "Copy")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool Copy();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "Paste")]
+ public static extern int Paste(float x, float y);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "Cut")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool Cut();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "Remove")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool Remove();
+
+
+ // PICTURE
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "PlacePicture")]
+ public static extern void AddPicture([MarshalAs(UnmanagedType.LPStr)] string path, float x, float y, float width, float height);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetCanvasBackground")]
+ public static extern void SetCanvasBackground([MarshalAs(UnmanagedType.LPStr)] string path, float x, float y, float width, float height);
+ //
+
+ // TEXT
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "AddText")]
+ public static extern void AddText([MarshalAs(UnmanagedType.LPStr)] string text, float x, float y, float size);
+ //
+ // NOTES
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "AddNote")]
+ public static extern uint AddNote(float x, float y, float w, float h);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "RemoveNote")]
+ public static extern void RemoveNote(uint noteID);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "ClearNote")]
+ public static extern void ClearNote();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "DragNote")]
+ public static extern bool DragNote(float x, float y);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "EndNoteDragging")]
+ public static extern bool EndNoteDragging();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetNoteColor")]
+ public static extern void SetNoteColor([MarshalAs(UnmanagedType.LPStr)] string hexColor, float a);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetActiveNote")]
+ public static extern void SetActiveNote(uint noteID);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "TouchedNote")]
+ public static extern uint TouchedNote(float x, float y);
+ //
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "Undo")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool Undo();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "Redo")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool Redo();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "CanUndo")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool CanUndo();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "CanRedo")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool CanRedo();
+
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "ResetUndo")]
+ public static extern void ResetUndo();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "ResetRedo")]
+ public static extern void ResetRedo();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "GetConfigurationCount")]
+ public static extern int GetConfigurationCount();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "GetAllConfigurations", CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr GetAllConfigurations();
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "GetConfiguration", CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr GetConfiguration([MarshalAs(UnmanagedType.LPStr)] string key);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetConfigurationInt", CallingConvention = CallingConvention.Cdecl)]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool SetConfigurationInt([MarshalAs(UnmanagedType.LPStr)] string key, int value);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetConfigurationFloat", CallingConvention = CallingConvention.Cdecl)]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool SetConfigurationFloat([MarshalAs(UnmanagedType.LPStr)] string key, float value);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetConfigurationString", CallingConvention = CallingConvention.Cdecl)]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool SetConfigurationString([MarshalAs(UnmanagedType.LPStr)] string key, [MarshalAs(UnmanagedType.LPStr)] string value);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SetConfigurationBool", CallingConvention = CallingConvention.Cdecl)]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool SetConfigurationBool([MarshalAs(UnmanagedType.LPStr)] string key, [MarshalAs(UnmanagedType.I1)] bool value);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "SaveProfiler")]
+ [return:MarshalAs(UnmanagedType.I1)]
+ public static extern bool SaveProfiler([MarshalAs(UnmanagedType.LPStr)] string path);
+
+ [global::System.Runtime.InteropServices.DllImport(Lib, EntryPoint = "ResetProfiler")]
+ public static extern void ResetProfiler();
+ }
+ }
+}
diff --git a/src/Tizen.NUI.PenWave/src/public/Canvas/PenWaveCanvas.cs b/src/Tizen.NUI.PenWave/src/public/Canvas/PenWaveCanvas.cs
new file mode 100644
index 00000000000..ab549e5fe01
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Canvas/PenWaveCanvas.cs
@@ -0,0 +1,411 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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;
+
+using Tizen.NUI.BaseComponents;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// PenWaveCanvas is a view that allows drawing on it using various tools.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class PenWaveCanvas : DirectRenderingGLView
+ {
+ ///
+ /// Events that are triggered when the tool starts an action.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler ActionStarted;
+
+ ///
+ /// Events that are triggered when the tool finishes an action.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler ActionFinished;
+
+ private UnRedoManager _unredoManager;
+ private PropertyNotification _propertyNotification;
+ private ToolBase _currentTool;
+ private ToolBase _canvasTool;
+ private uint _canvasId;
+
+ ///
+ /// Creates a new instance of a PenWaveCanvas.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public PenWaveCanvas() : base(DirectRenderingGLView.ColorFormat.RGBA8888, DirectRenderingGLView.BackendMode.UnsafeDirectRendering)
+ {
+ _canvasId = PenWaveRenderer.Instance.CreateCanvas(-1, -1);
+ PenWaveRenderer.Instance.SetCurrentCanvas(_canvasId);
+ InitializeCanvas();
+ }
+
+ ///
+ /// Creates a new instance of a PenWaveCanvas with a background image.
+ ///
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public PenWaveCanvas(string backgroundPath) : base(DirectRenderingGLView.ColorFormat.RGBA8888, DirectRenderingGLView.BackendMode.UnsafeDirectRendering)
+ {
+ _canvasId = PenWaveRenderer.Instance.CreateCanvasWithBackgroundImage(backgroundPath);
+ PenWaveRenderer.Instance.SetCurrentCanvas(_canvasId);
+ InitializeCanvas();
+ }
+
+ // Initialize canvas
+ private void InitializeCanvas()
+ {
+ _unredoManager = new UnRedoManager();
+ _canvasTool = new CanvasTool(this);
+ this.WidthResizePolicy = ResizePolicyType.FillToParent;
+ this.HeightResizePolicy = ResizePolicyType.FillToParent;
+
+ this.RenderingMode = GLRenderingMode.Continuous;
+ this.RegisterGLCallbacks(PenWaveRenderer.Instance.InitializeGL, PenWaveRenderer.Instance.RenderFrame, PenWaveRenderer.Instance.TerminateGL);
+ this.SetGraphicsConfig(false, false, 0, GLESVersion.Version20);
+
+ _propertyNotification = this.AddPropertyNotification("size", PropertyCondition.Step(1.0f));
+ _propertyNotification.Notified += (object source, PropertyNotification.NotifyEventArgs args) =>
+ {
+ Tizen.NUI.BaseComponents.View target = args.PropertyNotification.GetTarget() as Tizen.NUI.BaseComponents.View;
+ if (target != null)
+ {
+ PenWaveRenderer.Instance.Resize((int)target.SizeWidth, (int)target.SizeHeight);
+ }
+ };
+ }
+
+ ///
+ /// The tool used to draw on the canvas. If the tool is changed, the previous tool will be deactivated and the new tool will be activated.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ToolBase CurrentTool
+ {
+ get => _currentTool;
+ set
+ {
+ if (value == _currentTool) return;
+ if (_currentTool!= null)
+ {
+ _currentTool.Deactivate();
+ _currentTool.ActionStarted -= OnStarted;
+ _currentTool.ActionFinished -= OnFinished;;
+ }
+
+ _currentTool = value;
+
+ if (_currentTool != null)
+ {
+ _currentTool.Activate();
+ _currentTool.ActionStarted += OnStarted;
+ _currentTool.ActionFinished += OnFinished;
+ }
+ }
+ }
+
+ ///
+ /// Notifies that the canvas has started an action.
+ ///
+ private void NotifyActionStarted(object sender, EventArgs e)
+ {
+ ActionStarted?.Invoke(this, EventArgs.Empty);
+ }
+
+ ///
+ /// Notifies that the canvas has finished an action.
+ ///
+ private void NotifyActionFinished(object sender, EventArgs e)
+ {
+ ActionFinished?.Invoke(this, EventArgs.Empty);
+ }
+
+
+ // Event handlers for action started.
+ private void OnStarted(object sender, EventArgs e)
+ {
+ NotifyActionStarted(sender, e);
+ }
+
+ // Event handlers for action finished.
+ private void OnFinished(object sender, EventArgs e)
+ {
+ RegisterUndo();
+ NotifyActionFinished(sender, e);
+ }
+
+ private void RegisterUndo()
+ {
+ _unredoManager.RegisterUndo();
+ }
+
+ ///
+ /// Clears the canvas.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ClearCanvas()
+ {
+ PenWaveRenderer.Instance.ClearCurrentCanvas();
+ RegisterUndo();
+ NotifyActionFinished(this, EventArgs.Empty);
+ }
+
+ ///
+ /// Returns true if there are any actions that can be undone. Otherwise returns false.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool CanUndo => _unredoManager.CanUndo;
+
+ ///
+ /// Returns true if there are any actions that can be redone. Otherwise returns false.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool CanRedo => _unredoManager.CanRedo;
+
+ ///
+ /// Undoes the last action. If there are no actions to undo, nothing happens.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Undo()
+ {
+ _unredoManager.Undo();
+ }
+
+ ///
+ /// Redoes the last action. If there are no actions to redo, nothing happens.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Redo()
+ {
+ _unredoManager.Redo();
+ }
+
+ ///
+ /// Sets the color of the canvas.
+ ///
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetCanvasColor(Color color)
+ {
+ PenWaveRenderer.Instance.SetCanvasColor(color);
+ }
+
+ ///
+ /// Toggles the visibility of the grid.
+ ///
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ToggleGrid(GridDensityType gridType)
+ {
+ PenWaveRenderer.Instance.ToggleGrid(gridType);
+ }
+
+ ///
+ /// Adds a picture to canvas
+ ///
+ /// The image file path
+ /// The x position
+ /// The y position
+ /// The width
+ /// The height
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void AddPicture(string path, float x, float y, float width, float height)
+ {
+ PenWaveRenderer.Instance.AddPicture(path, x, y, width, height);
+ RegisterUndo();
+ NotifyActionFinished(this, EventArgs.Empty);
+ }
+
+ ///
+ /// Handles touch events. This touch event is delivered to the current tool.
+ ///
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void HandleInput(Touch touch)
+ {
+ if (!_canvasTool.HandleInput(touch))
+ {
+ _currentTool?.HandleInput(touch);
+ }
+ }
+
+ ///
+ /// Handles wheel events. This wheel event is delivered to the current tool.
+ ///
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void HandleInput(Wheel wheel)
+ {
+ if (!_canvasTool.HandleInput(wheel))
+ {
+ _currentTool?.HandleInput(wheel);
+ }
+ }
+
+ ///
+ /// Saves the canvas to a file.
+ ///
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SaveCanvas(string path)
+ {
+ PenWaveRenderer.Instance.SaveCanvas(_canvasId, path);
+ }
+
+ ///
+ /// Loads the canvas from a file.
+ ///
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void LoadCanvas(string path)
+ {
+ PenWaveRenderer.Instance.LoadCanvas(_canvasId, path);
+ }
+
+
+ ///
+ /// Takes a screen shot of the canvas and saves it to a file.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void TakeScreenShot(string path, int x, int y, int width, int height, EventHandler callback)
+ {
+ PenWaveRenderer.Instance.TakeScreenShot(_canvasId, path, x, y, width, height, () => {
+ callback?.Invoke(this, EventArgs.Empty);
+ });
+ }
+
+ ///
+ /// Start the canvas movement.
+ ///
+ /// True if the canvas move begin is successful, otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool MoveBegin()
+ {
+ return PenWaveRenderer.Instance.CanvasMoveBegin();
+ }
+
+ ///
+ /// Update the canvas movement.
+ ///
+ /// The x position
+ /// The y position
+ /// True if the canvas move update is successful, otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool MoveUpdate(int x, int y)
+ {
+ return PenWaveRenderer.Instance.CanvasMove(x, y);
+ }
+
+ ///
+ /// End the canvas movement.
+ ///
+ /// True if the canvas move end is successful, otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool MoveEnd()
+ {
+ return PenWaveRenderer.Instance.CanvasMoveEnd();
+ }
+
+ ///
+ /// Start the canvas zoom
+ ///
+ /// True if the canvas zoom begin is successful, otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ZoomBegin()
+ {
+ return PenWaveRenderer.Instance.CanvasZoomBegin();
+ }
+
+ ///
+ /// Update the canvas zoom
+ ///
+ /// The x position
+ /// The y position
+ /// The zoom value
+ /// The delta x, It will be zoomed in when it exceeds this value.
+ /// The delta y, It will be zoomed in when it exceeds this value.
+ /// True if the canvas zoom update is successful, otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ZoomUpdate(float x, float y, float zoom, float dx, float dy)
+ {
+ return PenWaveRenderer.Instance.CanvasZoom(x, y, zoom, dx, dy);
+ }
+
+ ///
+ /// End the canvas zoom
+ ///
+ /// True if the canvas zoom end is successful, otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ZoomEnd()
+ {
+ return PenWaveRenderer.Instance.CanvasZoomEnd();
+ }
+
+ ///
+ /// Get the canvas zoom value
+ ///
+ /// The zoom value
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int GetZoomValue()
+ {
+ return PenWaveRenderer.Instance.CanvasGetZoomValue();
+ }
+
+ ///
+ /// Set the canvas zoom value
+ ///
+ /// The zoom value
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetZoomValue(int zoomValue)
+ {
+ PenWaveRenderer.Instance.CanvasSetZoomValue(zoomValue);
+ }
+
+ ///
+ /// Zoom to the specified value
+ ///
+ /// The x position
+ /// The y position
+ /// The zoom value
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetZoomValue(int x, int y, int zoomValue)
+ {
+ PenWaveRenderer.Instance.CanvasSetZoomValue(x, y, zoomValue);
+ }
+
+ ///
+ /// Disposes the PenWaveCanvas.
+ ///
+ ///
+ protected override void Dispose(DisposeTypes type)
+ {
+ if(disposed) return;
+ _currentTool.ActionStarted -= OnStarted;
+ _currentTool.ActionFinished -= OnFinished;;
+ PenWaveRenderer.Instance.TerminateGL();
+ base.Dispose(type);
+ }
+ }
+}
diff --git a/src/Tizen.NUI.PenWave/src/public/Canvas/PenWaveRenderer.cs b/src/Tizen.NUI.PenWave/src/public/Canvas/PenWaveRenderer.cs
new file mode 100644
index 00000000000..0dbd098b195
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Canvas/PenWaveRenderer.cs
@@ -0,0 +1,1196 @@
+/*
+ * Copyright(c) 2025 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.IO;
+using Tizen.NUI.BaseComponents;
+
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// PenWaveRenderer class provides functions to draw on the canvas.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class PenWaveRenderer
+ {
+ private static readonly string s_fontPath = FrameworkInformation.ResourcePath + "fonts/font.ttf";
+ private static readonly string[] s_texturePaths = {
+ FrameworkInformation.ResourcePath + "images/textures/brush_acrylic.png",
+ FrameworkInformation.ResourcePath + "images/textures/brush_sponge.png",
+ FrameworkInformation.ResourcePath + "images/textures/dot_brush.png",
+ FrameworkInformation.ResourcePath + "images/textures/highlighter.png",
+ FrameworkInformation.ResourcePath + "images/textures/soft_brush.png"
+ };
+
+ ///
+ /// The delegate for the thumbnail saved callback.
+ ///
+ [UnmanagedFunctionPointer(CallingConvention.StdCall)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public delegate void ScreenShotCallback();
+
+ private static readonly PenWaveRenderer s_instance = new PenWaveRenderer();
+ private PenWaveRenderer()
+ {
+ SetResourcePath(FrameworkInformation.ResourcePath + "images/");
+ SetFontPath(s_fontPath);
+ SetTexturePaths(s_texturePaths, s_texturePaths.Length);
+ }
+
+ ///
+ /// Gets the singleton instance of PenWaveRenderer.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static PenWaveRenderer Instance => s_instance;
+
+ ///
+ /// Renders frame using OpenGL context. This method should be called from the render thread. It returns 0 when there is no more work to do. Otherwise it returns 1.
+ ///
+ /// DirectRenderingGLView.RenderCallbackInput
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int RenderFrame(in DirectRenderingGLView.RenderCallbackInput input)
+ {
+ return RenderFrameGL();
+ }
+
+ ///
+ /// Initializes the PenWave library.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void InitializeGL()
+ {
+ Interop.PenWaveRenderer.InitializeGL();
+ }
+
+ ///
+ /// Renders the frame using OpenGL.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int RenderFrameGL()
+ {
+ return Interop.PenWaveRenderer.RenderFrameGL();
+ }
+
+ ///
+ /// Renders the fully redraw frame using OpenGL.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void RenderFullyReDraw()
+ {
+ Interop.PenWaveRenderer.RenderFullyReDraw();
+ }
+
+ ///
+ /// Terminates the PenWave library.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void TerminateGL()
+ {
+ Interop.PenWaveRenderer.TerminateGL();
+ }
+
+ ///
+ /// Updates the window size in OpenGL. This should be called when the window is resized.
+ ///
+ /// The width
+ /// The height
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Resize(int w, int h)
+ {
+ Interop.PenWaveRenderer.UpdateGLWindowSize(w, h);
+ RenderFullyReDraw();
+ }
+
+ ///
+ /// Updates the window orientation in OpenGL. This should be called when the window is rotated.
+ ///
+ /// The angle
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void UpdateGLWindowOrientation(int angle)
+ {
+ Interop.PenWaveRenderer.UpdateGLWindowOrientation(angle);
+ }
+
+ ///
+ /// Start point for drawing
+ ///
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// The time
+ /// unique shape id
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public uint BeginShapeDraw(float x, float y, uint time = 1)
+ {
+ return Interop.PenWaveRenderer.BeginShapeDraw(x, y, time);
+ }
+
+ ///
+ /// Draw points on cavas
+ ///
+ /// ID of drawn shape
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// The time
+ ///
+ /// 0 No error in adding points
+ /// 1 Too many points (new shape has to be created)
+ /// 2 No canvas set
+ /// 3 No shapes in canvas
+ /// 4 Bad shapeID (not exists)
+ /// 5 Object with id=shapeID is not a shape
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ErrorShapeAddPointsType DrawShape(uint shapeID, float x, float y, uint time = 1)
+ {
+ return (ErrorShapeAddPointsType)Interop.PenWaveRenderer.DrawShape(shapeID, x, y, time);
+ }
+
+ ///
+ /// Finish shape drawing
+ ///
+ /// ID of drawn shape
+ /// The time
+ ///
+ /// 0 No error in adding points
+ /// 1 Too many points (new shape has to be created)
+ /// 2 No canvas set
+ /// 3 No shapes in canvas
+ /// 4 Bad shapeID (not exists)
+ /// 5 Object with id=shapeID is not a shape
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ErrorShapeAddPointsType EndShapeDraw(uint shapeID, uint time = 1)
+ {
+ return (ErrorShapeAddPointsType)Interop.PenWaveRenderer.EndShapeDraw(shapeID, time);
+ }
+
+ ///
+ ///
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// The time
+ /// unique shape id
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public uint BeginLineDraw(float x, float y, uint time = 1)
+ {
+ return Interop.PenWaveRenderer.BeginLineDraw(x, y, time);
+ }
+
+ ///
+ ///
+ /// ID of drawn shape
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// The time
+ ///
+ /// 0 No error in adding points
+ /// 1 Too many points (new shape has to be created)
+ /// 2 No canvas set
+ /// 3 No shapes in canvas
+ /// 4 Bad shapeID (not exists)
+ /// 5 Object with id=shapeID is not a shape
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ErrorShapeAddPointsType DrawLine(uint shapeID, float x, float y, uint time = 1)
+ {
+ return (ErrorShapeAddPointsType)Interop.PenWaveRenderer.DrawLine(shapeID, x, y, time);
+ }
+
+ ///
+ ///
+ /// ID of drawn shape
+ /// The time
+ ///
+ /// 0 No error in adding points
+ /// 1 Too many points (new shape has to be created)
+ /// 2 No canvas set
+ /// 3 No shapes in canvas
+ /// 4 Bad shapeID (not exists)
+ /// 5 Object with id=shapeID is not a shape
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ErrorShapeAddPointsType EndLineDraw(uint shapeID, uint time = 1)
+ {
+ return (ErrorShapeAddPointsType)Interop.PenWaveRenderer.EndLineDraw(shapeID, time);
+ }
+
+
+
+ ///
+ /// Stop erasing
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void StopErasing()
+ {
+ Interop.PenWaveRenderer.StopErasing();
+ }
+
+ ///
+ /// Erase selected shape
+ ///
+ /// Point X coordinated
+ /// Point Y coordinated
+ /// The radius
+ /// If true, only the touched parts of the shape are erased, otherwise the whole shape is erased.
+ /// Returns true if erasure was successful.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool EraseShape(int x, int y, float radius, bool partial)
+ {
+ return Interop.PenWaveRenderer.EraseShape(x, y, radius, partial);
+ }
+
+ ///
+ /// Adding rectangle with properties of current stroke
+ ///
+ /// coordinate of upper left corner of rectangle
+ /// coordinate of upper left corner of rectangle
+ /// coordinate of bottom right corner
+ /// coordinate of bottom right corner
+ /// true if drawing of rectangle is finished, false when it is set as current shape and has to be call FinishRectanglePath at the end of drawing
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public uint AddRectanglePath(float xStart, float yStart, float x, float y, bool finished)
+ {
+ return Interop.PenWaveRenderer.AddRectanglePath(xStart, yStart, x, y, finished);
+ }
+
+ ///
+ /// Adding arc with properties of current stroke
+ ///
+ /// coordinate of center of circle
+ /// coordinate of center of circle
+ /// radius of circle
+ /// coordinate of start point
+ /// coordinate of start point
+ /// true if drawing of rectangle is finished, false when it is set as current shape and has to be call FinishRectanglePath at the end of drawing
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public uint AddArcPath(float xCenter, float yCenter, float radius, float x, float y, bool finished)
+ {
+ return Interop.PenWaveRenderer.AddArcPath(xCenter, yCenter, radius, x, y, finished);
+ }
+
+ ///
+ /// Resize shape on canvas (rectangle or arc)
+ ///
+ /// ID of drawn shape
+ /// Point X coordinate of changed point
+ /// Point Y coordinate of changed point
+ ///
+ /// 0 No error in adding points
+ /// 1 Too many points (new shape has to be created)
+ /// 2 No canvas set
+ /// 3 No shapes in canvas
+ /// 4 Bad shapeID (not exists)
+ /// 5 Object with id=shapeID is not a shape
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ErrorShapeAddPointsType ResizeShapePath(uint shapeID, float x, float y)
+ {
+ return (ErrorShapeAddPointsType)Interop.PenWaveRenderer.ResizeShapePath(shapeID, x, y);
+ }
+
+ ///
+ /// End drawing shape on cavas (rectangle or arc)
+ ///
+ /// ID of drawn shape
+ ///
+ /// 0 No error in adding points
+ /// 1 Too many points (new shape has to be created)
+ /// 2 No canvas set
+ /// 3 No shapes in canvas
+ /// 4 Bad shapeID (not exists)
+ /// 5 Object with id=shapeID is not a shape
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int FinishShapePath(uint shapeID)
+ {
+ return Interop.PenWaveRenderer.FinishShapePath(shapeID);
+ }
+
+ ///
+ /// Zoom Canvas Begin
+ ///
+ /// false if some shape is being drawn, true otherwise
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool CanvasZoomBegin()
+ {
+ return Interop.PenWaveRenderer.CanvasZoomBegin();
+ }
+
+ ///
+ /// Zoom Canvas Point
+ ///
+ /// Point X coordinated
+ /// Point Y coordinated
+ /// Zoom level
+ /// The delta x, It will be zoomed in when it exceeds this value.
+ /// The delta y, It will be zoomed in when it exceeds this value.
+ /// false if zooming wasn't started with CanvasZoomBegin, true otherwise.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool CanvasZoom(float x, float y, float zoom, float dx, float dy)
+ {
+ return Interop.PenWaveRenderer.CanvasZoom(x, y, zoom, dx, dy);
+ }
+
+ ///
+ /// Zoom Canvas End
+ ///
+ /// false if zooming wasn't started with CanvasZoomBegin, true otherwise.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool CanvasZoomEnd()
+ {
+ return Interop.PenWaveRenderer.CanvasZoomEnd();
+ }
+
+ ///
+ /// Gets Zoom Canvas Value
+ ///
+ /// zoom level
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int CanvasGetZoomValue()
+ {
+ return Interop.PenWaveRenderer.CanvasGetZoomValue();
+ }
+
+ ///
+ /// Sets Zoom Canvas Value
+ ///
+ /// set zoom level
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void CanvasSetZoomValue(int zoomValue)
+ {
+ Interop.PenWaveRenderer.CanvasSetZoomValue((float)zoomValue);
+ }
+
+ ///
+ /// Zoom Canvas To Value
+ ///
+ /// Point X coordinated
+ /// Point Y coordinated
+ /// Zoom level
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void CanvasSetZoomValue(int x, int y, int zoomValue)
+ {
+ Interop.PenWaveRenderer.CanvasSetZoomValue(x, y, (float)zoomValue);
+ }
+
+ ///
+ /// Move Canvas Begin
+ ///
+ /// false if some shape is being drawn, true otherwise
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool CanvasMoveBegin()
+ {
+ return Interop.PenWaveRenderer.CanvasMoveBegin();
+ }
+
+ ///
+ /// Move Canvas Area
+ ///
+ /// Point X coordinated
+ /// Point Y coordinated
+ /// false if moving wasn't started with CanvasMoveBegin, true otherwise.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool CanvasMove(int x, int y)
+ {
+ return Interop.PenWaveRenderer.CanvasMove(x, y);
+ }
+
+ ///
+ /// Move Canvas End
+ ///
+ /// false if moving wasn't started with CanvasMoveBegin, true otherwise.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool CanvasMoveEnd()
+ {
+ return Interop.PenWaveRenderer.CanvasMoveEnd();
+ }
+
+ ///
+ /// Set Canvas Color
+ ///
+ /// The rgb color value
+ /// The alpha value
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetCanvasColor(string hexColor, float a)
+ {
+ Interop.PenWaveRenderer.CanvasSetColor(hexColor, a);
+ }
+
+ ///
+ /// Set Canvas Color
+ ///
+ /// The rgb color value
+ /// The alpha value
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetCanvasColor(Color color)
+ {
+ SetCanvasColor(ToHex(color), color.A);
+ }
+
+ ///
+ /// Sets canvas size
+ ///
+ /// The width
+ /// The height
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void CanvasSetSize(int width, int height)
+ {
+ Interop.PenWaveRenderer.CanvasSetSize(width, height);
+ }
+
+ ///
+ /// Sets resource path
+ ///
+ /// The resource path
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetResourcePath(string resourcePath)
+ {
+ Interop.PenWaveRenderer.SetResourcePath(resourcePath);
+ }
+
+ ///
+ /// Sets font path
+ ///
+ /// The font path
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetFontPath(string fontPath)
+ {
+ Interop.PenWaveRenderer.SetFontPath(fontPath);
+ }
+
+ ///
+ /// Sets the Texture Paths object
+ ///
+ /// The texture paths.
+ /// The number of textures.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetTexturePaths(string[] texturePaths, int textureCount)
+ {
+ Interop.PenWaveRenderer.SetTexturePaths(texturePaths, textureCount);
+ }
+
+ ///
+ /// Sets the radius of stroke line
+ ///
+ /// Brush size
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetStrokeSize(float size)
+ {
+ Interop.PenWaveRenderer.SetStrokeSize(size);
+ }
+
+ ///
+ /// Sets the stroke color
+ ///
+ /// The rgb color value
+ /// The alpha value
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetStrokeColor(string hexColor, float a)
+ {
+ Interop.PenWaveRenderer.SetStrokeColor(hexColor, a);
+ }
+
+ ///
+ /// Sets the stroke type
+ ///
+ /// The brush type
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetStrokeType(int brushType)
+ {
+ Interop.PenWaveRenderer.SetStrokeType(brushType);
+ }
+
+ ///
+ /// Sets the brush texture
+ ///
+ /// The brush texture
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetBrushTexture(int textureId)
+ {
+ Interop.PenWaveRenderer.SetBrushTexture(textureId);
+ }
+
+ ///
+ /// Sets the distance between neighbouring textures in brush
+ ///
+ /// The brush distance
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetBrushDistance(float distance)
+ {
+ Interop.PenWaveRenderer.SetBrushDistance(distance);
+ }
+
+ ///
+ /// Sets used dash array
+ ///
+ /// The dash array
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetDashArray(string dashArray)
+ {
+ Interop.PenWaveRenderer.SetDashArray(dashArray);
+ }
+
+ ///
+ /// Set the line angle
+ ///
+ /// The line angle in degrees
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetLineAngle(float angle)
+ {
+ Interop.PenWaveRenderer.SetLineAngle(angle);
+ }
+
+ ///
+ /// Gets Current Brush Size
+ ///
+ /// The brush Size
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public float GetBrushSize()
+ {
+ return Interop.PenWaveRenderer.GetBrushSize();
+ }
+
+ ///
+ /// Gets Current Brush Type
+ ///
+ /// The brush type
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int GetBrushType()
+ {
+ return Interop.PenWaveRenderer.GetBrushType();
+ }
+
+ ///
+ /// Gets Current Brush Texture
+ ///
+ /// The brush texture
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int GetBrushTexture()
+ {
+ return Interop.PenWaveRenderer.GetBrushTexture();
+ }
+
+ ///
+ /// Gets the distance between neighbouring textures in brush
+ ///
+ /// The brush distance
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public float GetBrushDistance()
+ {
+ return Interop.PenWaveRenderer.GetBrushDistance();
+ }
+
+ ///
+ /// Gets Current Line Angle
+ ///
+ /// The line angle
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public float GetLineAngle()
+ {
+ return Interop.PenWaveRenderer.GetLineAngle();
+ }
+
+ ///
+ /// Gets count of shapes on current canvas
+ ///
+ /// The shape count
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int GetShapeCount()
+ {
+ return Interop.PenWaveRenderer.GetShapeCount();
+ }
+
+ ///
+ /// Create Canvas
+ ///
+ /// The width
+ /// The height
+ /// The canvas ID
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public uint CreateCanvas(int canvasWidth, int canvasHeight)
+ {
+ return Interop.PenWaveRenderer.CreateCanvas(canvasWidth, canvasHeight);
+ }
+
+ ///
+ /// Create Canvas with background image from file path.
+ ///
+ /// The background image path
+ /// The canvas ID
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public uint CreateCanvasWithBackgroundImage(string path)
+ {
+ return Interop.PenWaveRenderer.CreateCanvasWithBackgroundImage(path);
+ }
+
+ ///
+ /// Sets Current Canvas
+ ///
+ /// The canvas ID
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetCurrentCanvas(uint canvasID)
+ {
+ Interop.PenWaveRenderer.SetCurrentCanvas(canvasID);
+ }
+
+ ///
+ /// Clear Current Canvas
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ClearCurrentCanvas()
+ {
+ Interop.PenWaveRenderer.ClearCurrentCanvas();
+ }
+
+ ///
+ /// Start Selecting Area
+ ///
+ /// Point X coordinate
+ /// Point Y coordinate
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void StartSelectingArea(float x, float y)
+ {
+ Interop.PenWaveRenderer.StartSelectingArea(x, y);
+ }
+
+ ///
+ /// Resize Current Selected Area
+ ///
+ /// Point X coordinate
+ /// Point Y coordinate
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ResizeSelectedArea(float x, float y)
+ {
+ Interop.PenWaveRenderer.ResizeSelectedArea(x, y);
+ }
+
+ ///
+ /// Is inside selected area
+ ///
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// Returns true if it is inside selected area. otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool InsideSelectedArea(float x, float y)
+ {
+ return Interop.PenWaveRenderer.InsideSelectedArea(x, y);
+ }
+
+ ///
+ /// Gets screen-space position of top-left corner and size of current selection
+ ///
+ /// destination reference for horizontal position or NaN if no selection is present
+ /// destination reference for vertical position or NaN if no selection is present
+ /// destination reference for selection width or NaN if no selection is present
+ /// destination reference for selection height or NaN if no selection is present
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool GetSelectionDimensions(ref float x, ref float y, ref float width, ref float height)
+ {
+ return Interop.PenWaveRenderer.GetSelectionDimensions(ref x, ref y, ref width, ref height);
+ }
+
+ ///
+ /// TouchedDrawable
+ ///
+ /// Point X coordinate
+ /// Point Y coordinate
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int TouchedDrawable(float x, float y)
+ {
+ return Interop.PenWaveRenderer.TouchedDrawable(x, y);
+ }
+
+ ///
+ /// Select Drawable
+ ///
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// The DrawableType
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int SelectDrawable(float x, float y)
+ {
+ return Interop.PenWaveRenderer.SelectDrawable(x, y);
+ }
+
+ ///
+ /// Select Drawables
+ ///
+ /// The DrawableType
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int SelectDrawables()
+ {
+ return Interop.PenWaveRenderer.SelectDrawables();
+ }
+
+ ///
+ /// Drag Selected Drawables
+ ///
+ /// Point X coordinate
+ /// Point Y coordinate
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void DragSelectedDrawables(float x, float y)
+ {
+ Interop.PenWaveRenderer.DragSelectedDrawables(x, y);
+ }
+
+ ///
+ /// End Draging
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void EndDraging()
+ {
+ Interop.PenWaveRenderer.EndDraging();
+ }
+
+ ///
+ /// Start rotating selected shapes
+ ///
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// Returns true if successful. otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool StartRotating(float x, float y)
+ {
+ return Interop.PenWaveRenderer.StartRotating(x, y);
+ }
+
+ ///
+ /// Rotate selected shapes
+ ///
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// Returns true if successful. otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool RotateSelected(float x, float y)
+ {
+ return Interop.PenWaveRenderer.RotateSelected(x, y);
+ }
+
+ ///
+ /// Finish rotating selected shapes
+ ///
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// Returns true if successful. otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool EndRotating(float x, float y)
+ {
+ return Interop.PenWaveRenderer.EndRotating(x, y);
+ }
+
+ ///
+ /// Initialize scaling of selected area.
+ /// Selecting neither or both of left/right or bottom/top anchor will put the anchor in the middle of relevant axis
+ ///
+ /// place anchor on the left side of current selection
+ /// place anchor on the right side of current selection
+ /// place anchor on the bottom side of current selection
+ /// place anchor on the top side of current selection
+ /// x cooridinate of anchor point
+ /// y cooridinate of anchor point
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void StartSelectionScale(bool anchorLeft, bool anchorRight, bool anchorTop, bool anchorBottom, ref float anchorX, ref float anchorY)
+ {
+ Interop.PenWaveRenderer.StartSelectionScale(anchorLeft, anchorRight, anchorTop, anchorBottom, ref anchorX, ref anchorY);
+ }
+
+ ///
+ /// Scale Selected Area
+ ///
+ /// horizontal scale factor relative to initial size
+ /// vertical scale factor relative to initial size
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ScaleSelection(float scaleFactorX, float scaleFactorY)
+ {
+ Interop.PenWaveRenderer.ScaleSelection(scaleFactorX, scaleFactorY);
+ }
+
+ ///
+ /// Finalize scaling of selected area
+ ///
+ /// horizontal scale factor relative to initial size
+ /// vertical scale factor relative to initial size
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void EndSelectionScale(float scaleFactorX, float scaleFactorY)
+ {
+ Interop.PenWaveRenderer.EndSelectionScale(scaleFactorX, scaleFactorY);
+ }
+
+ ///
+ /// Drop Selected Drawables
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void DropSelectedDrawables()
+ {
+ Interop.PenWaveRenderer.DropSelectedDrawables();
+ }
+
+ ///
+ /// Zoom Selected Area
+ ///
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// Zoom lebel
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SelectedAreaZoom(float x, float y, float zoom)
+ {
+ Interop.PenWaveRenderer.SelectedAreaZoom(x, y, zoom);
+ }
+
+ ///
+ /// Save canvas to file
+ ///
+ /// Canvas ID
+ /// file path
+ /// Returns true if successful. otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool SaveCanvas(uint canvasID, string path)
+ {
+ return Interop.PenWaveRenderer.SaveCanvas(canvasID, path);
+ }
+
+ ///
+ /// Load canvas from file
+ ///
+ /// Canvas ID
+ /// file path
+ /// Returns true if successful. otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool LoadCanvas(uint canvasID, string path)
+ {
+ if (!File.Exists(path))
+ {
+ Tizen.Log.Error("NUI", $"Loading canvas error. canvasID :{canvasID} path : {path}\n");
+ return false;
+ }
+ return Interop.PenWaveRenderer.LoadCanvas(canvasID, path);
+ }
+
+ ///
+ /// Take screenshot of canvas to file with given dimensions and save callback function.
+ ///
+ /// Canvas ID
+ /// file path
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// Width
+ /// Height
+ /// save callback function
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void TakeScreenShot(uint canvasID, string path, int x, int y, int width, int height, ScreenShotCallback thumbSaved)
+ {
+ Interop.PenWaveRenderer.TakeScreenShot(canvasID, path, x, y, width, height, thumbSaved);
+ }
+
+ ///
+ /// ToggleGrid
+ ///
+ /// The density type
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ToggleGrid(GridDensityType gridType)
+ {
+ Interop.PenWaveRenderer.ToggleGrid((int)gridType);
+ }
+
+ ///
+ /// Toggle Chart Grid
+ ///
+ /// The density type
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ToggleChartGrid(int densityType)
+ {
+ Interop.PenWaveRenderer.ToggleChartGrid(densityType);
+ }
+
+ ///
+ /// Gets Chart Grid Density
+ ///
+ /// chart grid density
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int GetChartGridDensity()
+ {
+ return Interop.PenWaveRenderer.GetChartGridDensity();
+ }
+
+ ///
+ /// Copy shape
+ ///
+ /// Returns true if successful. otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool CopySelectedDrawables()
+ {
+ return Interop.PenWaveRenderer.Copy();
+ }
+
+ ///
+ /// Paste shape in position
+ ///
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// Returns true if successful. otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int PasteDrawables(float x, float y)
+ {
+ return Interop.PenWaveRenderer.Paste(x, y);
+ }
+
+ ///
+ /// Cut shape
+ ///
+ /// Returns true if successful. otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool CutSelectedDrawables()
+ {
+ return Interop.PenWaveRenderer.Cut();
+ }
+
+ ///
+ /// Remove shape
+ ///
+ /// Returns true if successful. otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool Remove()
+ {
+ return Interop.PenWaveRenderer.Remove();
+ }
+
+ ///
+ /// Add chart
+ ///
+ /// chart type
+ /// path to chart
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void AddChart(int chartType, string path)
+ {
+ Interop.PenWaveRenderer.AddChart(chartType, path);
+ }
+
+ ///
+ /// Change mode
+ ///
+ /// mode
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ChangeMode(int mode)
+ {
+ Interop.PenWaveRenderer.ChangeMode(mode);
+ }
+
+ ///
+ /// Chart Position
+ ///
+ /// current chart x position
+ /// current chart y position
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ChartPosition(ref float x, ref float y)
+ {
+ Interop.PenWaveRenderer.ChartPosition(ref x, ref y);
+ }
+
+ ///
+ /// Add Picture to canvas with original size and at 0;0 position
+ ///
+ /// file path
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// Width
+ /// Height
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void AddPicture(string path, float x, float y, float width, float height)
+ {
+ Interop.PenWaveRenderer.AddPicture(path, x, y, width, height);
+ RenderFullyReDraw();
+ }
+
+ ///
+ /// Sets Canvas Background
+ ///
+ /// file path
+ /// file path
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// Width
+ /// Height
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetCanvasBackgroundImage(string path, float x, float y, float width, float height)
+ {
+ Interop.PenWaveRenderer.SetCanvasBackground(path, x, y, width, height);
+ }
+
+ ///
+ /// Add Text to canvas
+ ///
+ /// The text
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// Text size
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void AddText(string text, float x, float y, float size)
+ {
+ Interop.PenWaveRenderer.AddText(text, x, y, size);
+ }
+
+ ///
+ /// Add Note.
+ ///
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// Width
+ /// Height
+ /// The note ID
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public uint AddNote(float x, float y, float width, float height)
+ {
+ return Interop.PenWaveRenderer.AddNote(x, y, width, height);
+ }
+
+ ///
+ /// Remove Note.
+ ///
+ /// The note ID
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void RemoveNote(uint noteID)
+ {
+ Interop.PenWaveRenderer.RemoveNote(noteID);
+ }
+
+ ///
+ /// Clear Note.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ClearNote()
+ {
+ Interop.PenWaveRenderer.ClearNote();
+ }
+
+ ///
+ /// Drag the active note.
+ ///
+ /// Point X coordinate
+ /// Point Y coordinate
+ /// Returns true if successful. otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool DragNote(float x, float y)
+ {
+ return Interop.PenWaveRenderer.DragNote(x, y);
+ }
+
+ ///
+ /// End dragging of the active note.
+ ///
+ /// Returns true if successful. otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool EndNoteDragging()
+ {
+ return Interop.PenWaveRenderer.EndNoteDragging();
+ }
+
+ ///
+ /// Sets color of the active note.
+ ///
+ /// hex code of the color
+ /// alpha value
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetNoteColor(string hexColor, float a)
+ {
+ Interop.PenWaveRenderer.SetNoteColor(hexColor, a);
+ }
+
+ ///
+ /// Sets active note.
+ ///
+ /// The note ID
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetActiveNote(uint noteID)
+ {
+ Interop.PenWaveRenderer.SetActiveNote(noteID);
+ }
+
+ ///
+ /// Undo
+ ///
+ /// Returns true if successful. otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool Undo()
+ {
+ return Interop.PenWaveRenderer.Undo();
+ }
+
+ ///
+ /// Redo
+ ///
+ /// Returns true if successful. otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool Redo()
+ {
+ return Interop.PenWaveRenderer.Redo();
+ }
+
+ ///
+ /// CanUndo
+ ///
+ /// whether there is an operation to be undone or not
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool CanUndo()
+ {
+ return Interop.PenWaveRenderer.CanUndo();
+ }
+
+ ///
+ /// CanRedo
+ ///
+ /// whether there is an operation to be redone or not
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool CanRedo()
+ {
+ return Interop.PenWaveRenderer.CanRedo();
+ }
+
+ ///
+ /// Reset Undo
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ResetUndo()
+ {
+ Interop.PenWaveRenderer.ResetUndo();
+ }
+
+ ///
+ /// Reset Redo
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ResetRedo()
+ {
+ Interop.PenWaveRenderer.ResetRedo();
+ }
+
+ // Converts Color to hex string.
+ private string ToHex(Color color)
+ {
+ var red = (uint)(color.R * 255);
+ var green = (uint)(color.G * 255);
+ var blue = (uint)(color.B * 255);
+ return $"#{red:X2}{green:X2}{blue:X2}";
+ }
+ }
+}
diff --git a/src/Tizen.NUI.PenWave/src/public/Common/PenWaveConstants.cs b/src/Tizen.NUI.PenWave/src/public/Common/PenWaveConstants.cs
new file mode 100644
index 00000000000..31ec33b5010
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Common/PenWaveConstants.cs
@@ -0,0 +1,236 @@
+// Copyright (c) 2024 Samsung Electronics Co., Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// 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.ComponentModel;
+
+namespace Tizen.NUI.PenWave
+{
+ internal struct FrameworkInformation
+ {
+ public readonly static string ResourcePath = "/usr/share/dotnet.tizen/framework/res/";
+ }
+
+ ///
+ /// The type of brush used to draw.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public enum BrushType
+ {
+ ///
+ /// The Stroke brush type.
+ ///
+ Stroke = 0,
+
+ ///
+ /// The VarStroke brush type.
+ ///
+ VarStroke,
+
+ ///
+ /// The DotBrush brush type.
+ ///
+ DotBrush,
+
+ ///
+ /// The SprayBrush brush type.
+ ///
+ SprayBrush,
+
+ ///
+ /// The DashedLine brush type.
+ ///
+ DashedLine,
+
+ ///
+ /// The Highlighter brush type.
+ ///
+ Highlighter,
+
+ ///
+ /// The VarStrokeInc brush type.
+ ///
+ VarStrokeInc,
+
+ ///
+ /// The SoftBrush brush type.
+ ///
+ SoftBrush,
+
+ ///
+ /// The SharpBrush brush type.
+ ///
+ SharpBrush
+ }
+
+ ///
+ /// The type of eraser tool.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public enum EraserType
+ {
+ ///
+ /// The partial eraser type.
+ ///
+ Partial = 0,
+
+ ///
+ /// The full eraser type.
+ ///
+ Full,
+ }
+
+ ///
+ /// Enumeration for the type of selection transform
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public enum SelectionTransformType
+ {
+ ///
+ /// The move type.
+ ///
+ Move = 0,
+
+ ///
+ /// The scale type.
+ ///
+ Scale,
+
+ ///
+ /// The rotate type.
+ ///
+ Rotate,
+ }
+
+ ///
+ /// Enumeration for the type of selection operation
+ ///
+ public enum SelectionOperationType
+ {
+ ///
+ /// The none operation type.
+ ///
+ None,
+
+ ///
+ /// The copy operation type.
+ ///
+ Copy,
+
+ ///
+ /// The cut operation type.
+ ///
+ Cut,
+
+ ///
+ /// The paste operation type.
+ ///
+ Paste,
+ }
+
+ ///
+ /// Enumeration for the grid density type.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public enum GridDensityType
+ {
+ ///
+ /// The none grid density type.
+ ///
+ None = 0,
+
+ ///
+ /// The small grid density type.
+ ///
+ Small = 1,
+
+ ///
+ /// The medium grid density type.
+ ///
+ Medium = 2,
+
+ ///
+ /// The large grid density type.
+ ///
+ Large = 4
+ }
+
+ ///
+ /// Enumeration for the type of ruler operation
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public enum RulerType
+ {
+ ///
+ /// The line ruler type.
+ ///
+ Line = 0,
+
+ ///
+ /// The circle ruler type.
+ ///
+ Circle,
+
+ ///
+ /// The rectangle ruler type.
+ ///
+ Rectangle,
+ }
+
+ ///
+ /// The error codes returned from the native side when adding points to a shape.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public enum ErrorShapeAddPointsType
+ {
+ ///
+ /// No error occurred.
+ ///
+ NoError = 0,
+
+ ///
+ /// Too many points (new shape has to be created)
+ ///
+ OverflowShape,
+
+ ///
+ /// No canvas set
+ ///
+ NoCanvasSet,
+
+ ///
+ /// No shapes in canvas
+ ///
+ NoShapesInCanvas,
+
+ ///
+ /// Bad shapeID (not exists)
+ ///
+ BadIdShape,
+
+ ///
+ /// Object with id=shapeID is not a shape
+ ///
+ DrawableIsNotAShape,
+
+ ///
+ /// Object with id=shapeID is not a line
+ ///
+ DrawableIsNotALine,
+
+ ///
+ /// Drawing has been canceled.
+ ///
+ DrawingCanceled
+ }
+}
diff --git a/src/Tizen.NUI.PenWave/src/public/Picker/PenWaveToolPicker.cs b/src/Tizen.NUI.PenWave/src/public/Picker/PenWaveToolPicker.cs
new file mode 100644
index 00000000000..7bdefa4fa91
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Picker/PenWaveToolPicker.cs
@@ -0,0 +1,737 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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;
+using System.Collections.Generic;
+
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// PenWaveToolPicker class provides a user interface for selecting and configuring various drawing tools in the PenWave application.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class PenWaveToolPicker : View
+ {
+ ///
+ /// The ToolChanged event. It is triggered when the selected tool changes.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler ToolChanged;
+
+ // the resource path for images and icons
+ private static readonly string s_resourcePath = FrameworkInformation.ResourcePath + "images/light/";
+
+ // the icons for different brush types
+ private static readonly Dictionary s_brushIconMap = new Dictionary
+ {
+ { BrushType.Stroke, "icon_pencil.png" },
+ { BrushType.VarStroke, "icon_varstroke_dec.png" },
+ { BrushType.VarStrokeInc, "icon_varstroke_inc.png" },
+ { BrushType.SprayBrush, "icon_spray.png" },
+ { BrushType.DotBrush, "icon_dot.png" },
+ { BrushType.DashedLine, "icon_dashed_line.png" },
+ { BrushType.Highlighter, "icon_highlighter.png" },
+ { BrushType.SoftBrush, "icon_soft_brush.png" },
+ { BrushType.SharpBrush, "icon_sharp_brush.png" },
+ };
+
+ // the color palette for brushes
+ private static readonly List s_brushColorMap = new List
+ {
+ new Color("#F7B32C"), new Color("#FD5703"), new Color("#DA1727"),
+ new Color("#FF00A8"), new Color("#74BFB8"), new Color("#4087C5"),
+ new Color("#070044"), new Color("#0E0E0E")
+ };
+
+ // the icons for different eraser types
+ private static readonly Dictionary s_eraserIconMap = new Dictionary
+ {
+ { EraserType.Partial, "icon_eraser.png" },
+ { EraserType.Full, "icon_full_eraser.png" },
+ };
+
+ // the color palette for canvas background color
+ private static readonly List s_canvasColor = new List
+ {
+ new Color("#F0F0F0"), new Color("#B7B7B7"), new Color("#E3F2FF"),
+ new Color("#202022"), new Color("#515151"), new Color("#17234D"),
+ };
+
+ // the icons for different grid density types
+ private static readonly Dictionary s_gridIconMap = new Dictionary
+ {
+ { GridDensityType.Small, "icon_small_grid_density.png" },
+ { GridDensityType.Medium, "icon_medium_grid_density.png" },
+ { GridDensityType.Large, "icon_large_grid_density.png" },
+ };
+
+ private readonly PenWaveCanvas _canvasView;
+ private readonly Dictionary _tools;
+
+ private ImageView _selectedButton;
+ private ImageView _undoButton;
+ private ImageView _redoButton;
+
+
+ private const string _iconStateNormalColor = "#17234d";
+ private const string _iconStateSelectedColor = "#FF6200";
+ private const string _iconStateDisabledColor = "#CACACA";
+
+ /// The pickerView property. It contains the view that holds the tool buttons.
+ private View _pickerView;
+
+ /// The popupView property. It contains the view that holds the tool settings.
+ private View _popupView;
+
+ ///
+ /// Creates a new instance of PenWaveToolPicker.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public PenWaveToolPicker(PenWaveCanvas canvasView)
+ {
+ _canvasView = canvasView;
+ _tools = new Dictionary();
+
+ _canvasView.ActionFinished += OnFinished;
+ Initialize();
+ }
+
+ // Update UI when action finished. This method is called when the current action is finished.
+ private void OnFinished(object sender, EventArgs e)
+ {
+ UpdateUI();
+ }
+
+ // Initialize the tool picker and its components.
+ private void Initialize()
+ {
+ InitializeUI();
+ InitializeCanvasTools();
+ InitializeTools();
+ UpdateUI();
+ }
+
+ // Initialize the UI components of the tool picker.
+ private void InitializeUI()
+ {
+ WidthSpecification = LayoutParamPolicies.MatchParent;
+ HeightSpecification = LayoutParamPolicies.MatchParent;
+ Layout = new LinearLayout
+ {
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Top,
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ };
+
+ // Picker View
+ _pickerView = new View
+ {
+ CornerRadius = new Vector4(10, 10, 10, 10),
+ WidthSpecification = LayoutParamPolicies.WrapContent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Horizontal,
+ VerticalAlignment = VerticalAlignment.Center,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ CellPadding = new Size2D(5, 5),
+ },
+ BackgroundImage = s_resourcePath + "menu_bg.png",
+ };
+ _pickerView.TouchEvent += (s, e) => { return true; }; // Prevent touch events from propagating to the canvas view
+
+ // Popup View
+ _popupView = new View
+ {
+ WidthSpecification = LayoutParamPolicies.WrapContent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ Layout = new LinearLayout
+ {
+ VerticalAlignment = VerticalAlignment.Center,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = new Size2D(5, 5),
+ },
+ BackgroundImage = s_resourcePath + "picker_popup_bg.png",
+ Padding = new Extents(20, 20, 20, 20),
+ };
+ _popupView.Hide();
+ _popupView.TouchEvent += (s, e) => { return true; }; // Prevent touch events from propagating to the canvas view
+ _canvasView.TouchEvent += (s, e) => { ClearPopupView(); return false; }; // Hide popup when touching outside it
+
+ Add(_pickerView);
+ Add(_popupView);
+ _canvasView.Add(this);
+ }
+
+ // Initialize the canvas tools and their corresponding buttons.
+ private void InitializeCanvasTools()
+ {
+ var backgroundColorButton = CreateToolButton(s_resourcePath + "icon_color_palette.png", () =>
+ {
+ ShowPaletteSetting();
+ });
+ _pickerView.Add(backgroundColorButton);
+
+ var gridButton = CreateToolButton(s_resourcePath + "icon_medium_grid_density.png", () =>
+ {
+ ShowGridSetting();
+ });
+ _pickerView.Add(gridButton);
+
+ _undoButton = CreateToolButton(s_resourcePath + "icon_undo.svg", () =>
+ {
+ _canvasView.Undo();
+ UpdateUI();
+ });
+ _pickerView.Add(_undoButton);
+
+ _redoButton = CreateToolButton(s_resourcePath + "icon_redo.svg", () =>
+ {
+ _canvasView.Redo();
+ UpdateUI();
+ });
+ _pickerView.Add(_redoButton);
+
+ var clearButton = CreateToolButton(s_resourcePath + "icon_clear.png", () =>
+ {
+ _canvasView.ClearCanvas();
+ UpdateUI();
+ });
+ _pickerView.Add(clearButton);
+ }
+
+ // Show the color palette setting for the canvas background color.
+ private void ShowPaletteSetting()
+ {
+ ClearPopupView();
+
+ _popupView.Show();
+
+ var colorPicker = new View
+ {
+ Layout = new LinearLayout
+ {
+ VerticalAlignment = VerticalAlignment.Center,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ LinearOrientation = LinearLayout.Orientation.Horizontal,
+ CellPadding = new Size2D(5, 5),
+ }
+ };
+
+ foreach (var color in s_canvasColor)
+ {
+ var button = new ImageView
+ {
+ Size2D = new Size2D(48, 48),
+ Color = color,
+ ResourceUrl = s_resourcePath + "/color_icon_base.png",
+ };
+ button.TouchEvent += (s, e) =>
+ {
+ if (e.Touch.GetState(0) == PointStateType.Down)
+ {
+ _canvasView.SetCanvasColor(color);
+ }
+ return true;
+ };
+
+ colorPicker.Add(button);
+ }
+
+ _popupView.Add(colorPicker);
+ }
+
+ // Show the grid density setting for the canvas grid.
+ private void ShowGridSetting()
+ {
+ ClearPopupView();
+
+ _popupView.Show();
+
+ var gridPicker = new View
+ {
+ Layout = new LinearLayout
+ {
+ VerticalAlignment = VerticalAlignment.Center,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ LinearOrientation = LinearLayout.Orientation.Horizontal,
+ CellPadding = new Size2D(5, 5),
+ }
+ };
+
+ foreach (var icon in s_gridIconMap)
+ {
+ var button = CreateToolButton(s_resourcePath + icon.Value, () =>
+ {
+ _canvasView.ToggleGrid(icon.Key);
+ });
+ gridPicker.Add(button);
+ }
+
+ _popupView.Add(gridPicker);
+ }
+
+ // Initialize the tools and add them to the tool picker. Each tool has its own settings and behavior.
+ private void InitializeTools()
+ {
+ var pencilTool = new PencilTool(BrushType.Stroke, Color.Black, 3.0f);
+ AddTool(pencilTool, "icon_pencil.png");
+
+ var eraserTool = new EraserTool(EraserType.Partial, 48.0f);
+ AddTool(eraserTool, "icon_eraser.png");
+
+ var selectionTool = new SelectionTool(SelectionTransformType.Move);
+ AddTool(selectionTool, "icon_select.png");
+
+ var rulerTool = new RulerTool(RulerType.Line);
+ AddTool(rulerTool, "icon_shape.png");
+
+ _canvasView.CurrentTool = pencilTool;
+ }
+
+ // Add a tool to the tool picker and create a button for it. The button will be used to select the tool and show its settings.
+ private void AddTool(ToolBase tool, string icon)
+ {
+ _tools[tool.GetType()] = tool;
+
+ var toolButton = CreateToolButton(s_resourcePath + icon, () =>
+ {
+ SetTool(tool);
+ });
+ _pickerView.Add(toolButton);
+
+ }
+
+ // Set the current tool of the canvas view and show its settings.
+ private void SetTool(ToolBase tool)
+ {
+ if (_tools.ContainsKey(tool.GetType()))
+ {
+ _canvasView.CurrentTool = tool;
+ ShowToolSettings(tool);
+ ToolChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ // Show the settings for the given tool in the popup view. Each tool has its own settings and behavior.
+ private void ShowToolSettings(ToolBase tool)
+ {
+ ClearPopupView();
+
+ if (tool is PencilTool pencilTool)
+ ShowPencilToolSettings(pencilTool);
+ else if (tool is EraserTool eraserTool)
+ ShowEraserToolSettings(eraserTool);
+ else if (tool is SelectionTool selectionTool)
+ ShowSelectionToolSettings(selectionTool);
+ else if (tool is RulerTool rulerTool)
+ ShowRulerToolSettings(rulerTool);
+
+ _popupView.Show();
+ }
+
+ // Show the settings for the pencil tool in the popup view. The pencil tool has brush type, color, and size settings.
+ private void ShowPencilToolSettings(PencilTool pencilTool)
+ {
+ AddBrushPicker(pencilTool);
+ AddColorPicker(pencilTool);
+ AddSizeSlider(pencilTool);
+ }
+
+ // Show the settings for the eraser tool in the popup view. The eraser tool has eraser type and size settings.
+ private void ShowEraserToolSettings(EraserTool eraserTool)
+ {
+ AddEraserTypePicker(eraserTool);
+ AddSizeSlider(eraserTool);
+ }
+
+ // Show the settings for the selection tool in the popup view. The selection tool has selection type settings.
+ private void ShowSelectionToolSettings(SelectionTool selectionTool)
+ {
+ AddSelectionTypePicker(selectionTool);
+ AddCopyPastePicker(selectionTool);
+ }
+
+ private void ShowRulerToolSettings(RulerTool rulerTool)
+ {
+ AddRulerTypePicker(rulerTool);
+ }
+
+ // Create a button for the given tool and add it to the popup view.
+ private void AddBrushPicker(PencilTool pencilTool)
+ {
+ var brushPicker = new View
+ {
+ Layout = new LinearLayout
+ {
+ VerticalAlignment = VerticalAlignment.Center,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ LinearOrientation = LinearLayout.Orientation.Horizontal,
+ CellPadding = new Size2D(5, 5),
+ }
+ };
+
+ foreach (var icon in s_brushIconMap)
+ {
+ var button = CreateToolButton(s_resourcePath + icon.Value, () =>
+ {
+ pencilTool.Brush = icon.Key;
+ pencilTool.Activate();
+ });
+ brushPicker.Add(button);
+ }
+
+ _popupView.Add(brushPicker);
+ }
+
+ private ImageView _currentColorBorderButton;
+ // Create a button for the given color and add it to the popup view.
+ private void AddColorPicker(PencilTool pencilTool)
+ {
+ var colorPicker = new View
+ {
+ Layout = new LinearLayout
+ {
+ VerticalAlignment = VerticalAlignment.Center,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ LinearOrientation = LinearLayout.Orientation.Horizontal,
+ CellPadding = new Size2D(5, 5),
+ }
+ };
+
+ foreach (var color in s_brushColorMap)
+ {
+ var button = new ImageView
+ {
+ Size2D = new Size2D(48, 48),
+ Color = color,
+ ResourceUrl = s_resourcePath + "/color_icon_base.png",
+ };
+ var buttonBorder = new ImageView
+ {
+ Size2D = new Size2D(48, 48),
+ };
+ button.Add(buttonBorder);
+ button.TouchEvent += (s, e) =>
+ {
+ if (e.Touch.GetState(0) == PointStateType.Down)
+ {
+ pencilTool.BrushColor = color;
+ pencilTool.Activate();
+ }
+ return true;
+ };
+ buttonBorder.TouchEvent += (s, e) =>
+ {
+ if (e.Touch.GetState(0) == PointStateType.Down)
+ {
+ if (_currentColorBorderButton)
+ {
+ _currentColorBorderButton.ResourceUrl = "";
+ }
+ buttonBorder.ResourceUrl = s_resourcePath + "/color_icon_selected.png";
+ _currentColorBorderButton = buttonBorder;;
+ }
+ return false;
+ };
+
+ colorPicker.Add(button);
+ }
+
+ _popupView.Add(colorPicker);
+ }
+
+ // Create a slider for the given tool and add it to the popup view. The slider controls the size of the tool.
+ private void AddSizeSlider(ToolBase tool)
+ {
+
+ var slider = new Slider
+ {
+ WidthSpecification = 300
+ };
+ if (tool is PencilTool pencilTool)
+ {
+ slider.MinValue = 1;
+ slider.MaxValue = 20;
+ slider.CurrentValue = pencilTool.BrushSize;
+ }
+ else if (tool is EraserTool eraserTool)
+ {
+ slider.MinValue = 10;
+ slider.MaxValue = 100;
+ slider.CurrentValue = eraserTool.EraserRadius;
+ }
+
+ slider.ValueChanged += (s, e) =>
+ {
+ if (tool is PencilTool pencilTool)
+ {
+ pencilTool.BrushSize = e.CurrentValue;
+ }
+ else if (tool is EraserTool eraserTool)
+ {
+ eraserTool.EraserRadius = e.CurrentValue;
+ }
+
+ tool.Activate();
+ };
+
+ _popupView.Add(slider);
+ }
+
+ // Create a button for the given eraser type and add it to the popup view. The button toggles between partial and full eraser modes.
+ private void AddEraserTypePicker(EraserTool eraserTool)
+ {
+ var eraserPicker = new View
+ {
+ Layout = new LinearLayout
+ {
+ VerticalAlignment = VerticalAlignment.Center,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ LinearOrientation = LinearLayout.Orientation.Horizontal,
+ CellPadding = new Size2D(5, 5),
+ }
+ };
+
+ foreach (var icon in s_eraserIconMap)
+ {
+ var button = CreateToolButton(s_resourcePath + icon.Value, () =>
+ {
+ eraserTool.Eraser = eraserTool.Eraser == EraserType.Partial
+ ? EraserType.Full
+ : EraserType.Partial;
+ eraserTool.Activate();
+ });
+ eraserPicker.Add(button);
+ }
+ _popupView.Add(eraserPicker);
+ }
+
+ // Create a button for the given selection type and add it to the popup view. The button toggles between move, resize, and rotate modes.
+ private void AddSelectionTypePicker(SelectionTool selectionTool)
+ {
+ var picker = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Horizontal,
+ CellPadding = new Size2D(5, 5),
+ }
+ };
+
+ var types = Enum.GetValues(typeof(SelectionTransformType));
+ foreach (SelectionTransformType type in types)
+ {
+ var button = CreateToolButton(s_resourcePath + $"icon_{type.ToString().ToLower()}.png", () =>
+ {
+ selectionTool.Transform = type;
+ });
+ picker.Add(button);
+ }
+
+ _popupView.Add(picker);
+ }
+
+ private void AddCopyPastePicker(SelectionTool selectionTool)
+ {
+ var picker = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Horizontal,
+ CellPadding = new Size2D(5, 5),
+ }
+ };
+
+ var types = Enum.GetValues(typeof(SelectionOperationType));
+ foreach (SelectionOperationType type in types)
+ {
+ if (type != SelectionOperationType.None)
+ {
+ var button = CreateToolButton(s_resourcePath + $"icon_{type.ToString().ToLower()}.png", () =>
+ {
+ selectionTool.DoOperation(type);
+ });
+ if (!selectionTool.CanCopyPaste)
+ {
+ button.Color = new Color(_iconStateDisabledColor);
+ }
+ picker.Add(button);
+ }
+ }
+ _popupView.Add(picker);
+ }
+
+ // Create a button for the given ruler type and add it to the popup view. The button toggles between line, rectangle, and circular ruler modes.
+ private void AddRulerTypePicker(RulerTool rulerTool)
+ {
+ var picker = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Horizontal,
+ CellPadding = new Size2D(5, 5),
+ }
+ };
+
+ var types = Enum.GetValues(typeof(RulerType));
+ foreach (RulerType type in types)
+ {
+ var button = CreateToolButton(s_resourcePath + $"icon_{type.ToString().ToLower()}.png", () =>
+ {
+ rulerTool.Ruler = type;
+ });
+ picker.Add(button);
+ }
+
+ _popupView.Add(picker);
+ }
+
+ ///
+ /// Add a button to the picker view with the given icon path and click action.
+ ///
+ /// The icon image path
+ /// The action
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void AddButtonToPickerView(string iconPath, Action OnClick)
+ {
+ var button = CreateToolButton(iconPath, () =>
+ {
+ ClearPopupView();
+ OnClick?.Invoke();
+ });
+ _pickerView.Add(button);
+ }
+
+ ///
+ /// Add a button to the picker view with the given view.
+ ///
+ /// The view
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void AddViewToPickerView(View view)
+ {
+ _pickerView.Add(view);
+ }
+
+ ///
+ /// Add a button to the popup view with the given icon path and click action.
+ ///
+ /// The icon image path
+ /// The action
+ public void AddButtonToPopupView(string iconPath, Action OnClick)
+ {
+ var button = CreateToolButton(iconPath, OnClick);
+ _popupView.Add(button);
+ }
+
+ ///
+ /// Sets a button to the popup view with the given view
+ ///
+ /// The view
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetViewToPopupView(View view)
+ {
+ ClearPopupView();
+ _popupView.Add(view);
+ }
+
+ ///
+ /// Create a tool button with the given icon path and click action.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ private ImageView CreateToolButton(string iconPath, Action OnClick)
+ {
+ var button = new ImageView
+ {
+ Size2D = new Size2D(48, 48),
+ ResourceUrl = iconPath,
+ Color = new Color(_iconStateNormalColor),
+ };
+
+ button.TouchEvent += (s, e) =>
+ {
+ if (e.Touch.GetState(0) == PointStateType.Down)
+ {
+ if (_selectedButton != null)
+ {
+ _selectedButton.Color = new Color(_iconStateNormalColor);
+ }
+ _selectedButton = button;
+ button.Color = new Color(_iconStateSelectedColor);
+ OnClick?.Invoke();
+ }
+ return true;
+ };
+
+ return button;
+ }
+
+ // Update the UI based on the current state of the canvas view. This includes updating the selected tool, the undo/redo buttons, and the tool settings.
+ private void UpdateUI()
+ {
+ ClearPopupView();
+ // Update undo/redo buttons state and colors
+ if (_undoButton != null)
+ {
+ _undoButton.Color = _canvasView.CanUndo ? new Color(_iconStateNormalColor) : new Color(_iconStateDisabledColor);
+ }
+
+ if (_redoButton != null)
+ {
+ _redoButton.Color = _canvasView.CanRedo ? new Color(_iconStateNormalColor) : new Color(_iconStateDisabledColor);
+ }
+ }
+
+ ///
+ /// Clear the popup view and hide it.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ClearPopupView()
+ {
+ int childNum = (int)_popupView.ChildCount;
+ for (int i = childNum - 1; i >= 0; i--)
+ {
+ _popupView.Remove(_popupView.GetChildAt((uint)i));
+ }
+ _popupView.Hide();
+ }
+
+ ///
+ /// Show the popup view.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ShowPopupView()
+ {
+ _popupView.Show();
+ }
+
+ ///
+ /// Dispose
+ ///
+ /// The DisposeTypes
+ protected override void Dispose(DisposeTypes type)
+ {
+ if(disposed) return;
+ _canvasView.ActionFinished -= OnFinished;;
+ base.Dispose(type);
+ }
+ }
+}
diff --git a/src/Tizen.NUI.PenWave/src/public/Tools/Canvas/CanvasTool.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Canvas/CanvasTool.cs
new file mode 100644
index 00000000000..1663d35105d
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Canvas/CanvasTool.cs
@@ -0,0 +1,147 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// The CanvasTool class provides a tool that allows the user to move the canvas around.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class CanvasTool : ToolBase
+ {
+ private float _initialTouchX;
+ private float _initialTouchY;
+ private bool _isCanvasMoving = false;
+ private PenWaveCanvas _canvasView;
+
+ ///
+ /// Creates a new instance of a CanvasTool.
+ ///
+ /// The PenWaveCanvas
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public CanvasTool(PenWaveCanvas canvasView)
+ {
+ _canvasView = canvasView;
+ }
+
+ ///
+ /// Activates the tool.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Activate()
+ {
+ }
+
+ ///
+ /// Deactivates the tool.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Deactivate()
+ {
+ EndDrawing();
+ }
+
+ ///
+ /// Handles input from the user.
+ ///
+ /// The touch event.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool HandleInput(Touch touch)
+ {
+ if (touch == null || touch.GetPointCount() == 0) return false;
+
+ uint pointStateIndex = 0;
+ uint touchTime = touch.GetTime();
+
+ List touchPositionList = new List();
+ for (uint i = 0; i < touch.GetPointCount(); ++i)
+ {
+ touchPositionList.Add(touch.GetScreenPosition(i));
+ }
+
+ Vector2 position = touchPositionList[(int)pointStateIndex];
+ if (touch.GetMouseButton(0) == MouseButton.Secondary)
+ {
+ _isCanvasMoving = true;
+ }
+ switch (touch.GetState(pointStateIndex))
+ {
+ case PointStateType.Down:
+ StartDrawing(position.X, position.Y, touchTime);
+ break;
+ case PointStateType.Motion:
+ ContinueDrawing(position.X, position.Y, touchTime);
+ break;
+ case PointStateType.Up:
+ case PointStateType.Leave:
+ EndDrawing();
+ break;
+ }
+ return _isCanvasMoving;
+ }
+
+ // Start drawing when the first touch down event occurs.
+ private void StartDrawing(float positionX, float positionY, uint touchTime)
+ {
+ if (_isCanvasMoving)
+ {
+ _initialTouchX = positionX;
+ _initialTouchY = positionY;
+ _canvasView.MoveBegin();
+ }
+ }
+
+ // Continue drawing the touch motion events occur.
+ private void ContinueDrawing(float positionX, float positionY, uint touchTime)
+ {
+ if (_isCanvasMoving)
+ {
+ _canvasView.MoveUpdate((int)(positionX - _initialTouchX), (int)(positionY - _initialTouchY));
+ }
+ }
+
+ // End drawing when the last touch up event occurs.
+ private void EndDrawing()
+ {
+ if (_isCanvasMoving)
+ {
+ _canvasView.MoveEnd();
+ _isCanvasMoving = false;
+ }
+ }
+
+ ///
+ /// Handles input from the user.
+ ///
+ /// The wheel event.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool HandleInput(Wheel wheel)
+ {
+ Vector2 point = wheel.Point;
+ float zoom = 1.0f - ((float)wheel.Z * 0.05f);
+ _canvasView.ZoomBegin();
+ _canvasView.ZoomUpdate(point.X, point.Y, zoom, 0, 0);
+ _canvasView.ZoomEnd();
+ return true;
+ }
+
+ }
+}
+
diff --git a/src/Tizen.NUI.PenWave/src/public/Tools/Eraser/EraserTool.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Eraser/EraserTool.cs
new file mode 100644
index 00000000000..d5010440fe1
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Eraser/EraserTool.cs
@@ -0,0 +1,144 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// The EraserTool class provides functionality to erase shapes from the canvas.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class EraserTool : ToolBase
+ {
+ /// The current state of the tool.
+ private bool _isActive = false;
+
+ ///
+ /// Constructor
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public EraserTool(EraserType eraserType, float radius)
+ {
+ Eraser = eraserType;
+ EraserRadius = radius;
+ }
+
+ ///
+ /// The type of eraser tool.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public EraserType Eraser { get; set; }
+
+ ///
+ /// The radius of the eraser tool.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public float EraserRadius { get; set; }
+
+ ///
+ /// Activate the tool.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Activate()
+ {
+ }
+
+ ///
+ /// Deactivate the tool.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Deactivate()
+ {
+ EndDrawing();
+ }
+
+ ///
+ /// Handle input events.
+ ///
+ /// The touch event data.
+ /// true if the event was handled, otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool HandleInput(Touch touch)
+ {
+ if (touch == null || touch.GetPointCount() == 0) return false;
+
+ uint pointStateIndex = 0;
+ uint touchTime = touch.GetTime();
+
+ List touchPositionList = new List();
+ for (uint i = 0; i < touch.GetPointCount(); ++i)
+ {
+ touchPositionList.Add(touch.GetScreenPosition(i));
+ }
+
+ Vector2 position = touchPositionList[(int)pointStateIndex];
+ switch (touch.GetState(pointStateIndex))
+ {
+ case PointStateType.Down:
+ StartDrawing(position.X, position.Y, touchTime);
+ break;
+ case PointStateType.Motion:
+ ContinueDrawing(position.X, position.Y, touchTime);
+ break;
+ case PointStateType.Up:
+ case PointStateType.Leave:
+ EndDrawing();
+ break;
+ }
+ return true;
+ }
+
+ // Start drawing at the given position.
+ private void StartDrawing(float positionX, float positionY, uint touchTime)
+ {
+ _isActive = true;
+ PenWaveRenderer.Instance.EraseShape((int)positionX, (int)positionY, EraserRadius, (Eraser == EraserType.Partial));
+ NotifyActionStarted();
+ }
+
+ // Continue drawing at the given position.
+ private void ContinueDrawing(float positionX, float positionY, uint touchTime)
+ {
+ PenWaveRenderer.Instance.EraseShape((int)positionX, (int)positionY, EraserRadius, (Eraser == EraserType.Partial));
+ }
+
+ // End drawing at the given position.
+ private void EndDrawing()
+ {
+ if (_isActive)
+ {
+ PenWaveRenderer.Instance.StopErasing();
+ NotifyActionFinished();
+ _isActive = false;
+ }
+ }
+
+ ///
+ /// Handle input events. (Wheel)
+ ///
+ /// The wheel event data.
+ /// true if the event was handled, otherwise false
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool HandleInput(Wheel wheel)
+ {
+ return false;
+ }
+ }
+}
+
diff --git a/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/BrushStrategyFactory.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/BrushStrategyFactory.cs
new file mode 100644
index 00000000000..9aef83a7fc7
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/BrushStrategyFactory.cs
@@ -0,0 +1,71 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// The factory class that creates brush strategies.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class BrushStrategyFactory
+ {
+ private static readonly BrushStrategyFactory s_instance = new BrushStrategyFactory();
+ private Dictionary _brushStrategies = new Dictionary();
+
+ ///
+ /// Private constructor.
+ ///
+ private BrushStrategyFactory() { }
+
+ ///
+ /// Gets the singleton instance of the BrushStrategyFactory.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static BrushStrategyFactory Instance => s_instance;
+
+ ///
+ /// Gets the brush strategy for the specified brush type.
+ ///
+ /// The brush type.
+ public IBrushStrategy GetBrushStrategy(BrushType brushType)
+ {
+ if (!_brushStrategies.ContainsKey(brushType))
+ {
+ _brushStrategies[brushType] = brushType switch
+ {
+ BrushType.Stroke => new StrokeBrush(),
+ BrushType.VarStroke => new VarStrokeBrush(),
+ BrushType.VarStrokeInc => new VarStrokeIncBrush(),
+ BrushType.SprayBrush => new SprayBrush(),
+ BrushType.DotBrush => new DotBrush(),
+ BrushType.DashedLine => new DashedLineBrush(),
+ BrushType.Highlighter => new HighlighterBrush(),
+ BrushType.SoftBrush => new SoftBrush(),
+ BrushType.SharpBrush => new SharpBrush(),
+ _ => throw new ArgumentOutOfRangeException(nameof(brushType), brushType, null)
+ };
+ }
+
+ return _brushStrategies[brushType];
+ }
+
+ }
+}
diff --git a/src/Tizen.AIAvatar/src/Extensions/AvatarExtension.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/DashedLineBrush.cs
similarity index 53%
rename from src/Tizen.AIAvatar/src/Extensions/AvatarExtension.cs
rename to src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/DashedLineBrush.cs
index afe62cdd856..99b96014e99 100644
--- a/src/Tizen.AIAvatar/src/Extensions/AvatarExtension.cs
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/DashedLineBrush.cs
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright(c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,30 +15,24 @@
*
*/
-using System.Collections.Generic;
-using System.IO;
using System.ComponentModel;
-using static Tizen.AIAvatar.AIAvatar;
-
-namespace Tizen.AIAvatar
+namespace Tizen.NUI.PenWave
{
+ ///
+ /// The dashed line brush strategy.
+ ///
[EditorBrowsable(EditorBrowsableState.Never)]
- public static class AvatarExtension
+ public class DashedLineBrush : IBrushStrategy
{
+ ///
+ /// Apply brush settings.
+ ///
[EditorBrowsable(EditorBrowsableState.Never)]
- public static List GetDefaultAvatarList()
+ public void ApplyBrushSettings()
{
- var list = new List();
- var avatarModelFolders = Directory.GetDirectories(ApplicationResourcePath + EmojiAvatarResourcePath);
- foreach (var directoryInfo in avatarModelFolders)
- {
- Log.Info(LogTag, $"Directory Path : {directoryInfo}");
- var avatarInfo = new AvatarInfo(directoryInfo);
- list.Add(avatarInfo);
- }
-
- return list;
+ PenWaveRenderer.Instance.SetStrokeType(5);
+ PenWaveRenderer.Instance.SetDashArray("1 3");
}
}
}
diff --git a/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/DotBrush .cs b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/DotBrush .cs
new file mode 100644
index 00000000000..58b89674b88
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/DotBrush .cs
@@ -0,0 +1,39 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// The dot brush strategy.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class DotBrush : IBrushStrategy
+ {
+ ///
+ /// Apply the brush settings.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyBrushSettings()
+ {
+ PenWaveRenderer.Instance.SetStrokeType(1);
+ PenWaveRenderer.Instance.SetBrushTexture(1);
+ PenWaveRenderer.Instance.SetBrushDistance(2.0f);
+ }
+ }
+}
diff --git a/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/HighlighterBrush.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/HighlighterBrush.cs
new file mode 100644
index 00000000000..32356b6ddeb
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/HighlighterBrush.cs
@@ -0,0 +1,39 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// The highlighter brush strategy.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class HighlighterBrush : IBrushStrategy
+ {
+ ///
+ /// Apply the brush settings.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyBrushSettings()
+ {
+ PenWaveRenderer.Instance.SetStrokeType(1);
+ PenWaveRenderer.Instance.SetBrushTexture(3);
+ PenWaveRenderer.Instance.SetBrushDistance(0.25f);
+ }
+ }
+}
diff --git a/src/Tizen.AIAvatar/src/Extensions/AvatarScene.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/IBrushStrategy.cs
similarity index 61%
rename from src/Tizen.AIAvatar/src/Extensions/AvatarScene.cs
rename to src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/IBrushStrategy.cs
index 4f498b34e27..3ab2c765a62 100644
--- a/src/Tizen.AIAvatar/src/Extensions/AvatarScene.cs
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/IBrushStrategy.cs
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright(c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,11 +15,20 @@
*
*/
-using Tizen.NUI.Scene3D;
+using System.ComponentModel;
-namespace Tizen.AIAvatar
+namespace Tizen.NUI.PenWave
{
- internal class AvatarScene : SceneView
+ ///
+ /// Interface for brush strategy.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public interface IBrushStrategy
{
+ ///
+ /// Apply brush settings.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyBrushSettings();
}
}
diff --git a/src/Tizen.AIAvatar/src/Multimedia/Audio/RecordBufferChangedEventArgs.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/SharpBrush.cs
similarity index 58%
rename from src/Tizen.AIAvatar/src/Multimedia/Audio/RecordBufferChangedEventArgs.cs
rename to src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/SharpBrush.cs
index baf723d98bf..8fb77f4150d 100644
--- a/src/Tizen.AIAvatar/src/Multimedia/Audio/RecordBufferChangedEventArgs.cs
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/SharpBrush.cs
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright(c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,20 +15,23 @@
*
*/
-using System;
+using System.ComponentModel;
-namespace Tizen.AIAvatar
+namespace Tizen.NUI.PenWave
{
- internal class RecordBufferChangedEventArgs : EventArgs
+ ///
+ /// The sharp brush strategy.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class SharpBrush : IBrushStrategy
{
- public byte[] RecordedBuffer { get; set; }
- public int SampleRate { get; set; }
-
- public RecordBufferChangedEventArgs(byte[] recordedBuffer, int SampleRate)
+ ///
+ /// Apply the brush settings.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyBrushSettings()
{
- this.RecordedBuffer = recordedBuffer;
- this.SampleRate = SampleRate;
+ PenWaveRenderer.Instance.SetStrokeType(8);
}
}
-
}
diff --git a/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/SoftBrush.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/SoftBrush.cs
new file mode 100644
index 00000000000..7d09dd33fcb
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/SoftBrush.cs
@@ -0,0 +1,39 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// The soft brush strategy.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class SoftBrush : IBrushStrategy
+ {
+ ///
+ /// Apply the brush settings.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyBrushSettings()
+ {
+ PenWaveRenderer.Instance.SetStrokeType(1);
+ PenWaveRenderer.Instance.SetBrushTexture(4);
+ PenWaveRenderer.Instance.SetBrushDistance(1.0f);
+ }
+ }
+}
diff --git a/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/SprayBrush.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/SprayBrush.cs
new file mode 100644
index 00000000000..95c09c774da
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/SprayBrush.cs
@@ -0,0 +1,39 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// The spray brush strategy.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class SprayBrush : IBrushStrategy
+ {
+ ///
+ /// Apply the brush settings.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyBrushSettings()
+ {
+ PenWaveRenderer.Instance.SetStrokeType(1);
+ PenWaveRenderer.Instance.SetBrushTexture(0);
+ PenWaveRenderer.Instance.SetBrushDistance(3.0f);
+ }
+ }
+}
diff --git a/src/Tizen.AIAvatar/src/Animations/IBlendShapeModule.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/StrokeBrush.cs
similarity index 56%
rename from src/Tizen.AIAvatar/src/Animations/IBlendShapeModule.cs
rename to src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/StrokeBrush.cs
index 7f9daf667c4..6a8cd083591 100644
--- a/src/Tizen.AIAvatar/src/Animations/IBlendShapeModule.cs
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/StrokeBrush.cs
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright(c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,21 +15,23 @@
*
*/
-using Tizen.NUI;
+using System.ComponentModel;
-namespace Tizen.AIAvatar
+namespace Tizen.NUI.PenWave
{
- internal interface IBlendShapeModule
+ ///
+ /// The stroke brush strategy.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class StrokeBrush : IBrushStrategy
{
- public void Init(Animation animation);
-
- public void Play();
-
- public void Stop();
-
- public void Pause();
-
- public void Destroy();
+ ///
+ /// Apply the brush settings.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyBrushSettings()
+ {
+ PenWaveRenderer.Instance.SetStrokeType(0);
+ }
}
-
}
diff --git a/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/VarStrokeBrush.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/VarStrokeBrush.cs
new file mode 100644
index 00000000000..ea10b9c3a38
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/VarStrokeBrush.cs
@@ -0,0 +1,37 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// A brush strategy that applies a variable stroke type.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class VarStrokeBrush : IBrushStrategy
+ {
+ ///
+ /// Applies the settings for the brush.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyBrushSettings()
+ {
+ PenWaveRenderer.Instance.SetStrokeType(6);
+ }
+ }
+}
diff --git a/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/VarStrokeIncBrush.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/VarStrokeIncBrush.cs
new file mode 100644
index 00000000000..2c54b8e0b53
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/Brush/VarStrokeIncBrush.cs
@@ -0,0 +1,37 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// The brush strategy that increases the stroke size as the user draws longer strokes.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class VarStrokeIncBrush : IBrushStrategy
+ {
+ ///
+ /// Apply the brush settings to the PenWave instance.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyBrushSettings()
+ {
+ PenWaveRenderer.Instance.SetStrokeType(7);
+ }
+ }
+}
diff --git a/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/PencilTool.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/PencilTool.cs
new file mode 100644
index 00000000000..ea038435650
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Pencil/PencilTool.cs
@@ -0,0 +1,188 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.ComponentModel;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// The pencil tool allows the user to draw shapes on the canvas using a stylus or finger.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class PencilTool : ToolBase
+ {
+ // The id of the current shape being drawn.
+ private uint _currentShapeId;
+
+ ///
+ /// Creates a new instance of a PencilTool.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public PencilTool(BrushType brushType, Color color, float size)
+ {
+ Brush = brushType;
+ BrushColor = color;
+ BrushSize = size;
+ }
+
+ ///
+ /// The type of brush used to draw.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public BrushType Brush { get; set; }
+
+ ///
+ /// The color of the brush used to draw.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public Color BrushColor { get; set; }
+
+ ///
+ /// The size of the brush used to draw.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public float BrushSize { get; set; }
+
+ // Converts a color to a hex string. Used to pass colors to the native side.
+ private string ToHex(Color color)
+ {
+ var red = (uint)(color.R * 255);
+ var green = (uint)(color.G * 255);
+ var blue = (uint)(color.B * 255);
+ return $"#{red:X2}{green:X2}{blue:X2}";
+ }
+
+ // Sets the brush type and applies the settings for that brush.
+ private void SetBrushType(BrushType brushType)
+ {
+ var brushStragety = BrushStrategyFactory.Instance.GetBrushStrategy(brushType);
+ if (brushStragety!= null)
+ {
+ brushStragety.ApplyBrushSettings();
+ }
+ }
+
+ ///
+ /// Activates the tool.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Activate()
+ {
+ SetBrushType(Brush);
+ PenWaveRenderer.Instance.SetStrokeColor(ToHex(BrushColor), 1.0f);
+ PenWaveRenderer.Instance.SetStrokeSize(BrushSize);
+ }
+
+ ///
+ /// Deactivates the tool.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Deactivate()
+ {
+ EndDrawing();
+ }
+
+ ///
+ /// Handles input from the user.
+ ///
+ /// The touch event.
+ /// True if the input was handled, otherwise false.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool HandleInput(Touch touch)
+ {
+ if (touch == null || touch.GetPointCount() == 0) return false;
+
+ uint pointStateIndex = 0;
+ uint touchTime = touch.GetTime();
+
+ List touchPositionList = new List();
+ for (uint i = 0; i < touch.GetPointCount(); ++i)
+ {
+ touchPositionList.Add(touch.GetScreenPosition(i));
+ }
+
+ Vector2 position = touchPositionList[(int)pointStateIndex];
+ switch (touch.GetState(pointStateIndex))
+ {
+ case PointStateType.Down:
+ StartDrawing(position.X, position.Y, touchTime);
+ break;
+ case PointStateType.Motion:
+ ContinueDrawing(position.X, position.Y, touchTime);
+ break;
+ case PointStateType.Up:
+ case PointStateType.Leave:
+ EndDrawing();
+ break;
+ }
+ return true;
+ }
+
+ // Starts drawing a new shape. This will create a new shape on the canvas.
+ private void StartDrawing(float positionX, float positionY, uint touchTime)
+ {
+ _currentShapeId = PenWaveRenderer.Instance.BeginShapeDraw(positionX, positionY, touchTime);
+ if (_currentShapeId > 0)
+ {
+ NotifyActionStarted();
+ }
+ }
+
+ // Continues drawing the current shape.
+ private void ContinueDrawing(float positionX, float positionY, uint touchTime)
+ {
+ if (_currentShapeId > 0)
+ {
+ var result = PenWaveRenderer.Instance.DrawShape(_currentShapeId, positionX, positionY, touchTime);
+ if (result == ErrorShapeAddPointsType.OverflowShape)
+ {
+ EndDrawing();
+ StartDrawing(positionX, positionY, touchTime);
+ }
+ else if (result == ErrorShapeAddPointsType.DrawingCanceled)
+ {
+ EndDrawing();
+ }
+ }
+ }
+
+ // Ends drawing the current shape.
+ private void EndDrawing()
+ {
+ if (_currentShapeId > 0)
+ {
+ PenWaveRenderer.Instance.EndShapeDraw(_currentShapeId, 0);
+ _currentShapeId = 0;
+ NotifyActionFinished();
+ }
+ }
+
+ ///
+ /// Handles input from the user.
+ ///
+ /// The wheel event.
+ /// True if the input was handled, otherwise false.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool HandleInput(Wheel wheel)
+ {
+ return false;
+ }
+
+ }
+}
+
diff --git a/src/Tizen.NUI.PenWave/src/public/Tools/Ruler/RulerTool.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Ruler/RulerTool.cs
new file mode 100644
index 00000000000..1c4a0f46efe
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Ruler/RulerTool.cs
@@ -0,0 +1,190 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// The RulerTool class allows the user to draw lines, circles and rectangles on the screen.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class RulerTool : ToolBase
+ {
+
+ // The id of the current shape being drawn.
+ private uint _currentShapeId;
+ private float _dragStartX = 0;
+ private float _dragStartY = 0;
+ private bool _isDrawingCircular = false;
+
+ ///
+ /// The constructor.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RulerTool(RulerType rulerType)
+ {
+ Ruler = rulerType;
+ }
+
+ ///
+ /// The type of selection operation. Default is move.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RulerType Ruler { get; set; }
+
+ ///
+ /// Activates the tool.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Activate()
+ {
+ }
+
+ ///
+ /// Deactivates the tool.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Deactivate()
+ {
+ EndDrawing();
+ }
+
+ ///
+ /// Handles input from the user.
+ ///
+ /// The touch event.
+ /// True if the input was handled.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool HandleInput(Touch touch)
+ {
+ if (touch == null || touch.GetPointCount() == 0) return false;
+
+ uint pointStateIndex = 0;
+ uint touchTime = touch.GetTime();
+
+ List touchPositionList = new List();
+ for (uint i = 0; i < touch.GetPointCount(); ++i)
+ {
+ touchPositionList.Add(touch.GetScreenPosition(i));
+ }
+
+ Vector2 position = touchPositionList[(int)pointStateIndex];
+ switch (touch.GetState(pointStateIndex))
+ {
+ case PointStateType.Down:
+ StartDrawing(position.X, position.Y, touchTime);
+ break;
+ case PointStateType.Motion:
+ ContinueDrawing(position.X, position.Y, touchTime);
+ break;
+ case PointStateType.Up:
+ case PointStateType.Leave:
+ EndDrawing();
+ break;
+ }
+ return true;
+ }
+
+ private void StartDrawing(float positionX, float positionY, uint touchTime)
+ {
+
+ if (Ruler == RulerType.Line)
+ {
+ _currentShapeId = PenWaveRenderer.Instance.BeginLineDraw(positionX, positionY, touchTime);
+ _isDrawingCircular = false;
+ }
+ else if (Ruler == RulerType.Circle)
+ {
+ if(!_isDrawingCircular)
+ {
+ _dragStartX = positionX;
+ _dragStartY = positionY;
+ _isDrawingCircular = true;
+ }
+ else
+ {
+ double rad = Math.Sqrt(((_dragStartX - positionX) * (_dragStartX - positionX)) + ((_dragStartY - positionY) * (_dragStartY - positionY)));
+ _currentShapeId = PenWaveRenderer.Instance.AddArcPath(_dragStartX, _dragStartY,(float) rad, positionX, positionY, false );
+ _isDrawingCircular = false;
+ }
+ }
+ else if (Ruler == RulerType.Rectangle)
+ {
+ _currentShapeId = PenWaveRenderer.Instance.AddRectanglePath(positionX, positionY, 400.0f, 400.0f, false);
+ _isDrawingCircular = false;
+ }
+
+ if (_currentShapeId > 0)
+ {
+ NotifyActionStarted();
+ }
+ }
+
+
+ private void ContinueDrawing(float positionX, float positionY, uint touchTime)
+ {
+ if (_currentShapeId > 0)
+ {
+ if (Ruler == RulerType.Line)
+ {
+ PenWaveRenderer.Instance.DrawLine(_currentShapeId, positionX, positionY, touchTime);
+ }
+ else
+ {
+ var res = PenWaveRenderer.Instance.ResizeShapePath(_currentShapeId, positionX, positionY);
+ if (res == ErrorShapeAddPointsType.DrawingCanceled)
+ {
+ PenWaveRenderer.Instance.FinishShapePath(_currentShapeId);
+ }
+ }
+ }
+ }
+
+ private void EndDrawing()
+ {
+ if (_currentShapeId > 0)
+ {
+ if (Ruler == RulerType.Line)
+ {
+ PenWaveRenderer.Instance.EndLineDraw(_currentShapeId, 0);
+ }
+ else
+ {
+ PenWaveRenderer.Instance.FinishShapePath(_currentShapeId);
+ }
+ _currentShapeId = 0;
+ NotifyActionFinished();
+ }
+ }
+
+ ///
+ /// Handles input from the user.
+ ///
+ /// The wheel event.
+ /// True if the input was handled.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool HandleInput(Wheel wheel)
+ {
+ return false;
+ }
+
+ }
+}
+
diff --git a/src/Tizen.NUI.PenWave/src/public/Tools/Selection/SelectionTool.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Selection/SelectionTool.cs
new file mode 100644
index 00000000000..1aa36aea9b7
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Selection/SelectionTool.cs
@@ -0,0 +1,298 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// The selection tool allows the user to select
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class SelectionTool : ToolBase
+ {
+
+ // Enumeration for the type of drawable object
+ private enum DrawableType
+ {
+ None, //nothing found/selected
+ Multi, //more than one drawable selected
+ Shape,
+ Chart,
+ Picture,
+ Text
+ }
+
+ // Enumeration for the current mode of operation
+ private enum Mode
+ {
+ None,
+ Move,
+ Resize,
+ Scale,
+ Rotate
+ }
+
+ // Flag to check if the touch is inside the selected area
+ private bool _isTouchedInsideSelectedArea = false;
+
+ // Current mode of operation
+ private Mode _currentMode = Mode.None;
+
+ // Type of drawable object being interacted with
+ private DrawableType _drawableType = DrawableType.None;
+
+ // Initial touch position
+ private float _initialTouchX;
+ private float _initialTouchY;
+
+ // Variables used during scaling operations
+ private float _startScaleX;
+ private float _startScaleY;
+ private float _anchorX;
+ private float _anchorY;
+
+ ///
+ /// Constructor for the selection tool.
+ ///
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public SelectionTool(SelectionTransformType selectionType)
+ {
+ Transform = selectionType;
+ }
+
+ ///
+ /// The type of selection operation. Default is move.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public SelectionTransformType Transform { get; set; } = SelectionTransformType.Move;
+
+ ///
+ /// The type of selection operation. Default is none.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public SelectionOperationType Operation { get; set; } = SelectionOperationType.None;
+
+ ///
+ /// Indicates whether the selected drawables can be copied or pasted. This will be true if there is exactly one drawable selected.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool CanCopyPaste => _drawableType != DrawableType.None;
+
+ ///
+ /// Perform the specified operation on the selected drawables.
+ ///
+ /// The operation to perform.
+ public void DoOperation(SelectionOperationType operation)
+ {
+ Operation = operation;
+ switch (operation)
+ {
+ case SelectionOperationType.Copy:
+ PenWaveRenderer.Instance.CopySelectedDrawables();
+ break;
+ case SelectionOperationType.Cut:
+ PenWaveRenderer.Instance.CutSelectedDrawables();
+ break;
+ default:
+ break;
+ }
+ }
+
+
+ ///
+ /// Activate the selection tool.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Activate()
+ {
+ }
+
+ ///
+ /// Deactivate the selection tool.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Deactivate()
+ {
+ _currentMode = Mode.None;
+ EndDrawing(0, 0, 0);
+ }
+
+ ///
+ /// Handle input events from the touch.
+ ///
+ /// The touch event.
+ /// True if the input was handled, otherwise false.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool HandleInput(Touch touch)
+ {
+ if (touch == null || touch.GetPointCount() == 0) return false;
+
+ uint pointStateIndex = 0;
+ uint touchTime = touch.GetTime();
+
+ List touchPositionList = new List();
+ for (uint i = 0; i < touch.GetPointCount(); ++i)
+ {
+ touchPositionList.Add(touch.GetScreenPosition(i));
+ }
+
+ Vector2 position = touchPositionList[(int)pointStateIndex];
+ switch (touch.GetState(pointStateIndex))
+ {
+ case PointStateType.Down:
+ StartDrawing(position.X, position.Y, touchTime);
+ break;
+ case PointStateType.Motion:
+ ContinueDrawing(position.X, position.Y, touchTime);
+ break;
+ case PointStateType.Up:
+ case PointStateType.Leave:
+ EndDrawing(position.X, position.Y, touchTime);
+ break;
+ }
+ return true;
+ }
+
+ // Start drawing the selection area or interacting with the selected drawables.
+ private void StartDrawing(float positionX, float positionY, uint touchTime)
+ {
+ if (Operation == SelectionOperationType.Paste)
+ {
+ PenWaveRenderer.Instance.PasteDrawables(positionX, positionY);
+ Operation = SelectionOperationType.None;
+ return;
+ }
+
+ _initialTouchX = positionX;
+ _initialTouchY = positionY;
+ _isTouchedInsideSelectedArea = PenWaveRenderer.Instance.InsideSelectedArea(positionX, positionY);
+ if (!_isTouchedInsideSelectedArea)
+ {
+ PenWaveRenderer.Instance.DropSelectedDrawables();
+ _drawableType = (DrawableType)PenWaveRenderer.Instance.SelectDrawable(positionX, positionY);
+ }
+ else
+ {
+ if (Transform == SelectionTransformType.Rotate)
+ {
+ PenWaveRenderer.Instance.StartRotating(positionX, positionY);
+ }
+ else if (Transform == SelectionTransformType.Scale)
+ {
+ float topLeftX = 0, topLeftY = 0, widthSelection = 0, heightSelection = 0;
+ PenWaveRenderer.Instance.GetSelectionDimensions(ref topLeftX, ref topLeftY, ref widthSelection, ref heightSelection);
+ if (!Double.IsNaN(topLeftX))
+ {
+ PenWaveRenderer.Instance.StartSelectionScale(
+ _initialTouchX >= topLeftX + widthSelection * 0.5f,
+ _initialTouchX < topLeftX + widthSelection * 0.5f,
+ _initialTouchY >= topLeftY + heightSelection * 0.5f,
+ _initialTouchY < topLeftY + heightSelection * 0.5f,
+ ref _anchorX,
+ ref _anchorY
+ );
+
+ _currentMode = Mode.Scale;
+ }
+ _startScaleX = _initialTouchX;
+ _startScaleY = _initialTouchY;
+ }
+ }
+ NotifyActionStarted();
+ }
+
+ // Continue drawing the selection area or interacting with the selected drawables.
+ private void ContinueDrawing(float positionX, float positionY, uint touchTime)
+ {
+ if (_drawableType == DrawableType.None)
+ {
+ if (_currentMode == Mode.None)
+ {
+ PenWaveRenderer.Instance.StartSelectingArea(positionX, positionY);
+ }
+ PenWaveRenderer.Instance.ResizeSelectedArea(positionX, positionY);
+ _currentMode = Mode.Resize;
+ }
+ else if (_currentMode != Mode.Resize && _drawableType != DrawableType.None)
+ {
+ if (Transform == SelectionTransformType.Move)
+ {
+ PenWaveRenderer.Instance.DragSelectedDrawables(positionX, positionY);
+ _currentMode = Mode.Move;
+ }
+ else if (Transform == SelectionTransformType.Rotate)
+ {
+ PenWaveRenderer.Instance.RotateSelected(positionX, positionY);
+ _currentMode = Mode.Rotate;
+ }
+ else if (Transform == SelectionTransformType.Scale)
+ {
+ PenWaveRenderer.Instance.ScaleSelection(
+ (positionX - _anchorX) / (_startScaleX - _anchorX),
+ (positionY - _anchorY) / (_startScaleY - _anchorY));
+ _currentMode = Mode.Scale;
+ }
+ }
+ }
+
+ // End drawing the selection area or interacting with the selected drawables.
+ private void EndDrawing(float positionX, float positionY, uint touchTime)
+ {
+ switch (_currentMode)
+ {
+ case Mode.Move :
+ PenWaveRenderer.Instance.EndDraging();
+ break;
+ case Mode.Resize :
+ _drawableType = (DrawableType)PenWaveRenderer.Instance.SelectDrawables();
+ break;
+ case Mode.Rotate :
+ PenWaveRenderer.Instance.EndRotating(positionX, positionY);
+ break;
+ case Mode.Scale :
+ PenWaveRenderer.Instance.EndRotating(positionX, positionY);
+ PenWaveRenderer.Instance.EndSelectionScale(
+ (positionX - _anchorX) / (_startScaleX - _anchorX),
+ (positionY - _anchorY) / (_startScaleY - _anchorY));
+ break;
+ default :
+ PenWaveRenderer.Instance.DropSelectedDrawables();
+ break;
+ }
+ _isTouchedInsideSelectedArea = false;
+ _currentMode = Mode.None;
+ NotifyActionFinished();
+ }
+
+ ///
+ /// Handle input events from the mouse wheel.
+ ///
+ /// The wheel event.
+ /// True if the input was handled.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool HandleInput(Wheel wheel)
+ {
+ return false;
+ }
+
+ }
+}
+
diff --git a/src/Tizen.NUI.PenWave/src/public/Tools/Toolbase.cs b/src/Tizen.NUI.PenWave/src/public/Tools/Toolbase.cs
new file mode 100644
index 00000000000..e49126fda0f
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/Toolbase.cs
@@ -0,0 +1,87 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.PenWave
+{
+ ///
+ /// The base class for all tools in the PenWave.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public abstract class ToolBase
+ {
+ ///
+ /// Events that are triggered when the tool starts an action.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler ActionStarted;
+
+ ///
+ /// Events that are triggered when the tool finishes an action.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler ActionFinished;
+
+ ///
+ /// Handles input events such as touch events and updates the state of the tool accordingly.
+ ///
+ /// The touch event data.
+ /// True if the input was handled by the tool, false otherwise.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public abstract bool HandleInput(Touch touch);
+
+ ///
+ /// Handles input events such as wheel events and updates the state of the tool accordingly.
+ ///
+ /// The wheel event data.
+ /// True if the input was handled by the tool, false otherwise.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public abstract bool HandleInput(Wheel wheel);
+
+ ///
+ /// Activates the tool, making it ready to receive input and perform its functionality.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public abstract void Activate();
+
+ ///
+ /// Deactivates the tool, stopping it from receiving input and performing its functionality.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public abstract void Deactivate();
+
+ ///
+ /// Notifies that the tool has started an action.
+ ///
+ protected void NotifyActionStarted()
+ {
+ ActionStarted?.Invoke(this, EventArgs.Empty);
+ }
+
+ ///
+ /// Notifies that the tool has finished an action.
+ ///
+ protected void NotifyActionFinished()
+ {
+ ActionFinished?.Invoke(this, EventArgs.Empty);
+ }
+
+ }
+}
+
diff --git a/src/Tizen.NUI.PenWave/src/public/Tools/UnRedoManager.cs b/src/Tizen.NUI.PenWave/src/public/Tools/UnRedoManager.cs
new file mode 100644
index 00000000000..0fbe01fc1d5
--- /dev/null
+++ b/src/Tizen.NUI.PenWave/src/public/Tools/UnRedoManager.cs
@@ -0,0 +1,84 @@
+/*
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.PenWave
+{
+ ///
+ /// The UnRedoManager class manages undo and redo operations for commands.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal class UnRedoManager
+ {
+ // Stacks to store undo and redo commands
+ private uint _undoStack = 0;
+ private uint _redoStack = 0;
+
+ ///
+ /// Registers a new command to the undo stack and clears the redo stack.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal void RegisterUndo()
+ {
+ _undoStack++; // Push command to undo stack
+ _redoStack = 0; // Clear redo stack after executing a new command
+ }
+
+ ///
+ /// Undoes the last executed command and pushes it to the redo stack.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal void Undo()
+ {
+ if (_undoStack > 0)
+ {
+ _undoStack--; // Pop command from undo stack
+ PenWaveRenderer.Instance.Undo();
+ _redoStack++; // Push command to redo stack
+ }
+ }
+
+ ///
+ /// Redoes the last undone command and pushes it to the undo stack.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal void Redo()
+ {
+ if (_redoStack > 0)
+ {
+ _redoStack--; // Pop command from redo stack
+ PenWaveRenderer.Instance.Redo();
+ _undoStack++; // Push command to undo stack
+ }
+ }
+
+ ///
+ /// Determines whether an undo operation is possible.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal bool CanUndo => _undoStack > 0;
+
+ ///
+ /// Determines whether a redo operation is possible.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal bool CanRedo => _redoStack > 0;
+ }
+}
diff --git a/src/Tizen.NUI/Tizen.NUI.csproj b/src/Tizen.NUI/Tizen.NUI.csproj
index 3ec641551c9..b57c88cd56f 100755
--- a/src/Tizen.NUI/Tizen.NUI.csproj
+++ b/src/Tizen.NUI/Tizen.NUI.csproj
@@ -2,7 +2,7 @@
net6.0
$(NoWarn);CS0618;CS0809;CS1591;CA1054;CA1056
- 8.0
+ 9.0
$(DefineConstants);NUI_DEBUG_OFF;
diff --git a/src/Tizen.AIAvatar/src/RestClient/RestClientFactory.cs b/src/Tizen.NUI/src/devel/Base/DevelView.cs
old mode 100644
new mode 100755
similarity index 58%
rename from src/Tizen.AIAvatar/src/RestClient/RestClientFactory.cs
rename to src/Tizen.NUI/src/devel/Base/DevelView.cs
index 07b7fa1f99c..4c1864b3c25
--- a/src/Tizen.AIAvatar/src/RestClient/RestClientFactory.cs
+++ b/src/Tizen.NUI/src/devel/Base/DevelView.cs
@@ -1,5 +1,5 @@
-/*
- * Copyright(c) 2024 Samsung Electronics Co., Ltd.
+/*
+ * Copyright(c) 2025 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.
@@ -15,16 +15,19 @@
*
*/
-using System.Net.Http;
using System;
+using System.Text;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
-namespace Tizen.AIAvatar
+namespace Tizen.NUI.BaseComponents
{
- internal class RestClientFactory
+ public partial class View
{
- internal IRestClient CreateClient(string baseUrl)
- {
- return new RestClient(new HttpClient { BaseAddress = new Uri(baseUrl) });
- }
+ ///
+ /// Occurs when the value of changes.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler EnabledChanged;
}
}
diff --git a/src/Tizen.NUI/src/devel/Base/ViewState.cs b/src/Tizen.NUI/src/devel/Base/ViewState.cs
new file mode 100755
index 00000000000..7a079cff7a7
--- /dev/null
+++ b/src/Tizen.NUI/src/devel/Base/ViewState.cs
@@ -0,0 +1,214 @@
+/*
+ * Copyright(c) 2025 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.Text;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+
+namespace Tizen.NUI
+{
+ ///
+ /// Defines a value type of view state.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public readonly struct ViewState
+ {
+ ///
+ /// The All state is used in a selector class. It represents all states, so if this state is defined in a selector, the other states are ignored.
+ ///
+ public static readonly ViewState All = new (ControlStateUtility.FullMask);
+
+ ///
+ /// Normal State.
+ ///
+ public static readonly ViewState Normal = new (0UL);
+
+ ///
+ /// Focused State.
+ ///
+ public static readonly ViewState Focused = new (nameof(Focused));
+
+ ///
+ /// Pressed State.
+ ///
+ public static readonly ViewState Pressed = new (nameof(Pressed));
+
+ ///
+ /// Disabled State.
+ ///
+ public static readonly ViewState Disabled = new (nameof(Disabled));
+
+ ///
+ /// Selected State.
+ ///
+ public static readonly ViewState Selected = new (nameof(Selected));
+
+ ///
+ /// Pressed caused by key state.
+ ///
+ public static readonly ViewState PressedByKey = Pressed + new ViewState(nameof(PressedByKey));
+
+ ///
+ /// Pressed caused by touch state.
+ ///
+ public static readonly ViewState PressedByTouch = Pressed + new ViewState(nameof(PressedByTouch));
+
+ ///
+ /// SelectedPressed State.
+ ///
+ public static readonly ViewState SelectedPressed = Selected + Pressed;
+
+ ///
+ /// DisabledSelected State.
+ ///
+ public static readonly ViewState DisabledSelected = Disabled + Selected;
+
+ ///
+ /// DisabledFocused State.
+ ///
+ public static readonly ViewState DisabledFocused = Disabled + Focused;
+
+ ///
+ /// SelectedFocused State.
+ ///
+ public static readonly ViewState SelectedFocused = Selected + Focused;
+
+ ///
+ /// This is used in a selector class. It represents all other states except for states that are already defined in a selector.
+ ///
+ public static readonly ViewState Other = new ViewState(nameof(Other));
+
+ private readonly ulong bitFlags;
+
+ private ViewState(ulong bitMask)
+ {
+ bitFlags = bitMask;
+ }
+
+ private ViewState(string name) : this(ControlStateUtility.Register(name))
+ {
+ }
+
+ ///
+ /// Gets or sets a value indicating whether it has combined states.
+ ///
+ internal bool IsCombined => (bitFlags != 0UL) && ((bitFlags & (bitFlags - 1UL)) != 0UL);
+
+ ///
+ /// Create an instance of the with state name.
+ ///
+ /// The state name.
+ /// The instance which has single state.
+ /// Thrown when the given name is null.
+ /// Thrown when the given name is invalid.
+ public static ViewState Create(string name)
+ {
+ return new ViewState(name);
+ }
+
+ ///
+ /// Determines whether a state contains a specified state.
+ ///
+ /// The state to search for
+ /// true if the state contain a specified state, otherwise, false.
+ public bool Contains(ViewState state) => (bitFlags & state.bitFlags) == state.bitFlags;
+
+ ///
+ /// Checks if there is a intersection part between this and the other.
+ ///
+ /// The other state to check.
+ /// True if an intersection exists, otherwise false.
+ public bool HasIntersectionWith(ViewState other) => (bitFlags & other.bitFlags) != 0L;
+
+ ///
+ public override string ToString()
+ {
+ var sbuilder = new StringBuilder();
+ var states = ControlStateUtility.RegisteredStates();
+
+ if (bitFlags == 0UL)
+ {
+ return nameof(Normal);
+ }
+ else if (bitFlags == ControlStateUtility.FullMask)
+ {
+ return nameof(All);
+ }
+
+ foreach (var (name, bitMask) in states)
+ {
+ if ((bitFlags & bitMask) > 0)
+ {
+ if (sbuilder.Length != 0) sbuilder.Append(", ");
+ sbuilder.Append(name);
+ }
+ }
+
+ return sbuilder.ToString();
+ }
+
+ ///
+ /// Compares whether the two ControlStates are same or not.
+ ///
+ /// A on the left hand side.
+ /// A on the right hand side.
+ /// true if the ControlStates are equal; otherwise, false.
+ public static bool operator ==(ViewState lhs, ViewState rhs) => lhs.Equals(rhs);
+
+ ///
+ /// Compares whether the two ControlStates are different or not.
+ ///
+ /// A on the left hand side.
+ /// A on the right hand side.
+ /// true if the ControlStates are not equal; otherwise, false.
+ public static bool operator !=(ViewState lhs, ViewState rhs) => !lhs.Equals(rhs);
+
+ ///
+ /// The addition operator.
+ ///
+ /// A on the left hand side.
+ /// A on the right hand side.
+ /// The containing the result of the addition.
+ public static ViewState operator +(ViewState lhs, ViewState rhs) => new ViewState(lhs.bitFlags | rhs.bitFlags);
+
+ ///
+ /// The substraction operator.
+ ///
+ /// A on the left hand side.
+ /// A on the right hand side.
+ /// The containing the result of the substraction.
+ public static ViewState operator -(ViewState lhs, ViewState rhs) => new ViewState(lhs.bitFlags & ~(rhs.bitFlags));
+
+ public bool Equals(ViewState other) => bitFlags == other.bitFlags;
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is ViewState otherState)
+ {
+ return Equals(otherState);
+ }
+ return base.Equals(obj);
+ }
+
+ ///
+ public override int GetHashCode() => bitFlags.GetHashCode();
+
+ internal ControlState ToReferenceType() => new ControlState(this);
+ }
+}
diff --git a/src/Tizen.NUI/src/devel/Lite/L.Color.cs b/src/Tizen.NUI/src/devel/Lite/L.Color.cs
new file mode 100644
index 00000000000..6de76adf1ae
--- /dev/null
+++ b/src/Tizen.NUI/src/devel/Lite/L.Color.cs
@@ -0,0 +1,161 @@
+/*
+ * Copyright(c) 2025 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.ComponentModel;
+
+namespace Tizen.NUI.L
+{
+ ///
+ /// Defines a value type of color.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public struct Color
+ {
+ ///
+ /// The default color. (This is to distinguish from zero corners)
+ ///
+ public static readonly Color Default = new (-1, -1, -1, -1);
+
+ ///
+ /// The transparent color.
+ ///
+ public static readonly Color Transparent = new (0, 0, 0, 0);
+
+ ///
+ /// The transparent color.
+ ///
+ public static readonly Color Black = new (0, 0, 0, 1);
+
+ ///
+ /// The white color.
+ ///
+ public static readonly Color White = new (1, 1, 1, 1);
+
+ ///
+ /// The red color.
+ ///
+ public static readonly Color Red = new (1, 0, 0, 1);
+
+ ///
+ /// The green color.
+ ///
+ public static readonly Color Green = new (0, 1, 0, 1);
+
+ ///
+ /// The blue color.
+ ///
+ public static readonly Color Blue = new (0, 0, 1, 1);
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The red component.
+ /// The green component.
+ /// The blue component.
+ /// The alpha component.
+ public Color(float r, float g, float b, float a)
+ {
+ R = r;
+ G = g;
+ B = b;
+ A = a;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The value of 0xRRGGBB format.
+ /// The alpha value between 0.0 and 1.0.
+ ///
+ ///
+ /// new L.Color(0xFF0000, 1f); // Solid red
+ /// new L.Color(0x00FF00, 0.5f) // Half transparent green
+ ///
+ ///
+ public Color(uint value, float alpha)
+ {
+ R = ((value >> 16) & 0xff) / 255.0f;
+ G = ((value >> 8) & 0xff) / 255.0f;
+ B = (value & 0xff) / 255.0f;
+ A = alpha;
+ }
+
+ internal Color(NUI.Vector4 vector4) : this(vector4.X, vector4.Y, vector4.Z, vector4.W)
+ {
+ }
+
+ ///
+ /// Gets a value indicating whether this is default.
+ ///
+ public readonly bool IsDefault => R == -1 && G == -1 && B == -1 && A == -1;
+
+ ///
+ /// Gets a value indicating whether this is zero.
+ ///
+ public readonly bool IsZero => R == 0 && G == 0 && B == 0 && A == 0;
+
+ ///
+ /// Gets the red component of the color.
+ ///
+ public float R
+ {
+ get;
+ }
+
+ ///
+ /// Gets the green component of the color.
+ ///
+ public float G
+ {
+ get;
+ }
+
+ ///
+ /// Gets the blue component of the color.
+ ///
+ public float B
+ {
+ get;
+ }
+
+ ///
+ /// Gets the alpha component of the color.
+ ///
+ public float A
+ {
+ get;
+ }
+
+ ///
+ /// Multiplies the alpha component of the color by the specified value.
+ ///
+ /// The value to multiply the alpha component by.
+ /// The new color.
+ public readonly Color MultiplyAlpha(float alpha) => new Color(R, G, B, A * alpha);
+
+ ///
+ public override string ToString() => $"R:{R}, G:{G}, B:{B}, A:{A}";
+
+ ///
+ /// Returns a new color object with the specified alpha value.
+ ///
+ /// The new alpha value.
+ /// A new color object with the specified alpha value.
+ public readonly Color WithAlpha(float alpha) => new (R, G, B, alpha);
+
+ internal readonly NUI.Color ToReferenceType() => new NUI.Color(R, G, B, A);
+ }
+}
diff --git a/src/Tizen.NUI/src/devel/Lite/L.Corner.cs b/src/Tizen.NUI/src/devel/Lite/L.Corner.cs
new file mode 100644
index 00000000000..8a26c4c8747
--- /dev/null
+++ b/src/Tizen.NUI/src/devel/Lite/L.Corner.cs
@@ -0,0 +1,92 @@
+/*
+ * Copyright(c) 2025 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.ComponentModel;
+
+namespace Tizen.NUI.L
+{
+ ///
+ /// Defines a value type of corner radius.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public struct Corner
+ {
+ ///
+ /// The default corner. (This is to distinguish from zero corners)
+ ///
+ public static readonly Corner Default = new (-1, -1, -1, -1);
+
+ ///
+ /// The zero corner.
+ ///
+ public static readonly Corner Zero = new (0.0f, 0.0f, 0.0f, 0.0f);
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The uniform corner value.
+ public Corner(float uniform) : this(uniform, uniform, uniform, uniform)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The top-left value.
+ /// The top-right value.
+ /// The bottom-right value.
+ /// The bottom-left value.
+ public Corner(float topLeft, float topRight, float bottomRight, float bottomLeft)
+ {
+ TopLeft = topLeft;
+ TopRight = topRight;
+ BottomRight = bottomRight;
+ BottomLeft = bottomLeft;
+ }
+
+ ///
+ /// Gets a value indicating whether this is default.
+ ///
+ public readonly bool IsDefault => TopLeft == -1 && TopRight == -1 && BottomRight == -1 && BottomLeft == -1;
+
+ ///
+ /// Gets a value indicating whether this is zero.
+ ///
+ public readonly bool IsZero => TopLeft == 0 && TopRight == 0 && BottomRight == 0 && BottomLeft == 0;
+
+ ///
+ /// The radius of the top left corner of the rectangle.
+ ///
+ public float TopLeft { get; }
+
+ ///
+ /// The radius of the top right corner of the rectangle.
+ ///
+ public float TopRight { get; }
+
+ ///
+ /// The radius of the bottom right corner of the rectangle.
+ ///
+ public float BottomRight { get; }
+
+ ///
+ /// The radius of the bottom left corner of the rectangle.
+ ///
+ public float BottomLeft { get; }
+
+ internal readonly NUI.Vector4 ToReferenceType() => new NUI.Vector4(TopLeft, TopRight, BottomRight, BottomLeft);
+ }
+}
diff --git a/src/Tizen.NUI/src/devel/Lite/L.Shadow.cs b/src/Tizen.NUI/src/devel/Lite/L.Shadow.cs
new file mode 100644
index 00000000000..cc565ad2005
--- /dev/null
+++ b/src/Tizen.NUI/src/devel/Lite/L.Shadow.cs
@@ -0,0 +1,172 @@
+/*
+ * Copyright(c) 2025 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.ComponentModel;
+using Tizen.NUI.BaseComponents;
+
+namespace Tizen.NUI.L
+{
+ ///
+ /// Defines a value type of shadow.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public struct Shadow
+ {
+ ///
+ /// Create a Shadow.
+ ///
+ /// The blur radius value for the shadow. Bigger value, much blurry.
+ /// Optional. The x offset value from the top left corner. The default is 0.
+ /// Optional. The y offset value from the top left corner. The default is 0.
+ /// Optional. The shadow will extend its size by specified amount of length. The default is 0.
+ /// Optional. The shadow will extend its size by specified amount of length. The default is 0.
+ /// The policy of the shadow cutout. The default is .
+ public Shadow(float blurRadius, float offsetX = 0, float offsetY = 0, float extraWidth = 0, float extraHeight = 0, ColorVisualCutoutPolicyType cutoutPolicy = ColorVisualCutoutPolicyType.None)
+ : this(blurRadius, L.Color.Black, offsetX, offsetY, extraWidth, extraHeight, cutoutPolicy)
+ {
+ }
+
+ ///
+ /// Create a Shadow.
+ ///
+ /// The blur radius value for the shadow. Bigger value, much blurry.
+ /// The color for the shadow.
+ /// Optional. The x offset value from the top left corner. The default is 0.
+ /// Optional. The y offset value from the top left corner. The default is 0.
+ /// Optional. The shadow will extend its size by specified amount of length. The default is 0.
+ /// Optional. The shadow will extend its size by specified amount of length. The default is 0.
+ /// The policy of the shadow cutout. The default is .
+ public Shadow(float blurRadius, L.Color color, float offsetX = 0, float offsetY = 0, float extraWidth = 0, float extraHeight = 0, ColorVisualCutoutPolicyType cutoutPolicy = ColorVisualCutoutPolicyType.None)
+ {
+ BlurRadius = blurRadius;
+ Color = color;
+ OffsetX = offsetX;
+ OffsetY = offsetY;
+ ExtraWidth = extraWidth;
+ ExtraHeight = extraHeight;
+ CutoutPolicy = cutoutPolicy;
+ }
+
+ ///
+ /// The blur radius value for the shadow. Bigger value, much blurry.
+ ///
+ ///
+ /// Negative value is ignored. (no blur)
+ ///
+ public float BlurRadius
+ {
+ get;
+ init;
+ }
+
+ ///
+ /// The color for the shadow.
+ ///
+ public L.Color Color
+ {
+ get;
+ init;
+ }
+
+ ///
+ /// The position offset value (x, y) from the top left corner.
+ ///
+ public float OffsetX
+ {
+ get;
+ init;
+ }
+
+ ///
+ /// The position offset value (x, y) from the top left corner.
+ ///
+ public float OffsetY
+ {
+ get;
+ init;
+ }
+
+ ///
+ /// The shadow will extend its size by specified amount of length.
+ /// If the value is negative then the shadow will shrink.
+ /// For example, when View's size is (100, 100) and the Shadow's extra size are 5 and -5 respectively,
+ /// the output shadow will have size (105, 95).
+ ///
+ public float ExtraWidth
+ {
+ get;
+ init;
+ }
+
+ ///
+ /// The shadow will extend its size by specified amount of length.
+ /// If the value is negative then the shadow will shrink.
+ /// For example, when View's size is (100, 100) and the Shadow's extra size are 5 and -5 respectively,
+ /// the output shadow will have size (105, 95).
+ ///
+ public float ExtraHeight
+ {
+ get;
+ init;
+ }
+
+ ///
+ /// The Cutout policy for this shadow.
+ ///
+ ///
+ /// ColorVisualCutoutPolicyType.None = Fully render the shadow color (Default)
+ /// ColorVisualCutoutPolicyType.CutoutView = Do not render inside bounding box of view
+ /// ColorVisualCutoutPolicyType.CutoutViewWithCornerRadius = Do not render inside view, consider corner radius value
+ ///
+ public ColorVisualCutoutPolicyType CutoutPolicy
+ {
+ get;
+ init;
+ }
+
+ internal readonly NUI.Shadow ToShadow() => new NUI.Shadow(BlurRadius, Color.ToReferenceType(), new (OffsetX, OffsetY), new (ExtraWidth, ExtraHeight));
+
+ internal readonly PropertyMap BuildMap(View attachedView)
+ {
+ using var transform = new PropertyMap()
+ .Append((int)VisualTransformPropertyType.Offset, new L.Vector2(OffsetX, OffsetY))
+ .Append((int)VisualTransformPropertyType.OffsetPolicy, new L.Vector2((int)VisualTransformPolicyType.Absolute, (int)VisualTransformPolicyType.Absolute))
+ .Append((int)VisualTransformPropertyType.ExtraSize, new L.Vector2(ExtraWidth, ExtraHeight))
+ .Append((int)VisualTransformPropertyType.Origin, (int)Visual.AlignType.Center)
+ .Append((int)VisualTransformPropertyType.AnchorPoint, (int)Visual.AlignType.Center);
+
+ PropertyMap map = new PropertyMap()
+ .Append(Visual.Property.Type, (int)Visual.Type.Color)
+ .Append(ColorVisualProperty.MixColor, Color)
+ .Append(ColorVisualProperty.BlurRadius, BlurRadius < 0 ? 0 : BlurRadius)
+ .Append(ColorVisualProperty.CutoutPolicy, (int)CutoutPolicy)
+ .Append(Visual.Property.Transform, transform);
+
+ if (attachedView.CornerRadius != null || attachedView.CornerRadius != Vector4.Zero)
+ {
+ map.Append(Visual.Property.CornerRadius, attachedView.CornerRadius);
+ map.Append(Visual.Property.CornerRadiusPolicy, (int)attachedView.CornerRadiusPolicy);
+ }
+
+ if (attachedView.CornerSquareness != null || attachedView.CornerSquareness != Vector4.Zero)
+ {
+ map.Append(Visual.Property.CornerSquareness, attachedView.CornerSquareness);
+ }
+
+ return map;
+ }
+ }
+}
diff --git a/src/Tizen.NUI/src/devel/Lite/L.Vector2.cs b/src/Tizen.NUI/src/devel/Lite/L.Vector2.cs
new file mode 100644
index 00000000000..0d4ac7c96de
--- /dev/null
+++ b/src/Tizen.NUI/src/devel/Lite/L.Vector2.cs
@@ -0,0 +1,73 @@
+/*
+ * Copyright(c) 2025 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.ComponentModel;
+
+namespace Tizen.NUI.L
+{
+ ///
+ /// Defines a value type of vector2.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public readonly struct Vector2
+ {
+ ///
+ /// The zero vector2.
+ ///
+ public static readonly Vector2 Zero = new (0.0f, 0.0f);
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The x value.
+ /// The y value.
+ public Vector2(float x, float y)
+ {
+ X = x;
+ Y = y;
+ }
+
+ ///
+ /// Gets the x component of the vector2.
+ ///
+ public float X
+ {
+ get;
+ }
+
+ ///
+ /// Gets the y component of the vector2.
+ ///
+ public float Y
+ {
+ get;
+ }
+
+ public readonly bool IsZero => X == 0 && Y == 0;
+
+ ///
+ /// Gets the width component of the vector2.
+ ///
+ public float Width => X;
+
+ ///
+ /// Gets the height component of the vector2.
+ ///
+ public float Height => Y;
+
+ internal readonly NUI.Vector2 ToReferenceType() => new NUI.Vector2(X, Y);
+ }
+}
diff --git a/src/Tizen.NUI/src/devel/Markup/ViewExtensions.cs b/src/Tizen.NUI/src/devel/Markup/ViewExtensions.cs
new file mode 100644
index 00000000000..6ead5056b72
--- /dev/null
+++ b/src/Tizen.NUI/src/devel/Markup/ViewExtensions.cs
@@ -0,0 +1,266 @@
+/*
+ * Copyright(c) 2025 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.ComponentModel;
+using Tizen.NUI.BaseComponents;
+
+namespace Tizen.NUI.Markup
+{
+ ///
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static class ViewExtensions
+ {
+ ///
+ /// Assign this view reference to the given variable.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The variable to save the reference to.
+ /// The view itself.
+ public static T Self(this T view, out T self) where T : View
+ {
+ self = view;
+ return view;
+ }
+
+ ///
+ /// Sets the background color of the view.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The red component.
+ /// The green component.
+ /// The blue component.
+ /// The alpha component.
+ /// The view itself.
+ public static T BackgroundColor(this T view, float r, float g, float b, float a = 1f) where T : View
+ {
+ return view.BackgroundColor(new L.Color(r, g, b, a));
+ }
+
+ ///
+ /// Sets the background color of the view.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The value of 0xRRGGBB format.
+ /// The alpha value between 0.0 and 1.0.
+ /// The view itself.
+ public static T BackgroundColor(this T view, uint value, float alpha) where T : View
+ {
+ return view.BackgroundColor(new L.Color(value, alpha));
+ }
+
+ ///
+ /// Sets the background color of the view.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The color value.
+ /// The view itself.
+ public static T BackgroundColor(this T view, L.Color color) where T : View
+ {
+ view.SetBackgroundColor(color);
+ return view;
+ }
+
+ ///
+ /// Experimental getter for background color
+ ///
+ /// The extension target.
+ /// The background color value.
+ public static L.Color BackgroundColor(this View view)
+ {
+ return Object.InternalRetrievingVisualPropertyColor(view.SwigCPtr, View.Property.BACKGROUND, ColorVisualProperty.MixColor);
+ }
+
+ ///
+ /// Sets the size of the view.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The width value.
+ /// The height value.
+ /// The view itself.
+ public static T Size(this T view, float width, float height) where T : View
+ {
+ view.SizeWidth = width;
+ view.SizeHeight = height;
+ return view;
+ }
+
+ ///
+ /// Sets the size width of the view.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The width value.
+ /// The view itself.
+ public static T SizeWidth(this T view, float width) where T : View
+ {
+ view.SizeWidth = width;
+ return view;
+ }
+
+ ///
+ /// Sets the size height of the view.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The width value.
+ /// The view itself.
+ public static T SizeHeight(this T view, float height) where T : View
+ {
+ view.SizeHeight = height;
+ return view;
+ }
+
+ ///
+ /// Sets the position of the view.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The x value.
+ /// The y value.
+ /// The view itself.
+ public static T Position(this T view, float x, float y) where T : View
+ {
+ view.PositionX = x;
+ view.PositionY = y;
+ return view;
+ }
+
+ ///
+ /// Sets the position x of the view.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The x value.
+ /// The view itself.
+ public static T PositionX(this T view, float x) where T : View
+ {
+ view.PositionX = x;
+ return view;
+ }
+
+ ///
+ /// Sets the position y of the view.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The y value.
+ /// The view itself.
+ public static T PositionY(this T view, float y) where T : View
+ {
+ view.PositionY = y;
+ return view;
+ }
+
+ ///
+ /// Sets the corner radius of the view.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The uniform corner value.
+ /// Sets the corner radius policy to relative. The default is false.
+ /// The view itself.
+ public static T CornerRadius(this T view, float uniform, bool isRelative = false) where T : View
+ {
+ return view.CornerRadius(new L.Corner(uniform, uniform, uniform, uniform), isRelative);
+ }
+
+ ///
+ /// Sets the corner radius of the view.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The top-left value.
+ /// The top-right value.
+ /// The bottom-right value.
+ /// The bottom-left value.
+ /// Sets the corner radius policy to relative. The default is false.
+ /// The view itself.
+ public static T CornerRadius(this T view, float topLeft, float topRight, float bottomRight, float bottomLeft, bool isRelative = false) where T : View
+ {
+ return view.CornerRadius(new L.Corner(topLeft, topRight, bottomRight, bottomLeft), isRelative);
+ }
+
+ ///
+ /// Sets the corner radius of the view.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The corner value.
+ /// Sets the corner radius policy to relative. The default is false.
+ /// The view itself.
+ public static T CornerRadius(this T view, L.Corner corner, bool isRelative = false) where T : View
+ {
+ // TODO Do not create Vector4 here
+ view.CornerRadius = corner.ToReferenceType();
+ view.CornerRadiusPolicy = isRelative ? VisualTransformPolicyType.Relative : VisualTransformPolicyType.Absolute;
+ return view;
+ }
+
+ ///
+ /// Sets the box shadow of the view.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The blur radius value for the shadow. Bigger value, much blurry.
+ /// Optional. The x offset value from the top left corner. The default is 0.
+ /// Optional. The y offset value from the top left corner. The default is 0.
+ /// Optional. The shadow will extend its size by specified amount of length. The default is 0.
+ /// Optional. The shadow will extend its size by specified amount of length. The default is 0.
+ /// The policy of the shadow cutout. The default is .
+ /// The view itself.
+ public static T BoxShadow(this T view, float blurRadius, float offsetX = 0, float offsetY = 0, float extraWidth = 0, float extraHeight = 0, ColorVisualCutoutPolicyType cutoutPolicy = ColorVisualCutoutPolicyType.None) where T : View
+ {
+ return view.BoxShadow(new L.Shadow(blurRadius, offsetX, offsetY, extraWidth, extraHeight, cutoutPolicy));
+ }
+
+ ///
+ /// Sets the box shadow of the view.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The blur radius value for the shadow. Bigger value, much blurry.
+ /// The color for the shadow.
+ /// Optional. The x offset value from the top left corner. The default is 0.
+ /// Optional. The y offset value from the top left corner. The default is 0.
+ /// Optional. The shadow will extend its size by specified amount of length. The default is 0.
+ /// Optional. The shadow will extend its size by specified amount of length. The default is 0.
+ /// The policy of the shadow cutout. The default is .
+ /// The view itself.
+ public static T BoxShadow(this T view, float blurRadius, L.Color color, float offsetX = 0, float offsetY = 0, float extraWidth = 0, float extraHeight = 0, ColorVisualCutoutPolicyType cutoutPolicy = ColorVisualCutoutPolicyType.None) where T : View
+ {
+ return view.BoxShadow(new L.Shadow(blurRadius, color, offsetX, offsetY, extraWidth, extraHeight, cutoutPolicy));
+ }
+
+ ///
+ /// Sets the box shadow of the view.
+ ///
+ /// The type of the view.
+ /// The extension target.
+ /// The shadow value.
+ /// The view itself.
+ public static T BoxShadow(this T view, L.Shadow shadow) where T : View
+ {
+ view.SetBoxShadow(shadow);
+ return view;
+ }
+ }
+}
diff --git a/src/Tizen.NUI/src/internal/Application/Application.cs b/src/Tizen.NUI/src/internal/Application/Application.cs
index 4d775bc9579..5c6669b9518 100755
--- a/src/Tizen.NUI/src/internal/Application/Application.cs
+++ b/src/Tizen.NUI/src/internal/Application/Application.cs
@@ -1,5 +1,5 @@
/*
- * Copyright(c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright(c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -363,6 +363,47 @@ internal override void OnParentResourcesChanged(IEnumerable idleCallbackMap = new Dictionary();
+
+ private void RootIdleCallback()
+ {
+ if (idleCallbackMap == null || IsDisposedOrQueued)
+ {
+ Tizen.Log.Error("NUI", $"[Error] Application disposed! Fail to execute idle callback!\n");
+ return;
+ }
+
+ Tizen.Log.Debug("NUI", $"Application RootIdleCallback comes\n");
+ // Reset root idle callback as null now, since we could call AddIdle during delegate function invoke
+ rootIdleCallback = null;
+
+ // Copy key list of idle callback map
+ // (Since idle callback could change the dictionary itself during iteration, we need to make a copy of keys first)
+ List delegateList = new List();
+ foreach (var func in idleCallbackMap.Keys)
+ {
+ delegateList.Add(func);
+ }
+
+ foreach (var func in delegateList)
+ {
+ // Remove delegate at map first, and then invoke it.
+ bool isValid = false;
+ if (idleCallbackMap?.Remove(func, out isValid) ?? false)
+ {
+ if (isValid)
+ {
+ func.DynamicInvoke();
+ }
+ }
+ }
+ Tizen.Log.Debug("NUI", $"Application RootIdleCallback finished\n");
+ }
+
internal Application(global::System.IntPtr cPtr, bool cMemoryOwn) : base(cPtr, cMemoryOwn)
{
SetCurrentApplication(this);
@@ -376,145 +417,150 @@ protected override void Dispose(DisposeTypes type)
return;
}
- //Release your own unmanaged resources here.
- //You should not access any managed member here except static instance.
- //because the execution order of Finalizes is non-deterministic.
- if (applicationInitEventCallbackDelegate != null)
+ if (type == DisposeTypes.Explicit)
{
- initSignal?.Disconnect(applicationInitEventCallbackDelegate);
- initSignal?.Dispose();
- initSignal = null;
- }
+ //Release your own unmanaged resources here.
+ //You should not access any managed member here except static instance.
+ //because the execution order of Finalizes is non-deterministic.
+ if (applicationInitEventCallbackDelegate != null)
+ {
+ initSignal?.Disconnect(applicationInitEventCallbackDelegate);
+ initSignal?.Dispose();
+ initSignal = null;
+ }
- if (applicationTerminateEventCallbackDelegate != null)
- {
- terminateSignal?.Disconnect(applicationTerminateEventCallbackDelegate);
- terminateSignal?.Dispose();
- terminateSignal = null;
- }
+ if (applicationTerminateEventCallbackDelegate != null)
+ {
+ terminateSignal?.Disconnect(applicationTerminateEventCallbackDelegate);
+ terminateSignal?.Dispose();
+ terminateSignal = null;
+ }
- if (applicationPauseEventCallbackDelegate != null)
- {
- pauseSignal?.Disconnect(applicationPauseEventCallbackDelegate);
- pauseSignal?.Dispose();
- pauseSignal = null;
- }
+ if (applicationPauseEventCallbackDelegate != null)
+ {
+ pauseSignal?.Disconnect(applicationPauseEventCallbackDelegate);
+ pauseSignal?.Dispose();
+ pauseSignal = null;
+ }
- if (applicationResumeEventCallbackDelegate != null)
- {
- resumeSignal?.Disconnect(applicationResumeEventCallbackDelegate);
- resumeSignal?.Dispose();
- resumeSignal = null;
- }
+ if (applicationResumeEventCallbackDelegate != null)
+ {
+ resumeSignal?.Disconnect(applicationResumeEventCallbackDelegate);
+ resumeSignal?.Dispose();
+ resumeSignal = null;
+ }
- if (applicationResetEventCallbackDelegate != null)
- {
- resetSignal?.Disconnect(applicationResetEventCallbackDelegate);
- resetSignal?.Dispose();
- resetSignal = null;
- }
+ if (applicationResetEventCallbackDelegate != null)
+ {
+ resetSignal?.Disconnect(applicationResetEventCallbackDelegate);
+ resetSignal?.Dispose();
+ resetSignal = null;
+ }
- if (applicationLanguageChangedEventCallbackDelegate != null)
- {
- languageChangedSignal?.Disconnect(applicationLanguageChangedEventCallbackDelegate);
- languageChangedSignal?.Dispose();
- languageChangedSignal = null;
- }
+ if (applicationLanguageChangedEventCallbackDelegate != null)
+ {
+ languageChangedSignal?.Disconnect(applicationLanguageChangedEventCallbackDelegate);
+ languageChangedSignal?.Dispose();
+ languageChangedSignal = null;
+ }
- if (applicationRegionChangedEventCallbackDelegate != null)
- {
- regionChangedSignal?.Disconnect(applicationRegionChangedEventCallbackDelegate);
- regionChangedSignal?.Dispose();
- regionChangedSignal = null;
- }
+ if (applicationRegionChangedEventCallbackDelegate != null)
+ {
+ regionChangedSignal?.Disconnect(applicationRegionChangedEventCallbackDelegate);
+ regionChangedSignal?.Dispose();
+ regionChangedSignal = null;
+ }
- if (applicationBatteryLowEventCallbackDelegate != null)
- {
- batteryLowSignal?.Disconnect(applicationBatteryLowEventCallbackDelegate);
- batteryLowSignal?.Dispose();
- batteryLowSignal = null;
- }
+ if (applicationBatteryLowEventCallbackDelegate != null)
+ {
+ batteryLowSignal?.Disconnect(applicationBatteryLowEventCallbackDelegate);
+ batteryLowSignal?.Dispose();
+ batteryLowSignal = null;
+ }
- if (applicationMemoryLowEventCallbackDelegate != null)
- {
- memoryLowSignal?.Disconnect(applicationMemoryLowEventCallbackDelegate);
- memoryLowSignal?.Dispose();
- memoryLowSignal = null;
- }
+ if (applicationMemoryLowEventCallbackDelegate != null)
+ {
+ memoryLowSignal?.Disconnect(applicationMemoryLowEventCallbackDelegate);
+ memoryLowSignal?.Dispose();
+ memoryLowSignal = null;
+ }
- if (applicationDeviceOrientationChangedEventCallback != null)
- {
- deviceOrientationChangedSignal?.Disconnect(applicationDeviceOrientationChangedEventCallback);
- deviceOrientationChangedSignal?.Dispose();
- deviceOrientationChangedSignal = null;
- }
+ if (applicationDeviceOrientationChangedEventCallback != null)
+ {
+ deviceOrientationChangedSignal?.Disconnect(applicationDeviceOrientationChangedEventCallback);
+ deviceOrientationChangedSignal?.Dispose();
+ deviceOrientationChangedSignal = null;
+ }
- if (applicationAppControlEventCallbackDelegate != null)
- {
- appControlSignal?.Disconnect(applicationAppControlEventCallbackDelegate);
- appControlSignal?.Dispose();
- appControlSignal = null;
- }
+ if (applicationAppControlEventCallbackDelegate != null)
+ {
+ appControlSignal?.Disconnect(applicationAppControlEventCallbackDelegate);
+ appControlSignal?.Dispose();
+ appControlSignal = null;
+ }
- //Task
- if (applicationTaskInitEventCallbackDelegate != null)
- {
- taskInitSignal?.Disconnect(applicationTaskInitEventCallbackDelegate);
- taskInitSignal?.Dispose();
- taskInitSignal = null;
- }
+ //Task
+ if (applicationTaskInitEventCallbackDelegate != null)
+ {
+ taskInitSignal?.Disconnect(applicationTaskInitEventCallbackDelegate);
+ taskInitSignal?.Dispose();
+ taskInitSignal = null;
+ }
- if (applicationTaskTerminateEventCallbackDelegate != null)
- {
- taskTerminateSignal?.Disconnect(applicationTaskTerminateEventCallbackDelegate);
- taskTerminateSignal?.Dispose();
- taskTerminateSignal = null;
- }
+ if (applicationTaskTerminateEventCallbackDelegate != null)
+ {
+ taskTerminateSignal?.Disconnect(applicationTaskTerminateEventCallbackDelegate);
+ taskTerminateSignal?.Dispose();
+ taskTerminateSignal = null;
+ }
- if (applicationTaskLanguageChangedEventCallbackDelegate != null)
- {
- taskLanguageChangedSignal?.Disconnect(applicationTaskLanguageChangedEventCallbackDelegate);
- taskLanguageChangedSignal?.Dispose();
- taskLanguageChangedSignal = null;
- }
+ if (applicationTaskLanguageChangedEventCallbackDelegate != null)
+ {
+ taskLanguageChangedSignal?.Disconnect(applicationTaskLanguageChangedEventCallbackDelegate);
+ taskLanguageChangedSignal?.Dispose();
+ taskLanguageChangedSignal = null;
+ }
- if (applicationTaskRegionChangedEventCallbackDelegate != null)
- {
- taskRegionChangedSignal?.Disconnect(applicationTaskRegionChangedEventCallbackDelegate);
- taskRegionChangedSignal?.Dispose();
- taskRegionChangedSignal = null;
- }
+ if (applicationTaskRegionChangedEventCallbackDelegate != null)
+ {
+ taskRegionChangedSignal?.Disconnect(applicationTaskRegionChangedEventCallbackDelegate);
+ taskRegionChangedSignal?.Dispose();
+ taskRegionChangedSignal = null;
+ }
- if (applicationTaskBatteryLowEventCallbackDelegate != null)
- {
- taskBatteryLowSignal?.Disconnect(applicationTaskBatteryLowEventCallbackDelegate);
- taskBatteryLowSignal?.Dispose();
- taskBatteryLowSignal = null;
- }
+ if (applicationTaskBatteryLowEventCallbackDelegate != null)
+ {
+ taskBatteryLowSignal?.Disconnect(applicationTaskBatteryLowEventCallbackDelegate);
+ taskBatteryLowSignal?.Dispose();
+ taskBatteryLowSignal = null;
+ }
- if (applicationTaskMemoryLowEventCallbackDelegate != null)
- {
- taskMemoryLowSignal?.Disconnect(applicationTaskMemoryLowEventCallbackDelegate);
- taskMemoryLowSignal?.Dispose();
- taskMemoryLowSignal = null;
- }
+ if (applicationTaskMemoryLowEventCallbackDelegate != null)
+ {
+ taskMemoryLowSignal?.Disconnect(applicationTaskMemoryLowEventCallbackDelegate);
+ taskMemoryLowSignal?.Dispose();
+ taskMemoryLowSignal = null;
+ }
- if (applicationTaskDeviceOrientationChangedEventCallback != null)
- {
- taskDeviceOrientationChangedSignal?.Disconnect(applicationTaskDeviceOrientationChangedEventCallback);
- taskDeviceOrientationChangedSignal?.Dispose();
- taskDeviceOrientationChangedSignal = null;
- }
+ if (applicationTaskDeviceOrientationChangedEventCallback != null)
+ {
+ taskDeviceOrientationChangedSignal?.Disconnect(applicationTaskDeviceOrientationChangedEventCallback);
+ taskDeviceOrientationChangedSignal?.Dispose();
+ taskDeviceOrientationChangedSignal = null;
+ }
- if (applicationTaskAppControlEventCallbackDelegate != null)
- {
- taskAppControlSignal?.Disconnect(applicationTaskAppControlEventCallbackDelegate);
- taskAppControlSignal?.Dispose();
- taskAppControlSignal = null;
+ if (applicationTaskAppControlEventCallbackDelegate != null)
+ {
+ taskAppControlSignal?.Disconnect(applicationTaskAppControlEventCallbackDelegate);
+ taskAppControlSignal?.Dispose();
+ taskAppControlSignal = null;
+ }
+
+ window?.Dispose();
+ window = null;
}
- window?.Dispose();
- window = null;
+ idleCallbackMap = null;
base.Dispose(type);
}
@@ -1721,13 +1767,76 @@ public static Application NewApplication(string[] args, string stylesheet, bool
///
public bool AddIdle(System.Delegate func)
{
- System.IntPtr ip = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(func);
- System.IntPtr ip2 = Interop.Application.MakeCallback(new System.Runtime.InteropServices.HandleRef(this, ip));
+ bool idleAdded = false; // default value is false
- bool ret = Interop.Application.AddIdle(SwigCPtr, new System.Runtime.InteropServices.HandleRef(this, ip2));
+ if (idleCallbackMap == null || IsDisposedOrQueued)
+ {
+ Tizen.Log.Error("NUI", $"[Error] Application disposed! failed to add idle {func}\n");
+ return false;
+ }
- if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
- return ret;
+ // Register root idle callback for Tizen.NUI.Application
+ if (rootIdleCallback == null)
+ {
+ rootIdleCallback = RootIdleCallback;
+ System.IntPtr ip = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(rootIdleCallback);
+ System.IntPtr ip2 = Interop.Application.MakeCallback(new System.Runtime.InteropServices.HandleRef(this, ip));
+
+ bool ret = Interop.Application.AddIdle(SwigCPtr, new System.Runtime.InteropServices.HandleRef(this, ip2));
+ NDalicPINVOKE.ThrowExceptionIfExists();
+ if (!ret)
+ {
+ rootIdleCallback = null;
+ Tizen.Log.Error("NUI", $"[Error] failed to add idle {func}\n");
+ return false;
+ }
+ }
+
+ if (idleCallbackMap.TryGetValue(func, out bool isValid))
+ {
+ idleAdded = true; // success case
+ if (!isValid)
+ {
+ idleCallbackMap[func] = true;
+ }
+ else
+ {
+ Tizen.Log.Debug("NUI", $"Already added idle {func}\n");
+ }
+ }
+ else if (idleCallbackMap.TryAdd(func, true))
+ {
+ idleAdded = true; // success case
+ }
+
+ if (!idleAdded)
+ {
+ Tizen.Log.Error("NUI", $"[Error] failed to add idle {func}\n");
+ }
+
+ return idleAdded;
+ }
+
+ ///
+ /// Remove delegate what we added by AddIdle.
+ ///
+ /// The function to remove
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void RemoveIdle(System.Delegate func)
+ {
+ if (idleCallbackMap == null || IsDisposedOrQueued)
+ {
+ Tizen.Log.Error("NUI", $"[Error] Application disposed! failed to remove idle {func}\n");
+ return;
+ }
+
+ if (idleCallbackMap.TryGetValue(func, out bool isValid))
+ {
+ if (isValid)
+ {
+ idleCallbackMap[func] = false;
+ }
+ }
}
/**
@@ -1925,13 +2034,6 @@ public void Quit()
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
}
- internal bool AddIdle(SWIGTYPE_p_Dali__CallbackBase callback)
- {
- bool ret = Interop.Application.AddIdle(SwigCPtr, SWIGTYPE_p_Dali__CallbackBase.getCPtr(callback));
- if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
- return ret;
- }
-
public Window GetWindow()
{
if (window != null)
diff --git a/src/Tizen.NUI/src/internal/Application/NUICoreBackend.cs b/src/Tizen.NUI/src/internal/Application/NUICoreBackend.cs
index e736eba1259..bc79963932b 100755
--- a/src/Tizen.NUI/src/internal/Application/NUICoreBackend.cs
+++ b/src/Tizen.NUI/src/internal/Application/NUICoreBackend.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -158,6 +158,16 @@ public bool AddIdle(System.Delegate func)
return application.AddIdle(func);
}
+ ///
+ /// Remove delegate what we added by AddIdle.
+ ///
+ /// The function to remove
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void RemoveIdle(System.Delegate func)
+ {
+ application.RemoveIdle(func);
+ }
+
///
/// The Run application.
///
diff --git a/src/Tizen.NUI/src/internal/Common/ControlStateUtility.cs b/src/Tizen.NUI/src/internal/Common/ControlStateUtility.cs
new file mode 100644
index 00000000000..efdd0acc3e9
--- /dev/null
+++ b/src/Tizen.NUI/src/internal/Common/ControlStateUtility.cs
@@ -0,0 +1,75 @@
+/*
+ * Copyright(c) 2025 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;
+
+namespace Tizen.NUI
+{
+ ///
+ /// Manages state name and bit mask.
+ ///
+ internal static class ControlStateUtility
+ {
+ private const int MaxBitWidth = 62;
+ private static readonly Dictionary registeredStates = new Dictionary();
+ private static int nextBitPosition = 0;
+
+ ///
+ ///
+ public static ulong FullMask => (1UL << MaxBitWidth) - 1UL;
+
+ ///
+ ///
+ public static IEnumerable<(string, ulong)> RegisteredStates()
+ {
+
+ foreach (var (key, value) in registeredStates)
+ {
+ yield return (key, value);
+ }
+ }
+
+ public static ulong Register(string stateName)
+ {
+ if (stateName == null)
+ throw new ArgumentNullException($"{nameof(stateName)} cannot be null.", nameof(stateName));
+
+ if (string.IsNullOrWhiteSpace(stateName))
+ throw new ArgumentException($"{nameof(stateName)} cannot be whitespace.", nameof(stateName));
+
+ string trimmed = stateName.Trim();
+ ulong bitMask = 0UL;
+
+ if (trimmed == "All") return FullMask;
+
+ if (trimmed != "Normal" && !registeredStates.TryGetValue(trimmed, out bitMask))
+ {
+ if (nextBitPosition + 1 > MaxBitWidth)
+ {
+ throw new ArgumentException($"The given state name '{stateName}' is not acceptable since there is no more room to register a new state.");
+ }
+
+ bitMask = 1UL << nextBitPosition;
+ registeredStates.Add(trimmed, bitMask);
+ nextBitPosition++;
+ }
+
+ return bitMask;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Tizen.NUI/src/internal/Common/FriendAssembly.cs b/src/Tizen.NUI/src/internal/Common/FriendAssembly.cs
index eb0bf8cac7f..cc700af7089 100755
--- a/src/Tizen.NUI/src/internal/Common/FriendAssembly.cs
+++ b/src/Tizen.NUI/src/internal/Common/FriendAssembly.cs
@@ -46,6 +46,10 @@
[assembly: InternalsVisibleTo("Tizen.NUI.OneUI, " + Tizen.NUI.PublicKey.TizenNUIOneUI)]
+[assembly: InternalsVisibleTo("Tizen.NUI.FLUX, " + Tizen.NUI.PublicKey.TizenFLUX)]
+[assembly: InternalsVisibleTo("Tizen.NUI.FLUX.Components, " + Tizen.NUI.PublicKey.TizenFLUX)]
+[assembly: InternalsVisibleTo("Tizen.DA.FLUX.Components, " + Tizen.NUI.PublicKey.TizenFLUX)]
+
namespace Tizen.NUI
{
internal static class PublicKey
@@ -80,5 +84,12 @@ internal static class PublicKey
"cbcbd8f2d37eb91217120d199fe0b86e1bdfd955873558a630ee595bcca0ade643f1750b22a910" +
"60ea4e84639b40b346991f8271f18d4a6bd2802c41762846e13bf0ec0d82d4857be253ee8cbd7c" +
"9561c193";
+
+ internal const string TizenFLUX =
+ "PublicKey=0024000004800000940000000602000000240000525341310004000001000100ed445c2a988d35b" +
+ "99e5766f42eef33d89d1f67225db051f011abcfce47a4788875c39750a2e5695f1ec117f41d96610419811762" +
+ "669f98131db30a6e64c6bb8fde3731f373f6cda4c0087d121f5108559f216dc015807dc46ec5a4d1b63f5deff" +
+ "64c01754a0db0dc849bc300672572cbd2697432ab6c193ebf9fade6bf0f2aad";
+
}
}
diff --git a/src/Tizen.NUI/src/internal/Common/Object.cs b/src/Tizen.NUI/src/internal/Common/Object.cs
index 81a50787724..cf68612fec4 100755
--- a/src/Tizen.NUI/src/internal/Common/Object.cs
+++ b/src/Tizen.NUI/src/internal/Common/Object.cs
@@ -299,5 +299,80 @@ internal static int InternalSetPropertyVector2ActualVector3(HandleRef actor, int
}
return ret;
}
+
+ internal static int InternalRetrievingVisualPropertyInt(HandleRef actor, int visualIndex, int visualPropertyIndex, out int retrievingInt)
+ {
+ if (actor.Handle == System.IntPtr.Zero)
+ {
+ throw new System.InvalidOperationException("Error! NUI's native dali object is already disposed. OR the native dali object handle of NUI becomes null!");
+ }
+ var ret = Interop.View.InternalRetrievingVisualPropertyInt(actor, visualIndex, visualPropertyIndex, out retrievingInt);
+ NDalicPINVOKE.ThrowExceptionIfExists();
+ return ret;
+ }
+
+ internal static int InternalRetrievingVisualPropertyVector4(HandleRef actor, int visualIndex, int visualPropertyIndex, HandleRef retrievingVector4)
+ {
+ if (actor.Handle == System.IntPtr.Zero)
+ {
+ throw new System.InvalidOperationException("Error! NUI's native dali object is already disposed. OR the native dali object handle of NUI becomes null!");
+ }
+ var ret = Interop.View.InternalRetrievingVisualPropertyVector4(actor, visualIndex, visualPropertyIndex, retrievingVector4);
+ NDalicPINVOKE.ThrowExceptionIfExists();
+ return ret;
+ }
+
+ ///
+ /// Sets color value (vector4) to actor.
+ ///
+ ///
+ /// This is not thread safe.
+ ///
+ internal static void InternalSetPropertyColor(HandleRef actor, int propertyType, L.Color color)
+ {
+ if (actor.Handle == System.IntPtr.Zero)
+ {
+ throw new System.InvalidOperationException("Error! NUI's native dali object is already disposed. OR the native dali object handle of NUI becomes null!");
+ }
+
+ ReusablePool.GetOne((vector4, actor, propertyType, color) =>
+ {
+ vector4.Reset(color);
+ _ = Interop.Actor.InternalSetPropertyVector4(actor, propertyType, vector4.SwigCPtr);
+ NDalicPINVOKE.ThrowExceptionIfExists();
+ }, actor, propertyType, color);
+ }
+
+ ///
+ /// GEts color value (vector4) from actor.
+ ///
+ ///
+ /// This is not thread safe.
+ ///
+ internal static L.Color InternalRetrievingVisualPropertyColor(HandleRef actor, int visualIndex, int visualPropertyIndex)
+ {
+ if (actor.Handle == System.IntPtr.Zero)
+ {
+ throw new System.InvalidOperationException("Error! NUI's native dali object is already disposed. OR the native dali object handle of NUI becomes null!");
+ }
+
+ return ReusablePool.GetOne((vector4, actor, visualIndex, visualPropertyIndex) =>
+ {
+ vector4.Reset();
+ _ = Interop.View.InternalRetrievingVisualPropertyVector4(actor, visualIndex, visualPropertyIndex, vector4.SwigCPtr);
+ NDalicPINVOKE.ThrowExceptionIfExists();
+ return new L.Color(vector4);
+ }, actor, visualIndex, visualPropertyIndex);
+ }
+
+ internal static void InternalSetPropertyMap(HandleRef actor, int propertyType, HandleRef map)
+ {
+ if (actor.Handle == System.IntPtr.Zero)
+ {
+ throw new System.InvalidOperationException("Error! NUI's native dali object is already disposed. OR the native dali object handle of NUI becomes null!");
+ }
+ _ = Interop.Actor.InternalSetPropertyMap(actor, propertyType, map);
+ NDalicPINVOKE.ThrowExceptionIfExists();
+ }
}
}
diff --git a/src/Tizen.NUI/src/internal/Common/ReusablePool.cs b/src/Tizen.NUI/src/internal/Common/ReusablePool.cs
new file mode 100644
index 00000000000..6d643a01e3f
--- /dev/null
+++ b/src/Tizen.NUI/src/internal/Common/ReusablePool.cs
@@ -0,0 +1,189 @@
+/*
+ * Copyright(c) 2025 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;
+
+namespace Tizen.NUI
+{
+ ///
+ /// Provide methods to reuse `Disposable` primitive data such as Color and Size.
+ /// Please be aware that this class is not thread-safe.
+ ///
+ internal static class ReusablePool where T : Disposable, new()
+ {
+ private const int MaxPoolSize = 3;
+ private static readonly Stack pool = new ();
+
+ ///
+ /// Get available instance from pool or create new one.
+ /// The passed instance only valid in action scope.
+ ///
+ public static void GetOne(Action action)
+ {
+ if (!pool.TryPop(out T value))
+ {
+ value = new T();
+ }
+ action(value);
+ Push(value);
+ }
+
+ ///
+ /// Get available instance from pool or create new one.
+ /// The passed instance only valid in action scope.
+ ///
+ ///
+ /// This method is to avoid generating closure and action instance every time when using lambda expression.
+ /// It's better to use this method when possible.
+ ///
+ public static void GetOne(Action