Center widgets in Kivy - python

I am learning to use Kivy, so I walked through the Pong tutorial and started messing around with the code. So, I removed everything but the bouncing ball and decided to generate multiple balls on demand. The problem I am having is that while I can place balls where I want them when application is already running (for example, adding a ball on touch works fine), but when I add balls in the app build() they don't get placed right. Here is the code I have. The balls placed on touch, correctly start from the center. But the ball added in build() starts from the lower left corner. Why? I wanted to add more moving widgets with different properties, but I cannot seem to figure out how to place them on application start.
#:kivy 1.0.9
<World>:
canvas:
Ellipse:
pos: self.center
size: 10, 10
<Agent>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
from random import randint
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty, ListProperty
from kivy.vector import Vector
from kivy.clock import Clock
class World(Widget):
agents = ListProperty()
def add(self):
agent = Agent()
agent.center = self.center
agent.velocity = Vector(4, 0).rotate(randint(0, 360))
self.agents.append(agent)
self.add_widget(agent)
def on_touch_down(self, touch):
self.add()
def update(self, dt):
for agent in self.agents:
agent.move()
if agent.y < 0 or agent.top > self.height:
agent.velocity_y *= -1
if agent.x < 0 or agent.right > self.width:
agent.velocity_x *= -1
class Agent(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 WorldApp(App):
def build(self):
world = World()
# add one ball by default
world.add()
Clock.schedule_interval(world.update, 1.0/60.0)
return world
if __name__ == '__main__':
WorldApp().run()

Found the answer. The default widget size is 100, 100. By the time I add the initial ball, the World widget is not rendered and therefore has a default size. But it is possible to pass the windows size in the Widget constructor. So changing the World instantiation to
world = World(size=Window.size)
solved the problem

Related

Kivy Widget not moving

When the program runs, the ScreenManager shows the Main screen on which I added the white little square(Ball).
The widget Ball should be moving around but I cannot figure out why it is still static.
The update method is working but the position of the widget is not being updated. I tried to move things around and no effect.
If someone could help me to understand where I am wrong, would be great. Thx so much.
import kivy
kivy.require('1.10.1')
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.screenmanager import ScreenManager,Screen
from kivy.graphics import Rectangle
from kivy.uix.widget import Widget
from kivy.properties import ListProperty
from kivy.clock import Clock
# simple Screen on which the ball should move
class Main(Screen):
def __init__(self, **kwargs):
super(Main, self).__init__(**kwargs)
self.add_widget(Ball())
with self.canvas.before:
Rectangle(source = 'BG1.png',
size = Window.size,
pos = self.pos)
# the Ball should be bouncing around
class Ball(Widget):
velocity = ListProperty([10, 15])
def __init__(self, **kwargs):
super(Ball, self).__init__(**kwargs)
Clock.schedule_interval (self.update, 1 / 60)
with self.canvas:
Rectangle (color=[0, 0, 0, 1],
size=(10, 10),
pos = self.pos)
def update(self, *args):
print('wtf')
self.x += self.velocity[0]
self.y += self.velocity[1]
if self.x < 0 or (self.x + self.width) > Window.width:
self.velocity[0] *= -1
if self.y < 0 or (self.y + self.height) > Window.height:
self.velocity[1] *= -1
Window.size = (400, 300)
sm = ScreenManager()
sm.add_widget(Main(name = 'main'))
class Bubble(App):
def build(self):
return sm
if __name__ == '__main__':
Bubble().run()
Thank you Embryo and John, your answers led me to find the right answer. I found this code on this blog. Solved it all:Kivy blog
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color
class CornerRectangleWidget(Widget)
def __init__(self, **kwargs):
super(CornerRectangleWidget, self).__init__(**kwargs)
with self.canvas:
Color(1, 0, 0, 1) # set the colour to red
self.rect = Rectangle(pos=self.center,
size=(self.width/2.,
self.height/2.))
self.bind(pos=self.update_rect,
size=self.update_rect)
def update_rect(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size
The problem is that when you set up Canvas instructions in python (rather than in kv), you don't get the automatic bindings that kv does for you. So the Rectangle that you define in the Ball.__init__() method gets its pos defined as the pos of the Ball at its __init__() moment (which is [0,0]), and it does not change automatically. You can fix that by doing the update yourself. First, in your Ball.__init__() change your with self.canvas block to:
with self.canvas:
Color(0,0,0,1)
self.rect = Rectangle (size=(10, 10),
pos = self.pos)
One change is defining the Color, and the second is creating a reference to the Rectangle. Then in your update method add the line:
self.rect.pos = self.pos
This will move the rectangle with the Ball position.
Also, your update method has some problems because the size of the Ball is the same size as the Window. It is not the size you provide in the Rectangle.
Well, what is happening is this...
You are drawing the rectangle once, when you instantiate the Ball widget.
This is what you see in the bottom left corner.
After that you move the widget, but you don't draw it again.
If you print(self.pos) in the update function, you'll see it moving...

How to convert Kivy touch corordinates to widget space

i have a simple ModalView and its size is (640,426).My window's size is (1366,732).My Screen Resolution is (1366,768).When i click at the top left corner of the ModalView,i get something like 363,690.Which is my touch coordinates taken from the window itself.i would however like to somehow convert this value to local widget space so that touching the top left corner i get the coordinate (0,0) instead of (363,690).Is this possible with kivy or any other way.What Im trying to do,for those interested is to crop an image using a box drawn by the user.Drawing the box isn't the problem,the problem is getting those bounds and transfering them to the image's coords.
NB: I read about the to_local(),to_parent(),to_window() and those functions are simply not working...for some reason,maybe i missed a thing there,would appreciate your help big time
Here is the code similar to my usecase but stripped
from kivy.app import App
from kivy.uix.modalview import ModalView
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.label import Label
class CropBounds(ModalView):
def __init__(self, **kwargs):
super(CropBounds, self).__init__(**kwargs)
self.to_crop = True
self.size = (400,400)
print('Center: ',self.center)
print('Size: ',self.size)
print('Window Center: ',Window.center)
print('Window Size:(',Window.width,',',Window.height,')')
def on_touch_down(self, touch):
self.canvas.clear()
if self.collide_point(*touch.pos) and self.to_crop:
with self.canvas:
self.start_x = touch.x
self.start_y = touch.y
touch.ud['area'] = Line(points=(touch.x, touch.y, touch.x, 400,touch.x, touch.y,touch.x, touch.y, touch.x, touch.y))
print("Pos: ",touch.pos)
print(touch.x,touch.y)
return True
return MainWindow().on_touch_down(touch)
class GalleryWindow(BoxLayout):
def __init__(self, **kwargs):
super(GalleryWindow, self).__init__(**kwargs)
self.add_widget(Button(text='crop',size_hint=(1,None),size=(None,40),on_release=self.crop_img))
def crop_img(self):
bounds = CropBounds()
bounds.open()
class GalleryApp(App):
def build(self):
return GalleryWindow()
if __name__=='__main__':
GalleryApp().run()
Subtracting the ModalView position from the touch coordinates does work. I think your are getting confused about the size and position of your ModalView. The way your code is written, the ModalView is the same size and position as your GalleryWindow (recall that the default size_hint is (1.0, 1.0)). So, for there to be any difference between coordinates in the ModalView and GalleryWindow, you need to change the size_hint for the ModalView.
After correcting many errors in your code (to get it to run). I have made some changes to demonstrate the position of the ModalView and the location of the touch.
Here is the code:
from kivy.app import App
from kivy.core.window import Window
from kivy.graphics.vertex_instructions import Line
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.modalview import ModalView
from kivy.uix.button import Button
class CropBounds(ModalView):
def __init__(self, **kwargs):
super(CropBounds, self).__init__(**kwargs)
self.to_crop = True
self.size_hint = (None, None) # without this, the size below has no effect
self.size = (400,400)
print('Center: ',self.center)
print('Size: ',self.size)
print('Window Center: ',Window.center)
print('Window Size:(',Window.width,',',Window.height,')')
def on_touch_down(self, touch):
if self.collide_point(*touch.pos) and self.to_crop:
self.canvas.clear()
with self.canvas:
# plot the boundary of the ModalView
Line(points=[self.pos[0], self.pos[1],
self.pos[0], self.pos[1] + self.height,
self.pos[0] + self.width, self.pos[1] + self.height,
self.pos[0] + self.width, self.pos[1],
self.pos[0], self.pos[1]])
# plot a line from the touch point to the pos of the ModalView
Line(points=[self.pos[0], self.pos[1], touch.x, touch.y])
# calculate touch minus position of ModalView
touch_in_modal = (touch.x - self.pos[0], touch.y - self.pos[1])
print('touch : ' + str(touch.pos) + ', touch in modal: ' + str(touch_in_modal))
return True
#return MainWindow().on_touch_down(touch)
class GalleryWindow(BoxLayout):
def __init__(self, **kwargs):
super(GalleryWindow, self).__init__(**kwargs)
self.add_widget(Button(text='crop',size_hint=(1,None),size=(40,40),on_release=self.crop_img))
def crop_img(self, *args):
bounds = CropBounds()
bounds.open()
class GalleryApp(App):
def build(self):
return GalleryWindow()
if __name__=='__main__':
GalleryApp().run()
If you want to see what was happening in your code, just comment out the size_hint line.
Also, when we ask you to post a MCV example, please try running what you post. If we have to debug your example before we can see what you problem is, you won't get many responses.

Cannot get kivy to display the relevant canvas and items for a breakout game

I am trying to create a breakout game using Python and Kivy. I have tried displaying other kivy features like labels and buttons and they all work perfectly. However, when I try to run the below code I keep getting a blank screen. Any help would be appreciated.
BreakoutApp.py
from kivy.app import App # App is base for any kivy app
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.modalview import ModalView
from kivy.properties import (ListProperty, NumericProperty, ObjectProperty, StringProperty)
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
from kivy.uix.label import Label
from kivy.uix.scatter import Scatter
import random
__version__ = '0.1' # used in Android compilation
#Game is a big widget that will contain the entire game
# A subclass of Float layout as it will proportion its children in proportion to its own pos and size
class Game(FloatLayout): # Will contain everything
blocks = ListProperty([])
player = ObjectProperty() # The game's player instance
ball = ObjectProperty() # The game's ball instance
def setup_blocks(self):
for y_jump in range(5):
for x_jump in range(10):
print "in setup blocks"
block = Block(pos_hint={
'x': 0.05 + 0.09*x_jump,
'y': 0.05 + 0.09*y_jump})
self.blocks.append(block)
self.add_widget(block)
# App will initialise everything that kivy needs
class BreakoutApp(App):
def build(self):
print "in build self blocks"
g = Game()
g.setup_blocks()
return g
#f = FloatLayout()
#s = Scatter()
#l = Label(text="Hello!",
# font_size=150)
#f.add_widget(s)
#s.add_widget(l)
#return f
#Require a class for each game object
#kivy properties are special attributes declared at class level
class Player(Widget): # A moving paddle
position = NumericProperty(0.5)
direction = StringProperty("none")
def __init__(self, **kwargs):
super(Player, self).__init__(**kwargs)
with self.canvas:
Color(1, 0, 0, 1)
Rectangle(pos=self.pos, size=self.size)
class Ball(Widget): # A bouncing ball
# pos_hints are for proportional positioning, see below
pos_hint_x = NumericProperty(0.5)
pos_hint_y = NumericProperty(0.3)
proper_size = NumericProperty(0.)
velocity = ListProperty([0.1,0.5])
class Block(Widget): # Each coloured block to destroy
colour = ListProperty([1, 0, 0])
def __init__(self, **kwargs):
super(Block, self).__init__(**kwargs)
self.colour = random.choice([
(0.78,0.28,0), (0.28,0.63,0.28), (0.25,0.28,0.78)])
if __name__ == "__main__":
print "main method called"
BreakoutApp().run()
Breakout.kv
#:kivy 1.9.1
<Player>:
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
<Ball>:
canvas:
Color:
rgb: 1, 0.55, 0
Rectangle:
pos: self.pos
size: self.size
<Block>:
canvas:
Color:
rgb: self.color #property defined above
Rectangle:
pos: self.pos
size: self.size
Color:
rgb: 0.1, 0.1, 0.1
Line:
rectangle:
[self.x, self.y, self.width, self.height]
Also, do I need to explicitly refer to the layout .kv file in the python file or are there any specific naming restrictions. I found some documentation online to name the two as found below.
You have in kv file color
<Block>:
canvas:
Color:
rgb: self.color #property defined above
yet in py file there is colour (with U):
class Block(Widget): # Each coloured block to destroy
colour = ListProperty([1, 0, 0])
If you change it, you'll get the desired output I believe:
<Block>:
canvas:
Color:
rgb: self.colour #property defined above
Proof:

Kivy widgets behaving erratically

I have been playing around with the Kivy Pong tutorial, getting up to speed with the framework, seeing if I could implement a few ideas. I have removed most of the Pong functionality, so I could have only bouncing ball on the screen and added some code to generate multiple bouncing balls on the screen, generated on touch. That worked fine. I then added some extra canvas instructions, so I would have a line drawn indicating the direction the ball is moving. This is where things got weird. The first ball acts just as it should, bouncing around the screen. But any following clicks generate balls that go off screen, randomly change direction and speed and in general behave chaotically. I have been looking at my code and I cannot seem to find any indication of what might be going wrong. I keep all the references to the widgets, I add them to the root widget, I don't seem to be sharing any information between them... Anyway, here is the code, maybe someone can enlighten me. Using latest kivy and python 3.6.
from random import randint
from kivy.app import App
from kivy.clock import Clock
from kivy.config import Config
from kivy.vector import Vector
from kivy.uix.widget import Widget
from kivy.properties import AliasProperty, ListProperty, NumericProperty, ReferenceListProperty
class Playground(Widget):
critters = ListProperty([])
def update(self, dt):
for critter in self.critters:
critter.move()
if (critter.y self.height):
critter.v_y *= -1
if (critter.x self.width):
critter.v_x *= -1
self.score.text = "{}".format(len(self.critters))
def on_touch_down(self, touch):
critter = Critter()
critter.pos = touch.x, touch.y
self.critters.append(critter)
self.add_widget(critter)
class Critter(Widget):
angle = NumericProperty(0)
v_x = NumericProperty(0)
v_y = NumericProperty(0)
velocity = ReferenceListProperty(v_x, v_y)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.velocity = Vector(5, 0).rotate(randint(0, 360))
self.angle = Vector(*self.velocity).angle(Vector(1, 0))
def move(self):
self.pos = Vector(*self.velocity) + self.pos
self.angle = Vector(*self.velocity).angle(Vector(1, 0))
class WorldApp(App):
def build(self):
game = Playground()
Clock.schedule_interval(game.update, 1.0/60.0)
return game
if __name__ == '__main__':
Config.set('kivy', 'desktop', 1)
Config.set('kivy', 'exit_on_escape', 1)
Config.set('graphics', 'resizable', 0)
WorldApp().run()
and the KV file
<Playground>
score: score
canvas:
Color:
rgb: 0.0, 0.1, 0.0
Rectangle
pos: self.pos
size: self.size
Label:
id: score
pos: self.parent.width - self.size[0], self.parent.height - self.size[1]
font_size: 16
size: self.texture_size
<Critter>
size: 30, 30
canvas:
Rotate:
angle: self.angle
origin: self.center
axis: 0, 0, 1
Color:
rgb: 0.5, 0.0, 0.0
Ellipse:
pos: self.pos
size: self.size
Color:
rgb: 1, 1, 0.0
Line:
width: 2
points: self.center[0], self.center[1], self.center[0] + self.size[0] / 2, self.center[1]
I'm not sure if it's causing your problem, but your Rotate instructions aren't bounded by the widget rule and will affect any later widgets - so the Rotate of each Critter is applied to every later one.
To avoid this, add PushMatrix: at the top of the canvas rule and PopMatrix: at the bottom. These instructions effectively save and later revert to the initial rotation state before your change.

How to fix aspect ratio of a kivy game?

I've started playing with Kivy, but I'm rather new to all this and am already struggling.
I'm trying to make a board game - I want the window to be resizeable, but I don't want resizing the window to mess up the aspect ratio of the game (so in other words, I want the window to have black bars above or to the sides of the content if the window is resized to something other than the intended aspect ratio of the content)
The easiest way I could see to ensure this is to either:
a) Lock the aspect ratio of the Window itself so it's always 10:9 (the aspect ratio of the board and all on-screen elements) - even when fullscreen.
or
b) Use some sort of widget/surface/layout that is centered on the window and has a locked 10:9 aspect ratio. I then subsequently use this as the base onto which all my other images, widgets, grids etc are placed.
However, I really don't know how to do either of these. I'm not sure if I can lock the Window aspect ratio, and the only kivy-specific object I've found that lets me lock the aspect ratio is from kivy.graphics.image... which I don't seem to be able to use as a 'base' for my other stuff.
EDIT: So far I've written the code below: it creates a layout (and colours it slightly red) that 'fixes' its own aspect ratio whenever it is resized. However, it still isn't centered in the window, and more problematic, it results in an endless loop (probably because the aspect fix code corrects the size, but then kivy 'corrects' its size back to being the size of the window, triggering the aspect fix again, thought I could be wrong).
EDIT: Modified the code again, but it's still an endless loop. I though that referring only to the parent for size info would fix that, but apparently not.
I'd appreciate anyone helping me fix my code.
Code below:
test.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import (ObjectProperty,
NumericProperty,
ReferenceListProperty)
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
class Board(AnchorLayout):
pass
class BackgroundLayout(RelativeLayout):
def FixAspectRatio(self, *args):
correctedsize = self.parent.size
if correctedsize[0] > correctedsize[1]*(10/9):
correctedsize[0] = correctedsize[1]*(10/9)
elif correctedsize[0] < correctedsize[1]*(10/9):
correctedsize[1] = correctedsize[0]/(10/9)
return correctedsize
class test(App):
game = ObjectProperty(None)
def build(self):
self.game = Board()
return self.game
if __name__ == '__main__':
test().run()
test.kv
<Board>
BackgroundLayout:
canvas.before:
Color:
rgba: 1, 0, 0, 0.5
Rectangle:
size: self.size
pos: self.pos
size: self.FixAspectRatio(self.parent.size)
pos: self.parent.pos
One approach would be to create a Layout that always maximize the children given an aspect ratio. Here is an example::
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import NumericProperty
kv = """
ARLayout:
Widget:
canvas:
Color:
rgb: 1, 0, 0
Rectangle:
size: self.size
pos: self.pos
"""
class ARLayout(RelativeLayout):
# maximize the children given the ratio
ratio = NumericProperty(10 / 9.)
def do_layout(self, *args):
for child in self.children:
self.apply_ratio(child)
super(ARLayout, self).do_layout()
def apply_ratio(self, child):
# ensure the child don't have specification we don't want
child.size_hint = None, None
child.pos_hint = {"center_x": .5, "center_y": .5}
# calculate the new size, ensure one axis doesn't go out of the bounds
w, h = self.size
h2 = w * self.ratio
if h2 > self.height:
w = h / self.ratio
else:
h = h2
child.size = w, h
class TestApp(App):
def build(self):
return Builder.load_string(kv)
TestApp().run()
I managed to find a solution I was happy with with a lot of help from kived from the kivy chat.
The below bases the game bounds on a 'background' image, then places a Relative Layout over this background image. For all subsequent children, the coordinate (0, 0) will refer to the bottom left of the (aspect locked) background image, and they can use the 'magnification' property of the Background to adjust their size/position according to the screen size.
Provided any gamebounds.png image, the below code will implement this. Note that the RelativeLayout is shaded red (with 50% transparency) just to show its position (and show that it matches the gamebounds.png image size and position at all times):
test.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import (ObjectProperty,
NumericProperty,
ReferenceListProperty,
ListProperty)
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
class Game(FloatLayout):
pass
class Background(Image):
offset = ListProperty()
magnification = NumericProperty(0)
class Bounds(RelativeLayout):
pass
class test(App):
game = ObjectProperty(None)
def build(self):
self.game = Game()
return self.game
if __name__ == '__main__':
test().run()
test.kv
<Game>
Background:
id: BackgroundId
#Provide the image that will form the aspect-locked bounds of the
#game. Image widgets have aspect_lock set to True by default.
source: 'gamebounds.png'
#All the image to stretch to fill the available window space.
allow_stretch: True
size: self.parent.size
#Find the coordinates of the bottom-left corner of the image from
#the bottom-left corner of the window. Call this the 'offset'.
offset: [self.center_x - (self.norm_image_size[0]/2),
self.center_y - (self.norm_image_size[1]/2)]
#Find out the factor by which the image is magnified/shrunk from
#its 'default' value:
magnification: self.norm_image_size[0] / self.texture_size[0]
Bounds:
#The canvas below isn't needed, it's just used to show the
#position of the RelativeLayout
canvas:
Color:
rgba: 1, 0, 0, 0.5
Rectangle:
size: self.size
pos: (0, 0)
#Set the position of the RelativeLayout so it starts at the
#bottom left of the image
pos: self.parent.offset
#Set the size of the RelativeLayout to be equal to the size of
#the image in Background
size: self.parent.norm_image_size

Categories