Compartir a través de


Visuales de composición

Los objetos visuales de composición componen la estructura de árbol visual en la que usan y se basan todas las demás características de la API Microsoft.UI.Composition. La API permite a los desarrolladores definir y crear uno o varios objetos visuales, cada uno de los cuales representa un solo nodo en un árbol visual.

Visuals

Hay varios tipos de objetos visuales que componen la estructura del árbol visual más una clase de pincel base con varias subclases que afectan al contenido de un objeto visual:

Puede aplicar contenido y efectos a SpriteVisuals mediante CompositionBrush y sus subclases, incluidos CompositionColorBrush, CompositionSurfaceBrush y CompositionEffectBrush. Para obtener más información sobre los pinceles, consulte CompositionBrush Overview(Introducción a CompositionBrush).

El ejemplo CompositionVisual

Aquí veremos código de ejemplo de WinUI 3 que muestra los tres tipos visuales diferentes enumerados anteriormente. Aunque este ejemplo no cubre conceptos como animaciones o efectos más complejos, contiene los bloques de creación que usan todos esos sistemas. (El código de ejemplo completo aparece al final de este artículo).

En la muestra hay una serie de cuadrados de color sólidos en los que se puede hacer clic y arrastrar sobre la pantalla. Cuando se hace clic en un cuadrado, este se pondrá al frente, girará 45 grados y se volverá opaco mientras se arrastra.

Esto muestra una serie de conceptos básicos para trabajar con la API, entre los que se incluyen:

  • Creación de un compositor
  • Crear un SpriteVisual con CompositionColorBrush
  • Recorte del objeto visual
  • Rotación del objeto visual
  • Establecer opacidad
  • Cambiar la posición del objeto visual en la colección.

Creación de un compositor

Crear un compositor y almacenarlo en una variable para su uso como generador es una tarea sencilla. En una aplicación WinUI, normalmente se recupera el compositor de un elemento XAML que ya está conectado al árbol visual:

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

Si necesita un compositor y no tiene un UIElement disponible, puede usar CompositionTarget.GetCompositorForCurrentThread() en su lugar.

Crear un SpriteVisual y un Pincel de Color

El uso del Compositor es fácil para crear objetos siempre que los necesite, como un SpriteVisual y un CompositionColorBrush:

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

Aunque esta es solo unas pocas líneas de código, muestra un concepto eficaz: los objetos SpriteVisual son el corazón del sistema de efectos. SpriteVisual permite una gran flexibilidad e interacción en la creación de colores, imágenes y efectos. SpriteVisual es un único tipo visual que puede rellenar un rectángulo 2D con un pincel, en este caso, un color sólido.

Recortar un visual

El compositor también se puede usar para crear clips en un objeto Visual. A continuación se muestra un ejemplo del ejemplo de uso de InsetClip para recortar cada lado del objeto visual:

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

Al igual que otros objetos de la API, InsetClip puede tener animaciones aplicadas a sus propiedades.

Rotación de un clip

Un objeto Visual se puede transformar con una rotación. Tenga en cuenta que RotationAngle admite radianes y grados. El valor predeterminado es radianes, pero es fácil especificar grados como se muestra en el fragmento de código siguiente:

child.RotationAngleInDegrees = 45.0f;

La rotación es solo un ejemplo de un conjunto de componentes de transformación proporcionados por la API para facilitar estas tareas. Otros incluyen Offset, Scale, Orientation, RotationAxis y 4x4 TransformMatrix.

Establecer opacidad

Establecer la opacidad de un objeto visual es una operación sencilla mediante un valor float. Por ejemplo, en el ejemplo todos los cuadrados comienzan en la opacidad .8:

visual.Opacity = 0.8f;

Al igual que la rotación, la propiedad Opacity se puede animar.

Cambio de la posición del objeto visual en la colección

El Composition API permite cambiar la posición de un Visual en una VisualCollection de varias maneras. Se puede colocar encima de otro objeto visual con InsertAbove, situado debajo con InsertBelow, movido a la parte superior con InsertAtTop o la parte inferior con InsertAtBottom.

En el ejemplo, un Visual en el que se ha hecho clic se ordena al principio.

parent.Children.InsertAtTop(_currentVisual);

Ejemplo completo

En el ejemplo completo de WinUI, todos los conceptos anteriores se usan juntos para construir y recorrer un árbol simple de objetos Visuales para cambiar la opacidad sin representación personalizada de DirectX. En el ejemplo se usa WinUI Grid para la entrada de puntero y se aloja el árbol de composición con 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 necesitas inicializar un compositor antes de tener un elemento host XAML, usa CompositionTarget.GetCompositorForCurrentThread() y adjunta los objetos visuales a la interfaz de usuario cuando un elemento host esté disponible.