Search code examples
pythonfunctionclasskivykivy-language

Python, Kivy: Problem with calling functions from different classes/screens


I'm having trouble with correctly calling functions from different classes.

I am making a simple game which calculates the score using the amount of time it takes to clear a level. There's a stopwatch running in the background and I want to add a pause button that popup menu, and a resume button inside this popup menu.

The problem is that when calling the pause function from within the popup menu, it will also be returned inside the popup, instead of inside the main widget.

Here is a simplified version of the code:

import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.widget import Widget
from kivy.uix.popup import Popup
from kivy.clock import Clock

root_widget = Builder.load_file('app.kv')


class ExampleWidget(Widget):
    time = NumericProperty(0)
    paused = False
    stop = False

    # Keeping time
    def increment_time(self, interval):

        self.time += .1
        print(self.time)  # To check if stopwatch is running or not

    # Stop should mean that the stopwatch must reset when it starts again.
    # When paused it should resume when it starts again
    def stop_start_or_pause(self):

        # stop stopwatch
        if self.stop:
            Clock.unschedule(self.increment_time)
            print('Stopped')

        # Make sure time is 0 when restarting
        elif not self.stop and not self.paused:
            # Keeping time
            self.time = 0
            Clock.schedule_interval(self.increment_time, .1)

        # Pause stopwatch
        elif self.paused:
            Clock.unschedule(self.increment_time)
            print("!!", self.time)  # To make it easier to see if stopwatch actually resumes where it left off
            print('unscheduled')  # Just to confirm and to make it a bit easier to see

        # resume stopwatch
        elif not self.paused:
            Clock.schedule_interval(self.increment_time, .1)


class PopupMenu(Popup):
    example = ExampleWidget()


class MyApp(App):
    ExampleWidget = ExampleWidget()

    def build(self):
        return ExampleWidget()


MyApp().run()

.kv file:

#:import Factory kivy.factory.Factory
<PopupMenu@Popup>
    auto_dismiss: False
    size_hint_y: .8
    size_hint_x: .9
    title: 'Pause'
    example: app.ExampleWidget

    BoxLayout:
        Button:
            text: 'resume'
            on_press: root.example.paused = False
            on_release: root.dismiss(); root.example.stop_start_or_pause()
            size: self.size

<ExampleWidget>:
    GridLayout:
        col: 2
        rows: 3
        size: root.size
        Button:
            text: 'start'
            size: self.size
            on_press: root.stop = False; root.stop_start_or_pause()
        Button:
            text: 'stop'
            size: self.size
            on_press: root.stop = True; root.stop_start_or_pause()
        Button:
            text: 'Pause menu'
            size: self.size
            on_press: root.paused = True
            on_release: Factory.PopupMenu().open(); root.stop_start_or_pause()
        Label:
            text: str(round(root.time))
            size: self.size

I tried making a function and using Clock.schedule.interval() to keep checking if paused == True, but it keeps returning:

AttributeError: 'float' object has no attribute 'stopped'

This didn't seem like efficient solution anyways, so I didn't want to spend too much time on this function. I also tried to find 'stupid' mistakes (I.e. ',' instead of '.') but that was before I realised that the resume button returned a 'second' stopwatch instead of updating the one I actually wanted to use.

I hope that someone can help, and that my question is clear. English is not my first language so I sometimes have a hard time finding the best way to explain/ask questions.

Thank you in advance!


Solution

  • If I understand your question, the problem is with your MyApp class:

    class MyApp(App):
        ExampleWidget = ExampleWidget()
    
        def build(self):
            return ExampleWidget()
    

    This code is creating two instances of ExampleWidget. One is returned in the build() method, and one is saved as the ExampleWidget attribute of MyApp. Now, when you use the ExampleWidget attribute of MyApp, you are not referencing the ExampleWidget that is the root of your GUI, so it has no effect on what appears on the screen. The fix is to just creat a single instance of ExampleWidget, like this:

    class MyApp(App):
        ExampleWidget = ExampleWidget()
    
        def build(self):
            return self.ExampleWidget