/*
* GUIButton.cs
* Authors: Bradley Blankenship, Mike DeMauro
* Copyright (c) 2007-2008 Cornell University
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
using Microsoft.Xna.Framework.Graphics;
using Util;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using System;
namespace tAC_Engine.GUI
{
///
/// Represents a button for use by the GUI.
///
public class GUIButton : GUIComponent
{
#region Button State
///
/// The enumeration of the button's state
///
public enum ButtonState
{
///
/// State for when a button is up
///
UP,
///
/// State for when a button is down
///
DOWN,
///
/// State for when a button has the mouse over it
///
OVER
}
#endregion
#region Fields
///
/// The state of the button
///
protected ButtonState mState;
///
/// The name of the texture for button up state
///
protected string mButtonUpName = @"Textures\BasicButtonUp";
///
/// The name of the texture for button down state
///
protected string mButtonDownName = @"Textures\BasicButtonDown";
///
/// The name of the texture for mouseover state
///
protected string mButtonOverName = @"Textures\BasicButtonDown";
///
/// The textures representing being up and down
///
protected Texture2D mButtonUp;
///
/// The textures representing being up and down
///
protected Texture2D mButtonDown;
///
/// The texture representing being over
///
protected Texture2D mButtonOver;
///
/// The current texture of the button
///
protected Texture2D mCurrentTexture;
///
/// The font for the button
///
protected SpriteFont mSpriteFont;
///
/// The text for the button
///
protected string mText;
///
/// The position for the text (calculated to be cenetered based on the text of the button)
///
protected Vector2 mTextPosition = Vector2.Zero;
///
/// A custom offset for the text (exposed externally)
///
protected Vector2 mTextCustomOffset = Vector2.Zero;
///
/// Whether the button has changed state or not
///
protected bool mStateChanged = false;
///
/// The text color
///
protected Color mTextColor = Color.Black;
///
/// Size of the source border in texels
///
protected int mBorderSizeSource = 0;
///
/// Size of the destination border in pixels
///
protected int mBorderSizeDest = 0;
///
/// Icon texture
///
protected Texture2D mIcon;
///
/// The position for the icon
///
protected Rectangle mIconDestination = Rectangle.Empty;
#endregion
#region Texture Rectangles
///
/// Destination rectangle for the center of the button
///
protected Rectangle centerDest = Rectangle.Empty;
///
/// Source rectangle for the center of the button
///
protected Rectangle centerSource = Rectangle.Empty;
///
/// Destination rectangle for the top left of the button
///
protected Rectangle topLeftDest = Rectangle.Empty;
///
/// Source rectangle for the top left of the button
///
protected Rectangle topLeftSource = Rectangle.Empty;
///
/// Destination rectangle for the top of the button
///
protected Rectangle topDest = Rectangle.Empty;
///
/// Source rectangle for the top of the button
///
protected Rectangle topSource = Rectangle.Empty;
///
/// Destination rectangle for the top right of the button
///
protected Rectangle topRightDest = Rectangle.Empty;
///
/// Source rectangle for the top right of the button
///
protected Rectangle topRightSource = Rectangle.Empty;
///
/// Destination rectangle for the right of the button
///
protected Rectangle rightDest = Rectangle.Empty;
///
/// Source rectangle for the right of the button
///
protected Rectangle rightSource = Rectangle.Empty;
///
/// Destination rectangle for the bottom right of the button
///
protected Rectangle bottomRightDest = Rectangle.Empty;
///
/// Source rectangle for the bottom right of the button
///
protected Rectangle bottomRightSource = Rectangle.Empty;
///
/// Destination rectangle for the bottom of the button
///
protected Rectangle bottomDest = Rectangle.Empty;
///
/// Source rectangle for the bottom of the button
///
protected Rectangle bottomSource = Rectangle.Empty;
///
/// Destination rectangle for the bottom left of the button
///
protected Rectangle bottomLeftDest = Rectangle.Empty;
///
/// Source rectangle for the bottom left of the button
///
protected Rectangle bottomLeftSource = Rectangle.Empty;
///
/// Destination rectangle for the left of the button
///
protected Rectangle leftDest = Rectangle.Empty;
///
/// Source rectangle for the left of the button
///
protected Rectangle leftSource = Rectangle.Empty;
#endregion
#region Properties
///
/// Allows access to the size of the component.
///
public override Vector2 Size
{
get { return base.Size; }
set
{
base.Size = value;
updatePositions();
}
}
///
/// Allows access to the position of the component.
///
public override Vector2 Position
{
get { return base.Position; }
set
{
base.Position = value;
updatePositions();
}
}
///
/// Specifies an offset for the text label
///
public virtual Vector2 TextOffset
{
get { return mTextCustomOffset; }
set { mTextCustomOffset = value; }
}
///
/// Allows access to the button state internally.
///
public ButtonState State
{
get { return mState; }
set
{
// Only if the state is new
if (value != mState)
{
HasChanged = true;
mState = value;
if (mState == ButtonState.UP)
{
mCurrentTexture = mButtonUp;
}
else if(mState == ButtonState.DOWN)
{
mCurrentTexture = mButtonDown;
}
else if (mState == ButtonState.OVER)
{
mCurrentTexture = mButtonOver;
}
}
}
}
///
/// The name of the texture to load for the button up state
///
public string ButtonUpName
{
get { return mButtonUpName; }
set { mButtonUpName = value; }
}
///
/// The name of the texture to load for the button down state
///
public string ButtonDownName
{
get { return mButtonDownName; }
set { mButtonDownName = value; }
}
///
/// The name of the texture to load for the button over state
///
public string ButtonOverName
{
get { return mButtonOverName; }
set { mButtonOverName = value; }
}
///
/// Allows access to the button's up texture internally.
///
public Texture2D ButtonUp
{
get { return mButtonUp; }
set
{
mButtonUp = value;
if (State == ButtonState.UP)
{
// Set the current texture
mCurrentTexture = mButtonUp;
}
}
}
///
/// Allows access to the button's down texture internally.
///
public Texture2D ButtonDown
{
get { return mButtonDown; }
set
{
mButtonDown = value;
if (State == ButtonState.DOWN)
{
// Set the current texture
mCurrentTexture = mButtonDown;
}
}
}
///
/// Allows access to the button's over texture internally.
///
public Texture2D ButtonOver
{
get { return mButtonOver; }
set
{
mButtonOver = value;
if (State == ButtonState.OVER)
{
// Set the current texture
mCurrentTexture = mButtonOver;
}
}
}
///
/// Allow saccess to the button's current display texture.
///
protected Texture2D CurrentTexture
{
get { return mCurrentTexture; }
set { mCurrentTexture = value; updatePositions(); }
}
///
/// Allows access to the font of the button internally.
///
public SpriteFont Font
{
get { return mSpriteFont; }
set
{
mSpriteFont = value;
UpdateTextPosition();
}
}
///
/// Allows internal access for manipulating the button text.
///
public string Text
{
get { return mText; }
set
{
mText = value;
UpdateTextPosition();
}
}
///
/// Allows access to the value to determine if that state's changed locally.
///
protected bool HasChanged
{
get { return mStateChanged; }
set { mStateChanged = value; }
}
///
/// Allows access to the text color.
///
public Color TextColor
{
get { return mTextColor; }
set { mTextColor = value; }
}
///
/// Size of the source border in texels
///
public int BorderSizeSource
{
get { return mBorderSizeSource; }
set { mBorderSizeSource = value; }
}
///
/// Size of the destination border in pixels
///
public int BorderSizeDest
{
get { return mBorderSizeDest; }
set { mBorderSizeDest = value; }
}
///
/// Name of the Icon texture to be loaded
///
public Texture2D Icon
{
get { return mIcon; }
set { mIcon = value; }
}
#endregion
#region Initialization
///
/// Initializes the component. Override this method to load any non-graphics
/// resources and query for any required services.
///
public override void Initialize()
{
// Set the button's state up
State = ButtonState.UP;
// Now re-initialize to store the base data
Resize();
}
#endregion
#region Management
///
/// Loads all the necessary informatiion for content dependent objects
///
public override void LoadContent()
{
// If the parent screen is not null
if (mParentScreen != null)
{
// If we do not have a texture for our up state
if (mButtonUp == null)
{
// Load the basic texture for an up button
mButtonUp = mParentScreen.Content.Load(mButtonUpName);
}
mCurrentTexture = mButtonUp;
// If we do not have a texture for our down state
if (mButtonDown == null)
{
// Load the basic texture for a down button
mButtonDown = mParentScreen.Content.Load(mButtonDownName);
}
// If we do not have a texture for over state
if (mButtonOver == null)
{
mButtonOver = mParentScreen.Content.Load(mButtonOverName);
}
updatePositions();
}
base.LoadContent();
}
#endregion
#region Rescale
///
/// Adjusts the Size of this component based on the size of the texture
///
///
public void Rescale(float uniformScale)
{
Rescale(uniformScale, uniformScale);
}
///
/// Adjusts the Size of this component based on the size of the texture
///
///
///
public void Rescale(float xScale, float yScale)
{
System.Diagnostics.Debug.Assert(mButtonUp != null, "Must have a texture before calling Rescale");
Size = new Vector2(mButtonUp.Width * xScale, mButtonUp.Height * yScale);
}
#endregion
#region Event Handling
///
/// Event for when a button is pressed
///
public event EventHandler ButtonPressed;
///
/// Event for when a button is released
///
public event EventHandler ButtonReleased;
///
/// Removes all handlers from the ButtonReleased event.
///
public void RemoveButtonReleasedHandlers()
{
ButtonReleased = null;
}
///
/// Handle the given event.
///
/// The event to handle.
public override bool HandleMouseEvent(MouseInputEvent e)
{
// The return value
bool retVal = false;
// If we're visible
if (Visible && Enabled)
{
// Check to see if it's within our bounds
if (IsInside(e.Location))
{
// Switch on the given codes
switch (e.EventCode)
{
// Evaluate for a left mouse button press
case (int)GUIEventCodes.LEFT_MOUSE_CLICKED:
State = ButtonState.DOWN;
// Invoke Event if we're enabled
if (ButtonPressed != null)
ButtonPressed.Invoke(this, EventArgs.Empty);
break;
// Evaluate for a left mouse button release
case (int)GUIEventCodes.LEFT_MOUSE_RELEASED:
State = ButtonState.UP;
// Invoke Event if we're enabled
if (ButtonReleased != null)
ButtonReleased.Invoke(this, EventArgs.Empty);
break;
case (int)GUIEventCodes.MOUSE_OVER:
State = ButtonState.OVER;
break;
case (int)GUIEventCodes.MOUSE_OUT:
State = ButtonState.UP;
break;
}
// Set the ret val to true
retVal = true;
// Set the event args if they're null
if (e.EventArgs == null)
{
// Set the event args to us
e.EventArgs = this;
}
}
}
// Return
return retVal;
}
///
/// Handle the given key input event.
///
/// The key input event to handle.
public override bool HandleKeyEvent(KeyInputEvent e)
{
return false;
}
#endregion
#region Update
///
/// Update the button.
///
/// The current game time.
public override void Update(GameTime gameTime)
{}
///
/// Update the position of the text for the button.
///
private void UpdateTextPosition()
{
// Check to make sure they're not null
if (Font != null && Text != null)
{
// The position for the text
Vector2 size = Font.MeasureString(Text);
Vector2 centerOffset = new Vector2((Size.X - size.X) / 2, (Size.Y - size.Y) / 2);
Vector2 sumOffset = centerOffset + mTextCustomOffset;
float offsetLength = sumOffset.Length();
double offsetRotation = Math.Atan2(sumOffset.Y, sumOffset.X);
Vector2 rotatedOffset = new Vector2((float)(Math.Cos(offsetRotation + mRotation) * offsetLength), (float)(Math.Sin(offsetRotation + mRotation) * offsetLength));
mTextPosition = Position + rotatedOffset;
}
}
///
/// Updates teh button GUI
///
protected void updatePositions()
{
// Update background rectangles
if (mCurrentTexture != null)
{
centerDest = new Rectangle(mBounds.X + mBorderSizeDest, mBounds.Y + mBorderSizeDest, Bounds.Width - mBorderSizeDest * 2, Bounds.Height - mBorderSizeDest * 2);
centerSource = new Rectangle(mBorderSizeSource, mBorderSizeSource, CurrentTexture.Width - mBorderSizeSource * 2, CurrentTexture.Height - mBorderSizeSource * 2);
topLeftDest = new Rectangle(mBounds.X, mBounds.Y, mBorderSizeDest, mBorderSizeDest);
topLeftSource = new Rectangle(0, 0, mBorderSizeSource, mBorderSizeSource);
topDest = new Rectangle(mBounds.X + mBorderSizeDest, mBounds.Y, mBounds.Width - mBorderSizeDest * 2, mBorderSizeDest);
topSource = new Rectangle(mBorderSizeSource, 0, CurrentTexture.Width - mBorderSizeSource * 2, mBorderSizeSource);
topRightDest = new Rectangle(mBounds.X + mBounds.Width - mBorderSizeDest, mBounds.Y, mBorderSizeDest, mBorderSizeDest);
topRightSource = new Rectangle(CurrentTexture.Width - mBorderSizeSource, 0, mBorderSizeSource, mBorderSizeSource);
rightDest = new Rectangle(mBounds.X + mBounds.Width - mBorderSizeDest, mBounds.Y + mBorderSizeDest, mBorderSizeDest, mBounds.Height - mBorderSizeDest * 2);
rightSource = new Rectangle(CurrentTexture.Width - mBorderSizeSource, mBorderSizeSource, mBorderSizeSource, CurrentTexture.Height - mBorderSizeSource * 2);
bottomRightDest = new Rectangle(mBounds.X + mBounds.Width - mBorderSizeDest, mBounds.Y + mBounds.Height - mBorderSizeDest, mBorderSizeDest, mBorderSizeDest);
bottomRightSource = new Rectangle(CurrentTexture.Width - mBorderSizeSource, CurrentTexture.Height - mBorderSizeSource, mBorderSizeSource, mBorderSizeSource);
bottomDest = new Rectangle(mBounds.X + mBorderSizeDest, mBounds.Y + mBounds.Height - mBorderSizeDest, mBounds.Width - mBorderSizeDest * 2, mBorderSizeDest);
bottomSource = new Rectangle(mBorderSizeSource, CurrentTexture.Height - mBorderSizeSource, CurrentTexture.Width - mBorderSizeSource * 2, mBorderSizeSource);
bottomLeftDest = new Rectangle(mBounds.X, mBounds.Y + mBounds.Height - mBorderSizeDest, mBorderSizeDest, mBorderSizeDest);
bottomLeftSource = new Rectangle(0, CurrentTexture.Height - mBorderSizeSource, mBorderSizeSource, mBorderSizeSource);
leftDest = new Rectangle(mBounds.X, mBounds.Y + mBorderSizeDest, mBorderSizeDest, mBounds.Height - mBorderSizeDest * 2);
leftSource = new Rectangle(0, mBorderSizeSource, mBorderSizeSource, CurrentTexture.Height - mBorderSizeSource * 2);
}
// Update icon and text
if (mIcon == null)
{
// Update the text position
UpdateTextPosition();
mTextPosition.X += mBorderSizeDest;
mTextPosition.Y += mBorderSizeDest;
}
else
{
mIconDestination.X = mBorderSizeDest + (int)(mPosition.X - mScreenOffset.X);
mIconDestination.Y = mBorderSizeDest + (int)(mPosition.Y - mScreenOffset.Y);
mIconDestination.Height = (int)mSize.Y - mBorderSizeDest * 2;
mIconDestination.Width = (int)(mIcon.Width * ((float)mIconDestination.Height / mIcon.Height));
mTextPosition.X = mIconDestination.X + mIcon.Width + mPosition.X;
mTextPosition.Y = mBorderSizeDest + mPosition.Y;
}
}
///
/// Reinitialize the button to being up.
///
public override void Resize()
{
// Set the button's state up
State = ButtonState.UP;
// If the parent screen is not null
if (ParentScreen != null)
{
// Set the current texture to the up one
CurrentTexture = ButtonUp;
}
}
#endregion
#region Draw
///
/// Draw the button.
///
/// The current game time.
public override void Draw(Microsoft.Xna.Framework.GameTime gameTime)
{
// Do this if we're visible
if (Visible)
{
// If we have a current texture, draw it
if (CurrentTexture != null)
{
if (mBorderSizeSource <= 0 && mBorderSizeDest <= 0)
{
// Draw the background texture
Batch.Draw(CurrentTexture, Bounds, null, mAddColor, mRotation, Vector2.Zero, mSpriteEffects, mLayerDepth);
}
else
{
// TODO: Support rotation in bordered buttons
// Draw with scaled borders
Batch.Draw(CurrentTexture, topRightDest, topRightSource, mAddColor);
Batch.Draw(CurrentTexture, centerDest, centerSource, mAddColor);
Batch.Draw(CurrentTexture, topLeftDest, topLeftSource, mAddColor);
Batch.Draw(CurrentTexture, topDest, topSource, mAddColor);
Batch.Draw(CurrentTexture, topRightDest, topRightSource, mAddColor);
Batch.Draw(CurrentTexture, rightDest, rightSource, mAddColor);
Batch.Draw(CurrentTexture, bottomRightDest, bottomRightSource, mAddColor);
Batch.Draw(CurrentTexture, bottomDest, bottomSource, mAddColor);
Batch.Draw(CurrentTexture, bottomLeftDest, bottomLeftSource, mAddColor);
Batch.Draw(CurrentTexture, leftDest, leftSource, mAddColor);
}
}
// If we have text and a font
if (Font != null && Text != null)
{
// Draw the text to the screen
Batch.DrawString(Font, mText, mTextPosition - mScreenOffset, mTextColor, mRotation, Vector2.Zero, 1f, SpriteEffects.None, mLayerDepth);
}
if (mIcon != null)
{
Batch.Draw(mIcon, mIconDestination, null, mAddColor, mRotation, Vector2.Zero, mSpriteEffects, 0);
}
}
}
#endregion
}
}