Order of (some) GUI events

A big new update is now available, introducing biomes, caves and much more!
Latest hotfix: 0.7.0.3 (2024-02-21)
  • Scenario: a Panel (simulating a text entry dialogue box) with:

    • a GuiTextField into which to enter a value
    • a GuiLabel with the "OK" text and a suitable background colour (simulating an "OK" button)

    Operation:

    • The user clicks on the GuiTextField and enter some text
    • with the cursor in the GuiTextField, the user clicks the "OK" button

    these are the generated event for 2. (in this order):

    • PlayerGuiElementClickEvent for the click on the "OK" button
    • PlayerGuiInputEvent for the text input loosing focus.

    Managing event 1 usually entails closing the "dialogue box", which results in event 2 not being processed at all and the entered text to be lost (in the best case, if the relevant listener has been removed; in the worst case, it might generate an exception as the "dialogue box" pieces no longer exist).


    Would it be possible to have the PlayerGuiInputEvent before the PlayerGuiElementClickEvent? This also once that "real" GUI buttons are added to the API?


    (So far, I am working around this by requiring two clicks in a row on the "OK" button, but it cannot be a definitive solution.)

  • why use the PlayerGuiInputEvent then?


    let me explain:


    1) make the GuiTextField setListenForInput(false);
    2) then in the PlayerGuiElementClickEvent have a line like GuiTextField myTextField = (GuiTextField) player.getAttribute("myTextField"); or ofc in any other way you identify your Elements, I think you have said you use a unique ID number?
    3) then simply use String myText = myTextField.getCurrentText(blablabla);
    4) do whatever you want to do with the String
    5) close the GuiPanel


    this way you both get your text, process it and do whatever you want to do with it and close the panel both with only one event :)


    Would it be possible to have the PlayerGuiInputEvent before the PlayerGuiElementClickEvent? This also once that "real" GUI buttons are added to the API?

    In my opinion this is not a real solution as there could be a case someone would want them to trigger the other way around. And since there is a simple in my opinion solution to the problem as written above I don't believe any change to the API is required. But as always this is up to red to decide :)

  • 3) then simply use String myText = myTextField.getCurrentText(blablabla);
    4) do whatever you want to do with the String
    5) close the GuiPanel

    It is not so simple: GuiTextField.getCurrentText(Player player, Callback<java.lang.String> callback) takes a callback as an argument and I cannot close the [GuiPanel[/tt] and its children until the callback is called and the returned text string processed (in the callback, not in the code for the dialogue box); for just one GuiTextField it is somewhat devious but still manageable.


    But in the "Edit Area Extent" dialogue box I have 6 GuiTextField's: coordinating 6 callbacks results in incomprehensible, unmaintainable code.


    I still believe the simpler solution to be by far an exchange of the event order. About your point:

    there could be a case someone would want them to trigger the other way around

    it is possible, but I have described a very real and relatively frequent case where the current event order is "non-preferred". Can you describe a use case where, when the focus moves from element A to element B, it would be preferred to have the event for element B (the focus destination element) notified before the event for element A (the source element)?

  • I still believe you are making things much more complicated than they have to be.


    If the callbacks are called and processed in your EventMethod for the GuiElementClickEvent before the same method closes the panel and its children then there should be no problem at all. I don't really understand why you think that would make the code unmanageable.


    other options I can think of right now (the second is the best imo):

    • add a 1-2 second timer (should be enough time for the other event to execute properly) before the Ok button's actions happen and the panel closes (i.e. put the whole contents of the method in a timer then start the timer in the method)
    • make the Ok button save the information but not close the panel and have a second "x" button to close it, this way if I pressed Ok with a wrong value I have the chance to notice it and correct it, press Ok again and then close the panel myself when I am sure I am done
    • tell the users to click somewhere else or press Return while in the text field before clicking the Ok button

    it is possible, but I have described a very real and relatively frequent case where the current event order is "non-preferred". Can you describe a use case where, when the focus moves from element A to element B, it would be preferred to have the event for element B (the focus destination element) notified before the event for element A (the source element)?

    I can't really think of a case right now as I haven't used that kind of thing before (I just put a save button to save the content of my 20 or so textfields and then close the panel with an "x" button on the top right as in Windows (Linux sometimes can have it on the top left instead)).


    But my point was more a general one, shifting the problem is not really a solution.


    PS: anyway now that I thought of it a bit more, the 2 events should trigger at the same time not one before the other anyway. As in when I click somewhere else the GuiInputEvent is executed and when I click on a clickable label the GuiElementClickEvent is executed so by clicking the label right after being in the text field should cause both events to execute simultaneously just like when I hit with a pickaxe while moving both the DestroyTerrain and the PlayerChangePosition Events should trigger at the same time not in some order.

  • If the callbacks are called and processed in your EventMethod for the GuiElementClickEvent before the same method closes the panel

    No, they are not; callbacks are asynchronous by definition.


    In the case of GuiTextField.getCurrentText(), it queries the client for the text and returns immediately; when the client reply comes with the required text, the callback is called and this may happen (in particular bad cases) seconds after the GuiElementClickEvent handler method is done.


    In any case, there is no guaranty that this will happen within any specific time frame, including before the click handler is done.


    About your other suggestions, I agree that 2. (..."make the Ok button save the information but not close"...) would be the cleanest and clearest.


    Unfortunately I do not see how it could work, as the "Ok" button cannot save anything (which has not been saved before); it may only ask for the text data which, in general, may arrive after the user has pressed the [X] button, the Panel has been closed and the data become lost anyway (I agree it would be improbable, but in principle possible and therefore this way of working is breakable).


    As it seems to me you are using a procedure like this yourself, possibly this relies on the high probability that the needed data have arrived or the needed PlayerGuiInputEvent has been triggered by the time the user presses the 'other' button ([X] or whatever). However, the whole of this is not deterministic (as network running is not deterministic to start with) and leaves room for any kind of problems.


    To see that I am speaking of something real, I think it has happened to you too that you are digging and, for an unfortunate latency spike, you see the result of your digging to appear a few seconds after the pickaxe hit. If the same latency spike happens between the click event and the callback being called, the panel is likely to have been closed in the meantime, the text data would be lost and even exceptions may result, if the Panel & children have been cleaned up in the meantime.

    the 2 events should trigger at the same time

    Two events cannot be triggered at the time: one of them will necessarily be triggered before the other. It may be casual which one, but one shall be. However, at least for rather homogeneous cases like this one -- in which all events refer to GUI and in particular to children of the same GUI element and are generated by single user action--, it would be useful if order of events is documented (and swapped, as I was asking before ;) ).


    EDIT: I am rather sure that, in other GUI frameworks, in a case like this where one control looses focus and another gains it with a single click; the event for the loosing focus control is generated before the event for the focus gaining control; also because this is what makes more sense in a general way.

  • EDIT: I am rather sure that, in other GUI frameworks, in a case like this where one control looses focus and another gains it with a single click; the event for the loosing focus control is generated before the event for the focus gaining control; also because this is what makes more sense in a general way.

    this makes sense, the losing focus element should trigger before the gaining focus one.

    As it seems to me you are using a procedure like this yourself, possibly this relies on the high probability that the needed data have arrived or the needed PlayerGuiInputEvent has been triggered by the time the user presses the 'other' button ([X] or whatever). However, the whole of this is not deterministic (as network running is not deterministic to start with) and leaves room for any kind of problems.

    In fact what I have done is that my text input events to save the entered text as the default text and then the "Ok" button to save the text from all fields to the database and ofc internally in the plugin so they take immediate effect without the need for a server restart.


    In fact the swap you are recommending would benefit me as well in case the user clicks the "Ok" button before clicking somewhere else for the input event to be triggered first. I am just "arguing" to make sure the change if it happens is one that most people will prefer :)

  • Well, basically this problem is caused by the way how the click-event is handled: currently it's fired when the mouse button is pressed. At this stage, the old element is still focused, so the "lose focus" and "gain focus" events are only called after the mouse button event (and since the input event depends on the "lose focus" event, it is called after the onClick() event).
    Changing this requires a lot of work unfortunately. However, actually it's not intended to fire the click-event when the mouse button is pressed, instead it's supposed to be fired when the mouse button is released (this event is always called after the "lose focus" event). The game buttons etc also listen for the mouse release (instead the mouse press) event btw, since it's more convenient (actually several other gui applications out there do the same thing).


    tl;dr we will change the click event with the next update so it will only be fired when the mouse button is released, this will solve this problem :)

  • Well, basically this problem is caused by the way how the click-event is handled: currently it's fired when the mouse button is pressed. At this stage, the old element is still focused, so the "lose focus" and "gain focus" events are only called after the mouse button event (and since the input event depends on the "lose focus" event, it is called after the onClick() event).
    Changing this requires a lot of work unfortunately. However, actually it's not intended to fire the click-event when the mouse button is pressed, instead it's supposed to be fired when the mouse button is released (this event is always called after the "lose focus" event). The game buttons etc also listen for the mouse release (instead the mouse press) event btw, since it's more convenient (actually several other gui applications out there do the same thing).


    tl;dr we will change the click event with the next update so it will only be fired when the mouse button is released, this will solve this problem :)

    Will this mean that like in other GUIs, if I click on a label but decide I don't want to click it I can keep holding the mouse, move it outside the label and then release it? This way the ClickEvent will not be triggered correct?

Participate now!

Don’t have an account yet? Create a new account now and be part of our community!