How to wait in code for user decision/input in GUI (kivy) without blocking the MainThread?

Viewed 378

I want to pause or wait in code until user make change/decision (press ToggleButton) in Gui I found library asynckivy, but I don't know how to implement it on all buttons simultaneously and if any is pressed, continue. I tried some scheduling through Clock but not working or I did it wrong

Python code

from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import *
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.uix.behaviors import ToggleButtonBehavior


class FightScreen(Screen):
    player_action_pick = StringProperty(rebind=True, allownone=True)

    def __init__(self, **kwargs):
        super(FightScreen, self).__init__(**kwargs)
        self.af_init = Clock.create_trigger(self._init)
        self.player_action_pick = None
        self.af_init()
        self.starting_hit = "Enemy"

    def _init(self, dt):
        self.app = App.get_running_app()

    def fight(self, *args):
        hero_alive = True
        enemy_alive = True

        def enemy_move():

            # something
            return hero_alive

        def player_move():
            # NEED SOLUTION HERE
            # something like wait until any of ToggleButtonBehavior.get_widgets('player_action') is down
            # than reset button state like button.state = 'normal'

            # something
            return enemy_alive

        # while player.health > 0 and enemy.health > 0:
        if self.starting_hit == "Enemy":
            print("Enemy move")
            if not enemy_move():
                print("Player dead")

            elif not player_move():
                print("Enemy dead")
        else:
            print("Player move")

    def on_enter(self, *args):
        self.fight()

    def player_action(self, stat):
        self.player_action_pick = stat


class ScreenManagement(ScreenManager):
    pass


class Design(App):

    def __init__(self, **kwargs):
        super(Design, self).__init__(**kwargs)

    # Construct app
    def build(self):
        # design constructor
        kv = Builder.load_file('AppDesign.kv')
        return kv


if __name__ == "__main__":
    Design().run()

kivy code

ScreenManagement:
    id: screen_manager
    FightScreen:
        name: 'FightScreen'
        manager: screen_manager
        id: fight_screen

<PlayerActionButton@ToggleButton>
    size_hint: .3, .1
    background_color: '#654321'
    text: "Attack!"

<FightScreen>
    FloatLayout:
        GridLayout:
            cols: 1
            rows: 4
            PlayerActionButton:
                group: 'player_action'
                on_state:
                    if self.state == 'down': \
                    root.player_action('programming_stat')
            PlayerActionButton:
                group: 'player_action'
                on_state:
                    if self.state == 'down': \
                    root.player_action('design_stat')
            PlayerActionButton:
                group: 'player_action'
                on_state:
                    if self.state == 'down': \
                    root.player_action('creativity_stat')
            PlayerActionButton:
                group: 'player_action'
                on_state:
                    if self.state == 'down': \
                    root.player_action('heal')
2 Answers

If you use asynckivy, the code would be:

import asynckivy as ak

class FightScreen(Screen):
    async def fight(self, *args):
        hero_alive = True
        enemy_alive = True

        def enemy_move():

            # something
            return hero_alive

        async def player_move():
            buttons = ToggleButtonBehavior.get_widgets('player_action')
            await ak.or_from_iterable(
                ak.event(button, 'state') for button in buttons)
            for button in buttons:
                button.state = 'normal'
            return enemy_alive

        # while player.health > 0 and enemy.health > 0:
        if self.starting_hit == "Enemy":
            print("Enemy move")
            if not enemy_move():
                print("Player dead")

            elif not await player_move():
                print("Enemy dead")
            else:
                print('AAAA')
        else:
            print("Player move")

    def on_enter(self, *args):
        ak.start_soon(self.fight())

Have you tried creating an asyncio.future, awaiting on it, and having your buttons be able to set the future result? you'd need to be careful to ensure a future can only be set once.

It would probably be worth having function or class to create the future, set it on all your buttons and awaiting on it.

(untested, pie in the sky, code follows)

def which_button(group):
    future = loop.create_future()
    buttons = ToggleButtonBehavior.get_widgets(group):

    def set_result(button):
        for button in buttons:
           button.unbind(set_result)
           future.set_result(button) # or whatever you want from the button

    for button in buttons:
        button.bind(on_release=set_result)

    return future

then you could probably use it something like

def player_move():
    button = await which_button('player_action')
Related