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)
Related
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
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)
Below is the whole (test) app written with Kivy.
This is something like app preview application:
user enters a text of kv markup (see variable self.kv) and a text of classes (see variable self.text). Then he clicks the "preview" button and sees the result on the right side of the application.
Loading kv is implemented using kivy Builder.load_string(). Class loading is implemented using exec(, globals()).
The main problem is that for some reason I get the following error when I click on the preview button for the third time (the first 2 clicks work without errors):
TypeError: super(type, obj): obj must be an instance or subtype of type
The error can be because of exec(), (without exec I don’t get this error).
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
KV = '''
BoxLayout:
BoxLayout:
orientation: 'vertical'
CodeEd
id: code_editor
Button:
text: 'Preview'
on_release: app.preview()
Preview:
id: preview_area
<CodeEd#TextInput>
text: app.text
<Preview#RelativeLayout>
'''
class MyApp(App):
def build(self):
self._kv_filename = 'KvEditor_internal.' + str(self.uid)
self.text = '''
class MyButton(Button):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print (333)
super(MyButton, self).on_touch_down(touch)
'''
self.kv = 'MyButton'
self.root = Builder.load_string(KV)
def preview(self):
preview_area = self.root.ids.preview_area
#if 'MyButton' in globals():
# del globals()['MyButton']
#print ('===================')
#print ([i for i in dict(globals())])
try:
exec(self.text, globals())
except:
print ('some error when exec class ')
Builder.unload_file(self._kv_filename)
try:
preview_area.add_widget(Builder.load_string(self.kv, filename=self._kv_filename))
except Exception as e:
print (e.message if getattr(e, r"message", None) else str(e))
MyApp().run()
How to solve this problem?
Question - Preview area is RelativeLayout
It seems to work, but could you edit or add an example, please? I do
not need the number of buttons in the preview area increased. I just
want every time after pressing "Preview", in the preview area I have
content that just reflects the current text of self.kv and self.text.
Example
The following example has most of the enhancements applied and the Preview area is a RelativeLayout.
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.properties import NumericProperty, StringProperty
from kivy.factory import Factory
KV = '''
BoxLayout:
BoxLayout:
orientation: 'vertical'
CodeEd
id: code_editor
Button:
text: 'Preview'
on_release: app.preview()
Preview:
id: preview_area
<CodeEd#TextInput>
text: app.text
<Preview#RelativeLayout>
'''
class MyApp(App):
text = StringProperty('')
previous_text = StringProperty('')
def build(self):
self._kv_filename = 'KvEditor_internal.' + str(self.uid)
self.text = '''
class MyButton(Button):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print (333)
return super(Button, self).on_touch_down(touch)
'''
self.previous_text = self.text
self.kv = 'MyButton'
self.root = Builder.load_string(KV)
def preview(self):
preview_area = self.root.ids.preview_area
self.text = self.root.ids.code_editor.text
try:
# Class loading is implemented using exec(, globals())
exec(self.text, globals())
except:
print('some error when exec class ')
Builder.unload_file(self._kv_filename)
try:
# check for code changes
if self.text != self.previous_text:
Factory.unregister(self.kv)
Factory.register(self.kv, cls=globals()[self.kv])
total_children = len(preview_area.children)
preview_area.clear_widgets()
for child in range(total_children):
preview_area.add_widget(Builder.load_string(self.kv, filename=self._kv_filename))
self.previous_text = self.text
preview_area.add_widget(Builder.load_string(self.kv, filename=self._kv_filename))
except Exception as e:
print(e.message if getattr(e, r"message", None) else str(e))
MyApp().run()
Output
Question 1 - TypeError
TypeError: super(type, obj): obj must be an instance or subtype of
type
Solution - TypeError
There are two solutions to the TypeError.
Method 1
Replace super(MyButton, self).on_touch_down(touch) with return False
Method 2
Replace super(MyButton, self).on_touch_down(touch) with return super(Button, self).on_touch_down(touch)
Question 2 - Support code changes to MyButton class
What if MyButton class exists, but I want to make changes to this
class, for example change some of its methods, and so on?
Solution - Support code changes to MyButton class
In order to support code changes to MyButton class, the following enhancements are required:
Add import statement for Kivy Properties, from kivy.properties import NumericProperty, StringProperty
Add import statement for Kivy Factory object, from kivy.factory import Factory
Add a new class attribute, previous_text of type, StringProperty to keep track of code changes.
Initialize self.previous_text
Update self.text when method preview() is invoked.
Check for code changes
Unregister previous class MyButton using Kivy Factory
Register new class MyButton using Kivy Factory
Save the total number of MyButton added
Remove previously added MyButton using clear_widgets(). If previously added MyButtons are not removed, it will not have the functionality of the new features/code changes.
Use for loop to re-add previously added MyButton with the new functionality / features.
Assign new code changes, self.text to self.previous_text
Snippets
from kivy.properties import NumericProperty, StringProperty
from kivy.factory import Factory
from kivy.logger import Logger
...
class MyApp(App):
i = NumericProperty(0)
text = StringProperty('')
previous_text = StringProperty('')
def build(self):
...
self.text = '''
...
return True # consumed on_touch_down & don't propagate
# return False
return super(Button, self).on_touch_down(touch)
'''
self.previous_text = self.text
...
def preview(self):
preview_area = self.root.ids.preview_area
self.text = self.root.ids.code_editor.text
...
try:
# check for code changes
if self.text != self.previous_text:
Factory.unregister(self.kv)
Factory.register(self.kv, cls=globals()[self.kv])
total_children = len(preview_area.children)
preview_area.clear_widgets()
for child in range(total_children):
btn = Builder.load_string(self.kv, filename=self._kv_filename)
btn.text = str(child + 1)
preview_area.add_widget(btn)
self.previous_text = self.text
Example
The following example illustrates a code editor supporting code changes to MyButton class, and MyButton widgets are added into a GridLayout.
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty, StringProperty
from kivy.uix.button import Button
from kivy.logger import Logger
from kivy.factory import Factory
KV = '''
BoxLayout:
BoxLayout:
orientation: 'vertical'
CodeEd
id: code_editor
Button:
text: 'Preview'
on_release: app.preview()
Preview:
id: preview_area
<CodeEd#TextInput>:
text: app.text
<Preview#GridLayout>:
cols: 3
'''
class MyApp(App):
i = NumericProperty(0)
text = StringProperty('')
previous_text = StringProperty('')
def build(self):
self._kv_filename = 'KvEditor_internal.' + str(self.uid)
self.text = '''
class MyButton(Button):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print(f"touch.pos={touch.pos}")
print(f"Button.text={self.text}")
return True # consumed on_touch_down & don't propagate
# return False
return super(Button, self).on_touch_down(touch)
'''
self.previous_text = self.text
self.kv = 'MyButton'
self.root = Builder.load_string(KV)
def preview(self):
preview_area = self.root.ids.preview_area
self.text = self.root.ids.code_editor.text
try:
# Class loading is implemented using exec(, globals())
exec(self.text, globals())
except Exception as msg:
print('\nException: some error when exec class ')
Logger.error("KivyApp: Exception: some error when exec class")
print(msg)
quit()
Builder.unload_file(self._kv_filename)
try:
# check for code changes
if self.text != self.previous_text:
Factory.unregister(self.kv)
Factory.register(self.kv, cls=globals()['MyButton'])
total_children = len(preview_area.children)
preview_area.clear_widgets()
for child in range(total_children):
btn = Builder.load_string(self.kv, filename=self._kv_filename)
btn.text = str(child + 1)
preview_area.add_widget(btn)
self.previous_text = self.text
self.i += 1
btn = Builder.load_string(self.kv, filename=self._kv_filename)
btn.text = str(self.i)
preview_area.add_widget(btn)
except Exception as e:
print(e.message if getattr(e, r"message", None) else str(e))
MyApp().run()
Output
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.
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