Hello all, I recently implemented an animated Typewriter effect in Silverlight using F#. My code works, but I'd like to see if anyone has recommendations on ways the code might be improved. i.e. Would it be a good or bad idea to do it with Seq.iter or Seq.map? Any opinions and comments are welcome!
I also thought I'd document the code in case it might help anyone else
Feedback Edit: Added local function for hooking animations to the storyboard
let createTypewriter(text:string) =
//Controls the animation of each character
let storyboard = new Storyboard()
//This will contain every line of text
let textboard = new StackPanel(Orientation=Orientation.Vertical)
//Creates a new StackPanel to hold the words and puts it in the wrapPanel
let newWordContainer (wrapPanel:WrapPanel) =
let wordContainer = new StackPanel(Orientation=Orientation.Horizontal)
wrapPanel.Children.Add( wordContainer)
wordContainer
//Parse the entire string
let rec parseLetters letter delay wordContainer wrapPanel : Storyboard * StackPanel =
match letter with
//If there's nothing left to parse return the initialized storyboard
//and textboard
| [] -> (storyboard, textboard)
//Using pattern matching we recursively handle the current character (head)
//then rest of the characters (tail)
//Handle Spaces. If we encounter a space, we create a new horizontal
//StackPanel to put individual characters into. This new StackPanel
//is added to the wrapPanel
| head :: tail when head = ' ' ->
let newCont = newWordContainer wrapPanel
newCont.Children.Add(new TextBlock(Text=" "))
parseLetters tail (delay+1.0) newCont wrapPanel
//If we encounter a newline or a return, we want to move down to a new line.
//Thus we insert a new WrapPanel into our vertical StackPanel (textboard)
| head :: tail when head ='\n' || head = '\r' -> //Handle new lines
let newWrapPanel = new WrapPanel(MinHeight = this.FontSize)
let newCont = newWordContainer newWrapPanel
textboard.Children.Add(newWrapPanel)
parseLetters tail (delay+1.0) newCont newWrapPanel
//Letters will be placed in TextBlocks and added to horizontal StackPanels
//(wordContainer) to make words. Each TextBlock will have an animation
//controlled by the StoryBoard.
| head :: tail ->
//Create the animation transforms
let st = new ScaleTransform(ScaleX=5.0, ScaleY=5.0)
let tt = new TranslateTransform(X=(-40.0),Y=0.0)
let tg = new TransformGroup()
tg.Children.Add(st)
tg.Children.Add(tt)
//Create the TextBlock and set its transform
let tb = new TextBlock(Text=head.ToString(), Opacity=0.0, RenderTransform=tg,RenderTransformOrigin = new Point(0.5,0.5))
wordContainer.Children.Add(tb)
//Create the DoubleAnimations that specify how the text will animate,
//Darn Nullable types make this really ugly =/
let bt = TimeSpan.FromMilliseconds(1000.0 + delay * 30.0);
let duration = new Duration(TimeSpan.FromSeconds(0.1))
let opacityDA = new DoubleAnimation(From=Nullable(0.0), To=Nullable(1.0), Duration=duration, BeginTime=Nullable(bt))
let translateDA = new DoubleAnimation( From=Nullable(-40.0), To=Nullable(0.0), Duration=duration, BeginTime=Nullable(bt))
let scaleXDA = new DoubleAnimation(From=Nullable(5.0), To=Nullable(1.0), Duration=duration, BeginTime=Nullable(bt))
let scaleYDA = new DoubleAnimation(From=Nullable(5.0), To=Nullable(1.0), Duration=duration, BeginTime=Nullable(bt))
//Create a function that will hook the animation info to the storyboard.
let addToStoryboard doubleAni obj (propName:string) =
Storyboard.SetTarget(doubleAni, obj)
Storyboard.SetTargetProperty(doubleAni, new PropertyPath(propName))
storyboard.Children.Add(doubleAni)
addToStoryboard scaleXDA st "ScaleX"
addToStoryboard scaleYDA st "ScaleY"
addToStoryboard translateDA tt "X"
addToStoryboard opacityDA tb "Opacity"
//Parse the rest of the letters
parseLetters tail (delay+1.0) wordContainer wrapPanel
//Begin the recursion over the passed in string
let wrapPanel = new WrapPanel()
textboard.Children.Add(wrapPanel)
parseLetters (Seq.toList text) 0.0 (newWordContainer wrapPanel) wrapPanel