New UI Widgets

You can create a derived class with the overridden InitOnce() method to set callbacks on star get (or instantiate) from the pool and return to the pool.

public class CustomRating : Rating
{
	protected override void InitOnce()
	{
		base.InitOnce();

		StarsPoolEmpty.OnEnable = OnStarEnable;
		StarsPoolEmpty.OnDisable = MovedToCache;

		StarsPoolFull.OnEnable = OnStarEnable;
		StarsPoolFull.OnDisable = MovedToCache;
	}

	void OnStarEnable(RatingStar star)
	{

	}

	void MovedToCache(RatingStar star)
	{

	}
}
1 Like

Hi @ilih,
There is an issue with Rating, i have it as prefab with value set to 0.
First time when i instaniate it and set value to 0 on runtime it works.
then i as a user, select first star, then i return it back (setting inactive) and then i again get it from pool and set value to 0 but this time i get following errror.

ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
System.Collections.Generic.List`1[T].get_Item (System.Int32 index) (at <321eb2db7c6d43ea8fc39b54eaca3452>:0)
UIWidgets.ListComponentPool`1[TItemView].get_Item (System.Int32 index) (at Assets/New UI Widgets/Scripts/CollectionsUtilities/ListComponentPool.cs:140)
UIWidgets.Rating.ValueChanged () (at Assets/New UI Widgets/Scripts/Rating/Rating.cs:370)
UIWidgets.Rating.set_Value (System.Int32 value) (at Assets/New UI Widgets/Scripts/Rating/Rating.cs:58)

Sorry for the problem.
Fix:
Replace the line in the ValueChanged() method in the Rating.cs
old line:

UtilitiesUI.Select(StarsPoolFull[Value - 1].gameObject);

new lines:

if (Value > 0)
{
	UtilitiesUI.Select(StarsPoolFull[Value - 1].gameObject);
}
1 Like

I made that change, but now when i second time use it, clicking on stars (all 5 empty, because i set 0 value) nothing happens, no filled stars appear.

That change should not affect this problem.

Please check:

  • is Rating.Interactable enabled?
  • is there any object that can block click on starts (like a transparent image), to do this select EventSystem and check its data at the bottom of the Inspector window - what objects are displayed in pointerEnter, pointerPress, and Current Raycast
1 Like

You were right as usual, stupid me made a mistake. thanks for the hint

Hi @ilih,
It’d be really great if you could also start introducing InstantiateAsync support

For example, Currently we do prefabToast.Clone()
So it’d be great if you can also add prefabToast.CloneAsync so we can use it with UniTask

Thanks

I’ll think about it.

1 Like

Hi ilih,
Any idea when this error occurs?

The following objects are in the same column and have a total relative height of more than 1. It is not supported
UnityEngine.Debug:LogError (object)
EasyLayoutNS.LayoutElementsGroup:IsValidColumn (System.Collections.Generic.List`1<EasyLayoutNS.LayoutElementInfo>) (at Assets/New UI Widgets/Scripts/EasyLayout/LayoutElementsGroup.cs:375)
EasyLayoutNS.LayoutElementsGroup:Validate () (at Assets/New UI Widgets/Scripts/EasyLayout/LayoutElementsGroup.cs:335)
EasyLayoutNS.EasyLayoutBaseType:PerformLayout (System.Collections.Generic.List`1<EasyLayoutNS.LayoutElementInfo>,bool,EasyLayoutNS.ResizeType) (at Assets/New UI Widgets/Scripts/EasyLayout/EasyLayoutBaseType.cs:482)
EasyLayoutNS.EasyLayout:PerformLayout (bool,EasyLayoutNS.ResizeType) (at Assets/New UI Widgets/Scripts/EasyLayout/EasyLayout.cs:1178)
EasyLayoutNS.EasyLayout:SetLayoutVertical () (at Assets/New UI Widgets/Scripts/EasyLayout/EasyLayout.cs:997)
UnityEditor.Undo:Internal_CallWillFlushUndoRecord ()

This is my layout

Btw, first time when i add they work
but when i add them back then i get this error

You have elements with relative height (RectTransform: Anchor Min Y != Anchor Max Y) and their summary height is more than 1 (100% of container height), so it will cause an infinite height increase (combination of relative height of children and Children Height = Fit Container) until overflow error.
You need to replace this (vertical stretch):


to something like this (where Anchor Min Y == Anchor Max Y):

As first time it works, i remove them from this layout, i add them again and this issue happens
so to solve this, before adding to layout i should set itt to top left?

Yes.
It is not necessary top left, it can be any without any vertical stretch.

1 Like

Hi Ilia, hope all is well!

I’m encountering some weird behavior with TileViews that I hope you can help with.

I have a vertical TileView with variable size that I am assigning data to by first creating an ObservableList and then assigning it to the DataSource so something like this:

var items = new ObservableList<CustomItem>;
// populate the items list
TileView.DataSource = items;

Everything works fine until I try to change the data in the tileview after scrolling down the list.
For example, say I build the vertical TileView with 50 objects (top to bottom) and set it to 2 items per row (so 25 lines). Then, say I scroll down to row 15.

I then change the data in the tileview using a new list in TileView.DataSource = items. What happens next, is that, after the new data is loaded, the tileview jumps all the way to the bottom of the new list and then glitches when I try to back up to the top.

This behavior doesn’t occur with ListViews: I set the data on a listview using ListView.DataSource = items and scroll down the list. I then change the data of the list. What happens in this case is that the ListView jumps back to the top of the page and the ListView works perfectly.

How can I make the TileView start over from the top when the data changes?

The TileView is set to “TileView with Vertical Size” and “Retain Scroll Position” is not enabled

Please send a file with data received from “Copy Debug Information” in the TileView inspector during the glitch.

And some questions:

  • does ListView have a Fixed Size or Variable?
  • try to disable “Scroll Intertia Until Item Center” if enabled
  • check if this problem happens with TileView With Fixed Size and disabled Container.EasyLayout.ControlHeight

Sure, here it is:
Copy Debug Information.txt (947.7 KB)

Variable

This is already disabled. So the issue occurs with this option disabled

The problem does not occur. So behavior is correct with these options.

Just in case, my settings for the TileView and Container.EasyLayout (where the error does occur) are TileView with Variable Size and the EasyLayout is as follows:

Probably it is caused by AspectRatioFitter component, because it is not compatible with LayoutGroups.

Please try to replace with it this component and change Children Height to Set Preferred:

using System;
using System.Collections.Generic;
using UIWidgets.Pool;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

[ExecuteAlways]
[RequireComponent(typeof(RectTransform))]
[DisallowMultipleComponent]
public class AspectRatioLayoutElement : UIBehaviour, ILayoutElement
{
	[SerializeField]
	AspectRatioFitter.AspectMode aspectMode = AspectRatioFitter.AspectMode.None;

	[SerializeField]
	float aspectRatio = 1f;

	[SerializeField]
	bool useRectTransformSize = true;

	bool delayedSetDirty = false;

	bool doesParentExists = false;

	RectTransform rectTransform;

	public AspectRatioFitter.AspectMode AspectMode
	{
		get => aspectMode;

		set => Change(ref aspectMode, value);
	}

	public float AspectRatio
	{
		get => aspectRatio;

		set => Change(ref aspectRatio, value);
	}

	protected RectTransform RectTransform
	{
		get
		{
			if (rectTransform == null)
			{
				rectTransform = GetComponent<RectTransform>();
			}

			return rectTransform;
		}
	}

	[NonSerialized]
	protected Vector2 Size;

	public float minWidth => -1;

	public float preferredWidth => Size.x;

	public float flexibleWidth => -1;

	public float minHeight => -1;

	public float preferredHeight => Size.y;

	public float flexibleHeight => -1;

	[SerializeField]
	int m_layoutPriority = 1;

	public int layoutPriority => m_layoutPriority;

	protected bool IsAspectModeValid => !(!doesParentExists && (AspectMode == AspectRatioFitter.AspectMode.EnvelopeParent || AspectMode == AspectRatioFitter.AspectMode.FitInParent));

	protected Vector2 ParentSize
	{
		get
		{
			var parent = RectTransform.parent as RectTransform;
			return !parent ? Vector2.zero : parent.rect.size;
		}
	}

	protected bool IsComponentValidOnObject
	{
		get
		{
			var canvas = GetComponent<Canvas>();
			return !((bool)canvas && canvas.isRootCanvas && canvas.renderMode != RenderMode.WorldSpace);
		}
	}

	protected void Change<T>(ref T field, T value)
	{
		if (!EqualityComparer<T>.Default.Equals(field, value))
		{
			field = value;
			UpdateSize();
		}
	}

	protected override void OnEnable()
	{
		base.OnEnable();

		doesParentExists = RectTransform.parent ? true : false;
		UpdateSize();
	}

	protected override void Start()
	{
		base.Start();

		if (!IsComponentValidOnObject || !IsAspectModeValid)
		{
			enabled = false;
		}
	}

	protected override void OnDisable()
	{
		LayoutRebuilder.MarkLayoutForRebuild(RectTransform);
		base.OnDisable();
	}

	protected override void OnTransformParentChanged()
	{
		base.OnTransformParentChanged();
		doesParentExists = RectTransform.parent ? true : false;
		UpdateSize();
	}

	protected override void OnRectTransformDimensionsChange() => UpdateSize();

	public void CalculateLayoutInputHorizontal() => UpdateSize();

	public void CalculateLayoutInputVertical() => UpdateSize();

	protected void UpdateSize() => Size = CalculateSize();

	protected Vector2 GetLayoutElementSize()
	{
		using var _ = ListPool<ILayoutElement>.Get(out var temp);
		GetComponents(temp);
		temp.Remove(this);

		var result = new Vector2(-1f, -1f);
		var width_priority = int.MinValue;
		var height_priority = int.MinValue;

		foreach (var le in temp)
		{
			if (le is Behaviour behaviour && !behaviour.isActiveAndEnabled)
			{
				continue;
			}

			result.x = LayoutElementValue(le, result.x, Mathf.Max(le.minWidth, le.preferredWidth), ref width_priority);
			result.y = LayoutElementValue(le, result.y, Mathf.Max(le.minHeight, le.preferredHeight), ref height_priority);
		}

		return result;
	}

	protected float LayoutElementValue(ILayoutElement layoutElement, float current, float newValue, ref int priority)
	{
		if ((layoutElement.layoutPriority < priority) || (newValue < 0f))
		{
			return current;
		}

		if (layoutElement.layoutPriority > priority)
		{
			priority = layoutElement.layoutPriority;
			return newValue;
		}
		else if (newValue > current)
		{
			return newValue;
		}

		return current;
	}

	protected Vector2 CalculateSize()
	{
		if (!IsActive() || !IsComponentValidOnObject)
		{
			return new Vector2(-1f, -1f);
		}

		var current_size = useRectTransformSize ? RectTransform.rect.size : GetLayoutElementSize();
		switch (aspectMode)
		{
			case AspectRatioFitter.AspectMode.None:
				if (!Application.isPlaying)
				{
					aspectRatio = Mathf.Clamp(current_size.x / current_size.y, 0.001f, 1000f);
				}

				return new Vector2(-1f, -1f);
			case AspectRatioFitter.AspectMode.HeightControlsWidth:
				return new Vector2(current_size.y * aspectRatio, -1f);
			case AspectRatioFitter.AspectMode.WidthControlsHeight:
				return new Vector2(-1f, current_size.x / aspectRatio);
			case AspectRatioFitter.AspectMode.FitInParent:
			case AspectRatioFitter.AspectMode.EnvelopeParent:
				if (!doesParentExists)
				{
					return new Vector2(-1f, -1f);
				}

				var size = Vector2.zero;
				var parent_size = ParentSize;
				if ((parent_size.y * AspectRatio < parent_size.x) ^ (aspectMode == AspectRatioFitter.AspectMode.FitInParent))
				{
					size.y = GetSizeDeltaToProduceSize(parent_size.x / AspectRatio, 1);
				}
				else
				{
					size.x = GetSizeDeltaToProduceSize(parent_size.y * AspectRatio, 0);
				}

				return size;
			default:
				return new Vector2(-1f, -1f);
		}
	}

	float GetSizeDeltaToProduceSize(float size, int axis)
	{
		return size - ParentSize[axis] * (RectTransform.anchorMax[axis] - RectTransform.anchorMin[axis]);
	}

	#if UNITY_EDITOR
	protected virtual void Update()
	{
		if (delayedSetDirty)
		{
			delayedSetDirty = false;
			UpdateSize();
		}
	}

	protected override void OnValidate()
	{
		aspectRatio = Mathf.Clamp(aspectRatio, 0.001f, 1000f);
		delayedSetDirty = true;
	}
	#endif
}