KivyMD icon indicating copy to clipboard operation
KivyMD copied to clipboard

Fixed `on_enter` calling function in `MDTooltip` class

Open Neizvestnyj opened this issue 2 years ago • 40 comments

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

Neizvestnyj avatar Jun 24 '22 19:06 Neizvestnyj

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

HeaTTheatR avatar Jul 01 '22 12:07 HeaTTheatR

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 avatar Jul 01 '22 12:07 Neizvestnyj

@Neizvestnyj OK.

HeaTTheatR avatar Jul 01 '22 12:07 HeaTTheatR

I fixed it

https://user-images.githubusercontent.com/40869738/177012628-3541276a-a179-49ce-8986-b7f6bcf8b989.mp4

Neizvestnyj avatar Jul 02 '22 18:07 Neizvestnyj

@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

HeaTTheatR avatar Jul 07 '22 04:07 HeaTTheatR

@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

Neizvestnyj avatar Jul 07 '22 08:07 Neizvestnyj

@HeaTTheatR I add video how reproduce bug to PR

Neizvestnyj avatar Jul 07 '22 16:07 Neizvestnyj

@HeaTTheatR I add video how reproduce bug to PR

I don't see where...

HeaTTheatR avatar Jul 07 '22 16:07 HeaTTheatR

@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

Neizvestnyj avatar Jul 07 '22 17:07 Neizvestnyj

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 avatar Jul 07 '22 20:07 Neizvestnyj

@Neizvestnyj OSError: Unsupported platform 'Darwin'

HeaTTheatR avatar Jul 08 '22 03:07 HeaTTheatR

@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 avatar Jul 08 '22 07:07 Neizvestnyj

@Neizvestnyj The message says that the Darwin (macosx) platform is not supported

HeaTTheatR avatar Jul 08 '22 07:07 HeaTTheatR

Member

Its must work https://github.com/boppreh/mouse/blob/master/mouse/_darwinmouse.py

Neizvestnyj avatar Jul 08 '22 07:07 Neizvestnyj

pyobjc-framework-Quartz solve the problem?

Neizvestnyj avatar Jul 08 '22 07:07 Neizvestnyj

@Neizvestnyj I don't want to mess around with all these dependencies

HeaTTheatR avatar Jul 08 '22 07:07 HeaTTheatR

@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.

Neizvestnyj avatar Jul 08 '22 07:07 Neizvestnyj

If you wont I can write code to control the mouse purely for macos, but it will take time.

Neizvestnyj avatar Jul 08 '22 07:07 Neizvestnyj

@Neizvestnyj There is no free memory on my computer for unnecessary dependencies

HeaTTheatR avatar Jul 08 '22 07:07 HeaTTheatR

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

Neizvestnyj avatar Jul 08 '22 07:07 Neizvestnyj

There is no way without this module, it is the only one created to control the mouse on macos

Neizvestnyj avatar Jul 08 '22 07:07 Neizvestnyj

@Neizvestnyj OSError: Unsupported platform 'Darwin'

HeaTTheatR avatar Jul 08 '22 07:07 HeaTTheatR

@Neizvestnyj OSError: Unsupported platform 'Darwin'

From mouse module?

Neizvestnyj avatar Jul 08 '22 07:07 Neizvestnyj

@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()))

HeaTTheatR avatar Jul 08 '22 07:07 HeaTTheatR

@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

Neizvestnyj avatar Jul 08 '22 07:07 Neizvestnyj

@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()

Neizvestnyj avatar Jul 08 '22 09:07 Neizvestnyj

Error reproduced on Linux (using window manager : bspwm)

https://media.discordapp.net/attachments/614483622409535489/995367079203049522/simplescreenrecorder-2022-07-09_21.58.15.mp4

T-Dynamos avatar Jul 09 '22 16:07 T-Dynamos

https://user-images.githubusercontent.com/68729523/178115410-3e9994aa-ea5c-4ba7-bbcd-5be520eddab1.mp4

Reproduced using MDDatePicker

T-Dynamos avatar Jul 09 '22 16:07 T-Dynamos

simplescreenrecorder-2022-07-09_22.20.57.mp4 Reproduced using MDDatePicker

Does my PR fix it?

Neizvestnyj avatar Jul 09 '22 17:07 Neizvestnyj

@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()

This code doesn't work.

HeaTTheatR avatar Jul 09 '22 17:07 HeaTTheatR