컴퍼지션 시각적 계층

컴퍼지션 시각적 개체가 컴퍼지션 API의 다른 모든 기능이 사용하고 빌드하는 시각적 트리 구조를 구성합니다. 개발자들은 API를 사용하여 시각적 트리의 단일 노드를 나타내는 하나 이상의 시각적 개체를 정의하고 만들 수 있습니다.

화면 표시

시각적 트리 구조와 시각적 개체의 콘텐츠에 영향을 미치는 여러 하위 클래스가 있는 기본 브러시 클래스를 구성하는 여러 가지 시각적 형식이 있습니다.

  • Visual - 기준 개체, 대부분의 속성은 여기에 있으며 다른 시각적 개체에 의해 상속됩니다.
  • ContainerVisual - Visual에서 파생되며 자식을 만들 수 있는 기능을 추가합니다.
    • SpriteVisual - ContainerVisual에서 파생됩니다. 시각적 개체가 이미지, 효과, 단색 등의 픽셀을 렌더링할 수 있도록 브러시를 연결하는 기능이 있습니다.
    • LayerVisual - ContainerVisual에서 파생됩니다. 시각적 개체의 자식은 단일 계층으로 결합됩니다.
      (Windows 10, 버전 1607, SDK 14393에 도입되었습니다.)
    • ShapeVisual - ContainerVisual에서 파생됩니다. CompositionShape의 루트인 시각적 트리 노드입니다.
      (Windows 10, 버전 1803, SDK 17134에 도입되었습니다.)
    • RedirectVisual - ContainerVisual에서 파생됩니다. 시각적 개체는 다른 시각적 개체에서 해당 콘텐츠를 가져옵니다.
      (Windows 10, 버전 1809, SDK 17763에 도입되었습니다.)
    • SceneVisual - ContainerVisual에서 파생됩니다. 3D 장면의 노드에 대한 컨테이너 시각적 개체입니다.
      (Windows 10, 버전 1903, SDK 18362에 도입되었습니다.)

CompositionBrushCompositionColorBrush, CompositionSurfaceBrush, CompositionEffectBrush 등의 하위 클래스를 사용하여 SpriteVisuals에 콘텐츠 및 효과를 적용할 수 있습니다. 브러시에 대한 자세한 내용을 보려면 CompositionBrush 개요를 참조하세요.

CompositionVisual 샘플

여기서는 앞에서 나열한 세 가지 시각적 형식을 보여 주는 몇 가지 샘플 코드를 살펴보겠습니다. 이 샘플에서는 애니메이션 또는 더 복잡한 효과 등의 개념은 다루지 않으며 이러한 모든 시스템에서 사용하는 구성 요소만 다룹니다. 전체 샘플 코드는 이 문서의 끝에 나열되어 있습니다.

샘플에는 클릭하여 화면으로 끌 수 있는 단색 사각형이 여러 개 포함되어 있습니다. 정사각형을 클릭하면 전면으로 나와, 45도 회전하며, 주변을 끌면 불투명해집니다.

다음을 포함해 API를 사용하기 위한 다양한 기본 개념이 여기에 표시됩니다.

  • 작성기 만들기
  • CompositionColorBrush를 사용하여 SpriteVisual 만들기
  • 시각적 개체 클리핑
  • 시각적 개체 회전
  • 불투명도 설정
  • 컬렉션에서 시각적 개체의 위치 변경.

작성기 만들기

팩터리로 사용하기 위해 Compositor를 만들고 변수에 저장하는 작업은 매우 간단합니다. 다음 코드 조각은 새 작성기를 만드는 방법을 보여줍니다.

_compositor = new Compositor();

SpriteVisual 및 ColorBrush 만들기

작성기를 사용하면 SpriteVisualCompositionColorBrush 같은 개체를 필요할 때마다 쉽게 만들 수 있습니다.

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;

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

컬렉션에서 시각적 개체의 위치 변경

컴퍼지션 API를 사용하면 VisualCollection의 시각적 개체 위치를 여러 가지 방법으로 변경할 수 있습니다. InsertAbove를 사용하여 다른 시각적 개체 위에 배치할 수도 있고, InsertBelow를 사용하여 아래에 배치할 수도 있고, InsertAtTop을 사용하여 맨 위로 이동할 수도 있고, InsertAtBottom을 사용하여 맨 아래로 이동할 수도 있습니다.

이 샘플에서 Visual을 클릭하면 맨 위로 정렬됩니다.

parent.Children.InsertAtTop(_currentVisual);

전체 예

전체 샘플에서는 위의 모든 개념을 함께 사용하여 XAML, WWA 또는 DirectX를 사용하지 않고 불투명도를 변경하는 간단한 시각적 개체 트리를 구성하고 설명합니다. 이 샘플은 자식 시각적 개체를 만들고 추가하는 방법과 속성을 변경하는 방법을 설명합니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Composition;
using Windows.UI.Core;

namespace compositionvisual
{
    class VisualProperties : IFrameworkView
    {
        //------------------------------------------------------------------------------
        //
        // VisualProperties.Initialize
        //
        // This method is called during startup to associate the IFrameworkView with the
        // CoreApplicationView.
        //
        //------------------------------------------------------------------------------

        void IFrameworkView.Initialize(CoreApplicationView view)
        {
            _view = view;
            _random = new Random();
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.SetWindow
        //
        // This method is called when the CoreApplication has created a new CoreWindow,
        // allowing the application to configure the window and start producing content
        // to display.
        //
        //------------------------------------------------------------------------------

        void IFrameworkView.SetWindow(CoreWindow window)
        {
            _window = window;
            InitNewComposition();
            _window.PointerPressed += OnPointerPressed;
            _window.PointerMoved += OnPointerMoved;
            _window.PointerReleased += OnPointerReleased;
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.OnPointerPressed
        //
        // This method is called when the user touches the screen, taps it with a stylus
        // or clicks the mouse.
        //
        //------------------------------------------------------------------------------

        void OnPointerPressed(CoreWindow window, PointerEventArgs args)
        {
            Point position = args.CurrentPoint.Position;

            //
            // Walk our list of visuals to determine who, if anybody, was selected
            //
            foreach (var child in _root.Children)
            {
                //
                // Did we hit this child?
                //
                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))
                {
                    //
                    // This child was hit. Since the children are stored back to front,
                    // the last one hit is the front-most one so it wins
                    //
                    _currentVisual = child as ContainerVisual;
                    _offsetBias = new Vector2((float)(offset.X - position.X),
                                              (float)(offset.Y - position.Y));
                }
            }

            //
            // If a visual was hit, bring it to the front of the Z order
            //
            if (_currentVisual != null)
            {
                ContainerVisual parent = _currentVisual.Parent as ContainerVisual;
                parent.Children.Remove(_currentVisual);
                parent.Children.InsertAtTop(_currentVisual);
            }
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.OnPointerMoved
        //
        // This method is called when the user moves their finger, stylus or mouse with
        // a button pressed over the screen.
        //
        //------------------------------------------------------------------------------

        void OnPointerMoved(CoreWindow window, PointerEventArgs args)
        {
            //
            // If a visual is selected, drag it with the pointer position and
            // make it opaque while we drag it
            //
            if (_currentVisual != null)
            {
                //
                // Set up the properties of the visual the first time it is
                // dragged. This will last for the duration of the drag
                //
                if (!_dragging)
                {
                    _currentVisual.Opacity = 1.0f;

                    //
                    // Transform the first child of the current visual so that
                    // the image is rotated
                    //
                    foreach (var child in _currentVisual.Children)
                    {
                        child.RotationAngleInDegrees = 45.0f;
                        child.CenterPoint = new Vector3(_currentVisual.Size.X / 2, _currentVisual.Size.Y / 2, 0);
                        break;
                    }

                    //
                    // Clip the visual to its original layout rect by using an inset
                    // clip with a one-pixel margin all around
                    //
                    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 = args.CurrentPoint.Position;
                _currentVisual.Offset = new Vector3((float)(position.X + _offsetBias.X),
                                                    (float)(position.Y + _offsetBias.Y),
                                                    0.0f);
            }
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.OnPointerReleased
        //
        // This method is called when the user lifts their finger or stylus from the
        // screen, or lifts the mouse button.
        //
        //------------------------------------------------------------------------------

        void OnPointerReleased(CoreWindow window, PointerEventArgs args)
        {
            //
            // If a visual was selected, make it transparent again when it is
            // released and restore the transform and clip
            //
            if (_currentVisual != null)
            {
                if (_dragging)
                {
                    //
                    // Remove the transform from the first child
                    //
                    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;
            }
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.Load
        //
        // This method is called when a specific page is being loaded in the
        // application.  It is not used for this application.
        //
        //------------------------------------------------------------------------------

        void IFrameworkView.Load(string unused)
        {

        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.Run
        //
        // This method is called by CoreApplication.Run() to actually run the
        // dispatcher's message pump.
        //
        //------------------------------------------------------------------------------

        void IFrameworkView.Run()
        {
            _window.Activate();
            _window.Dispatcher.ProcessEvents(CoreProcessEventsOption.ProcessUntilQuit);
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.Uninitialize
        //
        // This method is called during shutdown to disconnect the CoreApplicationView,
        // and CoreWindow from the IFrameworkView.
        //
        //------------------------------------------------------------------------------

        void IFrameworkView.Uninitialize()
        {
            _window = null;
            _view = null;
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.InitNewComposition
        //
        // This method is called by SetWindow(), where we initialize Composition after
        // the CoreWindow has been created.
        //
        //------------------------------------------------------------------------------

        void InitNewComposition()
        {
            //
            // Set up Windows.UI.Composition Compositor, root ContainerVisual, and associate with
            // the CoreWindow.
            //

            _compositor = new Compositor();

            _root = _compositor.CreateContainerVisual();



            _compositionTarget = _compositor.CreateTargetForCurrentView();
            _compositionTarget.Root = _root;

            //
            // Create a few visuals for our window
            //
            for (int index = 0; index < 20; index++)
            {
                _root.Children.InsertAtTop(CreateChildElement());
            }
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.CreateChildElement
        //
        // Creates a small sub-tree to represent a visible element in our application.
        //
        //------------------------------------------------------------------------------

        Visual CreateChildElement()
        {
            //
            // Each element consists of three visuals, which produce the appearance
            // of a framed rectangle
            //
            var element = _compositor.CreateContainerVisual();
            element.Size = new Vector2(100.0f, 100.0f);

            //
            // Position this visual randomly within our window
            //
            element.Offset = new Vector3((float)(_random.NextDouble() * 400), (float)(_random.NextDouble() * 400), 0.0f);

            //
            // The outer rectangle is always white
            //
            //Note to preview API users - SpriteVisual and Color Brush replace SolidColorVisual
            //for example instead of doing
            //var visual = _compositor.CreateSolidColorVisual() and
            //visual.Color = Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF);
            //we now use the below

            var visual = _compositor.CreateSpriteVisual();
            element.Children.InsertAtTop(visual);
            visual.Brush = _compositor.CreateColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));
            visual.Size = new Vector2(100.0f, 100.0f);

            //
            // The inner rectangle is inset from the outer by three pixels all around
            //
            var child = _compositor.CreateSpriteVisual();
            visual.Children.InsertAtTop(child);
            child.Offset = new Vector3(3.0f, 3.0f, 0.0f);
            child.Size = new Vector2(94.0f, 94.0f);

            //
            // Pick a random color for every rectangle
            //
            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)));
            child.Brush = _compositor.CreateColorBrush(Color.FromArgb(0xFF, red, green, blue));

            //
            // Make the subtree root visual partially transparent. This will cause each visual in the subtree
            // to render partially transparent, since a visual's opacity is multiplied with its parent's
            // opacity
            //
            element.Opacity = 0.8f;

            return element;
        }

        // CoreWindow / CoreApplicationView
        private CoreWindow _window;
        private CoreApplicationView _view;

        // Windows.UI.Composition
        private Compositor _compositor;
        private CompositionTarget _compositionTarget;
        private ContainerVisual _root;
        private ContainerVisual _currentVisual;
        private Vector2 _offsetBias;
        private bool _dragging;

        // Helpers
        private Random _random;
    }


    public sealed class VisualPropertiesFactory : IFrameworkViewSource
    {
        //------------------------------------------------------------------------------
        //
        // VisualPropertiesFactory.CreateView
        //
        // This method is called by CoreApplication to provide a new IFrameworkView for
        // a CoreWindow that is being created.
        //
        //------------------------------------------------------------------------------

        IFrameworkView IFrameworkViewSource.CreateView()
        {
            return new VisualProperties();
        }


        //------------------------------------------------------------------------------
        //
        // main
        //
        //------------------------------------------------------------------------------

        static int Main(string[] args)
        {
            CoreApplication.Run(new VisualPropertiesFactory());

            return 0;
        }
    }
}