16 changed files with 464 additions and 54 deletions
@ -1,39 +1,253 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Perspex.Collections; |
|||
|
|||
namespace Perspex.Controls |
|||
{ |
|||
public class Classes : PerspexList<string> |
|||
/// <summary>
|
|||
/// Holds a collection of style classes for an <see cref="IControl"/>.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Similar to CSS, each control may have any number of styling classes applied.
|
|||
/// </remarks>
|
|||
public class Classes : PerspexList<string>, IPseudoClasses |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Classes"/> class.
|
|||
/// </summary>
|
|||
public Classes() |
|||
{ |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Classes"/> class.
|
|||
/// </summary>
|
|||
/// <param name="items">The initial items.</param>
|
|||
public Classes(IEnumerable<string> items) |
|||
: base(items) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Classes"/> class.
|
|||
/// </summary>
|
|||
/// <param name="items">The initial items.</param>
|
|||
public Classes(params string[] items) |
|||
: base(items) |
|||
{ |
|||
} |
|||
|
|||
public override void Add(string item) |
|||
/// <summary>
|
|||
/// Adds a style class to the collection.
|
|||
/// </summary>
|
|||
/// <param name="name">The class name.</param>
|
|||
/// <remarks>
|
|||
/// Only standard classes may be added via this method. To add pseudoclasses (classes
|
|||
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
|
|||
/// property.
|
|||
/// </remarks>
|
|||
public override void Add(string name) |
|||
{ |
|||
ThrowIfPseudoclass(name, "added"); |
|||
|
|||
if (!Contains(name)) |
|||
{ |
|||
base.Add(name); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a style classes to the collection.
|
|||
/// </summary>
|
|||
/// <param name="names">The class names.</param>
|
|||
/// <remarks>
|
|||
/// Only standard classes may be added via this method. To add pseudoclasses (classes
|
|||
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
|
|||
/// property.
|
|||
/// </remarks>
|
|||
public override void AddRange(IEnumerable<string> names) |
|||
{ |
|||
var c = new List<string>(); |
|||
|
|||
foreach (var name in names) |
|||
{ |
|||
ThrowIfPseudoclass(name, "added"); |
|||
|
|||
if (!Contains(name)) |
|||
{ |
|||
c.Add(name); |
|||
} |
|||
} |
|||
|
|||
base.AddRange(c); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Inserts a style class into the collection.
|
|||
/// </summary>
|
|||
/// <param name="index">The index to insert the class at.</param>
|
|||
/// <param name="name">The class name.</param>
|
|||
/// <remarks>
|
|||
/// Only standard classes may be added via this method. To add pseudoclasses (classes
|
|||
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
|
|||
/// property.
|
|||
/// </remarks>
|
|||
public override void Insert(int index, string name) |
|||
{ |
|||
ThrowIfPseudoclass(name, "added"); |
|||
|
|||
if (!Contains(name)) |
|||
{ |
|||
base.Insert(index, name); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Inserts style classes into the collection.
|
|||
/// </summary>
|
|||
/// <param name="index">The index to insert the class at.</param>
|
|||
/// <param name="names">The class names.</param>
|
|||
/// <remarks>
|
|||
/// Only standard classes may be added via this method. To add pseudoclasses (classes
|
|||
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
|
|||
/// property.
|
|||
/// </remarks>
|
|||
public override void InsertRange(int index, IEnumerable<string> names) |
|||
{ |
|||
var c = new List<string>(); |
|||
|
|||
foreach (var name in names) |
|||
{ |
|||
ThrowIfPseudoclass(name, "added"); |
|||
|
|||
if (!Contains(name)) |
|||
{ |
|||
c.Add(name); |
|||
} |
|||
} |
|||
|
|||
base.InsertRange(index, c); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes a style class from the collection.
|
|||
/// </summary>
|
|||
/// <param name="name">The class name.</param>
|
|||
/// <remarks>
|
|||
/// Only standard classes may be removed via this method. To remove pseudoclasses (classes
|
|||
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
|
|||
/// property.
|
|||
/// </remarks>
|
|||
public override bool Remove(string name) |
|||
{ |
|||
ThrowIfPseudoclass(name, "removed"); |
|||
return base.Remove(name); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes style classes from the collection.
|
|||
/// </summary>
|
|||
/// <param name="names">The class name.</param>
|
|||
/// <remarks>
|
|||
/// Only standard classes may be removed via this method. To remove pseudoclasses (classes
|
|||
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
|
|||
/// property.
|
|||
/// </remarks>
|
|||
public override void RemoveAll(IEnumerable<string> names) |
|||
{ |
|||
if (!Contains(item)) |
|||
var c = new List<string>(); |
|||
|
|||
foreach (var name in names) |
|||
{ |
|||
base.Add(item); |
|||
ThrowIfPseudoclass(name, "removed"); |
|||
|
|||
if (!Contains(name)) |
|||
{ |
|||
c.Add(name); |
|||
} |
|||
} |
|||
|
|||
base.RemoveAll(c); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes a style class from the collection.
|
|||
/// </summary>
|
|||
/// <param name="index">The index of the class in the collection.</param>
|
|||
/// <remarks>
|
|||
/// Only standard classes may be removed via this method. To remove pseudoclasses (classes
|
|||
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
|
|||
/// property.
|
|||
/// </remarks>
|
|||
public override void RemoveAt(int index) |
|||
{ |
|||
var name = this[index]; |
|||
ThrowIfPseudoclass(name, "removed"); |
|||
base.RemoveAt(index); |
|||
} |
|||
|
|||
public override void AddRange(IEnumerable<string> items) |
|||
/// <summary>
|
|||
/// Removes style classes from the collection.
|
|||
/// </summary>
|
|||
/// <param name="index">The first index to remove.</param>
|
|||
/// <param name="count">The number of items to remove.</param>
|
|||
public override void RemoveRange(int index, int count) |
|||
{ |
|||
base.AddRange(items.Where(x => !Contains(x))); |
|||
var names = GetRange(index, count); |
|||
base.RemoveRange(index, count); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes all non-pseudoclasses in the collection and adds a new set.
|
|||
/// </summary>
|
|||
/// <param name="source">The new contents of the collection.</param>
|
|||
public void Replace(IList<string> source) |
|||
{ |
|||
var toRemove = new List<string>(); |
|||
|
|||
foreach (var name in source) |
|||
{ |
|||
ThrowIfPseudoclass(name, "added"); |
|||
} |
|||
|
|||
foreach (var name in this) |
|||
{ |
|||
if (!name.StartsWith(":")) |
|||
{ |
|||
toRemove.Add(name); |
|||
} |
|||
} |
|||
|
|||
base.RemoveAll(toRemove); |
|||
base.AddRange(source); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
void IPseudoClasses.Add(string name) |
|||
{ |
|||
if (!Contains(name)) |
|||
{ |
|||
base.Add(name); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
bool IPseudoClasses.Remove(string name) |
|||
{ |
|||
return base.Remove(name); |
|||
} |
|||
|
|||
private void ThrowIfPseudoclass(string name, string operation) |
|||
{ |
|||
if (name.StartsWith(":")) |
|||
{ |
|||
throw new ArgumentException( |
|||
$"The pseudoclass '{name}' may only be {operation} by the control itself."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Perspex.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes an interface for setting pseudoclasses on a <see cref="Classes"/> collection.
|
|||
/// </summary>
|
|||
public interface IPseudoClasses |
|||
{ |
|||
/// <summary>
|
|||
/// Adds a pseudoclass to the collection.
|
|||
/// </summary>
|
|||
/// <param name="name">The pseudoclass name.</param>
|
|||
void Add(string name); |
|||
|
|||
/// <summary>
|
|||
/// Removes a pseudoclass from the collection.
|
|||
/// </summary>
|
|||
/// <param name="name">The pseudoclass name.</param>
|
|||
bool Remove(string name); |
|||
} |
|||
} |
|||
@ -0,0 +1,153 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Xunit; |
|||
|
|||
namespace Perspex.Controls.UnitTests |
|||
{ |
|||
public class ClassesTests |
|||
{ |
|||
[Fact] |
|||
public void Duplicates_Should_Not_Be_Added() |
|||
{ |
|||
var target = new Classes(); |
|||
|
|||
target.Add("foo"); |
|||
target.Add("foo"); |
|||
|
|||
Assert.Equal(new[] { "foo" }, target); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Duplicates_Should_Not_Be_Added_Via_AddRange() |
|||
{ |
|||
var target = new Classes(); |
|||
|
|||
target.Add("foo"); |
|||
target.AddRange(new[] { "foo", "bar" }); |
|||
|
|||
Assert.Equal(new[] { "foo", "bar" }, target); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Duplicates_Should_Not_Be_Added_Via_Pseudoclasses() |
|||
{ |
|||
var target = new Classes(); |
|||
var ps = (IPseudoClasses)target; |
|||
|
|||
ps.Add(":foo"); |
|||
ps.Add(":foo"); |
|||
|
|||
Assert.Equal(new[] { ":foo" }, target); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Duplicates_Should_Not_Be_Inserted() |
|||
{ |
|||
var target = new Classes(); |
|||
|
|||
target.Add("foo"); |
|||
target.Insert(0, "foo"); |
|||
|
|||
Assert.Equal(new[] { "foo" }, target); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Duplicates_Should_Not_Be_Inserted_Via_InsertRange() |
|||
{ |
|||
var target = new Classes(); |
|||
|
|||
target.Add("foo"); |
|||
target.InsertRange(1, new[] { "foo", "bar" }); |
|||
|
|||
Assert.Equal(new[] { "foo", "bar" }, target); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Be_Able_To_Add_Pseudoclass() |
|||
{ |
|||
var target = new Classes(); |
|||
|
|||
Assert.Throws<ArgumentException>(() => target.Add(":foo")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Be_Able_To_Add_Pseudoclasses_Via_AddRange() |
|||
{ |
|||
var target = new Classes(); |
|||
|
|||
Assert.Throws<ArgumentException>(() => target.AddRange(new[] { "foo", ":bar" })); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Be_Able_To_Insert_Pseudoclass() |
|||
{ |
|||
var target = new Classes(); |
|||
|
|||
Assert.Throws<ArgumentException>(() => target.Insert(0, ":foo")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Be_Able_To_Insert_Pseudoclasses_Via_InsertRange() |
|||
{ |
|||
var target = new Classes(); |
|||
|
|||
Assert.Throws<ArgumentException>(() => target.InsertRange(0, new[] { "foo", ":bar" })); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Be_Able_To_Remove_Pseudoclass() |
|||
{ |
|||
var target = new Classes(); |
|||
|
|||
Assert.Throws<ArgumentException>(() => target.Remove(":foo")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Be_Able_To_Remove_Pseudoclasses_Via_RemoveAll() |
|||
{ |
|||
var target = new Classes(); |
|||
|
|||
Assert.Throws<ArgumentException>(() => target.RemoveAll(new[] { "foo", ":bar" })); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Be_Able_To_Remove_Pseudoclasses_Via_RemoveRange() |
|||
{ |
|||
var target = new Classes(); |
|||
|
|||
Assert.Throws<ArgumentException>(() => target.RemoveRange(0, 1)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Be_Able_To_Remove_Pseudoclass_Via_RemoveAt() |
|||
{ |
|||
var target = new Classes(); |
|||
|
|||
((IPseudoClasses)target).Add(":foo"); |
|||
|
|||
Assert.Throws<ArgumentException>(() => target.RemoveAt(0)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Replace_Should_Not_Replace_Pseudoclasses() |
|||
{ |
|||
var target = new Classes("foo", "bar"); |
|||
|
|||
((IPseudoClasses)target).Add(":baz"); |
|||
|
|||
target.Replace(new[] { "qux" }); |
|||
|
|||
Assert.Equal(new[] { ":baz", "qux" }, target); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Replace_Should_Not_Accept_Pseudoclasses() |
|||
{ |
|||
var target = new Classes(); |
|||
|
|||
Assert.Throws<ArgumentException>(() => target.Replace(new[] { ":qux" })); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue