組合視覺效果

組合視覺效果構成了視覺化樹狀結構,組合 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 中引進。)

您可以使用 CompositionBrush 及其子類別 (包括 CompositionColorBrushCompositionSurfaceBrushCompositionEffectBrush),將內容和效果套用至 SpriteVisuals。 若要深入了解筆刷,請參閱 CompositionBrush 概觀

CompositionVisual 範例

在這裡,我們將探討一些範例程式碼,示範先前所列的三種不同視覺效果類型。 雖然此範例未涵蓋動畫或更複雜效果之類的概念,但它包含所有這些系統所使用的建構元素。 (本文結尾會列出完整的範例程式碼。)

在範例中,有數個純色方塊可以在畫面上加以按下並拖曳。 按下方塊時,方塊會移到最前面、旋轉45度,並在拖曳時變成不透明。

這會顯示一些使用 API 的基本概念,包括:

  • 建立撰寫器
  • 使用 CompositionColorBrush 建立 SpriteVisual
  • 剪輯視覺效果
  • 旋轉視覺效果
  • 設定不透明度
  • 在集合中變更視覺效果的位置。

建立撰寫器

建立 Compositor,並將它儲存在變數中來當成處理站使用,其實是個簡單的工作。 下列程式碼片段示範如何建立新的 Compositor

_compositor = new Compositor();

建立 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 提供的轉換元件組,可讓這些工作更加容易,而 Rotation 只是其中一個範例。 其他包括 Offset、Scale、Orientation、RotationAxis 和 4x4 TransformMatrix。

設定不透明度

設定視覺效果的不透明度,是個使用浮點數的簡單作業。 例如,在範例中,所有的正方形都從 .8 不透明度開始:

visual.Opacity = 0.8f;

如同旋轉,不透明度 Opacity 屬性可以產生動畫效果。

在集合中變更視覺效果的位置

Composition API 允許在 VisualCollection 中以數種方式變更視覺效果的位置。 它可以透過 InsertAbove 放在另一個視覺效果上方、使用 InsertBelow 放在下方、使用 InsertAtTop 移至頂端,或是使用 InsertAtBottom 移至底部。

在範例中,被按下的視覺效果順序會移到頂端:

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;
        }
    }
}