다음을 통해 공유


구성 비주얼

컴포지션 비주얼은 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에 연결하십시오.