Kivy - MotionEvent: on_mouse_pos - python

Does kivy support MouseEvent that is triggered on mouse_pos change without pressing mouse button?
I found in documentation this:
def on_motion(self, etype, motionevent):
# will receive all motion events.
pass
Window.bind(on_motion=on_motion)
You can also listen to changes of the mouse position by watching mouse_pos.
However I cant implement it. I managed to bind it and add to on_motion function 'print('Hello world')' but it was triggered only by pressing-type events.
Thanks in advance

Solution
Bind mouse_pos to a callback. Please refer to example for details.
Window.bind(mouse_pos=self.mouse_pos)
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
class MousePosDemo(BoxLayout):
def __init__(self, **kwargs):
super(MousePosDemo, self).__init__(**kwargs)
self.label = Label()
self.add_widget(self.label)
Window.bind(mouse_pos=self.mouse_pos)
def mouse_pos(self, window, pos):
self.label.text = str(pos)
class TestApp(App):
title = "Kivy Mouse Pos Demo"
def build(self):
return MousePosDemo()
if __name__ == "__main__":
TestApp().run()
Output

You actually want to do:
Window.bind(mouse_pos=on_motion)

Related

Why does the button print twice? (Kivy Python)

I'm trying to make a button and I'm doing this print as a test, I'm not why it prints twice?
I'm new to Kivy so I'm not too sure on what I'm doing, any help is appreciated thank you.
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
def bannedBooksMain(self, instance):
print('The button test is being pressed')
class mainApp(GridLayout):
def __init__(self, **kwargs):
super(mainApp, self).__init__(**kwargs)
self.cols = 2
btn1 = Button(text='Home Page')
btn1.bind(state=bannedBooksMain)
self.add_widget(btn1)
class MyApp(App):
def build(self):
return mainApp()
MyApp().run()
btn1.bind(state=bannedBooksMain)
This (state=) will initiate a callback whenever the state property changes, this happens for both button press and release, which is why you're seeing it twice
You should use on_press= or on_release= instead, depending on when you want the action fired.
Personally, I prefer the on_release= combined with always_release being false. That way, if I inadvertently press the button, I can move the mouse point away from the button before releasing, and there will be no callback.
See here for further detail.

How to update a Kivy Scrollview Label in real time?

I could really really need some help with my actually quite simple Python Kivy Problem! I wrote a program that first announces counting to 5 and then should start counting from 1 to 5. The info should be shown in a scrollview-Label. The code roughly does its job but does not update the scrollview step-by-step but all at once after time is elapsed...can anybody please help? Thank you in advance!
import kivy
from kivy.config import Config
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
import time
kivy.require("2.0.0")
Config.set('kivy', 'keyboard_mode', 'systemandmulti')
class MainMenu(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 1
self.rows = 2
self.infowindow = ScrollableInfo(height=Window.size[1]*0.8, size_hint_y=None)
self.add_widget(self.infowindow)
self.ButtonCheckConnection = Button(text="Start Counting to 5")
self.ButtonCheckConnection.bind(on_press=self.countingtofive)
self.add_widget(self.ButtonCheckConnection)
def countingtofive(self, *_):
self.infowindow.update_scrollview(f"Counting to 5 is going to start in 3 seconds")
time.sleep(3)
countingmaximum = 5
for i in range(countingmaximum):
currentnumber = i+1
self.infowindow.update_scrollview(str(currentnumber))
time.sleep(1)
class ScrollableInfo(ScrollView):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.layout = GridLayout(cols=1, size_hint_y=None)
self.add_widget(self.layout)
self.connectioninfo_history = Label(size_hint_y=None, markup=True)
self.layout.add_widget(self.connectioninfo_history)
def update_scrollview(self, newinfo):
self.connectioninfo_history.text += '\n' + newinfo
self.layout.height = self.connectioninfo_history.texture_size[1]+15
self.connectioninfo_history.height = self.connectioninfo_history.texture_size[1]
self.connectioninfo_history.text_size = (self.connectioninfo_history.width*0.98, None)
class Counting(App):
def build(self):
self.screen_manager = ScreenManager()
self.mainmenu_page = MainMenu()
screen = Screen(name="MainMenu")
screen.add_widget(self.mainmenu_page)
self.screen_manager.add_widget(screen)
return self.screen_manager
if __name__ == "__main__":
counting_app = Counting()
counting_app.run()
The problem is that you are running your countingtofive() method on the main thread. Since Kivy uses the main thread to update the GUI, it cannot do that until you release the main thread (by returning from the countingtofive() method). That is why you never see anything until that method completes.
To fix that, run the countingtofive() method in another thread, like this:
def start_counting_thread(self, *args):
Thread(target=self.countingtofive, daemon=True).start()
And change the Button to bind to the start_counting_thread() method:
self.ButtonCheckConnection.bind(on_press=self.start_counting_thread)
And one minor change to the update_scrollview() method (add the #mainthread decorator):
#mainthread
def update_scrollview(self, newinfo):
The #mainthread decorator forces the decorated method to be run on the main thread. The same can be accomplished by using Clock.schedule_once(), but the decorator is easier. Just the piece of the code that actually updates the GUI must be run on the main thread. Generally, you should try to avoid long running methods on the main thread.

MDSlider Bug in KivyMD - on_touch_up gets fired when clicked on the screen

I am facing a problem, where I want to get the value of the MDslider in KivyMD using the on_touch_up() method, it works but the problem is even when I click/touch anywhere on the screen apart from the MDSlider the on_touch_up() method gets fired.
And when I click/touch on the MDSlider the method gets fired twice
Code
from kivy.lang import Builder, builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.app import MDApp
from kivymd.uix.button import MDRectangleFlatButton
from kivymd.uix.slider import MDSlider
from kivymd.uix.gridlayout import MDGridLayout
class MyScreen(Screen):
def __init__(self, **kwargs):
super(MyScreen, self).__init__(**kwargs)
self.slider = MDSlider(
min=0,
max=100,
size_hint=(0.4,0.1),
pos_hint={'center_x':0.5, 'center_y':0.5}
)
self.slider.bind(on_touch_up = self.get_slider_value)
self.add_widget(self.slider)
def get_slider_value(self, obj, obj_prop):
print(obj.value)
class MyApp(MDApp):
def build(self):
return MyScreen()
if __name__ == "__main__":
MyApp().run()
Thank you for your time :)
That's not a bug, it is the designed behavior. All Widgets get the on_touch_up event. You must use the collide_point(*touch.pos) method to determine if the touch is on your Slider. However, the on_touch_up() being fired twice does seem like a bug. Consider using:
self.slider.bind(value = self.get_slider_value)
instead of on_touch_up.
To use collide_point() in the get_slider_value() (if you bind to on_touch_up) try something like:
def get_slider_value(self, slider, touch):
if slider.collide_point(*touch.pos):
print(slider.value)

How to fire one event when we release the click on a kivy switch item?

Goal: when I click then release the click on the switch widget, it fires me an event on the release.
Problem: I used the on_touch_down() Switch method but:
it release 2 event on the release of the click and not only one.
when I release and it shows "off", it says "True" and it shows "False" when it's on "on" !
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
KV = '''
MyGridLayout:
Switch:
id: switch_ecs
on_touch_up:
root.on_switch("ecs")
'''
class MyGridLayout(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def on_switch(self,element_id):
print(self.ids["switch_" + element_id].active)
class MyApp(MDApp):
def build(self):
self.screen = Builder.load_string(KV)
return self.screen
if __name__ == '__main__':
MyApp().run()
Kivy has some obscure logic that results in touch up events getting dispatched twice to some widgets (like Buttons and Switches). The fix for your code is to use a different trigger for your on_switch() method. You can use:
on_touch_down:
root.on_switch("ecs")
or:
on_active:
root.on_switch("ecs")

How to change screen by swiping in kivy python

I am trying to change to another screen by swiping the screen. I've tried carousel but it seems that it only works with images, so I've tried detecting a swipe motion and changing the screen after it has been detected.
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.uix.button import ButtonBehavior
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.carousel import Carousel
from kivy.uix.widget import Widget
from kivy.uix.popup import Popup
class HomeScreen(Screen):
def on_touch_move(self, touch):
if touch.x < touch.ox: # this line checks if a left swipe has been detected
MainApp().change_screen(screen_name="swipedhikr_screen") # calls the method in the main app that changes the screen
class ImageButton(ButtonBehavior, Image):
pass
class LabelButton(ButtonBehavior, Label):
pass
class SettingsScreen(Screen):
pass
class SwipeDhikrScreen(Screen):
pass
#def quit_verification():
# pop = Popup(title="verification", content=Label(text= "Are you sure?"))
GUI = Builder.load_file("main.kv")
class MainApp(App):
def build(self):
return GUI
def change_screen(self, screen_name):
# get the screen manager from the kv file
screen_manager = self.root.ids["screen_manager"]
screen_manager.transition.direction = "up"
screen_manager.current = screen_name
def quit_app(self):
MainApp().stop()
MainApp().run()
I got an attribute error: "None type object has no attribute 'ids'"
MainApp().change_screen(screen_name="swipedhikr_screen")
This line creates a new instance of MainApp, which doesn't have any widgets and therefore naturally fails when you try to access them.
Use the existing instance of MainApp, i.e. the one that you're actually running, via MainApp.get_running_app().
Also you are not correct that Carousel works only with images.

Categories

Resources