Matériel Python. Cartes personnalisées avec effets OpenGL


Salutations, chers amoureux et experts en Python!

Dans cet article, je vais vous montrer comment appliquer des effets OpenGL à vos cartes personnalisées si vous utilisez des outils multiplateformes tels que le cadre Kivy et la bibliothèque de conception de matériaux pour ce cadre, KivyMD , dans vos applications . Allons-y!

KivyMD a un composant standard MDCard - la classe de base pour créer diverses cartes personnalisées ( spécifications Material Design, cartes ). Si vous n'entrez pas dans les détails, alors sous le capot de la MDCard se trouve un BoxLayout ordinaire - un conteneur qui vous permet de placer d'autres widgets dans une orientation verticale ou horizontale. Autrement dit, si vous aviez besoin de créer une sorte de carte, par exemple des informations sur l'utilisateur, vous le faites vous-même. MDCard n'implémente que ripple_behavior , touch_behavior et cast shadows:

Voici un exemple de programme qui affiche une carte vierge:

from kivy.lang import Builder

from kivymd.app import MDApp

KV = '''
Screen:  #  

    MDCard:  # 
        #    
        size_hint: .6, .5
        pos_hint: {"center_x": .5, "center_y": .5}
'''


class TestCard(MDApp):
    def build(self):
        return Builder.load_string(KV)

TestCard().run()

Résultat:


Cela semble assez simple. Mais que se passe-t-il si nous voulons une belle carte avec un effet de flou en cas de réception du focus? Comme, par exemple, dans l'application Flutter UI Designs :


Faudra le faire vous-même! De plus, cela n'a rien de compliqué. Créez d'abord la classe de base de la future carte:

class RestaurantCard(MDCard):
    source = StringProperty()  #     
    shadow = StringProperty()  #    -
    text = StringProperty()  #  

L'image principale de la carte:


Image d'ombre:


Nous allons maintenant remplir la carte avec des composants dont nous avons déterminé les propriétés en utilisant le langage DSL spécial Kv-Language , conçu pour une conception pratique des dispositions d'interface:

<RestaurantCard>
    elevation: 12

    RelativeLayout:

        # ,       .
        FitImage:  #   
            source: root.source

        FitImage:  # -
            source: root.shadow
            size_hint_y: None
            height: "120dp"

        MDLabel:  #  
            text: root.text
            markup: True
            size_hint_y: None
            height: self.texture_size[1]
            x: "10dp"
            y: "10dp"
            theme_text_color: "Custom"
            text_color: 1, 1, 1, 1

Nous avons mis le widget RelativeLayout sur la carte , ce qui nous permet de mélanger les composants les uns au-dessus des autres de cette manière:


Tout d'abord, nous avons placé l'image principale, mis une ombre et du texte sur le dessus. Maintenant, si nous exécutons notre code:

from kivy.lang import Builder
from kivy.properties import StringProperty

from kivymd.app import MDApp
from kivymd.uix.card import MDCard

KV = """
<RestaurantCard>
    elevation: 12

    RelativeLayout:

        FitImage:
            source: root.source

        FitImage:
            source: root.shadow
            size_hint_y: None
            height: "120dp"

        MDLabel:
            text: root.text
            markup: True
            size_hint_y: None
            height: self.texture_size[1]
            x: "10dp"
            y: "10dp"
            theme_text_color: "Custom"
            text_color: 1, 1, 1, 1


Screen:

    RestaurantCard:
        text: "[size=23][b]Restaurant[/b][/size]\\nTuborg Havnepark 15, Hellerup 2900 Denmark"
        shadow: "shadow-black.png"
        source: "restourant.jpg"
        pos_hint: {"center_x": .5, "center_y": .5}
        size_hint: .7, .5
"""


class RestaurantCard(MDCard):
    source = StringProperty()
    text = StringProperty()
    shadow = StringProperty()


class BlurCard(MDApp):
    def build(self):
        return Builder.load_string(KV)

BlurCard().run()

... on obtient le résultat:


Et le résultat, bien sûr, est loin d'être celui attendu, car nous ne verrons aucun effet de flou ou bords arrondis sur la carte. Commençons par l'effet de flou. Kivy a un widget EffectWidget standard qui peut appliquer divers effets graphiques à ses enfants. Il fonctionne en rendant les instances Fbo à l'aide de shaders OpenGL personnalisés. Nous devons appliquer l'effet de flou à l'image principale et à l'ombre sur la carte. Par conséquent, nous devons mettre leurs composants dans le widget EffectWidget:

#:import effect kivy.uix.effectwidget.EffectWidget
#:import HorizontalBlurEffect kivy.uix.effectwidget.HorizontalBlurEffect

<RestaurantCard>

    ...

    RelativeLayout:
        
        #   ,      .
        EffectWidget:
            #  .
            effects: (HorizontalBlurEffect(size=root.blur),)

            FitImage:
                source: root.source

            FitImage:
                source: root.shadow
                size_hint_y: None
                height: "120dp"

    ...    

Ajoutez un champ pour la valeur du degré d'effet de flou:

class RestaurantCard(MDCard):
    ...
    blur = NumericProperty(8)

Nous commençons et voyons:


Lorsque vous survolez (si c'est un ordinateur de bureau) ou appuyez sur (si c'est un mobile), rien ne se passe. Pour que la carte réponde à l'événement on_focus, nous devons activer la lecture de cet événement dans les propriétés de la règle RestaurantCard et affecter des méthodes qui seront exécutées lorsque cet événement sera enregistré:

#:import Animation kivy.animation.Animation

<RestaurantCard>
    focus_behavior: True  #    on_focus
    # ,       .
    #   Animation,    .
    on_enter: Animation(blur=0, d=0.3).start(self)
    on_leave: Animation(blur=8, d=0.3).start(self)

Déjà mieux:


Pour couper les coins de la carte, j'ai décidé d'appliquer un pochoir (stencil) au widget EffectWidget:

#:import Stencil kivymd.uix.graphics.Stencil


#   ,   EffectWidget  Stencil.
<Effect@EffectWidget+Stencil>
    radius: [20,]


<RestaurantCard>
    ...

    RelativeLayout:

        Effect:
            ...

Et maintenant, tout fonctionne comme prévu:


Exemple de code complet
from kivy.lang import Builder
from kivy.properties import StringProperty, NumericProperty

from kivymd.app import MDApp
from kivymd.uix.card import MDCard

KV = """
#:import Stencil kivymd.uix.graphics.Stencil
#:import Animation kivy.animation.Animation
#:import effect kivy.uix.effectwidget.EffectWidget
#:import HorizontalBlurEffect kivy.uix.effectwidget.HorizontalBlurEffect


<Effect@EffectWidget+Stencil>
    radius: [20,]


<RestaurantCard>
    md_bg_color: 0, 0, 0, 0
    elevation: 12
    focus_behavior: True
    on_enter: Animation(blur=0, d=0.3).start(self)
    on_leave: Animation(blur=8, d=0.3).start(self)
    radius: [20,]

    RelativeLayout:

        Effect:
            effects: (HorizontalBlurEffect(size=root.blur),)

            FitImage:
                source: root.source

            FitImage:
                source: root.shadow
                size_hint_y: None
                height: "120dp"

        MDLabel:
            text: root.text
            markup: True
            size_hint_y: None
            height: self.texture_size[1]
            x: "10dp"
            y: "10dp"
            theme_text_color: "Custom"
            text_color: 1, 1, 1, 1


FloatLayout:

    RestaurantCard:
        text: "[size=23][b]Restaurant[/b][/size]\\nTuborg Havnepark 15, Hellerup 2900 Denmark"
        shadow: "shadow-black.png"
        source: "restourant.jpg"
        pos_hint: {"center_x": .5, "center_y": .5}
        size_hint: .7, .5
"""


class RestaurantCard(MDCard):
    source = StringProperty()
    text = StringProperty()
    shadow = StringProperty()
    blur = NumericProperty(8)


class BlurCard(MDApp):
    def build(self):
        return Builder.load_string(KV)


BlurCard().run()


Enfin, je veux montrer une vidéo dans laquelle deux programmes fonctionnent: l'un, écrit en utilisant le framework Flutter, et le second - en utilisant Kivy et KivyMD. À la fin de l'article, je laisse une enquête dans laquelle vous devez deviner quelle technologie est utilisée et où.


All Articles