Widgets Generator
You can generate widgets for your data type with Context menu / Create / New UI Widgets / Generate Widgets.
List of Generated Widgets
Autocomplete: requires at least one field or property of
stringtype.AutoCombobox:
ComboboxwithAutocompleteto filter and select items by typing; requires at least one field or property ofstringtype.ComboboxMultiselect: same
Comboboxconfigured to display multiple selected values.DragInfo: displays content of dragged data
PickerListView:
Pickerto select the value fromListViewPickerTreeView:
Pickerto select the value fromTreeView
Requirements
[System.Serializable] attribute.Autocomplete, AutoCombobox, and ListView require the data type to implement the IEquatable<T> interface to work correctly.Supported types
Text types (string or types convertible to the string):
stringnumeric data types (
int,float, etc)any type with overridden ToString() method and not derived from
UnityEngine.Object.
Graphic types:
SpriteTexture,Texture2DColorColor32
Limitations
Autocomplete
Requires at least one field or property of the
stringtype.Table
Requires at least one field or property of the text type.
Attributes
[GeneratorPaths]: specify paths for the created scripts, prefabs, scenes[GeneratorNamespace]: specify namespaces for the created scripts[GeneratorIgnore]: mark fields or properties that should not be used in widgets[GeneratorAutocomplete]: mark the field or property that should be used for autocomplete (will be used only first field with this attribute)
[GeneratorPaths(
"Assets/Widgets/Scenes",
"Assets/Widgets/Scripts",
"Assets/Widgets/Editor",
"Assets/Widgets/Prefabs")]
[GeneratorNamespace("Widgets", "Widgets.Editor")]
public class DataSprite
{
[GeneratorAutocomplete]
public string Name;
public string Text;
public Sprite Icon;
[GeneratorIgnore]
public Texture Texture;
}
Known Problems
Widget generator does not work with struct or interface types inside a namespace with some Unity versions due to bug.
Workaround
interface or struct to class in the type definition. Then run widgets generator and return type to interface or struct.Extending and Overriding Classes
All generated classes are marked as partial to make possible it to split the definition of a class over two or more source files.
The recommended way to extending generated class is to create a new source file with class definition and add new methods or overridden methods to it.
It will prevent code loss in case of a new run of widgets generator for the same data type.
INotifyPropertyChanged and IObservable Support
ObservableList<T> used by widgets provide support for INotifyPropertyChanged and IObservable interface of the data type, so widget will be updated if property changed and was raised corresponding event.INotifyPropertyChanged or IObservable implementation to your data type.IObservable interface is preferable if you want to reduce memory allocations.public class ListViewIconsItemDescription : INotifyPropertyChanged
{
[SerializeField]
string name;
public string Name
{
get
{
return name;
}
set
{
if (name != value)
{
name = value;
Changed("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void Changed(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
...
}
public class ListViewIconsItemDescription : IObservable
{
[SerializeField]
string name;
public string Name
{
get
{
return name;
}
set
{
if (name != value)
{
name = value;
Changed();
}
}
}
public event OnChange OnChange;
protected void Changed()
{
OnChange?.Invoke(this);
}
...
}
This way name of the first item displayed with the widget will be changed:
ListView.DataSource[0].Name = "New name";
You can disable this behavior with ObserveItems property:
ListView.DataSource.ObserveItems = false;
// name displayed with the widget will be not changed
ListView.DataSource[0].Name = "New name";
Replacing generated code
Generated code can be freely modified.
Important
Collections
Widgets to display collections consist of the three classes:
your custom data type (class, struct or interface)
Widget class (required because of the generic components not allowed)
DefaultItem class to control tile view
Widget and DefaultItem classes created with widget generator for your type and you will need only to modify created DefaultItem class if it needs at all.
Functions to modify in the DefaultItem class:
SetData() to display passed data. Called when the item displayed or recycled.
MovedToCache() to unload unused resources like Sprite. Called when the item is out of sight and not be displayed or recycled (can happen when items list cleared).
For example you can replace default widgets used to display item fields with other widgets.
This example show Item.Number field displayed with Spinner instead of Text and field value update with Spinner changes.
Original code:
namespace UIWidgets.Examples.WidgetGeneration.Widgets
{
/// <summary>
/// ListView component for the DataItem.
/// </summary>
public class ListViewComponentDataItem : UIWidgets.ListViewItem,
UIWidgets.IResizableItem,
UIWidgets.IViewData<UIWidgets.Examples.WidgetGeneration.DataItem>
{
...
/// <summary>
/// The Number.
/// </summary>
public UIWidgets.TextAdapter Number;
...
/// <summary>
/// Gets the current item.
/// </summary>
public UIWidgets.Examples.WidgetGeneration.DataItem Item
{
get;
protected set;
}
/// <summary>
/// Sets component data with specified item.
/// </summary>
/// <param name="item">Item.</param>
public virtual void SetData(UIWidgets.Examples.WidgetGeneration.DataItem item)
{
Item = item;
if (Number != null)
{
Number.text = Item.Number.ToString();
}
...
}
...
}
}
New code:
namespace UIWidgets.Examples.WidgetGeneration.Widgets
{
/// <summary>
/// ListView component for the DataItem.
/// </summary>
public class ListViewComponentDataItem : UIWidgets.ListViewItem,
UIWidgets.IResizableItem,
UIWidgets.IViewData<UIWidgets.Examples.WidgetGeneration.DataItem>
{
...
/// <summary>
/// The Number.
/// </summary>
public UIWidgets.Spinner Number;
...
/// <summary>
/// Gets the current item.
/// </summary>
public UIWidgets.Examples.WidgetGeneration.DataItem Item
{
get;
protected set;
}
/// <summary>
/// Add callbacks.
/// </summary>
protected override void Start()
{
base.Start();
if (Number != null)
{
Number.onValueChangeInt.AddListener(UpdateNumber);
}
}
/// <summary>
/// Update Item.Number when spinner value changed.
/// </summary>
void UpdateNumber(int value)
{
Item.Number = value;
}
/// <summary>
/// Sets component data with specified item.
/// </summary>
/// <param name="item">Item.</param>
public virtual void SetData(UIWidgets.Examples.WidgetGeneration.DataItem item)
{
Item = item;
if (Number != null)
{
Number.Value = Item.Number;
}
...
}
/// <summary>
/// Remove callbacks.
/// </summary>
protected override void OnDestroy()
{
if (Number != null)
{
Number.onValueChangeInt.RemoveListener(UpdateNumber);
}
base.OnDestroy();
}
...
}
}
If you need to dynamicaly change the state of the objects like enabling or disabling them and restore state after item recycled then this can be done with SetData function:
public virtual void SetData(UIWidgets.Examples.WidgetGeneration.DataItem item)
{
Item = item;
// set state after item recycled
ToggableObject.setActive(item.IsToggableObjectActive);
...
}
Autocomplete
You can override Startswith, Contains, and GetStringValue functions to use different field or use other match condition.
This example show Text field replaced with SomeOtherText field and match with EndsWith instead of Contains.
Original code:
namespace UIWidgets.Examples.WidgetGeneration.Widgets
{
/// <summary>
/// Autocomplete for the DataItem.
/// </summary>
public class AutocompleteDataItem : UIWidgets.AutocompleteCustom<UIWidgets.Examples.WidgetGeneration.DataItem,
ListViewComponentDataItem, ListViewDataItem>
{
...
/// <summary>
/// Returns a value indicating whether Input occurs within specified value.
/// </summary>
/// <param name="value">Value.</param>
/// <returns>true if the Input occurs within value parameter; otherwise, false.</returns>
public override bool Contains(UIWidgets.Examples.WidgetGeneration.DataItem value)
{
if (CaseSensitive)
{
return value.Text.Contains(Query);
}
return value.Text.ToLower().Contains(Query.ToLower());
}
}
}
New code:
namespace UIWidgets.Examples.WidgetGeneration.Widgets
{
/// <summary>
/// Autocomplete for the DataItem.
/// </summary>
public class AutocompleteDataItem : UIWidgets.AutocompleteCustom<UIWidgets.Examples.WidgetGeneration.DataItem,
ListViewComponentDataItem, ListViewDataItem>
{
...
/// <summary>
/// Returns a value indicating whether Input occurs within specified value.
/// </summary>
/// <param name="value">Value.</param>
/// <returns>true if the Input occurs within value parameter; otherwise, false.</returns>
public override bool Contains(UIWidgets.Examples.WidgetGeneration.DataItem value)
{
if (CaseSensitive)
{
return value.SomeOtherText.EndsWith(Query);
}
return value.SomeOtherText.ToLower().EndsWith(Query.ToLower());
}
}
}