Search code examples
pythonmultithreadingfunctionanimationkivy

Why doesn't my kivy load screen animation move? Stuck/frozen program?


I have been working on an app with Kivy for some time now and am adding final touches. I have been searching for days now for a solution to my problem, to no avail. I have tried Threading, Clock, and running functions in various orders to try and get this to work. I have reproduced a working example below but made it as minimalistic as possible, so as to not make my post huge. In my real app's get_briefing and strip_first_brief functions, I have a couple of POST requests and a couple of GET requests that vary from 5-20 seconds to perform. I have therefore implemented time.sleep(5) into each of these to emulate the working time. This gives the EXACT same result as my real app. It isn't the POST or GET functions alone that are stopping the animation, it's every process involved....

Because of these load times, I have also cut a corner to get to my loading screen wherein my button on_press immediately changes screen, otherwise with card transition, the screen was getting stuck about 1/5 through the transition. I obviously have more than one problem to unpack in this program, and I think most would be solved if I could get around the "frozen app" issue...

If someone could please copy and test my provided program, and come back with a solution, I would be very grateful. I have been stuck for way too long on this (literally).

main.py

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, NoTransition, CardTransition, SwapTransition
from kivy.uix.button import ButtonBehavior
from kivy.uix.image import Image
from kivy.animation import Animation
import time
from threading import Thread

class ImageButton(ButtonBehavior, Image):
    pass
class HomeScreen(Screen):
    pass
class Results1Screen(Screen):
    pass
class LoadScreen(Screen):
    pass

GUI = Builder.load_file("main.kv")
class TestApp(App):
    def build(self):
        return GUI

    def change_screen(self, screen_name):
        screen_manager = self.root.ids['screen_manager']
        screen_manager.current = screen_name

    def widget_thread(self):
        p1 = Thread(target=self.spin_widget())
        p1.start()
        #Clock.schedule_once(lambda dt: self.spin_widget(), 0)

    def spin_widget(self):
        widget = self.root.ids["load_screen"].ids["spinning_widget"]
        anim = Animation(angle=-3000, duration=40)
        anim.start(widget)

    def immediate_screen1(self):
        self.root.ids["screen_manager"].transition = NoTransition()
        self.change_screen("load_screen")
        self.root.ids["screen_manager"].transition = CardTransition()

    def thread1(self, dest1, dest2):
        self.thread = 1
        p2 = Thread(target=self.get_briefing(dest1, dest2))
        p2.start()

    def get_briefing(self, dest1, dest2):
        time.sleep(5)

    def strip_first_brief(self, destX, etaX):
        time.sleep(5)
        #p3 = Thread(self.strip_first_brief(dest2, eta2))
        #p3.start()

TestApp().run()

main.kv

#:import utils kivy.utils
#:include kv/homescreen.kv
#:include kv/results1screen.kv
#:include kv/loadscreen.kv
GridLayout:
    cols: 1
    FloatLayout:
        canvas:
            Color:
                rgb: utils.get_color_from_hex("#000523")
            Rectangle:
                size: self.size
                pos: self.pos
        rows: 1
        pos_hint: {"top": 1, "left": 1}
        size_hint: 1, .04
    ScreenManager:
        size_hint: 1, .8
        pos_hint: {"top": .8, "left": 1}
        id: screen_manager
        HomeScreen:
            name: "home_screen"
            id: home_screen
        Results1Screen:
            name: "results_1_screen"
            id: results_1_screen
        LoadScreen:
            name: "load_screen"
            id: load_screen

homescreen.kv

<HomeScreen>:
    FloatLayout:
        canvas:
            Color:
                rgb: utils.get_color_from_hex("#000523")
            Rectangle:
                size: self.size
                pos: self.pos
    TextInput:
        id: manual_dest1
        hint_text: "first destination"
        text: self.text.upper() if self.text is not None else ''
        size_hint: .7, .08
        pos_hint: {"top": .75, "center_x": .5}
    TextInput:
        id: eta1
        hint_text: "first ETA in UTC"
        text: self.text.upper() if self.text is not None else ''
        size_hint: .7, .08
        pos_hint: {"top": .5, "center_x": .5}
    Button:
        text: "Strip TAF"
        pos_hint:  {"top": .15, "center_x": .5}
        size_hint: .5, .08
        padding: 20, 20
        opacity: 1 if self.state == 'normal' else .5
        on_press:
            app.immediate_screen1()
            app.widget_thread()
        on_release:
            app.thread1(manual_dest1.text, manual_dest2.text)
            app.strip_first_brief(manual_dest1.text, eta1.text)
            app.change_screen("results_1_screen")

results1screen.kv

<Results1Screen>:
    FloatLayout:
        canvas:
            Color:
                rgb: utils.get_color_from_hex("#000523")
            Rectangle:
                size: self.size
                pos: self.pos
    Label:
        rows: 1
        text: "RESULTS\nblah\nblah\nblah"
        pos_hint: {"top": .95, "center_x": .5}
        size_hint: 1, .05

loadscreen.kv

<LoadScreen>:
    FloatLayout:
        canvas:
            Color:
                rgb: utils.get_color_from_hex("#000523")
            Rectangle:
                size: self.size
                pos: self.pos
        Label:
            text: "loading"
        SpinningWidget:
            id: spinning_widget
            source: "icons/turbine.png"
            angle: 0
            size_hint: .15, .15
            pos_hint: {"center_x": .5, "center_y": .6}
<SpinningWidget@Image>
    angle: 0
    canvas.before:
        PushMatrix
        Rotate:
            angle: root.angle
            axis: 0, 0, 1
            origin: root.center
    canvas.after:
        PopMatrix

When you do trial this, there's no need to input a destination or eta obviously. Also there's no image supplied, but you'll see the square spin about 2 degrees before getting stuck, then see it again right at the last second trying to spin again. I apologise for the lengthy post, but I have no other way to solve this. Please help....


Solution

  • p2 = Thread(target=self.get_briefing(dest1, dest2))
    

    This calls self.get_briefing and passes the result to target, i.e. it's equivalent to:

    result = self.get_briefing(dest1, dest2)
    p2 = Thread(target=result)
    

    You probably want to pass in the function to be called as the argument, something like

    p2 = Thread(target=lambda: self.get_briefing(dest1, dest2))