├── .travis.yml
├── LICENSE
├── README.md
├── demo
├── kivymd_logo.png
├── ttv_demo.py
└── ttv_demo_2.gif
├── requirements.txt
├── setup.py
├── taptargetview
├── __init__.py
└── taptargetview.py
└── test
└── test1.py
/.travis.yml:
--------------------------------------------------------------------------------
1 | cache: pip
2 |
3 | matrix:
4 | fast_finish: true
5 | include:
6 | - name: "Black"
7 | env: RUN=black
8 | language: python
9 | python: 3.7
10 | os: linux
11 | dist: bionic
12 |
13 | script: python3 test/test1.py
14 |
15 | after_failure:
16 | - sleep 10;
17 | - echo == End ==
18 |
19 | deploy:
20 | provider: pypi
21 | user: "__token__"
22 | password:
23 | secure: "pypi-AgEIcHlwaS5vcmcCJDdhMTkyODMzLWE1Y2MtNDc3My1hYzI0LTM2ZGU5NTRkNmY4MAACPnsicGVybWlzc2lvbnMiOiB7InByb2plY3RzIjogWyJ0YXB0YXJnZXR2aWV3Il19LCAidmVyc2lvbiI6IDF9AAAGIAnncDRYv5MyAxTGReIb8VblzDX1pWqXr5dDNL5OhBc3"
24 | distributions: "sdist bdist_wheel"
25 | on:
26 | tags: true
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Shashi Ranjan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # TapTargetView [](https://travis-ci.org/shashi278/TapTargetView)
4 | ###### This is now being used in [KivyMD](https://github.com/HeaTTheatR/KivyMD)
5 |
6 | An attempt to mimic android's TapTargetView using Python and Kivy.
7 |
8 | Inspired by [Android's TapTargetView](https://github.com/KeepSafe/TapTargetView)
9 |
10 | ## Installation
11 | #### *Using Pip*
12 | * `pip install taptargetview`
13 |
14 | #### *Manually*
15 |
16 | * `git clone https://github.com/shashi278/TapTargetView.git`
17 |
18 | * `cd TapTargetView`
19 |
20 | * `python setup.py install`
21 |
22 | ## Simple Usage
23 | ```python
24 |
25 | TapTargetView(
26 | my_button,
27 | outer_circle_color= [0,1,1],
28 | outer_circle_alpha= .85,
29 | title_text= "My Button",
30 | description_text="It does something when pressed",
31 | widget_position="center",
32 | title_position="right_bottom",
33 | end= my_callback
34 | ).start()
35 |
36 | ```
37 | Refer to [demo](demo/ttv_demo.py) for extensive usages.
38 |
39 | ### Sequencing
40 | Sequencing is easier. Just bind `start` of one instance to the `on_end` of another instance.
41 | ```python
42 |
43 | ttv2= TapTargetView(
44 | my_button2,
45 | outer_circle_color= [1,0,1],
46 | outer_circle_alpha= .05,
47 | title_text= "My Second Button",
48 | description_text="It too does something when pressed",
49 | widget_position="left",
50 | end= my_callback
51 | )
52 |
53 | ttv1= TapTargetView(
54 | my_button1,
55 | outer_circle_color= [0,1,1],
56 | outer_circle_alpha= .85,
57 | title_text= "My First Button",
58 | description_text="It does something when pressed",
59 | widget_position="center",
60 | title_position="right_bottom",
61 | end= ttv2.start
62 | )
63 |
64 | ttv1.start()
65 |
66 | ```
67 |
68 | ### Customizable attributes:
69 | ```python
70 | """
71 | widget: widget to add TapTargetView upon
72 | outer_radius: (optional), Radius for outer circle, defaults to dp(300)
73 | outer_circle_color: (optional), Color for the outer circle, defaults to [1,0,0]
74 | outer_circle_alpha: (optional), Alpha value for outer circle, defaults to .96
75 | target_radius: (optional), Radius for target circle, defaults to dp(45)
76 | target_circle_color: (optional), Color for target circle, defaults to [1,1,1]
77 | title_text: (optional), Title to be shown on the view, defaults to ''
78 | title_text_size: (optional), Text size for title, defaults to dp(25)
79 | title_text_color: (optional), Text color for title, defaults to [1,1,1,1]
80 | title_text_bold: (optional), Whether title should be bold, defaults to `True`
81 | description_text: (optional), Description to be shown below the title(Keep it short),
82 | defaults to ''
83 | description_text_size: (optional), Text size for description text, defaults to dp(20)
84 | description_text_color: (optional), Text color for description text, defaults to [.9,.9,.9,1]
85 | description_text_bold: (optional), Whether description should be bold, defaults to False
86 | draw_shadow: (optional), Whether to show shadow, defaults to False
87 | cancelable: (optional), Whether clicking outside the outer circle dismisses the view,
88 | defaults to False
89 | widget_position: (optional), Sets the position of the widget on the outer_circle.
90 | Can be one of "left","right","top","bottom","left_top","right_top",
91 | "left_bottom","right_bottom", and "center", defaults to "left"
92 | title_position: (optional), Sets the position of `title_text` on the outer circle.
93 | Only works if `widget_position` is set to "center". In all other cases,
94 | it calculates the `title_position` itself.
95 | Must be set to other than "auto" when `widget_position` is set to "center".
96 | Can be one of "left","right","top","bottom","left_top","right_top",
97 | "left_bottom", and "right_bottom", defaults to "auto" (since `widget_position` is "left")
98 | stop_on_outer_touch: (optional), whether clicking on outer circle stops the animation,
99 | defaults to False
100 | stop_on_target_touch: (optional), whether clicking on target circle should stop the animation,
101 | defaults to True
102 | end: (optional), Function to be called when the animation stops, defaults to None
103 | """
104 | ```
105 |
--------------------------------------------------------------------------------
/demo/kivymd_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shashi278/TapTargetView/eca53c282a1492c372c3d130043c110576c51f53/demo/kivymd_logo.png
--------------------------------------------------------------------------------
/demo/ttv_demo.py:
--------------------------------------------------------------------------------
1 | from kivy.animation import Animation
2 | from kivy.lang import Builder
3 | from kivy.metrics import dp
4 | from kivy.uix.boxlayout import BoxLayout
5 |
6 | from kivymd.app import MDApp
7 | from kivymd.uix.behaviors import (
8 | RectangularElevationBehavior,
9 | SpecificBackgroundColorBehavior,
10 | )
11 | from taptargetview.taptargetview import TapTargetView
12 |
13 | example_kv = """
14 | Screen:
15 |
16 | Image:
17 | id: logo
18 | source: "kivymd_logo.png"
19 |
20 | CustomToolbar:
21 | id: toolbar
22 | size_hint_y: None
23 | height: app.theme_cls.standard_increment
24 | md_bg_color: app.theme_cls.primary_color
25 | elevation: 10
26 | padding: "8dp", 0, 0, 0
27 | pos_hint: {"top": 1}
28 |
29 | MDIconButton:
30 | id: menu_btn
31 | icon: "menu"
32 | theme_text_color: "Custom"
33 | text_color: toolbar.specific_text_color
34 | md_bg_color: app.theme_cls.primary_color
35 | pos_hint: {"center_y": .5}
36 |
37 | Widget:
38 | size_hint_x: None
39 | width: "25dp"
40 |
41 | MDLabel:
42 | text: "TapTargetView"
43 | shorten: True
44 | font_style: 'H6'
45 | theme_text_color: "Custom"
46 | text_color: toolbar.specific_text_color
47 |
48 | MDIconButton:
49 | id: search_btn
50 | icon: "magnify"
51 | md_bg_color: 0, 0, 0, 0
52 | theme_text_color: "Custom"
53 | text_color: 1, 1, 1, 1
54 | pos_hint: {"center_y": .5}
55 |
56 | MDIconButton:
57 | id: info_btn
58 | icon: "information-outline"
59 | md_bg_color: 0, 0, 0, 0
60 | theme_text_color: "Custom"
61 | text_color: 1, 1, 1, 1
62 | pos_hint: {"center_y": .5}
63 |
64 | MDLabel:
65 | id: lbl
66 | text: "Congrats! You're" + "\\n" + "educated now!!"
67 | opacity: 0
68 | font_size: "24sp"
69 | halign: "center"
70 |
71 | MDFloatingActionButton:
72 | id: add_btn
73 | icon: "plus"
74 | pos: 10, 10
75 | """
76 |
77 |
78 | class CustomToolbar(
79 | RectangularElevationBehavior, SpecificBackgroundColorBehavior, BoxLayout
80 | ):
81 | pass
82 |
83 |
84 | class TapTargetViewDemo(MDApp):
85 | def build(self):
86 | self.screen = Builder.load_string(example_kv)
87 |
88 | ttv4 = TapTargetView(
89 | widget=self.screen.ids.add_btn,
90 | outer_radius=dp(320),
91 | cancelable=True,
92 | outer_circle_color=self.theme_cls.primary_color[:-1],
93 | outer_circle_alpha=0.9,
94 | title_text="This is an add button",
95 | description_text="You can cancel it by clicking outside",
96 | widget_position="left_bottom",
97 | end=self.complete,
98 | )
99 |
100 | ttv3 = TapTargetView(
101 | widget=self.screen.ids.info_btn,
102 | outer_radius=dp(440),
103 | outer_circle_color=self.theme_cls.primary_color[:-1],
104 | outer_circle_alpha=0.8,
105 | target_circle_color=[255 / 255, 34 / 255, 212 / 255],
106 | title_text="This is the info button",
107 | description_text="No information available yet!",
108 | widget_position="center",
109 | title_position="left_bottom",
110 | end=ttv4.start,
111 | )
112 |
113 | ttv2 = TapTargetView(
114 | widget=self.screen.ids.search_btn,
115 | outer_circle_color=[155 / 255, 89 / 255, 182 / 255],
116 | target_circle_color=[0.2, 0.2, 0.2],
117 | title_text="This is the search button",
118 | description_text="It won't search anything for now.",
119 | widget_position="center",
120 | title_position="left_bottom",
121 | end=ttv3.start,
122 | )
123 |
124 | ttv1 = TapTargetView(
125 | widget=self.screen.ids.menu_btn,
126 | outer_circle_color=self.theme_cls.primary_color[:-1],
127 | outer_circle_alpha=0.85,
128 | title_text="Menu Button",
129 | description_text="Opens up the drawer",
130 | widget_position="center",
131 | title_position="right_bottom",
132 | end=ttv2.start,
133 | )
134 | ttv1.start()
135 |
136 | return self.screen
137 |
138 | def complete(self, *args):
139 | Animation(opacity=0.3, d=0.2).start(self.screen.ids.logo)
140 | Animation(opacity=0.3, d=0.2).start(self.screen.ids.lbl)
141 |
142 |
143 | TapTargetViewDemo().run()
144 |
--------------------------------------------------------------------------------
/demo/ttv_demo_2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shashi278/TapTargetView/eca53c282a1492c372c3d130043c110576c51f53/demo/ttv_demo_2.gif
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | kivy
2 | kivymd
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | with open("README.md", "r") as fh:
4 | long_description = fh.read()
5 |
6 | setup(
7 | name="TapTargetView",
8 | version="0.1.2",
9 | packages=["taptargetview"],
10 | scripts=["taptargetview/taptargetview.py"],
11 | package_data={"taptargetview": ["*.py"],},
12 | # metadata to display on PyPI
13 | author="Shashi Ranjan",
14 | author_email="shashiranjankv@gmail.com",
15 | description="Attempt to mimic Android's TapTargetView using Kivy and Python",
16 | long_description=long_description,
17 | long_description_content_type="text/markdown",
18 | keywords="Kivy Python TapTargetView",
19 | url="https://github.com/shashi278/TapTargetView",
20 | classifiers=[
21 | "Programming Language :: Python :: 3",
22 | "License :: OSI Approved :: MIT License",
23 | "Operating System :: OS Independent",
24 | ],
25 | install_requires=["kivy",],
26 | python_requires=">=3.6",
27 | )
28 |
--------------------------------------------------------------------------------
/taptargetview/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shashi278/TapTargetView/eca53c282a1492c372c3d130043c110576c51f53/taptargetview/__init__.py
--------------------------------------------------------------------------------
/taptargetview/taptargetview.py:
--------------------------------------------------------------------------------
1 | """
2 | .. seealso::
3 |
4 | `TapTargetView, GitHub `_
5 |
6 | `TapTargetView, Material archive https://material.io/archive/guidelines/growth-communications/feature-discovery.html#>`_
7 |
8 | .. rubric:: Attempt to mimic the working of Android's TapTargetView using Kivy and Python..
9 | """
10 |
11 | from kivy.animation import Animation
12 | from kivy.metrics import dp
13 | from kivy.graphics import Color, Ellipse, Rectangle
14 | from kivy.event import EventDispatcher
15 | from kivy.properties import (
16 | ObjectProperty,
17 | NumericProperty,
18 | ListProperty,
19 | StringProperty,
20 | BooleanProperty,
21 | OptionProperty,
22 | )
23 | from kivy.uix.label import Label
24 |
25 |
26 | class TapTargetView(EventDispatcher):
27 | """Rough try to mimic the working of Android's TapTargetView."""
28 |
29 | widget = ObjectProperty()
30 | """
31 | Widget to add ``TapTargetView`` upon.
32 |
33 | :attr:`widget` is an :class:`~kivy.properties.ObjectProperty`
34 | and defaults to `None`.
35 | """
36 |
37 | outer_radius = NumericProperty(dp(300))
38 | """
39 | Radius for outer circle.
40 |
41 | :attr:`outer_radius` is an :class:`~kivy.properties.NumericProperty`
42 | and defaults to `dp(300)`.
43 | """
44 |
45 | outer_circle_color = ListProperty([1, 0, 0])
46 | """
47 | Color for the outer circle.
48 |
49 | :attr:`outer_circle_color` is an :class:`~kivy.properties.ListProperty`
50 | and defaults to `[1, 0, 0]`.
51 | """
52 |
53 | outer_circle_alpha = NumericProperty(0.96)
54 | """
55 | Alpha value for outer circle.
56 |
57 | :attr:`outer_circle_alpha` is an :class:`~kivy.properties.NumericProperty`
58 | and defaults to `0.96`.
59 | """
60 |
61 | target_radius = NumericProperty(dp(45))
62 | """
63 | Radius for target circle.
64 |
65 | :attr:`target_radius` is an :class:`~kivy.properties.NumericProperty`
66 | and defaults to `dp(45)`.
67 | """
68 |
69 | target_circle_color = ListProperty([1, 1, 1])
70 | """
71 | Color for target circle.
72 |
73 | :attr:`target_circle_color` is an :class:`~kivy.properties.ListProperty`
74 | and defaults to `[1, 1, 1]`.
75 | """
76 |
77 | title_text = StringProperty()
78 | """
79 | Title to be shown on the view.
80 |
81 | :attr:`title_text` is an :class:`~kivy.properties.StringProperty`
82 | and defaults to `''`.
83 | """
84 |
85 | title_text_size = NumericProperty(dp(25))
86 | """
87 | Text size for title.
88 |
89 | :attr:`title_text_size` is an :class:`~kivy.properties.NumericProperty`
90 | and defaults to `dp(25)`.
91 | """
92 |
93 | title_text_color = ListProperty([1, 1, 1, 1])
94 | """
95 | Text color for title.
96 |
97 | :attr:`title_text_color` is an :class:`~kivy.properties.ListProperty`
98 | and defaults to `[1, 1, 1, 1]`.
99 | """
100 |
101 | title_text_bold = BooleanProperty(True)
102 | """
103 | Whether title should be bold.
104 |
105 | :attr:`title_text_bold` is an :class:`~kivy.properties.BooleanProperty`
106 | and defaults to `True`.
107 | """
108 |
109 | description_text = StringProperty()
110 | """
111 | Description to be shown below the title (keep it short).
112 |
113 | :attr:`description_text` is an :class:`~kivy.properties.StringProperty`
114 | and defaults to `''`.
115 | """
116 |
117 | description_text_size = NumericProperty(dp(20))
118 | """
119 | Text size for description text.
120 |
121 | :attr:`description_text_size` is an :class:`~kivy.properties.NumericProperty`
122 | and defaults to `dp(20)`.
123 | """
124 |
125 | description_text_color = ListProperty([0.9, 0.9, 0.9, 1])
126 | """
127 | Text size for description text.
128 |
129 | :attr:`description_text_color` is an :class:`~kivy.properties.ListProperty`
130 | and defaults to `[0.9, 0.9, 0.9, 1]`.
131 | """
132 |
133 | description_text_bold = BooleanProperty(False)
134 | """
135 | Whether description should be bold.
136 |
137 | :attr:`description_text_bold` is an :class:`~kivy.properties.BooleanProperty`
138 | and defaults to `False`.
139 | """
140 |
141 | draw_shadow = BooleanProperty(False)
142 | """
143 | Whether to show shadow.
144 |
145 | :attr:`draw_shadow` is an :class:`~kivy.properties.BooleanProperty`
146 | and defaults to `False`.
147 | """
148 |
149 | cancelable = BooleanProperty(False)
150 | """
151 | Whether clicking outside the outer circle dismisses the view.
152 |
153 | :attr:`cancelable` is an :class:`~kivy.properties.BooleanProperty`
154 | and defaults to `False`.
155 | """
156 |
157 | widget_position = OptionProperty(
158 | "left",
159 | options=[
160 | "left",
161 | "right",
162 | "top",
163 | "bottom",
164 | "left_top",
165 | "right_top",
166 | "left_bottom",
167 | "right_bottom",
168 | "center",
169 | ],
170 | )
171 | """
172 | Sets the position of the widget on the outer_circle. Available options are
173 | `'left`', `'right`', `'top`', `'bottom`', `'left_top`', `'right_top`',
174 | `'left_bottom`', `'right_bottom`', `'center`'.
175 |
176 | :attr:`widget_position` is an :class:`~kivy.properties.OptionProperty`
177 | and defaults to `'left'`.
178 | """
179 |
180 | title_position = OptionProperty(
181 | "auto",
182 | options=[
183 | "auto",
184 | "left",
185 | "right",
186 | "top",
187 | "bottom",
188 | "left_top",
189 | "right_top",
190 | "left_bottom",
191 | "right_bottom",
192 | ],
193 | )
194 | """
195 | Sets the position of :attr`~title_text` on the outer circle. Only works if
196 | :attr`~widget_position` is set to `'center'`. In all other cases, it
197 | calculates the :attr`~title_position` itself.
198 | Must be set to other than `'auto`' when :attr`~widget_position` is set
199 | to `'center`'.
200 | Available options are `'auto'`, `'left`', `'right`', `'top`', `'bottom`',
201 | `'left_top`', `'right_top`', `'left_bottom`', `'right_bottom`', `'center`'.
202 |
203 | :attr:`title_position` is an :class:`~kivy.properties.OptionProperty`
204 | and defaults to `'auto'`.
205 | """
206 |
207 | stop_on_outer_touch = BooleanProperty(False)
208 | """
209 | Whether clicking on outer circle stops the animation.
210 |
211 | :attr:`stop_on_outer_touch` is an :class:`~kivy.properties.BooleanProperty`
212 | and defaults to `False`.
213 | """
214 |
215 | stop_on_target_touch = BooleanProperty(True)
216 | """
217 | Whether clicking on target circle should stop the animation.
218 |
219 | :attr:`stop_on_target_touch` is an :class:`~kivy.properties.BooleanProperty`
220 | and defaults to `True`.
221 | """
222 |
223 | end = ObjectProperty()
224 | """
225 | Function to be called when the animation stops.
226 |
227 | :attr:`end` is an :class:`~kivy.properties.ObjectProperty`
228 | and defaults to `None`.
229 | """
230 |
231 | def __init__(self, **kwargs):
232 | self.ripple_max_dist = dp(90)
233 | self.outer_radius *= 2
234 | self.target_radius *= 2
235 |
236 | self.core_title_text = Label(
237 | markup=True, size_hint=(None, None), bold=self.title_text_bold
238 | )
239 | self.core_title_text.bind(texture_size=self.core_title_text.setter("size"))
240 | self.core_description_text = Label(markup=True, size_hint=(None, None))
241 | self.core_description_text.bind(
242 | texture_size=self.core_description_text.setter("size")
243 | )
244 |
245 | super().__init__(**kwargs)
246 | self.register_event_type("on_outer_touch")
247 | self.register_event_type("on_target_touch")
248 | self.register_event_type("on_outside_click")
249 |
250 | def _initialize(self):
251 | setattr(self.widget, "outer_radius", 0)
252 | setattr(self.widget, "target_radius", 0)
253 | setattr(self.widget, "target_ripple_radius", 0)
254 | setattr(self.widget, "target_ripple_alpha", 0)
255 |
256 | # Bind some function on widget event when this function is called
257 | # instead of when the class itself is initialized to prevent all
258 | # widgets of all instances to get bind at once and start messing up.
259 | self.widget.bind(on_touch_down=self._some_func)
260 |
261 | def _draw_canvas(self):
262 | _pos = self._ttv_pos()
263 | self.widget.canvas.before.clear()
264 |
265 | with self.widget.canvas.before:
266 | # Outer circle.
267 | Color(*self.outer_circle_color, self.outer_circle_alpha, group="ttv_group")
268 | _rad1 = self.widget.outer_radius
269 | Ellipse(size=(_rad1, _rad1), pos=_pos[0], group="ttv_group")
270 |
271 | # Title text.
272 | Color(*self.title_text_color, group="ttv_group")
273 | Rectangle(
274 | size=self.core_title_text.texture.size,
275 | texture=self.core_title_text.texture,
276 | pos=_pos[1],
277 | group="ttv_group",
278 | )
279 |
280 | # Description text.
281 | Color(*self.description_text_color, group="ttv_group")
282 | Rectangle(
283 | size=self.core_description_text.texture.size,
284 | texture=self.core_description_text.texture,
285 | pos=(_pos[1][0], _pos[1][1] - self.core_description_text.size[1] - 5),
286 | group="ttv_group",
287 | )
288 |
289 | # Target circle.
290 | Color(*self.target_circle_color, group="ttv_group")
291 | _rad2 = self.widget.target_radius
292 | Ellipse(
293 | size=(_rad2, _rad2),
294 | pos=(
295 | self.widget.x - (_rad2 / 2 - self.widget.size[0] / 2),
296 | self.widget.y - (_rad2 / 2 - self.widget.size[0] / 2),
297 | ),
298 | group="ttv_group",
299 | )
300 |
301 | # Target ripple.
302 | Color(
303 | *self.target_circle_color,
304 | self.widget.target_ripple_alpha,
305 | group="ttv_group",
306 | )
307 | _rad3 = self.widget.target_ripple_radius
308 | Ellipse(
309 | size=(_rad3, _rad3),
310 | pos=(
311 | self.widget.x - (_rad3 / 2 - self.widget.size[0] / 2),
312 | self.widget.y - (_rad3 / 2 - self.widget.size[0] / 2),
313 | ),
314 | group="ttv_group",
315 | )
316 |
317 | def stop(self, *args):
318 | # It needs a better implementation.
319 | self.anim_ripple.unbind(on_complete=self._repeat_ripple)
320 | self.description_text_color = [1, 1, 1, 0]
321 | self.title_text_color = [1, 1, 1, 0]
322 | anim = Animation(
323 | d=0.15,
324 | t="in_cubic",
325 | **dict(
326 | zip(
327 | ["outer_radius", "target_radius", "target_ripple_radius"], [0, 0, 0]
328 | )
329 | ),
330 | )
331 | anim.bind(on_complete=self._after_stop)
332 | anim.start(self.widget)
333 |
334 | def _after_stop(self, *args):
335 | self.widget.canvas.before.remove_group("ttv_group")
336 | args[0].stop_all(self.widget)
337 | elev = getattr(self.widget, "elevation", None)
338 |
339 | if elev:
340 | self._fix_elev()
341 | if self.end:
342 | self.end(self)
343 |
344 | # Don't forget to unbind the function or it'll mess
345 | # up with other next bindings.
346 | self.widget.unbind(on_touch_down=self._some_func)
347 |
348 | def _fix_elev(self):
349 | with self.widget.canvas.before:
350 | Color(a=self.widget._soft_shadow_a)
351 | Rectangle(
352 | texture=self.widget._soft_shadow_texture,
353 | size=self.widget._soft_shadow_size,
354 | pos=self.widget._soft_shadow_pos,
355 | )
356 | Color(a=self.widget._hard_shadow_a)
357 | Rectangle(
358 | texture=self.widget._hard_shadow_texture,
359 | size=self.widget._hard_shadow_size,
360 | pos=self.widget._hard_shadow_pos,
361 | )
362 |
363 | Color(a=1)
364 |
365 | def start(self, *args):
366 | self._initialize()
367 | self._animate_outer()
368 |
369 | def _animate_outer(self):
370 | anim = Animation(
371 | d=0.2,
372 | t="out_cubic",
373 | **dict(
374 | zip(
375 | ["outer_radius", "target_radius"],
376 | [self.outer_radius, self.target_radius],
377 | )
378 | ),
379 | )
380 | anim.cancel_all(self.widget)
381 | anim.bind(on_progress=lambda x, y, z: self._draw_canvas())
382 | anim.bind(on_complete=self._animate_ripple)
383 | anim.start(self.widget)
384 | setattr(self.widget, "target_ripple_radius", self.target_radius)
385 | setattr(self.widget, "target_ripple_alpha", 1)
386 |
387 | def _animate_ripple(self, *args):
388 | self.anim_ripple = Animation(
389 | d=1,
390 | t="in_cubic",
391 | target_ripple_radius=self.target_radius + self.ripple_max_dist,
392 | target_ripple_alpha=0,
393 | )
394 | self.anim_ripple.stop_all(self.widget)
395 | self.anim_ripple.bind(on_progress=lambda x, y, z: self._draw_canvas())
396 | self.anim_ripple.bind(on_complete=self._repeat_ripple)
397 | self.anim_ripple.start(self.widget)
398 |
399 | def _repeat_ripple(self, *args):
400 | setattr(self.widget, "target_ripple_radius", self.target_radius)
401 | setattr(self.widget, "target_ripple_alpha", 1)
402 | self._animate_ripple()
403 |
404 | def on_description_text(self, instance, value):
405 | self.core_description_text.text = value
406 |
407 | def on_description_text_size(self, instance, value):
408 | self.core_description_text.font_size = value
409 |
410 | def on_description_text_bold(self, instance, value):
411 | self.core_description_text.bold = value
412 |
413 | def on_title_text(self, instance, value):
414 | self.core_title_text.text = value
415 |
416 | def on_title_text_size(self, instance, value):
417 | self.core_title_text.font_size = value
418 |
419 | def on_title_text_bold(self, instance, value):
420 | self.core_title_text.bold = value
421 |
422 | def on_target_touch(self):
423 | if self.stop_on_target_touch:
424 | self.stop()
425 |
426 | def on_outer_touch(self):
427 | if self.stop_on_outer_touch:
428 | self.stop()
429 |
430 | def on_outside_click(self):
431 | if self.cancelable:
432 | self.stop()
433 |
434 | def _some_func(self, wid, touch):
435 | """
436 | This function decides which one to dispatch based on the touch
437 | position.
438 | """
439 |
440 | if self._check_pos_target(touch.pos):
441 | self.dispatch("on_target_touch")
442 | elif self._check_pos_outer(touch.pos):
443 | self.dispatch("on_outer_touch")
444 | else:
445 | self.dispatch("on_outside_click")
446 |
447 | def _check_pos_outer(self, pos):
448 | """
449 | Checks if a given `pos` coordinate is within the :attr:`~outer_radius`.
450 | """
451 |
452 | cx = self.circ_pos[0] + self.outer_radius / 2
453 | cy = self.circ_pos[1] + self.outer_radius / 2
454 | r = self.outer_radius / 2
455 | h, k = pos
456 |
457 | lhs = (cx - h) ** 2 + (cy - k) ** 2
458 | rhs = r ** 2
459 | if lhs <= rhs:
460 | return True
461 | return False
462 |
463 | def _check_pos_target(self, pos):
464 | """
465 | Checks if a given `pos` coordinate is within the
466 | :attr:`~target_radius`.
467 | """
468 |
469 | cx = self.widget.pos[0] + self.widget.width / 2
470 | cy = self.widget.pos[1] + self.widget.height / 2
471 | r = self.target_radius / 2
472 | h, k = pos
473 |
474 | lhs = (cx - h) ** 2 + (cy - k) ** 2
475 | rhs = r ** 2
476 | if lhs <= rhs:
477 | return True
478 | return False
479 |
480 | def _ttv_pos(self):
481 | """
482 | Calculates the `pos` value for outer circle and text
483 | based on the position provided.
484 |
485 | :returns: A tuple containing pos for the circle and text.
486 | """
487 |
488 | _rad1 = self.widget.outer_radius
489 | _center_x = self.widget.x - (_rad1 / 2 - self.widget.size[0] / 2)
490 | _center_y = self.widget.y - (_rad1 / 2 - self.widget.size[0] / 2)
491 |
492 | if self.widget_position == "left":
493 | circ_pos = (_center_x + _rad1 / 3, _center_y)
494 | title_pos = (_center_x + _rad1 / 1.4, _center_y + _rad1 / 1.4)
495 | elif self.widget_position == "right":
496 | circ_pos = (_center_x - _rad1 / 3, _center_y)
497 | title_pos = (_center_x - _rad1 / 10, _center_y + _rad1 / 1.4)
498 | elif self.widget_position == "top":
499 | circ_pos = (_center_x, _center_y - _rad1 / 3)
500 | title_pos = (_center_x + _rad1 / 4, _center_y + _rad1 / 4)
501 | elif self.widget_position == "bottom":
502 | circ_pos = (_center_x, _center_y + _rad1 / 3)
503 | title_pos = (_center_x + _rad1 / 4, _center_y + _rad1 / 1.2)
504 | # Corner ones need to be at a little smaller distance
505 | # than edge ones that's why _rad1/4.
506 | elif self.widget_position == "left_top":
507 | circ_pos = (_center_x + _rad1 / 4, _center_y - _rad1 / 4)
508 | title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 4)
509 | elif self.widget_position == "right_top":
510 | circ_pos = (_center_x - _rad1 / 4, _center_y - _rad1 / 4)
511 | title_pos = (_center_x - _rad1 / 10, _center_y + _rad1 / 4)
512 | elif self.widget_position == "left_bottom":
513 | circ_pos = (_center_x + _rad1 / 4, _center_y + _rad1 / 4)
514 | title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 1.2)
515 | elif self.widget_position == "right_bottom":
516 | circ_pos = (_center_x - _rad1 / 4, _center_y + _rad1 / 4)
517 | title_pos = (_center_x, _center_y + _rad1 / 1.2)
518 | else:
519 | # Center.
520 | circ_pos = (_center_x, _center_y)
521 |
522 | if self.title_position == "auto":
523 | raise ValueError(
524 | "widget_position='center' requires title_position to be set."
525 | )
526 | elif self.title_position == "left":
527 | title_pos = (_center_x + _rad1 / 10, _center_y + _rad1 / 2)
528 | elif self.title_position == "right":
529 | title_pos = (_center_x + _rad1 / 1.6, _center_y + _rad1 / 2)
530 | elif self.title_position == "top":
531 | title_pos = (_center_x + _rad1 / 2.5, _center_y + _rad1 / 1.3)
532 | elif self.title_position == "bottom":
533 | title_pos = (_center_x + _rad1 / 2.5, _center_y + _rad1 / 4)
534 | elif self.title_position == "left_top":
535 | title_pos = (_center_x + _rad1 / 8, _center_y + _rad1 / 1.4)
536 | elif self.title_position == "right_top":
537 | title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 1.3)
538 | elif self.title_position == "left_bottom":
539 | title_pos = (_center_x + _rad1 / 8, _center_y + _rad1 / 4)
540 | elif self.title_position == "right_bottom":
541 | title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 3.5)
542 | else:
543 | raise ValueError(
544 | f"'{self.title_position}'"
545 | f"is not a valid value for title_position"
546 | )
547 |
548 | self.circ_pos = circ_pos
549 | return circ_pos, title_pos
550 |
--------------------------------------------------------------------------------
/test/test1.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | sys.path.append("../")
4 |
5 | """
6 | from taptargetview.taptargetview import TapTargetView
7 | """
8 |
--------------------------------------------------------------------------------