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.
Related
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)
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]
I am trying update Label on canvas , but it changed only if I call update() method from MyApp(APP).
And what is interesting that method obj Slider (on touch) works perfectly
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import (Color, Ellipse)
from kivy.uix.slider import Slider
import random
from functools import partial
class Canvas_w(Widget):
def __init__(self, **kwargs):
self.i = 0
super(Canvas_w, self).__init__(**kwargs)
self.label_text =Label(text= "0",color = (255,0,0))
def update_text(self, text):
self.label_text.text = text
def on_touch_down(self, touch):
with self.canvas:
d=30
self.i+=1
Color(1., 0, 0)
Ellipse(pos=(touch.x - d/2, touch.y - d/2), size=(20,20))
self.label_text=str(self.i)
class Label_l(GridLayout):
bones = 0
def __init__(self, **kwargs):
super(Label_l, self).__init__(**kwargs)
self.label_text =Label(text= "0")
self.add_widget(self.label_text)
#self.label_text.text=self.C.i
def update_text(self, text):
Label_l.bones=text
self.label_text.text = str(Label_l().bones)#
Label_l.text=str(Label_l().bones)
class WidgetContainer(GridLayout):
sld_value = 0.00001
def __init__(self, **kwargs):
super(WidgetContainer, self).__init__(**kwargs)
self.cols = 1
self.speedControl = Slider(min=0.00001, max=3, step=0.0001)
self.add_widget(Label(text="Speed Iteration"))
self.add_widget(self.speedControl)
self.add_widget(Label(text=''))
self.speedValue = Label(text="0.00001")
self.add_widget(self.speedValue)
self.speedControl.bind(value=self.on_value)
def on_value(self, instance, speed):
self.sld_value = speed
WidgetContainer.sld_value = self.sld_value
self.speedValue.text = str(round(speed,5))
class MyApp(App):
def build(self):
b3 = BoxLayout(orientation="horizontal")
b1 = FloatLayout()
self.painter = Canvas_w()
self.label_l= Label_l()
b1.add_widget(self.label_l)
b1.add_widget(self.painter)
b1.add_widget(WidgetContainer())
b3.add_widget(b1)
return(b3)
if __name__=="__main__":
MyApp().run()
I am trying update Label on canvas , but it changed only if I call update() method from MyApp(APP).
And what is interesting that method obj Slider (on touch) works perfectly
A few problems with your code:
The color attribute of a Label requires a list of four values with each value in the range 0 to 1. Change self.label_text =Label(text= "0",color = (255,0,0)) to self.label_text =Label(text= "0",color = (1,0,0,1)) in the Canvas_w class.
The above Label is never getting drawn. You need to add self.add_widget(self.label_text) to the __init__() method of Canvas_w.
You have another Label in the Label_l class that is being drawn on top of the Label in the Canvas_w.
After fixing the above problems, I believe your code will work as you expect.
If you call self.canvas.clear(), it appears as though the label_text child of Canvas_w is removed. Actually it is not removed, but is just not visible (perhaps overdrawn). One way to make it visible is to just make sure it is drawn last by removing it and then re-adding it (last added child is drawn last), like this:
self.canvas.clear()
self.remove_widget(self.label_text)
self.add_widget(self.label_text)
After a bit more research, I believe that the canvas.clear() is removing the drawing of the Label from the Canvas. You can, however, do your drawing on canvas.before by using:
with self.canvas.before:
in the on_touch_down() method. And rather than using:
self.canvas.clear()
use:
self.canvas.before.clear()
I believe that your Label will be displayed correctly after doing the above.
I have some simple code below that is just creating some rectangles with a color assigned to them, and then storing them into a FloatLayout. For some reason, the very first rectangle 'Brick' that I create, doesnt get a color, but all subsequent ones do. I have issues with my game also where when another widget collides with a brick, it updates the attributes of the brick to its left, and not itself. I think the two issues are related.
What is going on with the first instance of Brick (brick1) that is being added to the FloatLayout that it doesnt get the color created?
import kivy
kivy.require('1.10.0')
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.core.window import Window
from kivy.properties import NumericProperty, ReferenceListProperty
from kivy.clock import Clock
from kivy.graphics import Rectangle, Ellipse, Color
from kivy.uix.floatlayout import FloatLayout
from kivy.vector import Vector
from kivy.utils import get_color_from_hex
import random
from kivy.config import Config
Window.size = (300,600)
class Brick(Widget):
def __init__(self, xloc, yloc, **kwargs):
super().__init__(**kwargs)
with self.canvas:
self.size = (25,25)
self.x = xloc
self.y = yloc
self.pos = (self.x,self.y)
self.body = Rectangle(pos=self.pos,size = self.size)
self.c = Color(1,0,1)
class Game(Widget):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.brick_container = FloatLayout(size = (25,25))
brick1 = Brick(50,100)
brick2 = Brick(100,100)
self.brick_container.add_widget(brick1)
self.brick_container.add_widget(brick2)
self.add_widget(self.brick_container)
def update(self,dt):
self.name = 'nothing'
class MyApp(App):
def build(self):
game = Game()
Clock.schedule_interval(game.update, 1.0/60.0)
return game
if __name__ == '__main__':
MyApp().run()
You have to set the color first and then the rectangle
class Brick(Widget):
def __init__(self, xloc, yloc, **kwargs):
super().__init__(**kwargs)
self.size = (25,25)
self.pos = (xloc , yloc)
with self.canvas:
self.c = Color(1,0,1)
self.body = Rectangle(pos=self.pos,size = self.size)
According to the docs when using a Drawing Instruction, use the color set above.
Drawing instructions
Drawing instructions range from very simple
ones, like drawing a line or a polygon, to more complex ones, like
meshes or bezier curves:
with self.canvas:
# draw a line using the default color
Line(points=(x1, y1, x2, y2, x3, y3))
# lets draw a semi-transparent red square
Color(1, 0, 0, .5, mode='rgba')
Rectangle(pos=self.pos, size=self.size)
For that reason in your original code the first Brick used the color by default(white), and the others if they had the correct color.
I want to place image on button. Button's size depends on size of screen. I want to select ideal image's size according to button's size. Buttons are defined in class ButtonTools. With my knowledges I cann obtain size only in fuction of class ButtonTools. How do I obtain size of screen?
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.image import Image
class Paint(Widget):
pass
class ButtonTools(BoxLayout):
def __init__(self, **kwargs):
super(ButtonTools, self).__init__(**kwargs)
self.size_hint = (1, None)
self.height = 50
but = Button(on_press = self.DrawAbscissa)
but.background_normal = 'abscissa.png'
self.add_widget(but)
but = Button( on_press = self.DrawCurve)
but.background_normal ='curve.png'
self.add_widget(but)
def DrawAbscissa(self, obj):
size = self.parent.size
p=1
pass
def DrawCurve(self, obj):
pass
class WorkShop(BoxLayout):
def __init__(self, **kwargs):
super(WorkShop, self).__init__(**kwargs)
self.orientation = "vertical"
self.paint = Paint()
self.tools = ButtonTools()
self.add_widget(self.paint)
self.add_widget(self.tools)
class MyPaintApp(App):
def build(self):
return WorkShop()
if __name__ == '__main__':
MyPaintApp().run()
You can obtain the screen size using the following:
from kivy.core.window import Window
...
print("Window.size={}".format(Window.size))
print("Window.height={}".format(Window.height))
print("Window.width={}".format(Window.width))