소스 검색

Mobile WebView Work

Josh Engebretson 8 년 전
부모
커밋
c6a9614383

+ 1 - 1
Build/CMake/Modules/AtomicMac.cmake

@@ -20,4 +20,4 @@ else ()
 endif ()
 endif ()
 
 
 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof -stdlib=libc++")
 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof -stdlib=libc++")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -framework AudioToolbox -framework Carbon -framework Cocoa -framework CoreAudio -framework CoreVideo -framework ForceFeedback -framework IOKit -framework OpenGL -framework CoreServices -framework Security -framework SystemConfiguration")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -framework AudioToolbox -framework Carbon -framework Cocoa -framework CoreAudio -framework CoreVideo -framework ForceFeedback -framework IOKit -framework OpenGL -framework CoreServices -framework Security -framework SystemConfiguration -framework WebKit")

+ 335 - 0
Data/AtomicEditor/Deployment/Android/src/com/atomicgameengine/player/AtomicWebView.java

@@ -0,0 +1,335 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+// Copyright (c) 2011-2017, The Game Engine Company LLC (Apache License 2.0)
+
+package com.atomicgameengine.player;
+
+import java.util.Hashtable;
+import java.util.Set;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.view.ViewGroup;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.webkit.WebChromeClient;
+import android.widget.RelativeLayout;
+
+/**
+ *    Java class that manages WebView instances. This maps directly to the platformWebView C API
+ */
+public class AtomicWebView {
+
+    private static native void nativeCallback(String data, long callback, long payload, int type);
+
+    private static void deferNativeCallback(String data, long callback, long payload, int type)
+    {
+        final String fData = data;
+        final long fCallback = callback;
+        final long fPayload = payload;
+        final int fType = type;
+
+        // TODO: does this require queueEvent?
+        activity.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                nativeCallback(fData, fCallback, fPayload, fType);
+            }
+        });
+    }
+
+    public static boolean create(final int handle, final long callback, final long payload)
+    {
+        final Context context = activity;
+
+        activity.runOnUiThread(new Runnable() {
+
+            @Override
+            public void run() {
+                WebView webView = new WebView(context);
+
+                AtomicWebView.webViews.put(handle, webView);
+
+                webView.setWebChromeClient(new WebChromeClient());
+                webView.setWebViewClient(new WebViewClient() {
+                    @Override
+                    public void onReceivedError(WebView view, int errorCode,
+                            String description, String failingUrl) {
+                        super.onReceivedError(view, errorCode, description, failingUrl);
+                        deferNativeCallback(description, callback, payload, 1);
+                    }
+
+                    @Override
+                    public boolean shouldOverrideUrlLoading(WebView view, String url) {
+                        return false;
+                    }
+
+                    @Override
+                    public void onPageStarted(WebView view, String url,
+                            Bitmap favicon) {
+                        super.onPageStarted(view, url, favicon);
+                        deferNativeCallback(url, callback, payload, 0);
+                    }
+
+                });
+
+                webView.getSettings().setJavaScriptEnabled(true);
+                webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
+            }
+
+        });
+
+        return true;
+
+    }
+
+    public static void show(final int handle)
+    {
+        final ViewGroup layout = rootLayout;
+
+        activity.runOnUiThread(new Runnable() {
+
+            @Override
+            public void run() {
+                WebView webView = AtomicWebView.webViews.get(handle);
+                layout.addView(webView);
+                RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(webView.getLayoutParams());
+                params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
+                webView.setLayoutParams(params);
+            }
+        });
+    }
+
+    public static void hide(final int handle)
+    {
+        final ViewGroup layout = rootLayout;
+
+        activity.runOnUiThread(new Runnable() {
+
+            @Override
+            public void run() {
+                WebView webView = AtomicWebView.webViews.get(handle);
+
+                if(webView.getParent() == layout)
+                    layout.removeView(webView);
+            }
+        });
+    }
+
+    public static void destroy(final int handle)
+    {
+        final ViewGroup layout = rootLayout;
+
+        activity.runOnUiThread(new Runnable() {
+
+            @Override
+            public void run() {
+                WebView webView = webViews.get(handle);
+                AtomicWebView.webViews.remove(handle);
+
+                if(webView != null && webView.getParent() == layout)
+                    layout.removeView(webView);
+            }
+        });
+    }
+
+    public static void destroyAll()
+    {
+        final ViewGroup layout = rootLayout;
+
+        activity.runOnUiThread(new Runnable() {
+
+            @Override
+            public void run() {
+                Set<Integer> keys = webViews.keySet();
+                for(Integer key: keys){
+                    WebView webView = AtomicWebView.webViews.remove(key);
+
+                    if(webView != null && webView.getParent() == layout)
+                        layout.removeView(webView);
+                }
+            }
+        });
+    }
+
+    public static void request(final int handle, final String url)
+    {
+        activity.runOnUiThread(new Runnable() {
+
+            @Override
+            public void run() {
+                WebView webView = AtomicWebView.webViews.get(handle);
+                webView.loadUrl(url);
+            }
+        });
+    }
+
+    public static boolean goBack(final int handle)
+    {
+        final AtomicWebViewPayload payload = new AtomicWebViewPayload();
+
+        activity.runOnUiThread(new Runnable() {
+
+            @Override
+            public void run() {
+                WebView webView = AtomicWebView.webViews.get(handle);
+
+                if(webView.canGoBack()) {
+                    payload.boolValue = true;
+                    webView.goBack();
+                }
+                else {
+                    payload.boolValue = false;
+                }
+
+                synchronized (payload) {
+                    payload.notify();
+                }
+            }
+        });
+
+        try {
+            synchronized (payload) {
+                payload.wait();
+            }
+            return payload.boolValue;
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        return false;
+    }
+
+    public static boolean goForward(final int handle)
+    {
+        final AtomicWebViewPayload payload = new AtomicWebViewPayload();
+
+        activity.runOnUiThread(new Runnable() {
+
+            @Override
+            public void run() {
+                WebView webView = AtomicWebView.webViews.get(handle);
+
+                if(webView.canGoForward()) {
+                    payload.boolValue = true;
+                    webView.goForward();
+                }
+                else {
+                    payload.boolValue = false;
+                }
+
+                synchronized (payload) {
+                    payload.notify();
+                }
+            }
+        });
+
+        try {
+            synchronized (payload) {
+                payload.wait();
+            }
+            return payload.boolValue;
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        return false;
+    }
+
+    public static boolean canGoBack(final int handle)
+    {
+        final AtomicWebViewPayload payload = new AtomicWebViewPayload();
+
+        activity.runOnUiThread(new Runnable() {
+
+            @Override
+            public void run() {
+                WebView webView = AtomicWebView.webViews.get(handle);
+
+                payload.boolValue = webView.canGoBack();
+
+                synchronized (payload) {
+                    payload.notify();
+                }
+            }
+        });
+
+        try {
+            synchronized (payload) {
+                payload.wait();
+            }
+            return payload.boolValue;
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        return false;
+    }
+
+    public static boolean canGoForward(final int handle)
+    {
+        final AtomicWebViewPayload payload = new AtomicWebViewPayload();
+
+        activity.runOnUiThread(new Runnable() {
+
+            @Override
+            public void run() {
+                WebView webView = AtomicWebView.webViews.get(handle);
+
+                payload.boolValue = webView.canGoForward();
+
+                synchronized (payload) {
+                    payload.notify();
+                }
+            }
+        });
+
+        try {
+            synchronized (payload) {
+                payload.wait();
+            }
+            return payload.boolValue;
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        return false;
+    }
+
+    public static void setDimensions(final int handle, final int x, final int y, final int width, final int height)
+    {
+        activity.runOnUiThread(new Runnable() {
+
+            @Override
+            public void run() {
+                WebView webView = AtomicWebView.webViews.get(handle);
+                RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)webView.getLayoutParams();
+                if(params != null)
+                {
+                    params.bottomMargin = y;
+                    params.leftMargin = x;
+                    params.width = width;
+                    params.height = height;
+                    webView.setLayoutParams(params);
+                }
+
+            }
+        });
+    }
+
+    public static void setRootLayout(ViewGroup value)
+    {
+        rootLayout = value;
+        activity = (Activity)rootLayout.getContext();
+    }
+
+    protected static ViewGroup rootLayout;
+    protected static Activity activity;
+    protected static Hashtable<Integer, WebView> webViews = new Hashtable<Integer, WebView>();
+}
+
+class AtomicWebViewPayload
+{
+    public Boolean boolValue;
+    public int intValue;
+}

+ 3 - 2
Script/Packages/Atomic/UI.json

@@ -1,6 +1,6 @@
 {
 {
 	"name" : "UI",
 	"name" : "UI",
-	"sources" : ["Source/Atomic/UI"],
+	"sources" : ["Source/Atomic/UI", "Source/Atomic/UI/Platform"],
 	"includes" : ["<Atomic/Graphics/Material.h>", "<Atomic/Scene/Node.h>", "<Atomic/Scene/Scene.h>", "<Atomic/Graphics/Texture2D.h>",
 	"includes" : ["<Atomic/Graphics/Material.h>", "<Atomic/Scene/Node.h>", "<Atomic/Scene/Scene.h>", "<Atomic/Graphics/Texture2D.h>",
 								"<Atomic/UI/UIBatch.h>"],
 								"<Atomic/UI/UIBatch.h>"],
 	"classes" : ["UI", "UIWidget", "UILayout", "UIView", "UIWindow", "UIButton", "UITextField",
 	"classes" : ["UI", "UIWidget", "UILayout", "UIView", "UIWindow", "UIButton", "UITextField",
@@ -10,7 +10,8 @@
 								"UISkinImage", "UITabContainer", "UISceneView", "UIPreferredSize", "UIDragObject",
 								"UISkinImage", "UITabContainer", "UISceneView", "UIPreferredSize", "UIDragObject",
 								"UIContainer", "UISection", "UIInlineSelect", "UITextureWidget", "UIColorWidget", "UIColorWheel",
 								"UIContainer", "UISection", "UIInlineSelect", "UITextureWidget", "UIColorWidget", "UIColorWheel",
 								"UIScrollContainer", "UISeparator", "UIDimmer", "UISelectDropdown", "UISlider", "UIBargraph",
 								"UIScrollContainer", "UISeparator", "UIDimmer", "UISelectDropdown", "UISlider", "UIBargraph",
-								"UIPromptWindow", "UIFinderWindow", "UIPulldownMenu", "UIRadioButton", "UIScrollBar"],
+								"UIPromptWindow", "UIFinderWindow", "UIPulldownMenu", "UIRadioButton", "UIScrollBar",
+								"UIPlatformWebView"],
 	"overloads" : {
 	"overloads" : {
 	},
 	},
 	"typescript_decl" : {
 	"typescript_decl" : {

+ 13 - 3
Source/Atomic/CMakeLists.txt

@@ -16,7 +16,10 @@ endif()
 file (GLOB ATOMIC2D_SOURCE Atomic2D/*.cpp Atomic2D/*.h)
 file (GLOB ATOMIC2D_SOURCE Atomic2D/*.cpp Atomic2D/*.h)
 file (GLOB SCENE_SOURCE Scene/*.cpp Scene/*.h)
 file (GLOB SCENE_SOURCE Scene/*.cpp Scene/*.h)
 file (GLOB UI_SOURCE UI/*.cpp UI/*.h)
 file (GLOB UI_SOURCE UI/*.cpp UI/*.h)
+file (GLOB SYSTEM_UI_PLATFORM_SOURCE UI/Platform/*.cpp UI/Platform/*.h)
+file (GLOB SYSTEM_UI_PLATFORM_OBJC_SOURCE UI/Platform/*.mm)
 file (GLOB SYSTEM_UI_SOURCE UI/SystemUI/*.cpp UI/SystemUI/*.h)
 file (GLOB SYSTEM_UI_SOURCE UI/SystemUI/*.cpp UI/SystemUI/*.h)
+
 file (GLOB PHYSICS_SOURCE Physics/*.cpp Physics/*.h)
 file (GLOB PHYSICS_SOURCE Physics/*.cpp Physics/*.h)
 file (GLOB NAVIGATION_SOURCE Navigation/*.cpp Navigation/*.h)
 file (GLOB NAVIGATION_SOURCE Navigation/*.cpp Navigation/*.h)
 file (GLOB ENVIRONMENT_SOURCE Environment/*.cpp Environment/*.h)
 file (GLOB ENVIRONMENT_SOURCE Environment/*.cpp Environment/*.h)
@@ -45,8 +48,15 @@ else()
     file (GLOB GRAPHICS_IMPL_SOURCE Graphics/OpenGL/*.cpp Graphics/OpenGL/*.h)
     file (GLOB GRAPHICS_IMPL_SOURCE Graphics/OpenGL/*.cpp Graphics/OpenGL/*.h)
 endif()
 endif()
 
 
-if (APPLE AND NOT IOS)
-    set (PLATFORM_SOURCE IO/MacFileWatcher.mm UI/UIDragDropMac.mm)
+if (APPLE)
+    set (SYSTEM_UI_PLATFORM_SOURCE ${SYSTEM_UI_PLATFORM_SOURCE} ${SYSTEM_UI_PLATFORM_OBJC_SOURCE})
+    if (NOT IOS)
+        set (PLATFORM_SOURCE IO/MacFileWatcher.mm UI/UIDragDropMac.mm)
+    endif()
+endif()
+
+if (ANDROID)
+    file (GLOB PLATFORM_SOURCE Platform/Android/*.cpp Platform/Android/*.h)
 endif()
 endif()
 
 
 set (SOURCE_FILES ${CONTAINER_SOURCE} ${CORE_SOURCE} ${ENGINE_SOURCE} ${INPUT_SOURCE}
 set (SOURCE_FILES ${CONTAINER_SOURCE} ${CORE_SOURCE} ${ENGINE_SOURCE} ${INPUT_SOURCE}
@@ -55,7 +65,7 @@ set (SOURCE_FILES ${CONTAINER_SOURCE} ${CORE_SOURCE} ${ENGINE_SOURCE} ${INPUT_SO
                   ${GRAPHICS_SOURCE} ${GRAPHICS_IMPL_SOURCE}
                   ${GRAPHICS_SOURCE} ${GRAPHICS_IMPL_SOURCE}
                   ${ATOMIC3D_SOURCE}
                   ${ATOMIC3D_SOURCE}
                   ${ATOMIC2D_SOURCE} ${ENVIRONMENT_SOURCE}
                   ${ATOMIC2D_SOURCE} ${ENVIRONMENT_SOURCE}
-                  ${SCENE_SOURCE} ${UI_SOURCE} ${SYSTEM_UI_SOURCE}
+                  ${SCENE_SOURCE} ${UI_SOURCE} ${SYSTEM_UI_PLATFORM_SOURCE} ${SYSTEM_UI_SOURCE}
                   ${WEB_SOURCE} ${SCRIPT_SOURCE} ${METRICS_SOURCE}
                   ${WEB_SOURCE} ${SCRIPT_SOURCE} ${METRICS_SOURCE}
                   ${PLATFORM_SOURCE} ${DATABASE_SOURCE} ${IK_SOURCE} ${NAVIGATION_SOURCE})
                   ${PLATFORM_SOURCE} ${DATABASE_SOURCE} ${IK_SOURCE} ${NAVIGATION_SOURCE})
 
 

+ 169 - 0
Source/Atomic/Platform/Android/AndroidJNI.cpp

@@ -0,0 +1,169 @@
+//
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+// Copyright (c) 2011-2017, The Game Engine Company LLC (Apache License 2.0)
+// Copyright (c) 2013-2014 Chukong Technologies Inc.
+// Copyright (c) 2010-2012 cocos2d-x.org
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <SDL.h>
+
+#include "../../IO/Log.h"
+
+#include "AndroidJNI.h"
+
+namespace Atomic
+{
+
+static bool GetJSEnv(JNIEnv **env)
+{
+    if (!env)
+    {
+        return false;
+
+    }
+
+    *env = (JNIEnv*)SDL_AndroidGetJNIEnv();
+
+    return *env != NULL;
+}
+
+
+JNIEnv* AtomicJNIMethodInfo::GetEnv()
+{
+    JNIEnv *env = NULL;
+    GetJSEnv(&env);
+    return env;
+}
+
+jclass AtomicJNI::GetClassID(const String &className, JNIEnv *env)
+{
+    JNIEnv *pEnv = env;
+    jclass ret   = 0;
+
+    if (!pEnv)
+    {
+        if (!GetJSEnv(&pEnv))
+        {
+            return 0;
+        }
+    }
+
+    ret = pEnv->FindClass(className.CString());
+
+    if (!ret)
+    {
+        ATOMIC_LOGWARNINGF("AtomicJNI::GetClassID() - Failed to find class of %s", className.CString());
+        return 0;
+    }
+
+    // ATOMIC_LOGINFOF("AtomicJNI::GetClassID() - Found class of %s", className.CString());
+
+    return ret;
+}
+
+
+bool AtomicJNI::GetStaticMethodInfo(AtomicJNIMethodInfo& methodinfo, const String& className, const String& methodName, const String& paramCode)
+{
+    jmethodID methodID = 0;
+    JNIEnv    *pEnv    = 0;
+
+    if (!GetJSEnv(&pEnv))
+    {
+        return false;
+    }
+
+    jclass classID = GetClassID(className, pEnv);
+
+    if (!classID)
+    {
+        return false;
+    }
+
+    methodID = pEnv->GetStaticMethodID(classID, methodName.CString(), paramCode.CString());
+
+    if (!methodID)
+    {
+        ATOMIC_LOGWARNINGF("AtomicJNI::GetStaticMethodInfo() - Failed to find method id of %s - %s", methodName.CString(), paramCode.CString());
+        return false;
+    }
+
+    methodinfo.classID  = classID;
+    methodinfo.methodID = methodID;
+
+    return true;
+
+}
+
+
+bool AtomicJNI::GetMethodInfo(AtomicJNIMethodInfo& methodinfo, const String& className, const String& methodName, const String& paramCode)
+{
+    jmethodID methodID = 0;
+    JNIEnv    *pEnv    = 0;
+
+    if (!GetJSEnv(&pEnv))
+    {
+        return false;
+    }
+
+    jclass classID = GetClassID(className, pEnv);
+
+    if (!classID)
+    {
+        return false;
+    }
+
+    methodID = pEnv->GetMethodID(classID, methodName.CString(), paramCode.CString());
+    if (!methodID)
+    {
+        ATOMIC_LOGWARNINGF("AtomicJNI::GetMethodInfo() - Failed to find method id of %s", methodName.CString());
+        return false;
+    }
+
+    methodinfo.classID  = classID;
+    methodinfo.methodID = methodID;
+
+    return true;
+}
+
+
+String AtomicJNI::JavaStringToString(jstring jstr)
+{
+    if (jstr == NULL)
+    {
+        return String::EMPTY;
+    }
+
+    JNIEnv *env = 0;
+
+    if (!GetJSEnv(&env))
+    {
+        return String::EMPTY;
+    }
+
+    const char *chars = env->GetStringUTFChars(jstr, NULL);
+    String ret(chars);
+    env->ReleaseStringUTFChars(jstr, chars);
+    return ret;
+}
+
+}
+
+

+ 54 - 0
Source/Atomic/Platform/Android/AndroidJNI.h

@@ -0,0 +1,54 @@
+//
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+// Copyright (c) 2011-2017, The Game Engine Company LLC (Apache License 2.0)
+// Copyright (c) 2013-2014 Chukong Technologies Inc.
+// Copyright (c) 2010-2012 cocos2d-x.org
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <jni.h>
+
+namespace Atomic
+{
+
+struct AtomicJNIMethodInfo
+{
+    JNIEnv *GetEnv();
+    jclass    classID;
+    jmethodID methodID;
+};
+
+class AtomicJNI
+{
+
+public:
+
+    static jclass GetClassID(const String& className, JNIEnv *env = 0);
+    static bool GetStaticMethodInfo(AtomicJNIMethodInfo& methodinfo, const String& className, const String& methodName, const String& paramCode);
+    static bool GetMethodInfo(AtomicJNIMethodInfo& methodinfo, const String& className, const String& methodName, const String& paramCode);
+    static String JavaStringToString(jstring jstr);
+
+private:
+};
+
+}
+

+ 86 - 0
Source/Atomic/UI/Platform/UIPlatformWebView.cpp

@@ -0,0 +1,86 @@
+
+
+#include "../../Core/CoreEvents.h"
+#include "../../IO/Log.h"
+#include "../UI.h"
+
+#include "UIPlatformWebView.h"
+
+
+namespace Atomic
+{
+
+AtomicWebViewHandle UIPlatformWebView::webViewHandleCounter_ = 1;
+HashMap<AtomicWebViewHandle, WeakPtr<UIPlatformWebView>> UIPlatformWebView::webViewLookup_;
+
+UIPlatformWebView::UIPlatformWebView(Context* context, bool createWidget) : UIWidget(context, false),
+    webViewHandle_(UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+{
+    if (createWidget)
+    {
+        widget_ = new tb::TBWidget();
+        widget_->SetDelegate(this);
+        widget_->SetIsFocusable(true);
+        GetSubsystem<UI>()->WrapWidget(this, widget_);
+    }
+
+    SubscribeToEvent(E_ENDFRAME, ATOMIC_HANDLER(UIPlatformWebView, HandleEndFrame));
+}
+
+UIPlatformWebView::~UIPlatformWebView()
+{
+    if (webViewHandle_ != UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        PlatformDestroyWebView();
+    }
+
+}
+
+void UIPlatformWebView::OnFocusChanged(bool focused)
+{
+    UIWidget::OnFocusChanged(focused);
+
+    // ATOMIC_LOGINFOF("UIPlatformWebView::OnFocusChanged(%s)", focused ? "true" : "false");
+}
+
+void UIPlatformWebView::OnRequestSent(const String& request)
+{
+
+}
+
+void UIPlatformWebView::OnError(const String& request)
+{
+
+}
+
+void UIPlatformWebView::HandleEndFrame(StringHash eventType, VariantMap& eventData)
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        if (!GetWidth() || !GetHeight())
+        {
+            return;
+        }
+
+        if (!PlatformCreateWebView())
+        {
+            return;
+        }
+
+        PlatformShowWebView();
+    }
+
+    if (!widget_->GetVisibilityCombined())
+    {
+        PlatformHideWebView();
+    }
+    else
+    {
+        PlatformShowWebView();
+    }
+
+    PlatformPositionWebView();
+}
+
+}
+

+ 95 - 0
Source/Atomic/UI/Platform/UIPlatformWebView.h

@@ -0,0 +1,95 @@
+//
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+// Copyright (c) 2011-2017, The Game Engine Company LLC (Apache License 2.0)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "../UIWidget.h"
+
+namespace Atomic
+{
+
+typedef int AtomicWebViewHandle;
+
+static const int UI_PLATFORM_WEBVIEW_INVALID_HANDLE = 0;
+
+// A platform WebView which always displays on top of other UIWidgets as it is, by necessity, a platform window
+class ATOMIC_API UIPlatformWebView : public UIWidget
+{
+    ATOMIC_OBJECT(UIPlatformWebView, UIWidget)
+
+public:
+
+    UIPlatformWebView(Context* context, bool createWidget = true);
+    virtual ~UIPlatformWebView();
+
+    /// Load the specified url into the main frame of the browser
+    void LoadURL(const String& url);
+
+    /// Reload the current page
+    void Reload();
+
+    /// Attempt to go back in the WebView associated with the specified handle. Returns false if there is nothing to go back to.
+    bool GoBack();
+
+    /// Attempt to go forward in the WebView associated with the specified handle. Returns false if there is nothing to go forward to.
+    bool GoForward();
+
+    /// Returns false if there is nothing to go back to. True otherwise.
+    bool CanGoBack() const;
+
+    /// Returns true if there is nothing to go back to. True otherwise.
+    bool CanGoForward() const;
+
+    /// Removes all listeners and stops interacting with WebViews on platforms that require it (iOS).
+    static void PauseAll();
+
+    /// Resumes normal operation of WebViews after pausing.
+    static void ResumeAll();
+
+    void OnRequestSent(const String& request);
+
+    void OnError(const String& request);
+
+    void OnFocusChanged(bool focused);
+
+private:
+
+    void HandleEndFrame(StringHash eventType, VariantMap& eventData);
+
+    void PlatformShowWebView(bool visible = true);    
+    void PlatformHideWebView() { PlatformShowWebView(false); }
+
+    bool PlatformCreateWebView();
+    void PlatformDestroyWebView();
+    void PlatformPositionWebView();
+
+    AtomicWebViewHandle webViewHandle_;
+
+    static HashMap<AtomicWebViewHandle, WeakPtr<UIPlatformWebView>> webViewLookup_;
+    static AtomicWebViewHandle webViewHandleCounter_;
+
+    static void DestroyAll();
+
+    String queuedRequest_;
+
+};
+
+}

+ 286 - 0
Source/Atomic/UI/Platform/UIPlatformWebViewAndroid.cpp

@@ -0,0 +1,286 @@
+//
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+// Copyright (c) 2011-2017, The Game Engine Company LLC (Apache License 2.0)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#ifdef ATOMIC_PLATFORM_ANDROID
+
+/* Notes
+
+1) This is based on the Android webview from the LoomSDK: https://github.com/LoomSDK/LoomSDK/blob/master/loom/common/platform/platformWebViewAndroid.cpp
+
+*/
+
+#include "../../IO/Log.h"
+#include "../../Graphics/Graphics.h"
+#include "UIPlatformWebView.h"
+#include "../../Platform/Android/AndroidJNI.h"
+
+namespace Atomic
+
+{
+
+//_________________________________________________________________________
+// JNI Helpers
+//_________________________________________________________________________
+static AtomicJNIMethodInfo gCreateMethodInfo;
+static AtomicJNIMethodInfo gDestroyMethodInfo;
+static AtomicJNIMethodInfo gDestroyAllMethodInfo;
+static AtomicJNIMethodInfo gShowMethodInfo;
+static AtomicJNIMethodInfo gHideMethodInfo;
+static AtomicJNIMethodInfo gRequestMethodInfo;
+static AtomicJNIMethodInfo gGoBackMethodInfo;
+static AtomicJNIMethodInfo gGoForwardMethodInfo;
+static AtomicJNIMethodInfo gCanGoBackMethodInfo;
+static AtomicJNIMethodInfo gCanGoForwardMethodInfo;
+static AtomicJNIMethodInfo gSetDimensionsMethodInfo;
+
+static void android_webViewEnsureInitialized()
+{
+    static bool initialized = false;
+
+    if (!initialized)
+    {
+        // initialize all of our jni method infos
+        AtomicJNI::GetStaticMethodInfo(gCreateMethodInfo,
+                                     "com/atomicgameengine/player/AtomicWebView",
+                                     "create",
+                                     "(IJJ)Z");
+
+        AtomicJNI::GetStaticMethodInfo(gDestroyMethodInfo,
+                                     "com/atomicgameengine/player/AtomicWebView",
+                                     "destroy",
+                                     "(I)V");
+
+        AtomicJNI::GetStaticMethodInfo(gDestroyAllMethodInfo,
+                                     "com/atomicgameengine/player/AtomicWebView",
+                                     "destroyAll",
+                                     "()V");
+
+        AtomicJNI::GetStaticMethodInfo(gShowMethodInfo,
+                                     "com/atomicgameengine/player/AtomicWebView",
+                                     "show",
+                                     "(I)V");
+
+        AtomicJNI::GetStaticMethodInfo(gHideMethodInfo,
+                                     "com/atomicgameengine/player/AtomicWebView",
+                                     "hide",
+                                     "(I)V");
+
+        AtomicJNI::GetStaticMethodInfo(gRequestMethodInfo,
+                                     "com/atomicgameengine/player/AtomicWebView",
+                                     "request",
+                                     "(ILjava/lang/String;)V");
+
+        AtomicJNI::GetStaticMethodInfo(gGoBackMethodInfo,
+                                     "com/atomicgameengine/player/AtomicWebView",
+                                     "goBack",
+                                     "(I)Z");
+
+        AtomicJNI::GetStaticMethodInfo(gGoForwardMethodInfo,
+                                     "com/atomicgameengine/player/AtomicWebView",
+                                     "goForward",
+                                     "(I)Z");
+
+        AtomicJNI::GetStaticMethodInfo(gCanGoBackMethodInfo,
+                                     "com/atomicgameengine/player/AtomicWebView",
+                                     "canGoBack",
+                                     "(I)Z");
+
+        AtomicJNI::GetStaticMethodInfo(gCanGoForwardMethodInfo,
+                                     "com/atomicgameengine/player/AtomicWebView",
+                                     "canGoForward",
+                                     "(I)Z");
+
+        AtomicJNI::GetStaticMethodInfo(gSetDimensionsMethodInfo,
+                                     "com/atomicgameengine/player/AtomicWebView",
+                                     "setDimensions",
+                                     "(IIIII)V");
+
+        initialized = true;
+    }
+}
+
+
+bool UIPlatformWebView::PlatformCreateWebView()
+{
+    if (webViewHandle_ != UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        ATOMIC_LOGWARNING("UIPlatformWebView::CreatePlatformWebView() - Attempting to create platform webview with valid handle");
+        return false;
+    }
+
+    webViewHandle_ = webViewHandleCounter_++;
+
+    android_webViewEnsureInitialized();
+    return (bool) gCreateMethodInfo.GetEnv()->CallStaticBooleanMethod(gCreateMethodInfo.classID, gCreateMethodInfo.methodID, (jint) webViewHandle_, (jlong)0, (jlong)0);
+
+}
+
+void UIPlatformWebView::PlatformShowWebView(bool visible)
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        ATOMIC_LOGWARNING("UIPlatformWebView::ShowPlatformWebView() - Invalid webview handle");
+        return;
+    }
+
+    if (visible)
+    {
+        gShowMethodInfo.GetEnv()->CallStaticVoidMethod(gShowMethodInfo.classID, gShowMethodInfo.methodID, (jint)webViewHandle_);
+    }
+    else
+    {
+        gHideMethodInfo.GetEnv()->CallStaticVoidMethod(gHideMethodInfo.classID, gHideMethodInfo.methodID, (jint)webViewHandle_);
+    }
+}
+
+void UIPlatformWebView::PlatformDestroyWebView()
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        return;
+    }
+
+    webViewLookup_.Erase(webViewHandle_);
+
+    gDestroyMethodInfo.GetEnv()->CallStaticVoidMethod(gDestroyMethodInfo.classID, gDestroyMethodInfo.methodID, (jint)webViewHandle_);
+
+    webViewHandle_ = UI_PLATFORM_WEBVIEW_INVALID_HANDLE;
+
+}
+
+void UIPlatformWebView::PlatformPositionWebView()
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        return;
+    }
+
+    IntVector2 rootPos = ConvertToRoot(IntVector2( 0, 0));
+
+    int height = GetSubsystem<Graphics>()->GetHeight();
+    int posY = height - GetHeight() - rootPos.y_;
+
+    gSetDimensionsMethodInfo.GetEnv()->CallStaticVoidMethod(gSetDimensionsMethodInfo.classID, gSetDimensionsMethodInfo.methodID, (jint)webViewHandle_, (jint)rootPos.x_, (jint)posY, (jint)GetWidth(), (jint)GetHeight());
+
+}
+
+void UIPlatformWebView::DestroyAll()
+{
+    android_webViewEnsureInitialized();
+    gDestroyAllMethodInfo.GetEnv()->CallStaticVoidMethod(gDestroyAllMethodInfo.classID, gDestroyAllMethodInfo.methodID);
+}
+
+bool UIPlatformWebView::GoBack()
+{
+
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        return false;
+    }
+
+    jboolean result = gGoBackMethodInfo.GetEnv()->CallStaticBooleanMethod(gGoBackMethodInfo.classID, gGoBackMethodInfo.methodID, (jint)webViewHandle_);
+    return (bool)result;
+}
+
+bool UIPlatformWebView::GoForward()
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        return false;
+    }
+
+    jboolean result = gGoForwardMethodInfo.GetEnv()->CallStaticBooleanMethod(gGoForwardMethodInfo.classID, gGoForwardMethodInfo.methodID, (jint)webViewHandle_);
+    return (bool)result;
+}
+
+bool UIPlatformWebView::CanGoBack() const
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        return false;
+    }
+
+    jboolean result = gCanGoBackMethodInfo.GetEnv()->CallStaticBooleanMethod(gCanGoBackMethodInfo.classID, gCanGoBackMethodInfo.methodID, (jint)webViewHandle_);
+    return (bool)result;
+}
+
+bool UIPlatformWebView::CanGoForward() const
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        return false;
+    }
+
+    jboolean result = gCanGoForwardMethodInfo.GetEnv()->CallStaticBooleanMethod(gCanGoForwardMethodInfo.classID, gCanGoForwardMethodInfo.methodID, (jint)webViewHandle_);
+    return (bool)result;
+}
+
+void UIPlatformWebView::PauseAll()
+{
+}
+
+void UIPlatformWebView::ResumeAll()
+{
+}
+
+void UIPlatformWebView::LoadURL(const String& url)
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        return;
+    }
+
+    jstring urlString = gRequestMethodInfo.GetEnv()->NewStringUTF(url.CString());
+    gRequestMethodInfo.GetEnv()->CallStaticVoidMethod(gRequestMethodInfo.classID, gRequestMethodInfo.methodID, (jint)webViewHandle_, urlString);
+    gRequestMethodInfo.GetEnv()->DeleteLocalRef(urlString);
+}
+
+void UIPlatformWebView::Reload()
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        return;
+    }
+
+}
+
+
+}
+
+extern "C"
+{
+void Java_com_atomicgameengine_player_AtomicWebView_nativeCallback(JNIEnv *env, jobject thiz, jstring data, jlong callback, jlong payload, jint type)
+{
+    /*
+    atomic_webViewCallback cb          = (atomic_webViewCallback)callback;
+    const char *dataString = env->GetStringUTFChars(data, 0);
+
+    cb((void *)payload, (atomic_webViewCallbackType)type, dataString);
+
+    env->ReleaseStringUTFChars(data, dataString);
+    */
+}
+}
+
+
+#endif

+ 503 - 0
Source/Atomic/UI/Platform/UIPlatformWebViewApple.mm

@@ -0,0 +1,503 @@
+//
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+// Copyright (c) 2011-2017, The Game Engine Company LLC (Apache License 2.0)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#import <Foundation/Foundation.h>
+
+/*
+
+Notes:
+
+1) This is based on the Cocoa/iOS webview from the LoomSDK: https://github.com/LoomSDK/LoomSDK/blob/master/loom/common/platform/platformWebViewCocoa.mm
+2) macOS mouse tracking complicates the platform's webview and causes issues with the Atomic UI controls beneath it
+
+*/
+
+#ifdef ATOMIC_PLATFORM_OSX
+
+#import <WebKit/WebKit.h>
+typedef WebView CocoaWebView;
+typedef NSRect CocoaRect;
+typedef NSView CocoaView;
+#define CocoaMakeRect NSMakeRect
+
+#else
+
+#import <UIKit/UIKit.h>
+typedef UIWebView CocoaWebView;
+typedef CGRect CocoaRect;
+typedef UIView CocoaView;
+#define CocoaMakeRect CGRectMake
+
+#endif
+
+#include "../../IO/Log.h"
+#include "UIPlatformWebView.h"
+
+using namespace Atomic;
+
+//_________________________________________________________________________
+// Helpers
+//_________________________________________________________________________
+
+#ifdef ATOMIC_PLATFORM_IOS
+static float pixelsToPoints(float pixels)
+{
+    float scale = [UIScreen mainScreen].scale;
+    return pixels / scale;
+}
+#endif
+
+
+static CocoaView* GetMainView()
+{
+#ifdef ATOMIC_PLATFORM_OSX
+    return [[[[NSApplication sharedApplication] windows] objectAtIndex:0] contentView];
+#else
+    return [[[[UIApplication sharedApplication] keyWindow] rootViewController] view];
+#endif
+}
+
+#ifdef ATOMIC_PLATFORM_OSX
+
+static NSWindow* GetMainWindow()
+{
+    return [[[NSApplication sharedApplication] windows] objectAtIndex:0];
+}
+
+#endif
+
+@interface AtomicWebViewDelegate : NSObject
+{
+    UIPlatformWebView* uiWebView;
+}
+
+-(id)initWithWebView:(UIPlatformWebView*)webview;
+
+@end
+
+@interface WebViewRef : NSObject {
+    CocoaRect _rect;
+    CocoaWebView* _view;
+    AtomicWebViewDelegate* _delegate;
+}
+@property CocoaRect rect;
+@property (retain) CocoaWebView* view;
+@property (retain) AtomicWebViewDelegate* delegate;
+@end
+
+
+@implementation WebViewRef
+
+@synthesize view = _view;
+@synthesize delegate = _delegate;
+
+- (CocoaRect)rect
+{
+    return _rect;
+}
+- (void)setRect:(CocoaRect)newRect;
+{
+    _rect = newRect;
+    CocoaRect frame;
+#ifdef ATOMIC_PLATFORM_OSX
+    frame = newRect;
+#else
+    frame.size.width = pixelsToPoints(newRect.size.width);
+    frame.size.height = pixelsToPoints(newRect.size.height);
+    frame.origin.x = pixelsToPoints(newRect.origin.x);
+    frame.origin.y = getMainView().frame.size.height - frame.size.height - pixelsToPoints(newRect.origin.y);
+#endif
+    self.view.frame = frame;
+}
+
+@end
+
+static NSMutableDictionary* gWebViews;
+NSMutableDictionary* webViews()
+{
+    if(gWebViews == NULL)
+        gWebViews = [[NSMutableDictionary dictionary] retain];
+
+    return gWebViews;
+}
+
+WebViewRef* GetWebViewRef(AtomicWebViewHandle handle)
+{
+    return [webViews() objectForKey:[NSNumber numberWithInt:handle]];
+}
+
+//_________________________________________________________________________
+// WebView Delegate
+//_________________________________________________________________________
+@implementation AtomicWebViewDelegate
+
+-(id)initWithWebView:(UIPlatformWebView*)webview
+{
+    self = [self init];
+    uiWebView = webview;
+    return self;
+}
+
+
+#ifdef ATOMIC_PLATFORM_OSX
+
+- (void)webView:(CocoaWebView *)sender didStartProvisionalLoadForFrame:(WebFrame *)frame
+{
+    if(frame == [sender mainFrame])
+    {
+        NSURLRequest* request = frame.provisionalDataSource.request;
+        NSString *urlString = [[request URL] absoluteString];
+        uiWebView->OnRequestSent([urlString cStringUsingEncoding:1]);
+    }
+}
+
+- (void)webView:(CocoaWebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
+{
+    if(frame == [sender mainFrame])
+    {
+        NSInteger code = [error code];
+        NSString *codeString = [NSString stringWithFormat:@"WebKit Error code: %ld",(long)code];
+        uiWebView->OnError([codeString cStringUsingEncoding:1]);
+    }
+}
+
+- (void)webView:(CocoaWebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
+{
+    if(frame == [sender mainFrame])
+    {
+        NSInteger code = [error code];
+        NSString *codeString = [NSString stringWithFormat:@"WebKit Error code: %ld",(long)code];
+        uiWebView->OnError([codeString cStringUsingEncoding:1]);
+    }
+}
+
+#else
+
+- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
+{
+    NSString *urlString = [[request URL] absoluteString];
+    callback(payload, WEBVIEW_REQUEST_SENT, [urlString cStringUsingEncoding:1]);
+
+    return YES;
+}
+
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
+{
+    NSInteger code = [error code];
+    NSString *codeString = [NSString stringWithFormat:@"WebKit Error code: %ld",(long)code];
+    callback(payload, WEBVIEW_REQUEST_ERROR, [codeString cStringUsingEncoding:1]);
+}
+
+#endif
+
+
+@end
+
+namespace Atomic
+{
+
+bool UIPlatformWebView::PlatformCreateWebView()
+{
+    if (webViewHandle_ != UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        ATOMIC_LOGWARNING("UIPlatformWebView::CreatePlatformWebView() - Attempting to create platform webview with valid handle");
+        return false;
+    }
+
+    webViewHandle_ = webViewHandleCounter_++;
+
+    webViewLookup_[webViewHandle_]  = this;
+
+    IntVector2 rootPos = ConvertToRoot();
+
+    CocoaRect bounds;
+    bounds.size.width = (float) GetWidth();
+    bounds.size.height = (float) GetHeight();
+
+    float mainHeight = GetMainView().frame.size.height;
+
+    bounds.origin.x = (float) rootPos.x_;
+    bounds.origin.y = mainHeight - bounds.size.height - rootPos.y_;
+
+    CocoaWebView* webView = [[[CocoaWebView alloc] initWithFrame:bounds] retain];
+    AtomicWebViewDelegate* delegate = [[[AtomicWebViewDelegate alloc] initWithWebView:this] retain];
+
+#ifdef ATOMIC_PLATFORM_OSX
+    [webView setFrameLoadDelegate:(id)delegate];
+    [webView setWantsLayer:YES];
+#else
+    [webView setDelegate:delegate];
+#endif
+
+    WebViewRef *ref = [WebViewRef alloc];
+    ref.view = webView;
+    ref.rect = webView.frame;
+    ref.delegate = delegate;
+
+    [webViews() setObject:ref forKey:[NSNumber numberWithInt:webViewHandle_]];
+
+    if (!queuedRequest_.Empty())
+    {
+        LoadURL(queuedRequest_);
+        queuedRequest_.Clear();
+    }
+
+    return true;
+
+}
+
+void UIPlatformWebView::PlatformShowWebView(bool visible)
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        ATOMIC_LOGWARNING("UIPlatformWebView::ShowPlatformWebView() - Invalid webview handle");
+        return;
+    }
+
+    CocoaWebView* webView = GetWebViewRef(webViewHandle_).view;
+
+    if (!webView)
+    {
+        ATOMIC_LOGERROR("UIPlatformWebView::ShowPlatformWebView() - Unable to retrieve webview from handle");
+        return;
+    }
+
+    if (visible)
+    {
+
+#ifdef ATOMIC_PLATFORM_OSX
+
+        if (webView.superview != GetMainView())
+        {
+            [GetMainView() addSubview:webView];
+        }
+
+#endif
+
+    }
+    else
+    {
+#ifdef ATOMIC_PLATFORM_OSX
+
+        if (webView.superview == GetMainView())
+        {
+            [webView removeFromSuperview];
+        }
+
+#endif
+    }
+}
+
+void UIPlatformWebView::PlatformDestroyWebView()
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        return;
+    }
+
+    webViewLookup_.Erase(webViewHandle_);
+
+    WebViewRef* ref = GetWebViewRef(webViewHandle_);
+
+    CocoaWebView* webView = ref.view;
+    AtomicWebViewDelegate* delegate = ref.delegate;
+
+    [webView removeFromSuperview];
+    [webView release];
+    [delegate release];
+    [ref release];
+
+    [webViews() removeObjectForKey:[NSNumber numberWithInt:webViewHandle_]];
+
+    webViewHandle_ = UI_PLATFORM_WEBVIEW_INVALID_HANDLE;
+
+}
+
+void UIPlatformWebView::PlatformPositionWebView()
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        return;
+    }
+
+    WebViewRef* ref = GetWebViewRef(webViewHandle_);
+
+    IntVector2 rootPos = ConvertToRoot(IntVector2( 0, 0));
+
+    CocoaRect frame;
+    frame.size.width = (float) GetWidth();
+    frame.size.height = (float) GetHeight();
+
+    float mainHeight = GetMainView().frame.size.height;
+
+    frame.origin.x = (float) rootPos.x_;
+    frame.origin.y = mainHeight - frame.size.height - rootPos.y_;
+
+    if (frame.origin.x == ref.rect.origin.x &&
+        frame.origin.y == ref.rect.origin.y &&
+        frame.size.width == ref.rect.size.width &&
+        frame.size.height == ref.rect.size.height )
+    {
+        return;
+    }
+
+
+    ref.rect = frame;
+}
+
+void UIPlatformWebView::DestroyAll()
+{
+    NSArray* keys = [webViews() allKeys];
+    for (int i=0; i<[keys count]; i++)
+    {
+        NSNumber* num = [keys objectAtIndex:i];
+        const WeakPtr<UIPlatformWebView>& ptr = webViewLookup_[[num intValue]];
+        if (ptr.NotNull())
+        {
+            ptr->PlatformDestroyWebView();
+        }
+    }
+}
+
+bool UIPlatformWebView::GoBack()
+{
+
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        return false;
+    }
+
+    CocoaWebView* webView = GetWebViewRef(webViewHandle_).view;
+
+    if (![webView canGoBack]) return false;
+
+    [webView goBack];
+    return true;
+}
+
+bool UIPlatformWebView::GoForward()
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        return false;
+    }
+
+    CocoaWebView* webView = GetWebViewRef(webViewHandle_).view;
+
+    if (![webView canGoForward]) return false;
+
+    [webView goForward];
+
+    return true;
+}
+
+bool UIPlatformWebView::CanGoBack() const
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        return false;
+    }
+
+    CocoaWebView* webView = GetWebViewRef(webViewHandle_).view;
+
+    return [webView canGoBack];
+}
+
+bool UIPlatformWebView::CanGoForward() const
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        return false;
+    }
+
+    CocoaWebView* webView = GetWebViewRef(webViewHandle_).view;
+
+    return [webView canGoForward];
+}
+
+void UIPlatformWebView::PauseAll()
+{
+#ifdef ATOMIC_PLATFORM_IOS
+    NSArray* keys = [webViews() allKeys];
+    for (int i=0; i<[keys count]; i++)
+    {
+        NSNumber* num = [keys objectAtIndex:i];
+        WebViewRef* ref = getWebViewRef([num intValue]);
+        CocoaWebView* webView = ref.view;
+        [webView setDelegate:nil];
+    }
+#endif
+}
+
+void UIPlatformWebView::ResumeAll()
+{
+#ifdef ATOMIC_PLATFORM_IOS
+    NSArray* keys = [webViews() allKeys];
+    for (int i=0; i<[keys count]; i++)
+    {
+        NSNumber* num = [keys objectAtIndex:i];
+        WebViewRef* ref = getWebViewRef([num intValue]);
+        CocoaWebView* webView = ref.view;
+        [webView setDelegate:(id)ref.delegate];
+    }
+#endif
+}
+
+void UIPlatformWebView::Reload()
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        return;
+    }
+
+    CocoaWebView* webView = GetWebViewRef(webViewHandle_).view;
+
+#ifdef ATOMIC_PLATFORM_OSX
+    [[webView mainFrame] reload];
+#else
+    [webView reload];
+#endif
+
+}
+
+void UIPlatformWebView::LoadURL(const String& url)
+{
+    if (webViewHandle_ == UI_PLATFORM_WEBVIEW_INVALID_HANDLE)
+    {
+        queuedRequest_ = url;
+        return;
+    }
+
+    CocoaWebView* webView = GetWebViewRef(webViewHandle_).view;
+    NSURL* urlObj = [NSURL URLWithString:[NSString stringWithUTF8String:url.CString()]];
+    NSURLRequest* request = [NSURLRequest requestWithURL:urlObj];
+
+#ifdef ATOMIC_PLATFORM_OSX
+    [[webView mainFrame] loadRequest:request];
+#else
+    [webView loadRequest:request];
+#endif
+}
+
+}
+

+ 1 - 1
Source/Atomic/UI/UIViewInput.cpp

@@ -71,7 +71,7 @@ bool UIView::FilterDefaultInput(bool keyEvent) const
     if (!keyEvent && !GetMouseEnabled())
     if (!keyEvent && !GetMouseEnabled())
         return true;
         return true;
 
 
-    if (keyEvent && !GetKeyboardEnabled() || ui_->keyboardDisabled_)
+    if (keyEvent && (!GetKeyboardEnabled() || ui_->keyboardDisabled_))
         return true;
         return true;
 
 
     return false;
     return false;

+ 2 - 2
Source/Atomic/UI/UIWidget.h

@@ -218,7 +218,7 @@ class ATOMIC_API UIWidget : public Object, public tb::TBWidgetDelegate
 
 
     /// Set focus to first widget which accepts it
     /// Set focus to first widget which accepts it
     void SetFocusRecursive();
     void SetFocusRecursive();
-    void OnFocusChanged(bool focused);
+    virtual void OnFocusChanged(bool focused);
 
 
     void SetState(UI_WIDGET_STATE state, bool on);
     void SetState(UI_WIDGET_STATE state, bool on);
     bool GetState(UI_WIDGET_STATE state);
     bool GetState(UI_WIDGET_STATE state);
@@ -319,7 +319,7 @@ class ATOMIC_API UIWidget : public Object, public tb::TBWidgetDelegate
     int GetLayoutMaxHeight();
     int GetLayoutMaxHeight();
 
 
     //  Get x and y (relative to this widget) relative to the upper left corner of the root widget
     //  Get x and y (relative to this widget) relative to the upper left corner of the root widget
-    IntVector2 ConvertToRoot(const IntVector2 position ) const;
+    IntVector2 ConvertToRoot(const IntVector2 position = IntVector2::ZERO) const;
 
 
     // Get x and y (relative to the upper left corner of the root widget) relative to this widget
     // Get x and y (relative to the upper left corner of the root widget) relative to this widget
     IntVector2 ConvertFromRoot(const IntVector2 position) const;
     IntVector2 ConvertFromRoot(const IntVector2 position) const;

+ 1 - 1
Source/AtomicJS/Javascript/JSAtomic.cpp

@@ -389,7 +389,7 @@ void jsapi_init_atomic(JSVM* vm)
     duk_put_prop_string(ctx, -2, "print");
     duk_put_prop_string(ctx, -2, "print");
 
 
     String platform = GetPlatform();
     String platform = GetPlatform();
-    if (platform == "Mac OS X")
+    if (platform == "macOS")
         platform = "MacOSX";
         platform = "MacOSX";
 
 
     duk_push_string(ctx, platform.CString());
     duk_push_string(ctx, platform.CString());

+ 1 - 1
Source/AtomicNET/NETNative/CMakeLists.txt

@@ -32,7 +32,7 @@ target_include_directories(AtomicNETNative PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
 
 
 if (APPLE)
 if (APPLE)
     if (NOT IOS)
     if (NOT IOS)
-        target_link_libraries( AtomicNETNative "-stdlib=libc++ -framework AudioToolbox -framework Carbon -framework Cocoa -framework CoreAudio -framework CoreVideo -framework ForceFeedback -framework IOKit -framework OpenGL -framework CoreServices -framework Security -framework SystemConfiguration")
+        target_link_libraries( AtomicNETNative "-stdlib=libc++ -framework AudioToolbox -framework Carbon -framework Cocoa -framework CoreAudio -framework CoreVideo -framework ForceFeedback -framework IOKit -framework OpenGL -framework CoreServices -framework Security -framework SystemConfiguration -framework WebKit")
     else()
     else()
         set_target_properties(AtomicNETNative PROPERTIES
         set_target_properties(AtomicNETNative PROPERTIES
             FRAMEWORK TRUE
             FRAMEWORK TRUE

+ 6 - 1
Source/ThirdParty/easy_profiler/easy_profiler_core/CMakeLists.txt

@@ -121,7 +121,12 @@ easy_define_target_option(easy_profiler EASY_OPTION_PREDEFINED_COLORS EASY_OPTIO
 
 
 if (UNIX)
 if (UNIX)
     target_compile_options(easy_profiler PRIVATE -Wall -Wno-long-long -Wno-reorder -Wno-braced-scalar-init -pedantic -O3)
     target_compile_options(easy_profiler PRIVATE -Wall -Wno-long-long -Wno-reorder -Wno-braced-scalar-init -pedantic -O3)
-    target_link_libraries(easy_profiler pthread)
+    # ATOMIC BEGIN
+    # Don't link pthread on Android platform
+    if (NOT ANDROID)
+        target_link_libraries(easy_profiler pthread)
+    endif()
+    # ATOMIC END
 elseif (WIN32)
 elseif (WIN32)
     target_compile_definitions(easy_profiler PRIVATE -D_WIN32_WINNT=0x0600 -D_CRT_SECURE_NO_WARNINGS -D_WINSOCK_DEPRECATED_NO_WARNINGS)
     target_compile_definitions(easy_profiler PRIVATE -D_WIN32_WINNT=0x0600 -D_CRT_SECURE_NO_WARNINGS -D_WINSOCK_DEPRECATED_NO_WARNINGS)
     target_link_libraries(easy_profiler ws2_32 psapi)
     target_link_libraries(easy_profiler ws2_32 psapi)

+ 17 - 0
Source/ToolCore/Build/AndroidProjectGenerator.cpp

@@ -107,10 +107,27 @@ bool AndroidProjectGenerator::GenerateActivitySource()
     String source;
     String source;
     source.AppendWithFormat("package %s;\n", packageName.CString());
     source.AppendWithFormat("package %s;\n", packageName.CString());
 
 
+    source += "import android.os.Bundle;\n";
+    source += "import android.view.ViewGroup;\n";
+    source += "import android.widget.FrameLayout;\n";
+    source += "import android.widget.RelativeLayout;\n";
     source += "import org.libsdl.app.SDLActivity;\n";
     source += "import org.libsdl.app.SDLActivity;\n";
+    source += "import com.atomicgameengine.player.AtomicWebView;\n";
 
 
     source += "public class AtomicGameEngine extends SDLActivity {\n";
     source += "public class AtomicGameEngine extends SDLActivity {\n";
 
 
+    source += "    @Override\n";
+    source += "    protected void onCreate(Bundle savedInstanceState) {\n";
+    source += "        super.onCreate(savedInstanceState);\n";
+    source += "        ViewGroup.LayoutParams framelayout_params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);\n";
+    source += "        FrameLayout framelayout = new FrameLayout(this);\n";
+    source += "        framelayout.setLayoutParams(framelayout_params);\n";
+    source += "        ViewGroup webViewGroup = new RelativeLayout(this);\n";
+    source += "        framelayout.addView(webViewGroup);\n";
+    source += "        mLayout.addView(framelayout);\n";
+    source += "        AtomicWebView.setRootLayout(webViewGroup);\n";
+    source += "    }\n";
+
     source += "}\n";
     source += "}\n";
 
 
     File file(context_, path + "/AtomicGameEngine.java", FILE_WRITE);
     File file(context_, path + "/AtomicGameEngine.java", FILE_WRITE);