201 lines
5.9 KiB
Python
201 lines
5.9 KiB
Python
|
"""
|
|||
|
Effects/FadingEdgeEffect
|
|||
|
========================
|
|||
|
|
|||
|
.. versionadded:: 1.0.0
|
|||
|
|
|||
|
The `FadingEdgeEffect` class implements a fade effect for `KivyMD` widgets:
|
|||
|
|
|||
|
.. code-block:: python
|
|||
|
|
|||
|
from kivy.lang import Builder
|
|||
|
from kivy.uix.scrollview import ScrollView
|
|||
|
|
|||
|
from kivymd.app import MDApp
|
|||
|
from kivymd.effects.fadingedge.fadingedge import FadingEdgeEffect
|
|||
|
from kivymd.uix.list import OneLineListItem
|
|||
|
|
|||
|
KV = '''
|
|||
|
MDScreen:
|
|||
|
|
|||
|
FadeScrollView:
|
|||
|
fade_height: self.height / 2
|
|||
|
fade_color: root.md_bg_color
|
|||
|
|
|||
|
MDList:
|
|||
|
id: container
|
|||
|
'''
|
|||
|
|
|||
|
|
|||
|
class FadeScrollView(FadingEdgeEffect, ScrollView):
|
|||
|
pass
|
|||
|
|
|||
|
|
|||
|
class Test(MDApp):
|
|||
|
def build(self):
|
|||
|
return Builder.load_string(KV)
|
|||
|
|
|||
|
def on_start(self):
|
|||
|
for i in range(20):
|
|||
|
self.root.ids.container.add_widget(
|
|||
|
OneLineListItem(text=f"Single-line item {i}")
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
Test().run()
|
|||
|
|
|||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fading-edge-effect-white.gif
|
|||
|
:align: center
|
|||
|
|
|||
|
.. note:: Use the same color value for the fade_color parameter as for the
|
|||
|
parent widget.
|
|||
|
"""
|
|||
|
|
|||
|
from typing import Union
|
|||
|
|
|||
|
from kivy.clock import Clock
|
|||
|
from kivy.graphics.context_instructions import Color
|
|||
|
from kivy.graphics.vertex_instructions import Rectangle
|
|||
|
from kivy.metrics import dp
|
|||
|
from kivy.properties import BooleanProperty, ColorProperty, NumericProperty
|
|||
|
|
|||
|
from kivymd.theming import ThemableBehavior
|
|||
|
|
|||
|
__all_ = ("FadingEdgeEffect",)
|
|||
|
|
|||
|
|
|||
|
class FadingEdgeEffect(ThemableBehavior):
|
|||
|
"""
|
|||
|
The class implements the fade effect.
|
|||
|
|
|||
|
.. versionadded:: 1.0.0
|
|||
|
"""
|
|||
|
|
|||
|
fade_color = ColorProperty(None)
|
|||
|
"""
|
|||
|
Fade color.
|
|||
|
|
|||
|
:attr:`fade_color` is an :class:`~kivy.properties.ColorProperty`
|
|||
|
and defaults to `None`.
|
|||
|
"""
|
|||
|
|
|||
|
fade_height = NumericProperty(0)
|
|||
|
"""
|
|||
|
Fade height.
|
|||
|
|
|||
|
:attr:`fade_height` is an :class:`~kivy.properties.ColorProperty`
|
|||
|
and defaults to `0`.
|
|||
|
"""
|
|||
|
|
|||
|
edge_top = BooleanProperty(True)
|
|||
|
"""
|
|||
|
Display fade edge top.
|
|||
|
|
|||
|
:attr:`edge_top` is an :class:`~kivy.properties.BooleanProperty`
|
|||
|
and defaults to `True`.
|
|||
|
"""
|
|||
|
|
|||
|
edge_bottom = BooleanProperty(True)
|
|||
|
"""
|
|||
|
Display fade edge bottom.
|
|||
|
|
|||
|
:attr:`edge_bottom` is an :class:`~kivy.properties.BooleanProperty`
|
|||
|
and defaults to `True`.
|
|||
|
"""
|
|||
|
|
|||
|
_height_segment = 10
|
|||
|
|
|||
|
def __init__(self, **kwargs):
|
|||
|
super().__init__(**kwargs)
|
|||
|
Clock.schedule_once(self.set_fade)
|
|||
|
|
|||
|
# TODO: Perhaps it would be better if we used a Shader for the fade effect.
|
|||
|
# But, I think the canvas instructions shouldn't affect performance
|
|||
|
def set_fade(self, interval: Union[int, float]) -> None:
|
|||
|
"""Draws a bottom and top fade border on the canvas."""
|
|||
|
|
|||
|
fade_color = (
|
|||
|
self.theme_cls.primary_color
|
|||
|
if not self.fade_color
|
|||
|
else self.fade_color
|
|||
|
)
|
|||
|
height_segment = (
|
|||
|
self.fade_height if self.fade_height else dp(100)
|
|||
|
) // self._height_segment
|
|||
|
alpha = 1.1
|
|||
|
|
|||
|
with self.canvas:
|
|||
|
for i in range(self._height_segment):
|
|||
|
alpha -= 0.1
|
|||
|
|
|||
|
Color(rgba=(fade_color[:-1] + [round(alpha, 1)]))
|
|||
|
rectangle_top = (
|
|||
|
Rectangle(
|
|||
|
pos=(self.x, self.height - (i * height_segment)),
|
|||
|
size=(self.width, height_segment),
|
|||
|
)
|
|||
|
if self.edge_top
|
|||
|
else None
|
|||
|
)
|
|||
|
rectangle_bottom = (
|
|||
|
Rectangle(
|
|||
|
pos=(self.x, i * height_segment),
|
|||
|
size=(self.width, height_segment),
|
|||
|
)
|
|||
|
if self.edge_bottom
|
|||
|
else None
|
|||
|
)
|
|||
|
# How I hate lambda functions because of their length :(
|
|||
|
# But I don’t want to call the arguments by short,
|
|||
|
# incomprehensible names 'a', 'b', 'c'.
|
|||
|
self.bind(
|
|||
|
pos=lambda instance_fadind_edge_effect, window_size, rectangle_top=rectangle_top, rectangle_bottom=rectangle_bottom, index=i: self.update_canvas(
|
|||
|
instance_fadind_edge_effect,
|
|||
|
window_size,
|
|||
|
rectangle_top,
|
|||
|
rectangle_bottom,
|
|||
|
index,
|
|||
|
),
|
|||
|
size=lambda instance_fadind_edge_effect, window_size, rectangle_top=rectangle_top, rectangle_bottom=rectangle_bottom, index=i: self.update_canvas(
|
|||
|
instance_fadind_edge_effect,
|
|||
|
window_size,
|
|||
|
rectangle_top,
|
|||
|
rectangle_bottom,
|
|||
|
index,
|
|||
|
),
|
|||
|
)
|
|||
|
self.update_canvas(
|
|||
|
self, self.size, rectangle_top, rectangle_bottom, i
|
|||
|
)
|
|||
|
|
|||
|
def update_canvas(
|
|||
|
self,
|
|||
|
instance_fadind_edge_effect,
|
|||
|
size: list[int, int],
|
|||
|
rectangle_top: Rectangle,
|
|||
|
rectangle_bottom: Rectangle,
|
|||
|
index: int,
|
|||
|
) -> None:
|
|||
|
"""
|
|||
|
Updates the position and size of the fade border on the canvas.
|
|||
|
Called when the application screen is resized.
|
|||
|
"""
|
|||
|
|
|||
|
height_segment = (
|
|||
|
self.fade_height if self.fade_height else dp(100)
|
|||
|
) // self._height_segment
|
|||
|
|
|||
|
if rectangle_top:
|
|||
|
rectangle_top.pos = (
|
|||
|
instance_fadind_edge_effect.x,
|
|||
|
size[1]
|
|||
|
- (index * height_segment - instance_fadind_edge_effect.y),
|
|||
|
)
|
|||
|
rectangle_top.size = (size[0], height_segment)
|
|||
|
if rectangle_bottom:
|
|||
|
rectangle_bottom.pos = (
|
|||
|
instance_fadind_edge_effect.x,
|
|||
|
index * height_segment + instance_fadind_edge_effect.y,
|
|||
|
)
|
|||
|
rectangle_bottom.size = (size[0], height_segment)
|