Last active
November 10, 2017 14:23
-
-
Save PERECil/8505c2b698c6bef54dcfe8aeed124da0 to your computer and use it in GitHub Desktop.
Advanced keyboard/mouse management component for Monogame
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//----------------------------------------------------------------------- | |
// <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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//----------------------------------------------------------------------- | |
// <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