openCom-Companion/sbapp/kivymd/uix/button/button.py
2022-07-07 22:16:10 +02:00

1983 lines
60 KiB
Python
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Components/Button
=================
.. seealso::
`Material Design spec, Buttons <https://material.io/components/buttons>`_
`Material Design spec, Buttons: floating action button <https://material.io/components/buttons-floating-action-button>`_
.. rubric:: Buttons allow users to take actions, and make choices,
with a single tap.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/buttons.png
:align: center
`KivyMD` provides the following button classes for use:
- MDIconButton_
- MDFloatingActionButton_
- MDFlatButton_
- MDRaisedButton_
- MDRectangleFlatButton_
- MDRectangleFlatIconButton_
- MDRoundFlatButton_
- MDRoundFlatIconButton_
- MDFillRoundFlatButton_
- MDFillRoundFlatIconButton_
- MDTextButton_
- MDFloatingActionButtonSpeedDial_
.. MDIconButton:
MDIconButton
------------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button.gif
:align: center
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
MDScreen:
MDIconButton:
icon: "language-python"
pos_hint: {"center_x": .5, "center_y": .5}
'''
class Example(MDApp):
def build(self):
return Builder.load_string(KV)
Example().run()
The :class:`~MDIconButton.icon` parameter must have the name of the icon
from ``kivymd/icon_definitions.py`` file.
You can also use custom icons:
.. code-block:: kv
MDIconButton:
icon: "data/logo/kivy-icon-256.png"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-custom-button.gif
:align: center
By default, :class:`~MDIconButton` button has a size ``(dp(48), dp (48))``.
Use :class:`~BaseButton.icon_size` attribute to resize the button:
.. code-block:: kv
MDIconButton:
icon: "android"
icon_size: "64sp"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button-user-font-size.gif
:align: center
By default, the color of :class:`~MDIconButton`
(depending on the style of the application) is black or white.
You can change the color of :class:`~MDIconButton` as the text color
of :class:`~kivymd.uix.label.MDLabel`, substituting ``theme_icon_color`` for
``theme_text_color`` and ``icon_color`` for ``text_color``.
The use of ``user_font_size``, ``text_color`` and ``theme_text_color`` for
:class:`~MDIconButton` is deprecated.
.. code-block:: kv
MDIconButton:
icon: "android"
theme_icon_color: "Custom"
icon_color: app.theme_cls.primary_color
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button-theme-text-color.png
:align: center
.. MDFloatingActionButton:
MDFloatingActionButton
----------------------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button.png
:align: center
The above parameters for :class:`~MDIconButton` apply
to :class:`~MDFloatingActionButton`.
To change :class:`~MDFloatingActionButton` background, use the
``md_bg_color`` parameter:
.. code-block:: kv
MDFloatingActionButton:
icon: "android"
md_bg_color: app.theme_cls.primary_color
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-md-bg-color.png
:align: center
Material design style 3
-----------------------
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.button import MDFloatingActionButton
KV = '''
MDScreen:
md_bg_color: "#f7f2fa"
MDBoxLayout:
id: box
spacing: "56dp"
adaptive_size: True
pos_hint: {"center_x": .5, "center_y": .5}
'''
class TestNavigationDrawer(MDApp):
def build(self):
self.theme_cls.material_style = "M3"
return Builder.load_string(KV)
def on_start(self):
data = {
"standard": {"md_bg_color": "#fefbff", "text_color": "#6851a5"},
"small": {"md_bg_color": "#e9dff7", "text_color": "#211c29"},
"large": {"md_bg_color": "#f8d7e3", "text_color": "#311021"},
}
for type_button in data.keys():
self.root.ids.box.add_widget(
MDFloatingActionButton(
icon="pencil",
type=type_button,
theme_icon_color="Custom",
md_bg_color=data[type_button]["md_bg_color"],
icon_color=data[type_button]["text_color"],
)
)
TestNavigationDrawer().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-m3.gif
:align: center
.. MDFlatButton:
MDFlatButton
------------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-flat-button.gif
:align: center
To change the text color of: class:`~MDFlatButton` use the ``text_color`` parameter:
.. code-block:: kv
MDFlatButton:
text: "MDFLATBUTTON"
theme_text_color: "Custom"
text_color: 0, 0, 1, 1
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-flat-button-text-color.png
:align: center
Or use markup:
.. code-block:: kv
MDFlatButton:
text: "[color=#00ffcc]MDFLATBUTTON[/color]"
To specify the font size and font name, use the parameters as in the usual
`Kivy` buttons:
.. code-block:: kv
MDFlatButton:
text: "MDFLATBUTTON"
font_size: "18sp"
font_name: "path/to/font"
.. MDRaisedButton:
MDRaisedButton
--------------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-raised-button.gif
:align: center
This button is similar to the :class:`~MDFlatButton` button except that you
can set the background color for :class:`~MDRaisedButton`:
.. code-block:: kv
MDRaisedButton:
text: "MDRAISEDBUTTON"
md_bg_color: 1, 0, 1, 1
.. MDRectangleFlatButton:
MDRectangleFlatButton
---------------------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-button.gif
:align: center
.. code-block:: kv
MDRectangleFlatButton:
text: "MDRECTANGLEFLATBUTTON"
theme_text_color: "Custom"
text_color: 1, 0, 0, 1
line_color: 0, 0, 1, 1
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-button-md-bg-color.png
:align: center
.. MDRectangleFlatIconButton:
MDRectangleFlatIconButton
-------------------------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button.png
:align: center
Button parameters :class:`~MDRectangleFlatIconButton` are the same as
button :class:`~MDRectangleFlatButton`, with the addition of the
``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`.
.. code-block:: kv
MDRectangleFlatIconButton:
icon: "android"
text: "MDRECTANGLEFLATICONBUTTON"
theme_text_color: "Custom"
text_color: 0, 0, 1, 1
line_color: 1, 0, 1, 1
theme_icon_color: "Custom"
icon_color: 1, 0, 0, 1
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button-custom.png
:align: center
Without border
--------------
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.button import MDRectangleFlatIconButton
class Example(MDApp):
def build(self):
screen = MDScreen()
screen.add_widget(
MDRectangleFlatIconButton(
text="MDRectangleFlatIconButton",
icon="language-python",
line_color=(0, 0, 0, 0),
pos_hint={"center_x": .5, "center_y": .5},
)
)
return screen
Example().run()
.. code-block:: kv
MDRectangleFlatIconButton:
text: "MDRectangleFlatIconButton"
icon: "language-python"
line_color: 0, 0, 0, 0
pos_hint: {"center_x": .5, "center_y": .5}
.. MDRoundFlatButton:
MDRoundFlatButton
-----------------
.. code-block:: kv
MDRoundFlatButton:
text: "MDROUNDFLATBUTTON"
text_color: 0, 1, 0, 1
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-button-text-color.png
:align: center
.. MDRoundFlatIconButton:
MDRoundFlatIconButton
---------------------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-icon-button.png
:align: center
Button parameters :class:`~MDRoundFlatIconButton` are the same as
button :class:`~MDRoundFlatButton`, with the addition of the
``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`:
.. code-block:: kv
MDRoundFlatIconButton:
icon: "android"
text: "MDROUNDFLATICONBUTTON"
.. MDFillRoundFlatButton:
MDFillRoundFlatButton
---------------------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-fill-round-flat-button.png
:align: center
Button parameters :class:`~MDFillRoundFlatButton` are the same as
button :class:`~MDRaisedButton`.
.. MDFillRoundFlatIconButton:
MDFillRoundFlatIconButton
-------------------------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-fill-round-flat-icon-button.png
:align: center
Button parameters :class:`~MDFillRoundFlatIconButton` are the same as
button :class:`~MDRaisedButton`, with the addition of the
``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`.
.. note:: Notice that the width of the :class:`~MDFillRoundFlatIconButton`
button matches the size of the button text.
.. MDTextButton:
MDTextButton
------------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-text-button.png
:align: center
.. code-block:: kv
MDTextButton:
text: "MDTEXTBUTTON"
custom_color: 0, 1, 0, 1
.. MDFloatingActionButtonSpeedDial:
MDFloatingActionButtonSpeedDial
-------------------------------
.. Note:: See the full list of arguments in the class
:class:`~MDFloatingActionButtonSpeedDial`.
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
MDScreen:
MDFloatingActionButtonSpeedDial:
data: app.data
root_button_anim: True
'''
class Example(MDApp):
data = {
'Python': 'language-python',
'PHP': 'language-php',
'C++': 'language-cpp',
}
def build(self):
return Builder.load_string(KV)
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial.gif
:align: center
Or without KV Language:
.. code-block:: python
from kivymd.uix.screen import MDScreen
from kivymd.app import MDApp
from kivymd.uix.button import MDFloatingActionButtonSpeedDial
class Example(MDApp):
data = {
'Python': 'language-python',
'PHP': 'language-php',
'C++': 'language-cpp',
}
def build(self):
screen = MDScreen()
speed_dial = MDFloatingActionButtonSpeedDial()
speed_dial.data = self.data
speed_dial.root_button_anim = True
screen.add_widget(speed_dial)
return screen
Example().run()
You can use various types of animation of labels for buttons on the stack:
.. code-block:: kv
MDFloatingActionButtonSpeedDial:
hint_animation: True
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-hint.gif
:align: center
You can set your color values for background, text of buttons etc:
.. code-block:: kv
MDFloatingActionButtonSpeedDial:
bg_hint_color: app.theme_cls.primary_light
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-hint-color.png
:align: center
.. seealso::
`See full example <https://github.com/kivymd/KivyMD/wiki/Components-Button>`_
"""
__all__ = (
"BaseButton",
"MDIconButton",
"MDFloatingActionButton",
"MDFlatButton",
"MDRaisedButton",
"MDRectangleFlatButton",
"MDRectangleFlatIconButton",
"MDRoundFlatButton",
"MDRoundFlatIconButton",
"MDFillRoundFlatButton",
"MDFillRoundFlatIconButton",
"MDTextButton",
"MDFloatingActionButtonSpeedDial",
)
import os
from typing import Union
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, sp
from kivy.properties import (
BooleanProperty,
BoundedNumericProperty,
ColorProperty,
DictProperty,
NumericProperty,
ObjectProperty,
OptionProperty,
StringProperty,
VariableListProperty,
)
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivymd import uix_path
from kivymd.color_definitions import text_colors
from kivymd.font_definitions import theme_font_styles
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import (
CommonElevationBehavior,
FakeRectangularElevationBehavior,
RectangularRippleBehavior,
RoundedRectangularElevationBehavior,
)
from kivymd.uix.label import MDLabel
from kivymd.uix.tooltip import MDTooltip
with open(
os.path.join(uix_path, "button", "button.kv"), encoding="utf-8"
) as kv_file:
Builder.load_string(kv_file.read())
theme_text_color_options = (
"Primary",
"Secondary",
"Hint",
"Error",
"Custom",
"ContrastParentBackground",
)
class BaseButton(
RectangularRippleBehavior, ThemableBehavior, ButtonBehavior, AnchorLayout
):
"""Base class for all buttons."""
padding = VariableListProperty([dp(16), dp(8), dp(16), dp(8)])
"""
Padding between the widget box and its children, in pixels:
[padding_left, padding_top, padding_right, padding_bottom].
padding also accepts a two argument form [padding_horizontal,
padding_vertical] and a one argument form [padding].
.. versionadded:: 1.0.0
:attr:`padding` is a :class:`~kivy.properties.VariableListProperty`
and defaults to [16dp, 8dp, 16dp, 8dp].
"""
halign = OptionProperty("center", options=("left", "center", "right"))
"""
Horizontal anchor.
.. versionadded:: 1.0.0
:attr:`anchor_x` is an :class:`~kivy.properties.OptionProperty`
and defaults to 'center'. It accepts values of 'left', 'center' or 'right'.
"""
valign = OptionProperty("center", options=("top", "center", "bottom"))
"""
Vertical anchor.
.. versionadded:: 1.0.0
:attr:`anchor_y` is an :class:`~kivy.properties.OptionProperty`
and defaults to 'center'. It accepts values of 'top', 'center' or 'bottom'.
"""
text = StringProperty("")
"""
Button text.
:attr:`text` is a :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
icon = StringProperty("")
"""
Button icon.
:attr:`icon` is a :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
font_style = OptionProperty("Body1", options=theme_font_styles)
"""
Button text font style.
Available vanilla font_style are: `'H1'`, `'H2'`, `'H3'`, `'H4'`, `'H5'`,
`'H6'`, `'Subtitle1'`, `'Subtitle2'`, `'Body1'`, `'Body2'`, `'Button'`,
`'Caption'`, `'Overline'`, `'Icon'`.
:attr:`font_style` is a :class:`~kivy.properties.StringProperty`
and defaults to `'Body1'`.
"""
theme_text_color = OptionProperty(None, options=theme_text_color_options)
"""
Button text type. Available options are: (`"Primary"`, `"Secondary"`,
`"Hint"`, `"Error"`, `"Custom"`, `"ContrastParentBackground"`).
:attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `None` (set by button class).
"""
theme_icon_color = OptionProperty(None, options=theme_text_color_options)
"""
Button icon type. Available options are: (`"Primary"`, `"Secondary"`,
`"Hint"`, `"Error"`, `"Custom"`, `"ContrastParentBackground"`).
.. versionadded:: 1.0.0
:attr:`theme_icon_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `None` (set by button subclass).
"""
text_color = ColorProperty(None)
"""
Button text color in (r, g, b, a) format.
:attr:`text_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
icon_color = ColorProperty(None)
"""
Button icon color in (r, g, b, a) format.
:attr:`icon_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
font_name = StringProperty()
"""
Button text font name.
:attr:`font_name` is a :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
font_size = NumericProperty("14sp")
"""
Button text font size.
:attr:`font_size` is a :class:`~kivy.properties.NumericProperty`
and defaults to `14sp`.
"""
icon_size = NumericProperty()
"""
Icon font size.
Use this parameter as the font size, that is, in sp units.
.. versionadded:: 1.0.0
:attr:`icon_size` is a :class:`~kivy.properties.NumericProperty`
and defaults to `None`.
"""
user_font_size = NumericProperty(0, deprecated=True)
"""
Custom font size for :class:`~MDIconButton`.
.. deprecated in 1.0.0::
Use :attr:`icon_size` instead.
:attr:`user_font_size` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0`.
"""
line_width = NumericProperty(1)
"""
Line width for button border.
:attr:`line_width` is a :class:`~kivy.properties.NumericProperty`
and defaults to `1`.
"""
line_color = ColorProperty(None)
"""
Line color for button border.
:attr:`line_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
line_color_disabled = ColorProperty(None)
"""
Disabled line color for button border.
.. versionadded:: 1.0.0
:attr:`line_color_disabled` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
md_bg_color = ColorProperty(None)
"""
Button background color.
:attr:`md_bg_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
md_bg_color_disabled = ColorProperty(None)
"""
The background color of the button when the button is disabled.
:attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
disabled_color = ColorProperty(None)
"""
The color of the text and icon when the button is disabled, in the
(r, g, b, a) format.
.. versionadded:: 1.0.0
:attr:`disabled_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
rounded_button = BooleanProperty(False)
"""
Should the button have fully rounded corners (e.g. like M3 buttons)?
.. versionadded:: 1.0.0
:attr:`rounded_button` is a :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
# Note - _radius must be > 0 to avoid rendering issues.
_radius = BoundedNumericProperty(dp(4), min=0.0999, errorvalue=0.1)
# Properties used for rendering.
_disabled_color = ColorProperty(None)
_md_bg_color = ColorProperty(None)
_md_bg_color_disabled = ColorProperty(None)
_line_color = ColorProperty(None)
_line_color_disabled = ColorProperty(None)
_theme_text_color = OptionProperty(None, options=theme_text_color_options)
_theme_icon_color = OptionProperty(None, options=theme_text_color_options)
_text_color = ColorProperty(None)
_icon_color = ColorProperty(None)
# Defaults which can be overridden in subclasses
_min_width = NumericProperty(dp(64))
_min_height = NumericProperty(dp(36))
# Default colors - set to None to use primary theme colors
_default_md_bg_color = [0.0, 0.0, 0.0, 0.0]
_default_md_bg_color_disabled = [0.0, 0.0, 0.0, 0.0]
_default_line_color = [0.0, 0.0, 0.0, 0.0]
_default_line_color_disabled = [0.0, 0.0, 0.0, 0.0]
_default_theme_text_color = StringProperty("Primary")
_default_theme_icon_color = StringProperty("Primary")
_default_text_color = ColorProperty(None)
_default_icon_color = ColorProperty(None)
_animation_fade_bg = ObjectProperty(None, allownone=True)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.theme_cls.bind(
primary_palette=self.set_all_colors,
theme_style=self.set_all_colors,
)
self.bind(
md_bg_color=self.set_button_colors,
md_bg_color_disabled=self.set_button_colors,
line_color=self.set_button_colors,
line_color_disabled=self.set_button_colors,
theme_text_color=self.set_text_color,
text_color=self.set_text_color,
theme_icon_color=self.set_icon_color,
icon_color=self.set_icon_color,
disabled_color=self.set_disabled_color,
rounded_button=self.set_radius,
height=self.set_radius,
)
Clock.schedule_once(self.set_all_colors)
Clock.schedule_once(self.set_radius)
def set_disabled_color(self, *args):
"""
Sets the color for the icon, text and line of the button when button
is disabled.
"""
if self.disabled:
disabled_color = (
self.disabled_color
if self.disabled_color
else self.theme_cls.disabled_hint_text_color
)
self._disabled_color = disabled_color
# Button icon color.
if "lbl_ic" in self.ids:
self.ids.lbl_ic.disabled_color = disabled_color
# Button text color.
if "lbl_txt" in self.ids:
self.ids.lbl_txt.disabled_color = disabled_color
else:
self._disabled_color = self._line_color
def set_all_colors(self, *args) -> None:
"""Set all button colours."""
self.set_button_colors()
self.set_text_color()
self.set_icon_color()
def set_button_colors(self, *args) -> None:
"""Set all button colours (except text/icons)."""
# Set main color
self._md_bg_color = (
self.md_bg_color
or self._default_md_bg_color
or self.theme_cls.primary_color
)
# Set disabled color
self._md_bg_color_disabled = (
self.md_bg_color_disabled
or (
[sum(self.md_bg_color[0:3]) / 3.0] * 3
+ [0.38 if self.theme_cls.theme_style == "Light" else 0.5]
if self.md_bg_color
else None
)
or self._default_md_bg_color_disabled
or self.theme_cls.disabled_primary_color
)
# Set line color
self._line_color = (
self.line_color
or self._default_line_color
or self.theme_cls.primary_color
)
# Set disabled line color
self._line_color_disabled = (
self.line_color_disabled
or (
[sum(self.line_color[0:3]) / 3.0] * 3
+ [0.38 if self.theme_cls.theme_style == "Light" else 0.5]
if self.line_color
else None
)
or self._default_line_color_disabled
or self.theme_cls.disabled_primary_color
)
def set_text_color(self, *args) -> None:
"""
Set _theme_text_color and _text_color based on defaults and options.
"""
self._theme_text_color = (
self.theme_text_color or self._default_theme_text_color
)
if self._default_text_color == "PrimaryHue":
default_text_color = text_colors[self.theme_cls.primary_palette][
self.theme_cls.primary_hue
]
elif self._default_text_color == "Primary":
default_text_color = self.theme_cls.primary_color
else:
default_text_color = self.theme_cls.text_color
self._text_color = self.text_color or default_text_color
def set_icon_color(self, *args) -> None:
"""
Set _theme_icon_color and _icon_color based on defaults and options.
"""
self._theme_icon_color = (
self.theme_icon_color or self._default_theme_icon_color
)
if self._default_icon_color == "PrimaryHue":
default_icon_color = text_colors[self.theme_cls.primary_palette][
self.theme_cls.primary_hue
]
elif self._default_icon_color == "Primary":
default_icon_color = self.theme_cls.primary_color
else:
default_icon_color = self.theme_cls.text_color
self._icon_color = self.icon_color or default_icon_color
def set_radius(self, *args) -> None:
"""
Set the radius, if we are a rounded button, based on the
current height.
"""
if self.rounded_button:
self._radius = self.height / 2
# Touch events that cause transparent buttons to fade to background
def on_touch_down(self, touch):
"""
Animates fade to background on press, for buttons with no
background color.
"""
if touch.is_mouse_scrolling:
return False
elif not self.collide_point(touch.x, touch.y):
return False
elif self in touch.ud:
return False
elif self.disabled:
return False
else:
if self._md_bg_color[3] == 0.0:
self._animation_fade_bg = Animation(
duration=0.5, _md_bg_color=[0.0, 0.0, 0.0, 0.1]
)
self._animation_fade_bg.start(self)
return super().on_touch_down(touch)
def on_touch_up(self, touch):
"""Animates return to original background on touch release."""
if not self.disabled and self._animation_fade_bg:
self._animation_fade_bg.stop_property(self, "_md_bg_color")
self._animation_fade_bg = None
md_bg_color = (
self.md_bg_color
or self._default_md_bg_color
or self.theme_cls.primary_color
)
Animation(duration=0.05, _md_bg_color=md_bg_color).start(self)
return super().on_touch_up(touch)
def on_disabled(self, instance_button, disabled_value: bool) -> None:
Clock.schedule_once(self.set_disabled_color)
class ButtonElevationBehaviour(CommonElevationBehavior):
"""
Implements elevation behavior as well as the recommended down/disabled
colors for raised buttons.
The minimum elevation for any raised button is `'1dp'`,
by default, set to `'2dp'`.
The `_elevation_raised` is automatically computed and is set to
`self.elevation + 6` each time `self.elevation` is updated.
"""
_elevation_raised = NumericProperty()
_anim_raised = ObjectProperty(None, allownone=True)
_default_elevation = 2
def __init__(self, **kwargs):
if self.elevation == 0:
self.elevation = self._default_elevation
super().__init__(**kwargs)
self.bind(_radius=self.setter("radius"))
self.on_elevation(self, self.elevation)
def on_elevation(self, instance_button, elevation_value: int) -> None:
super().on_elevation(instance_button, elevation_value)
self._elevation_raised = self.elevation + 6
self.on_disabled(self, self.disabled)
def on__elevation_raised(
self, instance_button, elevation_value: int
) -> None:
Animation.cancel_all(self, "_elevation")
self._anim_raised = Animation(_elevation=self._elevation_raised, d=0.15)
def on_disabled(self, instance_button, disabled_value: bool) -> None:
if self.disabled is True:
Animation.cancel_all(self, "_elevation")
super().on_disabled(instance_button, disabled_value)
def on_touch_down(self, touch):
if not self.disabled:
if touch.is_mouse_scrolling:
return False
if not self.collide_point(touch.x, touch.y):
return False
if self in touch.ud:
return False
if self._anim_raised:
self._anim_raised.start(self)
return super().on_touch_down(touch)
def on_touch_up(self, touch):
if not self.disabled:
if touch.grab_current is not self:
self.stop_elevation_anim()
return super().on_touch_up(touch)
self.stop_elevation_anim()
return super().on_touch_up(touch)
def stop_elevation_anim(self):
Animation.cancel_all(self, "_elevation")
self._elevation = self.elevation
class ButtonContentsText:
"""Contents for :class:`~BaseButton` class consisting of a single label."""
class ButtonContentsIcon:
"""
Contents for a round BaseButton consisting of an :class:`~MDIcon` class.
"""
_min_width = NumericProperty(0)
def __init__(self, **kwargs):
super().__init__(**kwargs)
if self.user_font_size:
self.icon_size = self.user_font_size
self.bind(user_font_size=self.setter("icon_size"))
def on_text_color(self, instance_button, color: list) -> None:
"""
Set icon_color equal to text_color.
For backwards compatibility - can use text_color instead
of icon_color.
"""
if color:
self.icon_color = color
class ButtonContentsIconText:
"""
Contents for :class:`~BaseButton` class consisting of a
:class:`~kivy.uix.boxlayout.BoxLayout` with an icon and a label.
"""
padding = VariableListProperty([dp(12), dp(8), dp(16), dp(8)])
"""
Padding between the widget box and its children, in pixels:
[padding_left, padding_top, padding_right, padding_bottom].
padding also accepts a two argument form [padding_horizontal,
padding_vertical] and a one argument form [padding].
.. versionadded:: 1.0.0
:attr:`padding` is a :class:`~kivy.properties.VariableListProperty`
and defaults to [12dp, 8dp, 16dp, 8dp].
"""
# Old MD Button classes
class OldButtonIconMixin:
"""Backwards-compatibility for icons."""
icon = StringProperty("android")
def on_icon_color(self, instance_button, color: list) -> None:
"""
If we are setting an icon color, set theme_icon_color to Custom.
For backwards compatibility (before theme_icon_color existed).
"""
if color and (self.theme_text_color == "Custom"):
self.theme_icon_color = "Custom"
class MDFlatButton(ButtonContentsText, BaseButton):
"""
A flat rectangular button with (by default) no border or background.
Text is the default text color.
"""
padding = VariableListProperty([dp(8), dp(8), dp(8), dp(8)])
"""
Padding between the widget box and its children, in pixels:
[padding_left, padding_top, padding_right, padding_bottom].
padding also accepts a two argument form [padding_horizontal,
padding_vertical] and a one argument form [padding].
.. versionadded:: 1.0.0
:attr:`padding` is a :class:`~kivy.properties.VariableListProperty`
and defaults to [8dp, 8dp, 8dp, 8dp].
"""
class MDRaisedButton(
FakeRectangularElevationBehavior,
ButtonElevationBehaviour,
ButtonContentsText,
BaseButton,
):
"""
A flat button with (by default) a primary color fill and matching
color text.
"""
# FIXME: Move the underlying attributes to the :class:`~BaseButton` class.
# This applies to all classes of buttons that have similar attributes.
_default_md_bg_color = None
_default_md_bg_color_disabled = None
_default_theme_text_color = "Custom"
_default_text_color = "PrimaryHue"
class MDRectangleFlatButton(ButtonContentsText, BaseButton):
"""
A flat button with (by default) a primary color border and primary
color text.
"""
_default_line_color = None
_default_line_color_disabled = None
_default_theme_text_color = "Custom"
_default_text_color = "Primary"
class MDRectangleFlatIconButton(
OldButtonIconMixin, ButtonContentsIconText, BaseButton
):
"""
A flat button with (by default) a primary color border, primary color text
and a primary color icon on the left.
"""
_default_line_color = None
_default_line_color_disabled = None
_default_theme_text_color = "Custom"
_default_theme_icon_color = "Custom"
_default_text_color = "Primary"
_default_icon_color = "Primary"
class MDRoundFlatButton(ButtonContentsText, BaseButton):
"""
A flat button with (by default) fully rounded corners, a primary
color border and primary color text.
"""
_default_line_color = None
_default_line_color_disabled = None
_default_theme_text_color = "Custom"
_default_text_color = "Primary"
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.rounded_button = True
class MDRoundFlatIconButton(
OldButtonIconMixin,
ButtonContentsIconText,
BaseButton,
):
"""
A flat button with (by default) rounded corners, a primary color border,
primary color text and a primary color icon on the left.
"""
_default_line_color = None
_default_line_color_disabled = None
_default_theme_text_color = "Custom"
_default_theme_icon_color = "Custom"
_default_text_color = "Primary"
_default_icon_color = "Primary"
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.rounded_button = True
class MDFillRoundFlatButton(ButtonContentsText, BaseButton):
"""
A flat button with (by default) rounded corners, a primary color fill
and primary color text.
"""
_default_md_bg_color = None
_default_md_bg_color_disabled = None
_default_theme_text_color = "Custom"
_default_text_color = "PrimaryHue"
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.rounded_button = True
class MDFillRoundFlatIconButton(
OldButtonIconMixin,
ButtonContentsIconText,
BaseButton,
):
"""
A flat button with (by default) rounded corners, a primary color fill,
primary color text and a primary color icon on the left.
"""
_default_md_bg_color = None
_default_md_bg_color_disabled = None
_default_theme_text_color = "Custom"
_default_theme_icon_color = "Custom"
_default_text_color = "PrimaryHue"
_default_icon_color = "PrimaryHue"
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.rounded_button = True
class MDIconButton(OldButtonIconMixin, ButtonContentsIcon, BaseButton):
"""A simple rounded icon button."""
icon = StringProperty("checkbox-blank-circle")
"""
Button icon.
:attr:`icon` is a :class:`~kivy.properties.StringProperty`
and defaults to `'checkbox-blank-circle'`.
"""
text_color = ColorProperty(None, deprecated=True)
"""
Button icon color in (r, g, b, a) format.
.. deprecated in 1.0.0::
Deprecated for :class:`~MDIconButton`. Use ``icon_color`` instead.
:attr:`text_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
theme_text_color = OptionProperty(
None, options=theme_text_color_options, deprecated=True
)
"""
Button icon type. Available options are: (`"Primary"`, `"Secondary"`,
`"Hint"`, `"Error"`, `"Custom"`, `"ContrastParentBackground"`).
.. deprecated in 1.0.0::
Deprecated for :class:`~MDIconButton`. Use ``theme_icon_color`` instead.
:attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `None` (set by button class).
"""
_min_width = NumericProperty(0)
_default_icon_pad = max(dp(48) - sp(24), 0)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.rounded_button = True
# FIXME: GraphicException: Invalid width value, must be > 0
self.line_width = 0.001
Clock.schedule_once(self.set_size)
def set_size(self, interval: Union[int, float]) -> None:
"""
Sets the icon width/height based on the current `icon_size`
attribute, or the default value if it is zero. The icon size
is set to `(48, 48)` for an icon with the default font_size 24sp.
"""
diameter = self._default_icon_pad + (self.icon_size or sp(24))
self.width = diameter
self.height = diameter
class MDFloatingActionButton(
OldButtonIconMixin,
RoundedRectangularElevationBehavior,
ButtonElevationBehaviour,
ButtonContentsIcon,
BaseButton,
):
"""
Implementation
`FAB <https://m3.material.io/components/floating-action-button/overview>`_
button.
"""
type = OptionProperty("standard", options=["small", "large", "standard"])
"""
Type of M3 button.
.. versionadded:: 1.0.0
Available options are: 'small', 'large', 'standard'.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-types.png
:align: center
:attr:`type` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'standard'`.
"""
_default_md_bg_color = None
_default_md_bg_color_disabled = None
_default_theme_icon_color = "Custom"
_default_icon_color = "PrimaryHue"
def __init__(self, **kwargs):
super().__init__(**kwargs)
# FIXME: GraphicException: Invalid width value, must be > 0
self.line_width = 0.001
self.theme_cls.bind(material_style=self.set_size)
self.theme_cls.bind(material_style=self.set__radius)
Clock.schedule_once(self.set_size)
Clock.schedule_once(self.set__radius)
Clock.schedule_once(self.set_font_size)
def set_font_size(self, *args) -> None:
if self.theme_cls.material_style == "M3":
if self.type == "large":
self.icon_size = "36sp"
else:
self.icon_size = 0
def set__radius(self, *args) -> None:
if self.theme_cls.material_style == "M2":
self.rounded_button = True
else:
self.rounded_button = False
if self.type == "small":
self._radius = dp(12)
elif self.type == "standard":
self._radius = dp(16)
elif self.type == "large":
self._radius = dp(28)
def set_size(self, *args) -> None:
if self.theme_cls.material_style == "M2":
self.size = dp(56), dp(56)
else:
if self.type == "small":
self.size = dp(40), dp(40)
elif self.type == "standard":
self.size = dp(56), dp(56)
elif self.type == "large":
self.size = dp(96), dp(96)
def on_type(self, instance_md_floating_action_button, type: str) -> None:
self.set_size()
self.set_font_size()
class MDTextButton(ButtonBehavior, MDLabel):
color = ColorProperty(None)
"""
Button color in (r, g, b, a) format.
:attr:`color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
color_disabled = ColorProperty(None)
"""
Button color disabled in (r, g, b, a) format.
:attr:`color_disabled` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
_color = ColorProperty(None) # last current button text color
def animation_label(self) -> None:
def set_default_state_label(*args):
Animation(opacity=1, d=0.1, t="in_out_cubic").start(self)
anim = Animation(opacity=0.5, d=0.2, t="in_out_cubic")
anim.bind(on_complete=set_default_state_label)
anim.start(self)
def on_press(self, *args):
self.animation_label()
return super().on_press(*args)
def on_disabled(self, instance_button, disabled_value) -> None:
if disabled_value:
if not self.color_disabled:
self.color_disabled = self.theme_cls.disabled_hint_text_color
self._color = self.color
self.text_color = self.color_disabled
else:
self.text_color = self._color
# SpeedDial classes
class BaseFloatingRootButton(MDFloatingActionButton):
_angle = NumericProperty(0)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.elevation = 5
class BaseFloatingBottomButton(MDFloatingActionButton, MDTooltip):
_canvas_width = NumericProperty(0)
_padding_right = NumericProperty(0)
_bg_color = ColorProperty(None)
def set_size(self, interval: Union[int, float]) -> None:
self.width = "46dp"
self.height = "46dp"
# FIXME: Use :class:`~kivymd.uix.boxlayout.MDBoxLayout` instead
# :class:`~kivy.uix.boxlayout.BoxLayout`.
class BaseFloatingLabel(
ThemableBehavior, FakeRectangularElevationBehavior, BoxLayout
):
text = StringProperty()
text_color = ColorProperty(None)
bg_color = ColorProperty(None)
class MDFloatingBottomButton(BaseFloatingBottomButton):
pass
class MDFloatingRootButton(BaseFloatingRootButton):
pass
class MDFloatingLabel(BaseFloatingLabel):
pass
class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
"""
:Events:
:attr:`on_open`
Called when a stack is opened.
:attr:`on_close`
Called when a stack is closed.
"""
icon = StringProperty("plus")
"""
Root button icon name.
:attr:`icon` is a :class:`~kivy.properties.StringProperty`
and defaults to `'plus'`.
"""
anchor = OptionProperty("right", option=["right"])
"""
Stack anchor. Available options are: `'right'`.
:attr:`anchor` is a :class:`~kivy.properties.OptionProperty`
and defaults to `'right'`.
"""
callback = ObjectProperty(lambda x: None)
"""
Custom callback.
.. code-block:: kv
MDFloatingActionButtonSpeedDial:
callback: app.callback
.. code-block:: python
def callback(self, instance):
print(instance.icon)
:attr:`callback` is a :class:`~kivy.properties.ObjectProperty`
and defaults to `None`.
"""
label_text_color = ColorProperty([0, 0, 0, 1])
"""
Floating text color in (r, g, b, a) format.
:attr:`label_text_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 1]`.
"""
data = DictProperty()
"""
Must be a dictionary
.. code-block:: python
{
'name-icon': 'Text label',
...,
...,
}
"""
right_pad = BooleanProperty(True)
"""
If `True`, the button will increase on the right side by 2.5 pixels
if the :attr:`~hint_animation` parameter equal to `True`.
.. rubric:: False
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad.gif
:align: center
.. rubric:: True
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad-true.gif
:align: center
:attr:`right_pad` is a :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
root_button_anim = BooleanProperty(False)
"""
If ``True`` then the root button will rotate 45 degrees when the stack
is opened.
:attr:`root_button_anim` is a :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
opening_transition = StringProperty("out_cubic")
"""
The name of the stack opening animation type.
:attr:`opening_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_cubic'`.
"""
closing_transition = StringProperty("out_cubic")
"""
The name of the stack closing animation type.
:attr:`closing_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_cubic'`.
"""
opening_transition_button_rotation = StringProperty("out_cubic")
"""
The name of the animation type to rotate the root button when opening the
stack.
:attr:`opening_transition_button_rotation` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_cubic'`.
"""
closing_transition_button_rotation = StringProperty("out_cubic")
"""
The name of the animation type to rotate the root button when closing the
stack.
:attr:`closing_transition_button_rotation` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_cubic'`.
"""
opening_time = NumericProperty(0.5)
"""
Time required for the stack to go to: attr:`state` `'open'`.
:attr:`opening_time` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.2`.
"""
closing_time = NumericProperty(0.2)
"""
Time required for the stack to go to: attr:`state` `'close'`.
:attr:`closing_time` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.2`.
"""
opening_time_button_rotation = NumericProperty(0.2)
"""
Time required to rotate the root button 45 degrees during the stack
opening animation.
:attr:`opening_time_button_rotation` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.2`.
"""
closing_time_button_rotation = NumericProperty(0.2)
"""
Time required to rotate the root button 0 degrees during the stack
closing animation.
:attr:`closing_time_button_rotation` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.2`.
"""
state = OptionProperty("close", options=("close", "open"))
"""
Indicates whether the stack is closed or open.
Available options are: `'close'`, `'open'`.
:attr:`state` is a :class:`~kivy.properties.OptionProperty`
and defaults to `'close'`.
"""
bg_color_root_button = ColorProperty(None)
"""
Root button color in (r, g, b, a) format.
:attr:`bg_color_root_button` is a :class:`~kivy.properties.ColorProperty`
and defaults to `[]`.
"""
bg_color_stack_button = ColorProperty(None)
"""
The color of the buttons in the stack (r, g, b, a) format.
:attr:`bg_color_stack_button` is a :class:`~kivy.properties.ColorProperty`
and defaults to `[]`.
"""
color_icon_stack_button = ColorProperty(None)
"""
The color icon of the buttons in the stack (r, g, b, a) format.
:attr:`color_icon_stack_button` is a :class:`~kivy.properties.ColorProperty`
and defaults to `[]`.
"""
color_icon_root_button = ColorProperty(None)
"""
The color icon of the root button (r, g, b, a) format.
:attr:`color_icon_root_button` is a :class:`~kivy.properties.ColorProperty`
and defaults to `[]`.
"""
bg_hint_color = ColorProperty(None)
"""
Background color for the text of the buttons in the stack (r, g, b, a) format.
:attr:`bg_hint_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
hint_animation = BooleanProperty(False)
"""
Whether to use button extension animation to display text labels.
:attr:`hint_animation` is a :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
_label_pos_y_set = False
_anim_buttons_data = {}
_anim_labels_data = {}
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.register_event_type("on_open")
self.register_event_type("on_close")
Window.bind(on_resize=self._update_pos_buttons)
def on_open(self, *args):
"""Called when a stack is opened."""
def on_close(self, *args):
"""Called when a stack is closed."""
def on_leave(self, instance_button: MDFloatingBottomButton) -> None:
"""Called when the mouse cursor goes outside the button of stack."""
if self.state == "open":
for widget in self.children:
if isinstance(widget, MDFloatingLabel) and self.hint_animation:
Animation.cancel_all(widget)
for item in self.data.items():
if widget.text in item:
Animation(
_canvas_width=0,
_padding_right=0,
d=self.opening_time,
t=self.opening_transition,
_elevation=0,
).start(instance_button)
Animation(
opacity=0, d=0.1, t=self.opening_transition
).start(widget)
def on_enter(self, instance_button: MDFloatingBottomButton) -> None:
"""Called when the mouse cursor is over a button from the stack."""
if self.state == "open":
for widget in self.children:
if isinstance(widget, MDFloatingLabel) and self.hint_animation:
widget._elevation = 0
Animation.cancel_all(widget)
for item in self.data.items():
if widget.text in item:
Animation(
_canvas_width=widget.width + dp(24),
_padding_right=dp(5) if self.right_pad else 0,
d=self.opening_time,
t=self.opening_transition,
).start(instance_button)
if (
instance_button.icon
== self.data[f"{widget.text}"]
):
Animation(
opacity=1,
d=self.opening_time,
t=self.opening_transition,
).start(widget)
else:
Animation(
opacity=0, d=0.1, t=self.opening_transition
).start(widget)
def on_data(self, instance_speed_dial, data: dict) -> None:
"""Creates a stack of buttons."""
# FIXME: Don't know how to fix AttributeError error:
# File "kivymd/uix/button.py", line 1597, in on_data
# self.add_widget(bottom_button)
# File "kivy/uix/floatlayout.py", line 140, in add_widget
# return super(FloatLayout, self).add_widget(widget, index, canvas)
# File "kivy/uix/layout.py", line 97, in add_widget
# return super(Layout, self).add_widget(widget, index, canvas)
# File "kivy/uix/widget.py", line 629, in add_widget
# canvas.add(widget.canvas)
# AttributeError: 'NoneType' object has no attribute 'add'
super().__init__()
self.clear_widgets()
self._anim_buttons_data = {}
self._anim_labels_data = {}
self._label_pos_y_set = False
# Bottom buttons.
for name, name_icon in data.items():
bottom_button = MDFloatingBottomButton(
icon=name_icon,
on_enter=self.on_enter,
on_leave=self.on_leave,
opacity=0,
)
bottom_button.bind(
on_release=lambda x=bottom_button: self.callback(x)
)
self.set_pos_bottom_buttons(bottom_button)
self.add_widget(bottom_button)
# Labels.
floating_text = name
if floating_text:
label = MDFloatingLabel(text=floating_text, opacity=0)
label.text_color = self.label_text_color
self.add_widget(label)
# Top root button.
root_button = MDFloatingRootButton(on_release=self.open_stack)
root_button.icon = self.icon
self.set_pos_root_button(root_button)
self.add_widget(root_button)
def on_icon(self, instance_speed_dial, name_icon: str) -> None:
self._get_count_widget(MDFloatingRootButton).icon = name_icon
def on_label_text_color(self, instance_speed_dial, color: list) -> None:
for widget in self.children:
if isinstance(widget, MDFloatingLabel):
widget.text_color = color
def on_color_icon_stack_button(
self, instance_speed_dial, color: list
) -> None:
for widget in self.children:
if isinstance(widget, MDFloatingBottomButton):
widget.text_color = color
def on_hint_animation(self, instance_speed_dial, value: bool) -> None:
for widget in self.children:
if isinstance(widget, MDFloatingLabel):
widget.bg_color = (0, 0, 0, 0)
def on_bg_hint_color(self, instance_speed_dial, color: list) -> None:
for widget in self.children:
if isinstance(widget, MDFloatingBottomButton):
widget._bg_color = color
def on_color_icon_root_button(
self, instance_speed_dial, color: list
) -> None:
self._get_count_widget(MDFloatingRootButton).text_color = color
def on_bg_color_stack_button(
self, instance_speed_dial, color: list
) -> None:
for widget in self.children:
if isinstance(widget, MDFloatingBottomButton):
widget.md_bg_color = color
def on_bg_color_root_button(self, instance_speed_dial, color: list) -> None:
self._get_count_widget(MDFloatingRootButton).md_bg_color = color
def set_pos_labels(self, instance_floating_label: MDFloatingLabel) -> None:
"""
Sets the position of the floating labels.
Called when the application's root window is resized.
"""
if self.anchor == "right":
instance_floating_label.x = (
Window.width - instance_floating_label.width - dp(86)
)
def set_pos_root_button(
self, instance_floating_root_button: MDFloatingRootButton
) -> None:
"""
Sets the position of the root button.
Called when the application's root window is resized.
"""
if self.anchor == "right":
instance_floating_root_button.y = dp(20)
instance_floating_root_button.x = Window.width - (dp(56) + dp(20))
def set_pos_bottom_buttons(
self, instance_floating_bottom_button: MDFloatingBottomButton
) -> None:
"""
Sets the position of the bottom buttons in a stack.
Called when the application's root window is resized.
"""
if self.anchor == "right":
if self.state != "open":
instance_floating_bottom_button.y = (
instance_floating_bottom_button.height / 2
)
instance_floating_bottom_button.x = Window.width - (
instance_floating_bottom_button.height
+ instance_floating_bottom_button.width / 2
)
def open_stack(
self, instance_floating_root_button: MDFloatingRootButton
) -> None:
"""Opens a button stack."""
for widget in self.children:
if isinstance(widget, MDFloatingLabel):
Animation.cancel_all(widget)
if self.state != "open":
y = 0
label_position = dp(56)
anim_buttons_data = {}
anim_labels_data = {}
for widget in self.children:
if isinstance(widget, MDFloatingBottomButton):
# Sets new button positions.
y += dp(56)
widget.y = widget.y * 2 + y
if not self._anim_buttons_data:
anim_buttons_data[widget] = Animation(
opacity=1,
d=self.opening_time,
t=self.opening_transition,
)
elif isinstance(widget, MDFloatingLabel):
# Sets new labels positions.
label_position += dp(56)
# Sets the position of signatures only once.
if not self._label_pos_y_set:
widget.y = widget.y * 2 + label_position
widget.x = Window.width - widget.width - dp(86)
if not self._anim_labels_data:
anim_labels_data[widget] = Animation(
opacity=1, d=self.opening_time
)
elif (
isinstance(widget, MDFloatingRootButton)
and self.root_button_anim
):
# Rotates the root button 45 degrees.
Animation(
_angle=-45,
d=self.opening_time_button_rotation,
t=self.opening_transition_button_rotation,
).start(widget)
if anim_buttons_data:
self._anim_buttons_data = anim_buttons_data
if anim_labels_data and not self.hint_animation:
self._anim_labels_data = anim_labels_data
self.state = "open"
self.dispatch("on_open")
self.do_animation_open_stack(self._anim_buttons_data)
self.do_animation_open_stack(self._anim_labels_data)
if not self._label_pos_y_set:
self._label_pos_y_set = True
else:
self.close_stack()
def do_animation_open_stack(self, anim_data: dict) -> None:
"""
:param anim_data:
{
<kivymd.uix.button.MDFloatingBottomButton object>:
<kivy.animation.Animation>,
<kivymd.uix.button.MDFloatingBottomButton object>:
<kivy.animation.Animation object>,
...,
}
"""
def on_progress(animation, widget, value):
if value >= 0.1:
animation_open_stack()
def animation_open_stack(*args):
try:
widget = next(widgets_list)
animation = anim_data[widget]
animation.bind(on_progress=on_progress)
animation.start(widget)
except StopIteration:
pass
widgets_list = iter(list(anim_data.keys()))
animation_open_stack()
def close_stack(self):
"""Closes the button stack."""
for widget in self.children:
if isinstance(widget, MDFloatingBottomButton):
Animation(
y=widget.height / 2,
d=self.closing_time,
t=self.closing_transition,
opacity=0,
).start(widget)
elif isinstance(widget, MDFloatingLabel):
Animation(opacity=0, d=0.1).start(widget)
elif (
isinstance(widget, MDFloatingRootButton)
and self.root_button_anim
):
Animation(
_angle=0,
d=self.closing_time_button_rotation,
t=self.closing_transition_button_rotation,
).start(widget)
self.state = "close"
self.dispatch("on_close")
def _update_pos_buttons(self, instance, width, height):
# Updates button positions when resizing screen.
for widget in self.children:
if isinstance(widget, MDFloatingBottomButton):
self.set_pos_bottom_buttons(widget)
elif isinstance(widget, MDFloatingRootButton):
self.set_pos_root_button(widget)
elif isinstance(widget, MDFloatingLabel):
self.set_pos_labels(widget)
def _get_count_widget(self, instance):
widget = None
for widget in self.children:
if isinstance(widget, instance):
break
return widget