Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NUI] Refactoring ControlState to use bitmask #6530

Open
wants to merge 1 commit into
base: DevelNUI
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions src/Tizen.NUI/src/internal/Common/ControlStateUtility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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 = 32;
private static readonly Dictionary<string, long> registeredStates = new Dictionary<string, long>();
private static int nextBitPosition = 0;

/// <summary>
/// </summary>
public static long FullMask => (1L << MaxBitWidth) - 1L;

/// <summary>
/// </summary>
public static IEnumerable<(string, long)> RegisteredStates()
{
foreach (var (key, value) in registeredStates)
{
yield return (key, value);
}
}

public static long 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().ToLowerInvariant();

if (!registeredStates.TryGetValue(trimmed, out long 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 = 1L << nextBitPosition;
registeredStates.Add(trimmed, bitMask);

nextBitPosition++;
}

return bitMask;
}
}
}
169 changes: 77 additions & 92 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,9 +16,8 @@
*/

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

namespace Tizen.NUI.BaseComponents
{
Expand All @@ -30,38 +29,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(0L);
/// <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 +84,24 @@ 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));

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

/// <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 != 0L && Math.Ceiling(Math.Log2(bitFlags)) != Math.Floor(Math.Log2(bitFlags));

private ControlState() { }
ControlState(long 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 +113,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 @@ -138,7 +127,8 @@ public static ControlState Create(params ControlState[] states)
if (states.Length == 1)
return states[0];

ControlState newState = new ControlState();
var newState = new ControlState(0L);

for (int i = 0; i < states.Length; i++)
{
if (states[i] == Normal)
Expand All @@ -147,17 +137,7 @@ public static ControlState Create(params ControlState[] states)
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];
newState += states[i];
}

return newState;
Expand All @@ -172,38 +152,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 +170,24 @@ 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();

foreach (var (name, bitMask) in ControlStateUtility.RegisteredStates())
{
name += ((i == 0) ? "" : ", ") + stateList[i].name;
if ((bitFlags & bitMask) > 0)
{
if (sbuilder.Length != 0) sbuilder.Append(", ");
sbuilder.Append(name);
}
}
return name;

return sbuilder.ToString();
}

/// <summary>
Expand All @@ -247,7 +211,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 +230,10 @@ 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)
{
return Add(lhs, rhs);
}

/// <summary>
/// The substraction operator.
Expand All @@ -279,36 +245,55 @@ public override string ToString()
[EditorBrowsable(EditorBrowsableState.Never)]
public static ControlState operator -(ControlState lhs, ControlState rhs)
{
if (null == lhs)
return Remove(lhs, rhs);
}

/// <summary>
/// Add multiple states.
/// </summary>
/// <param name="operand1">The first operand object.</param>
/// <param name="operand2">The second operand object.</param>
/// <param name="rest">The rest operand objects.</param>
/// <returns>The operation result.</returns>
static ControlState Add(ControlState operand1, ControlState operand2, params ControlState[] rest)
{
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)
long newBitFlags = operand1.bitFlags | operand2.bitFlags;

foreach (var state in rest)
{
return ReferenceEquals(lhs, rhs) ? Normal : lhs;
newBitFlags |= state.bitFlags;
}

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

if (count == 0)
/// <summary>
/// Remove a state from another.
/// </summary>
/// <param name="operand1">The first operand object.</param>
/// <param name="operand2">The second operand object.</param>
/// <returns>The operation result.</returns>
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 +304,7 @@ public override object ConvertFromInvariantString(string value)
{
value = value.Trim();

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