KivyMD
KivyMD copied to clipboard
Navigation Rail with MDTabs Clicking Off-Positioned
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 MDNavigationRail works the same way as MDBottomNavigation - https://kivymd.readthedocs.io/en/latest/components/bottomnavigation/#usage
@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
Do you mean that the Navigation Rail should be placed under MDScreen?
@kxpang-ae I don't understand what result you want to get...
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
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
@HeaTTheatR Should I open a new Issue or leave this as a comment?
@kubapilch No need to open a new issue.
@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
.
Edit:
From what I tested, it is not called inside of neither MDTabs
or AnchorLayout
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
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 Interesting...
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
.
@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
@kxpang-ae A bug in the Carousel
class.
@kxpang-ae If you remove the left panel, everything will work correctly. Therefore, this bug is not related to the KivyMD
library.
Thanks!