I'm trying to rotate a widget and then use the method collide_point to check if I'm touching the widget or not
The problem is that when I rotate the widget, the canvas (image) works fine, but the widget itself doesn't seems to rotate, so the check fails
.py:
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget
class Game(Widget):
pass
def on_touch_down(self, touch):
if self.player.collide_point(*touch.pos):
print("collide")
class TestApp(App):
def build(self):
Window.size = 300, 300
self.game = Game()
return self.game
if __name__ == "__main__":
TestApp().run()
.kv:
<Game>:
player: player
Widget:
id: player
angle: 50
size: 40, 200
canvas:
Color:
rgba: 1,0.5,0,1
Rectangle:
pos: self.pos
size: self.size
canvas.before:
PushMatrix
Rotate:
axis: 0, 0, 1
angle: self.angle
origin: self.right, self.top
canvas.after:
PopMatrix
I expect to print collide when I touch in the image, but only print collide when I touch where the image was before the rotation
edited:
I must do someting like this? (it works, but there is not an easy way?)
def on_touch_down(self, touch):
top_center = Vector(self.player.center_x, self.player.top)
touch_pos = Vector(*touch.pos)
top2touch = touch_pos - top_center
top2touch_rotate = top2touch.rotate(-self.player.angle)
correct_touch = top_center + top2touch_rotate
if self.player.collide_point(*correct_touch):
print("collide")
Related
I am trying to add buttons to a kivy gridlayout with a rectangle shape above each of them. But all the rectangles seem to stack up at the bottom left of the screen, nowhere near the buttons. I think multiple rectangles are being created, but all of them are placed above each other, and not over the button. How do I fix it?
main.py:
from kivymd.app import MDApp
from kivy.app import App
from kivy.graphics import Ellipse, Rectangle, Color
from kivy.uix.button import ButtonBehavior, Button
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from kivy.metrics import dp, sp
Window.size = (550, 730)
class Recta(Button):
def __init__(self, **kwargs):
super().__init__(**kwargs)
with self.canvas.after:
Color(1., 1., 0)
Rectangle(pos=self.pos, size=(self.width, self.height))
class Grid(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.rows = 8
self.cols = 8
self.size_hint = None, None
if Window.height > Window.width:
self.width = Window.width
self.height = Window.width
elif Window.height < Window.width:
self.width = Window.height
self.height = Window.height
elif Window.height == Window.width:
self.width = Window.width
self.height = Window.height
self.pos_hint = {"center_x": 0.5, "center_y": 0.5}
for i in range(64):
self.add_widget(Recta())
class TestApp(MDApp):
def build(self):
grid_w = Grid()
return grid_w
if __name__ == '__main__':
TestApp().run()
The problem is that you are drawing the Rectangle in the __init__() method using pos and size of the Recta widget. In the __init__(), the pos and size of a widget have not yet been set, and are still the default values of (0,0) and (100,100).
You can fix this in either of two ways. The easiest is just to define the Rectangle using kv like this:
Builder.load_string('''
<Recta>:
canvas.after:
Color:
rgb: 1,1,0
Rectangle:
pos: self.pos
size: self.size
''')
and remove the drawing of the Rectangle from the __init__() method
The second way is to create the bindings (that kv does automatically) like this:
class Recta(Button):
def __init__(self, **kwargs):
super().__init__(**kwargs)
with self.canvas.after:
Color(1., 1., 0)
self.rect = Rectangle(pos=self.pos, size=(self.width, self.height))
self.bind(pos=self.update)
self.bind(size=self.update)
def update(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size
I have a couple of questions:
I have animation of spinning ball which should be always on top of screen and screen should always show only half of it. I can do it but only by clicking buttom to call function which take ball to the right place. I need ball to be always in right place not only then i click the button. I tried to use init function but it always give me this error: 'super' object has no attribute 'getattr'. I also tried to use getattr instead of init but it doesn't take ball to the right place.
So I want click a button and ball start spins for a couple of seconds. When it stops I want ball to be another color or even another image of ball. I tried to use on_complete but I don't understand where I should use it.
My main py file:
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
from kivy.animation import Animation
from kivy.properties import NumericProperty
class OpenScreen(Screen):
angle = NumericProperty(0)
#Trying to make __init__ function
#def __init__(self, **kwargs):
#super(OpenScreen, self).__init__(**kwargs)
#y = self.height
#y1 = self.width / 2
#self.ids.test.pos = 0, y - y1
#Function of taking ball in the right place and spinning it
def z(self):
anim = Animation(angle=360, duration=0.5)
anim += Animation(angle=360, duration=0.5)
anim.start(self)
y = self.height
y1 = self.width / 2
self.ids.test.pos = 0, y - y1
self.ids.test.source = 'redball.png'
def on_angle(self, item, angle):
if angle == 360:
item.angle = 0
...screens classes...
GUI = Builder.load_file('game.kv')
class GameApp(App):
def build(self):
return GUI
def change_screen(self, screen_name):
screen_manager = self.root.ids['screen_manager']
screen_manager.current = screen_name
GameApp().run()
My Openscreen.kv file:
#:kivy 1.10.1
<OpenScreen>:
FloatLayout:
canvas:
Rectangle:
size: self.size
pos: self.pos
source: 'bg.png'
Image:
id: test
size_hint_y: None
height: self.width
source: 'blueball.png'
allow_stretch: True
keep_ratio: False
canvas.before:
PushMatrix
Rotate:
angle: root.angle
axis: 0, 0, 1
origin: self.center
canvas.after:
PopMatrix
Button:
text: "spin"
font_size: self.height - 29
valign: 'middle'
halign: 'center'
padding: 2,2
size_hint: .7, .1
pos_hint:{"x":.15, "y":.585}
background_color: 0,0,0,0
on_press:
root.z()
....
How I can do this?
You can start the animation using Clock.schedule_once() as:
def __init__(self, **kwargs):
super(OpenScreen, self).__init__(**kwargs)
Clock.schedule_once(self.z)
You will need to add a dt argument to the z() method and utilize the on_complete event as:
def z(self, dt):
anim = Animation(angle=360, duration=0.5)
anim += Animation(angle=360, duration=0.5)
anim.bind(on_complete=self.switch_source)
anim.start(self)
Then use a switch_source() method as:
def switch_source(self, *args):
y = self.height
y1 = self.width / 2
self.ids.test.pos = 0, y - y1
self.ids.test.source = 'redball.png'
I would like to give all of the widgets on the screen a white canvas. I know how to create the intended canvas in KV but in this case I must use Python code.
In my code, I tried self.cavas.add(...) and in the code below, I use with self.canvas:. Both attempts resulted in a canvas being drawn in the corner of the screen, rather than inside of the widgets.
How do I put a canvas to be inside of every widget using Python code?
Code:
from kivy.app import App
from kivy.core.window import Window
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
from kivy.lang import Builder
from random import random
from kivy.uix.button import Button
from kivy.animation import Animation
from kivy.clock import Clock
class LittleButtons(Button):
dur = 2
def reup(self, *args):
Animation.cancel_all(self)
Animation(pos_hint = {'center_x': random(), 'center_y': random()}, duration = self.dur).start(self)
def __init__(self, **kwargs):
super(LittleButtons, self).__init__(**kwargs)
self.pos_hint = {'center_x': random(), 'center_y': random()}
self.size_hint = None, None
self.width = random() * (Window.width / 20)
self.height = self.width
self.background_color = [0,0,0,.05]
with self.canvas:
Color(rgba = [1,1,1,.2])
Rectangle(pos = self.pos, size = self.size)
Animation(pos_hint = {'center_x': random(), 'center_y': random()}, duration = self.dur).start(self)
Clock.schedule_interval(self.reup, self.dur)
KV = Builder.load_string("""
#:import Factory kivy.factory.Factory
Screen:
FloatLayout:
on_parent:
(lambda ltext: [self.add_widget(Factory.LittleButtons(text=ltext)) for i in range (150)])('hi!')
Button:
background_color: 0,0,0,0
canvas:
Color:
rgba: 0,1,1,1
Rectangle:
pos: self.pos
size:self.size
""")
class MyApp(App):
def build(self):
return KV
if __name__ == '__main__':
MyApp().run()
The problem is that in the LittleButtons __init__() method, its position is still the default of (0,0), so the position of the Rectangle is set to (0,0). When you use KV, it cleverly binds the Rectangle position to the LittleButtons position when you reference self.pos. Unfortunately, in a .py file, you must provide that binding yourself. So, here is a modification of your LittleButtons that should handle the position changes:
class LittleButtons(Button):
dur = 2
def reup(self, *args):
Animation.cancel_all(self)
Animation(pos_hint = {'center_x': random(), 'center_y': random()}, duration = self.dur).start(self)
def __init__(self, **kwargs):
self.rect = None
super(LittleButtons, self).__init__(**kwargs)
self.pos_hint = {'center_x': random(), 'center_y': random()}
self.size_hint = None, None
self.width = random() * (Window.width / 20)
self.height = self.width
self.background_color = [0,0,0,.05]
with self.canvas:
Color(rgba = [1,1,1,.2])
self.rect = Rectangle(pos = self.pos, size = self.size)
Animation(pos_hint = {'center_x': random(), 'center_y': random()}, duration = self.dur).start(self)
Clock.schedule_interval(self.reup, self.dur)
def on_pos(self, instance, pos):
if self.rect is not None:
self.rect.pos = pos
The changes are the addition of a self.rect attribute and a on_pos method.
You can add something similar if you need to handle changing size.
The aim is to change color of pen in kivy when user press button.The color will changed to button's background color.Code of the canvas:
def on_touch_move(self,color,touch):
with self.pc.canvas:
global wtd,pencolor
Color(pencolor)
if wtd == 1:
Ellipse(pos=(touch.x,touch.y),size=(penrad,penrad))
Code of the on_press binded to button:
def newclr(self,instance):
global pencolor
pencolor = instance.background_color
self.on_touch_move
return pencolor
Buttons events works normally!
Solution
Provide the mode (rgb, rgba) of the Color. Replace Color(pencolor) with Color(rgba=pencolor)
Please refer to the demo below for details. The pen colour started off as red and changed to white i.e. the background colour of the button.
Example
main.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ListProperty, NumericProperty
from kivy.graphics import *
class MyWidget(Widget):
wtd = NumericProperty(1)
penrad = NumericProperty(10)
pencolor = ListProperty([1, 0, 0, 1]) # Red
def newclr(self, instance):
print("Before Change#newclr: pencolor=", self.pencolor)
self.pencolor = instance.background_color
print("After Change#newclr: pencolor=", self.pencolor)
def on_touch_move(self, touch):
print("on_touch_move: touch=", touch)
print("on_touch_move: pencolor=", self.pencolor)
with self.canvas:
Color(rgba=self.pencolor)
if self.wtd == 1:
Ellipse(pos=(touch.x, touch.y), size=(self.penrad, self.penrad))
class TestApp(App):
title = "Kivy - Change Pen Colour"
def build(self):
return MyWidget()
if __name__ == "__main__":
TestApp().run()
test.kv
#:kivy 1.10.0
<CustomButton>:
<MyWidget>:
canvas.before:
Color:
rgba: 0, 0, 1, 1 # Blue
Rectangle:
pos: self.pos
size: self.size
Button:
text: "Change Pen Color"
size: 150, 100
size_hint: None, None
on_press: root.newclr(self)
Output
I am trying to make a Pong game using Python and Kivy, but I cannot change the position of the ball. Whenever I try to, the ball doesn't change unless I call the method within the class which I don't want to do.
Python:
#Imported everything
class PongGame(Widget):
ball = ObjectProperty()
def update(self):
self.ball.pos = (1200, 1200)
class PongBall(Widget):
pass
class PongApp(App):
def build(self):
PongGame().update() #Doesn't work (doesn't do anything)
print(PongGame().ball.pos)) #Not even printing right coordinates
return PongGame()
if __name__ = "__main__":
PongApp().run()
Kv:
<PongGame>:
ball: pball
PongBall:
id: pball
pos: (root.center_x - (root.width * 0.05), root.center_y * (1/12))
size: (root.height * (1/20), root.height * (1/20))
<PongBall>:
canvas:
Color:
rgb: [1, 1, 1]
Ellipse:
pos: self.pos
size: self.size
1) Two open brackets, three closing:
print(PongGame().ball.pos))
2) = should be changed to ==:
if __name__ = "__main__":
3) Here you create 3 different PongGame objects (witch would have different states) instead of creating one:
PongGame().update() #Doesn't work (doesn't do anything)
print(PongGame().ball.pos)) #Not even printing right coordinates
return PongGame()
Should be:
root = PongGame() # Create one object and change it's state.
root.update()
print(root.ball.pos) # will print 1200, 1200
return root
4) kvlang binds widgets property to variables it dependence of. So if you want to change ball position in future, you shouldn't bind it to root ignoring ball's pos. In other words,
pos: (root.center_x - (root.width * 0.05), root.center_y * (1/12))
should dependence of self.pos:
pos: self.pos
-) That's what important.
I also added on_touch_down handling to show ball's position changing (just click window to move ball):
Builder.load_string(b'''
<PongGame>:
ball: pball
PongBall:
id: pball
pos: self.pos
size: 20, 20
<PongBall>:
canvas:
Color:
rgb: [1, 1, 1]
Ellipse:
pos: self.pos
size: self.size
''')
class PongGame(Widget):
ball = ObjectProperty()
def update(self):
self.ball.pos = (200, 200)
def on_touch_down(self, touch):
self.ball.pos = touch.pos # change ball position to point of click
class PongBall(Widget):
pass
class PongApp(App):
def build(self):
root = PongGame()
root.update() # init ball position
return root
if __name__ == '__main__':
PongApp().run()