구성 비주얼

컴포지션 비주얼은 Microsoft.UI.Composition API의 다른 모든 기능이 사용하고 토대로 하는 시각적 트리 구조를 형성합니다. 개발자는 API를 사용하여 시각적 트리의 단일 노드를 나타내는 하나 이상의 시각적 개체를 정의하고 만들 수 있습니다.

시각적 요소

시각적 트리 구조를 구성하는 여러 시각적 개체 형식과 시각적 개체의 내용에 영향을 주는 여러 서브클래스가 있는 기본 브러시 클래스가 있습니다.

  • Visual – 기본 개체, 대부분의 속성이 여기에 있으며 다른 시각적 개체에 의해 상속됩니다.
  • ContainerVisualVisual에서 파생되며 자식을 생성하는 기능을 추가합니다.
    • SpriteVisualContainerVisual에서 파생됩니다. Visual은 브러시를 연관시켜 이미지, 효과 또는 단색을 포함한 픽셀을 렌더링할 수 있는 기능이 있습니다.
    • LayerVisualContainerVisual에서 파생됩니다. 시각적 개체의 자식은 하나의 계층으로 병합됩니다.
    • ShapeVisualContainerVisual에서 파생됩니다. CompositionShape의 루트인 시각적 트리 노드입니다.
    • RedirectVisualContainerVisual에서 파생됩니다. 시각적 개체는 다른 시각적 개체에서 해당 콘텐츠를 가져옵니다.
    • SceneVisualContainerVisual에서 파생됩니다. 3D 장면의 노드를 위한 컨테이너 시각화입니다.

CompositionBrush 및 CompositionColorBrush, CompositionSurfaceBrushCompositionEffectBrush를 비롯한 하위 클래스를 사용하여 SpriteVisuals에 콘텐츠 및 효과를 적용할 수 있습니다. 브러시에 대한 자세한 내용은 CompositionBrush 개요를 참조하세요.

CompositionVisual 샘플

여기서는 이전에 나열된 세 가지 시각적 개체 형식을 보여 주는 일부 WinUI 3 샘플 코드를 살펴보겠습니다. 이 샘플에서는 애니메이션 또는 더 복잡한 효과와 같은 개념을 다루지 않지만 모든 시스템에서 사용하는 구성 요소를 포함합니다. (전체 샘플 코드는 이 문서의 끝에 나열됩니다.)

샘플에는 화면에 대해 클릭하고 끌 수 있는 여러 단색 사각형이 있습니다. 사각형을 클릭할 때 앞으로 이동하여 45도 회전하고 끌면 불투명해집니다.

다음을 포함하여 API를 사용하기 위한 다양한 기본 개념을 보여 줍니다.

  • 컴포지터 생성
  • CompositionColorBrush를 사용하여 SpriteVisual 만들기
  • 시각 클리핑
  • 시각 요소 회전
  • 불투명도 설정
  • 컬렉션 내 시각적 요소의 위치를 변경

Compositor 만들기

Compositor를 만들고 팩터리로 사용할 변수에 저장하는 것은 간단한 작업입니다. WinUI 앱에서는 일반적으로 시각적 트리에 이미 연결된 XAML 요소에서 작성자를 검색합니다.

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

대체할 작성기가 필요하고 UIElement를 사용할 수 없는 경우 CompositionTarget.GetCompositorForCurrentThread()를 대신 사용할 수 있습니다.

SpriteVisual 및 ColorBrush 만들기

Compositor를 사용하면 SpriteVisualCompositionColorBrush와 같이 필요할 때마다 개체를 쉽게 만들 수 있습니다.

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

이는 몇 줄의 코드에 불과하지만 강력한 개념을 보여 줍니다. SpriteVisual 개체는 효과 시스템의 핵심입니다. SpriteVisual을 사용하면 색상, 이미지 및 효과 생성에서 뛰어난 유연성과 상호 작용을 할 수 있습니다. SpriteVisual은 2D 사각형을 브러시(이 경우 단색)로 채울 수 있는 단일 시각적 형식입니다.

그래픽 잘라내기

Compositor를 사용하여 비주얼에 클립을 생성할 수도 있습니다. 다음은 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에서 제공하는 변환 구성 요소 집합의 한 예일 뿐입니다. 오프셋, 배율, 방향, RotationAxis 및 4x4 TransformMatrix 등이 있습니다.

불투명도 설정

시각적 개체의 불투명도를 설정하는 것은 부동 소수 자릿수 값을 사용하는 간단한 작업입니다. 예를 들어 샘플에서 모든 사각형은 .8 불투명도에서 시작합니다.

visual.Opacity = 0.8f;

회전과 마찬가지로 Opacity 속성에 애니메이션 효과를 적용할 수 있습니다.

컬렉션에서 그 비주얼의 위치 변경

컴퍼지션 API를 사용하면 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()을(를) 먼저 사용한 후, 호스트 요소가 사용 가능해지면 시각적 개체를 UI에 연결하십시오.