Skip to content

Commit

Permalink
feat(QueryAttribute): combine QueryAttribute and QueryClassAttribute
Browse files Browse the repository at this point in the history
  • Loading branch information
jonisavo committed Jun 10, 2022
1 parent 3fde333 commit 52375d7
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 125 deletions.
10 changes: 8 additions & 2 deletions Assets/Samples/Query/QueryExampleComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ public class QueryExampleComponent : UIComponent
[Query("my-label")]
private readonly Label MyLabel;

[Query("my-foldout")]
[Query(Name = "my-foldout")]
private readonly Foldout MyFoldout;

[QueryClass("description")]
[Query(Class = "description")]
private readonly Label[] DescriptionLabels;

[Query]
private readonly VisualElement[] Everything;

public QueryExampleComponent()
{
Expand All @@ -24,6 +27,9 @@ public QueryExampleComponent()

foreach (var label in DescriptionLabels)
label.style.color = Color.green;

foreach (var element in Everything)
element.tooltip = "Everything has this tooltip.";
}
}
}
38 changes: 35 additions & 3 deletions Assets/UIComponents.Tests/QueryAttributeTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NUnit.Framework;
using System.Collections.Generic;
using NUnit.Framework;
using UIComponents.Experimental;
using UnityEngine.UIElements;

Expand All @@ -13,8 +14,24 @@ private class ComponentWithQueryAttribute : UIComponent
[Query("hello-world-label")]
public readonly Label HelloWorldLabel;

[Query("test-foldout")]
[Query(Name = "test-foldout")]
public readonly Foldout TestFoldout;

[Query]
public readonly Label FirstLabel;

[Query(Class = "text")]
public readonly Label[] LabelsWithTextClass;

[Query(Name = "hello-world-label", Class = "text")]
public readonly Label HelloWorldLabelWithTextClass;

[Query]
public readonly List<Label> AllLabelsImplicit;

[Query(Name = "hello-world-label")]
[Query(Name = "foldout-content")]
public readonly List<Label> AllLabelsExplicit;
}

[Test]
Expand All @@ -24,8 +41,23 @@ public void Should_Populate_Fields()

Assert.That(component.HelloWorldLabel, Is.InstanceOf<Label>());
Assert.That(component.HelloWorldLabel.text, Is.EqualTo("Hello world!"));

Assert.That(component.TestFoldout, Is.InstanceOf<Foldout>());

Assert.That(component.FirstLabel, Is.SameAs(component.HelloWorldLabel));

Assert.That(component.LabelsWithTextClass, Is.Not.Null);
Assert.That(component.LabelsWithTextClass.Length, Is.EqualTo(2));

Assert.That(component.HelloWorldLabelWithTextClass, Is.SameAs(component.HelloWorldLabel));

Assert.That(component.AllLabelsImplicit, Is.InstanceOf<List<Label>>());
Assert.That(component.AllLabelsImplicit.Count, Is.EqualTo(2));
Assert.That(component.AllLabelsImplicit[1].text, Is.EqualTo("Foldout content"));

Assert.That(component.AllLabelsExplicit, Is.InstanceOf<List<Label>>());
Assert.That(component.AllLabelsExplicit.Count, Is.EqualTo(2));
Assert.That(component.AllLabelsExplicit[1].text, Is.EqualTo("Foldout content"));
}

private class ChildComponentWithQueryAttribute : ComponentWithQueryAttribute
Expand Down
38 changes: 19 additions & 19 deletions Assets/UIComponents.Tests/QueryClassAttributeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,38 @@ public class QueryClassAttributeTests
[Layout("UIComponentTests/QueryClassAttributeTest")]
private class QueryClassTestComponent : UIComponent
{
[QueryClass("class1")]
[Query(Class = "class1")]
public VisualElement[] AllClassOneElementsArray;

[QueryClass("class1")]
[Query(Class = "class1")]
public List<VisualElement> AllClassOneElementsList;

[QueryClass("class1")]
[Query(Class = "class1")]
public VisualElement ClassOneElement;

[QueryClass("class1")]
[Query(Class = "class1")]
public Label[] ClassOneLabelArray;

[QueryClass("class1")]
[Query(Class = "class1")]
public List<Label> ClassOneLabelList;

[QueryClass("class1")]
[QueryClass("class2")]
[QueryClass("class3")]
[Query(Class = "class1")]
[Query(Class = "class2")]
[Query(Class = "class3")]
public VisualElement[] AllElements;

[QueryClass("class1")]
[QueryClass("class2")]
[QueryClass("class3")]
[Query(Class = "class1")]
[Query(Class = "class2")]
[Query(Class = "class3")]
public Label[] AllLabels;

[QueryClass("no-such-class")]
[Query(Class = "no-such-class")]
public Label[] EmptyLabelArray;

[QueryClass("no-such-class")]
[Query(Class = "no-such-class")]
public List<Label> EmptyLabelList;

[QueryClass("no-such-class")]
[Query(Class = "no-such-class")]
public Label EmptyLabel;
}

Expand Down Expand Up @@ -133,18 +133,18 @@ public void Leaves_Field_Null_If_No_Element_Is_Found()
}

[Layout("UIComponentTests/QueryClassAttributeTest")]
private class UIComponentWithBothQueryAttributes : UIComponent
private class UIComponentWithNameAndClassQuery : UIComponent
{
[Query("class1-first")]
[QueryClass("class2")]
[QueryClass("class3")]
[Query(Class = "class2")]
[Query(Class = "class3")]
public readonly Label[] Labels;
}

[Test]
public void Works_With_The_Query_Attribute()
public void Works_With_Both_Name_And_Class_Query()
{
var component = new UIComponentWithBothQueryAttributes();
var component = new UIComponentWithNameAndClassQuery();

Assert.That(component.Labels, Is.Not.Null);
Assert.That(component.Labels.Length, Is.EqualTo(3));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<UXML xmlns:ui="UnityEngine.UIElements">
<ui:Label name="hello-world-label" text="Hello world!" />
<ui:Label name="hello-world-label" text="Hello world!" class="text" />
<ui:Foldout name="test-foldout">
<ui:Label name="foldout-content" text="Foldout content" />
<ui:Label name="foldout-content" text="Foldout content" class="text" />
</ui:Foldout>
</UXML>
6 changes: 3 additions & 3 deletions Assets/UIComponents/Core/Cache/FieldCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ namespace UIComponents.Cache
/// </summary>
public readonly struct FieldCache
{
public readonly Dictionary<FieldInfo, QueryAttributeBase[]> QueryAttributes;
public readonly Dictionary<FieldInfo, QueryAttribute[]> QueryAttributes;

public FieldCache(Type type)
{
var fieldInfos = type.GetFields(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

QueryAttributes = new Dictionary<FieldInfo, QueryAttributeBase[]>();
QueryAttributes = new Dictionary<FieldInfo, QueryAttribute[]>();

for (var i = 0; i < fieldInfos.Length; i++)
{
var queryAttributes = (QueryAttributeBase[]) fieldInfos[i].GetCustomAttributes<QueryAttributeBase>();
var queryAttributes = (QueryAttribute[]) fieldInfos[i].GetCustomAttributes<QueryAttribute>();

if (queryAttributes.Length > 0)
QueryAttributes[fieldInfos[i]] = queryAttributes;
Expand Down
42 changes: 31 additions & 11 deletions Assets/UIComponents/Core/Experimental/QueryAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,49 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine.UIElements;

namespace UIComponents.Experimental
{
/// <summary>
/// An attribute for specifying the name of a VisualElement, which is then
/// automatically queried and populated in the UIComponent constructor.
/// If the field type is an array or List, the queried element will be
/// placed into it.
/// An attribute for specifying a single query for a field.
/// A name and class can be specified for each query.
/// Queries are made in the UIComponent constructor.
/// </summary>
/// <seealso cref="QueryClassAttribute"/>
[AttributeUsage(AttributeTargets.Field)]
public class QueryAttribute : QueryAttributeBase
/// <example>
/// [Layout("MyLayout")]
/// public class ComponentWithQueries : UIComponent
/// {
/// [Query(Name = "hello-world-label")]
/// private readonly Label HelloWorldLabel;
///
/// [Query(Class = "red")]
/// private readonly Label[] RedLabels;
///
/// [Query]
/// private readonly Label FirstLabel;
///
/// [Query]
/// public readonly Label[] AllLabels;
/// }
/// </example>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
public class QueryAttribute : Attribute
{
private readonly string _name;
public string Name { get; set; }

public string Class { get; set; }

public QueryAttribute(string name)
{
_name = name;
Name = name;
}

public QueryAttribute() {}

public override void Query(VisualElement root, List<VisualElement> results)
public void Query(VisualElement root, List<VisualElement> results)
{
root.Query(_name).ToList(results);
root.Query(Name, Class).ToList(results);
}
}
}
11 changes: 0 additions & 11 deletions Assets/UIComponents/Core/Experimental/QueryAttributeBase.cs

This file was deleted.

11 changes: 0 additions & 11 deletions Assets/UIComponents/Core/Experimental/QueryAttributeBase.cs.meta

This file was deleted.

29 changes: 0 additions & 29 deletions Assets/UIComponents/Core/Experimental/QueryClassAttribute.cs

This file was deleted.

This file was deleted.

1 change: 1 addition & 0 deletions Assets/UIComponents/Core/UIComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ private void PopulateQueryFields()
}
else if (results.Count > 0)
{
results.RemoveAll(result => !fieldType.IsInstanceOfType(result));
fieldInfo.SetValue(this, results[0]);
}
}
Expand Down
55 changes: 24 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,9 @@ It is accessible via the `UIComponents.Experimental` namespace.
```xml
<!-- Resources/MyLayout.uxml -->
<UXML xmlns:ui="UnityEngine.UIElements">
<ui:Label class="label" name="my-label" text="Hello world!" />
<ui:Foldout name="my-foldout">
<ui:Label class="label" text="Foldout content" />
<ui:Label name="hello-world-label" text="Hello world!" class="text" />
<ui:Foldout name="test-foldout">
<ui:Label name="foldout-content" text="Foldout content" class="text" />
</ui:Foldout>
</UXML>
```
Expand All @@ -166,39 +166,32 @@ using UIComponents.Experimental;
[Layout("MyLayout")]
public class MyComponent : UIComponent
{
[Query("my-label")]
public readonly Label MyLabel;
[Query("hello-world-label")]
public readonly Label HelloWorldLabel;

[Query(Name = "test-foldout")]
public readonly Foldout TestFoldout;

[Query("my-foldout")]
public readonly Foldout MyFoldout;
[Query]
public readonly Label FirstLabel;

public MyComponent()
{
MyLabel.text = "Goodbye world!";
MyFoldout.Add(new Label("More content!"));
}
}
```

`[QueryClass]` can be used to query by class instead of by name. Multiple `[QueryClass]` attributes
can be used on a single field. Each attribute will be queried in the order they are specified.

```c#
using UIComponents;
using UIComponents.Experimental;

[Layout("MyLayout")]
public class MyComponent : UIComponent
{
[QueryClass("label")]
public readonly Label[] Labels;
[Query(Class = "text")]
public readonly Label[] LabelsWithTextClass;

[Query(Name = "hello-world-label", Class = "text")]
public readonly Label HelloWorldLabelWithTextClass;

[Query]
public readonly List<Label> AllLabelsImplicit;

[Query(Name = "hello-world-label")]
[Query(Name = "foldout-content")]
public readonly List<Label> AllLabelsExplicit;

public MyComponent()
{
foreach (var label in Labels)
{
label.text = "Hello world!";
}
HelloWorldLabel.text = "Goodbye world!";
TestFoldout.Add(new Label("More content!"));
}
}
```
Expand Down

0 comments on commit 52375d7

Please sign in to comment.