[NUI] Introduce ThemeResource (#2176)
authorYeongJong Lee <cleanlyj@naver.com>
Fri, 6 Nov 2020 07:42:50 +0000 (16:42 +0900)
committerGitHub <noreply@github.com>
Fri, 6 Nov 2020 07:42:50 +0000 (16:42 +0900)
You can define resources(e.g. color, image etc.) of theme in seperate files.
ThemeResource can be changed in runtime without changing theme file.

src/Tizen.NUI/src/internal/XamlBinding/Internals/ResourceLoader.cs
src/Tizen.NUI/src/public/Theme/Theme.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ThemeResourceSample.cs [new file with mode: 0644]
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/res/SampleTheme.xaml [new file with mode: 0644]
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/res/SampleThemeResourceDark.xaml [new file with mode: 0644]
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/res/SampleThemeResourceDefault.xaml [new file with mode: 0644]

index 9016f4d..364d6ab 100755 (executable)
@@ -9,8 +9,11 @@ namespace Tizen.NUI.Binding.Internals
     {
         static Func<AssemblyName, string, string> resourceProvider = (asmName, path) =>
         {
-            string resource = Tizen.Applications.Application.Current.DirectoryInfo.Resource;
-            path = resource + path;
+            if (typeof(Theme).Assembly.GetName().FullName != asmName.FullName)
+            {
+                string resource = Tizen.Applications.Application.Current.DirectoryInfo.Resource;
+                path = resource + path;
+            }
 
             string ret = File.ReadAllText(path);
             return ret;
index ab48b4e..c7392fc 100644 (file)
@@ -18,6 +18,7 @@ using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.ComponentModel;
+using System.Reflection;
 using System.Xml;
 using Tizen.NUI.BaseComponents;
 using Tizen.NUI.Binding;
@@ -27,10 +28,28 @@ namespace Tizen.NUI
 {
     /// <summary></summary>
     [EditorBrowsable(EditorBrowsableState.Never)]
-    public class Theme : BindableObject
+    public class Theme : BindableObject, IResourcesProvider
     {
         private readonly Dictionary<string, ViewStyle> map;
         private string baseTheme;
+        private string resource;
+        private string xamlFile;
+
+        /// <summary>
+        /// The resource file path that is used in the theme.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public string Resource
+        {
+            get => resource;
+            set
+            {
+                if (resource == value) return;
+                resource = value;
+
+                Reload();
+            }
+        }
 
         /// <summary>Create an empty theme.</summary>
         [EditorBrowsable(EditorBrowsableState.Never)]
@@ -52,25 +71,31 @@ namespace Tizen.NUI
                 throw new ArgumentNullException("The xaml file path cannot be null or empty string", nameof(xamlFile));
             }
 
-            try
-            {
-                using(var reader = XmlReader.Create(xamlFile))
-                {
-                    XamlLoader.Load(this, reader);
-                }
-            }
-            catch (global::System.IO.IOException e)
-            {
-                Tizen.Log.Info("NUI", $"Could not load \"{xamlFile}\".\n");
-                throw e;
-            }
-            catch (Exception e)
-            {
-                Tizen.Log.Info("NUI", $"Could not parse \"{xamlFile}\".\n");
-                Tizen.Log.Info("NUI", "Make sure the all used assemblies (e.g. Tizen.NUI.Components) are included in the application project.\n");
-                Tizen.Log.Info("NUI", "Make sure the type and namespace are correct.\n");
-                throw e;
-            }
+            LoadFromXaml(xamlFile);
+            this.xamlFile = xamlFile;
+        }
+
+        /// <summary>
+        /// Create a new theme from the xaml file with theme resource.
+        /// </summary>
+        /// <param name="xamlFile">An absolute path to the xaml file.</param>
+        /// <param name="themeResource">An absolute path to the theme resource file.</param>
+        /// <exception cref="ArgumentNullException">Thrown when the given xamlFile is null or empty string.</exception>
+        /// <exception cref="System.IO.IOException">Thrown when there are file IO problems.</exception>
+        /// <exception cref="Exception">Thrown when the content of the xaml file is not valid xaml form.</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Theme(string xamlFile, string themeResource) : this()
+        {
+            if (string.IsNullOrEmpty(xamlFile))
+                throw new ArgumentNullException(nameof(xamlFile), "The xaml file path cannot be null or empty string");
+            if (string.IsNullOrEmpty(themeResource))
+                throw new ArgumentNullException(nameof(themeResource), "The theme resource file path cannot be null or empty string");
+
+            resource = themeResource;
+            XamlResources.SetAndLoadSource(new Uri(themeResource), themeResource, Assembly.GetAssembly(GetType()), null);
+
+            LoadFromXaml(xamlFile);
+            this.xamlFile = xamlFile;
         }
 
         /// <summary></summary>
@@ -108,6 +133,14 @@ namespace Tizen.NUI
             }
         }
 
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public bool IsResourcesCreated { get; } = true;
+
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ResourceDictionary XamlResources { get; set; } = new ResourceDictionary();
+
         /// <summary>
         /// For Xaml use only.
         /// Note that it is not a normal indexer.
@@ -237,7 +270,9 @@ namespace Tizen.NUI
         {
             if (theme == null)
                 throw new ArgumentNullException(nameof(theme));
-            
+
+            this.xamlFile = theme.xamlFile;
+
             foreach (var item in theme)
             {
                 if (item.Value == null)
@@ -259,5 +294,43 @@ namespace Tizen.NUI
         /// Internal use only.
         /// </summary>
         internal void AddStyleWithoutClone(string styleName, ViewStyle value) => map[styleName] = value;
+
+        internal void Reload()
+        {
+            if (xamlFile == null)
+                throw new InvalidOperationException("Cannot reload without xaml file.");
+
+            map.Clear();
+            if (Resource != null)
+            {
+                XamlResources.Clear();
+                XamlResources.SetAndLoadSource(new Uri(Resource), Resource, Assembly.GetAssembly(GetType()), null);
+            }
+
+            LoadFromXaml(xamlFile);
+        }
+
+        private void LoadFromXaml(string xamlFile)
+        {
+            try
+            {
+                using (var reader = XmlReader.Create(xamlFile))
+                {
+                    XamlLoader.Load(this, reader);
+                }
+            }
+            catch (System.IO.IOException)
+            {
+                Tizen.Log.Error("NUI", $"Could not load \"{xamlFile}\".\n");
+                throw;
+            }
+            catch (Exception)
+            {
+                Tizen.Log.Error("NUI", $"Could not parse \"{xamlFile}\".\n");
+                Tizen.Log.Error("NUI", "Make sure the all used assemblies (e.g. Tizen.NUI.Components) are included in the application project.\n");
+                Tizen.Log.Error("NUI", "Make sure the type and namespace are correct.\n");
+                throw;
+            }
+        }
     }
 }
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ThemeResourceSample.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ThemeResourceSample.cs
new file mode 100644 (file)
index 0000000..86a893d
--- /dev/null
@@ -0,0 +1,43 @@
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+
+namespace Tizen.NUI.Samples
+{
+    public class ThemeResourceSample : IExample
+    {
+        public void Activate()
+        {
+            string resourceDefault = System.IO.Path.Combine("res", "resSampleThemeResourceDefault.xaml");
+            string resourceDark = System.IO.Path.Combine("res", "SampleThemeResourceDark.xaml");
+            Theme sampleTheme = new Theme(System.IO.Path.Combine("res", "SampleTheme.xaml"), resourceDefault);
+            ThemeManager.ApplyTheme(sampleTheme);
+
+            View root = new View();
+            root.WidthSpecification = LayoutParamPolicies.MatchParent;
+            root.HeightSpecification = LayoutParamPolicies.MatchParent;
+            Window.Instance.GetDefaultLayer().Add(root);
+
+            Button button = new Button();
+            button.ThemeChangeSensitive = true;
+            button.Size = new Size2D(200, 200);
+            button.Clicked += (object sender, ClickedEventArgs e) =>
+            {
+                if (sampleTheme.Resource == resourceDefault)
+                {
+                    sampleTheme.Resource = resourceDark;
+                    ThemeManager.ApplyTheme(sampleTheme);
+
+                }
+                else
+                {
+                    sampleTheme.Resource = resourceDefault;
+                    ThemeManager.ApplyTheme(sampleTheme);
+                }
+            };
+            root.Add(button);
+        }
+
+        public void Deactivate() {}
+    }
+}
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/res/SampleTheme.xaml b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/res/SampleTheme.xaml
new file mode 100644 (file)
index 0000000..041fd25
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Theme
+  xmlns="http://tizen.org/Tizen.NUI/2018/XAML"
+  xmlns:c="clr-namespace:Tizen.NUI.Components;assembly=Tizen.NUI.Components"
+  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+  Id="MyTheme"
+  BasedOn="Tizen.NUI.Theme.Common">
+
+
+    <!--Button-->
+    <c:ButtonStyle x:Key="Tizen.NUI.Components.Button" >
+        <c:ButtonStyle.BackgroundColor>
+            <Selector x:TypeArguments="Color" Normal="{x:StaticResource ButtonBackgroundColorNormal}" Pressed="{x:StaticResource ButtonBackgroundColorPressed}" Disabled="{x:StaticResource ButtonBackgroundColorDisabled}"/>
+        </c:ButtonStyle.BackgroundColor>
+    </c:ButtonStyle>
+</Theme>
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/res/SampleThemeResourceDark.xaml b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/res/SampleThemeResourceDark.xaml
new file mode 100644 (file)
index 0000000..c6ea46d
--- /dev/null
@@ -0,0 +1,8 @@
+<ResourceDictionary
+      xmlns="http://tizen.org/Tizen.NUI/2018/XAML"
+      xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
+
+    <Color x:Key="ButtonBackgroundColorNormal">0.309, 0.309, 0.309,1</Color>
+    <Color x:Key="ButtonBackgroundColorPressed">0.631,0.631,0.631,1</Color>
+    <Color x:Key="ButtonBackgroundColorDisabled">0.8,0.8,0.8,1</Color>
+</ResourceDictionary>
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/res/SampleThemeResourceDefault.xaml b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/res/SampleThemeResourceDefault.xaml
new file mode 100644 (file)
index 0000000..f198231
--- /dev/null
@@ -0,0 +1,8 @@
+<ResourceDictionary
+      xmlns="http://tizen.org/Tizen.NUI/2018/XAML"
+      xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
+
+    <Color x:Key="ButtonBackgroundColorNormal">0.054, 0.631, 0.921,1 </Color>
+    <Color x:Key="ButtonBackgroundColorPressed">0.454, 0.752, 0.905,1</Color>
+    <Color x:Key="ButtonBackgroundColorDisabled">0.88,0.88,0.88,1</Color>
+</ResourceDictionary>