如何在不阻塞 MainThread 的情况下在 GUI (kivy) 中等待用户 decision/input 的代码?

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

我想在代码中暂停或等待,直到用户使change/decision按下切换按钮) 在 Gui 中 我找到了库 asyncpivy,但我不知道如何同时在所有按钮上实现它,如果按下任何按钮,请继续。 我尝试通过 Clock 进行一些安排但没有工作或者我做错了

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

您是否尝试过创建一个 asyncio.future、等待它并让您的按钮能够设置未来的结果?你需要小心确保未来只能设置一次。

创造未来的功能或class可能值得,将其设置在所有按钮上并等待它。

(未经测试,天上掉馅饼,代码如下)

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

那么你可能会像这样使用它

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

如果您使用 asynckivy,代码将是:

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