Kivy draw on a widget - python

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.

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 position of the Label moves when resizing screen

I'm making an app in python kivy and in my tacscreen I have a draggable Image and an Label, whenever I drag my draggable Image my label drags with it as well. The position of the label is just below the draggable Image. The problem that I'm facing is whenever I resize my window and drag the image the label goes from this to this the space between the Image and the label is too much. How can I fix this? I want the label to always be like how it is in the first screenshot even after I resize the screen and drag. Below is my code. I have been trying to find a solution to this for a months now. I really appreciate any help. Thanks!
main.py
from kivy.properties import BooleanProperty
from kivy.properties import ObjectProperty
from kivy.metrics import dp
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
from kivy.uix.behaviors import DragBehavior
from kivy.uix.image import Image
class DragImage1(DragBehavior, Image):
dragging = BooleanProperty(False)
drag_label = ObjectProperty()
def on_touch_move(self, touch):
if touch.grab_current is self:
self.drag_label.pos = self.x, self.y - dp(300)
return super().on_touch_move(touch)
def on_touch_up(self, touch):
uid = self._get_uid()
if uid in touch.ud:
# do something on drop
print(self.source, 'dropped at', touch.x, touch.y)
return super(DragImage1, self).on_touch_up(touch)
class TacScreen(Screen):
Pass
GUI = Builder.load_file("main.kv")
class MainApp(App):
def build(self):
return GUI
def change_screen(self, screen_name, *args):
self.root.current = screen_name
MainApp().run()
main.kv
#:include tacscreen.kv
ScreenManager:
id: screen_manager
TacScreen:
name: "tac_screen"
id: tac_screen
tacscreen.kv
<tacscreen>:
#:import utils kivy.utils
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "Background.png"
DragImage1:
drag_label: per
pos: root.center_x - self.width/2, root.center_y - self.height/0.3
size_hint: 1, .1
source: "Image.png"
Label:
id: per
text: "Hello"
font_size: "20dp"
pos: root.center_x - self.width/2, root.center_y - self.height/1.2
You can modify your kv to do the positioning for you:
DragImage1:
id: di # added id for use by the Label
drag_label: per
pos: root.center_x - self.width/2, root.center_y - self.height/0.3
size_hint: 1, .1
source: "Image.png"
Label:
id: per
text: "Hello"
font_size: "20dp"
size_hint: None, None
size: self.texture_size # set size to just contain the text
pos: di.center_x - self.width/2, di.y - self.height # adjust postion based on the DragImage1
And, since kv is now handling the positioning, you should remove the on_touch_move() method from the DragImage1 class.
Note that this will handle positioning when resizing before dragging. But if you drag before resizing, then the resizing will revert the position to that defined in the kv.

How to draw a rectangle over a button with fixed width in Kivy?

I want to cover a button with a red rectangle in Kivy. This button stands in a BoxLayout. I have defined the MyButton class that draws a rectangle over a button and it works. However, if I fix the width of the BoxLayout the button is in, the rectangle goes to the bottom left corner.
MWE:
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.graphics import Color, Rectangle
Builder.load_string('''
<MainApp>:
orientation: 'horizontal'
BoxLayout:
orientation: 'vertical'
# width: 100 # uncommenting these two lines breaks the rectangle positioning
# size_hint: (None,1)
MyButton:
text: 'button1'
size_hint: (1,None)
height: 50
Label:
Label:
''')
class MyButton(Button):
def __init__(self, **kwargs):
super(MyButton, self).__init__(**kwargs)
def on_size(self, *args):
self.canvas.after.clear()
with self.canvas.after:
Color(1, 0, 0, 1)
Rectangle(pos=self.pos, size=self.size)
class MainApp(BoxLayout):
pass
class TestApp(App):
def build(self):
return MainApp()
if __name__ == '__main__':
TestApp().run()
Uncommenting the two commented lines in Builder.load_string breaks the rectangle positioning. What am I missing?
In your MyButton class, you have a on_size() method, but no on_pos() method.
I think you just need to add that method:
def on_pos(self, *args):
self.on_size()

Move Kivy window by label drag - Kivy, Python

I want to create custom window header of a Kivy window. I am very new to kivy so please provide some explanation how the events work. I need to simply move the window by "moving" the label.
First of all I want to know, why this does call any function when I click or drag the label. It is in KvLang:
#:import main main.window
CustBoxLayout:
<CustBoxLayout>:
orientation: 'vertical'
Label:
id: header
text: 'MyApp'
font_size: 24
padding_x: 16
color: self.theme_cls.primary_color
on_touch_down: main.click
on_touch_move: main.move
...
Any function is not called when I click or drag the label. However if I change main.click to for example print('touched!') it works.
So I created my own class:
class HeadLabel(MaterialLabel):
def on_touch_down(self, touch):
window.click(touch)
def on_touch_move(self, touch):
window.drag(touch)
This works. But now I don't know how to get the screen position out of the MotionEvent event. This is my actual code of window:
class WindowApp(App):
theme_cls = ThemeManager()
def build(self):
self.theme_cls.theme_style = 'Light'
self.theme_cls.primary_palette = 'Purple'
return CustBoxLayout()
def click(self, touch):
self.touch_x, self.touch_y = touch.spos[0], touch.spos[1]
def drag(self, touch):
Window.top = self.touch_y + touch.spos[0]
Window.left = self.touch_x + touch.spos[1])
Any help will be highly appreciated.
Define a class for the custom Label and implement on_touch_down and on_touch_move methods. Please refer to the example below for details.
Programming Guide » Input management » Touch event basics
By default, touch events are dispatched to all currently displayed
widgets. This means widgets receive the touch event whether it occurs
within their physical area or not.
In order to provide the maximum flexibility, Kivy dispatches the
events to all the widgets and lets them decide how to react to them.
If you only want to respond to touch events inside the widget, you
simply check for collision.
Example
main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.core.window import Window
from kivymd.theming import ThemeManager
class CustomLabel(Label):
def on_touch_down(self, touch):
print("\nCustomLabel.on_touch_down:")
if self.collide_point(*touch.pos):
print("\ttouch.pos =", touch.pos)
self.touch_x, self.touch_y = touch.spos[0], touch.spos[1]
return True
return super(CustomLabel, self).on_touch_down(touch)
def on_touch_move(self, touch):
print("\nCustomLabel.on_touch_move:")
if self.collide_point(*touch.pos):
print("\ttouch.pos =", touch.pos)
Window.top = self.touch_y + touch.spos[0]
Window.left = self.touch_x + touch.spos[1]
return True
return super(CustomLabel, self).on_touch_move(touch)
class CustBoxLayout(BoxLayout):
pass
class TestApp(App):
theme_cls = ThemeManager()
def build(self):
self.theme_cls.theme_style = 'Light'
self.theme_cls.primary_palette = 'Purple'
return CustBoxLayout()
if __name__ == "__main__":
TestApp().run()
test.kv
#:kivy 1.10.0
<CustomLabel>:
<CustBoxLayout>:
orientation: 'vertical'
CustomLabel:
id: header
text: 'MyApp'
font_size: 24
padding_x: 16
color: app.theme_cls.primary_color
Button:
text: 'Button Widget'
font_size: 24
padding_x: 16
color: app.theme_cls.primary_color
Output

How do I make on_touch_down widget specific?

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)

Categories