How to make circular button in kivy using button behavior? - python

I think there is a way to do it in kivy rather than going round the python code.
my code in the kv file:
ButtonBehavior:
source: "round-icon.png"
the app crashes saying
AttributeError: 'ButtonBehavior' object has no attribute
'register_event_type'
round-icon.png is circular icon. The last thing that I will appreciate recommendation, How to set this button to certain size is the window?

Try importing Image also:
Image+ButtonBehavior:
source: "round-icon.png"

You need to make a custom rule(/class) that has the behavior and you need to include Image as el3phanten stated, yet it won't work only that way, because it'll return you a missing class.
Kv language creates a widget from either already registered classes, or custom classes, which may be Python + Kv (that thing you define in both files), or kv only(dynamic). I guess your intention is the dynamic one, so I'll go this way:
from kivy.app import App
from kivy.lang import Builder
kv = """
<Test#ButtonBehavior+Image>:
canvas:
Color:
rgba: 1, 0, 0, .3
Rectangle:
size: self.size
pos: self.pos
BoxLayout:
Button:
Test:
source: 'image.png'
"""
class TestApp(App):
def build(self):
return Builder.load_string(kv)
if __name__ == '__main__':
TestApp().run()
The behavior needs to be first similar to inheriting in Python, then you may add another parts, such as Image.
There's however a big problem for you in this implementation, which is visible with the Rectangle in my example. That is an area that captures every touch, which means you might have a circular image, yet the touch are is still a rectangle.
To get around that you'll need to check for a collision, thus you need to switch from dynamic class to the one that's defined even in Python, so that you can make custom collisions. Example.
That, however, might be really costly even for a single button, therefore a better solution is using something like this:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.image import Image
kv = """
<RoundButton>:
canvas:
Color:
rgba: 1, 0, 0, .3
Rectangle:
size: self.size
pos: self.pos
Color:
rgba: 0, 0, 1, 1
Rectangle:
size:
[self.size[0] - root.OFFSET_x,
self.size[1] - root.OFFSET_y]
pos:
[self.pos[0] + root.OFFSET_x / 2.0,
self.pos[1] + root.OFFSET_y / 2.0]
BoxLayout:
Button:
RoundButton:
source: 'image.png'
on_release: print('Release event!')
"""
class RoundButton(ButtonBehavior, Image):
OFFSET_x = NumericProperty(100)
OFFSET_y = NumericProperty(200)
def on_touch_down(self, touch):
left = touch.x >= self.pos[0] + self.OFFSET_x / 2.0
down = touch.y >= self.pos[1] + self.OFFSET_y / 2.0
up = touch.y <= self.pos[1] + self.size[1] - self.OFFSET_y / 2.0
right = touch.x <= self.pos[0] + self.size[0] - self.OFFSET_x / 2.0
if left and down and up and right:
print('collided!')
self.dispatch('on_release')
else:
print('outside of area!')
class TestApp(App):
def build(self):
return Builder.load_string(kv)
if __name__ == '__main__':
TestApp().run()
or a really low number of points to a better area coverage, yet higher than for a simple rectangle.
How to set this button to certain size is the window?
Use size_hint or directly Window.size to manipulate your widget's size. size_hint is definitely better unless you want to have everything in Window, i.e. without any proper Layout

Related

Kivy canvas doesn't fit to the parent layout

I am trying to make a trial canvas (having just yellow rectangle as bounding box) which should fit to the parent layout (in this case FloatLayout).
At the beginning, the canvas is created at [0,0] of size [100,100]. 2) If I resize by dragging the App window corners, the canvas resizes well. However, 3) & 4) if I maximize or minimize, the canvas fails to resize properly. So as weird canvas size is noticed if I drag too fast. Any help would be highly appreciated :)
Please note create_canvas method an example and it should be in the main.py.
main.py
from kivy.app import App
from kivy.graphics.vertex_instructions import Line
from kivy.metrics import dp
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import InstructionGroup, Color
class TestOut(BoxLayout):
canvas_items = InstructionGroup()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.create_canvas(pos=[dp(150), dp(0)], size=[dp(650), dp(500)])
def on_size(self, *args):
self.can_obj = self.ids.can
# print(self.can_obj.pos, self.can_obj.size)
self.canvas_items.clear()
self.can_obj.canvas.clear()
self.create_canvas(self.can_obj.pos, self.can_obj.size)
def create_canvas(self, pos, size):
self.canvas_items.add(Color(1, 1, 0, 1))
self.canvas_items.add(Line(rectangle=(pos[0], pos[1], size[0], size[1]), width=5))
self.canvas.add(self.canvas_items)
class PlaygroundApp(App):
title = "blabla"
def build(self):
return TestOut()
if __name__ == "__main__":
PlaygroundApp().run()
playground.kv
<TestOut>:
orientation: 'vertical'
Button:
id: button1
text:'A'
height: dp(100)
size_hint: 1, None
pos_hint: {'top':1}
BoxLayout:
orientation: 'horizontal'
Button:
id: button2
text:'B'
width: dp(150)
size_hint: None, 1
FloatLayout:
id: can
The problem is that you want the rectangle size to follow the FloatLayout size, but your on_size() is triggered by changes in the size of TestOut. One easy fix is to just remove the canvas drawing from the python and put the rectangle in the kv, like this:
FloatLayout:
id: can
canvas:
Color:
rgba: 1, 1, 0, 1
Line:
width: 5
rectangle: self.x, self.y, self.width, self.height
A different fix (that leaves the canvas drawing in the python), is to trigger on the size of the FloatLayout. First, change the name of on_size() to something else, say do_on_size():
def do_on_size(self, *args): # changed name to eliminate dependency on size of TestOut
self.can_obj = self.ids.can
# print('on_size:', self.can_obj.pos, self.can_obj.size)
self.canvas_items.clear()
self.can_obj.canvas.clear()
self.create_canvas(self.can_obj.pos, self.can_obj.size)
Then add some code in the PlaygroundApp to set the binding to the size of the FloatLayout:
class PlaygroundApp(App):
title = "blabla"
def build(self):
Clock.schedule_once(self.set_binding)
return TestOut()
def set_binding(self, dt):
fl = self.root.ids.can
fl.bind(size=self.root.do_on_size)

Python kivy dragging boundaries

I'm making an App in python kivy, and I have a problem. I'm dragging an image and even If I hold outside the image it still drags. I want the image to drag only if I'm holding on the image. I tried to fix the problem but couldn't find any solution. Below is my code! Any help is appreciated! Thank You!
This is my .kv
kv = '''
<DragImage>:
drag_rectangle: self.x+self.width/3, self.y+self.height/3, self.width/3, self.height/3
drag_timeout: 10000000
drag_distance: 0
<MainScreen>:
#:import utils kivy.utils
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "Background.png"
DragImage
id: book
pos: 0, 102
size_hint: 1, .1
source: "Tv.png"
'''
This is my main.py
class DragImage(DragBehavior, Image):
def on_touch_up(self, touch):
uid = self._get_uid()
if uid in touch.ud:
print(self.source, 'dropped at', touch.x, touch.y)
return super(DragImage, self).on_touch_up(touch)
You can use the actual size of the picture that you see instead of the widget size when calculating the drag_rectangle:
<DragImage>:
drag_rectangle: self.center[0] - self.norm_image_size[0]/6, self.center[1] - self.norm_image_size[1]/6, \
self.norm_image_size[0]/3, self.norm_image_size[1]/3

Advanced open and close animation for a button

The animation I currently have for opening and closing a button, is simply a sub button comes out from the main button and expands using size_hint. This animation simply enlarges the sublayout. I would like to 'unveil' the sublayout instead. What I mean by that, is to not mess with size_hint of the sub and instead of expanding itself, I want it to 'unveil' itself which makes for a much cleaner animation. Heres a link of a button animation that uses the same effect. https://codemyui.com/button-to-a-list-animation/.
In the linked animation, the bottom border goes down and reveals the list underneath, the same effect I want.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import *
from kivy.animation import Animation
from kivy.lang.builder import Builder
from kivy.uix.behaviors import *
from kivy.properties import *
Builder.load_string("""
<Root>
Button1:
size_hint: 0.2,0.1
pos_hint: {'top':0.9,'center_x':0.5}
canvas:
Color:
rgba: [0.4,0.4,0.4,1]
Rectangle
size: self.size
pos: self.pos
SubLayout:
pos_hint: {'top':1,'center_x':0.5}
canvas:
Color:
rgba: [0.7,0.7,0.7,1]
Rectangle
size: self.size
pos: self.pos
Color:
rgba: [1,1,1,1]
Ellipse:
pos: self.pos
size: self.size
""")
class Root(FloatLayout):
pass
class Button1(ToggleButtonBehavior, FloatLayout):
def __init__(self,**kwargs):
super(Button1,self).__init__(**kwargs)
def on_state(self, *args):
if self.state == 'down':
anim = Animation(pos_hint={'top':0},d=0.2)
anim += Animation(size_hint=(2,2),d=0.2) #: expanding sublayout, how do I unveil instead?
anim.start(self.children[0])
else:
anim = Animation(size_hint=(1,1),d=0.2)
anim += Animation(pos_hint={'top':1},d=0.2)
anim.start(self.children[0])
class SubLayout(FloatLayout):
pass
class TestApp(App):
def build(self):
return Root()
if __name__ == '__main__':
TestApp().run()
As you can see, SubLayout expands and you can see the ellipse inside expanding with the button. What I would like is for the borders to move away, revealing whats underneath them while leaving the ellipse's size the same, which is the 'unveiling' effect. Any ideas on how to do this would be appreciated. Maybe something to do with an Fbo?

drawing in a widget contained by another widget

I'm trying to understand how kv files work.
So far, i've been able to tackle a couple errors, but I'm stuck with something that doesn't produces errors but doesn't produce the intended result.
Expected :
My goal is to create a parent widget containing two isntances of a sub-widget. The sub-widget contains a rectangle and a touch-move instruction. I want each instance to cover only part of the main widget (the rectangle is here for me to see where the sub-widget is). I assume the on-touch-move instructions should trigger only on the part of the screen where the sub-widget instance is.
Actual:
The sub-widget rectangles don't show, and the on-touch-move behaviour is triggered anywhere twice (which makes be think both sub-widgets span on the whole screen but the rectangle isn't shown).
Removing the parent widget canvas doesn't solve my problem, neither does adding only one sub-widget.
What am I doing wrong ?
python file :
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle
class MainWidget(Widget):
pass
class SubWidget(Widget):
def on_touch_move(self, touch):
self.center_x, self.center_y = (touch.x, touch.y)
print touch.x, touch.y
class testApp(App):
def build(self):
x = MainWidget()
return x
if __name__ == '__main__':
testApp().run()
kv file:
#:kivy 1.8.0
<MainWidget>:
canvas:
Color:
rgb: 0,1,0
Rectangle:
pos: self.center
size: 10,10
SubWidget:
pos: self.width - self.width/5 ,0
size: self.width/5 , self.height
SubWidget:
pos: 0, 0
size: self.width/5 , self.height
<SubWidget>:
canvas:
Color:
rgb: 1,0,0
Rectangle:
pos: self.pos
size: self.size
Thanks in advance for answers.
edit :
1) child widgets should be added within a layout. Still need to find a way to
position my widgets properly within the layout.
2) widgets' touch events are triggered even if the widget isn't directly clicked. Using widget.collide_point(*touch.pos) makes it work.
edit2 :
Fixed the .kv. Self.parent.pos/size didn't behave consistently so I moved to root.pos/size :
#:kivy 1.8.0
<MainWidget>:
canvas:
Color:
rgb: 0,1,0
Rectangle:
pos: self.center
size: 10,10
FloatLayout:
SubWidget:
pos: root.width - root.width/5 ,0
size: root.width/5 , root.height
SubWidget:
pos: 0, 0
size: root.width/5 , root.height
<SubWidget>:
canvas:
Color:
rgb: 1,0,0
Rectangle:
pos: self.pos
size: self.size
I believe you need to put the child elements into a box layout.
So it would be something like this.
<MainWidget>:
canvas:
...
BoxLayout:
SubWidget:
...
SubWidget:
...
The SubWidget elements' attributes (like pos) might need some changing.
Some more info here too.
I assume the on-touch-move instructions should trigger only on the part of the screen where the sub-widget instance is.
This assumption is incorrect. To check for the collision yourself, you can do if self.collide_point(*touch.pos): ... in your on_touch_move method.
Also, your positioning problems are caused by using only Widgets - these do not impose anything on their children, so everything except the root widget has the default pos of (0, 0) and size of (100, 100). You should use Layout classes to have widgets automatically resize and position their children.

Kivy create new instance of widget

I'm fairly new to Kivy and have a few questions about widgets.
I started out messing with Kivy a few months back. I've read some documentation but I might have missed out lots of stuff.
Is it possible to create multiple instances of the same widget class with their own properties?
My goal is to have a few rectangles that I can resize and drag around independently.
I'm taking a java class so I'll compare to what I learnt in that class:
For example, lets say I have a basic rect.java class set up to take in two variables for width and height.
So in my main .java code file I would write something like this to create a few instances of the rectangle class:
rect s1 = new rect(2,3); // width & height
rect s2 = new rect(5,4);
Then, s1.height and s2.height will have different values.
Is it possible to achieve something similar in Kivy? For now I have many classes with the same properties set up in my .kv file:
<rect1>:
canvas:
Color:
rgba: 1, 0, 1, 0.5
Rectangle:
pos: root.center_x - root.width/2,root.center_y - root.height/2
size: self.size
<rect2>:
canvas:
Color:
rgba: 1, 1, 0, 0.5
Rectangle:
pos: root.center_x - root.width/2,root.center_y - root.height/2
size: self.size
<rect3>:
canvas:
Color:
rgba: 0, 1, 0, 0.5
Rectangle:
pos: root.center_x - root.width/2,root.center_y - root.height/2
size: self.size
I've written code in my .py file for it to be resized and dragged around. For now, I copied/modified the code to work with each additional class.
For now, if I use:
Window.add_widget(rect1)
It will create a new instance directly on top of the older one but they still share the same coordinates and other properties etc. If I drag with my mouse, all the instances of that class follow the same coordinates. Once again, my goal is to have multiple rectangles that I can resize and drag around independently.
The Window should only have one widget (the application root widget). It's best to let this widget be added automatically by returning the root widget instance from App.build() or by including a root widget in your app's kv file.
In this case, a FloatLayout would make the most sense.
Also, you can use the Scatter widget to handle transformations - move (translate), resize and rotate - which might be easier than doing it yourself. Just wrap each widget in a Scatter, or make your widgets extend Scatter.
Each entry you define with the angle brackets (<, >) are class declarations not instances. If you want to instantiate a class in the kv file with different properties then use the name without the angle brackets.
Here's some working code based on the code fragments you supplied:
<MovableRect>:
size: 50, 50
canvas:
Color:
rgba: root.color
Rectangle:
size: self.size
pos: self.pos
<MyRoot#Widget>:
MovableRect:
id: rect1
color: 1, 0, 1, 0.5
pos: 5, 5
MovableRect:
id: rect2
color: 1, 1, 0, 0.5
pos: 130, 130
MovableRect:
id: rect3
color: 0, 1, 0, 0.5
pos: 250, 250
# instantiation of root widget
MyRoot:
Here's the python file (without your movement functionality because you didn't list it):
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.screenmanager import ScreenManager
from kivy.properties import ListProperty
class MovableRect(Widget):
color = ListProperty([1, 0, 1, 0.5])
class Test1App(App):
def build(self):
pass
if __name__ == '__main__':
Test1App().run()
For a more complete example you can refer to the excellent kivy crash course series on youtube. There's one example that's very similar to what you're trying to do:
https://www.youtube.com/watch?v=ChmfVOu9aIc

Categories