소스 검색

Fixed bug with rendering nontextured UI elements.
Fixed EVENT_TEXTFINISHED being sent for every change in LineEdit.
Added possibility to specify less than fully opaque colors for UI elements, instead of having to use the opacity property, which is always inherited.
Added variant userdata to UI elements.
Added defocusable flag to LineEdit. This makes it possible to disable the default ESC-defocusing.
Added debug console, which prints log output, and executes AngelScript from the input prompt.

Lasse Öörni 15 년 전
부모
커밋
74b348c0a5

+ 0 - 4
Bin/Data/Scripts/Ninja.as

@@ -251,10 +251,6 @@ class Ninja : ScriptObject, GameObject
         prevControls = controls;
         
         resetWorldCollision();
-        
-        // Test removing self
-        if (input.getKeyPress('K'))
-        	scene.removeEntity(entity);
     }
     
     void handleEntityCollision(StringHash eventType, VariantMap& eventData)

+ 45 - 26
Bin/Data/Scripts/NinjaSnowWar.as

@@ -18,6 +18,7 @@ bool paused = false;
 void init()
 {
     initAudio();
+    initConsole();
     initScene();
     createCamera();
     createOverlays();
@@ -33,7 +34,7 @@ void init()
 void runFrame()
 {
     engine.runFrame(gameScene, gameCamera, !paused);
-    
+
     if (input.getKeyPress(KEY_ESCAPE))
         engine.exit();
 }
@@ -49,6 +50,18 @@ void initAudio()
     song.play(0);
 }
 
+void initConsole()
+{
+    Console@ console = engine.createConsole();
+    console.setNumRows(16);
+    console.setFont(cache.getResource("Font", "cour.ttf"), 12);
+    console.setToggleKey(220);
+    BorderImage@ cursor = console.getLineEditElement().getCursorElement();
+    cursor.setWidth(4);
+    cursor.setTexture(cache.getResource("Texture2D", "Textures/UI.png"));
+    cursor.setImageRect(112, 0, 116, 16);
+}
+
 void initScene()
 {
     @gameScene = engine.createScene("ScriptTest", BoundingBox(-100000.0, 100000.0), 8, true);
@@ -129,18 +142,21 @@ void spawnPlayer()
 
 void handleUpdate(StringHash eventType, VariantMap& eventData)
 {
-    if (input.getKeyPress('P'))
+    if (!console.isVisible())
     {
-        paused = !paused;
-        if (paused)
-            messageText.setText("PAUSED");
-        else
-            messageText.setText("");
+        if (input.getKeyPress(KEY_F1))
+            debugHud.toggleAll();
+        if (input.getKeyPress(KEY_F2))
+            engine.setDebugDrawMode(engine.getDebugDrawMode() ^ DEBUGDRAW_PHYSICS);
+        if (input.getKeyPress('P'))
+        {
+            paused = !paused;
+            if (paused)
+                messageText.setText("PAUSED");
+            else
+                messageText.setText("");
+        }
     }
-    if (input.getKeyPress(KEY_F1))
-        debugHud.toggleAll();
-    if (input.getKeyPress(KEY_F2))
-        engine.setDebugDrawMode(engine.getDebugDrawMode() ^ DEBUGDRAW_PHYSICS);
 
     if (!paused)
         updateControls();
@@ -154,29 +170,32 @@ void handlePostUpdate(StringHash eventType, VariantMap& eventData)
 void updateControls()
 {
     playerControls.set(CTRL_ALL, false);
-    
-    if (input.getKeyDown('W'))
-        playerControls.set(CTRL_UP, true);
-    if (input.getKeyDown('S'))
-        playerControls.set(CTRL_DOWN, true);
-    if (input.getKeyDown('A'))
-        playerControls.set(CTRL_LEFT, true);
-    if (input.getKeyDown('D'))
-        playerControls.set(CTRL_RIGHT, true);
-    if (input.getKeyDown(KEY_CONTROL))
-        playerControls.set(CTRL_FIRE, true);
-    if (input.getKeyDown(' '))
-        playerControls.set(CTRL_JUMP, true);
+
+    if (!console.isVisible())
+    {
+        if (input.getKeyDown('W'))
+            playerControls.set(CTRL_UP, true);
+        if (input.getKeyDown('S'))
+            playerControls.set(CTRL_DOWN, true);
+        if (input.getKeyDown('A'))
+            playerControls.set(CTRL_LEFT, true);
+        if (input.getKeyDown('D'))
+            playerControls.set(CTRL_RIGHT, true);
+        if (input.getKeyDown(KEY_CONTROL))
+            playerControls.set(CTRL_FIRE, true);
+        if (input.getKeyDown(' '))
+            playerControls.set(CTRL_JUMP, true);
+    }
     
     if (input.getMouseButtonDown(MOUSEB_LEFT))
         playerControls.set(CTRL_FIRE, true);
     if (input.getMouseButtonDown(MOUSEB_RIGHT))
         playerControls.set(CTRL_JUMP, true);
-        
+
     playerControls.yaw += mouseSensitivity * input.getMouseMoveX();
     playerControls.pitch += mouseSensitivity * input.getMouseMoveY();
     playerControls.pitch = clamp(playerControls.pitch, -60, 60);
-    
+
     Entity@ playerEntity = gameScene.getEntity("ObjPlayer");
     if (@playerEntity == null)
         return;

+ 26 - 2
Engine/Common/Log.cpp

@@ -81,14 +81,18 @@ void Log::write(LogLevel level, const std::string& message)
     time(&sysTime);
     const char* dateTime = ctime(&sysTime);
     std::string dateTimeString = replace(std::string(dateTime), "\n", "");
+    std::string formattedMessage = "[" + dateTimeString + "] " + levelPrefixes[level] + ": " + message;
     
-    printf("[%s] %s: %s\n", dateTimeString.c_str(), levelPrefixes[level].c_str(), message.c_str());
+    printf("%s\n", formattedMessage.c_str());
     
     if (mHandle)
     {
-        fprintf(mHandle, "[%s] %s: %s\n", dateTimeString.c_str(), levelPrefixes[level].c_str(), message.c_str());
+        fprintf(mHandle, "%s\n", formattedMessage.c_str());
         fflush(mHandle);
     }
+    
+    for (std::vector<LogListener*>::const_iterator i = mListeners.begin(); i != mListeners.end(); ++i)
+        (*i)->write(formattedMessage);
 }
 
 void Log::writeRaw(const std::string& message)
@@ -100,6 +104,26 @@ void Log::writeRaw(const std::string& message)
         fprintf(mHandle, "%s", message.c_str());
         fflush(mHandle);
     }
+    
+    for (std::vector<LogListener*>::const_iterator i = mListeners.begin(); i != mListeners.end(); ++i)
+        (*i)->write(message);
+}
+
+void Log::addListener(LogListener* listener)
+{
+    mListeners.push_back(listener);
+}
+
+void Log::removeListener(LogListener* listener)
+{
+    for (std::vector<LogListener*>::iterator i = mListeners.begin(); i != mListeners.end(); ++i)
+    {
+        if ((*i) == listener)
+        {
+            mListeners.erase(i);
+            return;
+        }
+    }
 }
 
 void writeToLog(LogLevel level, const std::string& message)

+ 16 - 0
Engine/Common/Log.h

@@ -28,7 +28,9 @@
 
 #include <cstdio>
 #include <string>
+#include <vector>
 
+//! Logging levels
 enum LogLevel
 {
     LOG_DEBUG = 0,
@@ -38,6 +40,14 @@ enum LogLevel
     LOG_NONE
 };
 
+//! Log message listener
+class LogListener
+{
+public:
+    //! Write a log message
+    virtual void write(const std::string& message) = 0;
+};
+
 //! Urho3D log file
 class Log : public RefCounted
 {
@@ -55,6 +65,10 @@ public:
     void writeRaw(const std::string& message);
     //! Set logging level
     void setLevel(LogLevel level);
+    //! Add a log listener
+    void addListener(LogListener* listener);
+    //! Remove a log listener
+    void removeListener(LogListener* listener);
     
     //! Return logging level
     LogLevel getLevel() const { return mLevel; }
@@ -64,6 +78,8 @@ private:
     FILE* mHandle;
     //! Logging level
     LogLevel mLevel;
+    //! Log listeners
+    std::vector<LogListener*> mListeners;
     
     //! Log instance
     static Log* sInstance;

+ 224 - 0
Engine/Engine/Console.cpp

@@ -0,0 +1,224 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+#include "BorderImage.h"
+#include "Console.h"
+#include "Engine.h"
+#include "EngineEvents.h"
+#include "Font.h"
+#include "InputEvents.h"
+#include "LineEdit.h"
+#include "ScriptEngine.h"
+#include "StringUtils.h"
+#include "Text.h"
+#include "UI.h"
+#include "UIEvents.h"
+
+Console::Console(Engine* engine) :
+    mEngine(engine),
+    mFontSize(1),
+    mToggleKey(0),
+    mFocus(false)
+{
+    LOGINFO("Console created");
+    
+    if (!mEngine)
+        return;
+    
+    Log* log = getLog();
+    if (log)
+        log->addListener(this);
+    
+    UIElement* uiRoot = mEngine->getUIRoot();
+    if (!uiRoot)
+        return;
+    
+    mBackground = new BorderImage();
+    mBackground->setWidth(uiRoot->getWidth());
+    mBackground->setColor(C_TOPLEFT, Color(0.25f, 0.25f, 0.75f, 0.5f));
+    mBackground->setColor(C_TOPRIGHT, Color(0.25f, 0.25f, 0.75f, 0.5f));
+    mBackground->setColor(C_BOTTOMLEFT, Color(0.5f, 0.5f, 1.0f, 0.5f));
+    mBackground->setColor(C_BOTTOMRIGHT, Color(0.5f, 0.5f, 1.0f, 0.5f));
+    mBackground->setEnabled(true);
+    mBackground->setVisible(false);
+    mBackground->setPriority(200); // Show on top of the debug HUD
+    
+    mLineEdit = new LineEdit();
+    mLineEdit->setWidth(uiRoot->getWidth() - 8);
+    mLineEdit->setColor(Color(0.0f, 0.0f, 0.0f, 0.5f));
+    mLineEdit->setDefocusable(false);
+    mBackground->addChild(mLineEdit);
+    
+    uiRoot->addChild(mBackground);
+    
+    updateElements();
+    
+    subscribeToEvent(EVENT_UPDATE, EVENT_HANDLER(Console, handleUpdate));
+    subscribeToEvent(EVENT_KEYDOWN, EVENT_HANDLER(Console, handleKeyDown));
+    subscribeToEvent(EVENT_TEXTFINISHED, EVENT_HANDLER(Console, handleTextFinished));
+}
+
+Console::~Console()
+{
+    if (mEngine)
+    {
+        UIElement* uiRoot = mEngine->getUIRoot();
+        if (uiRoot)
+            uiRoot->removeChild(mBackground);
+    }
+    
+    Log* log = getLog();
+    if (log)
+        log->removeListener(this);
+    
+    LOGINFO("Console shut down");
+}
+
+void Console::write(const std::string& message)
+{
+    if (!mRows.size())
+        return;
+    
+    // Be prepared for possible multi-line messages
+    std::vector<std::string> rows = split(message, '\n');
+    
+    for (unsigned i = 0; i < rows.size(); ++i)
+    {
+        for (int j = 0; j < (int)mRows.size() - 1; ++j)
+            mRows[j]->setText(mRows[j + 1]->getText());
+        
+        mRows[mRows.size() - 1]->setText(rows[i]);
+    }
+}
+
+void Console::setVisible(bool enable)
+{
+    if (!mBackground)
+        return;
+    
+    mBackground->setVisible(enable);
+    if (enable)
+        mFocus = true;
+    else
+        mLineEdit->setFocus(false);
+}
+
+void Console::setNumRows(unsigned rows)
+{
+    if (!mBackground)
+        return;
+    
+    for (unsigned i = 0; i < mRows.size(); ++i)
+        mBackground->removeChild(mRows[i]);
+    mRows.clear();
+    mBackground->removeChild(mLineEdit);
+    
+    mRows.resize(rows);
+    for (unsigned i = 0; i < mRows.size(); ++i)
+    {
+        mRows[i] = new Text();
+        mBackground->addChild(mRows[i]);
+    }
+    mBackground->addChild(mLineEdit);
+    
+    updateElements();
+}
+
+void Console::setFont(Font* font, int size)
+{
+    if (!mBackground)
+        return;
+    
+    mFont = font;
+    mFontSize = max(size, 1);
+    
+    updateElements();
+}
+
+void Console::setToggleKey(int key)
+{
+    mToggleKey = key;
+}
+
+bool Console::isVisible() const
+{
+    if (!mBackground)
+        return false;
+    return mBackground->isVisible();
+}
+
+void Console::updateElements()
+{
+    if (mFont)
+    {
+        try
+        {
+            for (unsigned i = 0; i < mRows.size(); ++i)
+                mRows[i]->setFont(mFont, mFontSize);
+            mLineEdit->getTextElement()->setFont(mFont, mFontSize);
+        }
+        catch (...)
+        {
+        }
+    }
+    
+    mLineEdit->setHeight(mLineEdit->getTextElement()->getRowHeight());
+    mBackground->layoutVertical(0, IntRect(4, 4, 4, 4), true, true);
+    
+    int maxWidth = mEngine->getUIRoot()->getWidth();
+    if (mBackground->getWidth() > maxWidth)
+        mBackground->setWidth(maxWidth);
+}
+
+void Console::handleUpdate(StringHash eventType, VariantMap& eventData)
+{
+    // Focus now if console has been made visible
+    if (mFocus)
+    {
+        mEngine->getUI()->setFocusElement(mLineEdit);
+        mFocus = false;
+    }
+}
+
+void Console::handleKeyDown(StringHash eventType, VariantMap& eventData)
+{
+    using namespace KeyDown;
+    
+    if (eventData[P_KEY].getInt() == mToggleKey)
+        setVisible(!isVisible());
+}
+
+void Console::handleTextFinished(StringHash eventType, VariantMap& eventData)
+{
+    using namespace TextFinished;
+    
+    if (eventData[P_ELEMENT].getPtr() == (void*)mLineEdit)
+    {
+        std::string line = mLineEdit->getText();
+        ScriptEngine* scriptEngine = mEngine->getScriptEngine();
+        if (scriptEngine)
+            scriptEngine->execute(line);
+        mLineEdit->setText(std::string());
+    }
+}

+ 97 - 0
Engine/Engine/Console.h

@@ -0,0 +1,97 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#ifndef ENGINE_CONSOLE_H
+#define ENGINE_CONSOLE_H
+
+#include "EventListener.h"
+#include "Log.h"
+#include "SharedPtr.h"
+
+class BorderImage;
+class Engine;
+class Font;
+class Text;
+class LineEdit;
+
+//! A console window with log history and AngelScript prompt
+class Console : public RefCounted, public EventListener, public LogListener
+{
+public:
+    //! Construct with an Engine pointer
+    Console(Engine* engine);
+    //! Destruct
+    ~Console();
+    
+    //! Write a log message
+    virtual void write(const std::string& message);
+    
+    //! Show/hide. Showing automatically focuses the line edit
+    void setVisible(bool enable);
+    //! Set number of rows
+    void setNumRows(unsigned rows);
+    //! Set font to use
+    void setFont(Font* font, int size);
+    //! Set key for toggling
+    void setToggleKey(int key);
+    
+    //! Return whether is visible
+    bool isVisible() const;
+    //! Return number of rows
+    unsigned getNumRows() const { return mRows.size(); }
+    //! Return key for toggling
+    int getToggleKey() const { return mToggleKey; }
+    //! Return background element
+    BorderImage* getBackgroundElement() const { return mBackground; }
+    //! Return line edit element
+    LineEdit* getLineEditElement() const { return mLineEdit; }
+    
+private:
+    //! Update layout
+    void updateElements();
+    //! Handle update
+    void handleUpdate(StringHash eventType, VariantMap& eventData);
+    //! Handle key press
+    void handleKeyDown(StringHash eventType, VariantMap& eventData);
+    //! Handle enter pressed on the line edit
+    void handleTextFinished(StringHash eventType, VariantMap& eventData);
+    
+    //! Engine
+    WeakPtr<Engine> mEngine;
+    //! Background
+    SharedPtr<BorderImage> mBackground;
+    //! Font
+    SharedPtr<Font> mFont;
+    //! Text rows
+    std::vector<SharedPtr<Text> > mRows;
+    //! Line edit
+    SharedPtr<LineEdit> mLineEdit;
+    //! Font size
+    int mFontSize;
+    //! Key for toggling
+    int mToggleKey;
+    //! Focus flag
+    bool mFocus;
+};
+
+#endif // ENGINE_CONSOLE_H

+ 8 - 0
Engine/Engine/Engine.cpp

@@ -28,6 +28,7 @@
 #include "BaseComponentFactory.h"
 #include "BaseResourceFactory.h"
 #include "Client.h"
+#include "Console.h"
 #include "Cursor.h"
 #include "DebugHud.h"
 #include "DebugRenderer.h"
@@ -343,6 +344,13 @@ ScriptEngine* Engine::createScriptEngine()
     return mScriptEngine;
 }
 
+Console* Engine::createConsole()
+{
+    if (!mConsole)
+        mConsole = new Console(this);
+    return mConsole;
+}
+
 DebugHud* Engine::createDebugHud()
 {
     if (!mDebugHud)

+ 7 - 0
Engine/Engine/Engine.h

@@ -39,6 +39,7 @@ static const int DEBUGDRAW_PHYSICS = 2;
 class Audio;
 class Camera;
 class Client;
+class Console;
 class Cursor;
 class DebugHud;
 class DebugRenderer;
@@ -81,6 +82,8 @@ public:
     Server* createServer();
     //! Create the script engine. Call before creating scripted scenes or loading script files
     ScriptEngine* createScriptEngine();
+    //! Create the console
+    Console* createConsole();
     //! Create the debug hud
     DebugHud* createDebugHud();
     //! Remove the client subsystem
@@ -108,6 +111,8 @@ public:
     Audio* getAudio() const { return mAudio; }
     //! Return client subsystem
     Client* getClient() const { return mClient; }
+    //! Return console if created
+    Console* getConsole() const { return mConsole; }
     //! Return debug hud if created
     DebugHud* getDebugHud() const { return mDebugHud; }
     //! Return debug renderer
@@ -172,6 +177,8 @@ private:
     SharedPtr<Audio> mAudio;
     //! Resource cache
     SharedPtr<ResourceCache> mCache;
+    //! Console
+    SharedPtr<Console> mConsole;
     //! Debug hud
     SharedPtr<DebugHud> mDebugHud;
     //! Debug renderer

+ 27 - 0
Engine/Engine/RegisterEngine.cpp

@@ -24,6 +24,7 @@
 #include "Precompiled.h"
 #include "AnimationController.h"
 #include "Client.h"
+#include "Console.h"
 #include "Connection.h"
 #include "DebugHud.h"
 #include "Engine.h"
@@ -344,6 +345,30 @@ static void registerParticleEmitter(asIScriptEngine* engine)
     registerRefCasts<Node, ParticleEmitter>(engine, "Node", "ParticleEmitter");
 }
 
+static Console* GetConsole()
+{
+    return getEngine()->getConsole();
+}
+
+static void registerConsole(asIScriptEngine* engine)
+{
+    engine->RegisterObjectType("Console", 0, asOBJ_REF);
+    engine->RegisterObjectBehaviour("Console", asBEHAVE_ADDREF, "void f()", asMETHOD(Console, addRef), asCALL_THISCALL);
+    engine->RegisterObjectBehaviour("Console", asBEHAVE_RELEASE, "void f()", asMETHOD(Console, releaseRef), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "void setVisible(bool)", asMETHOD(Console, setVisible), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "void setNumRows(uint)", asMETHOD(Console, setNumRows), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "void setFont(Font@+, int)", asMETHOD(Console, setFont), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "void setToggleKey(int)", asMETHOD(Console, setToggleKey), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "bool isVisible() const", asMETHOD(Console, isVisible), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "uint getNumRows() const", asMETHOD(Console, getNumRows), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "int getToggleKey() const", asMETHOD(Console, getToggleKey), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "BorderImage@+ getBackgroundElement() const", asMETHOD(Console, getBackgroundElement), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "LineEdit@+ getLineEditElement() const", asMETHOD(Console, getLineEditElement), asCALL_THISCALL);
+    
+    engine->RegisterGlobalFunction("Console@+ getConsole()", asFUNCTION(GetConsole), asCALL_CDECL);
+    engine->RegisterGlobalFunction("Console@+ get_console()", asFUNCTION(GetConsole), asCALL_CDECL);
+}
+
 static DebugHud* GetDebugHud()
 {
     return getEngine()->getDebugHud();
@@ -393,6 +418,7 @@ static void registerEngine(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Engine", "Scene@ createScene(const string& in, const BoundingBox& in, uint, bool)", asFUNCTION(EngineCreateScene), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Engine", "Client@+ createClient(const string& in)", asMETHOD(Engine, createClient), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "Server@+ createServer()", asMETHOD(Engine, createServer), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Engine", "Console@+ createConsole()", asMETHOD(Engine, createConsole), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "DebugHud@+ createDebugHud()", asMETHOD(Engine, createDebugHud), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void removeClient()", asMETHOD(Engine, removeClient), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void removeServer()", asMETHOD(Engine, removeServer), asCALL_THISCALL);
@@ -426,6 +452,7 @@ void registerEngineLibrary(asIScriptEngine* engine)
     registerServer(engine);
     registerAnimationController(engine);
     registerParticleEmitter(engine);
+    registerConsole(engine);
     registerDebugHud(engine);
     registerEngine(engine);
 }

+ 16 - 0
Engine/Engine/RegisterScript.cpp

@@ -26,9 +26,25 @@
 #include "ScriptFile.h"
 #include "ScriptInstance.h"
 
+static bool ScriptFileExecute(const std::string& functionName, CScriptArray* srcParams, ScriptFile* ptr)
+{
+    if (!srcParams)
+        return false;
+    
+    unsigned numParams = srcParams->GetSize();
+    std::vector<Variant> destParams;
+    destParams.resize(numParams);
+    
+    for (unsigned i = 0; i < numParams; ++i)
+        destParams[i] = *(static_cast<Variant*>(srcParams->At(i)));
+    
+    return ptr->execute(functionName, destParams);
+}
+
 static void registerScriptFile(asIScriptEngine* engine)
 {
     registerResource<ScriptFile>(engine, "ScriptFile");
+    engine->RegisterObjectMethod("ScriptFile", "bool execute(const string& in, const array<Variant>@+)", asFUNCTION(ScriptFileExecute), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("ScriptFile", "bool isCompiled() const", asMETHOD(ScriptFile, isCompiled), asCALL_THISCALL);
     registerRefCasts<Resource, ScriptFile>(engine, "Resource", "ScriptFile");
 }

+ 2 - 0
Engine/Engine/RegisterTemplates.h

@@ -392,6 +392,7 @@ template <class T> void registerUIElement(asIScriptEngine* engine, const char* c
     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 setVisible(bool)", asMETHOD(T, setVisible), 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 bringToFront()", asMETHOD(T, bringToFront), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void addChild(UIElement@+)", asMETHOD(T, addChild), asCALL_THISCALL);
@@ -421,6 +422,7 @@ template <class T> void registerUIElement(asIScriptEngine* engine, const char* c
     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 hasColorGradient() const", asMETHOD(T, hasColorGradient), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "const Variant& getUserData() const", asMETHOD(T, getUserData), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "uint getNumChildren(bool) const", asMETHOD(T, getNumChildren), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "UIElement@+ getChild(uint) const", asMETHODPR(T, getChild, (unsigned) const, UIElement*), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "UIElement@+ getChild(const string& in, bool) const", asMETHODPR(T, getChild, (const std::string&, bool) const, UIElement*), asCALL_THISCALL);

+ 2 - 0
Engine/Engine/RegisterUI.cpp

@@ -163,10 +163,12 @@ static void registerLineEdit(asIScriptEngine* engine)
     engine->RegisterObjectMethod("LineEdit", "void setEchoCharacter(uint8)", asMETHOD(LineEdit, setEchoCharacter), 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 setDefocusable(bool)", asMETHOD(LineEdit, setDefocusable), 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", "float getCursorBlinkRate() const", asMETHOD(LineEdit, getCursorBlinkRate), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "uint getMaxLength() const", asMETHOD(LineEdit, getMaxLength), 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", "BorderImage@+ getCursorElement() const", asMETHOD(LineEdit, getCursorElement), asCALL_THISCALL);
     registerRefCasts<UIElement, LineEdit>(engine, "UIElement", "LineEdit");

+ 6 - 1
Engine/UI/BorderImage.cpp

@@ -56,9 +56,14 @@ void BorderImage::setStyle(const XMLElement& element, ResourceCache* cache)
 
 void BorderImage::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor)
 {
+    bool allOpaque = true;
+    if ((getDerivedOpacity() < 1.0f) || (mColor[C_TOPLEFT].mA < 1.0f) || (mColor[C_TOPRIGHT].mA < 1.0f) ||
+        (mColor[C_BOTTOMLEFT].mA < 1.0f) || (mColor[C_BOTTOMRIGHT].mA < 1.0f))
+        allOpaque = false;
+        
     UIBatch batch;
     batch.begin(&quads);
-    batch.mBlendMode = getDerivedOpacity() == 1.0f ? BLEND_REPLACE : BLEND_ALPHA;
+    batch.mBlendMode = allOpaque ? BLEND_REPLACE : BLEND_ALPHA;
     batch.mScissor = currentScissor;
     batch.mTexture = mTexture;
     

+ 13 - 4
Engine/UI/LineEdit.cpp

@@ -34,6 +34,7 @@ LineEdit::LineEdit(const std::string& name, const std::string& text) :
     mCursorBlinkRate(1.0f),
     mCursorBlinkTimer(0.0f),
     mMaxLength(0),
+    mDefocusable(true),
     mDefocus(false)
 {
     mClipChildren = true;
@@ -108,6 +109,8 @@ void LineEdit::update(float timeStep)
         else
             setChildOffset(IntVector2::sZero);
     }
+    else
+        setChildOffset(IntVector2::sZero);
     
     mCursor->setVisible(cursorVisible);
     
@@ -138,11 +141,12 @@ void LineEdit::onChar(unsigned char c)
         VariantMap eventData;
         eventData[P_ELEMENT] = (void*)this;
         sendEvent(EVENT_TEXTFINISHED, eventData);
-        
-        mDefocus = true;
     }
     else if (c == 27)
-        mDefocus = true;
+    {
+        if (mDefocusable)
+            mDefocus = true;
+    }
     else if ((c >= 0x20) && ((!mMaxLength) || (currentLength < mMaxLength)))
     {
         mLine += (char)c;
@@ -160,7 +164,7 @@ void LineEdit::onChar(unsigned char c)
         VariantMap eventData;
         eventData[P_ELEMENT] = (void*)this;
         eventData[P_TEXT] = mLine;
-        sendEvent(EVENT_TEXTFINISHED, eventData);
+        sendEvent(EVENT_TEXTCHANGED, eventData);
     }
 }
 
@@ -187,6 +191,11 @@ void LineEdit::setMaxLength(unsigned length)
     mMaxLength = length;
 }
 
+void LineEdit::setDefocusable(bool enable)
+{
+    mDefocusable = enable;
+}
+
 void LineEdit::updateText()
 {
     if (!mEchoCharacter)

+ 6 - 0
Engine/UI/LineEdit.h

@@ -54,6 +54,8 @@ public:
     void setCursorBlinkRate(float rate);
     //! Set maximum text length. 0 for unlimited
     void setMaxLength(unsigned length);
+    //! Set whether can defocus with ESC, default true
+    void setDefocusable(bool enable);
     
     //! Return text
     const std::string& getText() const { return mLine; }
@@ -63,6 +65,8 @@ public:
     float getCursorBlinkRate() const { return mCursorBlinkRate; }
     //! Return maximum text length
     unsigned getMaxLength() const { return mMaxLength; }
+    //! Return whether can defocus with ESC
+    bool isDefocusable() const { return mDefocusable; }
     //! Return text element
     Text* getTextElement() const { return mText; }
     //! Return cursor element
@@ -82,6 +86,8 @@ protected:
     float mCursorBlinkTimer;
     //! Maximum text length
     unsigned mMaxLength;
+    //! ESC defocus flag
+    bool mDefocusable;
     //! Text element
     SharedPtr<Text> mText;
     //! Cursor element

+ 4 - 0
Engine/UI/Text.cpp

@@ -286,6 +286,10 @@ void Text::calculateTextSize()
             height += rowHeight;
             mRowWidths.push_back(rowWidth);
         }
+        
+        // Set row height even if text is empty
+        if (!height)
+            height = rowHeight;
     }
     
     setSize(width, height);

+ 13 - 7
Engine/UI/UIBatch.cpp

@@ -69,7 +69,10 @@ void UIBatch::addQuad(UIElement& element, int x, int y, int width, int height, i
         Color color = element.getColor(C_TOPLEFT);
         if (element.isHovering())
             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 (color.mA <= 0.0f)
+            return;
         unsigned uintColor = getD3DColor(color);
         quad.mTopLeftColor = uintColor;
         quad.mTopRightColor = uintColor;
@@ -110,7 +113,10 @@ void UIBatch::addQuad(UIElement& element, int x, int y, int width, int height, i
         Color color = element.getColor(C_TOPLEFT);
         if (element.isHovering())
             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 (color.mA <= 0.0f)
+            return;
         unsigned uintColor = getD3DColor(color);
         quad.mTopLeftColor = uintColor;
         quad.mTopRightColor = uintColor;
@@ -127,7 +133,7 @@ bool UIBatch::merge(const UIBatch& batch)
     if ((batch.mBlendMode != mBlendMode) ||
         (batch.mScissor != mScissor) ||
         (batch.mTexture != mTexture) ||
-        (batch.mQuads != mQuads) || 
+        (batch.mQuads != mQuads) ||
         (batch.mQuadStart != mQuadStart + mQuadCount))
         return false;
     
@@ -203,10 +209,10 @@ void UIBatch::draw(Renderer* renderer, VertexShader* vs, PixelShader* ps) const
     }
     else
     {
-        renderer->beginImmediate(TRIANGLE_LIST, quads.size() * 6, MASK_POSITION | MASK_COLOR);
+        renderer->beginImmediate(TRIANGLE_LIST, mQuadCount * 6, MASK_POSITION | MASK_COLOR);
         float* dest = (float*)renderer->getImmediateDataPtr();
         
-        for (unsigned i = 0; i < quads.size(); ++i)
+        for (unsigned i = mQuadStart; i < mQuadStart + mQuadCount; ++i)
         {
             const UIQuad& quad = quads[i];
             static Vector2 topLeft, bottomRight, topLeftUV, bottomRightUV;
@@ -264,7 +270,7 @@ unsigned UIBatch::getInterpolatedColor(UIElement& element, int x, int y)
         Color color = topColor.lerp(bottomColor, cLerpY);
         if (element.isHovering())
             color += element.getHoverColor();
-        color.mA = element.getDerivedOpacity();
+        color.mA *= element.getDerivedOpacity();
         return getD3DColor(color);
     }
     else
@@ -272,7 +278,7 @@ unsigned UIBatch::getInterpolatedColor(UIElement& element, int x, int y)
         Color color = element.getColor(C_TOPLEFT);
         if (element.isHovering())
             color += element.getHoverColor();
-        color.mA = element.getDerivedOpacity();
+        color.mA *= element.getDerivedOpacity();
         return getD3DColor(color);
     }
 }

+ 5 - 0
Engine/UI/UIElement.cpp

@@ -410,6 +410,11 @@ void UIElement::setVisible(bool enable)
     mVisible = enable;
 }
 
+void UIElement::setUserData(const Variant& userData)
+{
+    mUserData = userData;
+}
+
 void UIElement::setStyleAuto(XMLFile* file, ResourceCache* cache)
 {
     XMLElement element = getStyleElement(file);

+ 6 - 0
Engine/UI/UIElement.h

@@ -149,6 +149,8 @@ public:
     void setFocus(bool enable);
     //! Set whether is visible
     void setVisible(bool enable);
+    //! Set userdata
+    void setUserData(const Variant& userData);
     //! Set style from an XML file. Find the style element automatically
     void setStyleAuto(XMLFile* file, ResourceCache* cache);
     //! Bring UI element to front
@@ -208,6 +210,8 @@ public:
     bool isHovering() const { return mHovering; }
     //! Return whether has different color in at least one corner
     bool hasColorGradient() const { return mHasColorGradient; }
+    //! Return userdata
+    Variant getUserData() const { return mUserData; }
     //! Return number of child elements
     unsigned getNumChildren(bool recursive = false) const;
     //! Return child element by index
@@ -284,6 +288,8 @@ protected:
     bool mVisible;
     //! Hovering flag
     bool mHovering;
+    //! Userdata
+    Variant mUserData;
     
 private:
     //! Return child elements recursively

+ 1 - 1
Examples/NinjaSnowWar/Game.cpp

@@ -311,7 +311,7 @@ void Game::createOverlays()
     
     mMessage = new Text();
     mMessage->setFont(mCache->getResource<Font>("Fonts/BlueHighway.ttf"), 17);
-    mMessage->setColor(Color(1.0f, 0.0f, 0.0f, 0.0f));
+    mMessage->setColor(Color(1.0f, 0.0f, 0.0f));
     mMessage->setAlignment(HA_CENTER, VA_CENTER);
     mMessage->setPosition(0, -height * 2);
     uiRoot->addChild(mMessage);

+ 3 - 4
Examples/ScriptTest/Game.cpp

@@ -22,12 +22,14 @@
 //
 
 #include "Camera.h"
+#include "Console.h"
 #include "Engine.h"
 #include "Font.h"
 #include "Game.h"
 #include "Geometry.h"
 #include "IndexBuffer.h"
 #include "Input.h"
+#include "LineEdit.h"
 #include "Log.h"
 #include "Model.h"
 #include "PackageFile.h"
@@ -40,6 +42,7 @@
 #include "ScriptEngine.h"
 #include "ScriptFile.h"
 #include "StringUtils.h"
+#include "Texture2D.h"
 #include "VertexBuffer.h"
 
 #include <angelscript.h>
@@ -74,10 +77,6 @@ void Game::run()
     while (!mEngine->isExiting())
     {
         mScriptFile->execute("void runFrame()");
-        
-        // Test reloading the ninja script
-        if (mEngine->getInput()->getKeyPress('R'))
-            mCache->reloadResource(mCache->getResource<ScriptFile>("Scripts/Ninja.as"));
     }
 }