Browse Source

Merge with default

ebr11 Eric Reed spam 12 years ago
parent
commit
7ed11c0bba
70 changed files with 3384 additions and 30 deletions
  1. 2 0
      .hgignore
  2. 81 0
      MediaBrowser.Api/Drawing/DrawingUtils.cs
  3. 148 0
      MediaBrowser.Api/Drawing/ImageProcessor.cs
  4. 1 1
      MediaBrowser.Api/MediaBrowser.Api.csproj
  5. 11 4
      MediaBrowser.Common/Kernel/BaseKernel.cs
  6. 1 0
      MediaBrowser.Common/MediaBrowser.Common.csproj
  7. 43 0
      MediaBrowser.Common/Mef/MefUtils.cs
  8. 2 1
      MediaBrowser.Common/Plugins/BasePlugin.cs
  9. 65 1
      MediaBrowser.Common/Plugins/BaseTheme.cs
  10. 0 1
      MediaBrowser.Controller/Kernel.cs
  11. 43 0
      MediaBrowser.Plugins.DefaultTheme/Converters/TileBackgroundConverter.cs
  12. 43 0
      MediaBrowser.Plugins.DefaultTheme/Converters/WeatherImageConverter.cs
  13. 29 1
      MediaBrowser.Plugins.DefaultTheme/MediaBrowser.Plugins.DefaultTheme.csproj
  14. 64 0
      MediaBrowser.Plugins.DefaultTheme/Pages/LoginPage.xaml
  15. 15 0
      MediaBrowser.Plugins.DefaultTheme/Pages/LoginPage.xaml.cs
  16. 3 2
      MediaBrowser.Plugins.DefaultTheme/Plugin.cs
  17. 14 0
      MediaBrowser.Plugins.DefaultTheme/Resources/AppResources.cs
  18. 81 0
      MediaBrowser.Plugins.DefaultTheme/Resources/AppResources.xaml
  19. BIN
      MediaBrowser.Plugins.DefaultTheme/Resources/Images/CurrentUserDefault.png
  20. BIN
      MediaBrowser.Plugins.DefaultTheme/Resources/Images/UserLoginDefault.png
  21. BIN
      MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Overcast.png
  22. BIN
      MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Rain.png
  23. BIN
      MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Snow.png
  24. BIN
      MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Sunny.png
  25. BIN
      MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Thunder.png
  26. 1 1
      MediaBrowser.ServerApplication/App.config
  27. 25 17
      MediaBrowser.UI.sln
  28. 9 0
      MediaBrowser.UI/App.config
  29. 14 0
      MediaBrowser.UI/App.xaml
  30. 213 0
      MediaBrowser.UI/App.xaml.cs
  31. 27 0
      MediaBrowser.UI/Configuration/UIApplicationConfiguration.cs
  32. 8 0
      MediaBrowser.UI/Configuration/UIApplicationPaths.cs
  33. 231 0
      MediaBrowser.UI/Controller/PluginUpdater.cs
  34. 97 0
      MediaBrowser.UI/Controller/UIKernel.cs
  35. 73 0
      MediaBrowser.UI/Controls/EnhancedScrollViewer.cs
  36. 92 0
      MediaBrowser.UI/Controls/ExtendedImage.cs
  37. 226 0
      MediaBrowser.UI/Controls/TreeHelper.cs
  38. 91 0
      MediaBrowser.UI/Controls/WindowCommands.xaml
  39. 50 0
      MediaBrowser.UI/Controls/WindowCommands.xaml.cs
  40. 26 0
      MediaBrowser.UI/Converters/CurrentUserVisibilityConverter.cs
  41. 34 0
      MediaBrowser.UI/Converters/DateTimeToStringConverter.cs
  42. 86 0
      MediaBrowser.UI/Converters/LastSeenTextConverter.cs
  43. 60 0
      MediaBrowser.UI/Converters/UserImageConverter.cs
  44. 31 0
      MediaBrowser.UI/Converters/WeatherTemperatureConverter.cs
  45. 20 0
      MediaBrowser.UI/Converters/WeatherVisibilityConverter.cs
  46. 50 0
      MediaBrowser.UI/MainWindow.xaml
  47. 368 0
      MediaBrowser.UI/MainWindow.xaml.cs
  48. 196 0
      MediaBrowser.UI/MediaBrowser.UI.csproj
  49. 33 0
      MediaBrowser.UI/Pages/BaseLoginPage.cs
  50. 79 0
      MediaBrowser.UI/Pages/BasePage.cs
  51. 53 0
      MediaBrowser.UI/Properties/AssemblyInfo.cs
  52. 71 0
      MediaBrowser.UI/Properties/Resources.Designer.cs
  53. 117 0
      MediaBrowser.UI/Properties/Resources.resx
  54. 30 0
      MediaBrowser.UI/Properties/Settings.Designer.cs
  55. 7 0
      MediaBrowser.UI/Properties/Settings.settings
  56. 122 0
      MediaBrowser.UI/Resources/AppResources.xaml
  57. BIN
      MediaBrowser.UI/Resources/Images/BackButton.png
  58. BIN
      MediaBrowser.UI/Resources/Images/ExitButton.png
  59. BIN
      MediaBrowser.UI/Resources/Images/ForwardButton.png
  60. BIN
      MediaBrowser.UI/Resources/Images/Icon.ico
  61. BIN
      MediaBrowser.UI/Resources/Images/MuteButton.png
  62. BIN
      MediaBrowser.UI/Resources/Images/SettingsButton.png
  63. BIN
      MediaBrowser.UI/Resources/Images/VolumeDownButton.png
  64. BIN
      MediaBrowser.UI/Resources/Images/VolumeUpButton.png
  65. BIN
      MediaBrowser.UI/Resources/Images/mblogoblack.png
  66. BIN
      MediaBrowser.UI/Resources/Images/mblogowhite.png
  67. 43 0
      MediaBrowser.UI/Resources/MainWindowResources.xaml
  68. 122 0
      MediaBrowser.UI/Resources/NavBarResources.xaml
  69. 32 0
      MediaBrowser.UI/Themes/Generic.xaml
  70. 1 1
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

+ 2 - 0
.hgignore

@@ -29,6 +29,8 @@ syntax: glob
 obj/
 [Rr]elease*/
 ProgramData*/
+ProgramData-Server*/
+ProgramData-UI*/
 _ReSharper*/
 [Tt]humbs.db
 [Tt]est[Rr]esult*

+ 81 - 0
MediaBrowser.Api/Drawing/DrawingUtils.cs

@@ -0,0 +1,81 @@
+using System;
+using System.Drawing;
+
+namespace MediaBrowser.Api.Drawing
+{
+    public static class DrawingUtils
+    {
+        /// <summary>
+        /// Resizes a set of dimensions
+        /// </summary>
+        public static Size Resize(int currentWidth, int currentHeight, int? width, int? height, int? maxWidth, int? maxHeight)
+        {
+            return Resize(new Size(currentWidth, currentHeight), width, height, maxWidth, maxHeight);
+        }
+
+        /// <summary>
+        /// Resizes a set of dimensions
+        /// </summary>
+        /// <param name="size">The original size object</param>
+        /// <param name="width">A new fixed width, if desired</param>
+        /// <param name="height">A new fixed neight, if desired</param>
+        /// <param name="maxWidth">A max fixed width, if desired</param>
+        /// <param name="maxHeight">A max fixed height, if desired</param>
+        /// <returns>A new size object</returns>
+        public static Size Resize(Size size, int? width, int? height, int? maxWidth, int? maxHeight)
+        {
+            decimal newWidth = size.Width;
+            decimal newHeight = size.Height;
+
+            if (width.HasValue && height.HasValue)
+            {
+                newWidth = width.Value;
+                newHeight = height.Value;
+            }
+
+            else if (height.HasValue)
+            {
+                newWidth = GetNewWidth(newHeight, newWidth, height.Value);
+                newHeight = height.Value;
+            }
+
+            else if (width.HasValue)
+            {
+                newHeight = GetNewHeight(newHeight, newWidth, width.Value);
+                newWidth = width.Value;
+            }
+
+            if (maxHeight.HasValue && maxHeight < newHeight)
+            {
+                newWidth = GetNewWidth(newHeight, newWidth, maxHeight.Value);
+                newHeight = maxHeight.Value;
+            }
+
+            if (maxWidth.HasValue && maxWidth < newWidth)
+            {
+                newHeight = GetNewHeight(newHeight, newWidth, maxWidth.Value);
+                newWidth = maxWidth.Value;
+            }
+
+            return new Size(Convert.ToInt32(newWidth), Convert.ToInt32(newHeight));
+        }
+
+        private static decimal GetNewWidth(decimal currentHeight, decimal currentWidth, int newHeight)
+        {
+            decimal scaleFactor = newHeight;
+            scaleFactor /= currentHeight;
+            scaleFactor *= currentWidth;
+
+            return scaleFactor;
+        }
+
+        private static decimal GetNewHeight(decimal currentHeight, decimal currentWidth, int newWidth)
+        {
+            decimal scaleFactor = newWidth;
+            scaleFactor /= currentWidth;
+            scaleFactor *= currentHeight;
+
+            return scaleFactor;
+        }
+    }
+}

+ 148 - 0
MediaBrowser.Api/Drawing/ImageProcessor.cs

@@ -0,0 +1,148 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Api.Drawing
+{
+    public static class ImageProcessor
+    {
+        /// <summary>
+        /// Processes an image by resizing to target dimensions
+        /// </summary>
+        /// <param name="entity">The entity that owns the image</param>
+        /// <param name="imageType">The image type</param>
+        /// <param name="imageIndex">The image index (currently only used with backdrops)</param>
+        /// <param name="toStream">The stream to save the new image to</param>
+        /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
+        /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
+        /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
+        /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
+        /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
+        public static void ProcessImage(BaseEntity entity, ImageType imageType, int imageIndex, Stream toStream, int? width, int? height, int? maxWidth, int? maxHeight, int? quality)
+        {
+            Image originalImage = Image.FromFile(GetImagePath(entity, imageType, imageIndex));
+
+            // Determine the output size based on incoming parameters
+            Size newSize = DrawingUtils.Resize(originalImage.Size, width, height, maxWidth, maxHeight);
+
+            Bitmap thumbnail;
+
+            // Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
+            if (originalImage.PixelFormat.HasFlag(PixelFormat.Indexed))
+            {
+                thumbnail = new Bitmap(originalImage, newSize.Width, newSize.Height);
+            }
+            else
+            {
+                thumbnail = new Bitmap(newSize.Width, newSize.Height, originalImage.PixelFormat);
+            }
+
+            thumbnail.MakeTransparent();
+
+            // Preserve the original resolution
+            thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
+
+            Graphics thumbnailGraph = Graphics.FromImage(thumbnail);
+
+            thumbnailGraph.CompositingQuality = CompositingQuality.HighQuality;
+            thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality;
+            thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
+            thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
+            thumbnailGraph.CompositingMode = CompositingMode.SourceOver;
+
+            thumbnailGraph.DrawImage(originalImage, 0, 0, newSize.Width, newSize.Height);
+
+            ImageFormat outputFormat = originalImage.RawFormat;
+
+            // Write to the output stream
+            SaveImage(outputFormat, thumbnail, toStream, quality);
+
+            thumbnailGraph.Dispose();
+            thumbnail.Dispose();
+            originalImage.Dispose();
+        }
+
+        public static string GetImagePath(BaseEntity entity, ImageType imageType, int imageIndex)
+        {
+            var item = entity as BaseItem;
+
+            if (item != null)
+            {
+                if (imageType == ImageType.Logo)
+                {
+                    return item.LogoImagePath;
+                }
+                if (imageType == ImageType.Backdrop)
+                {
+                    return item.BackdropImagePaths.ElementAt(imageIndex);
+                }
+                if (imageType == ImageType.Banner)
+                {
+                    return item.BannerImagePath;
+                }
+                if (imageType == ImageType.Art)
+                {
+                    return item.ArtImagePath;
+                }
+                if (imageType == ImageType.Thumbnail)
+                {
+                    return item.ThumbnailImagePath;
+                }
+            }
+
+            return entity.PrimaryImagePath;
+        }
+
+        public static void SaveImage(ImageFormat outputFormat, Image newImage, Stream toStream, int? quality)
+        {
+            // Use special save methods for jpeg and png that will result in a much higher quality image
+            // All other formats use the generic Image.Save
+            if (ImageFormat.Jpeg.Equals(outputFormat))
+            {
+                SaveJpeg(newImage, toStream, quality);
+            }
+            else if (ImageFormat.Png.Equals(outputFormat))
+            {
+                newImage.Save(toStream, ImageFormat.Png);
+            }
+            else
+            {
+                newImage.Save(toStream, outputFormat);
+            }
+        }
+
+        public static void SaveJpeg(Image image, Stream target, int? quality)
+        {
+            if (!quality.HasValue)
+            {
+                quality = 90;
+            }
+
+            using (var encoderParameters = new EncoderParameters(1))
+            {
+                encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality.Value);
+                image.Save(target, GetImageCodecInfo("image/jpeg"), encoderParameters);
+            }
+        }
+
+        public static ImageCodecInfo GetImageCodecInfo(string mimeType)
+        {
+            ImageCodecInfo[] info = ImageCodecInfo.GetImageEncoders();
+
+            for (int i = 0; i < info.Length; i++)
+            {
+                ImageCodecInfo ici = info[i];
+                if (ici.MimeType.Equals(mimeType, StringComparison.OrdinalIgnoreCase))
+                {
+                    return ici;
+                }
+            }
+            return info[1];
+        }
+    }
+}

+ 1 - 1
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -105,7 +105,7 @@
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
-    <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\" /y</PostBuildEvent>
+    <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData-Server\Plugins\" /y</PostBuildEvent>
   </PropertyGroup>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.

+ 11 - 4
MediaBrowser.Common/Kernel/BaseKernel.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Logging;
+using MediaBrowser.Common.Mef;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Common.Plugins;
@@ -10,6 +11,7 @@ using System;
 using System.Collections.Generic;
 using System.ComponentModel.Composition;
 using System.ComponentModel.Composition.Hosting;
+using System.ComponentModel.Composition.Primitives;
 using System.IO;
 using System.Linq;
 using System.Reflection;
@@ -88,6 +90,9 @@ namespace MediaBrowser.Common.Kernel
         /// </summary>
         private IDisposable HttpListener { get; set; }
 
+        /// <summary>
+        /// Gets the MEF CompositionContainer
+        /// </summary>
         private CompositionContainer CompositionContainer { get; set; }
 
         protected virtual string HttpServerUrlPrefix
@@ -184,18 +189,20 @@ namespace MediaBrowser.Common.Kernel
             // This will prevent the .dll file from getting locked, and allow us to replace it when needed
             IEnumerable<Assembly> pluginAssemblies = Directory.GetFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly).Select(f => Assembly.Load(File.ReadAllBytes((f))));
 
-            var catalog = new AggregateCatalog(pluginAssemblies.Select(a => new AssemblyCatalog(a)));
+            var catalogs = new List<ComposablePartCatalog>();
+
+            catalogs.AddRange(pluginAssemblies.Select(a => new AssemblyCatalog(a)));
 
             // Include composable parts in the Common assembly 
-            catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
+            catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
 
             if (includeCurrentAssembly)
             {
                 // Include composable parts in the subclass assembly
-                catalog.Catalogs.Add(new AssemblyCatalog(GetType().Assembly));
+                catalogs.Add(new AssemblyCatalog(GetType().Assembly));
             }
 
-            return new CompositionContainer(catalog);
+            return MefUtils.GetSafeCompositionContainer(catalogs);
         }
 
         /// <summary>

+ 1 - 0
MediaBrowser.Common/MediaBrowser.Common.csproj

@@ -87,6 +87,7 @@
     <Compile Include="Logging\BaseLogger.cs" />
     <Compile Include="Logging\LogSeverity.cs" />
     <Compile Include="Logging\TraceFileLogger.cs" />
+    <Compile Include="Mef\MefUtils.cs" />
     <Compile Include="Net\Handlers\StaticFileHandler.cs" />
     <Compile Include="Net\MimeTypes.cs" />
     <Compile Include="Plugins\BaseTheme.cs" />

+ 43 - 0
MediaBrowser.Common/Mef/MefUtils.cs

@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using System.ComponentModel.Composition.Hosting;
+using System.ComponentModel.Composition.Primitives;
+using System.Linq;
+using System.Reflection;
+
+namespace MediaBrowser.Common.Mef
+{
+    public static class MefUtils
+    {
+        /// <summary>
+        /// Plugins that live on both the server and UI are going to have references to assemblies from both sides.
+        /// But looks for Parts on one side, it will throw an exception when it seems Types from the other side that it doesn't have a reference to.
+        /// For example, a plugin provides a Resolver. When MEF runs in the UI, it will throw an exception when it sees the resolver because there won't be a reference to the base class.
+        /// This method will catch those exceptions while retining the list of Types that MEF is able to resolve.
+        /// </summary>
+        public static CompositionContainer GetSafeCompositionContainer(IEnumerable<ComposablePartCatalog> catalogs)
+        {
+            var newList = new List<ComposablePartCatalog>();
+
+            // Go through each Catalog
+            foreach (var catalog in catalogs)
+            {
+                try
+                {
+                    // Try to have MEF find Parts
+                    catalog.Parts.ToArray();
+
+                    // If it succeeds we can use the entire catalog
+                    newList.Add(catalog);
+                }
+                catch (ReflectionTypeLoadException ex)
+                {
+                    // If it fails we can still get a list of the Types it was able to resolve and create TypeCatalogs
+                    var typeCatalogs = ex.Types.Where(t => t != null).Select(t => new TypeCatalog(t));
+                    newList.AddRange(typeCatalogs);
+                }
+            }
+
+            return new CompositionContainer(new AggregateCatalog(newList));
+        }
+    }
+}

+ 2 - 1
MediaBrowser.Common/Plugins/BasePlugin.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Common.Serialization;
 using MediaBrowser.Model.Plugins;
 using System;
 using System.IO;
+using System.Reflection;
 
 namespace MediaBrowser.Common.Plugins
 {
@@ -12,7 +13,7 @@ namespace MediaBrowser.Common.Plugins
     /// </summary>
     public abstract class BasePlugin : IDisposable
     {
-        private IKernel Kernel { get; set; }
+        protected IKernel Kernel { get; private set; }
 
         /// <summary>
         /// Gets or sets the plugin's current context

+ 65 - 1
MediaBrowser.Common/Plugins/BaseTheme.cs

@@ -1,4 +1,12 @@
-
+using MediaBrowser.Common.Mef;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.ComponentModel.Composition.Hosting;
+using System.ComponentModel.Composition.Primitives;
+using System.Windows;
+using System.Windows.Controls;
+
 namespace MediaBrowser.Common.Plugins
 {
     public abstract class BaseTheme : BasePlugin
@@ -10,5 +18,61 @@ namespace MediaBrowser.Common.Plugins
                 return true;
             }
         }
+
+        /// <summary>
+        /// Gets the MEF CompositionContainer
+        /// </summary>
+        private CompositionContainer CompositionContainer { get; set; }
+
+        /// <summary>
+        /// Gets the list of global resources
+        /// </summary>
+        [ImportMany(typeof(ResourceDictionary))]
+        public IEnumerable<ResourceDictionary> GlobalResources { get; private set; }
+
+        /// <summary>
+        /// Gets the list of pages
+        /// </summary>
+        [ImportMany(typeof(Page))]
+        public IEnumerable<Page> Pages { get; private set; }
+
+        /// <summary>
+        /// Gets the pack Uri of the Login page
+        /// </summary>
+        public abstract Uri LoginPageUri { get; }
+
+        protected override void InitializeInUi()
+        {
+            base.InitializeInUi();
+
+            ComposeParts();
+        }
+
+        private void ComposeParts()
+        {
+            var catalog = new AssemblyCatalog(GetType().Assembly);
+
+            CompositionContainer = MefUtils.GetSafeCompositionContainer(new ComposablePartCatalog[] { catalog });
+
+            CompositionContainer.ComposeParts(this);
+
+            CompositionContainer.Catalog.Dispose();
+        }
+
+        protected override void DisposeInUi()
+        {
+            base.DisposeInUi();
+
+            CompositionContainer.Dispose();
+        }
+
+        protected Uri GeneratePackUri(string relativePath)
+        {
+            string assemblyName = GetType().Assembly.GetName().Name;
+
+            string uri = string.Format("pack://application:,,,/{0};component/{1}", assemblyName, relativePath);
+
+            return new Uri(uri, UriKind.Absolute);
+        }
     }
 }

+ 0 - 1
MediaBrowser.Controller/Kernel.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Common.Kernel;
 using MediaBrowser.Common.Logging;
-using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.IO;

+ 43 - 0
MediaBrowser.Plugins.DefaultTheme/Converters/TileBackgroundConverter.cs

@@ -0,0 +1,43 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace MediaBrowser.Plugins.DefaultTheme.Converters
+{
+    public class TileBackgroundConverter : IValueConverter
+    {
+        private static readonly Brush[] TileColors = new Brush[] {
+                new SolidColorBrush(Color.FromRgb((byte)111,(byte)189,(byte)69)),
+                new SolidColorBrush(Color.FromRgb((byte)75,(byte)179,(byte)221)),
+                new SolidColorBrush(Color.FromRgb((byte)65,(byte)100,(byte)165)),
+                new SolidColorBrush(Color.FromRgb((byte)225,(byte)32,(byte)38)),
+                new SolidColorBrush(Color.FromRgb((byte)128,(byte)0,(byte)128)),
+                new SolidColorBrush(Color.FromRgb((byte)0,(byte)128,(byte)64)),
+                new SolidColorBrush(Color.FromRgb((byte)0,(byte)148,(byte)255)),
+                new SolidColorBrush(Color.FromRgb((byte)255,(byte)0,(byte)199)),
+                new SolidColorBrush(Color.FromRgb((byte)255,(byte)135,(byte)15)),
+                new SolidColorBrush(Color.FromRgb((byte)127,(byte)0,(byte)55))
+    
+            };
+
+        private static int _currentIndex = new Random(DateTime.Now.Millisecond).Next(0, TileColors.Length);
+
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            int index;
+
+            lock (TileColors)
+            {
+                index = (_currentIndex++) % TileColors.Length;
+            }
+
+            return TileColors[index++];
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new System.NotImplementedException();
+        }
+    }
+}

+ 43 - 0
MediaBrowser.Plugins.DefaultTheme/Converters/WeatherImageConverter.cs

@@ -0,0 +1,43 @@
+using MediaBrowser.Model.Weather;
+using System;
+using System.ComponentModel.Composition;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace MediaBrowser.Plugins.DefaultTheme.Converters
+{
+    [PartNotDiscoverable]
+    public class WeatherImageConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var weather = value as WeatherInfo;
+
+            if (weather != null)
+            {
+                switch (weather.CurrentWeather.Condition)
+                {
+                    case WeatherConditions.Thunderstorm:
+                        return "../Images/Weather/Thunder.png";
+                    case WeatherConditions.Overcast:
+                        return "../Images/Weather/Overcast.png";
+                    case WeatherConditions.Mist:
+                    case WeatherConditions.Sleet:
+                    case WeatherConditions.Rain:
+                        return "../Images/Weather/Rain.png";
+                    case WeatherConditions.Blizzard:
+                    case WeatherConditions.Snow:
+                        return "../Images/Weather/Snow.png";
+                    default:
+                        return "../Images/Weather/Sunny.png";
+                }
+            }
+            return null;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 29 - 1
MediaBrowser.Plugins.DefaultTheme/MediaBrowser.Plugins.DefaultTheme.csproj

@@ -31,6 +31,9 @@
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
+  <PropertyGroup>
+    <RunPostBuildEvent>Always</RunPostBuildEvent>
+  </PropertyGroup>
   <ItemGroup>
     <Reference Include="System" />
     <Reference Include="System.ComponentModel.Composition" />
@@ -48,6 +51,12 @@
     <Reference Include="PresentationFramework" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Pages\LoginPage.xaml.cs">
+      <DependentUpon>LoginPage.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Resources\AppResources.cs" />
+    <Compile Include="Converters\TileBackgroundConverter.cs" />
+    <Compile Include="Converters\WeatherImageConverter.cs" />
     <Compile Include="Plugin.cs" />
     <Compile Include="Properties\AssemblyInfo.cs">
       <SubType>Code</SubType>
@@ -90,9 +99,28 @@
       <Name>MediaBrowser.UI</Name>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup>
+    <Page Include="Pages\LoginPage.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Resources\AppResources.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resources\Images\CurrentUserDefault.png" />
+    <Resource Include="Resources\Images\UserLoginDefault.png" />
+    <Resource Include="Resources\Images\Weather\Overcast.png" />
+    <Resource Include="Resources\Images\Weather\Rain.png" />
+    <Resource Include="Resources\Images\Weather\Snow.png" />
+    <Resource Include="Resources\Images\Weather\Sunny.png" />
+    <Resource Include="Resources\Images\Weather\Thunder.png" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
-    <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\" /y</PostBuildEvent>
+    <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData-Server\Plugins\" /y</PostBuildEvent>
   </PropertyGroup>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.

+ 64 - 0
MediaBrowser.Plugins.DefaultTheme/Pages/LoginPage.xaml

@@ -0,0 +1,64 @@
+<base:BaseLoginPage x:Class="MediaBrowser.Plugins.DefaultTheme.Pages.LoginPage"
+      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+      xmlns:base="clr-namespace:MediaBrowser.UI.Pages;assembly=MediaBrowser.UI"
+                    xmlns:DTO="clr-namespace:MediaBrowser.Model.DTO;assembly=MediaBrowser.Model"
+                    xmlns:controls="clr-namespace:MediaBrowser.UI.Controls;assembly=MediaBrowser.UI" mc:Ignorable="d" 
+      d:DesignHeight="300" 
+      d:DesignWidth="300"
+	  Title="LoginPage">
+
+    <Page.Resources>
+        <ResourceDictionary>
+            <DataTemplate DataType="{x:Type DTO:DtoUser}">
+                <Grid HorizontalAlignment="Left" Margin="3">
+
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="auto"></ColumnDefinition>
+                        <ColumnDefinition Width="475"></ColumnDefinition>
+                    </Grid.ColumnDefinitions>
+
+                    <controls:ExtendedImage HasImage="{Binding HasImage}"
+                                        PlaceHolderSource="../Resources/Images/UserLoginDefault.png"
+                                        Source="{Binding Converter={StaticResource UserImageConverter}, ConverterParameter='225,225,0,0'}"
+                                        Stretch="Uniform"
+                                        Width="225"
+                                        Height="225"
+                                        Background="{Binding Converter={StaticResource TileBackgroundConverter}}"/>
+                    <TextBlock Text="{Binding Name}" VerticalAlignment="Top" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="0" Margin="25 30 0 0" FontSize="{StaticResource Heading2FontSize}"></TextBlock>
+                    <TextBlock Text="{Binding Converter={StaticResource LastSeenTextConverter}}" VerticalAlignment="Center" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="0" Margin="25 80 0 0"></TextBlock>
+                </Grid>
+            </DataTemplate>
+
+        </ResourceDictionary>
+    </Page.Resources>
+    <Grid>
+
+        <Grid.RowDefinitions>
+            <RowDefinition Height="auto"></RowDefinition>
+            <RowDefinition Height="*"></RowDefinition>
+        </Grid.RowDefinitions>
+
+        <Image Style="{StaticResource MBLogoImageBlack}" Margin="0 0 0 10" Height="125" Stretch="Uniform" HorizontalAlignment="Left"></Image>
+
+        <Grid VerticalAlignment="Stretch" HorizontalAlignment="Center" Grid.Row="1">
+
+            <Grid.RowDefinitions>
+                <RowDefinition Height="auto"></RowDefinition>
+                <RowDefinition Height="*"></RowDefinition>
+            </Grid.RowDefinitions>
+
+            <TextBlock FontSize="{StaticResource Heading2FontSize}" Grid.Row="0" Margin="0 0 0 30">Select Profile</TextBlock>
+
+            <ListView HorizontalAlignment="Center" Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ItemsSource="{Binding Path=Users}" Style="{StaticResource ListViewStyle}" ItemContainerStyle="{StaticResource ListViewItemStyle}">
+                <ListView.ItemsPanel>
+                    <ItemsPanelTemplate>
+                        <WrapPanel Orientation="Vertical" />
+                    </ItemsPanelTemplate>
+                </ListView.ItemsPanel>
+            </ListView>
+        </Grid>
+    </Grid>
+</base:BaseLoginPage>

+ 15 - 0
MediaBrowser.Plugins.DefaultTheme/Pages/LoginPage.xaml.cs

@@ -0,0 +1,15 @@
+using MediaBrowser.UI.Pages;
+
+namespace MediaBrowser.Plugins.DefaultTheme.Pages
+{
+    /// <summary>
+    /// Interaction logic for LoginPage.xaml
+    /// </summary>
+    public partial class LoginPage : BaseLoginPage
+    {
+        public LoginPage()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 3 - 2
MediaBrowser.Plugins.DefaultTheme/Plugin.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.Plugins;
+using System;
 using System.ComponentModel.Composition;
 
 namespace MediaBrowser.Plugins.DefaultTheme
@@ -11,9 +12,9 @@ namespace MediaBrowser.Plugins.DefaultTheme
             get { return "Default Theme"; }
         }
 
-        protected override void InitializeInUi()
+        public override Uri LoginPageUri
         {
-            base.InitializeInUi();
+            get { return GeneratePackUri("Pages/LoginPage.xaml"); }
         }
     }
 }

+ 14 - 0
MediaBrowser.Plugins.DefaultTheme/Resources/AppResources.cs

@@ -0,0 +1,14 @@
+using System.ComponentModel.Composition;
+using System.Windows;
+
+namespace MediaBrowser.Plugins.DefaultTheme.Resources
+{
+    [Export(typeof(ResourceDictionary))]
+    public partial class AppResources : ResourceDictionary
+    {
+        public AppResources()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 81 - 0
MediaBrowser.Plugins.DefaultTheme/Resources/AppResources.xaml

@@ -0,0 +1,81 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:themeconverters="clr-namespace:MediaBrowser.Plugins.DefaultTheme.Converters"
+                    x:Class="MediaBrowser.Plugins.DefaultTheme.Resources.AppResources">
+
+    <themeconverters:WeatherImageConverter x:Key="WeatherImageConverter"></themeconverters:WeatherImageConverter>
+    <themeconverters:TileBackgroundConverter x:Key="TileBackgroundConverter"></themeconverters:TileBackgroundConverter>
+
+    <Style x:Key="ListViewItemStyle" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource BaseListViewItemStyle}">
+
+        <Style.Resources>
+            <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="LightBlue"/>
+        </Style.Resources>
+
+        <Style.Triggers>
+            <Trigger Property="IsMouseOver" Value="True">
+                <Setter Property="Opacity" Value=".85" />
+            </Trigger>
+        </Style.Triggers>
+    </Style>
+
+    <!--Override MainWindow style-->
+    <Style TargetType="Window" x:Key="MainWindow" BasedOn="{StaticResource BaseWindow}">
+        <Setter Property="Background">
+            <Setter.Value>
+                <RadialGradientBrush RadiusX=".75" RadiusY=".75">
+                    <GradientStop Color="White" Offset="0.0"/>
+                    <GradientStop Color="WhiteSmoke" Offset="0.5"/>
+                    <GradientStop Color="#cfcfcf" Offset="1.0"/>
+                </RadialGradientBrush>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
+    <!--Override PageContentTemplate-->
+    <ControlTemplate x:Key="PageContentTemplate">
+
+        <Grid Margin="20 15 20 20">
+
+            <StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Right">
+
+                <!--Display CurrentUser-->
+                <StackPanel Orientation="Horizontal" Margin="0 0 30 0" Visibility="{Binding Path=CurrentUser,Converter={StaticResource CurrentUserVisibilityConverter}}">
+                    <TextBlock FontSize="{StaticResource Heading2FontSize}" Text="{Binding Path=CurrentUser.Name}" Margin="0 0 5 0">
+                    </TextBlock>
+                    <Image>
+                        <Image.Style>
+                            <Style TargetType="{x:Type Image}">
+                                <Setter Property="Image.Source" Value="Images\CurrentUserDefault.png" />
+                                <Setter Property="Stretch" Value="None" />
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding Path=CurrentUser.HasImage}" Value="true">
+                                        <Setter Property="Image.Source" Value="{Binding Path=CurrentUser,Converter={StaticResource UserImageConverter}, ConverterParameter='0,64,0,0'}" />
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Image.Style>
+                    </Image>
+                </StackPanel>
+
+                <!--Display Weather-->
+                <StackPanel Orientation="Horizontal" Margin="0 0 30 0" Visibility="{Binding Path=CurrentWeather,Converter={StaticResource WeatherVisibilityConverter}}">
+
+                    <TextBlock FontSize="{StaticResource Heading2FontSize}" Text="{Binding Path=CurrentWeather,Converter={StaticResource WeatherTemperatureConverter}}" Margin="0 0 5 0">
+                    </TextBlock>
+                    <Image Stretch="None" Source="{Binding Path=CurrentWeather,Converter={StaticResource WeatherImageConverter}}"></Image>
+                </StackPanel>
+
+                <!--Display Clock-->
+                <TextBlock FontSize="{StaticResource Heading2FontSize}">
+                    <TextBlock.Text>
+                        <Binding Path="CurrentTime" Converter="{StaticResource DateTimeToStringConverter}" ConverterParameter="h:mm" />
+                    </TextBlock.Text>
+                </TextBlock>
+            </StackPanel>
+
+            <Frame x:Name="PageFrame"></Frame>
+        </Grid>
+    </ControlTemplate>
+
+</ResourceDictionary>

BIN
MediaBrowser.Plugins.DefaultTheme/Resources/Images/CurrentUserDefault.png


BIN
MediaBrowser.Plugins.DefaultTheme/Resources/Images/UserLoginDefault.png


BIN
MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Overcast.png


BIN
MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Rain.png


BIN
MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Snow.png


BIN
MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Sunny.png


BIN
MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Thunder.png


+ 1 - 1
MediaBrowser.ServerApplication/App.config

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <configuration>
   <appSettings>
-    <add key="ProgramDataPath" value="..\..\..\ProgramData" />
+    <add key="ProgramDataPath" value="..\..\..\ProgramData-Server" />
   </appSettings>
   <startup>
     <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />

+ 25 - 17
MediaBrowser.Plugins.sln → MediaBrowser.UI.sln

@@ -1,11 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 2012
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Plugins.DefaultTheme", "MediaBrowser.Plugins.DefaultTheme\MediaBrowser.Plugins.DefaultTheme.csproj", "{6E892999-711D-4E24-8BAC-DACF5BFA783A}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.UI", "..\MediaBrowserUI\MediaBrowser.UI\MediaBrowser.UI.csproj", "{B5ECE1FB-618E-420B-9A99-8E972D76920A}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.UI", "MediaBrowser.UI\MediaBrowser.UI.csproj", "{B5ECE1FB-618E-420B-9A99-8E972D76920A}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}"
 EndProject
@@ -13,36 +9,48 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model", "Media
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.ApiInteraction", "MediaBrowser.ApiInteraction\MediaBrowser.ApiInteraction.csproj", "{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Plugins.DefaultTheme", "MediaBrowser.Plugins.DefaultTheme\MediaBrowser.Plugins.DefaultTheme.csproj", "{6E892999-711D-4E24-8BAC-DACF5BFA783A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
+		Debug|x86 = Debug|x86
 		Release|Any CPU = Release|Any CPU
+		Release|x86 = Release|x86
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{6E892999-711D-4E24-8BAC-DACF5BFA783A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{6E892999-711D-4E24-8BAC-DACF5BFA783A}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{6E892999-711D-4E24-8BAC-DACF5BFA783A}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{6E892999-711D-4E24-8BAC-DACF5BFA783A}.Release|Any CPU.Build.0 = Release|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU
 		{B5ECE1FB-618E-420B-9A99-8E972D76920A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{B5ECE1FB-618E-420B-9A99-8E972D76920A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{B5ECE1FB-618E-420B-9A99-8E972D76920A}.Debug|x86.ActiveCfg = Debug|Any CPU
 		{B5ECE1FB-618E-420B-9A99-8E972D76920A}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{B5ECE1FB-618E-420B-9A99-8E972D76920A}.Release|Any CPU.Build.0 = Release|Any CPU
+		{B5ECE1FB-618E-420B-9A99-8E972D76920A}.Release|x86.ActiveCfg = Release|Any CPU
 		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|x86.ActiveCfg = Debug|Any CPU
 		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.ActiveCfg = Release|Any CPU
 		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x86.ActiveCfg = Debug|Any CPU
 		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.ActiveCfg = Release|Any CPU
 		{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Debug|x86.ActiveCfg = Debug|Any CPU
 		{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Release|Any CPU.Build.0 = Release|Any CPU
+		{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Release|x86.ActiveCfg = Release|Any CPU
+		{6E892999-711D-4E24-8BAC-DACF5BFA783A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6E892999-711D-4E24-8BAC-DACF5BFA783A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6E892999-711D-4E24-8BAC-DACF5BFA783A}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{6E892999-711D-4E24-8BAC-DACF5BFA783A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6E892999-711D-4E24-8BAC-DACF5BFA783A}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6E892999-711D-4E24-8BAC-DACF5BFA783A}.Release|x86.ActiveCfg = Release|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.ActiveCfg = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 9 - 0
MediaBrowser.UI/App.config

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+  <appSettings>
+    <add key="ProgramDataPath" value="..\..\..\ProgramData-UI" />
+  </appSettings>
+  <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
+    </startup>
+</configuration>

+ 14 - 0
MediaBrowser.UI/App.xaml

@@ -0,0 +1,14 @@
+<z:BaseApplication x:Class="MediaBrowser.UI.App"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:z="clr-namespace:MediaBrowser.Common.UI;assembly=MediaBrowser.Common">
+    <Application.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="Resources/AppResources.xaml" />
+                <ResourceDictionary Source="Resources/MainWindowResources.xaml" />
+                <ResourceDictionary Source="Resources/NavBarResources.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </Application.Resources>
+</z:BaseApplication>

+ 213 - 0
MediaBrowser.UI/App.xaml.cs

@@ -0,0 +1,213 @@
+using MediaBrowser.Common.Kernel;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Common.UI;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.DTO;
+using MediaBrowser.Model.Weather;
+using MediaBrowser.UI.Controller;
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media.Imaging;
+
+namespace MediaBrowser.UI
+{
+    /// <summary>
+    /// Interaction logic for App.xaml
+    /// </summary>
+    public partial class App : BaseApplication, IApplication
+    {
+        private Timer ClockTimer { get; set; }
+        private Timer ServerConfigurationTimer { get; set; }
+
+        public static App Instance
+        {
+            get
+            {
+                return Application.Current as App;
+            }
+        }
+
+        public DtoUser CurrentUser
+        {
+            get
+            {
+                return UIKernel.Instance.CurrentUser;
+            }
+            set
+            {
+                UIKernel.Instance.CurrentUser = value;
+                OnPropertyChanged("CurrentUser");
+            }
+        }
+
+        public ServerConfiguration ServerConfiguration
+        {
+            get
+            {
+                return UIKernel.Instance.ServerConfiguration;
+            }
+            set
+            {
+                UIKernel.Instance.ServerConfiguration = value;
+                OnPropertyChanged("ServerConfiguration");
+            }
+        }
+
+        private DateTime _currentTime = DateTime.Now;
+        public DateTime CurrentTime
+        {
+            get
+            {
+                return _currentTime;
+            }
+            private set
+            {
+                _currentTime = value;
+                OnPropertyChanged("CurrentTime");
+            }
+        }
+
+        private WeatherInfo _currentWeather;
+        public WeatherInfo CurrentWeather
+        {
+            get
+            {
+                return _currentWeather;
+            }
+            private set
+            {
+                _currentWeather = value;
+                OnPropertyChanged("CurrentWeather");
+            }
+        }
+
+        private BaseTheme _currentTheme;
+        public BaseTheme CurrentTheme
+        {
+            get
+            {
+                return _currentTheme;
+            }
+            private set
+            {
+                _currentTheme = value;
+                OnPropertyChanged("CurrentTheme");
+            }
+        }
+
+        [STAThread]
+        public static void Main()
+        {
+            RunApplication<App>("MediaBrowserUI");
+        }
+
+        #region BaseApplication Overrides
+        protected override IKernel InstantiateKernel()
+        {
+            return new UIKernel();
+        }
+
+        protected override Window InstantiateMainWindow()
+        {
+            return new MainWindow();
+        }
+
+        protected override void OnKernelLoaded()
+        {
+            base.OnKernelLoaded();
+
+            PropertyChanged += AppPropertyChanged;
+            
+            // Update every 10 seconds
+            ClockTimer = new Timer(ClockTimerCallback, null, 0, 10000);
+
+            // Update every 30 minutes
+            ServerConfigurationTimer = new Timer(ServerConfigurationTimerCallback, null, 0, 1800000);
+
+            CurrentTheme = UIKernel.Instance.Plugins.OfType<BaseTheme>().First();
+
+            foreach (var resource in CurrentTheme.GlobalResources)
+            {
+                Resources.MergedDictionaries.Add(resource);
+            }
+        }
+        #endregion
+
+        async void AppPropertyChanged(object sender, PropertyChangedEventArgs e)
+        {
+            if (e.PropertyName.Equals("ServerConfiguration"))
+            {
+                if (string.IsNullOrEmpty(ServerConfiguration.WeatherZipCode))
+                {
+                    CurrentWeather = null;
+                }
+                else
+                {
+                    CurrentWeather = await UIKernel.Instance.ApiClient.GetWeatherInfoAsync(ServerConfiguration.WeatherZipCode);
+                }
+            }
+        }
+
+        private void ClockTimerCallback(object stateInfo)
+        {
+            CurrentTime = DateTime.Now;
+        }
+
+        private async void ServerConfigurationTimerCallback(object stateInfo)
+        {
+            ServerConfiguration = await UIKernel.Instance.ApiClient.GetServerConfigurationAsync();
+        }
+
+        public async Task<Image> GetImage(string url)
+        {
+            var image = new Image();
+
+            image.Source = await GetBitmapImage(url);
+
+            return image;
+        }
+
+        public async Task<BitmapImage> GetBitmapImage(string url)
+        {
+            Stream stream = await UIKernel.Instance.ApiClient.GetImageStreamAsync(url);
+
+            BitmapImage bitmap = new BitmapImage();
+
+            bitmap.CacheOption = BitmapCacheOption.Default;
+
+            bitmap.BeginInit();
+            bitmap.StreamSource = stream;
+            bitmap.EndInit();
+
+            return bitmap;
+        }
+
+        public async Task LogoutUser()
+        {
+            CurrentUser = null;
+
+            if (ServerConfiguration.EnableUserProfiles)
+            {
+                Navigate(CurrentTheme.LoginPageUri);
+            }
+            else
+            {
+                DtoUser defaultUser = await UIKernel.Instance.ApiClient.GetDefaultUserAsync();
+                CurrentUser = defaultUser;
+
+                Navigate(new Uri("/Pages/HomePage.xaml", UriKind.Relative));
+            }
+        }
+
+        public void Navigate(Uri uri)
+        {
+            (MainWindow as MainWindow).Navigate(uri);
+        }
+    }
+}

+ 27 - 0
MediaBrowser.UI/Configuration/UIApplicationConfiguration.cs

@@ -0,0 +1,27 @@
+using MediaBrowser.Model.Configuration;
+
+namespace MediaBrowser.UI.Configuration
+{
+    /// <summary>
+    /// This is the UI's device configuration that applies regardless of which user is logged in.
+    /// </summary>
+    public class UIApplicationConfiguration : BaseApplicationConfiguration
+    {
+        /// <summary>
+        /// Gets or sets the server host name (myserver or 192.168.x.x)
+        /// </summary>
+        public string ServerHostName { get; set; }
+
+        /// <summary>
+        /// Gets or sets the port number used by the API
+        /// </summary>
+        public int ServerApiPort { get; set; }
+
+        public UIApplicationConfiguration()
+            : base()
+        {
+            ServerHostName = "localhost";
+            ServerApiPort = 8096;
+        }
+    }
+}

+ 8 - 0
MediaBrowser.UI/Configuration/UIApplicationPaths.cs

@@ -0,0 +1,8 @@
+using MediaBrowser.Common.Kernel;
+
+namespace MediaBrowser.UI.Configuration
+{
+    public class UIApplicationPaths : BaseApplicationPaths
+    {
+    }
+}

+ 231 - 0
MediaBrowser.UI/Controller/PluginUpdater.cs

@@ -0,0 +1,231 @@
+using MediaBrowser.Common.Logging;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Common.Serialization;
+using MediaBrowser.Model.DTO;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.ComponentModel.Composition.Hosting;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.UI.Controller
+{
+    /// <summary>
+    /// This keeps ui plugin assemblies in sync with plugins installed on the server
+    /// </summary>
+    public class PluginUpdater
+    {
+        /// <summary>
+        /// Gets the list of currently installed UI plugins
+        /// </summary>
+        [ImportMany(typeof(BasePlugin))]
+        private IEnumerable<BasePlugin> CurrentPlugins { get; set; }
+
+        private CompositionContainer CompositionContainer { get; set; }
+
+        public async Task<PluginUpdateResult> UpdatePlugins()
+        {
+            // First load the plugins that are currently installed
+            ReloadComposableParts();
+
+            Logger.LogInfo("Downloading list of installed plugins");
+            PluginInfo[] allInstalledPlugins = await UIKernel.Instance.ApiClient.GetInstalledPluginsAsync().ConfigureAwait(false);
+
+            IEnumerable<PluginInfo> uiPlugins = allInstalledPlugins.Where(p => p.DownloadToUI);
+
+            PluginUpdateResult result = new PluginUpdateResult();
+
+            result.DeletedPlugins = DeleteUninstalledPlugins(uiPlugins);
+
+            await DownloadPluginAssemblies(uiPlugins, result).ConfigureAwait(false);
+
+            // If any new assemblies were downloaded we'll have to reload the CurrentPlugins list
+            if (result.NewlyInstalledPlugins.Any())
+            {
+                ReloadComposableParts();
+            }
+
+            result.UpdatedConfigurations = await DownloadPluginConfigurations(uiPlugins).ConfigureAwait(false);
+
+            CompositionContainer.Dispose();
+
+            return result;
+        }
+
+        /// <summary>
+        /// Downloads plugin assemblies from the server, if they need to be installed or updated.
+        /// </summary>
+        private async Task DownloadPluginAssemblies(IEnumerable<PluginInfo> uiPlugins, PluginUpdateResult result)
+        {
+            List<PluginInfo> newlyInstalledPlugins = new List<PluginInfo>();
+            List<PluginInfo> updatedPlugins = new List<PluginInfo>();
+
+            // Loop through the list of plugins that are on the server
+            foreach (PluginInfo pluginInfo in uiPlugins)
+            {
+                // See if it is already installed in the UI
+                BasePlugin installedPlugin = CurrentPlugins.FirstOrDefault(p => p.AssemblyFileName.Equals(pluginInfo.AssemblyFileName, StringComparison.OrdinalIgnoreCase));
+
+                // Download the plugin if it is not present, or if the current version is out of date
+                bool downloadPlugin = installedPlugin == null;
+
+                if (installedPlugin != null)
+                {
+                    Version serverVersion = Version.Parse(pluginInfo.Version);
+
+                    downloadPlugin = serverVersion > installedPlugin.Version;
+                }
+
+                if (downloadPlugin)
+                {
+                    await DownloadPlugin(pluginInfo).ConfigureAwait(false);
+
+                    if (installedPlugin == null)
+                    {
+                        newlyInstalledPlugins.Add(pluginInfo);
+                    }
+                    else
+                    {
+                        updatedPlugins.Add(pluginInfo);
+                    }
+                }
+            }
+
+            result.NewlyInstalledPlugins = newlyInstalledPlugins;
+            result.UpdatedPlugins = updatedPlugins;
+        }
+
+        /// <summary>
+        /// Downloads plugin configurations from the server.
+        /// </summary>
+        private async Task<List<PluginInfo>> DownloadPluginConfigurations(IEnumerable<PluginInfo> uiPlugins)
+        {
+            List<PluginInfo> updatedPlugins = new List<PluginInfo>();
+
+            // Loop through the list of plugins that are on the server
+            foreach (PluginInfo pluginInfo in uiPlugins)
+            {
+                // See if it is already installed in the UI
+                BasePlugin installedPlugin = CurrentPlugins.First(p => p.AssemblyFileName.Equals(pluginInfo.AssemblyFileName, StringComparison.OrdinalIgnoreCase));
+
+                if (installedPlugin.ConfigurationDateLastModified < pluginInfo.ConfigurationDateLastModified)
+                {
+                    await DownloadPluginConfiguration(installedPlugin, pluginInfo).ConfigureAwait(false);
+
+                    updatedPlugins.Add(pluginInfo);
+                }
+            }
+
+            return updatedPlugins;
+        }
+
+        /// <summary>
+        /// Downloads a plugin assembly from the server
+        /// </summary>
+        private async Task DownloadPlugin(PluginInfo plugin)
+        {
+            Logger.LogInfo("Downloading {0} Plugin", plugin.Name);
+
+            string path = Path.Combine(UIKernel.Instance.ApplicationPaths.PluginsPath, plugin.AssemblyFileName);
+
+            // First download to a MemoryStream. This way if the download is cut off, we won't be left with a partial file
+            using (MemoryStream memoryStream = new MemoryStream())
+            {
+                Stream assemblyStream = await UIKernel.Instance.ApiClient.GetPluginAssemblyAsync(plugin).ConfigureAwait(false);
+
+                await assemblyStream.CopyToAsync(memoryStream).ConfigureAwait(false);
+
+                memoryStream.Position = 0;
+
+                using (FileStream fileStream = new FileStream(path, FileMode.Create))
+                {
+                    await memoryStream.CopyToAsync(fileStream).ConfigureAwait(false);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Downloads the latest configuration for a plugin
+        /// </summary>
+        private async Task DownloadPluginConfiguration(BasePlugin plugin, PluginInfo pluginInfo)
+        {
+            Logger.LogInfo("Downloading {0} Configuration", plugin.Name);
+
+            object config = await UIKernel.Instance.ApiClient.GetPluginConfigurationAsync(pluginInfo, plugin.ConfigurationType).ConfigureAwait(false);
+
+            XmlSerializer.SerializeToFile(config, plugin.ConfigurationFilePath);
+
+            File.SetLastWriteTimeUtc(plugin.ConfigurationFilePath, pluginInfo.ConfigurationDateLastModified);
+        }
+
+        /// <summary>
+        /// Deletes any plugins that have been uninstalled from the server
+        /// </summary>
+        private IEnumerable<string> DeleteUninstalledPlugins(IEnumerable<PluginInfo> uiPlugins)
+        {
+            var deletedPlugins = new List<string>();
+
+            foreach (BasePlugin plugin in CurrentPlugins)
+            {
+                PluginInfo latest = uiPlugins.FirstOrDefault(p => p.AssemblyFileName.Equals(plugin.AssemblyFileName, StringComparison.OrdinalIgnoreCase));
+
+                if (latest == null)
+                {
+                    DeletePlugin(plugin);
+
+                    deletedPlugins.Add(plugin.Name);
+                }
+            }
+
+            return deletedPlugins;
+        }
+
+        /// <summary>
+        /// Deletes an installed ui plugin.
+        /// Leaves config and data behind in the event it is later re-installed
+        /// </summary>
+        private void DeletePlugin(BasePlugin plugin)
+        {
+            Logger.LogInfo("Deleting {0} Plugin", plugin.Name);
+
+            string path = plugin.AssemblyFilePath;
+
+            if (File.Exists(path))
+            {
+                File.Delete(path);
+            }
+        }
+
+        /// <summary>
+        /// Re-uses MEF within the kernel to discover installed plugins
+        /// </summary>
+        private void ReloadComposableParts()
+        {
+            if (CompositionContainer != null)
+            {
+                CompositionContainer.Dispose();
+            }
+
+            CompositionContainer = UIKernel.Instance.GetCompositionContainer();
+
+            CompositionContainer.ComposeParts(this);
+
+            CompositionContainer.Catalog.Dispose();
+
+            foreach (BasePlugin plugin in CurrentPlugins)
+            {
+                plugin.Initialize(UIKernel.Instance, false);
+            }
+        }
+    }
+
+    public class PluginUpdateResult
+    {
+        public IEnumerable<string> DeletedPlugins { get; set; }
+        public IEnumerable<PluginInfo> NewlyInstalledPlugins { get; set; }
+        public IEnumerable<PluginInfo> UpdatedPlugins { get; set; }
+        public IEnumerable<PluginInfo> UpdatedConfigurations { get; set; }
+    }
+}

+ 97 - 0
MediaBrowser.UI/Controller/UIKernel.cs

@@ -0,0 +1,97 @@
+using MediaBrowser.ApiInteraction;
+using MediaBrowser.Common.Kernel;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.DTO;
+using MediaBrowser.Model.Progress;
+using MediaBrowser.UI.Configuration;
+using System;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.UI.Controller
+{
+    /// <summary>
+    /// This controls application logic as well as server interaction within the UI.
+    /// </summary>
+    public class UIKernel : BaseKernel<UIApplicationConfiguration, UIApplicationPaths>
+    {
+        public static UIKernel Instance { get; private set; }
+
+        public ApiClient ApiClient { get; private set; }
+        public DtoUser CurrentUser { get; set; }
+        public ServerConfiguration ServerConfiguration { get; set; }
+
+        public UIKernel()
+            : base()
+        {
+            Instance = this;
+        }
+
+        public override KernelContext KernelContext
+        {
+            get { return KernelContext.Ui; }
+        }
+
+        /// <summary>
+        /// Give the UI a different url prefix so that they can share the same port, in case they are installed on the same machine.
+        /// </summary>
+        protected override string HttpServerUrlPrefix
+        {
+            get
+            {
+                return "http://+:" + Configuration.HttpServerPortNumber + "/mediabrowser/ui/";
+            }
+        }
+
+        /// <summary>
+        /// Performs initializations that can be reloaded at anytime
+        /// </summary>
+        protected override async Task ReloadInternal(IProgress<TaskProgress> progress)
+        {
+            ReloadApiClient();
+
+            await new PluginUpdater().UpdatePlugins().ConfigureAwait(false);
+
+            await base.ReloadInternal(progress).ConfigureAwait(false);
+        }
+
+        /// <summary>
+        /// Updates and installs new plugin assemblies and configurations from the server
+        /// </summary>
+        protected async Task<PluginUpdateResult> UpdatePlugins()
+        {
+            return await new PluginUpdater().UpdatePlugins().ConfigureAwait(false);
+        }
+
+        /// <summary>
+        /// Disposes the current ApiClient and creates a new one
+        /// </summary>
+        private void ReloadApiClient()
+        {
+            DisposeApiClient();
+
+            ApiClient = new ApiClient
+            {
+                ServerHostName = Configuration.ServerHostName,
+                ServerApiPort = Configuration.ServerApiPort
+            };
+        }
+
+        /// <summary>
+        /// Disposes the current ApiClient
+        /// </summary>
+        private void DisposeApiClient()
+        {
+            if (ApiClient != null)
+            {
+                ApiClient.Dispose();
+            }
+        }
+
+        public override void Dispose()
+        {
+            base.Dispose();
+
+            DisposeApiClient();
+        }
+    }
+}

+ 73 - 0
MediaBrowser.UI/Controls/EnhancedScrollViewer.cs

@@ -0,0 +1,73 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace MediaBrowser.UI.Controls
+{
+    /// <summary>
+    /// Provides a ScrollViewer that can be scrolled by dragging the mouse
+    /// </summary>
+    public class EnhancedScrollViewer : ScrollViewer
+    {
+        private Point _scrollTarget;
+        private Point _scrollStartPoint;
+        private Point _scrollStartOffset;
+        private const int PixelsToMoveToBeConsideredScroll = 5;
+        
+        protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
+        {
+            if (IsMouseOver)
+            {
+                // Save starting point, used later when determining how much to scroll.
+                _scrollStartPoint = e.GetPosition(this);
+                _scrollStartOffset.X = HorizontalOffset;
+                _scrollStartOffset.Y = VerticalOffset;
+
+                // Update the cursor if can scroll or not.
+                Cursor = (ExtentWidth > ViewportWidth) ||
+                    (ExtentHeight > ViewportHeight) ?
+                    Cursors.ScrollAll : Cursors.Arrow;
+
+                CaptureMouse();
+            }
+            
+            base.OnPreviewMouseDown(e);
+        }
+
+        protected override void OnPreviewMouseMove(MouseEventArgs e)
+        {
+            if (IsMouseCaptured)
+            {
+                Point currentPoint = e.GetPosition(this);
+
+                // Determine the new amount to scroll.
+                var delta = new Point(_scrollStartPoint.X - currentPoint.X, _scrollStartPoint.Y - currentPoint.Y);
+
+                if (Math.Abs(delta.X) < PixelsToMoveToBeConsideredScroll &&
+                    Math.Abs(delta.Y) < PixelsToMoveToBeConsideredScroll)
+                    return;
+
+                _scrollTarget.X = _scrollStartOffset.X + delta.X;
+                _scrollTarget.Y = _scrollStartOffset.Y + delta.Y;
+
+                // Scroll to the new position.
+                ScrollToHorizontalOffset(_scrollTarget.X);
+                ScrollToVerticalOffset(_scrollTarget.Y);
+            }
+            
+            base.OnPreviewMouseMove(e);
+        }
+
+        protected override void OnPreviewMouseUp(MouseButtonEventArgs e)
+        {
+            if (IsMouseCaptured)
+            {
+                Cursor = Cursors.Arrow;
+                ReleaseMouseCapture();
+            }
+            
+            base.OnPreviewMouseUp(e);
+        }
+    }
+}

+ 92 - 0
MediaBrowser.UI/Controls/ExtendedImage.cs

@@ -0,0 +1,92 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace MediaBrowser.UI.Controls
+{
+    /// <summary>
+    /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
+    ///
+    /// Step 1a) Using this custom control in a XAML file that exists in the current project.
+    /// Add this XmlNamespace attribute to the root element of the markup file where it is 
+    /// to be used:
+    ///
+    ///     xmlns:MyNamespace="clr-namespace:MediaBrowser.UI.Controls"
+    ///
+    ///
+    /// Step 1b) Using this custom control in a XAML file that exists in a different project.
+    /// Add this XmlNamespace attribute to the root element of the markup file where it is 
+    /// to be used:
+    ///
+    ///     xmlns:MyNamespace="clr-namespace:MediaBrowser.UI.Controls;assembly=MediaBrowser.UI.Controls"
+    ///
+    /// You will also need to add a project reference from the project where the XAML file lives
+    /// to this project and Rebuild to avoid compilation errors:
+    ///
+    ///     Right click on the target project in the Solution Explorer and
+    ///     "Add Reference"->"Projects"->[Browse to and select this project]
+    ///
+    ///
+    /// Step 2)
+    /// Go ahead and use your control in the XAML file.
+    ///
+    ///     <MyNamespace:ExtendedImage/>
+    ///
+    /// </summary>
+    public class ExtendedImage : Control
+    {
+        public static readonly DependencyProperty HasImageProperty = DependencyProperty.Register(
+            "HasImage", 
+            typeof (bool), 
+            typeof (ExtendedImage),
+            new PropertyMetadata(default(bool)));
+
+        public bool HasImage
+        {
+            get { return (bool)GetValue(HasImageProperty); }
+            set { SetValue(HasImageProperty, value); }
+        }
+
+        public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
+            "Source", 
+            typeof(ImageSource), 
+            typeof(ExtendedImage), 
+            new PropertyMetadata(default(ImageBrush)));
+
+        public ImageSource Source
+        {
+            get { return (ImageSource)GetValue(SourceProperty); }
+            set { SetValue(SourceProperty, value); }
+        }
+
+        public static readonly DependencyProperty StretchProperty = DependencyProperty.Register(
+            "Stretch", 
+            typeof (Stretch), 
+            typeof (ExtendedImage), 
+            new PropertyMetadata(default(Stretch)));
+
+        public Stretch Stretch
+        {
+            get { return (Stretch) GetValue(StretchProperty); }
+            set { SetValue(StretchProperty, value); }
+        }
+
+        public static readonly DependencyProperty PlaceHolderSourceProperty = DependencyProperty.Register(
+            "PlaceHolderSource",
+            typeof(ImageSource),
+            typeof(ExtendedImage),
+            new PropertyMetadata(default(ImageBrush)));
+
+        public ImageSource PlaceHolderSource
+        {
+            get { return (ImageSource)GetValue(PlaceHolderSourceProperty); }
+            set { SetValue(PlaceHolderSourceProperty, value); }
+        }
+
+        static ExtendedImage()
+        {
+            DefaultStyleKeyProperty.OverrideMetadata(typeof(ExtendedImage),
+                new FrameworkPropertyMetadata(typeof(ExtendedImage)));
+        }
+    }
+}

+ 226 - 0
MediaBrowser.UI/Controls/TreeHelper.cs

@@ -0,0 +1,226 @@
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Media;
+
+namespace MediaBrowser.UI.Controls
+{
+    /// <summary>
+    /// Helper methods for UI-related tasks.
+    /// </summary>
+    public static class TreeHelper
+    {
+        /// <summary>
+        /// Finds a Child of a given item in the visual tree. 
+        /// </summary>
+        /// <param name="parent">A direct parent of the queried item.</param>
+        /// <typeparam name="T">The type of the queried item.</typeparam>
+        /// <param name="childName">x:Name or Name of child. </param>
+        /// <returns>The first parent item that matches the submitted type parameter. 
+        /// If not matching item can be found, 
+        /// a null parent is being returned.</returns>
+        public static T FindChild<T>(DependencyObject parent, string childName)
+           where T : DependencyObject
+        {
+            // Confirm parent and childName are valid. 
+            if (parent == null) return null;
+
+            T foundChild = null;
+
+            int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
+            for (int i = 0; i < childrenCount; i++)
+            {
+                var child = VisualTreeHelper.GetChild(parent, i);
+                // If the child is not of the request child type child
+                T childType = child as T;
+                if (childType == null)
+                {
+                    // recursively drill down the tree
+                    foundChild = FindChild<T>(child, childName);
+
+                    // If the child is found, break so we do not overwrite the found child. 
+                    if (foundChild != null) break;
+                }
+                else if (!string.IsNullOrEmpty(childName))
+                {
+                    var frameworkElement = child as FrameworkElement;
+                    // If the child's name is set for search
+                    if (frameworkElement != null && frameworkElement.Name == childName)
+                    {
+                        // if the child's name is of the request name
+                        foundChild = (T)child;
+                        break;
+                    }
+                }
+                else
+                {
+                    // child element found.
+                    foundChild = (T)child;
+                    break;
+                }
+            }
+
+            return foundChild;
+        }
+
+        #region find parent
+
+        /// <summary>
+        /// Finds a parent of a given item on the visual tree.
+        /// </summary>
+        /// <typeparam name="T">The type of the queried item.</typeparam>
+        /// <param name="child">A direct or indirect child of the
+        /// queried item.</param>
+        /// <returns>The first parent item that matches the submitted
+        /// type parameter. If not matching item can be found, a null
+        /// reference is being returned.</returns>
+        public static T TryFindParent<T>(this DependencyObject child)
+            where T : DependencyObject
+        {
+            //get parent item
+            DependencyObject parentObject = GetParentObject(child);
+
+            //we've reached the end of the tree
+            if (parentObject == null) return null;
+
+            //check if the parent matches the type we're looking for
+            T parent = parentObject as T;
+            if (parent != null)
+            {
+                return parent;
+            }
+
+            //use recursion to proceed with next level
+            return TryFindParent<T>(parentObject);
+        }
+
+        /// <summary>
+        /// This method is an alternative to WPF's
+        /// <see cref="VisualTreeHelper.GetParent"/> method, which also
+        /// supports content elements. Keep in mind that for content element,
+        /// this method falls back to the logical tree of the element!
+        /// </summary>
+        /// <param name="child">The item to be processed.</param>
+        /// <returns>The submitted item's parent, if available. Otherwise
+        /// null.</returns>
+        public static DependencyObject GetParentObject(this DependencyObject child)
+        {
+            if (child == null) return null;
+
+            //handle content elements separately
+            ContentElement contentElement = child as ContentElement;
+            if (contentElement != null)
+            {
+                DependencyObject parent = ContentOperations.GetParent(contentElement);
+                if (parent != null) return parent;
+
+                FrameworkContentElement fce = contentElement as FrameworkContentElement;
+                return fce != null ? fce.Parent : null;
+            }
+
+            //also try searching for parent in framework elements (such as DockPanel, etc)
+            FrameworkElement frameworkElement = child as FrameworkElement;
+            if (frameworkElement != null)
+            {
+                DependencyObject parent = frameworkElement.Parent;
+                if (parent != null) return parent;
+            }
+
+            //if it's not a ContentElement/FrameworkElement, rely on VisualTreeHelper
+            return VisualTreeHelper.GetParent(child);
+        }
+
+        #endregion
+
+        #region find children
+
+        /// <summary>
+        /// Analyzes both visual and logical tree in order to find all elements of a given
+        /// type that are descendants of the <paramref name="source"/> item.
+        /// </summary>
+        /// <typeparam name="T">The type of the queried items.</typeparam>
+        /// <param name="source">The root element that marks the source of the search. If the
+        /// source is already of the requested type, it will not be included in the result.</param>
+        /// <returns>All descendants of <paramref name="source"/> that match the requested type.</returns>
+        public static IEnumerable<T> FindChildren<T>(this DependencyObject source) where T : DependencyObject
+        {
+            if (source != null)
+            {
+                var childs = GetChildObjects(source);
+                foreach (DependencyObject child in childs)
+                {
+                    //analyze if children match the requested type
+                    if (child is T)
+                    {
+                        yield return (T)child;
+                    }
+
+                    //recurse tree
+                    foreach (T descendant in FindChildren<T>(child))
+                    {
+                        yield return descendant;
+                    }
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// This method is an alternative to WPF's
+        /// <see cref="VisualTreeHelper.GetChild"/> method, which also
+        /// supports content elements. Keep in mind that for content elements,
+        /// this method falls back to the logical tree of the element.
+        /// </summary>
+        /// <param name="parent">The item to be processed.</param>
+        /// <returns>The submitted item's child elements, if available.</returns>
+        public static IEnumerable<DependencyObject> GetChildObjects(this DependencyObject parent)
+        {
+            if (parent == null) yield break;
+
+            if (parent is ContentElement || parent is FrameworkElement)
+            {
+                //use the logical tree for content / framework elements
+                foreach (object obj in LogicalTreeHelper.GetChildren(parent))
+                {
+                    var depObj = obj as DependencyObject;
+                    if (depObj != null) yield return (DependencyObject)obj;
+                }
+            }
+            else
+            {
+                //use the visual tree per default
+                int count = VisualTreeHelper.GetChildrenCount(parent);
+                for (int i = 0; i < count; i++)
+                {
+                    yield return VisualTreeHelper.GetChild(parent, i);
+                }
+            }
+        }
+
+        #endregion
+
+        #region find from point
+
+        /// <summary>
+        /// Tries to locate a given item within the visual tree,
+        /// starting with the dependency object at a given position. 
+        /// </summary>
+        /// <typeparam name="T">The type of the element to be found
+        /// on the visual tree of the element at the given location.</typeparam>
+        /// <param name="reference">The main element which is used to perform
+        /// hit testing.</param>
+        /// <param name="point">The position to be evaluated on the origin.</param>
+        public static T TryFindFromPoint<T>(UIElement reference, Point point)
+            where T : DependencyObject
+        {
+            DependencyObject element = reference.InputHitTest(point) as DependencyObject;
+
+            if (element == null) return null;
+
+            if (element is T) return (T)element;
+            
+            return TryFindParent<T>(element);
+        }
+
+        #endregion
+    }
+}

+ 91 - 0
MediaBrowser.UI/Controls/WindowCommands.xaml

@@ -0,0 +1,91 @@
+<UserControl x:Class="MediaBrowser.UI.Controls.WindowCommands"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             mc:Ignorable="d" 
+             d:DesignHeight="300" d:DesignWidth="300">
+
+    <UserControl.Resources>
+
+        <Style TargetType="StackPanel" x:Key="WindowCommandsPanel">
+            <Setter Property="Orientation" Value="Horizontal"/>
+            <Setter Property="HorizontalAlignment" Value="Right"/>
+        </Style>
+
+        <Style TargetType="Button" x:Key="WebdingsButton" BasedOn="{StaticResource ImageButton}">
+            <Setter Property="Margin" Value="0 0 15 0"/>
+            <Setter Property="KeyboardNavigation.IsTabStop" Value="false"/>
+        </Style>
+
+        <Style TargetType="TextBlock" x:Key="WebdingsTextBlock">
+            <Setter Property="FontFamily" Value="Webdings"/>
+            <Setter Property="FontSize" Value="14"/>
+            <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
+        </Style>
+
+        <Style TargetType="Button" x:Key="MinimizeApplicationButton" BasedOn="{StaticResource WebdingsButton}">
+            <Setter Property="ToolTip" Value="Minimize"/>
+            <Setter Property="Template">
+                <Setter.Value>
+                    <ControlTemplate>
+                        <TextBlock Style="{StaticResource WebdingsTextBlock}">0</TextBlock>
+                    </ControlTemplate>
+                </Setter.Value>
+            </Setter>
+        </Style>
+
+        <Style TargetType="Button" x:Key="MaximizeApplicationButton" BasedOn="{StaticResource WebdingsButton}">
+            <Setter Property="ToolTip" Value="Maximize"/>
+            <Setter Property="Template">
+                <Setter.Value>
+                    <ControlTemplate>
+                        <TextBlock Style="{StaticResource WebdingsTextBlock}">1</TextBlock>
+                    </ControlTemplate>
+                </Setter.Value>
+            </Setter>
+            <Style.Triggers>
+                <DataTrigger Binding="{Binding Path=WindowState, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="Maximized">
+                    <Setter Property="Visibility" Value="Collapsed" />
+                </DataTrigger>
+            </Style.Triggers>
+        </Style>
+
+        <Style TargetType="Button" x:Key="UndoMaximizeApplicationButton" BasedOn="{StaticResource WebdingsButton}">
+            <Setter Property="Visibility" Value="Collapsed"/>
+            <Setter Property="ToolTip" Value="Restore"/>
+            <Setter Property="Template">
+                <Setter.Value>
+                    <ControlTemplate>
+                        <TextBlock Style="{StaticResource WebdingsTextBlock}">2</TextBlock>
+                    </ControlTemplate>
+                </Setter.Value>
+            </Setter>
+            <Style.Triggers>
+                <DataTrigger Binding="{Binding Path=WindowState, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="Maximized">
+                    <Setter Property="Visibility" Value="Visible" />
+                </DataTrigger>
+            </Style.Triggers>
+        </Style>
+
+        <Style TargetType="Button" x:Key="CloseApplicationButton" BasedOn="{StaticResource WebdingsButton}">
+            <Setter Property="ToolTip" Value="Close"/>
+            <Setter Property="Template">
+                <Setter.Value>
+                    <ControlTemplate>
+                        <TextBlock Style="{StaticResource WebdingsTextBlock}">r</TextBlock>
+                    </ControlTemplate>
+                </Setter.Value>
+            </Setter>
+        </Style>
+
+    </UserControl.Resources>
+
+    <StackPanel Style="{StaticResource WindowCommandsPanel}">
+        <Button x:Name="MinimizeApplicationButton" Style="{StaticResource MinimizeApplicationButton}"></Button>
+        <Button x:Name="MaximizeApplicationButton" Style="{StaticResource MaximizeApplicationButton}"></Button>
+        <Button x:Name="UndoMaximizeApplicationButton" Style="{StaticResource UndoMaximizeApplicationButton}"></Button>
+        <Button x:Name="CloseApplicationButton" Style="{StaticResource CloseApplicationButton}"></Button>
+    </StackPanel>
+
+</UserControl>

+ 50 - 0
MediaBrowser.UI/Controls/WindowCommands.xaml.cs

@@ -0,0 +1,50 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace MediaBrowser.UI.Controls
+{
+    /// <summary>
+    /// Interaction logic for WindowCommands.xaml
+    /// </summary>
+    public partial class WindowCommands : UserControl
+    {
+        public Window ParentWindow
+        {
+            get { return TreeHelper.TryFindParent<Window>(this); }
+        }
+
+        public WindowCommands()
+        {
+            InitializeComponent();
+            Loaded += WindowCommandsLoaded;
+        }
+
+        void WindowCommandsLoaded(object sender, RoutedEventArgs e)
+        {
+            CloseApplicationButton.Click += CloseApplicationButtonClick;
+            MinimizeApplicationButton.Click += MinimizeApplicationButtonClick;
+            MaximizeApplicationButton.Click += MaximizeApplicationButtonClick;
+            UndoMaximizeApplicationButton.Click += UndoMaximizeApplicationButtonClick;
+        }
+
+        void UndoMaximizeApplicationButtonClick(object sender, RoutedEventArgs e)
+        {
+            ParentWindow.WindowState = WindowState.Normal;
+        }
+
+        void MaximizeApplicationButtonClick(object sender, RoutedEventArgs e)
+        {
+            ParentWindow.WindowState = WindowState.Maximized;
+        }
+
+        void MinimizeApplicationButtonClick(object sender, RoutedEventArgs e)
+        {
+            ParentWindow.WindowState = WindowState.Minimized;
+        }
+
+        void CloseApplicationButtonClick(object sender, RoutedEventArgs e)
+        {
+            ParentWindow.Close();
+        }
+    }
+}

+ 26 - 0
MediaBrowser.UI/Converters/CurrentUserVisibilityConverter.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace MediaBrowser.UI.Converters
+{
+    public class CurrentUserVisibilityConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (App.Instance.ServerConfiguration == null || !App.Instance.ServerConfiguration.EnableUserProfiles)
+            {
+                return Visibility.Collapsed;
+            }
+
+            return value == null ? Visibility.Collapsed : Visibility.Visible;
+        }
+
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 34 - 0
MediaBrowser.UI/Converters/DateTimeToStringConverter.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace MediaBrowser.UI.Converters
+{
+    public class DateTimeToStringConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var date = (DateTime)value;
+
+            string format = parameter as string;
+
+            if (string.IsNullOrEmpty(format))
+            {
+                return date.ToString();
+            }
+            
+            if (format.Equals("shorttime", StringComparison.OrdinalIgnoreCase))
+            {
+                return date.ToShortTimeString();
+            }
+
+            return date.ToString(format);
+        }
+
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 86 - 0
MediaBrowser.UI/Converters/LastSeenTextConverter.cs

@@ -0,0 +1,86 @@
+using MediaBrowser.Model.DTO;
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace MediaBrowser.UI.Converters
+{
+    public class LastSeenTextConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var user = value as DtoUser;
+
+            if (user != null)
+            {
+                if (user.LastActivityDate.HasValue)
+                {
+                    DateTime date = user.LastActivityDate.Value.ToLocalTime();
+
+                    return "Last seen " + GetRelativeTimeText(date);
+                }
+            }
+
+            return null;
+        }
+
+        private static string GetRelativeTimeText(DateTime date)
+        {
+            TimeSpan ts = DateTime.Now - date;
+
+            const int second = 1;
+            const int minute = 60 * second;
+            const int hour = 60 * minute;
+            const int day = 24 * hour;
+            const int month = 30 * day;
+
+            int delta = System.Convert.ToInt32(ts.TotalSeconds);
+
+            if (delta < 0)
+            {
+                return "not yet";
+            }
+            if (delta < 1 * minute)
+            {
+                return ts.Seconds == 1 ? "one second ago" : ts.Seconds + " seconds ago";
+            }
+            if (delta < 2 * minute)
+            {
+                return "a minute ago";
+            }
+            if (delta < 45 * minute)
+            {
+                return ts.Minutes + " minutes ago";
+            }
+            if (delta < 90 * minute)
+            {
+                return "an hour ago";
+            }
+            if (delta < 24 * hour)
+            {
+                return ts.Hours + " hours ago";
+            }
+            if (delta < 48 * hour)
+            {
+                return "yesterday";
+            }
+            if (delta < 30 * day)
+            {
+                return ts.Days + " days ago";
+            }
+            if (delta < 12 * month)
+            {
+                int months = System.Convert.ToInt32(Math.Floor((double)ts.Days / 30));
+                return months <= 1 ? "one month ago" : months + " months ago";
+            }
+
+            int years = System.Convert.ToInt32(Math.Floor((double)ts.Days / 365));
+            return years <= 1 ? "one year ago" : years + " years ago";
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 60 - 0
MediaBrowser.UI/Converters/UserImageConverter.cs

@@ -0,0 +1,60 @@
+using MediaBrowser.Model.DTO;
+using MediaBrowser.UI.Controller;
+using System;
+using System.Globalization;
+using System.Net.Cache;
+using System.Windows.Data;
+using System.Windows.Media.Imaging;
+
+namespace MediaBrowser.UI.Converters
+{
+    public class UserImageConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var user = value as DtoUser;
+
+            if (user != null && user.HasImage)
+            {
+                var config = parameter as string;
+
+                int? maxWidth = null;
+                int? maxHeight = null;
+                int? width = null;
+                int? height = null;
+
+                if (!string.IsNullOrEmpty(config))
+                {
+                    var vals = config.Split(',');
+
+                    width = GetSize(vals[0]);
+                    height = GetSize(vals[1]);
+                    maxWidth = GetSize(vals[2]);
+                    maxHeight = GetSize(vals[3]);
+                }
+
+                var uri = UIKernel.Instance.ApiClient.GetUserImageUrl(user.Id, width, height, maxWidth, maxHeight, 100);
+
+                return new BitmapImage(new Uri(uri), new RequestCachePolicy(RequestCacheLevel.Revalidate));
+            }
+
+            return null;
+        }
+
+        private int? GetSize(string val)
+        {
+            if (string.IsNullOrEmpty(val) || val == "0")
+            {
+                return null;
+            }
+
+            return int.Parse(val);
+        }
+
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 31 - 0
MediaBrowser.UI/Converters/WeatherTemperatureConverter.cs

@@ -0,0 +1,31 @@
+using MediaBrowser.Model.Weather;
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace MediaBrowser.UI.Converters
+{
+    public class WeatherTemperatureConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var weather = value as WeatherInfo;
+
+            if (weather != null)
+            {
+                if (App.Instance.ServerConfiguration.WeatherUnit == WeatherUnits.Celsius)
+                {
+                    return weather.CurrentWeather.TemperatureCelsius + "°C";
+                }
+
+                return weather.CurrentWeather.TemperatureFahrenheit + "°F";
+            }
+            return null;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 20 - 0
MediaBrowser.UI/Converters/WeatherVisibilityConverter.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace MediaBrowser.UI.Converters
+{
+    public class WeatherVisibilityConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value == null ? Visibility.Collapsed : Visibility.Visible;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 50 - 0
MediaBrowser.UI/MainWindow.xaml

@@ -0,0 +1,50 @@
+<Window x:Class="MediaBrowser.UI.MainWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:controls="clr-namespace:MediaBrowser.UI.Controls"
+        Title="media browser" 
+        Style="{StaticResource MainWindow}"
+        WindowStartupLocation="CenterScreen" 
+        AllowsTransparency="True" 
+        WindowStyle="None"
+        ResizeMode="CanResizeWithGrip"
+        KeyboardNavigation.DirectionalNavigation="Contained">
+
+    <!--The window itself is a tabstop, and it can't be disabled. So this is a workaround.-->
+    <Grid>
+
+        <Grid x:Name="BackdropGrid" Style="{StaticResource BackdropGrid}">
+        </Grid>
+
+        <!--This allows the user to drag the window.-->
+        <Grid x:Name="DragBar" Style="{StaticResource DragBar}"></Grid>
+
+        <!--This allows the user to drag the window.-->
+        <controls:WindowCommands x:Name="WindowCommands" Style="{StaticResource WindowCommands}"></controls:WindowCommands>
+
+        <!--Themes will supply this template to outline the window structure.-->
+        <ContentControl x:Name="PageContent" Template="{StaticResource PageContentTemplate}"></ContentControl>
+
+        <Grid x:Name="NavBarGrid" Style="{StaticResource NavBarGrid}">
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="auto"></ColumnDefinition>
+                <ColumnDefinition Width="*"></ColumnDefinition>
+                <ColumnDefinition Width="auto"></ColumnDefinition>
+            </Grid.ColumnDefinitions>
+
+            <StackPanel Style="{StaticResource NavBarGridLeftPanel}">
+                <Button x:Name="BackButton" Style="{StaticResource BackButton}"></Button>
+                <Button x:Name="ForwardButton" Style="{StaticResource ForwardButton}"></Button>
+            </StackPanel>
+            <StackPanel Style="{StaticResource NavBarGridCenterPanel}">
+                <Button x:Name="MuteButton" Style="{StaticResource MuteButton}"></Button>
+                <Button x:Name="VolumeDownButton" Style="{StaticResource VolumeDownButton}"></Button>
+                <Button x:Name="VolumeUpButton" Style="{StaticResource VolumeUpButton}"></Button>
+            </StackPanel>
+            <StackPanel Style="{StaticResource NavBarGridRightPanel}">
+                <Button x:Name="SettingsButton" Style="{StaticResource SettingsButton}"></Button>
+                <Button x:Name="ExitButton" Style="{StaticResource ExitButton}"></Button>
+            </StackPanel>
+        </Grid>
+    </Grid>
+</Window>

+ 368 - 0
MediaBrowser.UI/MainWindow.xaml.cs

@@ -0,0 +1,368 @@
+using MediaBrowser.Model.DTO;
+using MediaBrowser.UI.Controller;
+using MediaBrowser.UI.Controls;
+using System;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media.Animation;
+using System.Windows.Media.Imaging;
+
+namespace MediaBrowser.UI
+{
+    /// <summary>
+    /// Interaction logic for MainWindow.xaml
+    /// </summary>
+    public partial class MainWindow : Window, INotifyPropertyChanged
+    {
+        private Timer MouseIdleTimer { get; set; }
+        private Timer BackdropTimer { get; set; }
+        private Image BackdropImage { get; set; }
+        private string[] CurrentBackdrops { get; set; }
+        private int CurrentBackdropIndex { get; set; }
+
+        public MainWindow()
+        {
+            InitializeComponent();
+
+            BackButton.Click += BtnApplicationBackClick;
+            ExitButton.Click += ExitButtonClick;
+            ForwardButton.Click += ForwardButtonClick;
+            DragBar.MouseDown += DragableGridMouseDown;
+            Loaded += MainWindowLoaded;
+        }
+
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        public void OnPropertyChanged(String info)
+        {
+            if (PropertyChanged != null)
+            {
+                PropertyChanged(this, new PropertyChangedEventArgs(info));
+            }
+        }
+
+        private bool _isMouseIdle = true;
+        public bool IsMouseIdle
+        {
+            get { return _isMouseIdle; }
+            set
+            {
+                _isMouseIdle = value;
+                OnPropertyChanged("IsMouseIdle");
+            }
+        }
+
+        void MainWindowLoaded(object sender, RoutedEventArgs e)
+        {
+            DataContext = App.Instance;
+
+            if (App.Instance.ServerConfiguration == null)
+            {
+                App.Instance.PropertyChanged += ApplicationPropertyChanged;
+            }
+            else
+            {
+                LoadInitialPage();
+            }
+        }
+
+        void ForwardButtonClick(object sender, RoutedEventArgs e)
+        {
+            NavigateForward();
+        }
+
+        void ExitButtonClick(object sender, RoutedEventArgs e)
+        {
+            Close();
+        }
+
+        void ApplicationPropertyChanged(object sender, PropertyChangedEventArgs e)
+        {
+            if (e.PropertyName.Equals("ServerConfiguration"))
+            {
+                App.Instance.PropertyChanged -= ApplicationPropertyChanged;
+                LoadInitialPage();
+            }
+        }
+
+        private async void LoadInitialPage()
+        {
+            await App.Instance.LogoutUser().ConfigureAwait(false);
+        }
+
+        private void DragableGridMouseDown(object sender, MouseButtonEventArgs e)
+        {
+            if (e.ClickCount == 2)
+            {
+                WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
+            }
+            else if (e.LeftButton == MouseButtonState.Pressed)
+            {
+                DragMove();
+            }
+        }
+
+        void BtnApplicationBackClick(object sender, RoutedEventArgs e)
+        {
+            NavigateBack();
+        }
+
+        private Frame PageFrame
+        {
+            get
+            {
+                // Finding the grid that is generated by the ControlTemplate of the Button
+                return TreeHelper.FindChild<Frame>(PageContent, "PageFrame");
+            }
+        }
+
+        public void Navigate(Uri uri)
+        {
+            PageFrame.Navigate(uri);
+        }
+
+        /// <summary>
+        /// Sets the backdrop based on an ApiBaseItemWrapper
+        /// </summary>
+        public void SetBackdrops(DtoBaseItem item)
+        {
+            SetBackdrops(UIKernel.Instance.ApiClient.GetBackdropImageUrls(item, null, null, 1920, 1080));
+        }
+
+        /// <summary>
+        /// Sets the backdrop based on a list of image files
+        /// </summary>
+        public async void SetBackdrops(string[] backdrops)
+        {
+            // Don't reload the same backdrops
+            if (CurrentBackdrops != null && backdrops.SequenceEqual(CurrentBackdrops))
+            {
+                return;
+            }
+
+            if (BackdropTimer != null)
+            {
+                BackdropTimer.Dispose();
+            }
+
+            BackdropGrid.Children.Clear();
+
+            if (backdrops.Length == 0)
+            {
+                CurrentBackdrops = null;
+                return;
+            }
+
+            CurrentBackdropIndex = GetFirstBackdropIndex();
+
+            Image image = await App.Instance.GetImage(backdrops.ElementAt(CurrentBackdropIndex));
+            image.SetResourceReference(Image.StyleProperty, "BackdropImage");
+
+            BackdropGrid.Children.Add(image);
+
+            CurrentBackdrops = backdrops;
+            BackdropImage = image;
+
+            const int backdropRotationTime = 7000;
+
+            if (backdrops.Count() > 1)
+            {
+                BackdropTimer = new Timer(BackdropTimerCallback, null, backdropRotationTime, backdropRotationTime);
+            }
+        }
+
+        public void ClearBackdrops()
+        {
+            if (BackdropTimer != null)
+            {
+                BackdropTimer.Dispose();
+            }
+
+            BackdropGrid.Children.Clear();
+
+            CurrentBackdrops = null;
+        }
+
+        private void BackdropTimerCallback(object stateInfo)
+        {
+            // Need to do this on the UI thread
+            Application.Current.Dispatcher.InvokeAsync(() =>
+            {
+                var animFadeOut = new Storyboard();
+                animFadeOut.Completed += AnimFadeOutCompleted;
+
+                var fadeOut = new DoubleAnimation();
+                fadeOut.From = 1.0;
+                fadeOut.To = 0.5;
+                fadeOut.Duration = new Duration(TimeSpan.FromSeconds(1));
+
+                animFadeOut.Children.Add(fadeOut);
+                Storyboard.SetTarget(fadeOut, BackdropImage);
+                Storyboard.SetTargetProperty(fadeOut, new PropertyPath(Image.OpacityProperty));
+
+                animFadeOut.Begin(this);
+            });
+        }
+
+        async void AnimFadeOutCompleted(object sender, System.EventArgs e)
+        {
+            if (CurrentBackdrops == null)
+            {
+                return;
+            }
+
+            int backdropIndex = GetNextBackdropIndex();
+
+            BitmapImage image = await App.Instance.GetBitmapImage(CurrentBackdrops[backdropIndex]);
+            CurrentBackdropIndex = backdropIndex;
+
+            // Need to do this on the UI thread
+            BackdropImage.Source = image;
+            Storyboard imageFadeIn = new Storyboard();
+
+            DoubleAnimation fadeIn = new DoubleAnimation();
+
+            fadeIn.From = 0.25;
+            fadeIn.To = 1.0;
+            fadeIn.Duration = new Duration(TimeSpan.FromSeconds(1));
+
+            imageFadeIn.Children.Add(fadeIn);
+            Storyboard.SetTarget(fadeIn, BackdropImage);
+            Storyboard.SetTargetProperty(fadeIn, new PropertyPath(Image.OpacityProperty));
+            imageFadeIn.Begin(this);
+        }
+
+        private int GetFirstBackdropIndex()
+        {
+            return 0;
+        }
+
+        private int GetNextBackdropIndex()
+        {
+            if (CurrentBackdropIndex < CurrentBackdrops.Length - 1)
+            {
+                return CurrentBackdropIndex + 1;
+            }
+
+            return 0;
+        }
+
+        public void NavigateBack()
+        {
+            if (PageFrame.NavigationService.CanGoBack)
+            {
+                PageFrame.NavigationService.GoBack();
+            }
+        }
+
+        public void NavigateForward()
+        {
+            if (PageFrame.NavigationService.CanGoForward)
+            {
+                PageFrame.NavigationService.GoForward();
+            }
+        }
+
+        /// <summary>
+        /// Shows the control bar then starts a timer to hide it
+        /// </summary>
+        private void StartMouseIdleTimer()
+        {
+            IsMouseIdle = false;
+
+            const int duration = 10000;
+
+            // Start the timer if it's null, otherwise reset it
+            if (MouseIdleTimer == null)
+            {
+                MouseIdleTimer = new Timer(MouseIdleTimerCallback, null, duration, Timeout.Infinite);
+            }
+            else
+            {
+                MouseIdleTimer.Change(duration, Timeout.Infinite);
+            }
+        }
+
+        /// <summary>
+        /// This is the Timer callback method to hide the control bar
+        /// </summary>
+        private void MouseIdleTimerCallback(object stateInfo)
+        {
+            IsMouseIdle = true;
+            
+            if (MouseIdleTimer != null)
+            {
+                MouseIdleTimer.Dispose();
+                MouseIdleTimer = null;
+            }
+        }
+
+        /// <summary>
+        /// Handles OnMouseMove to show the control box
+        /// </summary>
+        protected override void OnMouseMove(MouseEventArgs e)
+        {
+            base.OnMouseMove(e);
+
+            StartMouseIdleTimer();
+        }
+
+        /// <summary>
+        /// Handles OnKeyUp to provide keyboard based navigation
+        /// </summary>
+        protected override void OnKeyUp(KeyEventArgs e)
+        {
+            base.OnKeyUp(e);
+
+            if (IsBackPress(e))
+            {
+                NavigateBack();
+            }
+
+            else if (IsForwardPress(e))
+            {
+                NavigateForward();
+            }
+        }
+
+        /// <summary>
+        /// Determines if a keypress should be treated as a backward press
+        /// </summary>
+        private bool IsBackPress(KeyEventArgs e)
+        {
+            if (e.Key == Key.BrowserBack || e.Key == Key.Back)
+            {
+                return true;
+            }
+
+            if (e.SystemKey == Key.Left && e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Alt))
+            {
+                return true;
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Determines if a keypress should be treated as a forward press
+        /// </summary>
+        private bool IsForwardPress(KeyEventArgs e)
+        {
+            if (e.Key == Key.BrowserForward)
+            {
+                return true;
+            }
+
+            if (e.SystemKey == Key.RightAlt && e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Alt))
+            {
+                return true;
+            }
+
+            return false;
+        }
+    }
+}

+ 196 - 0
MediaBrowser.UI/MediaBrowser.UI.csproj

@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{B5ECE1FB-618E-420B-9A99-8E972D76920A}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>MediaBrowser.UI</RootNamespace>
+    <AssemblyName>MediaBrowser.UI</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup>
+    <StartupObject>MediaBrowser.UI.App</StartupObject>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationIcon>Resources\Images\Icon.ico</ApplicationIcon>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.ComponentModel.Composition" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Net.Http.WebRequest" />
+    <Reference Include="System.Web" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="System.Xaml">
+      <RequiredTargetFramework>4.0</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="WindowsBase" />
+    <Reference Include="PresentationCore" />
+    <Reference Include="PresentationFramework" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Configuration\UIApplicationConfiguration.cs" />
+    <Compile Include="Configuration\UIApplicationPaths.cs" />
+    <Compile Include="Controller\PluginUpdater.cs" />
+    <Compile Include="Controls\EnhancedScrollViewer.cs" />
+    <Compile Include="Controls\ExtendedImage.cs" />
+    <Compile Include="Controls\TreeHelper.cs" />
+    <Compile Include="Controls\WindowCommands.xaml.cs">
+      <DependentUpon>WindowCommands.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Converters\CurrentUserVisibilityConverter.cs" />
+    <Compile Include="Converters\DateTimeToStringConverter.cs" />
+    <Compile Include="Converters\LastSeenTextConverter.cs" />
+    <Compile Include="Converters\WeatherTemperatureConverter.cs" />
+    <Compile Include="Converters\WeatherVisibilityConverter.cs" />
+    <Compile Include="Controller\UIKernel.cs" />
+    <Compile Include="Pages\BaseLoginPage.cs" />
+    <Page Include="App.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="Controls\WindowCommands.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="MainWindow.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Compile Include="App.xaml.cs">
+      <DependentUpon>App.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Pages\BasePage.cs" />
+    <Compile Include="Converters\UserImageConverter.cs" />
+    <Compile Include="MainWindow.xaml.cs">
+      <DependentUpon>MainWindow.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+    <Page Include="Resources\AppResources.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Resources\MainWindowResources.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Resources\NavBarResources.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Themes\Generic.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DesignTime>True</DesignTime>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+    </EmbeddedResource>
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <AppDesigner Include="Properties\" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config">
+      <SubType>Designer</SubType>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\MediaBrowserServer\MediaBrowser.ApiInteraction\MediaBrowser.ApiInteraction.csproj">
+      <Project>{921c0f64-fda7-4e9f-9e73-0cb0eedb2422}</Project>
+      <Name>MediaBrowser.ApiInteraction</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\MediaBrowserServer\MediaBrowser.Common\MediaBrowser.Common.csproj">
+      <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+      <Name>MediaBrowser.Common</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\MediaBrowserServer\MediaBrowser.Model\MediaBrowser.Model.csproj">
+      <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+      <Name>MediaBrowser.Model</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resources\Images\BackButton.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resources\Images\ForwardButton.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resources\Images\ExitButton.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resources\Images\SettingsButton.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resources\Images\VolumeUpButton.png" />
+    <Resource Include="Resources\Images\VolumeDownButton.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resources\Images\MuteButton.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resources\Images\Icon.ico" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resources\Images\mblogoblack.png" />
+    <Resource Include="Resources\Images\mblogowhite.png" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

+ 33 - 0
MediaBrowser.UI/Pages/BaseLoginPage.cs

@@ -0,0 +1,33 @@
+using MediaBrowser.Model.DTO;
+using MediaBrowser.UI.Controller;
+using System;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.UI.Pages
+{
+    public class BaseLoginPage : BasePage
+    {
+        private DtoUser[] _users;
+        public DtoUser[] Users
+        {
+            get { return _users; }
+
+            set
+            {
+                _users = value;
+                OnPropertyChanged("Users");
+            }
+        }
+
+        protected override async Task LoadData()
+        {
+            Users = await UIKernel.Instance.ApiClient.GetAllUsersAsync().ConfigureAwait(false);
+        }
+
+        protected void UserClicked(DtoUser user)
+        {
+            App.Instance.CurrentUser = user;
+            //App.Instance.Navigate(new Uri("/Pages/HomePage.xaml", UriKind.Relative));
+        }
+    }
+}

+ 79 - 0
MediaBrowser.UI/Pages/BasePage.cs

@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Threading.Tasks;
+using System.Web;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace MediaBrowser.UI.Pages
+{
+    public abstract class BasePage : Page, INotifyPropertyChanged
+    {
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        public void OnPropertyChanged(String info)
+        {
+            if (PropertyChanged != null)
+            {
+                PropertyChanged(this, new PropertyChangedEventArgs(info));
+            }
+        }
+
+        protected Uri Uri
+        {
+            get
+            {
+                return NavigationService.CurrentSource;
+            }
+        }
+
+        protected MainWindow MainWindow
+        {
+            get
+            {
+                return App.Instance.MainWindow as MainWindow;
+            }
+        }
+
+        private NameValueCollection _queryString;
+        protected NameValueCollection QueryString
+        {
+            get
+            {
+                if (_queryString == null)
+                {
+                    string url = Uri.ToString();
+
+                    int index = url.IndexOf('?');
+
+                    if (index == -1)
+                    {
+                        _queryString = new NameValueCollection();
+                    }
+                    else
+                    {
+                        _queryString = HttpUtility.ParseQueryString(url.Substring(index + 1));
+                    }
+                }
+
+                return _queryString;
+            }
+        }
+
+        protected BasePage()
+            : base()
+        {
+            Loaded += BasePageLoaded;
+        }
+
+        async void BasePageLoaded(object sender, RoutedEventArgs e)
+        {
+            await LoadData();
+
+            DataContext = this;
+        }
+
+        protected abstract Task LoadData();
+    }
+}

+ 53 - 0
MediaBrowser.UI/Properties/AssemblyInfo.cs

@@ -0,0 +1,53 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("MediaBrowser.UI")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MediaBrowser.UI")]
+[assembly: AssemblyCopyright("Copyright ©  2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+//In order to begin building localizable applications, set 
+//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
+//inside a <PropertyGroup>.  For example, if you are using US english
+//in your source files, set the <UICulture> to en-US.  Then uncomment
+//the NeutralResourceLanguage attribute below.  Update the "en-US" in
+//the line below to match the UICulture setting in the project file.
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+    //(used if a resource is not found in the page, 
+    // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+    //(used if a resource is not found in the page, 
+    // app, or any theme specific resource dictionaries)
+)]
+
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 71 - 0
MediaBrowser.UI/Properties/Resources.Designer.cs

@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.17626
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace MediaBrowser.UI.Properties
+{
+
+
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources
+    {
+
+        private static global::System.Resources.ResourceManager resourceMan;
+
+        private static global::System.Globalization.CultureInfo resourceCulture;
+
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources()
+        {
+        }
+
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager
+        {
+            get
+            {
+                if ((resourceMan == null))
+                {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MediaBrowser.UI.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture
+        {
+            get
+            {
+                return resourceCulture;
+            }
+            set
+            {
+                resourceCulture = value;
+            }
+        }
+    }
+}

+ 117 - 0
MediaBrowser.UI/Properties/Resources.resx

@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 30 - 0
MediaBrowser.UI/Properties/Settings.Designer.cs

@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.17626
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace MediaBrowser.UI.Properties
+{
+
+
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+    {
+
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+        public static Settings Default
+        {
+            get
+            {
+                return defaultInstance;
+            }
+        }
+    }
+}

+ 7 - 0
MediaBrowser.UI/Properties/Settings.settings

@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

+ 122 - 0
MediaBrowser.UI/Resources/AppResources.xaml

@@ -0,0 +1,122 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:System="clr-namespace:System;assembly=mscorlib"
+                    xmlns:converters="clr-namespace:MediaBrowser.UI.Converters">
+
+    <!--Themes should override these as needed-->
+    <FontFamily x:Key="DefaultFontFamily">Segoe UI, Lucida Sans Unicode, Verdana</FontFamily>
+    <FontWeight x:Key="DefaultFontWeight">Thin</FontWeight>
+    <Brush x:Key="DefaultForeground">Black</Brush>
+    <System:Double x:Key="DefaultFontSize">36</System:Double>
+    <System:Double x:Key="Heading1FontSize">84</System:Double>
+    <System:Double x:Key="Heading2FontSize">60</System:Double>
+
+    <!--Define all the standard converters here in one place-->
+    <converters:DateTimeToStringConverter x:Key="DateTimeToStringConverter"></converters:DateTimeToStringConverter>
+    <converters:UserImageConverter x:Key="UserImageConverter"></converters:UserImageConverter>
+    <converters:WeatherTemperatureConverter x:Key="WeatherTemperatureConverter"></converters:WeatherTemperatureConverter>
+    <converters:LastSeenTextConverter x:Key="LastSeenTextConverter"></converters:LastSeenTextConverter>
+    <converters:WeatherVisibilityConverter x:Key="WeatherVisibilityConverter"></converters:WeatherVisibilityConverter>
+    <converters:CurrentUserVisibilityConverter x:Key="CurrentUserVisibilityConverter"></converters:CurrentUserVisibilityConverter>
+
+    <!--Default Frame style. -->
+    <Style TargetType="Frame">
+        <Setter Property="NavigationUIVisibility" Value="Hidden"/>
+        <Setter Property="KeyboardNavigation.IsTabStop" Value="false"/>
+    </Style>
+
+    <!--Default Frame style. -->
+    <Style TargetType="ContentControl">
+        <Setter Property="KeyboardNavigation.IsTabStop" Value="false"/>
+    </Style>
+
+    <!--Default Window style. -->
+    <Style TargetType="Window" x:Key="BaseWindow">
+        <Setter Property="FontSize" Value="{StaticResource DefaultFontSize}"/>
+        <Setter Property="FontFamily" Value="{StaticResource DefaultFontFamily}"/>
+        <Setter Property="FontWeight" Value="{StaticResource DefaultFontWeight}"/>
+        <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
+        <Setter Property="BorderBrush" Value="#cccccc"/>
+        <Setter Property="BorderThickness" Value="1"/>
+    </Style>
+
+    <!--Default TextBlock style. -->
+    <Style TargetType="TextBlock">
+        <Setter Property="FontSize" Value="{StaticResource DefaultFontSize}"/>
+        <Setter Property="FontFamily" Value="{StaticResource DefaultFontFamily}"/>
+        <Setter Property="FontWeight" Value="{StaticResource DefaultFontWeight}"/>
+        <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
+        <Setter Property="TextWrapping" Value="Wrap" />
+    </Style>
+
+    <!--Default Label style. -->
+    <Style TargetType="Label">
+        <Setter Property="FontSize" Value="{StaticResource DefaultFontSize}"/>
+        <Setter Property="FontFamily" Value="{StaticResource DefaultFontFamily}"/>
+        <Setter Property="FontWeight" Value="{StaticResource DefaultFontWeight}"/>
+        <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
+    </Style>
+
+    <!--Default Button style. -->
+    <Style TargetType="Button">
+        <Setter Property="FontSize" Value="{StaticResource DefaultFontSize}"/>
+        <Setter Property="FontFamily" Value="{StaticResource DefaultFontFamily}"/>
+        <Setter Property="FontWeight" Value="{StaticResource DefaultFontWeight}"/>
+        <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
+    </Style>
+
+    <!--Default style for buttons that have images. -->
+    <Style TargetType="Button" x:Key="ImageButton" BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}">
+        <Setter Property="Margin" Value="0"/>
+        <Setter Property="Padding" Value="0"/>
+        <Setter Property="BorderThickness" Value="0"/>
+        <Setter Property="Cursor" Value="Hand"/>
+        <Style.Triggers>
+            <Trigger Property="IsMouseOver" Value="True">
+                <Setter Property="Opacity" Value=".5" />
+            </Trigger>
+        </Style.Triggers>
+    </Style>
+
+    <!--Default ListViewItem style. -->
+    <Style x:Key="BaseListViewItemStyle" TargetType="{x:Type ListViewItem}">
+
+        <Setter Property="Padding" Value="0" />
+        <Setter Property="Margin" Value="0" />
+        <Setter Property="Cursor" Value="Hand"/>
+
+        <Style.Triggers>
+            <Trigger Property="IsKeyboardFocusWithin" Value="True">
+                <Setter Property="IsSelected" Value="True" />
+            </Trigger>
+        </Style.Triggers>
+    </Style>
+
+    <!--Themes should override this -->
+    <Style x:Key="ListViewItemStyle" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource BaseListViewItemStyle}">
+    </Style>
+
+    <!--Default ListView style. -->
+    <Style TargetType="ListView" x:Key="BaseListViewStyle">
+        <Setter Property="BorderThickness" Value="0"/>
+        <Setter Property="Background" Value="Transparent"/>
+        <Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
+        <Setter Property="KeyboardNavigation.DirectionalNavigation" Value="Continue"/>
+        <Setter Property="VirtualizingPanel.IsVirtualizing" Value="True"/>
+        <Setter Property="IsSynchronizedWithCurrentItem" Value="True"/>
+    </Style>
+
+    <!--Themes should override this -->
+    <Style x:Key="ListViewStyle" TargetType="{x:Type ListView}" BasedOn="{StaticResource BaseListViewStyle}">
+    </Style>
+
+    <!--MB Logo, black text. -->
+    <Style TargetType="Image" x:Key="MBLogoImageBlack">
+        <Setter Property="Source" Value="Images/mblogoblack.png"/>
+    </Style>
+
+    <!--MB Logo, white text. -->
+    <Style TargetType="Image" x:Key="MBLogoImageWhite">
+        <Setter Property="Source" Value="Images/mblogowhite.png"/>
+    </Style>
+</ResourceDictionary>

BIN
MediaBrowser.UI/Resources/Images/BackButton.png


BIN
MediaBrowser.UI/Resources/Images/ExitButton.png


BIN
MediaBrowser.UI/Resources/Images/ForwardButton.png


BIN
MediaBrowser.UI/Resources/Images/Icon.ico


BIN
MediaBrowser.UI/Resources/Images/MuteButton.png


BIN
MediaBrowser.UI/Resources/Images/SettingsButton.png


BIN
MediaBrowser.UI/Resources/Images/VolumeDownButton.png


BIN
MediaBrowser.UI/Resources/Images/VolumeUpButton.png


BIN
MediaBrowser.UI/Resources/Images/mblogoblack.png


BIN
MediaBrowser.UI/Resources/Images/mblogowhite.png


+ 43 - 0
MediaBrowser.UI/Resources/MainWindowResources.xaml

@@ -0,0 +1,43 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+
+    <!--Themes should override this to style the window-->
+    <Style TargetType="Window" x:Key="MainWindow" BasedOn="{StaticResource BaseWindow}">
+    </Style>
+
+    <!--Themes may want to override this to adjust the backdrop container style-->
+    <Style TargetType="Grid" x:Key="BackdropGrid">
+        <Setter Property="Background" Value="Transparent"/>
+        <Setter Property="Opacity" Value=".15"/>
+    </Style>
+
+    <!--Themes may want to override this to adjust the backdrop image style-->
+    <Style TargetType="Image" x:Key="BackdropImage">
+        <Setter Property="Stretch" Value="UniformToFill"/>
+    </Style>
+
+    <Style TargetType="Grid" x:Key="DragBar">
+        <Setter Property="Background" Value="Transparent"/>
+        <Setter Property="Height" Value="50"/>
+        <Setter Property="VerticalAlignment" Value="Top"/>
+        <Setter Property="Panel.ZIndex" Value="1"/>
+    </Style>
+    <Style TargetType="UserControl" x:Key="WindowCommands">
+        <Setter Property="Margin" Value="0 10 0 0"/>
+        <Setter Property="HorizontalAlignment" Value="Right"/>
+        <Setter Property="VerticalAlignment" Value="Top"/>
+        <Setter Property="Panel.ZIndex" Value="2"/>
+        <Setter Property="Visibility" Value="Collapsed" />
+        <Style.Triggers>
+            <DataTrigger Binding="{Binding Path=MainWindow.IsMouseIdle}" Value="false">
+                <Setter Property="Visibility" Value="Visible" />
+            </DataTrigger>
+        </Style.Triggers>
+    </Style>
+
+    <!--Themes should override this to layout window content-->
+    <ControlTemplate x:Key="PageContentTemplate">
+        <Frame x:Name="PageFrame"></Frame>
+    </ControlTemplate>
+
+</ResourceDictionary>

+ 122 - 0
MediaBrowser.UI/Resources/NavBarResources.xaml

@@ -0,0 +1,122 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+    <Style TargetType="Button" x:Key="NavBarButton" BasedOn="{StaticResource ImageButton}">
+        <Setter Property="Margin" Value="10 0 10 0"/>
+        <Setter Property="VerticalAlignment" Value="Center"/>
+        <Setter Property="HorizontalAlignment" Value="Left"/>
+        <Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
+    </Style>
+
+    <Style TargetType="Button" x:Key="BackButton" BasedOn="{StaticResource NavBarButton}">
+        <Setter Property="ToolTip" Value="Back"/>
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate>
+                    <Image x:Name="img" Source="..\Resources\Images\BackButton.png" Stretch="None" />
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
+    <Style TargetType="Button" x:Key="ForwardButton" BasedOn="{StaticResource NavBarButton}">
+        <Setter Property="ToolTip" Value="Forward"/>
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate>
+                    <Image x:Name="img" Source="..\Resources\Images\ForwardButton.png" Stretch="None" />
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
+    <Style TargetType="Button" x:Key="ExitButton" BasedOn="{StaticResource NavBarButton}">
+        <Setter Property="ToolTip" Value="Exit"/>
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate>
+                    <Image x:Name="img" Source="..\Resources\Images\ExitButton.png" Stretch="None" />
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
+    <Style TargetType="Button" x:Key="SettingsButton" BasedOn="{StaticResource NavBarButton}">
+        <Setter Property="ToolTip" Value="Settings"/>
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate>
+                    <Image x:Name="img" Source="..\Resources\Images\SettingsButton.png" Stretch="None" />
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
+    <Style TargetType="Button" x:Key="VolumeUpButton" BasedOn="{StaticResource NavBarButton}">
+        <Setter Property="ToolTip" Value="Increase Volume"/>
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate>
+                    <Image x:Name="img" Source="..\Resources\Images\VolumeUpButton.png" Stretch="None" />
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
+    <Style TargetType="Button" x:Key="VolumeDownButton" BasedOn="{StaticResource NavBarButton}">
+        <Setter Property="ToolTip" Value="Decrease Volume"/>
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate>
+                    <Image x:Name="img" Source="..\Resources\Images\VolumeDownButton.png" Stretch="None" />
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
+    <Style TargetType="Button" x:Key="MuteButton" BasedOn="{StaticResource NavBarButton}">
+        <Setter Property="ToolTip" Value="Mute"/>
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate>
+                    <Image x:Name="img" Source="..\Resources\Images\MuteButton.png" Stretch="None" />
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
+    <Style TargetType="Grid" x:Key="NavBarGrid">
+        <Setter Property="VerticalAlignment" Value="Bottom"/>
+        <Setter Property="Background">
+            <Setter.Value>
+                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1" Opacity=".8">
+                    <GradientStop Color="#333333" Offset="0.0"/>
+                    <GradientStop Color="Black" Offset="1.0"/>
+                </LinearGradientBrush>
+            </Setter.Value>
+        </Setter>
+        <Setter Property="Visibility" Value="Collapsed" />
+        <Style.Triggers>
+            <DataTrigger Binding="{Binding Path=MainWindow.IsMouseIdle}" Value="false">
+                <Setter Property="Visibility" Value="Visible" />
+            </DataTrigger>
+        </Style.Triggers>
+    </Style>
+    <Style TargetType="StackPanel" x:Key="NavBarGridLeftPanel">
+        <Setter Property="Grid.Column" Value="0"/>
+        <Setter Property="HorizontalAlignment" Value="Left"/>
+        <Setter Property="Orientation" Value="Horizontal"/>
+        <Setter Property="Margin" Value="15 20 15 20"/>
+    </Style>
+    <Style TargetType="StackPanel" x:Key="NavBarGridCenterPanel">
+        <Setter Property="Grid.Column" Value="1"/>
+        <Setter Property="HorizontalAlignment" Value="Center"/>
+        <Setter Property="Orientation" Value="Horizontal"/>
+        <Setter Property="Margin" Value="15 20 15 20"/>
+    </Style>
+    <Style TargetType="StackPanel" x:Key="NavBarGridRightPanel">
+        <Setter Property="Grid.Column" Value="2"/>
+        <Setter Property="HorizontalAlignment" Value="Right"/>
+        <Setter Property="Orientation" Value="Horizontal"/>
+        <Setter Property="Margin" Value="15 20 15 20"/>
+    </Style>
+</ResourceDictionary>

+ 32 - 0
MediaBrowser.UI/Themes/Generic.xaml

@@ -0,0 +1,32 @@
+<ResourceDictionary
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="clr-namespace:MediaBrowser.UI.Controls">
+
+    <Style TargetType="{x:Type local:ExtendedImage}">
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="{x:Type local:ExtendedImage}">
+                    <Border Background="{TemplateBinding Background}">
+                        <Image x:Name="theImage">
+                            <Image.Style>
+                                <Style TargetType="{x:Type Image}">
+                                    <Setter Property="Source"
+                                            Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PlaceHolderSource}" />
+                                    <Setter Property="Stretch"
+                                            Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path= Stretch}" />
+                                    <Style.Triggers>
+                                        <DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=HasImage}" Value="True">
+                                            <Setter Property="Source"
+                                                    Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Source}" />
+                                        </DataTrigger>
+                                    </Style.Triggers>
+                                </Style>
+                            </Image.Style>
+                        </Image>
+                    </Border>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+</ResourceDictionary>

+ 1 - 1
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -58,7 +58,7 @@
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
-    <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\" /y</PostBuildEvent>
+    <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData-Server\Plugins\" /y</PostBuildEvent>
   </PropertyGroup>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.