Kivy: how to change widget size using numericproperty? - python

Trying to make a brick widget change size each time it respawns.
The py file has
class Game(FloatLayout):
player = ObjectProperty(None)
playbutton = ObjectProperty(None)
ratebutton = ObjectProperty(None)
brickg = ObjectProperty(None)
ballsin = NumericProperty(0)
bricklist = ListProperty([])
score = NumericProperty(0)
switch = NumericProperty(0)
level = NumericProperty(0)
def __init__(self, *args, **kwargs):
super(Game, self).__init__(*args, **kwargs)
Clock.schedule_interval(self.update, 1./60)
def spawn_brick(self, *args):
b2 = BrickGreen(x=randint(50, self.width - 50), \
y=randint(self.height - self.height / 4, self.height - (self.height/13)))
self.bricklist.append(b2)
self.add_widget(b2)
def check_brick_spawn(self, *args):
if len(self.bricklist) == 0:
if self.level == 0:
BrickGreen.brickwidth = 100
self.spawn_brick()
elif self.level == 1:
BrickGreen.brickwidth = 75
self.spawn_brick()
else:
BrickGreen.brickwidth = 50
self.spawn_brick()
class BrickGreen(Widget):
def __init__(self, **kwargs):
super(BrickGreen, self).__init__(**kwargs)
brickwidth = NumericProperty(0)
and the kv file has
<BrickGreen>:
size_hint: None, None
size: self.brickwidth, 25
canvas:
Color:
rgba: 0, 1, 0, 1
Rectangle:
pos: self.pos
size: self.size
Essentially, level 0 should have bricks with length of 100, once it breaks it goes to level 1 and should spawn bricks with lenght of 75, but it's staying at 100

BrickGreen.brickwidth = 100
This replaces the NumericProperty at class level with the number 100. You need to set the value of brickwidth for instances of your objects, not the classes themselves. In this case, you could (for instance) pass the width you want to your spawn_brick method, and use it when instantiating the BrickGreen.
You also need to declare the NumericProperty at class level, not inside __init__.
I suggest reading the kivy documentation on properties and going through the examples to see how these should be used.

Related

How to make changes to global object attribute with Kivy TextInput and have it save for later use?

My app currently has 10 "players" with a name, offensive rating, and defensive rating. I want to give the user the opportunity to change the names of the players before running my game simulation code, but it appears I can only temporarily change the name inside of the current screen. Here's my current python code for when the user inputs the name from the gui:
class SecondWindow(Screen):
def pg1_name_val(self, widget):
pg1.name = widget.text
If I code in a print(pg1.name) right below that, it will print the correct name, but when the user hits "play" to shift to a results screen, the names go back to the default names.
How do I make pg1.name from my SecondWindow(Screen) become the global pg1.name? Is there a simple line of code I can use to return pg1.name as the global variable?
EDIT ----
So I've came a long way with my app this week, but now that I want to have a separate "results" screen, I've realized that I lost all of my data from the previous screen. It looks like all of the changes I make to my "players" are going back to defaults once the screen changes. I'll paste some code below for reference. Basically, my question now is this: Is there a way to keep the objects from one screen and return them back as the global instance of the object. Not just the local instance of the object.
.py -
class Player():
def __init__(self,name,off,deff):
self.name = name
self.off = off
self.deff = deff
self.stats = 0
class Team():
def init(self,pg, sg, sf, pf, c):
self.pg = pg
self.sg = sg
self.sf = sf
self.pf = pf
self.c = c
self.score = 0
self.results = None
self.to = 0
On the screen where I'm creating my team, I have a button to run the simulation. This button runs the sim correctly, records all of the game results in a string, prints all of the results to the console, attributes, and score correctly. The button also changes the screen to where I want to display a scroll view of the results string, but the results are empty.
I'd really appreciate any help on this, as I'm at a roadblock. Thanks!
It would kinda look the same. Depending on how exactly you wanna keep the 10 different players.
class Player():
def __init__(self, name, off, deff):
self.name = name
self.off = off
self.deff = deff
self.stats = 0
player = [Player('', 0, 0)] * 10
class SecondWindow(Screen):
def __init__(self, **kwargs):
super(SecondWindow, self).__init__(**kwargs)
self.player = player
def name_change(self):
self.player[0].name = self.ids.name.text
player[0].name = self.player[0].name
def next_screen(self):
self.manager.current = 'result'
class ResultWindow(Screen):
def __init__(self, **kwargs):
super(ResultWindow, self).__init__(**kwargs)
def on_enter(self, *args):
print(player[0].name)
class Manager(ScreenManager):
pass
class Player(App):
def build(self):
sm = ScreenManager()
sm.add_widget(SecondWindow(name='second'))
sm.add_widget(ResultWindow(name='result'))
return sm
if __name__ == '__main__':
Player().run()
You would have to work with OOP, here I created a list of you player object for all ten players it is an easy way to keep the code short.
You might like to keep them as 10 different objects the good part of a list it is easy to iterate through in a for loop and then get all the data for a stats board on a screen and also easy to expand later if you need 20 players or more.
I didn't change anything in the .kv file.
.kv
<SecondWindow>:
TextInput:
id: name
hint_text: 'Player name'
size: 260, 32
pos: (root.width / 2) - (self.width / 2), 248
multiline: False
on_text_validate: root.name_change()
Button:
id: create_btn
text: 'Next Screen'
size: 260, 32
pos: (root.width / 2) - (self.width / 2), 206
color: utils.get_color_from_hex('#ffffff')
background_color: utils.get_color_from_hex('#016bf8')
on_press: root.next_screen()
<ResultWindow>:
Label:
id: name
The on_text_validate only works with multiline false and when enter is pressed if you don't want to press enter user on_text instead.
--Edit--
I did some research this morning and thought maybe to update this a bit with my findings.
You could instead of doing it the import to all screens do it in the screenmanager and just call it from the screen like this:
pyhon.py
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
class Player:
def __init__(self, name, off, deff):
self.name = name
self.off = off
self.deff = deff
self.stats = 0
class SecondWindow(Screen):
def __init__(self, **kwargs):
super(SecondWindow, self).__init__(**kwargs)
def name_change(self):
self.manager.player.name = self.ids.name.text
def next_screen(self):
self.manager.current = 'result'
class ResultWindow(Screen):
def __init__(self, **kwargs):
super(ResultWindow, self).__init__(**kwargs)
def on_enter(self):
self.ids.name.text = self.manager.player.name
class Manager(ScreenManager):
player = Player('', 0, 0)
def __init__(self, **kwargs):
super(Manager, self).__init__(**kwargs)
class PlayerApp(App):
def build(self):
return Builder.load_file('test.kv')
if __name__ == '__main__':
PlayerApp().run()
the kv file would look something like this:
.kv
#:import utils kivy.utils
Manager:
SecondWindow:
name: 'second'
ResultWindow:
name: 'result'
<SecondWindow>:
TextInput:
id: name
hint_text: 'Player name'
size: 260, 32
size_hint: None, None
pos: (root.width / 2) - (self.width / 2), 248
multiline: False
on_text_validate: root.name_change()
Button:
id: create_btn
text: 'Next Screen'
size_hint: None, None
size: 260, 32
pos: 0, 0
color: utils.get_color_from_hex('#ffffff')
background_color: utils.get_color_from_hex('#016bf8')
on_press: root.next_screen()
<ResultWindow>:
Label:
id: name
hope it helps

Printing widgets Id in kivy

Is there a way to check or print a widgets id if you are not sure what id it has? I have a layout I made, its a simple BoxLayout where my Label has its id(its done in the kv file), than I use that Layout multiple times in a GridLayout. Now I have multiple Labels, but they technically have the same id or maybe kivy changes the id if you use it multiple times? Thats what I wanted to check by printining all their ids.
a3.py
from BareBones import Skeleton
menu = ['espresso', 'latte', 'capuccino', 'nescafe', 'Sahlep', 'Mahlep']
keys = []
class Double(BoxLayout):
pass
class NewLayout(GridLayout):
def set_text(self,text):
print(self.root.ids.Skeleton.ids['label_id'].text)
pass
class MainPage(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
iterator = 1
for i in range(len(menu)):
b = Button(size=(100,50))
self.ids[str(iterator)] = b
self.add_widget(b)
keys.append(str(iterator))
iterator = iterator+1
print(keys)
print(len(menu))
for x, y in zip(menu, keys):
self.ids[y].text = x
class RightSide(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
for i in range(len(menu)):
self.add_widget(Skeleton())
kv = Builder.load_file("b3.kv")
class MainApp(App):
def build(self):
return kv
MainApp().run()
b3.kv
NewLayout:
cols: 2
MainPage:
RightSide:
<MainPage>:
orientation: 'vertical'
<Skeleton>
b1: b1
b2: b2
label_id: label_id
Button:
id: b1
text:'+'
on_press: root.on_button_click_plus()
Label:
id: label_id
Button:
id: b2
text:'-'
on_press: root.on_button_click_minus()
BareBones.py
class Skeleton(BoxLayout):
count = 0
my_text = ''
drink_name = ''
def on_button_click_plus(self):
print("Button clicked")
self.count += 1
self.ids.label_id.text = str(self.count)
self.my_text = str(self.count)
return self.my_text
def on_button_click_minus(self):
print("Button clicked")
self.my_text = str(self.count)
self.ids.label_id.text = str(self.count)
if self.count > 0:
self.count -= 1
self.ids.label_id.text = str(self.count)
else:
pass
def trying(self):
pass
class PlusMinus(BoxLayout):
menu = ['espresso']
keys = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
menu = self.menu
self.add_widget(Skeleton())
So this basically looks like this:
What I basically want to do is have a button that collects all the labels texts (the numbers) So that I can pair them with the menu array to know how many of them were added to our basket. I also want to reset the labels however I dont know how I can get the texts that are so far deep in the code.
If you change the drink_name to a StringProperty in the Skeleton class:
class Skeleton(BoxLayout):
count = 0
my_text = ''
drink_name = StringProperty('')
and assign a value to that property when each Skeleton instance is created:
class RightSide(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
for i in range(len(menu)):
self.add_widget(Skeleton(drink_name=menu[i]))
Then you can get a tally of the number of drinks added with a method in the MainPage class:
def tally(self):
rightside = kv.ids.rightside
for skel in rightside.children:
if isinstance(skel, Skeleton):
print(skel.drink_name, skel.ids.label_id.text)
Plus one other required change. In your kv, add an id for the RightSide instance:
NewLayout:
cols: 2
MainPage:
RightSide:
id: rightside
So this is the error Pycharm shows, the red highlighting under kv.
class MainPage(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
iterator = 1
for i in range(len(menu)):
b = Button(size=(100,50))
self.ids[str(iterator)] = b
self.add_widget(b)
keys.append(str(iterator))
iterator = iterator+1
print(keys)
print(len(menu))
for x, y in zip(menu, keys):
self.ids[y].text = x
def tally(self):
rightside = self.kv.ids.rightside
for skel in rightside.children:
if isinstance(skel, Skeleton):
kv = Builder.load_file("b3.kv")
print(skel.drink_name, skel.ids.label_id.text)
class RightSide(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
for i in range(len(menu)):
self.add_widget(Skeleton(drink_name=menu[i]))
kv = Builder.load_file("b3.kv")
class MainApp(App):
def build(self):
return kv
MainApp().run()
So I changed the Stringproperty in the Skeleton file and the code looks like that with the changes you told me to add. However nothing happens, how do I actually use the tally() method?

How do I increase the width of the dropdown list within a spinner?

How do I increase the width of the dropdown list within a spinner? My button as you can see in the photo is small, and the values ​​in my drop-down list do not appear completely. I did a little research on the internet and I could see that some people say that I need to create a class spinner and add these features. But I don't know how to do this. Could someone show me a code example of how I do this?
main.kv (simplified code)
...
Spinner:
id: spinnerrpi
size_hint: None, None
width: '30sp'
height: '30sp'
border: 0,0,0,0
background_normal: 'seta1.png'
background_down: 'seta2.png'
values: "Branco Neve","Banco Gelo","Amarelo","Rosa Claro","Bege"
on_text: app.spinner_rpiso(spinnerrpi.text)
...
main.py (simplified code)
...
class PrimeiraJanela(Screen):
pass
class GerenciadorDeJanelas(ScreenManager):
pass
class MainApp(App):
texture = ObjectProperty()
def build(self):
self.title = 'MyApp'
self.texture = Image(source = 'wave.png').texture
sm = ScreenManager()
sm.add_widget(PrimeiraJanela(name = 'primeira'))
sm.current = 'primeira'
return sm
def spinner_rpiso(self, value):
if (value=='Branco Neve'):
self.root.get_screen('primeira').ids.rpi.text = str('0.90')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Banco Gelo'):
self.root.get_screen('primeira').ids.rpi.text = str('0.70')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Amarelo'):
self.root.get_screen('primeira').ids.rpi.text = str('0.70')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Rosa Claro'):
self.root.get_screen('primeira').ids.rpi.text = str('0.60')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Bege'):
self.root.get_screen('primeira').ids.rpi.text = str('0.60')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
def exit(self):
App.get_running_app().stop()
aplicativo = MainApp()
aplicativo.run()
Here is an extension of Spinner that honors an option_width property:
class SpinnerWithOptionWidth(Spinner):
option_width = NumericProperty(0) # the new property
def __init__(self, **kwargs):
self.invisible_attacher = None
super(SpinnerWithOptionWidth, self).__init__(**kwargs)
def on_is_open(self, instance, value):
# This method is modified from Spinner
attacher = self
if value:
if self.option_width > 0:
if self.invisible_attacher is None:
# The DropDown is the same width as the widget it attaches to
# so make an invisible widget with the desired width
self.invisible_attacher = Widget(opacity=0, size_hint=(None, None))
self.add_widget(self.invisible_attacher)
self.invisible_attacher.pos = (self.center_x - self.option_width/2, self.y)
self.invisible_attacher.size = (self.option_width, self.height)
attacher = self.invisible_attacher
# open th DropDown
self._dropdown.open(attacher)
else:
if self._dropdown.attach_to:
if self.invisible_attacher:
self.remove_widget(self.invisible_attacher)
self.invisible_attacher = None
self._dropdown.dismiss()

How to restart Kivy Animation

I am trying to create a simple countdown timer, i want to be able to restart it on the Button click, right now it won't start other, i just can not get it right. What am i missing here?
the KV
<RootWidget>:
#:import randint random.randint
orientation: "vertical"
CountDownLbl:
id: anim_label
text: "{0:.3f}".format(float(self.startCount - self.angle / 360))
font_size: 30
canvas:
Color:
rgb: 0,1,0
Line:
circle:self.center_x, self.center_y, 90, 0, self.angle % 360
width: 30
Button:
size_hint_y: 0.1
text: "Start"
on_press: anim_label.start()
and the code
COUNT=1
class RootWidget(FloatLayout):
pass
class CountDownLbl(Label):
startCount = COUNT
angle = NumericProperty(0)
def __init__(self, **kwargs):
super(CountDownLbl, self).__init__(**kwargs)
def start(self):
self.startCount = COUNT
self.anim = Animation(angle=360 * self.startCount, duration=self.startCount)
self.in_progress = True
self.anim.start(self)
class TestApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
TestApp().run()
The problem is that the first animation animates the angle property to 360, and further animations try to animate the angle property to 360 again, which results in no actual animation. The fix is to increment your COUNT after each animation, like this:
def start(self):
global COUNT
self.startCount = COUNT
self.anim = Animation(angle=360 * self.startCount, duration=self.startCount)
self.in_progress = True
self.anim.start(self)
COUNT += 1
Answering my own quesstion, Kudos to John.
Thank's John Anderson. This is the final version. Most important, i am updating text in a function instead of doing it in the KV, and second the increment has to be by TIMER value and not by 1. Latest example can be found here https://github.com/vmindru/amb_pit_timer/blob/main/simple_timer.py
code
TIMER_DURATION = 5
class CountDownLbl(Label):
angle = NumericProperty(0)
timer_duration = NumericProperty(0)
def __init__(self, **kwargs):
super(CountDownLbl, self).__init__(**kwargs)
self.anim_duration = self.timer_duration
self.in_progress = False
def start(self):
if not self.in_progress:
self.anim = Animation(angle=360 * self.anim_duration, duration=self.timer_duration)
self.in_progress = True
self.anim.bind(on_complete=self.finish, on_progress=self.update_timer)
self.anim.start(self)
self.anim_duration += TIMER_DURATION
def finish(self, animation, widget):
widget.text = "FINISHED"
self.in_progress = False
def update_timer(self, animation, widget, progression):
text = ((self.timer_duration * 60000)*(1-progression))/60000
widget.text = "{0:.3f}".format(float(text))
kv
CountDownLbl:
id: anim_label
timer_duration: 5
font_size: 30
text: "{}.000".format(self.timer_duration)
canvas:
Color:
rgb: 0,1,0
Line:
circle:self.center_x, self.center_y, 90, 0, self.angle % 360
width: 30

Kivy Bing images layout

I'm trying to make something like a "Bing images" layout.
That is:
Images are divided into several columns of the same width.
All images are the same width.
Images are added down in such a way that the images added first are
at the top of the layout.
Layout can be added to ScrollView to scroll with mousewheel
I did not find a way to do this using Stack Layout, so I decided to create my own layout.
I stopped here:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.properties import NumericProperty
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
KV = '''
#:import Window kivy.core.window.Window
ScrollView
size_hint: (1, None)
size: Window.size
MyLayout
id:my_l
Button
text:'1'
Button
size_hint_y: None
height: 900
text:'2'
Button
text:'3'
Button
text:'4'
Button
text:'5'
size_hint_y: None
height: 900
<MyLayout>:
#height: self.minimum_height
cols: 3
spacing: 10
size_hint_y:None
row_width: 300
'''
class MyLayout(FloatLayout):
def __init__(self, **kwargs):
super(MyLayout, self).__init__(**kwargs)
cols = NumericProperty(3)
row_width = NumericProperty(300)
spacing = NumericProperty(0)
def do_layout(self, *args):
self.i = 0
self.last_x = [self.height]*self.cols
for child in self.children[::-1]:
child.width = self.row_width
if isinstance(child, Image):
child.height = child.width / child.image_ratio
child.size_hint_y= None
child.size_hint_x= None
self.i+=1
if self.i == self.cols+1: self.i = 1
child.x = self.x+(self.i-1)*(self.row_width+self.spacing)
child.y = self.last_x[self.i-1]-child.height
self.last_x[self.i-1]-=child.height+self.spacing
def on_pos(self, *args):
self.do_layout()
def on_size(self, *args):
self.do_layout()
def add_widget(self, widget):
super(SuperGrid, self).add_widget(widget)
self.do_layout()
def remove_widget(self, widget):
super(SuperGrid, self).remove_widget(widget)
self.do_layout()
class MyApp(App):
def build(self):
self.root = Builder.load_string(KV)
Window.bind(on_dropfile=self.add)
def add(self, *args):
name= list(args)[1]
self.root.ids.my_l.add_widget(Image(source=name))
MyApp().run()
It is already partially working (you can run it and dragndrop some images from your folders to see what I'm about), but the problem is that I don't understand how to connect a ScrollView to it.
It looks like I need to add a line with something like height: self.minimum_height to KV string.
but it’s not clear where in layout class I need to calculate minimum_height.
How to make the code work with ScrollView?
You just need to calculate the height of your MyLayout instance. In your kv file add:
size_hint: (1, None)
in your MyLayout section
Then, in the do_layout method, calculate the height of your MyLayout. Do a self.height = just once at the end do_layout (to avoid infinite loop due to on_size method). For example here is a modified version of your do_layout:
def do_layout(self, *args):
self.i = 0
col_heights = [0] * self.cols # keeps track of the height of each column
self.last_x = [self.height]*self.cols
for child in self.children[::-1]:
child.width = self.row_width
if isinstance(child, Image):
child.height = child.width / child.image_ratio
child.size_hint_y= None
child.size_hint_x= None
self.i+=1
if self.i == self.cols+1:
self.i = 1
col_heights[self.i-1] += child.height + self.spacing
child.x = self.x+(self.i-1)*(self.row_width+self.spacing)
child.y = self.last_x[self.i-1]-child.height
self.last_x[self.i-1]-=child.height+self.spacing
if len(self.children) > 0:
self.height = max(col_heights)

Categories

Resources