My simplified code (there's also a GameWorld class which is the root widget):
class Player(Entity):
# done like this so `ammo` is a readonly property
_ammo = ListProperty()
ammo = AliasProperty(lambda self: self._ammo, bind=['_ammo'])
KV file:
<GameWorld>:
player: the_player
Player:
id: the_player
center: root.center
Label:
text: str(len(the_player.ammo))
top: root.top
The amount of ammo should be updated automatically on the screen. It doesn't. This is because ammo is bound to _ammo, but the latter never really changes. It always stays the same list object, with changing contents.
What would be the good practice Kivy solution to this problem?
EDIT:
Meanwhile, I found the following solution. I trigger the event associated with ammo myself. Anywhere in the code the ammo might change, I added this line:
self.__class__.ammo.dispatch(self)
I access ammo through self.__class__ because that gives me the ListProperty object, as opposed to the list. I then call dispatch(self) on it to broadcast that this property has changed. The GUI is then notified and updated.
It'll be awesome if anybody has a better way :)
You could add another AliasProperty:
ammo_count = AliasProperty(lambda self: len(self._ammo), bind=['_ammo'])
This will update every time _ammo changes its length.
Related
I'm kinda new to Kivy and I was looking for a way to animate the button when the cursor is over it.
I've tried to manage a way to get the mouse position and compare it with the button coordinates but no success at all.
This question has already been (mostly) answered at this post.There is a very nice example of this here by Olivier POYEN under LGPL license. Basically he has defined a class called HoverBehavior that you should inherit from to make a new class, e.g. HoverButton or HoverLabel (as his example shows). Then you have access to the on_enter and on_leave functions, which you can use to change the button's image, or change a label's text color, or whatever you need.
To answer your exact question, I would seek to understand the HoverBehavior class, then copy/paste it from the source above, then make a new class like so:
class HoverButton(Button, HoverBehavior):
def on_enter(self, *args):
self.background_normal = "some_image1.png" # Change the button's image when entered
def on_leave(self, *args):
self.background_normal = "some_other_image.png" # Change image when leaving
or you could use the kv language which looks even cleaner:
<HoverButton>:
background_normal: "some_image1.png" if self.hovered else "some_other_image.png"
just make sure you include a base class for the HoverButton in your python script if you use the 2nd option:
class HoverButton(Button, HoverBehavior):
pass
I have an assignment on classes. One of my tasks is as follows:
a. Augment the Tribute class by adding a new property, hunger, which will describe
the level of hunger for the Tribute. The initial value for hunger should be 0, as all
the Tributes will start the game with full stomach.
b. Create a method, get_hunger(), which return the current hunger level of the tribute.
c. Create a method, add_hunger(hunger), which will add a hunger value to the Tribute’s
hunger. When the hunger of a Tribute is equal or more than 100, he/she will
go_to_heaven(). (FYI go_to_heaven() is defined previously by other parent classes)
1)I wrote the following code, and when I tried running it I keep getting syntax error highlighted on the indentation right before self.get_hunger()+=hunger. May I know the reason for the syntax error since .get_hunger() is essentially self.hunger. self.get_hunger()=0 will work for other codes following this task but I don’t understand why self.get_hunger()+=hunger wont work. My lecturer stresses on not breaking the underlying layer of abstraction, which is why I would use the method .get_hunger() over attribute hunger, especially if I needed to get hunger value from instances of future child classes of Tribute, not sure if this concept is also embraced in practical situations.
class Tribute(Person):
def __init__(self, name, health):
super().__init__(name, health, -1)
self.hunger=0
def get_hunger(self):
return self.hunger
def add_hunger(self,hunger):
self.get_hunger()+=hunger #dk why can't assign to function call
if self.get_hunger()>=100:
self.go_to_heaven()
2)I also tried writing self.hunger+=hungerinstead of self.get_hunger()+=hunger to get past the syntax error and it works.However, I don’t find it intuitive why when defining a class method, and when I face a scenario where the name of the method parameter and the name of the class attribute is the same, the parameter will not overwrite the attribute in the form of hunger. Can anyone reason with me?
Assignments are performed on variables. That's just how Python works. Variables are references to objects in memory.
Function calls return objects, and you can't assign to an object.
I recommend using a setter method to handle the other side of the abstraction.
class Tribute(Person):
...
def get_hunger(self):
return self.hunger
def set_hunger(self, hunger):
self.hunger = hunger
def add_hunger(self,hunger):
self.set_hunger(self.get_hunger() + hunger)
if self.get_hunger() >= 100:
self.go_to_heaven()
Looks like you have abstraction already, since you're using a method to increase class field add_hunger() with health checking inside. Not using class field directly inside it's own method doesn't seem to have much sense.
You can't access class field self.hunger by using its method self.get_hunger().
Your method self.get_hunger() returns value of self.hunger (its copy), but not the variable itself. So you can add any number to that value, but you need to write it somewhere to keep its value. So, when you run self.get_hunger()+=hunger your method returns a copy of self.hunger, adds hunger from parameters to it and then this copy is lost, but self.hunger is the same.
So, if you want to increase self.hunger - you just need to use self.hunger+=hunger, which you checked already.
It would actually work if you would use the type of variable, that is passed by reference, not by value. Like list in this example, but I'd say it's kind of a perverted way to do so. ;)
class Tribute(Person):
def __init__(self, name, health):
super().__init__(name, health, -1)
self.hunger=[0]
def get_hunger(self):
return self.hunger
def add_hunger(self,hunger):
self.get_hunger()[0]+=hunger # dk why can't assign to function call
if self.get_hunger()[0]>=100:
self.go_to_heaven()
Using the same names for different things is not good. It can cause some errors. Even if one of them is variable and another one is method. If you try to pass that method to some thread later - it will never know which one you're passing there. If all names are different - it's safe and more readable.
I wish to retrieve the id of a widget in order to access its text attribute, in a widget class outside of the root class. I understand in order to affect or retrieve items with an id from kv-lang, the following syntax may be used:
self.ids.some_id.some_attribute = new_value
This works perfectly fine when used in the root class, as access is available to all its children. However in a widget class, the class only refers to the widget declared, so any ids outside of it are out of scope.
<Root>:
...
SomeButton:
...
TextInput:
id: some_id
What doesn't work:
class SomeButton(Button):
def on_press(self):
print(self.ids.some_id.text)
As I mentioned, this is understandable. But I don't know then what is used in this instance. Any assistance would be appreciated :)
The issue is that ids are local to the rule, not to the widget.
here your rule is declared for <Root>, so to access it you have to use a reference to this widget, not to the button.
If you want to give a reference to some_id to button, you can add a property to your button.
class SomeButton(Button):
target = ObjectProperty()
def on_press(self):
print self.target.text
and link them together in kv.
<Root>:
...
SomeButton:
target: some_id
TextInput:
id: some_id
I'm new in kivy but i think TextInput is not a child widget of a SomeButton but you are trying to access it from Button anyway. That's your problem.
Try self.parent.ids.some_id.text
Suppose I have two classes. The simple Square class:
class Square:
def __init__(self, side):
self.side = side
And the slightly more complex MyClass class:
class MyClass:
def __init__(self, square=None):
if square is None:
self.square = Square()
else:
self.square = square
self.rounded_side = round(self.square.side)
I instantiate a MyClass object like so:
myObj = MyClass()
In this situation, how can one achieve the following behavior?
Changing myObj.rounded_side to X, automatically changes myObj.square.side also to X.
Changing myObj.square.side to X, automatically changes myObj.rounded_side to round(X).
If possible, in a way that doesn't require any modifications to the Square class (this is a simplified version of the problem I'm currently facing; in the original version, I don't have access to the code for Square).
What I tried so far:
My first attempt was to transform rounded_side into a property. That makes it possible to obtain behavior 1. However, I fail to see how I can transform square also into a property, in a way that makes it possible to obtain behavior 2.
I also thought about making MyClass inherit from Square, so that both attributes are in the same depth, but then I'd lose some the desired structure of my class (I rather have the user access myObj.square.side, than myObj.side)
If someone is interested, the actual problem I'm facing:
I'm writing a game in pygame, and my Player class has an attribute for its position, which is an array with two floats (2D position). This is used for determining where the player is, and for deciding how to update it's position in the next update step of the game's physics.
However, I also want to have a Rect attribute in the Player class (which holds the information about a rectangle around the player's image), to be used when displaying the player in the screen, and when inferring collisions. The Rect class uses integers to store the position of the rectangle (pixel coordinates).
So as to be able to store the position information of the player in a float, but also use the Rect class for convenience, I thought about having this dependency between them, where changing one alters also the other accordingly.
As you've said, make rounded_side a property, but have it access the value in self.square for both getting and setting.
#property
def rounded_side(self):
return self.square.side
#rounded_side.setter
def rounded_side(self, side):
self.square.side = side
Now, setting rounded_side will use the setter which sets the value in square; setting the value on square directly will mean that it would be looked up from there by the property getter.
Classic common-sense programming says to separate the GUI code from the core processing. I started this way in Kivy, but ran into a problem in my first-round prototype.
deck.py
class Card:
def __init__(self, suit, value):
self.name = "%s %s" % (suit, value)
main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from deck import Card
class CardDisplay(BoxLayout):
card = ObjectProperty(Card("Default", 0))
class BoardDisplay(BoxLayout):
board = [[Card("Player1", 1),
Card("Player1", 2),
Card("Player1", 3),
Card("Player1", 4)],
[Card("Player2", 1),
Card("Player2", 2),
Card("Player2", 3),
Card("Player2", 4)]]
class GameApp(App):
pass
if __name__ in ("__main__", "__android__"):
GameApp().run()
game.kv
BoardDisplay:
orientation: "vertical"
BoxLayout:
CardDisplay:
card: root.board[0][0]
CardDisplay:
card: root.board[0][1]
CardDisplay:
card: root.board[0][2]
CardDisplay:
card: root.board[0][3]
BoxLayout:
CardDisplay:
card: root.board[1][0]
CardDisplay:
card: root.board[1][1]
CardDisplay:
card: root.board[1][2]
CardDisplay:
card: root.board[1][3]
<CardDisplay>:
Label:
text: root.card.name
Running this, I get an 8-card display as expected, but all of the cards are "Default 0". I think this is because I am using root.card.name, which is not a StringProperty, but just an attribute of the card class. However... what is the better way to do this? Am I really supposed to inherit from Widget (or something like it) in every class that contains something I'll want to display (in this case, Card)? Or is there a binding method I am failing to understand? I read through the Kivy docs and could swear it mentioned a problem just like this, but I wasn't able to find the reference again...
The problem is that root.card.name isn't a Property, so when you assign it (text: root.card.name) Kivy doesn't know to bind to anything. Binding happens automatically in kv, but it's not perfect. So here's an easy fix:
<CardDisplay>:
Label:
text: root.card and root.card.name
The result of the expression root.card and root.card.name will be the value of root.card.name, assuming root.card is assigned. But when Kivy reads that assignment, it sees that you are using root.card and will bind appropriately.
The key to using properties is knowing when you want to be notified about updates. You don't need root.card.name to be a StringProperty unless you want to know when that property is updated. In other words, if you change the Card instance used by CardDisplay, then it will update the Label. However, if you were to just update the name attribute of a Card, the Label would not update.
However, this applies equally to the board attribute on BoardDisplay. Updating this attribute will not update the display, since board isn't a property. But Kivy can handle lists of lists and provide notifications on updates:
board1 = ListProperty([Card("Player1", i) for i in range(4)])
board2 = ListProperty([Card("Player2", i) for i in range(4)])
board = ReferenceListProperty(board1, board2)
This way you will get notifications all around.
Oh, one thing I forgot to mention: if you do need to use properties on a non-Widget (like Card), you can extend from EventDispatcher to get properties to work. Kivy isn't just a UI, it's a framework. It's ok to use Kivy in non-UI code. If you have used data binding in .NET, you can think of Widget as a Control or UIElement and EventDispatcher as DependencyObject.