Thursday, August 28, 2008

Banshee "Now Playing" Animations

Aaron has been complaining lately that writing fancy graphics hacks for Banshee by hand using low-level Cairo calls is too "hard" and too tiresome to do.

Wimp.

For Hack Week, this week, I've been fixing up the GtkSharp Moonlight widget (aka GtkSilver) to work again so that The Great Bocky can write fancy graphics hacks using XAML instead.

One area that Aaron has mentioned that he'd love to do fancier animations in is the "Now Playing" view which currently is limited to a fade-in and a fade-out of the current track info (album cover w/ a reflection), track title, artist, and album name.

It took him far too long to write the code that displays that view (+ time spent optimizing it to get better than 11 FPS) and if he ever gets around to implementing anything fancier, he'll be popping those Excedrin pills like candy.

Lucky for him, I mocked up a slightly improved version of his "Now Playing" view in XAML (no code required except to update the text, album art, and to invoke the animations). All animations and graphics are represented in just 70 lines of XAML.

The great thing about designing this view in XAML is that it is easy to change the layout or adjust the animations without having to write any new code. Users can customize their "Now Playing" screens in any way they want to, all they have to do is keep the names of the controls and animations consistent with what the application expects. It's a lot like Glade XML files, only it allows the user to do fancy things like animations as well.

The demo I hacked up uses the following XAML:

<Canvas xmlns="http://schemas.microsoft.com/client/2007"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Background="Black" Width="700" Height="550">
  <Canvas x:Name="Display">
    <TextBlock Canvas.Left="10" Canvas.Top="200" x:Name="TrackInfo" Width="280"
               FontFamily="Sans" FontSize="14" TextAlignment="Right" Foreground="White">
      <Run x:Name="Title" FontSize="24">Wonderboy</Run><LineBreak/>
      <Run x:Name="By" FontSize="12" Foreground="Gray">by</Run>
      <Run x:Name="Artist">Tenacious D</Run><LineBreak/>
      <Run x:Name="From" FontSize="12" Foreground="Gray">from</Run>
      <Run x:Name="Album">Tenacious D</Run>
    </TextBlock>
    <Rectangle x:Name="AlbumArt" Canvas.Left="300" Canvas.Top="50" Width="300" Height="300">
      <Rectangle.Fill>
        <ImageBrush x:Name="AlbumArtBrush" Stretch="Fill" ImageSource="tenaciousd.jpg"/>
      </Rectangle.Fill>
    </Rectangle>
    <Rectangle x:Name="AlbumArtReflection" Canvas.Left="300" Canvas.Top="50" Width="300" Height="300"
               RenderTransformOrigin="0,1">
      <Rectangle.Fill>
        <ImageBrush x:Name="AlbumArtReflectionBrush" Stretch="Fill" ImageSource="tenaciousd.jpg"/>
      </Rectangle.Fill>
      <Rectangle.OpacityMask>
        <LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
          <LinearGradientBrush.GradientStops>
            <GradientStop Color="#8f8f8f8f" Offset="0"/>
            <GradientStop Color="#00000000" Offset="0.75"/>
            <GradientStop Color="#00000000" Offset="1"/>
          </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
      </Rectangle.OpacityMask>
      <Rectangle.RenderTransform>
        <TransformGroup>
          <ScaleTransform ScaleX="1" ScaleY="-0.35"/>
          <SkewTransform x:Name="SkewTransform" AngleX="45" AngleY="0"/>
        </TransformGroup>
      </Rectangle.RenderTransform>
    </Rectangle>
    <Canvas.Resources>
      <Storyboard x:Name="ReflectionAnimation">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="AlbumArtReflection"
         Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[1].(SkewTransform.AngleX)">
          <SplineDoubleKeyFrame KeyTime="00:00:00" Value="-45" KeySpline="0.25,0.00 0.75,0.75"/>
          <SplineDoubleKeyFrame KeyTime="00:00:02" Value="45" KeySpline="0.25,0.75 0.75,1.0"/>
        </DoubleAnimationUsingKeyFrames>
      </Storyboard>
      <Storyboard x:Name="FadeIn">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Display"
         Storyboard.TargetProperty="Opacity">
          <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
          <SplineDoubleKeyFrame KeyTime="00:00:01" Value="1"/>
        </DoubleAnimationUsingKeyFrames>
      </Storyboard>
      <Storyboard x:Name="FadeOut">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Display"
         Storyboard.TargetProperty="Opacity">
          <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
          <SplineDoubleKeyFrame KeyTime="00:00:01" Value="0"/>
        </DoubleAnimationUsingKeyFrames>
      </Storyboard>
    </Canvas.Resources>
  </Canvas>
</Canvas>

To see this in action, you'll need to grab Moonlight out of SVN (I ended up using some Silverlight 2.0 TextBlock features like TextAlignment="Right" so that no code was needed to align the text like what Aaron has to calculate manually in the current code). Once you have that installed, you should be able to view my Banshee "Now Playing" mockup in the iframe below. Mousing over the iframe should cause the fade-in effect to start and mousing out of the iframe area should cause the fade-out to take effect.

Oh, XAML, what is the secret of your power? Won't you take me far away from the mucky-muck man?

Update: It has been pointed out that the above iframe could have been done in pure XAML using EventTriggers for MouseEnter/Leave events to trigger the animations, however I designed the XAML to be used by Banshee where Aaron will likely want to be able to control when the animations start/stop programatically and not based on mouse hover events ;-)

Update: The XAML should now work with Silverlight (apparently it didn't like my LineStackingStrategy or LineHeight property on the TextBlock).

11 comments:

mpogut said...

Damn, you beat me to it! This is exactly the sort of thing I wanted to hack up for rythmbox using moonlight ;-)

The good news is that maybe it'll be easier for me to do it now that you've done some of the leg work.

Anonymous said...

awesome!

Anonymous said...

Why not create a short, clear API instead of all that XML vomitus?

Maybe Ruby has spoiled me...

Manuel Cerón said...

Great work. GtkSilver Improvements are completely needed for Lunar Ecilpse.

Jeffrey Stedfast said...

Why not create a short, clear API instead of all that XML vomitus?

Because:

1. it would require several years worth of work for the Banshee team to come up with a rendering engine as flexible as Moonlight is today

2. why reinvent the wheel?

That's the whole point of taking advantage of work another project has already done (in this case, Moonlight). It allows the developers of the project (in this case, Banshee) to spend more time focused on the task at hand (in this case, making a great media player) rather than reinventing wheels (flexible rendering engine that would allow the average joe to tweak his own "Now Playing" view) for the sake of reinventing wheels.

Jeffrey Stedfast said...

oh, and:

3. Because even after the Banshee team manages to design and implement a flexible rendering engine, they'd still have to design and implement some sort of file format that users could edit to change the view ;-)

Oh, and they'd also have to write a program that could edit those views for people who don't care to learn the file format.

Right now, Inkscape can be used to edit XAML, so there are already tools that Banshee users can use to make modifications if they don't feel like poking at the file manually.

There will also be LunarEclipse that is being worked on for editing XAML.

Gustavo Noronha Silva said...

I think this is awesome, and I really look forward to see what moonlight will be able to do to our desktops =).

One question that popped into my mind while reading is: why not clutter? I would think one of the reasons is you're closer to moonlight and wanted to get some of it applied in such a cool project, but do you think clutter would fit here, too?

Johannes said...

As I don't want to use svn stuff or repo previews (messed my installation up), I decided to switch to my Windows VM and installed there the latest Silverlight (which was verified by the Microsoft site).

But in both IE 7 and FF 3 your site shows only blank space (while having NoScript, I allowed all site content). I've tested the Channel 9 stuff, in case being in a VM messes things up, but I can see the vids (although they are shown in the Media Player - atypical behaviour). Do you have any idea what could be wrong?

Jeffrey Stedfast said...

Gustavo:

I don't know enough about Clutter to really say. I'm sure Clutter would have been another fine choice, but as you mentioned - I'm involved with the Moonlight project so it was easier for me to work with that.

Jeffrey Stedfast said...

Johannes:

Yea, I noticed it doesn't work in Windows either - it's a bit odd.

In fact, it kept crashing IE7 for me - and Firefox on Windows just showed a blank iframe :(

Jeffrey Stedfast said...

Woohoo! I got it working under Firefox on Windows by removing some unneeded XAML TextBlock properties!

Still getting a crash in IE7 tho :(

Code Snippet Licensing

All code posted to this blog is licensed under the MIT/X11 license unless otherwise stated in the post itself.