Skip to content

Commit

Permalink
feat: add experimental QueryAttribute
Browse files Browse the repository at this point in the history
  • Loading branch information
jonisavo committed May 27, 2022
1 parent b510947 commit 1b7e0d5
Show file tree
Hide file tree
Showing 28 changed files with 281 additions and 3 deletions.
3 changes: 3 additions & 0 deletions Assets/Samples/Query.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Assets/Samples/Query/Editor.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions Assets/Samples/Query/Editor/QueryExampleComponentWindow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using UnityEditor;
using UnityEngine;

namespace UIComponents.Samples.Query.Editor
{
public class QueryExampleComponentWindow : EditorWindow
{
[MenuItem("UIComponents Examples/Experimental/QueryAttribute")]
private static void ShowWindow()
{
var window = GetWindow<QueryExampleComponentWindow>();
window.titleContent = new GUIContent("QueryAttribute");
window.Show();
}

private void CreateGUI()
{
rootVisualElement.style.minWidth = 190;
rootVisualElement.style.minHeight = 40;
rootVisualElement.Add(new QueryExampleComponent());
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "UIComponents.Samples.Query.Editor",
"rootNamespace": "UIComponents.Samples",
"references": [
"GUID:d593635333b4cae48bac5b5f0b596e90",
"GUID:d180e9959e2b4d2580f328c2e6aa239d"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions Assets/Samples/Query/QueryExampleComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using UIComponents.Experimental;
using UnityEngine.UIElements;

namespace UIComponents.Samples.Query
{
[Layout("QueryExampleComponent")]
[Stylesheet("QueryExampleComponent.styles")]
public class QueryExampleComponent : UIComponent
{
[Query("my-label")]
public readonly Label MyLabel;

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

public QueryExampleComponent()
{
MyLabel.text = "This text was set in the component constructor.";
MyFoldout.Add(new Label("This label was added in the component constructor."));
}
}
}
3 changes: 3 additions & 0 deletions Assets/Samples/Query/QueryExampleComponent.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Assets/Samples/Query/Resources.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Label {
white-space: normal;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Assets/Samples/Query/Resources/QueryExampleComponent.uxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<UXML xmlns:ui="UnityEngine.UIElements">
<ui:Label text="This is a component for demonstrating QueryAttribute, an experimental feature in UIComponents." />
<ui:Label text="QueryAttribute allows for populating UIComponent fields automatically." />
<ui:Label name="my-label" text="" />
<ui:Foldout name="my-foldout" text="Foldout...">
<ui:Label text="Foldout content" />
</ui:Foldout>
</UXML>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions Assets/Samples/Query/UIComponents.Samples.Query.asmdef
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "UIComponents.Samples.Query",
"rootNamespace": "UIComponents.Samples",
"references": [
"GUID:d593635333b4cae48bac5b5f0b596e90"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
3 changes: 3 additions & 0 deletions Assets/Samples/Query/UIComponents.Samples.Query.asmdef.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Assets/UIComponents.Benchmarks/BenchmarkUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ namespace UIComponents.Benchmarks
{
public static class BenchmarkUtils
{
public const string Version = "0.9.0.0";
public const string Version = "0.9.0.1";

private static SampleGroup[] GetProfilerMarkers()
{
return new[]
{
new SampleGroup("UIComponent.DependencySetup", SampleUnit.Microsecond),
new SampleGroup("UIComponent.CacheSetup", SampleUnit.Microsecond),
new SampleGroup("UIComponent.LayoutAndStylesSetup", SampleUnit.Microsecond)
new SampleGroup("UIComponent.LayoutAndStylesSetup", SampleUnit.Microsecond),
new SampleGroup("UIComponent.QueryFieldsSetup", SampleUnit.Microsecond)
};
}

Expand Down
31 changes: 31 additions & 0 deletions Assets/UIComponents.Tests/QueryAttributeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using NUnit.Framework;
using UIComponents.Experimental;
using UnityEngine.UIElements;

namespace UIComponents.Tests
{
[TestFixture]
public class QueryAttributeTests
{
[Layout("UIComponentTests/LayoutAttributeTests")]
private class ComponentWithQueryAttribute : UIComponent
{
[Query("hello-world-label")]
public readonly Label HelloWorldLabel;

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

[Test]
public void Should_Populate_Fields()
{
var component = new ComponentWithQueryAttribute();

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

Assert.That(component.TestFoldout, Is.InstanceOf<Foldout>());
}
}
}
3 changes: 3 additions & 0 deletions Assets/UIComponents.Tests/QueryAttributeTests.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
<UXML xmlns:ui="UnityEngine.UIElements">
<ui:Label text="Hello world!" />
<ui:Label name="hello-world-label" text="Hello world!" />
<ui:Foldout name="test-foldout">
<ui:Label text="Foldout content" />
</ui:Foldout>
</UXML>
27 changes: 27 additions & 0 deletions Assets/UIComponents/Core/Cache/FieldCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UIComponents.Experimental;

namespace UIComponents.Cache
{
public readonly struct FieldCache
{
public readonly Dictionary<FieldInfo, QueryAttribute> QueryAttributes;

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

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

for (var i = 0; i < fieldInfos.Length; i++)
{
var queryAttribute = fieldInfos[i].GetCustomAttribute<QueryAttribute>();
if (queryAttribute != null)
QueryAttributes[fieldInfos[i]] = queryAttribute;
}
}
}
}
11 changes: 11 additions & 0 deletions Assets/UIComponents/Core/Cache/FieldCache.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Assets/UIComponents/Core/Cache/UIComponentCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ namespace UIComponents.Cache
public readonly LayoutAttribute LayoutAttribute;
public readonly List<StylesheetAttribute> StylesheetAttributes;
public readonly List<AssetPathAttribute> AssetPathAttributes;
public readonly FieldCache FieldCache;

public UIComponentCache(Type componentType)
{
Attributes = componentType.GetCustomAttributes(true);
FieldCache = new FieldCache(componentType);

LayoutAttribute = null;
StylesheetAttributes = new List<StylesheetAttribute>();
Expand Down
3 changes: 3 additions & 0 deletions Assets/UIComponents/Core/Experimental.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions Assets/UIComponents/Core/Experimental/QueryAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace UIComponents.Experimental
{
[AttributeUsage(AttributeTargets.Field)]
public class QueryAttribute : Attribute
{
public readonly string Name;

public QueryAttribute(string name)
{
Name = name;
}
}
}
3 changes: 3 additions & 0 deletions Assets/UIComponents/Core/Experimental/QueryAttribute.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions Assets/UIComponents/Core/UIComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public abstract class UIComponent : VisualElement
new ProfilerMarker("UIComponent.CacheSetup");
private static readonly ProfilerMarker LayoutAndStylesSetupProfilerMarker =
new ProfilerMarker("UIComponent.LayoutAndStylesSetup");
private static readonly ProfilerMarker QueryFieldsSetupProfilerMarker =
new ProfilerMarker("UIComponent.QueryFieldsSetup");


/// <summary>
/// UIComponent's constructor loads the configured layout and stylesheets.
Expand All @@ -76,6 +79,9 @@ protected UIComponent()
LoadLayout();
LoadStyles();
LayoutAndStylesSetupProfilerMarker.End();
QueryFieldsSetupProfilerMarker.Begin();
PopulateQueryFields();
QueryFieldsSetupProfilerMarker.End();
}

/// <summary>
Expand Down Expand Up @@ -173,5 +179,20 @@ private void LoadStyles()
if (loadedStyleSheets[i] != null)
styleSheets.Add(loadedStyleSheets[i]);
}

private void PopulateQueryFields()
{
var fieldCache = CacheDictionary[_componentType].FieldCache;
var queryAttributes = fieldCache.QueryAttributes;

foreach (var queryAttributeKeyPair in queryAttributes)
{
var fieldInfo = queryAttributeKeyPair.Key;
var queryAttribute = queryAttributeKeyPair.Value;

fieldInfo.SetValue(this, this.Q(queryAttribute.Name));
}
}

}
}
5 changes: 5 additions & 0 deletions Assets/UIComponents/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
"displayName": "Asset Loading from Addressables",
"description": "Contains an example of asset loading from Addressables.",
"path": "Samples~/Addressables"
},
{
"displayName": "QueryAttribute (experimental)",
"description": "Contains an example of using QueryAttribute to populate fields.",
"path": "Samples~/Query"
}
],
"license": "MIT",
Expand Down
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,41 @@ loaded automatically.
loaded automatically. Unlike `[Layout]`, multiple `[Stylesheet]`
attributes can be used on a single UIComponent.

## Experimental features

`[Query]` is an experimental feature that allows for populating fields automatically.
It is accessible via the `UIComponents.Experimental` namespace.

```xml
<!-- Resources/MyLayout.uxml -->
<UXML xmlns:ui="UnityEngine.UIElements">
<ui:Label name="my-label" text="Hello world!" />
<ui:Foldout name="my-foldout">
<ui:Label text="Foldout content" />
</ui:Foldout>
</UXML>
```
```c#
using UIComponents;
using UIComponents.Experimental;

[Layout("MyLayout")]
public class MyComponent : UIComponent
{
[Query("my-label")]
public readonly Label MyLabel;

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

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

## Dependency injection

### Summary
Expand Down

0 comments on commit 1b7e0d5

Please sign in to comment.