1
\$\begingroup\$

What is the proper way of handling synchronization between GUI animations' execution order and the user's dynamic input?

More specifically, I would like to know how to enforce some logical conditioning on the execution's order, while remembering, that the user's input cannot be foreseen.

For instance, a greatly simplified example:

  • 9 rectangles are shown on a screen.
  • A click on a rectangle fades it out.
  • The user clicks whichever rectangle he wants in whatever order he likes and as fast as he feels is right.
  • The condition here breaks into two simple ones:
    • The rectangles must not fade out simultaneously.
    • The rectangles' fade out effect occurs in the same order as their clicks.

I would assume a simple FIFO methodology here where threads would register themselves in some ordered queue and be allowed to enter the critical code only on their turn would be sufficient.


A slightly more complicated scenario:

  • 8 rectangles are shown on a screen - the 9th is an invisible rectangle.
  • A click on a rectangle that is a neighbor of the invisible one, activates a translate animation that swipes between the two.
  • The user clicks whichever rectangle he wants in whatever order he likes and as fast as he feels is right.
  • A "shuffle" button is shown on the screen - shuffles the rectangles in a legal manner, i.e., by randomly moving the rectangles many times.
  • The conditions:
    • The rectangles must not get swiped simultaneously when clicked directly, their animations must be played in the order they were clicked.
    • The rectangles must get swiped simultaneously when the "shuffle" button is clicked. Meaning, the user must only see each rectangle move to its final position.
  • Some constraints:
    • Each rectangle has a Coordinate field that itself contains xCoordinate & yCoordinate.
    • Checking if the clicked rectangle is a neighbor of the invisible one will be done by comparing the rectangles' Coordinates and not their positioning on the screen, i.e., translateX, translateY.
    • If they are neighbors: the rectangles swipe places.
    • Else: the rectangle shows a nudge animation (as in invalid move).
    • Only after the animation is finished, rectangles' Coordinates can be swiped.
    • Each process (containing the needed steps after a click) apart from the animation itself, should be handled in a different thread.

A question: If the Coordinates can be swiped only after the animation is done, meaning each click activates a thread that holds all other threads(clicks that came after) until it is done so it can swipe the coordinates so the next thread's neighbor check is based on correct data, how would it be possible to allow the "shuffle option" without creating an entirely different execution path, i.e., by reusing most of the already written code and leveraging the Java & JavaFX's strengths?

What if we add a constraint that only every second click should be executed?


My point being, these problems can appear in various ways. There must be someone who thought of providing some kind of framework or an abstract solution to this kind of problems. Or is it each case for itself?

\$\endgroup\$
1
\$\begingroup\$

I have no experience with JavaFX, but for what you described, I guess the most natural solution is to have a command queue which is completely independent from the specific animation technology. Let us focus on the a simple case where running animations don't have to be interrupted.

  • The command queue gets its input from the UI thread. It maps the UI actions (like "mouse-click at X,Y") to some high-level commands like "fade-out one rectangle", "shuffle all rectangles", or whatever commands your program requires.

  • Commands are taken out of the queue whenever the previous animation sequence is completed, and then processed in order.

Now, all of the conditions in your example can be handled by simply managing the insertion into the queue correctly, not necessarily in a FIFO manner. If certain actions require a higher priority than others, just adapt the order of insertion.

  • user clicks on a some rectangles in order: new "fade-out rectangle" command is generated and appended to the end of the queue

  • user clicks on "shuffle" button: the swipe command is generated and pushed to the beginning of the queue. The unprocessed "fade-out rectangle" commands can be removed, if that should happen, or they can still kept, if that is the idea.

For starting the animations, you then simply need a command processor which takes the next element out of the queue and starts either an animation sequence (containing one or more basic animation), and using as many threads as required. When the sequence ends, the next command is processed.

An additional constrained like "only every second click should be executed" is probably handled best at the UI level by counting clicks (the command queue does not know anything about "mouse clicks", only high-level commands). Other constraints may require to query the current animation status - which is no information the command queue can provide, so it has to be queried from the "animation controlling part".

In short, the key to keep this manageable is simply separation of concerns: separate clearly

  • the UI event part

  • the command processing part

  • the animation part

then you can easily add more constraints without making the code messy.

\$\endgroup\$
  • \$\begingroup\$ I did just that, there were some complications regarding the implementation of the animations' "OnFinished" handlers. Since each animation is dependant on the previous animation's "OnFinished" code execution. Nevertheless, overall, separation, as you suggested, did give me some more clarity as to what should be controlled from where and I found my way to a very nice & clean code thanks to your advice. Thank you again. \$\endgroup\$ – Julian Broude Jul 10 '19 at 11:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.