858 lines
26 KiB
Python
858 lines
26 KiB
Python
|
"""
|
||
|
Components/TapTargetView
|
||
|
========================
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
`TapTargetView, GitHub <https://github.com/KeepSafe/TapTargetView>`_
|
||
|
|
||
|
`TapTargetView, Material archive <https://material.io/archive/guidelines/growth-communications/feature-discovery.html#>`_
|
||
|
|
||
|
.. rubric:: Provide value and improve engagement by introducing users to new
|
||
|
features and functionality at relevant moments.
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-previous.gif
|
||
|
:align: center
|
||
|
|
||
|
Usage
|
||
|
-----
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
from kivy.lang import Builder
|
||
|
|
||
|
from kivymd.app import MDApp
|
||
|
from kivymd.uix.taptargetview import MDTapTargetView
|
||
|
|
||
|
KV = '''
|
||
|
Screen:
|
||
|
|
||
|
MDFloatingActionButton:
|
||
|
id: button
|
||
|
icon: "plus"
|
||
|
pos: 10, 10
|
||
|
on_release: app.tap_target_start()
|
||
|
'''
|
||
|
|
||
|
|
||
|
class TapTargetViewDemo(MDApp):
|
||
|
def build(self):
|
||
|
screen = Builder.load_string(KV)
|
||
|
self.tap_target_view = MDTapTargetView(
|
||
|
widget=screen.ids.button,
|
||
|
title_text="This is an add button",
|
||
|
description_text="This is a description of the button",
|
||
|
widget_position="left_bottom",
|
||
|
)
|
||
|
|
||
|
return screen
|
||
|
|
||
|
def tap_target_start(self):
|
||
|
if self.tap_target_view.state == "close":
|
||
|
self.tap_target_view.start()
|
||
|
else:
|
||
|
self.tap_target_view.stop()
|
||
|
|
||
|
|
||
|
TapTargetViewDemo().run()
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-usage.gif
|
||
|
:align: center
|
||
|
|
||
|
Widget position
|
||
|
---------------
|
||
|
|
||
|
Sets the position of the widget relative to the floating circle.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
self.tap_target_view = MDTapTargetView(
|
||
|
...
|
||
|
widget_position="right",
|
||
|
)
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-right.png
|
||
|
:align: center
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
self.tap_target_view = MDTapTargetView(
|
||
|
...
|
||
|
widget_position="left",
|
||
|
)
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-left.png
|
||
|
:align: center
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
self.tap_target_view = MDTapTargetView(
|
||
|
...
|
||
|
widget_position="top",
|
||
|
)
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-top.png
|
||
|
:align: center
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
self.tap_target_view = MDTapTargetView(
|
||
|
...
|
||
|
widget_position="bottom",
|
||
|
)
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-bottom.png
|
||
|
:align: center
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
self.tap_target_view = MDTapTargetView(
|
||
|
...
|
||
|
widget_position="left_top",
|
||
|
)
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-left_top.png
|
||
|
:align: center
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
self.tap_target_view = MDTapTargetView(
|
||
|
...
|
||
|
widget_position="right_top",
|
||
|
)
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-right_top.png
|
||
|
:align: center
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
self.tap_target_view = MDTapTargetView(
|
||
|
...
|
||
|
widget_position="left_bottom",
|
||
|
)
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-left_bottom.png
|
||
|
:align: center
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
self.tap_target_view = MDTapTargetView(
|
||
|
...
|
||
|
widget_position="right_bottom",
|
||
|
)
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-right_bottom.png
|
||
|
:align: center
|
||
|
|
||
|
If you use ``the widget_position = "center"`` parameter then you must
|
||
|
definitely specify the :attr:`~MDTapTargetView.title_position`.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
self.tap_target_view = MDTapTargetView(
|
||
|
...
|
||
|
widget_position="center",
|
||
|
title_position="left_top",
|
||
|
)
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-center.png
|
||
|
:align: center
|
||
|
|
||
|
Text options
|
||
|
------------
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
self.tap_target_view = MDTapTargetView(
|
||
|
...
|
||
|
title_text="Title text",
|
||
|
description_text="Description text",
|
||
|
)
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-text.png
|
||
|
:align: center
|
||
|
|
||
|
|
||
|
You can use the following options to control font size, color, and boldness:
|
||
|
|
||
|
- :attr:`~MDTapTargetView.title_text_size`
|
||
|
- :attr:`~MDTapTargetView.title_text_color`
|
||
|
- :attr:`~MDTapTargetView.title_text_bold`
|
||
|
- :attr:`~MDTapTargetView.description_text_size`
|
||
|
- :attr:`~MDTapTargetView.description_text_color`
|
||
|
- :attr:`~MDTapTargetView.description_text_bold`
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
self.tap_target_view = MDTapTargetView(
|
||
|
...
|
||
|
title_text="Title text",
|
||
|
title_text_size="36sp",
|
||
|
description_text="Description text",
|
||
|
description_text_color=[1, 0, 0, 1]
|
||
|
)
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-text-option.png
|
||
|
:align: center
|
||
|
|
||
|
But you can also use markup to set these values.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
self.tap_target_view = MDTapTargetView(
|
||
|
...
|
||
|
title_text="[size=36]Title text[/size]",
|
||
|
description_text="[color=#ff0000ff]Description text[/color]",
|
||
|
)
|
||
|
|
||
|
Events control
|
||
|
--------------
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
self.tap_target_view.bind(on_open=self.on_open, on_close=self.on_close)
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
def on_open(self, instance_tap_target_view):
|
||
|
'''Called at the time of the start of the widget opening animation.'''
|
||
|
|
||
|
print("Open", instance_tap_target_view)
|
||
|
|
||
|
def on_close(self, instance_tap_target_view):
|
||
|
'''Called at the time of the start of the widget closed animation.'''
|
||
|
|
||
|
print("Close", instance_tap_target_view)
|
||
|
|
||
|
.. Note:: See other parameters in the :class:`~MDTapTargetView` class.
|
||
|
"""
|
||
|
|
||
|
from kivy.animation import Animation
|
||
|
from kivy.event import EventDispatcher
|
||
|
from kivy.graphics import Color, Ellipse, Rectangle
|
||
|
from kivy.logger import Logger
|
||
|
from kivy.metrics import dp
|
||
|
from kivy.properties import (
|
||
|
BooleanProperty,
|
||
|
ListProperty,
|
||
|
NumericProperty,
|
||
|
ObjectProperty,
|
||
|
OptionProperty,
|
||
|
StringProperty,
|
||
|
)
|
||
|
from kivy.uix.label import Label
|
||
|
|
||
|
from kivymd.theming import ThemableBehavior
|
||
|
|
||
|
|
||
|
class MDTapTargetView(ThemableBehavior, EventDispatcher):
|
||
|
"""Rough try to mimic the working of Android's TapTargetView.
|
||
|
|
||
|
:Events:
|
||
|
:attr:`on_open`
|
||
|
Called at the time of the start of the widget opening animation.
|
||
|
:attr:`on_close`
|
||
|
Called at the time of the start of the widget closed animation.
|
||
|
"""
|
||
|
|
||
|
widget = ObjectProperty()
|
||
|
"""
|
||
|
Widget to add ``TapTargetView`` upon.
|
||
|
|
||
|
:attr:`widget` is an :class:`~kivy.properties.ObjectProperty`
|
||
|
and defaults to `None`.
|
||
|
"""
|
||
|
|
||
|
outer_radius = NumericProperty(dp(200))
|
||
|
"""
|
||
|
Radius for outer circle.
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-outer-radius.png
|
||
|
:align: center
|
||
|
|
||
|
:attr:`outer_radius` is an :class:`~kivy.properties.NumericProperty`
|
||
|
and defaults to `dp(200)`.
|
||
|
"""
|
||
|
|
||
|
outer_circle_color = ListProperty()
|
||
|
"""
|
||
|
Color for the outer circle in ``rgb`` format.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
self.tap_target_view = MDTapTargetView(
|
||
|
...
|
||
|
outer_circle_color=(1, 0, 0)
|
||
|
)
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-outer-circle-color.png
|
||
|
:align: center
|
||
|
|
||
|
:attr:`outer_circle_color` is an :class:`~kivy.properties.ListProperty`
|
||
|
and defaults to ``theme_cls.primary_color``.
|
||
|
"""
|
||
|
|
||
|
outer_circle_alpha = NumericProperty(0.96)
|
||
|
"""
|
||
|
Alpha value for outer circle.
|
||
|
|
||
|
:attr:`outer_circle_alpha` is an :class:`~kivy.properties.NumericProperty`
|
||
|
and defaults to `0.96`.
|
||
|
"""
|
||
|
|
||
|
target_radius = NumericProperty(dp(45))
|
||
|
"""
|
||
|
Radius for target circle.
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-target-radius.png
|
||
|
:align: center
|
||
|
|
||
|
:attr:`target_radius` is an :class:`~kivy.properties.NumericProperty`
|
||
|
and defaults to `dp(45)`.
|
||
|
"""
|
||
|
|
||
|
target_circle_color = ListProperty([1, 1, 1])
|
||
|
"""
|
||
|
Color for target circle in ``rgb`` format.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
self.tap_target_view = MDTapTargetView(
|
||
|
...
|
||
|
target_circle_color=(1, 0, 0)
|
||
|
)
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-target-circle-color.png
|
||
|
:align: center
|
||
|
|
||
|
:attr:`target_circle_color` is an :class:`~kivy.properties.ListProperty`
|
||
|
and defaults to `[1, 1, 1]`.
|
||
|
"""
|
||
|
|
||
|
title_text = StringProperty()
|
||
|
"""
|
||
|
Title to be shown on the view.
|
||
|
|
||
|
:attr:`title_text` is an :class:`~kivy.properties.StringProperty`
|
||
|
and defaults to `''`.
|
||
|
"""
|
||
|
|
||
|
title_text_size = NumericProperty(dp(25))
|
||
|
"""
|
||
|
Text size for title.
|
||
|
|
||
|
:attr:`title_text_size` is an :class:`~kivy.properties.NumericProperty`
|
||
|
and defaults to `dp(25)`.
|
||
|
"""
|
||
|
|
||
|
title_text_color = ListProperty([1, 1, 1, 1])
|
||
|
"""
|
||
|
Text color for title.
|
||
|
|
||
|
:attr:`title_text_color` is an :class:`~kivy.properties.ListProperty`
|
||
|
and defaults to `[1, 1, 1, 1]`.
|
||
|
"""
|
||
|
|
||
|
title_text_bold = BooleanProperty(True)
|
||
|
"""
|
||
|
Whether title should be bold.
|
||
|
|
||
|
:attr:`title_text_bold` is an :class:`~kivy.properties.BooleanProperty`
|
||
|
and defaults to `True`.
|
||
|
"""
|
||
|
|
||
|
description_text = StringProperty()
|
||
|
"""
|
||
|
Description to be shown below the title (keep it short).
|
||
|
|
||
|
:attr:`description_text` is an :class:`~kivy.properties.StringProperty`
|
||
|
and defaults to `''`.
|
||
|
"""
|
||
|
|
||
|
description_text_size = NumericProperty(dp(20))
|
||
|
"""
|
||
|
Text size for description text.
|
||
|
|
||
|
:attr:`description_text_size` is an :class:`~kivy.properties.NumericProperty`
|
||
|
and defaults to `dp(20)`.
|
||
|
"""
|
||
|
|
||
|
description_text_color = ListProperty([0.9, 0.9, 0.9, 1])
|
||
|
"""
|
||
|
Text size for description text.
|
||
|
|
||
|
:attr:`description_text_color` is an :class:`~kivy.properties.ListProperty`
|
||
|
and defaults to `[0.9, 0.9, 0.9, 1]`.
|
||
|
"""
|
||
|
|
||
|
description_text_bold = BooleanProperty(False)
|
||
|
"""
|
||
|
Whether description should be bold.
|
||
|
|
||
|
:attr:`description_text_bold` is an :class:`~kivy.properties.BooleanProperty`
|
||
|
and defaults to `False`.
|
||
|
"""
|
||
|
|
||
|
draw_shadow = BooleanProperty(False)
|
||
|
"""
|
||
|
Whether to show shadow.
|
||
|
|
||
|
:attr:`draw_shadow` is an :class:`~kivy.properties.BooleanProperty`
|
||
|
and defaults to `False`.
|
||
|
"""
|
||
|
|
||
|
cancelable = BooleanProperty(False)
|
||
|
"""
|
||
|
Whether clicking outside the outer circle dismisses the view.
|
||
|
|
||
|
:attr:`cancelable` is an :class:`~kivy.properties.BooleanProperty`
|
||
|
and defaults to `False`.
|
||
|
"""
|
||
|
|
||
|
widget_position = OptionProperty(
|
||
|
"left",
|
||
|
options=[
|
||
|
"left",
|
||
|
"right",
|
||
|
"top",
|
||
|
"bottom",
|
||
|
"left_top",
|
||
|
"right_top",
|
||
|
"left_bottom",
|
||
|
"right_bottom",
|
||
|
"center",
|
||
|
],
|
||
|
)
|
||
|
"""
|
||
|
Sets the position of the widget on the :attr:`~outer_circle`. Available options are
|
||
|
`'left`', `'right`', `'top`', `'bottom`', `'left_top`', `'right_top`',
|
||
|
`'left_bottom`', `'right_bottom`', `'center`'.
|
||
|
|
||
|
:attr:`widget_position` is an :class:`~kivy.properties.OptionProperty`
|
||
|
and defaults to `'left'`.
|
||
|
"""
|
||
|
|
||
|
title_position = OptionProperty(
|
||
|
"auto",
|
||
|
options=[
|
||
|
"auto",
|
||
|
"left",
|
||
|
"right",
|
||
|
"top",
|
||
|
"bottom",
|
||
|
"left_top",
|
||
|
"right_top",
|
||
|
"left_bottom",
|
||
|
"right_bottom",
|
||
|
],
|
||
|
)
|
||
|
"""
|
||
|
Sets the position of :attr`~title_text` on the outer circle. Only works if
|
||
|
:attr`~widget_position` is set to `'center'`. In all other cases, it
|
||
|
calculates the :attr`~title_position` itself.
|
||
|
Must be set to other than `'auto`' when :attr`~widget_position` is set
|
||
|
to `'center`'.
|
||
|
|
||
|
Available options are `'auto'`, `'left`', `'right`', `'top`', `'bottom`',
|
||
|
`'left_top`', `'right_top`', `'left_bottom`', `'right_bottom`', `'center`'.
|
||
|
|
||
|
:attr:`title_position` is an :class:`~kivy.properties.OptionProperty`
|
||
|
and defaults to `'auto'`.
|
||
|
"""
|
||
|
|
||
|
stop_on_outer_touch = BooleanProperty(False)
|
||
|
"""
|
||
|
Whether clicking on outer circle stops the animation.
|
||
|
|
||
|
:attr:`stop_on_outer_touch` is an :class:`~kivy.properties.BooleanProperty`
|
||
|
and defaults to `False`.
|
||
|
"""
|
||
|
|
||
|
stop_on_target_touch = BooleanProperty(True)
|
||
|
"""
|
||
|
Whether clicking on target circle should stop the animation.
|
||
|
|
||
|
:attr:`stop_on_target_touch` is an :class:`~kivy.properties.BooleanProperty`
|
||
|
and defaults to `True`.
|
||
|
"""
|
||
|
|
||
|
state = OptionProperty("close", options=["close", "open"])
|
||
|
"""
|
||
|
State of :class:`~MDTapTargetView`.
|
||
|
|
||
|
:attr:`state` is an :class:`~kivy.properties.OptionProperty`
|
||
|
and defaults to `'close'`.
|
||
|
"""
|
||
|
|
||
|
_outer_radius = NumericProperty(0)
|
||
|
_target_radius = NumericProperty(0)
|
||
|
|
||
|
__elevation = 0
|
||
|
|
||
|
def __init__(self, **kwargs):
|
||
|
self.ripple_max_dist = dp(90)
|
||
|
self.on_outer_radius(self, self.outer_radius)
|
||
|
self.on_target_radius(self, self.target_radius)
|
||
|
self.anim_ripple = None
|
||
|
|
||
|
self.core_title_text = Label(
|
||
|
markup=True, size_hint=(None, None), bold=self.title_text_bold
|
||
|
)
|
||
|
self.core_title_text.bind(
|
||
|
texture_size=self.core_title_text.setter("size")
|
||
|
)
|
||
|
self.core_description_text = Label(markup=True, size_hint=(None, None))
|
||
|
self.core_description_text.bind(
|
||
|
texture_size=self.core_description_text.setter("size")
|
||
|
)
|
||
|
|
||
|
super().__init__(**kwargs)
|
||
|
self.register_event_type("on_outer_touch")
|
||
|
self.register_event_type("on_target_touch")
|
||
|
self.register_event_type("on_outside_click")
|
||
|
self.register_event_type("on_open")
|
||
|
self.register_event_type("on_close")
|
||
|
|
||
|
if not self.outer_circle_color:
|
||
|
self.outer_circle_color = self.theme_cls.primary_color[:-1]
|
||
|
|
||
|
def start(self, *args):
|
||
|
"""Starts widget opening animation."""
|
||
|
|
||
|
self._initialize()
|
||
|
self._animate_outer()
|
||
|
self.state = "open"
|
||
|
self.core_title_text.opacity = 1
|
||
|
self.core_description_text.opacity = 1
|
||
|
self.dispatch("on_open")
|
||
|
|
||
|
elevation = getattr(self.widget, "elevation", None)
|
||
|
if elevation:
|
||
|
self.__elevation = elevation
|
||
|
self.widget.elevation = 0
|
||
|
|
||
|
def stop(self, *args):
|
||
|
"""Starts widget close animation."""
|
||
|
|
||
|
# It needs a better implementation.
|
||
|
if self.anim_ripple is not None:
|
||
|
self.anim_ripple.unbind(on_complete=self._repeat_ripple)
|
||
|
self.core_title_text.opacity = 0
|
||
|
self.core_description_text.opacity = 0
|
||
|
anim = Animation(
|
||
|
d=0.15,
|
||
|
t="in_cubic",
|
||
|
**dict(
|
||
|
zip(
|
||
|
["_outer_radius", "_target_radius", "target_ripple_radius"],
|
||
|
[0, 0, 0],
|
||
|
)
|
||
|
),
|
||
|
)
|
||
|
anim.bind(on_complete=self._after_stop)
|
||
|
anim.start(self.widget)
|
||
|
|
||
|
def on_open(self, *args):
|
||
|
"""Called at the time of the start of the widget opening animation."""
|
||
|
|
||
|
def on_close(self, *args):
|
||
|
"""Called at the time of the start of the widget closed animation."""
|
||
|
|
||
|
def on_draw_shadow(self, instance, value):
|
||
|
Logger.warning(
|
||
|
"The shadow adding method will be implemented in future versions"
|
||
|
)
|
||
|
|
||
|
def on_description_text(self, instance, value):
|
||
|
self.core_description_text.text = value
|
||
|
|
||
|
def on_description_text_size(self, instance, value):
|
||
|
self.core_description_text.font_size = value
|
||
|
|
||
|
def on_description_text_bold(self, instance, value):
|
||
|
self.core_description_text.bold = value
|
||
|
|
||
|
def on_title_text(self, instance, value):
|
||
|
self.core_title_text.text = value
|
||
|
|
||
|
def on_title_text_size(self, instance, value):
|
||
|
self.core_title_text.font_size = value
|
||
|
|
||
|
def on_title_text_bold(self, instance, value):
|
||
|
self.core_title_text.bold = value
|
||
|
|
||
|
def on_outer_radius(self, instance, value):
|
||
|
self._outer_radius = self.outer_radius * 2
|
||
|
|
||
|
def on_target_radius(self, instance, value):
|
||
|
self._target_radius = self.target_radius * 2
|
||
|
|
||
|
def on_target_touch(self):
|
||
|
if self.stop_on_target_touch:
|
||
|
self.stop()
|
||
|
|
||
|
def on_outer_touch(self):
|
||
|
if self.stop_on_outer_touch:
|
||
|
self.stop()
|
||
|
|
||
|
def on_outside_click(self):
|
||
|
if self.cancelable:
|
||
|
self.stop()
|
||
|
|
||
|
def _initialize(self):
|
||
|
setattr(self.widget, "_outer_radius", 0)
|
||
|
setattr(self.widget, "_target_radius", 0)
|
||
|
setattr(self.widget, "target_ripple_radius", 0)
|
||
|
setattr(self.widget, "target_ripple_alpha", 0)
|
||
|
|
||
|
# Bind some function on widget event when this function is called
|
||
|
# instead of when the class itself is initialized to prevent all
|
||
|
# widgets of all instances to get bind at once and start messing up.
|
||
|
self.widget.bind(on_touch_down=self._some_func)
|
||
|
|
||
|
def _draw_canvas(self):
|
||
|
_pos = self._ttv_pos()
|
||
|
self.widget.canvas.before.remove_group("ttv_group")
|
||
|
|
||
|
with self.widget.canvas.before:
|
||
|
# Outer circle.
|
||
|
Color(
|
||
|
*self.outer_circle_color,
|
||
|
self.outer_circle_alpha,
|
||
|
group="ttv_group",
|
||
|
)
|
||
|
_rad1 = self.widget._outer_radius
|
||
|
Ellipse(size=(_rad1, _rad1), pos=_pos[0], group="ttv_group")
|
||
|
|
||
|
# Title text.
|
||
|
Color(*self.title_text_color, group="ttv_group")
|
||
|
Rectangle(
|
||
|
size=self.core_title_text.texture.size,
|
||
|
texture=self.core_title_text.texture,
|
||
|
pos=_pos[1],
|
||
|
group="ttv_group",
|
||
|
)
|
||
|
|
||
|
# Description text.
|
||
|
Color(*self.description_text_color, group="ttv_group")
|
||
|
Rectangle(
|
||
|
size=self.core_description_text.texture.size,
|
||
|
texture=self.core_description_text.texture,
|
||
|
pos=(
|
||
|
_pos[1][0],
|
||
|
_pos[1][1] - self.core_description_text.size[1] - 5,
|
||
|
),
|
||
|
group="ttv_group",
|
||
|
)
|
||
|
|
||
|
# Target circle.
|
||
|
Color(*self.target_circle_color, group="ttv_group")
|
||
|
_rad2 = self.widget._target_radius
|
||
|
Ellipse(
|
||
|
size=(_rad2, _rad2),
|
||
|
pos=(
|
||
|
self.widget.x - (_rad2 / 2 - self.widget.size[0] / 2),
|
||
|
self.widget.y - (_rad2 / 2 - self.widget.size[0] / 2),
|
||
|
),
|
||
|
group="ttv_group",
|
||
|
)
|
||
|
|
||
|
# Target ripple.
|
||
|
Color(
|
||
|
*self.target_circle_color,
|
||
|
self.widget.target_ripple_alpha,
|
||
|
group="ttv_group",
|
||
|
)
|
||
|
_rad3 = self.widget.target_ripple_radius
|
||
|
Ellipse(
|
||
|
size=(_rad3, _rad3),
|
||
|
pos=(
|
||
|
self.widget.x - (_rad3 / 2 - self.widget.size[0] / 2),
|
||
|
self.widget.y - (_rad3 / 2 - self.widget.size[0] / 2),
|
||
|
),
|
||
|
group="ttv_group",
|
||
|
)
|
||
|
|
||
|
def _after_stop(self, *args):
|
||
|
self.widget.canvas.before.remove_group("ttv_group")
|
||
|
args[0].stop_all(self.widget)
|
||
|
|
||
|
elevation = getattr(self.widget, "elevation", None)
|
||
|
if elevation:
|
||
|
self.widget.elevation = self.__elevation
|
||
|
|
||
|
self.dispatch("on_close")
|
||
|
|
||
|
# Don't forget to unbind the function or it'll mess
|
||
|
# up with other next bindings.
|
||
|
self.widget.unbind(on_touch_down=self._some_func)
|
||
|
self.state = "close"
|
||
|
|
||
|
def _fix_elev(self):
|
||
|
with self.widget.canvas.before:
|
||
|
Color(a=self.widget._soft_shadow_a)
|
||
|
Rectangle(
|
||
|
texture=self.widget._soft_shadow_texture,
|
||
|
size=self.widget._soft_shadow_size,
|
||
|
pos=self.widget._soft_shadow_pos,
|
||
|
)
|
||
|
Color(a=self.widget._hard_shadow_a)
|
||
|
Rectangle(
|
||
|
texture=self.widget._hard_shadow_texture,
|
||
|
size=self.widget._hard_shadow_size,
|
||
|
pos=self.widget._hard_shadow_pos,
|
||
|
)
|
||
|
Color(a=1)
|
||
|
|
||
|
def _animate_outer(self):
|
||
|
anim = Animation(
|
||
|
d=0.2,
|
||
|
t="out_cubic",
|
||
|
**dict(
|
||
|
zip(
|
||
|
["_outer_radius", "_target_radius"],
|
||
|
[self._outer_radius, self._target_radius],
|
||
|
)
|
||
|
),
|
||
|
)
|
||
|
anim.cancel_all(self.widget)
|
||
|
anim.bind(on_progress=lambda x, y, z: self._draw_canvas())
|
||
|
anim.bind(on_complete=self._animate_ripple)
|
||
|
anim.start(self.widget)
|
||
|
setattr(self.widget, "target_ripple_radius", self._target_radius)
|
||
|
setattr(self.widget, "target_ripple_alpha", 1)
|
||
|
|
||
|
def _animate_ripple(self, *args):
|
||
|
self.anim_ripple = Animation(
|
||
|
d=1,
|
||
|
t="in_cubic",
|
||
|
target_ripple_radius=self._target_radius + self.ripple_max_dist,
|
||
|
target_ripple_alpha=0,
|
||
|
)
|
||
|
self.anim_ripple.stop_all(self.widget)
|
||
|
self.anim_ripple.bind(on_progress=lambda x, y, z: self._draw_canvas())
|
||
|
self.anim_ripple.bind(on_complete=self._repeat_ripple)
|
||
|
self.anim_ripple.start(self.widget)
|
||
|
|
||
|
def _repeat_ripple(self, *args):
|
||
|
setattr(self.widget, "target_ripple_radius", self._target_radius)
|
||
|
setattr(self.widget, "target_ripple_alpha", 1)
|
||
|
self._animate_ripple()
|
||
|
|
||
|
def _some_func(self, wid, touch):
|
||
|
"""
|
||
|
This function decides which one to dispatch based on the touch
|
||
|
position.
|
||
|
"""
|
||
|
|
||
|
if self._check_pos_target(touch.pos):
|
||
|
self.dispatch("on_target_touch")
|
||
|
elif self._check_pos_outer(touch.pos):
|
||
|
self.dispatch("on_outer_touch")
|
||
|
else:
|
||
|
self.dispatch("on_outside_click")
|
||
|
|
||
|
def _check_pos_outer(self, pos):
|
||
|
"""
|
||
|
Checks if a given `pos` coordinate is within the :attr:`~outer_radius`.
|
||
|
"""
|
||
|
|
||
|
cx = self.circ_pos[0] + self._outer_radius / 2
|
||
|
cy = self.circ_pos[1] + self._outer_radius / 2
|
||
|
r = self._outer_radius / 2
|
||
|
h, k = pos
|
||
|
|
||
|
lhs = (cx - h) ** 2 + (cy - k) ** 2
|
||
|
rhs = r**2
|
||
|
if lhs <= rhs:
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
def _check_pos_target(self, pos):
|
||
|
"""
|
||
|
Checks if a given `pos` coordinate is within the
|
||
|
:attr:`~target_radius`.
|
||
|
"""
|
||
|
|
||
|
cx = self.widget.pos[0] + self.widget.width / 2
|
||
|
cy = self.widget.pos[1] + self.widget.height / 2
|
||
|
r = self._target_radius / 2
|
||
|
h, k = pos
|
||
|
|
||
|
lhs = (cx - h) ** 2 + (cy - k) ** 2
|
||
|
rhs = r**2
|
||
|
if lhs <= rhs:
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
def _ttv_pos(self):
|
||
|
"""
|
||
|
Calculates the `pos` value for outer circle and text
|
||
|
based on the position provided.
|
||
|
|
||
|
:returns: A tuple containing pos for the circle and text.
|
||
|
"""
|
||
|
|
||
|
_rad1 = self.widget._outer_radius
|
||
|
_center_x = self.widget.x - (_rad1 / 2 - self.widget.size[0] / 2)
|
||
|
_center_y = self.widget.y - (_rad1 / 2 - self.widget.size[0] / 2)
|
||
|
|
||
|
if self.widget_position == "left":
|
||
|
circ_pos = (_center_x + _rad1 / 3, _center_y)
|
||
|
title_pos = (_center_x + _rad1 / 1.4, _center_y + _rad1 / 1.4)
|
||
|
elif self.widget_position == "right":
|
||
|
circ_pos = (_center_x - _rad1 / 3, _center_y)
|
||
|
title_pos = (_center_x - _rad1 / 10, _center_y + _rad1 / 1.4)
|
||
|
elif self.widget_position == "top":
|
||
|
circ_pos = (_center_x, _center_y - _rad1 / 3)
|
||
|
title_pos = (_center_x + _rad1 / 4, _center_y + _rad1 / 4)
|
||
|
elif self.widget_position == "bottom":
|
||
|
circ_pos = (_center_x, _center_y + _rad1 / 3)
|
||
|
title_pos = (_center_x + _rad1 / 4, _center_y + _rad1 / 1.2)
|
||
|
# Corner ones need to be at a little smaller distance
|
||
|
# than edge ones that's why _rad1/4.
|
||
|
elif self.widget_position == "left_top":
|
||
|
circ_pos = (_center_x + _rad1 / 4, _center_y - _rad1 / 4)
|
||
|
title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 4)
|
||
|
elif self.widget_position == "right_top":
|
||
|
circ_pos = (_center_x - _rad1 / 4, _center_y - _rad1 / 4)
|
||
|
title_pos = (_center_x - _rad1 / 10, _center_y + _rad1 / 4)
|
||
|
elif self.widget_position == "left_bottom":
|
||
|
circ_pos = (_center_x + _rad1 / 4, _center_y + _rad1 / 4)
|
||
|
title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 1.2)
|
||
|
elif self.widget_position == "right_bottom":
|
||
|
circ_pos = (_center_x - _rad1 / 4, _center_y + _rad1 / 4)
|
||
|
title_pos = (_center_x, _center_y + _rad1 / 1.2)
|
||
|
else:
|
||
|
# Center.
|
||
|
circ_pos = (_center_x, _center_y)
|
||
|
|
||
|
if self.title_position == "auto":
|
||
|
raise ValueError(
|
||
|
"widget_position='center' requires title_position to be set."
|
||
|
)
|
||
|
elif self.title_position == "left":
|
||
|
title_pos = (_center_x + _rad1 / 10, _center_y + _rad1 / 2)
|
||
|
elif self.title_position == "right":
|
||
|
title_pos = (_center_x + _rad1 / 1.6, _center_y + _rad1 / 2)
|
||
|
elif self.title_position == "top":
|
||
|
title_pos = (_center_x + _rad1 / 2.5, _center_y + _rad1 / 1.3)
|
||
|
elif self.title_position == "bottom":
|
||
|
title_pos = (_center_x + _rad1 / 2.5, _center_y + _rad1 / 4)
|
||
|
elif self.title_position == "left_top":
|
||
|
title_pos = (_center_x + _rad1 / 8, _center_y + _rad1 / 1.4)
|
||
|
elif self.title_position == "right_top":
|
||
|
title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 1.3)
|
||
|
elif self.title_position == "left_bottom":
|
||
|
title_pos = (_center_x + _rad1 / 8, _center_y + _rad1 / 4)
|
||
|
elif self.title_position == "right_bottom":
|
||
|
title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 3.5)
|
||
|
else:
|
||
|
raise ValueError(
|
||
|
f"'{self.title_position}'"
|
||
|
f"is not a valid value for title_position"
|
||
|
)
|
||
|
|
||
|
self.circ_pos = circ_pos
|
||
|
return circ_pos, title_pos
|