CompositionProToolkit v0.7 Released

This update of CompositionProToolkit brings a breaking change and introduces two new controls which are completely written using Composition APIs.

Breaking Change : CompositionGeneratorFactory is deprecated!

CompositionGeneratorFactory class has been deprecated and removed. Now you can use the Compositor‘s CreateCompositionGenerator() method to instantiate the  CompositionGenerator.


var generator = compositor.CreateCompositionGenerator();

FluidToggleSwitch

FluidToggleSwitch

FluidToggleSwitch is a toggleswitch control which uses Composition Visuals to render its components and provides a richer look and feel to the ToggleSwitch control. There are three primary components within the ToggleSwitch

  • Background – The outermost circular area.
  • Track – The pill shaped area.
  • Thumb – The innermost circular area.

The reason FluidToggleSwitch is encapsulated with a circular background component is because the hit area for a touch input is normally circular.

The following properties allow you to customize the FluidToggleSwitch

Dependency Property Type Description Default Value
ActiveColor Color Gets or sets the Color of the FluidToggleSwitch in Checked state. #4cd964
InactiveColor Color Gets or sets the Color of the FluidToggleSwitch in Unchecked state. #dfdfdf
DisabledColor Color Gets or sets the Color of the FluidToggleSwitch in Disabled state. #eaeaea

The above properties define the color of the Background component. The color of the Track component is derived automatically from the above properties. The color of the Thumb is white.

ProfileControl

ProfileControl

ProfileControl allows you to display an image (normally a user profile image) in an intuitive way. This control is mainly implemented using Composition Visuals and animations which provide a rich user experience. Depending on the width and height of the ProfileControl, its shape can be either circular or elliptical. There are two main components within the ProfileControl

  • Background Visual – The outermost circular or elliptical area. This area is filled with the CompositionBackdropBrush which blends the control with whatever is rendered beneath the control.
  • Image Visual – The innermost circular or elliptical area. This area renders the image provided.

The following properties allow you to customize the ProfileControl

Dependency Property Type Description Default Value
BlurRadius Double Gets or sets the amount by which the brush of the Background Visual must be blurred. 20.0
BorderGap Double Gets or sets the uniform gap between the Background visual and the Image visual. 10.0
FluidReveal Boolean Indicates whether the reveal animation should automatically be played when the Source property of the ProfileControl changes. If set to False, the image specified by the Source property is displayed directly without any animation. True
RevealDuration TimeSpan Gets or sets the duration of the reveal animation. 1 sec
Source Uri Gets or sets the Uri of the image to be displayed in the ProfileControl. null
Stretch Stretch Indicates how the image content is resized to fill its allocated space in the Image Visual. Stretch.Uniform
Tint Color Gets or sets the color overlay on the background of the ProfileControl. Colors.White

I shall provide more details into how these controls were implemented using Composition APIs in my next post.

Source Code, Nuget & Documentation

Happy Coding! 🙂

CompositionProToolkit v0.6 Released!

cpt_banner

Well, it has been almost 6 months since my last post. I have been busy working on a few apps and learning a few new technologies. In the meantime, Windows 10 Creators update was released, Build 2017 happened and Microsoft introduced the Fluent Design System (a much needed feature for Windows 10 UX). The Windows Composition team also open sourced the Expression Builder code which allows you to define the expressions in ExpressionAnimations in a more intuitive way.

A couple of weeks ago, I felt it was time to revisit my code in CompositionProToolkit and update it to the latest Windows SDK features. CompositionProToolkit version 0.5.x releases were more biased towards Win2d. So I decided to dedicate version 0.6 to the CompositionProToolkit.Expressions namespace.

cpte_banner

I had a look at the Expression Builder code to understand its features. It helps the developers to create type-safe expressions using ExpressionNode (and its derivatives). I was wondering if there was a way to modify the authoring feature in such a way that the ExpressionNode (and its derivatives) remain hidden from view to the developer and s/he may use a more simpler way to specify the expression with type-safety (using Lambda expressions).

It was then I realized the code in CompositionProToolkit.Expressions namespace did not support creation of expression templates.

I realized that there was a huge scope of improvement in the CompositionProToolkit.Expressions namespace and it was possible to introduce templates within the lambda expressions.

Thus, I am happy to announce that with the release of CompositionProToolkit v0.6, Expression parsing is more robust now. A lot of extension methods have been added to help the developer minimize the code written to create animations.

Defining the ExpressionAnimation and KeyFrameAnimation

The following extension methods have been defined on the Compositor to create the appropriate ExpressionAnimation object.

public static ExpressionAnimation CreateColorExpressionAnimation(this Compositor compositor);
public static ExpressionAnimation CreateQuaternionExpressionAnimation(this Compositor compositor);
public static ExpressionAnimation CreateScalarExpressionAnimation(this Compositor compositor);
public static ExpressionAnimation CreateVector2ExpressionAnimation(this Compositor compositor);
public static ExpressionAnimation CreateVector3ExpressionAnimation(this Compositor compositor);
public static ExpressionAnimation CreateVector4ExpressionAnimation(this Compositor compositor);
public static ExpressionAnimation CreateMatrix4x4ExpressionAnimation(this Compositor compositor);

The following extension method is defined for the Compositor to create a KeyFrameAnimation object for the appropriate animating property

public static KeyFrameAnimation GenerateColorKeyFrameAnimation(this Compositor compositor);
public static KeyFrameAnimation GenerateQuaternionKeyFrameAnimation(this Compositor compositor);
public static KeyFrameAnimation GenerateScalarKeyFrameAnimation(this Compositor compositor);
public static KeyFrameAnimation GenerateVector2KeyFrameAnimation(this Compositor compositor);
public static KeyFrameAnimation GenerateVector3KeyFrameAnimation(this Compositor compositor);
public static KeyFrameAnimation GenerateVector4KeyFrameAnimation(this Compositor compositor);

After creating the ExpressionAnimation object you can set its Expression property with the appropriate Expression.

The following extension methods allow StartAnimation to be called directly using an ExpressionAnimation or a KeyFrameAnimation object.

public static void StartAnimation(this CompositionObject compositionObject,
    Expression expression, KeyFrameAnimation keyframeAnimation);
public static void StartAnimation(this CompositionObject compositionObject,
    Expression expression, ExpressionAnimation expressionAnimation);

Using CompositionPropertySet within the Expression

In order to use a CompositionPropertySet within the Expression the following GetXXX extension methods have been defined

public static bool GetBoolean(this CompositionPropertySet propertySet, string key);
public static Color GetColor(this CompositionPropertySet propertySet, string key);
public static Matrix3x2 GetMatrix3x2(this CompositionPropertySet propertySet, string key);
public static Matrix4x4 GetMatrix4x4(this CompositionPropertySet propertySet, string key);
public static Quaternion GetQuaternion(this CompositionPropertySet propertySet, string key);
public static float GetScalar(this CompositionPropertySet propertySet, string key);
public static Vector2 GetVector2(this CompositionPropertySet propertySet, string key);
public static Vector3 GetVector3(this CompositionPropertySet propertySet, string key);
public static Vector4 GetVector4(this CompositionPropertySet propertySet, string key);

Expression Targets and Templates

It is now possible to use the new operator inside expressions. You can also define Expression Targets and Expression Templates within the Expression.

Expression Targets allow you to access the composition object (on which the expression animation is executed) within the expression.

Expression Templates allow you to reuse an expression by connecting them to different references (thus creating separate animations). It is similar to the way C# Generics allows you to define a class template and reuse the template for different types.

Here is an example

var visual = _compositor.CreateSpriteVisual();
visual.Offset = new Vector3(20, 30, 40);
visual.Size = new Vector2(200, 100);

var delta = new Vector3(50);

var offsetAnimation = _compositor.CreateVector3ExpressionAnimation();
offsetAnimation.Expression = 
    c => new VisualReference("myVisual").CenterPoint + new Vector3(10, 20, 30) + delta;
offsetAnimation.SetReference("myVisual", visual);

visual.StartAnimation(() => visual.CenterPoint, offsetAnimation);

var visual2 = _compositor.CreateSpriteVisual();
visual2.Offset = new Vector3(10, 10, 10);
visual2.Size = new Vector2(100, 100);

offsetAnimation.SetReference("myVisual", visual2);
offsetAnimation.SetReference("delta", new Vector3(100));

visual2.StartAnimation(() => visual2.CenterPoint, offsetAnimation);

Using Arrays in Expression

You can use an Array of objects deriving from CompositionObject within your Expression.

Example

var visual1 = compositor.CreateSpriteVisual();
...
var visual2 = compositor.CreateSpriteVisual();
...
var visualArray = new Visual[] { visual1, visual2 };

var offsetAnimation = compositor.CreateVector3ExpressionAnimation();
offsetAnimation.Expression = c => visualArray[0].Offset + new Vector(20);

visualArray[1].StartAnimation(() => visualArray[1].Offset, offsetAnimation);

Using List<> in Expression

You can use a List<> of objects deriving from CompositionObject within your Expression.

Example

var visual1 = compositor.CreateSpriteVisual();
...
var visual2 = compositor.CreateSpriteVisual();
...
var visualList = new List { visual1, visual2 };

var offsetAnimation = compositor.CreateVector3ExpressionAnimation();
offsetAnimation.Expression = c => visualList[0].Offset + new Vector(20);

visualList[1].StartAnimation(() => visualList[1].Offset, offsetAnimation);

Using Dictionary<,> in Expression

You can use a Dictionary<TKey, TValue> within your Expression.

TKey can be of types – int, float, double or string.

TValue should be an object deriving from CompositionObject.

Example

var visual1 = compositor.CreateSpriteVisual();
...
var visual2 = compositor.CreateSpriteVisual();
...
var visualDictionary = new Dictionary<string, Visual> 
    {
        ["first"] = visual1, 
        ["second"] = visual2 
    };

var offsetAnimation = compositor.CreateVector3ExpressionAnimation();
offsetAnimation.Expression = c => visualDictionary["first"].Offset + new Vector(20);

visualDictionary["second"].StartAnimation(() => visualDictionary["second"], offsetAnimation);

Custom Cubic Bezier Easing Functions

The following extension methods have been added to Compositor to create predefined CubicBezierEasingFunctions (these custom cubic bezier easing functions are based on the Robert Penner’s Easing Equations and the values are obtained from Ceaser CSS Easing Animation Tool )

public static CubicBezierEasingFunction CreateEaseInBackEasingFunction();
public static CubicBezierEasingFunction CreateEaseInCircleEasingFunction();
public static CubicBezierEasingFunction CreateEaseInCubicEasingFunction();
public static CubicBezierEasingFunction CreateEaseInExponentialFunction();
public static CubicBezierEasingFunction CreateEaseInQuadraticEasingFunction();
public static CubicBezierEasingFunction CreateEaseInQuarticEasingFunction();
public static CubicBezierEasingFunction CreateEaseInQuinticEasingFunction();
public static CubicBezierEasingFunction CreateEaseInSineEasingFunction();

public static CubicBezierEasingFunction CreateEaseOutBackEasingFunction();
public static CubicBezierEasingFunction CreateEaseOutCircleEasingFunction();
public static CubicBezierEasingFunction CreateEaseOutCubicEasingFunction();
public static CubicBezierEasingFunction CreateEaseOutExponentialFunction();
public static CubicBezierEasingFunction CreateEaseOutQuadraticEasingFunction();
public static CubicBezierEasingFunction CreateEaseOutQuarticEasingFunction();
public static CubicBezierEasingFunction CreateEaseOutQuinticEasingFunction();
public static CubicBezierEasingFunction CreateEaseOutSineEasingFunction();

public static CubicBezierEasingFunction CreateEaseInOutBackEasingFunction();
public static CubicBezierEasingFunction CreateEaseInOutCircleEasingFunction();
public static CubicBezierEasingFunction CreateEaseInOutCubicEasingFunction();
public static CubicBezierEasingFunction CreateEaseInOutExponentialFunction();
public static CubicBezierEasingFunction CreateEaseInOutQuadraticEasingFunction();
public static CubicBezierEasingFunction CreateEaseInOutQuarticEasingFunction();
public static CubicBezierEasingFunction CreateEaseInOutQuinticEasingFunction();
public static CubicBezierEasingFunction CreateEaseInOutSineEasingFunction();

DeviceLostHelper

CompositionProToolkit now uses the DeviceLostHelper class (provided in the WindowsUIDevLabs Sample Gallery application) to watch for CanvasDevice and it raises an appropriate event when the device is lost.

Project structure and Nuget

CompositionProToolkit v0.6 also revamps the project structure. The CompositionProToolkit.Ref project has been discarded and the CompositionDeviceHelper project has been added. It is a C++/CX project which encapsulates the DeviceLostHelper class.

The CompositionProToolkit.nuspec has also been updated. Now it includes the XML documentation file to provide better Intellisense support when using CompositionProToolkit in your code. I had some issues creating a single Nuget package targeting x86, x64 & ARM – which internally contained a UWP library and a Windows Runtime Component. There is no clear documentation provided for this scenario. I had to refer through numerous blogs and forum posts to finally get the nuget structure right. I will write in more detail about this in my next blog post.

Source Code, Nuget & Documentation

Happy Coding!

CompositionProToolkit v0.5.1 released : Win2d Mini Language

cpt_banner

Well, this is the first update for CompositionProToolkit in 2017, and it has turned out to be a huge update. I continued to build upon the work which I had started in the previous update i.e. parsing the SVG path language to convert it to CanvasGeometry. Now I am happy to announce that in this version, I have added more features to the existing path language, more commands to represent new shapes, brushes and strokes. It has now evolved into a more robust language which I am terming as the Win2d Mini Language.

In this post, I shall be explaining the work done to incorporate Win2d Mini Language into CompositionProToolkit and other related features.

ICanvasStroke and CanvasStroke

In Win2d, the stroke, that is used to render an outline to a CanvasGeometry, is comprised of three components

  • Stroke Width – defines the width of the stroke.
  • Stroke Brush – defines the ICanvasBrush that will be used to render the stroke.
  • Stroke Style – defines the CanvasStrokeStyle for the stroke.

ICanvasStroke interface, defined in the CompositionProToolkit.Win2d namespace, encapsulates these three components and the CanvasStroke class implements this interface. It provides several constructors to define the stroke.

public interface ICanvasStroke
{
  ICanvasBrush Brush { get; }
  float Width { get; }
  CanvasStrokeStyle Style { get; }
  Matrix3x2 Transform { get; set; }
}

public sealed class CanvasStroke : ICanvasStroke
{
  public float Width { get; }
  public CanvasStrokeStyle Style { get; }
  public ICanvasBrush Brush { get; }
  public Matrix3x2 Transform { get; set; }
  public CanvasStroke(ICanvasBrush brush, float strokeWidth = 1f);
  public CanvasStroke(ICanvasBrush brush, float strokeWidth, CanvasStrokeStyle strokeStyle);
  public CanvasStroke(ICanvasResourceCreator device, Color strokeColor, float strokeWidth = 1f);
  public CanvasStroke(ICanvasResourceCreator device, Color strokeColor, float strokeWidth, 
                      CanvasStrokeStyle strokeStyle);
}

The Transform property in CanvasStroke gets or sets the Transform property of the stroke brush.

IGeometrySurface

The IGeometrySurface interface is now updated to include ICanvasStroke to specify the definition of the stroke to be rendered on its geometry.

The following APIs are provided in ICompositionGenerator to create a IGeometrySurface

IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry, 
    ICanvasStroke stroke);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry, 
    Color fillColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasStroke stroke, Color fillColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    Color fillColor, Color backgroundColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasStroke stroke, Color fillColor, Color backgroundColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasBrush fillBrush);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasStroke stroke, ICanvasBrush fillBrush);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasBrush fillBrush, ICanvasBrush backgroundBrush);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasStroke stroke, ICanvasBrush fillBrush, ICanvasBrush backgroundBrush);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasBrush fillBrush, Color backgroundColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasStroke stroke, ICanvasBrush fillBrush, Color backgroundColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    Color fillColor, ICanvasBrush backgroundBrush);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasStroke stroke, Color fillColor, ICanvasBrush backgroundBrush);    
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);

IGeometrySurface now provides several additional APIs which allow you to update its geometry, size, stroke, fill and background (and thus the shape of the Visual).

void Redraw(CanvasGeometry geometry);
void Redraw(ICanvasStroke stroke);
void Redraw(Color fillColor);
void Redraw(ICanvasStroke stroke, Color fillColor);
void Redraw(Color fillColor, Color backgroundColor);
void Redraw(ICanvasStroke stroke, Color fillColor, Color backgroundColor);
void Redraw(ICanvasBrush fillBrush);
void Redraw(ICanvasStroke stroke, ICanvasBrush fillBrush);
void Redraw(ICanvasBrush fillBrush, ICanvasBrush backgroundBrush);
void Redraw(ICanvasStroke stroke, ICanvasBrush fillBrush, 
    ICanvasBrush backgroundBrush);
void Redraw(Color fillColor, ICanvasBrush backgroundBrush);
void Redraw(ICanvasStroke stroke, Color fillColor, 
    ICanvasBrush backgroundBrush);
void Redraw(ICanvasBrush fillBrush, Color backgroundColor);
void Redraw(ICanvasStroke stroke, ICanvasBrush fillBrush, 
    Color backgroundColor);
void Redraw(Size size, CanvasGeometry geometry);
void Redraw(Size size, CanvasGeometry geometry, ICanvasStroke stroke);
void Redraw(Size size, CanvasGeometry geometry, Color fillColor);
void Redraw(Size size, CanvasGeometry geometry, ICanvasStroke stroke, 
    Color fillColor);
void Redraw(Size size, CanvasGeometry geometry, Color fillColor, 
    Color backgroundColor);
void Redraw(Size size, CanvasGeometry geometry, ICanvasStroke stroke,
    Color fillColor, Color backgroundColor);
void Redraw(Size size, CanvasGeometry geometry, ICanvasBrush fillBrush);
void Redraw(Size size, CanvasGeometry geometry, ICanvasBrush fillBrush,
    ICanvasBrush backgroundBrush);
void Redraw(Size size, CanvasGeometry geometry, ICanvasStroke stroke,
    ICanvasBrush fillBrush, ICanvasBrush backgroundBrush);
void Redraw(Size size, CanvasGeometry geometry, ICanvasBrush fillBrush,
    Color backgroundColor);
void Redraw(Size size, CanvasGeometry geometry, ICanvasStroke stroke,
    ICanvasBrush fillBrush, Color backgroundColor);
void Redraw(Size size, CanvasGeometry geometry, Color fillColor,
    ICanvasBrush backgroundBrush);
void Redraw(Size size, CanvasGeometry geometry, ICanvasStroke stroke,
    Color fillColor, ICanvasBrush backgroundBrush);

CanvasDrawingSession extension methods

The following extension methods have been created for CanvasDrawingSession to incorporate ICanvasStroke in its DrawXXX() methods

public static void DrawCircle(this CanvasDrawingSession session,
    Vector2 centerPoint, float radius, ICanvasStroke stroke);
public static void DrawCircle(this CanvasDrawingSession session,
    float x, float y, float radius, ICanvasStroke stroke);
public static void DrawEllipse(this CanvasDrawingSession session, 
    Vector2 centerPoint, float radiusX, float radiusY, ICanvasStroke stroke);
public static void DrawEllipse(this CanvasDrawingSession session,
    float x, float y, float radiusX, float radiusY, ICanvasStroke stroke);
public static void DrawGeometry(this CanvasDrawingSession session,
    CanvasGeometry geometry, ICanvasStroke stroke);
public static void DrawGeometry(this CanvasDrawingSession session,
    CanvasGeometry geometry, Vector2 offset, ICanvasStroke stroke);
public static void DrawGeometry(this CanvasDrawingSession session,
    CanvasGeometry geometry, float x, float y, ICanvasStroke stroke);
public static void DrawLine(this CanvasDrawingSession session, Vector2 point0, 
    Vector2 point1, ICanvasStroke stroke);
public static void DrawLine(this CanvasDrawingSession session, float x0, 
    float y0, float x1, float y1, ICanvasStroke stroke);
public static void DrawRectangle(this CanvasDrawingSession session, Rect rect, 
    ICanvasStroke stroke) ;
public static void DrawRectangle(this CanvasDrawingSession session, float x, 
    float y, float w, float h, ICanvasStroke stroke);
public static void DrawRoundedRectangle(this CanvasDrawingSession session, 
    Rect rect, float radiusX, float radiusY, ICanvasStroke stroke);
public static void DrawRoundedRectangle(this CanvasDrawingSession session, float x,
    float y, float w, float h, float radiusX, float radiusY, ICanvasStroke stroke);

Win2d MiniLanguage specification

The CanvasGeometry class facilitates the drawing and manipulation of complex geometrical shapes. These shapes can be outlined with a stroke and filled with a brush (which can be a solid color, a bitmap pattern or a gradient).

While the CanvasGeometry class provides various static methods to create predefined shapes like Circle, Ellipse, Rectangle, RoundedRectangle, the CanvasPathBuilder class provides several methods to create freeform CanvasGeometry objects.

Creation of a complex freeform geometric shape may involve invoking of several CanvasPathBuilder commands. For example, the following code shows how to create a triangle geometry using CanvasPathBuilder

CanvasPathBuilder pathBuilder = new CanvasPathBuilder(device);

pathBuilder.BeginFigure(1, 1);
pathBuilder.AddLine(300, 300);
pathBuilder.AddLine(1, 300);
pathBuilder.EndFigure(CanvasFigureLoop.Closed);

CanvasGeometry triangleGeometry = CanvasGeometry.CreatePath(pathBuilder);

Win2d Mini Language is a powerful and sophisticated language which facilitates specifying complex geometries, color, brushes, strokes and stroke styles in a more compact manner.

Using Win2d Mini-Language, the geometry in above example can be created in the following way

string pathData = “M 1 1 300 300 1 300 Z”;
CanvasGeometry triangleGeometry = CanvasObject.CreateGeometry(device, pathData);

Win2d Mini Language now contains additional commands which allow easy creation of ICanvasBrush, ICanvasStroke, Color and CanvasStrokeStyle.

Win2d Mini Language is based on the SVG (Scalable Vector Graphics) Path language specification.

The following specification document describes the Win2d Markup Language in detail.

Win2d Mini Language Specification.pdf

Parsing Win2d Mini Language with CanvasObject

The CanvasObject static class replaces the CanvasGeometryParser static class and provides APIs that parse the Win2d Mini Language and instantiate the appropriate objects.

Color

From Hexadecimal Color string

There are two APIs that convert the hexadecimal color string in #RRGGBB or #AARRGGBB format to the corresponding Color object. The ‘#‘ character is optional.

public static Color CreateColor(string hexColor);
public static bool TryCreateColor(string hexColor, out Color color);

The first API will raise an ArgumentException if the argument is not in the correct format while the second API will attempt to convert the color string without raising an exception.

From High Dynamic Range Color string

The following API Converts a Vector4 High Dynamic Range Color to Color object. Negative components of the Vector4 will be sanitized by taking the absolute value of the component. The HDR Color components should have value in the range [0, 1]. If their value is more than 1, they will be clamped at 1. Vector4’s X, Y, Z, W components match to Color’s R, G, B, A components respectively.

public static Color CreateColor(Vector4 hdrColor);

CanvasGeometry

The following API converts a CanvasGeometry path string to CanvasGeometry object

public static CanvasGeometry CreateGeometry(ICanvasResourceCreator resourceCreator,
                                            string pathData, 
                                            StringBuilder logger = null);

The logger parameter in this method is an option argument of type StringBuilder which can be used to obtain the CanvasPathBuilder commands in text format. It is mainly intended for information/debugging purpose only.

NOTE: This method replaces CanvasGeometryParser.Parse() method (defined in CompositionProToolkit v0.5).

ICanvasBrush

The following API converts a brush data string to ICanvasBrush object

public static ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator, 
                                       string brushData);

CanvasStrokeStyle

The following API converts a style data string to CanvasStrokeStyle object

public static CanvasStrokeStyle CreateStrokeStyle(string styleData);

ICanvasStroke

The following API converts a stroke data string to ICanvasStroke object

public static ICanvasStroke CreateStroke(ICanvasResourceCreator resourceCreator,
                                         string strokeData);

CanvasPathBuilder extension methods

Two extension methods have been added to CanvasPathBuilder to add a Rectangle Figure and a RoundedRectangle figure to the path.

public static void AddRectangleFigure(this CanvasPathBuilder pathBuilder, float x, float y,
    float width, float height)
public static void AddRoundedRectangleFigure(this CanvasPathBuilder pathBuilder, float x, float y,
    float width, float height, float radiusX, float radiusY);

Check out the Sample Gallery project where you can interact with the CanvasObject class by providing the SVG/XAML path data and converting it to CanvasGeometry. You can alter the StrokeThickness, StrokeColor and FillColor of the rendered geometry.

You can view the CanvasPathBuilder commands called to create the parsed geometry.

Creating multilayer Vector shapes with CanvasElement

The CanvasElement class allows the creation of multilayer vector shapes. Each layer is represented by the CanvasRenderLayer class. The CanvasRenderLayer implements the ICanvasRenderLayer interface which encapsulates three properties required for rendering a layer

  • CanvasGeometry – The geometry to be rendered on the layer.
  • ICanvasBrush – The brush to fill the geometry.
  • ICanvasStroke – The stroke to outline the geometry.

The CanvasRenderLayer provides several constructors which accept the CanvasGeometry, ICanvasBrush and ICanvasStroke objects or their respective data definition strings.

public CanvasRenderLayer(CanvasGeometry geometry, ICanvasBrush brush,
    ICanvasStroke stroke);
public CanvasRenderLayer(ICanvasResourceCreator creator, string geometryData,
    string brushData, string strokeData);
public CanvasRenderLayer(ICanvasResourceCreator creator, string geometryData,
    ICanvasBrush brush, Color strokeColor, float strokeWidth = 1f);
public CanvasRenderLayer(ICanvasResourceCreator creator, string geometryData,
    ICanvasBrush brush, Color strokeColor, float strokeWidth, 
    CanvasStrokeStyle strokeStyle);
public CanvasRenderLayer(ICanvasResourceCreator creator, string geometryData,
    ICanvasBrush brush, ICanvasBrush strokeBrush, float strokeWidth = 1);
public CanvasRenderLayer(ICanvasResourceCreator creator, string geometryData, 
    ICanvasBrush brush, ICanvasBrush strokeBrush, float strokeWidth, 
    CanvasStrokeStyle strokeStyle);

The CanvasElement class implements the ICanvasElement interface which provides the following properties and APIs

interface ICanvasElement
{
    List Layers { get; set; }
    bool ScaleStroke { get; set; }
void Render(CanvasDrawingSession session, float width, float height, Vector2 offset,
    Vector4 padding, float rotation);

SpriteVisual CreateVisual(ICompositionGenerator generator, float width, float height,
    Vector2 offset, Vector4 padding, float rotation);

}

The Render API renders the CanvasElement layers on a CanvasControl or a CanvasAnimated control for the given dimensions, offset, padding and rotation.

The CreateVisual API creates a SpriteVisual which contains several SpriteVisuals, each representing a layer of the CanvasElement.

The constructor of CanvasElement requires the base dimensions of the element, the layers of the CanvasElement and an option whether to scale the stroke width when the CanvasElement is scaled (default is true).

public CanvasElement(float baseWidth, float baseHeight, IEnumerable layers,
            bool scaleStroke = true);

Using the base dimension, the CanvasLayer is able to scale its layers to any valid dimensions.

The layers are rendered based on their order in the layers list, i.e. the first layer in the list is rendered first and the subsequent layers are drawn on top of the previous layer. Thus the first layer in the list appears at the bottom and the last layer in the list is rendered top most.

CanvasElement can be primarily used to create vector based icons for UWP applications.

The following example code

CanvasElement _element;

private void OnCanvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args)
{
    var geom1 = CanvasObject.CreateGeometry(sender, "O 116 116 128 128");
    var fill1 = CanvasObject.CreateBrush(sender, "SC #00adef");
    var stroke1 = CanvasObject.CreateStroke(sender, "ST 8 SC #2a388f");
    var layer1 = new CanvasRenderLayer(geom1, fill1, stroke1);
    
    var geom2 = CanvasObject.CreateGeometry(sender, "U 56 56 64 64 8 8");
    var fill2 = CanvasObject.CreateBrush(sender, "SC #ed1c24");
    var stroke2 = CanvasObject.CreateStroke(sender, "ST 2 SC #404041");
    var layer2 = new CanvasRenderLayer(geom2, fill2, stroke2);

    var geom3 = CanvasObject.CreateGeometry(sender, "U 136 56 64 64 8 8");
    var fill3 = CanvasObject.CreateBrush(sender, "SC #38b449");
    var layer3 = new CanvasRenderLayer(geom3, fill3, stroke2);

    var geom4 = CanvasObject.CreateGeometry(sender, "U 56 136 64 64 8 8");
    var fill4 = CanvasObject.CreateBrush(sender, "SC #fff100");
    var layer4 = new CanvasRenderLayer(geom4, fill4, stroke2);

    var geom5 = CanvasObject.CreateGeometry(sender, "R 96 96 64 64");
    var fill5 = CanvasObject.CreateBrush(sender, "SC #f7931d");
    var layer5 = new CanvasRenderLayer(geom5, fill5, stroke2);

    var layers = new List { layer1, layer2, layer3, layer4, layer5 };
    _element = new CanvasElement(256f, 256f, layers);
}

private void OnCanvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
    _element?.Render(args.DrawingSession, 512f, 512f, Vector2.Zero, new Vector4(10), 0f);
}

will create the following

Utility methods and Constants

Float constants

The following floating point constants have been defined in the static class Float

  • Float.Pi – same as (float)Math.PI radians (180 degrees).
  • Float.TwoPi – Two times (float)Math.PI radians (360 degrees).
  • Float.PiByTwo – half of (float)Math.PI radians (90 degrees).
  • Float.PiByThree – one third of (float)Math.PI radians (600 degrees).
  • Float.PiByFour – one fourth of (float)Math.PI radians (45 degrees).
  • Float.PiBySix – one sixth of (float)Math.PI radians (30 degrees).
  • Float.ThreePiByTwo – three times half of (float)Math.PI radians (270 degrees).
  • Float.DegreeToRadians – 1 degree in radians.
  • Float.RadiansToDegree – 1 radian in degrees.

ToSingle extension method

The Single() extension method for System.Double is now marked as obsolete. Your code will still work, but you will receive a warning during build.

The Single() extension method is now replaced with ToSingle() extension method. It does the same job – converts System.Double to System.Single.

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

Dynamic FluidWrapPanel using AdaptiveTriggers

A few days ago, I was asked if the FluidWrapPanel could resize its children uniformly to use the maximum available space. My initial response was that FluidWrapPanel acts as a container and does not influence its children’s sizes. If it resized its children’s size to a very small size then it may make the content unreadable.

Then I got further details of the requested scenario – The FluidWrapPanel has 12 children. When the size of the FluidWrapPanel changes the layout of the children should also change (they should also be resized to occupy the maximum available space). For example when the FluidWrapPanel is displayed in Portrait mode (having a certain width), the children should be displayed in a 6 x 2 layout, when in Landscape mode (having medium size) they should be displayed in 4 x 3 layout and when in Landscape mode (with maximum width) the layout should change to 3 x 4. All this should happen without affecting the fluidity of the FluidWrapPanel.

DynamicFWP

Well, let me explain FluidWrapPanel’s behavior in this scenario. FluidWrapPanel will not know whether the children should be displayed in a 6 x 2 layout, a 4 x 3 layout or in a single row. Its logic for arranging its children is governed by the following factors

  • ItemWidth dependency property
  • ItemHeight dependency property

If the child’s width and height are less than the ItemWidth and ItemHeight respectively, there will be a gap between adjacent children. If they are more, the adjacent children with overlap each other.

So the solution is to listen to the SizeChanged event of the FluidWrapPanel and when it occurs

  • Calculate which layout needs to be displayed in the FluidWrapPanel
  • Based on the layout and the size of the FluidWrapPanel, calculate the desired item width and height.
  • Set each of the child’s width and height to the calculated item width and height respectively.
  • Set the ItemWidth and ItemHeight dependency properties of the FluidWrapPanel to the calculated item width and height respectively.

This solution works best when all the children are of same size i.e. having width and height equal to the ItemWidth and ItemHeight respectively.

Since this solution is applicable mainly in UWP applications, I thought it was best to utilize AdaptiveTriggers to solve this issue with ease.

I made a sample UWP project to implement this solution. Here is what I did.

1. Create a New Universal Project and add reference to WPFSpark.UWP NuGet package.

2. Create a UserControl called FluidItemControl which will be a child to the FluidWrapPanel. It just consists of a border (having random background) and a textblock (showing a number).

I came upon a strange behavior of Page class. If you add a new dependency property to MainPage.xaml.cs and try to bind to it in MainPage.xaml, then that property will not be recognized. After trying out several ways unsuccessfully, I found a workaround in StackOverflow. It seems you have to define a base class, say PageBase, which derives from Page and then define your dependency property there. Then MainPage should derive from PageBase. Now the binding will work. Strange but true!
3. So here is the definition of PageBase class.
public class PageBase : Page
{
    public enum PageDisplayType
    {
        None = 0,
        Display3x4 = 1,
        Display4x3 = 2,
        Display6x2 = 3
    }

    #region PageDisplay
    /// <summary>
    /// PageDisplay Dependency Property
    /// </summary>
    public static readonly DependencyProperty PageDisplayProperty =
        DependencyProperty.Register("PageDisplay", typeof(PageDisplayType), typeof(MainPage),
            new PropertyMetadata(PageBase.PageDisplayType.None, OnPageDisplayChanged));

    /// <summary>
    /// Gets or sets the PageDisplay property. This dependency property 
    /// indicates the number of rows and columns to set in the FluidWrapPanel.
    /// </summary>
    public PageBase.PageDisplayType PageDisplay
    {
        get { return (PageBase.PageDisplayType)GetValue(PageDisplayProperty); }
        set { SetValue(PageDisplayProperty, value); }
    }

    /// <summary>
    /// Handles changes to the PageDisplay property.
    /// </summary>
    /// <param name="d">MainPage</param>
    /// <param name="e">DependencyProperty changed event arguments</param>
    private static void OnPageDisplayChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var page = (PageBase)d;
        var oldPageDisplay = (PageBase.PageDisplayType)e.OldValue;
        var newPageDisplay = page.PageDisplay;
        page.OnPageDisplayChanged(oldPageDisplay, newPageDisplay);
    }

    /// <summary>
    /// Provides derived classes an opportunity to handle changes to the PageDisplay property.
    /// </summary>
    /// <param name="oldPageDisplay">Old Value</param>
    /// <param name="newPageDisplay">New Value</param>
    async void OnPageDisplayChanged(PageBase.PageDisplayType oldPageDisplay, PageBase.PageDisplayType newPageDisplay)
    {
        switch (newPageDisplay)
        {
            case PageBase.PageDisplayType.Display3x4:
                rows = 3;
                columns = 4;
                break;
            case PageBase.PageDisplayType.Display4x3:
                rows = 4;
                columns = 3;
                break;
            case PageBase.PageDisplayType.Display6x2:
                rows = 6;
                columns = 2;
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(newPageDisplay), newPageDisplay, null);
        }

        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, RefreshPanel);
    }

    #endregion

    protected double rows = 0;
    protected double columns = 0;

    protected virtual void RefreshPanel()
    {
        
    }
}

4. I defined the AdaptiveTriggers in MainPage.xaml. These triggers will change the PageDisplay dependency property based on the width of the MainPage.

<local:PageBase x:Class="TestFWPanel.MainPage"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="using:TestFWPanel"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:wpfSpark="using:WPFSpark"
                x:Name="RootPage"
                mc:Ignorable="d">
    <local:PageBase.Resources>
        <x:Double x:Key="SmallScreenWidth">0</x:Double>
        <x:Double x:Key="MediumScreenWidth">540</x:Double>
        <x:Double x:Key="LargeScreenWidth">900</x:Double>
    </local:PageBase.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState x:Name="Small">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="{StaticResource SmallScreenWidth}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="RootPage.PageDisplay"
                                Value="Display6x2"></Setter>
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Medium">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="{StaticResource MediumScreenWidth}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="RootPage.PageDisplay"
                                Value="Display4x3" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Large">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="{StaticResource LargeScreenWidth}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="RootPage.PageDisplay"
                                Value="Display3x4" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Grid x:Name="ContainerGrid"
              HorizontalAlignment="Stretch"
              VerticalAlignment="Stretch">
            <wpfSpark:FluidWrapPanel x:Name="fwPanel"
                                     Background="Beige"
                                     Margin="0"
                                     HorizontalAlignment="Stretch"
                                     VerticalAlignment="Stretch"
                                     ItemWidth="2"
                                     ItemHeight="2"
                                     IsComposing="True"
                                     SizeChanged="OnFWPSizeChanged"></wpfSpark:FluidWrapPanel>
        </Grid>
    </Grid>
</local:PageBase>

5. The size of the FluidWrapPanel also changes when the size of the MainPage changes. I am calling the RefreshPanel() method when this happens. This method updates the ItemWidth and ItemHeight properties along with the sizes of the children accordingly.

protected override void RefreshPanel()
{
    if ((rows.IsZero()) || (columns.IsZero()))
        return;

    var width = Math.Floor(fwPanel.Width/columns);
    var height = Math.Floor(fwPanel.Height/rows);

    foreach (var child in fwPanel.FluidItems.OfType<FluidItemControl>())
    {
        child.Width = width;
        child.Height = height;
    }

    fwPanel.ItemWidth = width;
    fwPanel.ItemHeight = height;
}

You can find the full source code here.

Visual Studio:TaskKill only if Process exists

My primary tool for development is Visual Studio. Often while building and testing applications, I have face the following scenario numerous times:

  • I build and run the application (Ctrl + F5).
  • I test my application and feel the need for some more changes in the code.
  • I go back to the code and modify and build again.
  • Visual Studio gives me the following error message:

Unable to copy file “MyApp.exe” to “\Debug\MyApp.exe”. The process cannot access the file ‘..\Debug\MyApp.exe’ because it is being used by another process.
Only then I realize that I had forgotten to close the application before rebuilding. 😦

I was thinking of finding a way to automatically close the application before each build. Initially I put the command “taskkill /f /im MyApp.exe” in the Pre-Build events of the project but this worked only if the application was running. If the application was already closed, the build failed with an error message saying that the script failed with an exit code 1.

I then found another solution on the web (link). This still gave me the same error. So, I tweaked it further to arrive at this script:

tasklist /FI "IMAGENAME eq $(TargetName).exe" 2>NUL | find /I /N "$(TargetName).exe">NUL
if "%ERRORLEVEL%"=="0" (taskkill /f /im $(TargetName).exe
) else ( exit 0)

It proved very effective and time-saving too!

Note: The above script will work only if you are building an .exe application. If you are building a DLL, try putting the name of the application, which is using the DLL, in place of $(TargetName).

Happy Coding! 🙂

P.S. I found another simpler solution here

taskkill /F /IM $(TargetName).exe 2>&1 | exit /B 0