Jump to content

Stop click-through in KSP 1.1?


Recommended Posts

Hi all,

I've been researching ways to prevent click-through, a situation where, when you're doing a GUILayout and the user clicks the mouse button, and there is a context menu open beneath the window, then the context menu also receives the click event. So far I haven't had much luck stopping that from happening. For context, here is an image of what I'm talking about. Notice how the context menu has a button right underneath the Coolant button. If I click the Coolant button, the context menu button also gets pressed. In this case, the button opens a KIS inventory, but imagine if it was something like a Decouple button..

Anyway, So far, I've found a few approaches:

Disabling the UIPartActionWindow: I tried this and get NREs, so I'm not sure it's working in KSP 1.1.

Input lock: This will lock the game controls (landing gear, lights, editor buttons), but won't stop the event from propagating to the context menu.

Event.Use(): The context menu doesn't appear to respect this. I suspect that it has to do with the fact that KSP is using the newer canvas-based UI in Unity.

Short of converting all my GUI code over to the newer canvas-based system, which might not solve the problem, is there another method that I'm not seeing?

Link to comment
Share on other sites

The new UI would solve your problem (and it's worthwhile to use: even optimized, the below creates at least 176 bytes in GC allocations per frame), but another option is to take advantage of the EventSystem the new UI uses by adding a raycaster that "hits" your window first and directs all events to it. Here's a hacky example (real world multi-window mod should probably factor out the raycaster code into some kind of manager and enable/disable the mouse controller a bit more intelligently to play nicely with anybody else doing the same)

[KSPAddon(KSPAddon.Startup.Flight, false)]
class ClickthroughExample : BaseRaycaster
{
    // normal UI stuff
    private Rect _window = new Rect(0f, 0f, 300f, 300f);

    // these two save a bit on garbage created in OnGUI
    private readonly GUILayoutOption[] _emptyOptions = new GUILayoutOption[0];
    private GUI.WindowFunction _windowFunction;

    // enable/disable this to prevent the "No Target" from popping up by double-clicking on the window 
    private Mouse _mouseController; 

    protected override void Awake()
    {
        _windowFunction = DrawWindow;
        _mouseController = HighLogic.fetch.GetComponent<Mouse>();

        _window.center = new Vector2(Screen.width * 0.5f, Screen.height * 0.5f);
        name = "ClickthroughExample";
    }

    private void OnDestroy()
    {
        if (_mouseController) _mouseController.enabled = true;
    }

    // 88 bytes GCAlloc per call, 2x per frame + 1 for each event
    private void OnGUI()
    {
        _window = KSPUtil.ClampRectToScreen(GUILayout.Window(GetInstanceID(), _window, _windowFunction, "Clickthrough Example", HighLogic.Skin.window, _emptyOptions));
    }

    private void DrawWindow(int winid)
    {
        GUILayout.Label("Drag this window around and see if you can click through on anything", _emptyOptions);
        GUI.DragWindow();
    }

    public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
    {
        var mouse = Input.mousePosition;
        var screenPos = new Vector2(mouse.x, Screen.height - mouse.y);

        if (!_window.Contains(screenPos))
        {
            _mouseController.enabled = true;
            return;
        }

        _mouseController.enabled = false;
        Mouse.Left.ClearMouseState();
        Mouse.Middle.ClearMouseState();
        Mouse.Right.ClearMouseState();

        resultAppendList.Add(new RaycastResult
        {
            depth = 0,
            distance = 0f,
            gameObject = gameObject,
            module = this,
            sortingLayer = MainCanvasUtil.MainCanvas.sortingLayerID,
            screenPosition = screenPos
        });
    }

    public override Camera eventCamera
    {
        get { return null; }
    }

    public override int sortOrderPriority
    {
        get { return MainCanvasUtil.MainCanvas.sortingOrder - 1; }
    }
}

 

Link to comment
Share on other sites

1 hour ago, xEvilReeperx said:

The new UI would solve your problem (and it's worthwhile to use: even optimized, the below creates at least 176 bytes in GC allocations per frame), but another option is to take advantage of the EventSystem the new UI uses by adding a raycaster that "hits" your window first and directs all events to it. Here's a hacky example (real world multi-window mod should probably factor out the raycaster code into some kind of manager and enable/disable the mouse controller a bit more intelligently to play nicely with anybody else doing the same)


_window = KSPUtil.ClampRectToScreen(GUILayout.Window(GetInstanceID(), _window, _windowFunction, "Clickthrough Example", HighLogic.Skin.window, _emptyOptions));

 

Edit : err. no. Ignore me while I hide in the corner :D

Edit 2 : try to replace "Clickthrough Example" with a static instance of  "new GUIContent( "Clickthrough Example" )", like the empty array

Edited by sarbian
Link to comment
Share on other sites

HighLogic.Skin.window is a GUIStyle, not a GUILayoutOption. This is the overload I'm using:

// UnityEngine.GUILayout
public static Rect Window(int id, Rect screenRect, GUI.WindowFunction func, string text, GUIStyle style, params GUILayoutOption[] options)

Edit to respond to edit: still isn't an issue :wink: That overload uses a temp GUIContent so it isn't allocating

 

Edited by xEvilReeperx
Link to comment
Share on other sites

17 hours ago, xEvilReeperx said:

The new UI would solve your problem (and it's worthwhile to use: even optimized, the below creates at least 176 bytes in GC allocations per frame)

Is there a good tutorial on setting up the new UI? Everything I've seen seems to require I build an entire GUI system from scratch in code, or else figure out the unity editor. I would much prefer to do it in code like now, the immediate mode GUI makes it very easy to set up basic windows...

Link to comment
Share on other sites

4 hours ago, westamastaflash said:

I would much prefer to do it in code like now, the immediate mode GUI makes it very easy to set up basic windows...

In my experience, it isn't worthwhile to set up the new GUI through pure code. It's possible for the simplest of UIs but you'll be much better off working inside the Unity editor in the vast majority of cases. Tutorial-wise, most of the standard UI tutorials apply and there's example code to load from AssetBundles lying around here somewhere

Link to comment
Share on other sites

I've been using that old part action window click-through workaround in 1.1.x and it seems to be fine.

But looking at the code again it seems like a horrendously inefficient method. That's a FindObjectsOfType and two Linq statements for every GUI frame when the mouse is over your window. I suppose if this is an infrequently used window that might be ok, but for anything that is meant to be open a lot it seems very bad.

UIPartActionController would seem to be the best option for looking for open part action windows. Unfortunately the simple list of windows looks like it's private, so the only way to get the active windows is by searching through for every part.

UIPartActionWindow window = UIPartActionController.Instance.GetItem(part, symmetry bool);

I'm not sure that running through each part is faster than the FindObjectsOfType method, it probably depends on how the UIPartActionController method works and how many parts you have. And you would have to run through the parts for all unpacked vessels to get accurate results...

Making a public version of that internal window list would really help.

Link to comment
Share on other sites

43 minutes ago, DMagic said:

UIPartActionController would seem to be the best option for looking for open part action windows. Unfortunately the simple list of windows looks like it's private, so the only way to get the active windows is by searching through for every part.

The window prefab is exposed so you can just add a tracking component that keeps them in a list to avoid FindObjectsOfType or any iterating. I agree that just making the list public would be handy though

Link to comment
Share on other sites

  • 1 month later...
This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...