Skip to content

Commit 4d1b9e2

Browse files
authored
chore: sdk ui improve default styles and hover feedback (#6846)
Additions * Updated theme file to include ScenesUIStyleSheet at root level for Scenes UI panel. That change already makes some default UI Toolkit behaviours from Unity to affect some UI components. * Implemented support for dynamic manipulation of selectedIndex by the scene * Added Opacity transition logic for Dropdown panel activation * Added default hover feedback for UiDropdown, UiInput and UiButton: * UiTransform border color darkening * UiTransform background color darkening * Added visual feedback for the disabling of UiDropdown, UiInput and UiButton * Added UiTransform default Border Width, Radius and Color values when those are missing for UiDropdown, UiInput and UiButton * Added UiTransform default Background Color value when that is missing for UiDropdown and UiInput (UiButton already injects default background from the SDK runtime side depending on the button being "primary" or "secondary") * Test coverage Fixes * Fixed bug that re-applied the selectedIndex property of the Dropdown on any dirty state of the component * Refactored how registered pointer event callbacks are handled for UiDropdown to avoid closure allocations * Fixed font property being ignored for UiDropdown and UiInput * Fixed textAlign property being ignored by UiInput
1 parent c94135f commit 4d1b9e2

29 files changed

+1427
-98
lines changed

Explorer/Assets/DCL/Infrastructure/ECS/Unity/ColorComponent/ColorDefaults.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ public static class ColorDefaults
88

99
public static readonly Color COLOR_BLACK = Color.black;
1010

11-
public static readonly Color PLACEHOLDER_COLOR = new () { r = 0.3f, g = 0.3f, b = 0.3f, a = 1.0f };
11+
public static readonly Color PLACEHOLDER_COLOR = new () { r = 0f, g = 0f, b = 0f, a = 0.5f };
1212
}
1313
}

Explorer/Assets/DCL/PluginSystem/World/SceneUIPlugin.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
using Arch.SystemGroups;
1+
using Arch.SystemGroups;
22
using Cysharp.Threading.Tasks;
33
using DCL.AssetsProvision;
4+
using DCL.ECSComponents;
45
using DCL.Input;
56
using DCL.Optimization.PerformanceBudgeting;
67
using DCL.Optimization.Pools;
@@ -18,11 +19,13 @@
1819
using DCL.SDKComponents.SceneUI.Utils;
1920
using ECS.ComponentsPooling.Systems;
2021
using ECS.LifeCycle;
22+
using ECS.LifeCycle.Systems;
2123
using System;
2224
using System.Collections.Generic;
2325
using System.Threading;
2426
using UnityEngine;
2527
using UnityEngine.UIElements;
28+
using Font = UnityEngine.Font;
2629

2730
namespace DCL.PluginSystem.World
2831
{
@@ -90,14 +93,16 @@ public void InjectToWorld(ref ArchSystemsWorldBuilder<Arch.Core.World> builder,
9093
UITextReleaseSystem.InjectToWorld(ref builder, componentPoolsRegistry);
9194
UIBackgroundInstantiationSystem.InjectToWorld(ref builder, componentPoolsRegistry, sharedDependencies.SceneData, frameTimeBudgetProvider, memoryBudgetProvider);
9295
finalizeWorldSystems.Add(UIBackgroundReleaseSystem.InjectToWorld(ref builder, componentPoolsRegistry));
93-
UIInputInstantiationSystem.InjectToWorld(ref builder, componentPoolsRegistry, sharedDependencies.EcsToCRDTWriter, inputBlock);
96+
UIInputInstantiationSystem.InjectToWorld(ref builder, componentPoolsRegistry, sharedDependencies.EcsToCRDTWriter, inputBlock, styleFontDefinitions);
9497
UIInputReleaseSystem.InjectToWorld(ref builder, componentPoolsRegistry);
95-
UIDropdownInstantiationSystem.InjectToWorld(ref builder, componentPoolsRegistry, sharedDependencies.EcsToCRDTWriter);
98+
UIDropdownInstantiationSystem.InjectToWorld(ref builder, componentPoolsRegistry, sharedDependencies.EcsToCRDTWriter, styleFontDefinitions);
9699
UIDropdownReleaseSystem.InjectToWorld(ref builder, componentPoolsRegistry);
97100
UIPointerEventsSystem.InjectToWorld(ref builder, sharedDependencies.SceneStateProvider, sharedDependencies.EcsToCRDTWriter);
98101
UICanvasInformationSystem.InjectToWorld(ref builder, sharedDependencies.EcsToCRDTWriter);
99102
UIFixPbPointerEventsSystem.InjectToWorld(ref builder);
100103

104+
ResetDirtyFlagSystem<PBUiTransform>.InjectToWorld(ref builder);
105+
ResetDirtyFlagSystem<PBUiBackground>.InjectToWorld(ref builder);
101106
finalizeWorldSystems.Add(ReleasePoolableComponentSystem<Label, UITextComponent>.InjectToWorld(ref builder, componentPoolsRegistry));
102107
}
103108

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,48 @@
1-
using DCL.SDKComponents.SceneUI.Utils;
1+
using System;
2+
using DCL.SDKComponents.SceneUI.Utils;
23
using UnityEngine.UIElements;
34

45
namespace DCL.SDKComponents.SceneUI.Components
56
{
67
public class UIDropdownComponent
78
{
89
public readonly DropdownField DropdownField = new ();
9-
public TextElement TextElement;
10+
public TextElement TextElement { get; private set; }
1011
public bool IsOnValueChangedTriggered;
12+
public int LastIndexSetByScene;
1113

12-
internal EventCallback<ChangeEvent<string>> currentOnValueChanged;
14+
internal Action? cachedScheduledAction;
1315

14-
public void Initialize(string dropdownName, string dropdownStyleClass, string textElementStyleClass)
16+
public void Initialize(string dropdownName)
1517
{
1618
DropdownField.name = dropdownName;
17-
DropdownField.AddToClassList(dropdownStyleClass);
19+
DropdownField.AddToClassList("dcl-dropdown");
1820
DropdownField.pickingMode = PickingMode.Position;
19-
TextElement = DropdownField.Q<TextElement>(className: textElementStyleClass);
21+
TextElement = DropdownField.Q<TextElement>(className: "unity-base-popup-field__text");
2022

2123
IsOnValueChangedTriggered = false;
24+
LastIndexSetByScene = int.MinValue; // -1 is used for the case of 'accept Empty value'
25+
2226
this.RegisterDropdownCallbacks();
2327
}
2428

2529
public void Dispose()
2630
{
2731
this.UnregisterDropdownCallbacks();
32+
DropdownField.UnregisterHoverStyleCallbacks();
33+
}
34+
35+
internal void AnimateDropdownOpacity()
36+
{
37+
// Unity instantiates and removes the Dropdown elements panel every time it's toggled, so the element
38+
// has to be looked up again.
39+
var root = DropdownField.panel.visualTree;
40+
var dropdownOuterContainer = root.Q(null, "unity-base-dropdown__container-outer");
41+
if (dropdownOuterContainer == null)
42+
return;
43+
44+
dropdownOuterContainer.experimental.animation
45+
.Start(0f, 1f, 200, Extensions.OPACITY_ANIMATION_CALLBACK);
2846
}
2947
}
3048
}

Explorer/Assets/DCL/SDKComponents/SceneUI/Components/UIInputComponent.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using DCL.Input;
1+
using DCL.Input;
22
using DCL.SDKComponents.SceneUI.Classes;
33
using DCL.SDKComponents.SceneUI.Utils;
44
using UnityEngine;
@@ -10,6 +10,7 @@ public class UIInputComponent
1010
{
1111
public readonly TextField TextField = new ();
1212
public readonly TextFieldPlaceholder Placeholder = new ();
13+
public TextElement TextElement { get; private set; }
1314
public bool IsOnValueChangedTriggered;
1415
public bool IsOnSubmitTriggered;
1516

@@ -21,16 +22,17 @@ public class UIInputComponent
2122
public void Initialize(
2223
IInputBlock inputBlock,
2324
string textFieldName,
24-
string styleClass,
2525
string text,
2626
string placeholderValue,
2727
Color placeholderColorValue)
2828
{
2929
TextField.name = textFieldName;
30-
TextField.AddToClassList(styleClass);
30+
TextField.AddToClassList("dcl-input");
3131
TextField.pickingMode = PickingMode.Position;
3232
TextField.SetValueWithoutNotify(text);
3333

34+
TextElement = TextField.Q<TextElement>();
35+
3436
Placeholder.Initialize(TextField, placeholderValue, placeholderColorValue);
3537

3638
IsOnValueChangedTriggered = false;
@@ -41,6 +43,7 @@ public void Initialize(
4143
public void Dispose()
4244
{
4345
this.UnregisterInputCallbacks();
46+
TextField.UnregisterHoverStyleCallbacks();
4447
Placeholder.Dispose();
4548
}
4649
}

Explorer/Assets/DCL/SDKComponents/SceneUI/Components/UITransformComponent.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Arch.Core;
1+
using Arch.Core;
22
using CRDT;
33
using DCL.ECSComponents;
44
using DCL.SDKComponents.SceneUI.Utils;
@@ -97,6 +97,7 @@ public void Dispose()
9797
if (IsRoot) return;
9898

9999
this.UnregisterPointerCallbacks();
100+
reusableTransform.UnregisterHoverStyleCallbacks();
100101
reusableTransform.tabIndex = 0;
101102
reusableTransform.RemoveFromHierarchy();
102103
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
2-
<Style src="project://database/Assets/DCL/SDKComponents/SceneUI/ScenesUIStyleSheet.uss" />
1+
<ui:UXML xmlns:ui="UnityEngine.UIElements" editor-extension-mode="False">
2+
<!-- Stylesheet now loaded at DCLSceneDefaultTheme -->
33
</ui:UXML>
Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
:root {
2+
--default-border-radius: 15px;
3+
}
4+
15
/* MAIN CANVAS STYLES */
26
.sceneUIMainCanvas {
37
width: 100%;
@@ -13,6 +17,7 @@
1317
align-content: stretch;
1418
position: absolute;
1519
}
20+
1621
/* INPUT STYLES */
1722
.dcl-input {
1823
flex-grow: 1;
@@ -22,11 +27,7 @@
2227
.dcl-input > .unity-base-text-field__input {
2328
margin: 0;
2429
background-color: rgba(0, 0, 0, 0);
25-
border-color: #959595;
26-
}
27-
28-
.dcl-input:hover > .unity-base-text-field__input {
29-
border-color: #808080;
30+
border-width: 0;
3031
}
3132

3233
/* DROPDOWN STYLES */
@@ -35,26 +36,50 @@
3536
margin : 0;
3637
}
3738

38-
.dcl-dropdown > .unity-base-popup-field__input > .unity-base-popup-field__text {
39+
.dcl-dropdown > .unity-base-popup-field__input ,
40+
.dcl-dropdown > .unity-base-popup-field__input > .unity-base-popup-field__text,
41+
.dcl-dropdown:hover:enabled > .unity-popup-field__input,
42+
.unity-base-popup-field:active:enabled > .unity-base-popup-field__input {
3943
margin: 0;
44+
background-color: rgba(0,0,0,0);
45+
border-color: rgba(0,0,0,0);
46+
border-width: 0;
4047
}
4148

42-
.dcl-dropdown > .unity-base-popup-field__input,
43-
.dcl-dropdown:hover > .unity-base-popup-field__input {
44-
margin: 0;
45-
background-color: rgba(0, 0, 0, 0);
49+
.unity-base-dropdown__container-outer {
50+
background-color: white;
51+
border-radius: var(--default-border-radius);
52+
color: rgb(0, 0, 0);
53+
overflow: hidden;
4654
}
4755

48-
.unity-base-dropdown__item:hover {
49-
background-color: #c2c2c2;
56+
.unity-base-dropdown__container-inner {
57+
background-color: white;
58+
margin: 10px;
5059
}
5160

52-
.unity-enum-field:hover.dcl-dropdown-readonly:hover > .unity-enum-field__input,
53-
.unity-base-popup-field:hover.dcl-dropdown-readonly:hover > .unity-base-popup-field__input {
54-
background-color: #bcbcbc;
61+
.unity-base-dropdown__item {
62+
border-radius: 15px;
63+
}
64+
65+
.unity-base-dropdown__item:hover:enabled > .unity-base-dropdown__item-content > .unity-base-dropdown__label,
66+
.unity-base-dropdown__item:hover:enabled {
67+
background-color: #ECEBED;
68+
color: rgb(0, 0, 0);
69+
}
70+
71+
.unity-base-dropdown__checkmark,
72+
.unity-base-dropdown__item:hover:enabled > .unity-base-dropdown__item-content > .unity-base-dropdown__checkmark {
73+
margin-top: 5px;
74+
-unity-background-image-tint-color: rgb(0, 0, 0);
75+
flex-grow: 0;
76+
flex-shrink: 0;
77+
-unity-background-scale-mode: scale-to-fit;
5578
}
5679

57-
/* override unity default style to hide the arrow */
5880
.unity-base-popup-field__arrow {
59-
visibility: hidden;
81+
margin-right: 10px;
82+
flex-grow: 0;
83+
flex-shrink: 0;
84+
-unity-background-scale-mode: scale-to-fit;
6085
}

Explorer/Assets/DCL/SDKComponents/SceneUI/Systems/UIBackground/UIBackgroundInstantiationSystem.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,6 @@ private void UpdateUIBackground(ref PBUiBackground sdkModel, ref UIBackgroundCom
8989
else if (uiBackgroundComponent is { TexturePromise: not null, Status: LifeCycle.LoadingFinished })
9090
// Ensure texture has latest data from model
9191
uiBackgroundComponent.Image.SetupFromSdkModel(ref sdkModel, uiBackgroundComponent.Image.Texture);
92-
93-
sdkModel.IsDirty = false;
9492
}
9593

9694
[Query]

Explorer/Assets/DCL/SDKComponents/SceneUI/Systems/UIDropdown/UIDropdownInstantiationSystem.cs

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Arch.Core;
1+
using Arch.Core;
22
using Arch.System;
33
using Arch.SystemGroups;
44
using CRDT;
@@ -10,52 +10,87 @@
1010
using DCL.SDKComponents.SceneUI.Groups;
1111
using DCL.SDKComponents.SceneUI.Utils;
1212
using ECS.Abstract;
13+
using ECS.LifeCycle.Components;
14+
using UnityEngine.UIElements;
1315

1416
namespace DCL.SDKComponents.SceneUI.Systems.UIDropdown
1517
{
18+
/*
19+
* As defined in the SDK, UiDropdown entities composition breakdown:
20+
* https://github.com/decentraland/js-sdk-toolchain/blob/main/packages/@dcl/react-ecs/src/components/Dropdown/index.tsx#L41-L53
21+
* - PBUiDropdown
22+
* - (optional, but Explorer queries require it) PBUiTransform
23+
* - (optional) PBUiBackground
24+
* - (optional) PBPointerEvents
25+
*/
26+
1627
[UpdateInGroup(typeof(SceneUIComponentInstantiationGroup))]
1728
[LogCategory(ReportCategory.SCENE_UI)]
1829
public partial class UIDropdownInstantiationSystem : BaseUnityLoopSystem
1930
{
2031
private const string COMPONENT_NAME = "UIDropdown";
32+
private const float HOVER_BORDER_DARKEN_FACTOR = 0.3f;
33+
private const float HOVER_BACKGROUND_DARKEN_FACTOR = 0.15f;
2134

2235
private readonly IComponentPool<UIDropdownComponent> dropdownsPool;
2336
private readonly IECSToCRDTWriter ecsToCRDTWriter;
37+
private readonly StyleFontDefinition[] styleFontDefinitions;
2438

25-
public UIDropdownInstantiationSystem(World world, IComponentPoolsRegistry poolsRegistry, IECSToCRDTWriter ecsToCRDTWriter) : base(world)
39+
public UIDropdownInstantiationSystem(World world, IComponentPoolsRegistry poolsRegistry, IECSToCRDTWriter ecsToCRDTWriter, in StyleFontDefinition[] styleFontDefinitions) : base(world)
2640
{
2741
dropdownsPool = poolsRegistry.GetReferenceTypePool<UIDropdownComponent>();
2842
this.ecsToCRDTWriter = ecsToCRDTWriter;
43+
this.styleFontDefinitions = styleFontDefinitions;
2944
}
3045

3146
protected override void Update(float t)
3247
{
48+
UpdateUIDropdownTransformDefaultsQuery(World);
49+
3350
InstantiateUIDropdownQuery(World);
3451
UpdateUIDropdownQuery(World);
52+
3553
TriggerDropdownResultsQuery(World);
3654
}
3755

3856
[Query]
39-
[All(typeof(PBUiDropdown), typeof(UITransformComponent))]
57+
[All(typeof(PBUiDropdown))]
4058
[None(typeof(UIDropdownComponent))]
41-
private void InstantiateUIDropdown(in Entity entity, ref UITransformComponent uiTransformComponent)
59+
private void InstantiateUIDropdown(in Entity entity, in PBUiTransform pbUiTransform, ref UITransformComponent uiTransformComponent)
4260
{
4361
var newDropdown = dropdownsPool.Get();
44-
newDropdown.Initialize(UiElementUtils.BuildElementName(COMPONENT_NAME, entity), "dcl-dropdown", "unity-base-popup-field__text");
62+
newDropdown.Initialize(UiElementUtils.BuildElementName(COMPONENT_NAME, entity));
4563
uiTransformComponent.Transform.Add(newDropdown.DropdownField);
64+
65+
UiElementUtils.ApplyDefaultUiTransformValues(in pbUiTransform, uiTransformComponent.Transform);
66+
UiElementUtils.ApplyDefaultUiBackgroundValues(World, entity, uiTransformComponent.Transform);
67+
UiElementUtils.ConfigureHoverStylesBehaviour(World, entity, in uiTransformComponent, newDropdown.DropdownField, HOVER_BORDER_DARKEN_FACTOR, HOVER_BACKGROUND_DARKEN_FACTOR);
68+
4669
World.Add(entity, newDropdown);
4770
}
4871

4972
[Query]
73+
[None(typeof(DeleteEntityIntention))]
5074
private void UpdateUIDropdown(ref UIDropdownComponent uiDropdownComponent, ref PBUiDropdown sdkModel)
5175
{
52-
if (!sdkModel.IsDirty)
53-
return;
76+
if (!sdkModel.IsDirty) return;
5477

55-
UiElementUtils.SetupUIDropdownComponent(ref uiDropdownComponent, ref sdkModel);
78+
UiElementUtils.SetupUIDropdownComponent(ref uiDropdownComponent, in sdkModel, in styleFontDefinitions);
5679
sdkModel.IsDirty = false;
5780
}
5881

82+
[Query]
83+
[All(typeof(UIDropdownComponent))]
84+
[None(typeof(DeleteEntityIntention))]
85+
private void UpdateUIDropdownTransformDefaults(in Entity entity, in UITransformComponent uiTransformComponent, in PBUiTransform pbUiTransform, ref UIDropdownComponent uiDropdownComponent)
86+
{
87+
if (!pbUiTransform.IsDirty) return;
88+
89+
UiElementUtils.ApplyDefaultUiTransformValues(in pbUiTransform, uiTransformComponent.Transform);
90+
// Re-configure hover so the stored border colors match the updated transform (e.g. defaults applied above); avoids wrong restore on hover leave when PBUiTransform changed while hovered.
91+
UiElementUtils.ConfigureHoverStylesBehaviour(World, entity, in uiTransformComponent, uiDropdownComponent.DropdownField, HOVER_BORDER_DARKEN_FACTOR, HOVER_BACKGROUND_DARKEN_FACTOR);
92+
}
93+
5994
[Query]
6095
private void TriggerDropdownResults(ref UIDropdownComponent uiDropdownComponent, ref CRDTEntity sdkEntity)
6196
{

0 commit comments

Comments
 (0)