|
// Hi. For the "good stuff" scroll down to line 74. |
|
// The rest is just bootstrapping |
|
|
|
// `open` are F# version of C# `using` |
|
open System |
|
open System.Globalization |
|
open System.Windows |
|
open System.Windows.Media |
|
open System.Windows.Media.Animation |
|
|
|
// Creates a CanvasElement class that will act like a canvas for us |
|
// We override the OnRender method to draw graphics. In order to make the graphics |
|
// animate we have a time animation that invalidates the element which forces a redraw |
|
type CanvasElement () = |
|
class |
|
// This is how in F# we inherit, this is typically not done as much |
|
// as in C# but in order to be part of WPF Visual tree we need to |
|
// inherit UIElement |
|
inherit UIElement () |
|
|
|
// Declaring a DependencyProperty member for Time |
|
// This is WPF magic but it's created so that we can create |
|
// an "animation" of the time value. |
|
// This will help use do smooth updates. |
|
// Nothing like web requestAnimationFrame in WPF AFAIK |
|
static let timeProperty = |
|
let pc = PropertyChangedCallback CanvasElement.TimePropertyChanged |
|
let md = PropertyMetadata (0., pc) |
|
DependencyProperty.Register ("Time", typeof<float>, typeof<CanvasElement>, md) |
|
|
|
// Freezing resources prevents updates of WPF Resources |
|
// Can help WPF optimize rendering |
|
// #Freezable is like C# constraint : where T : Freezable |
|
let freeze (f : #Freezable) = |
|
f.Freeze () |
|
f |
|
|
|
// Helper function to create pens |
|
let makePen thickness brush = |
|
Pen (Thickness = thickness, Brush = brush) |> freeze |
|
|
|
let treePen = makePen 2. Brushes.GreenYellow |
|
|
|
// More WPF dependency property magic |
|
// Not very interesting but this becomes member function in the class |
|
static member TimePropertyChanged (d : DependencyObject) (e : DependencyPropertyChangedEventArgs) = |
|
let g = d :?> CanvasElement |
|
// Whenever time change we invalidate the entire canvas element |
|
g.InvalidateVisual () |
|
|
|
// Idiomatically WPF Dependency properties should be readonly |
|
// static fields. However, F# don't allow us to declare that |
|
// Luckily it seems static readonly properties works fine |
|
static member TimeProperty = timeProperty |
|
|
|
// Gets the Time dependency property |
|
member x.Time = x.GetValue CanvasElement.TimeProperty :?> float |
|
|
|
// Create an animation that animates a floating point from 0 to 1E9 |
|
// over 1E9 seconds thus the time. This animation is then hooked onto the Time property |
|
// Basically more WPF magic |
|
member x.Start () = |
|
// Initial time value |
|
let b = 0.0 |
|
// End time, application animation stops after approx 30 years |
|
let e = 1E9 |
|
let dur = Duration (TimeSpan.FromSeconds (e - b)) |
|
let ani = DoubleAnimation (b, e, dur) |> freeze |
|
// Animating Time property |
|
x.BeginAnimation (CanvasElement.TimeProperty, ani); |
|
|
|
// Finally we get to the good stuff! |
|
// dc is a DeviceContext, basically a canvas we can draw on |
|
override x.OnRender dc = |
|
// Get the current time, will change over time (hohoh) |
|
let time = x.Time |
|
// This is the size of the canvas in pixels |
|
let rs = x.RenderSize |
|
|
|
// Let's draw some dancing circles |
|
// The angle is a function of time, this will animate the circles |
|
let a = 2.0*time |
|
let ay = sqrt 0.5 |
|
|
|
for i = 0 to 9 do |
|
// In F# shadowing a previous variable with the same name is |
|
// not consider a problem |
|
// The previous `a` still exists but locally in this loop |
|
// `a` now has a new value (with potentially a new type) |
|
let a = a+float i |
|
let x = 100.0*sin a + 0.5*rs.Width |
|
let y = 100.0*sin (ay*a) + 150.0 |
|
dc.DrawEllipse (Brushes.DarkGreen, treePen, Point (x, y), 10.0, 10.0) |
|
|
|
// Function to render a tree |
|
// This function is declared inside the OnRender function |
|
// something that is now commonly used in C# as well |
|
let tree dc time p = |
|
let rec recurse n (dc : DrawingContext) (lt : Matrix) (rt : Matrix) d (p : Point) = |
|
if n > 0 then |
|
let np = p + d |
|
|
|
dc.DrawLine (treePen, p, np) |
|
|
|
// Shortens the next branch |
|
let d = 0.75*d |
|
let ld = lt.Transform d |
|
let rd = rt.Transform d |
|
|
|
// Draw left branch |
|
recurse (n - 1) dc lt rt ld np |
|
// Draw right branch |
|
recurse (n - 1) dc lt rt rd np |
|
|
|
// Which direction the root branch has |
|
let up = Vector (0., -200.) |
|
|
|
// Transform for left branches |
|
let lt = Matrix.Identity |
|
// Transform for right branches |
|
let rt = Matrix.Identity |
|
|
|
// The angle between new branch and preceeding branch |
|
let a = 30. |
|
// Animate the angles using time |
|
let b = 10.*sin time |
|
|
|
// Apply rotation to transforms |
|
lt.Rotate (a + b) |
|
rt.Rotate -(a - b) |
|
|
|
// Draw the tree 9 levels deep |
|
recurse 9 dc lt rt up p |
|
|
|
// Bottom of the screen in the center X-wise |
|
let rootPos = Point (0.5*rs.Width, rs.Height) |
|
|
|
// Renders the tree tree |
|
tree dc time rootPos |
|
|
|
end |
|
|
|
// Tells F# that this method is the main entry point |
|
[<EntryPoint>] |
|
// More 1990s magic! Basically in Windows there's a requirement that |
|
// UI controls runs in something called a Single Threaded Apartment. |
|
// So we tell .NET that the thread that calls main should be in a |
|
// Single Threaded Apartment. |
|
// Basically MS idea in 1990s on how to solve the problem of writing |
|
// multi threaded applications. |
|
// The .NET equivalent to apartments could be SynchronizationContext |
|
[<STAThread>] |
|
let main argv = |
|
// Sets up the main window |
|
let window = Window (Title = "FsWpfBootstrap", Background = Brushes.Black) |
|
// Creates our canvas |
|
let element = CanvasElement () |
|
// Makes our canvas the content of the Window |
|
window.Content <- element |
|
// Starts the time animation |
|
element.Start () |
|
// Shows the Window |
|
window.ShowDialog () |> ignore |
|
0 |