Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

New UI Widgets

Discussion in 'Assets and Asset Store' started by ilih, Feb 11, 2015.

  1. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    You need to create a script with a data type definition: sprite (image to display), label, etc.
    Like this:
    ImageData.cs
    Code (CSharp):
    1. [System.Serializable]
    2. public class ImageData
    3. {
    4.     public string Label;
    5.  
    6.     public Sprite Sprite;
    7. }
    Then select the created script, click the right mouse button, and use "Create / New UI Widgets / Generate Widgets".
    It will create scripts and prefabs, and among them will be TileView. You can freely modify it to get the desired appearance.

    Please check the video for details about the Widgets Generator.
     
  2. sgmdev

    sgmdev

    Joined:
    Jul 26, 2023
    Posts:
    13
    Got it, will try, thank you!
    And what if it is a gameobject instead of just an image? Something exactly like the video search result in tiktok where the gameobject is rectangular, and inside of that there is an image, a text and an icon?
    Is it the exact same process that you described?
     
  3. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    Yes, the same process: in this case date type should have a sprite (image), text, and icon fields.
     
  4. sgmdev

    sgmdev

    Joined:
    Jul 26, 2023
    Posts:
    13
    Thank you! Just generated the widgets, but I cannot seem to find the widgets in the context menu to add them to the scene. I right click > UI but I only see New UI Widget option. The videos shows also an option called New UI Widget - Animal.
    Did I do something wrong?
     
  5. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    Please check:
    • are there any errors in the console window?
    • if not then try to create some empty script or edit an existing one: in very rare cases Unity does not compile created scripts immediately, this should force recompilation, and after this should be created a menu, prefabs, and demo scene
     
  6. sgmdev

    sgmdev

    Joined:
    Jul 26, 2023
    Posts:
    13
    Got it, thanks!
     
  7. jh092

    jh092

    Joined:
    Sep 2, 2018
    Posts:
    57
    Hi Ilia, may I ask please how can I change a theme option for one off my colors at runtime. For example I have an Image color set to theme option "My Color 1", how can I change it to use theme option "My Color 2"?

    many thanks and best wishes, John.
     
  8. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    This code should work:
    Code (CSharp):
    1. var old_option = Theme.Colors.GetOption("Option 1");
    2. var new_option = Theme.Colors.GetOption("Option 2");
    3. var component = GetComponent<ThemeTargetBase>();
    4. var changed = false;
    5. foreach (var color in component.Colors)
    6. {
    7.     if (color.OptionId == old_option.Id)
    8.     {
    9.         color.OptionId = new_option.Id;
    10.         changed = true;
    11.     }
    12. }
    13. if (changed)
    14. {
    15.     component.Refresh();
    16. }
    But why do you need it?
    You can change active variation (
    Theme.SetActiveVariation("Dark");
    ) if you want to change colors.
     
  9. jh092

    jh092

    Joined:
    Sep 2, 2018
    Posts:
    57
    Thank you for the super fast reply. Ah ok maybe a variation would be better then. My use-case is I have a message window that will be colored green, yellow or red depending if the message is info, warning or error. Instead of three different colors then, you're saying I should have perhaps just one theme color with two variations?
     
  10. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    I think Prefab Variants will be better in this case: they allow to use same base prefab (message window) with different properties (theme options).

    Other solutions have downsides:
    • change theme option will require restoring options or saving those options somewhere if you are reusing windows
    • change active variation will affect all message windows, it will be a problem if you have more than one opened window with different colors
     
    hopeful likes this.
  11. jh092

    jh092

    Joined:
    Sep 2, 2018
    Posts:
    57
    That does sound like the right approach, thanks again Ilia.
     
  12. sgmdev

    sgmdev

    Joined:
    Jul 26, 2023
    Posts:
    13
    Hello Ilia, I'm trying to create a ListView where the data for each item is fetched by a backend api by the defaultItem itself, but, after the data is received, the entire list is called again and again and again.

    What I mean is that I first make an api call to a backend to receive a list of ids.
    I then pass the list of ids as DataSource to the ListView.
    Each default component of the ListView has a custom child gameObject Prefab. This prefab has a script that then uses that id to fetch data and download an image using callbacks. Once the callbacks are processed, it sets the downloaded data to the image component and text components that are children of this prefab.

    However, once a value of the prefab is changed, the ListView is triggered to refresh all the list, which means that each prefab tries to download it's data again. So basically, if I have 15 ids, I download 15 times the data for all 15 ids.

    How can I make this work with the ListView?
     
  13. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    You should use a common external component (not attached to the DefaultItem) to fetch and cache data because item instances are reused, so any internal data can be lost.

    Something like this, the Api component is attached to the ListView.
    DefaultItem component:
    Code (CSharp):
    1. public int Id;
    2.  
    3. public void SetData(int id)
    4. {
    5.     Id = id;
    6.     Owner.GetComponent<Api>().Load(id, OnLoad);
    7. }
    8.  
    9. void OnLoad(int id, Sprite image)
    10. {
    11.     if (Id == id) // display image only if the instance has the same id
    12.     {
    13.         // show image
    14.     }
    15. }
    Api component:
    Code (CSharp):
    1. class Api : MonoBehaviour
    2. {
    3.     Dictionary<int, Sprite> cache = new Dictionary<int, Sprite>();
    4.     Dictionary<int, Action<int, Sprite>> requests = new Dictionary<int, Action<int, Sprite>>();
    5.  
    6.     public void Load(int id, Action<int, Sprite> callback)
    7.     {
    8.         if (cache.TryGetValue(id, out var sprite))
    9.         {
    10.             callback(id, sprite);
    11.             return;
    12.         }
    13.  
    14.         if (requests.ContainsKey(id)) // prevent multiple requests for the same id
    15.         {
    16.             requests[id] += callback; // save callback
    17.             return;
    18.         }
    19.  
    20.         requests[id] = callback;
    21.         sprite = await FetchData(id);
    22.  
    23.         cache[id] = sprite;
    24.         requests[id](id, sprite); // process all callbacks
    25.         requests.Remove(id);
    26.     }
    27. }
     
  14. sgmdev

    sgmdev

    Joined:
    Jul 26, 2023
    Posts:
    13
    Oooh, got it, thank you so much!
     
  15. jGate99

    jGate99

    Joined:
    Oct 22, 2013
    Posts:
    1,963
    Hi @ilih
    Im adding "Mark" of switch on runtime and as mark is stretched to left and positioned to left with a custom width, i cant able to set it programmatically, can you please provide me code. I tried following but it didnt work.
    Code (CSharp):
    1. rectTransform.anchorMin = new Vector2(0f, 0f); // bottom left
    2.             rectTransform.anchorMax = new Vector2(0f, 1f); // top left
    3.  
    4.             rectTransform.pivot = new Vector2(0f, 0.5f);
    5.  
    6.             rectTransform.sizeDelta = new Vector2(width, 0);
    7.  
    8.              rectTransform.anchoredPosition = Vector2.zero;
    upload_2024-4-19_6-34-41.png
     
  16. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    Probably your are forgot to set rectTransform parent or
    Switch.SetStatus()
    or change IsOn property to update mark position.

    This code works:
    Code (CSharp):
    1.     public class TestSwitchMark : MonoBehaviour
    2.     {
    3.         [SerializeField]
    4.         protected Switch Switch;
    5.  
    6.         public void Create()
    7.         {
    8.             var mark = new GameObject("Mark");
    9.             var mark_rt = mark.AddComponent<RectTransform>();
    10.             var mark_image = mark.AddComponent<Image>();
    11.             var width = 26f;
    12.  
    13.             // Switch or some nested object
    14.             mark_rt.SetParent(Switch.transform);
    15.  
    16.             mark_rt.anchorMin = new Vector2(0f, 0f);
    17.             mark_rt.anchorMax = new Vector2(0f, 1f);
    18.             mark_rt.pivot = new Vector2(0f, 0.5f);
    19.             mark_rt.sizeDelta = new Vector2(width, 0);
    20.             mark_rt.anchoredPosition = Vector2.zero;
    21.  
    22.             Switch.Mark = mark_rt;
    23.             Switch.MarkGraphic = mark_image;
    24.  
    25.             // call SetStatus() twice: first to actually change mark position and second on to restore IsOn
    26.             Switch.SetStatus(!Switch.IsOn, false);
    27.             Switch.SetStatus(!Switch.IsOn, false);
    28.         }
    29.     }
     
    jGate99 likes this.
  17. jGate99

    jGate99

    Joined:
    Oct 22, 2013
    Posts:
    1,963
    @ilih, notice how mark recttransform is reversed in my case
    upload_2024-4-19_23-19-16.png
    One reason is that in my case Mark is "sibling" of switch control and not its child
    upload_2024-4-19_23-20-5.png

    Could that be reason that positioning is wrong?


    Update 2:
    Ok I see the issue, if i set the mark visually in editor then it uses right position to on and off.
    which means i have to add a delay and then set it so it has enough time to get the right values.
     
    Last edited: Apr 19, 2024
  18. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    I see that the mark width is negative.
    Not sure why it happens, so cannot say will the delay can fix it or not.
     
    jGate99 likes this.
  19. jGate99

    jGate99

    Joined:
    Oct 22, 2013
    Posts:
    1,963
    Setting mark with delay solves it, not ideal though
     
  20. sgmdev

    sgmdev

    Joined:
    Jul 26, 2023
    Posts:
    13
    Hi Ilia, I'm trying to use a ListView to create a chat that scrolls from the bottom to the top. New messages are shown at the bottom and older messages are shown at the top. As the user scrolls towards the top, more messages are loaded and added.
    I have correctly setup a ListView with a permanent sort option which works well.

    What I'm having issues with is adding new chats as the user scrolls up:
    1. The list always starts at the top instead of at the bottom.
    2. When new chats are added to the top of the list while scrolling towards the top, the UI gets very glitchy. It seems that the scrollview is expanding at the bottom instead of the top and tries to keep the verticalNormalizedPosition.

    The behavior works well if the scrollview is from top to bottom and I add items to the bottom of the list. But it is glitchy when adding to the top.

    Am I doing something wrong? Do I have to flip some sort of axis or something?

    Currently, I have the following script attached to the parent of my ListView that is triggered when the user scrolls:

    Code (CSharp):
    1.  
    2. [SerializeField] ScrollRect messagesScrollRect;
    3. messagesScrollRect.onValueChanged.AddListener(OnScrollValueChanged);
    4.  
    5. public void OnScrollValueChanged(Vector2 position)
    6.     {
    7.         if (contentUpdating) return;
    8.  
    9.         // Check if the user has scrolled to 30% of the end of the list & enough time has passed since the last load
    10.         if (
    11.             Time.time >= lastLoadTime + cooldownTime
    12.             ) {
    13.             if (messagesScrollRect.verticalNormalizedPosition >= 0.7f)
    14.             {
    15.                 contentUpdating = true;
    16.                 lastLoadTime = Time.time; // Update the last load time
    17.              
    18.                 ListView.DataSource.AddRange(olderPosts)
    19.             }
    20.         }
    21.     }
     
    Last edited: Apr 20, 2024
  21. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    I can advise you to enable
    ListView.ReversedOrder
    (and change list sort so new messages will be first) and then restore scroll position after adding new items.

    Like this:
    Code (CSharp):
    1. var pos = ListView.GetScrollPosition();
    2. ListView.DataSource.AddRange(olderPosts);
    3. ListView.ScrollToPosition(Mathf.Max(0f, pos));
    The same is possible if
    ReversedOrder
    is disabled but will require the correct calculation for the scroll position because it is measured from the top.

    Upd.:
    You can add this method to your ListView class to add items without scroll:
    Code (CSharp):
    1.         public void AddItemsWithoutScroll(IReadOnlyList<YourItemType> items)
    2.         {
    3.             var pos = GetScrollPosition();
    4.             var start_size = ListRenderer.ListSize();
    5.  
    6.             DataSource.AddRange(items);
    7.  
    8.             if (!ReversedOrder)
    9.             {
    10.                 pos += ListRenderer.ListSize() - start_size;
    11.             }
    12.  
    13.             ScrollToPosition(Mathf.Max(0f, pos));
    14.         }
     
    Last edited: Apr 20, 2024
  22. sgmdev

    sgmdev

    Joined:
    Jul 26, 2023
    Posts:
    13
    Thank you! I reversed the order and it logically makes sense. But it doesn't seem to be working well either. On the first load, it seems to jump back down to the start:
    Before adding, verticalNormalizedPosition = 0.7786037
    After adding, verticalNormalizedPosition After = -1.877613

    Wouldn't there be a way to flip the scrollview with a reversed list so that it expands upwards instead?
     
  23. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    Try to replace
    ScrollToPosition(Mathf.Max(0f, pos));
    in the
    AddItemsWithoutScroll()
    method with:
    Code (CSharp):
    1.             if (ListRenderer.CanScroll)
    2.             {
    3.                 Layout.UpdateLayout();
    4.                 ScrollToPosition(pos);
    5.             }
    It should fix a problem if I understand it right.
     
  24. jGate99

    jGate99

    Joined:
    Oct 22, 2013
    Posts:
    1,963
    Hi @ilih
    How do i set selected index for LinearTileView, because i have index for realData but LinearTileView uses its own index system.
    Please advise
     
  25. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    You can get an item by original index and then find the index of this item in the DataSource:
    Code (CSharp):
    1. var item = ListView.RealDataSource[original_index];
    2. var new_index = ListView.DataSource.IndexOf(item, original_index);
    3. ListView.Select(new_index);
    And reverse:
    Code (CSharp):
    1. var item = ListView.DataSource[new_index];
    2. var original_index = ListView.RealDataSource.IndexOf(item);
     
    jGate99 likes this.
  26. jGate99

    jGate99

    Joined:
    Oct 22, 2013
    Posts:
    1,963
    Problem is in my case, im passing the same item multiple times so if i use indexOf approach will not work.
    is there an alternate way?

    Btw, ill be setting index right after assigning datasource.

    Update 2:
    Ok i managed to set index by looping over datasource and getting the index meant for cell.
     
    Last edited: Apr 24, 2024
  27. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    You can use FindIndex() and ReferenceEquals() if the same items are different objects with the same data.
    Code (CSharp):
    1. var new_index = ListView.DataSource.FindIndex(x => ReferenceEquals(x, item));
     
  28. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    v1.17.8 released

    Changelog:
    • COMPATIBILITY-BREAKING CHANGES:
      public void Init()
      methods are split into two
      public void Init()
      and
      protected virtual void InitOnce()
      to avoid excessive flag checks. To fix broken code you need to replace
      public override void Init()
      with
      protected override void InitOnce()
      .
    • COMPATIBILITY-BREAKING CHANGES: UIThemes ThemesReferences and ReferencesGUIDs scripts moved from the Editor folder to the Scripts folder
    • added SelectableMarker: shows the specified marker for the currently selected game object.
    • added TextMeshProPaginator for the TMP_Text (use with text Overflow = Page)
    • AutoCombobox: added DataSource, SelectedIndex, SelectedIndices, SelectedItem, and SelectedItems properties (proxies for the corresponding ListView properties)
    • Combobox: added DataSource, SelectedIndex, SelectedIndices, SelectedItem, and SelectedItems properties (proxies for the corresponding ListView properties)
    • EasyLayout: added MovementAnimateAll and ResizeAnimateAll options - animate movement/resize for all elements if enabled; otherwise new elements will not be animated.
    • ListView: CenterTheItems renamed to the AlignCenter
    • Notification: SlideUpOnHide now is obsolete, use the EasyLayout "Movement Animation" option instead
    • TreeNodes: added "alwaysIncludeNested" parameter to the Filter and FilterNodes method
    • Paginators: added the HideIfOnePage option - an active page will be hidden if Pages <= 1
     
    jGate99 likes this.
  29. jGate99

    jGate99

    Joined:
    Oct 22, 2013
    Posts:
    1,963
    Hi @ilih,
    Im using both SetSharedTemplates and TemplateSelector for multiple listsviews and all these listsviews share the same selector and Shared template. Now there are cases when i "Clear" the data source of a list and at that time i want to run a method on both VisibleItems which im able to as well as on that listview instance's "DisableContainer" which has few disabled listview items, so how do i access these?
    Thanks
     
  30. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    You can use the
    GetComponentsEnumerator()
    method:
    Code (CSharp):
    1. foreach (var disabled_instance in ListView.GetComponentsEnumerator(PoolEnumeratorMode.Cache))
    2. {
    3.  
    4. }
    PoolEnumeratorMode.Active
    provides visible instances of this ListView.
    PoolEnumeratorMode.Cache
    provides cached (disabled) instances.
    PoolEnumeratorMode.All
    provides both of them.

    Or you can iterate
    Templates
    list, it provide access to the all visible instances, not only of this ListView.
    Code (CSharp):
    1. foreach (var template in ListView.Templates)
    2. {
    3.    template.Instances.AllInstances.ForEach(...);
    4.    template.Requested.ForEach(...);
    5.    template.Cache.ForEach(...);
    6. }
     
    jGate99 likes this.
  31. jGate99

    jGate99

    Joined:
    Oct 22, 2013
    Posts:
    1,963
    I actually want to access for that single listview, reason is other lists views will still be active and using shared template+template selector. so should i use first approach? PoolEnumeratorMode.All ?


    Update 2:
    I also noticed it creates a new enumeratoer every time i do that, can you please create an overload that accepts a list (for reuse purposes) and fill it without creating a new enumrator
     
    Last edited: Apr 28, 2024
  32. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    Yes.

    It returns a new structure, it does not allocate memory like a class.
     
  33. jGate99

    jGate99

    Joined:
    Oct 22, 2013
    Posts:
    1,963
    Hi @ilih
    Im trying to understand why this error is happening, please guide.
    It works if i instantie a listview then pass the data
    however if its a 2nd listview instantiated and then i pass the data so then i get this error
    please note that both listviews share the same templateSelector and sharedTemplates.

    Code (CSharp):
    1. KeyNotFoundException: The given key '-118394' was not present in the dictionary.
    2. System.Collections.Generic.Dictionary`2[TKey,TValue].get_Item (TKey key) (at <901293c591874400b128ee88bd0b2657>:0)
    3. UIWidgets.ListViewBase+ListViewItemTemplate`1[TItemView].Create (UIWidgets.ListViewBase owner, UIWidgets.InstanceID ownerID) (at Assets/New UI Widgets/Scripts/ListView/ListViewItemTemplate.cs:444)
    4. UIWidgets.ListViewBase+ListViewItemTemplate`1[TItemView].RequestInstances (UIWidgets.ListViewBase owner, UIWidgets.InstanceID ownerID) (at Assets/New UI Widgets/Scripts/ListView/ListViewItemTemplate.cs:354)
    5. UIWidgets.ListViewCustom`2+ListViewComponentPool[TItemView,TItem].PrepareInstances (System.Collections.Generic.List`1[T] indices) (at Assets/New UI Widgets/Scripts/ListView/ListViewComponentPool.cs:311)
    6. UIWidgets.ListViewCustom`2+ListViewComponentPool[TItemView,TItem].DisplayedIndicesSet (System.Collections.Generic.List`1[T] newIndices) (at Assets/New UI Widgets/Scripts/ListView/ListViewComponentPool.cs:268)
    7. UIWidgets.ListViewCustom`2[TItemView,TItem].SetDisplayedIndices (System.Boolean isNewData) (at Assets/New UI Widgets/Scripts/ListView/ListViewCustom.cs:1702)
    8. UIWidgets.ListViewCustom`2[TItemView,TItem].UpdateView () (at Assets/New UI Widgets/Scripts/ListView/ListViewCustom.cs:1777)
    9. UIWidgets.ListViewCustom`2[TItemView,TItem].SetNewItems (UIWidgets.ObservableList`1[T] newItems, System.Boolean updateView) (at Assets/New UI Widgets/Scripts/ListView/ListViewCustom.cs:1828)
    10. UIWidgets.ListViewCustom`2[TItemView,TItem].UpdateItems () (at Assets/New UI Widgets/Scripts/ListView/ListViewCustom.cs:1158)
    11. UnityEngine.Events.InvokableCall.Invoke () (at <9003796c57374fd89cb8aecf8cdea52c>:0)
    12. UnityEngine.Events.UnityEvent.Invoke () (at <9003796c57374fd89cb8aecf8cdea52c>:0)
     
    Last edited: May 2, 2024
  34. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    Sorry for the problem.
    Here is a temporary fix:
    • add a line
      ComponentsPool.Init();
      at the end of
      SetSharedTemplates()
      method in ListViewCustom.cs
    • add the following lines at the start of the
      AddCallbacks()
      method in the ListViewItemTemplate.cs
      Code (CSharp):
      1. if (Callbacks.ContainsKey(ownerID))
      2. {
      3.    return;
      4. }
     
    jGate99 likes this.
  35. jGate99

    jGate99

    Joined:
    Oct 22, 2013
    Posts:
    1,963
    It worked, thanks a lot.
     
  36. ilih

    ilih

    Joined:
    Aug 6, 2013
    Posts:
    1,420
    I was hospitalized last night.
    Sorry, I not be able provide support for some time.
     
    jGate99 and hopeful like this.
  37. hopeful

    hopeful

    Joined:
    Nov 20, 2013
    Posts:
    5,698
    I wish you the best, and a speedy recovery!
     
    jGate99 and ilih like this.
  38. jGate99

    jGate99

    Joined:
    Oct 22, 2013
    Posts:
    1,963
    Best wishes for your quick recovery.
     
    ilih likes this.