[NUI] Add SetUnderline, GetUnderline to Text Components
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / BaseComponents / ControlState.cs
1 /*
2  * Copyright(c) 2020-2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 using System;
19 using System.Collections.Generic;
20 using System.ComponentModel;
21 using System.Linq;
22
23 namespace Tizen.NUI.BaseComponents
24 {
25     /// <summary>
26     /// Class for describing the states of control.
27     /// If a non-control view class would want to get the control state, please refer <see cref="View.EnableControlState"/>.
28     /// </summary>
29     /// <since_tizen> 9 </since_tizen>
30     [Binding.TypeConverter(typeof(ControlStateTypeConverter))]
31     public class ControlState : IEquatable<ControlState>
32     {
33         private static readonly Dictionary<string, ControlState> stateDictionary = new Dictionary<string, ControlState>();
34         //Default States
35         /// <summary>
36         /// 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.
37         /// </summary>
38         /// <since_tizen> 9 </since_tizen>
39         public static readonly ControlState All = Create("All");
40         /// <summary>
41         /// Normal State.
42         /// </summary>
43         /// <since_tizen> 9 </since_tizen>
44         public static readonly ControlState Normal = Create("Normal");
45         /// <summary>
46         /// Focused State.
47         /// </summary>
48         /// <since_tizen> 9 </since_tizen>
49         public static readonly ControlState Focused = Create("Focused");
50         /// <summary>
51         /// Pressed State.
52         /// </summary>
53         /// <since_tizen> 9 </since_tizen>
54         public static readonly ControlState Pressed = Create("Pressed");
55         /// <summary>
56         /// Disabled State.
57         /// </summary>
58         /// <since_tizen> 9 </since_tizen>
59         public static readonly ControlState Disabled = Create("Disabled");
60         /// <summary>
61         /// Selected State.
62         /// </summary>
63         /// <since_tizen> 9 </since_tizen>
64         public static readonly ControlState Selected = Create("Selected");
65         /// <summary>
66         /// SelectedPressed State.
67         /// </summary>
68         [EditorBrowsable(EditorBrowsableState.Never)]
69         public static readonly ControlState SelectedPressed = Selected + Pressed;
70         /// <summary>
71         /// DisabledSelected State.
72         /// </summary>
73         [EditorBrowsable(EditorBrowsableState.Never)]
74         public static readonly ControlState DisabledSelected = Disabled + Selected;
75         /// <summary>
76         /// DisabledFocused State.
77         /// </summary>
78         [EditorBrowsable(EditorBrowsableState.Never)]
79         public static readonly ControlState DisabledFocused = Disabled + Focused;
80         /// <summary>
81         /// SelectedFocused State.
82         /// </summary>
83         [EditorBrowsable(EditorBrowsableState.Never)]
84         public static readonly ControlState SelectedFocused = Selected + Focused;
85         /// <summary>
86         /// This is used in a selector class. It represents all other states except for states that are already defined in a selector.
87         /// </summary>
88         /// <since_tizen> 9 </since_tizen>
89         public static readonly ControlState Other = Create("Other");
90
91         private List<ControlState> stateList = new List<ControlState>();
92         private readonly string name = "";
93
94         /// <summary>
95         /// Gets or sets a value indicating whether it has combined states.
96         /// </summary>
97         [EditorBrowsable(EditorBrowsableState.Never)]
98         public bool IsCombined => stateList.Count > 1;
99
100         private ControlState() { }
101
102         private ControlState(string name) : this() => this.name = name;
103
104         /// <summary>
105         /// Create an instance of the <see cref="ControlState"/> with state name.
106         /// </summary>
107         /// <param name="name">The state name.</param>
108         /// <returns>The <see cref="ControlState"/> instance which has single state.</returns>
109         /// <exception cref="ArgumentNullException">Thrown when the given name is null.</exception>
110         /// <exception cref="ArgumentException">Thrown when the given name is invalid.</exception>
111         /// <since_tizen> 9 </since_tizen>
112         public static ControlState Create(string name)
113         {
114             if (name == null)
115                 throw new ArgumentNullException(nameof(name));
116             if (string.IsNullOrWhiteSpace(name))
117                 throw new ArgumentException("name cannot be empty string", nameof(name));
118
119             name = name.Trim();
120
121             if (stateDictionary.TryGetValue(name, out ControlState state))
122                 return state;
123
124             state = new ControlState(name);
125             state.stateList.Add(state);
126             stateDictionary.Add(name, state);
127             return state;
128         }
129
130         /// <summary>
131         /// Create an instance of the <see cref="ControlState"/> with combined states.
132         /// </summary>
133         /// <param name="states">The control state array.</param>
134         /// <returns>The <see cref="ControlState"/> instance which has combined state.</returns>
135         [EditorBrowsable(EditorBrowsableState.Never)]
136         public static ControlState Create(params ControlState[] states)
137         {
138             if (states.Length == 1)
139                 return states[0];
140
141             ControlState newState = new ControlState();
142             for (int i = 0; i < states.Length; i++)
143             {
144                 if (states[i] == Normal)
145                     continue;
146
147                 if (states[i] == All)
148                     return All;
149
150                 newState.stateList.AddRange(states[i].stateList);
151             }
152
153             if (newState.stateList.Count == 0)
154                 return Normal;
155
156             newState.stateList = newState.stateList.Distinct().ToList();
157
158             if (newState.stateList.Count == 1)
159             {
160                 return newState.stateList[0];
161             }
162
163             return newState;
164         }
165
166         /// <summary>
167         /// Determines whether a state contains a specified state.
168         /// </summary>
169         /// <param name="state">The state to search for</param>
170         /// <returns>true if the state contain a specified state, otherwise, false.</returns>
171         /// <exception cref="ArgumentNullException">Thrown when the given state is null.</exception>
172         /// <since_tizen> 9 </since_tizen>
173         public bool Contains(ControlState state)
174         {
175             if (state == null)
176                 throw new ArgumentNullException(nameof(state));
177
178             if (!IsCombined)
179                 return ReferenceEquals(this, state);
180
181             bool found;
182             for (int i = 0; i < state.stateList.Count; i++)
183             {
184                 found = false;
185                 for (int j = 0; j < stateList.Count; j++)
186                 {
187                     if (ReferenceEquals(state.stateList[i], stateList[j]))
188                     {
189                         found = true;
190                         break;
191                     }
192                 }
193                 if (!found) return false;
194             }
195
196             return true;
197         }
198
199         ///  <inheritdoc/>
200         [EditorBrowsable(EditorBrowsableState.Never)]
201         public bool Equals(ControlState other)
202         {
203             if (other is null || stateList.Count != other.stateList.Count)
204                 return false;
205
206             return Contains(other);
207         }
208
209         ///  <inheritdoc/>
210         /// <since_tizen> 9 </since_tizen>
211         public override bool Equals(object other) => this.Equals(other as ControlState);
212
213         ///  <inheritdoc/>
214         [EditorBrowsable(EditorBrowsableState.Never)]
215         public override int GetHashCode() => (name.GetHashCode() * 397) ^ IsCombined.GetHashCode();
216
217         ///  <inheritdoc/>
218         [EditorBrowsable(EditorBrowsableState.Never)]
219         public override string ToString()
220         {
221             string name = "";
222             for (int i = 0; i < stateList.Count; i++)
223             {
224                 name += ((i == 0) ? "" : ", ") + stateList[i].name;
225             }
226             return name;
227         }
228
229         /// <summary>
230         /// Compares whether the two ControlStates are same or not.
231         /// </summary>
232         /// <param name="lhs">A <see cref="ControlState"/> on the left hand side.</param>
233         /// <param name="rhs">A <see cref="ControlState"/> on the right hand side.</param>
234         /// <returns>true if the ControlStates are equal; otherwise, false.</returns>
235         /// <since_tizen> 9 </since_tizen>
236         public static bool operator ==(ControlState lhs, ControlState rhs)
237         {
238             // Check for null on left side.
239             if (lhs is null)
240             {
241                 if (rhs is null)
242                 {
243                     // null == null = true.
244                     return true;
245                 }
246
247                 // Only the left side is null.
248                 return false;
249             }
250             // Equals handles case of null on right side.
251             return lhs.Equals(rhs);
252         }
253
254         /// <summary>
255         /// Compares whether the two ControlStates are different or not.
256         /// </summary>
257         /// <param name="lhs">A <see cref="ControlState"/> on the left hand side.</param>
258         /// <param name="rhs">A <see cref="ControlState"/> on the right hand side.</param>
259         /// <returns>true if the ControlStates are not equal; otherwise, false.</returns>
260         /// <since_tizen> 9 </since_tizen>
261         public static bool operator !=(ControlState lhs, ControlState rhs) => !(lhs == rhs);
262
263         /// <summary>
264         /// The addition operator.
265         /// </summary>
266         /// <param name="lhs">A <see cref="ControlState"/> on the left hand side.</param>
267         /// <param name="rhs">A <see cref="ControlState"/> on the right hand side.</param>
268         /// <returns>The <see cref="ControlState"/> containing the result of the addition.</returns>
269         /// <since_tizen> 9 </since_tizen>
270         public static ControlState operator +(ControlState lhs, ControlState rhs) => Create(lhs, rhs);
271
272         /// <summary>
273         /// The substraction operator.
274         /// </summary>
275         /// <param name="lhs">A <see cref="ControlState"/> on the left hand side.</param>
276         /// <param name="rhs">A <see cref="ControlState"/> on the right hand side.</param>
277         /// <returns>The <see cref="ControlState"/> containing the result of the substraction.</returns>
278         /// <exception cref="ArgumentNullException"> Thrown when lhs or rhs is null. </exception>
279         [EditorBrowsable(EditorBrowsableState.Never)]
280         public static ControlState operator -(ControlState lhs, ControlState rhs)
281         {
282             if (null == lhs)
283             {
284                 throw new ArgumentNullException(nameof(lhs));
285             }
286             else if (null == rhs)
287             {
288                 throw new ArgumentNullException(nameof(rhs));
289             }
290
291             if (!lhs.IsCombined)
292             {
293                 return ReferenceEquals(lhs, rhs) ? Normal : lhs;
294             }
295
296             var rest = lhs.stateList.Except(rhs.stateList);
297             var count = rest.Count();
298
299             if (count == 0)
300             {
301                 return Normal;
302             }
303
304             if (count == 1)
305             {
306                 return rest.First();
307             }
308
309             ControlState newState = new ControlState();
310             newState.stateList.AddRange(rest);
311             return newState;
312         }
313
314         class ControlStateTypeConverter : Binding.TypeConverter
315         {
316             public override object ConvertFromInvariantString(string value)
317             {
318                 if (value != null)
319                 {
320                     value = value.Trim();
321
322                     ControlState convertedState = new ControlState();
323                     string[] parts = value.Split(',');
324                     foreach (string part in parts)
325                     {
326                         convertedState += Create(part);
327                     }
328                     return convertedState;
329                 }
330
331                 throw new InvalidOperationException($"Cannot convert \"{value}\" into {typeof(ControlState)}");
332             }
333         }
334     }
335 }