Me again, here is my code, why is "beep.wav" playing when I launch the application rather than when I press the buttons I assume I have bound it to?
import kivy
import random
from kivy.core.audio import SoundLoader
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
sound = SoundLoader.load('beep.wav')
red = [1,0,0,1]
green = [0,1,0,1]
blue = [0,0,1,1]
purple = [1,0,1,1]
class A_Cool_Program(App):
def Orientation(self, orient):
self.orient = orient
def build(self):
layout = BoxLayout(padding=0, orientation=self.orient)
colors = [red, green, blue, purple]
for i in range(5):
btn = Button(text="Test Button %s" % (i+1), background_color=random.choice(colors))
layout.add_widget(btn)
sound = SoundLoader.load('beep.wav')
btn.bind(on_press=sound.play())
return layout
if __name__ == "__main__":
app = A_Cool_Program()
app.Orientation(orient="vertical")
app.run()
btn.bind(on_press=sound.play())
This line calls the function, just as you should always expect when you use the function call syntax - you called sound.play(), therefore the function is called and you hear the sound. The bind method doesn't know about any of this, it only sees the return value of the function, which is probably just None.
I think in this case you want instead something like
btn.bind(on_press=lambda *args: sound.play())
This passes in a lambda function, and when that function is called then sound.play() is run. This example is actually unusual because sound.play doesn't seem to accept any arguments, but kivy bindings automatically pass at least the instance of the button, so this code uses a lambda function to eat that argument.
You normally could just do:
def some_function(*args):
print('the button was pressed!')
...
btn.bind(on_press=some_function)
Note the lack of brackets to actually call some_function. It will be called later when the button is pressed.
Related
I am currently making a text based game using Python, and I have been wanting to use Kivy to make a Graphical Interface for it. I have been unable to get it to work so far though.
The reasoning is that I have changed from what was Print and Input to self.label1 = Label(text='Hello world') etc (There is multiple variables- label2, 3 and 4.) and then for the input, a text input box, which the input is used by a function when the button is pressed (Currently the textbox isn't involved, since I am first just testing whether the button works.). The problem is, I need a way to update the text displayed with the new value. For example I would like label1 to change to "These are the controls". But when a button is clicked, changes don't happen- I would like the GUI to be updated with the new text, through the changing of the values of the label variables. I believe that since those are being returned, the code above no longer loops through. The ideas I've been given, is to put the different sections into functions, or to use threading. Does anyone have any tips to push me in the right direction. I understand it may be too much be to ask, if so I'll continue to look for a solution myself. I can show some of the code if needed.
import kivy.uix.boxlayout
import kivy.uix.textinput
import kivy.uix.label
import kivy.uix.button
from kivy.app import App
from random import shuffle
import time
from kivy.uix.button import Button
from kivy.clock import Clock
alive = 1
buttonPressed = 0
class SimpleApp(App):
def build(self):
global alive
global buttonPressed
donext = 0
alive = 1
def callback(self):
global buttonPressed
buttonPressed = 1
self.label1 = kivy.uix.label.Label(text="")
self.label2 = kivy.uix.label.Label(text="")
self.label3 = kivy.uix.label.Label(text="You have found yourself in a dungeon, somewhere is your escape path, will you make it out, and if so, what with?")
self.label4 = kivy.uix.label.Label(text="")
print(buttonPressed)
if buttonPressed == 1:
print("Has been pressed should work theoretically")
self.label1 = kivy.uix.label.Label(text="These are the basic controls-")
self.label2 = kivy.uix.label.Label(text="The controls-")
self.label3 = kivy.uix.label.Label(text="A- approach enemy/ attack enemy")
self.label4 = kivy.uix.label.Label(text="C- Go to chest")
print("Press enter to continue.")
self.boxLayout = kivy.uix.boxlayout.BoxLayout(orientation="vertical")
self.boxLayout.add_widget(self.label1)
self.boxLayout.add_widget(self.label2)
self.boxLayout.add_widget(self.label3)
self.boxLayout.add_widget(self.label4)
self.btn1 = Button(text='Hello world 1', on_press=callback)
self.boxLayout.add_widget(self.btn1)
return self.boxLayout # Causes script not to continue
if __name__ == "__main__":
simple = SimpleApp()
simple.run()
If you have been advised to use threads it seems that your advisor does not know about GUI, in the GUIs the tasks are done asynchronously through events, that is, the GUI will provide you with methods to indicate when something has happened in the GUI, for example the event on_press notifies you when the button is pressed, so they connect a signal to that event. On the other hand the GUIs have a high component of object-oriented programming, event-oriented programming, and in the .kv is a declarative language, so I recommend you read about those concepts and for it kivy offers a great documentation and examples, review them. If you want to update a Label at least it must be accessible in the whole class, so it must be an attribute of the class and use the text property, on the other hand if you want to show a text of several lines use \n to indicate that there is a jump of line.
Considering the above, the solution is as follows:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
class SimpleApp(App):
def build(self):
self.label = Label(text="You have found yourself in a dungeon,\nsomewhere is your escape path,\nwill you make it out, and if so, what with?")
self.button = Button(text="Press Me", on_press=self.on_clicked, size_hint=(1.0, None))
layout = BoxLayout(orientation="vertical")
layout.add_widget(self.label)
layout.add_widget(self.button)
return layout
def on_clicked(self, instance):
self.label.text = "These are the basic controls-\nThe controls-\nA- approach enemy/ attack enemy\nC- Go to chest"
if __name__ == "__main__":
SimpleApp().run()
wonder if anyone can help me out.
I am stuck with a problem. I want to change a buttons background_normal TWICE by clicking another button.
So for example first change to yellow than wait 2 seconds (time.sleep(2)) and then change to red.
I tried to make a simple functions that does this. But the problem is that it does not update the first change of the background_normal. It only displays the latest change.
Tried to split it into two functions, one changes it to the first color, the other one changes it to the second color. But still it does not display anything but the latest change.
Is there any possibility to solve this?
Update:
My code is ~2k lines so i will post a short form of my problem
class Game(FloatLayout):
def firstaction(self):
#change button1 to yellow and wait 2 seconds
def secondaction(self):
#change button1 to red
class GameApp(App):
def build(self):
return Game
kv file looks something like
button2:
on_press: Game.firstaction
on_press: Game.secondaction
You talk about colors so you should use background_color instead of background_normal. Going to the problem, the cause is time.sleep() because it blocks the event loop, the event loop helps the GUI to update due to OS or user events. The solution is to use Clock.
from kivy.app import App
from kivy.uix.button import Button
from kivy.clock import Clock
class MyButton(Button):
def on_press(self):
self.background_color = (1,1,0,1)
fn = lambda dt: setattr(self, "background_color", (1,0,0,1))
Clock.schedule_once(fn, 2) # <--- 2 seconds
class MyApp(App):
def build(self):
button = MyButton(text='Hello World')
return button
if __name__ == '__main__':
MyApp().run()
I recently learnt about the Python framework kivy and started to follow the simple paint app tutorial. Now I want to save what was drawn as a .png file but instead of the colored dots it only exports the black background.
This is my code example:
import kivy
kivy.require("1.9.1")
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.graphics import Color, Ellipse, Line
class MyPaintWidget(Widget):
def on_touch_down(self,touch):
color=(0,0,1)
with self.canvas:
Color(*color)
d=30.
Ellipse(pos=(touch.x-d/2., touch.y-d/2.), size=(d,d))
class MyPaintApp(App):
def build(self):
parent=Widget()
self.painter=MyPaintWidget()
ssbtn=Button(text='Save')
ssbtn.bind(on_release=self.save_screenshot)
parent.add_widget(self.painter)
parent.add_widget(ssbtn)
return parent
def save_screenshot(self,obj):
self.painter.export_to_png("screenshot.png")
if __name__ == '__main__':
MyPaintApp().run()
What am I doing wrong? Thanks for your help.
You are missing one thing - Widget won't set its size and position automatically to whole Window size, but only to default ones i.e. pos = [0, 0] size = [100, 100] because Window != Layout and even adding it to something that doesn't inherit from a Layout won't make it better (Widget != Layout) which you can easily see if you look a that saved png (is too small).
self.painter=MyPaintWidget(size=[800,600])
will make the job for you if you plan to stay only on pc and only with the default Window size. If not, then use:
from kivy.core.window import Window
self.painter=MyPaintWidget(size=Window.size)
which seems to be more practical, yet you're still not there. An optimal solution would be using a Layout which does these things for you such as BoxLayout, GridLayout or some others.
And a perfect solution for you would be to use a StencilViewin a Layout, which when is placed won't take a screenshot of the whole canvas, but only the part you'd think it'll take i.e. the part sized as the Widget itself placed on its position.
Try drawing outside of the red box. Inspector will provide you with showing widgets in a color and other functions, so definitely try it as it was mentioned in the comments.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.uix.stencilview import StencilView
from kivy.graphics import Color, Ellipse, Line, Rectangle
class MyPaintWidget(StencilView):
def on_touch_down(self,touch):
color=(0,0,1)
with self.canvas:
Color(*color)
d=30.
Ellipse(pos=(touch.x-d/2., touch.y-d/2.), size=(d,d))
class MyPaintApp(App):
def build(self):
parent=Widget()
self.painter=MyPaintWidget(size=[i/2.0 for i in Window.size])
with self.painter.canvas:
Color(1, 0, 0, 0.3)
Rectangle(pos=self.painter.pos, size=self.painter.size)
ssbtn=Button(text='Save')
ssbtn.bind(on_release=self.save_screenshot)
parent.add_widget(self.painter)
parent.add_widget(ssbtn)
return parent
def save_screenshot(self,obj):
self.painter.export_to_png("screenshot.png")
MyPaintApp().run()
I created a simple text-to-speech app with Kivy, using the FloatLayout option but am having trouble changing the color of the GUI without actually creating a .kv file (which I do not wish to do). The code of my app is here:
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
import requests
from threading import Thread
import os
class ButtonApp(App):
def talk(self):
self.info.text = self.text.text
command = "say %s" % (self.text.text)
os.system(command)
def say(self,instance):
t = Thread(target=self.talk)
t.start()
def build(self):
self.b = FloatLayout()
self.info = Label(text="Hello!", pos=(20,400) ,size_hint=(1,0.5), font_size="40sp")
self.text = TextInput(text='Hello!', pos=(20,200), size_hint=(1,0.5))
self.submit = Button(on_press=self.say,text='Submit',pos=(20,100), size_hint=(1,0.5))
self.b.add_widget(self.info)
self.b.add_widget(self.text)
self.b.add_widget(self.submit)
self.b.bind()
return self.b
if __name__ == "__main__":
ButtonApp().run()
Like I mentioned beforehand, all the suggestions I found doing prior research involved either Canvas (which I am not using), or creating a .kv file. Is there a pure python-kivy method of changing the color of a GUI?
You can do anything in pure python, though the reason you see so many kv examples is because it's easier and more concise due to being a more domain specific language, so I don't recommend avoiding it.
What kind of change do you actually want to make? For instance, you can change the background image of the Button with the background_normal or background_down properties (which take a filepath to an image), or tint its colour by setting its background_color to e.g. (1, 0, 0, 1) for red.
I made a little Kivy application as an interface. The carousel seems to be working, but I would like to make the carousel start by itself (that means without using a mouse). In fact, the app will be deployed on a little robot that does not have any mouse or keybord, thus that is why I need the carousel passing the images by itself.
As you can see in my code, the carousel does work, but I just can't figure out how to make it start without clicking on it with a mouse.
class Logo(App):
def build(self):
carousel = Carousel(direction='right')
for i in range(2):
src = "image.png"
image = Factory.AsyncImage(source=src, allow_stretch=True)
carousel.add_widget(image)
return carousel
if __name__ == '__main__':
Logo().run()
Any ideas ? Thanks.
You want an event to trigger the movement, i think the easiest way would be to use a Clock.
from kivy.clock import Clock
then in your build, you could schedule incrementing the position of the carousel at some interval, carousel has a convenient function for that load_next, but if you just do that, it'll get stuck on the last frame pretty fast, so you'll probably want to pass loop=True to the Carousel instantiation.
from kivy.app import App
from kivy.factory import Factory
from kivy.uix.carousel import Carousel
from kivy.clock import Clock
class Logo(App):
def build(self):
carousel = Carousel(direction='right', loop=True)
for i in range(2):
image = Factory.Label(text=str(i))
carousel.add_widget(image)
Clock.schedule_interval(carousel.load_next, 1)
return carousel
if __name__ == '__main__':
Logo().run()
(i changed the images by labels to see the difference more easily)