Kivy - rectangle binds to image with delay - python

I want a rectangle to stick to the bottom left corner of an image. It works, but when I change window size the rectangle is little bit off. It is where image was before update. Here's the code:
from kivy.app import App, Builder
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics import Rectangle
from kivy.graphics import Color
Builder.load_string(
'''
<Screen>:
canvas.before:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
FloatLayout:
Image:
id: im
keep_data: True
source: 'test.png'
allow_stretch: True
size_hint_y: 0.9
pos_hint: {'left':1, 'top':1}
Button:
text: 'Button'
size_hint_y: 0.1
pos_hint: {'x':0, 'y':0}
''')
class Screen(FloatLayout):
def __init__(self, **kwargs):
super(Screen, self).__init__(**kwargs)
with self.ids.im.canvas.after:
Color(1,0,1,.5)
self.rec = Rectangle(pos=(self.ids.im.center_x - self.ids.im.norm_image_size[0] / 2,
self.ids.im.center_y - self.ids.im.norm_image_size[1] / 2),
size=(self.ids.im.norm_image_size[0]/2,
self.ids.im.norm_image_size[1]/2))
self.bind(pos=self.update_canvas, size=self.update_canvas)
def update_canvas(self, *args):
self.rec.pos = (self.ids.im.center_x - self.ids.im.norm_image_size[0] / 2,
self.ids.im.center_y - self.ids.im.norm_image_size[1] / 2)
self.rec.size = (self.ids.im.norm_image_size[0]/2,
self.ids.im.norm_image_size[1]/2)
class QuestionApp(App):
def build(self):
return Screen()
if __name__ == "__main__":
QuestionApp().run()
I'm doing this in Python because I want to randomly spawn rectangles within the image, but I'm stuck here with rectangles when window resizing being wobbly.

If you want the rectangle to use the geometry of the image as reference then you must do the binding with the image, for this change:
self.bind(pos=self.update_canvas, size=self.update_canvas)
to
self.ids.im.bind(pos=self.update_canvas, size=self.update_canvas)

Related

How to create a custom widget in kivy-python

I am trying to make a custom button with border and rounded corners using kivy and I'm new to kivy and python (and its object oriented concepts).
I have partially implemented what I want but whenever I try to increase/decrease the border size or corner radius using an event like on_press it happens but as soon as I resize the window, the border_size/corner_radius becomes to what it was initialized.
Here is my code:
from kivy.uix.button import Button
from kivy.properties import ListProperty
from kivy.properties import NumericProperty
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.app import App
class CustomButton(Button):
border_color = ListProperty([1, 1, 1, 1])
corner_radius = NumericProperty(0)
bg_color = ListProperty([0, 0, 0, 1])
border_width = NumericProperty(0)
Builder.load_string("""
<CustomButton#Button>:
# should I define border_color, corner_radius, bg_color and border_width
# here or within my python script like I did here?
background_color: 0, 0, 0, 0
background_normal: ''
background_down: ''
canvas.before:
Color:
rgba: self.border_color
RoundedRectangle:
size: self.size
pos: self.pos
radius: [self.corner_radius]
Color:
rgba: self.bg_color
RoundedRectangle:
size: [i - 2 * self.border_width for i in self.size]
pos: [i + self.border_width for i in self.pos]
radius: [self.corner_radius - self.border_width if self.corner_radius != 0 else 0]
""")
class RootWidget(FloatLayout):
pass
Builder.load_string("""
<RootWidget>:
CustomButton:
size_hint: 0.2, 0.1
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
text: 'HELLO'
border_width: 0.1 * self.height
on_press:
self.border_width = 0.2 * self.height
""")
class TestApp(App):
def build(self):
return RootWidget()
TestApp().run()
Result:-
After running the script
After pressing the button
After resizing the window
I have tried this code snippet in a separate .kv file as well but I'm getting the same result.
Also I have tried to implement border using Line rounded_rectangle but its kinda bleeding.
Thank you for your time and efforts.
The line in your kv:
border_width: 0.1 * self.height
will reset the border_width whenever the height of the CustomButton changes. So when you resize the window, the above code is activated.
You can avoid this problem by making the 0.1 or 0.2 values into Properties like this:
bw_ratio: 0.1
border_width: self.bw_ratio * self.height
on_press:
self.bw_ratio = 0.2
Another option is to define an on_size() method for CustomButton:
def on_size(self, instance, new_size):
self.border_width = 0.2 * new_size[1]
And remove the border_width stuff from the 'kv'.
Then you can do anything that you want for setting border_width in the new method. Just don't do anything in that method that changes the size of the CustomButton or you will cause an infinite loop.

touch and effect any object

...
How can I give function path?
I couldn't find how to solve addressing.
Anyone have any idea how to fix this?
When I click on the green area only, I want to do the operation of the button in the white area.
...
'''python
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
from kivy.uix.textinput import TextInput
class Area(RelativeLayout):
pass
class Sec(RelativeLayout):
""" This is white area """
def chn(self):
self.ids.secbut.disabled = True
class Vec(RelativeLayout):
""" This is green area
I want the button to disable when the green area is clicked.
"""
def on_touch_down(self,touch):
if self.collide_point(*touch.pos):
Sec.ids.secbut.disabled = True
class runner(App):
def build(self):
self.area = Area()
return self.area
if __name__ == "__main__":
runner().run()
'''
...
runner.kv
...
'''kivy
<Area>:
size_hint: 1, 1
pos_hint: {"x":0,"y":0}
canvas:
Color:
rgba:0.9,0.9,0.9,1
Rectangle:
size:self.size
Sec:
Vec:
<Sec>:
size_hint: 1, 0.5
pos_hint: {"x":0,"y":0}
canvas:
Color:
rgba:0.9,0.8,0.7,1
Rectangle:
size:self.size
Button:
id:secbut
text:"click"
size_hint:0.5,0.5
<Vec>:
size_hint: 1, 0.5
pos_hint: {"x":0,"y":0.5}
canvas:
Color:
rgba:0.2,0.8,0.1,1
Rectangle:
size:self.size
'''
I couldn't find how to solve addressing.
You need to check if the touch actually collides with the area you care about.
if self.collide_point(*touch.pos):
self.ids.secbut.disabled = True
Kind of awkward, but you can do it by adding an id to your Sec like this:
<Area>:
size_hint: 1, 1
pos_hint: {"x":0,"y":0}
canvas:
Color:
rgba:0.9,0.9,0.9,1
Rectangle:
size:self.size
Sec:
id: sec
Vec:
Then use that in your on_touch_down() in Vec:
class Vec(RelativeLayout):
""" This is green area
I want the button to disable when the green area is clicked.
"""
def on_touch_down(self,touch):
if self.collide_point(*touch.pos):
App.get_running_app().root.ids.sec.ids.secbut.disabled = True

Why window resize with rotation disturbs UI layout include image in kivy

After an image rotated and window resized, the image moved no assumed location. In my assumption, image always moves to the center of left side pain. Please tell me solution.
This code realize to generate window with both pain(area window)s at left side and right side. The left side was assumed as a menubar. The right side was assumed to manipulate an image with rotating. If you move mouse on window, the image will be rotated according to your mouse distance.
MyEnv.
MacOS: 10.14.5
Kivy: 1.11.1
Python:3.6.8 by conda.
from cmath import sqrt
from kivy.app import App
from kivy.core.window import Window
from kivy.graphics.context_instructions import LoadIdentity
from kivy.input import MotionEvent
from kivy.properties import NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.lang import Builder
Builder.load_string("""
<Example>:
orientation:"horizontal"
BoxLayout:
orientation:"vertical"
size_hint:0.5,1 # rate of win, this is menubar.
canvas.before:
Color:
rgba: 0, 0, 0.5, 1
Rectangle:
pos: self.pos
size: self.size
Button:
size:1,0.5
Button:
size:1,0.5
FloatLayout:
id: near_layout
pos_hint: {'x':0.5,'y':0}
size_hint:0.5,1
canvas.before:
Color:
rgba: 0, 1, 0, 1
Rectangle:
pos: self.pos
size: self.size
Image:
id:img
center: near_layout.center
canvas.before:
Rotate:
angle: root.angle
axis: 0,0,1
origin: self.center
source: 'kivy.jpeg'
""")
class Example(App, BoxLayout):
angle = NumericProperty(0)
def build(self):
Window.bind(on_resize=self.on_resize)
return self
def on_resize(self, obj, x, y):
LoadIdentity()
self.img = self.ids.img # type:Image
self.img.reload() # Necessary,if not, image disappeared
def on_touch_move(self, touch):
self.angle += sqrt((touch.ppos[0] - touch.pos[0]) ** 2 + (touch.ppos[1] - touch.pos[1]) ** 2).real
print("pos:%s,size:%s" % (self.ids.near_layout.pos, self.ids.near_layout.size))
if __name__ == "__main__":
Example().run()

Kivy Bubble arrow blur

I created a Bubble widget around a custom widget to display some options, but the arrow of that Bubble turned out unexpectedly big and blurry. All those widgets are positioned in a GridLayout that is put inside of a ScrollView. What could cause this issue?
Here is the code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.bubble import Bubble
from kivy.properties import ListProperty
Builder.load_string('''
<PopupBubble>:
size_hint: None, None
<ScrollView>:
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
<Message>:
width: 500
BoxLayout:
pos: root.pos
height: self.height
canvas:
Color:
rgba: 0.8, 0.8, 0.8, 1
RoundedRectangle:
pos: root.pos
size: self.size
TextInput:
pos: root.pos
size: root.size
id: msg
background_color: 0, 0, 0, 0
cursor_color: 0, 0, 0, 0
PopupBubble:
pos: root.x, self.height + root.y + root.height
size: 100, 40
''')
class Message(Widget):
bg_color = ListProperty([0.99, 0.99, 0.99, 1])
class PopupBubble(Bubble):
pass
class Chat(Screen):
pass
class TestApp(App):
def msg_in(self):
msg_stack = self.msg_stack
msg = "test"
msg_stack.append(Message())
msg_stack[-1].ids['msg'].text = msg
msg_stack[-1].width = 160
msg_stack[-1].height = (len(msg_stack[-1].ids['msg']._lines_labels) + 1) * (msg_stack[-1].ids['msg'].line_height + 5)
for i in msg_stack[-1].walk():
i.height = msg_stack[-1].height
i.width = msg_stack[-1].width
self.msg_layout.add_widget(msg_stack[-1])
def build(self):
self.msg_stack = []
self.chat = Chat()
self.sv1_main = ScrollView(pos_hint = {"top":1, "center_x":0.5},
size_hint = (0.97, 0.65))
self.msg_layout = GridLayout(height = 10,
cols = 1,
padding = 10,
spacing = 10,
size_hint_y = None)
self.chat.add_widget(self.sv1_main)
self.sv1_main.add_widget(self.msg_layout)
for i in range(15):
self.msg_layout.add_widget(Widget())
# Just to bring the next widget down
self.msg_in()
return self.chat
TestApp().run()
Your line msg_stack[-1].width = 160 cripples it. Or rather the loop after it, where you set Bubble's width from that variable.
You set Bubble's size in kv, then in python you play its width again as you add Message as a widget to GridLayout and the result is clearly visible. Set its size once, in kv or in python and/or try to set one property only. Otherwise it works as expected:
from kivy.lang import Builder
from kivy.base import runTouchApp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.bubble import Bubble, BubbleButton
Builder.load_string('''
<Test>:
Button:
on_release: root.show_bubble()
FloatLayout:
id: box
''')
class Test(BoxLayout):
def show_bubble(self):
bubble = Bubble(size_hint=[None,None],size=[100,40],pos=[100,300])
bubble.add_widget(BubbleButton(text='Copy', size=[30,10]))
bubble.add_widget(BubbleButton(text='Paste'))
self.ids.box.add_widget(bubble)
runTouchApp(Test())

Kivy widget out of expected boundaries

I'm making custom touch handling for my widget. The way I have it set up is that the initial size of the widget is set and fixed, however, later, I change it dynamically depending on the contents. I turned that feature down for now to shorten the code, it doesn't affect the problem. I've noticed something strange when I clicked at a blank spot to the right of the widget and the touch was registered, so I added a canvas background to inspect. Apparently, the actual widget boundaries are set much bigger than what the actual widget instance is, and I don't really know what's causing this. Those widgets are positioned in a FloatLayout, but I never set size_hints, only sizes. Could that be the case? How do I shorten the widget boundaries to what they're supposed to be?
Here is the entire source code, the problem is most likely in the kv definition:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager, NoTransition
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
global pr_msg_y
pr_msg_y = 5
Builder.load_string('''
<ScrollView>:
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
<Message>:
width: 500
canvas:
Color:
rgba: 1, 0, 0, 0.3
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
pos: root.pos
height: self.height
canvas:
Color:
rgba: 0.6, 0.6, 0.6, 1
Rectangle:
pos: root.pos
size: self.size
TextInput:
pos: root.pos
size: root.size
id: msg
background_color: 0, 0, 0, 0
text: str(msg)
''')
class Message(Widget):
def __init__(self, **kwargs):
super(Message, self).__init__(**kwargs)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print("Touch within {}".format(self))
class Chat(Screen):
pass
class MessageInput(TextInput):
def __init__(self, **kwargs):
super(MessageInput, self).__init__(**kwargs)
class ChatApp(App):
def build(self):
def msg_in(btn):
global pr_msg_y
inst = Message()
inst.ids['msg'].text = "test" * 15
inst.width = 456 # something random, the sizing should be dynamic
inst.height = 40
for i in inst.walk():
i.height = inst.height
i.width = inst.width
inst.y = sv1_main.height - 5 - pr_msg_y - inst.height
msg_float.add_widget(inst)
pr_msg_y += inst.height + 5
Screens = ScreenManager(transition = NoTransition())
chat = Chat(name = "main")
sv1_main = ScrollView(pos_hint = {"top":0.87, "center_x":0.5},
size_hint = (0.97, 0.65))
msg_float = FloatLayout()
bt1_main = Button(pos_hint = {"top":0.097, "center_x":0.951},
on_press = msg_in)
Screens.add_widget(chat)
chat.add_widget(sv1_main)
sv1_main.add_widget(msg_float)
chat.add_widget(bt1_main)
return Screens
ChatApp().run()
size_hint does "overwrite" size. By default size_hint is set to 1,1, so if you only set size and you put the widget into a layout that recognizes size_hints, the widget will get the maximal available size.
Set size_hint = None, None to fix.

Categories

Resources