Creating a "League of Legends" Inspired Play Button in WPF

This article emphasizes the process of leveraging WPF features to create a versatile user interface component and offers a new perspective on open-source development. The article also explores advanced WPF features like animations and triggers to enhance the user interaction experience.

Abstract

League of Legends Play Button

This article provides a detailed explanation and analysis of developing a PLAY button inspired by the game "League of Legends" using pure WPF technology. It emphasizes the process of leveraging WPF features to create a versatile user interface component and offers a new perspective on open-source development. The article also explores advanced WPF features like animations and triggers to enhance the user interaction experience.

League of Legends Play Button

We have turned the content of this article into an instructional video and posted it on YouTube, where everyone can practice along with the video.

Introduction

User interface components are crucial for enhancing the user experience. In games, a responsive and visually appealing PLAY button serves as the gateway to the world of entertainment. This article showcases the process of creating a PLAY button using WPF, which provides a powerful framework for building rich desktop applications.

Project Background

The project discussed in this article aims to demonstrate the capabilities of WPF technology as comprehensively as possible. We released this project a few years ago and received a tremendous positive response, which has continued to motivate us to contribute to open-source development. While .NET technologies evolve, we are continually updating and refining the code previously shared on GitHub. Given the extensive content covered by this overall project, we decided to break it down and provide a detailed analysis of each part's composition and technical focus, hoping to assist more WPF enthusiasts in their learning journey.

Button Composition

League of Legends Play Button

By using an analyzer, we can see that this PLAY button inherits properties from WPFToggleButton. On the left side, there's a logo from the "League of Legends" game, while the right side comprises multiple elements such as Borders, images, and text with different designs. Additionally, interactive mouseover and checked trigger effects are added.

Key Content Analysis

1. Creating Irregular Shapes

League of Legends Play Button

The first two graphics can be easily encoded using Border controls. However, the third graphic, which includes a pointed end and an arc, cannot be encoded using a simple Border. Therefore, our initial thought might be to use a Polygon and coordinates for drawing. Still, the Polygon property cannot provide functionality for drawing arcs. Hence, we should use the Path control for encoding.

Detailed Analysis

<Style TargetType="{x:Type Path}" x:Key="Arrow">
    <Setter Property="Fill" Value="#1E2328"/>
    <Setter Property="Stroke" Value="{StaticResource ArrowStroke}"/>
    <Setter Property="StrokeThickness" Value="2"/>
    <Setter Property="Data" Value="M 0,0 L 103,0 L 118,14 L 103,28 L 0,28 C 10,14 0,0 0,0 Z"/>
    <Setter Property="Margin" Value="40 5 4 -5"/>
    <Setter Property="Effect">
        <Setter.Value>
            <DropShadowEffect BlurRadius="5" ShadowDepth="2"/>
        </Setter.Value>
    </Setter>
</Style>

In WPF, the Path control is a powerful tool for drawing various shapes and outlines. The Path control uses path data to define shapes, and path data consists of a series of commands and coordinates that specify how to draw the shape.

It's fundamental properties include:

  • Data Property: The Data property is a crucial attribute of the Path control, used to specify path data, which consists of a series of commands and coordinates to describe the outline of a shape. The format of path data includes various commands such as MoveTo (M), LineTo (L), CurveTo (C), ClosePath (Z), etc., combined with coordinates to define shapes. By providing path data in the Data property, we can create various shapes, including line segments, curves, polygons, and more.
  • Fill Property: The Fill property is used to specify the fill color inside a shape. It allows us to use colors, gradients, patterns, or transparency to fill the interior of a shape.
  • Stroke Property: The Stroke property is used to specify the color of the outline of a shape. It allows you to define the color of the outline lines using various colors.
  • StrokeThickness Property: The StrokeThickness property is used to specify the thickness of the outline lines. It determines the width of the outline.
  • Commands and Coordinates: Path data consists of a series of commands and coordinates, where these commands instruct WPF on how to draw shapes from one point to another. Common path commands include:
    • M (MoveTo): It moves the drawing point to the specified coordinates.  
    • L (LineTo): It draws a straight line to the specified coordinates.  
    • C (CurveTo): It draws a B&eacute;zier curve, using control points to define the curve&#39;s shape.  
    • Z (ClosePath): It closes the path, connecting the current point to the starting point, forming a closed shape.

The Data property is a critical attribute of the Path control, used to specify path data that includes commands and coordinates for defining the shape's outline. Path data employs a series of commands to describe the contour of a path. Here is a detailed explanation of the commands and coordinates in the path data for the project:

League of Legends Play Button

We can simply interpret this as X/Y coordinate axes. We set the length of this shape to be 118, and the width to be 28:

M 0,0: This is a "MoveTo" command, which moves the drawing point to the coordinates (0, 0), representing the starting point.

L 103,0: This is a "LineTo" command, which draws a straight line from the current point (0, 0) to the coordinates (103, 0). Subsequently, it continues to draw lines to (118, 14), (103, 28), and (0, 28).

Since this is a symmetrical shape, the Y-coordinate of the second line is half the total height of the shape: 14.

Next is the part for drawing curves: C 10,14 0,0 0,0 z: This is a "Bezier Curve" command, defining a Bézier curve where the preceding points are control points, and the subsequent point is the endpoint. This command defines a Bézier curve with control point (10, 14) and endpoint (0, 0), and it uses the 'z' command to close the path by connecting it to the starting point (0, 0).

2. Creating Gradient Colors

 <LinearGradientBrush x:Key="ArrowStroke" StartPoint="0.5,0" EndPoint="0.5,1" >
      <GradientStop Color="#CC3FE7EE" Offset="0"/>
      <GradientStop Color="#CC006D7D" Offset="0.5"/>
      <GradientStop Color="#CC0493A7" Offset="1"/>
 </LinearGradientBrush>

 <LinearGradientBrush x:Key="ArrowStrokeOver" StartPoint="0.5,0" EndPoint="0.5,1" >
      <GradientStop Color="#FFAFF5FF" Offset="0"/>
      <GradientStop Color="#FF46E6FF" Offset="0.5"/>
      <GradientStop Color="#FF00ADD4" Offset="1"/>
 </LinearGradientBrush>

 <LinearGradientBrush x:Key="ArrowFillOver" StartPoint="0.5,0" EndPoint="0.5,1" >
      <GradientStop Color="#FF1D3B4A" Offset="0"/>
      <GradientStop Color="#FF082734" Offset="1"/>
 </LinearGradientBrush>

In this section of the game, the stroke part is not a simple solid color but a gradient color composed of multiple hues. To achieve this effect, we can utilize LinearGradientBrush for customizing colors.

Key Properties and Usage of LinearGradientBrush

League of Legends Play Button

  • StartPoint and EndPoint:
    StartPoint specifies the gradient's starting point, typically represented using relative coordinates, where (0, 0) is the top-left corner, and (1, 1) is the bottom-right corner. EndPoint specifies the gradient's ending point, also using relative coordinates.

  • GradientStops:
    GradientStops is a collection of GradientStop objects, each defining a color and a relative position (Offset). The Color property of GradientStop defines the color at the specified position, and the Offset property defines the color's position in the gradient, typically ranging from 0 to 1.

  • Gradient Direction:
    The gradient's direction is determined by StartPoint and EndPoint. For example, if StartPoint is (0, 0) and EndPoint is (1, 1), the gradient will transition from the top-left corner to the bottom-right corner.

  • Gradient Type:
    LinearGradientBrush defaults to linear gradient, where colors transition along a straight line. By adjusting StartPoint and EndPoint, you can change the gradient's direction and starting point to create various gradient effects.

In this project, we aim to create a vertical gradient starting from the center of the shape and moving downwards. Therefore, we set StartPoint to (0.5, 0), indicating that the gradient's starting point is at the top center (horizontal midpoint). EndPoint is set to (0.5, 1), indicating that the gradient's ending point is at the bottom center (horizontal midpoint).

League of Legends Play Button

Next, the GradientStops collection includes three GradientStop objects, each defining different colors and relative positions:

  1. The third GradientStop:
    Color is set to #CC3FE7EE, representing a color value. Offset is set to 0, indicating that this color is located at the starting point of the gradient.

  2. The second GradientStop:
    Color is set to #CC006D7D. Offset is set to 0.5, indicating that this color is located at the midpoint of the gradient.

  3. The third GradientStop:
    Color is set to #CC0493A7. Offset is set to 1, indicating that this color is located at the ending point of the gradient.

3. Handling Path and Border for Thickness

League of Legends Play Button

In the Border control:

  • The borderlines of a Border control are contained within the Border itself. The thickness of the borderlines is controlled by the BorderThickness property, specifying the width of the borderlines in device-independent pixels (DIPs).

In the Path control:

  • The borderlines of a Path control are drawn based on the StrokeThickness property's center position. StrokeThickness controls the thickness of the borderlines, representing the distance by which the borderlines extend from the center.

In this fixed-size graphic, both Border and Path have their thickness set to 2, and Margin is set to 4 4 4 4. However, this setup reveals that the upper border of the Path extends beyond the Border.

Therefore, adjustments are needed in the Margin of the Path considering StrokeThickness. The left Margin is already set to 40, which can cover the GreenLine, so there's no issue. The top Margin should be increased by 1 pixel, set to 5 pixels, while the right and bottom Margins need no change. Since the Path has a fixed size of 118x28, only the left and top Margins need adjustment.

Additionally, since the top Margin increases by 5 pixels, the bottom may appear cut off, as in this situation. To prevent this, you can set the bottom Margin to -5 pixels. This balances the layout by removing the 5 pixels added at the top. Another approach is to keep the bottom Margin at 0 pixels. Both methods prevent the bottom from being cut off due to the added top Margin.

4. Creating Animations Using Jamesnet.WPF Nuget

<Application x:Class="VickyPlayButton.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:james="https://jamesnet.dev/xaml/presentation"
             StartupUri="MainWindow.xaml">

    <Application.Resources>
        <Style TargetType="{x:Type ToggleButton}">
            <Setter Property="Height" Value="38"/>
            <Setter Property="Width" Value="165"/>
            <Setter Property="Foreground" Value="#FFFFFF"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ToggleButton}">
                        <ControlTemplate.Resources>
                            <Storyboard x:Key="Checked">
                                <james:ThickItem Mode="CubicEaseInOut" TargetName="play" Property="Margin" Duration="0:0:0:0.5" To="30 100 0 0"/>
                                <james:ThickItem Mode="CubicEaseInOut" TargetName="stop" Property="Margin" Duration="0:0:0:0.5" To="30 0 0 0"/>
                            </Storyboard>
                            <Storyboard x:Key="UnChecked">
                                <james:ThickItem Mode="CubicEaseInOut" TargetName="play" Property="Margin" Duration="0:0:0:0.5" To="30 0 0 0"/>
                                <james:ThickItem Mode="CubicEaseInOut" TargetName="stop" Property="Margin" Duration="0:0:0:0.5" To="30 0 0 100"/>
                            </Storyboard>
                        </ControlTemplate.Resources>
                        <Grid Background="{TemplateBinding Background}">
                            <Border Style="{StaticResource GoldLine}"/>
                            <Image Style="{StaticResource Emblem}"/>
                            <Border Style="{StaticResource GreenLine}"/>
                            <Path x:Name="path" Style="{StaticResource Arrow}"/>
                            <Grid>
                                <Grid.Clip>
                                    <RectangleGeometry Rect="0,5,165,28"/>
                                </Grid.Clip>
                                <TextBlock x:Name="play" Style="{StaticResource Play}"/>
                                <TextBlock x:Name="stop" Style="{StaticResource Stop}"/>
                            </Grid>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="path" Property="Fill" Value="{StaticResource ArrowFillOver}"/>
                                <Setter TargetName="path" Property="Stroke" Value="{StaticResource ArrowStrokeOver}"/>
                                <Setter Property="Foreground" Value="#FFFCF1DC"/>
                                <Setter Property="Cursor" Value="Hand"/>
                            </Trigger>
                            <Trigger Property="IsChecked" Value="True">
                                <Setter TargetName="path" Property="Fill" Value="#1E2328"/>
                                <Setter TargetName="path" Property="Stroke" Value="#5C5B57"/>
                                <Setter Property="Foreground" Value="#3C3C41"/>
                                <Trigger.EnterActions>
                                    <BeginStoryboard Storyboard="{StaticResource Checked}"/>
                                </Trigger.EnterActions>
                                <Trigger.ExitActions>
                                    <BeginStoryboard Storyboard="{StaticResource UnChecked}"/>
                                </Trigger.ExitActions>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Application.Resources>
</Application>

In WPF, you can create various dynamic animations to make user interfaces more engaging. In this project, Thickness animations are used to add an interesting animation to the text part of the TextBlock.

Animations can be defined using ControlTemplate.Resources, which allows you to define two animation resources: "Checked" and "UnChecked." When checked, the "Play" text is removed, and the "Stop" text moves in, while in the "UnChecked" state, the "Stop" text is removed, and the "Play" text moves in. This creates an animation resembling a flip effect.

To facilitate the creation and use of animations, we have compiled and organized various animations from WPF into the Jamesnet.WPF Nuget package. By simply adding this package, you can easily use and write animations.

5. Using the Clip Property

<Grid Background="{TemplateBinding Background}">
       <Border Style="{StaticResource GoldLine}"/>
       <Image Style="{StaticResource Emblem}"/>
       <Border Style="{StaticResource GreenLine}"/>
       <Path x:Name="path" Style="{StaticResource Arrow}"/>
    <Grid>
       <Grid.Clip>
           <RectangleGeometry Rect="0,5,165,28"/>
       </Grid.Clip>
           <TextBlock x:Name="play" Style="{StaticResource Play}"/>
           <TextBlock x:Name="stop" Style="{StaticResource Stop}"/>
    </Grid> 
</Grid>

Since elements within the Grid overlap each other, when creating the animation for text scrolling up and down, a visual issue may arise where the text appears to extend beyond the borders. To address this, the <Grid.Clip> property is used.

League of Legends Play Button

<Grid.Clip> is an XAML element used to define a clipping region that restricts the visible area of child elements. The clipping region is typically a shape, such as a rectangle, and only the content within the clipping region is displayed, while content outside it is hidden.

In this project, the <Grid.Clip> region is set within the size of the Path: Rect="0,5,165,28". This ensures that the text only appears within this region, resulting in the up-and-down scrolling effect within the Path.