""" Components/Toolbar ================== .. seealso:: `Material Design spec, App bars: top `_ `Material Design spec, App bars: bottom `_ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/app-bar-top.png :align: center `KivyMD` provides the following toolbar positions for use: - Top_ - Bottom_ .. Top: Top --- .. code-block:: python from kivy.lang import Builder from kivymd.app import MDApp KV = ''' BoxLayout: orientation: "vertical" MDToolbar: title: "MDToolbar" MDLabel: text: "Content" halign: "center" ''' class Test(MDApp): def build(self): return Builder.load_string(KV) Test().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-1.png :align: center Add left menu ------------- .. code-block:: kv MDToolbar: title: "MDToolbar" left_action_items: [["menu", lambda x: app.callback()]] .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-2.png :align: center Add right menu -------------- .. code-block:: kv MDToolbar: title: "MDToolbar" right_action_items: [["dots-vertical", lambda x: app.callback()]] .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-3.png :align: center Add two item to the right menu ------------------------------ .. code-block:: kv MDToolbar: title: "MDToolbar" right_action_items: [["dots-vertical", lambda x: app.callback_1()], ["clock", lambda x: app.callback_2()]] .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-4.png :align: center Change toolbar color -------------------- .. code-block:: kv MDToolbar: title: "MDToolbar" md_bg_color: app.theme_cls.accent_color .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-5.png :align: center Change toolbar text color ------------------------- .. code-block:: kv MDToolbar: title: "MDToolbar" specific_text_color: app.theme_cls.accent_color .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-6.png :align: center Shadow elevation control ------------------------ .. code-block:: kv MDToolbar: title: "Elevation 10" elevation: 10 .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-7.png :align: center .. Bottom: Bottom ------ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/app-bar-bottom.png :align: center Usage ----- .. code-block:: python from kivy.lang import Builder from kivymd.app import MDApp KV = ''' BoxLayout: # Will always be at the bottom of the screen. MDBottomAppBar: MDToolbar: title: "Title" icon: "git" type: "bottom" left_action_items: [["menu", lambda x: x]] ''' class Test(MDApp): def build(self): return Builder.load_string(KV) Test().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-8.png :align: center Event on floating button ------------------------ Event ``on_action_button``: .. code-block:: kv MDBottomAppBar: MDToolbar: title: "Title" icon: "git" type: "bottom" left_action_items: [["menu", lambda x: x]] on_action_button: app.callback(self.icon) Floating button position ------------------------ Mode: - `'free-end'` - `'free-center'` - `'end'` - `'center'` .. code-block:: kv MDBottomAppBar: MDToolbar: title: "Title" icon: "git" type: "bottom" left_action_items: [["menu", lambda x: x]] mode: "end" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-9.png :align: center .. code-block:: kv MDBottomAppBar: MDToolbar: title: "Title" icon: "git" type: "bottom" left_action_items: [["menu", lambda x: x]] mode: "free-end" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-10.png :align: center .. seealso:: `Components-Bottom-App-Bar `_ """ from kivy.animation import Animation from kivy.clock import Clock from kivy.core.window import Window from kivy.lang import Builder from kivy.metrics import dp from kivy.properties import ( ListProperty, NumericProperty, OptionProperty, StringProperty, ) from kivy.uix.boxlayout import BoxLayout from kivy.uix.floatlayout import FloatLayout from kivymd.theming import ThemableBehavior from kivymd.uix.behaviors import ( RectangularElevationBehavior, SpecificBackgroundColorBehavior, ) from kivymd.uix.button import MDFloatingActionButton, MDIconButton Builder.load_string( """ #:import m_res kivymd.material_resources : md_bg_color: self.theme_cls.primary_color canvas.before: PushMatrix Scale: origin: self.center x: root._scale_x y: root._scale_y canvas.after: PopMatrix size_hint_y: None height: root.theme_cls.standard_increment padding: [root.theme_cls.horizontal_margins - dp(12), 0] opposite_colors: True elevation: root.elevation md_bg_color: self.theme_cls.primary_color if root.type != "bottom" else [0, 0, 0, 0] canvas: Color: rgba: root.theme_cls.primary_color RoundedRectangle: pos: self.pos \ if root.mode == "center" else \ (self.width - root.action_button.width + dp(6), self.y) size: (((self.width - root.action_button.width) / 2 - dp(6), self.height) \ if root.mode == "center" else \ (root.action_button.width - dp(6), self.height)) if root.type == "bottom" else (0, 0) radius: (0, root.round, 0, 0) if root.mode == "center" else (root.round, 0, 0, 0) Rectangle: pos: ((self.width / 2 - root.action_button.width / 2) - dp(6), self.y - root._shift) \ if root.mode == "center" else \ (self.width - root.action_button.width * 2 - dp(6), self.y - root._shift) size: (root.action_button.width + dp(6) * 2, self.height - root._shift * 2) \ if root.type == "bottom" else (0, 0) RoundedRectangle: pos: ((self.width + root.action_button.width) / 2 + dp(6), self.y) \ if root.mode == "center" else self.pos size: (((self.width - root.action_button.width) / 2 + dp(6), self.height) \ if root.mode == "center" else \ ((self.width - root.action_button.width * 2 - dp(6)), self.height)) \ if root.type == "bottom" else (0, 0) radius: (root.round, 0, 0, 0) if root.mode == "center" else (0, root.round, 0, 0) Color: rgba: 1, 1, 1, 1 Ellipse: pos: (self.center[0] - root.action_button.width / 2 - dp(6), self.center[1] - root._shift * 2) \ if root.mode == "center" else \ (self.width - root.action_button.width * 2 - dp(6), self.center[1] - root._shift * 2) size: (root.action_button.width + dp(6) * 2, root.action_button.width) \ if root.type == "bottom" else (0, 0) angle_start: root._angle_start angle_end: root._angle_end BoxLayout: id: left_actions orientation: 'horizontal' size_hint_x: None padding: [0, (self.height - dp(48))/2] BoxLayout: padding: dp(12), 0 MDLabel: id: label_title font_style: 'H6' opposite_colors: root.opposite_colors theme_text_color: 'Custom' text_color: root.specific_text_color text: root.title shorten: True shorten_from: 'right' halign: root.anchor_title BoxLayout: id: right_actions orientation: 'horizontal' size_hint_x: None padding: [0, (self.height - dp(48)) / 2] """ ) class MDActionBottomAppBarButton(MDFloatingActionButton): _scale_x = NumericProperty(1) _scale_y = NumericProperty(1) class MDToolbar( ThemableBehavior, RectangularElevationBehavior, SpecificBackgroundColorBehavior, BoxLayout, ): """ :Events: `on_action_button` Method for the button used for the :class:`~MDBottomAppBar` class. """ elevation = NumericProperty(6) """ Elevation value. :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` and defaults to `6`. """ left_action_items = ListProperty() """The icons on the left of the toolbar. To add one, append a list like the following: .. code-block:: kv left_action_items: [`'icon_name'`, callback] where `'icon_name'` is a string that corresponds to an icon definition and ``callback`` is the function called on a touch release event. :attr:`left_action_items` is an :class:`~kivy.properties.ListProperty` and defaults to `[]`. """ right_action_items = ListProperty() """The icons on the left of the toolbar. Works the same way as :attr:`left_action_items`. :attr:`right_action_items` is an :class:`~kivy.properties.ListProperty` and defaults to `[]`. """ title = StringProperty() """Text toolbar. :attr:`title` is an :class:`~kivy.properties.StringProperty` and defaults to `''`. """ md_bg_color = ListProperty([0, 0, 0, 0]) """Color toolbar. :attr:`md_bg_color` is an :class:`~kivy.properties.ListProperty` and defaults to `[0, 0, 0, 0]`. """ anchor_title = OptionProperty("left", options=["left", "center", "right"]) """Position toolbar title. Available options are: `'left'`, `'center'`, `'right'`. :attr:`anchor_title` is an :class:`~kivy.properties.OptionProperty` and defaults to `'left'`. """ mode = OptionProperty( "center", options=["free-end", "free-center", "end", "center"] ) """Floating button position. Only for :class:`~MDBottomAppBar` class. Available options are: `'free-end'`, `'free-center'`, `'end'`, `'center'`. :attr:`mode` is an :class:`~kivy.properties.OptionProperty` and defaults to `'center'`. """ round = NumericProperty("10dp") """ Rounding the corners at the notch for a button. Onle for :class:`~MDBottomAppBar` class. :attr:`round` is an :class:`~kivy.properties.NumericProperty` and defaults to `'10dp'`. """ icon = StringProperty("android") """ Floating button. Onle for :class:`~MDBottomAppBar` class. :attr:`icon` is an :class:`~kivy.properties.StringProperty` and defaults to `'android'`. """ icon_color = ListProperty() """ Color action button. Onle for :class:`~MDBottomAppBar` class. :attr:`icon_color` is an :class:`~kivy.properties.ListProperty` and defaults to `[]`. """ type = OptionProperty("top", options=["top", "bottom"]) """ When using the :class:`~MDBottomAppBar` class, the parameter ``type`` must be set to `'bottom'`: .. code-block:: kv MDBottomAppBar: MDToolbar: type: "bottom" Available options are: `'top'`, `'bottom'`. :attr:`type` is an :class:`~kivy.properties.OptionProperty` and defaults to `'top'`. """ _shift = NumericProperty("3.5dp") _angle_start = NumericProperty(90) _angle_end = NumericProperty(270) def __init__(self, **kwargs): self.action_button = MDActionBottomAppBarButton() super().__init__(**kwargs) self.register_event_type("on_action_button") self.action_button.bind( on_release=lambda x: self.dispatch("on_action_button") ) self.action_button.x = Window.width / 2 - self.action_button.width / 2 self.action_button.y = ( (self.center[1] - self.height / 2) + self.theme_cls.standard_increment / 2 + self._shift ) if not self.icon_color: self.icon_color = self.theme_cls.primary_color Window.bind(on_resize=self._on_resize) self.bind(specific_text_color=self.update_action_bar_text_colors) Clock.schedule_once( lambda x: self.on_left_action_items(0, self.left_action_items) ) Clock.schedule_once( lambda x: self.on_right_action_items(0, self.right_action_items) ) def on_action_button(self, *args): pass def on_md_bg_color(self, instance, value): if self.type == "bottom": self.md_bg_color = [0, 0, 0, 0] def on_left_action_items(self, instance, value): self.update_action_bar(self.ids["left_actions"], value) def on_right_action_items(self, instance, value): self.update_action_bar(self.ids["right_actions"], value) def update_action_bar(self, action_bar, action_bar_items): action_bar.clear_widgets() new_width = 0 for item in action_bar_items: new_width += dp(48) action_bar.add_widget( MDIconButton( icon=item[0], on_release=item[1], opposite_colors=True, text_color=self.specific_text_color, theme_text_color="Custom", ) ) action_bar.width = new_width def update_action_bar_text_colors(self, instance, value): for child in self.ids["left_actions"].children: child.text_color = self.specific_text_color for child in self.ids["right_actions"].children: child.text_color = self.specific_text_color def _on_resize(self, instance, width, height): if self.mode == "center": self.action_button.x = width / 2 - self.action_button.width / 2 else: self.action_button.x = width - self.action_button.width * 2 def on_icon(self, instance, value): self.action_button.icon = value def on_icon_color(self, instance, value): self.action_button.md_bg_color = value def on_mode(self, instance, value): def set_button_pos(*args): self.action_button.x = x self.action_button.y = y self.action_button._hard_shadow_size = (0, 0) self.action_button._soft_shadow_size = (0, 0) anim = Animation(_scale_x=1, _scale_y=1, d=0.05) anim.bind(on_complete=self.set_shadow) anim.start(self.action_button) if value == "center": self.set_notch() x = Window.width / 2 - self.action_button.width / 2 y = ( (self.center[1] - self.height / 2) + self.theme_cls.standard_increment / 2 + self._shift ) elif value == "end": self.set_notch() x = Window.width - self.action_button.width * 2 y = ( (self.center[1] - self.height / 2) + self.theme_cls.standard_increment / 2 + self._shift ) self.right_action_items = [] elif value == "free-end": self.remove_notch() x = Window.width - self.action_button.width - dp(10) y = self.action_button.height + self.action_button.height / 2 elif value == "free-center": self.remove_notch() x = Window.width / 2 - self.action_button.width / 2 y = self.action_button.height + self.action_button.height / 2 self.remove_shadow() anim = Animation(_scale_x=0, _scale_y=0, d=0.05) anim.bind(on_complete=set_button_pos) anim.start(self.action_button) def remove_notch(self): self._angle_start = 0 self._angle_end = 0 self.round = 0 self._shift = 0 def set_notch(self): self._angle_start = 90 self._angle_end = 270 self.round = dp(10) self._shift = dp(3.5) def remove_shadow(self): self.action_button._hard_shadow_size = (0, 0) self.action_button._soft_shadow_size = (0, 0) def set_shadow(self, *args): self.action_button._hard_shadow_size = (dp(112), dp(112)) self.action_button._soft_shadow_size = (dp(112), dp(112)) class MDBottomAppBar(FloatLayout): def __init__(self, **kwargs): super().__init__(**kwargs) self.size_hint_y = None def add_widget(self, widget, index=0, canvas=None): if widget.__class__ is MDToolbar: super().add_widget(widget) return super().add_widget(widget.action_button)