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
+ }
+}