Browse Source

Improve the hats response on screen joystick (in joystick event mode).
Refactor Samples base class to take advantage of the screen joystick.
Add second screen joystick layout for changing app settings.
Closes #264.

Yao Wei Tjong 姚伟忠 11 years ago
parent
commit
c78f34d5ca

+ 16 - 3
Bin/Data/Scripts/NinjaSnowWar.as

@@ -53,6 +53,7 @@ uint clientNodeID = 0;
 int clientScore = 0;
 int clientScore = 0;
 
 
 uint screenJoystickIndex = M_MAX_UNSIGNED;
 uint screenJoystickIndex = M_MAX_UNSIGNED;
+uint screenJoystickSettingsIndex = M_MAX_UNSIGNED;
 bool touchEnabled = false;
 bool touchEnabled = false;
 
 
 Array<Player> players;
 Array<Player> players;
@@ -203,7 +204,7 @@ void InitNetworking()
 void InitTouchInput()
 void InitTouchInput()
 {
 {
     touchEnabled = true;
     touchEnabled = true;
-    screenJoystickIndex = input.AddScreenJoystick();
+    screenJoystickIndex = input.AddScreenJoystick(cache.GetResource("XMLFile", "UI/ScreenJoystick_NinjaSnowWar.xml"));
 }
 }
 
 
 void CreateCamera()
 void CreateCamera()
@@ -493,9 +494,17 @@ void HandleKeyDown(StringHash eventType, VariantMap& eventData)
     {
     {
         gameScene.updateEnabled = !gameScene.updateEnabled;
         gameScene.updateEnabled = !gameScene.updateEnabled;
         if (!gameScene.updateEnabled)
         if (!gameScene.updateEnabled)
+        {
             SetMessage("PAUSED");
             SetMessage("PAUSED");
+            if (screenJoystickSettingsIndex == M_MAX_UNSIGNED)
+                screenJoystickSettingsIndex = input.AddScreenJoystick(cache.GetResource("XMLFile", "UI/ScreenJoystickSettings_NinjaSnowWar.xml"));
+            input.OpenJoystick(screenJoystickSettingsIndex);
+        }
         else
         else
+        {
             SetMessage("");
             SetMessage("");
+            input.CloseJoystick(screenJoystickSettingsIndex);
+        }
     }
     }
 }
 }
 
 
@@ -874,8 +883,12 @@ void UpdateControls()
             for (uint i = 0; i < input.numTouches; ++i)
             for (uint i = 0; i < input.numTouches; ++i)
             {
             {
                 TouchState@ touch = input.touches[i];
                 TouchState@ touch = input.touches[i];
-                playerControls.yaw += touchSensitivity * gameCamera.fov / graphics.height * touch.delta.x;
-                playerControls.pitch += touchSensitivity * gameCamera.fov / graphics.height * touch.delta.y;
+                if (touch.touchedElement.Get() is null)
+                {
+                    // Touch on empty space
+                    playerControls.yaw += touchSensitivity * gameCamera.fov / graphics.height * touch.delta.x;
+                    playerControls.pitch += touchSensitivity * gameCamera.fov / graphics.height * touch.delta.y;
+                }
             }
             }
         }
         }
 
 

BIN
Bin/Data/Textures/TouchInput.png


+ 15 - 14
Bin/Data/UI/ScreenJoystick.xml

@@ -13,16 +13,11 @@
         <attribute name="Hover Image Offset" value="0 0" />
         <attribute name="Hover Image Offset" value="0 0" />
         <attribute name="Pressed Image Offset" value="0 0" />
         <attribute name="Pressed Image Offset" value="0 0" />
         <element type="Text">
         <element type="Text">
-            <attribute name="Name" value="Label" />     
+            <attribute name="Name" value="Label" />
             <attribute name="Horiz Alignment" value="Center" />
             <attribute name="Horiz Alignment" value="Center" />
             <attribute name="Vert Alignment" value="Center" />
             <attribute name="Vert Alignment" value="Center" />
             <attribute name="Color" value="0 0 0 1" />
             <attribute name="Color" value="0 0 0 1" />
-            <attribute name="Text" value="Jump" />
-        </element>
-        <element type="Text">
-            <attribute name="Name" value="KeyBinding" />     
-            <attribute name="Is Visible" value="false" />
-            <attribute name="Text" value="SPACE" />
+            <attribute name="Text" value="Button0" />
         </element>
         </element>
     </element>
     </element>
     <element type="Button">
     <element type="Button">
@@ -36,18 +31,24 @@
         <attribute name="Hover Image Offset" value="0 0" />
         <attribute name="Hover Image Offset" value="0 0" />
         <attribute name="Pressed Image Offset" value="0 0" />
         <attribute name="Pressed Image Offset" value="0 0" />
         <element type="Text">
         <element type="Text">
-            <attribute name="Name" value="Label" />        
+            <attribute name="Name" value="Label" />
             <attribute name="Horiz Alignment" value="Center" />
             <attribute name="Horiz Alignment" value="Center" />
             <attribute name="Vert Alignment" value="Center" />
             <attribute name="Vert Alignment" value="Center" />
             <attribute name="Color" value="0 0 0 1" />
             <attribute name="Color" value="0 0 0 1" />
-            <attribute name="Text" value="Fire" />
-        </element>
-        <element type="Text">
-            <attribute name="Name" value="KeyBinding" />     
-            <attribute name="Is Visible" value="false" />
-            <attribute name="Text" value="LCTRL" />
+            <attribute name="Text" value="Button1" />
         </element>
         </element>
     </element>
     </element>
+    <element type="Button">
+        <attribute name="Name" value="Button2" />
+        <attribute name="Position" value="-12 12" />
+        <attribute name="Size" value="48 48" />
+        <attribute name="Horiz Alignment" value="Right" />
+        <attribute name="Opacity" value="0.5" />
+        <attribute name="Texture" value="Texture2D;Textures/TouchInput.png" />
+        <attribute name="Image Rect" value="192 0 240 48" />
+        <attribute name="Hover Image Offset" value="0 0" />
+        <attribute name="Pressed Image Offset" value="0 0" />
+    </element>
     <element type="Button">
     <element type="Button">
         <attribute name="Name" value="Hat0" />
         <attribute name="Name" value="Hat0" />
         <attribute name="Position" value="12 -12" />
         <attribute name="Position" value="12 -12" />

+ 73 - 0
Bin/Data/UI/ScreenJoystickSettings.xml

@@ -0,0 +1,73 @@
+<?xml version="1.0"?>
+<element>
+    <attribute name="Name" value="ScreenJoystickSettings" />
+    <attribute name="Size" value="800 600" />
+    <element type="Button">
+        <attribute name="Name" value="Button0" />
+        <attribute name="Position" value="-12 72" />
+        <attribute name="Size" value="96 96" />
+        <attribute name="Horiz Alignment" value="Right" />
+        <attribute name="Texture" value="Texture2D;Textures/TouchInput.png" />
+        <attribute name="Image Rect" value="96 0 192 96" />
+        <attribute name="Hover Image Offset" value="0 0" />
+        <attribute name="Pressed Image Offset" value="0 0" />
+        <element type="Text">
+            <attribute name="Name" value="Label" />
+            <attribute name="Horiz Alignment" value="Center" />
+            <attribute name="Vert Alignment" value="Center" />
+            <attribute name="Color" value="0 0 0 1" />
+            <attribute name="Text" value="Button0" />
+        </element>
+    </element>
+    <element type="Button">
+        <attribute name="Name" value="Button1" />
+        <attribute name="Position" value="-120 72" />
+        <attribute name="Size" value="96 96" />
+        <attribute name="Horiz Alignment" value="Right" />
+        <attribute name="Texture" value="Texture2D;Textures/TouchInput.png" />
+        <attribute name="Image Rect" value="96 0 192 96" />
+        <attribute name="Hover Image Offset" value="0 0" />
+        <attribute name="Pressed Image Offset" value="0 0" />
+        <element type="Text">
+            <attribute name="Name" value="Label" />
+            <attribute name="Horiz Alignment" value="Center" />
+            <attribute name="Vert Alignment" value="Center" />
+            <attribute name="Color" value="0 0 0 1" />
+            <attribute name="Text" value="Button1" />
+        </element>
+    </element>
+    <element type="Button">
+        <attribute name="Name" value="Button2" />
+        <attribute name="Position" value="-228 72" />
+        <attribute name="Size" value="96 96" />
+        <attribute name="Horiz Alignment" value="Right" />
+        <attribute name="Texture" value="Texture2D;Textures/TouchInput.png" />
+        <attribute name="Image Rect" value="96 0 192 96" />
+        <attribute name="Hover Image Offset" value="0 0" />
+        <attribute name="Pressed Image Offset" value="0 0" />
+        <element type="Text">
+            <attribute name="Name" value="Label" />
+            <attribute name="Horiz Alignment" value="Center" />
+            <attribute name="Vert Alignment" value="Center" />
+            <attribute name="Color" value="0 0 0 1" />
+            <attribute name="Text" value="Button2" />
+        </element>
+    </element>
+    <element type="Button">
+        <attribute name="Name" value="Button3" />
+        <attribute name="Position" value="-336 72" />
+        <attribute name="Size" value="96 96" />
+        <attribute name="Horiz Alignment" value="Right" />
+        <attribute name="Texture" value="Texture2D;Textures/TouchInput.png" />
+        <attribute name="Image Rect" value="96 0 192 96" />
+        <attribute name="Hover Image Offset" value="0 0" />
+        <attribute name="Pressed Image Offset" value="0 0" />
+        <element type="Text">
+            <attribute name="Name" value="Label" />
+            <attribute name="Horiz Alignment" value="Center" />
+            <attribute name="Vert Alignment" value="Center" />
+            <attribute name="Color" value="0 0 0 1" />
+            <attribute name="Text" value="Button3" />
+        </element>
+    </element>
+</element>

+ 34 - 0
Bin/Data/UI/ScreenJoystickSettings_NinjaSnowWar.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<element inherit="UI/ScreenJoystickSettings.xml">
+    <add sel="/element">
+        <attribute name="Position" value="0 18" />
+    </add>
+    <replace sel="/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value">Octree</replace>
+    <add sel="/element/element[./attribute[@name='Name' and @value='Button0']]">
+        <element type="Text">
+            <attribute name="Name" value="KeyBinding" />
+            <attribute name="Text" value="F4" />
+        </element>
+    </add>
+    <replace sel="/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value">Debug</replace>
+    <add sel="/element/element[./attribute[@name='Name' and @value='Button1']]">
+        <element type="Text">
+            <attribute name="Name" value="KeyBinding" />
+            <attribute name="Text" value="F3" />
+        </element>
+    </add>
+    <replace sel="/element/element[./attribute[@name='Name' and @value='Button2']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value">HUD</replace>
+    <add sel="/element/element[./attribute[@name='Name' and @value='Button2']]">
+        <element type="Text">
+            <attribute name="Name" value="KeyBinding" />
+            <attribute name="Text" value="F2" />
+        </element>
+    </add>
+    <replace sel="/element/element[./attribute[@name='Name' and @value='Button3']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value">Console</replace>
+    <add sel="/element/element[./attribute[@name='Name' and @value='Button3']]">
+        <element type="Text">
+            <attribute name="Name" value="KeyBinding" />
+            <attribute name="Text" value="F1" />
+        </element>
+    </add>
+</element>

+ 92 - 0
Bin/Data/UI/ScreenJoystickSettings_Samples.xml

@@ -0,0 +1,92 @@
+<?xml version="1.0"?>
+<element inherit="UI/ScreenJoystickSettings.xml">
+    <replace sel="/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value">S-Quality</replace>
+    <add sel="/element/element[./attribute[@name='Name' and @value='Button0']]">
+        <element type="Text">
+            <attribute name="Name" value="KeyBinding" />
+            <attribute name="Text" value="6" />
+        </element>
+    </add>
+    <replace sel="/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value">S-Size</replace>
+    <add sel="/element/element[./attribute[@name='Name' and @value='Button1']]">
+        <element type="Text">
+            <attribute name="Name" value="KeyBinding" />
+            <attribute name="Text" value="5" />
+        </element>
+    </add>
+    <replace sel="/element/element[./attribute[@name='Name' and @value='Button2']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value">Shadow</replace>
+    <add sel="/element/element[./attribute[@name='Name' and @value='Button2']]">
+        <element type="Text">
+            <attribute name="Name" value="KeyBinding" />
+            <attribute name="Text" value="4" />
+        </element>
+    </add>
+    <add sel="/element/element[./attribute[@name='Name' and @value='Button3']]">
+        <attribute name="Is Visible" value="false" />
+    </add>
+    <add sel="/element">
+        <element type="Button">
+            <attribute name="Name" value="Button4" />
+            <attribute name="Position" value="-12 180" />
+            <attribute name="Size" value="96 96" />
+            <attribute name="Horiz Alignment" value="Right" />
+            <attribute name="Texture" value="Texture2D;Textures/TouchInput.png" />
+            <attribute name="Image Rect" value="96 0 192 96" />
+            <attribute name="Hover Image Offset" value="0 0" />
+            <attribute name="Pressed Image Offset" value="0 0" />
+            <element type="Text">
+                <attribute name="Name" value="Label" />
+                <attribute name="Horiz Alignment" value="Center" />
+                <attribute name="Vert Alignment" value="Center" />
+                <attribute name="Color" value="0 0 0 1" />
+                <attribute name="Text" value="Occlusion" />
+            </element>
+            <element type="Text">
+                <attribute name="Name" value="KeyBinding" />
+                <attribute name="Text" value="7" />
+            </element>
+        </element>
+        <element type="Button">
+            <attribute name="Name" value="Button5" />
+            <attribute name="Position" value="-120 180" />
+            <attribute name="Size" value="96 96" />
+            <attribute name="Horiz Alignment" value="Right" />
+            <attribute name="Texture" value="Texture2D;Textures/TouchInput.png" />
+            <attribute name="Image Rect" value="96 0 192 96" />
+            <attribute name="Hover Image Offset" value="0 0" />
+            <attribute name="Pressed Image Offset" value="0 0" />
+            <element type="Text">
+                <attribute name="Name" value="Label" />
+                <attribute name="Horiz Alignment" value="Center" />
+                <attribute name="Vert Alignment" value="Center" />
+                <attribute name="Color" value="0 0 0 1" />
+                <attribute name="Text" value="Specular" />
+            </element>
+            <element type="Text">
+                <attribute name="Name" value="KeyBinding" />
+                <attribute name="Text" value="3" />
+            </element>
+        </element>
+        <element type="Button">
+            <attribute name="Name" value="Button6" />
+            <attribute name="Position" value="-228 180" />
+            <attribute name="Size" value="96 96" />
+            <attribute name="Horiz Alignment" value="Right" />
+            <attribute name="Texture" value="Texture2D;Textures/TouchInput.png" />
+            <attribute name="Image Rect" value="96 0 192 96" />
+            <attribute name="Hover Image Offset" value="0 0" />
+            <attribute name="Pressed Image Offset" value="0 0" />
+            <element type="Text">
+                <attribute name="Name" value="Label" />
+                <attribute name="Horiz Alignment" value="Center" />
+                <attribute name="Vert Alignment" value="Center" />
+                <attribute name="Color" value="0 0 0 1" />
+                <attribute name="Text" value="Material" />
+            </element>
+            <element type="Text">
+                <attribute name="Name" value="KeyBinding" />
+                <attribute name="Text" value="2" />
+            </element>
+        </element>
+    </add>
+</element>

+ 12 - 0
Bin/Data/UI/ScreenJoystick_NinjaSnowWar.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<element inherit="UI/ScreenJoystick.xml">
+    <replace sel="/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value">Jump</replace>
+    <replace sel="/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value">Fire</replace>
+    <replace sel="/element/element[./attribute[@name='Name' and @value='Button2']]/attribute[@name='Position']/@value">-12 36</replace>
+    <add sel="/element/element[./attribute[@name='Name' and @value='Button2']]">
+        <element type="Text">
+            <attribute name="Name" value="KeyBinding" />
+            <attribute name="Text" value="P" />
+        </element>
+    </add>
+</element>

+ 30 - 0
Bin/Data/UI/ScreenJoystick_Samples.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<element inherit="UI/ScreenJoystick.xml">
+    <replace sel="/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value">Console</replace>
+    <add sel="/element/element[./attribute[@name='Name' and @value='Button0']]">
+        <element type="Text">
+            <attribute name="Name" value="KeyBinding" />
+            <attribute name="Text" value="F1" />
+        </element>
+    </add>
+    <replace sel="/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value">HUD</replace>
+    <add sel="/element/element[./attribute[@name='Name' and @value='Button1']]">
+        <element type="Text">
+            <attribute name="Name" value="KeyBinding" />
+            <attribute name="Text" value="F2" />
+        </element>
+    </add>
+    <add sel="/element/element[./attribute[@name='Name' and @value='Button2']]">
+        <element type="Text">
+            <attribute name="Name" value="KeyBinding" />
+            <attribute name="Text" value="SELECT" />
+        </element>
+    </add>
+    <replace sel="/element/element[./attribute[@name='Name' and @value='Hat0']]/attribute[@name='Position']/@value">12 -76</replace>
+    <add sel="/element/element[./attribute[@name='Name' and @value='Hat0']]">
+        <element type="Text">
+            <attribute name="Name" value="KeyBinding" />
+            <attribute name="Text" value="WASD" />
+        </element>
+    </add>
+</element>

+ 189 - 95
Source/Engine/Input/Input.cpp

@@ -36,7 +36,6 @@
 #include "StringUtils.h"
 #include "StringUtils.h"
 #include "Text.h"
 #include "Text.h"
 #include "UI.h"
 #include "UI.h"
-#include "UIEvents.h"
 
 
 #include <cstring>
 #include <cstring>
 
 
@@ -54,8 +53,8 @@ namespace Urho3D
 {
 {
 
 
 const int SCREEN_JOYSTICK_START_INDEX = 1000;
 const int SCREEN_JOYSTICK_START_INDEX = 1000;
-const ShortStringHash VAR_INJECT_AS_KEY_EVENTS("VAR_INJECT_AS_KEY_EVENTS");
 const ShortStringHash VAR_BUTTON_KEY_BINDING("VAR_BUTTON_KEY_BINDING");
 const ShortStringHash VAR_BUTTON_KEY_BINDING("VAR_BUTTON_KEY_BINDING");
+const ShortStringHash VAR_LAST_KEYSYM("VAR_LAST_KEYSYM");
 const ShortStringHash VAR_SCREEN_JOYSTICK_INDEX("VAR_SCREEN_JOYSTICK_INDEX");
 const ShortStringHash VAR_SCREEN_JOYSTICK_INDEX("VAR_SCREEN_JOYSTICK_INDEX");
 
 
 /// Convert SDL keycode if necessary.
 /// Convert SDL keycode if necessary.
@@ -67,12 +66,6 @@ int ConvertSDLKeyCode(int keySym, int scanCode)
         return SDL_toupper(keySym);
         return SDL_toupper(keySym);
 }
 }
 
 
-JoystickState::~JoystickState()
-{
-    if (screenJoystick_)
-        screenJoystick_->Remove();
-}
-
 Input::Input(Context* context) :
 Input::Input(Context* context) :
     Object(context),
     Object(context),
     mouseButtonDown_(0),
     mouseButtonDown_(0),
@@ -244,8 +237,10 @@ bool Input::DetectJoysticks()
     return true;
     return true;
 }
 }
 
 
-unsigned Input::AddScreenJoystick(bool injectAsKeyEvents, XMLFile* layoutFile, XMLFile* styleFile)
+unsigned Input::AddScreenJoystick(XMLFile* layoutFile, XMLFile* styleFile)
 {
 {
+    static HashMap<String, int> keyBindings;
+
     if (!graphics_)
     if (!graphics_)
     {
     {
         LOGWARNING("Cannot add screen joystick in headless mode");
         LOGWARNING("Cannot add screen joystick in headless mode");
@@ -267,9 +262,9 @@ unsigned Input::AddScreenJoystick(bool injectAsKeyEvents, XMLFile* layoutFile, X
         return M_MAX_UNSIGNED;
         return M_MAX_UNSIGNED;
 
 
     screenJoystick->SetSize(ui->GetRoot()->GetSize());
     screenJoystick->SetSize(ui->GetRoot()->GetSize());
-    screenJoystick->SetVisible(false);      // Set to visible when it is "open" later
-    screenJoystick->SetVar(VAR_INJECT_AS_KEY_EVENTS, injectAsKeyEvents);
+    screenJoystick->SetVisible(false);      // Set to visible when it is opened later
     ui->GetRoot()->AddChild(screenJoystick);
     ui->GetRoot()->AddChild(screenJoystick);
+
     unsigned index = joysticks_.Size();
     unsigned index = joysticks_.Size();
     joysticks_.Resize(index + 1);
     joysticks_.Resize(index + 1);
     JoystickState& state = joysticks_[index];
     JoystickState& state = joysticks_[index];
@@ -290,64 +285,134 @@ unsigned Input::AddScreenJoystick(bool injectAsKeyEvents, XMLFile* layoutFile, X
             ++numButtons;
             ++numButtons;
 
 
             // Check whether the button has key binding
             // Check whether the button has key binding
-            if (injectAsKeyEvents)
+            Text* text = dynamic_cast<Text*>(element->GetChild("KeyBinding", false));
+            if (text)
             {
             {
-                Text* text = dynamic_cast<Text*>(element->GetChild("KeyBinding", false));
-                if (text)
+                text->SetVisible(false);
+                const String& key = text->GetText();
+                int keyBinding;
+                if (key.Length() == 1)
+                    keyBinding = key[0];
+                else
                 {
                 {
-                    text->SetVisible(false);
-                    const String& key = text->GetText();
-                    if (key.Length() == 1)
-                        element->SetVar(VAR_BUTTON_KEY_BINDING, key);
-                    else if (key == "SPACE")
-                        element->SetVar(VAR_BUTTON_KEY_BINDING, KEY_SPACE);
-                    else if (key == "LCTRL")
-                        element->SetVar(VAR_BUTTON_KEY_BINDING, KEY_LCTRL);
-                    else if (key == "RCTRL")
-                        element->SetVar(VAR_BUTTON_KEY_BINDING, KEY_RCTRL);
-                    else if (key == "LSHIFT")
-                        element->SetVar(VAR_BUTTON_KEY_BINDING, KEY_LSHIFT);
-                    else if (key == "RSHIFT")
-                        element->SetVar(VAR_BUTTON_KEY_BINDING, KEY_RSHIFT);
-                    else if (key == "LALT")
-                        element->SetVar(VAR_BUTTON_KEY_BINDING, KEY_LALT);
-                    else if (key == "RALT")
-                        element->SetVar(VAR_BUTTON_KEY_BINDING, KEY_RALT);
-                    else if (key == "TAB")
-                        element->SetVar(VAR_BUTTON_KEY_BINDING, KEY_TAB);
-                    else if (key == "RETURN")
-                        element->SetVar(VAR_BUTTON_KEY_BINDING, KEY_RETURN);
-                    else if (key == "LEFT")
-                        element->SetVar(VAR_BUTTON_KEY_BINDING, KEY_LEFT);
-                    else if (key == "RIGHT")
-                        element->SetVar(VAR_BUTTON_KEY_BINDING, KEY_RIGHT);
-                    else if (key == "UP")
-                        element->SetVar(VAR_BUTTON_KEY_BINDING, KEY_UP);
-                    else if (key == "DOWN")
-                        element->SetVar(VAR_BUTTON_KEY_BINDING, KEY_DOWN);
+                    if (keyBindings.Empty())
+                    {
+                        keyBindings.Insert(MakePair<String, int>("SPACE", KEY_SPACE));
+                        keyBindings.Insert(MakePair<String, int>("LCTRL", KEY_LCTRL));
+                        keyBindings.Insert(MakePair<String, int>("RCTRL", KEY_RCTRL));
+                        keyBindings.Insert(MakePair<String, int>("LSHIFT", KEY_LSHIFT));
+                        keyBindings.Insert(MakePair<String, int>("RSHIFT", KEY_RSHIFT));
+                        keyBindings.Insert(MakePair<String, int>("LALT", KEY_LALT));
+                        keyBindings.Insert(MakePair<String, int>("RALT", KEY_RALT));
+                        keyBindings.Insert(MakePair<String, int>("LGUI", KEY_LGUI));
+                        keyBindings.Insert(MakePair<String, int>("RGUI", KEY_RGUI));
+                        keyBindings.Insert(MakePair<String, int>("TAB", KEY_TAB));
+                        keyBindings.Insert(MakePair<String, int>("RETURN", KEY_RETURN));
+                        keyBindings.Insert(MakePair<String, int>("RETURN2", KEY_RETURN2));
+                        keyBindings.Insert(MakePair<String, int>("ENTER", KEY_KP_ENTER));
+                        keyBindings.Insert(MakePair<String, int>("SELECT", KEY_SELECT));
+                        keyBindings.Insert(MakePair<String, int>("LEFT", KEY_LEFT));
+                        keyBindings.Insert(MakePair<String, int>("RIGHT", KEY_RIGHT));
+                        keyBindings.Insert(MakePair<String, int>("UP", KEY_UP));
+                        keyBindings.Insert(MakePair<String, int>("DOWN", KEY_DOWN));
+                        keyBindings.Insert(MakePair<String, int>("F1", KEY_F1));
+                        keyBindings.Insert(MakePair<String, int>("F2", KEY_F2));
+                        keyBindings.Insert(MakePair<String, int>("F3", KEY_F3));
+                        keyBindings.Insert(MakePair<String, int>("F4", KEY_F4));
+                        keyBindings.Insert(MakePair<String, int>("F5", KEY_F5));
+                        keyBindings.Insert(MakePair<String, int>("F6", KEY_F6));
+                        keyBindings.Insert(MakePair<String, int>("F7", KEY_F7));
+                        keyBindings.Insert(MakePair<String, int>("F8", KEY_F8));
+                        keyBindings.Insert(MakePair<String, int>("F9", KEY_F9));
+                        keyBindings.Insert(MakePair<String, int>("F10", KEY_F10));
+                        keyBindings.Insert(MakePair<String, int>("F11", KEY_F11));
+                        keyBindings.Insert(MakePair<String, int>("F12", KEY_F12));
+                    }
+
+                    HashMap<String, int>::Iterator i = keyBindings.Find(key);
+                    if (i != keyBindings.End())
+                        keyBinding = i->second_;
+                    else
+                    {
+                        LOGERRORF("Unsupported key binding: %s", key.CString());
+                        keyBinding = M_MAX_INT;
+                    }
                 }
                 }
+
+                if (keyBinding != M_MAX_INT)
+                    element->SetVar(VAR_BUTTON_KEY_BINDING, keyBinding);
             }
             }
         }
         }
         else if (name.StartsWith("Axis"))
         else if (name.StartsWith("Axis"))
+        {
             ++numAxes;
             ++numAxes;
+
+            ///\todo Axis emulation for screen joystick is not fully supported yet.
+            LOGWARNING("Axis emulation for screen joystick is not fully supported yet");
+        }
         else if (name.StartsWith("Hat"))
         else if (name.StartsWith("Hat"))
+        {
             ++numHats;
             ++numHats;
+
+            Text* text = dynamic_cast<Text*>(element->GetChild("KeyBinding", false));
+            if (text)
+            {
+                text->SetVisible(false);
+                String keyBinding = text->GetText();
+                if (keyBinding.Length() != 4)
+                {
+                    LOGERRORF("%s has invalid key binding %s, fallback to WASD", name.CString(), keyBinding.CString());
+                    keyBinding = "WASD";
+                }
+
+                element->SetVar(VAR_BUTTON_KEY_BINDING, keyBinding);
+            }
+        }
+
         element->SetVar(VAR_SCREEN_JOYSTICK_INDEX, index);
         element->SetVar(VAR_SCREEN_JOYSTICK_INDEX, index);
     }
     }
 
 
+    // Make sure all the children are non-focusable so they do not mistakenly to be considered as active UI input controls by application
+    PODVector<UIElement*> allChildren;
+    state.screenJoystick_->GetChildren(allChildren, true);
+    for (PODVector<UIElement*>::Iterator iter = allChildren.Begin(); iter != allChildren.End(); ++iter)
+        (*iter)->SetFocusMode(FM_NOTFOCUSABLE);
+
     state.buttons_.Resize(numButtons);
     state.buttons_.Resize(numButtons);
     state.buttonPress_.Resize(numButtons);
     state.buttonPress_.Resize(numButtons);
     state.axes_.Resize(numAxes);
     state.axes_.Resize(numAxes);
     state.hats_.Resize(numHats);
     state.hats_.Resize(numHats);
 
 
-    // There could be potentially more than one screen joystick, however they all will be handled by a same handler
+    // There could be potentially more than one screen joystick, however they all will be handled by a same handler method
     // So there is no harm to replace the old handler with the new handler in each call to SubscribeToEvent()
     // So there is no harm to replace the old handler with the new handler in each call to SubscribeToEvent()
-    SubscribeToEvent(E_UIMOUSECLICK, HANDLER(Input, HandleMouseClick));
-    SubscribeToEvent(E_UIMOUSECLICKEND, HANDLER(Input, HandleMouseClick));
+    SubscribeToEvent(E_TOUCHBEGIN, HANDLER(Input, HandleScreenJoystickTouch));
+    SubscribeToEvent(E_TOUCHMOVE, HANDLER(Input, HandleScreenJoystickTouch));
+    SubscribeToEvent(E_TOUCHEND, HANDLER(Input, HandleScreenJoystickTouch));
 
 
     return index;
     return index;
 }
 }
 
 
+bool Input::RemoveScreenJoystick(unsigned index)
+{
+    if (index >= joysticks_.Size())
+    {
+        LOGERRORF("Joystick index #%d is out of bound", index);
+        return false;
+    }
+
+    JoystickState& state = joysticks_[index];
+    if (!state.screenJoystick_)
+    {
+        LOGERRORF("Failed to remove joystick at index #%d which is not a screen joystick", index);
+        return false;
+    }
+
+    state.screenJoystick_->Remove();
+    joysticks_.Erase(index);
+
+    return true;
+}
+
 void Input::SetScreenKeyboardVisible(bool enable)
 void Input::SetScreenKeyboardVisible(bool enable)
 {
 {
     if (!graphics_)
     if (!graphics_)
@@ -366,7 +431,7 @@ bool Input::OpenJoystick(unsigned index)
 {
 {
     if (index >= joysticks_.Size())
     if (index >= joysticks_.Size())
     {
     {
-        LOGERRORF("Joystick index #%d is out of bound.", index);
+        LOGERRORF("Joystick index #%d is out of bound", index);
         return false;
         return false;
     }
     }
 
 
@@ -464,12 +529,12 @@ String Input::GetScancodeName(int scancode) const
 
 
 bool Input::GetKeyDown(int key) const
 bool Input::GetKeyDown(int key) const
 {
 {
-    return keyDown_.Contains(key);
+    return keyDown_.Contains(SDL_toupper(key));
 }
 }
 
 
 bool Input::GetKeyPress(int key) const
 bool Input::GetKeyPress(int key) const
 {
 {
-    return keyPress_.Contains(key);
+    return keyPress_.Contains(SDL_toupper(key));
 }
 }
 
 
 bool Input::GetScancodeDown(int scancode) const
 bool Input::GetScancodeDown(int scancode) const
@@ -928,9 +993,9 @@ void Input::HandleSDLEvent(void* sdlEvent)
             eventData[P_TOUCHID] = touchID;
             eventData[P_TOUCHID] = touchID;
             eventData[P_X] = state.position_.x_;
             eventData[P_X] = state.position_.x_;
             eventData[P_Y] = state.position_.y_;
             eventData[P_Y] = state.position_.y_;
+            SendEvent(E_TOUCHEND, eventData);
 
 
             touches_.Erase(touchID);
             touches_.Erase(touchID);
-            SendEvent(E_TOUCHEND, eventData);
         }
         }
         break;
         break;
 
 
@@ -1176,12 +1241,14 @@ void Input::HandleBeginFrame(StringHash eventType, VariantMap& eventData)
     Update();
     Update();
 }
 }
 
 
-void Input::HandleMouseClick(StringHash eventType, VariantMap& eventData)
+void Input::HandleScreenJoystickTouch(StringHash eventType, VariantMap& eventData)
 {
 {
-    using namespace UIMouseClickEnd;
+    using namespace TouchBegin;
 
 
     // Only interested in events from screen joystick(s)
     // Only interested in events from screen joystick(s)
-    UIElement* element = static_cast<UIElement*>(eventData[eventType == E_UIMOUSECLICK ? P_ELEMENT : P_BEGINELEMENT].GetPtr());
+    TouchState& state = touches_[eventData[P_TOUCHID].GetInt()];
+    IntVector2 position(state.position_.x_, state.position_.y_);
+    UIElement* element = eventType == E_TOUCHBEGIN ? GetSubsystem<UI>()->GetElementAt(position) : state.touchedElement_;
     if (!element)
     if (!element)
         return;
         return;
     Variant variant = element->GetVar(VAR_SCREEN_JOYSTICK_INDEX);
     Variant variant = element->GetVar(VAR_SCREEN_JOYSTICK_INDEX);
@@ -1189,8 +1256,10 @@ void Input::HandleMouseClick(StringHash eventType, VariantMap& eventData)
         return;
         return;
     unsigned index = variant.GetUInt();
     unsigned index = variant.GetUInt();
 
 
-    int x = eventData[P_X].GetInt();
-    int y = eventData[P_Y].GetInt();
+    if (eventType == E_TOUCHEND)
+        state.touchedElement_.Reset();
+    else
+        state.touchedElement_ = element;
 
 
     // Prepare a fake SDL event
     // Prepare a fake SDL event
     SDL_Event evt;
     SDL_Event evt;
@@ -1198,60 +1267,36 @@ void Input::HandleMouseClick(StringHash eventType, VariantMap& eventData)
     const String& name = element->GetName();
     const String& name = element->GetName();
     if (name.StartsWith("Button"))
     if (name.StartsWith("Button"))
     {
     {
+        if (eventType == E_TOUCHMOVE)
+            return;
+
         // Determine whether to inject a joystick event or keyboard event
         // Determine whether to inject a joystick event or keyboard event
-        if (joysticks_[index].screenJoystick_->GetVar(VAR_INJECT_AS_KEY_EVENTS).GetBool())
+        Variant variant = element->GetVar(VAR_BUTTON_KEY_BINDING);
+        if (variant.IsEmpty())
         {
         {
-            Variant variant = element->GetVar(VAR_BUTTON_KEY_BINDING);
-            if (variant.IsEmpty())
-                return;
-
-            evt.type = eventType == E_UIMOUSECLICK ? SDL_KEYDOWN : SDL_KEYUP;
-            evt.key.keysym.sym = variant.GetInt();
-            evt.key.keysym.scancode = SDL_SCANCODE_UNKNOWN;
+            evt.type = eventType == E_TOUCHBEGIN ? SDL_JOYBUTTONDOWN : SDL_JOYBUTTONUP;
+            evt.jbutton.which = SCREEN_JOYSTICK_START_INDEX + index;
+            evt.jbutton.button = ToUInt(name.Substring(6));
         }
         }
         else
         else
         {
         {
-            evt.type = eventType == E_UIMOUSECLICK ? SDL_JOYBUTTONDOWN : SDL_JOYBUTTONUP;
-            evt.jbutton.which = SCREEN_JOYSTICK_START_INDEX + index;
-            evt.jbutton.button = ToUInt(name.Substring(6));
+            evt.type = eventType == E_TOUCHBEGIN ? SDL_KEYDOWN : SDL_KEYUP;
+            evt.key.keysym.sym = variant.GetInt();
+            evt.key.keysym.scancode = SDL_SCANCODE_UNKNOWN;
         }
         }
     }
     }
     else if (name.StartsWith("Hat"))
     else if (name.StartsWith("Hat"))
     {
     {
-        if (joysticks_[index].screenJoystick_->GetVar(VAR_INJECT_AS_KEY_EVENTS).GetBool())
-        {
-            evt.type = eventType == E_UIMOUSECLICK ? SDL_KEYDOWN : SDL_KEYUP;
-            IntVector2 relPosition = IntVector2(x, y) - element->GetScreenPosition() - element->GetSize() / 2;
-            if (relPosition.y_ < 0 && Abs(relPosition.x_ * 3 / 2) < Abs(relPosition.y_))
-            {
-                evt.key.keysym.sym = SDLK_w;
-                evt.key.keysym.scancode = SDL_SCANCODE_W;
-            }
-            else if (relPosition.y_ > 0 && Abs(relPosition.x_ * 3 / 2) < Abs(relPosition.y_))
-            {
-                evt.key.keysym.sym = SDLK_s;
-                evt.key.keysym.scancode = SDL_SCANCODE_S;
-            }
-            else if (relPosition.x_ < 0 && Abs(relPosition.y_ * 3 / 2) < Abs(relPosition.x_))
-            {
-                evt.key.keysym.sym = SDLK_a;
-                evt.key.keysym.scancode = SDL_SCANCODE_A;
-            }
-            else if (relPosition.x_ > 0 && Abs(relPosition.y_ * 3 / 2) < Abs(relPosition.x_))
-            {
-                evt.key.keysym.sym = SDLK_d;
-                evt.key.keysym.scancode = SDL_SCANCODE_D;
-            }
-        }
-        else
+        Variant variant = element->GetVar(VAR_BUTTON_KEY_BINDING);
+        if (variant.IsEmpty())
         {
         {
             evt.type = SDL_JOYHATMOTION;
             evt.type = SDL_JOYHATMOTION;
             evt.jaxis.which = SCREEN_JOYSTICK_START_INDEX + index;
             evt.jaxis.which = SCREEN_JOYSTICK_START_INDEX + index;
             evt.jhat.hat = ToUInt(name.Substring(3));
             evt.jhat.hat = ToUInt(name.Substring(3));
             evt.jhat.value = HAT_CENTER;
             evt.jhat.value = HAT_CENTER;
-            if (eventType == E_UIMOUSECLICK)
+            if (eventType != E_TOUCHEND)
             {
             {
-                IntVector2 relPosition = IntVector2(x, y) - element->GetScreenPosition() - element->GetSize() / 2;
+                IntVector2 relPosition = position - element->GetScreenPosition() - element->GetSize() / 2;
                 if (relPosition.y_ < 0 && Abs(relPosition.x_ * 3 / 2) < Abs(relPosition.y_))
                 if (relPosition.y_ < 0 && Abs(relPosition.x_ * 3 / 2) < Abs(relPosition.y_))
                     evt.jhat.value |= HAT_UP;
                     evt.jhat.value |= HAT_UP;
                 if (relPosition.y_ > 0 && Abs(relPosition.x_ * 3 / 2) < Abs(relPosition.y_))
                 if (relPosition.y_ > 0 && Abs(relPosition.x_ * 3 / 2) < Abs(relPosition.y_))
@@ -1262,6 +1307,55 @@ void Input::HandleMouseClick(StringHash eventType, VariantMap& eventData)
                     evt.jhat.value |= HAT_RIGHT;
                     evt.jhat.value |= HAT_RIGHT;
             }
             }
         }
         }
+        else
+        {
+            // Hat is binded by 4 keys, like 'WASD'
+            String keyBinding = variant.GetString();
+
+            if (eventType == E_TOUCHEND)
+            {
+                evt.type = SDL_KEYUP;
+                evt.key.keysym.sym = element->GetVar(VAR_LAST_KEYSYM).GetInt();
+                if (!evt.key.keysym.sym)
+                    return;
+
+                element->SetVar(VAR_LAST_KEYSYM, 0);
+            }
+            else
+            {
+                evt.type = SDL_KEYDOWN;
+                IntVector2 relPosition = position - element->GetScreenPosition() - element->GetSize() / 2;
+                if (relPosition.y_ < 0 && Abs(relPosition.x_ * 3 / 2) < Abs(relPosition.y_))
+                    evt.key.keysym.sym = keyBinding[0];
+                else if (relPosition.y_ > 0 && Abs(relPosition.x_ * 3 / 2) < Abs(relPosition.y_))
+                    evt.key.keysym.sym = keyBinding[2];
+                else if (relPosition.x_ < 0 && Abs(relPosition.y_ * 3 / 2) < Abs(relPosition.x_))
+                    evt.key.keysym.sym = keyBinding[1];
+                else if (relPosition.x_ > 0 && Abs(relPosition.y_ * 3 / 2) < Abs(relPosition.x_))
+                    evt.key.keysym.sym = keyBinding[3];
+                else
+                    return;
+
+                if (eventType == E_TOUCHMOVE && evt.key.keysym.sym != element->GetVar(VAR_LAST_KEYSYM).GetInt())
+                {
+                    // Dragging past the directional boundary will cause an additional key up event for previous key symbol
+                    SDL_Event evt;
+                    evt.type = SDL_KEYUP;
+                    evt.key.keysym.sym = element->GetVar(VAR_LAST_KEYSYM).GetInt();
+                    if (evt.key.keysym.sym)
+                    {
+                        evt.key.keysym.scancode = SDL_SCANCODE_UNKNOWN;
+                        HandleSDLEvent(&evt);
+                    }
+
+                    element->SetVar(VAR_LAST_KEYSYM, 0);
+                }
+
+                evt.key.keysym.scancode = SDL_SCANCODE_UNKNOWN;
+
+                element->SetVar(VAR_LAST_KEYSYM, evt.key.keysym.sym);
+            }
+        }
     }
     }
     else
     else
         return;
         return;

+ 8 - 6
Source/Engine/Input/Input.h

@@ -47,6 +47,8 @@ struct TouchState
     IntVector2 delta_;
     IntVector2 delta_;
     /// Finger pressure.
     /// Finger pressure.
     float pressure_;
     float pressure_;
+    /// Last touched UI element from screen joystick.
+    WeakPtr<UIElement> touchedElement_;
 };
 };
 
 
 /// %Input state for a joystick.
 /// %Input state for a joystick.
@@ -57,8 +59,6 @@ struct JoystickState
         joystick_(0), controller_(0)
         joystick_(0), controller_(0)
     {
     {
     }
     }
-    /// Destructor.
-    ~JoystickState();
 
 
     /// Return number of buttons.
     /// Return number of buttons.
     unsigned GetNumButtons() const { return buttons_.Size(); }
     unsigned GetNumButtons() const { return buttons_.Size(); }
@@ -144,8 +144,10 @@ public:
     void CloseJoystick(unsigned index);
     void CloseJoystick(unsigned index);
     /// Redetect joysticks. Return true if successful.
     /// Redetect joysticks. Return true if successful.
     bool DetectJoysticks();
     bool DetectJoysticks();
-    /// Add on-screen joystick. Return the joystick index number when successful or M_MAX_UNSIGNED when error. If layout file is not given, use the default screen joystick layout. If style file is not given, use the default style file from root UI element.
-    unsigned AddScreenJoystick(bool injectAsKeyEvents = false, XMLFile* layoutFile = 0, XMLFile* styleFile = 0);
+    /// Add screen joystick. Return the joystick index number when successful or M_MAX_UNSIGNED when error. If layout file is not given, use the default screen joystick layout. If style file is not given, use the default style file from root UI element.
+    unsigned AddScreenJoystick(XMLFile* layoutFile = 0, XMLFile* styleFile = 0);
+    /// Remove screen joystick by index. Return true if successful.
+    bool RemoveScreenJoystick(unsigned index);
     /// Show or hide on-screen keyboard on platforms that support it. When shown, keypresses from it are delivered as key events.
     /// Show or hide on-screen keyboard on platforms that support it. When shown, keypresses from it are delivered as key events.
     void SetScreenKeyboardVisible(bool enable);
     void SetScreenKeyboardVisible(bool enable);
 
 
@@ -237,8 +239,8 @@ private:
     void HandleScreenMode(StringHash eventType, VariantMap& eventData);
     void HandleScreenMode(StringHash eventType, VariantMap& eventData);
     /// Handle frame start event.
     /// Handle frame start event.
     void HandleBeginFrame(StringHash eventType, VariantMap& eventData);
     void HandleBeginFrame(StringHash eventType, VariantMap& eventData);
-    /// Handle mouse click begin and end event from the screen joystick(s), ignore event from others.
-    void HandleMouseClick(StringHash eventType, VariantMap& eventData);
+    /// Handle touch events from the controls of screen joystick(s).
+    void HandleScreenJoystickTouch(StringHash eventType, VariantMap& eventData);
     /// Handle SDL event.
     /// Handle SDL event.
     void HandleSDLEvent(void* sdlEvent);
     void HandleSDLEvent(void* sdlEvent);
 
 

+ 3 - 1
Source/Engine/LuaScript/pkgs/Input/Input.pkg

@@ -8,6 +8,7 @@ struct TouchState
     IntVector2 lastPosition_ @ lastPosition;
     IntVector2 lastPosition_ @ lastPosition;
     IntVector2 delta_ @ delta;
     IntVector2 delta_ @ delta;
     float pressure_ @ pressure;
     float pressure_ @ pressure;
+    WeakPtr<UIElement> touchedElement_ @ touchedElement;
 };
 };
 
 
 struct JoystickState
 struct JoystickState
@@ -33,7 +34,8 @@ class Input : public Object
     bool OpenJoystick(unsigned index);
     bool OpenJoystick(unsigned index);
     void CloseJoystick(unsigned index);
     void CloseJoystick(unsigned index);
     bool DetectJoysticks();
     bool DetectJoysticks();
-    unsigned AddScreenJoystick(bool injectAsKeyEvents = false, XMLFile* layoutFile = 0, XMLFile* styleFile = 0);
+    unsigned AddScreenJoystick(XMLFile* layoutFile = 0, XMLFile* styleFile = 0);
+    bool RemoveScreenJoystick(unsigned index);
     void SetScreenKeyboardVisible(bool enable);
     void SetScreenKeyboardVisible(bool enable);
 
 
     int GetKeyFromName(const String name) const;
     int GetKeyFromName(const String name) const;

+ 5 - 3
Source/Engine/Script/InputAPI.cpp

@@ -438,7 +438,8 @@ static void RegisterInput(asIScriptEngine* engine)
     engine->RegisterObjectProperty("TouchState", "const IntVector2 lastPosition", offsetof(TouchState, lastPosition_));
     engine->RegisterObjectProperty("TouchState", "const IntVector2 lastPosition", offsetof(TouchState, lastPosition_));
     engine->RegisterObjectProperty("TouchState", "const IntVector2 delta", offsetof(TouchState, delta_));
     engine->RegisterObjectProperty("TouchState", "const IntVector2 delta", offsetof(TouchState, delta_));
     engine->RegisterObjectProperty("TouchState", "const float pressure", offsetof(TouchState, pressure_));
     engine->RegisterObjectProperty("TouchState", "const float pressure", offsetof(TouchState, pressure_));
-    
+    engine->RegisterObjectProperty("TouchState", "const WeakHandle touchedElement", offsetof(TouchState, touchedElement_));
+
     engine->RegisterObjectType("JoystickState", 0, asOBJ_REF);
     engine->RegisterObjectType("JoystickState", 0, asOBJ_REF);
     engine->RegisterObjectBehaviour("JoystickState", asBEHAVE_ADDREF, "void f()", asFUNCTION(FakeAddRef), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("JoystickState", asBEHAVE_ADDREF, "void f()", asFUNCTION(FakeAddRef), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("JoystickState", asBEHAVE_RELEASE, "void f()", asFUNCTION(FakeReleaseRef), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("JoystickState", asBEHAVE_RELEASE, "void f()", asFUNCTION(FakeReleaseRef), asCALL_CDECL_OBJLAST);
@@ -450,12 +451,13 @@ static void RegisterInput(asIScriptEngine* engine)
     engine->RegisterObjectMethod("JoystickState", "bool get_buttonPress(uint) const", asMETHOD(JoystickState, GetButtonPress), asCALL_THISCALL);
     engine->RegisterObjectMethod("JoystickState", "bool get_buttonPress(uint) const", asMETHOD(JoystickState, GetButtonPress), asCALL_THISCALL);
     engine->RegisterObjectMethod("JoystickState", "float get_axisPosition(uint) const", asMETHOD(JoystickState, GetAxisPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("JoystickState", "float get_axisPosition(uint) const", asMETHOD(JoystickState, GetAxisPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("JoystickState", "int get_hatPosition(uint) const", asMETHOD(JoystickState, GetHatPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("JoystickState", "int get_hatPosition(uint) const", asMETHOD(JoystickState, GetHatPosition), asCALL_THISCALL);
-    
+
     RegisterObject<Input>(engine, "Input");
     RegisterObject<Input>(engine, "Input");
     engine->RegisterObjectMethod("Input", "bool OpenJoystick(uint)", asMETHOD(Input, OpenJoystick), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "bool OpenJoystick(uint)", asMETHOD(Input, OpenJoystick), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "void CloseJoystick(uint)", asMETHOD(Input, CloseJoystick), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "void CloseJoystick(uint)", asMETHOD(Input, CloseJoystick), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "bool DetectJoysticks()", asMETHOD(Input, DetectJoysticks), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "bool DetectJoysticks()", asMETHOD(Input, DetectJoysticks), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Input", "uint AddScreenJoystick(bool injectAsKeyEvents = false, XMLFile@+ layoutFile = null, XMLFile@+ styleFile = null)", asMETHOD(Input, AddScreenJoystick), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Input", "uint AddScreenJoystick(XMLFile@+ layoutFile = null, XMLFile@+ styleFile = null)", asMETHOD(Input, AddScreenJoystick), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Input", "bool RemoveScreenJoystick(uint)", asMETHOD(Input, RemoveScreenJoystick), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "int GetKeyFromName(const String&in) const", asMETHOD(Input, GetKeyFromName), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "int GetKeyFromName(const String&in) const", asMETHOD(Input, GetKeyFromName), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "int GetKeyFromScancode(int) const", asMETHOD(Input, GetKeyFromScancode), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "int GetKeyFromScancode(int) const", asMETHOD(Input, GetKeyFromScancode), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "String GetKeyName(int) const", asMETHOD(Input, GetKeyName), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "String GetKeyName(int) const", asMETHOD(Input, GetKeyName), asCALL_THISCALL);

+ 26 - 27
Source/Engine/UI/UI.cpp

@@ -107,7 +107,7 @@ UI::UI(Context* context) :
 {
 {
     rootElement_->SetTraversalMode(TM_DEPTH_FIRST);
     rootElement_->SetTraversalMode(TM_DEPTH_FIRST);
     rootModalElement_->SetTraversalMode(TM_DEPTH_FIRST);
     rootModalElement_->SetTraversalMode(TM_DEPTH_FIRST);
-    
+
     // Register UI library object factories
     // Register UI library object factories
     RegisterUILibrary(context_);
     RegisterUILibrary(context_);
 
 
@@ -303,7 +303,7 @@ void UI::Update(float timeStep)
     // Expire hovers
     // Expire hovers
     for (HashMap<WeakPtr<UIElement>, bool>::Iterator i = hoveredElements_.Begin(); i != hoveredElements_.End(); ++i)
     for (HashMap<WeakPtr<UIElement>, bool>::Iterator i = hoveredElements_.Begin(); i != hoveredElements_.End(); ++i)
         i->second_ = false;
         i->second_ = false;
-    
+
     // Drag begin based on time
     // Drag begin based on time
     if (dragElement_ && dragBeginPending_)
     if (dragElement_ && dragBeginPending_)
     {
     {
@@ -320,11 +320,11 @@ void UI::Update(float timeStep)
             SendDragOrHoverEvent(E_DRAGBEGIN, dragElement_, dragBeginPos_);
             SendDragOrHoverEvent(E_DRAGBEGIN, dragElement_, dragBeginPos_);
         }
         }
     }
     }
-    
+
     // Mouse hover
     // Mouse hover
     if (!usingTouchInput_ && cursorVisible)
     if (!usingTouchInput_ && cursorVisible)
         ProcessHover(cursorPos, mouseButtons_, qualifiers_, cursor_);
         ProcessHover(cursorPos, mouseButtons_, qualifiers_, cursor_);
-    
+
     // Touch hover
     // Touch hover
     Input* input = GetSubsystem<Input>();
     Input* input = GetSubsystem<Input>();
     unsigned numTouches = input->GetNumTouches();
     unsigned numTouches = input->GetNumTouches();
@@ -333,7 +333,7 @@ void UI::Update(float timeStep)
         TouchState* touch = input->GetTouch(i);
         TouchState* touch = input->GetTouch(i);
         ProcessHover(touch->position_, MOUSEB_LEFT, 0, 0);
         ProcessHover(touch->position_, MOUSEB_LEFT, 0, 0);
     }
     }
-    
+
     // End hovers that expired without refreshing
     // End hovers that expired without refreshing
     for (HashMap<WeakPtr<UIElement>, bool>::Iterator i = hoveredElements_.Begin(); i != hoveredElements_.End();)
     for (HashMap<WeakPtr<UIElement>, bool>::Iterator i = hoveredElements_.Begin(); i != hoveredElements_.End();)
     {
     {
@@ -343,7 +343,7 @@ void UI::Update(float timeStep)
             if (element)
             if (element)
             {
             {
                 using namespace HoverEnd;
                 using namespace HoverEnd;
-                
+
                 VariantMap& eventData = GetEventDataMap();
                 VariantMap& eventData = GetEventDataMap();
                 eventData[P_ELEMENT] = element;
                 eventData[P_ELEMENT] = element;
                 element->SendEvent(E_HOVEREND, eventData);
                 element->SendEvent(E_HOVEREND, eventData);
@@ -353,7 +353,7 @@ void UI::Update(float timeStep)
         else
         else
             ++i;
             ++i;
     }
     }
-    
+
     Update(timeStep, rootElement_);
     Update(timeStep, rootElement_);
     Update(timeStep, rootModalElement_);
     Update(timeStep, rootModalElement_);
 }
 }
@@ -366,7 +366,7 @@ void UI::RenderUpdate()
 
 
     // If the OS cursor is visible, do not render the UI's own cursor
     // If the OS cursor is visible, do not render the UI's own cursor
     bool osCursorVisible = GetSubsystem<Input>()->IsMouseVisible();
     bool osCursorVisible = GetSubsystem<Input>()->IsMouseVisible();
-    
+
     // Get rendering batches from the non-modal UI elements
     // Get rendering batches from the non-modal UI elements
     batches_.Clear();
     batches_.Clear();
     vertexData_.Clear();
     vertexData_.Clear();
@@ -397,7 +397,7 @@ void UI::Render()
     bool osCursorVisible = GetSubsystem<Input>()->IsMouseVisible();
     bool osCursorVisible = GetSubsystem<Input>()->IsMouseVisible();
     if (cursor_ && osCursorVisible)
     if (cursor_ && osCursorVisible)
         cursor_->ApplyOSCursorShape();
         cursor_->ApplyOSCursorShape();
-    
+
     SetVertexData(vertexBuffer_, vertexData_);
     SetVertexData(vertexBuffer_, vertexData_);
     SetVertexData(debugVertexBuffer_, debugVertexData_);
     SetVertexData(debugVertexBuffer_, debugVertexData_);
 
 
@@ -608,7 +608,7 @@ const String& UI::GetClipboardText() const
         if (text)
         if (text)
             SDL_free(text);
             SDL_free(text);
     }
     }
-    
+
     return clipBoard_;
     return clipBoard_;
 }
 }
 
 
@@ -940,7 +940,7 @@ void UI::ProcessHover(const IntVector2& cursorPos, int buttons, int qualifiers,
         if (!dragElement_ || dragElement_ == element || dragDropTest)
         if (!dragElement_ || dragElement_ == element || dragDropTest)
         {
         {
             element->OnHover(element->ScreenToElement(cursorPos), cursorPos, buttons, qualifiers, cursor);
             element->OnHover(element->ScreenToElement(cursorPos), cursorPos, buttons, qualifiers, cursor);
-            
+
             // Begin hover event
             // Begin hover event
             if (!hoveredElements_.Contains(element))
             if (!hoveredElements_.Contains(element))
             {
             {
@@ -997,7 +997,7 @@ void UI::ProcessClickBegin(const IntVector2& cursorPos, int button, int buttons,
 
 
             // Remember element clicked on for the click end
             // Remember element clicked on for the click end
             clickElement_ = element;
             clickElement_ = element;
-            
+
             // Fire double click event if element matches and is in time
             // Fire double click event if element matches and is in time
             if (doubleClickElement_ && element == doubleClickElement_ && clickTimer_.GetMSec(true) <
             if (doubleClickElement_ && element == doubleClickElement_ && clickTimer_.GetMSec(true) <
                 (unsigned)(doubleClickInterval_ * 1000) && lastMouseButtons_ == buttons)
                 (unsigned)(doubleClickInterval_ * 1000) && lastMouseButtons_ == buttons)
@@ -1011,7 +1011,7 @@ void UI::ProcessClickBegin(const IntVector2& cursorPos, int button, int buttons,
                 doubleClickElement_ = element;
                 doubleClickElement_ = element;
                 clickTimer_.Reset();
                 clickTimer_.Reset();
             }
             }
-            
+
             // Handle start of drag. Click handling may have caused destruction of the element, so check the pointer again
             // Handle start of drag. Click handling may have caused destruction of the element, so check the pointer again
             if (element && !dragElement_ && buttons == MOUSEB_LEFT)
             if (element && !dragElement_ && buttons == MOUSEB_LEFT)
             {
             {
@@ -1027,7 +1027,7 @@ void UI::ProcessClickBegin(const IntVector2& cursorPos, int button, int buttons,
             SetFocusElement(0);
             SetFocusElement(0);
             SendClickEvent(E_UIMOUSECLICK, element, cursorPos, button, buttons, qualifiers);
             SendClickEvent(E_UIMOUSECLICK, element, cursorPos, button, buttons, qualifiers);
         }
         }
-        
+
         lastMouseButtons_ = buttons;
         lastMouseButtons_ = buttons;
     }
     }
 }
 }
@@ -1041,9 +1041,9 @@ void UI::ProcessClickEnd(const IntVector2& cursorPos, int button, int buttons, i
         // Handle end of click
         // Handle end of click
         if (element)
         if (element)
             element->OnClickEnd(element->ScreenToElement(cursorPos), cursorPos, button, buttons, qualifiers, cursor, clickElement_);
             element->OnClickEnd(element->ScreenToElement(cursorPos), cursorPos, button, buttons, qualifiers, cursor, clickElement_);
-        
+
         SendClickEvent(E_UIMOUSECLICKEND, element, cursorPos, button, buttons, qualifiers);
         SendClickEvent(E_UIMOUSECLICKEND, element, cursorPos, button, buttons, qualifiers);
-        
+
         // Handle end of drag
         // Handle end of drag
         if (dragElement_ && !buttons)
         if (dragElement_ && !buttons)
         {
         {
@@ -1080,7 +1080,7 @@ void UI::ProcessClickEnd(const IntVector2& cursorPos, int button, int buttons, i
             dragElement_.Reset();
             dragElement_.Reset();
             dragBeginPending_ = false;
             dragBeginPending_ = false;
         }
         }
-        
+
         clickElement_.Reset();
         clickElement_.Reset();
     }
     }
 }
 }
@@ -1102,7 +1102,7 @@ void UI::ProcessMove(const IntVector2& cursorPos, int buttons, int qualifiers, C
                     SendDragOrHoverEvent(E_DRAGBEGIN, dragElement_, dragBeginPos_);
                     SendDragOrHoverEvent(E_DRAGBEGIN, dragElement_, dragBeginPos_);
                 }
                 }
             }
             }
-            
+
             if (!dragBeginPending_)
             if (!dragBeginPending_)
             {
             {
                 dragElement_->OnDragMove(dragElement_->ScreenToElement(cursorPos), cursorPos, buttons, qualifiers, cursor);
                 dragElement_->OnDragMove(dragElement_->ScreenToElement(cursorPos), cursorPos, buttons, qualifiers, cursor);
@@ -1146,11 +1146,11 @@ void UI::SendClickEvent(StringHash eventType, UIElement* element, const IntVecto
     eventData[UIMouseClick::P_BUTTON] = button;
     eventData[UIMouseClick::P_BUTTON] = button;
     eventData[UIMouseClick::P_BUTTONS] = buttons;
     eventData[UIMouseClick::P_BUTTONS] = buttons;
     eventData[UIMouseClick::P_QUALIFIERS] = qualifiers;
     eventData[UIMouseClick::P_QUALIFIERS] = qualifiers;
-    
+
     // For click end events, send also the element the click began on
     // For click end events, send also the element the click began on
     if (eventType == E_UIMOUSECLICKEND)
     if (eventType == E_UIMOUSECLICKEND)
         eventData[UIMouseClickEnd::P_BEGINELEMENT] = clickElement_;
         eventData[UIMouseClickEnd::P_BEGINELEMENT] = clickElement_;
-    
+
     SendEvent(eventType, eventData);
     SendEvent(eventType, eventData);
 }
 }
 
 
@@ -1284,7 +1284,6 @@ void UI::HandleTouchBegin(StringHash eventType, VariantMap& eventData)
     using namespace TouchBegin;
     using namespace TouchBegin;
 
 
     IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
     IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
-    WeakPtr<UIElement> element(GetElementAt(pos));
     usingTouchInput_ = true;
     usingTouchInput_ = true;
 
 
     ProcessClickBegin(pos, MOUSEB_LEFT, MOUSEB_LEFT, 0, 0, true);
     ProcessClickBegin(pos, MOUSEB_LEFT, MOUSEB_LEFT, 0, 0, true);
@@ -1329,7 +1328,7 @@ void UI::HandleKeyDown(StringHash eventType, VariantMap& eventData)
         bool cursorVisible;
         bool cursorVisible;
         GetCursorPositionAndVisible(cursorPos, cursorVisible);
         GetCursorPositionAndVisible(cursorPos, cursorVisible);
         SendDragOrHoverEvent(E_DRAGCANCEL, dragElement_, cursorPos);
         SendDragOrHoverEvent(E_DRAGCANCEL, dragElement_, cursorPos);
-        
+
         dragElement_.Reset();
         dragElement_.Reset();
         dragBeginPending_ = false;
         dragBeginPending_ = false;
         return;
         return;
@@ -1429,28 +1428,28 @@ void UI::HandleRenderUpdate(StringHash eventType, VariantMap& eventData)
 void UI::HandleDropFile(StringHash eventType, VariantMap& eventData)
 void UI::HandleDropFile(StringHash eventType, VariantMap& eventData)
 {
 {
     Input* input = GetSubsystem<Input>();
     Input* input = GetSubsystem<Input>();
-    
+
     // Sending the UI variant of the event only makes sense if the OS cursor is visible (not locked to window center)
     // Sending the UI variant of the event only makes sense if the OS cursor is visible (not locked to window center)
     if (input->IsMouseVisible())
     if (input->IsMouseVisible())
     {
     {
         IntVector2 screenPos = input->GetMousePosition();
         IntVector2 screenPos = input->GetMousePosition();
         UIElement* element = GetElementAt(screenPos);
         UIElement* element = GetElementAt(screenPos);
-        
+
         using namespace UIDropFile;
         using namespace UIDropFile;
-        
+
         VariantMap uiEventData;
         VariantMap uiEventData;
         uiEventData[P_FILENAME] = eventData[P_FILENAME];
         uiEventData[P_FILENAME] = eventData[P_FILENAME];
         uiEventData[P_X] = screenPos.x_;
         uiEventData[P_X] = screenPos.x_;
         uiEventData[P_Y] = screenPos.y_;
         uiEventData[P_Y] = screenPos.y_;
         uiEventData[P_ELEMENT] = element;
         uiEventData[P_ELEMENT] = element;
-        
+
         if (element)
         if (element)
         {
         {
             IntVector2 relativePos = element->ScreenToElement(screenPos);
             IntVector2 relativePos = element->ScreenToElement(screenPos);
             uiEventData[P_ELEMENTX] = relativePos.x_;
             uiEventData[P_ELEMENTX] = relativePos.x_;
             uiEventData[P_ELEMENTY] = relativePos.y_;
             uiEventData[P_ELEMENTY] = relativePos.y_;
         }
         }
-        
+
         SendEvent(E_UIDROPFILE, uiEventData);
         SendEvent(E_UIDROPFILE, uiEventData);
     }
     }
 }
 }

+ 7 - 1
Source/Samples/Sample.h

@@ -24,7 +24,6 @@
 
 
 #include "Application.h"
 #include "Application.h"
 
 
-
 namespace Urho3D
 namespace Urho3D
 {
 {
 
 
@@ -58,6 +57,8 @@ public:
     /// Setup after engine initialization. Creates the logo, console & debug HUD.
     /// Setup after engine initialization. Creates the logo, console & debug HUD.
     virtual void Start();
     virtual void Start();
 
 
+    /// Initialize touch input on mobile platform.
+    void InitTouchInput();
     /// Control logo visibility.
     /// Control logo visibility.
     void SetLogoVisible(bool enable);
     void SetLogoVisible(bool enable);
 
 
@@ -74,6 +75,11 @@ private:
     void CreateConsoleAndDebugHud();
     void CreateConsoleAndDebugHud();
     /// Handle key down event to process key controls common to all samples.
     /// Handle key down event to process key controls common to all samples.
     void HandleKeyDown(StringHash eventType, VariantMap& eventData);
     void HandleKeyDown(StringHash eventType, VariantMap& eventData);
+
+    unsigned screenJoystickIndex_;
+    unsigned screenJoystickSettingsIndex_;
+    bool touchEnabled_;
+    bool paused_;
 };
 };
 
 
 #include "Sample.inl"
 #include "Sample.inl"

+ 45 - 2
Source/Samples/Sample.inl

@@ -26,6 +26,7 @@
 #include "Engine.h"
 #include "Engine.h"
 #include "FileSystem.h"
 #include "FileSystem.h"
 #include "Graphics.h"
 #include "Graphics.h"
+#include "Input.h"
 #include "InputEvents.h"
 #include "InputEvents.h"
 #include "Renderer.h"
 #include "Renderer.h"
 #include "ResourceCache.h"
 #include "ResourceCache.h"
@@ -36,7 +37,11 @@
 #include "XMLFile.h"
 #include "XMLFile.h"
 
 
 Sample::Sample(Context* context) :
 Sample::Sample(Context* context) :
-    Application(context)
+    Application(context),
+    screenJoystickIndex_(M_MAX_UNSIGNED),
+    screenJoystickSettingsIndex_(M_MAX_UNSIGNED),
+    touchEnabled_(false),
+    paused_(false)
 {
 {
 }
 }
 
 
@@ -51,6 +56,9 @@ void Sample::Setup()
 
 
 void Sample::Start()
 void Sample::Start()
 {
 {
+    // Initialize touch input on mobile platforms
+    InitTouchInput();
+
     // Create logo
     // Create logo
     CreateLogo();
     CreateLogo();
 
 
@@ -64,6 +72,19 @@ void Sample::Start()
     SubscribeToEvent(E_KEYDOWN, HANDLER(Sample, HandleKeyDown));
     SubscribeToEvent(E_KEYDOWN, HANDLER(Sample, HandleKeyDown));
 }
 }
 
 
+void Sample::InitTouchInput()
+{
+    if (GetPlatform() == "Android" || GetPlatform() == "iOS")
+    {
+        touchEnabled_ = true;
+
+        ResourceCache* cache = GetSubsystem<ResourceCache>();
+        Input* input = GetSubsystem<Input>();
+        screenJoystickIndex_ = input->AddScreenJoystick(cache->GetResource<XMLFile>("UI/ScreenJoystick_Samples.xml"), cache->GetResource<XMLFile>("UI/DefaultStyle.xml"));
+        input->OpenJoystick(screenJoystickIndex_);
+    }
+}
+
 void Sample::SetLogoVisible(bool enable)
 void Sample::SetLogoVisible(bool enable)
 {
 {
     if (logoSprite_)
     if (logoSprite_)
@@ -125,6 +146,7 @@ void Sample::CreateConsoleAndDebugHud()
     // Create console
     // Create console
     Console* console = engine_->CreateConsole();
     Console* console = engine_->CreateConsole();
     console->SetDefaultStyle(xmlFile);
     console->SetDefaultStyle(xmlFile);
+    console->GetBackground()->SetOpacity(0.8f);
 
 
     // Create debug HUD.
     // Create debug HUD.
     DebugHud* debugHud = engine_->CreateDebugHud();
     DebugHud* debugHud = engine_->CreateDebugHud();
@@ -160,8 +182,29 @@ void Sample::HandleKeyDown(StringHash eventType, VariantMap& eventData)
     {
     {
         Renderer* renderer = GetSubsystem<Renderer>();
         Renderer* renderer = GetSubsystem<Renderer>();
         
         
+        // Preferences / Pause
+        if (key == KEY_SELECT && touchEnabled_)
+        {
+            Input* input = GetSubsystem<Input>();
+            if (screenJoystickSettingsIndex_ == M_MAX_UNSIGNED)
+            {
+                ResourceCache* cache = GetSubsystem<ResourceCache>();
+                screenJoystickSettingsIndex_ = input->AddScreenJoystick(cache->GetResource<XMLFile>("UI/ScreenJoystickSettings_Samples.xml"), cache->GetResource<XMLFile>("UI/DefaultStyle.xml"));
+                input->OpenJoystick(screenJoystickSettingsIndex_);
+                paused_ = true;
+            }
+            else
+            {
+                paused_ = !paused_;
+                if (paused_)
+                    input->OpenJoystick(screenJoystickSettingsIndex_);
+                else
+                    input->CloseJoystick(screenJoystickSettingsIndex_);
+            }
+        }
+
         // Texture quality
         // Texture quality
-        if (key == '1')
+        else if (key == '1')
         {
         {
             int quality = renderer->GetTextureQuality();
             int quality = renderer->GetTextureQuality();
             ++quality;
             ++quality;