Kivy widget is bigger than it should be - python

I tried to make by own coockie-clicker, so I creaded an kivy widget and declared an image of an coockie as part of it.
Everytime you click on the wiget, a counter goes up and the number is displayed on an label.
Everything went fine, after I got help here on stack overflow, but now I am faced with the problem, that the widet is to big, so even if I click on the right upper corner, to counter goes up, aldoug I do not clicked on the coockie.
Here is the sourcecode:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.animation import Animation
from kivy.core.text.markup import *
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import NumericProperty
from kivy.properties import StringProperty
Builder.load_string('''
<Root>:
Kecks:
pos: 300, 300
size: 100, 100
<Kecks>:
Image:
pos: root.pos
id: my_image
source: root.weg
Label:
id: my_Label
font_size: 50
text: root.txt
center_x: 345
center_y: 200
''')
class Root(FloatLayout):
def __init__(self, *args, **kwargs):
super(Root, self).__init__(*args, **kwargs)
class Kecks(Widget):
count = NumericProperty(0)
amount = NumericProperty(1)
txt = StringProperty()
level = NumericProperty(1)
weg = StringProperty('piernik.png')
def __init__(self, *args, **kwargs):
super(Kecks, self).__init__(*args, **kwargs)
#self.txt = str(self.count)
Clock.schedule_interval(self.Update, 1/60.)
def Update(self, *args):
self.txt = str(self.count)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.count += self.amount
class app(App):
def build(self):
Window.clearcolor = (10, 0, 0, 1)
return Root()
if __name__ == "__main__":
app().run()

The problem is you haven't defined your collide_points on which area you want that event to be triggered.
Consider if you want your collide_points on your my_image to trigger on_touch_down event, you need to adjust like this:
def on_touch_down(self, touch):
# to check if touch.pos collides on my_image
if self.ids.my_image.collide_point(*touch.pos):
self.count += self.amount
Also perhaps consider using pos_hint and size_hint as these will help you with consistency with your app running in different devices (size of screen, for instance), rather than using absolute size and/or position.
Hope this helps.

Related

How to add TextInput text to a label in python kivy?

I'm making an app in python kivy, and I have 2 draggable Images in my "TacScreen" and whenever I double tap on either of them It opens up a popup, inside the popup I have a Textinput field where I can input text. Whatever text I put in there I want that to show inside a Label below my Draggable Image. The problem that I'm facing now is when I type anything in the text input field and press the close button (you can see it below in my code) It closes the popup and I don't see any text. How can I fix this? Below is my code!
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.clock import Clock
from functools import partial
from kivy.lang import Builder
from kivy.uix.behaviors import DragBehavior
from kivy.uix.image import Image
from kivy.vector import Vector
from kivy.uix.popup import Popup
from kivy.uix.textinput import TextInput
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
class DragImage(DragBehavior, Image):
def on_touch_up(self, touch):
uid = self._get_uid()
if uid in touch.ud:
print(self.source, 'dropped at', touch.x, touch.y)
return super(DragImage, self).on_touch_up(touch)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos) and touch.is_double_tap:
print('this is a double tap')
self.layout = GridLayout(cols=1, row_force_default=True,
row_default_height=30,
spacing=80)
self.layout2 = GridLayout(cols=1,
row_force_default=True,
row_default_height=30,
spacing=80)
self.popuptextinput = TextInput(hint_text='Person Name',
multiline=False,
background_color='red',
halign='left',
cursor_blink=True,
cursor_color='white',
foreground_color='white',
font_size='14')
self.closebutton = Button(text="SAVE",
background_color="black",
font_size="14",
font_name='font1.otf')
self.output = Label(text='',)
self.layout.add_widget(self.popuptextinput)
self.layout.add_widget(self.closebutton)
self.layout2.add_widget(self.output)
self.popup = Popup(title='PERSON',
title_color="white",
separator_color="black",
title_align="center",
separator_height="0dp",
background_color="black",
title_font="font.otf",
title_size="12",
content=self.layout,
size_hint_y=.4,
size_hint_x=.5)
self.popup.open()
self.closebutton.bind(on_release=self.updttext)
return self.layout2
return super(DragImage, self).on_touch_down(touch)
def updttext(self, other):
self.output.text = self.popuptextinput.text
self.popup.dismiss()
class StartScreen(Screen):
pass
class TacScreen(Screen):
pass
class MyApp(App):
def build(self):
return Builder.load_string(kv)
MyApp().run()
my .kv file
kv = '''
<DragImage>:
drag_rectangle: self.center[0] - self.norm_image_size[0]/6,
self.center[1] -
self.norm_image_size[1]/6, \
self.norm_image_size[0]/3, self.norm_image_size[1]/3
drag_timeout: 10000000
drag_distance: 0
<TacScreen>:
#:import utils kivy.utils
DragImage
id: person1
pos: 0, 102
size_hint: 1, .1
source: "person1.png"
DragImage:
id: person2
pos: 50, 160
size_hint: 1, .1
source: "person2.png"
...

Displaying a background image in Kivy

I just started to learn Kivy so I am still familiar with its functionalities.
I am trying to put an image as a background to my app main page. This is what I did:
class Prin(BoxLayout):
def __init__(self,**kwargs):
super(Prin,self).__init__(**kwargs)
layout = BoxLayout(orientation='vertical')
with self.canvas:
self.rect = Rectangle(source='test.png', pos=layout.center, size=(self.width, self.height))
self.text = Label(text='Press start')
fb = Button(text='Start!', size_hint =(0.5, 0.1), pos_hint ={'center_x':.5, 'y':.5}, padding=(10, 0), on_press=self.start)
layout.add_widget(self.text)
layout.add_widget(fb)
self.add_widget(layout)
def start(self,event):
self.text.text = self.text.text+ "\nNew line"
class MainApp(App):
def build(self):
return Prin()
if __name__ == "__main__":
app = MainApp()
app.run()
The desired behavior is an image covering the whole screen, that's why I've put pos=self.center, size=(self.width, self.height)
This is the output:
So I have two questions:
1/ Why is the image appearing in the left bottom side ? What widget is actually there ? I am supposed to have only a BoxLayout with 2 widgets in a vertical orientation. I don't understand what is there.
2/ What should why put in size and pos to have the desired output ?
I would recommend putting all graphic elements in a .kv file, so there are fewer imports and it looks better.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
KV = ("""
<Prin>
BoxLayout:
orientation: 'vertical'
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: 'test.png'
Label:
id: label
text: 'TEXT'
Button:
text: 'Start!'
size_hint: None, None
size_hint: 0.5, 0.1
pos_hint: {'center_x': .5, 'center_y': .5}
padding: 10, 0
on_press: root.start()
""")
class Prin(BoxLayout):
Builder.load_string(KV)
def __init__(self, **kwargs):
super(Prin, self).__init__(**kwargs)
def start(self):
self.ids.label.text += "\nNew line"
class MainApp(App):
def build(self):
return Prin()
if __name__ == "__main__":
app = MainApp()
app.run()
If you still want to do this not in the kv file, then the problem is in self.size, by default, this value is [100, 100], only after calling the class and adding it to the main window it changes.
from kivy.core.window import Window
class Prin(BoxLayout):
def __init__(self, **kwargs):
super(Prin, self).__init__(**kwargs)
with self.canvas.before:
Rectangle(source='test.png', pos=self.pos, size=Window.size)
print(self.size) # [100, 100]
...
class MainApp(App):
def build(self):
self.screen = Prin()
return self.screen
def on_start(self):
print(self.screen.size) # [800, 600]
And don't forget about imports when you ask a question, the code should run without any manipulation
In response to your questions:
The image is appearing in that position because pos=layout.center is not a valid position and so instead sets it to a default value ([100, 100] I believe). To fix this, change pos=layout.center to pos=layout.pos
Your size is the default value also! This is getting a little technical but when you initialise your Prin class you are specifying the size of the Rectangle to be the current size of the BoxLayout. However, since it has not been initialised yet, the BoxLayout doesn't yet have a size! Again, Kivy handles this by giving it a default size.
Why are my Buttons and Labels correct?
Kivy automatically binds the children of a BoxLayout to the size and position of the BoxLayout. This binding ensures that when the position and size of the BoxLayout are changed, so too are the widgets within it (https://kivy.org/doc/stable/api-kivy.event.html).
Why doesn't Kivy bind the rectangle?
This has something to do with the canvas. The canvas is a drawing instruction shared by a widget, and not a property of any individual widget. Hence you'll programmatically bind your rectangle to the BoxLayout. (https://kivy.org/doc/stable/api-kivy.graphics.instructions.html)
How do I achieve this binding you speak of?
Two ways. Firstly (preferred), you can define your widgets in the KV language as this will automatically handle any binding you wish. Second, you can create an 'on_size' callback. Something like:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.graphics import Rectangle
class Prin(BoxLayout):
def __init__(self, **kwargs):
super(Prin, self).__init__(**kwargs)
layout = BoxLayout(orientation='vertical')
with self.canvas:
self.rect = Rectangle(source='test.png', pos=layout.pos, size=self.size)
self.text = Label(text='Press start')
fb = Button(text='Start!', size_hint=(0.5, 0.1), pos_hint={'center_x': .5, 'y': .5}, padding=(10, 0),
on_press=self.start)
layout.add_widget(self.text)
layout.add_widget(fb)
self.add_widget(layout)
def start(self, *_):
self.text.text = self.text.text + "\nNew line"
def resize(self, *_):
widgets = self.children[:]
self.canvas.clear()
self.clear_widgets()
with self.canvas:
self.rect = Rectangle(source='test.png', pos=self.pos, size=self.size)
for widget in widgets:
self.add_widget(widget)
on_size = resize
class TestApp(App):
def build(self):
return Prin()
if __name__ == "__main__":
app = TestApp()
app.run()
I just would like to add as a BIG P.S. although the above code solves your problem, it does so in probably the least efficient way imaginable. It is far better to define your widget in the kv file.

Kivy label texture_size is 0,0 after changing text

I have a label in my .kv file:
Label:
id: question
font_size: 40
center_x: root.center_x
center_y: root.center_y
I have the following in my root widget class:
class MainScreen(Widget):
question = ObjectProperty(None)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.set_question()
def set_question(self):
self.question.text = "placeholder"
print(self.question.texture_size)
def on_question(self,instance, value):
print(value.texture_size)
This returns [0,0] twice. I was under the impression that on_question would fire when the self.question.text changed, and that the value parameter would be the updated label, and thus with the correct texture_size. However, this is not the case and it appears that either texture_size is not updated, or that the print statement in on_question is called before texture_size is set.
How do I access texture_size after it is set?
This is an interesting problem as the docs recommend to bind on texture_size explicitly, which not worked for me. Furthermore a manuelly forced refresh with texture_update() did not work as well. So the only way I was able to get the texture size was (as already mentioned in the comments) with a Clock event. Here is my approach, maybe it helps you with your problem.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.lang.builder import Builder
from kivy.uix.label import Label
from kivy.clock import Clock
kv_string = """
<MainWidget>:
question: question_id
Label:
id: question_id
font_size: 40
center_x: root.center_x
center_y: root.center_y
"""
Builder.load_string(kv_string)
class MainWidget(Widget):
question = ObjectProperty(None)
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
self.question.bind(texture_size=self.on_question)
self.set_question()
def set_question(self):
self.question.text = "placeholder"
#print(self.question.texture_size)
def on_question(self, instance, value):
if isinstance(value, Label):
Clock.schedule_once(self.get_texture_size, 0)
def get_texture_size(self, dt):
print(self.question.texture_size)
class MyApp(App):
def build(self):
main = MainWidget()
return main
MyApp().run()

Kivy widget rectangle not moving

Hi there when i run my code , it is supposed to show a rectangle shape moving up and down. However, i can only get the rectangle shape in static mode (not moving). However, according to the function under the widget class, it should work. Any help will be aprreciated !
countdown.py file
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.uix.widget import Widget
from kivy.graphics import *
from kivy.clock import Clock
from kivy.uix.screenmanager import Screen, ScreenManager
class WindowManager(ScreenManager):
pass
class GameWindow(Screen):
def __init__(self, **kwargs):
super(GameWindow, self).__init__(**kwargs)
self.add_widget(Root())
self.add_widget(Rect())
class Root(Widget):
pass
class Rect(Widget):
velocity = ListProperty([10, 2])
def __init__(self, **kwargs):
super(Rect, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1/8)
def update(self, *args):
self.y += self.velocity[1]
if self.y < 0 or (self.y + self.height) > 250:
self.velocity[1] *= -1
kys = Builder.load_file("countdown.kv")
class MyMainApp(App):
def build(self):
return kys
if __name__ == "__main__":
MyMainApp().run()
countdown.kv file
WindowManager:
GameWindow:
<GameWindow>:
canvas:
Color:
rgba: 1, 0, 0, 1
Rectangle:
pos: self.pos
size: (42,15)
Rect:
pos: 85, 100
A Widget has some of the following characteristics:
If you are placed as a child of another Widget (Screen, Spinner, etc) this will take as much space as you can since the size_hint is (1,1), in your case it occupies the entire space of the Screen.
It cannot be distinguished since it does not have a background color or other visual element.
With the above mentioned it follows that even when "Rect" which is a Widget was moved this cannot be distinguished so we must use the canvas for its painting (I suppose that is what you tried when using the canvas in GameWindow), in addition to set a container independent size should be set the size_hint to (None, None).
Considering the above, the solution is:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.uix.screenmanager import Screen, ScreenManager
class WindowManager(ScreenManager):
pass
class GameWindow(Screen):
pass
class Rect(Widget):
velocity = ListProperty([10, 2])
def __init__(self, **kwargs):
super(Rect, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1. / 8.)
def update(self, *args):
self.y += self.velocity[1]
if self.y < 0 or (self.y + self.height) > 250:
self.velocity[1] *= -1
kys = Builder.load_file("countdown.kv")
class MyMainApp(App):
def build(self):
return kys
if __name__ == "__main__":
MyMainApp().run()
WindowManager:
GameWindow:
<GameWindow>:
Rect:
<Rect>:
size_hint : None, None
size : 42, 15
canvas:
Color:
rgba: 1, 0, 0, 1
Rectangle:
pos: self.pos
size: self.size

How to set a height to some buttons of GridLayout in Kivy Python?

I'm working in an app with kivy and I have an issue that involve the GridLayout. I have a screen with different rows and I want the buttons of the last row to have always the same height (11,1% of the height of the Screen). I have tried to modify the attribute height in the buttons but doesn't work properly. With size_hint_y works fine , but the fact is that i want to do with height because the screen won't have always the same number of rows (is responsive and it depends of the selections of previous screens). I attach here the code that I've done with the attribute height calculated through the command Window.height/9:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window
class LoginScreen(GridLayout):
def __init__(self,**kwargs):
super(LoginScreen, self).__init__(**kwargs)
self.cols=2
self.add_widget(Label(text='Subject'))
self.add_widget(Label(text=''))
self.add_widget(Label(text='1'))
self.add_widget(TextInput(multiline=False))
self.add_widget(Label(text='2'))
self.add_widget(TextInput(multiline=False))
self.add_widget(Label(text='3'))
self.add_widget(TextInput(multiline=False))
self.add_widget(Label(text='4'))
self.add_widget(TextInput(multiline=False))
b1=Button(text='Exit',background_color=[0,1,0,1],height=int(Window.height)/9.0) #doesn't work properly
self.add_widget(b1)
b2=Button(text='Run',background_color=[0,1,0,1],height=int(Window.height)/9.0) #doesn't work properly
self.add_widget(b2)
b1.bind(on_press=exit)
class SimpleKivy(App):
def build(self):
return LoginScreen()
if __name__=='__main__':
SimpleKivy().run()
I know it could be done with kivy language in a easier way but for my app is better to do in this way. If anyone knows how to fix this problem I would be very grateful.
If you want a widget in a grid/box layout to have a fixed size, you should set its size_hint to None first. And always use kivy lang at such tasks - no exceptions.
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
gui = '''
LoginScreen:
GridLayout:
cols: 2
Label:
text: 'Subject'
Label:
Label:
text: '1'
SingleLineTextInput:
Label:
text: '2'
SingleLineTextInput:
Label:
text: '3'
SingleLineTextInput:
Label:
text: '4'
SingleLineTextInput:
GreenButton:
text: 'Exit'
on_press: app.stop()
GreenButton:
text: 'Run'
<SingleLineTextInput#TextInput>:
multiline: False
<GreenButton#Button>:
background_color: 0, 1, 0, 1
size_hint_y: None
height: self.parent.height * 0.111
'''
class LoginScreen(Screen):
pass
class SimpleKivy(App):
def build(self):
return Builder.load_string(gui)
if __name__ == '__main__':
SimpleKivy().run()
Try this
class LoginScreen(GridLayout):
def __init__(self,**kwargs):
super(LoginScreen, self).__init__(**kwargs)
self.cols=2
self.add_widget(Label(text='Subject'))
self.add_widget(Label(text=''))
self.add_widget(Label(text='1'))
self.add_widget(TextInput(multiline=False))
self.add_widget(Label(text='2'))
self.add_widget(TextInput(multiline=False))
self.add_widget(Label(text='3'))
self.add_widget(TextInput(multiline=False))
self.add_widget(Label(text='4'))
self.add_widget(TextInput(multiline=False))
b1=Button(text='Exit',background_color=[0,1,0,1],size_hint_y=None, height=int(Window.height)/8.9)
self.add_widget(b1)
b2=Button(text='Run',background_color=[0,1,0,1],size_hint_y=None, height=int(Window.height)/8.9)
self.add_widget(b2)
b1.bind(on_press=exit)
Edited to change it to be at 11%.
And here one that keeps the button at 11% in response to the window size, where you redraw the Grid Layer whenever the window is resized (as by the bind to 'on_resize').
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
class LoginScreen(GridLayout):
def __init__(self,**kwargs):
super(LoginScreen, self).__init__(**kwargs)
#init and add grid layer
self.cols=2
self.layout = GridLayout(cols=self.cols)
self.add_widget(self.layout)
#function to set the buttons based on the current window size
self.set_content(Window.width, Window.height)
#bind above function to get called whenever the window resizes
Window.bind(on_resize=self.set_content)
def set_content(self, width, height, *args):
#first remove the old sized grid layer
self.remove_widget(self.layout)
#now build a new grid layer with the current size
self.layout =GridLayout(cols=self.cols)
self.layout.add_widget(Label(text='Subject'))
self.layout.add_widget(Label(text=''))
self.layout.add_widget(Label(text='1'))
self.layout.add_widget(TextInput(multiline=False))
self.layout.add_widget(Label(text='2'))
self.layout.add_widget(TextInput(multiline=False))
self.layout.add_widget(Label(text='3'))
self.layout.add_widget(TextInput(multiline=False))
self.layout.add_widget(Label(text='4'))
self.layout.add_widget(TextInput(multiline=False))
b1=Button(text='Exit',background_color=[0,1,0,1],size_hint_y=None, height=int(Window.height)/8.9)
self.layout.add_widget(b1)
b2=Button(text='Run',background_color=[0,1,0,1],size_hint_y=None, height=int(Window.height)/8.9)
self.layout.add_widget(b2)
b1.bind(on_press=exit)
#add the newly sized layer
self.add_widget(self.layout)
class SimpleKivy(App):
def build(self):
return LoginScreen()
if __name__=='__main__':
SimpleKivy().run()

Categories

Resources