Skip to content

Commit

Permalink
[NUI] Refactoring ControlState to use bitmask
Browse files Browse the repository at this point in the history
ControlState was implemented inefficently on the memory and the performance.
This patch purposed to reduce inefficency by using bitflags on the state instead of string list.
[https://github.sec.samsung.net/NUI/OneUIComponents/issues/15]

long type bitmask will be represent each states,
1 1 1 1 1
O S D P F

Normal   : 0L
Focused  : 1L
Pressed  : 2L
Disabled : 4L
Selected : 8L
Other    : 16L
and All  : 31L

This concept is based on VisualState of NUI2,
https://github.sec.samsung.net/dotnet/nui2/blob/main/src/Tizen.NUI2.Components/Base/ViewState.cs
but we had to modified few states to keep backward compatibility of NUI
ControlState.
  • Loading branch information
everLEEst committed Dec 31, 2024
1 parent 27baefc commit 2bc4569
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 103 deletions.
73 changes: 73 additions & 0 deletions src/Tizen.NUI/src/internal/Common/ControlStateUtility.cs
Original file line number Diff line number Diff line change
@@ -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;
using System.Collections.Generic;

namespace Tizen.NUI
{
/// <summary>
/// Manages state name and bit mask.
/// </summary>
internal static class ControlStateUtility
{
private const int MaxBitWidth = 62;
private static readonly Dictionary<string, ulong> registeredStates = new Dictionary<string, ulong>();
private static int nextBitPosition = 0;

/// <summary>
/// </summary>
public static ulong FullMask => (1UL << MaxBitWidth) - 1UL;

/// <summary>
/// </summary>
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 != "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;
}
}
}
166 changes: 63 additions & 103 deletions src/Tizen.NUI/src/public/BaseComponents/ControlState.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright(c) 2020-2021 Samsung Electronics Co., Ltd.
* Copyright(c) 2020-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.
Expand All @@ -16,7 +16,7 @@
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Linq;

Expand All @@ -30,38 +30,37 @@ namespace Tizen.NUI.BaseComponents
[Binding.TypeConverter(typeof(ControlStateTypeConverter))]
public class ControlState : IEquatable<ControlState>
{
private static readonly Dictionary<string, ControlState> stateDictionary = new Dictionary<string, ControlState>();
//Default States
/// <summary>
/// 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.
/// </summary>
/// <since_tizen> 9 </since_tizen>
public static readonly ControlState All = Create("All");
public static readonly ControlState All = new ControlState(ControlStateUtility.FullMask);
/// <summary>
/// Normal State.
/// </summary>
/// <since_tizen> 9 </since_tizen>
public static readonly ControlState Normal = Create("Normal");
public static readonly ControlState Normal = new ControlState(0UL);
/// <summary>
/// Focused State.
/// </summary>
/// <since_tizen> 9 </since_tizen>
public static readonly ControlState Focused = Create("Focused");
public static readonly ControlState Focused = new ControlState(nameof(Focused));
/// <summary>
/// Pressed State.
/// </summary>
/// <since_tizen> 9 </since_tizen>
public static readonly ControlState Pressed = Create("Pressed");
public static readonly ControlState Pressed = new ControlState(nameof(Pressed));
/// <summary>
/// Disabled State.
/// </summary>
/// <since_tizen> 9 </since_tizen>
public static readonly ControlState Disabled = Create("Disabled");
public static readonly ControlState Disabled = new ControlState(nameof(Disabled));
/// <summary>
/// Selected State.
/// </summary>
/// <since_tizen> 9 </since_tizen>
public static readonly ControlState Selected = Create("Selected");
public static readonly ControlState Selected = new ControlState(nameof(Selected));
/// <summary>
/// SelectedPressed State.
/// </summary>
Expand All @@ -86,20 +85,25 @@ public class ControlState : IEquatable<ControlState>
/// This is used in a selector class. It represents all other states except for states that are already defined in a selector.
/// </summary>
/// <since_tizen> 9 </since_tizen>
public static readonly ControlState Other = Create("Other");
public static readonly ControlState Other = new ControlState(nameof(Other));

readonly ulong bitFlags;

private List<ControlState> stateList = new List<ControlState>();
private readonly string name = "";

/// <summary>
/// Gets or sets a value indicating whether it has combined states.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsCombined => stateList.Count > 1;
public bool IsCombined => (bitFlags != 0UL) && ((bitFlags & (bitFlags - 1UL)) != 0UL);

private ControlState() { }
private ControlState(ulong bitMask)
{
bitFlags = bitMask;
}

private ControlState(string name) : this() => this.name = name;
private ControlState(string name) : this(ControlStateUtility.Register(name))
{
}

/// <summary>
/// Create an instance of the <see cref="ControlState"/> with state name.
Expand All @@ -111,20 +115,7 @@ private ControlState() { }
/// <since_tizen> 9 </since_tizen>
public static ControlState Create(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("name cannot be empty string", nameof(name));

name = name.Trim();

if (stateDictionary.TryGetValue(name, out ControlState state))
return state;

state = new ControlState(name);
state.stateList.Add(state);
stateDictionary.Add(name, state);
return state;
return new ControlState(name);
}

/// <summary>
Expand All @@ -135,32 +126,14 @@ public static ControlState Create(string name)
[EditorBrowsable(EditorBrowsableState.Never)]
public static ControlState Create(params ControlState[] states)
{
if (states.Length == 1)
return states[0];
ulong newbits = 0UL;

ControlState newState = new ControlState();
for (int i = 0; i < states.Length; i++)
{
if (states[i] == Normal)
continue;

if (states[i] == All)
return All;

newState.stateList.AddRange(states[i].stateList);
}

if (newState.stateList.Count == 0)
return Normal;

newState.stateList = newState.stateList.Distinct().ToList();

if (newState.stateList.Count == 1)
{
return newState.stateList[0];
newbits |= states[i].bitFlags;
}

return newState;
return new ControlState(newbits);
}

/// <summary>
Expand All @@ -172,38 +145,16 @@ public static ControlState Create(params ControlState[] states)
/// <since_tizen> 9 </since_tizen>
public bool Contains(ControlState state)
{
if (state == null)
throw new ArgumentNullException(nameof(state));

if (!IsCombined)
return ReferenceEquals(this, state);

bool found;
for (int i = 0; i < state.stateList.Count; i++)
{
found = false;
for (int j = 0; j < stateList.Count; j++)
{
if (ReferenceEquals(state.stateList[i], stateList[j]))
{
found = true;
break;
}
}
if (!found) return false;
}

return true;
if (state is null) return false;
return (bitFlags & state.bitFlags) == state.bitFlags;
}

/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool Equals(ControlState other)
{
if (other is null || stateList.Count != other.stateList.Count)
return false;

return Contains(other);
if (other is null) return false;
return this.bitFlags == other.bitFlags;
}

/// <inheritdoc/>
Expand All @@ -212,18 +163,30 @@ public bool Equals(ControlState other)

/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => (name.GetHashCode() * 397) ^ IsCombined.GetHashCode();
public override int GetHashCode() => bitFlags.GetHashCode();

/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string ToString()
{
string name = "";
for (int i = 0; i < stateList.Count; i++)
var sbuilder = new StringBuilder();
var states = ControlStateUtility.RegisteredStates();

if (bitFlags == 0UL)
{
name += ((i == 0) ? "" : ", ") + stateList[i].name;
return nameof(Normal);
}
return name;

foreach (var (name, bitMask) in states)
{
if ((bitFlags & bitMask) > 0)
{
if (sbuilder.Length != 0) sbuilder.Append(", ");
sbuilder.Append(name);
}
}

return sbuilder.ToString();
}

/// <summary>
Expand All @@ -247,7 +210,6 @@ public override string ToString()
// Only the left side is null.
return false;
}
// Equals handles case of null on right side.
return lhs.Equals(rhs);
}

Expand All @@ -267,7 +229,7 @@ public override string ToString()
/// <param name="rhs">A <see cref="ControlState"/> on the right hand side.</param>
/// <returns>The <see cref="ControlState"/> containing the result of the addition.</returns>
/// <since_tizen> 9 </since_tizen>
public static ControlState operator +(ControlState lhs, ControlState rhs) => Create(lhs, rhs);
public static ControlState operator +(ControlState lhs, ControlState rhs) => Add(lhs, rhs);

/// <summary>
/// The substraction operator.
Expand All @@ -277,38 +239,36 @@ public override string ToString()
/// <returns>The <see cref="ControlState"/> containing the result of the substraction.</returns>
/// <exception cref="ArgumentNullException"> Thrown when lhs or rhs is null. </exception>
[EditorBrowsable(EditorBrowsableState.Never)]
public static ControlState operator -(ControlState lhs, ControlState rhs)
public static ControlState operator -(ControlState lhs, ControlState rhs) => Remove(lhs, rhs);

static ControlState Add(ControlState operand1, ControlState operand2)
{
if (null == lhs)
if (operand1 is null)
{
throw new ArgumentNullException(nameof(lhs));
throw new ArgumentNullException(nameof(operand1));
}
else if (null == rhs)
if (operand2 is null)
{
throw new ArgumentNullException(nameof(rhs));
throw new ArgumentNullException(nameof(operand2));
}

if (!lhs.IsCombined)
{
return ReferenceEquals(lhs, rhs) ? Normal : lhs;
}
ulong newBitFlags = operand1.bitFlags | operand2.bitFlags;

var rest = lhs.stateList.Except(rhs.stateList);
var count = rest.Count();
return new ControlState(newBitFlags);
}

if (count == 0)
static ControlState Remove(ControlState operand1, ControlState operand2)
{
if (operand1 is null)
{
return Normal;
throw new ArgumentNullException(nameof(operand1));
}

if (count == 1)
if (operand2 is null)
{
return rest.First();
throw new ArgumentNullException(nameof(operand2));
}

ControlState newState = new ControlState();
newState.stateList.AddRange(rest);
return newState;
return new ControlState(operand1.bitFlags & ~(operand2.bitFlags));
}

class ControlStateTypeConverter : Binding.TypeConverter
Expand All @@ -319,7 +279,7 @@ public override object ConvertFromInvariantString(string value)
{
value = value.Trim();

ControlState convertedState = new ControlState();
ControlState convertedState = Normal;
string[] parts = value.Split(',');
foreach (string part in parts)
{
Expand Down

0 comments on commit 2bc4569

Please sign in to comment.