Draggable Image in Kivy - python

So I am new to Kivy and Gui coding in general.... I am trying to have a moveable image, and here is the code I have so far tried:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.uix.behaviors import DragBehavior
from kivy.uix.floatlayout import FloatLayout
class Box_layout(FloatLayout):
def __init__(self,**kwargs):
super(Box_layout, self).__init__(**kwargs)
self.size_hint = (.50,.50)
self.orientation = "vertical"
self.add_widget(MoveableImage())#drag_rectangle = [self.x, self.y, self.width, self.height],source="temp_plot.png"))
class MoveableImage(DragBehavior,Image):
def __init__(self, **kwargs):
super(MoveableImage, self).__init__(**kwargs)
self.drag_timeout = 10000000
self.drag_distance = 0
#self.drag = DragBehavior()
#self.drag.drag_rectangle = [self.x, self.y, self.width, self.height]
class gameApp(App):
def build(self):
wimg = MoveableImage(source="temp_plot.png")
m = Box_layout()
if __name__ == '__main__':
gameApp().run()
What happens is I currently have a blank 'image' that is draggable on the first click, but then reaches a timeout or something where it cannot be moved after it has been moved once..... I figured it was a timeout issue or something though self.drag_timeout = 10000000 did not fix the issue...what am I doing wrong here?
Further, when I am passing an actual source to MoveableImage, ie self.add_widget(MoveableImage(source='tmp.png')), the image never is moveable to begin with, which again is very confusing to me....if someone could help and explain what is going on and then explain why these behaviors are occurring, hat would be awesome!

You also need to keep the drag_rectangle of the MoveableImage updated. The easiest way to do that is by using the kv language. So your MoveableImage class can be simply:
class MoveableImage(DragBehavior, Image):
pass
Then load a kv rule like this:
kv = '''
<MoveableImage>:
# Define the properties for the MoveableImage
drag_rectangle: self.x, self.y, self.width, self.height
drag_timeout: 10000000
drag_distance: 0
'''
Builder.load_string(kv)
The advantage of using kv here is that it automatically sets up bindings that you would otherwise have to code up yourself. The drag_rectangle is an example of that, so when the MoveableImage is moved (dragged), the drag_rectangle is automatically updated.
If you want to set up those bindings yourself (and not use kv), yu can define your MoveableImage as:
class MoveableImage(DragBehavior, Image):
def __init__(self, **kwargs):
super(MoveableImage, self).__init__(**kwargs)
self.drag_timeout = 10000000
self.drag_distance = 0
self.drag_rectangle = [self.x, self.y, self.width, self.height]
def on_pos(self, *args):
self.drag_rectangle = [self.x, self.y, self.width, self.height]
def on_size(self, *args):
self.drag_rectangle = [self.x, self.y, self.width, self.height]

Related

Kivy: Draw circle to middle of screen on startup

I am building an app using Kivy and would like to draw a circle to the middle of a Widget as soon as the app starts up. I found how to run code on start in this question (How do I run a function once the form is loaded Kivy). However, as a comment points out, widths and heights are not initialized yet when calling on_start(). Does anyone know how I could do this?
I have the following code. Using this, the circle is drawn at position 50, 50, while I would like it in the middle of the Field widget.
main.py:
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color, Ellipse
from kivy.lang import Builder
from kivy.properties import ObjectProperty
class Field(Widget):
def __init__(self, **kwargs):
super(Field, self).__init__(**kwargs)
def init_circle(self):
with self.canvas:
Color(1,1,1,1, mode='rgba')
r = 20
self.circle = Ellipse(pos=(self.width//2 - r, self.height//2 - r), size=(2*r, 2*r))
print(self.circle)
def move_circle(self):
cx, cy = self.circle.pos
self.circle.pos = (cx + 10, cy)
class RootClass(Widget):
field = ObjectProperty(None)
def __init__(self, **kwargs):
super(RootClass, self).__init__(**kwargs)
class MyMainApp(App):
def build(self):
self.r = RootClass()
return self.r
def on_start(self, **kwargs):
self.r.field.init_circle()
if __name__ == '__main__':
Builder.load_file("my.kv")
MyMainApp().run()
my.kv:
<RootClass>
field: field_id
BoxLayout:
size: root.size
orientation: "vertical"
Field:
id: field_id
size_hint: 1, 0.9
Button:
size_hint: 1, 0.1
text: "Move"
on_press:
root.field.move_circle()
Method 1:
Using bind by binding to a callback method,
class Field(Widget):
def __init__(self, **kwargs):
super(Field, self).__init__(**kwargs)
# Bind a callback method, say here 'init_circle' to the prop. 'size' and 'pos'
# so that whenever those prop. change the method will be called.
# To prevent redrawing you may use method 'clear' or some other strategy.
self.bind(size = self.init_circle, pos = self.init_circle)
def init_circle(self, *args):
with self.canvas:
...
Method 2:
Using Clock by scheduling the process,
def on_start(self, **kwargs):
Clock.schedule_once(self.r.field.init_circle)

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.

How do you add a controllable graphic with an image overtop in kivy?

I am new to python and have messed around with Kivy a little. This is the first app of my own design I've been tying to make. The goal is for it to be a game where there is a character that moves in all directions around the screen. My problem right now is that I can't get the character widget to display and I don't know if it's a problem with the widget or the image in the widget. When I run the program all I get is a black screen and no errors. Can anyone see where I went wrong?
Also if you have any recommendations in a better way to structure anything it would be appreciated :)
import kivy
kivy.require('1.7.2')
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import NumericProperty
from kivy.graphics import Rectangle, Color, Canvas
from kivy.uix.image import Image
class WidgetDrawer(Widget):
def __inti__(self, imageStr, **kwargs):
super(WidgetDrawer,self).__inti__(**kwargs)
with self.canvas:
self.size = (Window.width*.05, Window.width*.05)
self.x = self.center_x
self.y = self.center_y
self.pos = (self.x, self.y)
self.rect_pl = Rectangle(source= imageStr, pos=self.pos, size=self.size)
self.bind(pos=self.update_graphics_pos)
self.rect_pl.pos = self.pos
def update_graphic_pos(self, value):
self.rect_pl.pos = value
def setSize(self, width, height):
self.size = (width,height)
class Player(WidgetDrawer):
impulse_x = 3
impulse_y = 3
winre = -0.1
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(0)
def move(self):
self.x = self.x + self.velocity_x
self.y = self.y + self.velocity_y
if self.x > Window.width*.95 or self.x < Window.width*.05:
velocity_x = 0
if self.y > Window.height*.95 or self.y < Window.height*.05:
velocity_y = 0
def determineVelocity(self):
self.velocity_x = self.impulse_x + self.winre
self.velocity_y = self.impulse_y + self.winre
def update(self):
self.determineVelocity()
self.move()
class GUI(Widget):
def __init___(self, **kwargs):
super(GUI, self).__init__(**kwargs)
self.character = Player(imageStr = './character.png')
self.character.x = Window.width/4
self.character.y = Window.height/2
self.add_widget(self.character)
class GameApp(App):
def build(self):
parent = Widget()
app = GUI()
parent.add_widget(app)
return parent
if __name__ == '__main__':
GameApp().run()
The problem is self.add_widget(self.character) i.e. adding widget in __init__ method. Try to run python main.py -m inspector, Ctrl+E and get to GUI(). No children, nothing. Add children after __init__.
About the structure:
use kv language
use one naming style
fix typos(__inti__ != __init__)
if you have 1.7.2 version, you definitely need to update asap

Python Kivy widget animation

I've posted similar topic recently, but this time I'll try to be more clear and specific. My problem is that widgets in Kivy aren't animating as they are expected to. Here's some example code, why are scatters better to animate than widgets:
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.uix.scatter import Scatter
from kivy.animation import Animation
from kivy.graphics import Color, Rectangle
class ExampleWidget(Widget):
def __init__(self, **kwargs):
super(ExampleWidget, self).__init__(**kwargs)
self.size = (100,100)
self.pos = (100,100)
with self.canvas:
Color(1,0,0)
self.texture = Rectangle(size=self.size, pos=self.pos)
def on_pos(self, obj, value):
try: self.texture.pos = value
except: pass
class ExampleScatterTexture(Widget):
def __init__(self, **kwargs):
super(ExampleScatterTexture, self).__init__(**kwargs)
with self.canvas:
Color(0,1,0)
texture = Rectangle(size=self.size, pos=self.pos)
class ExampleScatter(Scatter):
def __init__(self, **kwargs):
super(ExampleScatter, self).__init__(**kwargs)
self.do_rotation = False
self.do_scale = False
self.do_translation = False
self.size = (100,100)
self.pos = (100,300)
texture = ExampleScatterTexture(size=self.size)
self.add_widget(texture)
class ExampleScreen(Widget):
def __init__(self, **kwargs):
super(ExampleScreen, self).__init__(**kwargs)
self.size = Window.size
example_widget = ExampleWidget()
self.add_widget(example_widget)
example_scatter = ExampleScatter()
self.add_widget(example_scatter)
#SCATTER IS GREEN, WIDGET IS RED
example_widget_animation = Animation(pos=(300,100), duration=2., t='in_bounce') + Animation(pos=(100,100), duration=2., t='in_bounce')
example_scatter_animation = Animation(pos=(300,300), duration=2., t='in_bounce') + Animation(pos=(100,300), duration=2., t='in_bounce')
example_widget_animation.repeat = True
example_scatter_animation.repeat = True
example_widget_animation.start(example_widget)
example_scatter_animation.start(example_scatter)
class BadAnimationExample(App):
def build(self):
root = ExampleScreen()
return root
if __name__ == "__main__":
BadAnimationExample().run()
As you can see, the widget animation is executed very fast and then there comes a pause, while scatter animation is very much like we expect it to be. My problem is that when I have my finger on the scatters all my on_touch_move() functions aren't working. Is there any solution?
Ok, I fixed the problem. I used my on_touch_move() method in the child widget class and the solution is to use it in the parent widget. Then there is no problem with scatter selection.

Categories