diff --git a/Blarg.GameFramework/Blarg.GameFramework.csproj b/Blarg.GameFramework/Blarg.GameFramework.csproj index 0bf3db0..610c342 100644 --- a/Blarg.GameFramework/Blarg.GameFramework.csproj +++ b/Blarg.GameFramework/Blarg.GameFramework.csproj @@ -177,6 +177,8 @@ + + @@ -199,6 +201,7 @@ + diff --git a/Blarg.GameFramework/UI/GwenInputProcessor.cs b/Blarg.GameFramework/UI/GwenInputProcessor.cs new file mode 100644 index 0000000..3be3d32 --- /dev/null +++ b/Blarg.GameFramework/UI/GwenInputProcessor.cs @@ -0,0 +1,221 @@ +using System; +using Blarg.GameFramework.Input; + +namespace Blarg.GameFramework.UI +{ + public class GwenInputProcessor : IKeyboardListener, IMouseListener, ITouchListener + { + Gwen.Control.Canvas _canvas; + bool _isEnabled; + + public GwenInputProcessor(Gwen.Control.Canvas canvas) + { + if (canvas == null) + throw new ArgumentNullException("canvas"); + + _canvas = canvas; + } + + #region Enable / Disable + + public bool Enabled + { + get { return _isEnabled; } + set { _isEnabled = Enable(value); } + } + + private bool Enable(bool enable) + { + if (_isEnabled != enable) + { + var keyboard = Framework.Keyboard; + var mouse = Framework.Mouse; + var touchscreen = Framework.TouchScreen; + + if (enable) + { + if (keyboard != null) + keyboard.RegisterListener(this); + if (mouse != null) + mouse.RegisterListener(this); + if (touchscreen != null) + touchscreen.RegisterListener(this); + } + else + { + if (keyboard != null) + keyboard.UnregisterListener(this); + if (mouse != null) + mouse.UnregisterListener(this); + if (touchscreen != null) + touchscreen.UnregisterListener(this); + } + } + + return enable; + } + + #endregion + + #region Keyboard Events + + public bool OnKeyDown(Key key) + { + char ch = ConvertKeyToChar(key); + if (Gwen.Input.InputHandler.DoSpecialKeys(_canvas, ch)) + return false; + + var gwenKey = ConvertToGwenKey(key); + return _canvas.Input_Key(gwenKey, true); + } + + public bool OnKeyUp(Key key) + { + var gwenKey = ConvertToGwenKey(key); + return _canvas.Input_Key(gwenKey, false); + } + + #endregion + + #region Mouse Events + + public bool OnMouseButtonDown(MouseButton button, int x, int y) + { + int gwenButton = (int)button; + + int scaledX = (int)((float)x / _canvas.Scale); + int scaledY = (int)((float)y / _canvas.Scale); + + // trigger mouse move event for button events to ensure GWEN + // knows where the button event occured at + bool movedResult = _canvas.Input_MouseMoved(scaledX, scaledY, 0, 0); + bool clickResult = _canvas.Input_MouseButton(gwenButton, true); + + // TODO: is this really the right way to do this .. ? + return (movedResult || clickResult); + } + + public bool OnMouseButtonUp(MouseButton button, int x, int y) + { + int gwenButton = (int)button; + + int scaledX = (int)((float)x / _canvas.Scale); + int scaledY = (int)((float)y / _canvas.Scale); + + // trigger mouse move event for button events to ensure GWEN + // knows where the button event occured at + bool movedResult = _canvas.Input_MouseMoved(scaledX, scaledY, 0, 0); + bool clickResult = _canvas.Input_MouseButton(gwenButton, false); + + // TODO: is this really the right way to do this .. ? + return (movedResult || clickResult); + } + + public bool OnMouseMove(int x, int y, int deltaX, int deltaY) + { + // Gwen's input handling only processes coordinates in terms of scale = 1.0f + int scaledX = (int)((float)x / _canvas.Scale); + int scaledY = (int)((float)y / _canvas.Scale); + int scaledDeltaX = (int)((float)deltaX / _canvas.Scale); + int scaledDeltaY = (int)((float)deltaY / _canvas.Scale); + + return _canvas.Input_MouseMoved(scaledX, scaledY, scaledDeltaX, scaledDeltaY); + } + + #endregion + + #region Touchscreen Events + + public bool OnTouchDown(int id, int x, int y, bool isPrimary) + { + if (!isPrimary) + return false; + + // Gwen's input handling only processes coordinates in terms of scale = 1.0f + int scaledX = (int)((float)x / _canvas.Scale); + int scaledY = (int)((float)y / _canvas.Scale); + + bool movedResult = _canvas.Input_MouseMoved(scaledX, scaledY, 0, 0); + bool clickResult = _canvas.Input_MouseButton(0, true); + + // TODO: is this really the right way to do this .. ? + return (movedResult || clickResult); + } + + public bool OnTouchUp(int id, bool isPrimary) + { + if (!isPrimary) + return false; + + bool clickResult = _canvas.Input_MouseButton(0, false); + + // we do this so that GWEN isn't left thinking that the "mouse" is + // hovering over whatever we were just clicking/touching. This is + // done because obviously with a touchscreen, you don't hover over + // anything unless you are clicking/touching... + bool movedResult = _canvas.Input_MouseMoved(-1, -1, 0, 0); + + // TODO: is this really the right way to do this .. ? + return (movedResult || clickResult); + } + + public bool OnTouchMove(int id, int x, int y, int deltaX, int deltaY, bool isPrimary) + { + if (!isPrimary) + return false; + + // Gwen's input handling only processes coordinates in terms of scale = 1.0f + int scaledX = (int)((float)x / _canvas.Scale); + int scaledY = (int)((float)y / _canvas.Scale); + int scaledDeltaX = (int)((float)deltaX / _canvas.Scale); + int scaledDeltaY = (int)((float)deltaY / _canvas.Scale); + + bool movedResult = _canvas.Input_MouseMoved(scaledX, scaledY, scaledDeltaX, scaledDeltaY); + bool clickResult = _canvas.Input_MouseButton(0, true); + + // TODO: is this really the right way to do this .. ? + return (movedResult || clickResult); + } + + #endregion + + #region Misc + + private char ConvertKeyToChar(Key key) + { + if (key >= Key.A && key <= Key.Z) + return (char)('a' + ((int)key - (int)Key.A)); + else + return ' '; + } + + private Gwen.Key ConvertToGwenKey(Key key) + { + switch (key) + { + case Key.Backspace: return Gwen.Key.Backspace; + case Key.Enter: return Gwen.Key.Return; + case Key.Escape: return Gwen.Key.Escape; + case Key.Tab: return Gwen.Key.Tab; + case Key.Space: return Gwen.Key.Space; + case Key.Up: return Gwen.Key.Up; + case Key.Down: return Gwen.Key.Down; + case Key.Left: return Gwen.Key.Left; + case Key.Right: return Gwen.Key.Right; + case Key.Home: return Gwen.Key.Home; + case Key.End: return Gwen.Key.End; + case Key.Delete: return Gwen.Key.Delete; + case Key.LeftCtrl: return Gwen.Key.Control; + case Key.LeftAlt: return Gwen.Key.Alt; + case Key.LeftShift: return Gwen.Key.Shift; + case Key.RightCtrl: return Gwen.Key.Control; + case Key.RightAlt: return Gwen.Key.Alt; + case Key.RightShift: return Gwen.Key.Shift; + } + + return Gwen.Key.Invalid; + } + + #endregion + } +} diff --git a/Blarg.GameFramework/UI/GwenSpriteBatchRenderer.cs b/Blarg.GameFramework/UI/GwenSpriteBatchRenderer.cs new file mode 100644 index 0000000..a75c5c7 --- /dev/null +++ b/Blarg.GameFramework/UI/GwenSpriteBatchRenderer.cs @@ -0,0 +1,210 @@ +using System; +using Blarg.GameFramework.Content; +using Blarg.GameFramework.Graphics; + +namespace Blarg.GameFramework.UI +{ + public class GwenSpriteBatchRenderer : Gwen.Renderer.Base + { + const string LOG_TAG = "GWEN"; + + ContentManager _contentManager; + GraphicsDevice _graphicsDevice; + SpriteBatch _spriteBatch; + + public GwenSpriteBatchRenderer(ContentManager contentManager, GraphicsDevice graphicsDevice) + { + if (contentManager == null) + throw new ArgumentNullException("contentManager"); + if (graphicsDevice == null) + throw new ArgumentNullException("graphicsDevice"); + + _contentManager = contentManager; + _graphicsDevice = graphicsDevice; + Alpha = Color.AlphaOpaque; + } + + #region Begin / End + + public void PreRender(SpriteBatch spriteBatch) + { + if (spriteBatch == null) + throw new ArgumentNullException("spriteBatch"); + + _spriteBatch = spriteBatch; + } + + public void PostRender() + { + _spriteBatch = null; + } + + public override void Begin() + { + if (_spriteBatch == null) + throw new InvalidOperationException(); + base.Begin(); + } + + public override void End() + { + base.End(); + } + + #endregion + + #region Rendering States / Properties + + public float Alpha { get; set; } + + public override void StartClip() + { + var rect = ClipRegion; + + int left = (int)((float)rect.X * Scale); + int top = (int)((float)rect.Y * Scale); + int right = (int)((float)(rect.X + rect.Width) * Scale); + int bottom = (int)((float)(rect.Y + rect.Height) * Scale); + + _spriteBatch.BeginClipping(left, top, right, bottom); + } + + public override void EndClip() + { + _spriteBatch.EndClipping(); + } + + private void AdjustColorForAlpha(ref Color color) + { + color.A *= Alpha; + } + + #endregion + + #region General Rendering Operations + + public override void DrawFilledRect(Gwen.Rectangle rect) + { + Translate(rect); + + var renderColor = new Color((int)DrawColor.R, (int)DrawColor.G, (int)DrawColor.B, (int)DrawColor.A); + AdjustColorForAlpha(ref renderColor); + + // TODO: this solid color texture should probably be grabbed using a color + // that has A = 1.0 always otherwise any kind of fading, etc. will + // result in many different solid color's ending up in the solid color + // texture cache (due to all the different A values!) + var colorTexture = _graphicsDevice.GetSolidColorTexture(ref renderColor); + + _spriteBatch.Render(colorTexture, rect.X, rect.Y, rect.Width, rect.Height); + } + + #endregion + + #region Textured Rendering + + public override void LoadTexture(Gwen.Texture t) + { + Framework.Logger.Info(LOG_TAG, "SpriteBatchRenderer loading texture \"{0}\".", t.Name); + var texture = _contentManager.Get(t.Name); + + t.RendererData = texture; + t.Width = texture.Width; + t.Height = texture.Height; + } + + public override void FreeTexture(Gwen.Texture t) + { + // right now we don't care, ContentManager.RemoveAlLContent() can and will take care of this + // (Gwen doesn't really load too many resources that I think we really need to care about this) + } + + public override void DrawTexturedRect(Gwen.Texture t, Gwen.Rectangle targetRect, float u1, float v1, float u2, float v2) + { + if (t.RendererData == null) + throw new InvalidOperationException(); + + Translate(targetRect); + var texture = (Texture)t.RendererData; + + var renderColor = Color.White; + AdjustColorForAlpha(ref renderColor); + + _spriteBatch.Render(texture, targetRect.X, targetRect.Y, targetRect.Width, targetRect.Height, u1, v1, u2, v2, ref renderColor); + } + + public override Gwen.Color PixelColor(Gwen.Texture texture, uint x, uint y, Gwen.Color defaultColor) + { + if (texture.RendererData == null) + return defaultColor; + + // This method is really only used by Gwen to figure out various "system" colors + // at initialization time. Pixel colors are read from the renderer skin texture + // pretty sure no other textures are ever used with this method. Unless some + // future custom control eventually is needed to read pixel colors... + // + // We load the image using ContentManager, but it won't be released... at least + // not until the next ContentManager.RemoveAllContent() call (not done by this class) + // I feel that this is fine given the above about only one texture (and therefore + // only one image) being ever read by this method. + + var image = _contentManager.Get(texture.Name); + var pixelColor = image.GetColorAt((int)x, (int)y); + + return Gwen.Color.FromArgb(pixelColor.IntA, pixelColor.IntR, pixelColor.IntG, pixelColor.IntB); + } + + #endregion + + #region Font Rendering + + public override bool LoadFont(Gwen.Font font) + { + Framework.Logger.Info(LOG_TAG, "SpriteBatchRenderer loading font \"{0}\".", font.FaceName); + int size = font.Size; + var spriteFont = _contentManager.Get(font.FaceName, size); + + font.RendererData = spriteFont; + return true; + } + + public override void FreeFont(Gwen.Font font) + { + // right now we don't care, ContentManager.RemoveAlLContent() can and will take care of this + // (Gwen doesn't really load too many resources that I think we really need to care about this) + } + + public override void RenderText(Gwen.Font font, Gwen.Point position, string text) + { + Translate(position); + var spriteFont = (SpriteFont)font.RendererData; + + var renderColor = new Color((int)DrawColor.R, (int)DrawColor.G, (int)DrawColor.B, (int)DrawColor.A); + AdjustColorForAlpha(ref renderColor); + + _spriteBatch.Render(spriteFont, position.X, position.Y, ref renderColor, Scale, text); + } + + public override Gwen.Point MeasureText(Gwen.Font font, string text) + { + // HACK: is this supposed to work this way? seems that MeasureText + // can (and will) get called from Gwen's classes before a call + // to LoadFont is made... + if (font.RendererData == null) + LoadFont(font); + + if (font.RendererData == null) + throw new Exception("Failed to load font."); + + var spriteFont = (SpriteFont)font.RendererData; + + int width; + int height; + spriteFont.MeasureString(out width, out height, text); + + return new Gwen.Point(width, height); + } + + #endregion + } +}