kivymd / KivyMD

KivyMD is a collection of Material Design compliant widgets for use with Kivy, a framework for cross-platform, touch-enabled graphical applications. https://youtube.com/c/KivyMD https://twitter.com/KivyMD https://habr.com/ru/users/kivymd https://stackoverflow.com/tags/kivymd
https://kivymd.readthedocs.io
MIT License
2.22k stars 671 forks source link

MDTabs has a problem with selectable widgets in the content #764

Closed runette closed 3 years ago

runette commented 3 years ago

Description of the Bug

This one has been driving mad for over a day. I think I have finally worked what the problem is - if not the solution.

MDTabs are excellent - and one of the main reasons we are trying to migrate to kivyMD!

But - there is a problem if you have 'selectable content' (e.g Buttons, Switches. etc) in the MDTabsBase content. Basically - each slide is reserving the relevant area of the screen and capturing the clicks even when it is not visible with priority going - it seems - to the last slide in the set. This leads to some very unpredictable results - since it buttons stop working when there are other buttons overlapping in other screens.

It seems to be even more complicated - since some widgets do work. TextInput widgets, for instance, do focus correctly. Also, buttons that use the on_press event seem to be working. But I believe most MD widgets use the on_release event and there are very mixed results.

Note - there are no error logs or messages because there are no error messages - the error is that nothing happens when you click.

Versions

HeaTTheatR commented 3 years ago

@runette Thanks for this analysis. Please add a minimal example of code that demonstrates the problems you described

purushottam858 commented 3 years ago

This is my code , I tried to reduce it as much as possible . It is working good in linux but when it is made apk and used in android , the problem is arising .

from kivymd.app import MDApp
from kivy.uix.screenmanager import Screen,ScreenManager
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivymd.uix.tab import MDTabsBase
from kivymd.uix.swiper import MDSwiperItem
from kivymd.utils.fitimage import FitImage
from kivymd.uix.boxlayout import MDBoxLayout
from kivy.uix.scrollview import ScrollView
from kivymd.uix.list import MDList , ThreeLineAvatarListItem
from kivymd.uix.label import MDLabel
from kivymd.uix.list import OneLineListItem
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.image import Image
#from kivy.core.image import Image
from kivymd.uix.list import IRightBodyTouch
from kivy.metrics import sp, dp
from kivy.uix.stacklayout import StackLayout
from kivymd.uix import MDAdaptiveWidget

from kivymd.uix.button import MDFlatButton , MDIconButton

from kivymd.uix.list import ImageLeftWidget

from kivy.uix.carousel import Carousel
from kivymd.uix.carousel import MDCarousel

from kivy.properties import (
    AliasProperty,
    BooleanProperty,
    BoundedNumericProperty,
    ListProperty,
    NumericProperty,
    ObjectProperty,
    OptionProperty,
    StringProperty,
)

from kivy.core.window import Window
#Window.size = (420,860)

#Builder String

helper_string = '''

#: import mtx kivy.metrics
#: import  Window  kivy.core.window

<Car>:

<Post_11>:

    orientation: 'vertical'
    padding: ["0dp", "10dp","0dp", "10dp"]
    spacing: [ mtx.dp(10) ,  mtx.dp(10) ]
    adaptive_height: True

    MDBoxLayout:
        adaptive_height: True
        #size_hint_y: .1
        ThreeLineAvatarListItem:
            size_hint_x: .7
            text: root.name
            bold: True
            secondary_text: root.details
            tertiary_text:  root.distance

            ImageLeftWidget:
                source: root.profile_source

        MDIconButton:
            icon : 'adjust'
            #text: 'follow'
            #size_hint_x: .15
            on_press:

                app.callback_1()

    MDBoxLayout:

        id: scroll_parent
        md_bg_color:(1,0,0,1)
        adaptive_height: True
        orientation: 'vertical'

        MDBoxLayout:
            #size_hint: (None, None)
            #size:  [ Window.size[0] ,  Window.size[1] ]
            #padding: ["10dp", "10dp","10dp", "10dp"]
            spacing: [ mtx.dp(10) ,  mtx.dp(10) ]
            md_bg_color:(0,1,0,1)
            adaptive_height: True
            orientation: 'vertical'

            Car:
                id : car
                direction: 'right'

                loop:True
                #anim_move_duration: 0.01
                ignore_perpendicular_swipes:True
                md_bg_color:(1,1,1,1)

                #size_hint: ( 1, 1)
                #size: [ scroll_parent.width  , scroll_parent.height ]

                #size_hint_y: None
                #height: self.minimum_height

                size_hint: ( None  , None)
                size: [ Window.size[0] ,  Window.size[1] * (5/10)]

                FitImage:
                    source: root.post_source
                    allow_stretch: True
                    size_hint_x: 1

                    size_hint: (None, None)
                    size: [ Window.size[0] ,  Window.size[1] * (5/10) ]

                FitImage:
                    source:root.profile_source
                    allow_stretch: True
                    size_hint_x: 1

                    #size_hint: (None, None)
                    #size:  [ Window.size[0] ,  Window.size[1] ]

                FitImage:
                    source: root.post_source
                    allow_stretch: True
                    size_hint_x: 1

                    #size_hint: (None, None)
                    #size :  [ Window.size[0] ,  Window.size[1] ]

    MDBoxLayout:
        #size_hint_y: .08
        adaptive_height: True

        MDIconButton:
            icon: "heart"
            size_hint_x: .1

            theme_text_color: "Custom"
            text_color: (1,0,0,1)

        MDIconButton:
            icon: "comment-outline"
            size_hint_x: .1

            theme_text_color: "Custom"
            text_color: (0,0,1,1)

        MDIconButton:
            icon: "share"
            size_hint_x: .1

            theme_text_color: "Custom"
            text_color: (0,0,1,1)
        MDIconButton:
            id: bookmark
            icon: "bookmark"
            size_hint_x: .1
            on_release: root.ids.bookmark.icon = "bookmark-check"

            theme_text_color: "Custom"
            text_color: (0,1,0,1)

        MDIconButton:
            icon: "dots-vertical"
            size_hint_x: .1

            theme_text_color: "Custom"
            text_color: (1,1,0,1)

ScreenManager:

    Hello:

<Hello>:
    name: 'hello'
    BoxLayout:
        orientation:'vertical'
        MDToolbar:
            title: 'ONLINE 88'
            right_action_items: [ ["dots-vertical", lambda x: app.callback_1()]]

        MDTabs:

            id:tabs
            on_tab_switch: app.on_tab_switch(*args)
            #lock_swiping:True

            Tab:
                id: tabone
                text : "tab_1"

                MDBottomNavigation:
                    id : navpanel
                    panel_color : 1,1,1,1

                    MDBottomNavigationItem:
                        id : nearby

                        text: "Near by"
                        icon:  "map-marker-circle"
                        ScrollView:

                            do_scroll: [ False , True]
                            scroll_type: ['content']
                            #size_hint_y: None
                            size_hint : (1, None)
                            size:(Window.width, self.parent.height)
                            #height: self.parent.height
                            #effect_cls: 'ScrollEffect'

                            MDGridLayout:
                                id: post_lists

                                cols : 1

                                size_hint_y: None
                                size: self.minimum_size
                                padding: ["0dp", "150dp","0dp", "15dp"]
                                spacing: "10dp"

                    MDBottomNavigationItem:
                        name: 'trending'

                        text: "Trending"
                        icon: "trending-up"

                        MDLabel:
                            text: "Trending posts"
                            halign: 'center'

                    MDBottomNavigationItem:
                        name :'Favourite'

                        text: "Favourite"
                        icon: "heart-settings"

                        MDLabel:
                            text: "Favourite people and  places  "
                            halign: 'center'

            Tab:
                text: "tab_2"

                MDLabel:
                    id:"market"
                    text : "market"
                    halign : "center"

                MDBottomNavigation:
                    panel_color : 1,1,1,1

                    MDBottomNavigationItem:
                        id : 'near by'

                        text: "market"

                        MDLabel:
                            text: "Local Shops"
                            halign: 'center'

                    MDBottomNavigationItem:
                        name :'Favourite'

                        text: "Favourite"

                        MDLabel:
                            text: "Favourite people and  places  "
                            halign: 'center'

'''

class Hello(Screen):
    pass

class Tab(FloatLayout, MDTabsBase):
    pass

class Car(MDCarousel):

    pass

class post_1(ButtonBehavior, MDBoxLayout):

    name = StringProperty('')
    post_source = StringProperty('')
    profile_source = StringProperty('')
    description  = StringProperty('')
    distance = StringProperty('')
    details = StringProperty('')

    def __init__(self, **kw):
        super().__init__(**kw)

        self.bind(on_release=lambda x: self.view_post())

    def view_post(self):
        print('....>>>>')

class Post_11(post_1):

    def on_touch_down(self, touch):

        print(f'post >>> touched at {touch.pos}')  # This will print on any screen touch

        if self.collide_point(*touch.pos) and not  self.ids.car.collide_point(*touch.pos):       # Tests, was this widget touched?

            touch.grab(self)

            print('post  at {touch.pos} grabbling >>>')

            return   True #super().on_touch_down(touch)  # Pass the touch to the parent class

        else :
             super().on_touch_down(touch)

    def on_touch_move(self, touch):

        if touch.grab_current is self :
            print("grab post  post post post touch  moving  >>>>>")

    def on_touch_up(self , touch):
        print("up by post ")
        if touch.grab_current is self :
            touch.ungrab(self)
            print("ungrabbing by post >>>>>")

sm = ScreenManager()
sm.add_widget(Hello(name = 'hello'))

class DemoApp(MDApp):
    def build(self):

        self.theme_cls.primary_palette   = 'Indigo'
        self.theme_cls.accent_palette =  'Purple'

        screen = Screen()

        self.help_str = Builder.load_string(helper_string)

        screen.add_widget(self.help_str)

        return screen

    def on_start(self):

        posts__ = Post_11(name = 'purushottam' ,
         post_source = "photo-1503023345310-bd7c1de61c7d.jpeg" ,
         details = "with kishore............. at begumpet911111111111  " ,
         distance = "12 kms from you at 1/9/2020 6:41  am" ,
         profile_source =  "download.jpeg" ,
          description = 'Never stop improving your self  no matter what you should deal with ...' ,
          size_hint_x = 1 )

        self.help_str.get_screen('hello').ids.post_lists.add_widget(posts__)

        self.help_str.get_screen('hello').ids.post_lists.add_widget(Post_11(name = 'purushottam' ,
         post_source = "photo-1503023345310-bd7c1de61c7d.jpeg" ,
         details = "with kishore............. at begumpet911111111111  " ,
         distance = "12 kms from you at 1/9/2020 6:41  am" ,
         profile_source =  "download.jpeg" ,
          description = 'Never stop improving your self  no matter what you should deal with ...' ,
          size_hint_x = 1  ))

        self.help_str.get_screen('hello').ids.post_lists.add_widget(Post_11(name = 'purushottam' ,
         post_source = "photo-1503023345310-bd7c1de61c7d.jpeg" ,
         details = "with kishore............. at begumpet911111111111  " ,
         distance = "12 kms from you at 1/9/2020 6:41  am" ,
         profile_source =  "download.jpeg" ,
          description = 'Never stop improving your self  no matter what you should deal with ...' ,
          size_hint_x = 1  ))

    def callback_1(self):

        print("vertical dots clicked")
        print(Window.size)

        if self.theme_cls.theme_style == "Dark" :
            self.theme_cls.theme_style = "Light"

        else:
            self.theme_cls.theme_style = "Dark"

    def on_tab_switch(self,instance_tabs,instance_tab,instance_tab_label,tab_text):
        #instance_tab.ids.label.text = 'hello'
        print(instance_tabs , instance_tab , instance_tab_label , tab_text )

k = DemoApp()
k.run()
runette commented 3 years ago

I am still having intermittent problems but I cannot reliable reproduce outside of my app and mostly seem to have gone away when the tab content was put into MDCard containers (strangely!) and may have been related to misbehaviour by a non-MD component.

With respect to the paired "Carousel" issue - I also note that the Material Design site itself warns against putting swipeable content into swipeable tabs since it is always clear what action should do what. My issue with buttons is probably, as you suggest, similar in that it seems to be down to which control is in control but is less obviously contra-indicated (and indeed buttons in Tabs ARE standard practice).

Possibly, clear guidance about what content can be in tabs, and how, would be a good idea - but that always leaves open the question of who is going to write that!

I suggest that this issue should be closed as "Will not fix".

HeaTTheatR commented 3 years ago

@purushottam858 Format your code please.

purushottam858 commented 3 years ago

"Hello " screen has two tabs , one has bottom navigation with two bottom navigation items in it . one bottom navigation has scrollview which has gridlayout . Inside this gridlayout , instances of " post_11 " will be added by on_start() function . . post_11 has child widget carousel to swipe images . While swiping images inside this carousel , tabs are getting swiped . this is happening when apk running inside android device.

I tried to reduce the code and format it as much as possible . I am newbie to programming , sorry that is what I can do with the code . I dont know what to format more in the code . The above explanation may help you to understand the code .

Sorry for the inconvenience @HeaTTheatR

HeaTTheatR commented 3 years ago

@purushottam858 Format your code please.

HeaTTheatR commented 3 years ago

Fix in the master branch.