""" 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)