I'm making a meditation app with kivymd, and I'm trying to figure out how to connect a audio file to a progress bar. There are not too many videos on how to do it, so any help would be greatly appreciated, thanks.
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager
from kivymd.app import MDApp
from kivy.core.window import Window
Window.size = (400, 800)
class SecondWindow(Screen):
def play_sound(self):
sound = SoundLoader.load("audio2/mp3.mp3")
if sound:
sound.play()
sm = ScreenManager()
sm.add_widget(FirstWindow(name="First"))
sm.add_widget(SecondWindow(name="Second"))
class MainApp(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "BlueGray"
return Builder.load_file("thelab.kv")
MainApp().run()
kivy file -
<SecondWindow>:
MDIconButton:
id: my_sound
icon: "play"
user_font_size: 150
pos: 100, 100
on_press:
root.play_sound()
MDProgressBar:
id: progress_bar
size_hint: .8, .5
pos: 90, 200
min: 0
max: 100
You have to handle it yourself. For that you can use Clock.schedule_interval to update the MDProgressBar.value.
First create prop. in your SecondWindow to bind to the prop. of MDProgressBar. Then schedule a callback say, update_progress to update the current progress. I also included the end case when the audio stops playing.
class SecondWindow(Screen):
length = NumericProperty(0.0)
progress = NumericProperty(0.0)
def play_sound(self):
sound = SoundLoader.load("audio2/mp3.mp3")
if sound:
self.length = sound.length
sound.play()
# Now schedule and start updating after every 1.0 sec.
# Use 'Clock.create_trigger' for more control.
self.prog_ev = Clock.schedule_interval(self.update_progress, 1.0)
def update_progress(self, dt):
if self.progress < self.length:
self.progress += 1 # Update value.
else: # End case.
self.progress = 0 # Reset value.
self.prog_ev.cancel() # Stop updating.
Now the binding work in kvlang; here also I added required logic to change icon automatically.
<SecondWindow>:
MDIconButton:
id: my_sound
icon: "pause" if root.progress else "play"
user_font_size: 150
pos: 100, 100
on_press:
root.play_sound()
MDProgressBar:
id: progress_bar
size_hint: .8, .5
pos: 90, 200
min: 0
max: root.length
value: root.progress
Related
I want a ProgressBar to start loading automatically when opening the application (similar to a loading screen). I have already created the bar. However, I've been trying to get the automatic loading process for two days. Does one of you have an idea?
Thanks in advance!
py-file:
from kivy.uix.screenmanager import ScreenManager
from kivy.lang import Builder
from kivymd.app import MDApp
from kivy.clock import Clock
#Window.size = (1366, 768)
#Window.fullscreen = True
class GATR(MDApp):
def build(self):
global sm
sm = ScreenManager()
sm.add_widget(Builder.load_file("splash.kv"))
sm.add_widget(Builder.load_file("login.kv"))
return sm
def on_start(self):
Clock.schedule_once(self.login, 5)
def login(*args):
sm.current = "login"
if __name__=="__main__":
GATR().run()
kv-file (The file with the progress bar):
MDScreen:
name: "splash"
MDFloatLayout:
md_bg_color: 110/255, 123/255, 139/255, 1
Image:
source: "logo.png"
size: 50, 50
pos_hint: {"center_x":.5, "center_y":.6}
MDLabel:
text: "Text"
text_color: 255, 255, 255, 0
font_size: "60sp"
pos_hint: {"center_x":.51, "center_y":.25}
halign: "center"
font_name: "3.ttf"
ProgressBar:
id: pb
min: 0
max: 100
pos_hint: {"center_x": .5, "center_y": .1}
size_hint_x: .8
I think something like this should do the work.
def foo(self):
current = self.ids.pb.value #get current value of the progress bar
if current >= 100: #check when it hits 100%, stop it
return False
current += 1 #increment current value
self.ids.pb.value = current #update progress bar value
def on_start(self):
Clock.schedule_interval(self.foo, 1/25) #kivy clock object to time it
It is just an idea, you should play around with it and implement it correctly with your codebase.
Goal:
Periodic update of parent (screen) class / UI from child (boxlayout) class. Theconf2.dat is occasionally updated (from various other screens), and I want the UI to update every 5 seconds or so by re-running this class.
Latest code update:
In the __init__ function, I have Clock.schedule_interval(self.create_button, 1), which should cause the create_button function to rerun every second.
At the top of the create_button function, I have self.box_share.clear_widgets(), which should clear all the widgets so they can be repopulated (per the instructions outlined further down the create_button function).
Action:
I run the code
I navigate to NoTags screen by clicking the button with text title 'updating sequence'
I make changes to buttons that were dynamically created under scrollview by clicking on them. They successfully change color. This information is written to the conf2.dat file.
I navigate to SequenceScreen screen by first clicking 'home' button, then clicking 'sequence display' button. This SequenceScreen screen is the screen I wish to have updated to reflect the changes made to conf2.dat file.
Result:
UI associated withSequenceScreen(Screen) class still does not update per changes made from UI associated with NoTags(Screen) class.
However, when I restart the app altogether, I find the SequenceScreen UI successfully updated.
Suspicion:
I'm just one line of code away from getting this UI to update properly.
Python Code:
from kivy.app import App
# kivy.require("1.10.0")
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.textinput import TextInput
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import NumericProperty
from kivy.clock import Clock
from kivy.uix.widget import Widget
from kivy.uix.scrollview import ScrollView
#from kivy.base import runTouchApp
from kivy.properties import StringProperty, ObjectProperty, NumericProperty
from kivy.storage.dictstore import DictStore
import pickle
import datetime, threading
import time
from kivy.clock import mainthread
class BackHomeWidget(Widget):
pass
class SequenceBoxLayout_NoEdits(BoxLayout):
box_share = ObjectProperty()
config_file = DictStore('conf2.dat')
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoEdits, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_interval(self.create_button, 1)
def create_button(self, *args):
self.box_share.clear_widgets()
top_button_share = 1.1
color = (.4, .4, .4, 1)
for i in range(25):
top_button_share -= .4
id_ = "part" + str(i + 1)
if self.config_file.exists(id_):
btn_color = self.config_file[id_]["background_color"]
else:
self.config_file.put(id_, background_color=color)
btn_color = color
button_share = Button(background_normal='',
background_color=btn_color,
id=id_,
pos_hint={"x": 0, "top": top_button_share},
size_hint_y=None,
height=60,
font_size = 30,
text= str(i+1)
self.box_share.add_widget(button_share)
#Clock.schedule_interval(self.parent.ids.updatedisplay.create_button(self, *args) , 1)
#self.parent.ids.updatedisplay.create_button(self, *args)
class SequenceBoxLayout_NoTag(BoxLayout):
box_share = ObjectProperty()
config_file = DictStore('conf2.dat')
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoTag, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_once(self.create_button)
def create_button(self, *args):
df = pd.read_excel("Test.xlsx","Sheet1")
parts = df['parts'].values.tolist()
top_button_share = 1.1
color = (.4, .4, .4, 1)
for i in range(len(parts)):
top_button_share -= .4
id_ = "part" + str(i + 1)
if self.config_file.exists(id_):
btn_color = self.config_file[id_]["background_color"]
else:
self.config_file.put(id_, background_color=color)
btn_color = color
button_share = Button(background_normal='',
background_color=btn_color,
id=id_,
pos_hint={"x": 0, "top": top_button_share},
size_hint_y=None,
height=60,
font_size = 30,
text= str(i+1)+ ". " + str(parts[i]))
if self.parent.name == 'notags':
button_share.bind(on_press=self.update_buttons_notag)
self.box_share.add_widget(button_share)
def update_buttons_notag(self, button):
button.background_color = 0.86,0.54,0.04,1
self.config_file.put(button.id, background_color=(0.86,0.54,0.04,1))
class MainScreen(Screen):
pass
class AnotherScreen(Screen):
pass
class SequenceScreen(Screen):
pass
class NoTags(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("updatelistexample.kv")
class MainApp(App):
def build(self):
return presentation
if __name__ == "__main__":
MainApp().run()
KV Code:
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManagement:
transition: FadeTransition()
MainScreen:
AnotherScreen:
NoTags:
SequenceScreen:
<SmallNavButton#Button>:
font_size: 32
size: 125, 50
color: 0,1,0,1
<BigButton#Button>:
font_size: 40
size_hint: 0.5, 0.15
color: 0,1,0,1
<BackHomeWidget>:
SmallNavButton:
on_release: app.root.current = "main"
text: "Home"
pos: root.x, root.top - self.height
<MainScreen>:
name: "main"
FloatLayout:
BigButton:
on_release: app.root.current = "notags"
text: "updating sequence"
pos_hint: {"x":0.25, "top": 0.4}
BigButton:
on_release: app.root.current = "sequence"
text: "sequence display"
pos_hint: {"x":0.25, "top": 0.7}
<AnotherScreen>:
name: "newgarage"
<NoTags>:
name: "notags"
SequenceBoxLayout_NoTag:
BackHomeWidget:
FloatLayout:
BigButton:
text: "Select Parts w/o Tags"
pos_hint: {"x":0.5, "top": 0.6}
background_normal: ''
background_color: (0.4,0.4,0.4,1)
<SequenceBoxLayout_NoEdits>:
box_share: box_share
ScrollView:
GridLayout:
id: box_share
cols: 1
size_hint_y: None
size_hint_x: 0.5
spacing: 5
padding: 130
height: self.minimum_height
canvas:
Color:
rgb: 0, 0, 0
Rectangle:
pos: self.pos
size: self.size
<SequenceBoxLayout_NoTag>:
box_share: box_share
ScrollView:
GridLayout:
id: box_share
cols: 1
size_hint_y: None
size_hint_x: 0.5
spacing: 5
padding: 130
height: self.minimum_height
canvas:
Color:
rgb: 0, 0, 0
Rectangle:
pos: self.pos
size: self.size
<SequenceScreen>:
name: "sequence"
SequenceBoxLayout_NoEdits:
id: updatedisplay
BackHomeWidget:
Credit:
Based on advice provided by #Tshirtman in the comments thread of the posted question...
Summary:
The problem with the code had to do with the fact that I had two different DictStore pointing to the same file, which was tripping up the communication between both classes.
The solution was to instead use only one DictStore and define that variable in the App class, then reference that particular variable in the child classes [using App.get_running_app()], like so:
Define config_file in App class:
class MainApp(App):
config_file = DictStore('conf2.dat')
def build(self):
return presentation
Reference App variable in child classes:
class SequenceBoxLayout_NoEdits(BoxLayout):
...
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoEdits, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_interval(self.create_button, 1)
def create_button(self, *args):
self.box_share.clear_widgets()
app = App.get_running_app()
...
for i in range(len(parts)):
...
if app.config_file.exists(id_):
btn_color = app.config_file[id_]["background_color"]
else:
app.config_file.put(id_, background_color=color)
...
...
class SequenceBoxLayout_NoTag(BoxLayout):
...
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoTag, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_once(self.create_button)
def create_button(self, *args):
...
app = App.get_running_app()
...
for i in range(len(parts)):
...
if app.config_file.exists(id_):
btn_color = app.config_file[id_]["background_color"]
else:
app.config_file.put(id_, background_color=color)
...
...
def update_buttons_notag(self, button):
app = App.get_running_app()
...
app.config_file.put(button.id, background_color=(0.86,0.54,0.04,1))
Clock has a schedule_interval method, which works much like the schedule_once one, but will be called every n seconds instead of just once after n seconds.
So you could certainly just change that call in __init__ and start the create_button by calling self.box_share.clear_widgets() to remove widgets from the previous call before re-creating them.
This might be a bit wasteful, if you find yourself recreating a lot of widgets even if nothing changed, so you could add some logic to first check if the data didn't change, or even if it did, just reuse the old buttons if possible.
box = self.box_share
old_buttons = box.children[:]
box.clear_widgets()
# [your logic about computing the list of parts]
for i, p in enumerate(parts): # this is much better than doing range(len(parts))
# [the logic to get the content of the button]
if old_buttons: # check there are still buttons in the list of old buttons
btn = old_buttons.pop()
else:
btn = Button(
background_normal='',
background_color=btn_color,
pos_hint={"x": 0, "top": top_button_share},
# etc, all the things that are common to all your buttons
# but really, hardcoding them like this is a bit painful,
# you should use a subclass of button so you can style it
# in a kv rule so it would apply to all of them directly
)
btn.id=id_
btn.text = "{}. {}".format(i+1, p)
btn.pos_hint["top"] = top_button_share
# re-apply any other property that might have changed for this part
box.add_widget(btn)
But this logic is quite a common one, actually, and there are other things you can do to improve things in even more situations, though that's quite some work.
Thankfully, you are not the first one to need such thing, and we have been blessed with the wonderful RecycleView, which automates all of this and more, you just need to feed it a data directory, and it'll create the necessary widgets to fill the visible part of the scrollview if there is enough widgets to warrant scrolling, and automatically update when data changes, and when you scroll to see different parts of the list. I encourage you to check it, yourself. but the end result would certainly be something like.
<PartButton#Button>:
id_: None
part: ''
text: '{}. {}'.format(self.id, self.part)
<SequencedBoxLayout#Recycleview>:
parts: self.get_parts()
viewclass: 'PartButton'
data:
[
{
id_: i,
part: part
} for i, p in enumerate(self.parts or [])
]
RecycleBoxLayout:
im currently looking into kivy to start with crossplatform development. i have a bit of python experience (but basic) and now wanted to code a little game in kivy to get into. i probably wont finish this but i like learning stuff while doing it with something im intrested in.
Anyway my "App" is supposed to be seperated in two seperate "screens" the top one is only used for displaying stuff and the all interactive stuff is controlled from the bottom "screen".
Now i want to display some text in old school way by getting it written letter by letter to the screen.
This is working fine but for some reason the Label widget is only updated on screen if i call the "print_something" function from the top screen, if i call it from the bottom screen the function is indeed called but the Label widget wont change on screen.
Am i doing something wrong?
Here is a stripped version of the code:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.clock import Clock
Builder.load_string('''
<MainUI>:
orientation: 'vertical'
# both these variables can be the same name and this doesn't lead to
# an issue with uniqueness as the id is only accessible in kv.
<Screen1>:
print_txt: print_txt
layout: layout
RelativeLayout:
id: layout
pos: 0, 400
size: 480, 400
Button:
pos: 0, 200
size_hint: (1, 0.2)
text: "Test Print"
on_press: root.print_something('TEST PRINT FROM SCREEN1')
AnchorLayout:
anchor_x: 'center'
anchor_y: 'bottom'
Label:
id: print_txt
padding_x: 10
markup: True
text_size: self.size
halign: 'left'
valign: 'top'
size_hint: (1, 0.2)
text: ""
<Screen2>:
btn1: btn1
RelativeLayout:
pos: 0, 0
size: 480, 400
Button:
id: btn1
pos_hint: {'x': .15, 'center_y': .5}
size_hint: (0.7, 0.5)
text: "Test Print"
on_press: root.print_text()
''')
class Screen1(Widget):
print_txt = ObjectProperty(None)
layout = ObjectProperty(None)
def print_something(self, string):
print 'Function called...'
self.print_txt.text = ''
counter = [0]
string_len = len(string)
def print_step(dt):
if counter[0] == string_len:
return False
else:
self.print_txt.text += string[counter[0]]
counter[0] = counter[0] + 1
Clock.schedule_interval(print_step, 2.0/60.0)
print 'Function End..'
class Screen2(Widget):
btn1 = ObjectProperty(None)
def __init__(self):
super(Screen2, self).__init__()
def print_text(self):
print 'Trying to print Text from Screen2 to Screen1'
target = Screen1()
target.print_something('TEST PRINT FROM SCREEN2')
class MainUI(Widget):
def __init__(self):
super(MainUI, self).__init__()
self.screen1 = Screen1()
self.add_widget(self.screen1)
self.add_widget(Screen2())
class MainApp(App):
def build(self):
Window.size = (480, 800)
return MainUI()
if __name__ == '__main__':
MainApp().run()
Your Screen2 print_text method creates a new Screen1 instance, which is modified but not displayed anywhere so you don't see anything change.
You could change the call to instead something like
on_press: root.parent.screen1.print_text()
...to access the print_text function of the Screen1 instance that you actually want to update.
I have posted two posts about the same problem a few days ago, but I still can't get it to run.
I have two screens. Two buttons on first screen (Play and How to play). The second one does what I want, but I would like the first one to begin the game when released, as well as change Screen to second Screen (this works okay).
I have tried a lot of things and I always get errors about SnakeWidget not being defined, not containing self and so on.
That's the error I get:
File "C:\Users\Lara\Desktop\KIVY\LARA\poskus.py", line 33, in <module>
class GameScreen(Screen):
File "C:\Users\Lara\Desktop\KIVY\LARA\poskus.py", line 34, in GameScreen
snaky_game = SnakeWidget()
NameError: name 'SnakeWidget' is not defined
.py file:
import kivy
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.vector import Vector
from kivy.clock import Clock
from kivy.lang import Builder
class RootScreen(ScreenManager):
pass
class StartScreen(Screen):
def show_popup(self):
p = InstructionsPopup(content=Label(text="Instructions"))
p.open()
class GameScreen(Screen):
snaky_game = SnakeWidget()
snaky_game.begin()
Clock.schedule_interval(self.update, 1.0 / 60.0)
class InstructionsPopup(Popup):
pass
class SnakeWidget(Widget):
snaky = ObjectProperty(None)
def __init__(self, *args, **kwargs):
super(SnakeWidget, self).__init__(*args, **kwargs)
Clock.schedule_interval(self.update, 1.0 / 60.0)
def begin(self, vel=(4, 0)):
self.snaky.center = self.center
self.snaky.velocity = vel
def update(self, dt):
self.snaky.move()
if (self.snaky.y < self.y) or (self.snaky.top > self.top):
self.snaky.velocity_y *= -1
class Snake(Widget):
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(0)
velocity = ReferenceListProperty(velocity_x, velocity_y)
def move(self):
self.pos = Vector(*self.velocity) + self.pos
class PoskusApp(App):
def build(self):
self.load_kv("poskus.kv")
return RootScreen()
if __name__ == "__main__":
PoskusApp().run()
and .kv file:
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
<RootScreen>:
id: screen_manager
transition: FadeTransition()
StartScreen:
name: "start"
GameScreen:
name: "game"
<StartScreen>:
FloatLayout:
name: "start"
Button:
id: play
text: "Play!"
size_hint: 0.4, 0.3
pos_hint: {'center_x':.5, 'center_y':.5}
font_size: 70
on_release: root.manager.current = "game"
Button:
id: how
text: "How to play"
size_hint: 0.4, 0.1
pos_hint: {'center_x':.5, 'center_y':.3}
font_size: 40
on_press: root.show_popup()
<InstructionsPopup>:
size_hint: .5, .5
title: "How to play"
<GameScreen>:
name: "game"
SnakeWidget:
Button:
id: menu
text: "Menu"
size_hint: 0.2, 0.1
pos_hint: {"x": 0.8,"y":0.9}
font_size: 17
on_release: root.manager.current = "start"
<SnakeWidget>:
id: snake_widget
snaky: snake
canvas:
Rectangle:
size: self.size
pos: self.pos
Snake:
id: snake
center: self.parent.center
<Snake>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: 40, 40
If you can, please help me because I am stuck and getting nowhere.
I think you missed a couple of python basics. I will explain in form of points.
1. You forgot to import properties that you used in your python code.
from kivy.properties import NumericProperty,ReferenceListProperty,ObjectProperty
2. You need to know how the python interpreter works, It reads your code from top to bottom, Here class GameScreen is defined before defining SnakeWidget class, So when you try to make an object of SnakeWidget class which doesn't exists for the interpreter. It shows an error. NameError: name 'SnakeWidget' is not defined. So basically, move your class GameScreen below the class SnakeWidget and that shall fix the error.
3. You used self when you tried to schedule your function. Clock.schedule_interval(self.update, 1.0 / 60.0)
Using self would say that there is a method named update in the same class itself instead of looking in the SnakeWidget class.
class GameScreen(Screen):
snaky_game = SnakeWidget()
snaky_game.begin()
Clock.schedule_interval(snaky_game.update, 1.0 / 60.0)
Problem is with screenmanager where i have 2 screens say loadingscreen and menuscreen .
In starting I load loading screen first and after 3 seconds i switch it to menuscreen which help of clock schedule .
On menu screen I have a button which when pressed takes us back to loading screen .
Now i want to move back to menu screen after 3 seconds of loading screen is active .. Can someone suggest best way to do it . Below is a code which explains what i have and what i need :
I know changing below code to have Clock.schedule_interval(self.callNext, 3) in LoadingScreen Class will do the job but i am looking for a better option which is much more efficient
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen,FallOutTransition
from kivy.properties import ObjectProperty,NumericProperty
from kivy.uix.image import Image
from kivy.graphics import Color
from kivy.clock import Clock
gui_v3 = '''
<PlayScreen>:
play_Button: playButton
canvas.before:
Color:
rgb: (0, 0, 1)
GridLayout:
rows:1
size_hint: .1,.1
pos_hint: {'center_x': .5, 'center_y': .5}
Button:
id: playButton
size_hint_x: None
width: 100
text: 'Play !'
font_size: 12
bold: True
italic: False
border: 10,10,10,10
color: (0.5, 1, 0.5, 1)
on_press: root.playButton_press()
<LoadingScreen>:
canvas:
Color:
rgba: 0.4, 0.4, 0.4, 1
Rectangle:
pos: root.center
size: (32, 32)
BoxLayout:
Label:
text: 'JB'
font_size: 100
Label:
text: 'Loading...'
font_size: 10
'''
class PlayScreen(Screen):
play_Button = ObjectProperty(None)
def __init__(self, **kwargs):
super(PlayScreen, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1.0/2)
def update(self,dt):
print "Current screen is ",self.manager.current
def playButton_press(self):
print "Hi Play button is pressed"
sm.current = 'loading'
class LoadingScreen(Screen):
def __init__(self, **kwargs):
super(LoadingScreen, self).__init__(**kwargs)
Clock.schedule_once(self.callNext, 3)
def callNext(self,dt):
self.manager.current = 'play'
print "Hi this is call Next Function of loading 1"
# Create the screen manager
Builder.load_string(gui_v3)
sm = ScreenManager(transition= FallOutTransition())
sm.add_widget(LoadingScreen(name='loading'))
sm.add_widget(PlayScreen(name='play'))
class MyJB(App):
def build(self):
print sm.screen_names
return sm
if __name__ == '__main__':
MyJB().run()
You should use the screen's on_enter event. Simply do this in the kv file:
<LoadingScreen>:
on_enter: Clock.schedule_once(self.callNext, 3)
Also a the top of the kv you need to import Clock, #:import Clock kivy.clock.Clock. Now every time the screen is opened it'll schedule the callback.