Advertisement
Guest User

Untitled

a guest
May 20th, 2019
107
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 21.15 KB | None | 0 0
  1. # -*- coding: utf-8 -*-
  2.  
  3. """
  4. Tabs
  5. ====
  6.  
  7. Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors -
  8. KivyMD library up to version 0.1.2
  9. Copyright (c) 2019 Ivanov Yuri and KivyMD contributors -
  10. KivyMD library version 0.1.3 and higher
  11.  
  12. For suggestions and questions:
  13. <kivydevelopment@gmail.com>
  14.  
  15. This file is distributed under the terms of the same license,
  16. as the Kivy framework.
  17.  
  18. `Material Design spec, Tabs <https://material.io/design/components/tabs.html>`_
  19. """
  20.  
  21. from kivy.animation import Animation
  22. from kivy.clock import Clock
  23. from kivy.core.window import Window
  24. from kivy.lang import Builder
  25. from kivy.logger import Logger
  26. from kivy.metrics import dp, sp
  27. from kivy.properties import StringProperty, DictProperty, ListProperty,\
  28. ObjectProperty, OptionProperty, BoundedNumericProperty, NumericProperty,\
  29. BooleanProperty
  30. from kivy.uix.boxlayout import BoxLayout
  31. from kivy.uix.floatlayout import FloatLayout
  32. from kivy.uix.gridlayout import GridLayout
  33. from kivy.uix.screenmanager import Screen
  34.  
  35. from kivymd.backgroundcolorbehavior import BackgroundColorBehavior,\
  36. SpecificBackgroundColorBehavior
  37. from kivymd.button import MDFlatButton, BaseFlatButton, BasePressedButton
  38. from kivymd.elevation import RectangularElevationBehavior
  39. from kivymd.theming import ThemableBehavior
  40.  
  41. Builder.load_string('''
  42. #:import sm kivy.uix.screenmanager
  43. #:import Window kivy.core.window.Window
  44.  
  45.  
  46. <MDTabbedPanel>
  47. id: panel
  48. orientation:
  49. 'vertical' if panel.tab_orientation in ['top','bottom']\
  50. else 'horizontal'
  51.  
  52. ScrollView:
  53. id: scroll_view
  54. size_hint_y: None
  55. size_hint_x: 1
  56. height: panel._tab_display_height[panel.tab_display_mode]
  57. do_scroll_x: True
  58. do_scroll_y: False
  59.  
  60. MDTabBar:
  61. id: tab_bar
  62. size_hint_y: None
  63. height: panel._tab_display_height[panel.tab_display_mode]
  64. md_bg_color: panel.tab_color or panel.theme_cls.primary_color
  65. size_hint_x: 1 if root.tab_width_mode =='stacked' else None
  66. width: Window.width
  67. rows: 1
  68. spacing: dp(10)
  69.  
  70. canvas:
  71. # Draw bottom border
  72. Color:
  73. rgba:
  74. (panel.tab_border_color or panel.tab_color or\
  75. panel.theme_cls.primary_dark)
  76. Rectangle:
  77. size: (self.width,dp(2))
  78.  
  79. ScreenManager:
  80. id: tab_manager
  81. current: root.current
  82. screens: root.tabs
  83. transition: sm.SlideTransition()
  84.  
  85.  
  86. <MDTabHeader>
  87. canvas:
  88. Color:
  89. rgba: self.panel.tab_color or self.panel.theme_cls.primary_color
  90. Rectangle:
  91. size: self.size
  92. pos: self.pos
  93. # Draw indicator
  94. Color:
  95. rgba:
  96. (self.panel.tab_indicator_color\
  97. or self.panel.theme_cls.accent_color)\
  98. if self.tab and self.tab.manager\
  99. and self.tab.manager.current == self.tab.name\
  100. else (self.panel.tab_border_color\
  101. or self.panel.tab_color or self.panel.theme_cls.primary_dark)
  102. Rectangle:
  103. size: (self.width,dp(2))
  104. pos: self.pos
  105.  
  106. size_hint: None if root._tab_width_mode != 'stacked' else 1, 1
  107. padding: (dp(12), 0)
  108. theme_text_color: 'Custom'
  109. text_color:
  110. (self.panel.tab_text_color_active or self.panel.specific_text_color)\
  111. if self.tab and self.tab.manager\
  112. and self.tab.manager.current == self.tab.name\
  113. else (self.panel.tab_text_color\
  114. or self.panel.specific_secondary_text_color)
  115. on_press: self.tab.dispatch('on_tab_press')
  116. on_release: self.tab.dispatch('on_tab_release')
  117. on_touch_down: self.tab.dispatch('on_tab_touch_down',*args)
  118. on_touch_move: self.tab.dispatch('on_tab_touch_move',*args)
  119. on_touch_up: self.tab.dispatch('on_tab_touch_up',*args)
  120.  
  121. MDLabel:
  122. id: _label
  123. text:
  124. root.tab.text if root.panel.tab_display_mode == 'text'\
  125. else u'{}'.format(md_icons[root.tab.icon])
  126. font_style:
  127. 'Button' if root.panel.tab_display_mode == 'text' else 'Icon'
  128. size_hint_x: 1 # if root.panel.tab_width_mode=='fixed' else 1
  129. height: self.texture_size[1]
  130. theme_text_color: root.theme_text_color
  131. text_color: root.text_color
  132. valign: 'middle'
  133. halign: 'center'
  134. opposite_colors: root.opposite_colors
  135. shorten: True
  136. shorten_from: 'right'
  137.  
  138.  
  139. <MDBottomNavigation>
  140. id: panel
  141. orientation: 'vertical'
  142. height: dp(56) # Spec
  143.  
  144. ScreenManager:
  145. id: tab_manager
  146. transition: sm.FadeTransition(duration=.2)
  147. current: root.current
  148. screens: root.tabs
  149.  
  150. MDBottomNavigationBar:
  151. size_hint_y: None
  152. height: dp(56) # Spec
  153. md_bg_color: root.theme_cls.bg_dark
  154.  
  155. BoxLayout:
  156. pos_hint: {'center_x': .5, 'center_y': .5}
  157. id: tab_bar
  158. height: dp(56)
  159. pos: self.pos
  160. size_hint_x: None
  161. size_hint: None, None
  162.  
  163.  
  164. <MDBottomNavigationHeader>
  165. canvas:
  166. Color:
  167. rgba: self.panel.theme_cls.bg_dark
  168. Rectangle:
  169. size: self.size
  170. pos: self.pos
  171.  
  172. width:
  173. root.panel.width / len(root.panel.ids.tab_manager.screens)\
  174. if len(root.panel.ids.tab_manager.screens) != 0 else root.panel.width
  175. padding: (dp(12), dp(12))
  176. on_press:
  177. self.tab.dispatch('on_tab_press')
  178. on_release: self.tab.dispatch('on_tab_release')
  179. on_touch_down: self.tab.dispatch('on_tab_touch_down',*args)
  180. on_touch_move: self.tab.dispatch('on_tab_touch_move',*args)
  181. on_touch_up: self.tab.dispatch('on_tab_touch_up',*args)
  182.  
  183. FloatLayout:
  184.  
  185. MDLabel:
  186. id: _label_icon
  187. text: u'{}'.format(md_icons[root.tab.icon])
  188. font_style: 'Icon'
  189. size_hint_x: None
  190. text_size: (None, root.height)
  191. height: self.texture_size[1]
  192. theme_text_color: 'Custom'
  193. text_color: root._current_color
  194. valign: 'middle'
  195. halign: 'center'
  196. opposite_colors: root.opposite_colors
  197. pos: [self.pos[0], self.pos[1]]
  198. font_size: dp(24)
  199. pos_hint: {'center_x': .5, 'center_y': .7}
  200.  
  201. MDLabel:
  202. id: _label
  203. text: root.tab.text
  204. font_style: 'Button'
  205. size_hint_x: None
  206. text_size: (None, root.height)
  207. height: self.texture_size[1]
  208. theme_text_color: 'Custom'
  209. text_color: root._current_color
  210. valign: 'bottom'
  211. halign: 'center'
  212. opposite_colors: root.opposite_colors
  213. font_size: root._label_font_size
  214. pos_hint: {'center_x': .5, 'center_y': .6}
  215.  
  216.  
  217. <MDTab>
  218. canvas:
  219. Color:
  220. rgba: root.theme_cls.bg_normal
  221. Rectangle:
  222. size: root.size
  223. ''')
  224.  
  225.  
  226. class MDTabBar(ThemableBehavior, BackgroundColorBehavior, GridLayout):
  227. pass
  228.  
  229.  
  230. class MDBottomNavigationBar(ThemableBehavior, BackgroundColorBehavior,
  231. FloatLayout, RectangularElevationBehavior):
  232. pass
  233.  
  234.  
  235. class MDTabHeader(MDFlatButton):
  236. """ Internal widget for headers based on MDFlatButton"""
  237.  
  238. tab = ObjectProperty(None)
  239. panel = ObjectProperty(None)
  240. _tab_width_mode = OptionProperty('stacked', options=['stacked', 'fixed'])
  241.  
  242.  
  243. class MDBottomNavigationErrorCache:
  244. last_size_warning = 0
  245.  
  246.  
  247. def small_error_warn(x):
  248. if dp(x) <= dp(80):
  249. if MDBottomNavigationErrorCache.last_size_warning != x:
  250. MDBottomNavigationErrorCache.last_size_warning = x
  251. Logger.warning(
  252. "MDBottomNavigation: {}dp is less than the minimum size "
  253. "of 80dp for a MDBottomNavigationItem. "
  254. "We must now expand to 168dp.".format(x))
  255. # Did you come here to find out what the bug is?
  256. # The bug is that on startup, this function returning dp(80)
  257. # breaks the way it displays until you resize
  258. # I don't know why, this may or may not get fixed in the future
  259. return dp(168)
  260.  
  261.  
  262. class MDBottomNavigationHeader(BaseFlatButton, BasePressedButton):
  263. width = BoundedNumericProperty(dp(0), min=dp(80), max=dp(168),
  264. errorhandler=lambda x: small_error_warn(x))
  265. tab = ObjectProperty(None)
  266. panel = ObjectProperty(None)
  267. _label = ObjectProperty()
  268. _label_font_size = NumericProperty(sp(12))
  269. _current_color = ListProperty([.0, .0, .0, .0])
  270. text = StringProperty('')
  271. _capitalized_text = StringProperty('')
  272. active = BooleanProperty(False)
  273.  
  274. def on_text(self, instance, value):
  275. self._capitalized_text = value.upper()
  276.  
  277. def __init__(self, panel, height, tab):
  278. self.panel = panel
  279. self.height = height
  280. self.tab = tab
  281. super(MDBottomNavigationHeader, self).__init__()
  282. self._current_color = self.theme_cls.disabled_hint_text_color
  283. self._label = self.ids._label
  284. self._label_font_size = sp(12)
  285. self.theme_cls.bind(primary_color=self._update_theme_color,
  286. disabled_hint_text_color=self._update_theme_style)
  287. self.active = False
  288.  
  289. def on_press(self):
  290. Animation(_label_font_size=sp(14), d=.1).start(self)
  291. Animation(_current_color=self.theme_cls.primary_color, d=.1).start(
  292. self)
  293.  
  294. def _update_theme_color(self, instance, color):
  295. if self.active:
  296. self._current_color = self.theme_cls.primary_color
  297.  
  298. def _update_theme_style(self, instance, color):
  299. if not self.active:
  300. self._current_color = self.theme_cls.disabled_hint_text_color
  301.  
  302.  
  303. class MDTab(Screen, ThemableBehavior):
  304. """ A tab is simply a screen with meta information
  305. that defines the content that goes in the tab header.
  306. """
  307.  
  308. __events__ = ('on_tab_touch_down', 'on_tab_touch_move',
  309. 'on_tab_touch_up', 'on_tab_press', 'on_tab_release')
  310.  
  311. # Tab header text
  312. text = StringProperty("")
  313.  
  314. # Tab header icon
  315. icon = StringProperty("checkbox-blank-circle")
  316.  
  317. # Tab dropdown menu items
  318. menu_items = ListProperty()
  319.  
  320. # Tab dropdown menu (if you want to customize it)
  321. menu = ObjectProperty(None)
  322.  
  323. def __init__(self, **kwargs):
  324. super(MDTab, self).__init__(**kwargs)
  325. self.index = 0
  326. self.parent_widget = None
  327. self.register_event_type('on_tab_touch_down')
  328. self.register_event_type('on_tab_touch_move')
  329. self.register_event_type('on_tab_touch_up')
  330. self.register_event_type('on_tab_press')
  331. self.register_event_type('on_tab_release')
  332.  
  333. def on_tab_touch_down(self, *args):
  334. pass
  335.  
  336. def on_tab_touch_move(self, *args):
  337. pass
  338.  
  339. def on_tab_touch_up(self, *args):
  340. pass
  341.  
  342. def on_tab_press(self, *args):
  343. par = self.parent_widget
  344. if par.previous_tab is not self:
  345. if par.previous_tab.index > self.index:
  346. par.ids.tab_manager.transition.direction = "right"
  347. elif par.previous_tab.index < self.index:
  348. par.ids.tab_manager.transition.direction = "left"
  349. par.ids.tab_manager.current = self.name
  350. par.previous_tab = self
  351.  
  352. def on_tab_release(self, *args):
  353. pass
  354.  
  355. def __repr__(self):
  356. return "<MDTab name='{}', text='{}'>".format(self.name, self.text)
  357.  
  358.  
  359. class MDBottomNavigationItem(MDTab):
  360. header = ObjectProperty()
  361.  
  362. def on_tab_press(self, *args):
  363. par = self.parent_widget
  364. par.ids.tab_manager.current = self.name
  365. if par.previous_tab is not self:
  366. Animation(_label_font_size=sp(12), d=.1).start(
  367. par.previous_tab.header)
  368. Animation(
  369. _current_color=par.previous_tab.header.theme_cls.disabled_hint_text_color,
  370. d=.1).start(
  371. par.previous_tab.header)
  372. par.previous_tab.header.active = False
  373. self.header.active = True
  374. par.previous_tab = self
  375.  
  376. def on_leave(self, *args):
  377. pass
  378.  
  379.  
  380. class TabbedPanelBase(ThemableBehavior, SpecificBackgroundColorBehavior,
  381. BoxLayout):
  382. """
  383. A class that contains all variables a TabPannel must have
  384. It is here so I (zingballyhoo) don't get mad about
  385. the TabbedPannels not being DRY
  386. """
  387.  
  388. tabs = ListProperty([])
  389.  
  390. # Current tab name
  391. current = StringProperty(None)
  392.  
  393. previous_tab = ObjectProperty(None)
  394.  
  395.  
  396. class MDTabbedPanel(TabbedPanelBase):
  397. """ A tab panel that is implemented by delegating all tabs
  398. to a ScreenManager.
  399. """
  400. # If tabs should fill space
  401. tab_width_mode = OptionProperty('stacked', options=['stacked', 'fixed'])
  402.  
  403. # Where the tabs go
  404. tab_orientation = OptionProperty('top', options=[
  405. 'top']) # ,'left','bottom','right'])
  406.  
  407. # How tabs are displayed
  408. tab_display_mode = OptionProperty('text',
  409. options=['text', 'icons']) # ,'both'])
  410. _tab_display_height = DictProperty(
  411. {'text': dp(46), 'icons': dp(46), 'both': dp(72)})
  412.  
  413. # Tab background color (leave empty for theme color)
  414. tab_color = ListProperty([])
  415.  
  416. # Tab text color in normal state (leave empty for theme color)
  417. tab_text_color = ListProperty([])
  418.  
  419. # Tab text color in active state (leave empty for theme color)
  420. tab_text_color_active = ListProperty([])
  421.  
  422. # Tab indicator color (leave empty for theme color)
  423. tab_indicator_color = ListProperty([])
  424.  
  425. # Tab bar bottom border color (leave empty for theme color)
  426. tab_border_color = ListProperty([])
  427.  
  428. def __init__(self, **kwargs):
  429. super(MDTabbedPanel, self).__init__(**kwargs)
  430. self.index = 0
  431. self._refresh_tabs()
  432.  
  433. def on_tab_width_mode(self, *args):
  434. self._refresh_tabs()
  435.  
  436. def on_tab_display_mode(self, *args):
  437. self._refresh_tabs()
  438.  
  439. def _refresh_tabs(self):
  440. """ Refresh all tabs """
  441. # if fixed width, use a box layout
  442. if not self.ids:
  443. return
  444. tab_bar = self.ids.tab_bar
  445. tab_bar.clear_widgets()
  446. tab_manager = self.ids.tab_manager
  447. for tab in tab_manager.screens:
  448. tab_header = MDTabHeader(tab=tab,
  449. panel=self,
  450. height=tab_bar.height,
  451. _tab_width_mode=self.tab_width_mode)
  452. tab_bar.add_widget(tab_header)
  453.  
  454. def add_widget(self, widget, **kwargs):
  455. """ Add tabs to the screen or the layout.
  456. :param widget: The widget to add.
  457. """
  458. if isinstance(widget, MDTab):
  459. self.index += 1
  460. if self.index == 1:
  461. self.previous_tab = widget
  462. widget.index = self.index
  463. widget.parent_widget = self
  464. self.ids.tab_manager.add_widget(widget)
  465. self._refresh_tabs()
  466. else:
  467. super(MDTabbedPanel, self).add_widget(widget)
  468.  
  469. def remove_widget(self, widget):
  470. """ Remove tabs from the screen or the layout.
  471. :param widget: The widget to remove.
  472. """
  473. self.index -= 1
  474. if isinstance(widget, MDTab):
  475. self.ids.tab_manager.remove_widget(widget)
  476. self._refresh_tabs()
  477. else:
  478. super(MDTabbedPanel, self).remove_widget(widget)
  479.  
  480.  
  481. class MDBottomNavigation(TabbedPanelBase):
  482. """ A bottom navigation that is implemented by delegating
  483. all items to a ScreenManager."""
  484.  
  485. first_widget = ObjectProperty()
  486.  
  487. def __init__(self, **kwargs):
  488. super(MDBottomNavigation, self).__init__(**kwargs)
  489. self.previous_tab = None
  490. self.widget_index = 0
  491. self._refresh_tabs()
  492. Window.bind(on_resize=self.on_resize)
  493. Clock.schedule_once(lambda x: self.on_resize(), 2)
  494.  
  495. def _refresh_tabs(self):
  496. """ Refresh all tabs """
  497. if not self.ids:
  498. return
  499. tab_bar = self.ids.tab_bar
  500. tab_bar.clear_widgets()
  501. tab_manager = self.ids.tab_manager
  502. for tab in tab_manager.screens:
  503. tab_header = MDBottomNavigationHeader(
  504. tab=tab, panel=self, height=tab_bar.height)
  505. tab.header = tab_header
  506. tab_bar.add_widget(tab_header)
  507. if tab is self.first_widget:
  508. tab_header._current_color = self.theme_cls.primary_color
  509. tab_header._label_font_size = sp(14)
  510. tab_header.active = True
  511. else:
  512. tab_header._label_font_size = sp(12)
  513. self.on_resize()
  514.  
  515. def on_resize(self, instance=None, width=None, do_again=True):
  516. full_width = 0
  517. for tab in self.ids.tab_manager.screens:
  518. full_width += tab.header.width
  519. self.ids.tab_bar.width = full_width
  520. if do_again:
  521. Clock.schedule_once(lambda x: self.on_resize(do_again=False), .01)
  522.  
  523. def add_widget(self, widget, **kwargs):
  524. """ Add tabs to the screen or the layout.
  525. :param widget: The widget to add.
  526. """
  527. if isinstance(widget, MDBottomNavigationItem):
  528. self.widget_index += 1
  529. widget.index = self.widget_index
  530. widget.parent_widget = self
  531. tab_header = MDBottomNavigationHeader(tab=widget,
  532. panel=self,
  533. height=widget.height)
  534. self.ids.tab_bar.add_widget(tab_header)
  535. widget.header = tab_header
  536. self.ids.tab_manager.add_widget(widget)
  537. if self.widget_index == 1:
  538. self.previous_tab = widget
  539. tab_header._current_color = self.theme_cls.primary_color
  540. tab_header._label_font_size = sp(14)
  541. tab_header.active = True
  542. self.first_widget = widget
  543. else:
  544. tab_header._label_font_size = sp(12)
  545.  
  546. self._refresh_tabs()
  547. else:
  548. super(MDBottomNavigation, self).add_widget(widget)
  549.  
  550. def remove_widget(self, widget):
  551. """ Remove tabs from the screen or the layout.
  552. :param widget: The widget to remove.
  553. """
  554. if isinstance(widget, MDBottomNavigationItem):
  555. self.ids.tab_manager.remove_widget(widget)
  556. self._refresh_tabs()
  557. else:
  558. super(MDBottomNavigation, self).remove_widget(widget)
  559.  
  560.  
  561. if __name__ == '__main__':
  562. from kivy.app import App
  563. from kivymd.theming import ThemeManager
  564.  
  565.  
  566. class TabsApp(App):
  567. theme_cls = ThemeManager()
  568.  
  569. def build(self):
  570. from kivy.core.window import Window
  571. Window.size = (540, 720)
  572.  
  573. return Builder.load_string('''
  574. #:import MDToolbar kivymd.toolbar.MDToolbar
  575. #:import MDRaisedButton kivymd.button.MDRaisedButton
  576.  
  577.  
  578. BoxLayout:
  579. orientation:'vertical'
  580.  
  581. MDToolbar:
  582. id: toolbar
  583. title: 'Page title'
  584. md_bg_color: app.theme_cls.primary_color
  585. left_action_items: [['menu', lambda x: '']]
  586.  
  587. MDTabbedPanel:
  588. id: tab_mgr
  589. tab_display_mode:'icons'
  590.  
  591. MDTab:
  592. name: 'music'
  593. text: "Music"
  594. icon: "playlist-play"
  595. MDLabel:
  596. font_style: 'Body1'
  597. theme_text_color: 'Primary'
  598. text: "Here is my music list :)"
  599. halign: 'center'
  600.  
  601. MDTab:
  602. name: 'movies'
  603. text: 'Movies'
  604. icon: "movie"
  605. MDLabel:
  606. font_style: 'Body1'
  607. theme_text_color: 'Primary'
  608. text: "Show movies here :)"
  609. halign: 'center'
  610.  
  611. MDBottomNavigation:
  612.  
  613. MDBottomNavigationItem:
  614. name: 'movies'
  615. text: 'Movies'
  616. icon: "movie"
  617. MDLabel:
  618. font_style: 'Body1'
  619. theme_text_color: 'Primary'
  620. text: "Show movies here :)"
  621. halign: 'center'
  622.  
  623. MDBottomNavigationItem:
  624. name: 'files1'
  625. text: "Files"
  626. icon: "file"
  627. MDLabel:
  628. font_style: 'Body1'
  629. theme_text_color: 'Primary'
  630. text: "all of the files"
  631. halign: 'center'
  632.  
  633. MDBottomNavigationItem:
  634. name: 'files2'
  635. text: "Files"
  636. icon: "file"
  637. MDLabel:
  638. font_style: 'Body1'
  639. theme_text_color: 'Primary'
  640. text: "all of the files"
  641. halign: 'center'
  642.  
  643. MDBottomNavigationItem:
  644. name: 'files3'
  645. text: "Files"
  646. MDLabel:
  647. font_style: 'Body1'
  648. theme_text_color: 'Primary'
  649. text: "all of the files"
  650. halign: 'center'
  651. ''')
  652.  
  653.  
  654. TabsApp().run()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement