""" Behaviors/Elevation =================== .. rubric:: Classes implements a circular and rectangular elevation effects. To create a widget with rectangular or circular elevation effect, you must create a new class that inherits from the :class:`~RectangularElevationBehavior` or :class:`~CircularElevationBehavior` class. For example, let's create an button with a rectangular elevation effect: .. code-block:: python from kivy.lang import Builder from kivy.uix.behaviors import ButtonBehavior from kivymd.app import MDApp from kivymd.uix.behaviors import ( RectangularRippleBehavior, BackgroundColorBehavior, RectangularElevationBehavior, ) KV = ''' : size_hint: None, None size: "250dp", "50dp" Screen: # With elevation effect RectangularElevationButton: pos_hint: {"center_x": .5, "center_y": .6} elevation: 11 # Without elevation effect RectangularElevationButton: pos_hint: {"center_x": .5, "center_y": .4} ''' class RectangularElevationButton( RectangularRippleBehavior, RectangularElevationBehavior, ButtonBehavior, BackgroundColorBehavior, ): md_bg_color = [0, 0, 1, 1] class Example(MDApp): def build(self): return Builder.load_string(KV) Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-effect.gif :align: center Similarly, create a button with a circular elevation effect: .. code-block:: python from kivy.lang import Builder from kivy.uix.image import Image from kivy.uix.behaviors import ButtonBehavior from kivymd.app import MDApp from kivymd.uix.behaviors import ( CircularRippleBehavior, CircularElevationBehavior, ) KV = ''' #:import images_path kivymd.images_path : size_hint: None, None size: "100dp", "100dp" source: f"{images_path}/kivymd.png" Screen: # With elevation effect CircularElevationButton: pos_hint: {"center_x": .5, "center_y": .6} elevation: 5 # Without elevation effect CircularElevationButton: pos_hint: {"center_x": .5, "center_y": .4} elevation: 0 ''' class CircularElevationButton( CircularRippleBehavior, CircularElevationBehavior, ButtonBehavior, Image, ): md_bg_color = [0, 0, 1, 1] class Example(MDApp): def build(self): return Builder.load_string(KV) Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-elevation-effect.gif :align: center """ __all__ = ( "CommonElevationBehavior", "RectangularElevationBehavior", "CircularElevationBehavior", ) from kivy.lang import Builder from kivy.metrics import dp from kivy.properties import ListProperty, NumericProperty, ObjectProperty from kivymd.app import MDApp Builder.load_string( """ canvas.before: Color: a: self._soft_shadow_a Rectangle: texture: self._soft_shadow_texture size: self._soft_shadow_size pos: self._soft_shadow_pos Color: a: self._hard_shadow_a Rectangle: texture: self._hard_shadow_texture size: self._hard_shadow_size pos: self._hard_shadow_pos Color: a: 1 canvas.before: Color: a: self._soft_shadow_a Rectangle: texture: self._soft_shadow_texture size: self._soft_shadow_size pos: self._soft_shadow_pos Color: a: self._hard_shadow_a Rectangle: texture: self._hard_shadow_texture size: self._hard_shadow_size pos: self._hard_shadow_pos Color: a: 1 """ ) class CommonElevationBehavior(object): """Common base class for rectangular and circular elevation behavior.""" elevation = NumericProperty(1) """ Elevation value. :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` and defaults to 1. """ _elevation = NumericProperty(0) _soft_shadow_texture = ObjectProperty() _soft_shadow_size = ListProperty((0, 0)) _soft_shadow_pos = ListProperty((0, 0)) _soft_shadow_a = NumericProperty(0) _hard_shadow_texture = ObjectProperty() _hard_shadow_size = ListProperty((0, 0)) _hard_shadow_pos = ListProperty((0, 0)) _hard_shadow_a = NumericProperty(0) def __init__(self, **kwargs): self.bind( elevation=self._update_elevation, pos=self._update_shadow, size=self._update_shadow, ) super().__init__(**kwargs) def _update_shadow(self, *args): raise NotImplementedError def _update_elevation(self, instance, value): if not self._elevation: self._elevation = value self._update_shadow(instance, value) class RectangularElevationBehavior(CommonElevationBehavior): """Base class for rectangular elevation behavior. Controls the size and position of the shadow.""" def _update_shadow(self, *args): if self._elevation > 0: # Set shadow size. ratio = self.width / (self.height if self.height != 0 else 1) if -2 < ratio < 2: self._shadow = MDApp.get_running_app().theme_cls.quad_shadow width = soft_width = self.width * 1.9 height = soft_height = self.height * 1.9 elif ratio <= -2: self._shadow = MDApp.get_running_app().theme_cls.rec_st_shadow ratio = abs(ratio) if ratio > 5: ratio = ratio * 22 else: ratio = ratio * 11.5 width = soft_width = self.width * 1.9 height = self.height + dp(ratio) soft_height = ( self.height + dp(ratio) + dp(self._elevation) * 0.5 ) else: self._shadow = MDApp.get_running_app().theme_cls.quad_shadow width = soft_width = self.width * 1.8 height = soft_height = self.height * 1.8 self._soft_shadow_size = (soft_width, soft_height) self._hard_shadow_size = (width, height) # Set ``soft_shadow`` parameters. self._soft_shadow_pos = ( self.center_x - soft_width / 2, self.center_y - soft_height / 2 - dp(0.1 * 1.5 ** self._elevation), ) self._soft_shadow_a = 0.1 * 1.1 ** self._elevation self._soft_shadow_texture = self._shadow.textures[ str(int(round(self._elevation))) ] # Set ``hard_shadow`` parameters. self._hard_shadow_pos = ( self.center_x - width / 2, self.center_y - height / 2 - dp(0.5 * 1.18 ** self._elevation), ) self._hard_shadow_a = 0.4 * 0.9 ** self._elevation self._hard_shadow_texture = self._shadow.textures[ str(int(round(self._elevation))) ] else: self._soft_shadow_a = 0 self._hard_shadow_a = 0 class CircularElevationBehavior(CommonElevationBehavior): """Base class for circular elevation behavior. Controls the size and position of the shadow.""" def __init__(self, **kwargs): super().__init__(**kwargs) self._shadow = MDApp.get_running_app().theme_cls.round_shadow def _update_shadow(self, *args): if self.elevation > 0: # Set shadow size. width = self.width * 2 height = self.height * 2 x = self.center_x - width / 2 self._soft_shadow_size = (width, height) self._hard_shadow_size = (width, height) # Set ``soft_shadow`` parameters. y = self.center_y - height / 2 - dp(0.1 * 1.5 ** self._elevation) self._soft_shadow_pos = (x, y) self._soft_shadow_a = 0.1 * 1.1 ** self._elevation if hasattr(self, "_shadow"): self._soft_shadow_texture = self._shadow.textures[ str(int(round(self._elevation))) ] # Set ``hard_shadow`` parameters. y = self.center_y - height / 2 - dp(0.5 * 1.18 ** self._elevation) self._hard_shadow_pos = (x, y) self._hard_shadow_a = 0.4 * 0.9 ** self._elevation if hasattr(self, "_shadow"): self._hard_shadow_texture = self._shadow.textures[ str(int(round(self._elevation))) ] else: self._soft_shadow_a = 0 self._hard_shadow_a = 0