Widgets Generation

You can generate widgets for your data type with Context menu / Create / New UI Widgets / Generate Widgets.

_images/widget-generation.png

Requirements

Data type should have at least one public field or public readable property of the supported types.
To be available in the inspector window data type should have [System.Serializable] attribute.

Supported types

Text types (string or types convertible to the string):

  • string

  • numeric data types (int, float, etc)

  • any type with overridden ToString() method and not derived from UnityEngine.Object.

Graphic types:

  • Sprite

  • Texture2D

  • Color

  • Color32

Limitations

  • Autocomplete

    Requires at least one field or property of the string type.

  • Table

    Requires at least one field or property of the text type.

Known Problems

Widget generation does not work with struct or interface types inside a namespace with some Unity versions due to bug.

Workaround

Specify the type name in the Data Type field.
Another way is to change interface or struct to class in the type definition. Then run widgets generation 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 generation 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.
If you want to automatically update collections widgets (like ListView, TileView, Table) on item data changes, then you need to add INotifyPropertyChanged or IObservable implementation to your data type.
Implementation can be added even after widgets generation.
The 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
      {
         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
      {
         name = value;
         Changed();
      }
   }

   public event OnChange OnChange;

   protected void Changed(string propertyName)
   {
      OnChange?.Invoke(this, new PropertyChangedEventArgs(propertyName));
   }

   ...
}

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

Be careful not to overwrite modified scripts if you decide re-run widget generation.

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 generation 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());
      }
   }
}