How do I make on_touch_down widget specific? - python

I'm trying to create a simple drawing app in kivy but i'm having some issues with the
on_touch_down
function as it regards the entire class and not just a specific widget. So when I use the on touch down and on touch move functions to draw on the canvas it affects and effectively disables the touch down functions bound to buttons. Here's the code where the button doesn't work.
python code:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.graphics import Line
from kivy.graphics import *
from kivy.uix.widget import Widget
class MyScreenManager(ScreenManager):
pass
class MenuScreen(Screen):
pass
class DrawScreen(Screen):
def on_touch_down(self, touch):
with self.canvas.before:
Color(1, 0, 0)
touch.ud["line"] = Line(points=(touch.x, touch.y), width=5)
def on_touch_move(self, touch):
touch.ud["line"].points += (touch.x, touch.y)
class DrawApp(App):
def build(self):
return MyScreenManager()
DrawApp().run()
kivy code:
<MenuButton#Button>:
font_size: 65
size_hint: 0.4, 0.25
<MyScreenManager>:
MenuScreen:
id: menu
name: "menu"
DrawScreen:
id: draw
name: "draw"
<MenuScreen>:
canvas.before:
Color:
rgba: 1,1,1,1
Rectangle:
size: self.size
pos: self.pos
MenuButton:
text: "Draw"
on_release: root.manager.current = "draw"
pos_hint:{"center_x":0.5, "center_y":0.6}
MenuButton:
text: "Quit"
on_release: app.stop()
pos_hint:{"center_x":0.5, "center_y":0.3}
<DrawScreen>:
canvas.before:
Color:
rgba: 1,1,1,1
Rectangle:
size: self.size
pos: self.pos
Button:
id: but
size_hint: 0.2,0.1
pos_hint_x: 0 + self.width
font_size: 30
text: "Back"
on_release: root.manager.current = "menu"
I managed to find a simple work around by using collide_point, here's my workaround code:
class DrawScreen(Screen):
def on_touch_down(self, touch):
but = self.ids.but
if but.collide_point(touch.x, touch.y):
self.manager.current = "menu"
else:
with self.canvas.before:
Color(1, 0, 0)
touch.ud["line"] = Line(points=(touch.x, touch.y), width=5)
def on_touch_move(self, touch):
touch.ud["line"].points += (touch.x, touch.y)
But while this works it brings up a whole world of new problems like me having to manually configure every button to change source when held down and the function not running until the button is released. It also means that everything I add to the class has to be added to the if statement.
I'm quite positive that there has to be a simpler way. My first thought was that maybe one could add the on touch down to only affect one widget? My second thought was that maybe if would be better to not draw on the canvas or something?
Any help or pointers are appreciated, thanks!

when you overwrite a method, you must return the same method of the super class
something like this:
...
class DrawScreen(Screen):
def on_touch_down(self, touch):
with self.canvas.before:
Color(1, 0, 0)
touch.ud["line"] = Line(points=(touch.x, touch.y), width=5)
return super(DrawScreen, self).on_touch_down(touch)
def on_touch_move(self, touch):
touch.ud["line"].points += (touch.x, touch.y)
return super(DrawScreen, self).on_touch_move(touch)

Related

Drawing widget class won't draw when added to existing code Kivy Python

Everything from MyGridLayout(Widget) works fine. The drawing box for the DrawingWidget shows up, but does not draw a line. If I put the DrawingWidget in it's own solo program it allows me to draw just fine. Is there something wrong with the placement of the class? I have tried many arrangements of the KV document and am only able to get the drawing box to appear below all my other widgets from MyGridLayout that function fine.
class MyGridLayout(Widget):
def __init__(self, **kwargs):
super(MyGridLayout, self).__init__(**kwargs)
pass
class DrawingWidget(Widget):
def __init__(self, **kwargs):
super(DrawingWidget, self).__init__(**kwargs)
with self.canvas:
Color(1, 1, 1, 1)
self.rect = Rectangle(size=self.size,
pos=self.pos)
self.bind(pos=self.update_rectangle,
size=self.update_rectangle)
def update_rectangle(self, instance, value):
self.rect.pos = self.pos
self.rect.size = self.size
def on_touch_down(self, touch):
super(DrawingWidget, self).on_touch_down(touch)
if not self.collide_point(*touch.pos):
return
with self.canvas:
Color(0, 0, 0, 1)
self.line = Line(points=[touch.pos[0], touch.pos[1]], width=2)
def on_touch_move(self, touch):
if not self.collide_point(*touch.pos):
return
self.line.points = self.line.points + [touch.pos[0], touch.pos[1]]
class funcswspinnerall(App):
def build(self):
return MyGridLayout()
if __name__ == '__main__':
funcswspinnerall().run()
And here is an abbreviated KV file
<MyGridLayout>:
ScrollView:
size: root.width, root.height
GridLayout:
cols:1
padding:9,9,9,9
GridLayout:
cols:3
size_hint_y:.5
Label:
Button:
text:'Enter Activity'
on_press:root.sendmatt()
size_hint_x:3
size_hint_y:1
Label:
GridLayout:
cols:1
DrawingWidget:
<DrawingWidget>:
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: 50
The problem is that you have the DrawingWidget inside a ScrollView. The ScrollView wants to use the mouse touches for scrolling, so there is an interference with your drawing. The fix is to either move the DrawingWidget out of the ScrollView or add:
scroll_type: ['bars']
to the ScrollView, which limits your scrolling to using the scroll bars.

change the background canvas kivy

I have a canvas background but I am trying to switch the back ground when a person his a correct button to move on to the next level. I am trying to do this all within one class. Is there a way to assign an image to a canvas rectangle and then on the press of a button the canvas image will change to a new source.
main.py
class MazeSolution(Screen):
def CheckDoor1(self):
if self.ids.door1.source == "correct":
print("correct")
self.ids.change.source = 's-curvee selection.png'
else:
print("incorrect")
main.kv
#:import utils kivy.utils
<MazeSolution>:
FloatLayout:
canvas:
Rectangle:
id: change
source: 'selection grass.png'
pos: self.pos
size: self.size
Button:
pos_hint: {"top": .8, "right": .75}
size_hint: .5, .1
text:
"Door 1"
source: "<random_name>"
id: door1
on_press:
root.CheckDoor1()
I don't think assigning ids within canvas instructions is supported. But you can accomplish the same thing by creating the Rectangle in python:
from kivy.app import App
from kivy.graphics.vertex_instructions import Rectangle
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
class MazeSolution(Screen):
def __init__(self):
super(MazeSolution, self).__init__()
# make the Rectangle here and save a reference to it
with self.canvas.before:
self.rect = Rectangle(source='selection grass.png')
def on_pos(self, *args):
# update Rectangle position when MazeSolution position changes
self.rect.pos = self.pos
def on_size(self, *args):
# update Rectangle size when MazeSolution size changes
self.rect.size = self.size
def CheckDoor1(self):
if self.ids.door1.source == "correct":
print("correct")
# use the saved reference to change the background
self.rect.source = 's-curvee selection.png'
else:
print("incorrect")
Builder.load_string('''
<MazeSolution>:
FloatLayout:
Button:
pos_hint: {"top": .8, "right": .75}
size_hint: .5, .1
text:
"Door 1"
source: "correct"
id: door1
on_press:
root.CheckDoor1()
''')
class TestApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(MazeSolution())
return sm
TestApp().run()
The on_pos() and on_size() methods perform the operations that kv would set up for you automatically, but must be set up manually since the Rectangle is not created in kv.

Why does rescheduling Clock intervals cause image positions to reset?

Within the program, one can see that as the ball bounces around, one has the ability to open settings page from the top right corner. Doing so pauses the ball's motion and opens the settings page.
Sorry if this is an obvious question, but I am again stumped by the strange inner workings of Kivy, and the docs aren't much use on these types of issues.
Problem
Ball always start at centre position. Want ball to continue / resume from previous position before switching screen?
Steps to recreate problem
Click on label, "Tap to start". Ball started bouncing from centre position
Click on cogwheel image. Settings screen is displayed
Click on "x" to close Settings screen. Ball started bouncing from centre position.
Code:
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy import Config
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition,\
SlideTransition
from kivy.uix.widget import Widget
from kivy.animation import Animation
from kivy.properties import NumericProperty, ReferenceListProperty,\
ObjectProperty
from kivy.clock import Clock
from kivy.vector import Vector
from random import randint
Builder.load_string('''
<Ball>:
size_hint: None, None
source: '58-Breakout-Tiles.png'
pos: self.pos
size: 15, 15
<SettingsScreen>:
close: close
AnchorLayout:
anchor_x: 'left'
anchor_y: 'top'
Image:
id: close
size_hint: .03, .03
source: 'grey_crossGrey.png'
GridLayout:
cols: 2
Label:
font_name: 'vgafix.fon'
text: 'Music: '
Switch:
active: True
Label:
font_name: 'vgafix.fon'
text: 'Sounds: '
Switch:
active: True
<MenuScreen>:
cog: cog
AnchorLayout:
anchor_x: 'right'
anchor_y: 'top'
Image:
id: cog
size_hint: .03, .03
source: 'settings-cog.png'
BoxLayout:
orientation: 'vertical'
Image:
source: 'brickbreaker log.png'
Label:
font_name: 'vgafix.fon'
text: 'Tap to start'
<GameScreen>:
ball: ball
cog: cog
AnchorLayout:
anchor_x: 'right'
anchor_y: 'top'
Image:
id: cog
size_hint: .03, .03
source: 'settings-cog.png'
Ball:
id: ball
size_hint: None, None
center: self.parent.center
''')
Config.set('graphics', 'multisamples', '0')
class Ball(Image):
velocityX, velocityY = NumericProperty(0), NumericProperty(0)
velocity = ReferenceListProperty(velocityX, velocityY)
def move(self):
self.pos = Vector(*self.velocity) + self.pos
class Player(Widget):
pass
class Brick(Widget):
pass
class SettingsScreen(Screen):
def __init__(self, **kwargs):
super(SettingsScreen, self).__init__(**kwargs)
self.previous = False
def on_touch_down(self, touch):
if self.close.collide_point(*touch.pos):
sm.transition = SlideTransition(direction = 'right')
if self.previous == 'game':
sm.get_screen('game').interval()
sm.current = self.previous
class MenuScreen(Screen):
def __init__(self, **kwargs):
super(MenuScreen, self).__init__(**kwargs)
def on_touch_down(self, touch):
if self.cog.collide_point(*touch.pos):
sm.transition = SlideTransition(direction = 'left')
sm.get_screen('settings').previous = 'menu'
sm.current = 'settings'
else:
sm.transition = FadeTransition()
sm.current = 'game'
class GameScreen(Screen):
def __init__(self, **kwargs):
super(GameScreen, self).__init__(**kwargs)
self.initBall()
self.interval = Clock.schedule_interval(self.update, 1.0/60.0)
def on_touch_down(self, touch):
if self.cog.collide_point(*touch.pos):
sm.transition = SlideTransition(direction = 'left')
sm.get_screen('settings').previous = 'game'
self.interval.cancel()
sm.current = 'settings'
def initBall(self):
self.ball.center = self.center
self.ball.velocity = Vector(0, 4).rotate(randint(0, 360))
def update(self, dt):
self.ball.move()
if (self.ball.y < 0) or (self.ball.y > self.height-15):
self.ball.velocityY *= -1
# bounce off left and right
if (self.ball.x < 0) or (self.ball.x > self.width-15):
self.ball.velocityX *= -1
sm = ScreenManager(transition = FadeTransition())
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(GameScreen(name='game'))
sm.add_widget(SettingsScreen(name='settings'))
class BrickBreakerInsanityApp(App):
def build(self):
return sm
if __name__ == '__main__':
BrickBreakerInsanityApp().run()
Code assets (required):
https://drive.google.com/open?id=1GAnv5DfjNUuAXTybmsan90Dm0OuSVOfb
https://i.stack.imgur.com/rR799.png
https://i.stack.imgur.com/ngYvL.png
https://i.stack.imgur.com/AuxI3.png
https://i.stack.imgur.com/ypd7C.png
Root cause
The SlideTransition and direction is causing the ball to start at the centre.
Solution
Remove all references to SlideTransition.
Example
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.image import Image
from kivy import Config
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition,
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty, StringProperty
from kivy.clock import Clock
from kivy.vector import Vector
from random import randint
Builder.load_string('''
<Ball>:
size_hint: None, None
source: './assets/icons/58-Breakout-Tiles.png'
pos: self.pos
size: 15, 15
<SettingsScreen>:
close: close
AnchorLayout:
anchor_x: 'left'
anchor_y: 'top'
Image:
id: close
size_hint: .03, .03
source: './assets/icons/grey_crossGrey.png'
GridLayout:
cols: 2
Label:
font_name: "./assets/fonts/vgafix.fon"
text: 'Music: '
Switch:
active: True
Label:
font_name: "./assets/fonts/vgafix.fon"
text: 'Sounds: '
Switch:
active: True
<MenuScreen>:
cog: cog
AnchorLayout:
anchor_x: 'right'
anchor_y: 'top'
Image:
id: cog
size_hint: .03, .03
source: './assets/icons/settings-cog.png'
BoxLayout:
orientation: 'vertical'
Image:
source: "./assets/icons/brickbreaker log.png"
Label:
font_name: "./assets/fonts/vgafix.fon"
text: 'Tap to start'
<GameScreen>:
ball: ball
cog: cog
AnchorLayout:
anchor_x: 'right'
anchor_y: 'top'
Image:
id: cog
size_hint: .03, .03
source: './assets/icons/settings-cog.png'
Ball:
id: ball
size_hint: None, None
center: self.parent.center
''')
Config.set('graphics', 'multisamples', '0')
class Ball(Image):
velocityX, velocityY = NumericProperty(0), NumericProperty(0)
velocity = ReferenceListProperty(velocityX, velocityY)
def move(self):
self.pos = Vector(*self.velocity) + self.pos
class Player(Widget):
pass
class Brick(Widget):
pass
class SettingsScreen(Screen):
def __init__(self, **kwargs):
super(SettingsScreen, self).__init__(**kwargs)
self.previous = StringProperty('')
def on_touch_down(self, touch):
if self.close.collide_point(*touch.pos):
self.manager.current = self.previous
class MenuScreen(Screen):
def on_touch_down(self, touch):
if self.cog.collide_point(*touch.pos):
self.manager.get_screen('settings').previous = self.manager.current
self.manager.current = 'settings'
else:
self.manager.transition = FadeTransition()
self.manager.current = 'game'
class GameScreen(Screen):
def __init__(self, **kwargs):
super(GameScreen, self).__init__(**kwargs)
self.initBall()
def on_pre_enter(self, *args):
self.interval = Clock.schedule_interval(self.update, 1.0 / 60.0)
def on_touch_down(self, touch):
if self.cog.collide_point(*touch.pos):
self.manager.get_screen('settings').previous = self.manager.current
self.manager.current = 'settings'
def initBall(self):
self.ball.center = self.center
self.ball.velocity = Vector(0, 4).rotate(randint(0, 360))
def update(self, dt):
self.ball.move()
if (self.ball.y < 0) or (self.ball.y > self.height - 15):
self.ball.velocityY *= -1
# bounce off left and right
if (self.ball.x < 0) or (self.ball.x > self.width - 15):
self.ball.velocityX *= -1
def on_pre_leave(self, *args):
self.interval.cancel()
sm = ScreenManager(transition=FadeTransition())
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(GameScreen(name='game'))
sm.add_widget(SettingsScreen(name='settings'))
class BrickBreakerInsanityApp(App):
def build(self):
return sm
if __name__ == '__main__':
BrickBreakerInsanityApp().run()

Clean Canvas in kivy language from other class

i just started using Kivy for my application and i got a problem.
i try to clean my canvas but i cant relate the button to the canvas
class DrawInput(Widget):
def on_touch_down(self, touch):
print(touch)
with self.canvas:
touch.ud["line"] = Line(points=(touch.x, touch.y), width=100)
def on_touch_move(self, touch):
#print(touch)
touch.ud["line"].points += (touch.x, touch.y)
def on_touch_up(self, touch):
self.export_to_png("roy.png")
print("RELEASED!", touch)
def cleaner(self):
self.canvas.clear()
class AnotherScreen(Screen):
pass
presentation = Builder.load_file("main2.kv")
class MainApp(App):
def build(self):
return presentation
def clear_canvas(self, obj):
MainApp().run()
and here is the main2.kv
GridLayout:
cols: 2
Button:
on_release: root.change_text()
color: 0,1,0,1
font_size: 25
size_hint: 0.3,0.2
text: root.random_number
pos_hint: {"right":1, "top":1}
DrawInput
Button:
on_release: root.clean()
color: 0,1,0,1
font_size: 25
size_hint: 0.3,0.2
text: "Clear"
my problem is that i need to call the Clean Method from other class, but when i try it its says that i need to send "Self", does anyone can help me?
just trying to clean the canvas that relate to the DrawInput
You have to use ObjectProperty to hook up to the DrawInput child widget to reference the cleaner method. Please refer to the example below for details.
Example
main.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Line
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
class DrawInput(Widget):
def on_touch_down(self, touch):
print(touch)
with self.canvas:
touch.ud["line"] = Line(points=(touch.x, touch.y), width=100)
def on_touch_move(self, touch):
#print(touch)
touch.ud["line"].points += (touch.x, touch.y)
def on_touch_up(self, touch):
# self.export_to_png("roy.png")
self.export_to_png("kivy-logo-black-64.png")
print("RELEASED!", touch)
def cleaner(self):
print("cleaner: ", self)
self.canvas.clear()
class FirstScreen(Screen):
draw = ObjectProperty()
class AnotherScreen(Screen):
pass
class Manager(ScreenManager):
mgr = ObjectProperty()
def change_text(self):
pass
def random_number(self):
pass
def clean(self):
print("clean: ", self)
self.mgr.draw.cleaner()
class TestApp(App):
def build(self):
return Manager()
if __name__ == "__main__":
TestApp().run()
test.kv
#:kivy 1.10.0
<DrawInput>:
<Manager>:
mgr: first_screen
FirstScreen:
id: first_screen
AnotherScreen:
<FirstScreen>:
draw: draw_input
GridLayout:
cols: 2
Button:
on_release: app.root.change_text()
color: 0,1,0,1
font_size: 25
size_hint: 0.3, 0.2
# text: app.root.random_number()
text: "123"
pos_hint: {"right":1, "top":1}
DrawInput:
id: draw_input
Button:
on_release: app.root.clean()
color: 0,1,0,1
font_size: 25
size_hint: 0.3, 0.2
text: "Clear"
<AnotherScreen>:
Output

Kivy draw on a widget

I am new to Kivy and after I have done the tutorials my next step was to add the two tutorial's widgets in one application. The class CombWidget will be my Widget to which the Paint and PingPong widgets will bne added to. For an intermediate step a BoxLayout was added and
a few Buttons
MyPaintWidget
in the BoxLayout.
To restrict the drawing only to MyPaintWidget a if statement was added
def on_touch_move(self, touch):
if self.collide_point(touch.x, touch.y):
touch.ud['line'].points += [touch.x, touch.y]
The line are drawn only to a small spot just above the buttons. The dot are draw everywhere except on the Buttons. The Buttons also don't react to clicks any more.
The code:
from random import random
from kivy.app import App
from kivy.graphics import Color, Ellipse, Line
from kivy.uix.button import Button
from kivy.uix.widget import Widget
class CombWidget(Widget):
pass
class MyPaintWidget(Widget):
def on_touch_down(self, touch):
color = (random(), 1, 1)
with self.canvas:
Color(*color, mode='hsv')
d = 30.
Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))
touch.ud['line'] = Line(points=(touch.x, touch.y))
def on_touch_move(self, touch):
if self.collide_point(touch.x, touch.y):
touch.ud['line'].points += [touch.x, touch.y]
class MyPaintApp(App):
def build(self):
return CombWidget()
if __name__ == '__main__':
MyPaintApp().run()
and the layout file:
#:kivy 1.7.0
<CombWidget>:
BoxLayout:
orientation: 'vertical'
padding: 20
spacing: 50
MyPaintWidget:
size: 100000, 100000
size_hint: 100000, 100000
Button:
text: "Hallo"
Button:
text: "Hallo 1"
Button:
text: "Hallo 2"
To increase the size of MyPaintWidget I used the size: and size_hint: parameters in the kv file but not success.
Can anybody help met to increase the size of MyPaintWidget so that that area behaves just like the MyPaintyApp the in tutorial. Also why does my buttons show when you click on them.
Regards
The solution was to modify the layout file
<CombWidget>:
BoxLayout:
orientation: 'vertical'
size: root.size
also by adding 'return True' to the methods on_touch_down and on_touch_move, everything worked.

Categories