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).