#region File Description
//-----------------------------------------------------------------------------
// MenuComponent.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
#endregion
namespace XnaGraphicsDemo
{
///
/// Base class for all the different screens used in the demo. This provides
/// a simple touch menu which can display a list of options, and detect when
/// a menu item is clicked.
///
class MenuComponent : DrawableGameComponent
{
// Properties.
new public DemoGame Game { get { return (DemoGame)base.Game; } }
public SpriteBatch SpriteBatch { get { return Game.SpriteBatch; } }
public SpriteFont Font { get { return Game.Font; } }
public SpriteFont BigFont { get { return Game.BigFont; } }
protected List Entries { get; private set; }
protected Vector2 LastTouchPoint { get; private set; }
// Fields.
bool touchDown = true;
int touchSelection = -1;
static TimeSpan attractTimer;
static MouseState lastInputState = new MouseState(-1, -1, -1, 0, 0, 0, 0, 0);
///
/// Constructor.
///
public MenuComponent(DemoGame game)
: base(game)
{
Entries = new List();
}
///
/// Initializes the menu, computing the screen position of each entry.
///
public override void Initialize()
{
Vector2 pos = new Vector2(MenuEntry.Border, 800 - MenuEntry.Border - Entries.Count * MenuEntry.Height);
foreach (MenuEntry entry in Entries)
{
entry.Position = pos;
pos.Y += MenuEntry.Height;
}
base.Initialize();
}
///
/// Resets the menu, whenever we transition to or from a different screen.
///
virtual public void Reset()
{
if (touchSelection >= 0)
Entries[touchSelection].IsFocused = false;
touchDown = true;
touchSelection = -1;
}
///
/// Updates the menu state, processing user input.
///
public override void Update(GameTime gameTime)
{
// We read input using the mouse API, which will report the first touch point
// when run on the phone, but also works on Windows using a regular mouse.
MouseState input = Game.IsActive ? Mouse.GetState() : new MouseState();
// Scale input if we are running in an unusual screen resolution.
int touchX = input.X * 480 / Game.Graphics.PreferredBackBufferWidth;
int touchY = input.Y * 800 / Game.Graphics.PreferredBackBufferHeight;
// Process the input.
if (input.LeftButton == ButtonState.Pressed)
{
HandleTouchDown(touchX, touchY);
}
else
{
HandleTouchUp();
}
HandleAttractMode(gameTime, input);
}
///
/// Handles input while a touch is occurring.
///
void HandleTouchDown(int touchX, int touchY)
{
// Hit test the touch position against the list of menu items.
int currentEntry = -1;
for (int i = 0; i < Entries.Count; i++)
{
if ((touchY >= Entries[i].Position.Y) && (touchY < Entries[i].Position.Y + MenuEntry.Height))
{
currentEntry = i;
break;
}
}
if (touchDown)
{
// Are we already processing a touch?
if (touchSelection >= 0)
{
if (currentEntry == touchSelection || Entries[touchSelection].IsDraggable)
{
// Pass drag input to the currently selected item.
Entries[touchSelection].IsFocused = true;
Entries[touchSelection].OnDragged(touchX - LastTouchPoint.X);
}
else
{
// If the drag moves off the selected item, unfocus it.
Entries[touchSelection].IsFocused = false;
}
}
else
{
// If the touch was not on any menu item, process a backgroun drag.
OnDrag(new Vector2(touchX, touchY) - LastTouchPoint);
}
}
else
{
// We are not currently processing a touch.
touchDown = true;
touchSelection = currentEntry;
if (touchSelection >= 0)
{
// Focus the menu item that has just been touched.
Entries[touchSelection].IsFocused = true;
}
}
// Store the most recent touch location.
LastTouchPoint = new Vector2(touchX, touchY);
}
///
/// Handles input when the touch is released.
///
void HandleTouchUp()
{
if (touchDown && touchSelection >= 0 && Entries[touchSelection].IsFocused)
{
// If we were touching a menu item, and just released it, process the click action.
Entries[touchSelection].IsFocused = false;
Entries[touchSelection].OnClicked();
}
touchDown = false;
touchSelection = -1;
}
///
/// If no input is provided, we go into an automatic attract mode, which cycles
/// through the various options. This was great for leaving the demo unattended
/// at the kiosk during the MIX10 conference!
///
void HandleAttractMode(GameTime gameTime, MouseState input)
{
if (input != lastInputState || touchDown)
{
// If input has changed, reset the timer.
attractTimer = TimeSpan.FromSeconds(-15);
lastInputState = input;
}
else
{
// If no input occurs, increment the timer.
attractTimer += gameTime.ElapsedGameTime;
if (attractTimer > AttractDelay)
{
// Timeout! Run the attract action.
attractTimer = TimeSpan.Zero;
OnAttract();
}
}
}
///
/// Allows subclasses to customize their attract behavior. The default is
/// to simulate a click on the last menu entry, which is usually "back".
///
protected virtual void OnAttract()
{
Entries[Entries.Count - 1].OnClicked();
}
///
/// Allows subclasses to customize how long they wait before cycling through the attract sequence.
///
protected virtual TimeSpan AttractDelay { get { return TimeSpan.FromSeconds(10); } }
///
/// Draws the list of menu entries.
///
public override void Draw(GameTime gameTime)
{
SpriteBatch.Begin(0, null, null, null, null, null, Game.ScaleMatrix);
foreach (MenuEntry entry in Entries)
{
entry.Draw(SpriteBatch, Font, Game.BlankTexture);
}
SpriteBatch.End();
}
///
/// Draws the menu title.
///
protected void DrawTitle(string title, Color? backgroundColor, Color titleColor)
{
if (backgroundColor.HasValue)
GraphicsDevice.Clear(backgroundColor.Value);
SpriteBatch.Begin(0, null, null, null, null, null, Game.ScaleMatrix);
SpriteBatch.DrawString(BigFont, title, new Vector2(480, 24), titleColor, MathHelper.PiOver2, Vector2.Zero, 1, 0, 0);
SpriteBatch.End();
}
///
/// Handles a drag on the background of the screen.
///
protected virtual void OnDrag(Vector2 delta)
{
}
}
}