Search Unity

DropdownMenu with right click?

Discussion in 'UI Toolkit' started by MartinIsla, Dec 4, 2018.

  1. MartinIsla

    MartinIsla

    Joined:
    Sep 18, 2013
    Posts:
    104
    Hello!
    First of all, I want to say, as a tool developer, I'm *in love* with UIElements. I'm giving it a try in Unity 2019.1.0a10 right now. I'm remaking a fairly huge editor I made last year using IMGUI and the workflow is just better. Not having to recompile scripts over and over again until you get the size you want, performance, modularity (I have a different class for every "element" of the UI instead of a 3000 lines of code script). Everything is great.

    So thank you, guys. Great, great job.

    Now, that said, I want to open a contextual menu when right-clicking a specific area.
    I created a MouseManipulator : Manipulator script. OnMouseDown, I check if evt.button == 1. Debug.Logging tells me that's working perfectly.

    Now I want to create a context menu at mouse position and I couldn't find anything like that in the UIElementsExamples project I downloaded from GitHub. I could use a hand here!

    Thanks!
     
    ModLunar likes this.
  2. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    That's awesome to hear! I'll be curious to see the final result.

    As for the menu, here's how I would do it:

    Code (CSharp):
    1. void OnEnable()
    2. {
    3.     ...
    4.     elementToRightClickOn.RegisterCallback<MouseUpEvent>(HandleRightClick);
    5.     ...
    6. }
    7.  
    8. private void HandleRightClick(MouseUpEvent evt)
    9. {
    10.     if (evt.button != (int)MouseButton.RightMouse)
    11.         return;
    12.  
    13.     var targetElement = evt.target as VisualElement;
    14.     if (targetElement == null)
    15.         return;
    16.  
    17.     var menu = new GenericMenu();
    18.  
    19.     int menuItemValue = 5;
    20.  
    21.     // Add a single menu item
    22.     bool isSelected = true;
    23.     menu.AddItem(new GUIContent("some menu item name"), isSelected,
    24.         value => ChangeValueFromMenu(value),
    25.         menuItemValue);
    26.  
    27.     // Get position of menu on top of target element.
    28.     var menuPosition = new Vector2(targetElement.layout.xMin, targetElement.layout.height);
    29.     menuPosition = this.LocalToWorld(menuPosition);
    30.     var menuRect = new Rect(menuPosition, Vector2.zero);
    31.  
    32.     menu.DropDown(menuRect);
    33. }
    34.  
    35. private void ChangeValueFromMenu(object menuItem)
    36. {
    37.     doSomethingWithValue(menuItem as int);
    38. }
    But your MouseManipulator solution also works for invoking the menu.
     
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    Any chance an improvement to or replacement for GenericMenu will get introduced with UI elements? The ability to style the menu would be nice. The ability to define the type of the MenuFunctions so we don't have to cast the object would also be great.
     
    ModLunar likes this.
  4. MartinIsla

    MartinIsla

    Joined:
    Sep 18, 2013
    Posts:
    104
    Thanks a lot! I'll give this a try. My problem was I didn't know what class should be used for menus, so I just tried "DropdownMenu" which seemed to be the one.
    I'll be back with news!
     
    uDamian likes this.
  5. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    The GenericMenu has not been a focus for the initial release of UIElements and no immediate plans around it right now. It is something that might be addressed as part of the reskin effort in the future.

    That said, it's fairly straightforward to implement your own using a Popup-style EditorWindow and UIElements events. Here's some random snippet of code I found that brings up an custom EditorWindow where the mouse is in another window (the Host window) - might need some tweaking but should work:

    Code (CSharp):
    1. class HostWindow : EditorWindow
    2. {
    3.     void OnEnable()
    4.     {
    5.         rootVisualElement.RegisterCallback<MouseDownEvent>(OnMouseDown);
    6.     }
    7.  
    8.     void OnMouseDown(MouseDownEvent evt)
    9.     {
    10.         if (evt.button == 1) // Right click
    11.             CustomMenuWindow.Show(this, evt.mousePosition);
    12.     }
    13. }
    14.  
    15. class CustomMenuWindow : EditorWindow
    16. {
    17.     public static void Show(
    18.         EditorWindow host,
    19.         Vector2 displayPosition)
    20.     {
    21.         var window = CreateInstance<CustomMenuWindow>();
    22.         var position = GetPosition(host, displayPosition);
    23.         window.position = new Rect(position + host.position.position, s_DefaultSize);
    24.         window.ShowPopup();
    25.         window.Focus();
    26.     }
    27.  
    28.     private static Vector2 GetPosition(EditorWindow host, Vector2 displayPosition)
    29.     {
    30.         var x = displayPosition.x;
    31.         var y = displayPosition.y;
    32.  
    33.         // Searcher overlaps with the right boundary.
    34.         if (x + s_DefaultSize.x >= host.position.size.x)
    35.             x -= s_DefaultSize.x;
    36.  
    37.         // The displayPosition should be in window world space but the
    38.         // EditorWindow.position is actually the GetRootVisualContainer()
    39.         // rectangle, not including the tabs area. So we need to do a
    40.         // small correction here.
    41.         y -= host.GetRootVisualContainer().style.positionTop;
    42.  
    43.         // Searcher overlaps with the bottom boundary.
    44.         if (y + s_DefaultSize.y >= host.position.size.y)
    45.             y -= s_DefaultSize.y;
    46.  
    47.         return new Vector2(x, y);
    48.     }
    49. }
     
  6. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    It's worth adding that for bringing up system contextual menus we do have a Manipulator and Event in place. Here's some example usage:

    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3. using UnityEngine.UIElements;
    4.  
    5. public class TestWindow : EditorWindow
    6. {
    7.     [MenuItem("Example/ContextMenu")]
    8.     static void Bla() {
    9.         GetWindow<TestWindow>();
    10.     }
    11.  
    12.     void OnEnable()
    13.     {
    14.         var button = new Button() { text = "Menu" };
    15.         button.AddManipulator(new ContextualMenuManipulator((evt) =>
    16.         {
    17.             evt.menu.AppendAction("Custom Action 1",
    18.                 (a) => Debug.Log(a.userData as string),
    19.                 (a) => DropdownMenuAction.Status.Normal,
    20.                 "ACTION 1");
    21.             evt.menu.AppendAction("Custom Action 2",
    22.                 (a) => Debug.Log(a.userData as string),
    23.                 (a) => DropdownMenuAction.Status.Normal,
    24.                 "ACTION 2");
    25.         }));
    26.  
    27.         rootVisualElement.Add(button);
    28.     }
    29. }
     
    qcbf1, meaf75, ModLunar and 4 others like this.