I'm trying to translate the beginnings of a simple canvas app I wrote in JavaScript to the Kivy framework. I have been able to distribute vertices along the perimeter of a circle, but I have been unsuccessful in registering click events on each vertex whether attempted in Python or Kv language. A nice start might be changing the size of any vertex clicked. Any tips, feedback, solutions welcome.
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.graphics import *
from kivy.properties import NumericProperty
from random import randint
import math
class Vertex(Widget):
def __init__(self, position=(50,50), **kwargs):
super(Vertex, self).__init__(**kwargs)
self.position = position
self.size = (10,10)
def draw(self):
with self.canvas:
Color(1., 0, 0)
Ellipse(pos=self.position, size=self.size)
class ChromaticCircle(Widget):
vertices = []
def __init__(self, radius=100, **kwargs):
super(ChromaticCircle, self).__init__(**kwargs)
self.radius = radius
self.draw()
def draw(self):
interval = (math.pi * 2) / 12
with self.canvas:
Color(1., 0, 0)
for i in range(1, 13):
angle = (math.radians(360) / 12) * (i + 9)
position = ((self.center_x + 200) + (self.radius*math.cos(angle)), (self.center_y + 200)+(self.radius)*math.sin(angle))
self.vertices.append(Vertex(position))
for j in range(len(self.vertices)):
self.vertices[j].draw()
class MyApp(App):
def build(self):
return ChromaticCircle()
if __name__ == '__main__':
MyApp().run()
You can capture clicks on your Vertex widget by using the collide_point method in the on_touch_down method to check if a Touch event occurs within a Vertex:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.graphics import *
from kivy.properties import NumericProperty
from random import randint
import math
class Vertex(Widget):
def __init__(self, position=(50,50), **kwargs):
super(Vertex, self).__init__(**kwargs)
self.position = position
self.size = (10,10)
self.pos = position # need to set the `pos` as `collide_point` uses it
def draw(self):
with self.canvas:
Color(1., 0, 0)
Ellipse(pos=self.position, size=self.size)
class ChromaticCircle(Widget):
vertices = []
def __init__(self, radius=100, **kwargs):
super(ChromaticCircle, self).__init__(**kwargs)
self.radius = radius
self.draw()
def on_touch_down(self, touch):
# check if the touch is on a Vertex
for vert in self.vertices:
if vert.collide_point(touch.x, touch.y):
print('click on vertex: ' + str(vert.pos))
return True
return super(ChromaticCircle, self).on_touch_down(touch)
def draw(self):
interval = (math.pi * 2) / 12
with self.canvas:
Color(1., 0, 0)
for i in range(1, 13):
angle = (math.radians(360) / 12) * (i + 9)
position = ((self.center_x + 200) + (self.radius*math.cos(angle)), (self.center_y + 200)+(self.radius)*math.sin(angle))
print('adding vertex at ' + str(position))
self.vertices.append(Vertex(position))
for j in range(len(self.vertices)):
self.vertices[j].draw()
class MyApp(App):
def build(self):
return ChromaticCircle()
if __name__ == '__main__':
MyApp().run()
There are some additional details that you may want to consider. The pos of the Ellipse is the lower left corner, so your vertex position is not at the center of the drawn Ellipse.
Related
I tried to make Galaxy project. I am having A Mistake in between!
This I am try through freecodecamp.org kivy full course.
I am beginner to kivy.
See if there is problem in canvas.
I wasn't able to see my drawn lines on kivy window.
Here are My files:
main.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import *
from kivy.lang import Builder
from kivy.properties import NumericProperty
Builder.load_file('galaxy.kv')
class MainWidget(Widget):
perspective_point_x = NumericProperty(0)
perspective_point_y = NumericProperty(0)
V_NB_LINES = 7
V_NB_SPACING = .1
vertical_lines = []
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
self.init_vertical_lines()
def on_parent(self, widget, parent):
pass
def on_size(self, *args):
self.update_vertical_lines()
def on_perspective_point_x(self, widget, value):
pass
def on_perspective_point_y(self, widget, value):
pass
def init_vertical_lines(self):
with self.canvas:
Color(1, 1, 1)
for i in range(0, self.V_NB_LINES):
self.vertical_lines.append(Line())
def update_vertical_lines(self):
central_line_x = int(self.width/2)
spacing = int(self.V_NB_SPACING * self.width)
offset = -int(self.V_NB_LINES/2)
for i in range(0, self.V_NB_LINES):
line_x = central_line_x + offset*spacing
self.vertical_lines[i].points = [line_x, 0, line_x, self.height]
offset += 1
class GalaxyApp(App):
pass
GalaxyApp().run()
galaxy.kv
<MainWidget>
perspective_point_x: self.width / 2
perspective_point_y: self.height * 0.75
You where missing the build function in GalaxyApp
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import *
from kivy.lang import Builder
from kivy.properties import NumericProperty
Builder.load_file('galaxy.kv')
class MainWidget(Widget):
perspective_point_x = NumericProperty(0)
perspective_point_y = NumericProperty(0)
V_NB_LINES = 7
V_NB_SPACING = .1
vertical_lines = []
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
self.init_vertical_lines()
def on_parent(self, widget, parent):
pass
def on_size(self, *args):
self.update_vertical_lines()
def on_perspective_point_x(self, widget, value):
pass
def on_perspective_point_y(self, widget, value):
pass
def init_vertical_lines(self):
with self.canvas:
Color(1, 1, 1)
for i in range(0, self.V_NB_LINES):
self.vertical_lines.append(Line())
def update_vertical_lines(self):
central_line_x = int(self.width/2)
spacing = int(self.V_NB_SPACING * self.width)
offset = -int(self.V_NB_LINES/2)
for i in range(0, self.V_NB_LINES):
line_x = central_line_x + offset*spacing
with self.canvas:
self.vertical_lines[i].points = [line_x, 0, line_x, self.height]
offset += 1
class GalaxyApp(App):
def build(self):
return MainWidget()
GalaxyApp().run()
I'm trying to draw a rectangle and place it on the screen depending
on the size of the window / screen.
I've got this but it doesn't work:
with self.canvas:
Color: xyz
Rectangle(pos=(Window.size[0]-50,Window.size[1]-50),size=(50,50))
This didn't have any effect either:
with self.canvas:
Color: xyz
Rectangle(pos=(Window.height-50,Window.width-50),size=(50,50))
Runnable example:
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.app import App
from kivy.graphics import *
Window.size = (375, 812)
class Temporary(Widget):
def __init__(self, **kwargs):
super(Temporary, self).__init__(**kwargs)
with self.canvas:
Color(255/255.0, 99/255.0, 71/255.0)
Rectangle(pos=(0,0),size=(50,50))
class MainApp(App):
def build(self):
return Temporary()
if __name__ == '__main__':
MainApp().run()
I've managed to make it work as I intended:
class Temporary(Widget):
def __init__(self, **kwargs):
super(Temporary, self).__init__(**kwargs)
with self.canvas:
Color(255/255.0, 99/255.0, 71/255.0)
Rectangle(pos=(Window.size[0] / 2,Window.size[1] / 2),size=(50,50))
Nothing else worked. The above will do the trick.
I have followed the widget tutorial for Kivy, but I cannot get the canvas to clear after painting (from their 'a simple paint app' tutorial), unless I resize the window after pressing the clear button. Please help. I am using kivy v1.10.1 and python v3.7.0. on windows 10 64-bit OS. Further, a widget in scatter mode leaves a trace when you drag to a new position, this also clears when resizing the window. See code below:
from random import random
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.graphics import Color, Ellipse, Line
class MyPaintWidget(Widget):
def on_touch_down(self, touch):
color = (random(), 1, 1)
with self.canvas:
Color(*color, mode='hsv')
d = 30.
Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))
touch.ud['line'] = Line(points=(touch.x, touch.y))
def on_touch_move(self, touch):
touch.ud['line'].points += [touch.x, touch.y]
class MyPaintApp(App):
def build(self):
parent = Widget()
self.painter = MyPaintWidget()
clearbtn = Button(text='Clear')
clearbtn.bind(on_release=self.clear_canvas)
parent.add_widget(self.painter)
parent.add_widget(clearbtn)
return parent
def clear_canvas(self, obj):
self.painter.canvas.clear()
if __name__ == '__main__':
MyPaintApp().run()
I want to develop a paint app which creates ellipse or circle as the mouse is moved on the canvas
There is great examples on how to make drawing apps on kivy's website.
Take a look at it Drawing app in kivy
An example from the site.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse, Line
class MyPaintWidget(Widget):
def on_touch_down(self, touch):
with self.canvas:
Color(1, 1, 0)
d = 30.
Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))
touch.ud['line'] = Line(points=(touch.x, touch.y))
def on_touch_move(self, touch):
touch.ud['line'].points += [touch.x, touch.y]
class MyPaintApp(App):
def build(self):
return MyPaintWidget()
if __name__ == '__main__':
MyPaintApp().run()
Working with kivy, how can I check if a widget overlap another widget after they have been rotated. Using the collide_widget method does not take the rotation into account, so the following code snippet prints out "is colliding" even you can see that the rectangles are not overlapping. Is there something else clever I can do with kivy that would allow me to check a collision with rectangles after they have been rotated?
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics.context_instructions import PopMatrix, PushMatrix
from kivy.graphics import Rectangle, Rotate
class RotatableRect(Widget):
def __init__(self, angle=0, **kwargs):
super(RotatableRect, self).__init__(**kwargs)
with self.canvas.before:
PushMatrix()
self.rot = Rotate()
self.rot.angle = angle
self.rot.origin = self.center
self.rot.axis = (0, 0, 1)
self.rect = Rectangle(pos=self.pos, size=self.size)
with self.canvas.after:
PopMatrix()
class MainWidget(Widget):
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
self.rect1 = RotatableRect(pos=[10, 100], size=[100, 50])
self.rect2 = RotatableRect(pos=[100, 50], size=[100, 50], angle=45)
self.add_widget(self.rect1)
self.add_widget(self.rect2)
if self.rect1.collide_widget(self.rect2):
print 'is colliding'
class TheApp(App):
def build(self):
parent = Widget()
parent.add_widget(MainWidget())
return parent
if __name__ == '__main__':
TheApp().run()
Check the Rotabox widget in the kivy-garden.
I made it originally for this exact collision+rotation purpose, although it evolved a lot since then.
Firstly, include rotabox.py in your project.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle
from rotabox import Rotabox
class RotatableRect(Rotabox):
def __init__(self, **kwargs):
super(RotatableRect, self).__init__(**kwargs)
with self.canvas:
self.rect = Rectangle(pos=self.pos, size=self.size)
class MainWidget(Widget):
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
self.rect1 = RotatableRect(pos=[10, 100], size=[100, 50])
self.rect2 = RotatableRect(pos=[100, 50], size=[100, 50], angle=45)
self.add_widget(self.rect1)
self.add_widget(self.rect2)
# for a stationary rotabox, it's essential to wait for it to setup,
# before any collision checks
self.rect1.bind(ready=self.check)
def check(self, *args):
if self.rect1.collide_widget(self.rect2):
print ' is colliding'
class TheApp(App):
def build(self):
return MainWidget()
if __name__ == '__main__':
TheApp().run()
The rotation itself is handled by the widget as long as an angle is given and the default origin of rotation is the widget's center.