tricky_events.pyΒΆ

 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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
"""
Tricky cases for an event system.
"""

from flexx import app, event, ui


## Synced sliders

class SyncedSlidersBase(ui.Widget):

    def init(self):

        with ui.VBox():
            ui.Label(text=self.TITLE)
            self.slider1 = ui.Slider()
            self.slider2 = ui.Slider()

            ui.Widget(flex=1)

    @event.reaction('slider1.value', 'slider2.value')
    def sleep_when_slider_changes(self, *events):
        global time  # time() is a PScript builtin
        for ev in events:
            etime = time() + 0.05
            while time() < etime:
                pass


class SyncedSlidersWrong(SyncedSlidersBase):
    """ This example syncs two sliders by implementing one reaction for each
    slider. This is probably the worst way to do it; if there is some delay
    in your app, you quickly get into a situation where the event system has
    two queued actions: one to set slider 1 to value A and another to set
    slider 2 to value B. And these will keep interchanging.
    """

    TITLE = 'Synced sliders, done wrong'

    @event.reaction('slider1.value')
    def __slider1(self, *events):
        self.slider2.set_value(events[-1].new_value)

    @event.reaction('slider2.value')
    def __slider2(self, *events):
        self.slider1.set_value(events[-1].new_value)


class SyncedSlidersRight(SyncedSlidersBase):
    """ This example syncs two sliders in a much better way, making use of a
    single reaction, which is marked as greedy. This ensures that all events
    to either of the sliders get handled in a single call to the reaction,
    which avoids a ping-pong effect. Only having a single (normal) reaction
    reduced the chance of a ping-pong effect, but does not elliminate it.

    Even better would be to react to ``user_value``  or ``user_done``
    to avoid ping-ping altogether.

    A nice addition would be to add an action that sets both slider
    values at the same time.
    """

    TITLE = 'Synced sliders, done right'

    @event.reaction('slider1.value', 'slider2.value', mode='greedy')
    def __slider1(self, *events):
        value = events[-1].new_value
        self.slider1.set_value(value)
        self.slider2.set_value(value)


## Main

class Tricky(ui.Widget):
    """ A collection of tricky cases.
    """

    def init(self):
        with ui.VBox():

            self.reset = ui.Button(text='Reset event system')
            with ui.HFix(flex=1):
                SyncedSlidersWrong(flex=1)
                SyncedSlidersRight(flex=1)

            ui.Widget(flex=1)  # spacer

    @event.reaction('reset.pointer_click')
    def _reset(self):
        # You probably don't want to ever do this in a normal app.
        # Do via a timeout because reactions get handled by the event system,
        # so the reset will not work correctly.
        global window
        window.setTimeout(event.loop.reset, 0)


m = app.launch(Tricky, 'app')
app.run()