Created by Olivier Selliez | olivier.selliez.dev@gmail.com
A lightweight, class-based animation and tweening engine for Windows Forms in PowerShell. This library implements Robert Penner's easing equations to deliver silky-smooth, non-linear transitions and dynamic UI animations for desktop PowerShell applications.
And yes, it's quite useless but it's funny :)
- Features
- Project Architecture
- Getting Started
- Tween Class Reference
- Easing Algorithms Reference
- Advanced Patterns & Recipes
- Running the Interactive Demo
- Technical Notes & Best Practices
- License & Credits
- 41 Robert Penner Easing Curves: Includes all standard easing curves (Linear, Quad, Cubic, Quart, Quint, Sine, Expo, Circ, Elastic, Bounce, Back) across
In,Out,InOut, andOutInmodes. - Pure PowerShell 5.1+ OOP: Implemented natively using PowerShell classes for high performance and clean structure.
- Zero Binary Dependencies: Relies exclusively on built-in .NET
System.Windows.FormsandSystem.Drawingassemblies. - Comprehensive Control Coverage: Animate positions (
Point), colors (ForeColor/BackColorwith ARGB channel blending), numeric text strings, progress bars, and form opacity. - Event-Driven & Sequence-Ready: Native support for start delays, pause/resume, force-finish, and completion callbacks with arbitrary argument injection.
- Auto-Managed Lifecycle: Instantiated tweens automatically register to the active engine list and deregister themselves upon completion.
The library is organized into modular scripts under the Tweens/ directory:
PowerShell_TweenLibrary/
โโโ Demo.ps1 # Full interactive WinForms showcase application
โโโ run.bat # One-click batch launcher for Demo.ps1
โโโ Tweens/
โ โโโ TweensVariables.ps1 # Easing curve string constants ($Script:ease*)
โ โโโ Tweens.ps1 # OOP class definitions (Tween, TweenMoveTo, etc.)
โ โโโ TweensUpdater.ps1 # Global update loop and WinForms Timer manager
โ โโโ TweensMovement.ps1 # Easing math calculations and property modifiers
โโโ README.md # Project documentation
| File | Purpose | Key Responsibilities |
|---|---|---|
TweensVariables.ps1 |
Constants | Defines the 41 script-scoped easing identifier variables (e.g., $Script:easeBounceOut). |
Tweens.ps1 |
Data Models | Defines the Tween base class and specialized derived classes (TweenMoveTo, TweenColorARGB, etc.). |
TweensUpdater.ps1 |
Engine & Timer | Manages $Script:tweensTimer, $Script:tweensList, refresh rate, and frame-by-frame dispatching. |
TweensMovement.ps1 |
Math & Logic | Implements the core Ease math function and per-type mutation functions (MoveTo, ColorARGB, etc.). |
- PowerShell 5.1 (Windows PowerShell) or PowerShell 7+ (pwsh) on Windows.
- Standard .NET WinForms assemblies (
System.Windows.FormsandSystem.Drawing).
Import the library files into your script in the following order:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
. "$PSScriptRoot\Tweens\TweensVariables.ps1"
. "$PSScriptRoot\Tweens\Tweens.ps1"
. "$PSScriptRoot\Tweens\TweensUpdater.ps1"
. "$PSScriptRoot\Tweens\TweensMovement.ps1"The animation loop is driven by a System.Windows.Forms.Timer ($Script:tweensTimer). To ensure proper execution and prevent memory leaks, bind the timer to your Form's lifecycle events:
# 1. Start the animation loop when the window is displayed
$mainForm.Add_Shown({
$Script:tweensTimer.Start()
})
# 2. CRITICAL: Stop and dispose the timer when closing to release the UI thread
$mainForm.Add_Closing({
$Script:tweensTimer.Stop()
$Script:tweensTimer.Dispose()
})Tip
Customizing Frame Rate: The default refresh rate is 60 FPS ($Script:refreshRate = 60 in TweensUpdater.ps1). You can adjust this before starting the timer if needed.
Save and run the following self-contained script to see a button smoothly bounce into position:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# Dot-source the library
. "$PSScriptRoot\Tweens\TweensVariables.ps1"
. "$PSScriptRoot\Tweens\Tweens.ps1"
. "$PSScriptRoot\Tweens\TweensUpdater.ps1"
. "$PSScriptRoot\Tweens\TweensMovement.ps1"
# Create a Form
$form = [System.Windows.Forms.Form]::new()
$form.Text = "Tween Minimal Example"
$form.Size = [System.Drawing.Size]::new(400, 300)
$form.StartPosition = "CenterScreen"
# Create a Button
$btn = [System.Windows.Forms.Button]::new()
$btn.Text = "Click Me!"
$btn.Size = [System.Drawing.Size]::new(120, 40)
$btn.Location = [System.Drawing.Point]::new(20, 20)
$form.Controls.Add($btn)
# Trigger an animation on click
$btn.Add_Click({
$targetPos = [System.Drawing.Point]::new(220, 180)
$tween = [TweenMoveTo]::new($btn, $targetPos, 1.5, $Script:easeBounceOut)
$tween.setOnComplete({
$btn.Text = "Arrived!"
})
})
# Attach timer to form lifecycle
$form.Add_Shown({ $Script:tweensTimer.Start() })
$form.Add_Closing({
$Script:tweensTimer.Stop()
$Script:tweensTimer.Dispose()
})
# Show the GUI
[System.Windows.Forms.Application]::Run($form)All animation classes derive from the base Tween class. Whenever an instance of any Tween class is constructed, it is automatically registered to $Script:tweensList and will be updated on the next tick.
โโโโโโโโโโโโโโโโ
โ Tween โ (Base Class)
โโโโโโโโฌโโโโโโโโ
โโโโโโโโโโโโโโโโฌโโโโโดโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ
โ โ โ โ โ โ
โโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโ
โ TweenMoveTo โโTweenColorARGBโโTweenNumeric- โโTweenProgressBarโโ TweenOpacity โโ TweenWaiter โ
โ โโ โโ String โโ โโ โโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The foundation of all animations. Provides timing, delay, pausing, and callback management.
| Property | Type | Description |
|---|---|---|
control |
[System.Object] |
The target UI control or Form being animated. |
easing |
[string] |
Identifier of the Robert Penner easing curve. |
duration |
[double] |
Total duration in internal ticks (seconds * $refreshRate). |
delay |
[double] |
Remaining delay in internal ticks before animation starts. |
nbTicks |
[int] |
Number of elapsed ticks for the current phase. |
isPaused |
[bool] |
Whether the animation is currently paused. |
startTime |
[DateTime] |
Timestamp when the tween was instantiated. |
onComplete |
[ScriptBlock] |
Callback executed when the animation finishes. |
onCompleteArgs |
[System.Object[]] |
Optional arguments passed to the onComplete callback. |
| Method | Signature | Description | Example |
|---|---|---|---|
setDelay |
[void] setDelay([double]$seconds) |
Sets a delay in seconds before the animation begins. | $t.setDelay(0.5) |
setOnComplete |
[void] setOnComplete([scriptblock]$cb) |
Registers a scriptblock callback triggered upon completion. | $t.setOnComplete({ Write-Host "Done!" }) |
setOnComplete |
[void] setOnComplete([scriptblock]$cb, [object[]]$args) |
Registers a callback with custom arguments. | $t.setOnComplete({ param($t, $msg) Write-Host $msg }, @("Finished!")) |
pause |
[void] pause([bool]$isPaused) |
Pauses ($true) or resumes ($false) the animation. |
$t.pause($true) |
stop |
[void] stop() |
Immediately cancels the tween and removes it from the update loop. | $t.stop() |
forceEnd |
[void] forceEnd() |
Clears delay and advances ticks to duration, finalizing the tween on the next tick. | $t.forceEnd() |
Animates the 2D Location (Point(X, Y)) of any Windows Forms control.
[TweenMoveTo]::new([Control]$pControl, [System.Drawing.Point]$pDestPos, [double]$pDuration, [string]$pEasing)- Parameters:
$pControl: The WinForms control to move.$pDestPos:[System.Drawing.Point]destination coordinates.$pDuration: Movement duration in seconds.$pEasing: Easing curve identifier (e.g.,$Script:easeCubicOut).
- Example:
$dest = [System.Drawing.Point]::new(350, 120) $moveTween = [TweenMoveTo]::new($myButton, $dest, 1.2, $Script:easeElasticOut) $moveTween.setDelay(0.2)
Smoothly transitions any color property (ForeColor or BackColor) across individual Alpha, Red, Green, and Blue channels.
[TweenColorARGB]::new([Control]$pControl, [string]$pType, [System.Drawing.Color]$pStartColor, [System.Drawing.Color]$pEndColor, [double]$pDuration, [string]$pEasing)- Parameters:
$pControl: The target control.$pType: Color property to animate:"ForeColor"or"BackColor".$pStartColor: Initial[System.Drawing.Color].$pEndColor: Target[System.Drawing.Color].$pDuration: Transition duration in seconds.$pEasing: Easing curve identifier.
- Example:
$startCol = [System.Drawing.Color]::FromArgb(255, 30, 30, 30) $endCol = [System.Drawing.Color]::FromArgb(255, 0, 122, 255) $colorTween = [TweenColorARGB]::new($myLabel, "BackColor", $startCol, $endCol, 0.8, $Script:easeQuadInOut)
Animates a numeric count inside a control's Text property (ideal for score counters, statistics, and dashboards).
[TweenNumericString]::new([Control]$pControl, [string]$pType, [double]$pStartValue, [double]$pEndValue, [double]$pDuration, [string]$pEasing)- Parameters:
$pControl: The target control (e.g.,Label,Button,TextBox).$pType:"int"(floored whole numbers) or"double"(rounded to 2 decimal places).$pStartValue: Initial number.$pEndValue: Final number.$pDuration: Animation duration in seconds.$pEasing: Easing curve identifier.
- Example:
# Animate score counter from 0 to 1500 over 2.5 seconds $numTween = [TweenNumericString]::new($lblScore, "int", 0, 1500, 2.5, $Script:easeExpoOut)
Animates the Value property of a System.Windows.Forms.ProgressBar.
[TweenProgressBar]::new([ProgressBar]$pProgressBar, [double]$pStartValue, [double]$pEndValue, [double]$pDuration, [string]$pEasing)- Parameters:
$pProgressBar: TargetProgressBarinstance.$pStartValue: Starting percentage / integer value.$pEndValue: Target percentage / integer value.$pDuration: Duration in seconds.$pEasing: Easing curve identifier.
- Example:
$barTween = [TweenProgressBar]::new($progressBar1, 0, 100, 3.0, $Script:easeCubicInOut)
Smoothly fades a Form's transparency by modifying its Opacity property (clamped between 0.0 and 1.0).
[TweenOpacity]::new([Control]$pForm, [double]$pEndValue, [double]$pDuration, [string]$pEasing)- Parameters:
$pForm: TargetForminstance.$pEndValue: Target opacity (0.0for fully transparent,1.0for fully opaque).$pDuration: Fade duration in seconds.$pEasing: Easing curve identifier.
- Example:
# Fade in a Form over 0.75 seconds $fadeTween = [TweenOpacity]::new($myWindow, 1.0, 0.75, $Script:easeSineOut)
A non-blocking timer tween that executes a callback after a given delay without altering any control properties. Perfect for pacing sequences or scheduling delayed events.
[TweenWaiter]::new([System.Object]$pControl, [double]$pDuration, [scriptblock]$pCallBack)- Parameters:
$pControl: Context object (can be$nullor a specific control).$pDuration: Wait duration in seconds.$pCallBack: ScriptBlock executed upon expiration.
- Example:
[TweenWaiter]::new($null, 2.0, { [System.Windows.Forms.MessageBox]::Show("2 seconds have elapsed!") })
Easing equations control the acceleration and deceleration curve of an animation over time. For visual previews of these mathematical curves, visit easings.net.
Each algorithm family (except Linear) supports four distinct curve modes:
| Variation | Suffix | Description |
|---|---|---|
| Ease In | *EaseIn |
Starts slowly and accelerates toward the end. |
| Ease Out | *EaseOut |
Starts quickly and decelerates to a gentle stop. |
| Ease In-Out | *EaseInOut |
Accelerates halfway through, then decelerates to the end. |
| Ease Out-In | *EaseOutIn |
Decelerates during the first half, then accelerates in the second half. |
All constants are accessible as $Script:ease<Name> (or simply $ease<Name> in the local scope where TweensVariables.ps1 is dot-sourced):
| Family | Variable Name | Identifier String | Mathematical Description |
|---|---|---|---|
| Linear | $Script:easeLinear |
"Linear" |
Constant speed (linear interpolation). |
| Quad |
$Script:easeQuadIn$Script:easeQuadOut$Script:easeQuadInOut$Script:easeQuadOutIn
|
"QuadEaseIn""QuadEaseOut""QuadEaseInOut""QuadEaseOutIn"
|
Quadratic curve ( |
| Cubic |
$Script:easeCubicIn$Script:easeCubicOut$Script:easeCubicInOut$Script:easeCubicOutIn
|
"CubicEaseIn""CubicEaseOut""CubicEaseInOut""CubicEaseOutIn"
|
Cubic curve ( |
| Quart |
$Script:easeQuartIn$Script:easeQuartOut$Script:easeQuartInOut$Script:easeQuartOutIn
|
"QuartEaseIn""QuartEaseOut""QuartEaseInOut""QuartEaseOutIn"
|
Quartic curve ( |
| Quint |
$Script:easeQuintIn$Script:easeQuintOut$Script:easeQuintInOut$Script:easeQuintOutIn
|
"QuintEaseIn""QuintEaseOut""QuintEaseInOut""QuintEaseOutIn"
|
Quintic curve ( |
| Sine |
$Script:easeSineIn$Script:easeSineOut$Script:easeSineInOut$Script:easeSineOutIn
|
"SineEaseIn""SineEaseOut""SineEaseInOut""SineEaseOutIn"
|
Sinusoidal trigonometric curve. Gentle and organic. |
| Expo |
$Script:easeExpoIn$Script:easeExpoOut$Script:easeExpoInOut$Script:easeExpoOutIn
|
"ExpoEaseIn""ExpoEaseOut""ExpoEaseInOut""ExpoEaseOutIn"
|
Exponential curve ( |
| Circ |
$Script:easeCircIn$Script:easeCircOut$Script:easeCircInOut$Script:easeCircOutIn
|
"CircEaseIn""CircEaseOut""CircEaseInOut""CircEaseOutIn"
|
Circular arc curve ( |
| Back |
$Script:easeBackIn$Script:easeBackOut$Script:easeBackInOut$Script:easeBackOutIn
|
"BackEaseIn""BackEaseOut""BackEaseInOut""BackEaseOutIn"
|
Overshooting curve. Pulls back before moving or overshoots target. |
| Elastic |
$Script:easeElasticIn$Script:easeElasticOut$Script:easeElasticInOut$Script:easeElasticOutIn
|
"ElasticEaseIn""ElasticEaseOut""ElasticEaseInOut""ElasticEaseOutIn"
|
Damped spring / rubber-band oscillation. |
| Bounce |
$Script:easeBounceIn$Script:easeBounceOut$Script:easeBounceInOut$Script:easeBounceOutIn
|
"BounceEaseIn""BounceEaseOut""BounceEaseInOut""BounceEaseOutIn"
|
Decaying parabolic bouncing effect. |
Chain multiple animations sequentially using setOnComplete:
# Move right, then change color, then move down
$p1 = [System.Drawing.Point]::new(200, 50)
$p2 = [System.Drawing.Point]::new(200, 200)
$t1 = [TweenMoveTo]::new($btn, $p1, 0.8, $Script:easeCubicOut)
$t1.setOnComplete({
$c = [TweenColorARGB]::new($btn, "BackColor", [System.Drawing.Color]::Gray, [System.Drawing.Color]::ForestGreen, 0.5, $Script:easeLinear)
$c.setOnComplete({
[TweenMoveTo]::new($btn, $p2, 0.8, $Script:easeBounceOut)
})
})The callback mechanism automatically passes the $tweenObj as the first argument, followed by any additional arguments supplied via setOnComplete($callback, $args):
$myTween = [TweenMoveTo]::new($panel, [System.Drawing.Point]::new(0, 0), 1.0, $Script:easeExpoOut)
$callback = {
param($tweenInstance, $customMessage, $nextState)
Write-Host "Completed animation on control: $($tweenInstance.control.Name)"
Write-Host "Message: $customMessage | State: $nextState"
}
$myTween.setOnComplete($callback, @("Panel Open Success", 1))Animate multiple UI controls in a staggered wave using .setDelay():
$controls = @($card1, $card2, $card3, $card4)
$baseDelay = 0.1
for ($i = 0; $i -lt $controls.Count; $i++) {
$target = [System.Drawing.Point]::new(50, 50 + ($i * 60))
$tw = [TweenMoveTo]::new($controls[$i], $target, 0.6, $Script:easeBackOut)
$tw.setDelay($i * $baseDelay)
}Manage active animations programmatically:
# Pause an animation
$myTween.pause($true)
# Resume an animation
$myTween.pause($false)
# Stop and discard immediately
$myTween.stop()
# Force jump to completion immediately
$myTween.forceEnd()You can easily extend the engine with your own custom tween classes:
-
Inherit from
TweeninTweens.ps1:class TweenSize : Tween { [System.Drawing.Size]$startSize [System.Drawing.Size]$destSize [System.Drawing.Size]$delta TweenSize([Control]$pControl, [System.Drawing.Size]$pDestSize, [double]$pDuration, [string]$pEasing) { $this.control = $pControl $this.easing = $pEasing $this.destSize = $pDestSize $this.duration = $pDuration * $Script:refreshRate $this.startSize = $pControl.Size $this.delta = [System.Drawing.Size]::new($pDestSize.Width - $pControl.Width, $pDestSize.Height - $pControl.Height) } }
-
Add a mutation handler in
TweensMovement.ps1:function SizeControl([TweenSize]$tweenObj) { $tweenObj.nbTicks++ if ($tweenObj.nbTicks -gt $tweenObj.duration) { $tweenObj.control.Size = $tweenObj.destSize $Script:tweensList.Remove($tweenObj) Invoke-TweenCallback $tweenObj } else { $w = Ease $tweenObj.easing $tweenObj.startSize.Width $tweenObj.delta.Width $tweenObj.nbTicks $tweenObj.duration $h = Ease $tweenObj.easing $tweenObj.startSize.Height $tweenObj.delta.Height $tweenObj.nbTicks $tweenObj.duration $tweenObj.control.Size = [System.Drawing.Size]::new($w, $h) } }
-
Register the handler in
TweensUpdater.ps1:"TweenSize" { SizeControl $tween }
The repository includes a feature-rich demonstration GUI (Demo.ps1) showcasing every easing curve, duration slider, delay configuration, and tween type in real time.
To launch the demo:
Double-click run.bat or execute in terminal:
run.batPowerShell -NoProfile -ExecutionPolicy Bypass -File .\Demo.ps1-
WinForms Threading (STA): Windows Forms runs on a Single-Threaded Apartment (STA) model. All tween updates execute on the main UI thread. Avoid executing long, blocking synchronous operations (e.g.,
Start-Sleepor synchronous web requests) on the UI thread while tweens are playing to prevent frame drops. -
Tick Calculation vs. Real Time: Duration is represented internally as
ticks = durationInSeconds * refreshRate. If the UI thread is momentarily busy, ticks are incremented sequentially until completion, guaranteeing that all tweens reach their exact target destination without overshooting. -
Timer Cleanup: Always invoke
$Script:tweensTimer.Stop()and$Script:tweensTimer.Dispose()inside the Form'sClosingorFormClosedevent. Failing to do so can keep the background timer ticking in persistent PowerShell console sessions (such as PowerShell ISE or VS Code Integrated Terminal).
- Author: Olivier Selliez (olivier.selliez.dev@gmail.com)
- Easing Equations: Based on Robert Penner's Easing Functions (robertpenner.com/easing).
- License: MIT License. Free to use, modify, and distribute for personal and commercial projects.