Partilhar via


Visuais de composição

Composition Visuals compõem a estrutura de árvore visual que todas as outras funcionalidades da API Microsoft.UI.Composition utilizam e sobre a qual se baseiam. A API permite aos programadores definir e criar um ou vários objetos visuais, cada um representando um único nó numa árvore visual.

Elementos visuais

Existem vários tipos de visualizações que compõem a estrutura da árvore visual, juntamente com uma classe base de pincel e múltiplas subclasses que afetam o conteúdo de uma visualização.

Pode aplicar conteúdo e efeitos ao SpriteVisuals usando o CompositionBrush e as suas subclasses, incluindo o CompositionColorBrush, CompositionSurfaceBrush e CompositionEffectBrush. Para saber mais sobre pincéis, consulte Composition Brush Overview.

A Amostra Visual da Composição.

Aqui, vamos analisar algum código de exemplo do WinUI 3 que demonstra os três tipos visuais diferentes listados anteriormente. Embora este exemplo não cubra conceitos como animações ou efeitos mais complexos, contém os blocos de construção que todos esses sistemas utilizam. (O código de exemplo completo está listado no final deste artigo.)

No exemplo há vários quadrados de cor sólida que podem ser clicados e arrastados pelo ecrã. Quando um quadrado é clicado, ele vem para a frente, roda 45 graus e torna-se opaco quando arrastado.

Isto mostra vários conceitos básicos para trabalhar com a API, incluindo:

  • Criação de um compositor
  • Criar um SpriteVisual com um CompositionColorBrush
  • Recorte visual
  • Rodar a Visão
  • Definir Opacidade
  • Alterar a posição do Visual na coleção.

Criação de um Compositor

Criar um Compositor e armazená-lo numa variável para usar como fábrica é uma tarefa simples. Numa aplicação WinUI, costuma-se obter o compositor a partir de um elemento XAML que já está conectado à árvore visual:

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

Se precisares de um compositor e não tiveres um UIElement disponível, podes usar CompositionTarget.GetCompositorForCurrentThread() em vez disso.

Criação de um SpriteVisual e de um ColorBrush

Usando o Compositor é fácil criar objetos sempre que precisares, como um SpriteVisual e um CompositionColorBrush:

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

Embora isto seja apenas algumas linhas de código, demonstra um conceito poderoso: os objetos SpriteVisual são o coração do sistema de efeitos. O SpriteVisual permite grande flexibilidade e interação na criação de cor, imagem e efeitos. O SpriteVisual é um único tipo visual que pode preencher um retângulo 2D com um pincel, neste caso, uma cor sólida.

Recortar um visual

O Compositor também pode ser usado para criar clipes para um Visual. Abaixo está um exemplo do exemplo de utilização do InsetClip para cortar cada lado do visual:

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

Tal como outros objetos na API, o InsetClip pode ter animações aplicadas às suas propriedades.

Rodar um Clipe

Um elemento visual pode ser transformado por rotação. Note que o RotationAngle suporta tanto radianos como graus. Por defeito, são usados radianos, mas é fácil especificar graus, como mostrado no seguinte exemplo:

child.RotationAngleInDegrees = 45.0f;

A rotação é apenas um exemplo de um conjunto de componentes de transformação fornecidos pela API para facilitar estas tarefas. Outros incluem Offset, Scale, Orientation, RotationAxis e 4x4 TransformMatrix.

Definição de Opacidade

Definir a opacidade de um visual é uma operação simples usando um valor flutuante. Por exemplo, na amostra todos os quadrados começam com 0,8 de opacidade:

visual.Opacity = 0.8f;

Tal como a rotação, a propriedade Opacidade pode ser animada.

Alterando a posição do Visual na coleção

A API de Composições permite que a posição de um Visual numa VisualCollection seja alterada de várias formas. Pode ser colocado acima de outro Visual com InsertAbove, colocado abaixo com InsertBelow, movido para o topo com InsertAtTop, ou para baixo com InsertAtBottom.

No exemplo, um Visual que foi clicado é ordenado no topo:

parent.Children.InsertAtTop(_currentVisual);

Exemplo completo

No exemplo completo do WinUI, todos os conceitos acima são usados em conjunto para construir e percorrer uma árvore simples de objetos Visuais para alterar a opacidade sem renderização personalizada DirectX. O exemplo usa um WinUI Grid para entrada de ponteiros e hospeda a árvore de composição com 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;
        }
    }
}

Se precisares de inicializar um compositor antes de teres um elemento host XAML, usa CompositionTarget.GetCompositorForCurrentThread() e depois anexa os visuais à interface de utilizador quando um elemento host estiver disponível.