Condividi tramite


Composizione visiva

Gli oggetti visivi di composizione costituiscono la struttura ad albero visuale su cui vengono usate e compilate tutte le altre funzionalità dell'API Microsoft.UI.Composition. L'API consente agli sviluppatori di definire e creare uno o più oggetti visivi, ognuno dei quali rappresenta un singolo nodo in una struttura ad albero visuale.

Elementi grafici

Esistono diversi tipi di oggetto visivo che costituiscono la struttura ad albero visuale più una classe pennello di base con più sottoclassi che influiscono sul contenuto di un oggetto visivo:

  • Visual : oggetto di base, la maggior parte delle proprietà è qui e ereditata dagli altri oggetti Visual.
  • ContainerVisual – deriva da Visual e aggiunge la possibilità di creare figli.

È possibile applicare contenuto ed effetti a SpriteVisuals usando CompositionBrush e le relative sottoclassi, tra cui CompositionColorBrush, CompositionSurfaceBrush e CompositionEffectBrush. Per altre informazioni sui pennelli, vedere Cenni preliminari su CompositionBrush.

Esempio di Composition Visuale

In questo caso si esaminerà un codice di esempio winUI 3 che illustra i tre diversi tipi di oggetti visivi elencati in precedenza. Anche se questo esempio non tratta concetti come animazioni o effetti più complessi, contiene i blocchi predefiniti usati da tutti i sistemi. Il codice di esempio completo è elencato alla fine di questo articolo.

Nell'esempio sono presenti diversi quadrati a tinta unita su cui è possibile fare clic e trascinare sullo schermo. Quando si fa clic su un quadrato, si arriva alla parte anteriore, ruota 45 gradi e diventa opaco quando viene trascinato.

Vengono illustrati alcuni concetti di base per l'uso dell'API, tra cui:

  • Creazione di un compositore
  • Creazione di un oggetto SpriteVisual con CompositionColorBrush
  • Ritaglio della visualizzazione
  • Rotazione del display visivo
  • Impostazione dell'Opacità
  • Modifica della posizione dell'oggetto visivo nella raccolta.

Creazione di un compositore

La creazione di un programma di composizione e l'archiviazione in una variabile da usare come factory è un'attività semplice. In un'applicazione WinUI, tipicamente si recupera il compositore da un elemento XAML già connesso alla gerarchia visuale.

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

Se hai bisogno di un programma di composizione e non hai un UIElement disponibile, puoi usare CompositionTarget.GetCompositorForCurrentThread() invece.

Creazione di spriteVisual e ColorBrush

L'uso di Compositor è facile creare oggetti ogni volta che sono necessari, ad esempio SpriteVisual e CompositionColorBrush:

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

Anche se si tratta solo di poche righe di codice, dimostra un concetto potente: gli oggetti SpriteVisual sono il cuore del sistema degli effetti. SpriteVisual consente una grande flessibilità e interazione nel colore, nell'immagine e nella creazione di effetti. SpriteVisual è un singolo tipo di oggetto visivo che può riempire un rettangolo 2D con un pennello, in questo caso un colore a tinta unita.

Ritaglio di un elemento visivo

Il Compositor può anche essere usato per creare clip in un Visual. Di seguito è riportato un esempio dell'uso di InsetClip per tagliare ogni lato dell'oggetto visivo:

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

Analogamente ad altri oggetti nell'API, InsetClip può avere animazioni applicate alle relative proprietà.

Rotazione di un clip

Un oggetto visivo può essere trasformato con una rotazione. Si noti che RotationAngle supporta sia radianti che gradi. Il valore predefinito è radianti, ma è facile specificare i gradi, come illustrato nel frammento di codice seguente:

child.RotationAngleInDegrees = 45.0f;

La rotazione è solo un esempio di un set di componenti di trasformazione forniti dall'API per semplificare queste attività. Altri includono Offset, Scale, Orientation, RotationAxis e TransformMatrix 4x4.

Impostazione dell'Opacità

L'impostazione dell'opacità di un oggetto visivo è un'operazione semplice tramite un valore float. Ad esempio, nell'esempio tutti i quadrati iniziano con .8 opacità:

visual.Opacity = 0.8f;

Analogamente alla rotazione, la proprietà Opacity può essere animata.

Modifica della posizione del Visual nella raccolta

L'API Di composizione consente di modificare la posizione di un oggetto visivo in un oggetto VisualCollection in diversi modi. Può essere posizionato sopra un altro oggetto visivo con InsertAbove, posizionato sotto con InsertBelow, spostato in alto con InsertAtTop o in basso con InsertAtBottom.

Nell'esempio, un oggetto visivo su cui è stato fatto clic viene ordinato in alto:

parent.Children.InsertAtTop(_currentVisual);

Esempio completo

Nell'esempio completo di WinUI, tutti i concetti precedenti vengono usati insieme per costruire e navigare un semplice albero di oggetti Visual per cambiare l'opacità senza un rendering DirectX personalizzato. L'esempio usa un'interfaccia WinUI Grid per l'input del puntatore e ospita l'albero di composizione 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;
        }
    }
}

Se devi inizializzare un programma di composizione prima di avere un elemento host XAML, usa CompositionTarget.GetCompositorForCurrentThread() e associa gli oggetti visivi all'interfaccia utente quando diventa disponibile un elemento host.