فهرست منبع

Added setFixedUpdateFps() to ScriptInstance.
Added setSelected() to UIElement. Visually it is the same as hover.
Added onKey() to UIElement.
Added cursor movement by arrows or mouse click and DEL key support to LineEdit.
Added possibility to query each char position from Text.
Script code cleanup.

Lasse Öörni 15 سال پیش
والد
کامیت
523559f1a0

+ 5 - 8
Bin/Data/Scripts/GraphicsTest.as

@@ -55,9 +55,6 @@ void start()
 void runFrame()
 void runFrame()
 {
 {
     engine.runFrame(testScene, camera, true);
     engine.runFrame(testScene, camera, true);
-
-    if (input.getKeyPress(KEY_ESCAPE))
-        engine.exit();
 }
 }
 
 
 void initScene()
 void initScene()
@@ -327,7 +324,7 @@ void initUI()
     cursor.setStyleAuto(uiStyle);
     cursor.setStyleAuto(uiStyle);
     cursor.setPosition(renderer.getWidth() / 2, renderer.getHeight() / 2);
     cursor.setPosition(renderer.getWidth() / 2, renderer.getHeight() / 2);
     ui.setCursor(cursor);
     ui.setCursor(cursor);
-    
+
     //XMLFile@ uiLayout = cache.getResource("XMLFile", "UI/TestLayout.xml");
     //XMLFile@ uiLayout = cache.getResource("XMLFile", "UI/TestLayout.xml");
     //UIElement@ layoutRoot = ui.loadLayout(uiLayout, uiStyle);
     //UIElement@ layoutRoot = ui.loadLayout(uiLayout, uiStyle);
     //uiRoot.addChild(layoutRoot);
     //uiRoot.addChild(layoutRoot);
@@ -361,7 +358,7 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
     if (!paused)
     if (!paused)
         animateScene(timeStep);
         animateScene(timeStep);
 
 
-    if (@ui.getFocusElement() == null)
+    if (ui.getFocusElement() is null)
     {
     {
         float speedMultiplier = 1.0f;
         float speedMultiplier = 1.0f;
         if (input.getKeyDown(KEY_SHIFT))
         if (input.getKeyDown(KEY_SHIFT))
@@ -491,7 +488,7 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
             pipeline.setEdgeFilter(params);
             pipeline.setEdgeFilter(params);
         }
         }
         
         
-        if (input.getKeyPress(KEY_ESCAPE))
+        if ((input.getKeyPress(KEY_ESCAPE)) && (ui.getFocusElement() is null))
             engine.exit();
             engine.exit();
     }
     }
 }
 }
@@ -530,7 +527,7 @@ void handleMouseButtonDown(StringHash eventType, VariantMap& eventData)
         uiCursor.setVisible(false);
         uiCursor.setVisible(false);
 
 
     // Test creating a new physics object
     // Test creating a new physics object
-    if ((button == MOUSEB_LEFT) && (@ui.getElementAt(ui.getCursorPosition(), true) == null) && (@ui.getFocusElement() == null))
+    if ((button == MOUSEB_LEFT) && (ui.getElementAt(ui.getCursorPosition(), true) is null) && (ui.getFocusElement() is null))
     {
     {
         Entity@ newEntity = testScene.createEntity();
         Entity@ newEntity = testScene.createEntity();
         
         
@@ -565,7 +562,7 @@ void handleMouseButtonUp(StringHash eventType, VariantMap& eventData)
 void handlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
 void handlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
 {
 {
     IntVector2 pos = ui.getCursorPosition();
     IntVector2 pos = ui.getCursorPosition();
-    if (@ui.getElementAt(pos, true) == null)
+    if (ui.getElementAt(pos, true) is null)
     {
     {
         Ray cameraRay = camera.getScreenRay(float(pos.x) / renderer.getWidth(), float(pos.y) / renderer.getHeight());
         Ray cameraRay = camera.getScreenRay(float(pos.x) / renderer.getWidth(), float(pos.y) / renderer.getHeight());
         array<RayQueryResult> result = testScene.getOctree().raycast(cameraRay, NODE_GEOMETRY, NODE_BILLBOARDSET, 250.0f, RAY_TRIANGLE);
         array<RayQueryResult> result = testScene.getOctree().raycast(cameraRay, NODE_GEOMETRY, NODE_BILLBOARDSET, 250.0f, RAY_TRIANGLE);

+ 5 - 5
Bin/Data/Scripts/NinjaSnowWar.as

@@ -222,7 +222,7 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
 void handleFixedUpdate(StringHash eventType, VariantMap& eventData)
 void handleFixedUpdate(StringHash eventType, VariantMap& eventData)
 {
 {
     // Check that scene being updated matches (we have only one scene, but for completeness...)
     // Check that scene being updated matches (we have only one scene, but for completeness...)
-    if (@eventData["Scene"].getScene() != @gameScene)
+    if (eventData["Scene"].getScene() !is gameScene)
         return;
         return;
 
 
     float timeStep = eventData["TimeStep"].getFloat();
     float timeStep = eventData["TimeStep"].getFloat();
@@ -319,7 +319,7 @@ void spawnObjects(float timeStep)
 
 
 void checkEndAndRestart()
 void checkEndAndRestart()
 {
 {
-    if ((gameOn) && (@gameScene.getEntity("Player") == null))
+    if ((gameOn) && (gameScene.getEntity("Player") is null))
     {
     {
         gameOn = false;
         gameOn = false;
         messageText.setText("Press Fire or Jump to restart!");
         messageText.setText("Press Fire or Jump to restart!");
@@ -361,7 +361,7 @@ void updateControls()
     playerControls.pitch = clamp(playerControls.pitch, -60, 60);
     playerControls.pitch = clamp(playerControls.pitch, -60, 60);
 
 
     Entity@ playerEntity = gameScene.getEntity("Player");
     Entity@ playerEntity = gameScene.getEntity("Player");
-    if (@playerEntity != null)
+    if (playerEntity !is null)
     {
     {
         Ninja@ playerNinja = cast<Ninja>(playerEntity.getScriptObject());
         Ninja@ playerNinja = cast<Ninja>(playerEntity.getScriptObject());
         playerNinja.controls = playerControls;
         playerNinja.controls = playerControls;
@@ -371,7 +371,7 @@ void updateControls()
 void updateCamera()
 void updateCamera()
 {
 {
     Entity@ playerEntity = gameScene.getEntity("Player");
     Entity@ playerEntity = gameScene.getEntity("Player");
-    if (@playerEntity == null)
+    if (playerEntity is null)
         return;
         return;
 
 
     RigidBody@ body = playerEntity.getComponent("RigidBody");
     RigidBody@ body = playerEntity.getComponent("RigidBody");
@@ -407,7 +407,7 @@ void updateStatus()
     hiscoreText.setText("Hiscore " + hiscore);
     hiscoreText.setText("Hiscore " + hiscore);
 
 
     Entity@ playerEntity = gameScene.getEntity("Player");
     Entity@ playerEntity = gameScene.getEntity("Player");
-    if (@playerEntity == null)
+    if (playerEntity is null)
         return;
         return;
 
 
     GameObject@ object = cast<GameObject>(playerEntity.getScriptObject());
     GameObject@ object = cast<GameObject>(playerEntity.getScriptObject());

+ 2 - 0
Engine/Engine/RegisterScript.cpp

@@ -77,12 +77,14 @@ static void registerScriptInstance(asIScriptEngine* engine)
     registerComponent<ScriptInstance>(engine, "ScriptInstance");
     registerComponent<ScriptInstance>(engine, "ScriptInstance");
     engine->RegisterObjectMethod("ScriptInstance", "bool setScriptClass(ScriptFile@+, const string& in)", asMETHOD(ScriptInstance, setScriptClass), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScriptInstance", "bool setScriptClass(ScriptFile@+, const string& in)", asMETHOD(ScriptInstance, setScriptClass), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScriptInstance", "void setEnabled(bool)", asMETHOD(ScriptInstance, setEnabled), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScriptInstance", "void setEnabled(bool)", asMETHOD(ScriptInstance, setEnabled), asCALL_THISCALL);
+    engine->RegisterObjectMethod("ScriptInstance", "void setFixedUpdateFps(int)", asMETHOD(ScriptInstance, setFixedUpdateFps), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScriptInstance", "bool execute(const string& in, const array<Variant>@+)", asFUNCTION(ScriptInstanceExecute), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("ScriptInstance", "bool execute(const string& in, const array<Variant>@+)", asFUNCTION(ScriptInstanceExecute), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("ScriptInstance", "ScriptFile@+ getScriptFile() const", asMETHOD(ScriptInstance, getScriptFile), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScriptInstance", "ScriptFile@+ getScriptFile() const", asMETHOD(ScriptInstance, getScriptFile), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScriptInstance", "ScriptObject@+ getScriptObject() const", asMETHOD(ScriptInstance, getScriptObject), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScriptInstance", "ScriptObject@+ getScriptObject() const", asMETHOD(ScriptInstance, getScriptObject), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScriptInstance", "const string& getClassName() const", asMETHOD(ScriptInstance, getClassName), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScriptInstance", "const string& getClassName() const", asMETHOD(ScriptInstance, getClassName), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScriptInstance", "bool isRunning() const", asMETHOD(ScriptInstance, isRunning), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScriptInstance", "bool isRunning() const", asMETHOD(ScriptInstance, isRunning), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScriptInstance", "bool isEnabled() const", asMETHOD(ScriptInstance, isEnabled), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScriptInstance", "bool isEnabled() const", asMETHOD(ScriptInstance, isEnabled), asCALL_THISCALL);
+    engine->RegisterObjectMethod("ScriptInstance", "int getFixedUpdateFps() const", asMETHOD(ScriptInstance, getFixedUpdateFps), asCALL_THISCALL);
     registerRefCasts<Component, ScriptInstance>(engine, "Component", "ScriptInstance");
     registerRefCasts<Component, ScriptInstance>(engine, "Component", "ScriptInstance");
     
     
     engine->RegisterGlobalFunction("ScriptInstance@+ getSelf()", asFUNCTION(GetSelf), asCALL_CDECL);
     engine->RegisterGlobalFunction("ScriptInstance@+ getSelf()", asFUNCTION(GetSelf), asCALL_CDECL);

+ 2 - 0
Engine/Engine/RegisterTemplates.h

@@ -415,6 +415,7 @@ template <class T> void registerUIElement(asIScriptEngine* engine, const char* c
     engine->RegisterObjectMethod(className, "void setEnabled(bool)", asMETHOD(T, setEnabled), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setEnabled(bool)", asMETHOD(T, setEnabled), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setFocusable(bool)", asMETHOD(T, setFocusable), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setFocusable(bool)", asMETHOD(T, setFocusable), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setFocus(bool)", asMETHOD(T, setFocus), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setFocus(bool)", asMETHOD(T, setFocus), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void setSelected(bool)", asMETHOD(T, setSelected), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setVisible(bool)", asMETHOD(T, setVisible), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setVisible(bool)", asMETHOD(T, setVisible), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setUserData(const Variant& in)", asMETHOD(T, setUserData), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setUserData(const Variant& in)", asMETHOD(T, setUserData), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setStyleAuto(XMLFile@+)", asFUNCTION(UIElementSetStyleAuto<T>), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod(className, "void setStyleAuto(XMLFile@+)", asFUNCTION(UIElementSetStyleAuto<T>), asCALL_CDECL_OBJLAST);
@@ -443,6 +444,7 @@ template <class T> void registerUIElement(asIScriptEngine* engine, const char* c
     engine->RegisterObjectMethod(className, "bool isEnabled() const", asMETHOD(T, isEnabled), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool isEnabled() const", asMETHOD(T, isEnabled), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool isFocusable() const", asMETHOD(T, isFocusable), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool isFocusable() const", asMETHOD(T, isFocusable), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool hasFocus() const", asMETHOD(T, hasFocus), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool hasFocus() const", asMETHOD(T, hasFocus), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "bool isSelected() const", asMETHOD(T, isSelected), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool isVisible() const", asMETHOD(T, isVisible), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool isVisible() const", asMETHOD(T, isVisible), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool isHovering() const", asMETHOD(T, isHovering), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool isHovering() const", asMETHOD(T, isHovering), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool hasColorGradient() const", asMETHOD(T, hasColorGradient), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool hasColorGradient() const", asMETHOD(T, hasColorGradient), asCALL_THISCALL);

+ 4 - 2
Engine/Engine/RegisterUI.cpp

@@ -160,14 +160,16 @@ static void registerLineEdit(asIScriptEngine* engine)
 {
 {
     registerBorderImage<LineEdit>(engine, "LineEdit");
     registerBorderImage<LineEdit>(engine, "LineEdit");
     engine->RegisterObjectMethod("LineEdit", "void setText(const string& in)", asMETHOD(LineEdit, setText), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "void setText(const string& in)", asMETHOD(LineEdit, setText), asCALL_THISCALL);
-    engine->RegisterObjectMethod("LineEdit", "void setEchoCharacter(uint8)", asMETHOD(LineEdit, setEchoCharacter), asCALL_THISCALL);
+    engine->RegisterObjectMethod("LineEdit", "void setCursorPosition(uint)", asMETHOD(LineEdit, setCursorPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "void setCursorBlinkRate(float)", asMETHOD(LineEdit, setCursorBlinkRate), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "void setCursorBlinkRate(float)", asMETHOD(LineEdit, setCursorBlinkRate), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "void setMaxLength(uint)", asMETHOD(LineEdit, setMaxLength), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "void setMaxLength(uint)", asMETHOD(LineEdit, setMaxLength), asCALL_THISCALL);
+    engine->RegisterObjectMethod("LineEdit", "void setEchoCharacter(uint8)", asMETHOD(LineEdit, setEchoCharacter), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "void setDefocusable(bool)", asMETHOD(LineEdit, setDefocusable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "void setDefocusable(bool)", asMETHOD(LineEdit, setDefocusable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "const string& getText() const", asMETHOD(LineEdit, getText), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "const string& getText() const", asMETHOD(LineEdit, getText), asCALL_THISCALL);
-    engine->RegisterObjectMethod("LineEdit", "uint8 getEchoCharacter() const", asMETHOD(LineEdit, getEchoCharacter), asCALL_THISCALL);
+    engine->RegisterObjectMethod("LineEdit", "uint getCursorPosition() const", asMETHOD(LineEdit, getCursorPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "float getCursorBlinkRate() const", asMETHOD(LineEdit, getCursorBlinkRate), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "float getCursorBlinkRate() const", asMETHOD(LineEdit, getCursorBlinkRate), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "uint getMaxLength() const", asMETHOD(LineEdit, getMaxLength), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "uint getMaxLength() const", asMETHOD(LineEdit, getMaxLength), asCALL_THISCALL);
+    engine->RegisterObjectMethod("LineEdit", "uint8 getEchoCharacter() const", asMETHOD(LineEdit, getEchoCharacter), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "bool isDefocusable() const", asMETHOD(LineEdit, isDefocusable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "bool isDefocusable() const", asMETHOD(LineEdit, isDefocusable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "Text@+ getTextElement() const", asMETHOD(LineEdit, getTextElement), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "Text@+ getTextElement() const", asMETHOD(LineEdit, getTextElement), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "BorderImage@+ getCursorElement() const", asMETHOD(LineEdit, getCursorElement), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "BorderImage@+ getCursorElement() const", asMETHOD(LineEdit, getCursorElement), asCALL_THISCALL);

+ 76 - 13
Engine/Script/ScriptInstance.cpp

@@ -65,7 +65,11 @@ ScriptInstance::ScriptInstance(ScriptEngine* scriptEngine, const std::string& na
     Component(name),
     Component(name),
     mScriptEngine(scriptEngine),
     mScriptEngine(scriptEngine),
     mScriptObject(0),
     mScriptObject(0),
-    mEnabled(true)
+    mEnabled(true),
+    mFixedUpdateFps(0),
+    mFixedUpdateInterval(0.0f),
+    mFixedUpdateTimer(0.0f),
+    mFixedPostUpdateTimer(0.0f)
 {
 {
     if (!mScriptEngine)
     if (!mScriptEngine)
         EXCEPTION("Null script engine for ScriptInstance");
         EXCEPTION("Null script engine for ScriptInstance");
@@ -85,6 +89,8 @@ void ScriptInstance::save(Serializer& dest)
     dest.writeBool(mEnabled);
     dest.writeBool(mEnabled);
     dest.writeStringHash(getResourceHash(mScriptFile));
     dest.writeStringHash(getResourceHash(mScriptFile));
     dest.writeString(mClassName);
     dest.writeString(mClassName);
+    dest.writeInt(mFixedUpdateFps);
+    dest.writeFloat(mFixedUpdateTimer);
     
     
     // Save script's data into a separate buffer for safety
     // Save script's data into a separate buffer for safety
     static VectorBuffer scriptBuffer;
     static VectorBuffer scriptBuffer;
@@ -107,6 +113,9 @@ void ScriptInstance::load(Deserializer& source, ResourceCache* cache)
     StringHash scriptFile = source.readStringHash();
     StringHash scriptFile = source.readStringHash();
     std::string className = source.readString();
     std::string className = source.readString();
     setScriptClass(cache->getResource<ScriptFile>(scriptFile), className);
     setScriptClass(cache->getResource<ScriptFile>(scriptFile), className);
+    mFixedUpdateFps = source.readInt();
+    mFixedUpdateTimer = mFixedPostUpdateTimer = source.readFloat();
+    mFixedUpdateInterval = mFixedUpdateFps ? (1.0f / mFixedUpdateFps) : 0.0f;
     
     
     static VectorBuffer scriptBuffer;
     static VectorBuffer scriptBuffer;
     unsigned scriptDataSize = source.readVLE();
     unsigned scriptDataSize = source.readVLE();
@@ -133,6 +142,8 @@ void ScriptInstance::saveXML(XMLElement& dest)
     scriptElem.setBool("enabled", mEnabled);
     scriptElem.setBool("enabled", mEnabled);
     scriptElem.setString("name", getResourceName(mScriptFile));
     scriptElem.setString("name", getResourceName(mScriptFile));
     scriptElem.setString("class", mClassName);
     scriptElem.setString("class", mClassName);
+    scriptElem.setInt("fps", mFixedUpdateFps);
+    scriptElem.setFloat("timeacc", mFixedUpdateTimer);
     
     
     if (mMethods[METHOD_SAVEXML])
     if (mMethods[METHOD_SAVEXML])
     {
     {
@@ -150,6 +161,9 @@ void ScriptInstance::loadXML(const XMLElement& source, ResourceCache* cache)
     XMLElement scriptElem = source.getChildElement("script");
     XMLElement scriptElem = source.getChildElement("script");
     mEnabled = scriptElem.getBool("enabled");
     mEnabled = scriptElem.getBool("enabled");
     setScriptClass(cache->getResource<ScriptFile>(scriptElem.getString("name")), scriptElem.getString("class"));
     setScriptClass(cache->getResource<ScriptFile>(scriptElem.getString("name")), scriptElem.getString("class"));
+    mFixedUpdateFps = scriptElem.getInt("fps");
+    mFixedUpdateTimer = mFixedPostUpdateTimer = scriptElem.getFloat("timeacc");
+    mFixedUpdateInterval = mFixedUpdateFps ? (1.0f / mFixedUpdateFps) : 0.0f;
     
     
     if (mMethods[METHOD_LOADXML])
     if (mMethods[METHOD_LOADXML])
     {
     {
@@ -170,6 +184,8 @@ bool ScriptInstance::writeNetUpdate(Serializer& dest, Serializer& destRevision,
     checkBool(mEnabled, true, baseRevision, bits, 1);
     checkBool(mEnabled, true, baseRevision, bits, 1);
     checkStringHash(scriptFileHash, StringHash(), baseRevision, bits, 2);
     checkStringHash(scriptFileHash, StringHash(), baseRevision, bits, 2);
     checkString(mClassName, std::string(), baseRevision, bits, 2);
     checkString(mClassName, std::string(), baseRevision, bits, 2);
+    checkInt(mFixedUpdateFps, 0, baseRevision, bits, 4);
+    checkFloat(mFixedUpdateTimer, 0.0f, baseRevision, bits, 8);
     
     
     // Save script's data into a separate buffer for safety
     // Save script's data into a separate buffer for safety
     static VectorBuffer scriptBuffer;
     static VectorBuffer scriptBuffer;
@@ -185,13 +201,15 @@ bool ScriptInstance::writeNetUpdate(Serializer& dest, Serializer& destRevision,
     // Compare buffer to previous revision if available
     // Compare buffer to previous revision if available
     unsigned scriptDataSize = scriptBuffer.getSize();
     unsigned scriptDataSize = scriptBuffer.getSize();
     if (scriptDataSize)
     if (scriptDataSize)
-        checkBuffer(scriptBuffer, baseRevision, bits, 4);
+        checkBuffer(scriptBuffer, baseRevision, bits, 16);
     
     
     dest.writeUByte(bits);
     dest.writeUByte(bits);
     writeBoolDelta(mEnabled, dest, destRevision, bits & 1);
     writeBoolDelta(mEnabled, dest, destRevision, bits & 1);
     writeStringHashDelta(scriptFileHash, dest, destRevision, bits & 2);
     writeStringHashDelta(scriptFileHash, dest, destRevision, bits & 2);
     writeStringDelta(mClassName, dest, destRevision, bits & 2);
     writeStringDelta(mClassName, dest, destRevision, bits & 2);
-    writeBufferDelta(scriptBuffer, dest, destRevision, bits & 4);
+    writeIntDelta(mFixedUpdateFps, dest, destRevision, bits & 4);
+    writeFloatDelta(mFixedUpdateTimer, dest, destRevision, bits & 8);
+    writeBufferDelta(scriptBuffer, dest, destRevision, bits & 16);
     
     
     return bits != 0;
     return bits != 0;
 }
 }
@@ -208,6 +226,13 @@ void ScriptInstance::readNetUpdate(Deserializer& source, ResourceCache* cache, c
         setScriptClass(cache->getResource<ScriptFile>(scriptFile), className);
         setScriptClass(cache->getResource<ScriptFile>(scriptFile), className);
     }
     }
     if (bits & 4)
     if (bits & 4)
+    {
+        mFixedUpdateFps = source.readInt();
+        mFixedUpdateInterval = mFixedUpdateFps ? (1.0f / mFixedUpdateFps) : 0.0f;
+    }
+    if (bits & 8)
+        mFixedUpdateTimer = mFixedPostUpdateTimer = source.readFloat();
+    if (bits & 16)
     {
     {
         static VectorBuffer scriptBuffer;
         static VectorBuffer scriptBuffer;
         unsigned scriptDataSize = source.readVLE();
         unsigned scriptDataSize = source.readVLE();
@@ -293,6 +318,14 @@ void ScriptInstance::setEnabled(bool enable)
     mEnabled = enable;
     mEnabled = enable;
 }
 }
 
 
+void ScriptInstance::setFixedUpdateFps(int fps)
+{
+    mFixedUpdateFps = max(fps, 0);
+    mFixedUpdateInterval = mFixedUpdateFps ? (1.0f / mFixedUpdateFps) : 0.0f;
+    mFixedUpdateTimer = 0.0f;
+    mFixedPostUpdateTimer = 0.0f;
+}
+
 bool ScriptInstance::execute(const std::string& declaration, const std::vector<Variant>& parameters)
 bool ScriptInstance::execute(const std::string& declaration, const std::vector<Variant>& parameters)
 {
 {
     if (!mScriptObject)
     if (!mScriptObject)
@@ -401,7 +434,7 @@ void ScriptInstance::getSupportedMethods()
 
 
 void ScriptInstance::handleSceneUpdate(StringHash eventType, VariantMap& eventData)
 void ScriptInstance::handleSceneUpdate(StringHash eventType, VariantMap& eventData)
 {
 {
-    if ((!mEnabled) || (!mScriptFile) || (!mScriptObject))
+    if ((!mEnabled) || (!mScriptObject))
         return;
         return;
     
     
     using namespace SceneUpdate;
     using namespace SceneUpdate;
@@ -418,7 +451,7 @@ void ScriptInstance::handleSceneUpdate(StringHash eventType, VariantMap& eventDa
 
 
 void ScriptInstance::handleScenePostUpdate(StringHash eventType, VariantMap& eventData)
 void ScriptInstance::handleScenePostUpdate(StringHash eventType, VariantMap& eventData)
 {
 {
-    if ((!mEnabled) || (!mScriptFile) || (!mScriptObject))
+    if ((!mEnabled) || (!mScriptObject))
         return;
         return;
     
     
     using namespace ScenePostUpdate;
     using namespace ScenePostUpdate;
@@ -435,7 +468,7 @@ void ScriptInstance::handleScenePostUpdate(StringHash eventType, VariantMap& eve
 
 
 void ScriptInstance::handlePhysicsPreStep(StringHash eventType, VariantMap& eventData)
 void ScriptInstance::handlePhysicsPreStep(StringHash eventType, VariantMap& eventData)
 {
 {
-    if ((!mEnabled) || (!mScriptFile) || (!mScriptObject))
+    if ((!mEnabled) || (!mScriptObject))
         return;
         return;
     
     
     using namespace PhysicsPreStep;
     using namespace PhysicsPreStep;
@@ -444,15 +477,30 @@ void ScriptInstance::handlePhysicsPreStep(StringHash eventType, VariantMap& even
     Scene* scene = mEntity ? mEntity->getScene() : 0;
     Scene* scene = mEntity ? mEntity->getScene() : 0;
     if (eventData[P_SCENE].getPtr() == (void*)scene)
     if (eventData[P_SCENE].getPtr() == (void*)scene)
     {
     {
-        std::vector<Variant> parameters;
-        parameters.push_back(eventData[P_TIMESTEP]);
-        mScriptFile->execute(mScriptObject, mMethods[METHOD_UPDATEFIXED], parameters);
+        if (!mFixedUpdateFps)
+        {
+            std::vector<Variant> parameters;
+            parameters.push_back(eventData[P_TIMESTEP]);
+            mScriptFile->execute(mScriptObject, mMethods[METHOD_UPDATEFIXED], parameters);
+        }
+        else
+        {
+            float timeStep = eventData[P_TIMESTEP].getFloat();
+            mFixedUpdateTimer += timeStep;
+            if (mFixedUpdateTimer >= mFixedUpdateInterval)
+            {
+                mFixedUpdateTimer = fmodf(mFixedUpdateTimer, mFixedUpdateInterval);
+                std::vector<Variant> parameters;
+                parameters.push_back(mFixedUpdateInterval);
+                mScriptFile->execute(mScriptObject, mMethods[METHOD_UPDATEFIXED], parameters);
+            }
+        }
     }
     }
 }
 }
 
 
 void ScriptInstance::handlePhysicsPostStep(StringHash eventType, VariantMap& eventData)
 void ScriptInstance::handlePhysicsPostStep(StringHash eventType, VariantMap& eventData)
 {
 {
-    if ((!mEnabled) || (!mScriptFile) || (!mScriptObject))
+    if ((!mEnabled) || (!mScriptObject))
         return;
         return;
     
     
     using namespace PhysicsPostStep;
     using namespace PhysicsPostStep;
@@ -461,9 +509,24 @@ void ScriptInstance::handlePhysicsPostStep(StringHash eventType, VariantMap& eve
     Scene* scene = mEntity ? mEntity->getScene() : 0;
     Scene* scene = mEntity ? mEntity->getScene() : 0;
     if (eventData[P_SCENE].getPtr() == (void*)scene)
     if (eventData[P_SCENE].getPtr() == (void*)scene)
     {
     {
-        std::vector<Variant> parameters;
-        parameters.push_back(eventData[P_TIMESTEP]);
-        mScriptFile->execute(mScriptObject, mMethods[METHOD_POSTUPDATEFIXED], parameters);
+        if (!mFixedUpdateFps)
+        {
+            std::vector<Variant> parameters;
+            parameters.push_back(eventData[P_TIMESTEP]);
+            mScriptFile->execute(mScriptObject, mMethods[METHOD_UPDATEFIXED], parameters);
+        }
+        else
+        {
+            float timeStep = eventData[P_TIMESTEP].getFloat();
+            mFixedPostUpdateTimer += timeStep;
+            if (mFixedPostUpdateTimer >= mFixedUpdateInterval)
+            {
+                mFixedPostUpdateTimer = fmodf(mFixedPostUpdateTimer, mFixedUpdateInterval);
+                std::vector<Variant> parameters;
+                parameters.push_back(mFixedUpdateInterval);
+                mScriptFile->execute(mScriptObject, mMethods[METHOD_UPDATEFIXED], parameters);
+            }
+        }
     }
     }
 }
 }
 
 

+ 12 - 1
Engine/Script/ScriptInstance.h

@@ -101,6 +101,8 @@ public:
     bool setScriptClass(ScriptFile* scriptFile, const std::string& className);
     bool setScriptClass(ScriptFile* scriptFile, const std::string& className);
     //! Enable or disable scripted updates and event handlers
     //! Enable or disable scripted updates and event handlers
     void setEnabled(bool enable);
     void setEnabled(bool enable);
+    //! Set fixed updates per second. 0 (default) uses the physics frame rate
+    void setFixedUpdateFps(int fps);
     //! Query for a method by declaration and execute if found
     //! Query for a method by declaration and execute if found
     bool execute(const std::string& declaration, const std::vector<Variant>& parameters = std::vector<Variant>());
     bool execute(const std::string& declaration, const std::vector<Variant>& parameters = std::vector<Variant>());
     //! Execute a method
     //! Execute a method
@@ -118,6 +120,8 @@ public:
     bool isRunning() const { return mScriptObject != 0; }
     bool isRunning() const { return mScriptObject != 0; }
     //! Return whether scripted updates and event handlers are enabled
     //! Return whether scripted updates and event handlers are enabled
     bool isEnabled() const { return mEnabled; }
     bool isEnabled() const { return mEnabled; }
+    //! Return fixed updates per second
+    int getFixedUpdateFps() const { return mFixedUpdateFps; }
     
     
     //! Create the script object. Check for supported methods and register self to the ScriptFile if successful
     //! Create the script object. Check for supported methods and register self to the ScriptFile if successful
     bool createObject();
     bool createObject();
@@ -150,9 +154,16 @@ private:
     std::string mClassName;
     std::string mClassName;
     //! Pointers to supported inbuilt methods
     //! Pointers to supported inbuilt methods
     asIScriptFunction* mMethods[MAX_SCRIPT_METHODS];
     asIScriptFunction* mMethods[MAX_SCRIPT_METHODS];
-    
     //! Enabled flag
     //! Enabled flag
     bool mEnabled;
     bool mEnabled;
+    //! Fixed update FPS
+    int mFixedUpdateFps;
+    //! Fixed update time interval
+    float mFixedUpdateInterval;
+    //! Fixed update time accumulator
+    float mFixedUpdateTimer;
+    //! Fixed post update time accumulator
+    float mFixedPostUpdateTimer;
 };
 };
 
 
 //! Return the ScriptInstance of the active context
 //! Return the ScriptInstance of the active context

+ 1 - 1
Engine/UI/BorderImage.cpp

@@ -77,7 +77,7 @@ void BorderImage::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>&
         max(mImageRect.mBottom - mImageRect.mTop - mBorder.mTop - mBorder.mBottom, 0));
         max(mImageRect.mBottom - mImageRect.mTop - mBorder.mTop - mBorder.mBottom, 0));
     
     
     IntVector2 topLeft(mImageRect.mLeft, mImageRect.mTop);
     IntVector2 topLeft(mImageRect.mLeft, mImageRect.mTop);
-    if (mHovering)
+    if ((mHovering) || (mSelected))
         topLeft += mHoverOffset;
         topLeft += mHoverOffset;
     
     
     // Top
     // Top

+ 108 - 16
Engine/UI/LineEdit.cpp

@@ -22,6 +22,7 @@
 //
 //
 
 
 #include "Precompiled.h"
 #include "Precompiled.h"
+#include "Input.h"
 #include "LineEdit.h"
 #include "LineEdit.h"
 #include "Text.h"
 #include "Text.h"
 #include "UIEvents.h"
 #include "UIEvents.h"
@@ -30,10 +31,11 @@
 
 
 LineEdit::LineEdit(const std::string& name, const std::string& text) :
 LineEdit::LineEdit(const std::string& name, const std::string& text) :
     BorderImage(name),
     BorderImage(name),
-    mEchoCharacter(0),
+    mCursorPosition(0),
     mCursorBlinkRate(1.0f),
     mCursorBlinkRate(1.0f),
     mCursorBlinkTimer(0.0f),
     mCursorBlinkTimer(0.0f),
     mMaxLength(0),
     mMaxLength(0),
+    mEchoCharacter(0),
     mDefocusable(true),
     mDefocusable(true),
     mDefocus(false)
     mDefocus(false)
 {
 {
@@ -92,20 +94,22 @@ void LineEdit::update(float timeStep)
     
     
     if (mFocus)
     if (mFocus)
     {
     {
-        int textLength = 0;
-        const std::vector<int>& rowWidths = mText->getRowWidths();
-        if (rowWidths.size())
-            textLength = rowWidths[0];
+        int x;
+        const std::vector<IntVector2>& charPositions = mText->getCharPositions();
+        if (charPositions.size())
+            x = mCursorPosition < charPositions.size() ? charPositions[mCursorPosition].mX : charPositions.back().mX;
+        else
+            x = 0;
         
         
         // This assumes text alignment is top-left
         // This assumes text alignment is top-left
-        mCursor->setPosition(mText->getPosition() + IntVector2(textLength, 0));
+        mCursor->setPosition(mText->getPosition() + IntVector2(x, 0));
         mCursor->setSize(mCursor->getWidth(), mText->getRowHeight());
         mCursor->setSize(mCursor->getWidth(), mText->getRowHeight());
         cursorVisible = mCursorBlinkTimer < 0.5f;
         cursorVisible = mCursorBlinkTimer < 0.5f;
         
         
         // Scroll if text is longer than what can be visible at once
         // Scroll if text is longer than what can be visible at once
         int scrollThreshold = max(getWidth() - mClipBorder.mLeft - mClipBorder.mRight - mCursor->getWidth(), 0);
         int scrollThreshold = max(getWidth() - mClipBorder.mLeft - mClipBorder.mRight - mCursor->getWidth(), 0);
-        if (textLength > scrollThreshold)
-            setChildOffset(IntVector2(-(textLength - scrollThreshold), 0));
+        if (x > scrollThreshold)
+            setChildOffset(IntVector2(-x + scrollThreshold, 0));
         else
         else
             setChildOffset(IntVector2::sZero);
             setChildOffset(IntVector2::sZero);
     }
     }
@@ -121,16 +125,88 @@ void LineEdit::update(float timeStep)
     }
     }
 }
 }
 
 
+void LineEdit::onClick(const IntVector2& position, const IntVector2& screenPosition, unsigned buttons)
+{
+    if (buttons & MOUSEB_LEFT)
+    {
+        IntVector2 textPosition = mText->screenToElement(screenPosition);
+        const std::vector<IntVector2>& charPositions = mText->getCharPositions();
+        for (unsigned i = charPositions.size() - 1; i < charPositions.size(); --i)
+        {
+            if (textPosition.mX >= charPositions[i].mX)
+            {
+                mCursorPosition = i;
+                break;
+            }
+        }
+    }
+}
+
+void LineEdit::onKey(int key)
+{
+    bool changed = false;
+    bool cursorMoved = false;
+    
+    switch (key)
+    {
+    case KEY_LEFT:
+        if (mCursorPosition > 0)
+        {
+            --mCursorPosition;
+            cursorMoved = true;
+        }
+        break;
+        
+    case KEY_RIGHT:
+        if (mCursorPosition < mLine.length())
+        {
+            ++mCursorPosition;
+            cursorMoved = true;
+        }
+        break;
+        
+    case KEY_DELETE:
+        if (mCursorPosition < mLine.length())
+        {
+            if (mCursorPosition)
+                mLine = mLine.substr(0, mCursorPosition) + mLine.substr(mCursorPosition + 1);
+            else
+                mLine = mLine.substr(mCursorPosition + 1);
+            changed = true;
+        }
+        break;
+    }
+    
+    // Restart cursor blinking from the visible state
+    if (cursorMoved)
+        mCursorBlinkTimer = 0.0f;
+    
+    if (changed)
+    {
+        updateText();
+        
+        using namespace TextChanged;
+        
+        VariantMap eventData;
+        eventData[P_ELEMENT] = (void*)this;
+        eventData[P_TEXT] = mLine;
+        sendEvent(EVENT_TEXTCHANGED, eventData);
+    }
+}
+
 void LineEdit::onChar(unsigned char c)
 void LineEdit::onChar(unsigned char c)
 {
 {
-    unsigned currentLength = mLine.length();
     bool changed = false;
     bool changed = false;
     
     
     if (c == '\b')
     if (c == '\b')
     {
     {
-        if (mLine.length())
+        if ((mLine.length()) && (mCursorPosition))
         {
         {
-            mLine = mLine.substr(0, currentLength - 1);
+            if (mCursorPosition < mLine.length())
+                mLine = mLine.substr(0, mCursorPosition - 1) + mLine.substr(mCursorPosition);
+            else
+                mLine = mLine.substr(0, mCursorPosition - 1);
+            --mCursorPosition;
             changed = true;
             changed = true;
         }
         }
     }
     }
@@ -147,9 +223,16 @@ void LineEdit::onChar(unsigned char c)
         if (mDefocusable)
         if (mDefocusable)
             mDefocus = true;
             mDefocus = true;
     }
     }
-    else if ((c >= 0x20) && ((!mMaxLength) || (currentLength < mMaxLength)))
+    else if ((c >= 0x20) && ((!mMaxLength) || (mLine.length() < mMaxLength)))
     {
     {
-        mLine += (char)c;
+        static std::string charStr;
+        charStr.resize(1);
+        charStr[0] = c;
+        if (mCursorPosition == mLine.length())
+            mLine += charStr;
+        else
+            mLine = mLine.substr(0, mCursorPosition) + charStr + mLine.substr(mCursorPosition);
+        ++mCursorPosition;
         changed = true;
         changed = true;
     }
     }
     
     
@@ -175,10 +258,11 @@ void LineEdit::setText(const std::string& text)
     updateText();
     updateText();
 }
 }
 
 
-void LineEdit::setEchoCharacter(char c)
+void LineEdit::setCursorPosition(unsigned position)
 {
 {
-    mEchoCharacter = c;
-    updateText();
+    if (position > mLine.length())
+        position = mLine.length();
+    mCursorPosition = position;
 }
 }
 
 
 void LineEdit::setCursorBlinkRate(float rate)
 void LineEdit::setCursorBlinkRate(float rate)
@@ -191,6 +275,12 @@ void LineEdit::setMaxLength(unsigned length)
     mMaxLength = length;
     mMaxLength = length;
 }
 }
 
 
+void LineEdit::setEchoCharacter(char c)
+{
+    mEchoCharacter = c;
+    updateText();
+}
+
 void LineEdit::setDefocusable(bool enable)
 void LineEdit::setDefocusable(bool enable)
 {
 {
     mDefocusable = enable;
     mDefocusable = enable;
@@ -208,4 +298,6 @@ void LineEdit::updateText()
             echoText[i] = mEchoCharacter;
             echoText[i] = mEchoCharacter;
         mText->setText(echoText);
         mText->setText(echoText);
     }
     }
+    if (mCursorPosition > mLine.length())
+        mCursorPosition = mLine.length();
 }
 }

+ 17 - 7
Engine/UI/LineEdit.h

@@ -43,28 +43,36 @@ public:
     //! Perform UI element update
     //! Perform UI element update
     virtual void update(float timeStep);
     virtual void update(float timeStep);
     
     
-    //! React to a character typed on keyboard
+    //! React to mouse click
+    virtual void onClick(const IntVector2& position, const IntVector2& screenPosition, unsigned buttons);
+    //! React to a key press
+    virtual void onKey(int key);
+    //! React to a key press translated to a character
     virtual void onChar(unsigned char c);
     virtual void onChar(unsigned char c);
     
     
     //! Set text
     //! Set text
     void setText(const std::string& text);
     void setText(const std::string& text);
-    //! Set echo character for password entry and such. 0 (default) shows the actual text
-    void setEchoCharacter(char c);
+    //! Set cursor position
+    void setCursorPosition(unsigned position);
     //! Set cursor blink rate. 0 disables blinking
     //! Set cursor blink rate. 0 disables blinking
     void setCursorBlinkRate(float rate);
     void setCursorBlinkRate(float rate);
     //! Set maximum text length. 0 for unlimited
     //! Set maximum text length. 0 for unlimited
     void setMaxLength(unsigned length);
     void setMaxLength(unsigned length);
+    //! Set echo character for password entry and such. 0 (default) shows the actual text
+    void setEchoCharacter(char c);
     //! Set whether can defocus with ESC, default true
     //! Set whether can defocus with ESC, default true
     void setDefocusable(bool enable);
     void setDefocusable(bool enable);
     
     
     //! Return text
     //! Return text
     const std::string& getText() const { return mLine; }
     const std::string& getText() const { return mLine; }
-    //! Return echo character
-    char getEchoCharacter() const { return mEchoCharacter; }
+    //! Return cursor position
+    unsigned getCursorPosition() const { return mCursorPosition; }
     //! Return cursor blink rate
     //! Return cursor blink rate
     float getCursorBlinkRate() const { return mCursorBlinkRate; }
     float getCursorBlinkRate() const { return mCursorBlinkRate; }
     //! Return maximum text length
     //! Return maximum text length
     unsigned getMaxLength() const { return mMaxLength; }
     unsigned getMaxLength() const { return mMaxLength; }
+    //! Return echo character
+    char getEchoCharacter() const { return mEchoCharacter; }
     //! Return whether can defocus with ESC
     //! Return whether can defocus with ESC
     bool isDefocusable() const { return mDefocusable; }
     bool isDefocusable() const { return mDefocusable; }
     //! Return text element
     //! Return text element
@@ -78,14 +86,16 @@ protected:
     
     
     //! Text line
     //! Text line
     std::string mLine;
     std::string mLine;
-    //! Echo character
-    char mEchoCharacter;
+    //! Cursor position
+    unsigned mCursorPosition;
     //! Cursor blink rate
     //! Cursor blink rate
     float mCursorBlinkRate;
     float mCursorBlinkRate;
     //! Cursor blink timer
     //! Cursor blink timer
     float mCursorBlinkTimer;
     float mCursorBlinkTimer;
     //! Maximum text length
     //! Maximum text length
     unsigned mMaxLength;
     unsigned mMaxLength;
+    //! Echo character
+    char mEchoCharacter;
     //! ESC defocus flag
     //! ESC defocus flag
     bool mDefocusable;
     bool mDefocusable;
     //! Text element
     //! Text element

+ 48 - 13
Engine/UI/Text.cpp

@@ -38,7 +38,8 @@ Text::Text(const std::string& name, const std::string& text) :
     mMaxWidth(0),
     mMaxWidth(0),
     mText(text),
     mText(text),
     mTextAlignment(HA_LEFT),
     mTextAlignment(HA_LEFT),
-    mTextSpacing(1.0f)
+    mTextSpacing(1.0f),
+    mRowHeight(0)
 {
 {
 }
 }
 
 
@@ -132,7 +133,6 @@ void Text::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads,
     unsigned rowIndex = 0;
     unsigned rowIndex = 0;
     int x = getRowStartPosition(rowIndex);
     int x = getRowStartPosition(rowIndex);
     int y = 0;
     int y = 0;
-    int rowHeight = (int)(mTextSpacing * face->mRowHeight);
     
     
     for (unsigned i = 0; i < mPrintText.length(); ++i)
     for (unsigned i = 0; i < mPrintText.length(); ++i)
     {
     {
@@ -149,7 +149,7 @@ void Text::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads,
         {
         {
             rowIndex++;
             rowIndex++;
             x = getRowStartPosition(rowIndex);
             x = getRowStartPosition(rowIndex);
-            y += rowHeight;
+            y += mRowHeight;
         }
         }
     }
     }
     
     
@@ -159,30 +159,32 @@ void Text::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads,
     mHovering = false;
     mHovering = false;
 }
 }
 
 
-int Text::getRowHeight() const
-{
-    if (mFont)
-        return mFont->getFace(mFontSize)->mRowHeight;
-    else
-        return 0;
-}
-
 void Text::calculateTextSize()
 void Text::calculateTextSize()
 {
 {
     int width = 0;
     int width = 0;
     int height = 0;
     int height = 0;
     
     
     mRowWidths.clear();
     mRowWidths.clear();
+    mPrintText.clear();
+    
+    static std::vector<unsigned> printToText;
+    printToText.clear();
     
     
     if (mFont)
     if (mFont)
     {
     {
         const FontFace* face = mFont->getFace(mFontSize);
         const FontFace* face = mFont->getFace(mFontSize);
+        mRowHeight = face->mRowHeight;
         int rowWidth = 0;
         int rowWidth = 0;
-        int rowHeight = (int)(mTextSpacing * face->mRowHeight);
+        int rowHeight = (int)(mTextSpacing * mRowHeight);
         
         
         // First see if the text must be split up
         // First see if the text must be split up
         if (!mMaxWidth)
         if (!mMaxWidth)
+        {
             mPrintText = mText;
             mPrintText = mText;
+            printToText.resize(mText.length());
+            for (unsigned i = 0; i < mText.length(); ++i)
+                printToText[i] = i;
+        }
         else
         else
         {
         {
             unsigned nextBreak = 0;
             unsigned nextBreak = 0;
@@ -225,9 +227,14 @@ void Text::calculateTextSize()
                         {
                         {
                             int copyLength = max(j - i, 1);
                             int copyLength = max(j - i, 1);
                             mPrintText.append(mText.substr(i, copyLength));
                             mPrintText.append(mText.substr(i, copyLength));
-                            i += copyLength;
+                            for (int k = 0; k < copyLength; ++k)
+                            {
+                                printToText.push_back(i);
+                                ++i;
+                            }
                         }
                         }
                         mPrintText += '\n';
                         mPrintText += '\n';
+                        printToText.push_back(i);
                         rowWidth = 0;
                         rowWidth = 0;
                         nextBreak = lineStart = i;
                         nextBreak = lineStart = i;
                     }
                     }
@@ -237,12 +244,16 @@ void Text::calculateTextSize()
                         // When copying a space, we may be over row width
                         // When copying a space, we may be over row width
                         rowWidth += face->mGlyphs[face->mGlyphIndex[mText[i]]].mAdvanceX;
                         rowWidth += face->mGlyphs[face->mGlyphIndex[mText[i]]].mAdvanceX;
                         if (rowWidth <= mMaxWidth)
                         if (rowWidth <= mMaxWidth)
+                        {
                             mPrintText += mText[i];
                             mPrintText += mText[i];
+                            printToText.push_back(i);
+                        }
                     }
                     }
                 }
                 }
                 else
                 else
                 {
                 {
                     mPrintText += '\n';
                     mPrintText += '\n';
+                    printToText.push_back(i);
                     rowWidth = 0;
                     rowWidth = 0;
                     nextBreak = lineStart = i;
                     nextBreak = lineStart = i;
                 }
                 }
@@ -279,6 +290,30 @@ void Text::calculateTextSize()
         // Set row height even if text is empty
         // Set row height even if text is empty
         if (!height)
         if (!height)
             height = rowHeight;
             height = rowHeight;
+        
+        // Store position of each character
+        mCharPositions.resize(mText.length() + 1);
+        unsigned rowIndex = 0;
+        int x = getRowStartPosition(rowIndex);
+        int y = 0;
+        for (unsigned i = 0; i < mPrintText.length(); ++i)
+        {
+            mCharPositions[printToText[i]] = IntVector2(x, y);
+            unsigned char c = (unsigned char)mPrintText[i];
+            if (c != '\n')
+            {
+                const FontGlyph& glyph = face->mGlyphs[face->mGlyphIndex[c]];
+                x += glyph.mAdvanceX;
+            }
+            else
+            {
+                rowIndex++;
+                x = getRowStartPosition(rowIndex);
+                y += rowHeight;
+            }
+        }
+        // Store the ending position
+        mCharPositions[mText.length()] = IntVector2(x, y);
     }
     }
     
     
     setSize(width, height);
     setSize(width, height);

+ 8 - 2
Engine/UI/Text.h

@@ -69,12 +69,14 @@ public:
     HorizontalAlignment getTextAlignment() const { return mTextAlignment; }
     HorizontalAlignment getTextAlignment() const { return mTextAlignment; }
     //! Return row spacing
     //! Return row spacing
     float getTextSpacing() const { return mTextSpacing; }
     float getTextSpacing() const { return mTextSpacing; }
+    //! Return row height
+    int getRowHeight() const { return mRowHeight; }
     //! Return number of rows
     //! Return number of rows
     unsigned getNumRows() const { return mRowWidths.size(); }
     unsigned getNumRows() const { return mRowWidths.size(); }
     //! Return width of each row
     //! Return width of each row
     const std::vector<int>& getRowWidths() const { return mRowWidths; }
     const std::vector<int>& getRowWidths() const { return mRowWidths; }
-    //! Return row height
-    int getRowHeight() const;
+    //! Return position of each character
+    const std::vector<IntVector2>& getCharPositions() const { return mCharPositions; }
     
     
 protected:
 protected:
     //! Calculate text size
     //! Calculate text size
@@ -96,8 +98,12 @@ protected:
     HorizontalAlignment mTextAlignment;
     HorizontalAlignment mTextAlignment;
     //! Row spacing
     //! Row spacing
     float mTextSpacing;
     float mTextSpacing;
+    //! Row height
+    int mRowHeight;
     //! Row widths
     //! Row widths
     std::vector<int> mRowWidths;
     std::vector<int> mRowWidths;
+    //! Positions of each character
+    std::vector<IntVector2> mCharPositions;
 };
 };
 
 
 #endif // UI_STATICTEXT_H
 #endif // UI_STATICTEXT_H

+ 10 - 0
Engine/UI/UI.cpp

@@ -69,6 +69,7 @@ UI::UI(Renderer* renderer, ResourceCache* cache) :
     subscribeToEvent(EVENT_MOUSEMOVE, EVENT_HANDLER(UI, handleMouseMove));
     subscribeToEvent(EVENT_MOUSEMOVE, EVENT_HANDLER(UI, handleMouseMove));
     subscribeToEvent(EVENT_MOUSEBUTTONDOWN, EVENT_HANDLER(UI, handleMouseButtonDown));
     subscribeToEvent(EVENT_MOUSEBUTTONDOWN, EVENT_HANDLER(UI, handleMouseButtonDown));
     subscribeToEvent(EVENT_MOUSEBUTTONUP, EVENT_HANDLER(UI, handleMouseButtonUp));
     subscribeToEvent(EVENT_MOUSEBUTTONUP, EVENT_HANDLER(UI, handleMouseButtonUp));
+    subscribeToEvent(EVENT_KEYDOWN, EVENT_HANDLER(UI, handleKeyDown));
     subscribeToEvent(EVENT_CHAR, EVENT_HANDLER(UI, handleChar));
     subscribeToEvent(EVENT_CHAR, EVENT_HANDLER(UI, handleChar));
     
     
     mNoTextureVS = mCache->getResource<VertexShader>("Shaders/SM2/Basic_VCol.vs2");
     mNoTextureVS = mCache->getResource<VertexShader>("Shaders/SM2/Basic_VCol.vs2");
@@ -489,6 +490,15 @@ void UI::handleMouseButtonUp(StringHash eventType, VariantMap& eventData)
     }
     }
 }
 }
 
 
+void UI::handleKeyDown(StringHash eventType, VariantMap& eventData)
+{
+    using namespace KeyDown;
+    
+    UIElement* element = getFocusElement();
+    if (element)
+        element->onKey(eventData[P_KEY].getInt());
+}
+
 void UI::handleChar(StringHash eventType, VariantMap& eventData)
 void UI::handleChar(StringHash eventType, VariantMap& eventData)
 {
 {
     using namespace Char;
     using namespace Char;

+ 2 - 0
Engine/UI/UI.h

@@ -97,6 +97,8 @@ private:
     void handleMouseButtonDown(StringHash eventType, VariantMap& eventData);
     void handleMouseButtonDown(StringHash eventType, VariantMap& eventData);
     //! Handle mouse button up event
     //! Handle mouse button up event
     void handleMouseButtonUp(StringHash eventType, VariantMap& eventData);
     void handleMouseButtonUp(StringHash eventType, VariantMap& eventData);
+    //! Handle keypress event
+    void handleKeyDown(StringHash eventType, VariantMap& eventData);
     //! Handle character event
     //! Handle character event
     void handleChar(StringHash eventType, VariantMap& eventData);
     void handleChar(StringHash eventType, VariantMap& eventData);
     //! Load a UI layout from an XML file recursively
     //! Load a UI layout from an XML file recursively

+ 4 - 4
Engine/UI/UIBatch.cpp

@@ -67,7 +67,7 @@ void UIBatch::addQuad(UIElement& element, int x, int y, int width, int height, i
     else
     else
     {
     {
         Color color = element.getColor(C_TOPLEFT);
         Color color = element.getColor(C_TOPLEFT);
-        if (element.isHovering())
+        if ((element.isHovering()) || (element.isSelected()))
             color += element.getHoverColor();
             color += element.getHoverColor();
         color.mA *= element.getDerivedOpacity();
         color.mA *= element.getDerivedOpacity();
         // If alpha is 0, nothing will be rendered, so exit without adding the quad
         // If alpha is 0, nothing will be rendered, so exit without adding the quad
@@ -111,7 +111,7 @@ void UIBatch::addQuad(UIElement& element, int x, int y, int width, int height, i
     else
     else
     {
     {
         Color color = element.getColor(C_TOPLEFT);
         Color color = element.getColor(C_TOPLEFT);
-        if (element.isHovering())
+        if ((element.isHovering()) || (element.isSelected()))
             color += element.getHoverColor();
             color += element.getHoverColor();
         color.mA *= element.getDerivedOpacity();
         color.mA *= element.getDerivedOpacity();
         // If alpha is 0, nothing will be rendered, so exit without adding the quad
         // If alpha is 0, nothing will be rendered, so exit without adding the quad
@@ -268,7 +268,7 @@ unsigned UIBatch::getInterpolatedColor(UIElement& element, int x, int y)
         Color topColor = element.getColor(C_TOPLEFT).lerp(element.getColor(C_TOPRIGHT), cLerpX);
         Color topColor = element.getColor(C_TOPLEFT).lerp(element.getColor(C_TOPRIGHT), cLerpX);
         Color bottomColor = element.getColor(C_BOTTOMLEFT).lerp(element.getColor(C_BOTTOMRIGHT), cLerpX);
         Color bottomColor = element.getColor(C_BOTTOMLEFT).lerp(element.getColor(C_BOTTOMRIGHT), cLerpX);
         Color color = topColor.lerp(bottomColor, cLerpY);
         Color color = topColor.lerp(bottomColor, cLerpY);
-        if (element.isHovering())
+        if ((element.isHovering()) || (element.isSelected()))
             color += element.getHoverColor();
             color += element.getHoverColor();
         color.mA *= element.getDerivedOpacity();
         color.mA *= element.getDerivedOpacity();
         return getD3DColor(color);
         return getD3DColor(color);
@@ -276,7 +276,7 @@ unsigned UIBatch::getInterpolatedColor(UIElement& element, int x, int y)
     else
     else
     {
     {
         Color color = element.getColor(C_TOPLEFT);
         Color color = element.getColor(C_TOPLEFT);
-        if (element.isHovering())
+        if ((element.isHovering()) || (element.isSelected()))
             color += element.getHoverColor();
             color += element.getHoverColor();
         color.mA *= element.getDerivedOpacity();
         color.mA *= element.getDerivedOpacity();
         return getD3DColor(color);
         return getD3DColor(color);

+ 10 - 0
Engine/UI/UIElement.cpp

@@ -43,6 +43,7 @@ UIElement::UIElement(const std::string& name) :
     mEnabled(false),
     mEnabled(false),
     mFocusable(false),
     mFocusable(false),
     mFocus(false),
     mFocus(false),
+    mSelected(false),
     mVisible(true),
     mVisible(true),
     mHovering(false),
     mHovering(false),
     mPosition(IntVector2::sZero),
     mPosition(IntVector2::sZero),
@@ -227,6 +228,10 @@ float UIElement::getDerivedOpacity()
     return mDerivedOpacity;
     return mDerivedOpacity;
 }
 }
 
 
+void UIElement::onKey(int key)
+{
+}
+
 void UIElement::onChar(unsigned char c)
 void UIElement::onChar(unsigned char c)
 {
 {
 }
 }
@@ -405,6 +410,11 @@ void UIElement::setFocus(bool enable)
     }
     }
 }
 }
 
 
+void UIElement::setSelected(bool enable)
+{
+    mVisible = enable;
+}
+
 void UIElement::setVisible(bool enable)
 void UIElement::setVisible(bool enable)
 {
 {
     mVisible = enable;
     mVisible = enable;

+ 9 - 1
Engine/UI/UIElement.h

@@ -98,7 +98,9 @@ public:
     virtual void onDragMove(const IntVector2& position, const IntVector2& screenPosition, unsigned buttons);
     virtual void onDragMove(const IntVector2& position, const IntVector2& screenPosition, unsigned buttons);
     //! React to mouse drag end
     //! React to mouse drag end
     virtual void onDragEnd(const IntVector2& position, const IntVector2& screenPosition);
     virtual void onDragEnd(const IntVector2& position, const IntVector2& screenPosition);
-    //! React to a character typed on keyboard
+    //! React to a key press
+    virtual void onKey(int key);
+    //! React to a key press translated to a character
     virtual void onChar(unsigned char c);
     virtual void onChar(unsigned char c);
     
     
     //! Set name
     //! Set name
@@ -147,6 +149,8 @@ public:
     void setFocusable(bool enable);
     void setFocusable(bool enable);
     //! Set whether is focused. Usually called by UI
     //! Set whether is focused. Usually called by UI
     void setFocus(bool enable);
     void setFocus(bool enable);
+    //! Set selected mode. Actual meaning is element dependent, but is visually same as a constant hover
+    void setSelected(bool enable);
     //! Set whether is visible
     //! Set whether is visible
     void setVisible(bool enable);
     void setVisible(bool enable);
     //! Set userdata
     //! Set userdata
@@ -204,6 +208,8 @@ public:
     bool isFocusable() const { return mFocusable; }
     bool isFocusable() const { return mFocusable; }
     //! Return whether has focus
     //! Return whether has focus
     bool hasFocus() const { return mFocus; }
     bool hasFocus() const { return mFocus; }
+    //! Return whether is selected. Actual meaning is element dependent
+    bool isSelected() const { return mSelected; }
     //! Return whether is visible
     //! Return whether is visible
     bool isVisible() const { return mVisible; }
     bool isVisible() const { return mVisible; }
     //! Return whether the cursor is hovering on this element
     //! Return whether the cursor is hovering on this element
@@ -284,6 +290,8 @@ protected:
     bool mFocusable;
     bool mFocusable;
     //! Focused flag
     //! Focused flag
     bool mFocus;
     bool mFocus;
+    //! Selected flag
+    bool mSelected;
     //! Visible flag
     //! Visible flag
     bool mVisible;
     bool mVisible;
     //! Hovering flag
     //! Hovering flag