Partager via


Visuels de composition

Les visuels de composition composent la structure de l’arborescence visuelle sur laquelle toutes les autres fonctionnalités de l’API Microsoft.UI.Composition utilisent et s’appuient. L’API permet aux développeurs de définir et de créer un ou plusieurs objets visuels, chacun représentant un nœud unique dans une arborescence visuelle.

Objets visuels

Il existe plusieurs types visuels qui composent la structure de l’arborescence visuelle ainsi qu’une classe de pinceau de base avec plusieurs sous-classes qui affectent le contenu d’un visuel :

Vous pouvez appliquer du contenu et des effets à SpriteVisuals à l’aide de CompositionBrush et de ses sous-classes, notamment CompositionColorBrush, CompositionSurfaceBrush et CompositionEffectBrush. Pour en savoir plus sur les pinceaux, consultez La vue d’ensemble de CompositionBrush.

L’exemple CompositionVisual

Ici, nous allons examiner certains exemples de code WinUI 3 qui illustrent les trois types visuels répertoriés précédemment. Bien que cet exemple ne couvre pas les concepts tels que les animations ou les effets plus complexes, il contient les blocs de construction que tous ces systèmes utilisent. (L’exemple de code complet est répertorié à la fin de cet article.)

Dans l’exemple, il y a plusieurs carrés de couleur unie qui peuvent être cliqués et déplacés sur l’écran. Lorsqu’un carré est cliqué dessus, il arrive à l’avant, pivote à 45 degrés et devient opaque quand il est traîné.

Cela montre un certain nombre de concepts de base pour l’utilisation de l’API, notamment :

  • Création d’un compositeur
  • Création d’un SpriteVisual avec un CompositionColorBrush
  • Découpage du visuel
  • Rotation du visuel
  • Réglage de l’opacité
  • Modification de la position du visuel dans la collection.

Création d’un compositeur

La création d’un compositor et son stockage dans une variable à utiliser en tant que factory est une tâche simple. Dans une application WinUI, vous récupérez généralement le compositeur à partir d’un élément XAML déjà connecté à l’arborescence visuelle :

Compositor compositor = ElementCompositionPreview.GetElementVisual(MyHost).Compositor;

Si vous avez besoin d’un compositeur et que vous n’avez pas d’UIElement disponible, vous pouvez utiliser CompositionTarget.GetCompositorForCurrentThread() à la place.

Création d’un SpriteVisual et d’un ColorBrush

À l’aide du Compositor , il est facile de créer des objets chaque fois que vous en avez besoin, tels qu’un SpriteVisual et un CompositionColorBrush :

var visual = _compositor.CreateSpriteVisual();
visual.Brush = _compositor.CreateColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));

Bien qu’il ne s’agit que de quelques lignes de code, il illustre un concept puissant : les objets SpriteVisual sont le cœur du système d’effets. Le SpriteVisual permet une grande flexibilité et une interaction en couleur, en image et en création d’effet. SpriteVisual est un type visuel unique qui peut remplir un rectangle 2D avec un pinceau, dans ce cas, une couleur unie.

Découpage d’un visuel

Le compositeur peut également être utilisé pour créer des clips pour un visuel. Voici un exemple de l’exemple d’utilisation de InsetClip pour découper chaque côté du visuel :

var clip = _compositor.CreateInsetClip();
clip.LeftInset = 1.0f;
clip.RightInset = 1.0f;
clip.TopInset = 1.0f;
clip.BottomInset = 1.0f;
_currentVisual.Clip = clip;

Comme d’autres objets de l’API, InsetClip peut avoir des animations appliquées à ses propriétés.

Rotation d’un clip

Un visuel peut être transformé avec une rotation. Notez que RotationAngle prend en charge les radians et les degrés. Par défaut, il utilise les radians, mais il est facile de spécifier des degrés, comme le montre l'extrait de code suivant :

child.RotationAngleInDegrees = 45.0f;

La rotation n’est qu’un exemple d’ensemble de composants de transformation fournis par l’API pour faciliter ces tâches. D’autres incluent Offset, Scale, Orientation, RotationAxis et 4x4 TransformMatrix.

Réglage de l’opacité

La définition de l’opacité d’un visuel est une opération simple à l’aide d’une valeur float. Par exemple, dans l’exemple, tous les carrés commencent à l’opacité .8 :

visual.Opacity = 0.8f;

Comme la rotation, la propriété Opacity peut être animée.

Modification de la position du visuel dans la collection

L’API Composition permet de modifier la position d’un visuel dans un VisualCollection de plusieurs façons. Il peut être placé au-dessus d’un autre visuel avec InsertAbove, placé ci-dessous avec InsertBelow, déplacé vers le haut avec InsertAtTop ou le bas avec InsertAtBottom.

Dans l’exemple, un visuel qui a été cliqué est placé en haut :

parent.Children.InsertAtTop(_currentVisual);

Exemple complet

Dans l’exemple WinUI complet, tous les concepts ci-dessus sont utilisés ensemble pour construire et parcourir une arborescence simple d’objets visuels pour modifier l’opacité sans rendu DirectX personnalisé. L’exemple utilise un WinUI Grid pour l’entrée de pointeur et héberge l’arborescence de composition avec ElementCompositionPreview.

<Page
    x:Class="CompositionVisualSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Grid
        x:Name="MyHost"
        Background="Transparent"
        PointerPressed="OnPointerPressed"
        PointerMoved="OnPointerMoved"
        PointerReleased="OnPointerReleased" />
</Page>
using System;
using System.Numerics;
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Hosting;
using Microsoft.UI.Xaml.Input;
using Windows.Foundation;
using Windows.UI;

namespace CompositionVisualSample
{
    public sealed partial class MainPage : Page
    {
        private readonly Random _random = new();
        private Compositor _compositor;
        private ContainerVisual _root;
        private ContainerVisual _currentVisual;
        private Vector2 _offsetBias;
        private bool _dragging;

        public MainPage()
        {
            InitializeComponent();
            Loaded += OnLoaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            if (_root != null)
            {
                return;
            }

            _compositor = ElementCompositionPreview.GetElementVisual(MyHost).Compositor;
            _root = _compositor.CreateContainerVisual();
            ElementCompositionPreview.SetElementChildVisual(MyHost, _root);

            for (int index = 0; index < 20; index++)
            {
                _root.Children.InsertAtTop(CreateChildElement());
            }
        }

        private ContainerVisual CreateChildElement()
        {
            var element = _compositor.CreateContainerVisual();
            element.Size = new Vector2(100.0f, 100.0f);

            var maxX = (float)Math.Max(MyHost.ActualWidth - element.Size.X, 0.0);
            var maxY = (float)Math.Max(MyHost.ActualHeight - element.Size.Y, 0.0);
            element.Offset = new Vector3(
                (float)(_random.NextDouble() * maxX),
                (float)(_random.NextDouble() * maxY),
                0.0f);

            var outer = _compositor.CreateSpriteVisual();
            outer.Size = new Vector2(100.0f, 100.0f);
            outer.Brush = _compositor.CreateColorBrush(Colors.White);
            element.Children.InsertAtTop(outer);

            var inner = _compositor.CreateSpriteVisual();
            inner.Offset = new Vector3(3.0f, 3.0f, 0.0f);
            inner.Size = new Vector2(94.0f, 94.0f);

            byte red = (byte)(0xFF * (0.2f + (_random.NextDouble() / 0.8f)));
            byte green = (byte)(0xFF * (0.2f + (_random.NextDouble() / 0.8f)));
            byte blue = (byte)(0xFF * (0.2f + (_random.NextDouble() / 0.8f)));
            inner.Brush = _compositor.CreateColorBrush(Color.FromArgb(0xFF, red, green, blue));
            outer.Children.InsertAtTop(inner);

            element.Opacity = 0.8f;
            return element;
        }

        private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
        {
            Point position = e.GetCurrentPoint(MyHost).Position;
            _currentVisual = null;

            foreach (var child in _root.Children)
            {
                Vector3 offset = child.Offset;
                Vector2 size = child.Size;

                if ((position.X >= offset.X) &&
                    (position.X < offset.X + size.X) &&
                    (position.Y >= offset.Y) &&
                    (position.Y < offset.Y + size.Y))
                {
                    _currentVisual = child as ContainerVisual;
                    _offsetBias = new Vector2(
                        (float)(offset.X - position.X),
                        (float)(offset.Y - position.Y));
                }
            }

            if (_currentVisual != null)
            {
                ContainerVisual parent = (ContainerVisual)_currentVisual.Parent;
                parent.Children.Remove(_currentVisual);
                parent.Children.InsertAtTop(_currentVisual);
            }
        }

        private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
        {
            if (_currentVisual == null)
            {
                return;
            }

            if (!_dragging)
            {
                _currentVisual.Opacity = 1.0f;

                foreach (var child in _currentVisual.Children)
                {
                    child.RotationAngleInDegrees = 45.0f;
                    child.CenterPoint = new Vector3(_currentVisual.Size.X / 2, _currentVisual.Size.Y / 2, 0.0f);
                    break;
                }

                var clip = _compositor.CreateInsetClip();
                clip.LeftInset = 1.0f;
                clip.RightInset = 1.0f;
                clip.TopInset = 1.0f;
                clip.BottomInset = 1.0f;
                _currentVisual.Clip = clip;

                _dragging = true;
            }

            Point position = e.GetCurrentPoint(MyHost).Position;
            _currentVisual.Offset = new Vector3(
                (float)(position.X + _offsetBias.X),
                (float)(position.Y + _offsetBias.Y),
                0.0f);
        }

        private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
        {
            if (_currentVisual == null)
            {
                return;
            }

            if (_dragging)
            {
                foreach (var child in _currentVisual.Children)
                {
                    child.RotationAngle = 0.0f;
                    child.CenterPoint = new Vector3(0.0f, 0.0f, 0.0f);
                    break;
                }

                _currentVisual.Opacity = 0.8f;
                _currentVisual.Clip = null;
                _dragging = false;
            }

            _currentVisual = null;
        }
    }
}

Si vous devez initialiser un compositeur avant d’avoir un élément hôte XAML, utilisez CompositionTarget.GetCompositorForCurrentThread() et attachez les visuels à votre interface utilisateur lorsqu’un élément hôte devient disponible.