globalforest/kivymd/uix/button.py
2020-10-14 23:38:48 +03:00

1843 lines
54 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
.. 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 = '''
Screen:
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.user_font_size` attribute to resize the button:
.. code-block:: kv
MDIconButton:
icon: "android"
user_font_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`:
.. code-block:: kv
MDIconButton:
icon: "android"
theme_text_color: "Custom"
text_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
The length of the shadow is controlled by the ``elevation_normal`` parameter:
.. code-block:: kv
MDFloatingActionButton:
icon: "android"
elevation_normal: 12
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-elevation-normal.png
: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"
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]"
markup: True
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"
.. warning:: You cannot use the ``size_hint_x`` parameter for `KivyMD` buttons
(the width of the buttons is set automatically)!
.. 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
Button parameters :class:`~MDRectangleFlatButton` are the same as
button :class:`~MDRaisedButton`:
.. code-block:: kv
MDRectangleFlatButton:
text: "MDRECTANGLEFLATBUTTON"
text_color: 0, 0, 1, 1
md_bg_color: 1, 1, 0, 1
.. note:: Note that the frame color will be the same as the text color.
.. 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:`~MDRectangleFlatButton` are the same as
button :class:`~MDRectangleFlatButton`:
.. code-block:: kv
MDRectangleFlatIconButton:
icon: "android"
text: "MDRECTANGLEFLATICONBUTTON"
Without border
--------------
.. code-block:: python
from kivy.uix.screenmanager import Screen
from kivymd.app import MDApp
from kivymd.uix.button import MDRectangleFlatIconButton
class Example(MDApp):
def build(self):
screen = Screen()
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
-----------------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-button.png
:align: center
Button parameters :class:`~MDRoundFlatButton` are the same as
button :class:`~MDRectangleFlatButton`:
.. code-block:: kv
MDRoundFlatButton:
text: "MDROUNDFLATBUTTON"
.. warning:: The border color does change when using ``text_color`` parameter.
.. 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`:
.. 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`.
.. 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 = '''
Screen:
MDFloatingActionButtonSpeedDial:
data: app.data
rotation_root_button: True
'''
class Example(MDApp):
data = {
'language-python': 'Python',
'language-php': 'PHP',
'language-cpp': 'C++',
}
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 kivy.uix.screenmanager import Screen
from kivymd.app import MDApp
from kivymd.uix.button import MDFloatingActionButtonSpeedDial
class Example(MDApp):
data = {
'language-python': 'Python',
'language-php': 'PHP',
'language-cpp': 'C++',
}
def build(self):
screen = Screen()
speed_dial = MDFloatingActionButtonSpeedDial()
speed_dial.data = self.data
speed_dial.rotation_root_button = 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__ = (
"MDIconButton",
"MDFloatingActionButton",
"MDFlatButton",
"MDRaisedButton",
"MDRectangleFlatButton",
"MDRectangleFlatIconButton",
"MDRoundFlatButton",
"MDRoundFlatIconButton",
"MDFillRoundFlatButton",
"MDFillRoundFlatIconButton",
"MDTextButton",
"MDFloatingActionButtonSpeedDial",
)
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.graphics.context_instructions import Color
from kivy.graphics.stencil_instructions import (
StencilPop,
StencilPush,
StencilUnUse,
StencilUse,
)
from kivy.graphics.vertex_instructions import Ellipse, RoundedRectangle
from kivy.lang import Builder
from kivy.metrics import dp, sp
from kivy.properties import (
BooleanProperty,
BoundedNumericProperty,
DictProperty,
ListProperty,
NumericProperty,
ObjectProperty,
OptionProperty,
StringProperty,
)
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.uix.widget import Widget
from kivymd import images_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import (
CircularElevationBehavior,
CircularRippleBehavior,
CommonElevationBehavior,
RectangularElevationBehavior,
RectangularRippleBehavior,
SpecificBackgroundColorBehavior,
)
from kivymd.uix.tooltip import MDTooltip
Builder.load_string(
"""
#:import images_path kivymd.images_path
#:import md_icons kivymd.icon_definitions.md_icons
<BaseButton>
size_hint: (None, None)
anchor_x: 'center'
anchor_y: 'center'
<BaseFlatButton>
<BaseRaisedButton>
<BaseRoundButton>
canvas:
Clear
Color:
rgba: self._current_button_color if root.icon in md_icons else (0, 0, 0, 0)
Ellipse:
size: self.size
pos: self.pos
source: self.source if hasattr(self, "source") else ""
size:
(dp(48), dp(48)) \
if not root.user_font_size \
else (dp(root.user_font_size + 23), dp(root.user_font_size + 23))
lbl_txt: lbl_txt
padding: (dp(12), dp(12), dp(12), dp(12)) if root.icon in md_icons else (0, 0, 0, 0)
MDIcon:
id: lbl_txt
icon: root.icon
font_size:
root.user_font_size \
if root.user_font_size \
else self.font_size
font_name: root.font_name if root.font_name else self.font_name
theme_text_color: root.theme_text_color
text_color: root.text_color
disabled: root.disabled
valign: 'middle'
halign: 'center'
opposite_colors: root.opposite_colors
<BaseRectangularButton>
canvas:
Clear
Color:
rgba: self._current_button_color
RoundedRectangle:
size: self.size
pos: self.pos
radius: (root._radius, )
lbl_txt: lbl_txt
height: dp(22) + sp(root.font_size)
width: lbl_txt.texture_size[0] + dp(24)
padding: (dp(8), 0) # For MDRectangleFlatIconButton
theme_text_color: 'Primary' if not root.text_color else 'Custom'
markup: False
MDLabel:
id: lbl_txt
text: root.text if root.button_label else ''
font_size: sp(root.font_size)
font_name: root.font_name if root.font_name else self.font_name
size_hint_x: None
text_size: (None, root.height)
height: self.texture_size[1]
theme_text_color: root.theme_text_color
text_color: root._current_text_color
markup: root.markup
disabled: root.disabled
valign: 'middle'
halign: 'center'
opposite_colors: root.opposite_colors
<MDRoundFlatButton>
canvas.before:
Color:
rgba:
(root.theme_cls.primary_color if not root.text_color else root.text_color) \
if not root.disabled else root.theme_cls.disabled_hint_text_color
Line:
width: root.line_width
rounded_rectangle:
(self.x, self.y, self.width, self.height,\
root._radius, root._radius, root._radius, root._radius,\
self.height)
theme_text_color: 'Custom'
text_color:
(root.theme_cls.primary_color if not root.text_color else root.text_color) \
if not root.disabled else root.theme_cls.disabled_hint_text_color
<MDFillRoundFlatButton>
canvas.before:
Color:
rgba:
(root.theme_cls.primary_color if root.md_bg_color == [0.0, 0.0, 0.0, 0.0] else root.md_bg_color) \
if not root.disabled else root.theme_cls.disabled_hint_text_color
RoundedRectangle:
size: self.size
pos: self.pos
radius: [root._radius, ]
<MDFillRoundFlatIconButton>
md_bg_color:
root.theme_cls.primary_color if root._current_button_color == [0.0, 0.0, 0.0, 0.0] \
else root._current_button_color
line_width: 0.001
<MDRectangleFlatButton>
canvas.before:
Color:
rgba:
root.theme_cls.primary_color if not root.text_color else root.text_color
Line:
width: root.line_width
rectangle: (self.x, self.y, self.width, self.height)
theme_text_color: 'Custom'
text_color: root.theme_cls.primary_color if not root.text_color else root.text_color
<MDRectangleFlatIconButton>
canvas.before:
Color:
rgba:
root.line_color if root.line_color else \
(root.theme_cls.primary_color if not root.text_color else root.text_color) \
if not root.disabled else root.theme_cls.disabled_hint_text_color
Line:
width: 1
rectangle: (self.x, self.y, self.width, self.height)
size_hint_x: None
width: lbl_txt.texture_size[0] + lbl_ic.texture_size[0] + box.spacing * 3
markup: False
BoxLayout:
id: box
spacing: dp(10)
MDIcon:
id: lbl_ic
icon: root.icon
theme_text_color: 'Custom'
text_color:
(root.theme_cls.primary_color if not root.text_color else root.text_color) \
if not root.disabled else root.theme_cls.disabled_hint_text_color
size_hint_x: None
width: self.texture_size[0]
Label:
id: lbl_txt
text: root.text
font_size: sp(root.font_size)
font_name: root.font_name if root.font_name else self.font_name
shorten: True
width: self.texture_size[0]
color:
(root.theme_cls.primary_color if not root.text_color else root.text_color) \
if not root.disabled else root.theme_cls.disabled_hint_text_color
markup: root.markup
<MDRoundFlatIconButton>
size_hint_x: None
width: lbl_txt.texture_size[0] + lbl_ic.texture_size[0] + box.spacing * 3
markup: False
BoxLayout:
id: box
spacing: dp(10)
MDIcon:
id: lbl_ic
icon: root.icon
theme_text_color: 'Custom'
text_color:
root.theme_cls.primary_color \
if not root.text_color else root.text_color
size_hint_x: None
width: self.texture_size[0]
Label:
id: lbl_txt
text: root.text
font_size: sp(root.font_size)
font_name: root.font_name if root.font_name else self.font_name
shorten: True
size_hint_x: None
width: self.texture_size[0]
color: root.theme_cls.primary_color if not root.text_color else root.text_color
markup: root.markup
<MDRaisedButton>
md_bg_color: root.theme_cls.primary_color
theme_text_color: 'Custom'
text_color: root.specific_text_color
<MDFloatingActionButton>
# Defaults to 56-by-56 and a background of the accent color according to
# guidelines
size: (dp(56), dp(56))
theme_text_color: 'Custom'
<MDTextButton>
size_hint: None, None
size: self.texture_size
color:
root.theme_cls.primary_color \
if not len(root.custom_color) else root.custom_color
background_down: f'{images_path}transparent.png'
background_normal: f'{images_path}transparent.png'
opacity: 1
# SpeedDial classes
<BaseFloatingBottomButton>
size_hint: None, None
size: dp(46), dp(46)
theme_text_color: "Custom"
md_bg_color: self.theme_cls.primary_color
canvas.before:
Color:
rgba:
self.theme_cls.primary_color \
if not self._bg_color else self._bg_color
RoundedRectangle:
pos:
(self.x - self._canvas_width + dp(1.5)) + self._padding_right / 2, \
self.y - self._padding_right / 2 + dp(1.5)
size:
self.width + self._canvas_width - dp(3), \
self.height + self._padding_right - dp(3)
radius: [self.height / 2]
<BaseFloatingRootButton>
theme_text_color: "Custom"
md_bg_color: self.theme_cls.primary_color
canvas.before:
PushMatrix
Rotate:
angle: self._angle
axis: (0, 0, 1)
origin: self.center
canvas.after:
PopMatrix
<BaseFloatingLabel>
size_hint: None, None
padding: "8dp", "4dp", "8dp", "4dp"
height: label.texture_size[1] + self.padding[1] * 2
width: label.texture_size[0] + self.padding[0] * 2
elevation: 10
canvas:
Color:
rgba: self.theme_cls.primary_color if not root.bg_color else root.bg_color
RoundedRectangle:
pos: self.pos
size: self.size
radius: [5]
Label:
id: label
markup: True
text: root.text
size_hint: None, None
size: self.texture_size
color: root.theme_cls.text_color if not root.text_color else root.text_color
"""
)
class BaseButton(
ThemableBehavior,
ButtonBehavior,
SpecificBackgroundColorBehavior,
AnchorLayout,
Widget,
):
"""
Abstract base class for all MD buttons. This class handles the button's
colors (disabled/down colors handled in children classes as those depend on
type of button) as well as the disabled state.
"""
theme_text_color = OptionProperty(
"Primary",
options=[
"Primary",
"Secondary",
"Hint",
"Error",
"Custom",
"ContrastParentBackground",
],
)
"""
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 `'Primary'`.
"""
text_color = ListProperty()
"""
Text color in ``rgba`` format.
:attr:`text_color` is an :class:`~kivy.properties.ListProperty`
and defaults to `''`.
"""
font_name = StringProperty()
"""
Font name.
:attr:`font_name` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
font_size = NumericProperty(14)
"""
Font size.
:attr:`font_size` is an :class:`~kivy.properties.NumericProperty`
and defaults to `14`.
"""
user_font_size = NumericProperty()
"""Custom font size for :class:`~MDIconButton`.
:attr:`user_font_size` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0`.
"""
md_bg_color_disabled = ListProperty()
"""Color disabled.
:attr:`md_bg_color_disabled` is an :class:`~kivy.properties.ListProperty`
and defaults to ``.
"""
line_width = NumericProperty(1)
opposite_colors = BooleanProperty(False)
_current_button_color = ListProperty([0.0, 0.0, 0.0, 0.0])
_current_text_color = ListProperty([1.0, 1.0, 1.0, 1])
_md_bg_color_down = ListProperty([0.0, 0.0, 0.0, 0.1])
_md_bg_color_disabled = ListProperty([0.0, 0.0, 0.0, 0.0])
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.theme_cls.bind(primary_palette=self.update_md_bg_color)
Clock.schedule_once(self.check_current_button_color)
def update_md_bg_color(self, instance, value):
"""Called when the application color palette changes."""
def check_current_button_color(self, interval):
if self.md_bg_color_disabled:
self._md_bg_color_disabled = self.md_bg_color_disabled
else:
self._md_bg_color_disabled = self.theme_cls.disabled_hint_text_color
self.on_disabled(self, self.disabled)
if self._current_button_color == [0.0, 0.0, 0.0, 0.0]:
self._current_button_color = self.md_bg_color
def on_text_color(self, instance, value):
if value not in ([0, 0, 0, 0.87], [1.0, 1.0, 1.0, 1]):
self._current_text_color = value
def on_md_bg_color(self, instance, value):
if value != self.theme_cls.primary_color:
self._current_button_color = value
def on_disabled(self, instance, value):
if self.disabled:
self._current_button_color = self._md_bg_color_disabled
else:
self._current_button_color = self.md_bg_color
def on_font_size(self, instance, value):
def _on_font_size(interval):
if "lbl_ic" in instance.ids:
instance.ids.lbl_ic.font_size = sp(value)
Clock.schedule_once(_on_font_size)
class BasePressedButton(BaseButton):
"""
Abstract base class for those button which fade to a background color on
press.
"""
_fade_bg = None
def on_touch_down(self, touch):
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:
# Button dimming animation.
if self.md_bg_color == [0.0, 0.0, 0.0, 0.0]:
self._fade_bg = Animation(
duration=0.5, _current_button_color=self._md_bg_color_down
)
self._fade_bg.start(self)
return super().on_touch_down(touch)
def on_touch_up(self, touch):
if touch.grab_current is self and self._fade_bg:
self._fade_bg.stop_property(self, "_current_button_color")
Animation(
duration=0.05, _current_button_color=self.md_bg_color
).start(self)
return super().on_touch_up(touch)
class BaseFlatButton(BaseButton):
"""
Abstract base class for flat buttons which do not elevate from material.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.md_bg_color = (0.0, 0.0, 0.0, 0.0)
class BaseRaisedButton(CommonElevationBehavior, BaseButton):
"""
Abstract base class for raised buttons which elevate from material.
Raised buttons are to be used sparingly to emphasise primary/important
actions.
Implements elevation behavior as well as the recommended down/disabled
colors for raised buttons.
"""
_elevation_normal = NumericProperty(0)
_elevation_raised = NumericProperty(0)
_anim_raised = None
def update_md_bg_color(self, instance, value):
"""Called when the application color palette changes."""
self._current_button_color = self.theme_cls._get_primary_color()
def on_elevation(self, instance, value):
self._elevation_normal = self.elevation
self._elevation_raised = self.elevation
self._anim_raised = Animation(_elevation=value + 2, d=0.2)
self._anim_raised.bind(on_progress=self._do_anim_raised)
self._update_elevation(instance, value)
def on_disabled(self, instance, value):
if self.disabled:
self._elevation = 0
self._update_shadow(instance, 0)
else:
self._update_elevation(instance, self._elevation_normal)
super().on_disabled(instance, 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:
return super().on_touch_up(touch)
Animation.cancel_all(self, "_elevation")
self._elevation = self._elevation_raised
self._elevation_normal = self._elevation_raised
self._update_shadow(self, self._elevation)
return super().on_touch_up(touch)
def _do_anim_raised(self, animation, instance, value):
self._elevation += value
if self._elevation < self._elevation_raised + 2:
self._update_shadow(instance, self._elevation)
class BaseRoundButton(CircularRippleBehavior, BaseButton):
"""
Abstract base class for all round buttons, bringing in the appropriate
on-touch behavior
"""
class BaseRectangularButton(RectangularRippleBehavior, BaseButton):
"""
Abstract base class for all rectangular buttons, bringing in the
appropriate on-touch behavior. Also maintains the correct minimum width
as stated in guidelines.
"""
width = BoundedNumericProperty(
88, min=88, max=None, errorhandler=lambda x: 88
)
text = StringProperty("")
"""Button text.
:attr:`text` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
button_label = BooleanProperty(True)
"""
If ``False`` the text on the button will not be displayed.
:attr:`button_label` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `True`.
"""
_radius = NumericProperty("2dp")
_height = NumericProperty(0)
class MDIconButton(BaseRoundButton, BaseFlatButton, BasePressedButton):
icon = StringProperty("checkbox-blank-circle")
"""
Button icon.
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
and defaults to `'checkbox-blank-circle'`.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.md_bg_color_disabled = (0, 0, 0, 0)
class MDFlatButton(BaseRectangularButton, BaseFlatButton, BasePressedButton):
pass
class BaseFlatIconButton(MDFlatButton):
icon = StringProperty("android")
"""
Button icon.
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
and defaults to `'android'`.
"""
text = StringProperty("")
"""Button text.
:attr:`text` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
button_label = BooleanProperty(False)
def update_md_bg_color(self, instance, value):
self.text_color = self.theme_cls._get_primary_color()
class MDRaisedButton(
BaseRectangularButton,
RectangularElevationBehavior,
BaseRaisedButton,
BasePressedButton,
):
pass
class MDFloatingActionButton(
BaseRoundButton, CircularElevationBehavior, BaseRaisedButton
):
icon = StringProperty("android")
"""
Button icon.
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
and defaults to `'android'`.
"""
background_palette = StringProperty("Accent")
"""
The name of the palette used for the background color of the button.
:attr:`background_palette` is an :class:`~kivy.properties.StringProperty`
and defaults to `'Accent'`.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
if self.md_bg_color == [1.0, 1.0, 1.0, 0.0]:
self.md_bg_color = self.theme_cls.accent_color
def on_md_bg_color(self, instance, value):
if value != self.theme_cls.accent_color:
self._current_button_color = value
class MDRoundImageButton(MDFloatingActionButton):
source = StringProperty()
"""Path to button image.
:attr:`source` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
_current_button_color = [1, 1, 1, 1]
def on_source(self, instance, value):
self.source = value
def on_size(self, instance, value):
self.remove_widget(self.ids.lbl_txt)
class MDRectangleFlatButton(MDFlatButton):
def update_md_bg_color(self, instance, value):
self.text_color = self.theme_cls._get_primary_color()
def on_disabled(self, instance, value):
if self.disabled:
self.line_width = 0.001
self._current_button_color = (
self.theme_cls.disabled_hint_text_color
if not self.md_bg_color_disabled
else self.md_bg_color_disabled
)
else:
self._current_button_color = self.md_bg_color
self.line_width = 1
class MDRoundFlatButton(MDFlatButton):
_radius = NumericProperty("18dp")
def update_md_bg_color(self, instance, value):
self.text_color = self.theme_cls._get_primary_color()
def lay_canvas_instructions(self):
with self.canvas.after:
StencilPush()
RoundedRectangle(
size=self.size, pos=self.pos, radius=[self._radius]
)
StencilUse()
self.col_instruction = Color(rgba=self.ripple_color)
self.ellipse = Ellipse(
size=(self._ripple_rad, self._ripple_rad),
pos=(
self.ripple_pos[0] - self._ripple_rad / 2.0,
self.ripple_pos[1] - self._ripple_rad / 2.0,
),
)
StencilUnUse()
RoundedRectangle(
size=self.size, pos=self.pos, radius=[self._radius]
)
StencilPop()
self.bind(ripple_color=self._set_color, _ripple_rad=self._set_ellipse)
class MDTextButton(ThemableBehavior, Button):
custom_color = ListProperty()
"""Custom user button color in ``rgba`` format.
:attr:`custom_color` is an :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
def animation_label(self):
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, value):
if value:
self.disabled_color = self.theme_cls.disabled_hint_text_color
self.background_disabled_normal = f"{images_path}transparent.png"
class MDCustomRoundIconButton(CircularRippleBehavior, ButtonBehavior, Image):
pass
class MDFillRoundFlatButton(CircularElevationBehavior, MDRoundFlatButton):
_elevation_normal = NumericProperty()
def __init__(self, **kwargs):
self.text_color = (1, 1, 1, 1)
self.line_width = 0.001
super().__init__(**kwargs)
def update_md_bg_color(self, instance, value):
self.text_color = self.text_color
self.md_bg_color = self.theme_cls._get_primary_color()
def on_md_bg_color(self, instance, value):
if value != [0.0, 0.0, 0.0, 0.0]:
self._current_button_color = value
def on_elevation(self, instance, value):
if value:
self._elevation_normal = value
def on_disabled(self, instance, value):
# FIXME:The elevation parameter is not restored.
'''
from kivy.lang import Builder
from kivymd.app import MDApp
root_kv = """
Screen:
MDFillRoundFlatButton:
id: btn
text: "Click me!"
pos_hint: {"center_x": .5, "center_y": .6}
elevation: 8
on_press: self.disabled = True
MDFillRoundFlatButton:
text: "UNDISABLED"
pos_hint: {"center_x": .5, "center_y": .4}
on_press: btn.disabled = False
"""
class MainApp(MDApp):
def build(self):
self.root = Builder.load_string(root_kv)
MainApp().run()
'''
if self.disabled:
self.elevation = 0
self._update_shadow(instance, 0)
else:
self.elevation = self._elevation_normal
self._update_elevation(instance, self.elevation)
super().on_disabled(instance, value)
class MDRectangleFlatIconButton(BaseFlatIconButton):
line_color = ListProperty()
"""Button border color in ``rgba`` format.
:attr:`line_color` is an :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
class MDRoundFlatIconButton(MDRoundFlatButton, BaseFlatIconButton):
pass
class MDFillRoundFlatIconButton(MDRoundFlatIconButton):
text_color = ListProperty((1, 1, 1, 1))
def on_md_bg_color(self, instance, value):
if value != [0.0, 0.0, 0.0, 0.0]:
self._current_button_color = value
def update_md_bg_color(self, instance, value):
self._current_button_color = self.theme_cls.primary_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 = ListProperty()
class BaseFloatingLabel(
ThemableBehavior, RectangularElevationBehavior, BoxLayout
):
text = StringProperty()
text_color = ListProperty()
bg_color = ListProperty()
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 = ListProperty([0, 0, 0, 1])
"""
Floating text color in ``rgba`` format.
:attr:`label_text_color` is a :class:`~kivy.properties.ListProperty`
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 piesels
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`.
"""
rotation_root_button = BooleanProperty(False)
"""
If ``True`` then the root button will rotate 45 degrees when the stack
is opened.
:attr:`rotation_root_button` 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 = ListProperty()
"""
Root button color in ``rgba`` format.
:attr:`bg_color_root_button` is a :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
bg_color_stack_button = ListProperty()
"""
The color of the buttons in the stack ``rgba`` format.
:attr:`bg_color_stack_button` is a :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
color_icon_stack_button = ListProperty()
"""
The color icon of the buttons in the stack ``rgba`` format.
:attr:`color_icon_stack_button` is a :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
color_icon_root_button = ListProperty()
"""
The color icon of the root button ``rgba`` format.
:attr:`color_icon_root_button` is a :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
bg_hint_color = ListProperty()
"""
Background color for the text of the buttons in the stack ``rgba`` format.
:attr:`bg_hint_color` is a :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
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):
"""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)
if self.data[instance.icon] == widget.text:
Animation(
_canvas_width=0,
_padding_right=0,
d=self.opening_time,
t=self.opening_transition,
).start(instance)
if self.hint_animation:
Animation(
opacity=0, d=0.1, t=self.opening_transition
).start(widget)
break
def on_enter(self, instance):
"""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
if self.data[instance.icon] == widget.text:
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)
if self.hint_animation:
Animation(
opacity=1,
d=self.opening_time,
t=self.opening_transition,
).start(widget)
break
def on_data(self, instance, value):
"""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_icon in value.keys():
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 = value[name_icon]
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, value):
self._get_count_widget(MDFloatingRootButton).icon = value
def on_label_text_color(self, instance, value):
for widget in self.children:
if isinstance(widget, MDFloatingLabel):
widget.text_color = value
def on_color_icon_stack_button(self, instance, value):
for widget in self.children:
if isinstance(widget, MDFloatingBottomButton):
widget.text_color = value
def on_hint_animation(self, instance, value):
for widget in self.children:
if isinstance(widget, MDFloatingLabel):
widget.bg_color = (0, 0, 0, 0)
def on_bg_hint_color(self, instance, value):
for widget in self.children:
if isinstance(widget, MDFloatingBottomButton):
widget._bg_color = value
def on_color_icon_root_button(self, instance, value):
self._get_count_widget(MDFloatingRootButton).text_color = value
def on_bg_color_stack_button(self, instance, value):
for widget in self.children:
if isinstance(widget, MDFloatingBottomButton):
widget.md_bg_color = value
def on_bg_color_root_button(self, instance, value):
self._get_count_widget(MDFloatingRootButton).md_bg_color = value
def set_pos_labels(self, widget):
"""Sets the position of the floating labels."""
if self.anchor == "right":
widget.x = Window.width - widget.width - dp(86)
def set_pos_root_button(self, instance):
"""Sets the position of the root button."""
if self.anchor == "right":
instance.y = dp(20)
instance.x = Window.width - (dp(56) + dp(20))
def set_pos_bottom_buttons(self, instance):
"""Sets the position of the bottom buttons in a stack."""
if self.anchor == "right":
if self.state != "open":
instance.y = instance.height / 2
instance.x = Window.width - (instance.height + instance.width / 2)
def open_stack(self, instance):
"""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.rotation_root_button
):
# 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):
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.rotation_root_button
):
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