Tutorials - Coupling elements
In real life, GUI is only a mean to achieve something else which is the actual point of interest. In this tutorial, we show how to write precise reactions so that elements have a real impact on other things than themselves.
Let us decide an objective that will serve as example : we will simply have a window in which live a draggable element and two sliders. The sliders and the draggable element are not independents : the slider's values must always match the draggable's topleft coordinate. Thus, when draggable is moved, sliders have to adapt automatically - and when sliders are moved, the draggable has to adapt too.
It is strongly recommended that you read the tutorial on reactions before starting this tutorial.
Detailed tutorial
First we create the elements we need to check if all is okay. You should be familiar with elements creation, there is nothing special here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #ThorPy real life tutorial : step 1
import thorpy
def run_application():
W, H = 300, 300
application = thorpy.Application(size=(W,H), caption="Real life example")
draggable = thorpy.Draggable("Drag me")
sx = thorpy.SliderX(length=100, limvals=(0, W), text="X:", type_=int)
sy = thorpy.SliderX(length=100, limvals=(0, H), text="Y:", type_=int)
background = thorpy.Background(color=(200,255,255),
elements=[draggable, sx, sy])
thorpy.store(background, [sx, sy])
menu = thorpy.Menu(background) #create a menu for auto events handling
menu.play()
application.quit()
run_application()
|
Now, let's work harder. We need two reactions : one that happens when a slider is moved, and another when the draggable element is moved. We dice to define two functions : refresh_sliders
and refresh_drag
. The first one will be called when the draggable moves and the second one will be called when one of the sliders move.
In this code, no other element is created (except the background), and when a THORPY_EVENT
with id EVENT_SLIDE
appears, we are sure that it comes from one of the sliders. Thus, when reacting to this event, the function refresh_drag
doesn't need to verify from which element the event exactly comes from. However, if another slider (a scroll lift for example) is added to the application, this could result in a bug, so let's code safely : each reaction function will verify, through the if event.el == ...
statement, if the event comes from the right element. For that reason, we use Reaction
instances and not ConstantReaction
instances to create the reactions on the code below at lines 44 and 50. As said in the tutorial on reactions, functions linked to a Reaction
instance must take event
as first argument. This is the case as you can see on lines 4 and 18. Moreover, three more arguments concerning the draggable element and the two sliders are given to these reaction functions.
The code in refresh_sliders
can be understood as follow : if the element from which the EVENT_DRAG
event comes is the correct one, then we enter the function. First we get the topleft coordinates of the draggable element (line 6). Then, we unblit and update the draggers of each slider (lines 7-10), set the new value for the sliders (lines 11-12), and finally we blit and update the draggers of each slider (lines 13-16). We will see at the end how all these lines can be much simplified. Note that we could also unblit and reblit the whole sliders, but since only their dragger moves, it is smarter to unblit and reblit only this tiny part. The code in refresh_drag
is simply a mirror of what we saw above.
The last thing is to tell the application to call the right functions at the right moment. For this we define two reactions (lines 44 and 55) and add them to the background (lines 56 and 57). Please read the tutorial on reactions is you do not feel cumfortable with reactions.
Here we are : the code is working, we did the setup of a two-ways coupling between elements of different nature. However, it can be still improved and made more clear, as shown at the end of this tutorial.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | #ThorPy real life tutorial : step 2
import thorpy
def refresh_sliders(event, drag, sx, sy):
if event.el == drag:
pos_drag = drag.get_rect().topleft
sx.get_dragger().unblit()
sx.get_dragger().update()
sy.get_dragger().unblit()
sy.get_dragger().update()
sx.set_value(pos_drag[0])
sy.set_value(pos_drag[1])
sx.get_dragger().blit()
sx.get_dragger().update()
sy.get_dragger().blit()
sy.get_dragger().update()
def refresh_drag(event, drag, sx, sy):
if event.el == sx or event.el == sy:
drag.unblit()
drag.update()
drag.set_topleft((sx.get_value(), sy.get_value()))
drag.blit()
drag.update()
def run_application():
W, H = 300, 300
application = thorpy.Application(size=(W,H), caption="Real life example")
draggable = thorpy.Draggable("Drag me")
sx = thorpy.SliderX(length=100, limvals=(0, W), text="X:", type_=int)
sy = thorpy.SliderX(length=100, limvals=(0, H), text="Y:", type_=int)
background = thorpy.Background(color=(200,255,255),
elements=[draggable, sx, sy])
thorpy.store(background, [sx, sy])
reaction1 = thorpy.Reaction(reacts_to=thorpy.constants.THORPY_EVENT,
reac_func=refresh_drag,
event_args={"id":thorpy.constants.EVENT_SLIDE},
params={"drag":draggable, "sx":sx, "sy":sy},
reac_name="my reaction to slide event")
reaction2 = thorpy.Reaction(reacts_to=thorpy.constants.THORPY_EVENT,
reac_func=refresh_sliders,
event_args={"id":thorpy.constants.EVENT_DRAG},
params={"drag":draggable, "sx":sx, "sy":sy},
reac_name="my reaction to drag event")
background.add_reaction(reaction1)
background.add_reaction(reaction2)
menu = thorpy.Menu(background) #create a menu for auto events handling
menu.play() #launch the menu
application.quit()
run_application()
|
Improved full code
Let's improve the code. Functions refresh_sliders
and refresh_drag
are quite redundant, and can be shortened with the use of unblit_and_reblit_func(func, **kwargs)
method of Element
instances. This method tells ThorPy to do the following:
- unblit the element
- call
func
argument (which then must be a function) with**kwargs
as arguments - reblit the element and perform all the needed updates
unblit_and_reblit()
method instead of unblit_and_reblit_func
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | #ThorPy real life tutorial : full final code
import thorpy
def refresh_sliders(event, drag, sx, sy):
if event.el == drag:
pos_drag = drag.get_rect().topleft
sx.unblit_and_reblit_func(sx.set_value, value=pos_drag[0])
sy.unblit_and_reblit_func(sy.set_value, value=pos_drag[1])
def refresh_drag(event, drag, sx, sy):
if event.el == sx or event.el == sy:
drag.unblit_and_reblit_func(drag.set_topleft,
pos=(sx.get_value(), sy.get_value()))
def run_application():
W, H = 300, 300
application = thorpy.Application(size=(W,H), caption="Real life example")
draggable = thorpy.Draggable("Drag me")
sx = thorpy.SliderX(length=100, limvals=(0, W), text="X:", type_=int)
sy = thorpy.SliderX(length=100, limvals=(0, H), text="Y:", type_=int)
background = thorpy.Background(color=(200,255,255),
elements=[draggable, sx, sy])
thorpy.store(background, [sx, sy])
reaction1 = thorpy.Reaction(reacts_to=thorpy.constants.THORPY_EVENT,
reac_func=refresh_drag,
event_args={"id":thorpy.constants.EVENT_SLIDE},
params={"drag":draggable, "sx":sx, "sy":sy},
reac_name="my reaction to slide event")
reaction2 = thorpy.Reaction(reacts_to=thorpy.constants.THORPY_EVENT,
reac_func=refresh_sliders,
event_args={"id":thorpy.constants.EVENT_DRAG},
params={"drag":draggable, "sx":sx, "sy":sy},
reac_name="my reaction to drag event")
background.add_reaction(reaction1)
background.add_reaction(reaction2)
menu = thorpy.Menu(background) #create a menu for auto events handling
menu.play() #launch the menu
application.quit()
run_application()
|