Search code examples
pythonasync-awaitkivypython-multithreadingkivy-language

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


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')

Solution

  • 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())