views:

181

answers:

1

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
+2  A: 

The only thing that jumps out at me is the repeated StoryBoard code. It probably makes sense to create a local let function definition taking a StoryBoard, DoubleAnimation, string, and obj and condensing the logic for the ScaleX, ScaleY, X, and Opacity animations to one call each.

kvb
Thanks for the feedback. I added the let function; it looks better now. I didn't feel it necissary to pass the storyboard as a parama as it's defined as a closure at the top. Thanks again.
RodYan