Geeks With Blogs
Bob Taco Industries Blogging Division

If you’ve played around with the WP7 emulator, you may have gotten into the settings screen and may have noticed that there are these things called “themes”. The emulator’s default is the “dark” background with the “blue” accent. However the phone can also have a “light” background, and supports the following accents: “magenta”, “purple”, “teal”, “lime”, “brown”, “pink”, “orange”, “blue”, “red”, “green”, and an eleventh optional color that the manufacturer of the phone can set.

Most people naturally assume that these apply to Silverlight apps, with their StaticResource xaml incantations, but they needn’t be only for Silverlighters. You could easily theme your menus, for example, to match the theme the user currently has set. And you needn’t stop there either. Depending on the game, you could theme the entire experience. I will go ahead right now and say that you should give the user the option of changing the theme in an options menu if you do this. And that you should absolutely test out every single combination to make sure it looks good with all the current configurations.

So how do we accomplish this task? We simply have to query the ResourceDictionary that is injected into all applications by the phone’s operating system at runtime. You ought to start by reading the Theme Overview for Windows Phone on MSDN. This will give you an idea of how themes work and how you should use them. From there you should glance at and bookmark Theme Resources for Windows Phone. Since that’s where this comes from:

/* PhoneTheme.cs
 * Copyright 2010 Michael B. McLaughlin
 * Licensed under the Microsoft Permissive License: http://geekswithblogs.net/mikebmcl/archive/2010/08/02/microsoft-permissive-license.aspx
 */
 
using System.Windows;
using Microsoft.Xna.Framework;
using SolidColorBrush = System.Windows.Media.SolidColorBrush;
 
namespace MyGame
{
    public class PhoneTheme
    {
        static PhoneTheme _current;
        static readonly object currentLock = new object();
 
        public static PhoneTheme Current
        {
            get
            {
                if (_current == null)
                {
                    lock (currentLock)
                    {
                        if (_current == null)
                        {
                            _current = new PhoneTheme();
                        }
                    }
                }
                return _current;
            }
        }
 
        /// <summary>
        /// Default background for pages and other controls
        /// </summary>
        public Color PhoneBackgroundColor { get; private set; }
 
        /// <summary>
        /// Default foreground and border color
        /// </summary>
        public Color PhoneForegroundColor { get; private set; }
 
        /// <summary>
        /// Inactive foreground and border color
        /// </summary>
        public Color PhoneInactiveColor { get; private set; }
 
        /// <summary>
        /// Disabled foreground and border
        /// </summary>
        public Color PhoneDisabledColor { get; private set; }
 
        /// <summary>
        /// Subtle foreground and border
        /// </summary>
        public Color PhoneSubtleColor { get; private set; }
 
        /// <summary>
        /// Foreground color to single-out items of interest
        /// </summary>
        public Color PhoneAccentColor { get; private set; }
 
        /// <summary>
        /// Background for contrasting elements
        /// </summary>
        public Color PhoneContrastBackgroundColor { get; private set; }
 
        /// <summary>
        /// Foreground for contrasting elements
        /// </summary>
        public Color PhoneContrastForegroundColor { get; private set; }
 
        /// <summary>
        /// Color for the TextBox control
        /// </summary>
        public Color PhoneTextBoxColor { get; private set; }
 
        /// <summary>
        /// Color used for the border and background of the TextBox, CheckBox, PasswordBox, and RadioButton controls
        /// </summary>
        public Color PhoneBorderColor { get; private set; }
 
        /// <summary>
        /// Partially transparent color used to provide contrast against image backgrounds
        /// </summary>
        public Color PhoneSemitransparentColor { get; private set; }
 
        /// <summary>
        /// Color of the system tray and application bar
        /// </summary>
        public Color PhoneChromeColor { get; private set; }
 
        /// <summary>
        /// Should be used as a go-between background for text drawn using <see cref="PhoneForegroundColor"/> over an image to ensure readability. (Non-standard.)
        /// </summary>
        public Color ImageOverlay { get; private set; }
 
        /// <summary>
        /// Should be used as a go-between background for text drawn using <see cref="PhoneContrastForegroundColor"/> over an image to ensure readability. (Non-standard.)
        /// </summary>
        public Color ContrastImageOverlay { get; private set; }
        
        /// <summary>
        /// The thickness of a rectangular border around an item such as a button.
        /// </summary>
        public Thickness PhoneBorderThickness { get; private set; }
 
        private PhoneTheme()
        {
            SolidColorBrush phoneAccentBrush = Application.Current.Resources.Contains("PhoneAccentBrush") == true ? Application.Current.Resources["PhoneAccentBrush"] as SolidColorBrush : null;
            SolidColorBrush phoneForegroundBrush = Application.Current.Resources.Contains("PhoneForegroundBrush") == true ? Application.Current.Resources["PhoneForegroundBrush"] as SolidColorBrush : null;
            SolidColorBrush phoneBackgroundBrush = Application.Current.Resources.Contains("PhoneBackgroundBrush") == true ? Application.Current.Resources["PhoneBackgroundBrush"] as SolidColorBrush : null;
            SolidColorBrush phoneContrastBackgroundBrush = Application.Current.Resources.Contains("PhoneContrastBackgroundBrush") == true ? Application.Current.Resources["PhoneContrastBackgroundBrush"] as SolidColorBrush : null;
            SolidColorBrush phoneContrastForegroundBrush = Application.Current.Resources.Contains("PhoneContrastForegroundBrush") == true ? Application.Current.Resources["PhoneContrastForegroundBrush"] as SolidColorBrush : null;
            SolidColorBrush phoneInactiveBrush = Application.Current.Resources.Contains("PhoneInactiveBrush") == true ? Application.Current.Resources["PhoneInactiveBrush"] as SolidColorBrush : null;
            SolidColorBrush phoneDisabledBrush = Application.Current.Resources.Contains("PhoneDisabledBrush") == true ? Application.Current.Resources["PhoneDisabledBrush"] as SolidColorBrush : null;
            SolidColorBrush phoneSubtleBrush = Application.Current.Resources.Contains("PhoneSubtleBrush") == true ? Application.Current.Resources["PhoneSubtleBrush"] as SolidColorBrush : null;
            SolidColorBrush phoneTextBoxBrush = Application.Current.Resources.Contains("PhoneTextBoxBrush") == true ? Application.Current.Resources["PhoneTextBoxBrush"] as SolidColorBrush : null;
            SolidColorBrush phoneBorderBrush = Application.Current.Resources.Contains("PhoneBorderBrush") == true ? Application.Current.Resources["PhoneBorderBrush"] as SolidColorBrush : null;
            SolidColorBrush phoneSemitransparentBrush = Application.Current.Resources.Contains("PhoneSemitransparentBrush") == true ? Application.Current.Resources["PhoneSemitransparentBrush"] as SolidColorBrush : null;
            SolidColorBrush phoneChromeBrush = Application.Current.Resources.Contains("PhoneChromeBrush") == true ? Application.Current.Resources["PhoneChromeBrush"] as SolidColorBrush : null;
            Thickness? phoneBorderThickness = Application.Current.Resources.Contains("PhoneBorderThickness") == true ? (Thickness?)Application.Current.Resources["PhoneBorderThickness"] : null;
 
            if (phoneAccentBrush == null ||
                phoneBackgroundBrush == null ||
                phoneBorderBrush == null ||
                phoneChromeBrush == null ||
                phoneContrastBackgroundBrush == null ||
                phoneContrastForegroundBrush == null ||
                phoneDisabledBrush == null ||
                phoneForegroundBrush == null ||
                phoneInactiveBrush == null ||
                phoneSemitransparentBrush == null ||
                phoneSubtleBrush == null ||
                phoneTextBoxBrush == null ||
                !phoneBorderThickness.HasValue)
            {
                // Assign defaults for Dark theme with Blue accent if pulling anything from the injected ResourceDictionary failed
                PhoneAccentColor = new Color(27, 161, 226, 255);
                PhoneBackgroundColor = new Color(0, 0, 0, 255);
                PhoneBorderColor = new Color(255, 255, 255, 191);
                PhoneChromeColor = new Color(31, 31, 31, 255);
                PhoneContrastBackgroundColor = new Color(255, 255, 255, 255);
                PhoneContrastForegroundColor = new Color(0, 0, 0, 255);
                PhoneDisabledColor = new Color(102, 102, 102, 102);
                PhoneForegroundColor = new Color(255, 255, 255, 255);
                PhoneInactiveColor = new Color(51, 51, 51, 51);
                PhoneSemitransparentColor = new Color(0, 0, 0, 170);
                PhoneSubtleColor = new Color(153, 153, 153, 153);
                PhoneTextBoxColor = new Color(204, 204, 204, 204);
                PhoneBorderThickness = new Thickness(3.0);
                ImageOverlay = new Color(0.0f, 0.0f, 0.0f, 0.2f);
                ContrastImageOverlay = new Color(0.2f, 0.2f, 0.2f, 0.2f);
            }
            else
            {
                PhoneAccentColor = phoneAccentBrush.Color.ToXnaColor();
                PhoneBackgroundColor = phoneBackgroundBrush.Color.ToXnaColor();
                PhoneBorderColor = phoneBorderBrush.Color.ToXnaColor();
                PhoneChromeColor = phoneChromeBrush.Color.ToXnaColor();
                PhoneContrastBackgroundColor = phoneContrastBackgroundBrush.Color.ToXnaColor();
                PhoneContrastForegroundColor = phoneContrastForegroundBrush.Color.ToXnaColor();
                PhoneDisabledColor = phoneDisabledBrush.Color.ToXnaColor();
                PhoneForegroundColor = phoneForegroundBrush.Color.ToXnaColor();
                PhoneInactiveColor = phoneInactiveBrush.Color.ToXnaColor();
                PhoneSemitransparentColor = phoneSemitransparentBrush.Color.ToXnaColor();
                PhoneSubtleColor = phoneSubtleBrush.Color.ToXnaColor();
                PhoneTextBoxColor = phoneTextBoxBrush.Color.ToXnaColor();
                PhoneBorderThickness = phoneBorderThickness.Value;
 
                // Check for if it's the dark background or else the light background and set the overlay colors accordingly
                if (PhoneBackgroundColor.R < 170)
                {
                    ImageOverlay = new Color(0.0f, 0.0f, 0.0f, 0.2f);
                    ContrastImageOverlay = new Color(0.2f, 0.2f, 0.2f, 0.2f);
                }
                else
                {
                    ImageOverlay = new Color(0.2f, 0.2f, 0.2f, 0.2f);
                    ContrastImageOverlay = new Color(0.0f, 0.0f, 0.0f, 0.2f);
                }
            }
        }
    }
 
    /// <summary>
    /// A class for holding extension methods.
    /// </summary>
    public static class Extensions
    {
        /// <summary>
        /// Converts a System.Windows.Media.Color into a pre-multiplied alpha Microsoft.Xna.Framework.Color suitable
        /// for drawing with.
        /// </summary>
        /// <param name="color">The System.Windows.Media.Color to be converted.</param>
        /// <returns>The XNA 4.0 pre-multiplied alpha version of <paramref name="color"/></returns>
        public static Color ToXnaColor(this System.Windows.Media.Color color)
        {
            Color result;
            result = new Color(color.R * (byte)(color.A / 255.0f),
                color.G * (byte)(color.A / 255.0f),
                color.B * (byte)(color.A / 255.0f),
                color.A);
            return result;
        }
    }
}

 

Between the comments and the link to Theme Resources for Windows Phone, you should be able to figure the above code out easily. I would strongly recommend, at a minimum, adding an options menu setting to switch between a default scheme and one based off of the current theme (just because someone’s using a light theme with a pink accent does not necessarily mean they want to play your game in light pink mode :) ). Also, note that while the font sizes listed in the Theme Resources link are in pt sizes, those pt sizes are for a screen with a dpi of approximately 256. You will need to adjust up if you wish to match these closely (the simplest way would be to create a simple Silverlight App with some text set using one of the styles and then play around with the SpriteFont size in your XNA game until you’ve achieved size parity).

I was hoping to turn this into a full starting kit with the SoundAndMusic sample integrated into the options menu of the Phone GameStateManagement sample but I just haven’t had enough free time to finish it (it’s ~2/3 there) and now that the tools are RTM, I wanted to make y’all aware of this ASAP so you could consider theming your XNA games. Remember that you can be creative with the theme – a dark theme could mean a night time setting while a light theme could mean a daytime setting. Or you could check the actual time and integrate that in addition to the theme.

That’s all for this time. Go grab the updated Windows Phone 7 Application Certification Requirements (PDF). There are some important changes in there from the last version and I’m sure some of you never even got around to reading that version. Woe unto the developer who fails to read this important document soon – surely you will spend many days redesigning your program to meet the certification requirements that would be easy to meet if you designed with them in mind from the outset.

Posted on Thursday, September 16, 2010 6:26 PM xna , wp7 , tutorial | Back to top


Comments on this post: Using WP7 Themes In Your XNA Game

Comments are closed.
Comments have been closed on this topic.
Copyright © Michael B. McLaughlin | Powered by: GeeksWithBlogs.net