Browse Source

Added arrow key scrolling to ScrollView.
Clear UI element focus when a non-focusable element clicked.
ESC defocusing is now a property of UIElement. Defocusing is handled by UI.
Fixed UIElement::setSelected().
Renamed some keys.

Lasse Öörni 15 years ago
parent
commit
13ca9b42fe

+ 2 - 2
Bin/Data/Scripts/GraphicsTest.as

@@ -209,7 +209,7 @@ void initScene()
         billboard.setPosition(Vector3(random() * 200.0 - 100.0, random() * 15.0 + 5.0, random() * 200.0 - 100.0));
         billboard.setPosition(Vector3(random() * 200.0 - 100.0, random() * 15.0 + 5.0, random() * 200.0 - 100.0));
         billboard.setMaterial(cache.getResource("Material", "Materials/LitSmoke.xml"));
         billboard.setMaterial(cache.getResource("Material", "Materials/LitSmoke.xml"));
         billboard.setBillboardsSorted(true);
         billboard.setBillboardsSorted(true);
-        
+
         for (uint j = 0; j < NUM_BILLBOARDS; ++j)
         for (uint j = 0; j < NUM_BILLBOARDS; ++j)
         {
         {
             Billboard@ bb = billboard.getBillboard(j);
             Billboard@ bb = billboard.getBillboard(j);
@@ -488,7 +488,7 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
             pipeline.setEdgeFilter(params);
             pipeline.setEdgeFilter(params);
         }
         }
         
         
-        if ((input.getKeyPress(KEY_ESCAPE)) && (ui.getFocusElement() is null))
+        if ((input.getKeyPress(KEY_ESC)) && (ui.getFocusElement() is null))
             engine.exit();
             engine.exit();
     }
     }
 }
 }

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

@@ -60,7 +60,7 @@ void runFrame()
 {
 {
     engine.runFrame(gameScene, gameCamera, !paused);
     engine.runFrame(gameScene, gameCamera, !paused);
 
 
-    if (input.getKeyPress(KEY_ESCAPE))
+    if (input.getKeyPress(KEY_ESC))
         engine.exit();
         engine.exit();
 }
 }
 
 

+ 3 - 3
Engine/Engine/RegisterInput.cpp

@@ -44,14 +44,14 @@ static void registerKeyCodes(asIScriptEngine* engine)
     engine->RegisterGlobalProperty("const int KEY_MENU", (void*)&KEY_MENU);
     engine->RegisterGlobalProperty("const int KEY_MENU", (void*)&KEY_MENU);
     engine->RegisterGlobalProperty("const int KEY_PAUSE", (void*)&KEY_PAUSE);
     engine->RegisterGlobalProperty("const int KEY_PAUSE", (void*)&KEY_PAUSE);
     engine->RegisterGlobalProperty("const int KEY_CAPITAL", (void*)&KEY_CAPITAL);
     engine->RegisterGlobalProperty("const int KEY_CAPITAL", (void*)&KEY_CAPITAL);
-    engine->RegisterGlobalProperty("const int KEY_ESCAPE", (void*)&KEY_ESCAPE);
+    engine->RegisterGlobalProperty("const int KEY_ESC", (void*)&KEY_ESC);
     engine->RegisterGlobalProperty("const int KEY_CONVERT", (void*)&KEY_CONVERT);
     engine->RegisterGlobalProperty("const int KEY_CONVERT", (void*)&KEY_CONVERT);
     engine->RegisterGlobalProperty("const int KEY_NONCONVERT", (void*)&KEY_NONCONVERT);
     engine->RegisterGlobalProperty("const int KEY_NONCONVERT", (void*)&KEY_NONCONVERT);
     engine->RegisterGlobalProperty("const int KEY_ACCEPT", (void*)&KEY_ACCEPT);
     engine->RegisterGlobalProperty("const int KEY_ACCEPT", (void*)&KEY_ACCEPT);
     engine->RegisterGlobalProperty("const int KEY_MODECHANGE", (void*)&KEY_MODECHANGE);
     engine->RegisterGlobalProperty("const int KEY_MODECHANGE", (void*)&KEY_MODECHANGE);
     engine->RegisterGlobalProperty("const int KEY_SPACE", (void*)&KEY_SPACE);
     engine->RegisterGlobalProperty("const int KEY_SPACE", (void*)&KEY_SPACE);
-    engine->RegisterGlobalProperty("const int KEY_PRIOR", (void*)&KEY_PRIOR);
-    engine->RegisterGlobalProperty("const int KEY_NEXT", (void*)&KEY_NEXT);
+    engine->RegisterGlobalProperty("const int KEY_PAGEUP", (void*)&KEY_PAGEUP);
+    engine->RegisterGlobalProperty("const int KEY_PAGEDOWN", (void*)&KEY_PAGEDOWN);
     engine->RegisterGlobalProperty("const int KEY_END", (void*)&KEY_END);
     engine->RegisterGlobalProperty("const int KEY_END", (void*)&KEY_END);
     engine->RegisterGlobalProperty("const int KEY_HOME", (void*)&KEY_HOME);
     engine->RegisterGlobalProperty("const int KEY_HOME", (void*)&KEY_HOME);
     engine->RegisterGlobalProperty("const int KEY_LEFT", (void*)&KEY_LEFT);
     engine->RegisterGlobalProperty("const int KEY_LEFT", (void*)&KEY_LEFT);

+ 2 - 0
Engine/Engine/RegisterTemplates.h

@@ -413,6 +413,7 @@ template <class T> void registerUIElement(asIScriptEngine* engine, const char* c
     engine->RegisterObjectMethod(className, "void setClipChildren(bool)", asMETHOD(T, setClipChildren), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setClipChildren(bool)", asMETHOD(T, setClipChildren), asCALL_THISCALL);
     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 setDefocusable(bool)", asMETHOD(T, setDefocusable), 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 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);
@@ -441,6 +442,7 @@ template <class T> void registerUIElement(asIScriptEngine* engine, const char* c
     engine->RegisterObjectMethod(className, "bool getClipChildren() const", asMETHOD(T, getClipChildren), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool getClipChildren() const", asMETHOD(T, getClipChildren), asCALL_THISCALL);
     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 isDefocusable() const", asMETHOD(T, isDefocusable), 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 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);

+ 4 - 2
Engine/Engine/RegisterUI.cpp

@@ -130,10 +130,14 @@ static void registerScrollView(asIScriptEngine* engine)
     engine->RegisterObjectMethod("ScrollView", "void setViewSize(int, int)", asMETHODPR(ScrollView, setViewSize, (int, int), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "void setViewSize(int, int)", asMETHODPR(ScrollView, setViewSize, (int, int), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "void setHorizontalSlider(Slider@+)", asMETHOD(ScrollView, setHorizontalSlider), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "void setHorizontalSlider(Slider@+)", asMETHOD(ScrollView, setHorizontalSlider), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "void setVerticalSlider(Slider@+)", asMETHOD(ScrollView, setVerticalSlider), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "void setVerticalSlider(Slider@+)", asMETHOD(ScrollView, setVerticalSlider), asCALL_THISCALL);
+    engine->RegisterObjectMethod("ScrollView", "void setScrollStep(float)", asMETHOD(ScrollView, setScrollStep), asCALL_THISCALL);
+    engine->RegisterObjectMethod("ScrollView", "void setPageStep(float)", asMETHOD(ScrollView, setPageStep), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "const IntVector2& getViewPosition() const", asMETHOD(ScrollView, getViewPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "const IntVector2& getViewPosition() const", asMETHOD(ScrollView, getViewPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "const IntVector2& getViewSize() const", asMETHOD(ScrollView, getViewSize), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "const IntVector2& getViewSize() const", asMETHOD(ScrollView, getViewSize), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "Slider@+ getHorizontalSlider() const", asMETHOD(ScrollView, getHorizontalSlider), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "Slider@+ getHorizontalSlider() const", asMETHOD(ScrollView, getHorizontalSlider), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "Slider@+ getVerticalSlider() const", asMETHOD(ScrollView, getVerticalSlider), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "Slider@+ getVerticalSlider() const", asMETHOD(ScrollView, getVerticalSlider), asCALL_THISCALL);
+    engine->RegisterObjectMethod("ScrollView", "float getScrollStep() const", asMETHOD(ScrollView, getScrollStep), asCALL_THISCALL);
+    engine->RegisterObjectMethod("ScrollView", "float getPageStep() const", asMETHOD(ScrollView, getPageStep), asCALL_THISCALL);
     registerRefCasts<UIElement, ScrollView>(engine, "UIElement", "ScrollView");
     registerRefCasts<UIElement, ScrollView>(engine, "UIElement", "ScrollView");
 }
 }
 
 
@@ -172,7 +176,6 @@ static void registerLineEdit(asIScriptEngine* engine)
     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 setEchoCharacter(uint8)", asMETHOD(LineEdit, setEchoCharacter), asCALL_THISCALL);
-    engine->RegisterObjectMethod("LineEdit", "void setDefocusable(bool)", asMETHOD(LineEdit, setDefocusable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "void setCursorMovable(bool)", asMETHOD(LineEdit, setCursorMovable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "void setCursorMovable(bool)", asMETHOD(LineEdit, setCursorMovable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "void setTextSelectable(bool)", asMETHOD(LineEdit, setTextSelectable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "void setTextSelectable(bool)", asMETHOD(LineEdit, setTextSelectable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "void setTextCopyable(bool)", asMETHOD(LineEdit, setTextCopyable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "void setTextCopyable(bool)", asMETHOD(LineEdit, setTextCopyable), asCALL_THISCALL);
@@ -181,7 +184,6 @@ static void registerLineEdit(asIScriptEngine* engine)
     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", "uint8 getEchoCharacter() const", asMETHOD(LineEdit, getEchoCharacter), asCALL_THISCALL);
-    engine->RegisterObjectMethod("LineEdit", "bool isDefocusable() const", asMETHOD(LineEdit, isDefocusable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "bool isCursorMovable() const", asMETHOD(LineEdit, isCursorMovable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "bool isCursorMovable() const", asMETHOD(LineEdit, isCursorMovable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "bool isTextSelectable() const", asMETHOD(LineEdit, isTextSelectable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "bool isTextSelectable() const", asMETHOD(LineEdit, isTextSelectable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "bool isTextCopyable() const", asMETHOD(LineEdit, isTextCopyable), asCALL_THISCALL);
     engine->RegisterObjectMethod("LineEdit", "bool isTextCopyable() const", asMETHOD(LineEdit, isTextCopyable), asCALL_THISCALL);

+ 3 - 3
Engine/Input/InputEvents.h

@@ -99,14 +99,14 @@ static const int KEY_CTRL = 0x11;
 static const int KEY_MENU = 0x12;
 static const int KEY_MENU = 0x12;
 static const int KEY_PAUSE = 0x13;
 static const int KEY_PAUSE = 0x13;
 static const int KEY_CAPITAL = 0x14;
 static const int KEY_CAPITAL = 0x14;
-static const int KEY_ESCAPE = 0x1b;
+static const int KEY_ESC = 0x1b;
 static const int KEY_CONVERT = 0x1c;
 static const int KEY_CONVERT = 0x1c;
 static const int KEY_NONCONVERT = 0x1d;
 static const int KEY_NONCONVERT = 0x1d;
 static const int KEY_ACCEPT = 0x1e;
 static const int KEY_ACCEPT = 0x1e;
 static const int KEY_MODECHANGE = 0x1f;
 static const int KEY_MODECHANGE = 0x1f;
 static const int KEY_SPACE = 0x20;
 static const int KEY_SPACE = 0x20;
-static const int KEY_PRIOR = 0x21;
-static const int KEY_NEXT = 0x22;
+static const int KEY_PAGEUP = 0x21;
+static const int KEY_PAGEDOWN = 0x22;
 static const int KEY_END = 0x23;
 static const int KEY_END = 0x23;
 static const int KEY_HOME = 0x24;
 static const int KEY_HOME = 0x24;
 static const int KEY_LEFT = 0x25;
 static const int KEY_LEFT = 0x25;

+ 17 - 25
Engine/UI/LineEdit.cpp

@@ -24,7 +24,6 @@
 #include "Precompiled.h"
 #include "Precompiled.h"
 #include "Input.h"
 #include "Input.h"
 #include "LineEdit.h"
 #include "LineEdit.h"
-#include "Log.h"
 #include "Text.h"
 #include "Text.h"
 #include "UIEvents.h"
 #include "UIEvents.h"
 
 
@@ -40,11 +39,9 @@ LineEdit::LineEdit(const std::string& name, const std::string& text) :
     mCursorBlinkTimer(0.0f),
     mCursorBlinkTimer(0.0f),
     mMaxLength(0),
     mMaxLength(0),
     mEchoCharacter(0),
     mEchoCharacter(0),
-    mDefocusable(true),
     mCursorMovable(true),
     mCursorMovable(true),
     mTextSelectable(true),
     mTextSelectable(true),
-    mTextCopyable(true),
-    mDefocus(false)
+    mTextCopyable(true)
 {
 {
     mClipChildren = true;
     mClipChildren = true;
     mEnabled = true;
     mEnabled = true;
@@ -69,8 +66,6 @@ void LineEdit::setStyle(const XMLElement& element, ResourceCache* cache)
     
     
     if (element.hasChildElement("maxlength"))
     if (element.hasChildElement("maxlength"))
         setMaxLength(element.getChildElement("maxlength").getInt("value"));
         setMaxLength(element.getChildElement("maxlength").getInt("value"));
-    if (element.hasChildElement("defocusable"))
-        setDefocusable(element.getChildElement("defocusable").getBool("enable"));
     if (element.hasChildElement("cursormovable"))
     if (element.hasChildElement("cursormovable"))
         setCursorMovable(element.getChildElement("cursormovable").getBool("enable"));
         setCursorMovable(element.getChildElement("cursormovable").getBool("enable"));
     if (element.hasChildElement("textselectable"))
     if (element.hasChildElement("textselectable"))
@@ -121,12 +116,6 @@ void LineEdit::update(float timeStep)
     if (mFocus)
     if (mFocus)
         cursorVisible = mCursorBlinkTimer < 0.5f;
         cursorVisible = mCursorBlinkTimer < 0.5f;
     mCursor->setVisible(cursorVisible);
     mCursor->setVisible(cursorVisible);
-    
-    if (mDefocus)
-    {
-        setFocus(false);
-        mDefocus = false;
-    }
 }
 }
 
 
 void LineEdit::onClick(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers)
 void LineEdit::onClick(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers)
@@ -261,6 +250,22 @@ void LineEdit::onKey(int key, int buttons, int qualifiers)
         }
         }
         break;
         break;
         
         
+    case KEY_HOME:
+        if ((mCursorMovable) && (mCursorPosition > 0))
+        {
+            mCursorPosition = 0;
+            cursorMoved = true;
+        }
+        break;
+        
+    case KEY_END:
+        if ((mCursorMovable) && (mCursorPosition < mLine.length()))
+        {
+            mCursorPosition = mLine.length();
+            cursorMoved = true;
+        }
+        break;
+        
     case KEY_DELETE:
     case KEY_DELETE:
         if (!mText->getSelectionLength())
         if (!mText->getSelectionLength())
         {
         {
@@ -330,14 +335,6 @@ void LineEdit::onChar(unsigned char c, int buttons, int qualifiers)
         
         
         mText->clearSelection();
         mText->clearSelection();
     }
     }
-    else if (c == 27)
-    {
-        if (mDefocusable)
-        {
-            mText->clearSelection();
-            mDefocus = true;
-        }
-    }
     else if ((c >= 0x20) && ((!mMaxLength) || (mLine.length() < mMaxLength)))
     else if ((c >= 0x20) && ((!mMaxLength) || (mLine.length() < mMaxLength)))
     {
     {
         static std::string charStr;
         static std::string charStr;
@@ -429,11 +426,6 @@ void LineEdit::setEchoCharacter(char c)
     updateText();
     updateText();
 }
 }
 
 
-void LineEdit::setDefocusable(bool enable)
-{
-    mDefocusable = enable;
-}
-
 void LineEdit::setCursorMovable(bool enable)
 void LineEdit::setCursorMovable(bool enable)
 {
 {
     mCursorMovable = enable;
     mCursorMovable = enable;

+ 0 - 8
Engine/UI/LineEdit.h

@@ -69,8 +69,6 @@ public:
     void setMaxLength(unsigned length);
     void setMaxLength(unsigned length);
     //! Set echo character for password entry and such. 0 (default) shows the actual text
     //! Set echo character for password entry and such. 0 (default) shows the actual text
     void setEchoCharacter(char c);
     void setEchoCharacter(char c);
-    //! Set whether can defocus with ESC, default true
-    void setDefocusable(bool enable);
     //! Set whether can move cursor with arrows or mouse, default true
     //! Set whether can move cursor with arrows or mouse, default true
     void setCursorMovable(bool enable);
     void setCursorMovable(bool enable);
     //! Set whether selections are allowed, default true
     //! Set whether selections are allowed, default true
@@ -88,8 +86,6 @@ public:
     unsigned getMaxLength() const { return mMaxLength; }
     unsigned getMaxLength() const { return mMaxLength; }
     //! Return echo character
     //! Return echo character
     char getEchoCharacter() const { return mEchoCharacter; }
     char getEchoCharacter() const { return mEchoCharacter; }
-    //! Return whether can defocus with ESC
-    bool isDefocusable() const { return mDefocusable; }
     //! Return whether can move cursor with arrows or mouse
     //! Return whether can move cursor with arrows or mouse
     bool isCursorMovable() const { return mCursorMovable; }
     bool isCursorMovable() const { return mCursorMovable; }
     //! Return whether selections are allowed
     //! Return whether selections are allowed
@@ -131,16 +127,12 @@ protected:
     unsigned mMaxLength;
     unsigned mMaxLength;
     //! Echo character
     //! Echo character
     char mEchoCharacter;
     char mEchoCharacter;
-    //! ESC defocus flag
-    bool mDefocusable;
     //! Cursor movable flag
     //! Cursor movable flag
     bool mCursorMovable;
     bool mCursorMovable;
     //! Text selectable flag
     //! Text selectable flag
     bool mTextSelectable;
     bool mTextSelectable;
     //! Copy-paste enable flag
     //! Copy-paste enable flag
     bool mTextCopyable;
     bool mTextCopyable;
-    //! Defocus flag (defocus on next update)
-    bool mDefocus;
 };
 };
 
 
 #endif // UI_LINEEDIT_H
 #endif // UI_LINEEDIT_H

+ 102 - 1
Engine/UI/ScrollView.cpp

@@ -22,6 +22,7 @@
 //
 //
 
 
 #include "Precompiled.h"
 #include "Precompiled.h"
+#include "InputEvents.h"
 #include "ScrollView.h"
 #include "ScrollView.h"
 #include "Slider.h"
 #include "Slider.h"
 #include "UIEvents.h"
 #include "UIEvents.h"
@@ -32,10 +33,13 @@ ScrollView::ScrollView(const std::string& name) :
     BorderImage(name),
     BorderImage(name),
     mViewPosition(IntVector2::sZero),
     mViewPosition(IntVector2::sZero),
     mViewSize(IntVector2::sZero),
     mViewSize(IntVector2::sZero),
-    mLastSize(IntVector2::sZero)
+    mLastSize(IntVector2::sZero),
+    mScrollStep(0.25f),
+    mPageStep(1.0f)
 {
 {
     mClipChildren = true;
     mClipChildren = true;
     mEnabled = true;
     mEnabled = true;
+    mFocusable = true;
 }
 }
 
 
 ScrollView::~ScrollView()
 ScrollView::~ScrollView()
@@ -50,6 +54,10 @@ void ScrollView::setStyle(const XMLElement& element, ResourceCache* cache)
         setViewPosition(element.getChildElement("viewposition").getIntVector2("value"));
         setViewPosition(element.getChildElement("viewposition").getIntVector2("value"));
     if (element.hasChildElement("viewsize"))
     if (element.hasChildElement("viewsize"))
         setViewSize(element.getChildElement("viewsize").getIntVector2("value"));
         setViewSize(element.getChildElement("viewsize").getIntVector2("value"));
+    if (element.hasChildElement("scrollstep"))
+        setScrollStep(element.getChildElement("scrollstep").getFloat("value"));
+    if (element.hasChildElement("pagestep"))
+        setScrollStep(element.getChildElement("pagestep").getFloat("value"));
     
     
     UIElement* root = getRootElement();
     UIElement* root = getRootElement();
     if (root)
     if (root)
@@ -76,6 +84,89 @@ void ScrollView::update(float timeStep)
         updateViewFromSliders();
         updateViewFromSliders();
 }
 }
 
 
+void ScrollView::onKey(int key, int buttons, int qualifiers)
+{
+    switch (key)
+    {
+    case KEY_LEFT:
+        if (mHorizontalSlider)
+        {
+            if (qualifiers & QUAL_CTRL)
+                mHorizontalSlider->setValue(0.0f);
+            else
+                mHorizontalSlider->setValue(mHorizontalSlider->getValue() - mScrollStep);
+        }
+        break;
+        
+    case KEY_RIGHT:
+        if (mHorizontalSlider)
+        {
+            if (qualifiers & QUAL_CTRL)
+                mHorizontalSlider->setValue(mHorizontalSlider->getRange());
+            else
+                mHorizontalSlider->setValue(mHorizontalSlider->getValue() + mScrollStep);
+        }
+        break;
+        
+    case KEY_UP:
+        if (mVerticalSlider)
+        {
+            if (qualifiers & QUAL_CTRL)
+                mVerticalSlider->setValue(0.0f);
+            else
+                mVerticalSlider->setValue(mVerticalSlider->getValue() - mScrollStep);
+        }
+        break;
+        
+    case KEY_DOWN:
+        if (mVerticalSlider)
+        {
+            if (qualifiers & QUAL_CTRL)
+                mVerticalSlider->setValue(mVerticalSlider->getRange());
+            else
+                mVerticalSlider->setValue(mVerticalSlider->getValue() + mScrollStep);
+        }
+        break;
+        
+    case KEY_PAGEUP:
+        if (mVerticalSlider)
+            mVerticalSlider->setValue(mVerticalSlider->getValue() - mPageStep);
+        break;
+        
+    case KEY_PAGEDOWN:
+        if (mVerticalSlider)
+            mVerticalSlider->setValue(mVerticalSlider->getValue() + mPageStep);
+        break;
+    
+    case KEY_HOME:
+        if (mVerticalSlider)
+            mVerticalSlider->setValue(0.0f);
+        break;
+    
+    case KEY_END:
+        if (mVerticalSlider)
+            mVerticalSlider->setValue(mVerticalSlider->getRange());
+        break;
+    }
+}
+
+void ScrollView::onFocus()
+{
+    // Set selected state (constant hover) on the sliders to show they are now under key control
+    if (mHorizontalSlider)
+        mHorizontalSlider->setSelected(true);
+    if (mVerticalSlider)
+        mVerticalSlider->setSelected(true);
+}
+
+void ScrollView::onDefocus()
+{
+    if (mHorizontalSlider)
+        mHorizontalSlider->setSelected(false);
+    if (mVerticalSlider)
+        mVerticalSlider->setSelected(false);
+}
+
 void ScrollView::setViewPosition(const IntVector2& position)
 void ScrollView::setViewPosition(const IntVector2& position)
 {
 {
     updateView(position);
     updateView(position);
@@ -113,6 +204,16 @@ void ScrollView::setVerticalSlider(Slider* slider)
     updateSliders();
     updateSliders();
 }
 }
 
 
+void ScrollView::setScrollStep(float step)
+{
+    mScrollStep = max(step, 0.0f);
+}
+
+void ScrollView::setPageStep(float step)
+{
+    mPageStep = max(step, 0.0f);
+}
+
 void ScrollView::updateViewFromSliders()
 void ScrollView::updateViewFromSliders()
 {
 {
     if ((!mHorizontalSlider) && (!mVerticalSlider))
     if ((!mHorizontalSlider) && (!mVerticalSlider))

+ 18 - 0
Engine/UI/ScrollView.h

@@ -43,6 +43,12 @@ public:
     virtual void setStyle(const XMLElement& element, ResourceCache* cache);
     virtual void setStyle(const XMLElement& element, ResourceCache* cache);
     //! Perform UI element update
     //! Perform UI element update
     virtual void update(float timeStep);
     virtual void update(float timeStep);
+    //! React to a key press
+    virtual void onKey(int key, int buttons, int qualifiers);
+    //! React to gaining focus
+    virtual void onFocus();
+    //! React to losing focus
+    virtual void onDefocus();
     
     
     //! Set view offset from the top-left corner
     //! Set view offset from the top-left corner
     void setViewPosition(const IntVector2& position);
     void setViewPosition(const IntVector2& position);
@@ -56,6 +62,10 @@ public:
     void setHorizontalSlider(Slider* slider);
     void setHorizontalSlider(Slider* slider);
     //! Set vertical slider
     //! Set vertical slider
     void setVerticalSlider(Slider* slider);
     void setVerticalSlider(Slider* slider);
+    //! Set arrow key scroll step
+    void setScrollStep(float step);
+    //! Set arrow key page step
+    void setPageStep(float step);
     
     
     //! Return view offset from the top-left corner
     //! Return view offset from the top-left corner
     const IntVector2& getViewPosition() const { return mViewPosition; }
     const IntVector2& getViewPosition() const { return mViewPosition; }
@@ -65,6 +75,10 @@ public:
     Slider* getHorizontalSlider() const { return mHorizontalSlider; }
     Slider* getHorizontalSlider() const { return mHorizontalSlider; }
     //! Return vertical slider
     //! Return vertical slider
     Slider* getVerticalSlider() const { return mVerticalSlider; }
     Slider* getVerticalSlider() const { return mVerticalSlider; }
+    //! Return arrow key scroll step
+    float getScrollStep() const { return mScrollStep; }
+    //! Return arrow key page step
+    float getPageStep() const { return mPageStep; }
     
     
 protected:
 protected:
     //! Update the view from sliders if available
     //! Update the view from sliders if available
@@ -84,6 +98,10 @@ protected:
     IntVector2 mViewSize;
     IntVector2 mViewSize;
     //! Last known own size
     //! Last known own size
     IntVector2 mLastSize;
     IntVector2 mLastSize;
+    //! Arrow key scroll step
+    float mScrollStep;
+    //! Arrow key page step
+    float mPageStep;
 };
 };
 
 
 #endif // UI_SCROLLVIEW_H
 #endif // UI_SCROLLVIEW_H

+ 14 - 5
Engine/UI/Slider.cpp

@@ -74,8 +74,9 @@ void Slider::update(float timeStep)
     if (mDragSlider)
     if (mDragSlider)
         mHovering = true;
         mHovering = true;
     
     
-    // Copy hover effect to the slider button
+    // Copy hover and selection effect to the slider button
     mSlider->setHovering(mHovering);
     mSlider->setHovering(mHovering);
+    mSlider->setSelected(mSelected);
 }
 }
 
 
 void Slider::onHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers)
 void Slider::onHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers)
@@ -138,14 +139,22 @@ void Slider::setOrientation(UIElementOrientation orientation)
 
 
 void Slider::setRange(float range)
 void Slider::setRange(float range)
 {
 {
-    mRange = max(range, 0.0f);
-    updateSlider();
+    range = max(range, 0.0f);
+    if (range != mRange)
+    {
+        mRange = range;
+        updateSlider();
+    }
 }
 }
 
 
 void Slider::setValue(float value)
 void Slider::setValue(float value)
 {
 {
-    mValue = max(value, 0.0f);
-    updateSlider();
+    value = clamp(value, 0.0f, mRange);
+    if (value != mValue)
+    {
+        mValue = value;
+        updateSlider();
+    }
 }
 }
 
 
 void Slider::updateSlider()
 void Slider::updateSlider()

+ 18 - 7
Engine/UI/UI.cpp

@@ -54,6 +54,7 @@ UI::UI(Renderer* renderer, ResourceCache* cache) :
     mCache(cache),
     mCache(cache),
     mMouseDrag(false),
     mMouseDrag(false),
     mMouseDragElement(0),
     mMouseDragElement(0),
+    mDefocusElement(0),
     mMouseButtons(0),
     mMouseButtons(0),
     mQualifiers(0)
     mQualifiers(0)
 {
 {
@@ -117,11 +118,8 @@ void UI::setFocusElement(UIElement* element)
     eventData[P_ELEMENT] = (void*)element;
     eventData[P_ELEMENT] = (void*)element;
     sendEvent(EVENT_TRYFOCUS, eventData);
     sendEvent(EVENT_TRYFOCUS, eventData);
     
     
-    if (element)
-    {
-        if ((element->hasFocus()) || (!element->isFocusable()))
-            return;
-    }
+    if ((element) && (element->hasFocus()))
+        return;
     
     
     std::vector<UIElement*> allChildren = mRootElement->getChildren(true);
     std::vector<UIElement*> allChildren = mRootElement->getChildren(true);
     
     
@@ -133,7 +131,7 @@ void UI::setFocusElement(UIElement* element)
             other->setFocus(false);
             other->setFocus(false);
     }
     }
     
     
-    if (element)
+    if ((element) && (element->isFocusable()))
         element->setFocus(true);
         element->setFocus(true);
 }
 }
 
 
@@ -164,6 +162,15 @@ void UI::update(float timeStep)
         }
         }
     }
     }
     
     
+    // Defocus element now if should
+    if (mDefocusElement)
+    {
+        // Do nothing if the focus element changed in the meanwhile
+        if (mDefocusElement == getFocusElement())
+            setFocusElement(0);
+        mDefocusElement = 0;
+    }
+    
     {
     {
         PROFILE(UI_UpdateElements);
         PROFILE(UI_UpdateElements);
         
         
@@ -505,9 +512,9 @@ void UI::handleKeyDown(StringHash eventType, VariantMap& eventData)
     UIElement* element = getFocusElement();
     UIElement* element = getFocusElement();
     if (element)
     if (element)
     {
     {
+        // Switch focus between focusable sibling elements
         if (key == KEY_TAB)
         if (key == KEY_TAB)
         {
         {
-            // Switch focus between focusable sibling elements
             UIElement* parent = element->getParent();
             UIElement* parent = element->getParent();
             if (parent)
             if (parent)
             {
             {
@@ -530,6 +537,10 @@ void UI::handleKeyDown(StringHash eventType, VariantMap& eventData)
                 }
                 }
             }
             }
         }
         }
+        // Defocus the element
+        else if ((key == KEY_ESC) && (element->isDefocusable()))
+            mDefocusElement = element;
+        // If none of the special keys, pass the key to the focused element
         else
         else
             element->onKey(key, mMouseButtons, mQualifiers);
             element->onKey(key, mMouseButtons, mQualifiers);
     }
     }

+ 2 - 0
Engine/UI/UI.h

@@ -132,6 +132,8 @@ private:
     bool mMouseDrag;
     bool mMouseDrag;
     //! Mouse drag UI element
     //! Mouse drag UI element
     UIElement* mMouseDragElement;
     UIElement* mMouseDragElement;
+    //! Element to defocus on the next update
+    UIElement* mDefocusElement;
     //! Mouse buttons held down
     //! Mouse buttons held down
     int mMouseButtons;
     int mMouseButtons;
     //! Qualifier keys held down
     //! Qualifier keys held down

+ 9 - 1
Engine/UI/UIElement.cpp

@@ -43,6 +43,7 @@ UIElement::UIElement(const std::string& name) :
     mClipChildren(false),
     mClipChildren(false),
     mEnabled(false),
     mEnabled(false),
     mFocusable(false),
     mFocusable(false),
+    mDefocusable(true),
     mFocus(false),
     mFocus(false),
     mSelected(false),
     mSelected(false),
     mVisible(true),
     mVisible(true),
@@ -141,6 +142,8 @@ void UIElement::setStyle(const XMLElement& element, ResourceCache* cache)
         setEnabled(element.getChildElement("enabled").getBool("enable"));
         setEnabled(element.getChildElement("enabled").getBool("enable"));
     if (element.hasChildElement("focusable"))
     if (element.hasChildElement("focusable"))
         setFocusable(element.getChildElement("focusable").getBool("enable"));
         setFocusable(element.getChildElement("focusable").getBool("enable"));
+    if (element.hasChildElement("defocusable"))
+        setDefocusable(element.getChildElement("defocusable").getBool("enable"));
     if (element.hasChildElement("selected"))
     if (element.hasChildElement("selected"))
         setSelected(element.getChildElement("selected").getBool("enable"));
         setSelected(element.getChildElement("selected").getBool("enable"));
     if (element.hasChildElement("visible"))
     if (element.hasChildElement("visible"))
@@ -397,6 +400,11 @@ void UIElement::setFocusable(bool enable)
     mFocusable = enable;
     mFocusable = enable;
 }
 }
 
 
+void UIElement::setDefocusable(bool enable)
+{
+    mDefocusable = enable;
+}
+
 void UIElement::setFocus(bool enable)
 void UIElement::setFocus(bool enable)
 {
 {
     if (!mFocusable)
     if (!mFocusable)
@@ -421,7 +429,7 @@ void UIElement::setFocus(bool enable)
 
 
 void UIElement::setSelected(bool enable)
 void UIElement::setSelected(bool enable)
 {
 {
-    mVisible = enable;
+    mSelected = enable;
 }
 }
 
 
 void UIElement::setVisible(bool enable)
 void UIElement::setVisible(bool enable)

+ 6 - 0
Engine/UI/UIElement.h

@@ -149,6 +149,8 @@ public:
     void setEnabled(bool enable);
     void setEnabled(bool enable);
     //! Set whether can be focused
     //! Set whether can be focused
     void setFocusable(bool enable);
     void setFocusable(bool enable);
+    //! Set whether can be defocused by pressing ESC
+    void setDefocusable(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
     //! Set selected mode. Actual meaning is element dependent, but is visually same as a constant hover
@@ -206,6 +208,8 @@ public:
     bool isEnabled() const { return mEnabled; }
     bool isEnabled() const { return mEnabled; }
     //! Return whether can be focused
     //! Return whether can be focused
     bool isFocusable() const { return mFocusable; }
     bool isFocusable() const { return mFocusable; }
+    //! Return whether can be defocused with ESC
+    bool isDefocusable() const { return mDefocusable; }
     //! 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
     //! Return whether is selected. Actual meaning is element dependent
@@ -286,6 +290,8 @@ protected:
     bool mEnabled;
     bool mEnabled;
     //! Focusable flag
     //! Focusable flag
     bool mFocusable;
     bool mFocusable;
+    //! Defocusable flag
+    bool mDefocusable;
     //! Focused flag
     //! Focused flag
     bool mFocus;
     bool mFocus;
     //! Selected flag
     //! Selected flag

+ 1 - 1
Examples/NinjaSnowWar/Game.cpp

@@ -151,7 +151,7 @@ void Game::run()
             saveGame();
             saveGame();
         if (input->getKeyPress(KEY_F7))
         if (input->getKeyPress(KEY_F7))
             loadGame();
             loadGame();
-        if (input->getKeyPress(KEY_ESCAPE))
+        if (input->getKeyPress(KEY_ESC))
             mEngine->exit();
             mEngine->exit();
     }
     }
 }
 }