Поделиться через


Визуальные элементы композиции

Визуальные элементы композиции составляют структуру визуального дерева, на которую используются и строят все другие функции API композиции Microsoft.UI.Composition. API позволяет разработчикам определять и создавать один или несколько визуальных объектов, каждый из которых представляет один узел в визуальном дереве.

Визуальные элементы

Существует несколько типов визуальных элементов, составляющих структуру визуального дерева, а также базовый класс кисти с несколькими подклассами, влияющими на содержимое визуального элемента:

  • Визуальный — базовый объект, большинство свойств здесь и наследуются другими визуальными объектами.
  • ContainerVisual — производный от Visual и добавляет возможность создавать дочерние элементы.
    • SpriteVisual — производный от ContainerVisual. Имеет возможность связать кисть, чтобы визуальный элемент может отрисовывать пиксели, включая изображения, эффекты или сплошной цвет.
    • LayerVisual — производный от ContainerVisual. Дочерние элементы визуализации сведены в один слой.
    • ShapeVisual — производный от ContainerVisual. Узел визуального дерева, который является корнем CompositionShape.
    • RedirectVisual — производный от ContainerVisual. Визуальный элемент получает его содержимое из другого визуального элемента.
    • SceneVisual — производный от ContainerVisual. Визуальный элемент контейнера для узлов трехмерной сцены.

Содержимое и эффекты можно применить к SpriteVisuals с помощью CompositionBrush и его подклассов, включая CompositionColorBrush, CompositionSurfaceBrush и CompositionEffectBrush. Дополнительные сведения о кистях см. в разделе Обзор CompositionBrush.

Образец CompositionVisual

Здесь мы рассмотрим некоторый пример кода WinUI 3, демонстрирующий три различных типа визуальных элементов, перечисленных ранее. Хотя этот пример не охватывает такие понятия, как анимация или более сложные эффекты, он содержит стандартные блоки, которые используют все эти системы. (Полный пример кода указан в конце этой статьи.)

В примере есть ряд одноцветных квадратов, которые можно щелкнуть и перетащить по экрану. Когда по квадрату щелкают, он выходит на передний план, поворачивается на 45 градусов и становится непрозрачным при перемещении.

В этом разделе показано несколько основных понятий для работы с API, включая следующие:

  • Создание компостора
  • Создание SpriteVisual с помощью CompositionColorBrush
  • Обрезка визуального элемента
  • Поворот визуального элемента
  • Настройка непрозрачности
  • Изменение положения Visual в коллекции.

Создание compositor

Создание Compositor и его хранение в переменной для использования в качестве фабрики является простой задачей. В приложении WinUI вы обычно извлекаете композитор из XAML-элемента, который уже подключен к визуальному дереву.

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

Если вам нужен композитор и у вас нет доступного элемента UIElement, вместо этого можно использовать CompositionTarget.GetCompositorForCurrentThread().

Создание SpriteVisual и ColorBrush

С помощью Compositor легко создавать объекты при необходимости, например SpriteVisual и CompositionColorBrush:

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

Хотя это лишь несколько строк кода, он демонстрирует мощную концепцию: объекты SpriteVisual являются сердцем системы эффектов. SpriteVisual обеспечивает большую гибкость и взаимодействие с цветом, изображением и эффектом. SpriteVisual — это один визуальный тип, который может заполнить прямоугольник 2D кистью, в данном случае сплошным цветом.

Обрезка визуального элемента

Компонатор также можно использовать для создания клипов для визуального элемента. Ниже приведен пример использования InsetClip для обрезки каждой стороны визуального элемента:

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

Как и другие объекты в API, InsetClip может применять анимацию к его свойствам.

Поворот клипа

Визуальный элемент можно преобразовать с помощью поворота. Обратите внимание, что RotationAngle поддерживает как радианы, так и градусы. Он по умолчанию использует радианы, но легко указать градусы, как показано в следующем фрагменте кода:

child.RotationAngleInDegrees = 45.0f;

Поворот — это всего лишь один пример набора компонентов преобразования, предоставляемых API, чтобы упростить эти задачи. Другие включают Offset, Scale, Orientation, RotationAxis и 4x4 TransformMatrix.

Настройка непрозрачности

Настройка непрозрачности визуального элемента — это простая операция с помощью значения с плавающей запятой. Например, в примере все квадраты начинаются с непрозрачности .8:

visual.Opacity = 0.8f;

Как и поворот, свойство Opacity может быть анимировано.

Изменение положения элемента Visual в коллекции

API композиции позволяет изменить положение объекта Visual в коллекции VisualCollection несколькими способами. Его можно поместить над другим визуальным элементом с помощью InsertAbove, разместить ниже с помощью InsertBelow, переместить в верхнюю часть с помощью InsertAtTop или переместить в нижнюю часть с помощью InsertAtBottom.

В примере нажатый визуальный элемент отсортирован наверх:

parent.Children.InsertAtTop(_currentVisual);

Полный пример

В полном примере WinUI все описанные выше понятия используются вместе для создания и обхода простого дерева визуальных объектов для изменения прозрачности без пользовательской отрисовки DirectX. В этом примере используется WinUI Grid для ввода с указателя, и дерево композиции размещается с помощью 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;
        }
    }
}

Если необходимо инициализировать компостор перед наличием элемента узла XAML, используйте CompositionTarget.GetCompositorForCurrentThread() и прикрепите визуальные элементы к пользовательскому интерфейсу, когда элемент узла становится доступным.