KivyMD
KivyMD copied to clipboard
Fixed `on_enter` calling function in `MDTooltip` class
When hovering the cursor over a widget with a tooltip before creating a window and shifting the cursor by 1 pixel (for example) leads to a double trigger on_enter
(with self._tooltip = None
) this leads to the appearance of two tooltips on the screen.
Example
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.button import MDIconButton
from kivymd.uix.tooltip import MDTooltip
KV = '''
<MenuButton>
tooltip_text: self.icon
Screen:
orientation: "vertical"
MDToolbar:
title: "MDToolbar"
right_action_items: [["folder", lambda x: print(), 'Text ' * 5]]
pos_hint: {'top': 1}
MDBoxLayout:
adaptive_size: True
spacing: dp(10)
pos_hint: {"center_x": .5, "center_y": .5}
MenuButton:
icon: 'home'
MenuButton:
icon: 'language-python'
MenuButton:
icon: 'github'
'''
class MenuButton(MDIconButton, MDTooltip):
pass
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
Test().run()
Bug demonstration
https://user-images.githubusercontent.com/40869738/175658602-d8da533c-b707-4f9c-8c89-8fb0d6d81fd8.mp4
How reproduce bug
https://user-images.githubusercontent.com/40869738/177828635-66e37a6e-a480-47d5-a8ca-cc18b55c6b33.mp4
This PR breaks the MDTooltip widget:
https://user-images.githubusercontent.com/16930280/176890871-f83547ed-cef3-41d2-994e-a8cce9f2ccd1.mov
And this is how it works without your changes:
https://user-images.githubusercontent.com/16930280/176890896-cb737059-2087-4d9d-b675-5f7130b10604.mov
This PR breaks the MDTooltip widget:
2022-07-01.15.00.28.mov And this is how it works without your changes:
2022-07-01.15.01.33.mov
Hm, I see, trying to fix it
@Neizvestnyj OK.
I fixed it
https://user-images.githubusercontent.com/40869738/177012628-3541276a-a179-49ce-8986-b7f6bcf8b989.mp4
@Neizvestnyj I finally found the time to look at this problem. I can't reproduce this on my computer.
https://user-images.githubusercontent.com/16930280/177690150-6f3a9be5-2ccb-42d1-981f-784371d3fb7a.mov
@Neizvestnyj I finally found the time to look at this problem. I can't reproduce this on my computer.
https://user-images.githubusercontent.com/16930280/177690150-6f3a9be5-2ccb-42d1-981f-784371d3fb7a.mov
Before the ui appears, you must move the mouse (as in the video). Look at it carefully, I slowed down important moments. The point is that before the widgets appear on the screen, your mouse should be in the place of the tooltip, then the cursor should go outside the widget (thus provoking the deletion of the appeared tooltip), and then again move it to the widget
@HeaTTheatR I add video how reproduce bug to PR
@HeaTTheatR I add video how reproduce bug to PR
I don't see where...
@HeaTTheatR I add video how reproduce bug to PR
I don't see where...
At the very top, duplicate here
https://user-images.githubusercontent.com/40869738/177831147-528820ff-9274-4782-a4b2-ae7e0c12598a.mp4
Script for automatic bug reproduction.
pip install mouse
from kivy.core.window import Window
Window.maximize()
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.button import MDIconButton
from kivymd.uix.tooltip import MDTooltip
import math
import mouse
KV = '''
<MenuButton>
tooltip_text: self.icon
Screen:
orientation: "vertical"
MDToolbar:
id: toolbar
title: "MDToolbar"
right_action_items: [["folder", lambda x: print(), 'Text ' * 5]]
pos_hint: {'top': 1}
MDBoxLayout:
adaptive_size: True
spacing: dp(10)
pos_hint: {"center_x": .5, "center_y": .5}
MenuButton:
icon: 'home'
MenuButton:
icon: 'language-python'
MenuButton:
icon: 'github'
'''
class MenuButton(MDIconButton, MDTooltip):
pass
class Test(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.screen = Builder.load_string(KV)
self.get_widget_pos()
def build(self):
return self.screen
def get_widget_pos(self):
"""
:return:
right `MDToolbar` padding=12 and icon size=24
Do not touch the mouse while the application is running!
"""
widget_center = int(Window.width - 12 - 24), int(self.screen.ids.toolbar.height / 2)
mouse.move(*widget_center)
x, y = self.circle(widget_center)
for i in range(len(x)):
mouse.move(x[i], y[i], duration=0.01)
@staticmethod
def circle(pos: tuple, radius=5):
x, y = [], []
for i in range(360):
X, Y = pos
if i % 6 == 0:
x.append(X + radius * math.cos(math.radians(i)))
y.append(Y + radius * math.sin(math.radians(i)))
return x, y
Test().run()
https://user-images.githubusercontent.com/40869738/177862032-28c03b41-bc5d-479b-9af6-b5f842cfaa75.mp4
@Neizvestnyj OSError: Unsupported platform 'Darwin'
@Neizvestnyj OSError: Unsupported platform 'Darwin'
https://github.com/boppreh/mouse
Needed: requires granting accessibility permissions to terminal/python in System Preferences -> Security & Privacy
And install pyobjc-framework-Quartz
@Neizvestnyj The message says that the Darwin (macosx) platform is not supported
Member
Its must work https://github.com/boppreh/mouse/blob/master/mouse/_darwinmouse.py
pyobjc-framework-Quartz
solve the problem?
@Neizvestnyj I don't want to mess around with all these dependencies
@Neizvestnyj I don't want to mess around with all these dependencies
Just install this dependency, it's the only one. The mouse code works fine on Linux, Windows, you can run a VM and check there.
If you wont I can write code to control the mouse purely for macos, but it will take time.
@Neizvestnyj There is no free memory on my computer for unnecessary dependencies
pyobjc-framework-Quartz
You can remove it after installing, or I can send you a video from my MacOS. pyobjc-framework-Quartz
module weight about 18,1MB
There is no way without this module, it is the only one created to control the mouse on macos
@Neizvestnyj
OSError: Unsupported platform 'Darwin'
@Neizvestnyj
OSError: Unsupported platform 'Darwin'
From mouse module?
@Neizvestnyj
python3.10/site-packages/mouse/init.py:
if _platform.system() == 'Windows':
from. import _winmouse as _os_mouse
elif _platform.system() == 'Linux':
from. import _nixmouse as _os_mouse
else:
raise OSError("Unsupported platform '{}'".format(_platform.system()))
@Neizvestnyj
python3.10/site-packages/mouse/init.py:
if _platform.system() == 'Windows': from. import _winmouse as _os_mouse elif _platform.system() == 'Linux': from. import _nixmouse as _os_mouse else: raise OSError("Unsupported platform '{}'".format(_platform.system()))
Really strange, because it is support darwin https://github.com/boppreh/mouse/blob/master/mouse/init.py, can you install directly this module, I am already start my macos
@HeaTTheatR Hmm, I did not see this bug on macos, I think its only windows bug.
for Windows, Linux
pip install mouse
for MacOSX
pip install appkit
pip install pyobjc-framework-Quartz
from kivy.core.window import Window
Window.maximize()
from kivy import platform
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.button import MDIconButton
from kivymd.uix.tooltip import MDTooltip
import math
import os
from distutils.sysconfig import get_python_lib
from typing import Union
if platform != 'macosx':
import mouse
else:
if platform == 'macosx':
# run it before mouse installation
appkit_path = os.path.join(get_python_lib(), 'appkit')
appkit_upper_case = os.path.join(get_python_lib(), 'AppKit')
if not os.path.exists(appkit_upper_case):
os.rename(appkit_path, appkit_upper_case)
from Quartz.CoreGraphics import CGEventCreateMouseEvent
from Quartz.CoreGraphics import CGEventPost
from Quartz.CoreGraphics import kCGEventMouseMoved
from Quartz.CoreGraphics import kCGMouseButtonLeft
from Quartz.CoreGraphics import kCGHIDEventTap
class mouse:
@staticmethod
def move(posx, posy, duration: Union[float, int] = 0):
def mouseEvent(type, posx, posy):
theEvent = CGEventCreateMouseEvent(
None,
type,
(posx, posy),
kCGMouseButtonLeft)
CGEventPost(kCGHIDEventTap, theEvent)
mouseEvent(kCGEventMouseMoved, posx, posy)
else:
raise OSError
KV = '''
<MenuButton>
tooltip_text: self.icon
Screen:
orientation: "vertical"
MDTopAppBar:
id: toolbar
title: "MDToolbar"
right_action_items: [["folder", lambda x: print(), 'Text ' * 5]]
pos_hint: {'top': 1}
MDBoxLayout:
adaptive_size: True
spacing: dp(10)
pos_hint: {"center_x": .5, "center_y": .5}
MenuButton:
icon: 'home'
MenuButton:
icon: 'language-python'
MenuButton:
icon: 'github'
'''
class MenuButton(MDIconButton, MDTooltip):
pass
class Test(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.icon_pos = None, None # (2526.0, 80.0)
self.screen = Builder.load_string(KV)
self.move_around_widget()
def build(self):
return self.screen
def on_start(self):
pass
def _get_icon_pos(self, dt):
icon = self.screen.ids.toolbar.ids.right_actions.children[0]
padding = self.screen.ids.toolbar.ids.right_actions.padding
self.icon_pos = (icon.children[0].pos[0] + icon.width / 2 - padding[1],
Window.height - icon.children[0].pos[1] + icon.height / 2 + padding[3]
)
print(self.icon_pos)
def move_around_widget(self):
"""
:return:
right `MDToolbar` padding=12 and icon size=24
Do not touch the mouse while the application is running!
"""
if self.icon_pos:
speed = 0.01
mouse.move(*self.icon_pos)
x, y = self.circle(self.icon_pos)
for i in range(len(x)):
mouse.move(x[i], y[i], duration=speed)
else:
raise ValueError(
'You must specify `self.icon_pos` using `_get_icon_pos` in `on_start` function using `Clock.schedule_once(self._get_icon_pos, 1.0)`')
@staticmethod
def circle(pos: tuple, radius=4):
x, y = [], []
for i in range(360):
X, Y = pos
if i % 6 == 0:
x.append(X + radius * math.cos(math.radians(i)))
y.append(Y + radius * math.sin(math.radians(i)))
return x, y
Test().run()
Error reproduced on Linux (using window manager : bspwm)
https://media.discordapp.net/attachments/614483622409535489/995367079203049522/simplescreenrecorder-2022-07-09_21.58.15.mp4
https://user-images.githubusercontent.com/68729523/178115410-3e9994aa-ea5c-4ba7-bbcd-5be520eddab1.mp4
Reproduced using MDDatePicker
simplescreenrecorder-2022-07-09_22.20.57.mp4 Reproduced using MDDatePicker
Does my PR fix it?
@HeaTTheatR Hmm, I did not see this bug on macos, I think its only windows bug.
for Windows, Linux
pip install mouse
for MacOSXpip install appkit pip install pyobjc-framework-Quartz
from kivy.core.window import Window Window.maximize() from kivy import platform from kivy.lang import Builder from kivymd.app import MDApp from kivymd.uix.button import MDIconButton from kivymd.uix.tooltip import MDTooltip import math import os from distutils.sysconfig import get_python_lib from typing import Union if platform != 'macosx': import mouse else: if platform == 'macosx': # run it before mouse installation appkit_path = os.path.join(get_python_lib(), 'appkit') appkit_upper_case = os.path.join(get_python_lib(), 'AppKit') if not os.path.exists(appkit_upper_case): os.rename(appkit_path, appkit_upper_case) from Quartz.CoreGraphics import CGEventCreateMouseEvent from Quartz.CoreGraphics import CGEventPost from Quartz.CoreGraphics import kCGEventMouseMoved from Quartz.CoreGraphics import kCGMouseButtonLeft from Quartz.CoreGraphics import kCGHIDEventTap class mouse: @staticmethod def move(posx, posy, duration: Union[float, int] = 0): def mouseEvent(type, posx, posy): theEvent = CGEventCreateMouseEvent( None, type, (posx, posy), kCGMouseButtonLeft) CGEventPost(kCGHIDEventTap, theEvent) mouseEvent(kCGEventMouseMoved, posx, posy) else: raise OSError KV = ''' <MenuButton> tooltip_text: self.icon Screen: orientation: "vertical" MDTopAppBar: id: toolbar title: "MDToolbar" right_action_items: [["folder", lambda x: print(), 'Text ' * 5]] pos_hint: {'top': 1} MDBoxLayout: adaptive_size: True spacing: dp(10) pos_hint: {"center_x": .5, "center_y": .5} MenuButton: icon: 'home' MenuButton: icon: 'language-python' MenuButton: icon: 'github' ''' class MenuButton(MDIconButton, MDTooltip): pass class Test(MDApp): def __init__(self, **kwargs): super().__init__(**kwargs) self.icon_pos = None, None # (2526.0, 80.0) self.screen = Builder.load_string(KV) self.move_around_widget() def build(self): return self.screen def on_start(self): pass def _get_icon_pos(self, dt): icon = self.screen.ids.toolbar.ids.right_actions.children[0] padding = self.screen.ids.toolbar.ids.right_actions.padding self.icon_pos = (icon.children[0].pos[0] + icon.width / 2 - padding[1], Window.height - icon.children[0].pos[1] + icon.height / 2 + padding[3] ) print(self.icon_pos) def move_around_widget(self): """ :return: right `MDToolbar` padding=12 and icon size=24 Do not touch the mouse while the application is running! """ if self.icon_pos: speed = 0.01 mouse.move(*self.icon_pos) x, y = self.circle(self.icon_pos) for i in range(len(x)): mouse.move(x[i], y[i], duration=speed) else: raise ValueError( 'You must specify `self.icon_pos` using `_get_icon_pos` in `on_start` function using `Clock.schedule_once(self._get_icon_pos, 1.0)`') @staticmethod def circle(pos: tuple, radius=4): x, y = [], [] for i in range(360): X, Y = pos if i % 6 == 0: x.append(X + radius * math.cos(math.radians(i))) y.append(Y + radius * math.sin(math.radians(i))) return x, y Test().run()
This code doesn't work.