mirror of
https://github.com/liberatedsystems/openCom-Companion.git
synced 2024-11-30 01:00:37 +01:00
1940 lines
58 KiB
Python
Executable File
1940 lines
58 KiB
Python
Executable File
"""
|
||
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``.
|
||
|
||
.. 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,
|
||
DeclarativeBehavior,
|
||
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(
|
||
DeclarativeBehavior,
|
||
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`.
|
||
"""
|
||
|
||
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 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'`.
|
||
"""
|
||
|
||
_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
|