KivyMD icon indicating copy to clipboard operation
KivyMD copied to clipboard

Navigation Rail with MDTabs Clicking Off-Positioned

Open kxpang-ae opened this issue 1 year ago • 10 comments

Description of the Bug

When MDTabs is added to a screen with Navigation Rail. The clicking of mouse will be offsetted by the width of the Navigation Rail. It seems that the widgets "appear" to be at the position displayed but the "clicking" position is the display position minus the Navigation Rail width.

This issue should be easily reproducible, run the snippet below, click and hold "btn1" , you will find the ripple appears in btn2.

Code and Logs

from kivy.lang import Builder

from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.tab import MDTabsBase
KV = '''


MDBoxLayout:

    MDNavigationRail:

        MDNavigationRailItem:
            text: "Python"
            icon: "language-python"

        MDNavigationRailItem:
            text: "JavaScript"
            icon: "language-javascript"

        MDNavigationRailItem:
            text: "CPP"
            icon: "language-cpp"

        MDNavigationRailItem:
            text: "Git"
            icon: "git"

    MDScreen:
        MDTabs:
            AppTab:
                name : 'test'
                text : 'text'
                MDBoxLayout:
                    MDRaisedButton:
                        text : 'btn1'
                    MDRaisedButton:
                        text : 'btn2'
'''

class AppTab(MDBoxLayout, MDTabsBase):
    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        self.orientation = 'vertical'

class Example(MDApp):
    def build(self):
        return Builder.load_string(KV)


Example().run()

Versions

  • OS: Windows 10
  • Python: 3.9.7
  • Kivy: 2.0.0
  • KivyMD: Version: 1.0.0.dev0

kxpang-ae avatar Jul 27 '22 07:07 kxpang-ae

@kxpang-ae MDNavigationRail works the same way as MDBottomNavigation - https://kivymd.readthedocs.io/en/latest/components/bottomnavigation/#usage

HeaTTheatR avatar Jul 27 '22 08:07 HeaTTheatR

@kxpang-ae MDNavigationRail works the same way as MDBottomNavigation - https://kivymd.readthedocs.io/en/latest/components/bottomnavigation/#usage

Thanks but I don't find any solution in the link you provided

kxpang-ae avatar Jul 27 '22 09:07 kxpang-ae

Do you mean that the Navigation Rail should be placed under MDScreen?

kxpang-ae avatar Jul 27 '22 10:07 kxpang-ae

@kxpang-ae I don't understand what result you want to get...

HeaTTheatR avatar Jul 27 '22 17:07 HeaTTheatR

Have you tried running the code? Mouse clicks are off positioned. See my test run below

https://user-images.githubusercontent.com/82387839/181399224-d4eef28c-fb38-4aba-9b88-9e994763b584.mp4

kxpang-ae avatar Jul 28 '22 01:07 kxpang-ae

I have encountered similar problems with MDSliders inside MDTabs with MDNavigationRail.

Here is the code:

from kivy.lang import Builder

from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.tab import MDTabsBase

KV = '''
MDBoxLayout:
    orientation: 'vertical'

    MDToolbar:
        id: bar
        title: "Control"

    MDBoxLayout:
        orientation: 'horizontal'
        id: main_screen

        MDNavigationRail:
            id: rail"

            MDNavigationRailItem:
                icon: 'car-cruise-control'
                text: ''
                on_release: 
                    app.root.ids.bar.title = 'Control'
                    app.root.ids.screen_manager.current = 'controlScreen'

        ScreenManager:
            id: screen_manager

            ControlScreen:

<ControlScreen>:    
    name: 'controlScreen'

    MDTabs:
        id: tabs
        size_hint: (0.4, 1)
        lock_swiping: True

        Tab:
            title: 'tab 1'

            MDSlider:
                size_hint: (0.8, None)
                min: 1
                max: 10
                value: 5

        Tab:
            title: 'tab 2'

            MDSlider:
                size_hint: (0.8, None)
                min: 1
                max: 10
                value: 5

        Tab:
            title: 'tab 3'

            MDSlider:
                size_hint: (0.8, None)
                min: 1
                max: 10
                value: 5


<Tab>:
            
'''

class ControlScreen(MDScreen):
    pass

class Tab(MDFloatLayout, MDTabsBase):
    '''Class implementing content for a tab.'''


class Example(MDApp):
    def build(self):
        return Builder.load_string(KV)


Example().run()

https://user-images.githubusercontent.com/21139232/182950946-dd178837-d078-41a3-b32d-8e1ade5f95b3.mp4

Versions

OS: Debian GNU/Linux 11 (bullseye) (Raspberry pi)
Python: 3.9.2
Kivy: 2.1.0
KivyMD: Version: 1.0.0.dev0

kubapilch avatar Aug 04 '22 20:08 kubapilch

@HeaTTheatR Should I open a new Issue or leave this as a comment?

kubapilch avatar Aug 04 '22 21:08 kubapilch

@kubapilch No need to open a new issue.

HeaTTheatR avatar Aug 05 '22 05:08 HeaTTheatR

@HeaTTheatR I am trying to fix the bug. I narrowed it down to the fact that on_touch_up is called twice for MDSlider inside MDTabs, the first time with correct position and the second time offset by the width of the MDNavigationRail.

image

Edit:

From what I tested, it is not called inside of neither MDTabs or AnchorLayout image

It also seems that the on_touch_up is not called twice when just click inside the MDTab (above red line) and only happens when you click MDSlider

image

After modifying Widget inside kivy library:

def on_touch_up(self, touch):
        '''Receive a touch up event. The touch is in parent coordinates.

        See :meth:`on_touch_down` for more information.
        '''
        if self.disabled:
            return
        for child in self.children[:]:

            print(f'Dispached from: {self.__class__}')
            
            if child.dispatch('on_touch_up', touch):

                print(f'Touch up inside {child.__class__}: {touch.pos}') 

                return True

I am getting:

Dispached from: <class 'kivymd.uix.boxlayout.MDBoxLayout'>
Dispached from: <class 'kivymd.uix.screen.MDScreen'>
Dispached from: <class 'kivymd.uix.boxlayout.MDBoxLayout'>
Dispached from: <class 'kivymd.uix.boxlayout.MDBoxLayout'>
Dispached from: <class 'kivymd.uix.tab.tab.MDTabs'>
Dispached from: <class 'kivymd.uix.tab.tab.MDTabsBar'>
Dispached from: <class 'kivymd.uix.tab.tab.MDTabs'>
Dispached from: <class 'kivymd.uix.tab.tab.MDTabsMain'>
Touch down Inside: (119.0, 49.00000000000001)
Touch up inside <class 'kivymd.uix.tab.tab.MDTabsCarousel'>: (119.0, 49.00000000000001)
Touch up inside <class 'kivymd.uix.tab.tab.MDTabsMain'>: (119.0, 49.00000000000001)
Touch up inside anchor layout class: (119.0, 49.00000000000001)
Touch up inside MDTabs class: (119.0, 49.00000000000001)
Dispached from: <class 'kivymd.uix.boxlayout.MDBoxLayout'>
Dispached from: <class 'kivymd.uix.navigationrail.navigationrail.MDNavigationRail'>
Dispached from: <class 'kivymd.uix.navigationrail.navigationrail.PanelRoot'>
Dispached from: <class 'kivymd.uix.navigationrail.navigationrail.PanelItems'>
Dispached from: <class 'kivymd.uix.navigationrail.navigationrail.MDNavigationRailItem'>
Dispached from: <class 'kivy.uix.relativelayout.RelativeLayout'>
Dispached from: <class 'kivy.uix.relativelayout.RelativeLayout'>
Dispached from: <class 'kivymd.uix.label.label.MDIcon'>
Dispached from: <class 'kivy.uix.relativelayout.RelativeLayout'>
Dispached from: <class 'kivymd.uix.navigationrail.navigationrail.PanelItems'>
Dispached from: <class 'kivymd.uix.navigationrail.navigationrail.MDNavigationRailItem'>
Dispached from: <class 'kivy.uix.relativelayout.RelativeLayout'>
Dispached from: <class 'kivy.uix.relativelayout.RelativeLayout'>
Dispached from: <class 'kivymd.uix.label.label.MDIcon'>
Dispached from: <class 'kivy.uix.relativelayout.RelativeLayout'>
Dispached from: <class 'kivymd.uix.navigationrail.navigationrail.PanelItems'>
Dispached from: <class 'kivymd.uix.navigationrail.navigationrail.MDNavigationRailItem'>
Dispached from: <class 'kivy.uix.relativelayout.RelativeLayout'>
Dispached from: <class 'kivy.uix.relativelayout.RelativeLayout'>
Dispached from: <class 'kivymd.uix.label.label.MDIcon'>
Dispached from: <class 'kivy.uix.relativelayout.RelativeLayout'>
Dispached from: <class 'kivymd.uix.navigationrail.navigationrail.PanelItems'>
Dispached from: <class 'kivymd.uix.navigationrail.navigationrail.MDNavigationRailItem'>
Dispached from: <class 'kivy.uix.relativelayout.RelativeLayout'>
Dispached from: <class 'kivy.uix.relativelayout.RelativeLayout'>
Dispached from: <class 'kivymd.uix.label.label.MDIcon'>
Dispached from: <class 'kivy.uix.relativelayout.RelativeLayout'>
Touch up kivy.Slider: (119.0, 49.00000000000001)
<kivymd.uix.slider.slider.MDSlider object at 0x000001FDD6F937D0>
Touch up Inside: (119.0, 49.00000000000001)
Dispached from: <class 'kivymd.uix.tab.tab.MDTabsCarousel'>
Dispached from: <class 'kivy.uix.relativelayout.RelativeLayout'>
Dispached from: <class '__main__.AppTab'>
Dispached from: <class 'kivymd.uix.tab.tab.MDTabsCarousel'>
Dispached from: <class 'kivy.uix.relativelayout.RelativeLayout'>
Dispached from: <class '__main__.AppTab'>
Touch up kivy.Slider: (199.0, 49.00000000000001)
<kivymd.uix.slider.slider.MDSlider object at 0x000001FDD6F937D0>
Touch up Inside: (199.0, 49.00000000000001)

kubapilch avatar Aug 10 '22 21:08 kubapilch

@kubapilch Interesting...

HeaTTheatR avatar Aug 10 '22 21:08 HeaTTheatR

For anyone having this issue. I have found a workaround that can 'fix' the issue until it's properly fixed, or I have more spare time to look more into it.

Here is a working example from above:

from kivy.lang import Builder

from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.tab import MDTabsBase

import time

KV = '''


MDBoxLayout:

    MDNavigationRail:
        id: rail

        MDNavigationRailItem:
            text: "Python"
            icon: "language-python"

        MDNavigationRailItem:
            text: "JavaScript"
            icon: "language-javascript"

        MDNavigationRailItem:
            text: "CPP"
            icon: "language-cpp"

        MDNavigationRailItem:
            text: "Git"
            icon: "git"

    MDScreen:
        MDBoxLayout:
            MDTabs:
                AppTab:
                    name : 'test'
                    title : 'text'

                    MDSlider:
                        size_hint: (0.8, None)
                        name: 'Inside'
                        min: 1
                        max: 10
                        value: 5
            
            MDSlider:
                size_hint: (0.3, None)
                name: 'Outside'
                min: 1
                max: 10
                value: 5
'''

class AppTab(MDBoxLayout, MDTabsBase):
    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        self.orientation = 'vertical'

        self.last_touch_down = {'pos': (0, 0), 'time': time.time()}
    
    def on_touch_down(self, touch):
        self.last_touch_down['pos'] = touch.pos
        self.last_touch_down['time'] = time.time()

        return super().on_touch_down(touch)
    
    def on_touch_up(self, touch):
        if self.last_touch_down['pos'][0] != touch.pos[0] and time.time() - self.last_touch_down['time'] < 0.5:
            return

        return super().on_touch_up(touch)

class Example(MDApp):
    def build(self):
        return Builder.load_string(KV)

    def on_start(self):
        super().on_start()

        print(f'Rail width: {self.root.ids.rail.width}')


Example().run()

Exactly what was changed:

class AppTab(MDBoxLayout, MDTabsBase):
    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        self.orientation = 'vertical'

        self.last_touch_down = {'pos': (0, 0), 'time': time.time()}
    
    def on_touch_down(self, touch):
        self.last_touch_down['pos'] = touch.pos
        self.last_touch_down['time'] = time.time()

        return super().on_touch_down(touch)
    
    def on_touch_up(self, touch):
        if self.last_touch_down['pos'][0] != touch.pos[0] and time.time() - self.last_touch_down['time'] < 0.5:
            return

        return super().on_touch_up(touch)

All you have to do is overwrite on_touch_down and on_touch_up functions of your Tab. The downside of this solution is that widgets that specifically use on_touch_up event might not work properly. For example, I had to change all my buttons to use on_press instead of on_release inside Tab.

kubapilch avatar Aug 19 '22 15:08 kubapilch

@kxpang-ae This is a Kivy bug, not KivyMD.

from kivy.lang import Builder
from kivy.app import App
from kivy.uix.behaviors import TouchRippleBehavior
from kivy.uix.button import Button
from kivy.utils import get_color_from_hex

KV = '''
<RectangleFlatButton>:
    ripple_color: 0, 0, 0, .2
    background_color: 0, 0, 0, 0
    color: root.primary_color

    canvas.before:
        Color:
            rgba: root.primary_color
        Line:
            width: 1
            rectangle: (self.x, self.y, self.width, self.height)
            
            
BoxLayout:

    BoxLayout:
        size_hint_x:  None
        width: 80

        canvas:
            Color:
                rgba: .2, .2, .2, 1
            Rectangle:
                pos: self.pos
                size: self.size

    Screen:
            
        Carousel:
        
            BoxLayout:
                spacing: 24
    
                RectangleFlatButton:
                    pos_hint: {"center_x": 0.5, "center_y": 0.5}
                    size_hint: (None, None)
                    size: (dp(110), dp(35))
                    ripple_color: (0.8, 0.8, 0.8, 0.5)
                    
                RectangleFlatButton:
                    pos_hint: {"center_x": 0.5, "center_y": 0.5}
                    size_hint: (None, None)
                    size: (dp(110), dp(35))
                    ripple_color: (0.8, 0.8, 0.8, 0.5)
'''


class RectangleFlatButton(TouchRippleBehavior, Button):
    primary_color = get_color_from_hex("#EB8933")

    def on_touch_down(self, touch):
        collide_point = self.collide_point(touch.x, touch.y)
        if collide_point:
            touch.grab(self)
            self.ripple_show(touch)
            return True
        return False

    def on_touch_up(self, touch):
        if touch.grab_current is self:
            touch.ungrab(self)
            self.ripple_fade()
            return True
        return False


class Example(App):
    def build(self):
        return Builder.load_string(KV)


Example().run()

https://user-images.githubusercontent.com/16930280/187024140-16f7d7d3-b420-421d-8add-b10cf1f4bb98.mov

HeaTTheatR avatar Aug 27 '22 09:08 HeaTTheatR

@kxpang-ae A bug in the Carousel class.

HeaTTheatR avatar Aug 27 '22 09:08 HeaTTheatR

@kxpang-ae If you remove the left panel, everything will work correctly. Therefore, this bug is not related to the KivyMD library.

HeaTTheatR avatar Aug 27 '22 09:08 HeaTTheatR

Thanks!

kxpang-ae avatar Aug 29 '22 01:08 kxpang-ae