# Created on Jul 8, 2016 # # The default kivy tab implementation seems like a stupid design to me. The # ScreenManager is much better. # # @author: jrm from kivy.properties import StringProperty, DictProperty, ListProperty, \ ObjectProperty, OptionProperty, BoundedNumericProperty from kivy.uix.screenmanager import Screen from kivy.lang import Builder from kivy.metrics import dp from kivy.uix.boxlayout import BoxLayout from kivymd.theming import ThemableBehavior from kivymd.backgroundcolorbehavior import BackgroundColorBehavior from kivymd.button import MDFlatButton Builder.load_string(""" : id: panel orientation: 'vertical' if panel.tab_orientation in ['top','bottom'] else 'horizontal' ScrollView: id: scroll_view size_hint_y: None height: panel._tab_display_height[panel.tab_display_mode] MDTabBar: id: tab_bar size_hint_y: None height: panel._tab_display_height[panel.tab_display_mode] background_color: panel.tab_color or panel.theme_cls.primary_color canvas: # Draw bottom border Color: rgba: (panel.tab_border_color or panel.tab_color or panel.theme_cls.primary_dark) Rectangle: size: (self.width,dp(2)) ScreenManager: id: tab_manager current: root.current screens: root.tabs : canvas: Color: rgba: self.panel.tab_color or self.panel.theme_cls.primary_color Rectangle: size: self.size pos: self.pos # Draw indicator Color: rgba: (self.panel.tab_indicator_color or self.panel.theme_cls.accent_color) if self.tab \ and self.tab.manager and self.tab.manager.current==self.tab.name else (self.panel.tab_border_color \ or self.panel.tab_color or self.panel.theme_cls.primary_dark) Rectangle: size: (self.width,dp(2)) pos: self.pos size_hint: (None,None) #(1, None) if self.panel.tab_width_mode=='fixed' else (None,None) width: (_label.texture_size[0] + dp(16)) padding: (dp(12), 0) theme_text_color: 'Custom' text_color: (self.panel.tab_text_color_active or app.theme_cls.bg_light if app.theme_cls.theme_style == "Light" \ else app.theme_cls.opposite_bg_light) if self.tab and self.tab.manager \ and self.tab.manager.current==self.tab.name else (self.panel.tab_text_color \ or self.panel.theme_cls.primary_light) on_press: self.tab.dispatch('on_tab_press') # self.tab.manager.current = self.tab.name on_release: self.tab.dispatch('on_tab_release') on_touch_down: self.tab.dispatch('on_tab_touch_down',*args) on_touch_move: self.tab.dispatch('on_tab_touch_move',*args) on_touch_up: self.tab.dispatch('on_tab_touch_up',*args) MDLabel: id: _label text: root.tab.text if root.panel.tab_display_mode == 'text' else u"{}".format(md_icons[root.tab.icon]) font_style: 'Button' if root.panel.tab_display_mode == 'text' else 'Icon' size_hint_x: None# if root.panel.tab_width_mode=='fixed' else 1 text_size: (None, root.height) height: self.texture_size[1] theme_text_color: root.theme_text_color text_color: root.text_color valign: 'middle' halign: 'center' opposite_colors: root.opposite_colors """) class MDTabBar(ThemableBehavior, BackgroundColorBehavior, BoxLayout): pass class MDTabHeader(MDFlatButton): """ Internal widget for headers based on MDFlatButton""" width = BoundedNumericProperty(dp(None), min=dp(72), max=dp(264), errorhandler=lambda x: dp(72)) tab = ObjectProperty(None) panel = ObjectProperty(None) class MDTab(Screen): """ A tab is simply a screen with meta information that defines the content that goes in the tab header. """ __events__ = ('on_tab_touch_down', 'on_tab_touch_move', 'on_tab_touch_up', 'on_tab_press', 'on_tab_release') # Tab header text text = StringProperty("") # Tab header icon icon = StringProperty("circle") # Tab dropdown menu items menu_items = ListProperty() # Tab dropdown menu (if you want to customize it) menu = ObjectProperty(None) def __init__(self, **kwargs): super(MDTab, self).__init__(**kwargs) self.index = 0 self.parent_widget = None self.register_event_type('on_tab_touch_down') self.register_event_type('on_tab_touch_move') self.register_event_type('on_tab_touch_up') self.register_event_type('on_tab_press') self.register_event_type('on_tab_release') def on_leave(self, *args): self.parent_widget.ids.tab_manager.transition.direction = self.parent_widget.prev_dir def on_tab_touch_down(self, *args): pass def on_tab_touch_move(self, *args): pass def on_tab_touch_up(self, *args): pass def on_tab_press(self, *args): par = self.parent_widget if par.previous_tab is not self: par.prev_dir = str(par.ids.tab_manager.transition.direction) if par.previous_tab.index > self.index: par.ids.tab_manager.transition.direction = "right" elif par.previous_tab.index < self.index: par.ids.tab_manager.transition.direction = "left" par.ids.tab_manager.current = self.name par.previous_tab = self def on_tab_release(self, *args): pass def __repr__(self): return "".format(self.name, self.text) class MDTabbedPanel(ThemableBehavior, BackgroundColorBehavior, BoxLayout): """ A tab panel that is implemented by delegating all tabs to a ScreenManager. """ # If tabs should fill space tab_width_mode = OptionProperty('stacked', options=['stacked', 'fixed']) # Where the tabs go tab_orientation = OptionProperty('top', options=['top']) # ,'left','bottom','right']) # How tabs are displayed tab_display_mode = OptionProperty('text', options=['text', 'icons']) # ,'both']) _tab_display_height = DictProperty({'text': dp(46), 'icons': dp(46), 'both': dp(72)}) # Tab background color (leave empty for theme color) tab_color = ListProperty([]) # Tab text color in normal state (leave empty for theme color) tab_text_color = ListProperty([]) # Tab text color in active state (leave empty for theme color) tab_text_color_active = ListProperty([]) # Tab indicator color (leave empty for theme color) tab_indicator_color = ListProperty([]) # Tab bar bottom border color (leave empty for theme color) tab_border_color = ListProperty([]) # List of all the tabs so you can dynamically change them tabs = ListProperty([]) # Current tab name current = StringProperty(None) def __init__(self, **kwargs): super(MDTabbedPanel, self).__init__(**kwargs) self.previous_tab = None self.prev_dir = None self.index = 0 self._refresh_tabs() def on_tab_width_mode(self, *args): self._refresh_tabs() def on_tab_display_mode(self, *args): self._refresh_tabs() def _refresh_tabs(self): """ Refresh all tabs """ # if fixed width, use a box layout if not self.ids: return tab_bar = self.ids.tab_bar tab_bar.clear_widgets() tab_manager = self.ids.tab_manager for tab in tab_manager.screens: tab_header = MDTabHeader(tab=tab, panel=self, height=tab_bar.height, ) tab_bar.add_widget(tab_header) def add_widget(self, widget, **kwargs): """ Add tabs to the screen or the layout. :param widget: The widget to add. """ d = {} if isinstance(widget, MDTab): self.index += 1 if self.index == 1: self.previous_tab = widget widget.index = self.index widget.parent_widget = self self.ids.tab_manager.add_widget(widget) self._refresh_tabs() else: super(MDTabbedPanel, self).add_widget(widget) def remove_widget(self, widget): """ Remove tabs from the screen or the layout. :param widget: The widget to remove. """ self.index -= 1 if isinstance(widget, MDTab): self.ids.tab_manager.remove_widget(widget) self._refresh_tabs() else: super(MDTabbedPanel, self).remove_widget(widget) if __name__ == '__main__': from kivy.app import App from kivymd.theming import ThemeManager class TabsApp(App): theme_cls = ThemeManager() def build(self): from kivy.core.window import Window Window.size = (540, 720) # self.theme_cls.theme_style = 'Dark' return Builder.load_string(""" #:import Toolbar kivymd.toolbar.Toolbar BoxLayout: orientation:'vertical' Toolbar: id: toolbar title: 'Page title' background_color: app.theme_cls.primary_color left_action_items: [['menu', lambda x: '']] right_action_items: [['search', lambda x: ''],['more-vert',lambda x:'']] MDTabbedPanel: id: tab_mgr tab_display_mode:'icons' MDTab: name: 'music' text: "Music" # Why are these not set!!! icon: "playlist-audio" MDLabel: font_style: 'Body1' theme_text_color: 'Primary' text: "Here is my music list :)" halign: 'center' MDTab: name: 'movies' text: 'Movies' icon: "movie" MDLabel: font_style: 'Body1' theme_text_color: 'Primary' text: "Show movies here :)" halign: 'center' """) TabsApp().run()