CompositionProToolkit v0.4.4 released

Though CompositionProToolkit v0.4.4 was initially planned to be a small update aimed at fixing bugs and improving the user experience of the CompositionImageFrame, it turned out to be a larger update, ultimately requiring one of the oldest class in CompositionProToolkit to be refactored. Midway between the first phase of refactoring, I came up with another idea which led to the whole refactored code being discarded. I had to restart the refactoring from scratch and the final code appears to be much more streamlined than it was earlier. Another reason for refactoring was to shorten the names of the classes. Adding Composition to each of the classes resulted in very long names (CompositionSurfaceImageOptions for example).

Unfortunately, this refactoring led to some breaking changes again! But let me assure you, the pains that you will take to accommodate these breaking changes in your existing code will save a lot of headaches in the future.

Without much ado, let us delve into the details.

Breaking Changes

ICompositionMask

Of late, due to ever increasing requirements, I was adding more features to ICompositionMask which resulted in the class getting bloated. This class was playing a dual role – One, as the building block for creating a CompositionMaskBrush to render custom shaped visuals. Two, as the building block for creating a CompositionSurfaceBrush for rendering custom shaped geometries onto visuals.

Addition of new capabilities in ICompositionMask also introduced several flags in order to differentiate the two behaviors. It was high time to split them into two three interfaces –

  • IRenderSurface – This interface acts as the base interface for interfaces which render to the ICompositionSurface. It mainly contains references to an ICompositionGenerator object and an ICompositionSurface object which are the core objects required for rendering any geometry or image onto a ICompositionSurface.
  • IMaskSurface – This interface is used for rendering custom shaped geometries onto ICompositionSurface so that they can be useds as masks on Composition Visuals.
  • IGeometrySurface – This interface is used for rendering custom shaped geometries onto ICompositionSurface.

Both IMaskSurface and IGeometrySurface derive from IRenderSurface. Here is the interface hierarchy

RenderSurfaces

ICompositionSurfaceImage

If you look at the interface hierarchy image above, you will notice a fourth interface mentioned there – IImageSurface. It is actually the ICompositionSurfaceImage interface which has been refactored.

CompositionGenerator

CompositionGenerator‘s CreateMask APIs have been refactored into separate APIs for creating the three different types of render surfaces

IMaskSurface CreateMaskSurface(Size size, CanvasGeometry geometry);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry, 
    Color foregroundColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry, 
    Color foregroundColor, Color backgroundColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry, 
    ICanvasBrush foregroundBrush);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry, 
    ICanvasBrush foregroundBrush, ICanvasBrush backgroundBrush);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry, 
    ICanvasBrush foregroundBrush, Color backgroundColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry, 
    Color foregroundColor, ICanvasBrush backgroundBrush);
Task CreateImageSurfaceAsync(Uri uri, Size size, 
    ImageSurfaceOptions options);

CompositionImageFrame

CompositionImageFrame has been renamed to ImageFrame.

CompositionSurfaceImageOptions

The class with the longest name in CompositionProToolkit has now been renamed to ImageSurfaceOptions.

What’s new in CompositionProToolkit v0.4.4?

ImageFrame has been refactored with a better logical workflow. It has resulted in a better user experience. If you do not provide a definite Width and Height values to the ImageFrame, it will now size itself based on the loaded image and the parent control hosting the ImageFrame.

A new property RenderFast has been added to ImageFrame which gives you the option to turn off the animations which are used while rendering the image. This property is set to False by default. You can turn it on in scenarios where the Source property of the ImageFrame is being rapidly updated. For example, consider a ListBox containing numerous ImageFrames which is being scrolled very fast.

Property Type Description Default value
RenderFast Boolean Indicates whether the animations need to be switched off if the ImageFrame is being used in scenarios where it is being rapidly updated with new Source. False

NOTE: Do not confuse the RenderFast property with the RenderOptimized property. The former is used to turn off the animations during rendering, while the latter is used to reduces the memory footprint of the ImageFrame.

The latest source is available in GitHub and the Nuget package is available here.

CompositionProToolkit v0.4.3 released!

Another busy week and I feel thrilled to announce the release of CompositionProToolkit v0.4.3. It mainly contains feature additions to CompositionImageFrame and some minor refactoring (no breaking changes!). Let’s delve into the details.

CompositionGeneratorFactory

CompositionGeneratorFactory APIs have been simplified further. Now there are two ways to obtain the CompositionGenerator – by providing a Compositor or by providing a CompositionGraphicDevice.

public static ICompositionGenerator GetCompositionGenerator(Compositor compositor,
    bool useSharedCanvasDevice = true, bool useSoftwareRenderer = false);
public static ICompositionGenerator GetCompositionGenerator(
    CompositionGraphicsDevice graphicsDevice);

The first API also has couple of optional parameters

  • useSharedCanvasDevice – indicates whether the CompositionGenerator should use a shared CanvasDevice or creates a new one.
  • useSoftwareRenderer – this parameter is provided as a argument when creating a new CanvasDevice (i.e. when usedSharedCanvasDevice is false).

ICompositionMask

Two new APIs have been added in ICompositionMask class which allow the redraw of the mask with a new Color or ICanvasBrush.

void Redraw(ICanvasBrush brush);
void Redraw(Color color);

CompositionImageFrame

CompositionImageFrame has received most attention in this release. Two of the most requested features – Placeholder and Image Caching, have now been incorporated into CompositionImageFrame. Also now whenever the AlignX or AlignY changes, the image animates to its new location.

When the image is being (cached and) loaded, the placeholder shows a progress bar which indicates the load progress.

Placeholder_progress

The following new properties have been added to CompositionImageFrame to configure the Placeholder and the Image Cache features.

Property Type Description Default value
ShowPlaceHolder Boolean Indicates whether the placeholder needs to be displayed during image load or when no image is loaded in the CompositionImageFrame. False
PlaceholderBackground Color Indicates the background color of the Placeholder. Colors.Black
PlaceholderColor Color Indicates the color with which the rendered placeholder geometry should be filled RGB(192, 192, 192)
UseImageCache Boolean Indicates whether the images should be cached before rendering them. True

CPT_8_1.gif

Using CompositionImageFrame with FilePicker

If you have a CompostionImageFrame control in you application and your want to use the FilePicker to select an image file to be displayed on the CompostionImageFrame, then you must do the following

var picker = new Windows.Storage.Pickers.FileOpenPicker
{
    ViewMode = Windows.Storage.Pickers.PickerViewMode.Thumbnail,
    SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary
};
picker.FileTypeFilter.Add(".jpg");
picker.FileTypeFilter.Add(".jpeg");
picker.FileTypeFilter.Add(".png");
var file = await picker.PickSingleFileAsync();
ImageFrame.Source = await ImageCache.GetCachedUriAsync(file);

Since the CompositionImageFrame‘s Source property expects a Uri, while the FilePicker provides a StorageFile. So in order to obtain a Uri from the StorageFile, you must first cache it using the ImageCache.GetCachedUriAsync() method.

The latest source code is available in GitHub and NuGet package is available here.

CompositionProToolkit v0.4.2 released


Well, the last week has been pretty busy – learning little more details about the Composition APIs, major refactoring of the CompositionProToolkit code, breaking changes and adding new features and controls. The hard work finally paid off. I am excited to announce the release of CompositionProToolkit v0.4.2. Let’s see, in detail, what this new version contains.

Breaking Changes

First and foremost, you would like to know what will not work if you move to version 0.4.2. After digging deeper into the various classes within the Windows.UI.Composition namespace, I realized that before introducing new features and controls, it was high time I refactored my code. Thus I began – optimizing various classes, removing lot of redundant Task.Run(…)* encapsulations. As a result, many APIs which were earlier asynchronous are now synchronous (you no longer need to await them!). The main advantages are improved execution speed and more robust code.🙂

Here are some of the classes whose APIs have changed. If you are using them within their code you will have to update them to the new API calls.

CompositionGeneratorFactory

CompositionGeneratorFactory no longer requires a sharedLock parameter while obtaining the CompositionGenerator. The lock is now managed internally within each CompositionSurfaceImage. Since this was an optional argument in the GetCompositionGenerator API, if you were not providing this argument in your code the your code need not be changed.

v0.4.1

public static ICompositionGenerator GetCompositionGenerator(Compositor compositor, 
    CompositionGraphicsDevice graphicsDevice=null, object sharedLock=null);

v0.4.2

public static ICompositionGenerator GetCompositionGenerator(Compositor compositor, 
    CompositionGraphicsDevice graphicsDevice=null);

ICompositionMask

All the asynchronous APIs in ICompositionMask class have been refactored into synchronous APIs.

v0.4.1

Task RedrawAsync();
Task RedrawAsync(Geometry.CanvasGeometry geometry);
Task RedrawAsync(Size size, Geometry.CanvasGeometry geometry);
Task RedrawAsync(Size size, Geometry.CanvasGeometry geometry, ICanvasBrush brush);
Task RedrawAsync(Size size, Geometry.CanvasGeometry geometry, Color color);
Task ResizeAsync(Size size);

v0.4.2

void Redraw();
void Redraw(Geometry.CanvasGeometry geometry);
void Redraw(Size size, Geometry.CanvasGeometry geometry);
void Redraw(Size size, Geometry.CanvasGeometry geometry, ICanvasBrush brush);
void Redraw(Size size, Geometry.CanvasGeometry geometry, Color color);
void Resize(Size size);

ICompositionSurfaceImage

A few of the asynchronous APIs in ICompositionSurfaceImage class have been refactored into synchronous APIs.

v0.4.1

Task RedrawAsync(CompositionSurfaceImageOptions options);
Task ResizeAsync(Size size);
Task ResizeAsync(Size size, CompositionSurfaceImageOptions options);

v0.4.2

void Redraw(CompositionSurfaceImageOptions options);
void Resize(Size size);
void Resize(Size size, CompositionSurfaceImageOptions options);

ICompositionGenerator

All the asynchronous APIs in ICompositionGenerator related to creating of ICompositionMask and visual Reflection have been refactored into synchronous APIs.

v0.4.1

Task CreateMaskAsync(Size size, CanvasGeometry geometry);
Task CreateMaskAsync(Size size, CanvasGeometry geometry, ICanvasBrush brush);
Task CreateMaskAsync(Size size, CanvasGeometry geometry, Color color);
Task CreateReflectionAsync(ContainerVisual visual, float reflectionDistance, 
    float reflectionLength, ReflectionLocation location);

v0.4.2

ICompositionMask CreateMask(Size size, CanvasGeometry geometry);
ICompositionMask CreateMask(Size size, CanvasGeometry geometry, ICanvasBrush brush);
ICompositionMask CreateMask(Size size, CanvasGeometry geometry, Color color);
void CreateReflection(ContainerVisual visual, float reflectionDistance, 
    float reflectionLength, ReflectionLocation location));

So what’s new in v0.4.2?

Now that we are done with the breaking changes, let’s look what all new features and controls have been added to CompositionProToolkit v0.4.2.

CompositionSurfaceImageOptions

CompositionSurfaceImageOptions now has an additional property AutoResize which can be set to False when you are trying to render a large image onto the CompositionSurfaceImage as a thumbnail. This will optimize memory usage.

Property Type Description Default value
AutoResize Boolean Specifies whether the CompositionSurfaceImage should resize itself automatically to match the loaded image size. When set to True, the Stretch, HorizontalAlignment and VerticalAlignment options are ignored. False

CompositionImageFrame

CompositionImageFrame now provides a fade in animation while transitioning from one image to another. It also supports rounded corners and display of shadows which can be configured easily. It also allows an option to optimize the rendering of the image. This is useful for scenarios where you are trying to render a large image to a smaller size, for example thumbnails.

CPT6.gif

The following new properties have been added to CompositionImageFrame

Property Type Description Default value
CornerRadius CornerRadius Indicates the corner radius of the the ImageFrame. The image will be rendered with rounded corners. (0, 0, 0, 0)
DisplayShadow Boolean Indicates whether the shadow for this image should be displayed. False
RenderOptimized Boolean Indicates whether optimization must be used to render the image.Set this property to True if the CompositionImageFrame is very small compared to the actual image size. This will optimize memory usage. False
ShadowBlurRadius Double Specifies the Blur radius of the shadow. 0.0
ShadowColor Color Specifies the color of the shadow. Transparent
ShadowOffsetX Double Specifies the horizontal offset of the shadow. 0.0
ShadowOffsetY Double Specifies the vertical offset of the shadow. 0.0
ShadowOpacity Double Specifies the opacity of the shadow. 1
TransitionDuration Double Indicates the duration of the crossfade animation while transitioning from one image to another. 700ms

FluidBanner

FluidBanner is now a part of the CompositionProToolkit. It now internally uses CompositionSurfaceImage to host the images. It provides the following properties which can be used to customize the FluidBanner.

Dependency Property Type Description Default Value
AlignX AlignmentX Indicates how the image is positioned horizontally in the FluidBanner items. Center
AlignY AlignmentY Indicates how the image is positioned vertically in the FluidBanner items. Center
DecodeHeight int The height, in pixels, that the images are decoded to. (Optional) 0
DecodeWidth int The width, in pixels, that the images are decoded to. (Optional) 0
ItemBackground Color The background color of each item in the FluidBanner Black
ItemGap double The gap between adjacent items in the banner. 30
ItemsSource IEnumerable<Uri> The collection of Uris of images to be shown in the FluidBanner null
Padding Thickness The padding inside the FluidBanner Thickness(0)
Stretch Stretch Indicates how the image is resized to fill its allocated space within each FluidBanner item. Uniform

The latest source code is available in GitHub. The Nuget package is available here

CompositionProToolkit v0.4.1 released

Compared to the large number of features in CompositionProToolkit v0.4, version 0.4.1 incorporates the CompositionImageFrame control and code optimizations.

If you want to know more about the new features added to CompositionProToolkit v0.4 check my previous post.

CompositionImageFrame

CompositionImageFrame is a control which can be used for displaying images. It encapsulates a CompositionSurfaceImage object which is used for loading and rendering the images. It also supports Pointer interactions and raises events accordingly.

In order to configure the rendering of the image, CompositionImageFrame has the following properties

Dependency Property Type Description Default Value
AlignX AlignmentX Specifies how the image is positioned horizontally in the CompositionImageFrame. AlignmentX.Center
AlignY AlignmentY Specifies how the image is positioned vertically in the CompositionImageFrame. AlignmentY.Center
FrameBackground Color Specifies the background color of the CompositionImageFrame to fill the area where image is not rendered. Colors.Black
Interpolation CanvasImageInterpolation Specifies the interpolation used for rendering the image. HighQualityCubic
Source Uri Specifies the Uri of the image to be loaded into the CompositionImageFrame. null
Stretch Stretch Specifies how the image is resized to fill its allocated space in the CompositionImageFrame. Uniform

CompositionImageFrame raises the following events

  • ImageOpened – when the image has been successfully loaded from the Uri and rendered.
  • ImageFailed – when there is an error loading the image from the Uri.

CompositionProToolkit v0.4.1 is now available in NuGet. Source code is available in GitHub.

If you want to see the various controls and features of CompositionProToolkit in action, check out the SampleGallery project in GitHub.

CompositionProToolkit v0.4 Released!

I am thrilled to announce that CompositionProToolkit v0.4 is now released. It brings a few breaking changes and new features that will help you use the Composition APIs more efficiently. In this blog I will be talking in detail about them.

Breaking Changes

First, let me tell you about the breaking changes incorporated in CompositionProToolkit v0.4

  • ICompositionMaskGenerator has been renamed to ICompositionGenerator – you will now be using this interface to generate many other objects apart from ICompositionMask. (More on that later!)
  • ICompositionMaskGeneratorInternal has been renamed to ICompositionGeneratorInternal – this will not break any of your code as it is an internal class.
  • CompositionMaskGenerator has been renamed to CompositionGenerator – another internal class which implements the ICompositionGenerator and the ICompositionGeneratorInternal interfaces.
  • CompositionMaskFactory has been renamed to CompositionGeneratorFactory – You will be using this class to obtain the instance of ICompositionGenerator.
    • GetCompositionMaskGenerator API has been renamed to GetCompositionGenerator – The parameters of this API remain the same.
public static ICompositionGenerator GetCompositionGenerator(Compositor compositor,     
    CompositionGraphicsDevice graphicsDevice = null, object sharedLock = null)

Now that we are done with the breaking changes, lets discuss the new features that have been added to CompositionProToolkit.

Creating a ICompositionMask using ICanvasBrush

ICompositionGenerator now provides the following additional API to create a ICompositionMask using an ICanvasBrush or its derivatives like CanvasImageBrush, CanvasLinearGradientBrush, CanvasRadialGradientBrush and CanvasSolidColorBrush.

Task<ICompositionMask> CreateMaskAsync(Size size, CanvasGeometry geometry, ICanvasBrush brush);

ICompositionSurfaceImage

ICompositionSurfaceImage is an interface which encapsulates a CompositionDrawingSurface onto which an image can be loaded by providing a Uri. You can then use the CompositionDrawingSurface to create a CompositionSurfaceBrush which can be applied to any Visual.

SurfaceImage.png

ICompositionGenerator provides the following API which allows you to create a CompositionSurfaceImage

Task<ICompositionSurfaceImage> CreateSurfaceImageAsync(Uri uri, Size size, 
    CompositionSurfaceImageOptions options);

This API requires the Uri of the image to be loaded, the size of the CompositionDrawingSurface (usually the same size as the Visual on which it is finally applied) and the CompositionSurfaceImageOptions.

CompositionSurfaceImageOptions

The CompositionSurfaceImageOptions class encapsulates a set of properties which influence the rendering of the image on the CompositionSurfaceImage. The following table shows the list of these properties.

Property Type Description Possible Values
Stretch Stretch Describes how image is resized to fill its allocated space. None,
Uniform,
Fill,
UniformToFill
HorizontalAlignment AlignmentX Describes how image is positioned horizontally. Left,
Center,
Right
VerticalAlignment AlignmentY Describes how image is positioned vertically. Top,
Center,
Bottom
Opacity float Specifies the opacity of the rendered image. 0 - 1f inclusive
Interpolation CanvasImageInterpolation Specifies the interpolation used to render the image. NearestNeighbor, Linear,
Cubic,
MultiSampleLinear,
Anisotropic,
HighQualityCubic
SurfaceBackgroundColor Color Specifies the color which will be used to fill the surface before rendering the image. All possible values that can be created.

Here is how the image is aligned on the Visual’s surface based on the HorizontalAlignment and VerticalAlignment properties

Alignment

The area on the Visual’s surface, which is not covered by the rendered image, will be filled with the color specified by the SurfaceBackgroundColor property

Example

The following example shows how you can load an image onto a Visual

var compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
var generator = CompositionGeneratorFactory.GetCompositionGenerator(compositor);

var visual = compositor.CreateSpriteVisual();
visual.Size = new Vector2(this.ActualWidth.Single(), this.ActualHeight.Single());
visual.Offset = Vector3.Zero;

var options = new CompositionSurfaceImageOptions(Stretch.Uniform, AlignmentX.Center,
    AlignmentY.Center, 0.9f, CanvasImageInterpolation.Cubic)
    {
        SurfaceBackgroundColor = Colors.Maroon
    };

var surfaceImage =
    await generator.CreateSurfaceImageAsync(new Uri("ms-appx:///Assets/Images/Image3.jpg"), 
          visual.Size.ToSize(), options);

visual.Brush = compositor.CreateSurfaceBrush(_surfaceImage.Surface);

Once you create a CompositionSurfaceBrush from the ICompositionSurfaceImage and apply it to a Visual, you can use the following ICompositionSurfaceImage APIs to resize, provide a new Uri or change the rendering options

Task RedrawAsync();

Task RedrawAsync(CompositionSurfaceImageOptions options);

Task RedrawAsync(Uri uri, CompositionSurfaceImageOptions options);

Task RedrawAsync(Uri uri, Size size, CompositionSurfaceImageOptions options);

Task ResizeAsync(Size size);

Task ResizeAsync(Size size, CompositionSurfaceImageOptions options);

Once you call any of the above methods, the Visual’s brush is also updated.

Visual Reflection

A new API has been added to CompositionProToolkit which allows you to create the reflection of any Visual.

Task CreateReflectionAsync(ContainerVisual visual, float reflectionDistance = 0f,
    float reflectionLength = 0.7f, ReflectionLocation location = ReflectionLocation.Bottom);

The parameters required for this API are

  • visual – A ContainerVisual whose reflection has to be created.
  • reflectionDistance – The distance between the visual and its reflection.
  • reflectionLength – The normalized length (0 – 1f, inclusive) of the visual that must be visible in the reflection. Default value is 0.7f.
  • location – Specifies the location of the reflection with respect to the visual – Bottom, Top, Left or Right. Default value is ReflectionLocation.Bottom.

ReflectionLocation.png

This API will create a reflection even if an effect is applied to the Visual.

ReflectionWithEffect.png

If the visual has multiple other visuals in its visual tree, then the entire visual tree is reflected.

ReflectionMultipleVisuals.png

Other Changes

With the Anniversary Release of Windows 10, CompositionProToolkit project has migrated to Windows SDK 14393. Also it now requires CompositionExpressionToolkit v0.2.4 or higher, and Win2d v1.19 or higher (this is added automatically when you add a NuGet reference to CompositionProToolkit).

The source code is available in GitHub and the NuGet library is available here.

Creating a FluidBanner control using Windows Composition

Introduction

A few days ago, I came across the following tweet which showed a banner of images with a complex set of animations during user interactions. It was created for the web using CSS.

I wondered if the same could be achieved in Windows UWP applications using Composition APIs. After tinkering for a few days, I came up with the FluidBanner control.

fb3.gif

 

In this post I will be describing in detail about the FluidBanner control.

FluidBanner Dependency Properties

FluidBanner has the following dependency properties which allow the user to customize it.

Dependency Property Type Description Default Value
DecodeHeight int The height, in pixels, that the images are decoded to. (Optional) 0
DecodeWidth int The width, in pixels, that the images are decoded to. (Optional) 0
ItemBackground Color The background color of each item in the FluidBanner Black
ItemGap double The gap between adjacent items in the banner. 30
ItemsSource IEnumerable<Uri> The collection of Uris of images to be shown in the FluidBanner null
Padding Thickness The padding inside the FluidBanner Thickness(0)

Demystifying the FluidBanner Animations

For each of the animations in the FluidBanner, a key role is played by the the Clip property (which is an instance of the InsetClip class) of the visual. According to MSDN, the InsetClip clips a portion of a visual. The visible portion of the visual is a rectangle defined as inset values from the edges of the visual. The portion of the visual outside the rectangle is clipped.

InsetClip.PNG

 

Most of the animations within the FluidBanner is accomplished by animating the LeftInset and RightInset properties of the InsetClip class.

Digging deeper into the FluidBanner

FluidBanner Visual States

Based on FluidBanner’s behavior during user interaction, its visual states can be categorized into

    • Unselected state – In this state, which is also the initial state, none of the images are selected by the user and each image is represented by a BannerItem which is a vertical slice of the image. (I will provide more details about BannerItem later in this blog.) Adjacent BannerItems are separated by a predefined set of pixels (determined by the ItemGap property).

FluidBanner_Layout

  • Selected state – In this state, the user has selected an image by clicking or tapping on it and the selected BannerItem animates and expands to fill the total area of the FluidBanner. The rest of the BannerItems scale back and fade out.

FluidBanner_Layout_Selected

FluidBanner Layout

FluidBanner derives from the Panel class and it means that the responsibility to arrange and render its children lies solely on the FluidBanner. Before describing how FluidBanner renders its children, it is important to understand how the Layout Pass works.

Layout Pass

According to MSDN, the Layout Pass describes the process of measuring and arranging the members of a Panel-derived object’s collection of children, and then drawing them onscreen. The layout pass is a mathematically-intensive process—the larger the number of children in the collection, the greater the number of calculations required. At its simplest, layout is a recursive system that leads to an element being sized, positioned, and drawn onscreen.

Sounds complicated? Well, let me explain it in simpler terms. The Layout Pass can be thought of like a communication between the layout container and its children. This communication takes place in two stages which are often referred to as passes.

In the first pass, the parent informs each child about the size available for rendering (based on constraints such as Margin, Padding, Alignment etc) and asks how much space the child needs to render itself. Before a child informs the parent, how much space it needs, the child asks all of its children how much space they need. (This scenario is valid in case of nested panels). This goes on recursively until all the children have been queried. Once each child calculates the space required, its DesiredSize property is set.

At the end of the first pass, once all the children respond, the parent can determine its required size by aggregating the DesiredSize properties of its children according to its own layout rules. This pass is called the Measure pass.

In the second pass, the parent informs each child the final size that has been allocated to it and the child must render itself in that size. The child, in turn, calculates the final size for each of its child (based on its own layout rules) and informs them accordingly. This happens recursively until all the children have been notified. This pass is called the Arrange pass.

LayoutPass2

 

The Measure pass is characterized by the Measure() call made by the parent which invokes the MeasureOverride() method on its children. The Arrange pass is characterized by the Arrange() method call made by the parent which then invokes the ArrangeOverride() method on its children.

protected override Size MeasureOverride(Size availableSize);
protected override Size ArrangeOverride(Size finalSize);

The actual arrangement of children happens during the Arrange pass. It is important to understand that the availableSize in the MeasureOverride() method might not be same as the finalSize in the ArrangeOverride() method.

Layout Pass in FluidBanner

Measure Pass

In the Measure pass, since the panel will accommodate all sizes of images, there is no need to explicitly calculate the size of the its children. So the FluidBanner is happy with whatever size is available.

However, one crucial thing to note is that if you are implementing a custom control and handling the Layout passes yourself, then MeasureOverride method is the best place to initialize your Composition related objects (Compositor, Visuals, Animations etc) which you may be using throughout your application. (Make sure that you initialize them only once. You can accomplish this by checking whether the Compositor reference is already initialized.). Layout Pass happens before the Loaded event is raised.

Visual _rootVisual;
Compositor _compositor;

protected override Size MeasureOverride(Size availableSize)
{
    if (_compositor != null)
    {
        // Initialize Composition related elements
        InitializeComposition();
    }

    ...
}

public void InitializeComposition()
{
    _rootVisual = ElementCompositionPreview.GetElementVisual(this);
    _compositor = _rootVisual.Compositor;
    
    ...
}
Arrange Pass

As previously mentioned, Arrange pass is responsible for the arrangement of children within the parent. The following factors influence the arrangement of the BannerItemsPadding, the finalSize provided and the number of images to be displayed. Based on these factors, the size and offset of each BannerItem is calculated and each BannerItem created and added to the visual tree.

BannerItem

Ok, now lets talk about BannerItem in detail. BannerItem represents a single image displayed in the FluidBanner (whether in Selected or Unselected state).

BannerItem comprises of a three visuals –

  • contentVisual – It is a SpriteVisual which hosts the image loaded from the Uri.
  • bgVisual – It is a SpriteVisual filled with the color specified by the ItemBackground property (default Black). It serves as a background for the image if the image does not stretch to occupy the entire BannerItem area.
  • container – It is a ContainerVisual which hosts the contentVisual and the bgVisual. The Clip property of the container is set to an InsetClip whose LeftInset, RightInset, TopInset & BottomInset properties determine the size of the image slice displayed in the BannerItem. The BannerItem size is expanded and collapsed by animating these properties.

BannerItem

Layers and Animations

At the root of the FluidBanner’s visual tree is a ContainerVisual called _rootContainer. In order to accommodate its two visual states, the _rootContainer hosts two LayerVisuals_bgLayer and _topLayer. As the name suggests, _topLayer is rendered above the _bgLayer.

In the Unselected state, the _bgLayer hosts all the BannerItems. Clicking on any of the BannerItems will cause the FluidBanner to transition to the Selected state. During this transition, the selected BannerItem is moved to the _topLayer and its Clip animated to move to the left most position in the FluidBanner. After that the Clip is expanded to occupy the full surface area of the FluidBanner. Meanwhile the remaining BannerItems in the _bgLayer scale back and fade out.

Clicking or tapping on the expanded selected BannerItem will cause the FluidBanner to transition to the Unselected state. During this transition, the Clip of the selected BannerItem collapses to occupy the left most position in the FluidBanner and then it moves back to its original location in the _bgLayer. Meanwhile the remaining BannerItems in the _bgLayer fade in and scale up to normal size.

FB_LayoutAnim

Pointer Interactions

Pointer interactions are handled by subscribing to the PointerMoved and PointerPressed events in the FluidBanner. Since the visuals themselves do not support pointer interactions, the location of each of the BannerItems is kept track of and based on the Pointer event raised, the corresponding BannerItem under the Pointer is obtained and the appropriate animations initiated.

Source Code

The source code for FluidBanner is available in GitHub.