Pārlūkot izejas kodu

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 gadi atpakaļ
vecāks
revīzija
c78f34d5ca

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

@@ -53,6 +53,7 @@ uint clientNodeID = 0;
 int clientScore = 0;
 
 uint screenJoystickIndex = M_MAX_UNSIGNED;
+uint screenJoystickSettingsIndex = M_MAX_UNSIGNED;
 bool touchEnabled = false;
 
 Array<Player> players;
@@ -203,7 +204,7 @@ void InitNetworking()
 void InitTouchInput()
 {
     touchEnabled = true;
-    screenJoystickIndex = input.AddScreenJoystick();
+    screenJoystickIndex = input.AddScreenJoystick(cache.GetResource("XMLFile", "UI/ScreenJoystick_NinjaSnowWar.xml"));
 }
 
 void CreateCamera()
@@ -493,9 +494,17 @@ void HandleKeyDown(StringHash eventType, VariantMap& eventData)
     {
         gameScene.updateEnabled = !gameScene.updateEnabled;
         if (!gameScene.updateEnabled)
+        {
             SetMessage("PAUSED");
+            if (screenJoystickSettingsIndex == M_MAX_UNSIGNED)
+                screenJoystickSettingsIndex = input.AddScreenJoystick(cache.GetResource("XMLFile", "UI/ScreenJoystickSettings_NinjaSnowWar.xml"));
+            input.OpenJoystick(screenJoystickSettingsIndex);
+        }
         else
+        {
             SetMessage("");
+            input.CloseJoystick(screenJoystickSettingsIndex);
+        }
     }
 }
 
@@ -874,8 +883,12 @@ void UpdateControls()
             for (uint i = 0; i < input.numTouches; ++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="Pressed Image Offset" value="0 0" />
         <element type="Text">
-            <attribute name="Name" value="Label" />     
+            <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="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 type="Button">
@@ -36,18 +31,24 @@
         <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="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="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 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">
         <attribute name="Name" value="Hat0" />
         <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 "Text.h"
 #include "UI.h"
-#include "UIEvents.h"
 
 #include <cstring>
 
@@ -54,8 +53,8 @@ namespace Urho3D
 {
 
 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_LAST_KEYSYM("VAR_LAST_KEYSYM");
 const ShortStringHash VAR_SCREEN_JOYSTICK_INDEX("VAR_SCREEN_JOYSTICK_INDEX");
 
 /// Convert SDL keycode if necessary.
@@ -67,12 +66,6 @@ int ConvertSDLKeyCode(int keySym, int scanCode)
         return SDL_toupper(keySym);
 }
 
-JoystickState::~JoystickState()
-{
-    if (screenJoystick_)
-        screenJoystick_->Remove();
-}
-
 Input::Input(Context* context) :
     Object(context),
     mouseButtonDown_(0),
@@ -244,8 +237,10 @@ bool Input::DetectJoysticks()
     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_)
     {
         LOGWARNING("Cannot add screen joystick in headless mode");
@@ -267,9 +262,9 @@ unsigned Input::AddScreenJoystick(bool injectAsKeyEvents, XMLFile* layoutFile, X
         return M_MAX_UNSIGNED;
 
     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);
+
     unsigned index = joysticks_.Size();
     joysticks_.Resize(index + 1);
     JoystickState& state = joysticks_[index];
@@ -290,64 +285,134 @@ unsigned Input::AddScreenJoystick(bool injectAsKeyEvents, XMLFile* layoutFile, X
             ++numButtons;
 
             // 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"))
+        {
             ++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"))
+        {
             ++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);
     }
 
+    // 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.buttonPress_.Resize(numButtons);
     state.axes_.Resize(numAxes);
     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()
-    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;
 }
 
+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)
 {
     if (!graphics_)
@@ -366,7 +431,7 @@ bool Input::OpenJoystick(unsigned index)
 {
     if (index >= joysticks_.Size())
     {
-        LOGERRORF("Joystick index #%d is out of bound.", index);
+        LOGERRORF("Joystick index #%d is out of bound", index);
         return false;
     }
 
@@ -464,12 +529,12 @@ String Input::GetScancodeName(int scancode) const
 
 bool Input::GetKeyDown(int key) const
 {
-    return keyDown_.Contains(key);
+    return keyDown_.Contains(SDL_toupper(key));
 }
 
 bool Input::GetKeyPress(int key) const
 {
-    return keyPress_.Contains(key);
+    return keyPress_.Contains(SDL_toupper(key));
 }
 
 bool Input::GetScancodeDown(int scancode) const
@@ -928,9 +993,9 @@ void Input::HandleSDLEvent(void* sdlEvent)
             eventData[P_TOUCHID] = touchID;
             eventData[P_X] = state.position_.x_;
             eventData[P_Y] = state.position_.y_;
+            SendEvent(E_TOUCHEND, eventData);
 
             touches_.Erase(touchID);
-            SendEvent(E_TOUCHEND, eventData);
         }
         break;
 
@@ -1176,12 +1241,14 @@ void Input::HandleBeginFrame(StringHash eventType, VariantMap& eventData)
     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)
-    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)
         return;
     Variant variant = element->GetVar(VAR_SCREEN_JOYSTICK_INDEX);
@@ -1189,8 +1256,10 @@ void Input::HandleMouseClick(StringHash eventType, VariantMap& eventData)
         return;
     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
     SDL_Event evt;
@@ -1198,60 +1267,36 @@ void Input::HandleMouseClick(StringHash eventType, VariantMap& eventData)
     const String& name = element->GetName();
     if (name.StartsWith("Button"))
     {
+        if (eventType == E_TOUCHMOVE)
+            return;
+
         // 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
         {
-            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"))
     {
-        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.jaxis.which = SCREEN_JOYSTICK_START_INDEX + index;
             evt.jhat.hat = ToUInt(name.Substring(3));
             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_))
                     evt.jhat.value |= HAT_UP;
                 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;
             }
         }
+        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
         return;

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

@@ -47,6 +47,8 @@ struct TouchState
     IntVector2 delta_;
     /// Finger pressure.
     float pressure_;
+    /// Last touched UI element from screen joystick.
+    WeakPtr<UIElement> touchedElement_;
 };
 
 /// %Input state for a joystick.
@@ -57,8 +59,6 @@ struct JoystickState
         joystick_(0), controller_(0)
     {
     }
-    /// Destructor.
-    ~JoystickState();
 
     /// Return number of buttons.
     unsigned GetNumButtons() const { return buttons_.Size(); }
@@ -144,8 +144,10 @@ public:
     void CloseJoystick(unsigned index);
     /// Redetect joysticks. Return true if successful.
     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.
     void SetScreenKeyboardVisible(bool enable);
 
@@ -237,8 +239,8 @@ private:
     void HandleScreenMode(StringHash eventType, VariantMap& eventData);
     /// Handle frame start event.
     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.
     void HandleSDLEvent(void* sdlEvent);
 

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

@@ -8,6 +8,7 @@ struct TouchState
     IntVector2 lastPosition_ @ lastPosition;
     IntVector2 delta_ @ delta;
     float pressure_ @ pressure;
+    WeakPtr<UIElement> touchedElement_ @ touchedElement;
 };
 
 struct JoystickState
@@ -33,7 +34,8 @@ class Input : public Object
     bool OpenJoystick(unsigned index);
     void CloseJoystick(unsigned index);
     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);
 
     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 delta", offsetof(TouchState, delta_));
     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->RegisterObjectBehaviour("JoystickState", asBEHAVE_ADDREF, "void f()", asFUNCTION(FakeAddRef), 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", "float get_axisPosition(uint) const", asMETHOD(JoystickState, GetAxisPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("JoystickState", "int get_hatPosition(uint) const", asMETHOD(JoystickState, GetHatPosition), asCALL_THISCALL);
-    
+
     RegisterObject<Input>(engine, "Input");
     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", "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 GetKeyFromScancode(int) const", asMETHOD(Input, GetKeyFromScancode), 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);
     rootModalElement_->SetTraversalMode(TM_DEPTH_FIRST);
-    
+
     // Register UI library object factories
     RegisterUILibrary(context_);
 
@@ -303,7 +303,7 @@ void UI::Update(float timeStep)
     // Expire hovers
     for (HashMap<WeakPtr<UIElement>, bool>::Iterator i = hoveredElements_.Begin(); i != hoveredElements_.End(); ++i)
         i->second_ = false;
-    
+
     // Drag begin based on time
     if (dragElement_ && dragBeginPending_)
     {
@@ -320,11 +320,11 @@ void UI::Update(float timeStep)
             SendDragOrHoverEvent(E_DRAGBEGIN, dragElement_, dragBeginPos_);
         }
     }
-    
+
     // Mouse hover
     if (!usingTouchInput_ && cursorVisible)
         ProcessHover(cursorPos, mouseButtons_, qualifiers_, cursor_);
-    
+
     // Touch hover
     Input* input = GetSubsystem<Input>();
     unsigned numTouches = input->GetNumTouches();
@@ -333,7 +333,7 @@ void UI::Update(float timeStep)
         TouchState* touch = input->GetTouch(i);
         ProcessHover(touch->position_, MOUSEB_LEFT, 0, 0);
     }
-    
+
     // End hovers that expired without refreshing
     for (HashMap<WeakPtr<UIElement>, bool>::Iterator i = hoveredElements_.Begin(); i != hoveredElements_.End();)
     {
@@ -343,7 +343,7 @@ void UI::Update(float timeStep)
             if (element)
             {
                 using namespace HoverEnd;
-                
+
                 VariantMap& eventData = GetEventDataMap();
                 eventData[P_ELEMENT] = element;
                 element->SendEvent(E_HOVEREND, eventData);
@@ -353,7 +353,7 @@ void UI::Update(float timeStep)
         else
             ++i;
     }
-    
+
     Update(timeStep, rootElement_);
     Update(timeStep, rootModalElement_);
 }
@@ -366,7 +366,7 @@ void UI::RenderUpdate()
 
     // If the OS cursor is visible, do not render the UI's own cursor
     bool osCursorVisible = GetSubsystem<Input>()->IsMouseVisible();
-    
+
     // Get rendering batches from the non-modal UI elements
     batches_.Clear();
     vertexData_.Clear();
@@ -397,7 +397,7 @@ void UI::Render()
     bool osCursorVisible = GetSubsystem<Input>()->IsMouseVisible();
     if (cursor_ && osCursorVisible)
         cursor_->ApplyOSCursorShape();
-    
+
     SetVertexData(vertexBuffer_, vertexData_);
     SetVertexData(debugVertexBuffer_, debugVertexData_);
 
@@ -608,7 +608,7 @@ const String& UI::GetClipboardText() const
         if (text)
             SDL_free(text);
     }
-    
+
     return clipBoard_;
 }
 
@@ -940,7 +940,7 @@ void UI::ProcessHover(const IntVector2& cursorPos, int buttons, int qualifiers,
         if (!dragElement_ || dragElement_ == element || dragDropTest)
         {
             element->OnHover(element->ScreenToElement(cursorPos), cursorPos, buttons, qualifiers, cursor);
-            
+
             // Begin hover event
             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
             clickElement_ = element;
-            
+
             // Fire double click event if element matches and is in time
             if (doubleClickElement_ && element == doubleClickElement_ && clickTimer_.GetMSec(true) <
                 (unsigned)(doubleClickInterval_ * 1000) && lastMouseButtons_ == buttons)
@@ -1011,7 +1011,7 @@ void UI::ProcessClickBegin(const IntVector2& cursorPos, int button, int buttons,
                 doubleClickElement_ = element;
                 clickTimer_.Reset();
             }
-            
+
             // Handle start of drag. Click handling may have caused destruction of the element, so check the pointer again
             if (element && !dragElement_ && buttons == MOUSEB_LEFT)
             {
@@ -1027,7 +1027,7 @@ void UI::ProcessClickBegin(const IntVector2& cursorPos, int button, int buttons,
             SetFocusElement(0);
             SendClickEvent(E_UIMOUSECLICK, element, cursorPos, button, buttons, qualifiers);
         }
-        
+
         lastMouseButtons_ = buttons;
     }
 }
@@ -1041,9 +1041,9 @@ void UI::ProcessClickEnd(const IntVector2& cursorPos, int button, int buttons, i
         // Handle end of click
         if (element)
             element->OnClickEnd(element->ScreenToElement(cursorPos), cursorPos, button, buttons, qualifiers, cursor, clickElement_);
-        
+
         SendClickEvent(E_UIMOUSECLICKEND, element, cursorPos, button, buttons, qualifiers);
-        
+
         // Handle end of drag
         if (dragElement_ && !buttons)
         {
@@ -1080,7 +1080,7 @@ void UI::ProcessClickEnd(const IntVector2& cursorPos, int button, int buttons, i
             dragElement_.Reset();
             dragBeginPending_ = false;
         }
-        
+
         clickElement_.Reset();
     }
 }
@@ -1102,7 +1102,7 @@ void UI::ProcessMove(const IntVector2& cursorPos, int buttons, int qualifiers, C
                     SendDragOrHoverEvent(E_DRAGBEGIN, dragElement_, dragBeginPos_);
                 }
             }
-            
+
             if (!dragBeginPending_)
             {
                 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_BUTTONS] = buttons;
     eventData[UIMouseClick::P_QUALIFIERS] = qualifiers;
-    
+
     // For click end events, send also the element the click began on
     if (eventType == E_UIMOUSECLICKEND)
         eventData[UIMouseClickEnd::P_BEGINELEMENT] = clickElement_;
-    
+
     SendEvent(eventType, eventData);
 }
 
@@ -1284,7 +1284,6 @@ void UI::HandleTouchBegin(StringHash eventType, VariantMap& eventData)
     using namespace TouchBegin;
 
     IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
-    WeakPtr<UIElement> element(GetElementAt(pos));
     usingTouchInput_ = true;
 
     ProcessClickBegin(pos, MOUSEB_LEFT, MOUSEB_LEFT, 0, 0, true);
@@ -1329,7 +1328,7 @@ void UI::HandleKeyDown(StringHash eventType, VariantMap& eventData)
         bool cursorVisible;
         GetCursorPositionAndVisible(cursorPos, cursorVisible);
         SendDragOrHoverEvent(E_DRAGCANCEL, dragElement_, cursorPos);
-        
+
         dragElement_.Reset();
         dragBeginPending_ = false;
         return;
@@ -1429,28 +1428,28 @@ void UI::HandleRenderUpdate(StringHash eventType, VariantMap& eventData)
 void UI::HandleDropFile(StringHash eventType, VariantMap& eventData)
 {
     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)
     if (input->IsMouseVisible())
     {
         IntVector2 screenPos = input->GetMousePosition();
         UIElement* element = GetElementAt(screenPos);
-        
+
         using namespace UIDropFile;
-        
+
         VariantMap uiEventData;
         uiEventData[P_FILENAME] = eventData[P_FILENAME];
         uiEventData[P_X] = screenPos.x_;
         uiEventData[P_Y] = screenPos.y_;
         uiEventData[P_ELEMENT] = element;
-        
+
         if (element)
         {
             IntVector2 relativePos = element->ScreenToElement(screenPos);
             uiEventData[P_ELEMENTX] = relativePos.x_;
             uiEventData[P_ELEMENTY] = relativePos.y_;
         }
-        
+
         SendEvent(E_UIDROPFILE, uiEventData);
     }
 }

+ 7 - 1
Source/Samples/Sample.h

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

+ 45 - 2
Source/Samples/Sample.inl

@@ -26,6 +26,7 @@
 #include "Engine.h"
 #include "FileSystem.h"
 #include "Graphics.h"
+#include "Input.h"
 #include "InputEvents.h"
 #include "Renderer.h"
 #include "ResourceCache.h"
@@ -36,7 +37,11 @@
 #include "XMLFile.h"
 
 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()
 {
+    // Initialize touch input on mobile platforms
+    InitTouchInput();
+
     // Create logo
     CreateLogo();
 
@@ -64,6 +72,19 @@ void Sample::Start()
     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)
 {
     if (logoSprite_)
@@ -125,6 +146,7 @@ void Sample::CreateConsoleAndDebugHud()
     // Create console
     Console* console = engine_->CreateConsole();
     console->SetDefaultStyle(xmlFile);
+    console->GetBackground()->SetOpacity(0.8f);
 
     // Create debug HUD.
     DebugHud* debugHud = engine_->CreateDebugHud();
@@ -160,8 +182,29 @@ void Sample::HandleKeyDown(StringHash eventType, VariantMap& eventData)
     {
         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
-        if (key == '1')
+        else if (key == '1')
         {
             int quality = renderer->GetTextureQuality();
             ++quality;