Using Awesomium with MonoGame

I have been recently working on incorporating the Awesomium framework into a MonoGame project I am working on, however actually getting it functional was proving a bit of a problem. There are a lot of examples of Awesomium working in XNA out there, but none that I could find for MonoGame – and the XNA samples proved not to work when ported directly across.

 

The Base Class:

The first challenge was to create a class that could handle the Awesomium.Core.WebView class in a way that allowed you to use it your MonoGame project. Combining several XNA implementations I found, I created this AwesomiumComponent class:

 

    struct AwesomiumComponent
    {
        public WebView webView;
        public Microsoft.Xna.Framework.Rectangle Rectangle;

        public AwesomiumComponent(string Source, Microsoft.Xna.Framework.Rectangle rectangle)
        {
            webView = WebCore.CreateWebView(rectangle.Width, rectangle.Height);
            webView.Source = Source.ToUri();
            webView.IsTransparent = true;

            while (webView.IsLoading)
                WebCore.Update();

            Rectangle = rectangle;
        }

        public Texture2D GetTexture(GraphicsDevice graphicsDevice)
        {
            BitmapSurface surface = (BitmapSurface)webView.Surface;
            Bitmap b = new Bitmap(webView.Width, webView.Height, PixelFormat.Format32bppArgb);
            BitmapData bits0 = b.LockBits(
                new System.Drawing.Rectangle(0, 0, webView.Width, webView.Height),
                ImageLockMode.ReadWrite, b.PixelFormat);
            surface.CopyTo(bits0.Scan0, bits0.Stride, 4, false, false);
            b.UnlockBits(bits0);
            int bufferSize = b.Height * b.Width * 4;
            System.IO.MemoryStream memoryStream = new System.IO.MemoryStream(bufferSize);
            b.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
            Texture2D myTex = Texture2D.FromStream(graphicsDevice, memoryStream);

            return myTex;
        }
    }

To initialize the class you pass an address (e.g. http://www.strikelimit.co.uk, or C://Users/Default/Desktop/test.html), and the rectangle you wish it to take up on the screen.

The GetTexture() method allows you to pass your games GraphicsDevice to the component, and get a texture that represents the html page you have set your WebView to, which you can easily drawn using spriteBatch. It first gets the BitmapData from the WebView, and then uses a MemoryStream to pass that data into the MonoGame/XNA standard of Texture2D.

 

Input:

I ran into more problems when trying to inject input into the WebView. Mouse input can be added in relatively easily, using the WebView.InjectMouseDown, WebView.InjectMouseUp, and WebView.InjectMouseMove methods to pass input from the MonoGame MouseState classes to the game. I came up with the following Update() method for the AwesomiumComponent:

        public void Update(MouseState mouseState, MouseState previousMouseState)
        {
            if (mouseState.LeftButton == ButtonState.Pressed && previousMouseState.LeftButton == ButtonState.Released)
                webView.InjectMouseDown(MouseButton.Left);
            else if (mouseState.LeftButton == ButtonState.Released && previousMouseState.LeftButton == ButtonState.Pressed)
                webView.InjectMouseUp(MouseButton.Left);

            if (mouseState.RightButton == ButtonState.Pressed && previousMouseState.RightButton == ButtonState.Released)
                webView.InjectMouseDown(MouseButton.Right);
            else if (mouseState.RightButton == ButtonState.Released && previousMouseState.RightButton == ButtonState.Pressed)
                webView.InjectMouseUp(MouseButton.Right);

            if (mouseState.X != previousMouseState.X || mouseState.Y != previousMouseState.Y)
                webView.InjectMouseMove(mouseState.X - Rectangle.X, mouseState.Y - Rectangle.Y);
        }

(A note: you may only want to call this function when the mouse is detected inside the components rectangle, so to avoid weird drag selection happening over all the components in your solution.)

Keyboard input is a bit more difficult. Using the MonoGame KeyboardState classes would require a potentially large amount of work to get them to give single key press inputs, instead of just saying which keys are currently pressed.

If for some reason you are interested in doing this, the way I would suggest going about it is by comparing the pressed keys in the current and previous KeyboardStates and then injecting the keys that are no longer pressed. This however wouldn’t act like a normal keyboard regarding holding down keys – they would only be entered once when you stop holding the key.

There are several examples of how to tame the XNA input to give the Windows Messages for key presses that WebView class expects you to inject, however due to the use of OpenTK instead of the sort-of-Winforms-base that XNA uses, these won’t work – indeed as far as I have been able to tell the SetWindowsHook method from user32.dll isn’t available for use in MonoGame.

As suggested in the above linked thread, you can use OpenTK.GameWindow events to hook the keyboard input – and if you use the following methods (just the same as in the above post, but with a bit cut out), you get text input in the normal form:

        protected void HookKeys()
        {
            OpenTK.GameWindow OTKWindow = null;
            Type type = typeof(OpenTKGameWindow);
            System.Reflection.FieldInfo field = type.GetField("window", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

            if (field != null)
            {
                OTKWindow = field.GetValue(this.Window) as OpenTK.GameWindow;
            }

            if (OTKWindow != null)
            {
                OTKWindow.KeyPress += OTKWindow_KeyPress;
            }
        }

        private void OTKWindow_KeyPress(object sender, OpenTK.KeyPressEventArgs e)
        {
            // Do something with the input here
        }

To inject these events into the WebView class simply using constructors, it would expect you to have a full Windows Message in the form SetWindowsHook would give you, instead you need to use the following code:

                WebKeyboardEvent webKeyboardEvent = new WebKeyboardEvent
                {
                    Type = WebKeyboardEventType.Char,
                    Text = new String(new char[] { e.KeyChar, (char)0, (char)0, (char)0 })
                };
                webView.InjectKeyboardEvent(webKeyboardEvent);

A note: this code won’t work with the backspace key, see this follow up post for a solution.

1 comment

1 ping

    • Bjarke on January 29, 2014 at 4:04 pm
    • Reply

    Thank you very much, this helped alot!!

    To anyone having trouple with making webView actually update, just add WebCore.Update() to your Update method.

  1. […] I am writing a game in XNA which is using Awesomium webviews as the UI. The webviews are rendered to Texture2D using this class. […]

Leave a Reply

Your email address will not be published.