Skip to content

Commit

Permalink
feat: add RootClassAttribute
Browse files Browse the repository at this point in the history
  • Loading branch information
jonisavo committed Jun 24, 2022
1 parent 0e8278f commit 983a425
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 0 deletions.
31 changes: 31 additions & 0 deletions Assets/UIComponents.Tests/RootClassAttributeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using NUnit.Framework;

namespace UIComponents.Tests
{
[TestFixture]
public class RootClassAttributeTests
{
[RootClass("test-class")]
private class ComponentWithRootClass : UIComponent {}

[Test]
public void Adds_Class_To_Component()
{
var component = new ComponentWithRootClass();
Assert.That(component.ClassListContains("test-class"), Is.True);
}

[RootClass("other-test-class")]
[RootClass("final-test-class")]
private class ChildComponentWithRootClass : ComponentWithRootClass {}

[Test]
public void Adds_Class_To_Component_And_Child_Component()
{
var component = new ChildComponentWithRootClass();
Assert.That(component.ClassListContains("test-class"), Is.True);
Assert.That(component.ClassListContains("other-test-class"), Is.True);
Assert.That(component.ClassListContains("final-test-class"), Is.True);
}
}
}
3 changes: 3 additions & 0 deletions Assets/UIComponents.Tests/RootClassAttributeTests.cs.meta

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

33 changes: 33 additions & 0 deletions Assets/UIComponents.Tests/UIComponentEffectAttributeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using NUnit.Framework;

namespace UIComponents.Tests
{
[TestFixture]
public class UIComponentEffectAttributeTests
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
private class TestEffectAttribute : UIComponentEffectAttribute
{
public override void Apply(UIComponent component) {}
}

[TestEffect]
[TestEffect(Priority = -2)]
[TestEffect(Priority = 5)]
private class UIComponentWithEffects : UIComponent {}

[Test]
public void Effects_Are_Sorted_By_Priority()
{
var component = new UIComponentWithEffects();

Assert.That(UIComponent.TryGetCache<UIComponentWithEffects>(out var cache), Is.True);

Assert.That(cache.EffectAttributes.Count, Is.EqualTo(3));
Assert.That(cache.EffectAttributes[0].Priority, Is.EqualTo(5));
Assert.That(cache.EffectAttributes[1].Priority, Is.EqualTo(0));
Assert.That(cache.EffectAttributes[2].Priority, Is.EqualTo(-2));
}
}
}

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

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

public UIComponentCache(Type componentType)
Expand All @@ -23,10 +24,14 @@ public UIComponentCache(Type componentType)
LayoutAttribute = null;
StylesheetAttributes = new List<StylesheetAttribute>();
AssetPathAttributes = new List<AssetPathAttribute>();
EffectAttributes = new List<UIComponentEffectAttribute>();

LayoutAttribute = GetSingleAttribute<LayoutAttribute>();
PopulateAttributeListParentsFirst(componentType, StylesheetAttributes);
PopulateAttributeList(AssetPathAttributes);
PopulateAttributeList(EffectAttributes);

EffectAttributes.Sort((first, second) => second.CompareTo(first));
}

[CanBeNull]
Expand Down
20 changes: 20 additions & 0 deletions Assets/UIComponents/Core/RootClassAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;

namespace UIComponents
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class RootClassAttribute : UIComponentEffectAttribute
{
private readonly string _className;

public RootClassAttribute(string className)
{
_className = className;
}

public override void Apply(UIComponent component)
{
component.AddToClassList(_className);
}
}
}
3 changes: 3 additions & 0 deletions Assets/UIComponents/Core/RootClassAttribute.cs.meta

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

10 changes: 10 additions & 0 deletions Assets/UIComponents/Core/UIComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ protected UIComponent()
LayoutAndStylesSetupProfilerMarker.Begin();
LoadLayout();
LoadStyles();
ApplyEffects();
LayoutAndStylesSetupProfilerMarker.End();
QueryFieldsSetupProfilerMarker.Begin();
PopulateQueryFields();
Expand Down Expand Up @@ -223,6 +224,15 @@ private void LoadStyles()
styleSheets.Add(loadedStyleSheets[i]);
}

private void ApplyEffects()
{
var effectAttributes = CacheDictionary[_componentType].EffectAttributes;
var effectAttributeCount = effectAttributes.Count;

for (var i = 0; i < effectAttributeCount; i++)
effectAttributes[i].Apply(this);
}

private static readonly Type VisualElementType = typeof(VisualElement);

private void PopulateQueryFields()
Expand Down
23 changes: 23 additions & 0 deletions Assets/UIComponents/Core/UIComponentEffectAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using JetBrains.Annotations;

namespace UIComponents
{
/// <summary>
/// An attribute used to apply effects to a <see cref="UIComponent"/>.
/// They are applied after the layout and assets have been loaded.
/// Attributes with higher priority are applied first.
/// </summary>
[BaseTypeRequired(typeof(UIComponent))]
public abstract class UIComponentEffectAttribute : Attribute, IComparable<UIComponentEffectAttribute>
{
public int Priority { get; set; } = 0;

public abstract void Apply(UIComponent component);

public int CompareTo(UIComponentEffectAttribute other)
{
return Priority.CompareTo(other.Priority);
}
}
}
3 changes: 3 additions & 0 deletions Assets/UIComponents/Core/UIComponentEffectAttribute.cs.meta

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

22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ To update, change `upm/v0.15.0` to point to the latest version.

## Layouts and stylesheets

### LayoutAttribute

`[Layout]` allows specifying the path to a UXML file. The file will be
loaded automatically. A component can have a single `[Layout]` attribute. It can be
inherited from a parent class.
Expand All @@ -125,6 +127,8 @@ public class UIComponentWithOverriddenLayout : UIComponentWithLayout
}
```

### StylesheetAttribute

`[Stylesheet]` allows specifying paths to USS files. The files will be
loaded automatically. Unlike `[Layout]`, multiple `[Stylesheet]`
attributes can be used on a single UIComponent.
Expand All @@ -145,6 +149,24 @@ Stylesheets will be applied to `ChildComponent` in the following order:

This means that child components can override styles from their parents.

### RootClassAttribute

`[RootClass]` allows specifying the name of a class that will be added
to the root element of the UIComponent.

```css
/* Common.uss */

.root {
background-color: #ff0000;
}
```
```c#
[Stylesheet("Common")]
[RootClass("root")]
public class UIComponentWithRootClass : UIComponent {}
```

## Event interfaces

UIComponents supports a number of event interfaces. When implemented, the callbacks
Expand Down

0 comments on commit 983a425

Please sign in to comment.