ThorPy

A GUI library for pygame

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.make("Drag me")
    sx = thorpy.SliderX.make(length=100, limvals=(0, W), text="X:", type_=int)
    sy = thorpy.SliderX.make(length=100, limvals=(0, H), text="Y:", type_=int)

    background = thorpy.Background.make(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.make("Drag me")
    sx = thorpy.SliderX.make(length=100, limvals=(0, W), text="X:", type_=int)
    sy = thorpy.SliderX.make(length=100, limvals=(0, H), text="Y:", type_=int)

    background = thorpy.Background.make(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:

  1. unblit the element
  2. call func argument (which then must be a function) with **kwargs as arguments
  3. reblit the element and perform all the needed updates
This method is more clear and more efficient. Note also that if you do not need to call a function between the unblit and the reblit, you can use 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.make("Drag me")
    sx = thorpy.SliderX.make(length=100, limvals=(0, W), text="X:", type_=int)
    sy = thorpy.SliderX.make(length=100, limvals=(0, H), text="Y:", type_=int)

    background = thorpy.Background.make(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()