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
Related
I have been trying to make this code work. Im using ScreenManager to manage my screen.
I want the Input I entered on the first screen to be displayed the next screen. But instead, it just shows the initial value, and it doesn't change to the Inputted value.
Here is the Code i have done
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.clock import Clock
Builder.load_string("""
<MenuScreen>:
promptObject: prompts
BoxLayout:
orientation: 'horizontal'
TextInput:
id: prompts
pos: 20,20
Button:
text: "Enter Prompt"
pos: 30,30
size: 100, 30
on_press: root.submit()
<Newscreen>
BoxLayout:
orientation: 'vertical'
TextInput:
id: display_output
text: root.output
readonly: True
""")
class MenuScreen(Screen):
promptObject = ObjectProperty()
prompt = ''
def submit(self):
prompt = self.promptObject.text
global result
result = prompt
sm.add_widget(NewScreen(name="Settings"))
sm.switch_to(sm.get_screen("Settings"))
NewScreen.display(self)
class NewScreen(Screen):
output = "testing testing"
def display(self):
self.output = result
print(result) #To test if it works
class TestApp(App):
def build(self):
global sm
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
return sm
if __name__ == '__main__':
TestApp().run()
I'm also thinking if i can instead Declare the Layout for the second screen later right before i call for the next Screen. Maybe that could work but if you have other method, it would be nice to see it.
thank you for the concise single-file example. this is a very helpful way to submit a kivy question. I have modified and tested the below app with various changes.
I changed the root.submit to app.submit. This is not strictly required, it is just a choice in this example to put the logic in the main app. it is also possible to use root.submit and put the logic in the widget but one would have to pass a reference to the screen manager into that widget in that case.
imported TextInput object instead of using ObjectProperty. when using an IDE it is helpful to declare objects with the specific type because it enables auto-complete
assigned the ScreenManager to self.sm so this object is available throughout the app.
finally, got rid of any reference to global. I think it is better to avoid use of this keyword and explicitly create the variable at the highest level where you need it and pass the value into the objects requiring it.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
# from kivy.properties import ObjectProperty
from kivy.uix.textinput import TextInput
from kivy.properties import StringProperty
from kivy.clock import Clock
Builder.load_string("""
<MenuScreen>:
promptObject: prompts
BoxLayout:
orientation: 'horizontal'
TextInput:
id: prompts
pos: 20,20
Button:
text: "Enter Prompt"
pos: 30,30
size: 100, 30
on_press: app.submit()
<Newscreen>
BoxLayout:
orientation: 'vertical'
TextInput:
id: display_output
text: root.output
readonly: True
""")
class MenuScreen(Screen):
promptObject = TextInput()
class NewScreen(Screen):
output = StringProperty()
def __init__(self, **kw):
super().__init__(**kw)
def display(self, result):
# set the string property equal to the value you sent
self.output = result
print(result) # To test if it works
class TestApp(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# create screen manager with self so that you have access
# anywhere inside the App
self.sm = ScreenManager()
# create the main screen
self.menu_screen = MenuScreen(name='menu')
# this could be deferred, or created at initialization
self.settings_screen = NewScreen(name='Settings')
#
def submit(self):
prompt = self.menu_screen.promptObject.text
result = prompt
# optional, deferred creation
# self.settings_screen = NewScreen(name='Settings')
# add to the screen manager
self.sm.add_widget(self.settings_screen)
# enter the value into your other screen
self.settings_screen.display(result)
# switch to this screen
self.sm.current="Settings"
def build(self) -> ScreenManager:
# could create this screen right away, depending...
# self.sm.add_widget(self.settings_screen)
# of course you need the main screen
self.sm.add_widget(self.menu_screen)
# redundant, unless you create all screens at the beginning
self.sm.current = 'menu'
return self.sm
if __name__ == '__main__':
TestApp().run()
In below code I need the button on the edit_button_tab to switch to edit_input_tab. I really need to switch it that way as I need to switch between predefined classes EditButton and EditInput. This is a part of a bigger program with few Buttons in different location of a layout and I cant define them within <MainTabbedPanel> class. I've tried many ways to call switch_to (example in the quotes) but they didn't work.
CODE
from kivy.animation import Animation
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelStrip
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelHeader
from kivy.factory import Factory
theRoot = """
#:import Factory kivy.factory.Factory
<EditButton>
orientation: 'vertical'
Button:
text: 'Switch to Edit Screen'
on_press: root.change_tab('edit_screen')
<EditInput>
orientation: 'vertical'
TextInput:
<UnCloseableHeader>
color: 0,0,0,1
disabled_color: self.color
# variable tab_width
text: 'tabx'
size_hint_x: None
width: self.texture_size[0] + 40
BoxLayout:
pos: root.pos
size_hint: None, None
size_y: 20
padding: 3
Label:
id: lbl
text: root.text
<MainTabbedPanel#BoxLayout>
size_hint: (1, 1)
default_tab: edit_button_tab
tab_width: 130
FloatLayout:
EditButton:
id: edit_button
EditInput:
id: edit_input
UnCloseableHeader:
id: edit_button_tab
text: 'Edit'
content: edit_button.__self__
UnCloseableHeader:
id: edit_input_tab
text: 'Edit Tab'
content: edit_input.__self__
MainTabbedPanel:
"""
class EditInput(BoxLayout):
def __init__(self, **kwargs):
super(EditInput, self).__init__(**kwargs)
class EditButton(BoxLayout):
def __init__(self, **kwargs):
super(EditButton, self).__init__(**kwargs)
def change_tab(self, tab):
print('TAB', tab)
#call switch method from MainTabbedPanel
'''the way I've tried
mtp = MainTabbedPanel
mtp.switch_to('edit_input_tab')'''
class MainTabbedPanel(TabbedPanel):
def __init__(self, **kwargs):
super(MainTabbedPanel, self).__init__(**kwargs)
def switch(self, tab):
print("SWITCH TO", tab, self.ids.keys())
self.switch_to(self.ids[tab])
class UnCloseableHeader(TabbedPanelHeader):
pass
Factory.register('UnCloseableHeader', cls=UnCloseableHeader)
sm = Builder.load_string(theRoot)
class TabbedPanelApp(App):
def build(self):
return sm
if __name__ == '__main__':
TabbedPanelApp().run()
EDIT
I've tried with below snippet. It prints IDS of MainTabbedPanel but does not change the tabs.
class EditButton(BoxLayout):
def __init__(self, **kwargs):
super(EditButton, self).__init__(**kwargs)
def change_tab(self, tab):
print('TAB', tab)
MainTabbedPanel.tab = tab
MainTabbedPanel()
#call switch method from MainTabbedPanel
'''the way I've tried
mtp = MainTabbedPanel
mtp.switch_to('edit_input_tab')'''
class MainTabbedPanel(TabbedPanel):
tab = ''
def __init__(self, **kwargs):
super(MainTabbedPanel, self).__init__(**kwargs)
self.tabs_showing = True
if self.tab != '':
Clock.schedule_once(self.switch)
def switch(self, dt):
print("SWITCH TO", self.tab, self.ids.keys())
self.switch_to(self.ids[self.tab])
Use App.get_running_app() to get an instance of your app
Use root to get an instance of your root
Snippets
def change_tab(self, tab):
print('TAB', tab)
mtp = App.get_running_app().root
mtp.switch_to(mtp.ids.edit_input_tab)
Notes
In your kv, you defined a Dynamic class,
<MainTabbedPanel#BoxLayout>. It should be a class rule,
<MainTabbedPanel> because in your Python code, you have defined
class MainTabbedPanel(TabbedPanel): i.e. inheritance mismatch and
class type mismatch.
Whenever I press any area in MyWidget then it trigger the first MyButton 's on_touch_down event inside MyLayout class.
I am new to kivy and don't understand why this is happening.
<MyButton#Button>:
background_normal: ''
background_color: [1,0,1,1]
font_size: "44dp"
color:[1,1,0,1]
border:(1,1,1,1)
text_size:self.size
<MyWidget#Widget>:
id:customwidget
canvas.before:
Color:
rgba:0.2,0.44,0.66,1
Rectangle:
pos:self.pos
size:self.size
<MyLayout>:
MyWidget:
id:mywidget
MyButton:
id:button1
text: "User Name"
spacing: "10dp"
on_touch_down: button1.text= self.text + "?"
opacity: .8
MyButton:
text: "ikinci"
on_touch_down: root.export_to_png("filename.png")
MyButton:
text: "ucuncu"
And this is python:
import kivy
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.widget import Widget
class MyLayout(BoxLayout):
pass
class KivyApp(App):
def build(self):
return MyLayout()
if __name__ == "__main__":
KivyApp().run()
Python code
Create a class MyButton() and implement on_touch_down() method to check for collision of the touch with our widget.
Snippet - Python code
class MyButton(Button):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print("\tMyButton.on_touch_down:")
if self.text == 'User Name':
self.text= self.text + "?"
elif self.text == 'ikinci':
App.get_running_app().root.export_to_png("filename.png")
return True
return super(MyButton, self).on_touch_down(touch)
kv file
Rename dynamic class <MyButton#Button>: to classs rule, <MyButton>:
Remove all on_touch_down: from MyButton:
Snippet - kv file
<MyButton>:
background_normal: ''
background_color: [1,0,1,1]
font_size: "44dp"
color:[1,1,0,1]
border:(1,1,1,1)
text_size:self.size
...
MyButton:
id:button1
text: "User Name"
spacing: "10dp"
opacity: .8
MyButton:
text: "ikinci"
MyButton:
text: "ucuncu"
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:
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
# The touch has occurred inside the widgets area. Do stuff!
pass
Output
The on_touch events are not specific to any widget, but are propagated to all your widgets. See the documentation. If you only want your Button to respond, you probably want to use on_pressor on_release events instead (See button behavior).
I am kind of new to kivy and i am trying to display time in it
Here is the python code snippet(removed the unnecessary screens/parts for arbitrary reasons):
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
import time
from kivy.uix.label import Label
class MyScreen(Screen):
def update(self, *args):
self.timeb.text = time.asctime()
return time.asctime()
class MyApp(App):
def build(self):
x=MyScreen()
root = ScreenManager()
Clock.schedule_interval(x.update, 1)
root.add_widget(password(name='Screen0'))
root.add_widget(Correct(name='Screena'))
root.add_widget(MyScreen(name='Screen1'))
s=[x,root]
for i in s:
return i
if __name__ == '__main__':
MyApp().run()
.kv file(removed the unnecessary screens/parts for arbitrary reasons):
MyScreen:
<MyScreen>: #
timeb:time_box
BoxLayout:
orientation: "horizontal"
pos_hint: {'top':1}
height: "40dp"
size_hint_y: None
Label:
id:time_box
text:root.update()
size_hint_x: 6
font_size:30
font_name:"Roboto-Light.ttf"
As you can see in the code i have added a few screens but the My screen is the first to come up also if i change the
s=[x,root]
for i in s:
return i
to just
return root
then the time doesn't update itself.
Could anyone help?
Thanks!
Maybe you forgot to type something, most probably your kv is coded wrong. Why is there that hanging MyScreen: and what is it assigned to? You need a main rule somewhere, but I see none. I assigned your MyScreen: to <MyApp>: as a normal return MyScreen() or return some_scrmanager would do and it works.
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.clock import Clock
import time
from kivy.uix.label import Label
from kivy.lang import Builder
Builder.load_string('''
<MyApp>:
MyScreen:
<MyScreen>: #
timeb:time_box
BoxLayout:
orientation: "horizontal"
pos_hint: {'top':1}
height: "40dp"
size_hint_y: None
Label:
id:time_box
text:root.update()
size_hint_x: 6
font_size:30
''')
class MyScreen(Screen):
def update(self, *args):
self.timeb.text = time.asctime()
return time.asctime()
class MyApp(App):
def build(self):
x=MyScreen()
root = ScreenManager()
Clock.schedule_interval(x.update, 1)
root.add_widget(MyScreen(name='Screen1'))
s=[x,root]
for i in s:
return i
if __name__ == '__main__':
MyApp().run()
And the same kv, but more reasonable python code would looke like this:
<ScreenMgr>:
MyScreen:
<MyScreen>:
...
class MyScreen(Screen):
def __init__(self, **kw):
super(MyScreen, self).__init__(**kw)
Clock.schedule_interval(self.update, 1)
def update(self, *args):
self.timeb.text = time.asctime()
return time.asctime()
class ScreenMgr(ScreenManager):
pass
class MyApp(App):
def build(self):
x=MyScreen()
root = ScreenMgr()
root.add_widget(MyScreen(name='Screen1'))
return root
if __name__ == '__main__':
MyApp().run()
Because there's no need to call update() from Screen class in the building function and even outside a class where you want to use it presuming it'll run forever(Do you plan to stop time?).
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.