kivy detect clicks on widgets within screen - python

I have a screen with multiple widgets within it. I need to detect if the user clicked the label - 'home'
Whenever I click the screen the on_touch_down and on_touch_up is triggered. how can I determine the label that I clicked..
class WordScreen(Screen):
def __init__(self, **kwargs):
label_text = kwargs['label_text']
del kwargs['label_text']
super(WordScreen, self).__init__(**kwargs)
main_layout = BoxLayout(id='test', orientation='vertical')
navigation_layout = BoxLayout(orientation='horizontal',
size_hint_y=.1)
navigation_layout.add_widget(Label(text='home'))
navigation_layout.add_widget(Label(text='audio'))
navigation_layout.add_widget(Label(text='favorite'))
text_layout = BoxLayout(orientation='vertical')
text_layout.add_widget(Label(id='sight_text', text=label_text))
main_layout.add_widget(navigation_layout)
main_layout.add_widget(text_layout)
self.add_widget(main_layout)
def on_touch_down(self, touch):
self.initial = touch.x
def on_touch_up(self, touch):
print('on_touch_up - ', touch.x)
print(self.collide_point(*touch.pos))
print(touch.button)
s = None
try:
s = self.manager.get_screen('settings')
except ScreenManagerException:
pass
print('screen - ', s)
if s is not None:
self.manager.clear_widgets([s])
print('screen removed')
if touch.x < self.initial:
self.manager.transition = SlideTransition(direction="right")
self.manager.current = self.manager.next()
elif touch.x > self.initial:
self.manager.transition = SlideTransition(direction="right")
self.manager.current = self.manager.previous()

Based on link from John Anderson comment I created clickable Label.
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.behaviors import ButtonBehavior
class MyLabel(ButtonBehavior, Label):
def on_press(self):
print("press:", self.text)
def on_release(self):
print("release:", self.text)
class MyApp(App):
def build(self):
return MyLabel(text="Hello")
MyApp().run()
But problem can be that on_touch_down and on_touch_up may block other events and then even standard Button will not work. You may have to check on_touch_down and on_touch_up only in widget which doesn't need on_press, on_release
Probably it could be resolved using only on_touch_move without on_touch_down, on_touch_up
EDIT:
Minimal working code.
It uses clickable Label and on_touch_move without on_touch_down, on_touch_up to slide screen.
BTW: I use if touch.dx < -3: instead of if touch.dx < 0: to skip very small moves. I tested it only with mouse but for findgers it may need different value.
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from functools import partial
from kivy.uix.screenmanager import ScreenManager, Screen, SlideTransition
from kivy.uix.behaviors import ButtonBehavior
class MyLabel(ButtonBehavior, Label):
def on_press(self):
print("pressed:", self.text)
def on_release(self):
print("release:", self.text)
class WordScreen(Screen):
def __init__(self, **kwargs):
label_text = kwargs['label_text']
del kwargs['label_text']
super(WordScreen, self).__init__(**kwargs)
main_layout = BoxLayout(id='test', orientation='vertical')
navigation_layout = BoxLayout(orientation='horizontal',
size_hint_y=.1)
navigation_layout.add_widget(MyLabel(text='home'))
navigation_layout.add_widget(MyLabel(text='audio'))
navigation_layout.add_widget(MyLabel(text='favorite'))
text_layout = BoxLayout(orientation='vertical')
#text_layout.add_widget(Label(id='sight_text', text=label_text))
text_layout.add_widget(Label(id='sight_text', text=self.name))
main_layout.add_widget(navigation_layout)
main_layout.add_widget(text_layout)
self.add_widget(main_layout)
def on_touch_move(self, touch):
#print('touch variable:\n', "\n".join(dir(touch)))
print('touch.dx:', touch.dx)
print('touch.dsx:', touch.dsx)
#print('touch.distance:', touch.distance)
#print('touch.move:', touch.move)
if touch.dx < -3: # using `touch.dx < 0` it slides even on very small moves
self.manager.transition = SlideTransition(direction="right")
self.manager.current = self.manager.next()
elif touch.dx > 3: # using `touch.dx > 0` it slides even on very small moves
self.manager.transition = SlideTransition(direction="right")
self.manager.current = self.manager.previous()
sm = ScreenManager()
sm.add_widget(WordScreen(name='menu', label_text="Example"))
sm.add_widget(WordScreen(name='settings', label_text="Example"))
class MyApp(App):
def build(self):
return sm
if __name__ == '__main__':
MyApp().run()
EDIT:
Problem with on_touch_down, on_touch_up which block on_press, on_release can be resolved using super() to execute original code on_touch_down, on_touch_up
def on_touch_down(self, touch):
# ... code ...
return super(WordScreen, self).on_touch_down(touch)
def on_touch_up(self, touch):
# ... code ...
return super(WordScreen, self).on_touch_up(touch)
Based on example in documentation: Events - Dispatching a Property event

Related

kivy.uix.behaviors.TouchRippleButtonBehavior's on_release not working

I have a boxlayout that inherits from kivy.uix.behaviors.TouchRippleButtonBehavior, and the ripple effect works fine, however, when I define an 'on_press' or 'on_release' the functions never run. This is also true for kivy.uix.behaviors.ButtonBehavior, why is this? My Code snippet:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.behaviors import TouchRippleButtonBehavior
from kivy.graphics import Color, Rectangle
class RippleBox(TouchRippleButtonBehavior, BoxLayout):
def __init__(self, **kwargs):
super(RippleBox, self).__init__(**kwargs)
with self.canvas.before:
Color(.5, .5, .5, 1, mode='rgba')
self._rectangle = Rectangle(size=self.size, pos=self.pos)
def on_release(self): # also tried passing '*args' as an argument, did not change anything
print("got clicked!")
def on_size(self, *args):
self._rectangle.size = self.size
self._rectangle.pos = self.pos
def on_touch_down(self, touch):
collide_point = self.collide_point(touch.x, touch.y)
if collide_point:
touch.grab(self)
self.ripple_show(touch)
return True
return False
def on_touch_up(self, touch):
if touch.grab_current is self:
touch.ungrab(self)
self.ripple_fade()
return True
return False
class MainApp(App):
def build(self):
return RippleBox()
if __name__ == "__main__":
MainApp().run()
When you define any of the on_touch_down() or on_touch_up() methods you are over-riding the on_touch_down() and on_touch_up() methods that are in the TouchRippleButtonBehavior. The on_touch_up() method of TouchRippleButtonBehavior dispatches the on_release event, but cannot do that if it never gets called. The TouchRippleButtonBehavior methods also do the self.ripple_show() and self.ripple_fade() that your methods call. Normally, when you redefine those methods you should call super(), like this:
super(RippleBox, self).on_touch_down(touch)

about on_touch_down and on_touch_move

I want to work with on_touch_down with a left-click but it just with work right-click and on touch move didn't work with anything
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.graphics import Ellipse, Color, Line
Window.clearcolor = (47/255, 72/255, 125/255, 1)
class PaintWindow(Widget):
def on_touch_down(self, touch):
s = 30
self.canvas.add(Color(rgb=(184/255, 154/255, 200/255)))
self.canvas.add(Ellipse(pos=(touch.x - s/2, touch.y - s/2), size=(s, s)))
touch.ud["line"] = Line(points=(touch.x, touch.y))
self.canvas.add(touch.ud)
def on_touch_move(self, touch):
touch.ud["line"].points += Line(points=(touch.x, touch.y))
class PaintApp(App):
def bulid(self):
return PaintWindow()
PaintApp().run()
how can i fix this problem
You are not seeing the results of your on_touch_down() or on_touch_move(). Your PaintApp has a bulid() that probably should be build(). Check your spelling.

Is there a way to stop canva and textinput from overlapping in kivy?

I made a code where you can draw lines with a kivy canva and I wanted to add a Textinput. But the problem is that when I trie clicking on the Textinput, kivy wants to draw a line and it gives me an error :
" File "/myfolders/test.py", line 45, in on_touch_up
touch.ud['line'].points += [touch.x, touch.y]
KeyError: 'line' "
Here is my code :
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse, Line, Rectangle
from kivy.uix.image import Image
from kivy.clock import Clock
from kivy.uix.textinput import TextInput
from time import sleep
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scatter import Scatter
from kivy.uix.boxlayout import BoxLayout
from math import *
isLine=False
length=0
class MyBackground(Widget):
def __init__(self, **kwargs):
super(MyBackground, self).__init__(**kwargs)
with self.canvas:
self.bg = Rectangle(source='chouettes3.jpg', pos=self.pos, size=self.size)
self.bind(pos=self.update_bg)
self.bind(size=self.update_bg)
def update_bg(self, *args):
self.bg.pos = self.pos
self.bg.size = self.size
class MyPaintWidget(Widget):
def on_touch_down(self, touch):
#if self.collide_point(*touch.pos):
with self.canvas:
self.canvas.clear()
d=15
Rectangle(pos=(touch.x-d/2,touch.y-d/2),size=(d,d))
touch.ud['line'] = Line(points=(touch.x, touch.y), width=3)
def on_touch_up(self, touch):
#if self.collide_point(*touch.pos):
touch.ud['line'].points += [touch.x, touch.y]
points=touch.ud['line'].points
length=sqrt(((points[0]-points[2])**2)+((points[1]-points[3])**2))
print (length)
with self.canvas:
d=15
Rectangle(pos=(touch.x-d/2,touch.y-d/2),size=(d,d))
class MyPaintApp(App):
def build(self):
f = FloatLayout()
s = Scatter()
b = BoxLayout(orientation='vertical')
parent = MyBackground()
painter = MyPaintWidget(pos_hint={'x': 100, 'center_y': 100}, size_hint=(None, None))
textinput = TextInput(text='Hello world')
parent.add_widget(painter)
parent.add_widget(textinput)
f.add_widget(parent)
sleep(0.1)
return f
if __name__ == '__main__':
MyPaintApp().run()
I tried the self.collide_point technique, where something only works when you click on the widget in question but that just doesn't draw a line anymore.
Thanks in advance for your answers!
One problem is your use of the Widget class as a container for other Widgets. You should use a Layout class to contain other Widgets, otherwise, things like pos_hint have no effect. So I have modified your code to make MyBackground extend FloatLayout. Also, using collide_point() is a good idea, but usually, when you override a class method, you should also call the overridden method using super(). With that in mind, here is your modified code, that I think will work as you intended:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Line, Rectangle
from kivy.uix.textinput import TextInput
from kivy.uix.floatlayout import FloatLayout
from math import *
isLine=False
length=0
class MyBackground(FloatLayout):
def __init__(self, **kwargs):
super(MyBackground, self).__init__(**kwargs)
with self.canvas:
self.bg = Rectangle(source='chouettes3.jpg', pos=self.pos, size=self.size)
self.bind(pos=self.update_bg)
self.bind(size=self.update_bg)
def update_bg(self, *args):
self.bg.pos = self.pos
self.bg.size = self.size
class MyPaintWidget(Widget):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
with self.canvas:
self.canvas.clear()
d=15
Color(1, 0, 0, 1)
Rectangle(pos=(touch.x-d/2,touch.y-d/2),size=(d,d))
touch.ud['line'] = Line(points=(touch.x, touch.y), width=3)
return super(MyPaintWidget, self).on_touch_down(touch)
def on_touch_up(self, touch):
if 'line' not in touch.ud:
return super(MyPaintWidget, self).on_touch_up(touch)
if self.collide_point(*touch.pos):
touch.ud['line'].points += [touch.x, touch.y]
points=touch.ud['line'].points
length=sqrt(((points[0]-points[2])**2)+((points[1]-points[3])**2))
print (length)
with self.canvas:
d=15
Rectangle(pos=(touch.x-d/2,touch.y-d/2),size=(d,d))
return super(MyPaintWidget, self).on_touch_up(touch)
class MyPaintApp(App):
def build(self):
parent = MyBackground()
painter = MyPaintWidget(pos_hint={'x':0., 'center_y':0.5}, size_hint=(1, 1))
self.textinput = TextInput(text='Hello world', pos=(0,0), size_hint=(0.1,0.1))
parent.add_widget(painter)
parent.add_widget(self.textinput)
return parent
if __name__ == '__main__':
MyPaintApp().run()
I have also eliminated some unused code in the build() method.
As the code stands, clicking in the TextInput will not start a new line, but you can end a line behind the TextInput. If you don't want that to happen, you can add a collision test in the on_touch_up() method to handle it as shown below:
def on_touch_up(self, touch):
if 'line' not in touch.ud:
return super(MyPaintWidget, self).on_touch_up(touch)
ti = App.get_running_app().textinput
if self.collide_point(*touch.pos) and not ti.collide_point(*touch.pos):
touch.ud['line'].points += [touch.x, touch.y]
points=touch.ud['line'].points
length=sqrt(((points[0]-points[2])**2)+((points[1]-points[3])**2))
print (length)
with self.canvas:
d=15
Rectangle(pos=(touch.x-d/2,touch.y-d/2),size=(d,d))
else:
self.canvas.clear()
return super(MyPaintWidget, self).on_touch_up(touch)

Custom Kivy Widget yields only black screen

I am trying to create a custom kivy widget, but when I render it in my test app, a blank screen shows up. I've tried tracing code calls and it looks like all of the initialization and drawing methods are being called (I have included the terminal output from the print statements below the widget code. I am also including my test app, but this is very simple and is the same shell I've used for several working apps I've already created). My application main loop begins, but nothing is displayed. Why am I not seeing my widget?
[Edit]So I've gotten the NodeEnd objects to render with some major structural changes, bringing a lot of the code into .kv language. I'm replacing the code on the post with the newer version. Unfortunately, the label between them is still not rendering
nodewidget.kv
#: kivy 1.9.0
<NodeEnd>
canvas:
Color:
rgba: self.color[0], self.color[1], self.color[2], self.color[3]
Ellipse:
pos: self.center
size: self.width / 2, self.height / 2
angle_start: self.degree_range[0]
angle_end: self.degree_range[1]
<NodeWidget>
left: left
body: body
right: right
GridLayout:
cols: 3
NodeEnd:
id: left
degree_range: 180, 360
on_pressed: self.PressedLeft(self, args[0])
on_moved: self.MovedLeft(self, args[0])
on_released: self.ReleasedLeft(self, args[0])
on_color: self.LeftSwitch(self, args[0])
NodeBody:
id: body
text: self.title
on_pressed: self.PressedBody(self, args[0])
on_moved: self.MovedBody(self, args[0])
on_released: self.ReleasedBody(self, args[0])
on_pressed_flag: self.BodySwitch(self, args[0])
NodeEnd:
id: right
degree_range: 0, 180
on_pressed: self.PressedRight(self, args[0])
on_moved: self.MovedRight(self, args[0])
on_released: self.ReleasedRight(self, args[0])
on_color: self.RightSwitch(self, args[0])
NodeWidget3.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ListProperty, StringProperty, BooleanProperty, ObjectProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.graphics import *
from kivy.lang import Builder
Builder.load_file('nodewidget.kv')
class NodeWidget(GridLayout):
title = StringProperty('')
pressed_body = ListProperty([0, 0])
released_body = ListProperty([0, 0])
moved_body = ListProperty([0, 0])
switch_body = BooleanProperty(False)
pressed_left = ListProperty([0, 0])
released_left = ListProperty([0, 0])
moved_left = ListProperty([0, 0])
switch_left = BooleanProperty(False)
pressed_right = ListProperty([0, 0])
released_right = ListProperty([0, 0])
moved_right = ListProperty([0, 0])
switch_right = BooleanProperty(False)
left = ObjectProperty(None)
body = ObjectProperty(None)
right = ObjectProperty(None)
def __init__(self, **kwargs):
super(NodeWidget, self).__init__(**kwargs)
def PressedBody(self, *args):
self.pressed_body = args
def MovedBody(self, *args):
self.moved_body = args
def ReleasedBody(self, *args):
self.released_body = args
def BodySwitch(self, *args):
pass
def PressedLeft(self, *args):
self.pressed_left = args
def MovedLeft(self, *args):
self.moved_left = args
def ReleasedLeft(self, *args):
self.released_left = args
def LeftSwitch(self, *args):
pass
def PressedRight(self, *args):
self.pressed_right = args
def MovedRight(self, *args):
self.moved_right = args
def ReleasedRight(self, *args):
self.released_right = args
def RightSwitch(self, *args):
pass
class NodeBody(Label):
#on_color is triggered on down or up on the button and move off or on the button
#it will return the initial values when the button is not pressed
#when the button is pressed, this will return True
pressed_flag = BooleanProperty(False)
#on_pressed is triggered when the button is initially hit
pressed = ListProperty([0, 0])
#on_released is triggered when the button is released
released = ListProperty([0, 0])
#on_moved is triggered when the touch is moved and the touch is not taken outside the button
moved = ListProperty([0, 0])
title = StringProperty('')
def __init__(self, **kwargs):
super(NodeBody, self).__init__(**kwargs)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.pressed = touch.pos
self.pressed_flag = True
return touch
return super(NodeBody, self).on_touch_down(touch)
def on_touch_move(self, touch):
if self.collide_point(*touch.pos):
self.moved = touch.pos
self.pressed_flag = True
return touch
self.pressed_flag = False
return super(NodeBody, self).on_touch_move(touch)
def on_touch_up(self, touch):
if self.collide_point(*touch.pos):
self.released = touch.pos
self.pressed_flag = False
return touch
return super(NodeBody, self).on_touch_up(touch)
class NodeEnd(Widget):
#on_color is triggered on down or up on the button and move off the button
#it will return the initial values when the button is not pressed
#when the button is pressed, this will return [0.8, 0.2, 0.2, 1]
#Not meant to be updated via code, only generate events
color = ListProperty([0.7, 0.1, 0.1, 0.8])
#[90, 270] for left
#[270, 90] for right
degree_range = ListProperty([0, 0])
#on_pressed is triggered when the button is initially hit
pressed = ListProperty([0, 0])
#on_released is triggered when the button is released
released = ListProperty([0, 0])
#on_moved is triggered when the button is moved and the touch is not taken outside the button
moved = ListProperty([0, 0])
def __init__(self, **kwargs):
super(NodeEnd, self).__init__(**kwargs)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.pressed = touch.pos
self.color = [0.8, 0.2, 0.2, 1]
return touch
return super(NodeEnd, self).on_touch_down(touch)
def on_touch_move(self, touch):
if self.collide_point(*touch.pos):
self.moved = touch.pos
self.color = [0.8, 0.2, 0.2, 1]
return touch
self.color = [0.7, 0.1, 0.1, 0.8]
self.released = touch.pos
return super(NodeEnd, self).on_touch_move(touch)
def on_touch_up(self, touch):
if self.collide_point(*touch.pos):
self.released = touch.pos
self.color = [0.7, 0.1, 0.1, 0.8]
return touch
return super(NodeEnd, self).on_touch_up(touch)
The test app:
nodeeditor.kv
#kivy 1.9.0
<NodeEditorWidget>:
GridLayout:
size: root.size
cols: 3
NodeWidget:
title: 'Test 1'
on_pressed_body: app.SelectNode(self, args[0])
on_moved_body: app.MoveNode(self, args[0])
on_released_body: app.DeselectNode(self, args[0])
on_pressed_left: app.SelectLeft(self, args[0])
on_moved_left: app.MoveLeft(self, args[0])
on_released_left: app.DeselectLeft(self, args[0])
on_pressed_right: app.SelectRight(self, args[0])
on_moved_right: app.MoveRight(self, args[0])
on_released_right: app.DeselectRight(self, args[0])
NodeTest.py
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.textinput import TextInput
from kivy.properties import ObjectProperty, StringProperty
from kivy.lang import Builder
from NodeWidget3 import NodeWidget
Builder.load_file('nodeeditor.kv')
class NodeEditorWidget(Widget):
pass
class NodeEditorApp(App):
def build(self):
return NodeEditorWidget()
print('Node Editor initialized')
def SelectNode(self, *args):
pass
def MoveNode(self, *args):
pass
def DeselectNode(self, *args):
pass
def SelectLeft(self, *args):
pass
def MoveLeft(self, *args):
pass
def DeselectLeft(self, *args):
pass
def SelectRight(self, *args):
pass
def MoveRight(self, *args):
pass
def DeselectRight(self, *args):
pass
if __name__ == '__main__':
NodeEditorApp().run()
I'm new to Kivy and Python so i hope this helps. When declaring and adding the widgets to your layout try adding 'self' at the beginning. Also in your original app i noticed you imported App but i don't see an App class.
In your test I added self to all the things and I also added a button. The button appeared in the window but the rest was black. So I have to ask when you are adding your widgets did you pick a color for them? It may be they are appearing black by default or/and might be the self thing. Hope this helps.

adding an on_release action to a kivy button

I am trying to refactor the last code sample so that the Button is actually its own class with an on_release action. But my code is failing.
I want to not only refactor it (per my attempt below) but I also need to set the text of the Button to "Clear"
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):
userdata = touch.ud
userdata['color'] = c = (random(), 1, 1)
with self.canvas:
Color(*c, mode='hsv')
d = 30
Ellipse(pos=(touch.x - d/2, touch.y - d/2), size=(d, d))
userdata['line'] = Line(points=(touch.x, touch.y))
def on_touch_move(self, touch):
touch.ud['line'].points += [touch.x, touch.y]
class ClearButton(Button):
def __init__(self, paint_widget):
self.paint_widget=paint_widget
def on_release(self, button):
self.paint_widget.canvas.clear()
class MyPaintApp(App):
def build(self):
parent = Widget()
painter = MyPaintWidget()
clearbtn = ClearButton(painter)
parent.add_widget(painter)
parent.add_widget(clearbtn)
return parent
if __name__ == '__main__':
MyPaintApp().run()
Without subclassing, you could just do:
class MyPaintWidget(Widget):
# ... put your previous methods here
def clear_canvas(self, *largs):
self.canvas.clear()
class MyPaintApp(App):
def build(self):
root = FloatLayout()
painter = MyPaintWidget()
cleanbtn.bind(on_release=self.painter.clear_canvas)
root.add_widget(painter)
root.add_widget(clearbtn)
return root
With subclassing, i would prefer to go with Kv langage:
from kivy.lang import Builder
Builder.load_string('''
<ClearButton>:
text: "Clear"
on_release: app.painter.canvas.clear()
''')
class ClearButton(Button):
pass
class MyPaintApp(App):
def build(self):
parent = Widget()
painter = MyPaintWidget()
clearbtn = ClearButton()
parent.add_widget(painter)
parent.add_widget(clearbtn)
return parent

Categories

Resources