Skip to content

Instantly share code, notes, and snippets.

@PERECil
Last active November 10, 2017 14:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PERECil/8505c2b698c6bef54dcfe8aeed124da0 to your computer and use it in GitHub Desktop.
Save PERECil/8505c2b698c6bef54dcfe8aeed124da0 to your computer and use it in GitHub Desktop.
Advanced keyboard/mouse management component for Monogame
//-----------------------------------------------------------------------
// <copyright file="KeyboardComponent.cs" company="Mathieu Muller">
// Copyright © Mathieu Muller. All rights reserved.
// </copyright>
// <author>Mathieu Muller</author>
//-----------------------------------------------------------------------
namespace MonoGame.Extensions.Components.Input
{
#region Using statements
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extensions.Collections.Generic;
using MonoGame.Extensions.Components.Input.Keyboard;
#endregion
/// <summary>
/// Offers a service that provides data about the keyboard
/// </summary>
public class KeyboardComponent : GameComponent
{
#region Fields
/// <summary>
/// Stores the current state and the previous state of the keyboard
/// </summary>
private BufferedState<KeyboardState> state;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="KeyboardComponent"/> class.
/// </summary>
/// <param name="game">The game instance.</param>
public KeyboardComponent(Game game)
: base(game)
{
// Initialize the buffered state class with the KeyboardState type
this.state = new BufferedState<KeyboardState>();
// Register this component as a service
this.Game.Services.AddService(this.GetType(), this);
this.KeyboardDelay = 250;
this.KeyboardSpeed = 40;
this.Game.Window.TextInput += this.ProcessWindowTextInput;
this.KeyPressDuration = new Dictionary<Keys, double>();
this.Keys = Enum.GetValues(typeof(Keys)).Cast<Keys>();
this.Text = new List<CharKey>();
}
#endregion
#region Events
/// <summary>
/// This event is called whenever a the user typed a character
/// </summary>
public event EventHandler<TextInputEventArgs> TextInput;
#endregion
#region Properties
/// <summary>
/// Gets or sets keyboard repeat-delay setting, in milliseconds. Defaults to 250 milliseconds.
/// </summary>
public int KeyboardDelay { get; set; }
/// <summary>
/// Gets or sets the keyboard repeat period, in milliseconds. defaults to 40 milliseconds.
/// </summary>
public int KeyboardSpeed { get; set; }
/// <summary>
/// Gets the previous state of the keyboard.
/// </summary>
public KeyboardState Previous
{
get { return this.state.Previous; }
}
/// <summary>
/// Gets the current state of the keyboard.
/// </summary>
public KeyboardState Current
{
get { return this.state.Current; }
}
/// <summary>
/// Gets or sets the typed text since the last frame.
/// </summary>
public List<CharKey> Text { get; set; }
/// <summary>
/// Gets or sets the dictionary of key presses durations
/// </summary>
private Dictionary<Keys, double> KeyPressDuration { get; set; }
/// <summary>
/// Gets or sets all available keys enumeration.
/// </summary>
private IEnumerable<Keys> Keys { get; set; }
#endregion
#region Methods
/// <summary>
/// Allows the game component to perform any initialization it needs to before starting
/// to run. This is where it can query for any required services and load content.
/// </summary>
public override void Initialize()
{
base.Initialize();
}
/// <summary>
/// Returns the state of a particular key
/// </summary>
/// <param name="key">The key code to check</param>
/// <returns>true if the key is currently down</returns>
public bool IsKeyDown(Keys key)
{
return this.state.Current.IsKeyDown(key);
}
/// <summary>
/// Returns the down state of any of the keys.
/// </summary>
/// <returns>True if one of the key is down, false otherwise.</returns>
public bool IsKeyDown()
{
foreach (Keys key in this.Keys)
{
if (this.IsKeyDown(key))
{
return true;
}
}
return false;
}
/// <summary>
/// Returns the rising edge state of a particular key
/// </summary>
/// <param name="key">The key code to check</param>
/// <returns>true if the key just got pressed</returns>
public bool IsKeyPressed(Keys key)
{
return this.WasKeyUp(key) && this.IsKeyDown(key);
}
/// <summary>
/// Returns this rising edge state of any key
/// </summary>
/// <returns>True if a key got pressed</returns>
/// <remarks>Untested/non debugged code. May not work.</remarks>
public bool IsKeyPressed()
{
foreach (Keys key in this.Keys)
{
if (this.IsKeyPressed(key))
{
return true;
}
}
// Support for emulated TextInput
if (this.Text.Count > 0)
{
return true;
}
return false;
}
/// <summary>
/// Returns the state of a particular key
/// </summary>
/// <param name="key">The key code to check</param>
/// <returns>true if the key is currently down</returns>
public bool IsKeyUp(Keys key)
{
return this.state.Current.IsKeyUp(key);
}
/// <summary>
/// Returns the falling edge state of a particular key
/// </summary>
/// <param name="key">The key code to check</param>
/// <returns>true if the key just got released</returns>
public bool IsKeyReleased(Keys key)
{
return this.WasKeyDown(key) && this.IsKeyUp(key);
}
/// <summary>
/// Allows the game component to update itself and updates the keyboard data.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
public override void Update(GameTime gameTime)
{
// Reinitialize the typed text.
// this.Text = string.Empty;
// Updates the buffered state with fresh keyboard data.
this.state.Add(Microsoft.Xna.Framework.Input.Keyboard.GetState());
Keys[] pressedKeys = this.state.Current.GetPressedKeys();
IEnumerable<Keys> copyKeys = this.KeyPressDuration.Keys.ToArray();
// Updates the duration of a pressed key to emulate the typematic settings
// Note that this is badly done. We should update the dictionary with pressed keys only,
// it would be a lot faster than just checking every key.
// This must be replaced when https://github.com/MonoGame/MonoGame/issues/5808 will be fixed.
foreach (Keys key in copyKeys.Union(pressedKeys))
{
// If the pressed keys contains the current key
if (pressedKeys.Contains(key))
{
// Create the key if it doesn't exists
if (!this.KeyPressDuration.ContainsKey(key))
{
this.KeyPressDuration.Add(key, 0);
this.Text.Add(new CharKey(key, '\0'));
}
// Add it and set the duration.
this.KeyPressDuration[key] = this.KeyPressDuration[key] + gameTime.ElapsedGameTime.TotalMilliseconds;
if (this.IsRepeating(this.KeyPressDuration[key] - gameTime.ElapsedGameTime.TotalMilliseconds, this.KeyPressDuration[key]))
{
this.Text.Add(new CharKey(key, '\0'));
}
}
else
{
// Else, remove the key from the durations.
this.KeyPressDuration.Remove(key);
}
}
base.Update(gameTime);
}
/// <summary>
/// Returns the previous state of a particular key
/// </summary>
/// <param name="key">The key code to check</param>
/// <returns>true if the key was down on the last update</returns>
public bool WasKeyDown(Keys key)
{
return this.state.Previous.IsKeyDown(key);
}
/// <summary>
/// Returns the previous state of a particular key
/// </summary>
/// <param name="key">The key code to check</param>
/// <returns>true if the key was up on the last update</returns>
public bool WasKeyUp(Keys key)
{
return this.state.Previous.IsKeyUp(key);
}
/// <summary>
/// Returns the up state of any of the keys.
/// </summary>
/// <returns>True if one of the key is up, false otherwise.</returns>
public bool IsKeyUp()
{
foreach (Keys key in this.Keys)
{
if (this.IsKeyUp(key))
{
return true;
}
}
return false;
}
/// <summary>
/// Returns the released state of any of the keys.
/// </summary>
/// <returns>True if one of the key is released, false otherwise.</returns>
public bool IsKeyReleased()
{
foreach (Keys key in this.Keys)
{
if (this.IsKeyReleased(key))
{
return true;
}
}
return false;
}
/// <summary>
/// Returns the previous down state of any of the keys.
/// </summary>
/// <returns>True if one of the key was down, false otherwise.</returns>
public bool WasKeyDown()
{
foreach (Keys key in this.Keys)
{
if (this.WasKeyDown(key))
{
return true;
}
}
return false;
}
/// <summary>
/// Returns the previous up state of any of the keys.
/// </summary>
/// <returns>True if one of the key was up, false otherwise.</returns>
public bool WasKeyUp()
{
foreach (Keys key in this.Keys)
{
if (this.WasKeyUp(key))
{
return true;
}
}
return false;
}
/// <summary>
/// Method stub for the TextInput event.
/// </summary>
/// <param name="e">The <see cref="TextInputEventArgs"/> arguments.</param>
protected virtual void OnTextInput(TextInputEventArgs e)
{
this.Text.Add(new CharKey(e.Key, e.Character));
if (this.TextInput != null)
{
this.TextInput(this, e);
}
}
/// <summary>
/// Proxy for the TextInput event.
/// </summary>
/// <param name="sender">The object that sent the text input event.</param>
/// <param name="e">The <see cref="TextInputEventArgs"/> arguments.</param>
private void ProcessWindowTextInput(object sender, TextInputEventArgs e)
{
this.OnTextInput(e);
}
/// <summary>
/// Gets a boolean value indicating if the keyboard should repeat the key typed after a long key press.
/// </summary>
/// <param name="min">The previous frame time.</param>
/// <param name="max">The current frame time.</param>
/// <returns>True if the key press must be repeated, false otherwise.</returns>
private bool IsRepeating(double min, double max)
{
if (min < this.KeyboardDelay)
{
if (min <= this.KeyboardDelay && this.KeyboardDelay < max)
{
return true;
}
}
else
{
int cycleCount = (int)((min - this.KeyboardDelay) / this.KeyboardSpeed);
double cycleMin = (min - this.KeyboardDelay) - (this.KeyboardSpeed * cycleCount);
double cycleMax = (max - this.KeyboardDelay) - (this.KeyboardSpeed * cycleCount);
if (cycleMin <= this.KeyboardSpeed && this.KeyboardSpeed < cycleMax)
{
return true;
}
}
return false;
}
#endregion
}
}
//-----------------------------------------------------------------------
// <copyright file="MouseComponent.cs" company="Mathieu Muller">
// Copyright © Mathieu Muller. All rights reserved.
// </copyright>
// <author>Mathieu Muller</author>
//-----------------------------------------------------------------------
namespace MonoGame.Extensions.Components.Input
{
#region Using statements
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extensions.Collections.Generic;
using MonoGame.Extensions.Components.Input.Mouse;
using Buttons = MonoGame.Extensions.Components.Input.Mouse.Buttons;
#endregion
/// <summary>
/// Offers a service that provides data about the mouse
/// </summary>
public class MouseComponent : GameComponent
{
#region Fields
/// <summary>
/// Stores the current state and the previous state of the mouse
/// </summary>
private BufferedState<MouseState> state;
/// <summary>
/// Stores the position of the mouse cursor
/// </summary>
private Cursor cursor;
/// <summary>
/// Stores data about wheel's state
/// </summary>
private ScrollWheel wheel;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="MouseComponent"/> class.
/// </summary>
/// <param name="game">The game instance.</param>
public MouseComponent(Game game)
: base(game)
{
// Initialize the buffered state class with the <see cref="MouseState" /> type
this.state = new BufferedState<MouseState>();
// Register this component as a service
this.Game.Services.AddService(this.GetType(), this);
// Initialize internals
this.cursor = new Cursor(game);
this.wheel = new ScrollWheel(game);
}
#endregion
#region Properties
/// <summary>
/// Gets the previous state of the mouse
/// </summary>
public MouseState Previous
{
get { return this.state.Previous; }
}
/// <summary>
/// Gets the current state of the mouse.
/// </summary>
public MouseState Current
{
get { return this.state.Current; }
}
/// <summary>
/// Gets the scroll value of the mouse.
/// </summary>
public ScrollWheel Wheel
{
get { return this.wheel; }
}
/// <summary>
/// Gets the mouse position.
/// </summary>
public Cursor Cursor
{
get { return this.cursor; }
}
#endregion
#region Methods
/// <summary>
/// Allows the game component to perform any initialization it needs to before starting
/// to run. This is where it can query for any required services and load content.
/// </summary>
public override void Initialize()
{
base.Initialize();
}
/// <summary>
/// Returns the state of a particular key
/// </summary>
/// <param name="button">The button code to check</param>
/// <returns>true if the button is currently down</returns>
public bool IsButtonDown(Buttons button)
{
return MouseComponent.IsButtonDown(button, this.state.Current);
}
/// <summary>
/// Returns the down state of any of the buttons
/// </summary>
/// <returns>true if a button is currently down</returns>
public bool IsButtonDown()
{
Buttons[] values = (Buttons[])Enum.GetValues(typeof(Buttons));
foreach (Buttons key in values)
{
if (this.IsButtonDown(key))
{
return true;
}
}
return false;
}
/// <summary>
/// Returns the rising edge state of a particular button
/// </summary>
/// <param name="button">The button code to check</param>
/// <returns>true if the button just got pressed</returns>
public bool IsButtonPressed(Buttons button)
{
return this.WasButtonUp(button) && this.IsButtonDown(button);
}
/// <summary>
/// Returns the rising edge state of any of the button
/// </summary>
/// <returns>true if one of the button just got pressed</returns>
public bool IsButtonPressed()
{
Buttons[] values = (Buttons[])Enum.GetValues(typeof(Buttons));
foreach (Buttons key in values)
{
if (this.IsButtonPressed(key))
{
return true;
}
}
return false;
}
/// <summary>
/// Returns the state of a particular button
/// </summary>
/// <param name="button">The button code to check</param>
/// <returns>true if the button is currently down</returns>
public bool IsButtonUp(Buttons button)
{
return MouseComponent.IsButtonUp(button, this.state.Current);
}
/// <summary>
/// Returns the state of any of the buttons
/// </summary>
/// <returns>true if the button is currently down</returns>
public bool IsButtonUp()
{
return MouseComponent.IsButtonUp(this.state.Current);
}
/// <summary>
/// Returns the falling edge state of a particular key
/// </summary>
/// <param name="button">The button code to check</param>
/// <returns>true if the button just got released</returns>
public bool IsButtonReleased(Buttons button)
{
return this.WasButtonDown(button) && this.IsButtonUp(button);
}
/// <summary>
/// Returns the falling edge state of any button
/// </summary>
/// <returns>true if the button just got released</returns>
public bool IsButtonReleased()
{
Buttons[] values = (Buttons[])Enum.GetValues(typeof(Buttons));
foreach (Buttons key in values)
{
if (this.IsButtonReleased(key))
{
return true;
}
}
return false;
}
/// <summary>
/// Allows the game component to update itself and updates the mouse data.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
public override void Update(GameTime gameTime)
{
// Updates the buffered state with fresh mouse data
// Note that we need to use the full qualified name to permit the compiler
// to know that we are talking about the Mouse class from the XNA framework
this.state.Add(Microsoft.Xna.Framework.Input.Mouse.GetState());
// Update the mouse position
this.cursor.Delta = new Point(this.Current.X, this.Current.Y) - new Point(this.Previous.X, this.Previous.Y);
// Update the wheel status
this.wheel.Delta = this.Current.ScrollWheelValue - this.Previous.ScrollWheelValue;
base.Update(gameTime);
}
/// <summary>
/// Returns the previous state of a particular button
/// </summary>
/// <param name="button">The button code to check</param>
/// <returns>true if the button was down on the last update</returns>
public bool WasButtonDown(Buttons button)
{
return MouseComponent.IsButtonDown(button, this.state.Previous);
}
/// <summary>
/// Returns the previous state of any button
/// </summary>
/// <returns>true if the button was down on the last update</returns>
public bool WasButtonDown()
{
return MouseComponent.IsButtonDown(this.state.Previous);
}
/// <summary>
/// Returns the previous state of a particular button
/// </summary>
/// <param name="button">The button code to check</param>
/// <returns>true if the button was up on the last update</returns>
public bool WasButtonUp(Buttons button)
{
return MouseComponent.IsButtonUp(button, this.state.Previous);
}
/// <summary>
/// Returns the previous state of any button
/// </summary>
/// <returns>true if the button was up on the last update</returns>
public bool WasButtonUp()
{
return MouseComponent.IsButtonUp(this.state.Previous);
}
/// <summary>
/// Checks the down state of a mouse button
/// </summary>
/// <param name="button">The mouse button to check</param>
/// <param name="state">The MouseState containing the button state to check</param>
/// <returns>true if the button is down</returns>
private static bool IsButtonDown(Buttons button, MouseState state)
{
// In function of the enum value
switch (button)
{
// Check the left mouse button
case Buttons.Left:
return state.LeftButton == ButtonState.Pressed;
// Check the middle mouse button
case Buttons.Middle:
return state.MiddleButton == ButtonState.Pressed;
// Check the right mouse button
case Buttons.Right:
return state.RightButton == ButtonState.Pressed;
// Check the previous mouse button
case Buttons.Previous:
return state.XButton1 == ButtonState.Pressed;
// Check the next mouse button
case Buttons.Next:
return state.XButton2 == ButtonState.Pressed;
}
// This should not happen
return false;
}
/// <summary>
/// Checks the down state of all buttons
/// </summary>
/// <param name="state">The MouseState containing the button state to check</param>
/// <returns>true if the button is down</returns>
private static bool IsButtonDown(MouseState state)
{
bool down = false;
// Check the left mouse button
down |= state.LeftButton == ButtonState.Pressed;
// Check the middle mouse button
down |= state.MiddleButton == ButtonState.Pressed;
// Check the right mouse button
down |= state.RightButton == ButtonState.Pressed;
// Check the previous mouse button
down |= state.XButton1 == ButtonState.Pressed;
// Check the next mouse button
down |= state.XButton2 == ButtonState.Pressed;
return down;
}
/// <summary>
/// Checks the up state of a mouse button
/// </summary>
/// <param name="button">The mouse button to check</param>
/// <param name="state">The MouseState containing the button state to check</param>
/// <returns>true if the button is up</returns>
private static bool IsButtonUp(Buttons button, MouseState state)
{
return !MouseComponent.IsButtonDown(button, state);
}
/// <summary>
/// Checks the up state of any button
/// </summary>
/// <param name="state">The MouseState containing the button state to check</param>
/// <returns>true if the button is up</returns>
private static bool IsButtonUp(MouseState state)
{
bool up = false;
// Check the left mouse button
up |= state.LeftButton == ButtonState.Released;
// Check the middle mouse button
up |= state.MiddleButton == ButtonState.Released;
// Check the right mouse button
up |= state.RightButton == ButtonState.Released;
// Check the previous mouse button
up |= state.XButton1 == ButtonState.Released;
// Check the next mouse button
up |= state.XButton2 == ButtonState.Released;
return up;
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment