Search Unity

UIElements runtime popup example?

Discussion in 'UI Toolkit' started by khakola, Feb 13, 2020.

  1. khakola

    khakola

    Joined:
    Apr 13, 2019
    Posts:
    4
    Hi

    I've been trying to find an example of popup window implementation in UIElements runtime but haven't yet found any. Where could I find an example or could someone provide it here?

    Regards,
     
  2. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Are you looking for something like an modal error popup that blocks the entire screen and demands a choice is made? Or something like a PopupField or right-click menu?
     
  3. khakola

    khakola

    Joined:
    Apr 13, 2019
    Posts:
    4
    I was looking a modal popup that blocks the entire screen. I ended up doing the following helper method:
    Code (CSharp):
    1. public static void ShowPopup(VisualElement rootElementForPopup,
    2.         VisualElement popupContent, Button popupCloseButton,
    3.         Action callbackAfterPopupIsClosed,
    4.         float widthInPercents = 75.0f, float heightInPercents = 75.0f)
    5.     {
    6.         if (widthInPercents <= 0f || widthInPercents >= 100f)
    7.         {
    8.             throw new ArgumentException($"Width should be in the range 0 < width < 100.", "widthInPercents");
    9.         }
    10.  
    11.         if (heightInPercents <= 0f || heightInPercents >= 100f)
    12.         {
    13.             throw new ArgumentException($"Height should be in the range 0 < height < 100.", "heightInPercents");
    14.         }
    15.  
    16.         //Create visual element for popup
    17.         var popupElement = new VisualElement();
    18.         popupElement.style.position = new StyleEnum<Position>(Position.Absolute);
    19.         popupElement.style.top = 0;
    20.         popupElement.style.left = 0;
    21.         popupElement.style.flexGrow = new StyleFloat(1);
    22.         popupElement.style.height = new StyleLength(new Length(100, LengthUnit.Percent));
    23.         popupElement.style.width = new StyleLength(new Length(100, LengthUnit.Percent));
    24.  
    25.         //Popup background is button so that the popup is closed when the player
    26.         //clicks anywhere outside the popup.
    27.         var backgroundButton = new Button();
    28.         backgroundButton.style.position = new StyleEnum<Position>(Position.Absolute);
    29.         backgroundButton.style.top = 0;
    30.         backgroundButton.style.left = 0;
    31.         backgroundButton.style.flexGrow = new StyleFloat(1);
    32.         backgroundButton.style.height = new StyleLength(new Length(100, LengthUnit.Percent));
    33.         backgroundButton.style.width = new StyleLength(new Length(100, LengthUnit.Percent));
    34.         backgroundButton.style.opacity = new StyleFloat(0.4f);
    35.         backgroundButton.style.backgroundColor = new StyleColor(Color.black);
    36.         backgroundButton.text = string.Empty;
    37.  
    38.         backgroundButton.clickable.clicked += () =>
    39.         {
    40.             PopupClose(rootElementForPopup, popupElement, callbackAfterPopupIsClosed);
    41.         };
    42.         popupElement.Add(backgroundButton);
    43.  
    44.         if (popupCloseButton != null)
    45.         {
    46.             popupCloseButton.clickable.clicked += () =>
    47.             {
    48.                 PopupClose(rootElementForPopup, popupElement, callbackAfterPopupIsClosed);
    49.             };
    50.         }
    51.  
    52.         //Set content size
    53.         popupContent.style.width = new StyleLength(new Length(widthInPercents, LengthUnit.Percent));
    54.         popupContent.style.height = new StyleLength(new Length(heightInPercents, LengthUnit.Percent));
    55.  
    56.         //Show popupContent in the middle of the screen
    57.         popupContent.style.position = new StyleEnum<Position>(Position.Absolute);
    58.  
    59.         float topAndBottom = (100f - heightInPercents) / 2f;
    60.         popupContent.style.top = new StyleLength(new Length(topAndBottom, LengthUnit.Percent));
    61.         popupContent.style.bottom = new StyleLength(new Length(topAndBottom, LengthUnit.Percent));
    62.  
    63.         float leftAndRight = (100f - widthInPercents) / 2f;
    64.         popupContent.style.left = new StyleLength(new Length(leftAndRight, LengthUnit.Percent));
    65.         popupContent.style.right = new StyleLength(new Length(leftAndRight, LengthUnit.Percent));
    66.  
    67.         popupElement.Add(popupContent);
    68.  
    69.         rootElementForPopup.Add(popupElement);
    70.     }
    71.  
    72.     private static void PopupClose(VisualElement popupRoot, VisualElement popup,
    73.         Action callbackAfterPopupIsClosed)
    74.     {
    75.         popupRoot.Remove(popup);
    76.  
    77.         callbackAfterPopupIsClosed?.Invoke();
    78.     }
     
  4. stan-osipov

    stan-osipov

    Unity Technologies

    Joined:
    Feb 24, 2020
    Posts:
    31
    The approach is correct. Just few notes how to improve.
    Use VisualElement instead of the Button
    Code (CSharp):
    1. background.RegisterCallback<PointerDownEvent>(e =>
    2. {
    3.   e.StopImmediatePropagation();
    4.   PopupClose()...
    5. });
    You can skip setting default values.
    Code (CSharp):
    1. backgroundButton.style.top = 0;
    2. backgroundButton.style.left = 0;
    Since you are using Position.Absolute, you can remove this line
    Code (CSharp):
    1. backgroundButton.style.flexGrow = new StyleFloat(1);
     
    CodeSmile likes this.
  5. khakola

    khakola

    Joined:
    Apr 13, 2019
    Posts:
    4
    Thanks for the tips. I noticed that PointerDownEvent is not fired in the editor when clicking the background so I added also MouseDownEvent in order to make testing easier in the editor.
     
    gareth_untether likes this.
  6. oobartez

    oobartez

    Joined:
    Oct 12, 2016
    Posts:
    167
    @stan-osipov Your example only blocks mouse events. How would I go about stealing keyboard events and focusing elements outside the popup?
     
  7. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    On the top of my head, you'll need to background focusable, call background.Focus();

    Alternatively, if you want to eat all keyboard events going through that window (even in your popup children), you could RegisterCallback<KeyDownEvent>() (and KeyUpEvent) on the rootElement, in the trickleDown phase and call StopPropagation() + PreventDefault() when you get the event callback
     
  8. oobartez

    oobartez

    Joined:
    Oct 12, 2016
    Posts:
    167
    @uMathieu Sorry that was a badly constructed sentence :D What I meant was, stan-osipov's example blocks mouse events but we also have to prevent everything outside of the popup window from receiving focus and/or receiving keyboard events (essentially a standard modal window). How can we do that?
     
  9. oobartez

    oobartez

    Joined:
    Oct 12, 2016
    Posts:
    167
    So the best way to describe the problem is: how can I prevent any element outside of the popup from accepting input events or getting focus?
     
  10. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    This ability to block events at runtime is not there yet. uGui canvases or the scene will still receive events. Your popup will only block events from being received by other UIToolkit elements or panels.

    A solution is in the works and will be provided in the 1.0 release.
     
  11. oobartez

    oobartez

    Joined:
    Oct 12, 2016
    Posts:
    167
    @uMathieu That is fine, we do not need to block UGUI canvases. What I'm asking is how can I prevent UIToolkit elements outside of the popup from accepting input events or getting focus?
     
  12. pawelduda

    pawelduda

    Joined:
    Feb 1, 2019
    Posts:
    45
    But it looks like it blocks only mouse input. If I use Tab key on keyboard, it navigates through all focusable elements, even ones behind this popup, which are on different UiDocument entirely.
     
    Aristonaut likes this.
  13. Digika

    Digika

    Joined:
    Jan 7, 2018
    Posts:
    225
    The fact UIToolkit requires so much code for a simple pop=up concerns me where it is going...
     
    burningmime likes this.
  14. Mrbeardy

    Mrbeardy

    Joined:
    Jun 30, 2014
    Posts:
    13
    That example isn't really fair to use as a comparison though, as it's building up the entire UI through code. You'd have just as much code (if not more) if you were using UGUI and building up GameObjects.

    Ideally you'd have all of that markup within UXML and USS documents, and the code would be significantly smaller.

    To load the UXML and USS at runtime, you can pass your UXML file into a VisualAssetTree field on a MonoBehaviour, and you can then call `CloneTree()` on that field and you'd end up with a VisualElement of the document, which you can then add into the rootVisualElement of a UIDocument.

    I just did this in 2020.2.1f1 and it worked in both the editor and in a build.
     
  15. Digika

    Digika

    Joined:
    Jan 7, 2018
    Posts:
    225
    The example is perfectly fair, I was talking about building element from code.
     
  16. Mrbeardy

    Mrbeardy

    Joined:
    Jun 30, 2014
    Posts:
    13
    In that case, I'd like to see you write a shorter, purely code based UGUI implementation than the example you're comparing it to, only then will it be fair to say UI Toolkit is too verbose.
     
    Last edited: Feb 26, 2021
  17. Digika

    Digika

    Joined:
    Jan 7, 2018
    Posts:
    225
    I was mostly comparing to IMGUI.
    UGUI is GO based, it is irrelevant when we talk about code-generated UI
     
  18. Mrbeardy

    Mrbeardy

    Joined:
    Jun 30, 2014
    Posts:
    13
    Ah fair enough, that's reasonable, I was thinking about runtime systems.

    Still though, generating the UI through code isn't really the most efficient way to use UI Toolkit, pairing UXML and backend code for event handling is the ideal approach.
     
  19. oltranzista

    oltranzista

    Joined:
    Jul 21, 2015
    Posts:
    5
    I'm using this approach. It's using more of the declarative features of UI Toolkit.

    SampleScene.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UIElements;
    3.  
    4. public class SampleScene : MonoBehaviour
    5. {
    6.     public VisualElement Modal1;
    7.  
    8.     void OnEnable()
    9.     {
    10.         var root = GetComponent<UIDocument>().rootVisualElement;
    11.         Modal1 = root.Q<VisualElement>("Modal1");
    12.         var openModalButton = root.Q<Button>("OpenModalButton");
    13.         var closeModalButton = root.Q<Button>("CloseModalButton");
    14.         openModalButton.RegisterCallback<ClickEvent>(e => OnOpenModal());
    15.         closeModalButton.RegisterCallback<ClickEvent>(e => OnCloseModal());
    16.         Modal1.RegisterCallback<PointerDownEvent>((e) =>
    17.         {
    18.             e.StopImmediatePropagation();
    19.             CloseModal();
    20.         });
    21.     }
    22.  
    23.     private void OnOpenModal()
    24.     {
    25.         Modal1.style.display = DisplayStyle.Flex;
    26.     }
    27.  
    28.     private void OnCloseModal()
    29.     {
    30.         CloseModal();
    31.     }
    32.  
    33.     private void CloseModal()
    34.     {
    35.         Modal1.style.display = DisplayStyle.None;
    36.     }
    37. }
    38.  
    SampleScene.uxml
    Code (CSharp):
    1. <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
    2.   <Style src="Styles.uss" />
    3.   <ui:VisualElement name="Body" class="body">
    4.     <ui:Button text="Open Modal" display-tooltip-when-elided="true" name="OpenModalButton" />
    5.   </ui:VisualElement>
    6.   <ui:VisualElement name="Modal1" class="modal">
    7.     <ui:VisualElement name="ModalContent1" class="modal-content">
    8.       <ui:Label text="Label" display-tooltip-when-elided="true" />
    9.       <ui:Button text="Close Modal" name="CloseModalButton" display-tooltip-when-elided="true" />
    10.     </ui:VisualElement>
    11.   </ui:VisualElement>
    12. </ui:UXML>
    Styles.uss
    Code (CSharp):
    1. .body {
    2.     width: 100%;
    3.     height: 100%;
    4.     background-color: lightblue;
    5. }
    6.  
    7. .modal {
    8.     align-items: center;
    9.     justify-content: center;
    10.     width: 100%;
    11.     height: 100%;
    12.     display: none;
    13.     position: absolute;
    14.     background-color: rgba(0, 0, 0, 0.4);
    15. }
    16.  
    17. .modal-content {
    18.     width: 75%;
    19.     height: 75%;
    20.     background-color: lightgreen;
    21. }
    22.  
     
    Eastrall and divone- like this.
  20. Xelnath

    Xelnath

    Joined:
    Jan 31, 2015
    Posts:
    402
    For people looking for the right click style, here's a code snippet that works quickly!

    Code (CSharp):
    1.  
    2.         yourElement.AddManipulator(new ContextualMenuManipulator((ContextualMenuPopulateEvent evt) =>
    3.         {
    4.             UnityEngine.Debug.Log("right click");
    5.             evt.menu.AppendAction("Action A", (x) => { });
    6.         }));
    7.